图形编辑器基于Paper.js教程12:井身结构编辑器,多条完全平行的弯曲线,使用额外平行线来作为弯曲中心线的度量尺

news2024/11/13 12:29:35

背景

对于弯曲的三条平行线,一开始我以为只需要使用中心线,然后复制两条,一个向右下角平移,一个向左上角平移,就能让三条线实现完全平行,每一处的距离都相等。后来仔细思考后,发现我想错了,因为弯曲处的平行距离是,x移动,y移动的平方根。后来想使用曲线的缩放加上平移来实现三条线段弯曲平行,曲线部分依然无法达到完全平行。

最后请教了ChatGPT,对于曲线的平行线,要使用切线加法线的方式来确定。法线的距离就是平行距离。

具体就是获取曲线部分上的每一个点,然后求出该点的切线向量,然后再求出切线的法线,法线延长平行距离,就能确定平移后的点。

以下是效果图及完整的实现代码。

案例还有一些小问题,就是末尾的封口时,细节没有处理,只是简单地做作x轴,加减。理想情况是,想中心线作垂直线。然后对最外层的线段连线。

附加一个弯曲的中心线度量尺。

垂直的井身

在这里插入图片描述

垂直的井身

<!DOCTYPE html>
<html>

<head>
  <script src="./paper-full.min.js"></script>
  <style>
    canvas {
      width: 900px;
      height: 750px;
      border: 1px solid #ccc;
      display: block;
      margin: 0 auto;
    }
  </style>
</head>

