index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. <template>
  2. <div :class="{ focusAM: focusAM }" class="animation-layout">
  3. <Left
  4. :focus="focusAM"
  5. @update:focus="updateFocus"
  6. class="animation-left"
  7. @change-select="changeSelect"
  8. @delete="deleteAm"
  9. />
  10. <Right
  11. v-if="focusAM && !play"
  12. :am="focusAM"
  13. :frameAction="frameAction"
  14. @change-frame-action="(f) => (frameAction = f.action)"
  15. class="animation-right"
  16. v-model:activeAttrib="activeAttrib"
  17. @add-frame="add('frames', { duration: 0.1 })"
  18. @add-path="(preset) => add('paths', { reverse: false, ...preset })"
  19. @add-subtitle="add('subtitles', { content: '', background: '#000000' })"
  20. @add-action="(preset) => add('actions', preset)"
  21. @apply-global="(k) => applyGlobal(k)"
  22. />
  23. <GlobalFrame
  24. class="global-frame"
  25. v-if="activeAttrib?.key !== 'frames' && !play && focusAM && !currentIsFullView"
  26. :data="{ id: '0', name: 'global-frame', time: 0 }"
  27. :frame-action="frameAction"
  28. @change-frame-action="(action) => (frameAction = action.action)"
  29. />
  30. <BottomPano class="strengthen-top">
  31. <Bottom
  32. :am="focusAM"
  33. :follow="play || follow"
  34. v-model:current-time="currentTime"
  35. v-model:active="activeAttrib"
  36. />
  37. </BottomPano>
  38. </div>
  39. </template>
  40. <script lang="ts" setup>
  41. import Left from "./left.vue";
  42. import Right from "./right/index.vue";
  43. import GlobalFrame from "./right/frame.vue";
  44. import Bottom from "./bottom.vue";
  45. import BottomPano from "@/layout/bottom-pano.vue";
  46. import router, { back } from "@/router";
  47. import { enterEdit, sysBus } from "@/store";
  48. import { useViewStack } from "@/hook";
  49. import { ams, AnimationModel, autoSaveAnimationModel } from "@/store/animation";
  50. import { computed, nextTick, onUnmounted, reactive, ref, watch, watchEffect } from "vue";
  51. import { Active } from "./type";
  52. import {
  53. getAddTLItemAttr,
  54. getAddTLItemTimeByTime,
  55. } from "@/components/drawing-time-line/check";
  56. import { Dialog, Message } from "bill/expose-common";
  57. import { listener, mergeFuns, uuid } from "@/components/drawing/hook";
  58. import { title } from "./type";
  59. import {
  60. amMap,
  61. getAMKey,
  62. currentTime,
  63. play,
  64. animationGroup,
  65. } from "@/sdk/association/animation";
  66. import { sdk, AnimationModel3D } from "@/sdk";
  67. import {
  68. bottomBarHeightStack,
  69. showAMsStack,
  70. showBottomBarStack,
  71. showHeadBarStack,
  72. showLeftCtrlPanoStack,
  73. showLeftPanoStack,
  74. showModeTabStack,
  75. showRightPanoStack,
  76. showSearchStack,
  77. } from "@/env";
  78. import { clickListener } from "@/utils/event";
  79. import { useRMenus } from "@/components/right-menu";
  80. import { asyncTimeout } from "@/utils";
  81. import { currentIsFullView } from "@/utils/full";
  82. enterEdit(() => back());
  83. useViewStack(autoSaveAnimationModel);
  84. sysBus.on("leave", () => {
  85. updateFocus();
  86. });
  87. useViewStack(() => {
  88. const showRight = ref(false);
  89. animationGroup.enterEditPannel();
  90. return mergeFuns(
  91. showBottomBarStack.push(ref(true)),
  92. bottomBarHeightStack.push(ref("0px")),
  93. showRightPanoStack.push(showRight),
  94. showLeftPanoStack.push(ref(true)),
  95. showAMsStack.push(ref(true)),
  96. showHeadBarStack.push(computed(() => !play.value)),
  97. showLeftCtrlPanoStack.push(computed(() => !play.value)),
  98. showModeTabStack.push(computed(() => !play.value)),
  99. showSearchStack.push(computed(() => !play.value)),
  100. () => animationGroup.exitEditPannel(),
  101. watchEffect((onCleanup) => {
  102. const cleanups: any[] = [];
  103. if (play.value || activeAttrib.value?.key === "frames") {
  104. cleanups.push(showRightPanoStack.push(ref(false)));
  105. }
  106. if (play.value) {
  107. cleanups.push(showLeftPanoStack.push(ref(false)));
  108. }
  109. onCleanup(mergeFuns(cleanups));
  110. }),
  111. watchEffect(() => {
  112. showRight.value = !!focusAM.value;
  113. })
  114. );
  115. });
  116. const focusAM = ref<AnimationModel>();
  117. const activeAttrib = ref<Active>();
  118. const follow = ref(false);
  119. const frameAction = ref<string>();
  120. const applyGlobal = async (k: keyof AnimationModel) => {
  121. console.error(k, focusAM.value![k]);
  122. if (!(await Dialog.confirm("确定要将此属性应用到所有动画模型?"))) return;
  123. ams.value.forEach((am: any) => (am[k] = focusAM.value![k]));
  124. };
  125. const amM = computed(() => focusAM.value && amMap[getAMKey(focusAM.value)]);
  126. watch(play, (play, _, onCleanup) => {
  127. if (play) {
  128. const oldAction = frameAction.value;
  129. frameAction.value = undefined;
  130. onCleanup(() => (frameAction.value = oldAction));
  131. }
  132. });
  133. onUnmounted(() => {
  134. currentTime.value = 0;
  135. play.value = false;
  136. });
  137. const asyncOper = (item: AnimationModel, oper: (obj: AnimationModel3D) => void) => {
  138. let onCleanup = () => {};
  139. if (amMap[getAMKey(item)]?.am) {
  140. oper(amMap[getAMKey(item)]!.am!);
  141. } else {
  142. onCleanup = watchEffect(() => {
  143. if (amMap[getAMKey(item)]?.am!) {
  144. oper(amMap[getAMKey(item)]!.am!);
  145. onCleanup && onCleanup();
  146. }
  147. });
  148. }
  149. return onCleanup;
  150. };
  151. watchEffect((onCleanup) => {
  152. if (!amM.value) return;
  153. const am3d = amM.value.am;
  154. if (!am3d) return;
  155. const updateMat = (data: any) => {
  156. console.log("0.0.0", data);
  157. if (!data.byControl) return;
  158. const mat = JSON.parse(JSON.stringify(am3d.getModelPose()));
  159. if (data.quaAtPath) {
  160. focusAM.value!.mat = !focusAM.value!.mat
  161. ? { quaAtPath: data.quaAtPath }
  162. : {
  163. ...focusAM.value!.mat,
  164. quaAtPath: data.quaAtPath,
  165. };
  166. } else if (activeAttrib.value?.key === "frames") {
  167. const frame = focusAM.value!.frames[activeAttrib.value.ndx];
  168. frame.mat = mat;
  169. } else {
  170. focusAM.value!.mat = !focusAM.value!.mat
  171. ? mat
  172. : {
  173. quaAtPath: focusAM.value!.mat.quaAtPath,
  174. ...mat,
  175. };
  176. }
  177. };
  178. am3d.bus.on("transformChanged", updateMat);
  179. console.error(frameAction.value, am3d);
  180. switch (frameAction.value) {
  181. case "translate":
  182. am3d.enterMoveMode();
  183. break;
  184. case "rotate":
  185. am3d.enterRotateMode();
  186. break;
  187. case "scale":
  188. am3d.enterScaleMode();
  189. break;
  190. }
  191. onCleanup(() => {
  192. am3d.leaveTransform();
  193. am3d.bus.off("transformChanged", updateMat);
  194. });
  195. });
  196. const updateFocus = (am?: AnimationModel) => {
  197. if (focusAM.value && focusAM.value !== am) {
  198. asyncOper(focusAM.value, (item) => {
  199. item.changeSelect(false);
  200. });
  201. }
  202. activeAttrib.value = undefined;
  203. focusAM.value = am;
  204. am && asyncOper(am, (item) => item.changeSelect(true));
  205. };
  206. watch(
  207. () => [ams.value, ams.value.map((am) => amMap[getAMKey(am)]?.am)] as const,
  208. ([ams, am3ds], _, onCleanup) => {
  209. const cleanups = am3ds.map((am3d, ndx) => {
  210. const am = ams[ndx];
  211. console.log("监听", am.title, am3d);
  212. const update = (f: boolean) => {
  213. console.log("changeSelect", f);
  214. if (focusAM.value === am) {
  215. focusAM.value = f ? am : undefined;
  216. } else if (f) {
  217. focusAM.value = am;
  218. }
  219. };
  220. am3d?.bus.on("changeSelect", update);
  221. return () => {
  222. console.log("取消监听", am.title, am3d);
  223. am3d?.bus.off("changeSelect", update);
  224. };
  225. });
  226. onCleanup(mergeFuns(cleanups));
  227. },
  228. { immediate: true }
  229. );
  230. watch(
  231. activeAttrib,
  232. async (_a, _b, onCleanup) => {
  233. console.error("activeAttrib", activeAttrib.value);
  234. if (!activeAttrib.value) return;
  235. const cur = focusAM.value![activeAttrib.value.key][activeAttrib.value.ndx];
  236. const updateFocus = () => {
  237. const rang = [cur.time, cur.time + (cur.duration || 0)];
  238. if (currentTime.value < rang[0] || currentTime.value > rang[1]) {
  239. activeAttrib.value = undefined;
  240. }
  241. };
  242. follow.value = true;
  243. currentTime.value = cur.time!;
  244. nextTick(() => (follow.value = false));
  245. onCleanup(
  246. watch(
  247. () => [currentTime.value, cur.time, cur.time + (cur.duration || 0)],
  248. updateFocus,
  249. { flush: "sync" }
  250. )
  251. );
  252. },
  253. { flush: "sync" }
  254. );
  255. const add = <T extends Active["key"]>(
  256. key: T,
  257. preset: Partial<AnimationModel[T][0]> = {}
  258. ) => {
  259. const attr = getAddTLItemTimeByTime(
  260. focusAM.value![key],
  261. currentTime.value,
  262. typeof preset.duration === "number" ? preset.duration : 10
  263. );
  264. if (!attr) {
  265. Message.error("同一时间内请勿重复添加");
  266. } else {
  267. const item = reactive({
  268. id: uuid(),
  269. name: title[key],
  270. ...preset,
  271. ...attr,
  272. } as any);
  273. if (key === "frames") {
  274. asyncOper(
  275. focusAM.value!,
  276. (am3d) => (item.mat = JSON.parse(JSON.stringify(am3d.getModelPose())))
  277. );
  278. }
  279. focusAM.value![key].push(item);
  280. // currentTime.value = item.time;
  281. activeAttrib.value = {
  282. ndx: focusAM.value![key].length - 1,
  283. key,
  284. };
  285. }
  286. };
  287. let cleanSelect: (() => void) | null = null;
  288. const changeSelect = ({ select, unSelect }: Record<string, AnimationModel[]>) => {
  289. cleanSelect && cleanSelect();
  290. cleanSelect = mergeFuns(
  291. ...select.map((item) => asyncOper(item, (am) => am.changeShow(true))),
  292. ...unSelect.map((item) =>
  293. asyncOper(item, (am) => {
  294. if (item === focusAM.value) {
  295. updateFocus(undefined);
  296. }
  297. am.changeShow(false);
  298. })
  299. )
  300. );
  301. };
  302. const deleteAm = (am: AnimationModel) => {
  303. if (am === focusAM.value) {
  304. activeAttrib.value = undefined;
  305. focusAM.value = undefined;
  306. }
  307. ams.value.splice(ams.value.indexOf(am), 1);
  308. };
  309. let unMount: () => void;
  310. onUnmounted(
  311. mergeFuns([
  312. // listener(
  313. // document.querySelector("#layout-app .scene-canvas") as HTMLDivElement,
  314. // "contextmenu",
  315. // (ev) => {
  316. // ev.preventDefault();
  317. // }
  318. // ),
  319. clickListener(
  320. document.querySelector("#layout-app .scene-canvas") as HTMLDivElement,
  321. (pixel) => {
  322. const pos = sdk.getPositionByScreen(pixel);
  323. if (!focusAM.value) return;
  324. unMount && unMount();
  325. setTimeout(() => {
  326. unMount = useRMenus(pixel, [
  327. {
  328. label: "移动到这里",
  329. icon: "move",
  330. handler() {
  331. amMap[getAMKey(focusAM.value!)]?.am?.moveModelTo(pixel, pos?.worldPos);
  332. },
  333. },
  334. ]);
  335. });
  336. },
  337. 2
  338. ),
  339. ])
  340. );
  341. frameAction.value = "translate";
  342. </script>
  343. <style lang="scss" scoped>
  344. .animation-layout {
  345. --bottom-height: 70px;
  346. &.focusAM {
  347. --bottom-height: 225px;
  348. }
  349. }
  350. .animation-left {
  351. height: calc(100vh - var(--bottom-height));
  352. position: absolute;
  353. width: var(--left-pano-width);
  354. }
  355. .animation-right {
  356. height: calc(100vh - var(--bottom-height));
  357. padding-top: 0;
  358. position: absolute;
  359. right: 0;
  360. top: 0;
  361. }
  362. </style>
  363. <style>
  364. .animation-left .left-pano {
  365. bottom: 0 !important;
  366. }
  367. </style>