animation.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. import {
  2. AnimationModel,
  3. AnimationModelAction,
  4. AnimationModelFrame,
  5. AnimationModelPath,
  6. AnimationModelSubtitle,
  7. } from "@/api";
  8. import {
  9. AnimationGroup,
  10. AnimationModel3D,
  11. AnimationModelAction3D,
  12. AnimationModelFrame3D,
  13. AnimationModelPath3D,
  14. SDK,
  15. sdk as _sdk,
  16. } from "../sdk";
  17. import { computed, nextTick, reactive, ref, watch, watchEffect } from "vue";
  18. import { ams } from "@/store/animation";
  19. import { mergeFuns, uuid } from "@/components/drawing/hook";
  20. import { getPathNode } from "./path";
  21. import { debounce, diffArrayChange, mount } from "@/utils";
  22. import { Pos } from "@/utils/event";
  23. import Subtitle from "@/components/subtitle/index.vue";
  24. import { Size } from "@/components/drawing/dec";
  25. import router, { RoutesName } from "@/router";
  26. import { paths } from "@/store";
  27. export let animationGroup: AnimationGroup;
  28. export const getAMKey = (am: AnimationModel) => am.key || am.id;
  29. export const amMap: Record<
  30. string,
  31. {
  32. am?: AnimationModel3D;
  33. globalFrame?: AnimationModelFrame3D;
  34. frames: Record<string, AnimationModelFrame3D>;
  35. actions: Record<string, AnimationModelAction3D>;
  36. paths: Record<string, AnimationModelPath3D>;
  37. subtitles: Record<string, () => void>;
  38. }
  39. > = reactive({});
  40. export const addAM = (data: AnimationModel): Promise<AnimationModel3D> => {
  41. const key = getAMKey(data);
  42. const stopLoad = watch(
  43. () => {
  44. const exixts = ams.value.some((am) => getAMKey(am) === key);
  45. return [key, exixts] as const;
  46. },
  47. ([key, exixts]) => {
  48. if (!exixts) {
  49. const des = amMap[key];
  50. if (!des) return;
  51. Object.values(des.frames || {}).forEach((frame) => frame.destroy());
  52. Object.values(des.actions || {}).forEach((frame) => frame.destroy());
  53. Object.values(des.paths || {}).forEach((frame) => frame.destroy());
  54. des.am?.destroy();
  55. delete amMap[key];
  56. } else if (!amMap[key]) {
  57. amMap[key] = {
  58. frames: {},
  59. actions: {},
  60. paths: {},
  61. subtitles: {},
  62. };
  63. const am = animationGroup.addAnimationModel(data);
  64. am.bus.on("loadDone", () => {
  65. amMap[key].am = am;
  66. });
  67. }
  68. },
  69. { immediate: true }
  70. );
  71. const stopAttrib = mergeFuns(
  72. watchEffect(() =>
  73. amMap[key]?.am?.changeVisibilityRange(
  74. data.globalVisibility ? undefined : data.visibilityRange
  75. )
  76. ),
  77. watchEffect(() => amMap[key]?.am?.changeTitle(data.title)),
  78. watchEffect(() => amMap[key]?.am?.visibilityTitle(data.showTitle)),
  79. watchEffect(() => amMap[key]?.am?.changeFontSize(data.fontSize))
  80. );
  81. const stopWatch = watch(
  82. () => ams.value.includes(data),
  83. (exists) => {
  84. if (!exists) {
  85. stopLoad();
  86. stopAttrib();
  87. stopWatch();
  88. }
  89. },
  90. { flush: "post" }
  91. );
  92. return new Promise((resolve) => {
  93. const stopWatch = watchEffect(() => {
  94. if (amMap[key]?.am) {
  95. resolve(amMap[key]!.am!);
  96. nextTick(() => stopWatch());
  97. }
  98. });
  99. });
  100. };
  101. export const addFrame = (
  102. data: AnimationModelFrame
  103. ): Promise<AnimationModelFrame3D> => {
  104. console.log("addFrame");
  105. const am = ams.value.find((item) =>
  106. item.frames.find(({ id }) => id === data.id)
  107. );
  108. if (!am) {
  109. throw "找不到am数据";
  110. }
  111. const key = getAMKey(am);
  112. const stopLoad = watch(
  113. () => {
  114. const exists = am.frames.some(({ id }) => id === data.id);
  115. amMap[key]?.am;
  116. return [amMap[key], exists] as const;
  117. },
  118. ([map, exists]) => {
  119. if (!map?.am) return;
  120. if (exists && !map.frames[data.id]) {
  121. map.frames[data.id] = map.am.addFrame(data);
  122. } else if (!exists && map.frames[data.id]) {
  123. map.frames[data.id].destroy();
  124. delete map.frames[data.id];
  125. }
  126. },
  127. { immediate: true }
  128. );
  129. const stopAttrib = mergeFuns(
  130. watchEffect(() => amMap[key]?.frames[data.id]?.changeTime(data.time)),
  131. watchEffect(() => data.mat && amMap[key]?.frames[data.id]?.setMat(data.mat))
  132. );
  133. const stopWatch = watch(
  134. () => am.frames.includes(data),
  135. (exists) => {
  136. if (!exists) {
  137. stopLoad();
  138. stopAttrib();
  139. stopWatch();
  140. }
  141. },
  142. { flush: "post" }
  143. );
  144. return new Promise((resolve) => {
  145. const stopWatch = watchEffect(() => {
  146. if (amMap[key]?.frames[data.id]) {
  147. resolve(amMap[key].frames[data.id]);
  148. nextTick(() => stopWatch());
  149. }
  150. });
  151. });
  152. };
  153. export const addAction = (
  154. data: AnimationModelAction
  155. ): Promise<AnimationModelAction3D> => {
  156. const am = ams.value.find((item) =>
  157. item.actions.find(({ id }) => id === data.id)
  158. );
  159. if (!am) {
  160. throw "找不到am数据";
  161. }
  162. const key = getAMKey(am);
  163. const stopLoad = watch(
  164. () => {
  165. const exists = am.actions.some(({ id }) => id === data.id);
  166. amMap[key]?.am;
  167. return [amMap[key], exists] as const;
  168. },
  169. ([map, exists]) => {
  170. if (!map?.am) return;
  171. if (exists && !map.actions[data.id]) {
  172. map.actions[data.id] = map.am.addAction(data);
  173. } else if (!exists && map.actions[data.id]) {
  174. map.actions[data.id].destroy();
  175. delete map.actions[data.id];
  176. }
  177. },
  178. { immediate: true }
  179. );
  180. const stopAttrib = mergeFuns(
  181. watchEffect(() => amMap[key]?.actions[data.id]?.changeTime(data.time)),
  182. watchEffect(() => {
  183. amMap[key]?.actions[data.id]?.changeAmplitude(data.amplitude);
  184. }),
  185. watchEffect(() => amMap[key]?.actions[data.id]?.changeSpeed(data.speed)),
  186. watchEffect(() =>
  187. amMap[key]?.actions[data.id]?.changeDuration(data.duration)
  188. )
  189. );
  190. const stopWatch = watch(
  191. () => am.actions.includes(data),
  192. (exists) => {
  193. if (!exists) {
  194. stopLoad();
  195. stopAttrib();
  196. stopWatch();
  197. }
  198. },
  199. { flush: "post" }
  200. );
  201. return new Promise((resolve) => {
  202. const stopWatch = watchEffect(() => {
  203. if (amMap[key]?.actions[data.id]) {
  204. resolve(amMap[key].actions[data.id]);
  205. nextTick(() => stopWatch());
  206. }
  207. });
  208. });
  209. };
  210. export const addPath = (
  211. data: AnimationModelPath
  212. ): Promise<AnimationModelPath3D> => {
  213. const am = ams.value.find((item) =>
  214. item.paths.find(({ id }) => id === data.id)
  215. );
  216. if (!am) {
  217. throw "找不到am数据";
  218. }
  219. const path = computed(() =>
  220. data.pathId ? getPathNode(data.pathId) : undefined
  221. );
  222. const pathData = computed(() =>
  223. paths.value.find((item) => item.id === data.pathId)
  224. );
  225. const key = getAMKey(am);
  226. const stopLoad = watch(
  227. () => {
  228. const exists = am.paths.some(({ id }) => id === data.id);
  229. amMap[key]?.am;
  230. return [amMap[key], exists, path.value] as const;
  231. },
  232. ([map, exists, path]) => {
  233. if (!map?.am || !path) return;
  234. if (exists && !map.paths[data.id]) {
  235. map.paths[data.id] = map.am.addPath({ ...data, path });
  236. } else if (!exists && map.paths[data.id]) {
  237. map.paths[data.id].destroy();
  238. delete map.paths[data.id];
  239. }
  240. },
  241. { immediate: true }
  242. );
  243. const stopAttrib = mergeFuns(
  244. watchEffect(() => amMap[key]?.paths[data.id]?.changeTime(data.time)),
  245. watchEffect(() => amMap[key]?.paths[data.id]?.changeReverse(data.reverse)),
  246. watchEffect(() =>
  247. amMap[key]?.paths[data.id]?.changeDuration(data.duration)
  248. ),
  249. watchEffect(() => {
  250. path.value && amMap[key]?.paths[data.id]?.changePath(path.value);
  251. })
  252. );
  253. const stopWatch = watch(
  254. () => am.paths.includes(data),
  255. (exists) => {
  256. if (!exists) {
  257. stopLoad();
  258. stopAttrib();
  259. stopWatch();
  260. }
  261. },
  262. { flush: "post" }
  263. );
  264. return new Promise((resolve) => {
  265. const stopWatch = watchEffect(() => {
  266. if (amMap[key]?.paths[data.id]) {
  267. resolve(amMap[key].paths[data.id]);
  268. nextTick(() => stopWatch());
  269. }
  270. });
  271. });
  272. };
  273. export const addSubtitle = (data: AnimationModelSubtitle) => {
  274. const am = ams.value.find((item) =>
  275. item.subtitles.find(({ id }) => id === data.id)
  276. );
  277. if (!am) {
  278. throw "找不到am数据";
  279. }
  280. const key = getAMKey(am);
  281. const size = ref({ width: 0, height: 0 });
  282. const show = ref(false);
  283. const pixel = ref<Pos>();
  284. const stopLoad = watch(
  285. () => {
  286. const exists = am.subtitles.some(({ id }) => id === data.id);
  287. amMap[key]?.am;
  288. return [amMap[key], exists] as const;
  289. },
  290. ([map, exists]) => {
  291. if (!map?.am) return;
  292. if (exists && !map.subtitles[data.id]) {
  293. const mountEl = document.querySelector("#app")!
  294. const layer = document.createElement('div')
  295. layer.className = 'subtitle'
  296. const cleanups = [
  297. watchEffect(() => {
  298. layer.innerHTML = data.content
  299. size.value = { width: layer.offsetWidth, height: layer.offsetHeight }
  300. }),
  301. watchEffect(() => layer.style.background = data.background),
  302. watchEffect(() => {
  303. layer.style.visibility = pixel.value && show.value ? 'visible' : 'hidden'
  304. }),
  305. watchEffect(() => {
  306. if (pixel.value) {
  307. layer.style.transform = `translate(${pixel.value.x}px, ${pixel.value.y}px)`;
  308. }
  309. }),
  310. () => mountEl.removeChild(layer)
  311. ]
  312. mountEl.appendChild(layer)
  313. map.subtitles[data.id] = mergeFuns(cleanups)
  314. } else if (!exists && map.subtitles[data.id]) {
  315. map.subtitles[data.id]();
  316. delete map.subtitles[data.id];
  317. }
  318. },
  319. { immediate: true }
  320. );
  321. let isRun = false
  322. const update = () => {
  323. // if (isRun) return;
  324. // isRun = true
  325. // setTimeout(() => {
  326. pixel.value = amMap[am.id]?.am?.getCurrentSubtitlePixel(
  327. size.value
  328. );
  329. // isRun = false
  330. // }, 16);
  331. };
  332. const stopAttrib = mergeFuns(
  333. watch(
  334. [currentTime, () => amMap[am.id]?.am, size, play],
  335. (_a, _b, onCleanup) => {
  336. if (
  337. !play.value &&
  338. router.currentRoute.value.name !== RoutesName.animation
  339. ) {
  340. show.value = false;
  341. } else if (
  342. currentTime.value >= data.time &&
  343. currentTime.value <= data.time + data.duration
  344. ) {
  345. update();
  346. show.value = true;
  347. } else {
  348. show.value = false;
  349. }
  350. },
  351. { immediate: true }
  352. )
  353. );
  354. const stopWatch = watch(
  355. () => am.subtitles.includes(data),
  356. (exists) => {
  357. if (!exists) {
  358. stopLoad();
  359. stopAttrib();
  360. stopWatch();
  361. }
  362. },
  363. { flush: "post" }
  364. );
  365. };
  366. export const endTime = computed(() => {
  367. const amsEndTime = ams.value.map((am) => {
  368. const endPoints = [
  369. ...am.frames,
  370. ...am.actions,
  371. ...am.subtitles,
  372. ...am.paths,
  373. ].map((item) => item.time + (item.duration || 0));
  374. return Math.max(...endPoints);
  375. });
  376. return (
  377. Math.max(...amsEndTime) +
  378. ((animationGroup.delayEndTime && animationGroup.delayEndTime()) || 0)
  379. );
  380. });
  381. export const play = ref(false);
  382. watch(play, (_a, _b, onCleanup) => {
  383. play.value ? animationGroup?.play() : animationGroup?.pause();
  384. onCleanup(
  385. watchEffect(() => {
  386. if (currentTime.value >= endTime.value) {
  387. play.value = false;
  388. }
  389. })
  390. );
  391. });
  392. export const currentTime = ref(0);
  393. export const associationAnimation = (sdk: SDK, el: HTMLDivElement) => {
  394. animationGroup = sdk.createAnimationGroup();
  395. watchEffect(() => {
  396. animationGroup.setCurrentTime(currentTime.value);
  397. });
  398. animationGroup.bus.on("currentTime", (time) => {
  399. currentTime.value = time;
  400. });
  401. watch(
  402. () => [...ams.value],
  403. (newv, oldv = []) => {
  404. console.log("diffam", newv, oldv);
  405. const { added } = diffArrayChange(newv, oldv);
  406. added.forEach(addAM);
  407. },
  408. { immediate: true }
  409. );
  410. watch(
  411. () => ams.value.flatMap((am) => am.frames),
  412. (newv, oldv = []) => {
  413. const { added } = diffArrayChange(newv, oldv);
  414. added.forEach(addFrame);
  415. },
  416. { immediate: true }
  417. );
  418. watch(
  419. () => ams.value.flatMap((am) => am.actions),
  420. (newv, oldv = []) => {
  421. const { added } = diffArrayChange(newv, oldv);
  422. added.forEach(addAction);
  423. },
  424. { immediate: true }
  425. );
  426. watch(
  427. () => ams.value.flatMap((am) => am.paths),
  428. (newv, oldv = []) => {
  429. const { added } = diffArrayChange(newv, oldv);
  430. added.forEach(addPath);
  431. },
  432. { immediate: true }
  433. );
  434. watch(
  435. () => ams.value.flatMap((am) => am.subtitles),
  436. (newv, oldv = []) => {
  437. const { added } = diffArrayChange(newv, oldv);
  438. added.forEach(addSubtitle);
  439. },
  440. { immediate: true }
  441. );
  442. let cleanupMap: Record<string, () => void> = {};
  443. watch(
  444. () => {
  445. const gAms = ams.value.filter(
  446. (am) => !am.frames.length && amMap[am.id]?.am
  447. );
  448. return gAms;
  449. },
  450. (am3ds, oldAm3ds = []) => {
  451. const { added, deleted } = diffArrayChange(am3ds, oldAm3ds);
  452. for (const am of added) {
  453. const am3d = amMap[am.id];
  454. if (!am3d || !am3d.am) continue;
  455. const frame = am3d.am!.addFrame({
  456. id: uuid(),
  457. mat: am.mat || am3d.am.getModelPose(),
  458. name: "global-frame",
  459. time: 0,
  460. });
  461. am3d.globalFrame = frame;
  462. cleanupMap[am.id] = mergeFuns(
  463. watchEffect(() => {
  464. am.mat && frame.setMat(am.mat);
  465. console.log("set-global-frame", am.mat);
  466. }),
  467. () => {
  468. frame.destroy();
  469. am3d.globalFrame = undefined;
  470. delete cleanupMap[am.id];
  471. }
  472. );
  473. }
  474. for (const am of deleted) {
  475. cleanupMap[am.id] && cleanupMap[am.id]();
  476. }
  477. },
  478. { flush: "post", immediate: true }
  479. );
  480. };