Browse Source

Merge branch 'master' into merge-selection

bill 2 tháng trước cách đây
mục cha
commit
86ce53d20e

+ 1 - 1
package.json

@@ -9,7 +9,6 @@
     "build:fuse": "vite build --mode=fuse",
     "dev:hx": "vite --mode=hxdev",
     "build:hx": "vite build --mode=hx",
-
     "dev:jm": "vite --mode=jmdev",
     "build:jm": "vite build --mode=jm"
   },
@@ -27,6 +26,7 @@
     "jszip": "^3.10.1",
     "konva": "^9.3.18",
     "localforage": "^1.10.0",
+    "martinez-polygon-clipping": "^0.7.4",
     "mitt": "^3.0.1",
     "pinia": "^2.2.4",
     "sass": "^1.80.4",

+ 27 - 0
pnpm-lock.yaml

@@ -47,6 +47,9 @@ importers:
       localforage:
         specifier: ^1.10.0
         version: 1.10.0
+      martinez-polygon-clipping:
+        specifier: ^0.7.4
+        version: 0.7.4
       mitt:
         specifier: ^3.0.1
         version: 3.0.1
@@ -1450,6 +1453,9 @@ packages:
     resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==}
     engines: {node: '>=0.10.0'}
 
+  martinez-polygon-clipping@0.7.4:
+    resolution: {integrity: sha512-jBEwrKtA0jTagUZj2bnmb4Yg2s4KnJGRePStgI7bAVjtcipKiF39R4LZ2V/UT61jMYWrTcBhPazexeqd6JAVtw==}
+
   math-intrinsics@1.1.0:
     resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
     engines: {node: '>= 0.4'}
@@ -1708,6 +1714,9 @@ packages:
     resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==}
     engines: {node: '>= 0.8.15'}
 
+  robust-predicates@2.0.4:
+    resolution: {integrity: sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg==}
+
   rollup@4.40.2:
     resolution: {integrity: sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -1940,6 +1949,9 @@ packages:
     resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
     engines: {node: '>=0.10.0'}
 
+  splaytree@0.1.4:
+    resolution: {integrity: sha512-D50hKrjZgBzqD3FT2Ek53f2dcDLAQT8SSGrzj3vidNH5ISRgceeGVJ2dQIthKOuayqFXfFjXheHNo4bbt9LhRQ==}
+
   split-string@3.1.0:
     resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==}
     engines: {node: '>=0.10.0'}
@@ -2032,6 +2044,9 @@ packages:
   three@0.169.0:
     resolution: {integrity: sha512-Ed906MA3dR4TS5riErd4QBsRGPcx+HBDX2O5yYE5GqJeFQTPU+M56Va/f/Oph9X7uZo3W3o4l2ZhBZ6f6qUv0w==}
 
+  tinyqueue@1.2.3:
+    resolution: {integrity: sha512-Qz9RgWuO9l8lT+Y9xvbzhPT2efIUIFd69N7eF7tJ9lnQl0iLj1M7peK7IoUGZL9DJHw9XftqLreccfxcQgYLxA==}
+
   to-object-path@0.3.0:
     resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==}
     engines: {node: '>=0.10.0'}
@@ -3616,6 +3631,12 @@ snapshots:
     dependencies:
       object-visit: 1.0.1
 
+  martinez-polygon-clipping@0.7.4:
+    dependencies:
+      robust-predicates: 2.0.4
+      splaytree: 0.1.4
+      tinyqueue: 1.2.3
+
   math-intrinsics@1.1.0: {}
 
   mdn-data@2.0.14: {}
@@ -3906,6 +3927,8 @@ snapshots:
   rgbcolor@1.0.1:
     optional: true
 
+  robust-predicates@2.0.4: {}
+
   rollup@4.40.2:
     dependencies:
       '@types/estree': 1.0.7
@@ -4170,6 +4193,8 @@ snapshots:
 
   source-map@0.6.1: {}
 
+  splaytree@0.1.4: {}
+
   split-string@3.1.0:
     dependencies:
       extend-shallow: 3.0.2
