Orientation Matrix from Heading, Pitch and Roll - math

I have a problem converting Heading, Pitch and Roll to an Orientation Matrix
How would I go about improving this to ensure Y and Z and are correctly calculated.
--[[
get_orientation - Returns the orientation matrix of an object based on its heading, pitch, and roll in degrees.
#param heading number - The heading of the object in degrees.
#param pitch number - The pitch of the object in degrees.
#param roll number - The roll of the object in degrees.
#returns orientation table - The orientation matrix of the object, represented as a table with three unit vectors: x, y, and z.
]]
local function get_orientation(heading, pitch, roll)
local orientation = {}
-- Convert the heading, pitch, and roll from degrees to radians using math.rad
heading = math.rad(heading)
pitch = math.rad(pitch)
roll = math.rad(roll)
-- Calculate the x unit vector
-- x is the Vec3 unit vector that points in the direction of the object's front
orientation.x = {}
orientation.x.x = math.cos(heading) * math.cos(pitch)
orientation.x.y = math.sin(pitch)
orientation.x.z = math.sin(heading) * math.cos(pitch)
-- Calculate the y unit vector
-- y is the Vec3 unit vector that points in the direction of the object's top
orientation.y = {}
orientation.y.x = -math.cos(heading) * math.sin(pitch)
orientation.y.y = math.cos(pitch)
orientation.y.z = -math.sin(heading) * math.sin(pitch)
-- Calculate the z unit vector
-- z is the Vec3 unit vector that points in the direction of the object's right side
orientation.z = {}
orientation.z.x = -math.sin(heading)
orientation.z.z = -math.cos(heading) * math.cos(roll)
orientation.z.y = math.sin(roll)
-- Return the orientation matrix of the object
return orientation
end
---------------------------------------------------
---------------------------------------------------
local lat = 41.610278
local lon = 41.599444
local heading = 90
local pitch = 25
local roll = 0
local alt = 100
local x, y = terrain.convertLatLonToMeters(lat, lon)
local orientation = get_orientation(heading, pitch, roll)
local position = {
x=orientation.x,
y=orientation.y,
z=orientation.z,
p={x=x,y=alt,z=y}
}
Export.LoSetCameraPosition(position)
local actual = Export.LoGetCameraPosition()
return {position=position, actual=actual}
There is a little more information about the Orientation here
https://www.digitalcombatsimulator.com/en/support/faq/1256/#:~:text=level%2010%20m-,Orientation,-Object%20orientation%20is
I am attempting to build a function that takes lat, lon, alt, heading, pitch and roll and produce the Position matrix.
I have the example function so far, which does work but everything goes funky after -81 pitch and the roll does not work as intended.

Formulas for 3D rotation are not simple, but they can be simply deduced from three consecutive 2D rotations.
local function apply_rotation(a, b, angle)
local ax, ay, az, bx, by, bz = a.x, a.y, a.z, b.x, b.y, b.z
a.x = math.cos(angle) * ax + math.sin(angle) * bx
a.y = math.cos(angle) * ay + math.sin(angle) * by
a.z = math.cos(angle) * az + math.sin(angle) * bz
b.x = math.cos(angle) * bx - math.sin(angle) * ax
b.y = math.cos(angle) * by - math.sin(angle) * ay
b.z = math.cos(angle) * bz - math.sin(angle) * az
end
local function get_orientation(heading, pitch, roll)
-- Convert the heading, pitch, and roll from degrees to radians using math.rad
heading = math.rad(heading)
pitch = math.rad(pitch)
roll = math.rad(roll)
-- x is the Vec3 unit vector that points in the direction of the object's front
-- y is the Vec3 unit vector that points in the direction of the object's top
-- z is the Vec3 unit vector that points in the direction of the object's right side
local o = {
x = { x=1, y=0, z=0 },
y = { x=0, y=1, z=0 },
z = { x=0, y=0, z=1 },
}
apply_rotation(o.x, o.z, heading)
apply_rotation(o.x, o.y, pitch)
apply_rotation(o.z, o.y, roll)
-- Return the orientation matrix of the object
return o
end

Related

Rotation About an Arbitrary Axis in 3 Dimensions Using Matrix