<body>
  <canvas id="myCanvas" resize></canvas>
  <script>
    paper.setup('myCanvas');

    const casingColor = "#1e88e5"
    let initialHandlePointSize = null

    // 创建一个包含直角的路径
    var centerPath = new paper.Path({
      segments: [[0, 0], [0, 500.0]],
      strokeColor: '#ffc107',
      strokeWidth: 1,
      strokeScaling: false,
      // fullySelected: true  // 选中状态,便于查看路径详情
    });
    var length = centerPath.length;
    var quarterPoint2 = centerPath.getPointAt(length * 0.5);
    centerPath.insert(1, quarterPoint2);


    const radius = 15.0
    let innerPath = null
    let outerPath = null

    const casingData = [
      { name: '最内侧套管', color: '#5470c6', key: 'casing1', smallRadius: 15.0, bigRadius: 21.5, length: 400.0 },
      { name: '第二层套管', color: '#92cc76', key: 'casing2', smallRadius: 21.5, bigRadius: 31.5, length: 250.0 },
      { name: '第三层套管', color: '#fac858', key: 'casing3', smallRadius: 31.5, bigRadius: 44.5, length: 150.0 },
      { name: '第四层套管', color: '#ef6666', key: 'casing4', smallRadius: 44.5, bigRadius: 66.5, length: 50.0 }
    ]

    initOutPath()

    function addBaseLine() {
      var center = centerPath.firstSegment.point; // 中心点位置
      var extension = 200.0; // 向左右各扩展100单位

      // 计算起点和终点
      var startPoint = center.subtract([extension, 0]); // 向左扩展
      var endPoint = center.add([extension, 0]); // 向右扩展

      // 创建线段
      var baseLine = new paper.Path({
        name: 'fizzBase',
        segments: [startPoint, endPoint],
        strokeColor: 'black', // 线条颜色
        strokeWidth: 2 // 线条宽度
      });

    }

    function initOutPath() {
      // 创建内侧和外侧路径
      if (outerPath) {
        outerPath.remove()
        outerPath = null
      }
      if (innerPath) {
        innerPath.remove()
        innerPath = null
      }
      outerPath = new paper.Path({
        strokeColor: casingColor,
        strokeWidth: 1,
        // fullySelected: true
      });
      innerPath = new paper.Path({
        strokeColor: casingColor,
        strokeWidth: 1
      });

      const startPoint = centerPath.segments[0].point
      innerPath.add(startPoint.add(new paper.Point(-radius, 0)));
      outerPath.add(startPoint.add(new paper.Point(radius, 0)));

      var startLocationOffset = centerPath.getLocationOf(centerPath.segments[0].point).offset;
      var endLocationOffset = centerPath.getLocationOf(centerPath.lastSegment.point).offset;
      for (let i = startLocationOffset; i < endLocationOffset; i++) {
        const point = centerPath.getPointAt(i)
        // 获取法线向量
        var normal = centerPath.getNormalAt(i);

        // 计算内侧和外侧点的位置
        var innerPoint = point.add(normal.multiply(-radius));
        var outerPoint = point.add(normal.multiply(radius));

        // 将计算出的点添加到相应的路径
        innerPath.add(innerPoint);
        outerPath.add(outerPoint);

      }

      innerPath.add(innerPath.lastSegment.point.add(new paper.Point(radius, 0)));
      outerPath.add(outerPath.lastSegment.point.subtract(new paper.Point(radius, 0)));


      // const endPoint = centerPath.lastSegment.point
      // innerPath.add(endPoint.add(new paper.Point(0, radius)));
      // outerPath.add(endPoint.add(new paper.Point(0, -radius)));

      // 选中状态,便于查看路径详情
      // innerPath.fullySelected = true;
      // outerPath.fullySelected = true;
    }

    // 初始化套管
    function initCasing(arr) {
      for (let i = 0; i < arr.length; i++) {
        const { key, name, smallRadius, bigRadius, length,color } = arr[i];
        const outerCasingPath = new paper.Path({
          name: `name-outer-${key}`,
          strokeColor: color,
          strokeWidth: 1,
          strokeScaling: false
        });
        const innerCasingPath = new paper.Path({
          name: `name-inner-${key}`,
          strokeColor: color,
          strokeWidth: 1,
          strokeScaling: false
        });

        // const startPoint = centerPath.segments[0].point
        // innerPath.add(startPoint.add(new paper.Point(-radius, 0)));
        // outerPath.add(startPoint.add(new paper.Point(radius, 0)));

        var startLocationOffset = centerPath.getLocationOf(centerPath.firstSegment.point).offset;
        var endLocationOffset = centerPath.getLocationOf(centerPath.lastSegment.point).offset;
        for (let i = startLocationOffset; i < length; i++) {
          const point = centerPath.getPointAt(i)
          // 获取法线向量
          var normal = centerPath.getNormalAt(i);

          // 计算内侧和外侧点的位置
          var innerPoint = point.add(normal.multiply(-bigRadius));
          var outerPoint = point.add(normal.multiply(bigRadius));

          // 将计算出的点添加到相应的路径
          innerCasingPath.add(innerPoint);
          outerCasingPath.add(outerPoint);

        }

        innerCasingPath.add(innerCasingPath.lastSegment.point.add(new paper.Point(bigRadius - smallRadius, 0)));
        outerCasingPath.add(outerCasingPath.lastSegment.point.subtract(new paper.Point(bigRadius - smallRadius, 0)));

      }
    }

    function handleRuler({ originalPath, firstPath, secondPath, thirdPath }) {
      var firstPathLength = firstPath.length;
      var secondPathLength = secondPath.length;
      var thirdPathLength = thirdPath.length;

      var point1 = originalPath.segments[0].point;
      var point2 = originalPath.segments[1].point;
      var firstDistance = point1.getDistance(point2);
      var secondDistance = originalPath.segments[1].point.getDistance(originalPath.segments[2].point);
      var thirdDistance = originalPath.segments[1].point.getDistance(originalPath.segments[2].point);

      const firstZm = firstPathLength / firstDistance
      const secondZm = secondPathLength / secondDistance
      const thirdZm = thirdPathLength / thirdDistance


      drawScale(firstPath, firstZm);
      drawScale(secondPath, secondZm, firstDistance);
      drawScale(thirdPath, thirdZm);
    }

    // 更新外侧线位置的函数
    function updateOuterLines(i, delta) {
      if (i === 3) {
        innerPath.lastSegment.point = innerPath.lastSegment.point.add(delta)
        outerPath.lastSegment.point = outerPath.lastSegment.point.add(delta)
      } else if (i === 0) {
        innerPath.firstSegment.point = innerPath.firstSegment.point.add(delta)
        outerPath.firstSegment.point = outerPath.firstSegment.point.add(delta)
      }
    }

    function addHandle() {
      // 计算路径的总长度
      // var length = centerPath.length;

      // // 计算四等分点的位置
      // var quarterPoint1 = centerPath.getPointAt(length * 0.25);
      // var quarterPoint2 = centerPath.getPointAt(length * 0.5);
      // var quarterPoint3 = centerPath.getPointAt(length * 0.75);

      const pointData = {
        radius: 5.0,
        fillColor: 'red',
        data: {
          isHandlePoint: true
        }
      }

      // 在四等分点创建圆点
      var circle1 = new paper.Path.Circle({
        name: "fizz1",
        center: centerPath.segments[0].point.clone(),
        radius: 5.0,
        fillColor: 'red',
        data: {
          isHandlePoint: true
        }
        // ...pointData
      });

      var circle2 = new paper.Path.Circle({
        name: "fizz2",
        center: centerPath.segments[1].point.clone(),
        radius: 5.0,
        fillColor: 'red',
        data: {
          isHandlePoint: true
        }
      });

      var circle3 = new paper.Path.Circle({
        name: "fizz3",
        center: centerPath.segments[2].point.clone(),
        radius: 5.0,
        fillColor: 'red',
        data: {
          isHandlePoint: true
        }
      });
      initialHandlePointSize = circle1.bounds.size.clone();

      // circle1.onMouseDrag = function (event) {
      //   console.log(11)
      //   this.position = event.point;
      //   centerPath.segments[1].point = event.point;
      //   centerPath.segments[0].point.x = event.point.x;
      //   // if (index === 0) {
      //   //   segment.point = segment.point.add(delta);
      //   // }
      //   // if (index === 3) {
      //   //   segment.point = segment.point.add(delta);
      //   // }
      //   // initOutPath()
      //   // // updateOuterLines(index, event.delta);  // 更新外侧线位置
      //   // handle.position = segment.point;  // 确保操作点跟随移动
      // };



      // 为中心线的每个点添加拖动事件
      // centerPath.segments.forEach((segment, index) => {
      //   var handle = new paper.Path.Circle({
      //     center: segment.point,
      //     radius: 3,
      //     strokeWidth: 2,
      //     fillColor: '#92cc76',
      //     strokeColor: '#92cc76'
      //   });

      //   handle.onMouseDrag = function (event) {
      //     const delta = event.delta
      //     if (index === 0) {
      //       segment.point = segment.point.add(delta);
      //     }
      //     if (index === 3) {
      //       segment.point = segment.point.add(delta);
      //     }
      //     initOutPath()
      //     // updateOuterLines(index, event.delta);  // 更新外侧线位置
      //     handle.position = segment.point;  // 确保操作点跟随移动
      //   };
      // });

    }

    // 绘制刻度尺
    function drawScale(path, scale, start = 0) {
      const totalLength = path.length;
      const tickInterval = 20 * scale;
      const majorTickInterval = 100 * scale;
      const tickSize = radius / 2;
      const majorTickSize = 20;

      for (let i = 0; i <= totalLength; i += tickInterval) {
        const point = path.getPointAt(i);
        const normal = path.getNormalAt(i).multiply(tickSize);
        const tickEnd = point.add(normal);

        var tick = new paper.Path.Line({
          from: point,
          to: tickEnd,
          strokeColor: '#cbd5e1'
        });

        if (i % majorTickInterval === 0) {
          tick.strokeWidth = 1;
          tick.strokeColor = "#475569";
          tick.segments[1].point = point.add(normal.multiply(1.5));  // 每到100,增长刻度

          // Add labels for major ticks
          var text = new paper.PointText({
            point: tick.segments[0].point.add(new paper.Point(3, -5)),  // Offset text a bit for visibility
            content: (i / scale + start).toString(),
            fillColor: 'black',
            fontFamily: 'Courier New',
            fontSize: 8
          });
          text.rotate(90, tick.segments[0].point); // Rotate text to align with tick
        }
      }
    }

    function initTool() {
      var hitOptions = {
        segments: true,
        stroke: true,
        fill: true,
        tolerance: 5
      };

      var segment, path;
      var movePath = false;
      const tool = new paper.Tool();

      tool.onMouseDown = (event) => {
        segment = path = null;
        var hitResult = paper.project.hitTest(event.point, hitOptions);
        if (!hitResult)
          return;

        if (event.modifiers.shift) {
          if (hitResult.type == 'segment') {
            hitResult.segment.remove();
          };
          return;
        }

        if (hitResult) {
          path = hitResult.item;
          path.selected = true
          if (hitResult.type == 'segment') {
            segment = hitResult.segment;
          }

        }
      }


      tool.onMouseDrag = (event) => {
        if (segment) {
          segment.point = segment.point.add(event.delta);
          // path.smooth();
        } else if (path) {
          path.position = path.position.add(event.delta);
        }
      }
    }

    paper.view.draw();

    function initMoveTool() {
      const tool = new paper.Tool();
      var lastPoint = null; // 上一次鼠标位置
      var dragging = false;
      var lastViewCenter;

      tool.onMouseDown = (event) => {
        // 当鼠标按下时, 记录当前鼠标位置
        lastPoint = event.point;
        dragging = true;
      };

      tool.onMouseDrag = (event) => {
        if (dragging && lastPoint) {
          lastViewCenter = paper.view.center;
          const delta = lastPoint.subtract(event.point);
          paper.view.center = paper.view.center.add(delta);

          lastPoint = event.point.add(paper.view.center.subtract(lastViewCenter));
        }
      };

      tool.onMouseUp = () => {
        // 结束拖动
        dragging = false;
      };
    }

    function initZoomTool() {
      document.querySelector("#myCanvas").addEventListener("wheel", (event) => {
        event.preventDefault(); // 阻止默认滚动行为
        const delta = event.deltaY > 0 ? -0.02 : 0.02;

        const view = paper.view;
        const oldZoom = view.zoom;
        const newZoom = Math.max(0.1, oldZoom * (1 + delta));
        const mousePosition = new paper.Point(event.offsetX, event.offsetY);
        // 计算缩放前的鼠标位置到视图中心的向量
        const viewPosition = view.viewToProject(mousePosition);
        // 设置新的缩放级别
        view.zoom = newZoom;
        // 重新计算并设置视图中心,以鼠标位置为缩放中心
        const scaledPoint = view.viewToProject(mousePosition);
        const deltaPoint = viewPosition.subtract(scaledPoint);
        view.center = view.center.add(deltaPoint);
      });
    }

    // 重置操作点
    function resetHandlePoint() {
      const handlePoints = paper.project.getItems({
        data: {
          isHandlePoint: true
        }
      });

      handlePoints.forEach(x => {
        x.bounds.size = initialHandlePointSize.divide(paper.view.zoom);
      })
    }

    function fitCanvas() {
      // 获取画布父元素的大小
      var parent = paper.view.element.parentNode
      var rect = parent.getBoundingClientRect();
      const bounds = paper.project.activeLayer.bounds;
      const { width, height } = bounds;

      var widthScale = (rect.width * 0.9) / width;
      var heightScale = (rect.height * 0.9) / height;
      const minScale = Math.min(widthScale, heightScale);
      const currentScale = paper.view.zoom;

      // 重新设置 Paper.js 画布的大小
      paper.view.viewSize = new paper.Size(rect.width, rect.height);
      var scaleDelta = minScale / currentScale;
      paper.view.scale(scaleDelta);
      const center = new paper.Point(width / 2, height / 2);
      paper.view.center = center;

    }

    function initDragHandleTool() {

      var lastPoint = null; // 上一次鼠标位置
      var dragging = false;
      var lastViewCenter;

      var hitOptions = {
        segments: false, // 点
        // stroke: true, // 边
        fill: true, // 内部
        // tolerance: 2,
        tolerance: 2,
        match: (res) => {
          console.log(res)
          return res.item.data.isHandlePoint;
        },
      };


      const tool = new paper.Tool();

      tool.onMouseDown = (event) => {
        path = null;
        var hitResult = paper.project.hitTest(event.point, hitOptions);
        if (!hitResult) {
          lastPoint = event.point;
          dragging = true;
          return;
        }


        if (hitResult) {
          console.log(hitResult)
          path = hitResult.item;
          path.selected = true
        }
      }

      tool.onMouseDrag = (event) => {
        console.log(11)
        if (path) {
          path.position = path.position.add(event.delta);
          // this.position = event.point;
          centerPath.segments[1].point = event.point;
          centerPath.segments[0].point.x = event.point.x;
          return
        }
        if (dragging && lastPoint) {
          lastViewCenter = paper.view.center;
          const delta = lastPoint.subtract(event.point);
          paper.view.center = paper.view.center.add(delta);

          lastPoint = event.point.add(paper.view.center.subtract(lastViewCenter));
        }

      }
      tool.onMouseUp = (event) => {
        path = null
        dragging = false;
      }
    }

    initZoomTool()

    initCasing(casingData)
    addBaseLine()
    // fitCanvas()
    // initMoveTool()
    addHandle()

    initDragHandleTool()

    const rulerPath = centerPath.clone().translate(new paper.Point(-200,0))
    rulerPath.strokeColor = "red"
    drawScale(rulerPath, 1);
  </script>
