I'm trying to work a simple Bezier curve but I just can't understand the maths behind it, if someone could explain to me this simple Bezier curve it would be awesome :)
We have
point1
X: 0
Y: 0
point2
X: 100
Y: 100
control point1
X: 0
Y: 100
So basically we have a line that starts from the top left and will get a diagonal line from the top left to the bottom right of a 100 by 100 pixel square, but now I want to try and pull that line to the control point at X position 0 and Y position 100, could someone explain to me the simple maths that happens here, can if possible show the calculations with my values :).
Thank you
P.S. a basic picture
UPDATE
Here is my simple javascript code that uses the equation
canvasHolder = document.getElementById( 'canvas' );
canvas = canvasHolder.getContext('2d');
var deltaT = 0.10;
var point1 = new Point(300,300);
var point2 = new Point(400,400);
var controlPoint1 = new Point(100,200);
//Bezier cruve
for( var i = 0; i < 10; i++ )
{
var t = (i/10) * 0.1;
var xPos = ( 1.0 * - t*t ) * point1.x + 2.0 * ( 1 - t ) * t * controlPoint1.x + t * t * point2.x;
var yPos = ( 1.0 * - t*t ) * point1.y + 2.0 * ( 1 - t ) * t * controlPoint1.y + t * t * point2.y;
console.log( "xPos = " + xPos + ", yPos = " + yPos );
var particle = new Particle( "circle", xPos, yPos, 5, "#FF0000", "#333333");
particles[i] = particle;
}
here is the output for that
xPos = 0, yPos = 0 bezierCurve.js:35
xPos = 1.9900000000000004, yPos = 3.970000000000001 bezierCurve.js:35
xPos = 3.9600000000000004, yPos = 7.880000000000001 bezierCurve.js:35
xPos = 5.909999999999999, yPos = 11.729999999999999 bezierCurve.js:35
xPos = 7.840000000000001, yPos = 15.520000000000001 bezierCurve.js:35
xPos = 9.75, yPos = 19.25 bezierCurve.js:35
xPos = 11.639999999999997, yPos = 22.919999999999998 bezierCurve.js:35
xPos = 13.509999999999996, yPos = 26.529999999999998 bezierCurve.js:35
xPos = 15.360000000000003, yPos = 30.080000000000005 bezierCurve.js:35
xPos = 17.190000000000005, yPos = 33.57000000000001
any reason on why I am getting this result
Edit,
its ok i was doing this
var xPos = ( 1.0 * - t*t ) * point1.x + 2.0 * ( 1 - t ) * t * controlPoint1.x + t * t * point2.x;
when it is this
var xPos = ( 1.0 - t*t ) * point1.x + 2.0 * ( 1 - t ) * t * controlPoint1.x + t * t * point2.x;
Bezier curves are parametric curves. That means that you will have 2 equations: 1 for the x coordinates over the curve, and the other for the y coordinates over the curve.
In this case, you're working with a quadratic bezier. The equation for quadratic beziers is:
x(t) = (1.0 - t^2) * p0.x + 2.0 * (1 - t) * t * p1.x + t^2 * p2.x;
y(t) = (1.0 - t^2) * p0.y + 2.0 * (1 - t) * t * p1.y + t^2 * p2.y;
In this case, p0 is the start point, p1 is the control point, and p2 is the end point.
t varies from 0 to 1 over the curve. So to draw it using a standard line drawing function, you'd do the following:
float numDivisions = 100.0; // <- You need to decide what this value should be. See below
float t;
float deltaT = t / numDivisions;
MoveTo (p0.x, p0.y);
for (t = 0; t <= 1.0; t += deltaT)
{
x = (1.0 - t*t) * p0.x + 2.0 * (1 - t) * t * p1.x + t * t * p2.x;
y = (1.0 - t*t) * p0.y + 2.0 * (1 - t) * t * p1.y + t * t * p2.y;
LineTo (x, y);
}
The numDivisions will control the resolution of the curves. If you only have 3 divisions, you'll get the start point, 1 point in the middle (parametrically speaking), and the end point. If you use 10,000 points, you'll probably draw much more than you need to. A rule of thumb is that the straight lines connecting the points will always be longer than the actual curve, so you never need to use a number of divisions greater than that. (You can use a Euclidean distance between the points.)
You can derive the parametric equations for a bezier curve from the geometrical representation:
(a)--A-----(c)
x \
C
\
\
(b)
Draw a line from a->c->b
Subdivide the line from a->c by a point A, where A = t *(c- a) + a, i.e. t units from a towards c (0<=t<=1). Using the same ratio t, advance from c towards b, the same t units (point C).
(Recursively) subdivide A->C to sections of t and 1-t, placing the point x at the splitting point. That's the true point located at the Bezier curve.
The same procedure can be used to generate cubic Beziers -- adding one more step to the process.
Summing: (a = { ax, ay }), A = { Ax, Ay }) etc.
A = (c-a)*t + a = { cx-ax * t + ax, cy-ay * t + ay }
B = (b-c)*t + c = { bx-cx * t + cx, by-cy * t + cy }
x = (C-A)*t + A = { Cx-Ax * t + Ax, Cy-Ay * y + Ay }
(Notice, that if a = { ax, ay, az } i.e. a 3D point, the same principle holds)
These formulas can now be combined to produce a closed form equation for Cx, Cy, [Cz] as a quadratic function of t, and constants ax,ay,cx,cy,bx,by, as in the first answer by user1118321.
Im trying to make stars on the sky, but the stars distribution isnt even.
This is what i tried:
rx = rand(0.0f, PI*2.0f);
ry = rand(0.0f, PI);
x = sin(ry)*sin(rx)*range;
y = sin(ry)*cos(rx)*range;
z = cos(ry)*range;
Which results to:
img http://img716.imageshack.us/img716/3320/sphererandom.jpg
And:
rx = rand(-1.0f, 1.0f);
ry = rand(-1.0f, 1.0f);
rz = rand(-1.0f, 1.0f);
x = rx*range;
y = ry*range;
z = rz*range;
Which results to:
img2 http://img710.imageshack.us/img710/5152/squarerandom.jpg
(doesnt make a sphere, but opengl will not tell a difference, though).
As you can see, there is always some "corner" where are more points in average. How can i create random points on a sphere where the points will be distributed evenly?
you can do
z = rand(-1, 1)
rxy = sqrt(1 - z*z)
phi = rand(0, 2*PI)
x = rxy * cos(phi)
y = rxy * sin(phi)
Here rand(u,v) draws a uniform random from interal [u,v]
You don't need trigonometry if you can generate random gaussian variables, you can do (pseudocode)
x <- gauss()
y <- gauss()
z <- gauss()
norm <- sqrt(x^2 + y^2 + z^2)
result = (x / norm, y / norm, z / norm)
Or draw points inside the unit cube until one of them is inside the unit ball, then normalize:
double x, y, z;
do
{
x = rand(-1, 1);
y = rand(-1, 1);
z = rand(-1, 1);
} while (x * x + y * y + z * z > 1);
double norm = sqrt(x * x + y * y + z * z);
x / norm; y /= norm; z /= norm;
It looks like you can see that it's the cartesian coordinates that are creating the concentrations.
Here is an explanation of one right (and wrong) way to get a proper distribution.
I realize this might be more of a math problem.
To draw the lines for my rectangles I need to solve for their corners.
I have a rectangle center at (x,y) With a defined Width and Height.
To find the blue points on a non rotated rectangle on top (angle = 0)
It is
UL = (x-Width/2),(y+height/2)
UR = (x+Width/2),(y+height/2)
LR = (x+Width/2),(y-height/2)
LL = (x-Width/2),(y-height/2)
How do I find the points if the angle isn't 0?
Thanks in advance.
Update: although I have (0,0) in my picture as the center point most likely the center point won't be at that location.
First transform the centre point to 0,0
X' = X-x
Y' = Y-y
Then rotate for an angle of A
X'' = (X-x) * cos A - (Y-y) * sin A
Y'' = (Y-y) * cos A + (X-x) * sin A
Again transform back the centre point to x,y
X''' = (X-x) * cos A - (Y-y) * sin A + x
Y''' = (Y-y) * cos A + (X-x) * sin A + y
Hence compute for all 4 points of (X,Y) with following transformation
X''' = (X-x) * cos A - (Y-y) * sin A + x
Y''' = (Y-y) * cos A + (X-x) * sin A + y
where x, y are the centre points of rectangle and X,Y are the corner points
You have n't defined correctly even the corner points when Angle is 0 as I have given in the comments.
After substituting you will get
UL = x + ( Width / 2 ) * cos A - ( Height / 2 ) * sin A , y + ( Height / 2 ) * cos A + ( Width / 2 ) * sin A
UR = x - ( Width / 2 ) * cos A - ( Height / 2 ) * sin A , y + ( Height / 2 ) * cos A - ( Width / 2 ) * sin A
BL = x + ( Width / 2 ) * cos A + ( Height / 2 ) * sin A , y - ( Height / 2 ) * cos A + ( Width / 2 ) * sin A
BR = x - ( Width / 2 ) * cos A + ( Height / 2 ) * sin A , y - ( Height / 2 ) * cos A - ( Width / 2 ) * sin A
I think this suits your solution.
If 'theta' is the anti-clockwise angle of rotation, then the rotation matrix is:
| cos(theta) -sin(theta) |
| sin(theta) cos(theta) |
i.e.
x' = x.cos(theta) - y.sin(theta)
y' = x.sin(theta) + y.cos(theta)
If the rotation point isn't at the origin, subtract the center of rotation from your original coordinates, perform the rotation as shown above, and then add the center of rotation back in again.
There's examples of other transformations at http://en.wikipedia.org/wiki/Transformation_matrix
Rotation matrix
(this is becoming a FAQ)
See 2D Rotation.
q = initial angle, f = angle of rotation.
x = r cos q
y = r sin q
x' = r cos ( q + f ) = r cos q cos f - r sin q sin f
y' = r sin ( q + w ) = r sin q cos f + r cos q sin f
hence:
x' = x cos f - y sin f
y' = y cos f + x sin f
One of the easiest ways to do this is to take the location of the point before rotation and then apply a coordinate transform. Since it's centred on (0,0), this is simply a case of using:
x' = x cos(theta) - y sin(theta)
y' = y cos(theta) + x sin(theta)
use this....I got succeded...
ctx.moveTo(defaults.x1, defaults.y1);
// Rotation formula
var x2 = (defaults.x1) + defaults.lineWidth * Math.cos(defaults.rotation * (Math.PI / 180));
var y2 = (defaults.y1) + defaults.lineWidth * Math.sin(defaults.rotation * (Math.PI / 180));
ctx.lineTo(x2, y2);
x2 = (x2) + defaults.lineHeight * Math.cos((defaults.rotation + 90) * (Math.PI / 180));
y2 = (y2) + defaults.lineHeight * Math.sin((defaults.rotation + 90) * (Math.PI / 180));
ctx.lineTo(x2, y2);
x2 = (x2) + defaults.lineWidth * Math.cos((defaults.rotation + 180) * (Math.PI / 180));
y2 = (y2) + defaults.lineWidth * Math.sin((defaults.rotation + 180) * (Math.PI / 180));
ctx.lineTo(x2, y2);
x2 = (x2) + defaults.lineHeight * Math.cos((defaults.rotation + 270) * (Math.PI / 180));
y2 = (y2) + defaults.lineHeight * Math.sin((defaults.rotation + 270) * (Math.PI / 180));
If the major axis of the ellipse is vertical or horizontal, it's easy to calculate the bounding box, but what about when the ellipse is rotated?
The only way I can think of so far is to calculate all the points around the perimeter and find the max/min x and y values. It seems like there should be a simpler way.
If there's a function (in the mathematical sense) that describes an ellipse at an arbitrary angle, then I could use its derivative to find points where the slope is zero or undefined, but I can't seem to find one.
Edit: to clarify, I need the axis-aligned bounding box, i.e. it should not be rotated with the ellipse, but stay aligned with the x axis so transforming the bounding box won't work.
You could try using the parametrized equations for an ellipse rotated at an arbitrary angle:
x = h + a*cos(t)*cos(phi) - b*sin(t)*sin(phi) [1]
y = k + b*sin(t)*cos(phi) + a*cos(t)*sin(phi) [2]
...where ellipse has centre (h,k) semimajor axis a and semiminor axis b, and is rotated through angle phi.
You can then differentiate and solve for gradient = 0:
0 = dx/dt = -a*sin(t)*cos(phi) - b*cos(t)*sin(phi)
=>
tan(t) = -b*tan(phi)/a [3]
Which should give you many solutions for t (two of which you are interested in), plug that back into [1] to get your max and min x.
Repeat for [2]:
0 = dy/dt = b*cos(t)*cos(phi) - a*sin(t)*sin(phi)
=>
tan(t) = b*cot(phi)/a [4]
Lets try an example:
Consider an ellipse at (0,0) with a=2, b=1, rotated by PI/4:
[1] =>
x = 2*cos(t)*cos(PI/4) - sin(t)*sin(PI/4)
[3] =>
tan(t) = -tan(PI/4)/2 = -1/2
=>
t = -0.4636 + n*PI
We are interested in t = -0.4636 and t = -3.6052
So we get:
x = 2*cos(-0.4636)*cos(PI/4) - sin(-0.4636)*sin(PI/4) = 1.5811
and
x = 2*cos(-3.6052)*cos(PI/4) - sin(-3.6052)*sin(PI/4) = -1.5811
I found a simple formula at http://www.iquilezles.org/www/articles/ellipses/ellipses.htm (and ignored the z axis).
I implemented it roughly like this:
num ux = ellipse.r1 * cos(ellipse.phi);
num uy = ellipse.r1 * sin(ellipse.phi);
num vx = ellipse.r2 * cos(ellipse.phi+PI/2);
num vy = ellipse.r2 * sin(ellipse.phi+PI/2);
num bbox_halfwidth = sqrt(ux*ux + vx*vx);
num bbox_halfheight = sqrt(uy*uy + vy*vy);
Point bbox_ul_corner = new Point(ellipse.center.x - bbox_halfwidth,
ellipse.center.y - bbox_halfheight);
Point bbox_br_corner = new Point(ellipse.center.x + bbox_halfwidth,
ellipse.center.y + bbox_halfheight);
This is relative simple but a bit hard to explain since you haven't given us the way you represent your ellipse. There are so many ways to do it..
Anyway, the general principle goes like this: You can't calculate the axis aligned boundary box directly. You can however calculate the extrema of the ellipse in x and y as points in 2D space.
For this it's sufficient to take the equation x(t) = ellipse_equation(t) and y(t) = ellipse_equation(t). Get the first order derivate of it and solve it for it's root. Since we're dealing with ellipses that are based on trigonometry that's straight forward. You should end up with an equation that either gets the roots via atan, acos or asin.
Hint: To check your code try it with an unrotated ellipse: You should get roots at 0, Pi/2, Pi and 3*Pi/2.
Do that for each axis (x and y). You will get at most four roots (less if your ellipse is degenerated, e.g. one of the radii is zero). Evalulate the positions at the roots and you get all extreme points of the ellipse.
Now you're almost there. Getting the boundary box of the ellipse is as simple as scanning these four points for xmin, xmax, ymin and ymax.
Btw - if you have problems finding the equation of your ellipse: try to reduce it to the case that you have an axis aligned ellipse with a center, two radii and a rotation angle around the center.
If you do so the equations become:
// the ellipse unrotated:
temp_x(t) = radius.x * cos(t);
temp_y(t) = radius.y * sin(t);
// the ellipse with rotation applied:
x(t) = temp_x(t) * cos(angle) - temp_y(t) * sin(angle) + center.x;
y(t) = temp_x(t) * sin(angle) + temp_y(t) * cos(angle) + center.y;
Brilian Johan Nilsson.
I have transcribed your code to c# - ellipseAngle are now in degrees:
private static RectangleF EllipseBoundingBox(int ellipseCenterX, int ellipseCenterY, int ellipseRadiusX, int ellipseRadiusY, double ellipseAngle)
{
double angle = ellipseAngle * Math.PI / 180;
double a = ellipseRadiusX * Math.Cos(angle);
double b = ellipseRadiusY * Math.Sin(angle);
double c = ellipseRadiusX * Math.Sin(angle);
double d = ellipseRadiusY * Math.Cos(angle);
double width = Math.Sqrt(Math.Pow(a, 2) + Math.Pow(b, 2)) * 2;
double height = Math.Sqrt(Math.Pow(c, 2) + Math.Pow(d, 2)) * 2;
var x= ellipseCenterX - width * 0.5;
var y= ellipseCenterY + height * 0.5;
return new Rectangle((int)x, (int)y, (int)width, (int)height);
}
I think the most useful formula is this one. An ellipsis rotated from an angle phi from the origin has as equation:
where (h,k) is the center, a and b the size of the major and minor axis and t varies from -pi to pi.
From that, you should be able to derive for which t dx/dt or dy/dt goes to 0.
Here is the formula for the case if the ellipse is given by its foci and eccentricity (for the case where it is given by axis lengths, center and angle, see e. g. the answer by user1789690).
Namely, if the foci are (x0, y0) and (x1, y1) and the eccentricity is e, then
bbox_halfwidth = sqrt(k2*dx2 + (k2-1)*dy2)/2
bbox_halfheight = sqrt((k2-1)*dx2 + k2*dy2)/2
where
dx = x1-x0
dy = y1-y0
dx2 = dx*dx
dy2 = dy*dy
k2 = 1.0/(e*e)
I derived the formulas from the answer by user1789690 and Johan Nilsson.
If you work with OpenCV/C++ and use cv::fitEllipse(..) function, you may need bounding rect of ellipse. Here I made a solution using Mike's answer:
// tau = 2 * pi, see tau manifest
const double TAU = 2 * std::acos(-1);
cv::Rect calcEllipseBoundingBox(const cv::RotatedRect &anEllipse)
{
if (std::fmod(std::abs(anEllipse.angle), 90.0) <= 0.01) {
return anEllipse.boundingRect();
}
double phi = anEllipse.angle * TAU / 360;
double major = anEllipse.size.width / 2.0;
double minor = anEllipse.size.height / 2.0;
if (minor > major) {
std::swap(minor, major);
phi += TAU / 4;
}
double cosPhi = std::cos(phi), sinPhi = std::sin(phi);
double tanPhi = sinPhi / cosPhi;
double tx = std::atan(-minor * tanPhi / major);
cv::Vec2d eqx{ major * cosPhi, - minor * sinPhi };
double x1 = eqx.dot({ std::cos(tx), std::sin(tx) });
double x2 = eqx.dot({ std::cos(tx + TAU / 2), std::sin(tx + TAU / 2) });
double ty = std::atan(minor / (major * tanPhi));
cv::Vec2d eqy{ major * sinPhi, minor * cosPhi };
double y1 = eqy.dot({ std::cos(ty), std::sin(ty) });
double y2 = eqy.dot({ std::cos(ty + TAU / 2), std::sin(ty + TAU / 2) });
cv::Rect_<float> bb{
cv::Point2f(std::min(x1, x2), std::min(y1, y2)),
cv::Point2f(std::max(x1, x2), std::max(y1, y2))
};
return bb + anEllipse.center;
}
Here's a typescript function based on the above answers.
export function getRotatedEllipseBounds(
x: number,
y: number,
rx: number,
ry: number,
rotation: number
) {
const c = Math.cos(rotation)
const s = Math.sin(rotation)
const w = Math.hypot(rx * c, ry * s)
const h = Math.hypot(rx * s, ry * c)
return {
minX: x + rx - w,
minY: y + ry - h,
maxX: x + rx + w,
maxY: y + ry + h,
width: w * 2,
height: h * 2,
}
}
This code is based on the code user1789690 contributed above, but implemented in Delphi. I have tested this and as far as I can tell it works perfectly. I spent an entire day searching for an algorithm or some code, tested some that didn't work, and I was very happy to finally find the code above. I hope someone finds this useful. This code will calculate the bounding box of a rotated ellipse. The bounding box is axis aligned and NOT rotated with the ellipse. The radiuses are for the ellipse before it was rotated.
type
TSingleRect = record
X: Single;
Y: Single;
Width: Single;
Height: Single;
end;
function GetBoundingBoxForRotatedEllipse(EllipseCenterX, EllipseCenterY, EllipseRadiusX, EllipseRadiusY, EllipseAngle: Single): TSingleRect;
var
a: Single;
b: Single;
c: Single;
d: Single;
begin
a := EllipseRadiusX * Cos(EllipseAngle);
b := EllipseRadiusY * Sin(EllipseAngle);
c := EllipseRadiusX * Sin(EllipseAngle);
d := EllipseRadiusY * Cos(EllipseAngle);
Result.Width := Hypot(a, b) * 2;
Result.Height := Hypot(c, d) * 2;
Result.X := EllipseCenterX - Result.Width * 0.5;
Result.Y := EllipseCenterY - Result.Height * 0.5;
end;
This is my function for finding tight fit rectangle to ellipse with arbitrary orientation
I have opencv rect and point for implementation:
cg - center of the ellipse
size - major, minor axis of ellipse
angle - orientation of ellipse
cv::Rect ellipse_bounding_box(const cv::Point2f &cg, const cv::Size2f &size, const float angle) {
float a = size.width / 2;
float b = size.height / 2;
cv::Point pts[4];
float phi = angle * (CV_PI / 180);
float tan_angle = tan(phi);
float t = atan((-b*tan_angle) / a);
float x = cg.x + a*cos(t)*cos(phi) - b*sin(t)*sin(phi);
float y = cg.y + b*sin(t)*cos(phi) + a*cos(t)*sin(phi);
pts[0] = cv::Point(cvRound(x), cvRound(y));
t = atan((b*(1 / tan(phi))) / a);
x = cg.x + a*cos(t)*cos(phi) - b*sin(t)*sin(phi);
y = cg.y + b*sin(t)*cos(phi) + a*cos(t)*sin(phi);
pts[1] = cv::Point(cvRound(x), cvRound(y));
phi += CV_PI;
tan_angle = tan(phi);
t = atan((-b*tan_angle) / a);
x = cg.x + a*cos(t)*cos(phi) - b*sin(t)*sin(phi);
y = cg.y + b*sin(t)*cos(phi) + a*cos(t)*sin(phi);
pts[2] = cv::Point(cvRound(x), cvRound(y));
t = atan((b*(1 / tan(phi))) / a);
x = cg.x + a*cos(t)*cos(phi) - b*sin(t)*sin(phi);
y = cg.y + b*sin(t)*cos(phi) + a*cos(t)*sin(phi);
pts[3] = cv::Point(cvRound(x), cvRound(y));
long left = 0xfffffff, top = 0xfffffff, right = 0, bottom = 0;
for (int i = 0; i < 4; i++) {
left = left < pts[i].x ? left : pts[i].x;
top = top < pts[i].y ? top : pts[i].y;
right = right > pts[i].x ? right : pts[i].x;
bottom = bottom > pts[i].y ? bottom : pts[i].y;
}
cv::Rect fit_rect(left, top, (right - left) + 1, (bottom - top) + 1);
return fit_rect;
}
Here's a simple example of bounding box around rotated ellipse in javascript:
https://jsfiddle.net/rkn61mjL/1/
The idea is pretty simple and doesn't require complex calculations and solving gradients:
calculate a simple bounding box of non-rotated ellipse:
let p1 = [centerX - radiusX, centerY - radiusY];
let p2 = [centerX + radiusX, centerY - radiusY];
let p3 = [centerX + radiusX, centerY + radiusY];
let p4 = [centerX - radiusX, centerY + radiusY];
rotate all of the four points around the center of the ellipse:
p1 = [(p1[0]-centerX) * Math.cos(radians) - (p1[1]-centerY) * Math.sin(radians) + centerX,
(p1[0]-centerX) * Math.sin(radians) + (p1[1]-centerY) * Math.cos(radians) + centerY];
p2 = [(p2[0]-centerX) * Math.cos(radians) - (p2[1]-centerY) * Math.sin(radians) + centerX,
(p2[0]-centerX) * Math.sin(radians) + (p2[1]-centerY) * Math.cos(radians) + centerY];
p3 = [(p3[0]-centerX) * Math.cos(radians) - (p3[1]-centerY) * Math.sin(radians) + centerX,
(p3[0]-centerX) * Math.sin(radians) + (p3[1]-centerY) * Math.cos(radians) + centerY];
p4 = [(p4[0]-centerX) * Math.cos(radians) - (p4[1]-centerY) * Math.sin(radians) + centerX,
(p4[0]-centerX) * Math.sin(radians) + (p4[1]-centerY) * Math.cos(radians) + centerY];
Here is another version of Pranay Soni's code, implemented in js codepen I hope someone will find it useful
/**
* #param {Number} rotation
* #param {Number} majorAxis
* #param {Nmber} minorAxis
* #pivot {Point} pivot {x: number, y: number}
* #returns {Object}
*/
export function getElipseBoundingLines(ratation, majorAxis, minorAxis, pivot) {
const {cos, sin, tan, atan, round, min, max, PI} = Math;
let phi = rotation / 180 * PI;
if(phi === 0) phi = 0.00001;
// major axis
let a = majorAxis;
//minor axis
let b = minorAxis;
const getX = (pivot, phi, t) => {
return round(pivot.x + a * cos(t) * cos(phi) - b * sin(t) * sin(phi))
}
const getY = (pivot, phi, t) => {
return round(pivot.y + b * sin(t) * cos(phi) + a * cos(t) * sin(phi))
}
const X = [], Y = [];
let t = atan(-b * tan(phi) / a);
X.push(getX(pivot, phi, t));
Y.push(getY(pivot, phi, t));
t = atan(b * (1 / tan(phi) / a));
X.push(getX(pivot, phi, t));
Y.push(getY(pivot, phi, t));
phi += PI;
t = atan(-b * tan(phi) / a);
X.push(getX(pivot, phi, t));
Y.push(getY(pivot, phi, t));
t = atan(b * (1 / tan(phi)) / a);
X.push(getX(pivot, phi, t));
Y.push(getY(pivot, phi, t));
const left = min(...X);
const right = max(...X);
const top = min(...Y);
const bottom = max(...Y);
return {left, top, right, bottom};
}
The general method is to find the zeroes of the derivative of the parametric form of the ellipse along X and Y axes. The position of those zeroes give the edge points along vertical and horizontal directions (derivative is zero).
// compute point on ellipse from angle around ellipse (theta)
function arc(theta, cx, cy, rx, ry, alpha)
{
// theta is angle in radians around arc
// alpha is angle of rotation of ellipse in radians
var cos = Math.cos(alpha), sin = Math.sin(alpha),
x = rx*Math.cos(theta), y = ry*Math.sin(theta);
return {
x: cx + cos*x - sin*y,
y: cy + sin*x + cos*y
};
}
function bb_ellipse(cx, cy, rx, ry, alpha)
{
var tan = Math.tan(alpha),
p1, p2, p3, p4, theta,
xmin, ymin, xmax, ymax
;
// find min/max from zeroes of directional derivative along x and y
// along x axis
theta = Math.atan2(-ry*tan, rx);
// get point for this theta
p1 = arc(theta, cx, cy, rx, ry, alpha);
// get anti-symmetric point
p2 = arc(theta + Math.PI, cx, cy, rx, ry, alpha);
// along y axis
theta = Math.atan2(ry, rx*tan);
// get point for this theta
p3 = arc(theta, cx, cy, rx, ry, alpha);
// get anti-symmetric point
p4 = arc(theta + Math.PI, cx, cy, rx, ry, alpha);
// compute min/max values
ymin = Math.min(p3.y, p4.y)
xmin = Math.min(p1.x, p2.x);
ymax = Math.max(p3.y, p4.y);
xmax = Math.max(p1.x, p2.x);
// return bounding box vertices
return [
{x: xmin, y: ymin},
{x: xmax, y: ymin},
{x: xmax, y: ymax},
{x: xmin, y: ymax}
];
}
var cx = 120, cy = 120, rx = 100, ry = 40, alpha = -45;
function ellipse(cx, cy, rx, ry, alpha)
{
// create an ellipse
const ellipse = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');
ellipse.setAttribute('stroke', 'black');
ellipse.setAttribute('fill', 'none');
ellipse.setAttribute('cx', cx);
ellipse.setAttribute('cy', cy);
ellipse.setAttribute('rx', rx);
ellipse.setAttribute('ry', ry);
ellipse.setAttribute('transform', 'rotate('+alpha+' '+cx+' '+cy+')');
document.getElementById('svg').appendChild(ellipse);
// create the bounding box
const bb = bb_ellipse(cx, cy, rx, ry, /*angle in radians*/ alpha*Math.PI/180);
const polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
polygon.setAttribute('stroke', 'red');
polygon.setAttribute('fill', 'none');
polygon.setAttribute('points', bb.map(p => String(p.x) + ' ' + String(p.y)).join(' '));
document.getElementById('svg').appendChild(polygon);
}
ellipse(cx, cy, rx, ry, alpha);
<svg xmlns="http://www.w3.org/2000/svg" id="svg" style="position:relative;width:240px;height:240px" viewBox="0 0 240 240"></svg>