I built a simple 2D-car consisting of three elements, using Raphael JS. For that I have the body as an Image and two tires. I separated the elements because I want the tires to spin in an animation.
var curCar = raphael.image("car.png", /* x */ 0, /* y */ 0, /* width */ 120, /* height*/ 82)
var tires = raphael.set();
tires.push(
raphael.image("tire.png", 2, 50, 32, 32),
raphael.image("tire.png", 84, 50, 32, 32)
);
As you can see, the tires are positioned on the "bottom" of the car and are already aligned right.
Now, curCar follows a path using Raphael.animate, which works fine. For every update of the animation I translate the tires to curCar's position. It kinda looks like this:
function animationUpdate (pos) {
curCar.transform("t" + [pos.x, pos.y] +" r"+ pos.alpha);
tires.transform("t" + [pos.x, pos.y ]);
};
"t" stands for translate(x,y), r stands for rotation. These values are calculated automatically by Raphael.
When following a straight path this all works out, but as soon as curCar is rotating, the calculation for the tires fails.
An example:
curCar is at position x: 100 / y: 200 and is rotated by 10°.
The tires are translated to x:102 / y:150 and x: 184 / y: 250. Thus curCar is rotated by 10° the tires are out of place.
My main question is:
How do I calculate the correct X/Y-position considering the rotation?
There must be a common mathematical formula doing this.
The car wheels should be transformed w.r.t. center of rotation of car (body). It is to be noted that the center of rotation can be anywhere w.r.t. the car. For the sake of simplicity, the following code assumes the center of rotation to coincide with the car center.
Finally, Raphael can take care of the mathematical transformations, if translations and rotations are in right the order of execution.
var paper = Raphael.paper(0, 0, 500, 400);
// tire width
var tw = 10,
thw = tw * 0.5,
// car width
cw = 100,
chw = cw * 0.5,
// car height
ch = 50,
chh = ch * 0.5,
// x of tire 1 w.r.t. car
t1xc = -chw - thw,
// x of tire 2 w.r.t. car
t2xc = -chw - thw,
// y of tire 1 w.r.t. car
t1yc = -chh - thw,
// y of tire 2 w.r.t. car
t2yc = chh - thw;
var car = paper.rect(20, 20, cw, ch),
// car center
cx = car.attr('x') + chw,
cy = car.attr('y') + chh,
// tire 1
t1 = paper.rect(cx + t1xc, cy + t1yc, tw, tw),
// tire 2
t2 = paper.rect(cx + t2xc, cy + t2yc, tw, tw);
// Translate by 100, 100 and rotate by 30 deg
car.transform("...t100,100r30");
// car center post transformation
cx = car.attr('x') + chw;
cy = car.attr('y') + chh;
// Transform tires, with rotation w.r.t. car center
/* Edited - To add wheel rotation about axle (25 deg) */
t1.transform("...t100,100r30," + [cx, cy] + "r25");
t2.transform("...t100,100r30," + [cx, cy] + "r25");
Hope this helps.
Let's leave the translation of the whole car out for the moment. You want to rotate the car and its tires. For each tire, you can find its new center by applying the rotation matrix to its old center.
x' = x∙cos α − y∙sin α
y' = x∙sin α + y∙cos α
I assume that Raphaël uses the upper left corner as the position of an image. If so, then your original centers would be (2+32/2, 50+32/2)=(18,66) and (84+32/2, 50+32/2)=(100,66). So these would be (x, y) for the above formula. The resulting position would be again a center position, so you'd have to subtract 16 from all coordinates. The translation of the whole car could simply be added to that result.
Related
Here's an image to demonstrate the question:
Let's say I have Point A at [0,0], and Point B at [50, 30]. I want to find the coordinates of Point X, along a circle of radius 15, with an origin at Point A, which is also on a line between Point A and Point B.
Pointers on the best method to do this?
Since this has been tagged JavaScript, here's a simple implementation:
// disclaimer: code written in browser
function Point2D(x, y) {
this.x = x;
this.y = y;
}
function findCircleInteresction(center, radius, target) {
var vector = new Point2D(target.x - center.x, target.y - target.y);
var length = Math.sqrt(Math.pow(vector.x, 2) + Math.pow(vector.y, 2));
var normal = new Point2D(vector.x / length, vector.y / length);
var result = new Point2D(center.x + (normal.x * radius), center.y + (normal.y * radius));
return result;
}
findCircleInteresction(new Point2D(0, 0), 15, new Point2D(50, 30));
Point2D is just a class to make objects with x and y properties.
findCircleInteresction takes three parameters:
- center the center of the circle
- radius the radius of the circle
- target a point outside the circle
In findCircleInteresction:
- calculate the vector between the center and the target
- get the length of the resulting vector
- compute the normal (normalized) of the vector
- find the point where the vector intersects with the circle by adding the center of the circle plus the normalized vector components multiplied by the radius of the circle
This code could be heavily optimized and it's untested but I think it illustrated the idea.
You would want to think of this as two overlapping triangles, one with sides Bx-Ax and By-Ay. What you want is to find the coordinates of X, which would specifically be a triangle with sides Xx-Ax and Xy-Ay but with known hypotenuse R, which is your radius of the circle. Notice that the angle for both triangles are equal in respect to the x-coordinates-axis.
So to get the angle of the triangle, take the arctan(By-Ay/Bx-Ax) Now with that angle, call it T, you can solve for the smaller legs with your know radius R.
To get the x coordinate you would take Rcos(T)
To get the y coordinate you would take Rsin(T)
Bringing it all together you have that Xx = Rcos(T) and Xy = Rsin(T)
If you are not willing to use a Math library, which this method would use, you can use ratio's (as Pointy commented)
Suppose my div has left:200px and top:400px, after I apply a rotate transform of suppose 90 deg the above top and left positions no more point to the old positions. Now how can we calculate the new top and left for the transformed div which are equivalent to the left and top positions of the non-transformed div after rotation.
Edited answer
Besides the starting position of the corner point (top-left in your example), and the rotation angle, we also need to know the position of the reference point of the rotation. This is the point around which we rotate the div (CSS calls it transform-origin). If you don't specify it, then normally, the centre of mass of the element is used.
I don't know of any JavaScript method that simply calculates it for you, but I can show you its Math, and a simple JS implementation.
Math
P: original position of the corner point, with (Px, Py) coordinates
O: reference point of the rotation, with (Ox, Oy) coordinates
Calculate the original position of P, relative to O.
x = Px - Ox
y = Py - Oy
Calculate the rotated position of P, relative to O.
x' = x * cos(angle) - y * sin(angle)
y' = x * sin(angle) + y * cos(angle)
Convert this position back to the original coordinate system.
Px' = x' + Ox
Py' = y' + Oy
If you're not aware of the formulas in step #2, you can find an explanation here.
JavaScript implementation
function rotatedPosition(pLeft, pTop, oLeft, oTop, angle){
// 1
var x = pLeft - oLeft;
var y = pTop - oTop;
// 2
var xRot = x * Math.cos(angle) - y * Math.sin(angle);
var yRot = x * Math.sin(angle) + y * Math.cos(angle);
// 3
var pLeftRot = xRot + oLeft;
var pTopRot = yRot + oTop
return {left: pLeftRot, top: pTopRot};
}
rotatedPosition requires you to define the original position of the point and the reference point, plus the angle.
In case you need a method which takes only a single argument, the div element itself, and computes the rest for you, then you can do something like:
function divTopLeftRotatedPosition(div){
var pLeft = // ...
var pTop = // ...
var width = // ...
var height = // ...
var angle = // ...
return rotatedPosition(pLeft, pTop, pLeft + width / 2, pTop + height / 2, angle);
}
I am working on a progress bar plugin for jQuery that utilizes Raphael for smooth graphics.
I tried to transform the attribute function provided by this Raphael example (polar clock).
The problem is, that at first I didn't notice that the Raphael example also has the deformation error there. Relatively larger circles just mitigate it. Looking at smaller ones, it is noticeable.
And yes, I have basicly copy-pasted the function with some minor tweaks, but the end result sport the same error.
I have set up a JSBin where I've added reference circles to my scene, so it's easier to spot the issue: http://jsbin.com/ekovir/1
How do I tune the Arc function to draw proper circle?
I think it's a bug in Chrome's SVG rendering implementation. At least in FireFox and Safari it looks much better.
Also, when selecting the arc-to point, I think it's better to use (center.x + radius * cos(a-0.01), center.y + radius * sin(a-0.01)), instead of (center.x + radius * cos(a) - 0.01, center.y + radius * sin(a)), otherwise the center may shift a bit.
As a workaround, I suggest creating one set of segments for the progress bar and then changing their color as the work is done, instead of drawing new ones over the old. This should look fine in any browser, and I don't think the defects are easy to spot without the contrasting background circle.
I have found what caused the circle to be deformed.
I used stroke-width to set the "fatness" / "cap" of the circle, and the larger it gets, the more it deforms.
At least, those are my observations, it could as well technically be caued by something else.
Anyways, in order to get proper donut, I ended up with this method:
/**
* Donut circle drawing
*
* #param integer start Percentage to start with
* #param float diameter
* #param float fat How fat should the circle bar be
* #return object
*/
var fatDonutArc = function (start, diameter, fat)
{
var center = diameter / 2;
var outerRadius = center;
var innerRadius = center - fat; // subtract fat
var alpha = 360 / 100 * start;
var a = (90 - alpha) * Math.PI / -180; // -180 starts to draw from 12 o'clock
// calculate outer ring point coordinates
var outerX = center + outerRadius * Math.cos(a);
var outerY = center + outerRadius * Math.sin(a);
// calculate inner ring point coordinates
var innerX = center + innerRadius * Math.cos(a);
var innerY = center + innerRadius * Math.sin(a);
// path cache
var path;
if (start !== 100)
{
path = [
// move to start point of inner ring
[
"M",
center,
center - innerRadius
],
// draw a line to outer ring
[
"L",
center,
center - outerRadius
],
// arc to outer ring end
[
"A",
outerRadius,
outerRadius,
0,
+(alpha > 180),
1,
outerX,
outerY
],
// move to inner ring end
[
"L",
innerX,
innerY
],
// arc to inner ring start
[
"A",
innerRadius,
innerRadius,
0,
+(alpha > 180),
0,
center,
center - innerRadius
]
];
}
else
{
path = [
// move to outer ring start
[
"M",
center,
center - outerRadius
],
// arc around the clock
[
"A",
outerRadius,
outerRadius,
0,
+(alpha > 180),
1,
outerX - .1, // subtract, otherwise the path becomes "reset"
outerY
],
// connect
[
"z"
],
// move to inner circle start
[
"M",
innerX,
innerY
],
// arc around the clock
[
"A",
innerRadius,
innerRadius,
0,
+(alpha > 180),
0,
innerX + .1, // subtract, otherwise the path becomes "reset"
innerY
],
// and connect
[
"z"
]
];
}
return {
path : path
};
};
That's a mashup of: raphael.js - converting pie graph to donut graph + http://raphaeljs.com/polar-clock.html
Here I have set up an example, to see it in action: http://jsbin.com/erusos/1
There still is one unanswered question: In Chrome, is it the CSS renderer, that doesn't fully round the circle, or is it the SVG?
Enjoy!
Basically I'm asking this question for JavaScript: Calculate Bounding box coordinates from a rotated rectangle
In this case:
iX = Width of rotated (blue) HTML element
iY = Height of rotated (blue) HTML element
bx = Width of Bounding Box (red)
by = Height of Bounding Box (red)
x = X coord of Bounding Box (red)
y = Y coord of Bounding Box (red)
iAngle/t = Angle of rotation of HTML element (blue; not shown but
used in code below), FYI: It's 37 degrees in this example (not that it matters for the example)
How does one calculate the X, Y, Height and Width of a bounding box (all the red numbers) surrounding a rotated HTML element (given its width, height, and Angle of rotation) via JavaScript? A sticky bit to this will be getting the rotated HTML element (blue box)'s original X/Y coords to use as an offset somehow (this is not represented in the code below). This may well have to look at CSS3's transform-origin to determine the center point.
I've got a partial solution, but the calculation of the X/Y coords is not functioning properly...
var boundingBox = function (iX, iY, iAngle) {
var x, y, bx, by, t;
//# Allow for negetive iAngle's that rotate counter clockwise while always ensuring iAngle's < 360
t = ((iAngle < 0 ? 360 - iAngle : iAngle) % 360);
//# Calculate the width (bx) and height (by) of the .boundingBox
//# NOTE: See https://stackoverflow.com/questions/3231176/how-to-get-size-of-a-rotated-rectangle
bx = (iX * Math.sin(iAngle) + iY * Math.cos(iAngle));
by = (iX * Math.cos(iAngle) + iY * Math.sin(iAngle));
//# This part is wrong, as it's re-calculating the iX/iY of the rotated element (blue)
//# we want the x/y of the bounding box (red)
//# NOTE: See https://stackoverflow.com/questions/9971230/calculate-rotated-rectangle-size-from-known-bounding-box-coordinates
x = (1 / (Math.pow(Math.cos(t), 2) - Math.pow(Math.sin(t), 2))) * (bx * Math.cos(t) - by * Math.sin(t));
y = (1 / (Math.pow(Math.cos(t), 2) - Math.pow(Math.sin(t), 2))) * (-bx * Math.sin(t) + by * Math.cos(t));
//# Return an object to the caller representing the x/y and width/height of the calculated .boundingBox
return {
x: parseInt(x), width: parseInt(bx),
y: parseInt(y), height: parseInt(by)
}
};
I feel like I am so close, and yet so far...
Many thanks for any help you can provide!
TO HELP THE NON-JAVASCRIPTERS...
Once the HTML element is rotated, the browser returns a "matrix transform" or "rotation matrix" which seems to be this: rotate(Xdeg) = matrix(cos(X), sin(X), -sin(X), cos(X), 0, 0); See this page for more info.
I have a feeling this will enlighten us on how to get the X,Y of the bounding box (red) based solely on the Width, Height and Angle of the rotated element (blue).
New Info
Humm... interesting...
Each browser seems to handle the rotation differently from an X/Y perspective! FF ignores it completely, IE & Opera draw the bounding box (but its properties are not exposed, ie: bx & by) and Chrome & Safari rotate the rectangle! All are properly reporting the X/Y except FF. So... the X/Y issue seems to exist for FF only! How very odd!
Also of note, it seems that $(document).ready(function () {...}); fires too early for the rotated X/Y to be recognized (which was part of my original problem!). I am rotating the elements directly before the X/Y interrogation calls in $(document).ready(function () {...}); but they don't seem to update until some time after(!?).
When I get a little more time, I will toss up a jFiddle with the example, but I'm using a modified form of "jquery-css-transform.js" so I have a tiny bit of tinkering before the jFiddle...
So... what's up, FireFox? That ain't cool, man!
The Plot Thickens...
Well, FF12 seems to fix the issue with FF11, and now acts like IE and Opera. But now I am back to square one with the X/Y, but at least I think I know why now...
It seems that even though the X/Y is being reported correctly by the browsers for the rotated object, a "ghost" X/Y still exists on the un-rotated version. It seems as though this is the order of operations:
Starting with an un-rotated element at an X,Y of 20,20
Rotate said element, resulting in the reporting of X,Y as 15,35
Move said element via JavaScript/CSS to X,Y 10,10
Browser logically un-rotates element back to 20,20, moves to 10,10 then re-rotates, resulting in an X,Y of 5,25
So... I want the element to end up at 10,10 post rotation, but thanks to the fact that the element is (seemingly) re-rotated post move, the resulting X,Y differs from the set X,Y.
This is my problem! So what I really need is a function to take the desired destination coords (10,10), and work backwards from there to get the starting X,Y coords that will result in the element being rotated into 10,10. At least I know what my problem is now, as thanks to the inner workings of the browsers, it seems with a rotated element 10=5!
I know this is a bit late, but I've written a fiddle for exactly this problem, on an HTML5 canvas:
http://jsfiddle.net/oscarpalacious/ZdQKg/
I hope somebody finds it useful!
I'm actually not calculating your x,y for the upper left corner of the container. It's calculated as a result of the offset (code from the fiddle example):
this.w = Math.sin(this.angulo) * rotador.h + Math.cos(this.angulo) * rotador.w;
this.h = Math.sin(this.angulo) * rotador.w + Math.cos(this.angulo) * rotador.h;
// The offset on a canvas for the upper left corner (x, y) is
// given by the first two parameters for the rect() method:
contexto.rect(-(this.w/2), -(this.h/2), this.w, this.h);
Cheers
Have you tried using getBoundingClientRect() ?
This method returns an object with current values of "bottom, height, left, right, top, width" considering rotations
Turn the four corners into vectors from the center, rotate them, and get the new min/max width/height from them.
EDIT:
I see where you're having problems now. You're doing the calculations using the entire side when you need to be doing them with the offsets from the center of rotation. Yes, this results in four rotated points (which, strangely enough, is exactly as many points as you started with). Between them there will be one minimum X, one maximum X, one minimum Y, and one maximum Y. Those are your bounds.
My gist can help you
Bounding box of a polygon (rectangle, triangle, etc.):
Live demo https://jsfiddle.net/Kolosovsky/tdqv6pk2/
let points = [
{ x: 125, y: 50 },
{ x: 250, y: 65 },
{ x: 300, y: 125 },
{ x: 175, y: 175 },
{ x: 100, y: 125 },
];
let minX = Math.min(...points.map(point => point.x));
let minY = Math.min(...points.map(point => point.y));
let maxX = Math.max(...points.map(point => point.x));
let maxY = Math.max(...points.map(point => point.y));
let pivot = {
x: maxX - ((maxX - minX) / 2),
y: maxY - ((maxY - minY) / 2)
};
let degrees = 90;
let radians = degrees * (Math.PI / 180);
let cos = Math.cos(radians);
let sin = Math.sin(radians);
function rotatePoint(pivot, point, cos, sin) {
return {
x: (cos * (point.x - pivot.x)) - (sin * (point.y - pivot.y)) + pivot.x,
y: (sin * (point.x - pivot.x)) + (cos * (point.y - pivot.y)) + pivot.y
};
}
let boundingBox = {
x1: Number.POSITIVE_INFINITY,
y1: Number.POSITIVE_INFINITY,
x2: Number.NEGATIVE_INFINITY,
y2: Number.NEGATIVE_INFINITY,
};
points.forEach((point) => {
let rotatedPoint = rotatePoint(pivot, point, cos, sin);
boundingBox.x1 = Math.min(boundingBox.x1, rotatedPoint.x);
boundingBox.y1 = Math.min(boundingBox.y1, rotatedPoint.y);
boundingBox.x2 = Math.max(boundingBox.x2, rotatedPoint.x);
boundingBox.y2 = Math.max(boundingBox.y2, rotatedPoint.y);
});
Bounding box of an ellipse:
Live demo https://jsfiddle.net/Kolosovsky/sLc7ynd1/
let centerX = 350;
let centerY = 100;
let radiusX = 100;
let radiusY = 50;
let degrees = 200;
let radians = degrees * (Math.PI / 180);
let radians90 = radians + Math.PI / 2;
let ux = radiusX * Math.cos(radians);
let uy = radiusX * Math.sin(radians);
let vx = radiusY * Math.cos(radians90);
let vy = radiusY * Math.sin(radians90);
let width = Math.sqrt(ux * ux + vx * vx) * 2;
let height = Math.sqrt(uy * uy + vy * vy) * 2;
let x = centerX - (width / 2);
let y = centerY - (height / 2);
I have Google Maps icons which I need to rotate by certain angles before drawing on the map using MarkerImage. I do the rotation on-the-fly in Python using PIL, and the resulting image is of the same size as the original - 32x32. For example, with the following default Google Maps marker:
, a 30 degrees conter-clockwise rotation is achieved using the following python code:
# full_src is a variable holding the full path to image
# rotated is a variable holding the full path to where the rotated image is saved
image = Image.open(full_src)
png_info = image.info
image = image.copy()
image = image.rotate(30, resample=Image.BICUBIC)
image.save(rotated, **png_info)
The resulting image is
The tricky bit is getting the new anchor point to use when creating the MarkerImage using the new rotated image. This needs to be the pointy end of the icon. By default, the anchor point is the bottom middle [defined as (16,32) in x,y coordinates where (0,0) is the top left corner]. Can someone please explain to me how I can easily go about this in JavaScript?
Thanks.
Update 22 Jun 2011:
Had posted the wrong rotated image (original one was for 330 degrees counter-clockwise). I've corrected that. Also added resampling (Image.BICUBIC) which makes the rotated icon clearer.
To calculate the position of a rotated point you can use a rotation matrix.
Converted into JavaScript, this calculates the rotated point:
function rotate(x, y, xm, ym, a) {
var cos = Math.cos,
sin = Math.sin,
a = a * Math.PI / 180, // Convert to radians because that is what
// JavaScript likes
// 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];
}
rotate(16, 32, 16, 16, 30); // [8, 29.856...]
The formula for rotations about 0,0 is:
x1 = cos(theta) x0 - sin(theta) y0
y1 = sin(theta) x0 + cos(theta) y0
But that's for regular axes, and rotation about 0,0. The PIL rotation is clockwise with "graphics" axes. Plus, it's around the center of the image. The final confusing thing is that the size of the image can change, which needs to be accounted for in the final result.
Procedure: take original point, subtract off center of image, apply "graphics axes" corrected rotation, find new size of image, add back center position of new image.
Rotation using graphics axes is:
x1 = cos(theta) x0 + sin(theta) y0
y1 = -sin(theta) x0 + cos(theta) y0
16,32 - 16,16 is 0, 16. Rotate 30 degrees clockwise rotation (based on your images) gives a point cos(-30)*0+sin(-30)*16, -sin(-30)*0+cos(-30)*16 = -8, 13.86. The final step is adding back the center position of the rotated position.
In an image, downwards is positive Y and rightwards is positive X. However, to apply the rotation formula, we need upwards as positive Y. Therefore, step 1 would be to apply f(x,y) = f(x,h-y), where 'h' is the height of the image.
Let's say the image is rotated with respect to x0,y0. You'd then need to transform your origin to this point. Therefore, step 2 would be f(x,y) = f(x-x0,y-y0). At this stage (i.e. after the two steps), your new co-ordinates would be x-x0, h-y-y0. You're now ready to apply the rotation formula
x1 = x*cos(theta) - y*sin(theta)
y1 = xsin(theta) + ycos(theta)
Use the values of x and y obtained after step two.
You'd get
x1 = (x-x0)*cos(theta) - (h-y-y0)*sin(theta)
y1 = (x-x0)*sin(theta) + (h-y-y0)*cos(theta)
Now, undo transformations done in step 2 and step 1 (in that order).
After undoing step2: xNew = x1 + x0 and yNew = y1 + y0
After undoing step1: xNew = x1 + x0 and yNew = h - (y1 + y0)
This gives you:
xNew = (x-x0)*cos(theta) - (h-y-y0)*sin(theta) + x0
yNew = -(x-x0)*sin(theta) - (h-y-y0)*cos(theta) + (h-y0)