</body>

</html>

弯曲的井身

在这里插入图片描述

在这里插入图片描述

弯曲的井身


<!DOCTYPE html>
<html>

<head>
  <script src="./paper-full.min.js"></script>
  <style>
    canvas {
      width: 900px;
      height: 750px;
      border: 1px solid #ccc;
      display: block;
      margin: 0 auto;
    }
  </style>
</head>

<body>
  <canvas id="myCanvas" resize></canvas>
  <script>
    paper.setup('myCanvas');

    const casingColor = "#1e88e5"
    let initialHandlePointSize = null

    // 创建一个包含直角的路径
    var centerPath = new paper.Path({
      segments: [[0, 0], [0, 200], [50, 250], [400, 250.0]],
      strokeColor: '#ffc107',
      strokeWidth: 1,
      strokeScaling: false,
      // fullySelected: true  // 选中状态,便于查看路径详情
    });
    centerPath.segments[1].handleOut = new paper.Point(0, 30)
    centerPath.segments[2].handleIn = new paper.Point(-30, 0)

    // var length = centerPath.length;
    // var quarterPoint2 = centerPath.getPointAt(length * 0.5);
    // centerPath.insert(1, quarterPoint2);


    const radius = 15.0
    let innerPath = null
    let outerPath = null

    const casingData = [
      { name: '最内侧套管', color: '#5470c6', key: 'casing1', smallRadius: 15.0, bigRadius: 21.5, length: 400.0 },
      { name: '第二层套管', color: '#92cc76', key: 'casing2', smallRadius: 21.5, bigRadius: 31.5, length: 250.0 },
      { name: '第三层套管', color: '#fac858', key: 'casing3', smallRadius: 31.5, bigRadius: 44.5, length: 150.0 },
      { name: '第四层套管', color: '#ef6666', key: 'casing4', smallRadius: 44.5, bigRadius: 66.5, length: 50.0 }
    ]

    initOutPath()

    function addBaseLine() {
      var center = centerPath.firstSegment.point; // 中心点位置
      var extension = 200.0; // 向左右各扩展100单位

      // 计算起点和终点
      var startPoint = center.subtract([extension, 0]); // 向左扩展
      var endPoint = center.add([extension, 0]); // 向右扩展

      // 创建线段
      var baseLine = new paper.Path({
        name: 'fizzBase',
        segments: [startPoint, endPoint],
        strokeColor: 'black', // 线条颜色
        strokeWidth: 2 // 线条宽度
      });

    }

    function initOutPath() {
      // 创建内侧和外侧路径
      if (outerPath) {
        outerPath.remove()
        outerPath = null
      }
      if (innerPath) {
        innerPath.remove()
        innerPath = null
      }
      outerPath = new paper.Path({
        strokeColor: casingColor,
        strokeWidth: 1,
        // fullySelected: true
      });
      innerPath = new paper.Path({
        strokeColor: casingColor,
        strokeWidth: 1
      });

      const startPoint = centerPath.segments[0].point
      innerPath.add(startPoint.add(new paper.Point(-radius, 0)));
      outerPath.add(startPoint.add(new paper.Point(radius, 0)));

      var startLocationOffset = centerPath.getLocationOf(centerPath.segments[0].point).offset;
      var endLocationOffset = centerPath.getLocationOf(centerPath.lastSegment.point).offset;
      for (let i = startLocationOffset; i < endLocationOffset; i++) {
        const point = centerPath.getPointAt(i)
        // 获取法线向量
        var normal = centerPath.getNormalAt(i);

        // 计算内侧和外侧点的位置
        var innerPoint = point.add(normal.multiply(-radius));
        var outerPoint = point.add(normal.multiply(radius));

        // 将计算出的点添加到相应的路径
        innerPath.add(innerPoint);
        outerPath.add(outerPoint);

      }

      innerPath.add(innerPath.lastSegment.point.add(new paper.Point(radius, 0)));
      outerPath.add(outerPath.lastSegment.point.subtract(new paper.Point(radius, 0)));


      // const endPoint = centerPath.lastSegment.point
      // innerPath.add(endPoint.add(new paper.Point(0, radius)));
      // outerPath.add(endPoint.add(new paper.Point(0, -radius)));

      // 选中状态,便于查看路径详情
      // innerPath.fullySelected = true;
      // outerPath.fullySelected = true;
    }

    // 初始化套管
    function initCasing(arr) {
      for (let i = 0; i < arr.length; i++) {
        const { key, name, smallRadius, bigRadius, length, color } = arr[i];
        const outerCasingPath = new paper.Path({
          name: `name-outer-${key}`,
          strokeColor: color,
          strokeWidth: 1,
          strokeScaling: false
        });
        const innerCasingPath = new paper.Path({
          name: `name-inner-${key}`,
          strokeColor: color,
          strokeWidth: 1,
          strokeScaling: false
        });

        // const startPoint = centerPath.segments[0].point
        // innerPath.add(startPoint.add(new paper.Point(-radius, 0)));
        // outerPath.add(startPoint.add(new paper.Point(radius, 0)));

        var startLocationOffset = centerPath.getLocationOf(centerPath.firstSegment.point).offset;
        var endLocationOffset = centerPath.getLocationOf(centerPath.lastSegment.point).offset;
        for (let i = startLocationOffset; i < length; i++) {
          const point = centerPath.getPointAt(i)
          // 获取法线向量
          var normal = centerPath.getNormalAt(i);

          // 计算内侧和外侧点的位置
          var innerPoint = point.add(normal.multiply(-bigRadius));
          var outerPoint = point.add(normal.multiply(bigRadius));

          // 将计算出的点添加到相应的路径
          innerCasingPath.add(innerPoint);
          outerCasingPath.add(outerPoint);

        }

        innerCasingPath.add(innerCasingPath.lastSegment.point.add(new paper.Point(bigRadius - smallRadius, 0)));
        outerCasingPath.add(outerCasingPath.lastSegment.point.subtract(new paper.Point(bigRadius - smallRadius, 0)));

      }
    }

    function handleRuler({ originalPath, firstPath, secondPath, thirdPath }) {
      var firstPathLength = firstPath.length;
      var secondPathLength = secondPath.length;
      var thirdPathLength = thirdPath.length;

      var point1 = originalPath.segments[0].point;
      var point2 = originalPath.segments[1].point;
      var firstDistance = point1.getDistance(point2);
      var secondDistance = originalPath.segments[1].point.getDistance(originalPath.segments[2].point);
      var thirdDistance = originalPath.segments[1].point.getDistance(originalPath.segments[2].point);

      const firstZm = firstPathLength / firstDistance
      const secondZm = secondPathLength / secondDistance
      const thirdZm = thirdPathLength / thirdDistance


      drawScale(firstPath, firstZm);
      drawScale(secondPath, secondZm, firstDistance);
      drawScale(thirdPath, thirdZm);
    }

    // 更新外侧线位置的函数
    function updateOuterLines(i, delta) {
      if (i === 3) {
        innerPath.lastSegment.point = innerPath.lastSegment.point.add(delta)
        outerPath.lastSegment.point = outerPath.lastSegment.point.add(delta)
      } else if (i === 0) {
        innerPath.firstSegment.point = innerPath.firstSegment.point.add(delta)
        outerPath.firstSegment.point = outerPath.firstSegment.point.add(delta)
      }
    }

    function addHandle() {
      // 计算路径的总长度
      // var length = centerPath.length;

      // // 计算四等分点的位置
      // var quarterPoint1 = centerPath.getPointAt(length * 0.25);
      // var quarterPoint2 = centerPath.getPointAt(length * 0.5);
      // var quarterPoint3 = centerPath.getPointAt(length * 0.75);

      const pointData = {
        radius: 5.0,
        fillColor: 'red',
        data: {
          isHandlePoint: true
        }
      }

      // 在四等分点创建圆点
      var circle1 = new paper.Path.Circle({
        name: "fizz1",
        center: centerPath.segments[0].point.clone(),
        radius: 5.0,
        fillColor: 'red',
        data: {
          isHandlePoint: true
        }
        // ...pointData
      });

      var circle2 = new paper.Path.Circle({
        name: "fizz2",
        center: centerPath.segments[1].point.clone(),
        radius: 5.0,
        fillColor: 'red',
        data: {
          isHandlePoint: true
        }
      });

      var circle3 = new paper.Path.Circle({
        name: "fizz3",
        center: centerPath.segments[2].point.clone(),
        radius: 5.0,
        fillColor: 'red',
        data: {
          isHandlePoint: true
        }
      });
      initialHandlePointSize = circle1.bounds.size.clone();

      // circle1.onMouseDrag = function (event) {
      //   console.log(11)
      //   this.position = event.point;
      //   centerPath.segments[1].point = event.point;
      //   centerPath.segments[0].point.x = event.point.x;
      //   // if (index === 0) {
      //   //   segment.point = segment.point.add(delta);
      //   // }
      //   // if (index === 3) {
      //   //   segment.point = segment.point.add(delta);
      //   // }
      //   // initOutPath()
      //   // // updateOuterLines(index, event.delta);  // 更新外侧线位置
      //   // handle.position = segment.point;  // 确保操作点跟随移动
      // };



      // 为中心线的每个点添加拖动事件
      // centerPath.segments.forEach((segment, index) => {
      //   var handle = new paper.Path.Circle({
      //     center: segment.point,
      //     radius: 3,
      //     strokeWidth: 2,
      //     fillColor: '#92cc76',
      //     strokeColor: '#92cc76'
      //   });

      //   handle.onMouseDrag = function (event) {
      //     const delta = event.delta
      //     if (index === 0) {
      //       segment.point = segment.point.add(delta);
      //     }
      //     if (index === 3) {
      //       segment.point = segment.point.add(delta);
      //     }
      //     initOutPath()
      //     // updateOuterLines(index, event.delta);  // 更新外侧线位置
      //     handle.position = segment.point;  // 确保操作点跟随移动
      //   };
      // });

    }

    // 绘制刻度尺
    function drawScale(path, scale, start = 0) {
      const totalLength = path.length;
      const tickInterval = 20 * scale;
      const majorTickInterval = 100 * scale;
      const tickSize = radius / 2;
      const majorTickSize = 20;

      const rulerPath = new paper.Path({
        name: `rulerPath`,
        strokeColor: 'black',
        strokeWidth: 1,
        strokeScaling: false
      });

      var startLocationOffset = centerPath.getLocationOf(centerPath.firstSegment.point).offset;
      var endLocationOffset = centerPath.getLocationOf(centerPath.lastSegment.point).offset;
      for (let i = startLocationOffset; i < centerPath.length; i++) {
        const point = centerPath.getPointAt(i)
        var normal = centerPath.getNormalAt(i);
        var rulerPathPoint = point.add(normal.multiply(-100));
        rulerPath.add(rulerPathPoint);

        if (i % tickInterval === 0) {
          // const point = rulerPathPoint.clone()
          const normal = centerPath.getNormalAt(i).multiply(tickSize);
          const tickEnd = rulerPathPoint.add(normal);
          var tick = new paper.Path.Line({
            from: rulerPathPoint,
            to: tickEnd,
            strokeColor: '#cbd5e1'
          });

          if (i % majorTickInterval === 0) {
            tick.strokeWidth = 1;
            tick.strokeColor = "#475569";
            // 使用 rulerPathPoint 或 point 大不相同,可以进行调试
            tick.segments[1].point = rulerPathPoint.add(normal.multiply(1.5));  // 每到100,增长刻度

            // Add labels for major ticks
            var text = new paper.PointText({
              point: tick.segments[0].point.add(new paper.Point(3, -5)),  // Offset text a bit for visibility
              content: (i / scale + start).toString(),
              fillColor: 'black',
              fontFamily: 'Courier New',
              fontSize: 8
            });
            text.rotate(90, tick.segments[0].point); // Rotate text to align with tick
          }

        }

      }

      // 在中心线画标尺
      for (let i = 0; i <= totalLength; i += tickInterval) {

        const point = path.getPointAt(i);
        const normal = path.getNormalAt(i).multiply(tickSize);
        const tickEnd = point.add(normal);

        var tick = new paper.Path.Line({
          from: point,
          to: tickEnd,
          strokeColor: '#cbd5e1'
        });

        if (i % majorTickInterval === 0) {
          tick.strokeWidth = 1;
          tick.strokeColor = "#475569";
          tick.segments[1].point = point.add(normal.multiply(1.5));  // 每到100,增长刻度

          // Add labels for major ticks
          var text = new paper.PointText({
            point: tick.segments[0].point.add(new paper.Point(3, -5)),  // Offset text a bit for visibility
            content: (i / scale + start).toString(),
            fillColor: 'black',
            fontFamily: 'Courier New',
            fontSize: 8
          });
          text.rotate(90, tick.segments[0].point); // Rotate text to align with tick
        }

      }

    }

    function initTool() {
      var hitOptions = {
        segments: true,
        stroke: true,
        fill: true,
        tolerance: 5
      };

      var segment, path;
      var movePath = false;
      const tool = new paper.Tool();

      tool.onMouseDown = (event) => {
        segment = path = null;
        var hitResult = paper.project.hitTest(event.point, hitOptions);
        if (!hitResult)
          return;

        if (event.modifiers.shift) {
          if (hitResult.type == 'segment') {
            hitResult.segment.remove();
          };
          return;
        }

        if (hitResult) {
          path = hitResult.item;
          path.selected = true
          if (hitResult.type == 'segment') {
            segment = hitResult.segment;
          }

        }
      }


      tool.onMouseDrag = (event) => {
        if (segment) {
          segment.point = segment.point.add(event.delta);
          // path.smooth();
        } else if (path) {
          path.position = path.position.add(event.delta);
        }
      }
    }

    paper.view.draw();

    function initMoveTool() {
      const tool = new paper.Tool();
      var lastPoint = null; // 上一次鼠标位置
      var dragging = false;
      var lastViewCenter;

      tool.onMouseDown = (event) => {
        // 当鼠标按下时, 记录当前鼠标位置
        lastPoint = event.point;
        dragging = true;
      };

      tool.onMouseDrag = (event) => {
        if (dragging && lastPoint) {
          lastViewCenter = paper.view.center;
          const delta = lastPoint.subtract(event.point);
          paper.view.center = paper.view.center.add(delta);

          lastPoint = event.point.add(paper.view.center.subtract(lastViewCenter));
        }
      };

      tool.onMouseUp = () => {
        // 结束拖动
        dragging = false;
      };
    }

    function initZoomTool() {
      document.querySelector("#myCanvas").addEventListener("wheel", (event) => {
        event.preventDefault(); // 阻止默认滚动行为
        const delta = event.deltaY > 0 ? -0.02 : 0.02;

        const view = paper.view;
        const oldZoom = view.zoom;
        const newZoom = Math.max(0.1, oldZoom * (1 + delta));
        const mousePosition = new paper.Point(event.offsetX, event.offsetY);
        // 计算缩放前的鼠标位置到视图中心的向量
        const viewPosition = view.viewToProject(mousePosition);
        // 设置新的缩放级别
        view.zoom = newZoom;
        // 重新计算并设置视图中心,以鼠标位置为缩放中心
        const scaledPoint = view.viewToProject(mousePosition);
        const deltaPoint = viewPosition.subtract(scaledPoint);
        view.center = view.center.add(deltaPoint);
      });
    }

    // 重置操作点
    function resetHandlePoint() {
      const handlePoints = paper.project.getItems({
        data: {
          isHandlePoint: true
        }
      });

      handlePoints.forEach(x => {
        x.bounds.size = initialHandlePointSize.divide(paper.view.zoom);
      })
    }

    function fitCanvas() {
      // 获取画布父元素的大小
      var parent = paper.view.element.parentNode
      var rect = parent.getBoundingClientRect();
      const bounds = paper.project.activeLayer.bounds;
      const { width, height } = bounds;

      var widthScale = (rect.width * 0.9) / width;
      var heightScale = (rect.height * 0.9) / height;
      const minScale = Math.min(widthScale, heightScale);
      const currentScale = paper.view.zoom;

      // 重新设置 Paper.js 画布的大小
      paper.view.viewSize = new paper.Size(rect.width, rect.height);
      var scaleDelta = minScale / currentScale;
      paper.view.scale(scaleDelta);
      const center = new paper.Point(width / 2, height / 2);
      paper.view.center = center;

    }

    function initDragHandleTool() {

      var lastPoint = null; // 上一次鼠标位置
      var dragging = false;
      var lastViewCenter;

      var hitOptions = {
        segments: false, // 点
        // stroke: true, // 边
        fill: true, // 内部
        // tolerance: 2,
        tolerance: 2,
        match: (res) => {
          console.log(res)
          return res.item.data.isHandlePoint;
        },
      };


      const tool = new paper.Tool();

      tool.onMouseDown = (event) => {
        path = null;
        var hitResult = paper.project.hitTest(event.point, hitOptions);
        if (!hitResult) {
          lastPoint = event.point;
          dragging = true;
          return;
        }


        if (hitResult) {
          console.log(hitResult)
          path = hitResult.item;
          path.selected = true
        }
      }

      tool.onMouseDrag = (event) => {
        console.log(11)
        if (path) {
          path.position = path.position.add(event.delta);
          // this.position = event.point;
          centerPath.segments[1].point = event.point;
          centerPath.segments[0].point.x = event.point.x;
          return
        }
        if (dragging && lastPoint) {
          lastViewCenter = paper.view.center;
          const delta = lastPoint.subtract(event.point);
          paper.view.center = paper.view.center.add(delta);

          lastPoint = event.point.add(paper.view.center.subtract(lastViewCenter));
        }

      }
      tool.onMouseUp = (event) => {
        path = null
        dragging = false;
      }
    }

    initZoomTool()

    initCasing(casingData)
    addBaseLine()
    // fitCanvas()
    // initMoveTool()
    // addHandle()

    initDragHandleTool()

    drawScale(centerPath, 1);
  </script>
