0

I have working code to find the x,y at a given angle on an ellipse :

getPointOnEllipse(origin_x, origin_y, radius_x, radius_y, angle) {

  var r_end_angle = end_angle / 360 * 2 * Math.PI;
  var x        = origin_x + Math.cos(r_end_angle) * radius_x;
  var y        = origin_y + Math.sin(r_end_angle) * radius_y;

  return {x: x, y: y}

My question is how do I do this calculation if the ellipse is rotated at angle r, as if created by:

ellipse(origin_x, origin_y, radius_x, radius_y, r);

Update: I tried the suggestion below, and it didn't quite work. Here's the original (0) rotation:

Original Orientation

And here's after approximately 90-degree rotation:

After approximately 90-degree rotation

Here's the code I tried:

/* 
* origin_x - Center of the Ellipse X
* origin_y - Center of the Ellipse Y
* radius_x - Radius X
* radius_y - Radius Y
* angle    - angle along the ellipse on which to place the handle
* rotation - Angle which the ellipse is rotated
*/
getPointOnEllipse(origin_x, origin_y, radius_x, radius_y, angle, rotation) {
 var r_end_angle = (angle + rotation) / 360 * 2 * Math.PI;
 var endX        = origin_x + Math.cos(r_end_angle) * radius_x;
 var endY        = origin_y + Math.sin(r_end_angle) * radius_y;

 var cosA  = Math.cos(rotation);
 var sinA  = Math.sin(rotation);
 var dx    = endX - origin_x;
 var dy    = endY - origin_y;

 rotated_x = origin_x + dx * cosA - dy * sinA;
 rotated_y = origin_y + dx * sinA + dy * cosA;

Here's some logging:

X 369, Y 233, radiusX 104, radiusY 17, end_angle 0, rotation 0, endX 473, endY 233, cosA 1, sinA 0, dx 104, dy 0, rotated_x 473, rotated_y 233

X 369, Y 233, radiusX 104, radiusY 17, end_angle 90, rotation 0, endX 369, endY 250, cosA 1, sinA 0, dx 0, dy 17, rotated_x 369, rotated_y 250

X 369, Y 233, radiusX 104, radiusY 17, end_angle 180, rotation 0, endX 265, endY 233, cosA 1, sinA 0, dx -104, dy 0, rotated_x 265, rotated_y 233

X 369, Y 233, radiusX 104, radiusY 17, end_angle 270, rotation 0, endX 369, endY 216, cosA 1, sinA 0, dx 0, dy -17, rotated_x 369, rotated_y 216

Here after a 90-degree rotation the points don't seem to end up on the ellipse:

X 369, Y 233, radiusX 104, radiusY 17, end_angle 0, rotation 96.40608527543233, endX 357.396254311691, endY 249.89385326910204, cosA -0.5542897094655916, sinA 0.8323238059676955, dx -11.603745688309004, dy 16.89385326910204, rotated_x 361.3706805758866, rotated_y 213.97783720494053

X 369, Y 233, radiusX 104, radiusY 17, end_angle 90, rotation 96.40608527543233, endX 265.6493682360816, endY 231.10323387787258, cosA -0.5542897094655916, sinA 0.8323238059676955, dx -103.35063176391839, dy -1.896766122127417, rotated_x 427.86491525130737, rotated_y 148.03016676384783

X 369, Y 233, radiusX 104, radiusY 17, end_angle 180, rotation 96.40608527543233, endX 380.603745688309, endY 216.10614673089796, cosA -0.5542897094655916, sinA 0.8323238059676955, dx 11.603745688309004, dy -16.89385326910204, rotated_x 376.6293194241134, rotated_y 252.02216279505947

X 369, Y 233, radiusX 104, radiusY 17, end_angle 270, rotation 96.40608527543233, endX 472.35063176391833, endY 234.89676612212745, cosA -0.5542897094655916, sinA 0.8323238059676955, dx 103.35063176391833, dy 1.8967661221274454, rotated_x 310.1350847486927, rotated_y 317.969833236

I'm sure I got something wrong here - any ideas?

Michael Wilson
  • 800
  • 8
  • 15
  • Can you specify what is `end_angle` and why you are using `end_angle + rotation` to calculate point on the ellipse? It is confusing in your function argument name is `angle` but you use some `end_angle` var. Also the way you use rotation first time implies it is in degrees but it should be radians for rotate around point math – Alex Aug 06 '19 at 09:03
  • Sorry, I had editted the first example but not the second. I will update the second one for clarity. Sorry! – Michael Wilson Aug 06 '19 at 12:59

2 Answers2

2

You actually may not need to calculate this position.

The canvas API offers means to control the current transformation matrix of your context.
In many cases, this is way more convenient to embrace this than to calculate everything yourself.

For instance, your example places the four squares relatively to the ellipse own transformation. So what you need to do, is to first set your transformation matrix to this ellipse's position, and then to only move it by the relative position of each squares.

Here is a fast written example:

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

class Shape {
  constructor(cx, cy, parent) {
    this.cx = cx;
    this.cy = cy;
    this.parent = parent;
    this.path = new Path2D();
    this.angle = 0;
    this.color = "black";
  }
  applyTransform() {
    if (this.parent) { // recursively apply all the transforms
      this.parent.applyTransform();
    }
    ctx.transform(1, 0, 0, 1, this.cx, this.cy);
    ctx.rotate(this.angle);
  }
}

const rad_x = (canvas.width / 1.3) / 2;
const rad_y = (canvas.height / 1.3) / 2;

class Rect extends Shape {
  constructor(dx, dy, parent) {
    super(rad_x * dx, rad_y * dy, parent);
    this.path.rect(-5, -5, 10, 10);
    Object.defineProperty(this, 'angle', {
      get() {
        // so the squares are not rotated
        return parent.angle * -1;
      }
    })
  }
}

const ellipse = new Shape(canvas.width / 2, canvas.height / 2);
ellipse.path.ellipse(0, 0, rad_x, rad_y, 0, 0, Math.PI * 2);

const shapes = [ellipse].concat(
  [
    new Rect(0, -1, ellipse),
    new Rect(1, 0, ellipse),
    new Rect(0, 1, ellipse),
    new Rect(-1, 0, ellipse)
  ]
);

const mouse = {x:0, y:0};
canvas.onmousemove = ({offsetX, offsetY}) => {
  mouse.x = offsetX;
  mouse.y = offsetY;
};

draw();

function clearTransform() {
  ctx.setTransform(1, 0, 0, 1, 0, 0);
}

function draw() {
  // update ellipse's angle
  ellipse.angle = (ellipse.angle + Math.PI / 180) % (Math.PI * 2);
  
  // clear
  clearTransform();
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  // draw the shapes
  shapes.forEach(shape => {
    clearTransform(); // clear the transform matrix completely
    shape.applyTransform(); // will apply their parent's transform too

     // check if we are hovering this shape
     shape.color = ctx.isPointInPath(shape.path, mouse.x, mouse.y) ? 'red' : 'black';

    ctx.strokeStyle = shape.color;
    ctx.stroke(shape.path);
  });

  // do it again
  requestAnimationFrame(draw);
}
<canvas id="canvas"></canvas>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • That's excellent, but I still need to be able to calculate mouse hits on the ellipse or the handles (rectangles). I can calculate the hits on the ellipse using a solution like this: https://stackoverflow.com/questions/7946187/point-and-ellipse-rotated-position-test-algorithm but I am still stuck on the rectangle hits. Thanks! – Michael Wilson Aug 06 '19 at 12:58
  • @MichaelWilson [isPointInPath](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInPath) and [isPointInStroke](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInStroke) are your friends. I add an edit. – Kaiido Aug 06 '19 at 13:11
  • Will give it a try! Thanks! – Michael Wilson Aug 06 '19 at 18:57
  • Ok, this all worked out great! Thanks. P.S. Do you know the proper way to get from a degree angle to the X, Y on the Un-Rotated ellipse? The method I'm using (code at the very top) doesn't seem to work correctly. – Michael Wilson Aug 08 '19 at 19:14
1

Just rotate point you got with your function around center of an ellipse:

function rotatePoint(x, y, originX, originY, rotation) {
  const cosA = Math.cos(rotation);
  const sinA = Math.sin(rotation);
  const dx = x - originX;
  const dy = y - originY;
  return {
    x: originX + dx * cosA - dy * sinA,
    y: originY + dx * sinA + dy * cosA
  }
}

Please note that your function getPointOnEllipse does not return point corresponding to central angle.

Alex
  • 1,724
  • 13
  • 25
  • Thanks but it didn't quite work as expected - see my edits above I couldn't get all the information into the comment :-( – Michael Wilson Aug 05 '19 at 23:39
  • shouldn't x be x: originX + dx * cosA + dy * sinA instead of x: originX + dx * cosA - dy * sinA and similarly y be y: originY + dx * sinA - dy * cosA ? – Balchandar Reddy Jul 24 '20 at 04:09
  • @BalchandarReddy That depends on rotation direction. My answer is for counterclockwise rotation - right-handed coordinate system. – Alex Jul 24 '20 at 07:55