I come accross a math problem about Interactive Computer Graphics.
I summarize and abstract this problem as follows:
I'm going to rotation a 3d coordinate P(x1,y1,z1) around a point O(x0,y0,z0)
and there are 2 vectors u and v which we already know.
u is the direction to O before transformation.
v is the direction to O after transformation.
I want to know how to conduct the calculation and get the coordinate of Q
Thanks a lot.
Solution:
Rotation About an Arbitrary Axis in 3 Dimensions using the following matrix:
rotation axis vector (normalized): (u,v,w)
position coordinate of the rotation center: (a,b,c)
rotation angel: theta
Reference:
https://docs.google.com/viewer?a=v&pid=sites&srcid=ZGVmYXVsdGRvbWFpbnxnbGVubm11cnJheXxneDoyMTJiZTZlNzVlMjFiZTFi
for just single point no rotations is needed ... so knowns are:
u,v,O,P
so we now the distance is not changing:
|P-O| = |Q-O|
and directions are parallel to u,v so:
Q = O + v*(|P-O|/|v|)
But I suspect you want to construct rotation (transform matrix) such that more points (mesh perhaps) are transformed. If that is true then you need at least one known to get this right. Because there is infinite possible rotations transforming P -> Q but the rest of the mesh will be different for each ... so you need to know at least 2 non trivial points pair P0,P1 -> Q0,Q1 or axis of rotation or plane parallel to rotation or any other data known ...
Anyway in current state you can use as rotation axis vector perpendicular to u,v and angle obtained from dot product:
axis = cross (u,v)
ang = +/-acos(dot(u,v))
You just need to find out the sign of angle so try both and use the one for which the resultinq Q is where it should be so dot(Q-O,v) is max. To rotate around arbitrary axis and point use:
Rodrigues_rotation_formula
Also this might be helpfull:
Understanding 4x4 homogenous transform matrices
By computing dot product between v and u get the angle l between the vectors. Do a cross product of v and u (normalized) to produce axis of rotation vector a. Let w be a vector along vector u from O to P. To rotate point P into Q apply the following actions (in pseudo code) having axis a and angle l computed above:
float4 Rotate(float4 w, float l, float4 a)
{
float4x4 Mr = IDENTITY;
quat_t quat = IDENTITY;
float4 t = ZERO;
float xx, yy, zz, xy, xz, yz, wx, wy, wz;
quat[X] = a[X] * sin((-l / 2.0f));
quat[Y] = a[Y] * sin((-l / 2.0f));
quat[Z] = a[Z] * sin((-l / 2.0f));
quat[W] = cos((-l / 2.0f));
xx = quat[X] * quat[X];
yy = quat[Y] * quat[Y];
zz = quat[Z] * quat[Z];
xy = quat[X] * quat[Y];
xz = quat[X] * quat[Z];
yz = quat[Y] * quat[Z];
wx = quat[W] * quat[X];
wy = quat[W] * quat[Y];
wz = quat[W] * quat[Z];
Mr[0][0] = 1.0f - 2.0f * (yy + zz);
Mr[0][1] = 2.0f * (xy + wz);
Mr[0][2] = 2.0f * (xz - wy);
Mr[0][3] = 0.0f;
Mr[1][0] = 2.0f * (xy - wz);
Mr[1][1] = 1.0f - 2.0f * (xx + zz);
Mr[1][2] = 2.0f * (yz + wx);
Mr[1][3] = 0.0f;
Mr[2][0] = 2.0f * (xz + wy);
Mr[2][1] = 2.0f * (yz - wx);
Mr[2][2] = 1.0f - 2.0f * (xx + yy);
Mr[2][3] = 0.0f;
Mr[3][0] = 0.0f;
Mr[3][1] = 0.0f;
Mr[3][2] = 0.0f;
Mr[3][3] = 1.0f;
w = Mr * w;
return w;
}
Point Q is at the end of the rotated vector w. Algorithm used in the pseudo code is quaternion rotation.
If you know u, v, P, and O then I would suggest that you compute |OP| which should be preserved under rotations. Then multiply this length by the unit vector -v (I assumed u, v are unit vectors: if not - normalize them) and translate the origin by this -|OP|v vector. The negative sign in front of v comes from the description given in your question:"v is the direction to O after transformation".
P and Q are at the same distance R to O
R = sqrt( (x1-x0)^2 + (y1-y0)^2 + (z1-z0)^2 )
and OQ is collinear to v, so OQ = v * R / ||v|| where ||v|| is the norm of v
||v|| = sqrt( xv^2 + yv^2 + zv^2 )
So the coordinates of Q(xq,yq,zq) are:
xq= xo + xv * R / ||v||
yq= yo + yv * R / ||v||
zq= zo + zv * R / ||v||