</body>

</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1966045.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据管道为什么选择Kafka作为消息队列?

目录 关于Kafka 什么是消息队列&#xff1f; Kafka的特点 管道为什么需要消息队列&#xff1f; 管道任务为什么选择Kafka作为消息队列&#xff1f; 企业在构建数仓和中间库时&#xff0c;由于业务数据量级较大&#xff0c;如果使用批量定时同步数据的方式很难做到高性能的增量同…

文章解读与仿真程序复现思路——电力系统自动化EI\CSCD\北大核心《考虑隐私保护的虚拟电厂内部交易决策优化 》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

食源送系统项目的测试

一、对整个系统编写测试用例 功能测试 性能测试 兼容性测试 易用性测试 安全测试 二、接口测试 针对接口的功能测试&#xff0c;也就是检验接口是否按照接口文档输入输出 2.1 使用Postman发送HTTP请求 2.2 使用Java TestNG 编写自动化测试用例 登录界面功能 package com.sky.…

碳化硅陶瓷膜的机械强度

碳化硅陶瓷膜是一种高性能的过滤材料&#xff0c;它采用重结晶技术在高温条件下烧结而成。这种膜的特点是整个结构&#xff0c;包括多孔支撑层、过渡层和膜层&#xff0c;均由碳化硅(SiC)材料构成。碳化硅陶瓷膜因其独特的性能而在多个领域得到广泛应用&#xff0c;下面是对碳化…

