I need to retain coordinate information following a rotation and am struggling with the necessary math.
I have an image, with certain crop coordinates. These coordinates are given as the following object:
{
h: 139.68333435058594,
w: 139.68333435058594,
x: 60.31666564941406,
x2: 200,
y: 80,
y2: 219.68333435058594
}
I am then rotating this image (it is a canvas element) but I would like the new coordinates to accurately reflect the rotation, leaving the original crop intact.
e.g. if the top left quarter of a 400x400 image is selected the returned coordinates would be x:0, y:0, x2:200, y2:200. When rotated -90 degrees to the left, the new coordinates should be : x:0, y:200, x2:200, y:400.
How would I write a function to calculate these new coordinates?
Many thanks.
To help visualize this question properly I have included a quick image:
EDIT: The large square in the image above is the photo that I am rotating. It is being rotated around its midpoint and the crop coordinates are relative to the image itself. Upon rotation the actual coordinate plane is reset prior to recalculating the new coordinates meaning that the top left point of the image/container will always be 0,0.
This function uses the Rotation Matrix algorithm
function rotate(x, y, xm, ym, a) {
var cos = Math.cos, sin = Math.sin,
a = a * Math.PI / 180, // Convert to radians
// Subtract midpoints, so that midpoint is translated to origin
// and add it in the end again
xr = (x - xm) * cos(a) - (y - ym) * sin(a) + xm,
yr = (x - xm) * sin(a) + (y - ym) * cos(a) + ym;
return [xr, yr];
}
alert(rotate(0, 0, 200, 200, -90)); // 0,400
JSFiddle
The matrix for a 90 degree counterclockwise rotation around the (0,0) point (in your coordinate system) is
0 1
-1 0
Which translates in code to:
new_x = y; new_y = -x;
But you want to rotate around (200,200), so you will need to move your points before rotating them, and move them back afterwards.
new_x = (y-200) + 200;
new_y = -(x-200) + 200;
Simplified, this is just
new_x = y; new_y = 400-x;
You should be able to transform all of the crop coordinates like that.
Top left corner will be:
0, heightImg - heightCrop
Bottom Right will be:
widthCrop, widthImg
This is assuming the crop is snug with the top left corner before rotation.
I'll work out the general math in a bit.
The formulas to rotate a point (A) around a point (B) by angle (C) are as follows:
N.x = (A.x-B.x) * cos(c) - (A.y-B.y) * sin(c) + B.x
N.y = (A.y-B.y) * cos(c) + (A.x-B.x) * sin(c) + B.y
We can reduce these since we're rotating by 90 degrees. [cos(90) = 0, sin(90)=1]
N.x = 0 - (A.y-B.y) + B.x = B.x + B.y - A.y
N.y = 0 + (A.x-B.x) + B.y = B.y + A.x - B.x
So using this formula with the coordinates of the top-left point will give you the bottom-left point. (The x will be correct, but you'll have to subtract the crop area's height to get the correct y value for the new top-left point)
Related
I'm trying to rotate an SVG object (a rectangle, which is also draggable using x and y attributes) via rotate(angle x y), x and y is the midpoint of the object which I calculate with this function:
getMidPoint(x, y, width, height, angle, oldMidPointX, oldMidPointY, drawPoint) {
let angleRad = angle * Math.PI / 180;
let cosa = Math.cos(angleRad);
let sina = Math.sin(angleRad);
// Find new x/y values after rotation
let tempX = x - oldMidPointX;
let tempY = y - oldMidPointY;
let xAfterRotation = (tempX * cosa) - (tempY * sina);
let yAfterRotation = (tempX * sina) + (tempY * cosa);
drawPoint(x, y, "aqua");
// translate back
x = xAfterRotation + oldMidPointX;
y = yAfterRotation + oldMidPointY;
drawPoint(x, y, "green");
let wp = width / 2;
let hp = height / 2;
let px = x + (wp * cosa) - (hp * sina);
let py = y + (wp * sina) + (hp * cosa);
drawPoint(x, y, "red");
return {
px: px,
py: py };
}
Consecutive rotations are no problem. The jumping occurs if the object gets rotated, resized (for example increasing the width of the object) and then rotated again. While the function above gives the correct midpoint, the object first jumps and rotates around the the calculated point.
I don't know what I'm missing here, maybe someone can spot the mistake or give me some ideas to try out.
EDIT
Here is an image to showcase my problem.
1: Starting position; red dot marks the midpoint of the object and green dot is the position used for calculating the midpoint.
2: Rotating the object with no problem.
3: Resizing the object.
4: Calculating new midpoint on start; blue dot marks x and y coordinates of the object.
5: Rotating the second time, "jumping" occurs.
6-8: The object rotates (the same rotation started at previous step) around the midpoint but with an offset.
9: 360° rotation puts the top left corner of the object to it's x/y position
Maybe someone can spot the mistake now.
This question already has an answer here:
Extrapolate coordinates to edges of canvas object
(1 answer)
Closed 5 years ago.
I am trying to draw a line on JavaScript canvas. I have two points A and B (as shown in the picture).
I use this code to find the angle between these two points:
// p1 is point A and p2 is point B
var theta = Math.atan2(p2.y - p1.y, p2.x - p1.x);
Now I want to draw a line from point A till the end of the canvas and I also want to get the point where the line ends (point C in the picture).
Can this be achieved using the angle and size(width & height) of the canvas?
It is possible to solve this problem without trigonometric functions. At first construct parametric representation of given AB ray:
x = A.X + dx * t
y = A.Y + dy * t
where
dx = B.X - A.X
dy = B.Y - A.Y
The check what edge will be intersected first (with smaller t value):
//potential border positions
if dx > 0 then
ex = width
else
ex = 0
if dy > 0 then
ey = height
else
ey = 0
//check for horizontal/vertical lines
if dx = 0 then
return cx = A.X, cy = ey
if dy = 0 then
return cy = A.Y, cx = ex
//in general case find parameters of intersection with horizontal and vertical edge
tx = (ex - A.X) / dx
ty = (ey - A.Y) / dy
//and get intersection for smaller parameter value
if tx <= ty then
cx = ex
cy = A.Y + tx * dy
else
cy = ey
cx = A.X + ty * dx
return cx, cy
Results for Width = 400, Height = 300, fixed A point and various B points:
100:100 - 150:100 400: 100
100:100 - 150:150 300: 300
100:100 - 100:150 100: 300
100:100 - 50:150 0: 200
100:100 - 50:100 0: 100
100:100 - 50:50 0: 0
100:100 - 100:50 100: 0
100:100 - 150:50 200: 0
your example calculates the polar angle (not the angle between two Points - that's impossible). To calculate where the Extended line touches the canvas calculate the Y-difference (dY) between P1 and the bottom of the canvas, use the atan of the angle to calculate dX. If P1.x + dX is smaller than your canvas width, the line hits the bottom at cut.X = P1.x + dX. If it is bigger, it hits the right side. To get the Y value where it hits, think of a small triangle formed by the part of the right side of canvas below the Point where the Extended line cuts the right side and the Point where the line will cut the (horizontal Extended) bottom of the canvas. The Y part is cut.X -canvas.width * atan(90-angle).
Probably you have to determine, in which direction the extended line is Extended.
More simpler solution:
Since JavaScript draw clips at canvas border by itself, you could extend your line very long (i.e. by size of canvas diagonal length) and you are done.
I'm working on a library for canvas, and I can't figure out how I should be doing rotations around the center of an object. Below is the function I'm using to render them currently and an example object I'm giving it.
I feel there is a better way to do the 4 if statements, but I can't figure out the math for it. Currently I am taking each "pixel" of the object and rotating it around the center, but I can't see room for expansion. What am I doing wrong?
//Rendering function
Display.prototype.renderObject = function(object, direction) {
if (typeof object.rotation !== 'number') object.rotation = 0;
for (x=0;x<object.bounds.x;x++) {
for (y=0;y<object.bounds.y;y++) {
rotation = 45;
if (x==0 && y==0) rotation += 0;
if (x==0 && y==1) rotation += 90;
if (x==1 && y==0) rotation += 270;
if (x==1 && y==1) rotation += 180;
display.drawRect(object.color[x][y],
(display.width/2) - (players[playerIndex].position.x * 16) + (object.position.x * 16) - (object.bounds.x * object.scale)/4 - (object.bounds.x/3 * object.scale * Math.cos((object.rotation+rotation)*(Math.PI/180))),
(display.height/2) + (players[playerIndex].position.y * 16) - (object.position.y * 16) - (object.bounds.y * object.scale)/4 - (object.bounds.y/3 * object.scale * Math.sin((object.rotation+rotation)*(Math.PI/180))),
object.scale, object.scale, object.rotation * (direction || 1));
}
}
};
// Example object
block = {
"color": [
["#FFF","#CCC"], // Some colors to make
["#999","#666"] // a shaded illusion
],
"position": {
"x": 0,
"y": 0
},
"bounds": {
"x": 2, // color[0].length
"y": 2 // color.length
},
"rotation": 0, // 0 to 360
"scale": 4 // real pixels per object pixel
}
// Example usage
Display.renderObject(block);
- edit -
Maybe I need to have it calculate where each pixel's center coordinates would be, then get the distance from that to the origin of the object and the rotation each pixel would be offset at.
If x = 0 and y = 0, then it's +45 degrees with sin and -45 degrees with cos+45 degrees with sin. If (object.bounds.x-1)/2 gives us the center coords for dealing with x, then Math.sqrt(Math.pow(x,2)+Math.pow(y,2)) gives us the radius for the color block from the center of the object. I'm not sure where to go from there.
I am not 100% sure of what you're asking, but if it is how to rotate points around the points' center you have to translate all your points so that you object to rotate is centered around origin or point [0, 0] if you like.
A rotation matrix always rotates around origin so there is no way around (no pun intended).
So before you rotate your points calculate a delta value that you apply before the rotation, rotate and then reverse the delta and apply again to get the object back with its offset relative to origin.
So first step is to calculate the delta to find out how much you need to translate the points to get them into center.
For a rectangle it is easy: it is its x and y position plus half of its width and height:
deltaX = x + width * 0.5;
deltaY = y + height * 0.5;
This value you then need to subtract to the points. How you apply this delta is up to you: you can do it in advance with each point or inject it directly into the formula.
Then rotate and when done you simply apply add the delta value to each point again.
Here is a shorter version of a rotation matrix based on my article here:
var a = rotation * Math.PI / 180; /// calculate angle once
x -= deltaX; /// apply pre-translation
y -= deltaY;
/// rotate and apply post-translation
nx = x * Math.cos(a) + y * Math.sin(a) + deltaX;
ny = y * -Math.sin(a) + x * Math.cos(a) + deltaY;
The ifs you're using seem to come from code that use this for optimizations of the angles; when you use 0, 90 etc. as cos and sin would produce 1 and 0 respectively. You are however not utilizing this optimization as you could simply swap x for y etc. when these cases occurs. However, you bind then to x and y position so I am not sure what you try to achieve here..
I would suggest you just remove them as you calculate and applies sin/cos anyways - you could instead cache the sin and cos and use those for all the points:
var angle = rotation * Math.PI / 180;
sin = Math.sin(angle),
cos = Math.cos(angle),
...calc deltas here...
...enter loop here, then for each point:...
x -= deltaX;
y -= deltaY;
nx = x * cos + y * sin + deltaX;
ny = y * -sin + x * cos + deltaY;
Now the cost is lower for each point and your calculations will perform faster.
Hope this helps.
I am not so familiar trigonometry, but I have only two points to rotate in 2D:
*nx, ny
. -
. -
. angle -
*cx,cy.................*x,y
cx, cy = rotation center
x,y = current x,y
nx, ny = new coordinates
How to calculate new points in a certain angle?
function rotate(cx, cy, x, y, angle) {
var radians = (Math.PI / 180) * angle,
cos = Math.cos(radians),
sin = Math.sin(radians),
nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
return [nx, ny];
}
The first two parameters are the X and Y coordinates of the central point (the origin around which the second point will be rotated). The next two parameters are the coordinates of the point that we'll be rotating. The last parameter is the angle, in degrees.
As an example, we'll take the point (2, 1) and rotate it around the point (1, 1) by 90 degrees clockwise.
rotate(1, 1, 2, 1, 90);
// > [1, 0]
Three notes about this function:
For clockwise rotation, the last parameter angle should be positive. For counterclockwise rotation (like in the diagram you provided), it should be negative.
Note that even if you provide arguments that should yield a point whose coordinates are whole numbers -- i.e. rotating the point (5, 0) by 90 degrees about the origin (0, 0), which should yield (0, -5) -- JavaScript's rounding behavior means that either coordinate could still be a value that's frustratingly close to the expected whole number, but is still a float. For example:
rotate(0, 0, 5, 0, 90);
// > [3.061616997868383e-16, -5]
For this reason, both elements of the resulting array should be expected as a float. You can convert them to integers using Math.round(), Math.ceil(), or Math.floor() as needed.
Finally, note that this function assumes a Cartesian coordinate system, meaning that values on the Y axis become higher as you go "up" in the coordinate plane. In HTML / CSS, the Y axis is inverted -- values on the Y axis become higher as you move down the page.
First, translate the rotation center to the origin
Calculate the new coordinates (nx, ny)
Translate back to the original rotation center
Step 1
Your new points are
center: (0,0)
point: (x-cx, y-cy)
Step 2
nx = (x-cx)*cos(theta) - (y-cy)*sin(theta)
ny = (y-cy)*cos(theta) + (x-cx)*sin(theta)
Step 3
Translate back to original rotation center:
nx = (x-cx)*cos(theta) - (y-cy)*sin(theta) + cx
ny = (y-cy)*cos(theta) + (x-cx)*sin(theta) + cy
For deeper explanation, with some fancy diagrams, I recommend looking at this.
above accepted answer not work for me correctly, rotation are reversed , here is working function
/*
CX # Origin X
CY # Origin Y
X # Point X to be rotated
Y # Point Y to be rotated
anticlock_wise # to rotate point in clockwise direction or anticlockwise , default clockwise
return # {x,y}
*/
function rotate(cx, cy, x, y, angle,anticlock_wise = false) {
if(angle == 0){
return {x:parseFloat(x), y:parseFloat(y)};
}if(anticlock_wise){
var radians = (Math.PI / 180) * angle;
}else{
var radians = (Math.PI / -180) * angle;
}
var cos = Math.cos(radians);
var sin = Math.sin(radians);
var nx = (cos * (x - cx)) + (sin * (y - cy)) + cx;
var ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
return {x:nx, y:ny};
}
According to Polar coordinate system artycle on Wikipedia:
x = r * cos(deg)
y = r * sin(deg)
r (radius) is equal to distance between Rotation Centre and Rotated Point
deg (degrees) is angle measured in degrees
I think it is better to use matrices for such operations.
Here is the example with gl-matrix (but you can use something like THREEJS as well).
import * as glm from 'gl-matrix';
const rotateVector = (() => {
const q = glm.quat.create();
// const m = glm.mat4.create(); // 2nd way
return (v: glm.vec3, point: glm.vec3, axis: glm.vec3, angle: number) => {
glm.quat.setAxisAngle(q, axis, angle);
// glm.mat4.fromRotation(m, angle, axis); // 2nd way
glm.vec3.sub(v, v, point);
glm.vec3.transformQuat(v, v, q);
// glm.vec3.transformMat4(v, v, m); // 2nd way
glm.vec3.add(v, v, point);
return v;
}
})();
In 2D case you need to rotate around z-axis:
rotateVector([x, y, 0], [cX, cY, 0], [0, 0, 1], angleInRadians);
I have 4 points 1,2,3,4 that closes a rectangle.
The points are in a array in this following way: x1 y1 x2 y2 x3 y3 x4 y4
The problem I have is that the rectangle can be rotated in a angle.
How can I calculate the original points (gray outline), and the angle?
I'm trying to reproduce this effect in javascript+css3-transform, so I need to first know the straight dimensions and then rotate with the css.
I just know if the rectangle is straight by comparing points e.g. y1==y2
if(x1==x4 && x2==x3 && y1==y2 && y4==y3){
rectangle.style.top = y1;
rectangle.style.left = x1;
rectangle.style.width = x2-x1;
rectangle.style.height = y4-y1;
rectangle.style.transform = "rotate(?deg)";
}
You can use any coordinate pair on the same side to calculate the rotation angle. Note that mathematic angles normally assume 0 as long the +ve X axis and increase by rotating anti–clockwise (so along the +ve Y axis is 90°, -ve X axis is 180° and so on).
Also, javascript trigonometry functions return values in radians that must be converted to degrees before being used in a CSS transform.
If the shape is not rotated more than 90°, then life is fairly simple and you can use the tanget ratio of a right angle triangle:
tan(angle) = length of opposite side / length of adjacent side
For the OP, the best corners to use are 1 and 4 so that rotation is kept in the first quadrant and clockwise (per the draft CSS3 spec). In javascript terms:
var rotationRadians = Math.atan((x1 - x4) / (y1 - y4));
To convert to degrees:
var RAD2DEG = 180 / Math.PI;
var rotationDegrees = rotationRadians * RAD2DEG;
If the rotation is more than 90°, you will need to adjust the angle. e.g. where the angle is greater than 90° but less than 180°, you'll get a -ve result from the above and need to add 180°:
rotationDegrees += 180;
Also, if you are using page dimentions, y coordinates increase going down the page, which is the opposite of the normal mathetmatic sense so you need to reverse the sense of y1 - y4 in the above.
Edit
Based on the orientation of points in the OP, the following is a general function to return the center and clockwise rotation of the rectangle in degrees. That's all you should need, though you can rotate the corners to be "level" yourself if you wish. You can apply trigonometric functions to calculate new corners or just do some averages (similar to Ian's answer).
/** General case solution for a rectangle
*
* Given coordinages of [x1, y1, x2, y2, x3, y3, x4, y4]
* where the corners are:
* top left : x1, y1
* top right : x2, y2
* bottom right: x3, y3
* bottom left : x4, y4
*
* The centre is the average top left and bottom right coords:
* center: (x1 + x3) / 2 and (y1 + y3) / 2
*
* Clockwise rotation: Math.atan((x1 - x4)/(y1 - y4)) with
* adjustment for the quadrant the angle is in.
*
* Note that if using page coordinates, y is +ve down the page which
* is the reverse of the mathematic sense so y page coordinages
* should be multiplied by -1 before being given to the function.
* (e.g. a page y of 400 should be -400).
*
* #see https://stackoverflow.com/a/13003782/938822
*/
function getRotation(coords) {
// Get center as average of top left and bottom right
var center = [(coords[0] + coords[4]) / 2,
(coords[1] + coords[5]) / 2];
// Get differences top left minus bottom left
var diffs = [coords[0] - coords[6], coords[1] - coords[7]];
// Get rotation in degrees
var rotation = Math.atan(diffs[0]/diffs[1]) * 180 / Math.PI;
// Adjust for 2nd & 3rd quadrants, i.e. diff y is -ve.
if (diffs[1] < 0) {
rotation += 180;
// Adjust for 4th quadrant
// i.e. diff x is -ve, diff y is +ve
} else if (diffs[0] < 0) {
rotation += 360;
}
// return array of [[centerX, centerY], rotation];
return [center, rotation];
}
The center of the rectangle is right between two opposite corners:
cx = (x1 + x3) / 2
cy = (y1 + y3) / 2
The size of the rectangle is the distance between two points:
w = sqrt(pow(x2-x1, 2) + pow(y2-y1, 2))
h = sqrt(pow(x3-x2, 2) + pow(y3-y2, 2))
The corners of the gray rectangle can be calculated from the center and the size, for example the top left corner:
x = cx - w / 2
y = cy - h / 2
The angle is the arctangent of a side of the square:
a = arctan2(y4 - y1, x4 - x1)
(I'm not sure exactly which angle it returns, or what angle you expect for that matter, so you get to test a bit.)
This is how you get the angle between the vertical pink line and the black line starting at the pink line intersection:
var deg = 90 - Math.arctan((x2-x1) / (y2-y1));
The dimensions can be calculated with the help of the Pythagoras theorem:
var width = Math.sqrt((x2-x1)^2 / (y2-y1)^2));
var height = Math.sqrt((x1-x4)^2) / (y4-y1)^2));
The positional coordinates (left and top) are the averages of x1 and x3 and y1 and y3 respectively.
var left = Math.floor((x1 + x3) / 2);
var top = Math.floor((y1 + y3) / 2);
You want to use the negative-margin trick.
var marginLeft = -Math.ceil(width / 2);
var marginTop = -Math.ceil(height / 2);