import { AnimationModel, AnimationModelAction, AnimationModelFrame, AnimationModelPath, AnimationModelSubtitle, } from "@/api"; import sdk, { AnimationGroup, AnimationModel3D, AnimationModelAction3D, AnimationModelFrame3D, AnimationModelPath3D, SDK, sdk as _sdk, } from "../sdk"; import { computed, nextTick, reactive, ref, toRaw, watch, watchEffect, } from "vue"; import { ams } from "@/store/animation"; import { mergeFuns, uuid } from "@/components/drawing/hook"; import { getPathNode } from "./path"; import { debounce, diffArrayChange, mount, round } from "@/utils"; import { Pos } from "@/utils/event"; import Subtitle from "@/components/subtitle/index.vue"; import { Size } from "@/components/drawing/dec"; import router, { RoutesName } from "@/router"; import { isEdit, isTemploraryID, paths } from "@/store"; import { Color } from "three"; import { custom, showAMsStack } from "@/env"; export let animationGroup: AnimationGroup; export const getAMKey = (am: AnimationModel) => am.key || am.id; export const amMap: Record< string, { am?: AnimationModel3D; globalFrame?: AnimationModelFrame3D; frames: Record; actions: Record; paths: Record; subtitles: Record void>; } > = reactive({}); export const addAM = (data: AnimationModel): Promise => { const key = getAMKey(data); const stopLoad = watch( () => { const exixts = ams.value.some((am) => getAMKey(am) === key); return [key, exixts] as const; }, ([key, exixts]) => { if (!exixts) { const des = amMap[key]; if (!des) return; Object.values(des.frames || {}).forEach((frame) => frame.destroy()); Object.values(des.actions || {}).forEach((frame) => frame.destroy()); Object.values(des.paths || {}).forEach((frame) => frame.destroy()); des.am?.destroy(); console.error("destory", key, data); delete amMap[key]; } else if (!amMap[key]) { amMap[key] = { frames: {}, actions: {}, paths: {}, subtitles: {}, }; const am = animationGroup.addAnimationModel({ ...data, quaAtPath: data.mat?.quaAtPath, }); am.bus.on("loadDone", () => { amMap[key].am = am; if (isTemploraryID(data.id)) { console.error('putInFrontOfCam', data.id) am.putInFrontOfCam(); } console.log("0.0", am); }); } }, { immediate: true } ); const stopAttrib = mergeFuns( watchEffect(() => amMap[key]?.am?.changeVisibilityRange( data.globalVisibility ? undefined : data.visibilityRange ) ), watchEffect(() => amMap[key]?.am?.changeTitle(data.title)), watchEffect(() => amMap[key]?.am?.visibilityTitle(data.showTitle)), watchEffect(() => amMap[key]?.am?.changeFontSize(data.fontSize)) ); const stopWatch = watch( () => ams.value.includes(data), (exists) => { if (!exists) { stopLoad(); stopAttrib(); stopWatch(); } }, { flush: "post" } ); return new Promise((resolve) => { const stopWatch = watchEffect(() => { if (amMap[key]?.am) { resolve(amMap[key]!.am!); nextTick(() => stopWatch()); } }); }); }; export const addFrame = ( data: AnimationModelFrame ): Promise => { console.log("addFrame"); const am = ams.value.find((item) => item.frames.find(({ id }) => id === data.id) ); if (!am) { throw "找不到am数据"; } const key = getAMKey(am); const stopLoad = watch( () => { const exists = am.frames.some(({ id }) => id === data.id); amMap[key]?.am; return [amMap[key], exists] as const; }, ([map, exists]) => { if (!map?.am) return; if (exists && !map.frames[data.id]) { map.frames[data.id] = map.am.addFrame(data); } else if (!exists && map.frames[data.id]) { map.frames[data.id].destroy(); delete map.frames[data.id]; } }, { immediate: true } ); const stopAttrib = mergeFuns( watchEffect(() => amMap[key]?.frames[data.id]?.changeTime(data.time)), watchEffect(() => data.mat && amMap[key]?.frames[data.id]?.setMat(data.mat)) ); const stopWatch = watch( () => am.frames.includes(data), (exists) => { if (!exists) { stopLoad(); stopAttrib(); stopWatch(); } }, { flush: "post" } ); return new Promise((resolve) => { const stopWatch = watchEffect(() => { if (amMap[key]?.frames[data.id]) { resolve(amMap[key].frames[data.id]); nextTick(() => stopWatch()); } }); }); }; export const addAction = ( data: AnimationModelAction ): Promise => { const am = ams.value.find((item) => item.actions.find(({ id }) => id === data.id) ); if (!am) { throw "找不到am数据"; } const key = getAMKey(am); const stopLoad = watch( () => { const exists = am.actions.some(({ id }) => id === data.id); amMap[key]?.am; return [amMap[key], exists] as const; }, ([map, exists]) => { if (!map?.am) return; if (exists && !map.actions[data.id]) { map.actions[data.id] = map.am.addAction(data); } else if (!exists && map.actions[data.id]) { map.actions[data.id].destroy(); delete map.actions[data.id]; } }, { immediate: true } ); const stopAttrib = mergeFuns( watchEffect(() => amMap[key]?.actions[data.id]?.changeTime(data.time)), watchEffect(() => { amMap[key]?.actions[data.id]?.changeAmplitude(data.amplitude); }), watchEffect(() => amMap[key]?.actions[data.id]?.changeSpeed(data.speed)), watchEffect(() => amMap[key]?.actions[data.id]?.changeDuration(data.duration) ) ); const stopWatch = watch( () => am.actions.includes(data), (exists) => { if (!exists) { stopLoad(); stopAttrib(); stopWatch(); } }, { flush: "post" } ); return new Promise((resolve) => { const stopWatch = watchEffect(() => { if (amMap[key]?.actions[data.id]) { resolve(amMap[key].actions[data.id]); nextTick(() => stopWatch()); } }); }); }; export const addPath = ( data: AnimationModelPath ): Promise => { const am = ams.value.find((item) => item.paths.find(({ id }) => id === data.id) ); if (!am) { throw "找不到am数据"; } const path = computed(() => data.pathId ? getPathNode(data.pathId) : undefined ); const pathData = computed(() => paths.value.find((item) => item.id === data.pathId) ); const key = getAMKey(am); const stopLoad = watch( () => { const exists = am.paths.some(({ id }) => id === data.id); amMap[key]?.am; return [amMap[key], exists, path.value] as const; }, ([map, exists, path]) => { if (!map?.am || !path) return; if (exists && !map.paths[data.id]) { map.paths[data.id] = map.am.addPath({ ...data, path }); } else if (!exists && map.paths[data.id]) { map.paths[data.id].destroy(); delete map.paths[data.id]; } }, { immediate: true } ); const stopAttrib = mergeFuns( watchEffect(() => amMap[key]?.paths[data.id]?.changeTime(data.time)), watchEffect(() => amMap[key]?.paths[data.id]?.changeReverse(data.reverse)), watchEffect(() => amMap[key]?.paths[data.id]?.changeDuration(data.duration) ), watch( path, (p) => { const path = toRaw(p); path && amMap[key]?.paths[data.id]?.changePath(path); }, { immediate: true } ) ); const stopWatch = watch( () => am.paths.includes(data), (exists) => { if (!exists) { stopLoad(); stopAttrib(); stopWatch(); } }, { flush: "post" } ); return new Promise((resolve) => { const stopWatch = watchEffect(() => { if (amMap[key]?.paths[data.id]) { resolve(amMap[key].paths[data.id]); nextTick(() => stopWatch()); } }); }); }; export const addSubtitle = (data: AnimationModelSubtitle) => { const am = ams.value.find((item) => item.subtitles.find(({ id }) => id === data.id) ); if (!am) { throw "找不到am数据"; } const key = getAMKey(am); const size = ref({ width: 0, height: 0 }); const show = ref(false); const pixel = ref(); const modelShow = ref(true); const textShow = ref(true); const stopLoad = watch( () => { const exists = am.subtitles.some(({ id }) => id === data.id); amMap[key]?.am; return [amMap[key], exists] as const; }, ([map, exists], _, onCleanup) => { if (!map?.am) return; if (exists && !map.subtitles[data.id]) { const rawChangeShow = map?.am.changeShow; map.am.changeShow = (f: boolean) => { modelShow.value = f; return rawChangeShow(f); }; onCleanup(() => (map.am!.changeShow = rawChangeShow)); const mountEl = document.querySelector("#app")!; const layer = document.createElement("div"); layer.className = "subtitle"; mountEl.appendChild(layer); const cleanups = [ watchEffect(() => { layer.innerHTML = data.content; textShow.value = !!layer.textContent?.trim()?.length; size.value = { width: layer.offsetWidth, height: layer.offsetHeight, }; }), watchEffect(() => { const theme = new Color(); theme.setHex(parseInt(data.background.substring(1), 16)); const rgb = { r: 0, g: 0, b: 0 }; theme.getRGB(rgb); layer.style.backgroundColor = `rgba(${round( rgb.r * 255, 2 )},${round(rgb.g * 255, 2)},${round(rgb.b * 255, 2)},0.5)`; }), watchEffect(() => { // console.log(pixel.value, show.value, modelShow.value, textShow.value) layer.style.visibility = pixel.value && show.value && modelShow.value && textShow.value ? "visible" : "hidden"; }), watchEffect(() => { if (pixel.value) { layer.style.transform = `translate(${round( pixel.value.x, 0 )}px, calc(${round(pixel.value.y, 0)}px - 50%))`; } }), () => { console.error("rm dom", data.id); layer.parentNode?.removeChild(layer); }, ]; // map.subtitles[data.id] = mergeFuns(cleanups); onCleanup(() => { mergeFuns(cleanups)(); delete map.subtitles[data.id]; }); } else if (!exists && map.subtitles[data.id]) { map.subtitles[data.id](); delete map.subtitles[data.id]; } }, { immediate: true } ); let isRun = false; const update = () => { // if (isRun) return; // isRun = true // setTimeout(() => { pixel.value = amMap[getAMKey(am)]?.am?.getCurrentSubtitlePixel(size.value); // isRun = false // }, 16); }; const stopAttrib = mergeFuns( watch( [ () => router.currentRoute.value.name, currentTime, () => amMap[getAMKey(am)]?.am, () => data.time, () => data.duration, size, isEdit, play, () => amMap[key]?.am, ], (_a, _b, onCleanup) => { if ( !play.value && router.currentRoute.value.name !== RoutesName.animation && !(router.currentRoute.value.name === RoutesName.guide && isEdit.value) ) { show.value = false; } else if ( currentTime.value >= data.time && currentTime.value < data.time + data.duration ) { update(); show.value = true; onCleanup( mergeFuns( watch( play, (play, _a, onCleanup) => { if (!play && _sdk) { _sdk.sceneBus.on("cameraChange", update); onCleanup(() => _sdk.sceneBus.off("cameraChange", update)); } }, { immediate: true } ), watch( () => amMap[getAMKey(am)]?.am, (am, _, onCleanup) => { am && am.bus.on("transformChanged", update); onCleanup(() => am && am.bus.off("transformChanged", update)) }, { immediate: true } ) ) ); } else { show.value = false; } }, { immediate: true } ) ); const stopWatch = watch( () => am.subtitles.includes(data), (exists) => { if (!exists) { stopLoad(); stopAttrib(); stopWatch(); } }, { flush: "post" } ); }; export const endTime = computed(() => { const amsEndTime = ams.value.map((am) => { const endPoints = [ ...am.frames.map((item) => ({ ...item, duration: 0 })), ...am.actions, ...am.subtitles, ...am.paths, ].map((item) => item.time + (item.duration || 0)); return Math.max(...endPoints); }); return ( Math.max(...amsEndTime) + ((animationGroup.delayEndTime && animationGroup.delayEndTime()) || 0) ); }); export const play = ref(false); watch(play, (_a, _b, onCleanup) => { play.value ? animationGroup?.play() : animationGroup?.pause(); if (!play.value) return; if (currentTime.value >= endTime.value) { currentTime.value = 0; } onCleanup( watchEffect(() => { if (currentTime.value >= endTime.value) { play.value = false; } }) ); }); export const currentTime = ref(0); export const associationAnimation = (sdk: SDK, el: HTMLDivElement) => { animationGroup = sdk.createAnimationGroup(); watchEffect( () => { animationGroup.setCurrentTime(currentTime.value); }, { flush: "sync" } ); animationGroup.bus.on("currentTime", (time) => { currentTime.value = time; }); watch( () => [...ams.value], (newv, oldv = []) => { const { added, deleted } = diffArrayChange(newv, oldv); console.log("diffam", added, deleted, ams.value); added.forEach(addAM); }, { immediate: true, deep: true } ); watch( () => ams.value.flatMap((am) => am.frames), (newv, oldv = []) => { const { added } = diffArrayChange(newv, oldv); added.forEach(addFrame); }, { immediate: true, deep: true } ); watch( () => ams.value.flatMap((am) => am.actions), (newv, oldv = []) => { const { added } = diffArrayChange(newv, oldv); added.forEach(addAction); }, { immediate: true, deep: true } ); watch( () => ams.value.flatMap((am) => am.paths), (newv, oldv = []) => { const { added } = diffArrayChange(newv, oldv); added.forEach(addPath); }, { immediate: true, deep: true } ); watch( () => ams.value.flatMap((am) => am.subtitles), (newv, oldv = []) => { const { added } = diffArrayChange(newv, oldv); added.forEach(addSubtitle); }, { immediate: true, deep: true } ); let cleanupMap: Record void> = {}; watch( () => { // !am.frames.length && const gAms = ams.value.filter((am) => amMap[getAMKey(am)]?.am); return gAms; }, (am3ds, oldAm3ds = []) => { const { added, deleted } = diffArrayChange(am3ds, oldAm3ds); for (const am of added) { const am3d = amMap[getAMKey(am)]; if (!am3d || !am3d.am) continue; const getMat = () => { return am.mat && "position" in am.mat ? am.mat : am3d.am!.getModelPose(); }; am3d.am.setDefaultPose && am3d.am.setDefaultPose(getMat()); const mat = am.mat || am3d.am.getModelPose(); am3d.am.changePosition(mat.position!); am3d.am.changeRotation(mat.rotation!); am3d.am.changeScale(mat.scale!); cleanupMap[getAMKey(am)] = mergeFuns( watchEffect(() => { if (am.mat && am3d.am) { am3d.am.setDefaultPose && am3d.am.setDefaultPose(getMat()); } console.log("set-default-pose", am.mat); }), () => { am3d.am?.addFrame; am3d.globalFrame = undefined; delete cleanupMap[getAMKey(am)]; } ); } for (const am of deleted) { cleanupMap[getAMKey(am)] && cleanupMap[getAMKey(am)](); } }, { flush: "post", immediate: true } ); watch( () => [Object.values(amMap).map((item) => item.am), custom.showAMs] as const, ([ams, show]) => { console.error(ams, "show", show); ams.forEach((am) => { am?.changeShow(show); }); } ); }; showAMsStack.push(play);