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 { paths } from "@/store"; import { Color } from "three"; 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); am.bus.on("loadDone", () => { amMap[key].am = am; 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 stopLoad = watch( () => { const exists = am.subtitles.some(({ id }) => id === data.id); amMap[key]?.am; return [amMap[key], exists] as const; }, ([map, exists]) => { if (!map?.am) return; if (exists && !map.subtitles[data.id]) { const mountEl = document.querySelector("#app")!; const layer = document.createElement("div"); layer.className = "subtitle"; mountEl.appendChild(layer); const cleanups = [ watchEffect(() => { layer.innerHTML = data.content; 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(() => { layer.style.visibility = pixel.value && show.value ? "visible" : "hidden"; }), watchEffect(() => { if (pixel.value) { layer.style.transform = `translate(${pixel.value.x}px, calc(${pixel.value.y}px - 50%))`; } }), () => mountEl.removeChild(layer), ]; map.subtitles[data.id] = mergeFuns(cleanups); } 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( [currentTime, () => amMap[getAMKey(am)]?.am, size, play, () => amMap[key]?.am], (_a, _b, onCleanup) => { if ( !play.value && router.currentRoute.value.name !== RoutesName.animation ) { show.value = false; } else if ( currentTime.value >= data.time && currentTime.value <= data.time + data.duration ) { update(); show.value = true; onCleanup( watch( play, (play, _a, onCleanup) => { if (!play && _sdk) { _sdk.sceneBus.on("cameraChange", update); onCleanup(() => _sdk.sceneBus.off("cameraChange", 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, ...am.actions, ...am.subtitles, ...am.paths, ].map((item) => item.time + (item.duration || 0)); return Math.max(...endPoints); }); console.log('amsEndTime', amsEndTime, ams.value) 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); }); 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 frame = am3d.am!.addFrame({ // id: uuid(), // mat: am.mat || am3d.am.getModelPose(), // name: "global-frame", // time: 0, // }); // am3d.globalFrame = frame; 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(() => { // am.mat && frame.setMat(am.mat); if (am.mat && am3d.am) { console.log(am.mat) am3d.am.changePosition(mat.position!) am3d.am.changeRotation(mat.rotation!) am3d.am.changeScale(mat.scale!) } console.log("set-global-frame", am.mat); }), () => { // frame.destroy(); 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 } ); };