Selaa lähdekoodia

feat: 断线链接显示优化

bill 2 kuukautta sitten
vanhempi
commit
d018567f1a

+ 129 - 98
src/core/components/line/attach-view.ts

@@ -1,5 +1,10 @@
 import {
+  getVectorLine,
+  isPolygonPointInner,
+  lineCenter,
+  lineInner,
   lineIntersection,
+  lineLen,
   lineVector,
   lineVerticalVector,
   Pos,
@@ -11,111 +16,137 @@ 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 { useTestPoints } from "@/core/hook/use-debugger";
 
-const eloCacle = new WeakMap<LineData, string[]>();
-export const extendLinesOverlap = (
-  data: LineData,
-  line1: LineData["lines"][0]
-) => {
-  let cacle = eloCacle.get(data);
-  if (!cacle) {
-    eloCacle.set(data, (cacle = []));
-  }
-  const has = (ids: string[]) => {
-    const key = [...ids].sort().join("---");
-    return cacle.includes(key);
-  };
-  const set = (ids: string[]) => {
-    cacle.push([...ids].sort().join("---"));
-  };
+const minAngle = MathUtils.degToRad(0.1);
+const palAngle = MathUtils.degToRad(20);
 
-  type ELine = LineData["lines"][0] & { points: LineData["points"] };
-  const minAngle = MathUtils.degToRad(20);
-  const getExtendPolygons = (line1: ELine, line2: ELine) => {
-    const v1 = lineVector(line1.points);
-    const v2 = lineVector(line2.points);
-    const vv1 = verticalVector(v1);
-    const vv2 = verticalVector(v2);
-    const angle = vector2IncludedAngle(v1, v2);
-    const checkAngle = Math.abs(rangMod(angle, Math.PI));
-    if (checkAngle < minAngle || checkAngle > Math.PI - minAngle) return [];
-    const mul = angle > 0 ? -1 : 1;
-    const offset1 = vv1.multiplyScalar((line1.strokeWidth / 2) * mul);
-    const offset2 = vv2.multiplyScalar((line2.strokeWidth / 2) * mul);
-    const le1 = line1.points.map((p) => offset1.clone().add(p));
-    const le2 = line2.points.map((p) => offset2.clone().add(p));
-    const join = lineIntersection(le1, le2)!;
-
-    return [
-      {
-        points: flatPositions([le1[1], line1.points[1], join]),
-        fill: line1.stroke,
-        strokeWidth: 0.05,
-        stroke: line1.stroke,
-        closed: true,
-        listening: false,
-      },
-      {
-        points: flatPositions([le2[0], line1.points[1], join]),
-        fill: line2.stroke,
-        strokeWidth: 0.05,
-        stroke: line2.stroke,
-        closed: true,
-        listening: false,
-      },
-    ];
-  };
+const getLineRect = (points: Pos[], strokeWidth: number) => {
+  const v = lineVector(points);
+  const vv = verticalVector(v);
+  const offset = vv.clone().multiplyScalar(strokeWidth / 2);
+  const top = points.map((p) => offset.clone().add(p));
+  offset.multiplyScalar(-1);
+  const bottom = points.map((p) => offset.clone().add(p));
+  return [...top, bottom[1], bottom[0]];
+};
 
-  type ResultJoin = ReturnType<typeof getExtendPolygons>;
-  const joins = ref<ResultJoin[]>([]);
+export const useGetExtendPolygon = installGlobalVar(() => {
+  const testPoints = useTestPoints();
 
-  const updateJoins = (rev = false) => {
-    const points = computed(() => {
-      const pointIds = rev ? [line1.b, line1.a] : [line1.a, line1.b]
-      return pointIds.map(
-        (id) => data.points.find((item) => item.id === id)!
-      )
+  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[],
+      getNdx: number
+    ) => {
+      const jNdx = joinPoints.indexOf(linePoints[getNdx]);
+      if ((getNdx === 0 && jNdx === 0) || (getNdx === 1 && jNdx === 1)) {
+        joinPoints.reverse();
+      }
+      const direInv = getNdx === 0 && jNdx === 0;
+      const joinv = lineVector(joinPoints).multiplyScalar(-1);
+      const angle = vector2IncludedAngle(
+        direInv ? joinv : linev,
+        direInv ? linev : joinv
+      );
+      const checkAngle = rangMod(Math.abs(angle), Math.PI);
+      if (checkAngle < minAngle || checkAngle >  Math.PI - minAngle) return;
+      const join = lineIntersection(linePoints, joinPoints);
+      if (!join) return;
+
+      const center = lineCenter([...linePoints, ...joinPoints]);
+      const joinRect = getLineRect(joinPoints, joinLine.strokeWidth);
+      const cheJoinInnerNdxs = getNdx === 0 ? [0, 3] : [1, 2];
+      const cheLineInnerNdxs = getNdx === 0 ? [1, 2] : [0, 3];
+      if (
+        cheJoinInnerNdxs.some((ndx) =>
+          isPolygonPointInner(lineRect, joinRect[ndx])
+        ) ||
+        cheLineInnerNdxs.some((ndx) =>
+          isPolygonPointInner(joinRect, lineRect[ndx])
+        )
+      ) {
+        return;
+      }
+
+      let outerLine1, innerLine1, outerLine2, innerLine2;
+      const rectJust =
+        lineLen(center, lineRect[0]) > lineLen(center, lineRect[3]);
+      if (rectJust) {
+        outerLine1 = [lineRect[0], lineRect[1]];
+        innerLine1 = [lineRect[3], lineRect[2]];
+      } else {
+        outerLine1 = [lineRect[3], lineRect[2]];
+        innerLine1 = [lineRect[0], lineRect[1]];
+      }
+
+      if (lineLen(center, joinRect[0]) > lineLen(center, joinRect[3])) {
+        outerLine2 = [joinRect[0], joinRect[1]];
+        innerLine2 = [joinRect[3], joinRect[2]];
+      } else {
+        outerLine2 = [joinRect[3], joinRect[2]];
+        innerLine2 = [joinRect[0], joinRect[1]];
+      }
+
+      const outer = lineIntersection(outerLine1, outerLine2);
+      if (!outer) return;
 
-    watch(
-      () => getJoinLine(data, line1, points.value[1].id),
-      (joinLines) => {
-        for (let j = 0; j < joinLines.length; j++) {
-          const key = [points.value[0].id, points.value[1].id, joinLines[j].points[1].id];
-          if (has(key)) continue;
-          set(key);
-          watchEffect((onCleanup) => {
-            const polygons = getExtendPolygons(
-              { ...line1, points: points.value },
-              joinLines[j]
-            );
-            joins.value.push(polygons);
-            onCleanup(() => {
-              const ndx = joins.value.indexOf(polygons);
-              ~ndx && joins.value.splice(ndx, 1);
-            });
-          });
-        }
-      },
-      { immediate: true }
+      let inside: Pos = lineIntersection(innerLine1, innerLine2)!;
+      if (!inside) return;
+
+      const insideLineInner1 = lineInner(innerLine1, inside);
+      const insideLineInner2 = lineInner(innerLine2, inside);
+
+      if (!insideLineInner1 && !insideLineInner2) return;
+
+      let insides = [inside];
+      // 如果角度过于尖锐则使用平行线
+      let outers = [outer];
+      if (checkAngle < palAngle) {
+        const jov = getVectorLine(lineVerticalVector([join, outer]), join);
+        const outer1 = lineIntersection(jov, outerLine1)!;
+        outers = [outer1, join]
+      }
+
+      const repsResult: { rep: number; points: Pos[] }[] = [];
+      if (getNdx === 0) {
+        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() });
+      }
+      // testPoints.value.push(...insides, ...outers);
+      return repsResult;
+    };
+
+    const linePoints = [line.a, line.b].map(
+      (id) => data.points.find((item) => item.id === id)!
     );
-  };
+    const lineRect: Pos[] = getLineRect(linePoints, line.strokeWidth);
+    const polygon = [...lineRect];
+    const linev = lineVector(linePoints);
 
-  watchEffect((onCleanup) => {
-    console.log(data.lines.indexOf(line1))
-    onCleanup(() => 'has?')
-  })
+    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;
 
-  const stopWatch = watch(
-    () => [line1, line1.a, line1.b],
-    () => {
-      if (!data.lines.includes(line1)) {
-        return stopWatch();
+      for (const rep of repsResult) {
+        const ndx = polygon.indexOf(lineRect[rep.rep]);
+        polygon.splice(ndx, 1, ...rep.points);
       }
-      updateJoins(false);
-      updateJoins(true);
-    },
-    { immediate: true }
-  );
-  return joins;
-};
+    });
+
+    return getConfig(polygon);
+  };
+});