@@ -4287,6 +4312,8 @@ snapshots:
 
   three@0.169.0: {}
 
+  tinyqueue@1.2.3: {}
+
   to-object-path@0.3.0:
     dependencies:
       kind-of: 3.2.2

+ 78 - 18
src/core/components/line/attach-view.ts

@@ -1,4 +1,5 @@
 import {
+  getDiffPolygons,
   getVectorLine,
   isPolygonPointInner,
   lineCenter,
@@ -12,12 +13,16 @@ import {
   verticalVector,
 } from "@/utils/math";
 import { LineData } from ".";
-import { getJoinLine, getLinePoints } from "./attach-server";
-import { MathUtils, Vector2 } from "three";
-import { computed, reactive, ref, Ref, watch, watchEffect } from "vue";
-import { flatPositions, mergeFuns, rangMod } from "@/utils/shared";
-import { installGlobalVar } from "@/core/hook/use-global-vars";
+import { getJoinLine } from "./attach-server";
+import { MathUtils } from "three";
+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 { Transform } from "konva/lib/Util";
 import { useTestPoints } from "@/core/hook/use-debugger";
+import { sortFn } from "@/core/store/store";
 
 const minAngle = MathUtils.degToRad(0.1);
 const palAngle = MathUtils.degToRad(20);
@@ -33,15 +38,8 @@ const getLineRect = (points: Pos[], strokeWidth: number) => {
 };
 
 export const useGetExtendPolygon = installGlobalVar(() => {
-  const testPoints = useTestPoints();
-
+  // const testPoints = useTestPoints();
   return (data: LineData, line: LineData["lines"][0]) => {
-    const getConfig = (points: Pos[], stroke?: string) => ({
-      points: flatPositions(points),
-      fill: line.stroke,
-      closed: true,
-      listening: false,
-    });
     const getJoinInfo = (
       joinLine: LineData["lines"][0],
       joinPoints: Pos[],
@@ -58,7 +56,7 @@ export const useGetExtendPolygon = installGlobalVar(() => {
         direInv ? linev : joinv
       );
       const checkAngle = rangMod(Math.abs(angle), Math.PI);
-      if (checkAngle < minAngle || checkAngle >  Math.PI - minAngle) return;
+      if (checkAngle < minAngle || checkAngle > Math.PI - minAngle) return;
       const join = lineIntersection(linePoints, joinPoints);
       if (!join) return;
 
@@ -113,16 +111,22 @@ export const useGetExtendPolygon = installGlobalVar(() => {
       if (checkAngle < palAngle) {
         const jov = getVectorLine(lineVerticalVector([join, outer]), join);
         const outer1 = lineIntersection(jov, outerLine1)!;
-        outers = [outer1, join]
+        outers = [outer1, join];
       }
 
       const repsResult: { rep: number; points: Pos[] }[] = [];
       if (getNdx === 0) {
-        repsResult.push({ rep: 0, points: rectJust ? outers.reverse() : insides });
+        repsResult.push({
+          rep: 0,
+          points: rectJust ? outers.reverse() : insides,
+        });
         repsResult.push({ rep: 3, points: rectJust ? insides : outers });
       } else {
         repsResult.push({ rep: 1, points: rectJust ? outers : insides });
-        repsResult.push({ rep: 2, points: rectJust ? insides : outers.reverse() });
+        repsResult.push({
+          rep: 2,
+          points: rectJust ? insides : outers.reverse(),
+        });
       }
       // testPoints.value.push(...insides, ...outers);
       return repsResult;
@@ -147,6 +151,62 @@ export const useGetExtendPolygon = installGlobalVar(() => {
       }
     });
 
-    return getConfig(polygon);
+    return polygon;
+  };
+});
+
+// 计算与icon相差的多边形
+export const useGetDiffPolygons = installGlobalVar(() => {
+  const store = useStore();
+  const iconPolygons = reactive({}) as { [key in string]: Pos[] };
+  const icons = computed(() => store.getTypeItems("icon"));
+  const line = computed(() => store.getTypeItems("line")[0]);
+  const watchIcon = (id: string) => {
+    const stopWatch = watchEffect(() => {
+      const icon = icons.value.find((item) => item.id === id);
+      if (!icon) {
+        stopWatch();
+        delete iconPolygons[id];
+        return;
+      }
+      if (!line.value || sortFn(line.value, icon) > 0) {
+        delete iconPolygons[id];
+        return;
+      }
+
+      const rect = [
+        { x: -icon.width / 2, y: -icon.height / 2 },
+        { x: icon.width / 2, y: -icon.height / 2 },
+        { x: icon.width / 2, y: icon.height / 2 },
+        { x: -icon.width / 2, y: icon.height / 2 },
+      ];
+      const mat = new Transform(icon.mat);
+      iconPolygons[id] = rect.map((p) => mat.point(p));
+    });
+  };
+
+  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];
+      });
+      added.forEach(watchIcon);
+    },
+    { immediate: true }
+  );
+
+  return {
+    var: (polygon: Pos[]) => {
+      const targets = Object.values(iconPolygons);
+      if (!targets.length) return [polygon];
+      return getDiffPolygons(polygon, targets);
+    },
+    onDestroy: stopWatch,
   };
 });

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

