Bläddra i källkod

feat: 优化导入场景需求代码

bill 2 månader sedan
förälder
incheckning
eb65a375e9

+ 3 - 3
public/icons/men_p_l.svg

@@ -1,6 +1,6 @@
 <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
 <path d="M26.5 19.5C26.5 19.5 24.2143 12 19.5 9C14.7857 5.99996 7.5 6.5 7.5 6.5" stroke="black"/>
-<path fix="true" d="M31.5 19.5H26.5V24.5H31.5L31.5 19.5Z" stroke="black"/>
-<path fix="true" d="M5.5 19.5H0.5V24.5H5.5L5.5 19.5Z" stroke="black"/>
-<path fix="true" d="M7.5 6.5H5.5V20.5H7.5L7.5 6.5Z" stroke="black"/>
+<path fix-center="RB" fix="true" d="M31.5 19.5H26.5V24.5H31.5L31.5 19.5Z" stroke="black"/>
+<path d="M5.5 19.5H0.5V24.5H5.5L5.5 19.5Z" stroke="black"/>
+<path d="M7.5 6.5H5.5V20.5H7.5L7.5 6.5Z" stroke="black"/>
 </svg>

+ 4 - 4
src/core/components/icon/icon.vue

@@ -14,10 +14,7 @@
 import TempIcon from "./temp-icon.vue";
 import { IconData, getMouseStyle, defaultStyle, matResponse } from "./index.ts";
 import { useComponentStatus } from "@/core/hook/use-component.ts";
-import {
-  PropertyUpdate,
-  Operate,
-} from "../../html-mount/propertys/index.ts";
+import { PropertyUpdate, Operate } from "../../html-mount/propertys/index.ts";
 import { Transform } from "konva/lib/Util";
 import { useCustomTransformer } from "@/core/hook/use-transformer.ts";
 import { Group } from "konva/lib/Group";
@@ -68,6 +65,9 @@ const { shape, tData, data, operateMenus, describes } = useComponentStatus({
       },
       callback,
       openSnap: false,
+      transformerConfig: {
+        flipEnabled: true,
+      },
     });
   },
   defaultStyle,

+ 1 - 1
src/core/components/icon/temp-icon.vue

@@ -5,6 +5,7 @@
       <Path
         v-for="config in pathConfigs"
         :config="(config as any)"
+        :svg="svg"
         :mat="mat"
       />
       <!-- <v-path v-for="config in pathConfigs" :config="config" name="icon-path" /> -->
@@ -46,7 +47,6 @@ watch(
       svg.value = null;
       history.preventTrack(() => store.delItem("icon", props.data.id));
     } else {
-      console.log(content);
       svg.value = content;
     }
   },

+ 8 - 6
src/core/components/icon/temp-path.vue

@@ -1,7 +1,7 @@
 <template>
   <v-path :config="config" name="icon-path" />
 
-  <v-path :config="props.config" name="icon-path" />
+  <!-- <v-path :config="{ ...props.config, x: 32 }" name="icon-path" /> -->
 </template>
 
 <script lang="ts" setup>
@@ -10,6 +10,7 @@ import { Transform } from "konva/lib/Util";
 import { computed } from "vue";
 
 const props = defineProps<{
+  svg: ReturnType<typeof parseSvgContent>;
   config: ReturnType<typeof parseSvgContent>["paths"][0];
   mat: Transform;
 }>();
@@ -19,13 +20,14 @@ const config = computed(() => {
     return props.config;
   }
   // return props.config;
-  const inv = props.mat.copy().invert();
-  const invDec = inv.decompose();
+  const dec = props.mat.decompose();
+  // console.log(props.svg.width, props.svg.height, props.config.box);
   const config = {
     ...props.config,
-    ...invDec,
-    x: 0,
-    y: 0,
+    scaleX: 1 / dec.scaleX,
+    scaleY: 1 / dec.scaleY,
+    // x: props.config.box.x - props.svg.width / 2,
+    // y: props.config.box.y - props.svg.height / 2,
   };
   return config;
 });

+ 1 - 1
src/core/components/image/index.ts

@@ -62,7 +62,7 @@ export type ImageData = Partial<typeof defaultStyle> &
     };
     heightRaw?: number
     strokeWidth?: number;
-    cornerRadius: number;
+    cornerRadius?: number;
     url: string;
     mat: number[];
   };

+ 5 - 5
src/core/components/line/attach-view.ts

@@ -19,7 +19,7 @@ import { diffArrayChange, flatPositions, rangMod } from "@/utils/shared";
 import { globalWatch, installGlobalVar } from "@/core/hook/use-global-vars";
 import { useStore } from "@/core/store";
 import { IconData } from "../icon";
-import { computed, reactive, watch, watchEffect } from "vue";
+import { computed, reactive, Ref, watch, watchEffect } from "vue";
 import { Transform } from "konva/lib/Util";
 import { useTestPoints } from "@/core/hook/use-debugger";
 import { sortFn } from "@/core/store/store";
