animation.ts 15 KB

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