Tesselation of the circle in OpenGL

I'm having trouble understanding the math behind this function. I would like to hear the logic behind the formulas (especially what is this tangential and radial factor) written here to create points which later (when it send the vec3 array to a function) form a circle in OpenGL.
void doTesselate(const Arc& arc, int slices, std::vector<glm::vec3>& vertices)
{
double dang = (arc.endAngle() - arc.startAngle()) * Deg2Rad;
double radius = arc.radius();
double angIncr = dang / slices;
double tangetial_factor = tan(angIncr);
double radial_factor = 1 - cos(angIncr);
double startAngle = arc.startAngle() * Deg2Rad;
const glm::vec3& center = arc.center();
double x = center.x - radius * cos(startAngle);
double y = center.y - radius * sin(startAngle);
++slices;
for (int ii = 0; ii < slices; ii++) {
vertices.push_back(glm::vec3(x, y, center.z));
double tx = center.y - y;
double ty = x - center.x;
x += tx * tangetial_factor;
y += ty * tangetial_factor;
double rx = center.x - x;
double ry = center.y - y;
x += rx * radial_factor;
y += ry * radial_factor;
}
}
The idea is the following:
Starting from the current point, you go a bit in tangential direction and then back towards the center.
The vector (tx, ty) is the tangent at the current point with length equal to the radius. In order to get to the new angle, you have to move tan(angle) * radius along the tangent. radius is already incorporated in the tangent vector and tan(angle) is the tangetial_factor (you get that directly from tangent's definition).
After that, (rx, ry) is the vector towards the center. This vector has the length l:
cos(angle) = radius / l
l = radius / cos(angle)
We need to find a multiple m of this vector, such that the corrected point lies on the circle with the given radius again. If we just inspect the lengths, then we want to find:
target distance = current distance - m * length of (rx, ry)
radius = radius / cos(angle) - m * radius / cos(angle)
1 = (1 - m) / cos(angle)
cos(angle) = 1 - m
1 - cos(angle) = m
And this multiple is exactly the radial_factor (the amount which you need to move towards the center to get onto the circle).

2D Lua - Bullet velocity is not correct nor predictable

I'm using some stuff I've put together from various methods around the internet to make this work yet my bullets fly off in seemingly random directions. I've tried throwing negative signs in front of stuff and switching up the trig but to no avail. I've tried using the rotation of the player's arm because that accurately points to the user's mouse but that failed to give me any more accuracy.
I've tried to determine if the bullets follow a pattern like how I needed to invert the Y variable for my arm, but I cannot find a pattern here.
local x, y = objects.PlayerArm:GetPos()
local bullet = createBullet( x + objects.Player:GetWide(), y )
local mX, mY = gui.MouseX(), gui.MouseY()
local shootAngle = math.atan2((mY - y), (mX - x))
shootAngle = math.deg( math.Clamp(shootAngle, -90, 90) )
--shootAngle = objects.PlayerArm.Rotation
bullet.VelocityX = math.cos(shootAngle) * 5
bullet.VelocityY = math.sin(shootAngle) * 5
--bullet.Rotation = shootAngle
print("Angle", shootAngle, "Velocity X and Y", bullet.VelocityX, bullet.VelocityY)
Here is some of what was printed in console each time I shot a bullet.
Angle 47.920721521 Velocity X and Y -3.4948799788437 -3.5757256513158
Angle 24.928474135461 Velocity X and Y 4.8960495864893 -1.0142477244922
Angle 16.837472625676 Velocity X and Y -2.1355174970471 -4.5210137159497
Angle 10.684912400003 Velocity X and Y -1.5284445365972 -4.7606572338855
Angle -1.029154804306 Velocity X and Y 2.5777162320797 -4.2843178018061
Angle -11.63363399894 Velocity X and Y 2.978190104641 4.0162648942293
Angle -22.671343621981 Velocity X and Y -3.8872502993046 3.1447233758403
http://i.gyazo.com/e8ed605098a91bd450b10fda7d484975.png
As #iamnotmaynard suspected, Lua uses C's math library and so all the trig functions use radians instead of degrees. It is best to store all angles in radians and just print them in degrees for a more friendly format. Otherwise you have a lot of conversions to and from radians every time an angle is used. Below is the code updated to only use radians and print in degrees.
local mX, mY = gui.MouseX(), gui.MouseY()
local shootAngle = math.atan2((mY - y), (mX - x))
shootAngle = math.max(-math.pi/2, math.min(shootAngle, math.pi/2))
bullet.VelocityX = math.cos(shootAngle) * 5
bullet.VelocityY = math.sin(shootAngle) * 5
print("Angle (radians)", shootAngle, "(degrees)", math.deg(shootAngle),
"Velocity X and Y", bullet.VelocityX, bullet.VelocityY)
However to compute velocity in the x and y directions angles are not necessary at all. The function below computes VelocityX and VelocityY using only the displacements and makes sure that the velocity is only in the lower right and upper right quadrants as well.
function shoot(x, y, dirx, diry, vel)
local dx = math.max(dirx - x, 0)
local dy = diry - y
local sdist = dx * dx + dy * dy
if sdist > 0 then
local m = vel / math.sqrt(sdist)
return dx * m, dy * m
end
end
bullet.VelocityX, bullet.VeclocityY = shoot(x, y, gui.MouseX(), gui.MouseY(), 5)

Extracting Yaw from a Quaternion

I have a rotation quaternion and want to extract the angle of rotation about the Up axis (the yaw). I am using XNA and as far as I can tell there is no inbuilt function for this. What is the best way to do this?
Thanks for any help,
Venatu
The quaternion representation of rotation is a variation on axis and angle. So if you rotate by r radians around axis x, y, z, then your quaternion q is:
q[0] = cos(r/2);
q[1] = sin(r/2)*x;
q[2] = sin(r/2)*y;
q[3] = sin(r/2)*z;
If you want to create a quaternion that only rotates around the y axis, you zero out the x and z axes and then re-normalize the quaternion:
q[1] = 0;
q[3] = 0;
double mag = sqrt(q[0]*q[0] + q[2]*q[2]);
q[0] /= mag;
q[2] /= mag;
If you want the resulting angle:
double ang = 2*acos(q[0]);
This assumes that the quaternion representation is stored: w,x,y,z. If both q[0] and q[2] are zero, or close to it, the resulting quaternion should just be {1,0,0,0}.
Having given a Quaternion q, you can calculate roll, pitch and yaw like this:
var yaw = atan2(2.0*(q.y*q.z + q.w*q.x), q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z);
var pitch = asin(-2.0*(q.x*q.z - q.w*q.y));
var roll = atan2(2.0*(q.x*q.y + q.w*q.z), q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z);
This should fit for intrinsic tait-bryan rotation of xyz-order. For other rotation orders, extrinsic and proper-euler rotations other conversions have to be used.
Note: I've verified below code against Wikipedia's equations plus Pixhawk's documentation and it is correct.
If you are working with drones/aviation, below is the code (taken directly from DJI SDK). Here q0, q1, q2, q3 corresponds to w,x,y,z components of the quaternion respectively. Also note that yaw, pitch, roll may be referred to as heading, attitude and bank respectively in some literature.
float roll = atan2(2.0 * (q.q3 * q.q2 + q.q0 * q.q1) , 1.0 - 2.0 * (q.q1 * q.q1 + q.q2 * q.q2));
float pitch = asin(2.0 * (q.q2 * q.q0 - q.q3 * q.q1));
float yaw = atan2(2.0 * (q.q3 * q.q0 + q.q1 * q.q2) , - 1.0 + 2.0 * (q.q0 * q.q0 + q.q1 * q.q1));
If you need to calculate all 3 then you can avoid recalculating common terms by using following functions:
//Source: http://docs.ros.org/latest-lts/api/dji_sdk_lib/html/DJI__Flight_8cpp_source.html#l00152
EulerianAngle Flight::toEulerianAngle(QuaternionData data)
{
EulerianAngle ans;
double q2sqr = data.q2 * data.q2;
double t0 = -2.0 * (q2sqr + data.q3 * data.q3) + 1.0;
double t1 = +2.0 * (data.q1 * data.q2 + data.q0 * data.q3);
double t2 = -2.0 * (data.q1 * data.q3 - data.q0 * data.q2);
double t3 = +2.0 * (data.q2 * data.q3 + data.q0 * data.q1);
double t4 = -2.0 * (data.q1 * data.q1 + q2sqr) + 1.0;
t2 = t2 > 1.0 ? 1.0 : t2;
t2 = t2 < -1.0 ? -1.0 : t2;
ans.pitch = asin(t2);
ans.roll = atan2(t3, t4);
ans.yaw = atan2(t1, t0);
return ans;
}
QuaternionData Flight::toQuaternion(EulerianAngle data)
{
QuaternionData ans;
double t0 = cos(data.yaw * 0.5);
double t1 = sin(data.yaw * 0.5);
double t2 = cos(data.roll * 0.5);
double t3 = sin(data.roll * 0.5);
double t4 = cos(data.pitch * 0.5);
double t5 = sin(data.pitch * 0.5);
ans.q0 = t2 * t4 * t0 + t3 * t5 * t1;
ans.q1 = t3 * t4 * t0 - t2 * t5 * t1;
ans.q2 = t2 * t5 * t0 + t3 * t4 * t1;
ans.q3 = t2 * t4 * t1 - t3 * t5 * t0;
return ans;
}
Note on Eigen Library
If you are using Eigen library, it has another way to do this conversion, however, this may not be as optimized as above direct code:
Vector3d euler = quaternion.toRotationMatrix().eulerAngles(2, 1, 0);
yaw = euler[0]; pitch = euler[1]; roll = euler[2];
Conversion Quaternion to Euler
I hope you know that yaw, pitch and roll are not good for arbitrary rotations. Euler angles suffer from singularities (see the above link) and instability. Look at 38:25 of the presentation of David Sachs
http://www.youtube.com/watch?v=C7JQ7Rpwn2k
Good luck!
A quaternion consists of two components: a 3d vector component and a scalar component.
The vector component of the quaternion describes independent rotations about each axis, so zero'ing out the x- and y-components of the vector component and leaving z-component as-is is all you need to do in order to solve for the vector term:
// Don't modify qz
double qx = 0;
double qy = 0;
The scalar term represents the magnitude of rotation. For a unit quaternion (such as one used to represent attitude), the entire quaternion must have a magnitude of 1. Thus, the scalar term can be solved by:
double qw = sqrt(1 - qx*qx - qy*qy - qz*qz);
Since qx and qy are zero, the scalar component is given by
double qw = sqrt(1 - qz*qz);
Thus, the full quaternion representing yaw is given by
double qx = 0;
double qy = 0;
// Don't modify qz
double qw = sqrt(1 - qz*qz);
The transformation from quaternion to yaw, pitch, and roll depends on the conventions used to define the quaternion and the yaw, pitch, and roll. For a given convention there are many "almost correct" transformations that will work for the majority of angles but only one truly correct transformation that will work for all angles including south and north poles where the "almost correct" transformations produce gimbal locks (spurious flips and rotations).
See this tutorial for more information:
https://youtu.be/k5i-vE5rZR0

How to calculate center of an ellipse by two points and radius sizes

While working on SVG implementation for Internet Explorer to be based on its own VML format I came to a problem of translation of an SVG elliptical arc to an VML elliptical arc.
In VML an arc is given by: two angles for two points on ellipse and lengths of radiuses,
In SVG an arc is given by: two pairs of coordinates for two points on ellipse and sizes of ellipse boundary box
So, the question is: How to express angles of two points on ellipse to two pairs of their coordinates.
An intermediate question could be: How to find the center of an ellipse by coordinates of a pair of points on its curve.
Update: Let's have a precondition saying that an ellipse is normally placed (its radiuses are parallel to linear coordinate system axis), thus no rotation is applied.
Update: This question is not related to svg:ellipse element, rather to "a" elliptical arc command in svg:path element (SVG Paths: The elliptical arc curve commands)
So the solution is here:
The parametrized formula of an ellipse:
x = x0 + a * cos(t)
y = y0 + b * sin(t)
Let's put known coordinates of two points to it:
x1 = x0 + a * cos(t1)
x2 = x0 + a * cos(t2)
y1 = y0 + b * sin(t1)
y2 = y0 + b * sin(t2)
Now we have a system of equations with 4 variables: center of ellipse (x0/y0) and two angles t1, t2
Let's subtract equations in order to get rid of center coordinates:
x1 - x2 = a * (cos(t1) - cos(t2))
y1 - y2 = b * (sin(t1) - sin(t2))
This can be rewritten (with product-to-sum identities formulas) as:
(x1 - x2) / (2 * a) = sin((t1 + t2) / 2) * sin((t1 - t2) / 2)
(y2 - y1) / (2 * b) = cos((t1 + t2) / 2) * sin((t1 - t2) / 2)
Let's replace some of the equations:
r1: (x1 - x2) / (2 * a)
r2: (y2 - y1) / (2 * b)
a1: (t1 + t2) / 2
a2: (t1 - t2) / 2
Then we get simple equations system:
r1 = sin(a1) * sin(a2)
r2 = cos(a1) * sin(a2)
Dividing first equation by second produces:
a1 = arctan(r1/r2)
Adding this result to the first equation gives:
a2 = arcsin(r2 / cos(arctan(r1/r2)))
Or, simple (using compositions of trig and inverse trig functions):
a2 = arcsin(r2 / (1 / sqrt(1 + (r1/r2)^2)))
or even more simple:
a2 = arcsin(sqrt(r1^2 + r2^2))
Now the initial four-equations system can be resolved with easy and all angles as well as eclipse center coordinates can be found.
The elliptical curve arc link you posted includes a link to elliptical arc implementation notes.
In there, you will find the equations for conversion from endpoint to centre parameterisation.
Here is my JavaScript implementation of those equations, taken from an interactive demo of elliptical arc paths, using Sylvester.js to perform the matrix and vector calculations.
// Calculate the centre of the ellipse
// Based on http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
var x1 = 150; // Starting x-point of the arc
var y1 = 150; // Starting y-point of the arc
var x2 = 400; // End x-point of the arc
var y2 = 300; // End y-point of the arc
var fA = 1; // Large arc flag
var fS = 1; // Sweep flag
var rx = 100; // Horizontal radius of ellipse
var ry = 50; // Vertical radius of ellipse
var phi = 0; // Angle between co-ord system and ellipse x-axes
var Cx, Cy;
// Step 1: Compute (x1′, y1′)
var M = $M([
[ Math.cos(phi), Math.sin(phi)],
[-Math.sin(phi), Math.cos(phi)]
]);
var V = $V( [ (x1-x2)/2, (y1-y2)/2 ] );
var P = M.multiply(V);
var x1p = P.e(1); // x1 prime
var y1p = P.e(2); // y1 prime
// Ensure radii are large enough
// Based on http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
// Step (a): Ensure radii are non-zero
// Step (b): Ensure radii are positive
rx = Math.abs(rx);
ry = Math.abs(ry);
// Step (c): Ensure radii are large enough
var lambda = ( (x1p * x1p) / (rx * rx) ) + ( (y1p * y1p) / (ry * ry) );
if(lambda > 1)
{
rx = Math.sqrt(lambda) * rx;
ry = Math.sqrt(lambda) * ry;
}
// Step 2: Compute (cx′, cy′)
var sign = (fA == fS)? -1 : 1;
// Bit of a hack, as presumably rounding errors were making his negative inside the square root!
if((( (rx*rx*ry*ry) - (rx*rx*y1p*y1p) - (ry*ry*x1p*x1p) ) / ( (rx*rx*y1p*y1p) + (ry*ry*x1p*x1p) )) < 1e-7)
var co = 0;
else
var co = sign * Math.sqrt( ( (rx*rx*ry*ry) - (rx*rx*y1p*y1p) - (ry*ry*x1p*x1p) ) / ( (rx*rx*y1p*y1p) + (ry*ry*x1p*x1p) ) );
var V = $V( [rx*y1p/ry, -ry*x1p/rx] );
var Cp = V.multiply(co);
// Step 3: Compute (cx, cy) from (cx′, cy′)
var M = $M([
[ Math.cos(phi), -Math.sin(phi)],
[ Math.sin(phi), Math.cos(phi)]
]);
var V = $V( [ (x1+x2)/2, (y1+y2)/2 ] );
var C = M.multiply(Cp).add(V);
Cx = C.e(1);
Cy = C.e(2);
An ellipse cannot be defined by only two points. Even a circle (a special cased ellipse) is defined by three points.
Even with three points, you would have infinite ellipses passing through these three points (think: rotation).
Note that a bounding box suggests a center for the ellipse, and most probably assumes that its major and minor axes are parallel to the x,y (or y,x) axes.
The intermediate question is fairly easy... you don't. You work out the centre of an ellipse from the bounding box (namely, the centre of the box is the centre of the ellipse, as long as the ellipse is centred in the box).
For your first question, I'd look at the polar form of the ellipse equation, which is available on Wikipedia. You would need to work out the eccentricity of the ellipse as well.
Or you could brute force the values from the bounding box... work out if a point lies on the ellipse and matches the angle, and iterate through every point in the bounding box.
TypeScript implementation based on the answer from Rikki.
Default DOMMatrix and DOMPoint are used for the calculations (Tested in the latest Chrome v.80) instead of the external library.
ellipseCenter(
x1: number,
y1: number,
rx: number,
ry: number,
rotateDeg: number,
fa: number,
fs: number,
x2: number,
y2: number
): DOMPoint {
const phi = ((rotateDeg % 360) * Math.PI) / 180;
const m = new DOMMatrix([
Math.cos(phi),
-Math.sin(phi),
Math.sin(phi),
Math.cos(phi),
0,
0,
]);
let v = new DOMPoint((x1 - x2) / 2, (y1 - y2) / 2).matrixTransform(m);
const x1p = v.x;
const y1p = v.y;
rx = Math.abs(rx);
ry = Math.abs(ry);
const lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);
if (lambda > 1) {
rx = Math.sqrt(lambda) * rx;
ry = Math.sqrt(lambda) * ry;
}
const sign = fa === fs ? -1 : 1;
const div =
(rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p) /
(rx * rx * y1p * y1p + ry * ry * x1p * x1p);
const co = sign * Math.sqrt(Math.abs(div));
// inverse matrix b and c
m.b *= -1;
m.c *= -1;
v = new DOMPoint(
((rx * y1p) / ry) * co,
((-ry * x1p) / rx) * co
).matrixTransform(m);
v.x += (x1 + x2) / 2;
v.y += (y1 + y2) / 2;
return v;
}
Answering part of the question with code
How to find the center of an ellipse by coordinates of a pair of points on its curve.
This is a TypeScript function which is based on the excellent accepted answer by Sergey Illinsky above (which ends somewhat halfway through, IMHO). It calculates the center of an ellipse with given radii, given the condition that both provided points a and b must lie on the circumference of the ellipse. Since there are (almost) always two solutions to this problem, the code choses the solution that places the ellipse "above" the two points:
(Note that the ellipse must have major and minor axis parallel to the horizontal/vertical)
/**
* We're in 2D, so that's what our vertors look like
*/
export type Point = [number, number];
/**
* Calculates the vector that connects the two points
*/
function deltaXY (from: Point, to: Point): Point {
return [to[0]-from[0], to[1]-from[1]];
}
/**
* Calculates the sum of an arbitrary amount of vertors
*/
function vecAdd (...vectors: Point[]): Point {
return vectors.reduce((acc, curr) => [acc[0]+curr[0], acc[1]+curr[1]], [0, 0]);
}
/**
* Given two points a and b, as well as ellipsis radii rX and rY, this
* function calculates the center-point of the ellipse, so that it
* is "above" the two points and has them on the circumference
*/
function topLeftOfPointsCenter (a: Point, b: Point, rX: number, rY: number): Point {
const delta = deltaXY(a, b);
// Sergey's work leads up to a simple system of liner equations.
// Here, we calculate its general solution for the first of the two angles (t1)
const A = Math.asin(Math.sqrt((delta[0]/(2*rX))**2+(delta[1]/(2*rY))**2));
const B = Math.atan(-delta[0]/delta[1] * rY/rX);
const alpha = A + B;
// This may be the new center, but we don't know to which of the two
// solutions it belongs, yet
let newCenter = vecAdd(a, [
rX * Math.cos(alpha),
rY * Math.sin(alpha)
]);
// Figure out if it is the correct solution, and adjusting if not
const mean = vecAdd(a, [delta[0] * 0.5, delta[1] * 0.5]);
const factor = mean[1] > newCenter[1] ? 1 : -1;
const offMean = deltaXY(mean, newCenter);
newCenter = vecAdd(mean, [offMean[0] * factor, offMean[1] * factor]);
return newCenter;
}
This function does not check if a solution is possible, meaning whether the radii provided are large enough to even connect the two points!

Resources