@@ -2,7 +2,7 @@
   <EditLine
     :ref="(d: any) => shape = d?.shape"
     :data="{ ...line, ...style, lineJoin: 'miter' }"
-    :opacity="showEditPoint ? 1 : 0"
+    :opacity="0"
     :points="points"
     :closed="false"
     :id="line.id"
@@ -27,6 +27,16 @@
     :stroke="style.stroke"
   />
 
+  <v-line
+    v-for="polygon in diffPolygons"
+    :config="{
+      points: flatPositions(polygon),
+      fill: line.stroke,
+      closed: true,
+      listening: false,
+    }"
+    v-if="diffPolygons"
+  />
   <template v-if="showEditPoint">
     <EditPoint
       v-for="(point, ndx) in points"
@@ -46,7 +56,6 @@
       @delete="delPoint(point)"
     />
   </template>
-  <v-line :config="polygon" v-else-if="polygon" />
 
   <PropertyUpdate
     :describes="describes"
@@ -88,7 +97,7 @@ import {
 } from "@/core/hook/use-mouse-status.ts";
 import { themeColor } from "@/constant";
 import { Vector2 } from "three";
-import { useGetExtendPolygon } from "./attach-view.ts";
+import { useGetDiffPolygons, useGetExtendPolygon } from "./attach-view.ts";
 import EditPoint from "../share/edit-point.vue";
 
 const mode = useMode();
@@ -103,6 +112,8 @@ const props = defineProps<{
 
 const getExtendPolygon = useGetExtendPolygon();
 const polygon = computed(() => getExtendPolygon(props.data, props.line));
+const getDiffPolygons = useGetDiffPolygons();
+const diffPolygons = computed(() => getDiffPolygons(polygon.value));
 const showEditPoint = computed(
   () => (!mode.include(Mode.readonly) && props.canEdit) || isDrawIng.value
 );

+ 1 - 1
src/core/store/store.ts

@@ -2,7 +2,7 @@ import { defineStore } from "pinia";
 import { DrawData, DrawItem, ShapeType } from "../components";
 import { defaultLayer } from "@/constant";
 
-const sortFn = (
+export const sortFn = (
   a: Pick<DrawItem, "zIndex" | "createTime">,
   b: Pick<DrawItem, "zIndex" | "createTime">
 ) => a.zIndex - b.zIndex || a.createTime - b.createTime;

+ 3 - 0
src/example/platform/platform-draw.ts

@@ -183,6 +183,7 @@ const drawLayerResource = async (
     lines: [],
     points: [],
     polygon: [],
+    zIndex: 0,
     createTime: createTime,
   };
 
@@ -300,6 +301,7 @@ const drawLayerResource = async (
         ...getBaseItem(),
         ...textDefaultStyle,
         content: item.url,
+        zIndex: 1,
         mat: tf.m,
       });
     } else {
@@ -330,6 +332,7 @@ const drawLayerResource = async (
         url: item.url,
         mat: tf.m,
         cornerRadius: 0,
+        zIndex: 1,
         ...attach,
       });
     }