@@ -39,7 +39,7 @@ const getLineRect = (points: Pos[], strokeWidth: number) => {
 
 export const useGetExtendPolygon = installGlobalVar(() => {
   // const testPoints = useTestPoints();
-  return (data: LineData, line: LineData["lines"][0]) => {
+  return (data: LineData, line: LineData["lines"][0], useJoin = true) => {
     const getJoinInfo = (
       joinLine: LineData["lines"][0],
       joinPoints: Pos[],
@@ -138,13 +138,15 @@ export const useGetExtendPolygon = installGlobalVar(() => {
     const lineRect: Pos[] = getLineRect(linePoints, line.strokeWidth);
     const polygon = [...lineRect];
     const linev = lineVector(linePoints);
+    if (!useJoin) {
+      return polygon
+    }
 
     linePoints.forEach((point, ndx) => {
       const joinLines = getJoinLine(data, line, point.id);
       if (joinLines.length !== 1) return;
       const repsResult = getJoinInfo(joinLines[0], joinLines[0].points, ndx);
       if (!repsResult) return;
-
       for (const rep of repsResult) {
         const ndx = polygon.indexOf(lineRect[rep.rep]);
         polygon.splice(ndx, 1, ...rep.points);
@@ -187,12 +189,10 @@ export const useGetDiffPolygons = installGlobalVar(() => {
 
   const stopWatch = globalWatch(
     () => {
-      console.log(icons.value.length);
       return icons.value.map((item) => item.id);
     },
     (ids, oIds = []) => {
       const { added, deleted } = diffArrayChange(ids, oIds);
-      console.log(added, deleted);
       deleted.forEach((id) => {
         delete iconPolygons[id];
       });

+ 3 - 1
src/core/components/line/single-line.vue

@@ -111,7 +111,9 @@ const props = defineProps<{
 }>();
 
 const getExtendPolygon = useGetExtendPolygon();
-const polygon = computed(() => getExtendPolygon(props.data, props.line));
+const polygon = computed(() =>
+  getExtendPolygon(props.data, props.line, !showEditPoint.value)
+);
 const getDiffPolygons = useGetDiffPolygons();
 const diffPolygons = computed(() => getDiffPolygons(polygon.value));
 const showEditPoint = computed(

+ 1 - 7
src/example/constant.ts

@@ -143,7 +143,7 @@ export const getIconItem = (icon: string) => {
 };
 
 export const aiIconMap = {
-  SingleDoor: "men",
+  SingleDoor: "men_l",
   DoubleDoor: "shuangkaimen",
   SlideDoor: "yimen",
   Pass: "yakou",
@@ -168,9 +168,3 @@ export const styleIconMap = {
   "style-16": "danke_o",
   "style-17": "chelunhenji_o",
 };
-
-if (import.meta.env.DEV) {
-  iconGroups[0].children[0].children.push(
-    { wall: true, icon: "men_p_l", name: "百分比左开门" },
-  )
-}

+ 11 - 19
src/example/dialog/ai/ai.vue

@@ -33,15 +33,9 @@
 import { computed, ref, watch } from "vue";
 import VR from "../vr/vr.vue";
 import { ElRadioGroup, ElRadio, ElMessage, ElSelect, ElOption } from "element-plus";
-import {
-  compassGets,
-  getFloors,
-  lineGets,
-  Scene,
-  SCENE_TYPE,
-  taggingGets,
-} from "../../platform/platform-resource";
-import { AIExposeData } from ".";
+import { Scene, SCENE_TYPE } from "../../platform/platform-resource";
+import { getFloors } from "../../platform/resource-swkk";
+import { SelectSceneData } from ".";
 
 const scene = ref<Scene>();
 const options = {
@@ -67,9 +61,9 @@ watch(scene, () => {
   } else {
     syncTag.value = options[scene.value.type][0].value;
     if (scene.value.type === SCENE_TYPE.mesh) {
-      getFloors[scene.value.type](scene.value).then((data) => {
+      getFloors(scene.value).then((data) => {
         if (token === curToken) {
-          floors.value = data.floors;
+          floors.value = data;
           syncFloor.value = floors.value[0].name;
         }
       });
@@ -79,7 +73,7 @@ watch(scene, () => {
 
 defineExpose({
   disabled: computed(() => !scene.value),
-  submit: async (): Promise<AIExposeData> => {
+  submit: async (): Promise<SelectSceneData> => {
     if (!scene.value) {
       ElMessage.error("请选择同步场景");
       throw "请选择同步场景";
@@ -92,13 +86,11 @@ defineExpose({
       ElMessage.error("请选择楼层");
       throw "请选择楼层";
     }
-    if (!scene.value) return { taggings: [], floors: [] };
-    const [taggings, floorGeos, compass] = await Promise.all([
-      taggingGets[scene.value.type](scene.value, [syncTag.value]),
-      lineGets[scene.value.type](scene.value, syncFloor.value),
-      compassGets[scene.value.type](scene.value),
-    ]);
-    return { taggings, floors: floorGeos, compass };
+    return {
+      scene: scene.value,
+      floorName: syncFloor.value || floors.value[0].name,
+      syncs: [syncTag.value],
+    };
   },
 });
 </script>

+ 4 - 5
src/example/dialog/ai/index.ts

@@ -1,26 +1,25 @@
 import { markRaw, reactive } from "vue";
 import AI from "./ai.vue";
-import { SceneFloors, Tagings } from "../../platform/platform-resource";
+import { ResourceArgs } from "@/example/platform/platform-resource";
 
+export type SelectSceneData = Omit<ResourceArgs, 'scale'>
 type Props = {
   title: string;
   width: string;
   visiable: boolean;
   height?: string;
   content: any;
-  submit: (info: AIExposeData) => void;
+  submit: (args: SelectSceneData) => void;
   cancel: () => void;
 };
 
-export type AIExposeData = { taggings: Tagings, floors: SceneFloors, compass?: number }
-
 export const props = reactive({
   title: "全景VR",
   width: "500px",
 }) as Props;
 
 export const selectAI = () =>
-  new Promise<AIExposeData>((resolve, reject) => {
+  new Promise<SelectSceneData>((resolve, reject) => {
     props.content = markRaw(AI);
     props.title = "选择场景";
     props.visiable = true;

+ 174 - 309
src/example/platform/platform-draw.ts

@@ -1,147 +1,21 @@
 import { genBound, onlyId } from "@/utils/shared";
-import { AIExposeData } from "../dialog/ai";
 import { Draw } from "../components/container/use-draw";
-import { SceneFloor } from "./platform-resource";
+import { SceneResource, getResource } from "./platform-resource";
 import { getBaseItem } from "@/core/components/util";
 import { LineData, defaultStyle } from "@/core/components/line";
 import {
   getInitCtx,
   normalLineData,
 } from "@/core/components/line/attach-server";
-import {
-  getIconStyle,
-  defaultStyle as iconDefaultStyle,
-} from "@/core/components/icon";
+import { getIconStyle, IconData } from "@/core/components/icon";
 import { defaultStyle as textDefaultStyle } from "@/core/components/text";
 import { Transform } from "konva/lib/Util";
 import { ElMessage } from "element-plus";
 import { ImageData } from "@/core/components/image";
-import { Size } from "@/utils/math";
+import { Pos, Size } from "@/utils/math";
 import { watchEffect } from "vue";
 import { TextData } from "@/core/components/text";
-
-const scaleResource = (info: AIExposeData, scale: number) => {
-  console.log(info.taggings);
-  const floors = info.floors.map((item) => ({
-    ...item,
-    geos: item.geos.map((geo) =>
-      geo.map((p) => {
-        return { x: p.x * scale, y: p.y * scale, z: p.z && p.z * scale };
-      })
-    ),
-    box: item.box && {
-      ...item.box,
-      bound: {
-        x_min: item.box.bound.x_min * scale,
-        x_max: item.box.bound.x_max * scale,
-        y_min: item.box.bound.y_min * scale,
-        y_max: item.box.bound.y_max * scale,
-        z_min: item.box.bound.z_min * scale,
-        z_max: item.box.bound.z_max * scale,
-      },
-    },
-  }));
-
-  const taggings = info.taggings
-    .map((item) => {
-      if (!item.pixel) {
-        return {
-          ...item,
-          position: {
-            x: item.position.x * scale,
-            y: item.position.y * scale,
-            z: item.position.z && item.position.z * scale,
-          },
-          size: item.size
-            ? {
-                width: item.size!.width * scale,
-                height: item.size!.height * scale,
-              }
-            : undefined,
-        };
-      } else if (item.subgroup !== undefined) {
-        const floor = floors.find((floor) => floor.subgroup === item.subgroup);
-        if (!floor || !floor.box) return;
-        const w = floor.box.bound.x_max - floor.box.bound.x_min;
-        const h = floor.box.bound.y_max - floor.box.bound.y_min;
-        return {
-          ...item,
-          position: {
-            x: floor.box.bound.x_min + w * item.position.x,
-            y: floor.box.bound.y_min + h * item.position.y,
-            z: floor.box.bound.z_min + 0.001,
-          },
-          size: item.size
-            ? {
-                width: item.size!.width * w,
-                height: item.size!.height * h,
-              }
-            : undefined,
-        };
-      }
-    })
-    .filter((item) => !!item);
-
-  return {
-    ...info,
-    taggings,
-    floors,
-  };
-};
-
-const getResourceLayers = (data: AIExposeData) => {
-  return data.floors
-    .map((floor) => {
-      let box: SceneFloor["box"];
-      // if (true) {
-      if (!floor.box || !floor.box.bound.x_max || !floor.box.bound.z_max) {
-        const xs = floor.geos.flatMap((item) => item.map((p) => p.x));
-        const ys = floor.geos.flatMap((item) => item.map((p) => p.y));
-        const zs = floor.geos.flatMap((item) => item.map((p) => p.z));
-        box = {
-          bound: {
-            x_min: Math.min(...xs),
-            x_max: Math.max(...xs),
-            y_min: Math.min(...ys),
-            y_max: Math.max(...ys),
-            z_min: Math.min(...zs),
-            z_max: Math.max(...zs),
-          },
-          scale: 1,
-          rotate: 0,
-        };
-
-        if (isNaN(box.bound.z_min) || isNaN(box.bound.z_max)) {
-          box.bound.z_min = -999999;
-          box.bound.z_max = 999999;
-        }
-        floor.box = box;
-      } else {
-        box = floor.box;
-      }
-      box.bound.z_min = -999999;
-      box.bound.z_max = 999999;
-
-      return {
-        ...floor,
-        box,
-        taggings: data.taggings
-          .filter((item) => {
-            return (
-              item.position.z === undefined ||
-              (item.position.z >= box.bound.z_min &&
-                item.position.z <= box.bound.z_max)
-            );
-          })
-          .map((item) => ({
-            ...item,
-            position: { x: item.position.x, y: item.position.y },
-          })),
-        geos: floor.geos.map((item) => item.map((p) => ({ x: p.x, y: p.y }))),
-      };
-    })
-    .filter((floor) => floor.taggings.length > 0 || floor.geos.length > 0);
-};
+import { SelectSceneData } from "../dialog/ai";
 
 const getSizePlaceBoxImage = ({ width, height }: Size) => {
   const borderWidth = 10;
@@ -169,32 +43,25 @@ const getSizePlaceBoxImage = ({ width, height }: Size) => {
   return canvas.toDataURL(); // 返回图片的Data URL字符串
 };
 
-const drawLayerResource = async (
-  layerResource: ReturnType<typeof getResourceLayers>[number],
-  draw: Draw
-) => {
-  const bound = genBound();
-  const images: any[] = [];
-  const texts: TextData[] = [];
-  const createTime = Date.now();
-
+const getCoverShapes = (cover: SceneResource["cover"]) => {
   let geo: LineData = {
     ...getBaseItem(),
     lines: [],
     points: [],
     polygon: [],
-    zIndex: 0,
-    createTime: createTime,
+    zIndex: -1,
+    createTime: Date.now(),
   };
 
-  const geoCtx = getInitCtx();
-
-  layerResource.geos.forEach((item) => {
-    bound.update(item);
-    if (item.length < 1) return;
+  if (!cover) {
+    return { geo };
+  }
 
+  const geoCtx = getInitCtx();
+  cover.geos.forEach((item) => {
     let a: string = onlyId(),
       b: string;
+
     let i = 0;
     for (i = 0; i < item.length - 1; i++) {
       b = onlyId();
@@ -213,214 +80,212 @@ const drawLayerResource = async (
   });
   geo = normalLineData(geo, geoCtx);
 
-  let thumb: ImageData;
-  if (layerResource.thumb) {
-    const box = layerResource.box;
-    const width = box.bound.x_max - box.bound.x_min;
-    const height = box.bound.y_max - box.bound.y_min;
-    const mat = new Transform().translate(
-      box.bound.x_min + width / 2,
-      box.bound.y_min + height / 2
-    );
-    // .rotate(-MathUtils.degToRad(floor.box.rotate));
-    thumb = {
+  if (!cover.thumb) return { geo };
+
+  const width = cover.bound.x_max - cover.bound.x_min;
+  const height = cover.bound.y_max - cover.bound.y_min;
+  const mat = new Transform().translate(
+    cover.bound.x_min + width / 2,
+    cover.bound.y_min + height / 2
+  );
+
+  const thumb: ImageData = {
+    ...getBaseItem(),
+    createTime: geo.createTime,
+    url: cover.thumb,
+    mat: mat.m,
+    width,
+    height,
+    cornerRadius: 0,
+    zIndex: -2,
+  };
+
+  return { geo, thumb };
+};
+
+const getTaggingShapes = async (taggings: SceneResource["taggings"]) => {
+  const icons: IconData[] = [];
+  const images: ImageData[] = [];
+  const texts: TextData[] = [];
+  const now = Date.now();
+  const reqs: Promise<any>[] = [];
+
+  for (let ndx = 0; ndx < taggings.length; ndx++) {
+    const item = taggings[ndx];
+    const mat = new Transform(item.mat);
+
+    if (!item.mat && item.position) {
+      mat.translate(item.position.x, item.position.y);
+      item.rotate && mat.rotate(item.rotate);
+    }
+
+    if (item.isText) {
+      texts.push({
+        ...getBaseItem(),
+        ...textDefaultStyle,
+        content: item.url,
+        zIndex: 1,
+        mat: mat.m,
+      });
+      continue;
+    }
+    const shape = {
       ...getBaseItem(),
-      createTime: createTime - 1,
-      url: layerResource.thumb,
+      ...(item.size || { width: 100, height: 100 }),
+      name: item.name,
+      createTime: now + ndx,
+      url: item.url,
       mat: mat.m,
-      width,
-      height,
-      cornerRadius: 0,
-      zIndex: -1,
+      zIndex: 1,
     };
-    images.push(thumb);
-  }
 
-  let offset = { x: 0, y: 0 };
+    if (!item.url.includes(".svg")) {
+      images.push(shape);
+      continue;
+    }
+
+    reqs.push(
+      getIconStyle(item.url, shape.width, shape.height, item.fixed)
+        .then((style) => {
+          icons.push({ ...shape, ...style });
+        })
+        .catch(() => {})
+    );
+  }
+  await Promise.all(reqs)
+  return {
+    texts,
+    images,
+    icons,
+  };
+};
 
-  let sGeo = draw.store.getTypeItems("line")[0];
-  if (sGeo?.itemName) {
+const getDrawResourceOffset = (
+  draw: Draw,
+  bound: ReturnType<typeof genBound>,
+  thumb?: ImageData
+) =>
+  new Promise<Pos>((resolve, reject) => {
     ElMessage.warning("请在画图面板中选择放置位置,按右键取消");
-    await new Promise<void>((resolve, reject) => {
-      const data = thumb ? thumb : { url: getSizePlaceBoxImage(bound.get()!) };
-      const key = "place-plaform";
-      draw.enterDrawShape("image", { ...data, key }, true);
-      const stopWatch = watchEffect(() => {
-        if (!draw.drawing) {
-          stopWatch();
-          const item = draw.store.items.find(
-            (item) => item.key === key
-          ) as ImageData;
-
-          if (!item) {
-            reject("用户已取消");
-          } else {
-            draw.store.delItem("image", item.id);
-            offset.x = item.mat[4];
-            offset.y = item.mat[5];
-            resolve();
-          }
-        }
-      });
+
+    const data = thumb ? thumb : { url: getSizePlaceBoxImage(bound.get()!) };
+    const key = "place-plaform";
+    draw.enterDrawShape("image", { ...data, key }, true);
+
+    const stopWatch = watchEffect(() => {
+      if (draw.drawing) return;
+
+      stopWatch();
+      const item = draw.store.items.find(
+        (item) => item.key === key
+      ) as ImageData;
+
+      if (!item) {
+        reject("用户已取消");
+      } else {
+        draw.store.delItem("image", item.id);
+        resolve({
+          x: item.mat[4],
+          y: item.mat[5],
+        });
+      }
     });
-  } else if (sGeo) {
-    sGeo.itemName = "1";
+  });
+
+const drawSceneResource = async (resource: SceneResource, draw: Draw) => {
+  const { geo, thumb } = getCoverShapes(resource.cover);
+  const geoKey = "scene-geo-resource";
+
+  let offset = { x: 0, y: 0 };
+  let oldGeo = draw.store.getTypeItems("line")[0];
+  if (oldGeo?.itemName === geoKey) {
+    const bound = genBound();
+    geo.points.forEach((p) => bound.update(p));
+    offset = await getDrawResourceOffset(draw, bound, thumb);
+  } else if (oldGeo) {
+    oldGeo.itemName = geoKey;
   } else {
-    geo.itemName = "1";
+    geo.itemName = geoKey;
   }
+
+  const bound = genBound();
   geo.points.forEach((p) => {
     p.x += offset.x;
     p.y += offset.y;
+    bound.update(p);
+  });
+
+  const { icons, images, texts } = await getTaggingShapes(resource.taggings);
+  const tagShapes = [...icons, ...images, ...texts, thumb];
+  tagShapes.forEach((shape) => {
+    if (shape) {
+      shape.mat[4] += offset.x;
+      shape.mat[5] += offset.y;
+      bound.update({ x: shape.mat[4], y: shape.mat[5] });
+    }
   });
 
-  if (sGeo) {
-    sGeo.points = sGeo.points.concat(geo.points);
-    sGeo.lines = sGeo.lines.concat(geo.lines);
-    sGeo.polygon = sGeo.polygon.concat(geo.polygon);
-    geo = sGeo;
+  if (oldGeo) {
+    oldGeo.points = oldGeo.points.concat(geo.points);
+    oldGeo.lines = oldGeo.lines.concat(geo.lines);
+    oldGeo.polygon = oldGeo.polygon.concat(geo.polygon);
     draw.store.setItem("line", { id: geo.id, value: geo });
   } else {
     draw.store.addItem("line", geo);
   }
-  if (thumb!) {
-    thumb.mat[4] = offset.x;
-    thumb.mat[5] = offset.y;
-  }
-  for (let ndx = 0; ndx < layerResource.taggings.length; ndx++) {
-    const item = layerResource.taggings[ndx];
-    bound.update(item.position);
-    const tf = new Transform().translate(
-      item.position.x + offset.x,
-      item.position.y + offset.y
-    );
-    if (item.rotate) {
-      tf.rotate(item.rotate);
-    }
-    if (item.isText) {
-      texts.push({
-        ...getBaseItem(),
-        ...textDefaultStyle,
-        content: item.url,
-        zIndex: 1,
-        mat: tf.m,
-      });
-    } else {
-      let attach: any = {
-        width: item.size ? item.size.width : 100,
-        height: item.size ? item.size.height : 100,
-      };
-      if (item.url.includes(".svg")) {
-        attach = {
-          ...iconDefaultStyle,
-          stroke: "#000000",
-          fill: null,
-        };
-        try {
-          attach = await getIconStyle(
-            item.url,
-            item.size?.width,
-            item.size?.height,
-            item.fixed
-          );
-        } catch {}
-      }
-
-      images.push({
-        ...getBaseItem(),
-        name: item.name,
-        createTime: createTime + layerResource.geos.length + ndx,
-        url: item.url,
-        mat: tf.m,
-        cornerRadius: 0,
-        zIndex: 1,
-        ...attach,
-      });
-    }
-  }
 
-  draw.store.addItems(
-    "icon",
-    images.filter((item) => item.url.includes(".svg"))
-  );
+  console.log(icons, texts, images)
+  draw.store.addItems("icon", icons);
   draw.store.addItems("text", texts);
+  draw.store.addItems("image", images);
+  if (thumb) {
+    draw.store.addItem("image", thumb);
+  }
 
-  draw.store.addItems(
-    "image",
-    images.filter((item) => !item.url.includes(".svg"))
-  );
-
-  // draw.store.addItem("group", {
-  //   ...getBaseItem(),
-  //   ids: [...images, geo.id].map((item) => item.id),
-  // });
-
-  const box = bound.get()!;
-  return {
-    ...box,
-    x: box.x + offset.x,
-    y: box.y + offset.y,
-    maxX: box.maxX + offset.x,
-    maxY: box.maxY + offset.y,
-    center: {
-      x: box.center.x + offset.x,
-      y: box.center.y + offset.y,
-    },
-  };
+  return bound.get()!;
 };
 
-export const drawPlatformResource = async (data: AIExposeData, draw: Draw) => {
-  // 默认为米,为了方便绘图 一米转为100
-  const layers = getResourceLayers(scaleResource(data, 100));
-  const layerBounds: ReturnType<typeof drawLayerResource> extends Promise<
-    infer T
-  >
-    ? T[]
-    : number[] = [];
+export const drawPlatformResource = async (
+  sceneData: SelectSceneData,
+  draw: Draw
+) => {
+  // 默认为米,转为厘米
+  const resource = await getResource({ ...sceneData, scale: 100 });
+  let bound = null as ReturnType<ReturnType<typeof genBound>["get"]>;
 
+  console.log(resource)
   await draw.history.onceTrack(async () => {
-    // draw.store.setConfig({ proportion: { scale: 10, unit: 'mm' } });
     draw.store.setConfig({ proportion: { scale: 10, unit: "mm" } });
-    for (const layer of layers) {
-      // if (!draw.store.layers.includes(layer.name)) {
-      //   draw.store.addLayer(layer.name);
-      // }
-      // draw.store.setCurrentLayer(layer.name);
-      layerBounds.push(await drawLayerResource(layer, draw)!);
-    }
-    if (typeof data.compass === "number") {
+    bound = await drawSceneResource(resource, draw);
+    if (typeof resource.compass === "number") {
       draw.store.setConfig({
         compass: {
-          rotation: data.compass,
+          rotation: resource.compass,
           url: draw.store.config.compass.url,
         },
       });
     }
   });
 
-  if (layerBounds.length === 0 || !draw.viewer.size) return;
-
-  const flyBound = layerBounds[layerBounds.length - 1]!;
+  if (!draw.viewer.size || !bound) return;
   const size = draw.viewer.size;
 
-  if (flyBound.width < 10 || flyBound.height < 10) {
+  if (bound.width < 10 || bound.height < 10) {
     draw.viewer.setViewMat([
       1,
       0,
       0,
       1,
-      flyBound.center.x + size.width / 2,
-      flyBound.center.y + size.height / 2,
+      bound.center.x + size.width / 2,
+      bound.center.y + size.height / 2,
     ]);
   } else {
-    const viewWidth = Math.max(flyBound.width, size.width);
-    const viewHeight = Math.max(flyBound.height, size.height);
+    const viewWidth = Math.max(bound.width, size.width);
+    const viewHeight = Math.max(bound.height, size.height);
     const padding = Math.max(
-      Math.min(
-        (viewWidth - flyBound.width) / 2,
-        (viewHeight - flyBound.height) / 2
-      ),
+      Math.min((viewWidth - bound.width) / 2, (viewHeight - bound.height) / 2),
       40
     );
-    draw.viewer.setBound({ targetBound: flyBound, padding });
+    draw.viewer.setBound({ targetBound: bound, padding });
   }
 };

+ 37 - 407
src/example/platform/platform-resource.ts

@@ -1,9 +1,7 @@
 import { Pos, Size } from "@/utils/math";
-import { extractConnectedSegments } from "@/utils/polygon";
-import { validNum } from "@/utils/shared";
-import { aiIconMap, getIconItem, iconGroups, styleIconMap } from "../constant";
-import { Euler, MathUtils, Object3D, Quaternion, Vector3 } from "three";
-import { getSvgContent, parseSvgContent } from "@/utils/resource";
+import { getResource as getCloudResource } from "./resource-cloud";
+import { getResource as getSWKKResource } from "./resource-swkk";
+import { getResource as getFuseResource } from "./resource-fuse";
 
 export enum SCENE_TYPE {
   fuse = "fuse",
@@ -17,38 +15,43 @@ export type Scene = {
   id: string;
 };
 
-export type SceneFloor = {
+export type CoverLine = {
   name: string;
   subgroup?: number;
-  geos: (Pos & { z: number })[][];
   thumb?: string;
-  box?: {
-    bound: {
-      x_min: number;
-      x_max: number;
-      y_min: number;
-      y_max: number;
-      z_min: number;
-      z_max: number;
-    };
-    rotate: number;
-    scale: number;
+  bound: {
+    y_min: number;
+    y_max: number;
+    x_min: number;
+    x_max: number;
+    z_min?: number;
+    z_max?: number;
   };
-  compass?: number;
+  geos: Pos[][];
 };
 
-export type SceneFloors = SceneFloor[];
-
-export type Taging = {
+export type TaggingInfo = {
   url: string;
-  position: Pos & { z: number };
-  size?: Size;
-  fixed?: boolean;
+  position?: Pos & { z?: number };
+  mat?: number[];
   rotate?: number;
+  size?: Size;
   name?: string;
-  pixel?: boolean;
   isText?: boolean;
-  subgroup?: string;
+  fixed?: boolean;
+};
+
+export type SceneResource = {
+  taggings: TaggingInfo[];
+  cover?: CoverLine;
+  compass?: number;
+};
+
+export type ResourceArgs = {
+  scene: Scene;
+  floorName: string;
+  syncs: string[];
+  scale: number;
 };
 
 export const SceneTypeNames = {
@@ -82,384 +85,11 @@ export const getSceneApi = async (type: string | undefined, url: string) => {
   return uri;
 };
 
-export type Tagings = Taging[];
-
-const getBillYaw = (bill: any) => {
-  function isLieDown(quaternion: Quaternion) {
-    let direction = new Vector3(0, 0, -1).applyQuaternion(quaternion);
-    return Math.abs(direction.y) > 0.9;
-  }
-
-  let billboard = new Object3D();
-  let plane = new Object3D();
-  billboard.add(plane);
-  billboard.quaternion
-    .copy({ x: bill.qua[0], y: bill.qua[1], z: bill.qua[2], w: bill.qua[3] })
-    .normalize(); //qua数据里的
-  plane.quaternion.setFromAxisAngle(
-    new Vector3(0, 0, 1),
-    MathUtils.degToRad(-bill.faceAngle)
-  );
-
-  const up = new Vector3(0, 1, 0); //plane的上方指示着方向
-  const right = new Vector3(1, 0, 0); //令躺倒时的旋转轴
-
-  let qua = plane.getWorldQuaternion(new Quaternion());
-  const ld = isLieDown(billboard.quaternion);
-  if (!ld) {
-    //使朝其后方躺倒后再求angle
-    let rotAxis = right.clone().applyQuaternion(qua); //旋转轴
-    (rotAxis.y = 0), rotAxis.normalize();
-    let rot = new Quaternion().setFromAxisAngle(rotAxis, Math.PI / 2);
-    qua.premultiply(rot);
-  }
-  let dir = up.clone().applyQuaternion(qua); //在墙面朝x时正反得到的一样,很奇怪,所以得到的会反向
-  let yaw = Math.atan2(-dir.z, dir.x) - Math.PI / 2;
-  return -yaw;
-};
-
-export const compassGets = {
-  [SCENE_TYPE.fuse]: () => void 0,
-  [SCENE_TYPE.cloud]: () => void 0,
-  [SCENE_TYPE.mesh]: async (scene: Scene) => {
-    const prev = `/scene_view_data/${scene.m}`;
-    const config = await getSceneApi("oss", `${prev}/data/scene.json`)
-      .then((url) => fetch(url))
-      .then((res) => res.json())
-      .catch(() => ({ version: 0, billboards: 0, tags: 0, orientation: 0 }));
-
-    const floorpanCompass = await getSceneApi(
-      "oss",
-      `${prev}/user/floorplan.json?_=${config.version}`
-    )
-      .then((url) => fetch(url))
-      .then((res) => res.json())
-      .then((data) => data.compass)
-      .catch(() => null);
-
-    return typeof floorpanCompass === "number"
-      ? floorpanCompass
-      : Number(config.orientation || 0);
-  },
-};
-
-export const taggingGets = {
-  [SCENE_TYPE.fuse]: async (scene: Scene, options: string[]) => {
-    if (!options.includes("hot")) return [];
-
-    const reqOpts = { headers: { share: "1" } };
-    const icons = await getSceneApi(
-      scene.type,
-      `/fusion/edit/hotIcon/list?caseId=${scene.m}`
-    )
-      .then((url) => fetch(url, reqOpts))
-      .then((res) => res.json())
-      .then((res) => res.data)
-      .catch(() => []);
-
-    const tagTypes: any[] = await getSceneApi(
-      scene.type,
-      `/fusion/caseTag/allList?caseId=${scene.m}`
-    )
-      .then((url) => fetch(url, reqOpts))
-      .then((res) => res.json())
-      .then((res) => res.data)
-      .catch(() => []);
-
-    const tags: Tagings = [];
-    const reqs = tagTypes.map((type) =>
-      getSceneApi(
-        scene.type,
-        `/fusion/caseTagPoint/allList?tagId=${type.tagId}`
-      )
-        .then((url) => fetch(url, reqOpts))
-        .then((res) => res.json())
-        .then((res) => res.data)
-        .then((items) => {
-          items.forEach((item: any) => {
-            tags.push({
-              url: icons.find((icon: any) => icon.iconId === type.hotIconId)
-                ?.iconUrl,
-              position: JSON.parse(item.tagPoint),
-            });
-          });
-        })
-        .catch(() => [])
-    );
-    await Promise.all(reqs);
-    return tags;
-  },
-
-  [SCENE_TYPE.cloud]: async (scene: Scene, options: string[]) => {
-    if (!options.includes("hot")) return [];
-
-    const tags: Tagings = await getSceneApi(
-      scene.type,
-      `/laser/poi/${scene.m}/list`
-    )
-      .then((url) => fetch(url))
-      .then((res) => res.json())
-      .then((res) => res.data.list)
-      .then((pois) =>
-        pois.map((poi: any) => ({
-          url: poi.hotStyleAtom.icon,
-          position: poi.dataset_location,
-        }))
-      )
-      .catch(() => []);
-    return tags;
-  },
-  [SCENE_TYPE.mesh]: async (scene: Scene, options: string[]) => {
-    const tags: Tagings = [];
-    const prev = `/scene_view_data/${scene.m}`;
-    const config = await getSceneApi("oss", `${prev}/data/scene.json`)
-      .then((url) => fetch(url))
-      .then((res) => res.json())
-      .catch(() => ({ version: 0, billboards: 0, tags: 0, orientation: 0 }));
-
-    if (options.includes("hot") && config.tags) {
-      const medias = await getSceneApi(
-        "oss",
-        `${prev}/user/hot.json?_=${config.version}`
-      )
-        .then((url) => fetch(url))
-        .then((res) => res.json())
-        .catch(() => []);
-
-      const reqs = medias.map((media: any) => {
-        if (!validNum(media.position.x) || !validNum(media.position.y)) return;
-        return getSceneApi("oss", `${prev}/user/${media.icon}`)
-          .then((url) => {
-            tags.push({ url, position: media.position });
-          })
-          .catch(() => {});
-      });
-
-      await Promise.all(reqs);
-    }
-
-    if (options.includes("signage") && config.billboards) {
-      const signages = await getSceneApi(
-        "oss",
-        `${prev}/user/billboards.json?_=${config.version}`
-      )
-        .then((url) => fetch(url))
-        .then((res) => res.json())
-        .catch(() => []);
-
-      const reqs = signages.map((signage: any) => {
-        if (!validNum(signage.pos[0]) || !validNum(signage.pos[2])) return;
-        const styleMap = (styleIconMap as any)[signage.icon];
-        const getIcon =
-          signage.icon.indexOf("style-") === 0
-            ? styleMap
-              ? Promise.resolve(`./icons/${styleMap}.svg`)
-              : getSceneApi("./", `./styles/${signage.icon}.svg`).catch((e) => {
-                  console.error(e);
-                  return getSceneApi(
-                    "ossRoot",
-                    `/sdk/images/billboard/${signage.icon}.png`
-                  );
-                })
-            : getSceneApi("oss", `${prev}/user/${signage.icon}`);
-
-        const yRotate = getBillYaw(signage);
-
-        console.log(signage);
-        return getIcon
-          .then((url) => {
-            const name = (styleMap && getIconItem(styleMap)?.name) || ''
-
-            tags.push({
-              url,
-              name,
-              position: {
-                x: signage.pos[0],
-                y: signage.pos[2],
-                z:
-                  signage.pos[1] < 0
-                    ? Math.ceil(signage.pos[1] * 10) / 10
-                    : Math.floor(signage.pos[1] * 10) / 10,
-              },
-              rotate: yRotate,
-              size: {
-                // width: signage.width * (signage.scaleRatio / 100),
-                // height: signage.height * (signage.scaleRatio / 100),
-                width: signage.width,
-                height: signage.height,
-              },
-            });
-          })
-          .catch(() => {});
-      });
-
-      await Promise.all(reqs);
-    }
-
-    await getSceneApi(
-      "oss",
-      `${prev}/data/floorplan/ai-entire.json?_=${config.version}`
-    )
-      .then((url) => fetch(url))
-      .then((res) => res.json())
-      .then((datas) => {
-        const loadPromises: Promise<void>[] = [];
-        for (const data of datas) {
-          const reg = data.imagePath.match(/floor_(\d)\.png/);
-          const subgroup = reg ? Number(reg[1]) : undefined;
-          for (const shape of data.shapes) {
-            const pos = {
-              x: (shape.bbox[0] + shape.bbox[2]) / 2 / data.imageWidth,
-              y: (shape.bbox[1] + shape.bbox[3]) / 2 / data.imageHeight,
-              z: undefined,
-            };
-            const size = {
-              width: (shape.bbox[2] - shape.bbox[0]) / data.imageWidth,
-              height: (shape.bbox[3] - shape.bbox[1]) / data.imageHeight,
-            };
-            const icon =
-              shape.category in aiIconMap
-                ? (aiIconMap as any)[shape.category]
-                : shape.category;
-            
-            
-            const itemIcon = getIconItem(icon)
-            const name = itemIcon?.name
-            const isWall = itemIcon && "wall" in itemIcon ? itemIcon.wall : false;
-            const isTag = icon === "Tag";
-            if (name || isTag) {
-              const item = {
-                isText: isTag,
-                position: pos,
-                rotate: 0,
-                url: isTag
-                  ? shape.name
-                  : `./icons/${icon ? icon : "circle"}.svg`,
-                name,
-                pixel: true,
-                fixed: false,
-                size,
-                subgroup,
-              };
-              if (isWall) {
-                const wh = Math.max(item.size.width, item.size.height);
-                loadPromises.push(
-                  getSvgContent(item.url)
-                    .then((svgContent) => {
-                      const content = parseSvgContent(svgContent);
-                      item.fixed = true;
-                      // item.size.width = item.size.height = wh
-
-                      if (wh === item.size.width) {
-                        item.size.width = wh;
-                        item.size.height =
-                          (data.imageWidth / data.imageHeight) *
-                          ((content.height / content.width) * wh);
-                        item.position.y -= item.size.height / 2;
-                      } else {
-                        item.size.width = wh;
-                        item.size.height =
-                          (data.imageWidth / data.imageHeight) *
-                          ((content.height / content.width) * wh);
-                        item.position.x += item.size.height / 2;
-                        item.rotate = Math.PI / 2;
-                      }
-                      tags.push(item as any);
-                    })
-                    .catch(() => {})
-                );
-              } else {
-                tags.push(item as any);
-              }
-            } else {
-              console.error("找不到ai家具", icon, name, pos);
-            }
-          }
-        }
-        return Promise.all(loadPromises);
-      })
-      .catch((e) => {
-        console.error(e);
-      });
-
-    console.log("tags", tags);
-    return tags;
-  },
-};
-
-export const getFloors = {
-  [SCENE_TYPE.mesh]: async (scene: Scene) => {
-    return getSceneApi(
-      "oss",
-      `/scene_view_data/${scene.m}/data/floorplan_cad.json?_=${Date.now()}`
-    )
-      .then((url) => fetch(url))
-      .then((res) => res.json())
-      .catch(() => ({ floors: [] }));
-  },
-};
-
-export const lineGets = {
-  [SCENE_TYPE.fuse]: async (scene: Scene) => {
-    const tags = await taggingGets[SCENE_TYPE.fuse](scene, ["hot"]);
-    return { name: "1楼", geos: [tags.map((item) => item.position)] };
-  },
-  [SCENE_TYPE.mesh]: async (scene: Scene, floorName?: string) => {
-    const prev = `/scene_view_data/${scene.m}/data/`;
-    const [{ floors }, bounds] = await Promise.all([
-      getFloors[SCENE_TYPE.mesh](scene),
-      getSceneApi("oss", `${prev}floorplan/info.json`)
-        .then((url) => fetch(url))
-        .then((res) => res.json())
-        .then((data) => data.floors)
-        .catch(() => []),
-    ]);
-
-    const data: any = [];
-
-    const reqs = floors
-      .filter((item: any) => !floorName || item.name === floorName)
-      .map((floor: any) => {
-        const bound = {
-          ...(floor.cadInfo.cadBoundingBox || {}),
-          ...(bounds.find((i: any) => i.subgroup === floor.subgroup)?.bound ||
-            {}),
-        };
-        const item: any = {
-          name: floor.name,
-          subgroup: floor.subgroup,
-          thumb: "",
-          box: {
-            bound: {
-              ...bound,
-              y_min: -bound.y_max,
-              y_max: -bound.y_min,
-              z_max: bound.z_max ? Number(bound.z_max) : bound.z_max,
-              z_min: bound.z_min ? Number(bound.z_min) : bound.z_min,
-            },
-            rotate: floor.cadInfo.res,
-            scale: floor.cadInfo.currentScale,
-          },
-          geos: extractConnectedSegments(floor.segment).map((geo) => {
-            return geo.map((id) => {
-              const p = floor["vertex-xy"].find((item: any) => item.id === id);
-              return { x: p.x, y: -p.y } as Pos;
-            });
-          }),
-        };
-        data.push(item);
-        return getSceneApi(
-          "oss",
-          `${prev}floorplan/floor_${floor.subgroup}.png`
-        )
-          .then((url) => (item.thumb = url))
-          .catch(() => (item.thumb = ""));
-      });
-    await Promise.all(reqs);
-    return data;
-  },
-  [SCENE_TYPE.cloud]: async (scene: Scene) => {
-    const tags = await taggingGets[SCENE_TYPE.cloud](scene, ["hot"]);
-    return { name: "1楼", geos: [tags.map((item) => item.position)] };
-  },
+export const getResource = (args: ResourceArgs) => {
+  const getResourceMap = {
+    [SCENE_TYPE["mesh"]]: getSWKKResource,
+    [SCENE_TYPE["fuse"]]: getFuseResource,
+    [SCENE_TYPE["cloud"]]: getCloudResource,
+  };
+  return getResourceMap[args.scene.type](args);
 };

+ 54 - 0
src/example/platform/resource-cloud.ts

@@ -0,0 +1,54 @@
+import { genCache } from "@/utils/shared";
+import {
+  getSceneApi,
+  ResourceArgs,
+  Scene,
+  SceneResource,
+  TaggingInfo,
+} from "./platform-resource";
+
+const fetchResource = genCache(
+  (scene: Scene) => scene.m,
+  async (scene: Scene) => {
+    const reqOpts = { headers: { share: "1" } };
+    const get = (url: string, def?: any) =>
+      getSceneApi(scene.type, url)
+        .then((url) => fetch(url, reqOpts))
+        .then((res) => res.json())
+        .then((res) => res.data)
+        .catch(() => def);
+
+    const tags = await get(`/laser/poi/${scene.m}/list`, { list: [] }).then(
+      (data) => data.list
+    );
+
+    return {
+      tags,
+    };
+  },
+  150000
+);
+
+export const getHotTaggingInfos = async (scene: Scene) => {
+  const { tags } = await fetchResource(scene);
+  return tags.map((item: any) => ({
+    url: item.hotStyleAtom.icon,
+    position: item.dataset_location,
+  })) as TaggingInfo[];
+};
+
+export const getResource = async ({ scene, scale }: ResourceArgs) => {
+  const taggings = await getHotTaggingInfos(scene);
+  taggings.forEach((item) => {
+    item.position!.x *= scale;
+    item.position!.y *= scale;
+    if (item.size) {
+      item.size.width *= scale;
+      item.size.height *= scale;
+    }
+  });
+
+  return {
+    taggings: taggings,
+  } as SceneResource;
+};

+ 69 - 0
src/example/platform/resource-fuse.ts

@@ -0,0 +1,69 @@
+import { genCache } from "@/utils/shared";
+import { getSceneApi, ResourceArgs, Scene, SceneResource, TaggingInfo } from "./platform-resource";
+
+const fetchResource = genCache(
+  (scene: Scene) => scene.m,
+  async (scene: Scene) => {
+    const reqOpts = { headers: { share: "1" } };
+    const get = (url: string, def?: any) =>
+      getSceneApi(scene.type, url)
+        .then((url) => fetch(url, reqOpts))
+        .then((res) => res.json())
+        .then((res) => res.data)
+        .catch(() => def);
+
+    const [tagTypes, icons] = await Promise.all([
+      get(`/fusion/caseTag/allList?caseId=${scene.m}`, []),
+      get(`/fusion/edit/hotIcon/list?caseId=${scene.m}`, []),
+    ]);
+    const tags: any[] = [];
+
+    await Promise.all(
+      tagTypes.map((type: any) =>
+        get(`/fusion/caseTagPoint/allList?tagId=${type.tagId}`)
+      )
+    );
+
+    return {
+      tagTypes,
+      tags,
+      icons,
+    };
+  },
+  150000
+);
+
+export const getHotTaggingInfos = async (
+  scene: Scene
+) => {
+  const { tags, icons, tagTypes } = await fetchResource(scene);
+  return tags
+    .map((item: any) => {
+      const type = tagTypes.find((type: any) => type.tagId === item.tagId);
+      const icon =
+        type && icons.find((icon: any) => icon.iconId === type.hotIconId);
+      if (icon) {
+        return {
+          url: icon.iconUrl,
+          position: JSON.parse(item.tagPoint),
+        };
+      }
+    })
+    .filter((info) => !!info) as TaggingInfo[];
+};
+
+export const getResource = async ({ scene, scale }: ResourceArgs) => {
+  const taggings = await getHotTaggingInfos(scene)
+  taggings.forEach((item) => {
+    item.position!.x *= scale;
+    item.position!.y *= scale;
+    if (item.size) {
+      item.size.width *= scale;
+      item.size.height *= scale;
+    }
+  });
+
+  return {
+    taggings: taggings,
+  } as SceneResource;
+};

+ 437 - 0
src/example/platform/resource-swkk.ts

@@ -0,0 +1,437 @@
+import { genCache, validNum } from "@/utils/shared";
+import {
+  CoverLine,
+  getSceneApi,
+  ResourceArgs,
+  Scene,
+  SceneResource,
+  TaggingInfo,
+} from "./platform-resource";
+import {
+  lineCenter,
+  lineLen,
+  lineVector,
+  Pos,
+  vector2IncludedAngle,
+} from "@/utils/math";
+import { aiIconMap, getIconItem, styleIconMap } from "../constant";
+import { MathUtils, Object3D, Quaternion, Vector2, Vector3 } from "three";
+import { extractConnectedSegments } from "@/utils/polygon";
+import { Transform } from "konva/lib/Util";
+import { getSvgContent, parseSvgContent } from "@/utils/resource";
+
+const fetchResource = genCache(
+  (scene: Scene) => scene.m,
+  async (scene: Scene) => {
+    const prev = `/scene_view_data/${scene.m}`;
+    let version = Date.now();
+    const get = (url: string, def?: any) =>
+      getSceneApi("oss", `${prev}${url}?_=${version}`)
+        .then((url) => fetch(url))
+        .then((res) => res.json())
+        .catch(() => def);
+
+    const config = await get(`/data/scene.json`, {
+      version: 0,
+      billboards: 0,
+      tags: 0,
+      orientation: 0,
+    });
+    version = config.version;
+
+    const [userFloorpan, floorplan, hots, billboards, ais, cadInfo, cad] =
+      await Promise.all([
+        get("/user/floorplan.json"),
+        get("/data/floorplan.json", { floors: [] }),
+        get("/user/hot.json", []),
+        get("/user/billboards.json", []),
+        get("/data/floorplan/ai.json", []),
+        get("/data/floorplan/info.json", { floors: [] }),
+        get("/data/floorplan_cad.json", { floors: [] }),
+      ]);
+
+    return {
+      userFloorpan,
+      hots,
+      billboards,
+      ais,
+      floorplan,
+      cad,
+      cadInfo,
+      config,
+    };
+  },
+  150000
+);
+
+export const getFloors = async (scene: Scene) => {
+  const { floorplan } = await fetchResource(scene);
+  return floorplan.floors;
+};
+
+export const getCoverLine = async (
+  scene: Scene,
+  subgroup: any,
+  scale: number
+) => {
+  const { cadInfo, cad } = await fetchResource(scene);
+  const floors = cad.floors;
+  let info: CoverLine;
+  const reqs: Promise<any>[] = [];
+
+  for (let i = 0; i < floors.length; i++) {
+    const floor = floors[i];
+    if (floor.subgroup !== subgroup) continue;
+
+    const floorInfo = cadInfo.floors.find(
+      (item: any) => item.subgroup === floor.subgroup
+    );
+    const geos = extractConnectedSegments(floor.segment).map((geo) => {
+      return geo.map((id) => {
+        const p = floor["vertex-xy"].find((item: any) => item.id === id);
+        return { x: p.x * scale, y: -p.y * scale } as Pos;
+      });
+    });
+    
+    let bound = {
+      ...(floor.cadInfo.cadBoundingBox || {}),
+      ...floorInfo?.bound,
+    };
+    
+    console.log(floor.cadInfo.cadBoundingBox, floorInfo?.bound)
+    if (!bound || !("x_max" in bound)) {
+      const xs = geos.flatMap((item) => item.map((p) => p.x));
+      const ys = geos.flatMap((item) => item.map((p) => p.y));
+      bound = {
+        x_min: Math.min(...xs),
+        x_max: Math.max(...xs),
+        y_min: Math.min(...ys),
+        y_max: Math.max(...ys),
+      };
+    } else {
+      bound = {
+        x_min: bound.x_min * scale,
+        x_max: bound.x_max * scale,
+        y_min: bound.y_min * scale,
+        y_max: bound.y_max * scale,
+      }
+    }
+    console.log(bound)
+
+    const item = {
+      name: floor.name,
+      subgroup: Number(floor.subgroup),
+      thumb: "",
+      bound,
+      geos,
+    };
+
+    info = item;
+    reqs.push(
+      getSceneApi(
+        "oss",
+        `/scene_view_data/${scene.m}/data/floorplan/floor_${floor.subgroup}.png`
+      )
+        .then((url) => (item.thumb = url))
+        .catch(() => {})
+    );
+  }
+
+  await Promise.all(reqs);
+  return info!;
+};
+
+export const getCompass = async (scene: Scene) => {
+  const { config, userFloorpan } = await fetchResource(scene);
+  return userFloorpan !== undefined
+    ? userFloorpan.compass
+    : Number(config.orientation || 0);
+};
+
+export const getHotTaggingInfos = async (scene: Scene, scale: number) => {
+  const { hots } = await fetchResource(scene);
+  const infos: TaggingInfo[] = [];
+  const reqs: Promise<any>[] = [];
+
+  for (const hot of hots) {
+    if (!validNum(hot.position.x) || !validNum(hot.position.y)) continue;
+    reqs.push(
+      getSceneApi("oss", `/scene_view_data/${scene.m}/user/${hot.icon}`)
+        .then((url) =>
+          infos.push({
+            position: { x: hot.position.x * scale, y: hot.position.y * scale },
+            url,
+          })
+        )
+        .catch(() => {})
+    );
+  }
+  await Promise.all(reqs);
+
+  return infos;
+};
+
+const getBillYaw = (bill: any) => {
+  function isLieDown(quaternion: Quaternion) {
+    let direction = new Vector3(0, 0, -1).applyQuaternion(quaternion);
+    return Math.abs(direction.y) > 0.9;
+  }
+
+  let billboard = new Object3D();
+  let plane = new Object3D();
+  billboard.add(plane);
+  billboard.quaternion
+    .copy({ x: bill.qua[0], y: bill.qua[1], z: bill.qua[2], w: bill.qua[3] })
+    .normalize(); //qua数据里的
+  plane.quaternion.setFromAxisAngle(
+    new Vector3(0, 0, 1),
+    MathUtils.degToRad(-bill.faceAngle)
+  );
+
+  const up = new Vector3(0, 1, 0); //plane的上方指示着方向
+  const right = new Vector3(1, 0, 0); //令躺倒时的旋转轴
+
+  let qua = plane.getWorldQuaternion(new Quaternion());
+  const ld = isLieDown(billboard.quaternion);
+  if (!ld) {
+    //使朝其后方躺倒后再求angle
+    let rotAxis = right.clone().applyQuaternion(qua); //旋转轴
+    (rotAxis.y = 0), rotAxis.normalize();
+    let rot = new Quaternion().setFromAxisAngle(rotAxis, Math.PI / 2);
+    qua.premultiply(rot);
+  }
+  let dir = up.clone().applyQuaternion(qua); //在墙面朝x时正反得到的一样,很奇怪,所以得到的会反向
+  let yaw = Math.atan2(-dir.z, dir.x) - Math.PI / 2;
+  return -yaw;
+};
+
+export const getBillTaggingInfos = async (scene: Scene, scale: number) => {
+  const { billboards } = await fetchResource(scene);
+  const infos: TaggingInfo[] = [];
+  const reqs: Promise<any>[] = [];
+
+  for (const bill of billboards) {
+    if (!validNum(bill.pos[0]) || !validNum(bill.pos[2])) continue;
+    const styleMap = (styleIconMap as any)[bill.icon];
+    const getIcon =
+      bill.icon.indexOf("style-") === 0
+        ? styleMap
+          ? Promise.resolve(`./icons/${styleMap}.svg`)
+          : getSceneApi("./", `./styles/${bill.icon}.svg`).catch((e) => {
+              return getSceneApi(
+                "ossRoot",
+                `/sdk/images/billboard/${bill.icon}.png`
+              );
+            })
+        : getSceneApi("oss", `/scene_view_data/${scene.m}/user/${bill.icon}`);
+
+    const yRotate = getBillYaw(bill);
+    const name = (styleMap && getIconItem(styleMap)?.name) || "";
+
+    reqs.push(
+      getIcon
+        .then((url) => ({
+          url,
+          name,
+          position: {
+            x: bill.pos[0] * scale,
+            y: bill.pos[2] * scale,
+            z:
+              bill.pos[1] < 0
+                ? Math.ceil(bill.pos[1] * scale * 10) / 10
+                : Math.floor(bill.pos[1] * scale * 10) / 10,
+          },
+          rotate: yRotate,
+          size: {
+            width: bill.width * scale,
+            height: bill.height * scale,
+          },
+        }))
+        .then((info) => infos.push(info))
+        .catch(() => {})
+    );
+  }
+  await Promise.all(reqs);
+
+  return infos;
+};
+
+export const getAITaggingInfos = async (
+  scene: Scene,
+  subgroup: any,
+  bound: Record<string, number>
+) => {
+  const { ais } = await fetchResource(scene);
+  const infos: TaggingInfo[] = [];
+  const drawBound = {
+    x: bound.x_min,
+    y: bound.y_min,
+    w: bound.x_max - bound.x_min,
+    h: bound.y_max - bound.y_min,
+  };
+
+  for (const data of ais) {
+    const reg = data.imagePath.match(/floor_(\d)\.png/);
+    const curSubgroup = reg ? Number(reg[1]) : undefined;
+    if (curSubgroup !== subgroup) continue
+
+    for (const shape of data.shapes) {
+      const icon =
+        shape.category in aiIconMap
+          ? (aiIconMap as any)[shape.category]
+          : shape.category;
+      const itemIcon = getIconItem(icon);
+      const isWall = itemIcon && "wall" in itemIcon ? itemIcon.wall : false;
+      if (isWall) continue;
+
+      const name = itemIcon?.name || "";
+      const isTag = icon === "Tag";
+      if (!name && !isTag) continue;
+
+      const pixelCenter = {
+        x: ((shape.bbox[0] + shape.bbox[2]) / 2) / data.imageWidth,
+        y: ((shape.bbox[1] + shape.bbox[3]) / 2) / data.imageHeight,
+      };
+      console.log(drawBound)
+      const center = {
+        x: pixelCenter.x * drawBound.w + drawBound.x,
+        y: pixelCenter.y * drawBound.h + drawBound.y,
+      };
+      const size = {
+        width:
+          ((shape.bbox[2] - shape.bbox[0]) / data.imageWidth) * drawBound.w,
+        height:
+          ((shape.bbox[3] - shape.bbox[1]) / data.imageHeight) * drawBound.h,
+      };
+
+      infos.push({
+        isText: isTag,
+        position: center,
+        rotate: 0,
+        url: isTag ? shape.name : `./icons/${icon ? icon : "circle"}.svg`,
+        name,
+        size,
+      });
+    }
+  }
+  return infos;
+};
+
+export const getWallAITaggingInfos = async (
+  scene: Scene,
+  subgroup: any,
+  scale: number
+) => {
+  const { floorplan } = await fetchResource(scene);
+  const infos: TaggingInfo[] = [];
+
+  for (const floor of floorplan.floors) {
+    if (floor.subgroup !== subgroup) continue;
+
+    for (const key in floor.symbols) {
+      const item = floor.symbols[key];
+      const icon =
+        item.geoType in aiIconMap
+          ? (aiIconMap as any)[item.geoType]
+          : item.geoType;
+
+      const itemIcon = getIconItem(icon);
+      let name = itemIcon?.name;
+      const isWall = itemIcon && "wall" in itemIcon ? itemIcon.wall : false;
+      if (!isWall) continue;
+
+      const line = [item.startPoint, item.endPoint].map((p) => ({
+        x: p.x * scale,
+        y: -p.y * scale,
+      }));
+      const lineRotate = vector2IncludedAngle(lineVector(line), { x: 1, y: 0 });
+      const moveRotate =
+        lineRotate + ((item.openSide === "LEFT" ? 1 : -1) * Math.PI) / 2;
+      const movev = new Vector2(Math.cos(moveRotate), -Math.sin(moveRotate));
+      const shapeRotate = vector2IncludedAngle({ x: 0, y: -1 }, movev);
+
+      const size = {
+        width: lineLen(line[0], line[1]),
+        height: item.len * scale,
+      };
+      const url = `./icons/${icon ? icon : "circle"}.svg`;
+      try {
+        const svg = parseSvgContent(await getSvgContent(url));
+        size.height = (svg.height / svg.width) * size.width;
+      } catch {}
+
+      const center = lineCenter(line);
+      const offset = movev.clone().multiplyScalar(size.height / 2);
+
+      const mat = new Transform()
+        .translate(offset.x, offset.y)
+        .translate(center.x, center.y)
+        .rotate(shapeRotate);
+
+      const afterStart = mat.point({
+        x: -size.width / 2,
+        y: -size.height / 2,
+      });
+      if (lineLen(afterStart, line[0]) > lineLen(afterStart, line[1])) {
+        mat.scale(-1, 1);
+      }
+
+      infos.push({
+        name: name,
+        mat: mat.m,
+        size,
+        url,
+        fixed: true,
+      });
+    }
+  }
+
+  return infos;
+};
+
+export const getResource = async ({
+  scene,
+  floorName,
+  syncs,
+  scale,
+}: ResourceArgs) => {
+  const data = await fetchResource(scene);
+  const floor = data.floorplan.floors.find(
+    (item: any) => item.name === floorName
+  );
+  const key = floor.subgroup;
+  const taggings: TaggingInfo[] = [];
+  let coverLine: CoverLine;
+
+  const compass = await getCompass(scene);
+
+  console.log(scale)
+  const reqs: Promise<any>[] = [
+    getCompass(scene),
+    getCoverLine(scene, key, scale)
+      .then((lines) => (coverLine = lines))
+      .then(({ bound }) => {
+        getAITaggingInfos(scene, key, bound).then((ts) => taggings.push(...ts));
+      }),
+    getWallAITaggingInfos(scene, key, scale).then((ts) => taggings.push(...ts)),
+  ];
+
+  if (syncs.includes("hot")) {
+    reqs.push(
+      getHotTaggingInfos(scene, scale).then((ts) => taggings.push(...ts))
+    );
+  }
+  if (syncs.includes("signage")) {
+    reqs.push(
+      getBillTaggingInfos(scene, scale).then((ts) => taggings.push(...ts))
+    );
+  }
+
+  await Promise.all(reqs);
+
+  return {
+    taggings,
+    cover: coverLine!,
+    compass: compass,
+  } as SceneResource;
+};

+ 3 - 1
src/utils/resource.ts

@@ -67,7 +67,8 @@ export type SVGPath = {
   stroke?: string;
   strokeWidth?: number;
   data: string;
-  fix: boolean
+  fix: boolean;
+  box: DOMRect
 };
 export type SVGParseResult = Size & Pos & {
   paths: SVGPath[];
@@ -107,6 +108,7 @@ export let parseSvgContent = (svgContent: string): SVGParseResult => {
         fill,
         data,
         stroke,
+        box,
         fix,
         strokeWidth: (strokeWidth && Number(strokeWidth)) || 1,
       };

+ 39 - 16
src/utils/shared.ts

@@ -382,14 +382,13 @@ export const genBound = () => {
   };
 };
 
-
-export const validNum = (num: any) => typeof num === 'number' && !Number.isNaN(num)
-
+export const validNum = (num: any) =>
+  typeof num === "number" && !Number.isNaN(num);
 
 // 检测字体是否可用(返回第一个支持的字体)
 export function getSupportedFont(fonts: string[]) {
-  const canvas = document.createElement('canvas');
-  const ctx = canvas.getContext('2d')!;
+  const canvas = document.createElement("canvas");
+  const ctx = canvas.getContext("2d")!;
   const defaultFont = ctx.font;
 
   for (const font of fonts) {
@@ -404,23 +403,47 @@ export function getSupportedFont(fonts: string[]) {
 }
 
 export const repeatedlyOnly = <T extends (...args: any[]) => any>(fn: T) => {
-  let empty = uuid()
-  let prevResult: any = empty
+  let empty = uuid();
+  let prevResult: any = empty;
   const pack = (...args: any[]) => {
     if (prevResult === empty) {
-      const result = fn(...args)
+      const result = fn(...args);
       if (result instanceof Promise) {
-        prevResult = result
-        return result.then(r => {
-          prevResult = empty
-          return r
+        prevResult = result;
+        return result.then((r) => {
+          prevResult = empty;
+          return r;
         });
       } else {
-        return result
+        return result;
       }
     } else {
-      return prevResult
+      return prevResult;
+    }
+  };
+  return pack as T;
+};
+
+export const genCache = <T, R>(
+  getKey: (args: T) => string | number,
+  calcResult: (args: T) => R,
+  delay: number | null = null
+) => {
+  const cache: Record<string, R> = {}
+
+  return (args: T): R => {
+    const key = getKey(args)
+    if (key in cache) {
+      return cache[key]
+    }
+
+    const result = cache[key] = calcResult(args)
+    if (delay !== null) {
+      setTimeout(() => {
+        delete cache[key]
+      }, delay)
     }
+
+    return result;
   }
-  return pack as T
-}
+};