在javascript中找到三次贝塞尔曲线上的点及其角度

问题描述 投票:0回答:1

我需要在三次贝塞尔曲线上找到一个点及其角度,可以使用javascript动态更改该点。 我向chatGPT询问了这个问题,它生成了以下代码,但是角度计算不正确,我在哪里还是chatGPT错了?

      // Initialize with some initial control points
      let points = [
        { x: 50, y: 100 }, // Start point
        { x: 150, y: 50 }, // First control point
        { x: 250, y: 150 }, // Second control point
        { x: 350, y: 100 } // End point
      ];

      function deCasteljau(points, t) {
        if (points.length === 1) {
          return points[0];
        }

        const newPoints = [];
        for (let i = 0; i < points.length - 1; i++) {
          const x = (1 - t) * points[i].x + t * points[i + 1].x;
          const y = (1 - t) * points[i].y + t * points[i + 1].y;
          newPoints.push({ x, y });
        }

        return deCasteljau(newPoints, t);
      }

      function cubicBezierDerivative(points, t) {
        const derivativePoints = [];
        const n = points.length - 1;
        for (let i = 0; i < n; i++) {
            const dx = n * (points[i + 1].x - points[i].x);
            const dy = n * (points[i + 1].y - points[i].y);
            derivativePoints.push({ x: dx, y: dy });
        }
        return derivativePoints;
      }


      function bezierAngle(points, t) {
        const dPoints = cubicBezierDerivative(points, t);
        const point = deCasteljau(points, t);
        const dx = dPoints[0].x;
        const dy = dPoints[0].y;
        const radian = Math.atan2(dy, dx);
        //const angle = radian*180/Math.PI;
        return radian;
      }
      const point = deCasteljau(points, 0.9);
          
      const angle = bezierAngle(points, 0.9);


现场演示:

const canvas = document.getElementById('splineCanvas');
const ctx = canvas.getContext('2d');

let points = []; // Array to hold control points
let selectedPointIndex = -1; // Index of the currently selected control point

// Event listener for mouse down to select control point
canvas.addEventListener('mousedown', function(event) {
    const rect = canvas.getBoundingClientRect();
    const mouseX = event.clientX - rect.left;
    const mouseY = event.clientY - rect.top;

    // Check if mouse is over any control point
    for (let i = 0; i < points.length; i++) {
        const dx = points[i].x - mouseX;
        const dy = points[i].y - mouseY;
        const dist = Math.sqrt(dx * dx + dy * dy);
        if (dist < 6) { // 6 is the radius for selecting control point
            selectedPointIndex = i;
            canvas.addEventListener('mousemove', onMouseMove);
            canvas.addEventListener('mouseup', onMouseUp);
            break;
        }
    }
});

// Event listener for mouse move to update control point position
function onMouseMove(event) {
    const rect = canvas.getBoundingClientRect();
    const mouseX = event.clientX - rect.left;
    const mouseY = event.clientY - rect.top;

    points[selectedPointIndex].x = mouseX;
    points[selectedPointIndex].y = mouseY;
    drawSpline();
}

// Event listener for mouse up to stop updating control point position
function onMouseUp() {
    canvas.removeEventListener('mousemove', onMouseMove);
    canvas.removeEventListener('mouseup', onMouseUp);
    selectedPointIndex = -1;
}

let testAngle = 65;

// Draw spline function
function drawSpline() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.beginPath();
    ctx.moveTo(points[0].x, points[0].y);

    for (let i = 1; i < points.length - 2; i+=3) {
        ctx.bezierCurveTo(
          points[i].x, 
          points[i].y,
          points[i+1].x,
          points[i+1].y,
          points[i+2].x,
          points[i+2].y,
        );
    }

    ctx.stroke();

    // Draw control points
    for (const point of points) {
        ctx.beginPath();
        ctx.arc(point.x, point.y, 6, 0, Math.PI * 2);
        ctx.fillStyle = "#ff0000";
        ctx.fill();
        ctx.closePath();
    }

    const point = deCasteljau(points, 0.9);
    //console.log('point = ', point);
    const angle = bezierAngle(points, 0.9);
    ctx.save();
    ctx.translate(point.x, point.y);
    ctx.rotate(angle);
    ctx.translate(-point.x, -point.y);

    ctx.fillStyle = "green";
    ctx.fillRect(point.x-5, point.y-5, 10, 10);
    ctx.restore();
}

// Initialize with some initial control points
points = [
  { x: 50, y: 100 }, // Start point
  { x: 150, y: 50 }, // First control point
  { x: 250, y: 150 }, // Second control point
  { x: 350, y: 100 } // End point
];

function deCasteljau(points, t) {
  if (points.length === 1) {
    return points[0];
  }

  const newPoints = [];
  for (let i = 0; i < points.length - 1; i++) {
    const x = (1 - t) * points[i].x + t * points[i + 1].x;
    const y = (1 - t) * points[i].y + t * points[i + 1].y;
    newPoints.push({ x, y });
  }

  return deCasteljau(newPoints, t);
}