+ 63 - 46
src/utils/math.ts

@@ -1,10 +1,11 @@
 import { Vector2, ShapeUtils, Box2 } from "three";
 import { Transform } from "konva/lib/Util";
-import { round } from "./shared.ts";
+import { flatPositions, round } from "./shared.ts";
 import { IRect } from "konva/lib/types";
+import { diff as diffPolygons, MultiPolygon } from "martinez-polygon-clipping";
 
 export type Pos = { x: number; y: number };
-export type Size = { width: number, height: number }
+export type Size = { width: number; height: number };
 
 export const vector = (pos: Pos = { x: 0, y: 0 }): Vector2 => {
   return new Vector2(pos.x, pos.y);
@@ -130,9 +131,9 @@ export function getPolygonDirection(points: Pos[]) {
   let area = 0;
   const numPoints = points.length;
   for (let i = 0; i < numPoints; i++) {
-      const p1 = points[i];
-      const p2 = points[(i + 1) % numPoints];
-      area += (p2.x - p1.x) * (p2.y + p1.y);
+    const p1 = points[i];
+    const p2 = points[(i + 1) % numPoints];
+    area += (p2.x - p1.x) * (p2.y + p1.y);
   }
 
   // 如果面积为正,是逆时针;否则是顺时针
@@ -174,19 +175,18 @@ export const lineAndVectorIncludedAngle = (line: Pos[], v: Pos) =>
  * @returns
  */
 export const lineCenter = (line: Pos[]) => {
-  const start = vector(line[0])
+  const start = vector(line[0]);
   for (let i = 1; i < line.length; i++) {
-    start.add(line[i])
+    start.add(line[i]);
   }
   return start.multiplyScalar(1 / line.length);
-}
-
+};
 
 export const lineSpeed = (line: Pos[], step: number) => {
-  const p = vector(line[0])
-  const v = vector(line[1]).sub(line[0])
-  return p.add(v.multiplyScalar(step))
-}
+  const p = vector(line[0]);
+  const v = vector(line[1]).sub(line[0]);
+  return p.add(v.multiplyScalar(step));
+};
 
 export const pointsCenter = (points: Pos[]) => {
   if (points.length === 0) return { x: 0, y: 0 };
@@ -371,15 +371,15 @@ export const lineInner = (line: Pos[], position: Pos) => {
     return false;
   }
   // 检查点 P 的坐标是否在 A 和 B 的坐标范围内
-  const minX = Math.min(A.x, B.x)
-  const maxX = Math.max(A.x, B.x)
-  const minY = Math.min(A.y, B.y)
-  const maxY = Math.max(A.y, B.y)
+  const minX = Math.min(A.x, B.x);
+  const maxX = Math.max(A.x, B.x);
+  const minY = Math.min(A.y, B.y);
+  const maxY = Math.max(A.y, B.y);
   return (
     (minX < P.x || numEq(minX, P.x)) &&
     (P.x < maxX || numEq(maxX, P.x)) &&
     (minY < P.y || numEq(minY, P.y)) &&
-    (P.y < maxY || numEq(maxY, P.y)) 
+    (P.y < maxY || numEq(maxY, P.y))
   );
 };
 
@@ -453,7 +453,6 @@ export const isPolygonLineIntersect = (polygon: Pos[], line: Pos[]) => {
   return false;
 };
 
