How to calculate a point on a rotated ellipse - javascript

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:
And here's 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?

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>

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.

Related

How do you set up multiple orbiting canvas drawings (pure js)

I am making a solar system with pre-made planets and want to know how to get more than one to rotate around the sun. I ran into the issue of not being able to rotate 2 at once. Any solutions?Here is current code:
Orbiting page:
var canvasP = document.getElementById("planetsOrbit");
var ctx2 = canvasP.getContext("2d");
var angle = 6 * Math.PI / 180;
var cx = window.innerWidth / 2;
var cy = window.innerHeight / 2.12;
var radiusNew = (window.innerHeight + window.innerWidth) * 0.15;
function resizeCanvasPOrbit() {
ctx2.clearRect(0, 0, canvasP.width, canvasP.height);
if (canvasP.width < window.innerWidth) {
canvasP.width = window.innerWidth * 0.99;
}'
if (canvasP.height < window.innerHeight)
{
canvasP.height = window.innerHeight * 0.98;
}
w = canvasP.width
h = canvasP.height
}
function draw(x, y) {
ctx2.clearRect(0, 0, w, h);
ctx2.save();
ctx2.beginPath();
ctx2.beginPath();
roa(x, y, window.innerHeight * window.innerWidth * 0.00008);
ctx2.stroke();
ctx2.restore();
};
function keepDrawing() {
ctx2.clearRect(0, 0, w, h);
draw(newX, newY);
setTimeout(keepDrawing, 250);
}
window.requestAnimFrame = (function (callback) {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 5000 / 60);
};
})();
var fps = 60;
function animate() {
setTimeout(function () {
requestAnimationFrame(animate);
// increase the angle of rotation A.K.A SPEED!
angle += 1 * Math.PI / 3600;
//calculate the new ball.x / ball.y
var newX = cx - radiusNew * Math.cos(angle);
var newY = cy + radiusNew * Math.sin(angle);
//draw
ctx2.clearRect(0, 0, w, h);
draw(newX, newY);
//draw the centerpoint
ctx2.beginPath();
ctx2.arc(cx, cy, radiusNew, 0, Math.PI * 2, false);
ctx2.closePath();
}, 1000 / fps);
}
animate();
and the Premade Planets:
//sun
solus = function(xAxis, yAxis, radius) {
ctx.shadowBlur=400
ctx.shadowColor="red"
ctx.fillStyle ="#ff9900";
ctx.beginPath();
ctx.arc(xAxis, yAxis, radius, 0, Math.PI * 2, false)
ctx.fill();
ctx.shadowBlur = 0;
}
//Fighting Pits
pits = function(xAxis, yAxis, radius) {
ctx.beginPath();
ctx.fillStyle ="#990000"
ctx.arc(xAxis, yAxis, radius, 0, Math.PI , false)
ctx.moveTo(xSpot1,ySpot1)
ctx.lineTo(xSpot1,ySpot2)
ctx.lineTo(xSpot2,ySpot2)
ctx.lineTo(xSpot2,ySpot3)
ctx.lineTo(xSpot3,ySpot4)
ctx.lineTo(xSpot4,ySpot3)
ctx.lineTo(xSpot4,ySpot2)
ctx.lineTo(xSpot5,ySpot2)
ctx.lineTo(xSpot5,ySpot1)
ctx.lineTo(xSpot1,ySpot1)
ctx.fill();
}
//Water Planet
roa = function(xAxis, yAxis, radius) {
ctx2.shadowBlur = 0;
ctx2.beginPath();
ctx2.fillStyle ="#00ffff"
ctx2.arc(xAxis, yAxis, radius, 0, Math.PI * 2, false)
ctx2.fill();
}
//Forest planet atmoshpere
eldridA = function(xAxis, yAxis, radius) {
ctx.beginPath();
ctx.fillStyle ="rgba(230, 230, 230, 0.3)";
ctx.arc(xAxis, yAxis, radius, 0, Math.PI * 2, false)
ctx.fill();
}
//forest core
eldrid = function(xAxis, yAxis, radius) {
ctx.shadowColor = "rgba(230, 230, 230, 0.2)";
ctx.shadowBlur = 200;
ctx.beginPath();
ctx.fillStyle ="#ff9900"
ctx.arc(xAxis, yAxis, radius / 2, 0, Math.PI * 2, false)
ctx.fill();
xAxis2 = xAxis - window.innerWidth * 0.009
yAxis2 = yAxis + window.innerHeight * 0.007
ctx.arc(xAxis2, yAxis2, radius / 4, 0, Math.PI * 2, false)
ctx.fill();
ctx.beginPath();
xAxis3 = xAxis + window.innerWidth * 0.011
ctx.arc(xAxis3 , yAxis2, radius / 3, 0, Math.PI * 2, false)
ctx.fill();
ctx.beginPath();
yAxis3 = yAxis - window.innerHeight * 0.03
ctx.arc(xAxis, yAxis3, radius / 3, 0, Math.PI * 2, false)
ctx.fill();
ctx.shadowBlur = 0;
ctx.shadowColor = null;
}
So how can I get more than one planet to orbit the Sun? any Help is appreciated.
I did a simple demo of a solar system with multiple planets using canvas: http://codepen.io/giladaya/pen/PWWKLP
I believe that you can easily adapt it to your needs and I'll explain the main parts of the code:
Each planet has it's own properties like radius, distance from center, radial velocity and a reference to a draw function.
var planets = [
{
name: 'sun', // for reference
rad: 30, // Planet radius
distance: 0, // Planet distance from center
rv: 0, // radial velocity (deg/sec)
drawFunc: drawSun // draw function
},
{
name: 'foo',
rad: 10,
distance: 70,
rv: 1,
drawFunc: drawBlue
},
{
name: 'bar',
rad: 15,
distance: 100,
rv: 2,
drawFunc: drawRed
}
];
The main loop iterates over all the planets, updates their current angle according to the radial velocity and draws each one in it's new, updated location.
function draw() {
ctx.fillRect(-cW/2, -cH/2, cW, cH);
var now = Date.now(),
dts = (now - lastFrameTime) / 1000;
planets.forEach(function(planet, idx){
var theta = 0;
planetsAngle[idx] += planet.rv/Math.PI * dts;
theta = planetsAngle[idx];
var x = planet.distance * Math.cos(theta);
var y = planet.distance * Math.sin(theta);
planet.drawFunc(ctx, x, y, planet.rad);
});
lastFrameTime = now;
requestAnimationFrame(draw);
}
Note that if using requestAnimationFrame, you don't need to set timers.
The draw function for each planet can be as complicated as you want in order to have more elements on the planet. For example:
function drawRed(ctx, x, y, rad) {
ctx.save();
ctx.fillStyle = 'red';
ctx.translate(x, y);
ctx.beginPath();
ctx.arc(0, 0, rad, 0, 2*Math.PI , false);
ctx.fill();
ctx.fillStyle = '#A00';
ctx.fillRect(-4, -4, 8, 8);
ctx.restore();
}

