如何随着形状的旋转和位置的变化移动点

问题描述

我有一个网络应用程序,我在其中绘制一个三角形并在其顶部绘制点以显示它所具有的顶点。 (图 1 在 0 弧度处)

enter image description here

圆形和三角形旋转得很好,只是我似乎无法适当移动的蓝点。当我旋转三角形(连同圆)时,除了红点之外,X 和 Y 不会转换到三角形的任何一个角。 (图 2 在 0.75 弧度处)

enter image description here

整个形状是用以下顶点作为显示点绘制的。

this.transform = ctx.getTransform();
this.boundPoints[0] = { //red point
        x: (this.transform.a + this.x)+(this.radius)* Math.cos(this.rotation),y: (this.transform.d + this.y)+(this.radius)* Math.sin(this.rotation)
}

this.boundPoints[1] = { //blue point
        x:  (this.transform.a + this.x)+(this.radius+ this.range)* Math.cos(this.rotation),y:  (this.transform.d + this.y)+(this.radius)* Math.sin(this.rotation)
    }

我希望发生的事情是

enter image description here

无论它在画布中的位置和旋转如何,该点都保持其相对于三角形的位置。不旋转,我可以把它保持在那里,它的 Y 是

y: (this.transform.d + this.y+this.range)

但现在我无法旋转或移动形状而点不会丢失其位置。 (注意:this.rotation 是以弧度为单位的角度)

解决方法

我跟踪任何形状的所有点的方法是在我的类中创建任何数组,将这些值与您要绘制的实际点分开存储。我将这些存储点主要用于具有已变换/旋转/缩放的奇怪形状的碰撞检测。

如果没有您的代码,很难看出我将如何实现此技术,但这里有一个旋转三角形示例,您可以对其进行缩放和变换,并且始终跟踪点。这个例子还包括一段注释掉的代码,展示了如何使用质心从中心旋转。

this.position 是翻译 this.size 是比例 this.r 是旋转的

let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 300;
canvas.height = 300;

let ptACopy,ptBCopy,ptCCopy;
class Triangle {
  constructor(ptA,ptB,ptC) {
    this.type = "tri";
    this.ptA = ptACopy = ptA;
    this.ptB = ptBCopy = ptB;
    this.ptC = ptCCopy = ptC;
    this.position = { x: 100,y: 100 }; //use this to position
    this.size = { x: 2,y: 1 };
    this.centroid = {
      ox: (this.ptA.x + this.ptB.x + this.ptC.x) / 3,oy: (this.ptA.y + this.ptB.y + this.ptC.y) / 3
    };
    this.c = "red";
    this.a = 0;
    this.r = this.a * (Math.PI / 180);
    this.points = [];
    for (let i = 0; i < 3; i++) {
      this.points.push({ x: 0,y: 0 });
    }
  }
  draw() {
    //updates the points to counter the translating of the canvas to the centroid
   //this is used to rotate from center if wanted
    /*this.ptA = {
      x: ptACopy.x - this.centroid.ox,y: ptACopy.y - this.centroid.oy
    };
    this.ptB = {
      x: ptBCopy.x - this.centroid.ox,y: ptBCopy.y - this.centroid.oy
    };
    this.ptC = {
      x: ptCCopy.x - this.centroid.ox,y: ptCCopy.y - this.centroid.oy
    };*/ 

    let cos = Math.cos(this.r);
    let sin = Math.sin(this.r);
    ctx.save();
    ctx.beginPath();
    ctx.fillStyle = this.c;
    ctx.setTransform(cos * this.size.x,sin * this.size.x,-sin * this.size.y,cos * this.size.y,this.position.x,this.position.y);
    ctx.moveTo(this.ptA.x,this.ptA.y);
    ctx.lineTo(this.ptB.x,this.ptB.y);
    ctx.lineTo(this.ptC.x,this.ptC.y);
    ctx.lineTo(this.ptA.x,this.ptA.y);
    ctx.fill();
    ctx.closePath();
    ctx.restore();
  }
  updateCorners() {
    this.a += 0.1;
    this.r = this.a * (Math.PI / 180);
    let cos = Math.cos(this.r);
    let sin = Math.sin(this.r);
    this.points[0].x =
      this.ptA.x * this.size.x * cos -
      this.ptA.y * this.size.y * sin +
      this.position.x;
    this.points[0].y =
      this.ptA.x * this.size.x * sin +
      this.ptA.y * this.size.y * cos +
      this.position.y;
    this.points[1].x =
      this.ptB.x * this.size.x * cos -
      this.ptB.y * this.size.y * sin +
      this.position.x;
    this.points[1].y =
      this.ptB.x * this.size.x * sin +
      this.ptB.y * this.size.y * cos +
      this.position.y;
    this.points[2].x =
      this.ptC.x * this.size.x * cos -
      this.ptC.y * this.size.y * sin +
      this.position.x;
    this.points[2].y =
      this.ptC.x * this.size.x * sin +
      this.ptC.y * this.size.y * cos +
      this.position.y;
  }
  drawPoints() {
    ctx.fillStyle = "blue";
    this.points.map((x) => {
      ctx.beginPath();
      ctx.arc(x.x,x.y,3,Math.PI * 2);
      ctx.fill();
    });
  }
}
let triangle = new Triangle(
  { x: 10,y: 20 },{ x: 50,y: 60 },{ x: 30,y: 100 }
);

