bill 5 mesi fa
parent
commit
d854b56852

BIN
public/animation/Soldier.glb


BIN
public/animation/Xbot.glb


BIN
public/animation/dog.glb


BIN
public/animation/man--running.glb


BIN
public/animation/man--walk.glb


+ 24 - 82
src/api/animation.ts

@@ -13,7 +13,7 @@ export interface AnimationAction {
   id: string;
   title: string;
   url: string;
-  
+  action: string
 }
 export type AnimationModelAction = {
   amplitude: number;
@@ -21,6 +21,7 @@ export type AnimationModelAction = {
   time: number;
   duration: number;
   id: string;
+  key: string
   name: string
 };
 export type AnimationModelSubtitle = {
@@ -39,7 +40,7 @@ export type AnimationModelFrame = {
 };
 export type AnimationModelPath = {
   reverse: boolean;
-  pathId: string;
+  pathId?: string;
   time: number;
   duration: number;
   id: string;
@@ -74,80 +75,18 @@ export const fetchAnimationModels = async () => {
   return [
     {
       id: "1",
-      title: "模型1",
-      url: "",
-      frames: [
-        { time: 10, name: "帧1" },
-        { time: 20, name: "帧2" },
-        { time: 30, name: "帧3" },
-      ],
-      actions: [
-        { time: 10, duration: 3, name: "动作1" },
-        { time: 20, duration: 5, name: "动作2" },
-        { time: 30, duration: 3, name: "动作3" },
-      ],
-      subtitles: [
-        { time: 0, duration: 15, name: "字幕1" },
-        { time: 20, duration: 15, name: "字幕2" },
-        { time: 40, duration: 30, name: "字幕3" },
-      ],
-      paths: [
-        { time: 0, duration: 15, name: "路径1" },
-        { time: 20, duration: 15, name: "路径2" },
-        { time: 40, duration: 30, name: "路径3" },
-      ],
+      title: 'dog',
+      url: "/animation/dog.glb",
+      showTitle: true,
+      fontSize: 12,
+      globalVisibility: true,
+      visibilityRange: 100,
+      frames: [],
+      actions: [],
+      subtitles: [],
+      paths: [],
     },
-    {
-      id: "2",
-      title: "模型2",
-      url: "",
-      frames: [
-        { time: 10, name: "帧1" },
-        { time: 20, name: "帧2" },
-        { time: 30, name: "帧3" },
-      ],
-      actions: [
-        { time: 10, duration: 3, name: "动作1" },
-        { time: 20, duration: 5, name: "动作2" },
-        { time: 30, duration: 3, name: "动作3" },
-      ],
-      subtitles: [
-        { time: 0, duration: 15, name: "字幕1" },
-        { time: 20, duration: 15, name: "字幕2" },
-        { time: 40, duration: 30, name: "字幕3" },
-      ],
-      paths: [
-        { time: 0, duration: 15, name: "路径1" },
-        { time: 20, duration: 15, name: "路径2" },
-        { time: 40, duration: 30, name: "路径3" },
-      ],
-    },
-    {
-      id: "3",
-      title: "模型3",
-      url: "",
-      frames: [
-        { time: 10, name: "帧1" },
-        { time: 20, name: "帧2" },
-        { time: 30, name: "帧3" },
-      ],
-      actions: [
-        { time: 10, duration: 3, name: "动作1" },
-        { time: 20, duration: 5, name: "动作2" },
-        { time: 30, duration: 3, name: "动作3" },
-      ],
-      subtitles: [
-        { time: 0, duration: 15, name: "字幕1" },
-        { time: 20, duration: 15, name: "字幕2" },
-        { time: 40, duration: 30, name: "字幕3" },
-      ],
-      paths: [
-        { time: 0, duration: 15, name: "路径1" },
-        { time: 20, duration: 15, name: "路径2" },
-        { time: 40, duration: 30, name: "路径3" },
-      ],
-    },
-  ] as unknown as AnimationModel[];
+  ] as AnimationModel[];
 
   const ams = await axios.get<ServiceAnimationModel[]>(AM_MODEL_LIST, {
     params: { caseId: params.caseId },
@@ -157,13 +96,16 @@ export const fetchAnimationModels = async () => {
 
 export const fetchAnimationActions = async () => {
   return [
-    { id: "1", title: "模型1", url: "" },
-    { id: "2", title: "模型2", url: "" },
-    { id: "3", title: "模型3", url: "" },
-    { id: "2", title: "模型2", url: "" },
-    { id: "3", title: "模型3", url: "" },
-    { id: "2", title: "模型2", url: "" },
-    { id: "3", title: "模型3", url: "" },
+    { id: "1", action: 'Walk', title: "走", url: "" },
+    { id: "2", action: 'Run', title: "跑", url: "" },
+    { id: "3", action: 'Climb', title: "爬", url: "" },
+    { id: "2", action: 'JumpUp', title: "向上跳", url: "" },
+    { id: "3", action: 'JumpDown', title: "向下跳", url: "" },
+    { id: "2", action: 'TurnLeft', title: "左转", url: "" },
+    { id: "3", action: 'TurnRight', title: "右转", url: "" },
+    { id: "3", action: 'FallForward', title: "向前倒地", url: "" }, 
+    { id: "3", action: 'FallBackward', title: "向后倒地", url: "" },
+
   ];
 };
 

+ 17 - 0
src/api/material.ts

@@ -75,6 +75,23 @@ export const fetchMaterialPage = async (params: MaterialPageProps) => {
       uploadId: item.uploadId
     }))
   }
+
+  const testUrls = [
+    'dog.glb', 'man--running.glb', 'man--walk.glb', 'Soldier.glb', 'Xbot.glb'
+  ]
+  nm.list.unshift(
+    ...testUrls.map((item, ndx) => ({
+      id: ndx,
+      name: item,
+      format: 'glb',
+      url: `/animation/${item}`,
+      size: 1,
+      groupId: 1,
+      status: 1,
+      group: '动画模型',
+      uploadId: 1
+    }))
+  )
   
   return nm;
 };

+ 7 - 5
src/components/drawing-time-line/check.ts

@@ -1,3 +1,5 @@
+import { round } from "@/utils";
+
 export type TLItem = { time: number; duration?: number };
 
 export const getAddTLItemAttr = (
@@ -34,7 +36,7 @@ export const getAddTLItemAttr = (
     if (dur < minDur) {
       return null;
     } else {
-      return { time: cur, duration: dur };
+      return { time: cur, duration: round(dur, 2) };
     }
   }
 };
@@ -45,10 +47,10 @@ export const checkTLItem = (items: TLItem[], cur: TLItem, i?: number) => {
     .sort((a, b) => a.time - b.time)
     .map((item) => ({
       x: item.time,
-      xe: item.time + (item.duration || 0.001),
+      xe: item.time + (item.duration || 0.5),
     }));
 
-  const curRect = { x: cur.time, xe: cur.time + (cur.duration || 0.001) };
+  const curRect = { x: cur.time, xe: cur.time + (cur.duration || 0.5) };
   for (let i = 0; i < exRects.length; i++) {
     const exRect = exRects[i];
     if (
@@ -63,14 +65,14 @@ export const checkTLItem = (items: TLItem[], cur: TLItem, i?: number) => {
   return true;
 };
 
-export const getAddTLItemTime = (items: TLItem[], refNdx = 0, dur = 0.01) => {
+export const getAddTLItemTime = (items: TLItem[], refNdx = 0, dur = 0.5) => {
   const ref = items[refNdx];
   items = [...items].sort((a, b) => a.time - b.time);
   refNdx = items.indexOf(ref);
 
   let time = 0;
   for (let i = refNdx; i < items.length; i++) {
-    time = items[i].time + (items[i].duration || 0.01);
+    time = items[i].time + (items[i].duration || 0.5);
     if (checkTLItem([...items].splice(i), { time, duration: dur })) {
       break;
     }

+ 3 - 2
src/components/drawing-time-line/frame.vue

@@ -15,7 +15,7 @@ import {
   useViewerInvertTransformConfig,
 } from "../drawing/hook";
 import { Transform } from "konva/lib/Util";
-import { Line } from "konva/lib/shapes/Line";
+import { Line, LineConfig } from "konva/lib/shapes/Line";
 import { DC } from "../drawing/dec";
 
 const { misPixel } = useGlobalVar();
@@ -42,12 +42,13 @@ const lines = computed(() => {
       points: flatPositions(points),
       closed: true,
       fill: "#fff",
+      hitStrokeWidth: 5,
       ...new Transform()
         .translate(origin.x, 0)
         .scale(invConfig.value.scaleX, 1)
         .translate(-origin.x, 0)
         .decompose(),
-    };
+    } as LineConfig;
   });
 });
 

+ 1 - 1
src/components/drawing-time-line/index.vue

@@ -14,7 +14,7 @@
     :items="items"
     :top="top"
     :activeNdx="active ? items.indexOf(active) : -1"
-    :ref="({ shapes }: any) => itemShapes = shapes"
+    :ref="(r: any) => itemShapes = r ? r.shapes : []"
   />
   <template v-for="(itemShape, i) in itemShapes">
     <Operate

+ 2 - 2
src/components/drawing/hook.ts

@@ -403,7 +403,7 @@ export const useViewer = installGlobalVar(() => {
     const scaleDestroy = scaleListener(dom, (info) => {
       const currentScalex = viewer.viewMat.decompose().scaleX;
       const finalScale = currentScalex * info.scale;
-      const scale = Math.min(Math.max(finalScale, 0.5), 3);
+      const scale = Math.min(Math.max(finalScale, 0.5), 8);
       if (cursor.value !== "move") {
         viewer.scalePixel(info.center, { x: scale / currentScalex, y: 1 });
       }
@@ -539,7 +539,7 @@ export const useDrag = (
     });
 
     return mergeFuns(
-      listener(dom, "pointerup", () => {
+      listener(document.documentElement, "pointerup", () => {
         pop && pop();
         pop = null;
         drag.value = undefined;

+ 3 - 1
src/hook/ids.ts

@@ -1,5 +1,5 @@
 import { diffArrayChange } from "@/utils";
-import { ref, Ref, watch } from "vue";
+import { computed, ref, Ref, watch } from "vue";
 
 export const useSelects = <T extends { id: any }>(items: Ref<T[]>) => {
   const selects = ref<T[]>([]);
@@ -11,6 +11,7 @@ export const useSelects = <T extends { id: any }>(items: Ref<T[]>) => {
     } else {
       ~ndx && selects.value.splice(ndx, 1);
     }
+    console.log(selects.value)
   };
   const updateSelectId = (id: any, select: boolean) => {
     const item = items.value.find((s) => s.id === id);
@@ -35,6 +36,7 @@ export const useSelects = <T extends { id: any }>(items: Ref<T[]>) => {
 
   return {
     selects,
+    unSelects: computed(() => items.value.filter(item => !selects.value.includes(item as any))),
     updateSelect,
     updateSelectId,
   };

+ 7 - 2
src/views/animation/bottom.vue

@@ -10,7 +10,7 @@
           class="slider"
           v-model:value="scale"
           :min="0.5"
-          :max="3"
+          :max="8"
           :step="0.01"
           :tooltipOpen="false"
         />
@@ -26,7 +26,12 @@
           :top="prop.top"
           :itemsRenderer="prop.component"
           @update="({ ndx, time }) => (am[prop.attr][ndx].time = time)"
-          @add="(item) => am[prop.attr].push(item)"
+          @add="
+            (item) => {
+              am[prop.attr].push(item);
+              $emit('update:active', { key: prop.attr, ndx: am[prop.attr].length - 1 });
+            }
+          "
           @del="(ndx) => am[prop.attr].splice(ndx, 1)"
           :active="prop.attr === active?.key ? am[prop.attr][active.ndx] : undefined"
           @update:active="(active: any) => $emit('update:active', active && { key: prop.attr, ndx: am[prop.attr].indexOf(active) })"

+ 54 - 22
src/views/animation/index.vue

@@ -1,13 +1,22 @@
 <template>
   <div :class="{ focusAM: focusAM }" class="animation-layout">
-    <Left :focus="focusAM" @update:focus="updateFocus" class="animation-left" />
+    <Left
+      :focus="focusAM"
+      @update:focus="updateFocus"
+      class="animation-left"
+      @change-select="changeSelect"
+      @delete="deleteAm"
+    />
     <Right
       v-if="focusAM"
       :am="focusAM"
       class="animation-right"
       v-model:activeAttrib="activeAttrib"
-      @add-frame="addFrame"
-      @add-path="addPath"
+      @add-frame="add('frames')"
+      @add-path="add('paths', { reverse: false })"
+      @add-subtitle="add('subtitles', { content: '', background: '#fff' })"
+      @add-action="(preset) => add('actions', preset)"
+      @apply-global="k => ams.forEach((am: any) => (am[k] = focusAM![k]))"
     />
     <Bottom
       v-if="focusAM"
@@ -27,16 +36,18 @@ import router from "@/router";
 import { enterEdit } from "@/store";
 import { useViewStack } from "@/hook";
 import {
+  ams,
   AnimationModel,
   autoSaveAnimationModel,
   initAnimationActions,
   initialAnimationModels,
 } from "@/store/animation";
-import { ref, watchEffect } from "vue";
+import { reactive, ref, watch, watchEffect } from "vue";
 import { Active } from "./type";
 import { getAddTLItemAttr } from "@/components/drawing-time-line/check";
 import { Message } from "bill/expose-common";
 import { uuid } from "@/components/drawing/hook";
+import { title } from "./type";
 
 enterEdit(() => router.back());
 initialAnimationModels();
@@ -50,34 +61,55 @@ const currentTime = ref(0);
 const updateFocus = (am?: AnimationModel) => {
   activeAttrib.value = undefined;
   focusAM.value = am;
-  console.error(am);
 };
 
-const addFrame = () => {
-  const attr = getAddTLItemAttr(focusAM.value!.frames, currentTime.value, 10, 1);
+watch(activeAttrib, (_a, _b, onCleanup) => {
+  if (!activeAttrib.value) return;
+  const cur = focusAM.value![activeAttrib.value.key][activeAttrib.value.ndx];
+  currentTime.value = cur.time!;
+  onCleanup(
+    watch(currentTime, () => {
+      const rang = [cur.time, cur.time + (cur.duration || 0)];
+      if (currentTime.value < rang[0] || currentTime.value > rang[1]) {
+        activeAttrib.value = undefined;
+      }
+    })
+  );
+});
+
+const add = <T extends Active["key"]>(
+  key: T,
+  preset: Partial<AnimationModel[T][0]> = {}
+) => {
+  const attr = getAddTLItemAttr(focusAM.value![key], currentTime.value, 10, 1);
   if (!attr) {
-    Message.error("当前时间已存在其他帧");
+    Message.error("当前时间已存在其他" + title[key]);
   } else {
-    focusAM.value!.frames.push({
+    const item = reactive({
       id: uuid(),
-      name: "帧",
+      name: title[key],
       ...attr,
-      duration: 0,
-    });
+      ...preset,
+    } as any);
+    focusAM.value![key].push(item);
+    activeAttrib.value = {
+      ndx: focusAM.value![key].length - 1,
+      key,
+    };
   }
 };
 
-const addPath = () => {
-  const attr = getAddTLItemAttr(focusAM.value!.paths, currentTime.value, 10, 1);
-  if (!attr) {
-    Message.error("当前时间已存在其他路径");
-  } else {
-    focusAM.value!.frames.push({
-      id: uuid(),
-      name: "路径",
-      ...attr,
-    });
+const changeSelect = ({ select, unSelect }: Record<string, AnimationModel[]>) => {
+  console.log("select", select);
+  console.log("unSelect", unSelect);
+};
+
+const deleteAm = (am: AnimationModel) => {
+  if (am === focusAM.value) {
+    activeAttrib.value = undefined;
+    focusAM.value = undefined;
   }
+  ams.value.splice(ams.value.indexOf(am), 1);
 };
 </script>
 

+ 21 - 9
src/views/animation/left.vue

@@ -25,10 +25,10 @@
               <ui-input
                 type="checkbox"
                 @click.stop
-                :modalValue="selectAMs.includes(item)"
-                @update:modalValue="(select: any) => updateSelect(item, select)"
+                :modelValue="selectAMs.includes(item)"
+                @update:modelValue="(select: any) => updateSelectAm(item, select)"
               />
-              <ui-icon type="del" ctrl @click="delHandler(item)" />
+              <ui-icon type="del" ctrl @click.stop="delHandler(item)" />
             </div>
           </div>
         </div>
@@ -45,7 +45,7 @@ import { LeftPano } from "@/layout";
 import { selectMaterials } from "@/components/materials/quisk";
 import { moundLeftPanoStack, showLeftPanoStack } from "@/env";
 import { togetherCallback } from "@/utils";
-import { ref } from "vue";
+import { ref, watch } from "vue";
 import { TabPane, Tabs } from "ant-design-vue";
 import { ams, AnimationModel, createAnimationModel } from "@/store/animation";
 import { useSelects } from "@/hook/ids";
@@ -58,10 +58,21 @@ useViewStack(() =>
 );
 
 const props = defineProps<{ focus?: AnimationModel }>();
-const emit = defineEmits<{ (e: "update:focus", item?: AnimationModel): void }>();
+const emit = defineEmits<{
+  (e: "update:focus", item?: AnimationModel): void;
+  (e: "delete", item: AnimationModel): void;
+  (
+    e: "changeSelect",
+    data: { select: AnimationModel[]; unSelect: AnimationModel[] }
+  ): void;
+}>();
 
 const activeKey = ref("model");
-const { updateSelect, selects: selectAMs } = useSelects(ams);
+const { updateSelect, selects: selectAMs, unSelects } = useSelects(ams);
+const updateSelectAm = (item: AnimationModel, select: boolean) => {
+  updateSelect(item, select);
+  emit("changeSelect", { select: selectAMs.value, unSelect: unSelects.value });
+};
 
 if (import.meta.env.DEV) {
   activeKey.value = "animation";
@@ -73,7 +84,7 @@ if (import.meta.env.DEV) {
 const delHandler = (item: AnimationModel) => {
   const ndx = ams.value.indexOf(item);
   if (~ndx) {
-    ams.value.splice(ndx, 1);
+    emit("delete", ams.value[ndx]);
   }
 };
 
@@ -87,13 +98,14 @@ const clickHandler = (item: AnimationModel) => {
 
 const selectModel = async () => {
   const list = await selectMaterials({
-    uploadFormat: ["zip"],
-    format: ["obj", "ply", "las", "laz", "b3dm", "shp", "osgb"],
+    uploadFormat: ["animation-model"],
+    format: ["glb"],
     maxSize: 2 * 1024 * 1024 * 1024,
   });
   if (!list?.length) return;
   list.forEach((item) => {
     ams.value.push(createAnimationModel({ title: item.name, url: item.url }));
+    emit("update:focus", ams.value[ams.value.length - 1]);
   });
 };
 </script>

+ 28 - 4
src/views/animation/right/am.vue

@@ -74,9 +74,13 @@
           <span class="label">动作库</span>
         </ui-group-option>
         <ui-group-option class="actions">
-          <span v-for="action in amActions">
+          <div
+            v-for="action in amActions"
+            @click="$emit('addAction', { key: action.action, name: action.title })"
+          >
             <img :src="action.url" />
-          </span>
+            <span>{{ action.title }}</span>
+          </div>
         </ui-group-option>
       </ui-group>
     </TabPane>
@@ -92,7 +96,7 @@ import { amActions } from "@/store/animation";
 
 defineProps<{ am: AnimationModel }>();
 defineEmits<{
-  (e: "addFrame" | "addPath" | "addSubtitle"): void;
+  (e: "addFrame" | "addPath" | "addSubtitle" | "addAction", preset?: any): void;
   (e: "applyGlobal", d: keyof AnimationModel): void;
 }>();
 const activeKey = ref("setting");
@@ -123,13 +127,33 @@ const activeKey = ref("setting");
   display: flex;
   flex-wrap: wrap;
 
-  span {
+  div {
     width: calc((100% - 28px) / 3);
     background: #fff;
     margin-top: 7px;
     margin-bottom: 7px;
     height: 80px;
     cursor: pointer;
+    position: relative;
+
+    &::after {
+      content: "";
+      position: absolute;
+      inset: 0;
+      z-index: 1;
+      background: rgba(0, 0, 0, 0.5);
+    }
+    span {
+      position: absolute;
+      z-index: 2;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+      font-size: 16px;
+      color: #fff;
+      width: 100%;
+      text-align: center;
+    }
 
     &:nth-child(3n - 1) {
       margin-left: 14px;

+ 10 - 14
src/views/animation/right/index.vue

@@ -1,14 +1,16 @@
 <template>
   <RightFillPano class="animation-right" v-if="activeAttrib?.key !== 'frames'">
     <AM
-      v-if="!activeAttrib || !~activeAttrib.ndx"
+      v-show="!activeAttrib || !~activeAttrib.ndx"
       :am="am"
-      @add-frame="emit('addFrame')"
-      @add-path="emit('addPath')"
-      @add-subtitle="emit('addSubtitle')"
+      @add-frame="(preset) => emit('addFrame', preset)"
+      @add-path="(preset) => emit('addPath', preset)"
+      @add-subtitle="(preset) => emit('addSubtitle', preset)"
+      @add-action="(preset) => emit('addAction', preset)"
       @apply-global="(k) => emit('applyGlobal', k)"
+      @applyGlobal="(k) => emit('applyGlobal', k)"
     />
-    <template v-else>
+    <template v-if="!(!activeAttrib || !~activeAttrib.ndx)">
       <ui-icon
         style="font-size: 16px"
         type="close"
@@ -33,13 +35,13 @@
 
 <script lang="ts" setup>
 import { RightFillPano } from "@/layout";
-import { Active } from "../type";
+import { Active, title } from "../type";
 import AM from "./am.vue";
 import Action from "./action.vue";
 import Frame from "./frame.vue";
 import Path from "./path.vue";
 import Subtitle from "./subtitle.vue";
-import { checkTLItem } from "@/components/drawing-time-line/check";
+import { checkTLItem, getAddTLItemAttr } from "@/components/drawing-time-line/check";
 import { Message } from "bill/expose-common";
 import { AnimationModel, AnimationModelFrame } from "@/store/animation";
 
@@ -50,7 +52,7 @@ const props = defineProps<{
 }>();
 const emit = defineEmits<{
   (e: "update:activeAttrib", d: undefined): void;
-  (e: "addFrame" | "addPath" | "addSubtitle"): void;
+  (e: "addFrame" | "addPath" | "addSubtitle" | "addAction", preset?: any): void;
   (e: "applyGlobal", d: keyof AnimationModel): void;
   (e: "changeFrameAction", d: { action?: string; frame: AnimationModelFrame }): void;
 }>();
@@ -60,12 +62,6 @@ const comps = {
   paths: Path,
   subtitles: Subtitle,
 };
-const title = {
-  actions: "动作",
-  paths: "路径",
-  subtitles: "字幕",
-  frames: "",
-};
 const setDuration = (dur: number) => {
   const ndx = props.activeAttrib!.ndx;
   const items = props.am[props.activeAttrib!.key];

+ 8 - 0
src/views/animation/type.ts

@@ -2,3 +2,11 @@ export type Active = {
   key: "frames" | "actions" | "subtitles" | "paths";
   ndx: number;
 };
+
+
+export const title = {
+  actions: "动作",
+  paths: "路径",
+  subtitles: "字幕",
+  frames: "",
+};