+ 1 - 2
src/core/components/line/line.vue

@@ -7,8 +7,7 @@ import { useAutomaticData } from "@/core/hook/use-automatic-data.ts";
 import { LineData } from "./index.ts";
 import TempLine from "./temp-line.vue";
 
-const props = defineProps<{ data: LineData }>();
-const data = useAutomaticData(() => props.data);
+defineProps<{ data: LineData }>();
 const emit = defineEmits<{
   (e: "updateShape", value: LineData): void;
   (e: "addShape", value: LineData): void;

+ 10 - 9
src/core/components/line/single-line.vue

@@ -2,7 +2,7 @@
   <EditLine
     :ref="(d: any) => shape = d?.shape"
     :data="{ ...line, ...style, lineJoin: 'miter' }"
-    :opacity="isDrawIng ? 0.5 : 1"
+    :opacity="showEditPoint ? 1 : 0"
     :points="points"
     :closed="false"
     :id="line.id"
@@ -13,9 +13,6 @@
     @dragend="emit('dragLineEnd', props.line)"
     @add-point="addPoint"
   />
-  <template v-for="polygons in joinsPolygons">
-    <v-line v-for="config in polygons" :config="config" />
-  </template>
 
   <SizeLine
     v-if="
@@ -30,13 +27,13 @@
     :stroke="style.stroke"
   />
 
-  <template v-if="(!mode.include(Mode.readonly) && canEdit) || isDrawIng">
+  <template v-if="showEditPoint">
     <EditPoint
       v-for="(point, ndx) in points"
       :key="point.id"
       :size="line.strokeWidth"
       :points="points"
-      :opacity="0.1"
+      :opacity="1"
       :drawIng="ndx === 0 && isDrawIng"
       :ndx="ndx"
       :closed="false"
@@ -49,6 +46,7 @@
       @delete="delPoint(point)"
     />
   </template>
+  <v-line :config="polygon" v-else-if="polygon" />
 
   <PropertyUpdate
     :describes="describes"
@@ -90,7 +88,7 @@ import {
 } from "@/core/hook/use-mouse-status.ts";
 import { themeColor } from "@/constant";
 import { Vector2 } from "three";
-import { extendLinesOverlap } from "./attach-view.ts";
+import { useGetExtendPolygon } from "./attach-view.ts";
 import EditPoint from "../share/edit-point.vue";
 
 const mode = useMode();
@@ -103,8 +101,11 @@ const props = defineProps<{
   dragPointIds?: string[];
 }>();
 
-const joinsPolygons = extendLinesOverlap(props.data, props.line);
-watchEffect(() => props.data.lines.indexOf(props.line));
+const getExtendPolygon = useGetExtendPolygon();
+const polygon = computed(() => getExtendPolygon(props.data, props.line));
+const showEditPoint = computed(
+  () => (!mode.include(Mode.readonly) && props.canEdit) || isDrawIng.value
+);
 
 const emit = defineEmits<{
   (e: "updatePoint", value: LineData["points"][number]): void;

+ 10 - 3
src/core/components/line/temp-line.vue

@@ -34,7 +34,7 @@ import {
   NLineDataCtx,
   normalLineData,
 } from "./attach-server.ts";
-import { computed, ref } from "vue";
+import { computed, ref, watch } from "vue";
 import { useZIndex } from "@/core/hook/use-layer.ts";
 import { DC } from "@/deconstruction.js";
 import { Group } from "konva/lib/Group";
@@ -49,6 +49,13 @@ const props = defineProps<{
   canEdit?: boolean;
 }>();
 
+watch(
+  () => props.data,
+  (ndata, odata) => {
+    console.log("data change", ndata, odata, ndata === odata);
+  }
+);
+
 const operMode = useOperMode();
 const initData = useInitData();
 const emit = defineEmits<{
@@ -99,8 +106,8 @@ const addLineHandler = (l: LineData["lines"][0]) => {
 };
 
 const updateHandler = () => {
-  // normalLineData(props.data, ctx);
-  // emit("updateShape");
+  normalLineData(props.data, ctx);
+  emit("updateShape");
   track = false;
   dragPointIds.value = undefined;
 };

+ 8 - 1
src/core/helper/debugger.vue

@@ -1,5 +1,12 @@
 <template>
-  <v-circle v-for="p in points" :config="{ ...p, radius: 8, fill: 'red' }" />
+  <v-circle
+    v-for="(p, ndx) in points"
+    :config="{
+      ...p,
+      radius: testPoints[ndx].size || 8,
+      fill: testPoints[ndx].color || 'red',
+    }"
+  />
 </template>
 
 <script lang="ts" setup>

+ 1 - 1
src/core/hook/use-debugger.ts

@@ -4,7 +4,7 @@ import { Pos } from "@/utils/math";
 import { DrawItem } from "../components";
 
 export const useTestPoints = installGlobalVar(() => {
-  return ref<Pos[]>([]);
+  return ref<(Pos & {color?: string, size?: number})[]>([]);
 });
 
 

+ 54 - 6
src/utils/math.ts

@@ -173,8 +173,13 @@ export const lineAndVectorIncludedAngle = (line: Pos[], v: Pos) =>
  * @param line
  * @returns
  */
-export const lineCenter = (line: Pos[]) =>
-  vector(line[0]).add(line[1]).multiplyScalar(0.5);
+export const lineCenter = (line: Pos[]) => {
+  const start = vector(line[0])
+  for (let i = 1; i < line.length; i++) {
+    start.add(line[i])
+  }
+  return start.multiplyScalar(1 / line.length);
+}
 
 
 export const lineSpeed = (line: Pos[], step: number) => {
@@ -366,11 +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)
   return (
-    Math.min(A.x, B.x) <= P.x &&
-    P.x <= Math.max(A.x, B.x) &&
-    Math.min(A.y, B.y) <= P.y &&
-    P.y <= Math.max(A.y, B.y)
+    (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)) 
   );
 };
 
@@ -444,6 +453,45 @@ export const isPolygonLineIntersect = (polygon: Pos[], line: Pos[]) => {
   return false;
 };
 
+
+/**
+ * 判断点是否在多边形内部
+ * @param polygon 多边形顶点数组,按顺时针或逆时针顺序排列
+ * @param pos 要判断的点
+ * @returns 点在多边形内返回true,否则返回false
+ */
+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);
+    
+    if (intersect) {
+      inside = !inside;
+    }
+  }
+  
+  return inside;
+};
+
 /**
  * 通过角度和两个点获取两者的连接点,
  * @param p1