123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- <template>
- <div :class="{ focusAM: focusAM }" class="animation-layout">
- <Left
- :focus="focusAM"
- @update:focus="updateFocus"
- class="animation-left"
- @change-select="changeSelect"
- @delete="deleteAm"
- />
- <Right
- v-if="focusAM && !play"
- :am="focusAM"
- :frameAction="frameAction"
- @change-frame-action="(f) => (frameAction = f.action)"
- class="animation-right"
- v-model:activeAttrib="activeAttrib"
- @add-frame="add('frames', { duration: 0.1 })"
- @add-path="(preset) => add('paths', { reverse: false, ...preset })"
- @add-subtitle="add('subtitles', { content: '', background: '#000000' })"
- @add-action="(preset) => add('actions', preset)"
- @apply-global="(k) => applyGlobal(k)"
- />
- <GlobalFrame
- class="global-frame"
- v-if="activeAttrib?.key !== 'frames' && !play && focusAM && !currentIsFullView"
- :data="{ id: '0', name: 'global-frame', time: 0 }"
- :frame-action="frameAction"
- @change-frame-action="(action) => (frameAction = action.action)"
- />
- <BottomPano class="strengthen-top">
- <Bottom
- :am="focusAM"
- :follow="play || follow"
- v-model:current-time="currentTime"
- v-model:active="activeAttrib"
- />
- </BottomPano>
- </div>
- </template>
- <script lang="ts" setup>
- import Left from "./left.vue";
- import Right from "./right/index.vue";
- import GlobalFrame from "./right/frame.vue";
- import Bottom from "./bottom.vue";
- import BottomPano from "@/layout/bottom-pano.vue";
- import router, { back } from "@/router";
- import { enterEdit, sysBus } from "@/store";
- import { useViewStack } from "@/hook";
- import { ams, AnimationModel, autoSaveAnimationModel } from "@/store/animation";
- import { computed, nextTick, onUnmounted, reactive, ref, watch, watchEffect } from "vue";
- import { Active } from "./type";
- import {
- getAddTLItemAttr,
- getAddTLItemTimeByTime,
- } from "@/components/drawing-time-line/check";
- import { Dialog, Message } from "bill/expose-common";
- import { listener, mergeFuns, uuid } from "@/components/drawing/hook";
- import { title } from "./type";
- import {
- amMap,
- getAMKey,
- currentTime,
- play,
- animationGroup,
- } from "@/sdk/association/animation";
- import { sdk, AnimationModel3D } from "@/sdk";
- import {
- bottomBarHeightStack,
- showAMsStack,
- showBottomBarStack,
- showHeadBarStack,
- showLeftCtrlPanoStack,
- showLeftPanoStack,
- showModeTabStack,
- showRightPanoStack,
- showSearchStack,
- } from "@/env";
- import { clickListener } from "@/utils/event";
- import { useRMenus } from "@/components/right-menu";
- import { asyncTimeout } from "@/utils";
- import { currentIsFullView } from "@/utils/full";
- enterEdit(() => back());
- useViewStack(autoSaveAnimationModel);
- sysBus.on("leave", () => {
- updateFocus();
- });
- useViewStack(() => {
- const showRight = ref(false);
- animationGroup.enterEditPannel();
- return mergeFuns(
- showBottomBarStack.push(ref(true)),
- bottomBarHeightStack.push(ref("0px")),
- showRightPanoStack.push(showRight),
- showLeftPanoStack.push(ref(true)),
- showAMsStack.push(ref(true)),
- showHeadBarStack.push(computed(() => !play.value)),
- showLeftCtrlPanoStack.push(computed(() => !play.value)),
- showModeTabStack.push(computed(() => !play.value)),
- showSearchStack.push(computed(() => !play.value)),
- () => animationGroup.exitEditPannel(),
- watchEffect((onCleanup) => {
- const cleanups: any[] = [];
- if (play.value || activeAttrib.value?.key === "frames") {
- cleanups.push(showRightPanoStack.push(ref(false)));
- }
- if (play.value) {
- cleanups.push(showLeftPanoStack.push(ref(false)));
- }
- onCleanup(mergeFuns(cleanups));
- }),
- watchEffect(() => {
- showRight.value = !!focusAM.value;
- })
- );
- });
- const focusAM = ref<AnimationModel>();
- const activeAttrib = ref<Active>();
- const follow = ref(false);
- const frameAction = ref<string>();
- const applyGlobal = async (k: keyof AnimationModel) => {
- console.error(k, focusAM.value![k]);
- if (!(await Dialog.confirm("确定要将此属性应用到所有动画模型?"))) return;
- ams.value.forEach((am: any) => (am[k] = focusAM.value![k]));
- };
- const amM = computed(() => focusAM.value && amMap[getAMKey(focusAM.value)]);
- watch(play, (play, _, onCleanup) => {
- if (play) {
- const oldAction = frameAction.value;
- frameAction.value = undefined;
- onCleanup(() => (frameAction.value = oldAction));
- }
- });
- onUnmounted(() => {
- currentTime.value = 0;
- play.value = false;
- });
- const asyncOper = (item: AnimationModel, oper: (obj: AnimationModel3D) => void) => {
- let onCleanup = () => {};
- if (amMap[getAMKey(item)]?.am) {
- oper(amMap[getAMKey(item)]!.am!);
- } else {
- onCleanup = watchEffect(() => {
- if (amMap[getAMKey(item)]?.am!) {
- oper(amMap[getAMKey(item)]!.am!);
- onCleanup && onCleanup();
- }
- });
- }
- return onCleanup;
- };
- watchEffect((onCleanup) => {
- if (!amM.value) return;
- const am3d = amM.value.am;
- if (!am3d) return;
- const updateMat = (data: any) => {
- if (!data.byControl) return;
- const mat = JSON.parse(JSON.stringify(am3d.getModelPose()));
- if (data.quaAtPath) {
- focusAM.value!.mat = !focusAM.value!.mat
- ? { quaAtPath: data.quaAtPath }
- : {
- ...focusAM.value!.mat,
- quaAtPath: data.quaAtPath,
- };
- } else if (activeAttrib.value?.key === "frames") {
- const frame = focusAM.value!.frames[activeAttrib.value.ndx];
- frame.mat = mat;
- } else {
- focusAM.value!.mat = !focusAM.value!.mat
- ? mat
- : {
- quaAtPath: focusAM.value!.mat.quaAtPath,
- ...mat,
- };
- }
- };
- am3d.bus.on("transformChanged", updateMat);
- console.error(frameAction.value, am3d);
- switch (frameAction.value) {
- case "translate":
- am3d.enterMoveMode();
- break;
- case "rotate":
- am3d.enterRotateMode();
- break;
- case "scale":
- am3d.enterScaleMode();
- break;
- }
- onCleanup(() => {
- am3d.leaveTransform();
- am3d.bus.off("transformChanged", updateMat);
- });
- });
- const updateFocus = (am?: AnimationModel) => {
- if (focusAM.value && focusAM.value !== am) {
- asyncOper(focusAM.value, (item) => {
- item.changeSelect(false);
- });
- }
- activeAttrib.value = undefined;
- focusAM.value = am;
- am && asyncOper(am, (item) => item.changeSelect(true));
- };
- watch(
- () => [ams.value, ams.value.map((am) => amMap[getAMKey(am)]?.am)] as const,
- ([ams, am3ds], _, onCleanup) => {
- const cleanups = am3ds.map((am3d, ndx) => {
- const am = ams[ndx];
- console.log("监听", am.title, am3d);
- const update = (f: boolean) => {
- console.log("changeSelect", f);
- if (focusAM.value === am) {
- focusAM.value = f ? am : undefined;
- } else if (f) {
- focusAM.value = am;
- }
- };
- am3d?.bus.on("changeSelect", update);
- return () => {
- console.log("取消监听", am.title, am3d);
- am3d?.bus.off("changeSelect", update);
- };
- });
- onCleanup(mergeFuns(cleanups));
- },
- { immediate: true }
- );
- watch(
- activeAttrib,
- async (_a, _b, onCleanup) => {
- console.error("activeAttrib", activeAttrib.value);
- if (!activeAttrib.value) return;
- const cur = focusAM.value![activeAttrib.value.key][activeAttrib.value.ndx];
- const updateFocus = () => {
- const rang = [cur.time, cur.time + (cur.duration || 0)];
- if (currentTime.value < rang[0] || currentTime.value > rang[1]) {
- activeAttrib.value = undefined;
- }
- };
- follow.value = true;
- currentTime.value = cur.time!;
- nextTick(() => (follow.value = false));
- onCleanup(
- watch(
- () => [currentTime.value, cur.time, cur.time + (cur.duration || 0)],
- updateFocus,
- { flush: "sync" }
- )
- );
- },
- { flush: "sync" }
- );
- const add = <T extends Active["key"]>(
- key: T,
- preset: Partial<AnimationModel[T][0]> = {}
- ) => {
- const attr = getAddTLItemTimeByTime(
- focusAM.value![key],
- currentTime.value,
- typeof preset.duration === "number" ? preset.duration : 10
- );
- if (!attr) {
- Message.error("同一时间内请勿重复添加");
- } else {
- const item = reactive({
- id: uuid(),
- name: title[key],
- ...preset,
- ...attr,
- } as any);
- if (key === "frames") {
- asyncOper(
- focusAM.value!,
- (am3d) => (item.mat = JSON.parse(JSON.stringify(am3d.getModelPose())))
- );
- }
- focusAM.value![key].push(item);
- // currentTime.value = item.time;
- activeAttrib.value = {
- ndx: focusAM.value![key].length - 1,
- key,
- };
- }
- };
- let cleanSelect: (() => void) | null = null;
- const changeSelect = ({ select, unSelect }: Record<string, AnimationModel[]>) => {
- cleanSelect && cleanSelect();
- cleanSelect = mergeFuns(
- ...select.map((item) => asyncOper(item, (am) => am.changeShow(true))),
- ...unSelect.map((item) =>
- asyncOper(item, (am) => {
- if (item === focusAM.value) {
- updateFocus(undefined);
- }
- am.changeShow(false);
- })
- )
- );
- };
- const deleteAm = (am: AnimationModel) => {
- if (am === focusAM.value) {
- activeAttrib.value = undefined;
- focusAM.value = undefined;
- }
- ams.value.splice(ams.value.indexOf(am), 1);
- };
- let unMount: () => void;
- onUnmounted(
- mergeFuns([
- // listener(
- // document.querySelector("#layout-app .scene-canvas") as HTMLDivElement,
- // "contextmenu",
- // (ev) => {
- // ev.preventDefault();
- // }
- // ),
- clickListener(
- document.querySelector("#layout-app .scene-canvas") as HTMLDivElement,
- (pixel) => {
- const pos = sdk.getPositionByScreen(pixel);
- if (!focusAM.value) return;
- unMount && unMount();
- setTimeout(() => {
- unMount = useRMenus(pixel, [
- {
- label: "移动到这里",
- icon: "move",
- handler() {
- amMap[getAMKey(focusAM.value!)]?.am?.moveModelTo(pixel, pos?.worldPos);
- },
- },
- ]);
- });
- },
- 2
- ),
- ])
- );
- frameAction.value = "translate";
- </script>
- <style lang="scss" scoped>
- .animation-layout {
- --bottom-height: 70px;
- &.focusAM {
- --bottom-height: 225px;
- }
- }
- .animation-left {
- height: calc(100vh - var(--bottom-height));
- position: absolute;
- width: var(--left-pano-width);
- }
- .animation-right {
- height: calc(100vh - var(--bottom-height));
- padding-top: 0;
- position: absolute;
- right: 0;
- top: 0;
- }
- </style>
- <style>
- .animation-left .left-pano {
- bottom: 0 !important;
- }
- </style>
|