How to test if a point is in a rectangle area which rotates an angle?

I am trying to test if a point is inside a rectangle area that rotates an angle around (x, y), like the image below. This is language agnostic problem but I am working with HTML5 canvas now.
Suppose the point we need to test is (x1, y1), the width of the rectangle is 100 and the height is 60. In normal cartesian coordinate system the rectangle ABCD top left point A is (canvas.width / 2, canvas.height / 2 -rect.height/2). I assume that (canvas.width / 2, canvas.height / 2) is at the middle of line AB where B is (canvas.width / 2, canvas.height / 2 + rect.height /2).
I have read some resources here and wrote a test project, but it doesn't test the correct area. In my test project I want the this effect:
if the mouse is on a point that is within the range of the testing rectangle area a dot will be displayed around the mouse. If it is outside the rectangle nothing will be displayed.
However my test project looks like this: (Note that although I used the vector based technique to test the point in a rotated rectangle area, the test area remains the rectangle before rotation)
// Detecting a point is in a rotated rectangle area
// using vector based method
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext('2d');
class Rectangle {
constructor(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.searchPoint = { x: 0, y: 0};
this.binding();
}
binding() {
let self = this;
window.addEventListener('mousemove', e => {
if (!e) return;
let rect = canvas.getBoundingClientRect();
let mx = e.clientX - rect.left - canvas.clientLeft;
let my = e.clientY - rect.top - canvas.clientTop;
self.searchPoint = { x: mx, y: my };
});
}
}
let rect = new Rectangle(canvas.width /2, canvas.height /2 - 30, 100, 60);
function vector(p1, p2) {
return {
x: (p2.x - p1.x),
y: (p2.y - p1.y)
};
}
function point(x, y) {
return { x, y };
}
// Vector dot operation
function dot(a, b) {
return a.x * b.x + a.y * b.y;
}
function pointInRect(p, rect, angle) {
let a = newPointTurningAngle(0, -rect.height / 2, angle);
let b = newPointTurningAngle(0, rect.height / 2, angle);
let c = newPointTurningAngle(rect.width, rect.height / 2, angle);
let AB = vector(a, b);
let AM = vector(a, p);
let BC = vector(b, c);
let BM = vector(b, p);
let dotABAM = dot(AB, AM);
let dotABAB = dot(AB, AB);
let dotBCBM = dot(BC, BM);
let dotBCBC = dot(BC, BC);
return 0 <= dotABAM && dotABAM <= dotABAB && 0 <= dotBCBM && dotBCBM <= dotBCBC;
}
function drawLine(x, y) {
ctx.strokeStyle = 'black';
ctx.lineTo(x, y);
ctx.stroke();
}
function text(text, x, y) {
ctx.font = "18px serif";
ctx.fillText(text, x, y);
}
function newPointTurningAngle(nx, ny, angle) {
return {
x: nx * Math.cos(angle) - ny * Math.sin(angle),
y: nx * Math.sin(angle) + ny * Math.cos(angle)
};
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.moveTo(canvas.width / 2, 0);
drawLine(canvas.width /2, canvas.height / 2);
ctx.moveTo(0, canvas.height / 2);
drawLine(canvas.width / 2, canvas.height /2);
let angle = -Math.PI / 4;
ctx.setTransform(Math.cos(angle), Math.sin(angle), -Math.sin(angle), Math.cos(angle), canvas.width / 2, canvas.height / 2);
//ctx.setTransform(1, 0, 0, 1, canvas.width/2, canvas.height / 2);
ctx.strokeStyle = 'red';
ctx.strokeRect(0, -rect.height / 2, rect.width, rect.height);
let p = newPointTurningAngle(rect.searchPoint.x - canvas.width / 2, rect.searchPoint.y - canvas.height / 2, angle);
let testResult = pointInRect(p, rect, angle);
if (testResult) {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.beginPath();
ctx.fillStyle = 'black';
ctx.arc(rect.searchPoint.x, rect.searchPoint.y, 5, 0, Math.PI * 2);
ctx.fill();
}
ctx.setTransform(1, 0, 0, 1, 0, 0);
text('searchPoint x: ' + rect.searchPoint.x + ', y: ' + rect.searchPoint.y, 60, 430);
text('x: ' + canvas.width / 2 + ', y: ' + canvas.height / 2, 60, 480);
requestAnimationFrame(animate);
}
animate();
<canvas id='canvas'></canvas>
Updated Solution
I am still using the vector based method as followed:
0 <= dot(AB,AM) <= dot(AB,AB) &&
0 <= dot(BC,BM) <= dot(BC,BC)
Now I have changed the point's rotated angle and the corner point coordinates so the point can be detected in the rectangle. The corner points are already in the rotated coordinate system so they don't need to be translated, however the point of the mouse location needs to be translated before testing it in the rectangle area.
In setTransform method the angle rotated is positive when rotated clockwise, the form is :
ctx.setTransform(angle_cosine, angle_sine, -angle_sine, angle_cosine, x, y);
So when calculating the point's new coordinate after rotating an angle, the formula need to change to this so that the angle is also positive when rotated clockwise:
new_x = x * angle_cosine + y * angle_sine;
new_y = -x * angle_sine + y * angle_cos;
// Detecting a point is in a rotated rectangle area
// using vector based method
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext('2d');
class Rectangle {
constructor(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.searchPoint = { x: 0, y: 0};
this.binding();
}
binding() {
let self = this;
window.addEventListener('mousemove', e => {
if (!e) return;
let rect = canvas.getBoundingClientRect();
let mx = e.clientX - rect.left - canvas.clientLeft;
let my = e.clientY - rect.top - canvas.clientTop;
self.searchPoint = { x: mx, y: my };
});
}
}
let rect = new Rectangle(canvas.width /2, canvas.height /2 - 30, 100, 60);
function vector(p1, p2) {
return {
x: (p2.x - p1.x),
y: (p2.y - p1.y)
};
}
function point(x, y) {
return { x, y };
}
// Vector dot operation
function dot(a, b) {
return a.x * b.x + a.y * b.y;
}
function pointInRect(p, rect) {
let a = { x: 0, y: -rect.height / 2};
let b = { x: 0, y: rect.height / 2};
let c = { x: rect.width, y: rect.height / 2};
text('P x: ' + p.x.toFixed() + ', y: ' + p.y.toFixed(), 60, 430);
text('A x: ' + a.x.toFixed() + ', y: ' + a.y.toFixed(), 60, 455);
text('B x: ' + b.x.toFixed() + ', y: ' + b.y.toFixed(), 60, 480);
let AB = vector(a, b);
let AM = vector(a, p);
let BC = vector(b, c);
let BM = vector(b, p);
let dotABAM = dot(AB, AM);
let dotABAB = dot(AB, AB);
let dotBCBM = dot(BC, BM);
let dotBCBC = dot(BC, BC);
return 0 <= dotABAM && dotABAM <= dotABAB && 0 <= dotBCBM && dotBCBM <= dotBCBC;
}
function drawLine(x, y) {
ctx.strokeStyle = 'black';
ctx.lineTo(x, y);
ctx.stroke();
}
function text(text, x, y) {
ctx.font = "18px serif";
ctx.fillText(text, x, y);
}
function newPointTurningAngle(nx, ny, angle) {
let cos = Math.cos(angle);
let sin = Math.sin(angle);
return {
x: nx * cos + ny * sin,
y: -nx * sin + ny * cos
};
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.moveTo(canvas.width / 2, 0);
drawLine(canvas.width /2, canvas.height / 2);
ctx.moveTo(0, canvas.height / 2);
drawLine(canvas.width / 2, canvas.height /2);
let angle = - Math.PI / 4;
ctx.setTransform(Math.cos(angle), Math.sin(angle), -Math.sin(angle), Math.cos(angle), canvas.width / 2, canvas.height / 2);
ctx.strokeStyle = 'red';
ctx.strokeRect(0, -rect.height / 2, rect.width, rect.height);
let p = newPointTurningAngle(rect.searchPoint.x - canvas.width / 2, rect.searchPoint.y - canvas.height / 2, angle);
ctx.setTransform(1, 0, 0, 1, 0, 0);
let testResult = pointInRect(p, rect);
if (testResult) {
ctx.beginPath();
ctx.fillStyle = 'black';
ctx.arc(rect.searchPoint.x, rect.searchPoint.y, 5, 0, Math.PI * 2);
ctx.fill();
}
ctx.setTransform(1, 0, 0, 1, 0, 0);
text('searchPoint x: ' + rect.searchPoint.x + ', y: ' + rect.searchPoint.y, 60, 412);
text('x: ' + canvas.width / 2 + ', y: ' + canvas.height / 2, 60, 510);
requestAnimationFrame(animate);
}
animate();
<canvas id='canvas'></canvas>
Assuming that you know how to check whether a dot is in the rectangle the approach to solution is to rotate and translate everything (dot and rectangle) to "normalized" coordinating system (Cartesian coordinate system that is familiar to us) and then to check it trivially.
For more information you should check Affine transformations. The good link where you could start is
http://www.mathworks.com/discovery/affine-transformation.html?requestedDomain=www.mathworks.com
As you can see on this Codepen i did (to detect 2 rotate rect collide).
You have to check the 2 projections of your point (in my case, the 4 points of a rect) and look if the projections are on the other rect
You have to handle the same thing but only for a point and a rect instead of 2 rects
All projections are not colliding
All projections are colliding
required code for codepen link
The browser always report mouse position untransformed (==unrotated).
So to test if the mouse is inside a rotated rectangle, you can:
Get the unrotated mouse position from the mouse event (relative to the canvas).
Rotate the mouse x,y versus the rotation point by the same rotation as the rectangle.
Test if the mouse is inside the rectangle. Now that both the rect and the mouse position have been similarly rotated, you can just test as if the mouse and rect were unrotated.
Annotated code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
var isDown=false;
var startX,startY;
var rect=makeRect(50,20,35,20,Math.PI/4,60,30);
function makeRect(x,y,w,h,angle,rotationPointX,rotationPointY){
return({
x:x,y:y,width:w,height:h,
rotation:angle,rotationPoint:{x:rotationPointX,y:rotationPointY},
});
}
drawRect(rect);
$("#canvas").mousedown(function(e){handleMouseDown(e);});
function drawRect(r){
var rx=r.rotationPoint.x;
var ry=r.rotationPoint.y;
// demo only, draw the rotation point
dot(rx,ry,'blue');
// draw the rotated rect
ctx.translate(rx,ry);
ctx.rotate(r.rotation);
ctx.strokeRect(rect.x-rx,rect.y-ry,r.width,r.height);
// always clean up, undo the transformations (in reverse order)
ctx.rotate(-r.rotation);
ctx.translate(-rx,-ry);
}
function dot(x,y,fill){
ctx.fillStyle=fill;
ctx.beginPath();
ctx.arc(x,y,3,0,Math.PI*2);
ctx.fill();
}
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get mouse position relative to canvas
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// rotate the mouse position versus the rotationPoint
var dx=mouseX-rect.rotationPoint.x;
var dy=mouseY-rect.rotationPoint.y;
var mouseAngle=Math.atan2(dy,dx);
var mouseDistance=Math.sqrt(dx*dx+dy*dy);
var rotatedMouseX=rect.rotationPoint.x+mouseDistance*Math.cos(mouseAngle-rect.rotation);
var rotatedMouseY=rect.rotationPoint.y+mouseDistance*Math.sin(mouseAngle-rect.rotation);
// test if rotated mouse is inside rotated rect
var mouseIsInside=rotatedMouseX>rect.x &&
rotatedMouseX<rect.x+rect.width &&
rotatedMouseY>rect.y &&
rotatedMouseY<rect.y+rect.height;
// draw a dot at the unrotated mouse position
// green if inside rect, otherwise red
var hitColor=mouseIsInside?'green':'red';
dot(mouseX,mouseY,hitColor);
}
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Clicks inside rect are green, otherwise red.</h4>
<canvas id="canvas" width=512 height=512></canvas>

