Przeglądaj źródła

修改道路曲线策略,添加三次曲线选择策略

bill 2 lat temu
rodzic
commit
423361bd4f
2 zmienionych plików z 254 dodań i 42 usunięć
  1. 39 26
      src/graphic/Renderer/Draw.js
  2. 215 16
      src/graphic/Util/MathUtil.js

+ 39 - 26
src/graphic/Renderer/Draw.js

@@ -96,21 +96,19 @@ export default class Draw {
 
 
   drawCurveRoad(vector, isTemp) {
   drawCurveRoad(vector, isTemp) {
     const getStyleLines = () => {
     const getStyleLines = () => {
-      const leftPoints = vector.leftLanes
-        .map(line => line.map(coordinate.getScreenXY.bind(coordinate)))
-        .map(mathUtil.getCurvesByPoints)
+      let leftPoints = vector.leftLanes
+        .map(line => line.map(coordinate.getScreenXY.bind(coordinate)));
+      leftPoints = leftPoints.map(points => mathUtil.getCurvesByPoints(points))
       const rightPoints = vector.rightLanes
       const rightPoints = vector.rightLanes
         .map(line => line.map(coordinate.getScreenXY.bind(coordinate)))
         .map(line => line.map(coordinate.getScreenXY.bind(coordinate)))
-        .map(mathUtil.getCurvesByPoints)
+        .map(points => mathUtil.getCurvesByPoints(points))
 
 
       console.log(dataService.getCurveEdge(vector.rightEdgeId))
       console.log(dataService.getCurveEdge(vector.rightEdgeId))
-      // dataService.getCurveEdge(vector.rightEdgeId)
-      //   dataService.getCurveEdge(vector.leftEdgeId)
 
 
       const styleLines = {
       const styleLines = {
         dotted: [
         dotted: [
-          // ...leftPoints,
-          // ...rightPoints,
+          ...leftPoints,
+          ...rightPoints,
         ],
         ],
         solid: [
         solid: [
           vector.curves.map(line =>
           vector.curves.map(line =>
@@ -122,21 +120,24 @@ export default class Draw {
         ],
         ],
       };
       };
       return styleLines
       return styleLines
-      // return Object.entries(styleLines).reduce((t, [key, lines]) => {
-      //   t[key] = lines.map((line) =>
-      //     line.map((point) => coordinate.getScreenXY(point))
-      //   );
-      //   return t;
-      // }, {});
     };
     };
 
 
     const draw = (curveLines, drawPoints = false) => {
     const draw = (curveLines, drawPoints = false) => {
       if (drawPoints) {
       if (drawPoints) {
+        const curveLines = [
+          ...vector.leftLanes, ...vector.rightLanes,
+          vector.points,
+          dataService.getCurveEdge(vector.rightEdgeId).points,
+          dataService.getCurveEdge(vector.leftEdgeId).points,
+        ]
         const radius = Style.Point.radius * coordinate.ratio;
         const radius = Style.Point.radius * coordinate.ratio;
-        for (const point of curveLines) {
-          ctx.beginPath();
-          ctx.arc(point.x, point.y, radius, 0, 2 * Math.PI);
-          ctx.fill();
+        for (const line of curveLines) {
+          for (let point of line) {
+            point = coordinate.getScreenXY(point)
+            ctx.beginPath();
+            ctx.arc(point.x, point.y, radius, 0, 2 * Math.PI);
+            ctx.fill();
+          }
         }
         }
       }
       }
 
 
@@ -144,16 +145,28 @@ export default class Draw {
       let i = 0;
       let i = 0;
       for (const curve of curveLines) {
       for (const curve of curveLines) {
         ctx.beginPath();
         ctx.beginPath();
-        ctx.bezierCurveTo(
+        ctx.moveTo(
           curve.start.x,
           curve.start.x,
-          curve.start.y,
-          curve.end.x,
-          curve.end.y,
-          curve.control.x,
-          curve.control.y
-        );
+          curve.start.y
+        )
+        if (curve.controls.length === 1) {
+          ctx.quadraticCurveTo(
+            curve.controls[0].x,
+            curve.controls[0].y,
+            curve.end.x,
+            curve.end.y,
+          );
+        } else {
+          ctx.bezierCurveTo(
+            curve.controls[0].x,
+            curve.controls[0].y,
+            curve.controls[1].x,
+            curve.controls[1].y,
+            curve.end.x,
+            curve.end.y,
+          )
+        }
         ctx.strokeStyle = ['red', 'blue'][i++ % curveLines.length]
         ctx.strokeStyle = ['red', 'blue'][i++ % curveLines.length]
-        console.log(['red', 'blue'][i++ % curveLines.length], i++ % curveLines.length)
         ctx.stroke();
         ctx.stroke();
       }
       }
     };
     };

+ 215 - 16
src/graphic/Util/MathUtil.js

@@ -1039,6 +1039,28 @@ export default class MathUtil {
     return v1.x * v2.y - v1.y * v2.x;
     return v1.x * v2.y - v1.y * v2.x;
   }
   }
 
 
+  // 两点相减
+  pointMinus(v1, v2) {
+    return {
+      x: v1.x - v2.x,
+      y: v1.y - v2.y
+    }
+  }
+  // 两点相加
+  pointPlus(v1, v2) {
+    return {
+      x: v1.x + v2.x,
+      y: v1.y + v2.y
+    }
+  }
+  // 点放大
+  pointScale(v, a) {
+    return {
+      x: v.x * a,
+      y: v.y * a
+    }
+  }
+
   clamp(value, min, max) {
   clamp(value, min, max) {
     return Math.max(min, Math.min(max, value));
     return Math.max(min, Math.min(max, value));
   }
   }
@@ -1047,29 +1069,206 @@ export default class MathUtil {
     return v.x * v.x + v.y * v.y;
     return v.x * v.x + v.y * v.y;
   }
   }
 
 
+
+  // 当前点 下一个点 下下个点
+  getCurvesControls(p1, pt, p2, scale = 0.3) {
+    const vec1T = mathUtil.pointMinus(p1, pt);
+    const vecT2 = mathUtil.pointMinus(p1, pt);
+    const len1 = Math.hypot(vec1T.x, vec1T.y);
+    const len2 = Math.hypot(vecT2.x, vecT2.y);
+    const v = len1 / len2;
+
+    let delta;
+    if (v > 1) {
+      delta = mathUtil.pointMinus(
+        p1,
+        mathUtil.pointPlus(pt, mathUtil.pointScale(mathUtil.pointMinus(p2, pt), (1 / v))),
+      );
+    } else {
+      delta = mathUtil.pointMinus(
+        mathUtil.pointPlus(pt, mathUtil.pointScale(mathUtil.pointMinus(p1, pt), v)),
+        p2,
+      );
+    }
+    delta = mathUtil.pointScale(delta, scale);
+
+    const control1 = {
+      x: mathUtil.pointPlus(pt, delta).x,
+      y: mathUtil.pointPlus(pt, delta).y,
+    };
+    const control2 = {
+      x: mathUtil.pointMinus(pt, delta).x,
+      y: mathUtil.pointMinus(pt, delta).y,
+    };
+    return {control1, control2}
+  }
+
   getCurvesByPoints(points, scale = 0.2) {
   getCurvesByPoints(points, scale = 0.2) {
     const curves = [];
     const curves = [];
-    for (let i = 1; i < points.length; i++) {
-      const prev1Index = i - 1
-      const prev2Index = i === 1 ? prev1Index : i - 2;
-      const nextIndex = i === points.length - 1 ? points.length - 1 : i + 1
-      const { x: nowX, y: nowY } = points[i];
-      const { x: last1X, y: last1Y } = points[prev1Index]
-      const { x: last2X, y: last2Y } = points[prev2Index]
-      const { x: nextX, y: nextY } = points[nextIndex]
-      const cAx = last1X + (nowX - last2X) * scale,
-        cAy = last1Y + (nowY - last2Y) * scale,
-        cBx = nowX - (nextX - last1X) * scale,
-        cBy = nowY - (nextY - last1Y) * scale
-
+    let preControl1, preControl2
+    for (let i = 0; i < points.length - 2; i++) {
+      const {control1, control2} = mathUtil.getCurvesControls(
+        points[i],
+        points[i + 1],
+        points[i + 2],
+        scale
+      )
       curves.push({
       curves.push({
-        start: i === 1 ? {x: points[0].x, y: points[0].y} : { x: cAx, y: cAy },
-        control: { x: nowX, y: nowY },
-        end: { x: cBx, y: cBy },
+        start: points[i],
+        end: points[i + 1],
+        controls: i === 0 ? [control1] :  [preControl2, control1]
       })
       })
+      if (i + 2 === points.length - 1) {
+        curves.push({
+          start: points[i + 1],
+          controls: [control2],
+          end: points[i + 2],
+        })
+      }
+      preControl1 = control1
+      preControl2 = control2
     }
     }
+    curves.push({
+      start: points[points.length - 2],
+      controls: [preControl2],
+      end: points[points.length - 1],
+    })
     return curves
     return curves
   }
   }
+
+
+
+  /**
+   * 已知四个控制点,及曲线中的某一个点的 x/y,反推求 t
+   * @param {number} x1 起点 x/y
+   * @param {number} x2 控制点1 x/y
+   * @param {number} x3 控制点2 x/y
+   * @param {number} x4 终点 x/y
+   * @param {number} X 曲线中的某个点 x/y
+   * @returns {number[]} t[]
+   */
+  getThreeBezierT(x1, x2, x3, x4, X) {
+    const a = -x1 + 3 * x2 - 3 * x3 + x4
+    const b = 3 * x1 - 6 * x2 + 3 * x3
+    const c = -3 * x1 + 3 * x2
+    const d = x1 - X
+
+    // 盛金公式, 预先需满足, a !== 0
+    // 判别式
+    const A = Math.pow(b, 2) - 3 * a * c
+    const B = b * c - 9 * a * d
+    const C = Math.pow(c, 2) - 3 * b * d
+    const delta = Math.pow(B, 2) - 4 * A * C
+
+    let t1 = -100, t2 = -100, t3 = -100
+
+    // 3个相同实数根
+    if (A === B && A === 0) {
+      t1 = -b / (3 * a)
+      t2 = -c / b
+      t3 = -3 * d / c
+      return [t1, t2, t3]
+    }
+
+    // 1个实数根和1对共轭复数根
+    if (delta > 0) {
+      const v = Math.pow(B, 2) - 4 * A * C
+      const xsv = v < 0 ? -1 : 1
+
+      const m1 = A * b + 3 * a * (-B + (v * xsv) ** (1 / 2) * xsv) / 2
+      const m2 = A * b + 3 * a * (-B - (v * xsv) ** (1 / 2) * xsv) / 2
+
+      const xs1 = m1 < 0 ? -1 : 1
+      const xs2 = m2 < 0 ? -1 : 1
+
+      t1 = (-b - (m1 * xs1) ** (1 / 3) * xs1 - (m2 * xs2) ** (1 / 3) * xs2) / (3 * a)
+      // 涉及虚数,可不考虑。i ** 2 = -1
+    }
+
+    // 3个实数根
+    if (delta === 0) {
+      const K = B / A
+      t1 = -b / a + K
+      t2 = t3 = -K / 2
+    }
+
+    // 3个不相等实数根
+    if (delta < 0) {
+      const xsA = A < 0 ? -1 : 1
+      const T = (2 * A * b - 3 * a * B) / (2 * (A * xsA) ** (3 / 2) * xsA)
+      const theta = Math.acos(T)
+
+      if (A > 0 && T < 1 && T > -1) {
+        t1 = (-b - 2 * A ** (1 / 2) * Math.cos(theta / 3)) / (3 * a)
+        t2 = (-b + A ** (1 / 2) * (Math.cos(theta / 3) + 3 ** (1 / 2) * Math.sin(theta / 3))) / (3 * a)
+        t3 = (-b + A ** (1 / 2) * (Math.cos(theta / 3) - 3 ** (1 / 2) * Math.sin(theta / 3))) / (3 * a)
+      }
+    }
+    return [t1, t2, t3]
+  }
+
+  /**
+   * @desc 获取三阶贝塞尔曲线的线上坐标
+   * B(t) = P0 * (1-t)^3 + 3 * P1 * t * (1-t)^2 + 3 * P2 * t^2 * (1-t) + P3 * t^3, t ∈ [0,1]
+   * @param {number} t 当前百分比
+   * @param {Array} p1 起点坐标
+   * @param {Array} p2 终点坐标
+   * @param {Array} cp1 控制点1
+   * @param {Array} cp2 控制点2
+   */
+  getThreeBezierPoint(t, p1, cp1, cp2, p2) {
+    const {x: x1, y: y1} = p1
+    const {x: x2, y: y2} = p2
+    const {x: cx1, y: cy1} = cp1
+    const {x: cx2, y: cy2} = cp2
+
+    const x =
+      x1 * (1 - t) * (1 - t) * (1 - t) +
+      3 * cx1 * t * (1 - t) * (1 - t) +
+      3 * cx2 * t * t * (1 - t) +
+      x2 * t * t * t
+    const y =
+      y1 * (1 - t) * (1 - t) * (1 - t) +
+      3 * cy1 * t * (1 - t) * (1 - t) +
+      3 * cy2 * t * t * (1 - t) +
+      y2 * t * t * t
+    return {x, y}
+  }
+
+  isAboveThreeBezier(offsetX, offsetY, curve, rang = 3) {
+    // 用 x 求出对应的 t,用 t 求相应位置的 y,再比较得出的 y 与 offsetY 之间的差值
+    const tsx = mathUtil.getThreeBezierT(curve.start.x, curve.controls[0].x, curve.controls[1].x, curve.end.x, offsetX)
+    for (let x = 0; x < 3; x++) {
+      if (tsx[x] <= 1 && tsx[x] >= 0) {
+        const point = mathUtil.getThreeBezierPoint(tsx[x], curve.start, curve.controls[0], curve.controls[1], curve.end)
+        if (Math.abs(point.y - offsetY) < rang) {
+          return point
+        }
+      }
+    }
+    // 如果上述没有结果,则用 y 求出对应的 t,再用 t 求出对应的 x,与 offsetX 进行匹配
+    const tsy = mathUtil.getThreeBezierT(curve.start.y, curve.controls[0].y, curve.controls[1].y, curve.end.y, offsetY)
+    for (let y = 0; y < 3; y++) {
+      if (tsy[y] <= 1 && tsy[y] >= 0) {
+        const point = mathUtil.getThreeBezierPoint(tsy[y], curve.start, curve.controls[0], curve.controls[1], curve.end)
+        if (Math.abs(point.x - offsetX) < rang) {
+          return point
+        }
+      }
+    }
+  }
+
+  isAboveCurves(pos, curves) {
+    for (const curve of curves) {
+      if (curve.controls.length === 2) {
+        if (mathUtil.isAboveThreeBezier(pos.x, pos.y, curve)) {
+          return true;
+        }
+      } else {
+        // 二次曲线
+      }
+    }
+  }
 }
 }
 
 
 const mathUtil = new MathUtil();
 const mathUtil = new MathUtil();