animation.ts 14 KB

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