Draw arc on canvas from two x, y points and a center x, y point

I'm trying to draw an arc from two points (X, Y cords).
But I can't figure out how to do it so that I can specify the start angle and end angle
I got the center point(p2), radius = r. The start point(p1) and the end point(p3).
like shown below
And what I wanna do is use the arc to draw a round line like shown below
All I have found on this subject is only example where arc draw from 0 to something like 2*Math.PI.
ctx.arc(100,75,50,0,2*Math.PI);
Like this. A can't figure out a way that I can use the p1 and p3 instead of those numbers. Anyone that can explain how this work, and maybe give a shot on how I can solve this?
The arc() method works only with angles so points has to be converted based on their location and distance to center (distance, representing the radius, has to be the same for both in this case).
The signature of arc() is:
void arc(unrestricted double x,
unrestricted double y,
unrestricted double radius,
unrestricted double startAngle,
unrestricted double endAngle,
optional boolean anticlockwise = false);
You can find the two angles from center P2 to P1/P3 by simple trigonometry:
var startAngle = Math.atan2(p1.y - p2.y, p1.x - p2.x),
endAngle = Math.atan2(p3.y - p2.y, p3.x - p2.x);
These can now be fed into the arc method assuming radius is known:
ctx.arc(p2.x, p2.y, radius, startAngle, endAngle);
If radius is unknown but known to be the same you can do:
var diffX = p1.x - p2.x,
diffY = p1.y - p2.y,
radius = Math.abs(Math.sqrt(diffX*diffX + diffY*diffY));
Example
var p2 = {x: 100 , y: 100 },
p1 = {x: 111, y: 30.9},
p3 = {x: 149.5 , y: 149.5},
diffX = p1.x - p2.x,
diffY = p1.y - p2.y,
radius = Math.abs(Math.sqrt(diffX*diffX + diffY*diffY)),
startAngle = Math.atan2(diffY, diffX),
endAngle = Math.atan2(p3.y - p2.y, p3.x - p2.x),
ctx = document.querySelector("canvas").getContext("2d");
// arc
ctx.arc(p2.x, p2.y, radius, startAngle, endAngle, false);
ctx.stroke();
// points / lines helpers:
ctx.fillRect(p1.x - 2, p1.y - 2, 4, 4);
ctx.fillRect(p2.x - 2, p2.y - 2, 4, 4);
ctx.fillRect(p3.x - 2, p3.y - 2, 4, 4);
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.x);
ctx.lineTo(p3.x, p3.x);
ctx.strokeStyle = "#999";
ctx.stroke();
<canvas height=180></canvas>
Result wo/helper lines
var p2 = {x: 100 , y: 100 },
p1 = {x: 111, y: 30.9},
p3 = {x: 149.5 , y: 149.5},
diffX = p1.x - p2.x,
diffY = p1.y - p2.y,
radius = Math.abs(Math.sqrt(diffX*diffX + diffY*diffY)),
startAngle = Math.atan2(diffY, diffX),
endAngle = Math.atan2(p3.y - p2.y, p3.x - p2.x),
ctx = document.querySelector("canvas").getContext("2d");
// arc
ctx.arc(p2.x, p2.y, radius, startAngle, endAngle, false);
ctx.stroke();
<canvas height=180></canvas>
ctx.arc(100,75,50,0,2*Math.PI);
Here first two arguments are point p2 x,y co-ordinates and third argument is radius of circle r.
Fourth and fifth argumentsare the start angle and end angle of arc.
let m21 be the slope of line joining point1(x1, y1) and point2(x2, y2)
m21 = (y2-y1)/(x2-x1)
let m21 be the slope of line joining point3(x3, y3) and point2(x2, y2)
m23 = (y2-y3)/(x2-x3)
ctx.arc(100, 75, 50, Math.atan(m21), Math.atan(m23), true)
will do it.
If point2 is origin then the solution is more simpler.
ctx.arc(100, 75, 50, Math.atan2(x1,y1), Math.atan2(x3,y3), true)