function animate() {
  ctx.clearRect(0,canvas.width,canvas.height);
  triangle.draw();
  triangle.updateCorners();
  triangle.drawPoints();

  requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>

澄清代码中的一些事情: 使用以下代码的目的是为了确保在翻译时准确绘制形状。

this.ptA = {
          x: ptACopy.x - this.centroid.ox,y: ptACopy.y - this.centroid.oy
        };

如果不复制点并尝试做

this.ptA = {
          x: ptA.x - this.centroid.ox,y: ptA.y - this.centroid.oy
        };

然后我会得到未定义,因为我正在尝试使用 ptA 来计算 ptA。

此外,在创建三角形时,如果我希望 ptA 位于 (0,0) 处,那么我可以将其设置在那里,然后使用上述函数偏移三角形以进行旋转。使用它让它围绕 ptA 旋转 20px 半径的示例:

let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 300;
canvas.height = 300;

let ptACopy,y: 100 }; //use this to position
    this.size = { x: 1,y: 0 });
    }
  }
  draw() {
    //updates the points to counter the translating of the canvas to the centroid
    this.ptA = {
      x: ptACopy.x + 20,y: ptACopy.y + 20
    };
    this.ptB = {
      x: ptBCopy.x + 20,y: ptBCopy.y + 20
    };
    this.ptC = {
      x: ptCCopy.x + 20,y: ptCCopy.y + 20
    };
    let cos = Math.cos(this.r);
    let sin = Math.sin(this.r);
    ctx.save();
    ctx.beginPath();
    ctx.fillStyle = this.c;
    ctx.setTransform(cos * this.size.x,this.ptA.y);
    ctx.fill();
    ctx.closePath();
    ctx.fillStyle = 'rgba(0,0.2)';
    ctx.fillRect(0,canvas.height);
    ctx.restore();
  }
  updateCorners() {
    this.a += 0.5;
    this.r = this.a * (Math.PI / 180);
    let cos = Math.cos(this.r);
    let sin = Math.sin(this.r);
    this.points[0].x =
      this.ptA.x * this.size.x * cos -
      this.ptA.y * this.size.y * sin +
      this.position.x;
    this.points[0].y =
      this.ptA.x * this.size.x * sin +
      this.ptA.y * this.size.y * cos +
      this.position.y;
    this.points[1].x =
      this.ptB.x * this.size.x * cos -
      this.ptB.y * this.size.y * sin +
      this.position.x;
    this.points[1].y =
      this.ptB.x * this.size.x * sin +
      this.ptB.y * this.size.y * cos +
      this.position.y;
    this.points[2].x =
      this.ptC.x * this.size.x * cos -
      this.ptC.y * this.size.y * sin +
      this.position.x;
    this.points[2].y =
      this.ptC.x * this.size.x * sin +
      this.ptC.y * this.size.y * cos +
      this.position.y;
  }
  drawPoints() {
    ctx.fillStyle = "blue";
    this.points.map((x) => {
      ctx.beginPath();
      ctx.arc(x.x,Math.PI * 2);
      ctx.fill();
    });
  }
}
let triangle = new Triangle(
  { x: 0,y: 0 },canvas.height);
  triangle.draw();
  triangle.updateCorners();
  triangle.drawPoints();

  requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>