【面试题解答】一个有序数组 nums ,原地删除重复出现的元素

面试题解答 仅供学习 文章目录 面试题解答题目一、python代码1.1 代码1.2 示例用法1.2.1 示例11.2.2 示例2 二、讲解2.1 初始化2.2 遍历2.3 返回 题目 要解决这个问题&#xff0c;可以使用双指针方法进行原地修改&#xff0c;以确保每个元素最多出现两次。 一、python代码 1.1…

文件上传漏洞大总结:原理与复现

文章目录 原理f8x靶场安装文件上传漏洞前端验证概念步骤&#xff1a; 上传特殊可解析后缀概念步骤 ::$DATA绕过概念主要流类型 点空格绕过概念代码审计**步骤&#xff1a;** 文件类型检测概念常见的文件类型 过程 文件头检测概念过程 黑名单绕过概念特殊文件爆破常用的文件名过…

电商 API 接口的最佳实践与案例分析

在当今数字化的商业世界中&#xff0c;电商平台的发展日新月异&#xff0c;而 API 接口在其中扮演着至关重要的角色。通过合理地利用电商 API 接口&#xff0c;企业能够实现更高效的运营、更优质的用户体验以及更强大的业务拓展能力。本文将深入探讨电商 API 接口的最佳实践&am…