get height of oval drawn on canvas

I want to get height of oval drawn on canvas.Below is my code to draw oval on canvas.
function drawOval(startX, startY, endX, endY, strokeColor) {
x = endX;
y = endY;
context.beginPath();
context.strokeStyle = strokeColor;
context.moveTo(startX, startY + (y - startY) / 2);
context.bezierCurveTo(startX, startY, x, startY, x, startY + (y - startY) / 2);
context.bezierCurveTo(x, y, startX, y, startX, startY + (y - startY) / 2);
context.stroke();
context.fillStyle = strokeColor;
context.fill();
}
I would suggest drawing the oval in a different way:
Bezier is more complex and performance hungry
Bezier does not draw an accurate oval (ellipse)
More difficult to extract certain points for measure without using a manual formula.
To use a different approach which is signature compatible with what you have you could do:
function drawOval(startX, startY, endX, endY, strokeColor) {
var rx = (endX - startX) * 0.5, // get radius for x
ry = (endY - startY) * 0.5, // get radius for y
cx = startX + rx, // get center position for ellipse
cy = startY + ry,
a = 0, // current angle
step = 0.01, // angle step
pi2 = Math.PI * 2;
context.beginPath();
context.strokeStyle = strokeColor;
context.moveTo(cx + rx, cy); // initial point at 0 deg.
for(; a < pi2; a += step)
context.lineTo(cx + rx * Math.cos(a), // create ellipse path
cy + ry * Math.sin(a));
context.closePath(); // close for stroke
context.stroke();
context.fillStyle = strokeColor;
context.fill();
}
Live demo here
Now you will get height (and width) simply by doing:
var width = Math.abs(endX - startX),
height = Math.abs(endY - startY);

