【问题标题】:HTML5 Canvas atan2 off by 90 degreesHTML5 Canvas atan2 倾斜 90 度
【发布时间】:2018-08-13 18:34:52
【问题描述】:

我试图让绿色三角形围绕其中心旋转并将其自身定位到鼠标位置。我能够做到这一点,您可以在此处查看完整的代码和结果:

https://codepen.io/Carpetfizz/project/editor/DQbEVe

考虑以下代码行:

  r = Math.atan2(mouseY - centerY, mouseX - centerX)

  ctx.rotate(r + Math.PI/2)

我在我的角度计算中任意添加了Math.PI/2,因为没有它,旋转似乎偏离了 90 度(通过检查)。我想更好地了解计算 atan2 所依据的坐标系,以便我可以证明将角度偏移 90 度的原因(并希望简化代码)。

编辑:

据我了解,Math.atan2 正在测量蓝色所示的角度。不应该旋转两个蓝色角度的三角形,使其朝向鼠标指针(橙色点)?嗯 - 显然不是,因为它是同一个角度并且它们是两个不同的方向,但我似乎无法向自己证明这一点。

【问题讨论】:

  • from MDN "这是正 X 轴 和点 (x, y) 之间的逆时针角度,以弧度为单位。您的图像已旋转,因此它指向正 Y 轴,因此您需要添加 Math.PI/2
  • @Kaiido 我在想象这张图片中的 x 轴时遇到了一些麻烦,因为画布坐标系与普通的笛卡尔坐标系不同。 atan2 是否以某种方式“知道”正 x 轴在哪里? (我非常怀疑这一点,但这是说明我的担忧的最佳方式)
  • MDN 的这个interactive version 是否有助于您更好地理解 Math.atan2 方法会发生什么?
  • 画布坐标设置为 0, 0 => 左上角,这就是为什么我们需要将 h / 2 和 w / 2 减去 mouseY 和 mouseX,也许我应该编辑小提琴......
  • 这里是a version,仅使用笛卡尔坐标,画布的变换矩阵已被转换到其中心。

标签: javascript html canvas html5-canvas


【解决方案1】:

我遇到了同样的问题,并且能够通过以下轴“技巧”达到预期的结果:

// Default usage (works fine if your image / shape points to the RIGHT)
let angle = Math.atan2(delta_y, delta_x); 

// 'Tricky' usage (works fine if your image / shape points to the LEFT)
let angle = Math.atan2(delta_y, -delta_x); 

// 'Tricky' usage (works fine if your image / shape points to the BOTTOM)
let angle = Math.atan2(delta_x, delta_y);

// 'Tricky' usage (works fine if your image / shape points to the TOP)
let angle = Math.atan2(delta_x, -delta_y);