开源AI智能名片O2O商城微信小程序:利用超级赠品与厌恶损失心理促进行动转化的策略研究

摘要&#xff1a;在数字化转型的浪潮中&#xff0c;企业如何利用创新技术提升顾客转化率成为了关键议题。开源AI智能名片O2O商城微信小程序作为新兴营销工具&#xff0c;凭借其智能化、便捷性和个性化服务&#xff0c;为企业开辟了新的营销路径。本文聚焦于如何通过超级赠品与厌…

QQ 腾讯官方机器人搭建(更新中)

前言 QQ机器人通过开放的平台承载机器人的定制化功能&#xff0c;让开发者获得更畅快的开发体验。 以下是接入流程&#xff1a; 本文提供QQ机器人使用指南。 文章目录 前言开发前准备工作使用机器人控制台配置gpt—API获取本地公网ip配置机器人 开发前准备工作 首先前往Q…

高效可靠安全的大文件传输系统,了解一下

在数字化转型的浪潮中&#xff0c;数据已成为企业不可或缺的宝贵资源&#xff0c;而高效的文件传输机制则是保障数据流通的基石。无论是企业还是个人&#xff0c;经常需要处理大文件、远距离文件的传输任务&#xff0c;需要文件传输系统来处理。 在远距离传输大型文件时&#x…