-
 /**
  * 判断点是否在多边形内部
  * @param polygon 多边形顶点数组,按顺时针或逆时针顺序排列
@@ -463,32 +462,32 @@ export const isPolygonLineIntersect = (polygon: Pos[], line: Pos[]) => {
 export const isPolygonPointInner = (polygon: Pos[], pos: Pos): boolean => {
   // 如果多边形少于3个点,直接返回false
   if (polygon.length < 3) return false;
-  
+
   let inside = false;
   const x = pos.x;
   const y = pos.y;
-  
+
   // 使用射线法判断
   for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
     const xi = polygon[i].x;
     const yi = polygon[i].y;
     const xj = polygon[j].x;
     const yj = polygon[j].y;
-    
+
     // 检查点是否在多边形的顶点上
     if ((numEq(xi, x) && numEq(yi, y)) || (numEq(xj, x) && numEq(yj, y))) {
       return true;
     }
-    
+
     // 检查点是否在边的水平射线上
-    const intersect = ((yi > y) !== (yj > y)) &&
-      (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
-    
+    const intersect =
+      yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
+
     if (intersect) {
       inside = !inside;
     }
   }
-  
+
   return inside;
 };
 
@@ -593,50 +592,68 @@ export const getLineRelationMat = (l1: [Pos, Pos], l2: [Pos, Pos]) => {
     .rotate(angle)
     .translate(-P1.x, -P1.y);
 
-  
   if (!eqPoint(mat.point(P1), P2)) {
-    console.error('对准不正确 旋转后P1', mat.point(P1), P2)
+    console.error("对准不正确 旋转后P1", mat.point(P1), P2);
   }
   if (!eqPoint(mat.point(P1End), P2End)) {
-    console.error('对准不正确 旋转后P2', mat.point(P1End), P1End)
+    console.error("对准不正确 旋转后P2", mat.point(P1End), P1End);
   }
-  return mat
+  return mat;
 };
 
 // 判断两向量是否垂直
 export const isVertical = (v1: Pos, v2: Pos) => {
-  console.log(vector(v1).dot(v2))
-  return zeroEq(vector(v1).dot(v2))
-}
+  console.log(vector(v1).dot(v2));
+  return zeroEq(vector(v1).dot(v2));
+};
 
 /**
  * 判断rect1是否完整包含rect2
- * @param rect1 
- * @param rect2 
- * @returns 
+ * @param rect1
+ * @param rect2
+ * @returns
  */
 export const isRectContained = (rect1: IRect, rect2: IRect) => {
   // 计算 rect1 的左右边界
   const rect1Left = Math.min(rect1.x, rect1.x + rect1.width);
   const rect1Right = Math.max(rect1.x, rect1.x + rect1.width);
-  
+
   // 计算 rect1 的上下边界
   const rect1Top = Math.min(rect1.y, rect1.y + rect1.height);
   const rect1Bottom = Math.max(rect1.y, rect1.y + rect1.height);
-  
+
   // 计算 rect2 的左右边界
   const rect2Left = Math.min(rect2.x, rect2.x + rect2.width);
   const rect2Right = Math.max(rect2.x, rect2.x + rect2.width);
-  
+
   // 计算 rect2 的上下边界
   const rect2Top = Math.min(rect2.y, rect2.y + rect2.height);
   const rect2Bottom = Math.max(rect2.y, rect2.y + rect2.height);
-  
+
   // 检查 rect2 是否完全在 rect1 内
   return (
-      rect2Left >= rect1Left &&
-      rect2Right <= rect1Right &&
-      rect2Top >= rect1Top &&
-      rect2Bottom <= rect1Bottom
+    rect2Left >= rect1Left &&
+    rect2Right <= rect1Right &&
+    rect2Top >= rect1Top &&
+    rect2Bottom <= rect1Bottom
   );
-}
+};
+
+export const getDiffPolygons = (
+  originPolygon: Pos[],
+  targetPolygons: Pos[][]
+) => {
+  const geo1 = originPolygon.map(({ x, y }) => [x, y]);
+  geo1.push(geo1[0])
+  const geo2s = targetPolygons.map((targetPolygon) => {
+    const geo2 = targetPolygon.map(({ x, y }) => [x, y]);
+    geo2.push(geo2[0]);
+    return geo2;
+  });
+
+  const subGeos = diffPolygons([geo1], geo2s);
+  return subGeos.map((mulPolygon) => {
+    const polygon = mulPolygon as number[][][];
+    return polygon[0].map((position) => ({ x: position[0], y: position[1] }));
+  }) as Pos[][];
+};