我为画布设置了背景以实现更好的可视化。

更新:

我正在添加一个使用 getTransform() 返回变换矩阵的示例。

然后我们可以使用这些值通过将它们传递给函数来计算每个点。这会稍微缩短类中的代码并使 IMO 看起来更简洁。

let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 400;

let t;

class Rect {
  constructor(x,y,w,h) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.scale = {x: 1,y: 1}
    this.cx = this.x + this.w / 2;
    this.cy = this.y + this.h / 2;
    this.color = "red";
    this.angle = 0;
    this.rotation = (this.angle * Math.PI) / 180;
    this.pts = []
  }
  draw() {
    this.angle += 0.5;
    this.rotation = (this.angle * Math.PI) / 180;
    const cos = Math.cos(this.rotation)
    const sin = Math.sin(this.rotation)
    ctx.save();
    ctx.setTransform(cos * this.scale.x,sin * this.scale.x,-sin * this.scale.y,cos * this.scale.y,this.x,this.y);
    t = ctx.getTransform();
    ctx.fillStyle = this.color;
    ctx.fillRect(-this.w / 2,-this.h / 2,this.w,this.h);
    ctx.restore();
  }
  drawVertices() {
    for (let i=0; i < this.pts.length; i++) {
      ctx.beginPath();
      ctx.fillStyle = "blue";
      ctx.arc(this.pts[i].x,this.pts[i].y,Math.PI * 2);
      ctx.fill();
      ctx.closePath();
    }
  }
  updateVertices() {   
    this.pts[0] = calcVertices(t['a'],t['b'],t['c'],t['d'],t['e'],t['f'],this.cx,this.cy)//top left width and height are passed as 0.
    this.pts[1] = calcVertices(t['a'],this.cy) //top right only passes width. Height is 0.
    this.pts[2] = calcVertices(t['a'],this.h,this.cy) //bottom right passes both wodth and height.
    this.pts[3] = calcVertices(t['a'],this.cy)//bottom left only passes height. Width is 0.
   }
}

let rect1 = new Rect(100,100,50,75);
let rect2 = new Rect(250,150,25);

function calcVertices(a,b,c,d,e,f,h,cx,cy) {
  let x,y;
  x = (e + w - cx) * a + (f + h - cy) * c + (e);
  y = (e + w - cx) * b + (f + h - cy) * d + (f);
  
  return {x: x,y: y}
}

function animate() {
  ctx.clearRect(0,canvas.height);
  rect1.draw();
  rect1.updateVertices();
  rect1.drawVertices();
  rect2.draw();
  rect2.updateVertices();
  rect2.drawVertices();
  requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>

,

为方便起见,我将用复数回答,但每个复数表达式都只能用实数重写。

您没有指定旋转中心(大概是原点),为了一般性,我将假设点 c。要围绕该中心旋转任意点 p,我们应用等式

p' = r.(p - c) + c

其中 r = cos Θ + i sin Θ 实现按角度 Θ 的旋转。

现在要旋转任何形状,假设该形状有一个参考点(比如它的中心或其他一些显着点,例如角),您需要将旋转应用于该参考点,并指定角度 {{1 }} 到形状(如果形状已经分配了角度 Θ,则新角度为 τ。)

如果您还想重新缩放形状,请使用 τ + Θ,其中 r = s (cos Θ + i sin Θ) 是所需的缩放因子,并将此变换应用于参考点。还将因子 s 应用于形状的所有维度参数(或 s 是参数已经应用了因子 t . s。)