一个简洁、高效、可扩展的企业级低代码开发平台,前后端分离,支持国密加密(附源码)

前言 在当今数字化转型的浪潮中&#xff0c;企业面临着快速开发和部署应用的挑战。传统的开发模式往往因为其繁琐的流程、高昂的成-本和僵化的架构而无法满足市场需求。开发者常常受限于复杂的系统配置、安全问题、以及不同数据库的兼容性问题。这些痛点不仅拖慢了开发进度&am…

字符串并查集:1061. 按字典序排列最小的等效字符串和990. 等式方程的可满足性

文章目录 题目一&#xff1a;1061. 按字典序排列最小的等效字符串题目二&#xff1a;990. 等式方程的可满足性 题目一&#xff1a;1061. 按字典序排列最小的等效字符串 1061. 按字典序排列最小的等效字符串 并查集使用整数&#xff0c;字符自然和整数有一个映射&#xff0c;A…

手把手教你用家用电脑完成图片生成卡通动漫风格

一. 效果图 二.animegan2-pytorch 介绍 animegan2-pytorch 是可以将图片转成卡通动漫形式的一个工程。 首先感谢作者开源&#xff0c;respect&#xff01;respect&#xff01;respect&#xff01; animegan2-pytorch地址&#xff1a;bryandlee/animegan2-pytorch: PyTorch impl…

