最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - HTML5 Canvas atan2 off by 90 degrees - Stack Overflow

programmeradmin11浏览0评论

I was trying to get the green triangle to rotate about its center and orient itself towards the mouse position. I was able to acplish this, and you can view the full code and result here:

Consider the following lines of code:

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

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

I arbitrarily added Math.PI/2 to my angle calculation because without it, the rotations seemed to be 90 degrees off (by inspection). I want a better understanding of the coordinate system which atan2 is being calculated with respect to so I can justify the reason for offsetting the angle by 90 degrees (and hopefully simplify the code).

EDIT:

To my understanding, Math.atan2 is measuring the angle illustrated in blue. Shouldn't rotating both triangles that blue angle orient it towards the mouse mouse pointer (orange dot) ? Well - obviously not since it's the same angle and they are two different orientations, but I cannot seem to prove this to myself.

I was trying to get the green triangle to rotate about its center and orient itself towards the mouse position. I was able to acplish this, and you can view the full code and result here:

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

Consider the following lines of code:

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

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

I arbitrarily added Math.PI/2 to my angle calculation because without it, the rotations seemed to be 90 degrees off (by inspection). I want a better understanding of the coordinate system which atan2 is being calculated with respect to so I can justify the reason for offsetting the angle by 90 degrees (and hopefully simplify the code).

EDIT:

To my understanding, Math.atan2 is measuring the angle illustrated in blue. Shouldn't rotating both triangles that blue angle orient it towards the mouse mouse pointer (orange dot) ? Well - obviously not since it's the same angle and they are two different orientations, but I cannot seem to prove this to myself.

Share Improve this question edited Mar 6, 2018 at 4:13 Carpetfizz asked Mar 6, 2018 at 0:45 CarpetfizzCarpetfizz 9,20923 gold badges95 silver badges155 bronze badges 7
  • 1 from MDN "This is the counterclockwise angle, measured in radians, between the positive X axis, and the point (x, y)". Your image is rotated so it points on the positive Y axis, hence your need to add Math.PI/2. – Kaiido Commented Mar 6, 2018 at 1:29
  • @Kaiido I'm having a bit of trouble imagining the x-axis in this picture since the canvas coordinate system is different from a normal cartesian coordinate system. Does atan2 somehow "know" where the positive x-axis is? (I highly doubt this but it's the best way to illustrate my concern) – Carpetfizz Commented Mar 6, 2018 at 3:45
  • Does this interactive version of MDN's figure helps you better understand what happens with Math.atan2 method? – Kaiido Commented Mar 6, 2018 at 4:26
  • 1 The canvas coordinates is set to 0, 0 => top left corner, that's why we need to substract h / 2 and w / 2 to mouseY and mouseX, maybe I should edit the fiddle... – Kaiido Commented Mar 6, 2018 at 4:36
  • 1 Here is a version using cartesian coords only, where the canvas' transform matrix has been translated to its center. – Kaiido Commented Mar 6, 2018 at 4:45
 |  Show 2 more ments

4 Answers 4

Reset to default 5

This is because of how the Math.atan2 works.

From MDN:

This is the counterclockwise angle, measured in radians, between the positive X axis, and the point (x, y).

In above figure, the positive X axis is the horizontal segment going from the junction to the right-most position.

To make it clearer, here is an interactive version of this diagram, where x, y values are converted to [-1 ~ 1] values.

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>

So now, if we go back to your image, it currently points to the top (positive Y axis), while the angle you just measured is realtive to the x axis, so it doesn't point where you intended.

Now we know the problem, the solution is quite easy:

  • either apply the + Math.PI / 2 offset to your angle like you did,
  • either modify your original image so that it points to the positive X axis directly.

The coordinate system on canvas works with 0° pointing right. This means anything you want to point "up" must be initially drawn right.

All you need to do in this case is to change this drawing:

to

pointing "up" 0°

and you can strip the math back to what you'd expect it to be.

var ctx = c.getContext("2d"), img = new Image;
img.onload = go; img.src = "https://i.sstatic/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>

When you do arctangents in math class, you're generally dealing with an y-axis that increases going upwards. In most puter graphics systems, however, including canvas graphics, y increases going downward. [erroneous statement deleted]

Edit: I have to admit what I wrote before was wrong for two reasons:

  • A change in the direction of the axis would be pensated for by adding π, not π/2.
  • The canvas context rotate function rotates clockwise for positive angles, and that alone should pensate for the flip of the y-axis.

I played around with a copy of your code in Plunker, and now I realize the 90° rotation simply pensates for the starting orientation of the graphic image you're drawing. If the arrowhead pointed right to start with, instead of straight up, you wouldn't need to add π/2.

I encountered the same problem and was able to achieve the desired result with a following axis 'trick':

// 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);
发布评论

评论列表(0)

  1. 暂无评论