How to correctly draw a circle with gaps in a canvas element?

I want to draw a circle. But, not really. I want a series of arcs (segments) that will imply a full circle. And it'll spin and it will be awesome. But I am obviously doing something wrong.
Please, check this fiddle: http://jsfiddle.net/utWdM/
function draw() {
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
ctx.lineWidth = 15;
ctx.strokeStyle = 'hsla(111, 56%, 50%, 0.67)';
ctx.beginPath();
ctx.arc(250, 250, 100, 0, Math.PI * 8 / 30);
ctx.moveTo(250, 250);
ctx.arc(250, 250, 100, Math.PI * 12 / 30, Math.PI * 20 / 30);
ctx.moveTo(250, 250);
ctx.arc(250, 250, 100, Math.PI * 24 / 30, Math.PI * 32 / 30);
ctx.moveTo(250, 250);
ctx.arc(250, 250, 100, Math.PI * 36 / 30, Math.PI * 44 / 30);
ctx.moveTo(250, 250);
ctx.arc(250, 250, 100, Math.PI * 48 / 30, Math.PI * 56 / 30);
ctx.stroke();
ctx.closePath();
}
}
draw();
I don't want the line from the center to the start of an arc (and the first one doesn't have it).
I've been studying basic drawing with canvas and hope understand what's going on and more someday, but I couldn't wait to know what's wrong in this situation.
Any help, highly appreciated.
If you want a little flexible solution, ie. if you want to change number of segments, size of each segments, then you can create a generic function such as this:
/**
* ctx = context
* x / y = center
* radius = of circle
* offset = rotation in angle (radians)
* segments = How many segments circle should be in
* size = size of each segment (of one segment) [0.0, 1.0]
*/
function dashedCircle(ctx, x, y, radius, offset, segments, size) {
var pi2 = 2 * Math.PI, /// cache 2*Math.PI = 360 degrees in rads
segs = pi2 / segments, /// calc. size of each segment in rads
len = segs * size, /// calc. length of segment in rads
i = 0,
ax, ay;
ctx.save();
ctx.translate(x, y);
ctx.rotate(offset); /// rotate canvas
ctx.translate(-x, -y);
for(; i < pi2; i += segs) {
ax = x + radius * Math.cos(i); /// calculate start position of arc
ay = y + radius * Math.sin(i);
ctx.moveTo(ax, ay); /// make sure to move to beginning of arc
ctx.arc(x, y, radius, i, i + len); /// draw arc
}
ctx.restore(); /// remove rotation
}
Then in you code you simply call:
ctx.beginPath();
dashedCircle(ctx, 250, 250, 100, 0, 5, 0.7);
ctx.lineWidth = 15;
ctx.strokeStyle = 'hsla(111, 56%, 50%, 0.67)';
ctx.stroke();
/// don't closePath here
Modified fiddle here
Using the offset parameter makes it a breeze to rotate it:
var offset = 0;
var step = 0.03;
var pi2 = 2 * Math.PI;
(function loop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
dashedCircle(ctx, 250, 250, 100, offset % pi2, 5, 0.7);
ctx.stroke();
offset += step;
requestAnimationFrame(loop);
})();
Rotating circle fiddle
The moveTo needs to be to the beginning of the arc not the centre. This is most easily achieved by translating to the centre of the circle as below. http://jsfiddle.net/utWdM/1
function draw() {
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
ctx.lineWidth = 15;
ctx.strokeStyle = 'hsla(111, 56%, 50%, 0.67)';
ctx.beginPath();
ctx.translate(250,250);
ctx.arc(0, 0, 100, 0, Math.PI * 8 / 30);
ctx.moveTo(100*Math.cos(Math.PI*12/30), 100*Math.sin(Math.PI*12/30));
ctx.arc(0, 0, 100, Math.PI * 12 / 30, Math.PI * 20 / 30);
ctx.moveTo(100*Math.cos(Math.PI*24/30), 100*Math.sin(Math.PI*24/30));
ctx.arc(0, 0, 100, Math.PI * 24 / 30, Math.PI * 32 / 30);
ctx.moveTo(100*Math.cos(Math.PI*36/30), 100*Math.sin(Math.PI*36/30));
ctx.arc(0, 0, 100, Math.PI * 36 / 30, Math.PI * 44 / 30);
ctx.moveTo(100*Math.cos(Math.PI*48/30), 100*Math.sin(Math.PI*48/30));
ctx.arc(0, 0, 100, Math.PI * 48 / 30, Math.PI * 56 / 30);
ctx.stroke();
ctx.closePath();
}
}
The easiest way to avoid complicated math is to call .beginPath() and .stroke(); for each segment. I know it's very repetitive but that how it would look like:
function draw() {
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
ctx.lineWidth = 15;
ctx.strokeStyle = 'hsla(111, 56%, 50%, 0.67)';
ctx.beginPath();
ctx.arc(250, 250, 100, 0, Math.PI * 8 / 30);
ctx.stroke();
ctx.beginPath();
ctx.arc(250, 250, 100, Math.PI * 12 / 30, Math.PI * 20 / 30);
ctx.stroke();
ctx.beginPath();
ctx.arc(250, 250, 100, Math.PI * 24 / 30, Math.PI * 32 / 30);
ctx.stroke();
ctx.beginPath();
ctx.arc(250, 250, 100, Math.PI * 36 / 30, Math.PI * 44 / 30);
ctx.stroke();
ctx.beginPath();
ctx.arc(250, 250, 100, Math.PI * 48 / 30, Math.PI * 56 / 30);
ctx.stroke();
}
}
draw();
And here's your fiddle: http://jsfiddle.net/utWdM/13/

Categories

Resources