function cubicBezierDerivative(points, t) {
  const derivativePoints = [];
  const n = points.length - 1;
  for (let i = 0; i < n; i++) {
      const dx = n * (points[i + 1].x - points[i].x);
      const dy = n * (points[i + 1].y - points[i].y);
      derivativePoints.push({ x: dx, y: dy });
  }
  return derivativePoints;
}


function bezierAngle(points, t) {
  const dPoints = cubicBezierDerivative(points, t);
  const point = deCasteljau(points, t);
  const dx = dPoints[0].x;
  const dy = dPoints[0].y;
  const radian = Math.atan2(dy, dx);
  //const angle = radian*180/Math.PI;
  return radian;
}

drawSpline();
<canvas id="splineCanvas" width="600" height="300"></canvas>

javascript html5-canvas curve bezier cubic-bezier
1个回答
0
投票

仅从数学角度来看,我会质疑你在 2D 空间中“找到一个点及其角度”的意思,就像你在点数组中表示的那样,只是

{x, y}
没有角度......

我们可以计算两点之间的角度,我假设这就是您所需要的,我将在此基础上回答下面的问题,而且我将假设

function deCasteljau
返回的点是正确的,不进行研究任何关于如何进行计算的事情。

因此我们可以修改

function bezierAngle
以返回两个给定点之间的角度并传递一个点和我所说的“下一个”点,这样正方形就“面向”正确的方向

const canvas = document.getElementById('splineCanvas');
const ctx = canvas.getContext('2d');

let points = [
  { x: 50, y: 100 }, // Start point
  { x: 150, y: 50 }, // First control point
  { x: 250, y: 150 }, // Second control point
  { x: 350, y: 100 } // End point
]
let selectedPointIndex = -1;

// Event listener for mouse down to select control point
canvas.addEventListener('mousedown', function(event) {
    const rect = canvas.getBoundingClientRect();
    const mouseX = event.clientX - rect.left;
    const mouseY = event.clientY - rect.top;

    // Check if mouse is over any control point
    for (let i = 0; i < points.length; i++) {
        const dx = points[i].x - mouseX;
        const dy = points[i].y - mouseY;
        const dist = Math.sqrt(dx * dx + dy * dy);
        if (dist < 6) { // 6 is the radius for selecting control point
            selectedPointIndex = i;
            canvas.addEventListener('mousemove', onMouseMove);
            canvas.addEventListener('mouseup', onMouseUp);
            break;
        }
    }
});

// Event listener for mouse move to update control point position
function onMouseMove(event) {
    const rect = canvas.getBoundingClientRect();
    const mouseX = event.clientX - rect.left;
    const mouseY = event.clientY - rect.top;

    points[selectedPointIndex].x = mouseX;
    points[selectedPointIndex].y = mouseY;
    drawSpline();
}

// Event listener for mouse up to stop updating control point position
function onMouseUp() {
    canvas.removeEventListener('mousemove', onMouseMove);
    canvas.removeEventListener('mouseup', onMouseUp);
    selectedPointIndex = -1;
}

let testAngle = 65;

// Draw spline function
function drawSpline() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.beginPath();
    ctx.moveTo(points[0].x, points[0].y);

    ctx.bezierCurveTo(
      points[1].x, 
      points[1].y,
      points[2].x,
      points[2].y,
      points[3].x,
      points[3].y,
    );
    ctx.stroke();

    // Draw control points
    for (const point of points) {
        ctx.beginPath();
        ctx.arc(point.x, point.y, 6, 0, Math.PI * 2);
        ctx.fillStyle = "#ff0000";
        ctx.fill();
        ctx.closePath();
    }
   drawRect(0.2)
   drawRect(0.8)
}

function drawRect(x) {
    const point = deCasteljau(points, x);
    const next = deCasteljau(points, x + 0.01);
    const angle = bezierAngle([point, next], x);
    ctx.save();
    ctx.globalAlpha = 0.8
    ctx.translate(point.x, point.y);
    ctx.rotate(angle);
    ctx.translate(-point.x, -point.y);

    ctx.fillStyle = "green";
    ctx.fillRect(point.x-15, point.y-15, 30, 30);
    ctx.restore();
}

function deCasteljau(points, t) {
  if (points.length === 1) {
    return points[0];
  }

  const newPoints = [];
  for (let i = 0; i < points.length - 1; i++) {
    const x = (1 - t) * points[i].x + t * points[i + 1].x;
    const y = (1 - t) * points[i].y + t * points[i + 1].y;
    newPoints.push({ x, y });
  }

  return deCasteljau(newPoints, t);
}

function bezierAngle(points, t) {
  const dx = points[1].x - points[0].x;
  const dy = points[1].y - points[0].y;
  const radian = Math.atan2(dy, dx);
  return radian;
}

drawSpline();
<canvas id="splineCanvas" width="600" height="300"></canvas>

© www.soinside.com 2019 - 2024. All rights reserved.