【云原生】Kubernetes中关于污点、亲和性和容忍度的详细用法教程与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

在峡江的转弯处:陈行甲人生笔记 读书笔记

书籍信息 在峡江的转弯处&#xff1a;陈行甲人生笔记 书名&#xff1a; 在峡江的转弯处&#xff1a;陈行甲人生笔记作者&#xff1a; 陈行甲简介&#xff1a; 《在峡江的转弯处&#xff1a;陈行甲人生笔记》是陈行甲的自传体随笔&#xff0c;从童年岁月写起&#xff0c;写母亲…

html+css+js hover流光效果按钮

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽效果&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 文…

记录某次“有趣的“挖矿木马排查

挖矿木马是什么&#xff1f; 挖矿木马是一种恶意软件&#xff0c;它在用户不知情或未经同意的情况下&#xff0c;利用受害者的计算机资源进行加密货币挖矿。这类软件通过执行大量运算来挖掘数字货币&#xff0c;如比特币或门罗币等。挖矿木马通常通过漏洞利用、弱口令爆破或非…

探索电商 API 接口的创新应用与接入技巧

在当今数字化的商业环境中&#xff0c;电商 API 接口已成为推动业务增长和创新的关键因素。它们不仅为企业提供了与电商平台高效交互的途径&#xff0c;还开启了无数创新应用的可能性。本文将深入探讨电商 API 接口的创新应用&#xff0c;并分享一些实用的接入技巧&#xff0c;…

python---为某个项目使用虚拟环境

目录 为什么要为项目建立虚拟环境建立步骤打开终端&#xff08;Terminal&#xff09;进入项目文件夹所在目录该目录下创建虚拟环境激活新建的虚拟环境安装本项目所需要的库 过程完整截图未来再次使用本虚拟环境先导航到项目目录然后激活虚拟环境 为什么要为项目建立虚拟环境 为…

Excel 通过函数实现数据透视表

函数技巧演示系列。深入介绍 Excel 函数的使用技巧。 本篇通过函数的方式&#xff0c;实现数据透视表&#xff0c;部分函数需要 Office 2021版本或者 Office 365 版。 示例数据 要求&#xff1a;基于产品名称&#xff08;E列&#xff09;和销售渠道&#xff08;G列&#xff09…