【讨论】:

    【解决方案2】:

    这是因为Math.atan2 的工作原理。

    From MDN:

    这是正 X 轴和点 (x, y) 之间的逆时针角度,以弧度为单位。

    在上图中,正X轴是从路口到最右边的水平线段。

    为了更清楚,这里是这个图表的一个交互式版本,其中 x、y 值被转换为 [-1 ~ 1] 值。

    const ctx = canvas.getContext('2d'),
      w = canvas.width,
      h = canvas.height,
      radius = 0.3;
    
    ctx.textAlign = 'center';
    canvas.onmousemove = canvas.onclick = e => {
      // offset mouse values so they are relative to the center of our canvas
      draw(as(e.offsetX), as(e.offsetY));
    }
    
    draw(0, 0);
    
    function draw(x, y) {
      clear();
      drawCross();
      drawLineToPoint(x, y);
      drawPoint(x, y);
    
      const angle = Math.atan2(y, x);
      drawAngle(angle);
      writeAngle(angle);
    }
    
    function clear() {
      ctx.clearRect(0, 0, w, h);
    }
    
    function drawCross() {
      ctx.lineWidth = 1;
      ctx.beginPath();
      ctx.moveTo(s(0), s(-1));
      ctx.lineTo(s(0), s(1));
      ctx.moveTo(s(-1), s(0));
      ctx.lineTo(s(0), s(0));
      ctx.strokeStyle = ctx.fillStyle = '#2e404f';
      ctx.stroke();
      // positive X axis
      ctx.lineWidth = 3;
      ctx.beginPath();
      ctx.moveTo(s(0), s(0));
      ctx.lineTo(s(1), s(0));
      ctx.stroke();
      ctx.lineWidth = 1;
      ctx.font = '20px/1 sans-serif';
      ctx.fillText('+X', s(1) - 20, s(0) - 10);
    }
    
    function drawPoint(x, y) {
      ctx.beginPath();
      ctx.arc(s(x), s(y), 10, 0, Math.PI * 2);
      ctx.fillStyle = 'red';
      ctx.fill();
      ctx.font = '12px/1 sans-serif';
      ctx.fillText(`x: ${x.toFixed(2)} y: ${y.toFixed(2)}`, s(x), s(y) - 15);
    }
    
    function drawLineToPoint(x, y) {
      ctx.beginPath();
      ctx.moveTo(s(0), s(0));
      ctx.lineTo(s(x), s(y));
      ctx.strokeStyle = 'red';
      ctx.setLineDash([5, 5]);
      ctx.stroke();
      ctx.setLineDash([0]);
    }
    
    function drawAngle(angle) {
      ctx.beginPath();
      ctx.moveTo(s(radius), s(0));
      ctx.arc(s(0), s(0), radius * w / 2,
        0, // 'arc' method also starts from positive X axis (3 o'clock)
        angle,
        true // Math.atan2 returns the anti-clockwise angle
      );
      ctx.strokeStyle = ctx.fillStyle = 'blue';
      ctx.stroke();
      ctx.font = '20px/1 sans-serif';
      ctx.fillText('∂: ' + angle.toFixed(2), s(0), s(0));
    }
    
    // below methods will add the w / 2 offset
    // because canvas coords set 0, 0 at top-left corner
    
    // converts from [-1 ~ 1] to px
    function s(value) {
      return value * w / 2 + (w / 2);
    }
    // converts from px to [-1 ~ 1]
    function as(value) {
      return (value - w / 2) / (w / 2);
    }
    <canvas id="canvas" width="500" height="500"></canvas>

    所以现在,如果我们回到您的图像,它当前指向顶部(正 Y 轴),而您刚刚测量的角度与 x 轴无关,所以它不会t 指向您想要的位置。

    现在我们知道了问题所在,解决方案很简单:

    • 要么像以前那样将+ Math.PI / 2 偏移量应用于您的角度,
    • 修改原始图像,使其直接指向正 X 轴。

    【讨论】:

      【解决方案3】:

      画布上的坐标系使用 0° 指向。这意味着您想要“向上”指向的任何内容最初都必须正确绘制。

      在这种情况下,您需要做的就是更改此绘图:

      指向“向上”0°

      您可以将数学剥离回您期望的样子。

      var ctx = c.getContext("2d"), img = new Image;
      img.onload = go; img.src = "https://i.stack.imgur.com/Yj9DU.jpg";
      
      function draw(pos) {
        var cx = c.width>>1, 
            cy = c.height>>1,
            angle = Math.atan2(pos.y - cy, pos.x - cx);
            
        ctx.setTransform(1,0,0,1,cx, cy);
        ctx.rotate(angle);
        ctx.drawImage(img, -img.width>>1, -img.height>>1);
      }
      
      function go() {
        ctx.globalCompositeOperation = "copy";
        window.onmousemove = function(e) {draw({x: e.clientX, y: e.clientY})}
      }
      html, body {margin:0;background:#ccc}
      #c {background:#fff}
      <canvas id=c width=600 height=600></canvas>

      【讨论】:

      • 我必须同意@Carpetfizz,我也不相信这种解释。转换矩阵如何指向任何地方?这是什么意思? Math.atan2 方法确实返回了向量和正 X 轴之间的角度,但画布变换与这里无关。有些方法确实也使用正 X 轴,例如 arc(),但再一次,与 transfrom 矩阵关系不大。 (但是打高尔夫球的荣誉;-))
      【解决方案4】:

      当你在数学课上做反正切时,你通常会处理一个向上增加的 y 轴。然而,在大多数计算机图形系统中,包括画布图形,y 会向下增加。 [删除错误陈述]

      编辑:我不得不承认我之前写的内容是错误的,原因有两个:

      • 将通过添加 π 而不是 π/2 来补偿轴方向的变化。
      • canvas 上下文 rotate 函数顺时针旋转正角度,仅此一项就可以补偿 y 轴的翻转。

      我在 Plunker 中使用了您的代码副本,现在我意识到 90° 旋转只是补偿了您正在绘制的图形图像的起始方向。如果箭头指向右边而不是直接向上,则不需要添加 π/2。

      【讨论】:

      • 你的意思是y轴吗?
      • 谢谢。那么,有没有办法让我的测量值避免增加 90 度?
      • 你可以试试Math.atan2(centerY - mouseY, mouseX - centerX)
      • 我已经尝试过了,不会产生预期的行为
      • 我会先自己尝试一下,但你的 codepen 是只读的。
      猜你喜欢
      • 1970-01-01
      • 2014-06-14
      • 2023-03-23
      • 2018-10-25
      • 2016-02-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多