animation.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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 } from "@/components/drawing/hook";
  20. import { getPathNode } from "./path";
  21. import { 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. export let animationGroup: AnimationGroup;
  26. export const getAMKey = (am: AnimationModel) => am.key || am.id;
  27. export const amMap: Record<
  28. string,
  29. {
  30. am?: AnimationModel3D;
  31. frames: Record<string, AnimationModelFrame3D>;
  32. actions: Record<string, AnimationModelAction3D>;
  33. paths: Record<string, AnimationModelPath3D>;
  34. subtitles: Record<string, () => void>;
  35. }
  36. > = reactive({});
  37. export const addAM = (data: AnimationModel): Promise<AnimationModel3D> => {
  38. const key = getAMKey(data);
  39. const stopLoad = watch(
  40. () => {
  41. const exixts = ams.value.some((am) => getAMKey(am) === key);
  42. return [key, exixts] as const;
  43. },
  44. ([key, exixts]) => {
  45. if (!exixts) {
  46. const des = amMap[key];
  47. if (!des) return;
  48. Object.values(des.frames || {}).forEach((frame) => frame.destroy());
  49. Object.values(des.actions || {}).forEach((frame) => frame.destroy());
  50. Object.values(des.paths || {}).forEach((frame) => frame.destroy());
  51. des.am?.destroy();
  52. delete amMap[key];
  53. } else if (!amMap[key]) {
  54. amMap[key] = {
  55. frames: {},
  56. actions: {},
  57. paths: {},
  58. subtitles: {},
  59. };
  60. const am = animationGroup.addAnimationModel(data);
  61. am.bus.on("loadDone", () => {
  62. amMap[key].am = am;
  63. });
  64. }
  65. },
  66. { immediate: true }
  67. );
  68. const stopAttrib = mergeFuns(
  69. watchEffect(() =>
  70. amMap[key].am?.changeVisibilityRange(
  71. data.globalVisibility ? undefined : data.visibilityRange
  72. )
  73. ),
  74. watchEffect(() => amMap[key].am?.changeTitle(data.title)),
  75. watchEffect(() => amMap[key].am?.visibilityTitle(data.showTitle)),
  76. watchEffect(() => amMap[key].am?.changeFontSize(data.fontSize))
  77. );
  78. const stopWatch = watch(
  79. () => ams.value.includes(data),
  80. (exists) => {
  81. if (!exists) {
  82. stopLoad();
  83. stopAttrib();
  84. stopWatch();
  85. }
  86. },
  87. { flush: "post" }
  88. );
  89. return new Promise((resolve) => {
  90. const stopWatch = watchEffect(() => {
  91. if (amMap[key]?.am) {
  92. resolve(amMap[key]!.am!);
  93. nextTick(() => stopWatch());
  94. }
  95. });
  96. });
  97. };
  98. export const addFrame = (
  99. data: AnimationModelFrame
  100. ): Promise<AnimationModelFrame3D> => {
  101. console.log('addFrame')
  102. const am = ams.value.find((item) =>
  103. item.frames.find(({ id }) => id === data.id)
  104. );
  105. if (!am) {
  106. throw "找不到am数据";
  107. }
  108. const key = getAMKey(am);
  109. const stopLoad = watch(
  110. () => {
  111. const exists = am.frames.some(({ id }) => id === data.id);
  112. return [amMap[key], exists] as const;
  113. },
  114. ([map, exists]) => {
  115. if (!map.am) return;
  116. if (exists && !map.frames[data.id]) {
  117. console.log('add?')
  118. map.frames[data.id] = map.am.addFrame(data);
  119. } else if (!exists && map.frames[data.id]) {
  120. map.frames[data.id].destroy();
  121. delete map.frames[data.id];
  122. }
  123. },
  124. { immediate: true }
  125. );
  126. const stopAttrib = mergeFuns(
  127. watchEffect(() => amMap[key].frames[data.id]?.changeTime(data.time)),
  128. watchEffect(() => data.mat && amMap[key].frames[data.id]?.setMat(data.mat))
  129. );
  130. const stopWatch = watch(
  131. () => am.frames.includes(data),
  132. (exists) => {
  133. if (!exists) {
  134. stopLoad();
  135. stopAttrib();
  136. stopWatch();
  137. }
  138. },
  139. { flush: "post" }
  140. );
  141. return new Promise((resolve) => {
  142. const stopWatch = watchEffect(() => {
  143. if (amMap[key]?.frames[data.id]) {
  144. resolve(amMap[key].frames[data.id]);
  145. nextTick(() => stopWatch());
  146. }
  147. });
  148. });
  149. };
  150. export const addAction = (
  151. data: AnimationModelAction
  152. ): Promise<AnimationModelAction3D> => {
  153. const am = ams.value.find((item) =>
  154. item.actions.find(({ id }) => id === data.id)
  155. );
  156. if (!am) {
  157. throw "找不到am数据";
  158. }
  159. const key = getAMKey(am);
  160. const stopLoad = watch(
  161. () => {
  162. const exists = am.actions.some(({ id }) => id === data.id);
  163. return [amMap[key], exists] as const;
  164. },
  165. ([map, exists]) => {
  166. if (!map.am) return;
  167. if (exists && !map.actions[data.id]) {
  168. map.actions[data.id] = map.am.addAction(data);
  169. } else if (!exists && map.actions[data.id]) {
  170. map.actions[data.id].destroy();
  171. delete map.actions[data.id];
  172. }
  173. },
  174. { immediate: true }
  175. );
  176. const stopAttrib = mergeFuns(
  177. watchEffect(() => amMap[key].actions[data.id]?.changeTime(data.time)),
  178. watchEffect(() => {
  179. amMap[key].actions[data.id]?.changeAmplitude(data.amplitude)
  180. }),
  181. watchEffect(() => amMap[key].actions[data.id]?.changeSpeed(data.speed)),
  182. watchEffect(() =>
  183. amMap[key].actions[data.id]?.changeDuration(data.duration)
  184. )
  185. );
  186. const stopWatch = watch(
  187. () => am.actions.includes(data),
  188. (exists) => {
  189. if (!exists) {
  190. stopLoad();
  191. stopAttrib();
  192. stopWatch();
  193. }
  194. },
  195. { flush: "post" }
  196. );
  197. return new Promise((resolve) => {
  198. const stopWatch = watchEffect(() => {
  199. if (amMap[key]?.actions[data.id]) {
  200. resolve(amMap[key].actions[data.id]);
  201. nextTick(() => stopWatch());
  202. }
  203. });
  204. });
  205. };
  206. export const addPath = (
  207. data: AnimationModelPath
  208. ): Promise<AnimationModelPath3D> => {
  209. const am = ams.value.find((item) =>
  210. item.paths.find(({ id }) => id === data.id)
  211. );
  212. if (!am) {
  213. throw "找不到am数据";
  214. }
  215. const path = computed(() =>
  216. data.pathId ? getPathNode(data.pathId) : undefined
  217. );
  218. const key = getAMKey(am);
  219. const stopLoad = watch(
  220. () => {
  221. const exists = am.paths.some(({ id }) => id === data.id);
  222. return [amMap[key], exists, path.value] as const;
  223. },
  224. ([map, exists, path]) => {
  225. if (!map.am || !path) return;
  226. if (exists && !map.paths[data.id]) {
  227. map.paths[data.id] = map.am.addPath({ ...data, path });
  228. } else if (!exists && map.paths[data.id]) {
  229. map.paths[data.id].destroy();
  230. delete map.paths[data.id];
  231. }
  232. },
  233. { immediate: true }
  234. );
  235. const stopAttrib = mergeFuns(
  236. watchEffect(() => amMap[key].paths[data.id]?.changeTime(data.time)),
  237. watchEffect(() => amMap[key].paths[data.id]?.changeReverse(data.reverse)),
  238. watchEffect(() => amMap[key].paths[data.id]?.changeDuration(data.duration)),
  239. watchEffect(() => amMap[key].paths[data.id]?.changePath(path.value))
  240. );
  241. const stopWatch = watch(
  242. () => am.paths.includes(data),
  243. (exists) => {
  244. if (!exists) {
  245. stopLoad();
  246. stopAttrib();
  247. stopWatch();
  248. }
  249. },
  250. { flush: "post" }
  251. );
  252. return new Promise((resolve) => {
  253. const stopWatch = watchEffect(() => {
  254. if (amMap[key]?.paths[data.id]) {
  255. resolve(amMap[key].paths[data.id]);
  256. nextTick(() => stopWatch());
  257. }
  258. });
  259. });
  260. };
  261. export const addSubtitle = (data: AnimationModelSubtitle) => {
  262. const am = ams.value.find((item) =>
  263. item.subtitles.find(({ id }) => id === data.id)
  264. );
  265. if (!am) {
  266. throw "找不到am数据";
  267. }
  268. const key = getAMKey(am);
  269. const size = ref({ width: 0, height: 0 });
  270. const show = ref(false);
  271. const pixel = ref<Pos>();
  272. const stopLoad = watch(
  273. () => {
  274. const exists = am.subtitles.some(({ id }) => id === data.id);
  275. return [amMap[key], exists] as const;
  276. },
  277. ([map, exists]) => {
  278. if (!map.am) return;
  279. if (exists && !map.subtitles[data.id]) {
  280. map.subtitles[data.id] = mount(
  281. document.querySelector("#app")!,
  282. Subtitle,
  283. reactive({
  284. pixel,
  285. show,
  286. data,
  287. sizeChang: (csize: Size) => (size.value = csize),
  288. })
  289. );
  290. } else if (!exists && map.subtitles[data.id]) {
  291. map.subtitles[data.id]();
  292. delete map.subtitles[data.id];
  293. }
  294. },
  295. { immediate: true }
  296. );
  297. const stopAttrib = mergeFuns(
  298. watch([currentTime, () => amMap[am.id].am, size], (_a, _b, onCleanup) => {
  299. if (
  300. currentTime.value >= data.time &&
  301. currentTime.value <= (data.time + data.duration)
  302. ) {
  303. const update = () => pixel.value = amMap[am.id].am?.getCurrentSubtitlePixel(size.value);
  304. update()
  305. show.value = true;
  306. _sdk.sceneBus.on("cameraChange", update);
  307. onCleanup(() => _sdk.sceneBus.off("cameraChange", update))
  308. } else {
  309. show.value = false;
  310. }
  311. }, {immediate: true})
  312. );
  313. const stopWatch = watch(
  314. () => am.subtitles.includes(data),
  315. (exists) => {
  316. if (!exists) {
  317. stopLoad();
  318. stopAttrib();
  319. stopWatch();
  320. }
  321. },
  322. { flush: "post" }
  323. );
  324. };
  325. export const endTime = computed(() => {
  326. const amsEndTime = ams.value.map((am) => {
  327. const endPoints = [...am.frames, ...am.actions, ...am.subtitles, ...am.paths].map(
  328. (item) => item.time + (item.duration || 0)
  329. );
  330. return Math.max(...endPoints);
  331. })
  332. return Math.max(...amsEndTime) + animationGroup.delayEndTime()
  333. })
  334. export const currentTime = ref(0);
  335. export const associationAnimation = (sdk: SDK, el: HTMLDivElement) => {
  336. animationGroup = sdk.createAnimationGroup();
  337. watchEffect(() => {
  338. animationGroup.setCurrentTime(currentTime.value);
  339. });
  340. animationGroup.bus.on("currentTime", (time) => {
  341. currentTime.value = time;
  342. });
  343. watch(
  344. () => [...ams.value],
  345. (newv, oldv = []) => {
  346. const { added } = diffArrayChange(newv, oldv);
  347. added.forEach(addAM);
  348. }
  349. );
  350. watch(
  351. () => ams.value.flatMap((am) => am.frames),
  352. (newv, oldv = []) => {
  353. const { added } = diffArrayChange(newv, oldv);
  354. added.forEach(addFrame);
  355. }
  356. );
  357. watch(
  358. () => ams.value.flatMap((am) => am.actions),
  359. (newv, oldv = []) => {
  360. const { added } = diffArrayChange(newv, oldv);
  361. added.forEach(addAction);
  362. }
  363. );
  364. watch(
  365. () => ams.value.flatMap((am) => am.paths),
  366. (newv, oldv = []) => {
  367. const { added } = diffArrayChange(newv, oldv);
  368. added.forEach(addPath);
  369. }
  370. );
  371. watch(
  372. () => ams.value.flatMap((am) => am.subtitles),
  373. (newv, oldv = []) => {
  374. const { added } = diffArrayChange(newv, oldv);
  375. added.forEach(addSubtitle);
  376. }
  377. );
  378. };