Tesselation of the circle in OpenGL - math

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).

Related

Orientation Matrix from Heading, Pitch and Roll

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

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||

Computing the 3D coordinates on a unit sphere from a 2D point

I have a square bitmap of a circle and I want to compute the normals of all the pixels in that circle as if it were a sphere of radius 1:
The sphere/circle is centered in the bitmap.
What is the equation for this?
Don't know much about how people program 3D stuff, so I'll just give the pure math and hope it's useful.
Sphere of radius 1, centered on origin, is the set of points satisfying:
x2 + y2 + z2 = 1
We want the 3D coordinates of a point on the sphere where x and y are known. So, just solve for z:
z = ±sqrt(1 - x2 - y2).
Now, let us consider a unit vector pointing outward from the sphere. It's a unit sphere, so we can just use the vector from the origin to (x, y, z), which is, of course, <x, y, z>.
Now we want the equation of a plane tangent to the sphere at (x, y, z), but this will be using its own x, y, and z variables, so instead I'll make it tangent to the sphere at (x0, y0, z0). This is simply:
x0x + y0y + z0z = 1
Hope this helps.
(OP):
you mean something like:
const int R = 31, SZ = power_of_two(R*2);
std::vector<vec4_t> p;
for(int y=0; y<SZ; y++) {
for(int x=0; x<SZ; x++) {
const float rx = (float)(x-R)/R, ry = (float)(y-R)/R;
if(rx*rx+ry*ry > 1) { // outside sphere
p.push_back(vec4_t(0,0,0,0));
} else {
vec3_t normal(rx,sqrt(1.-rx*rx-ry*ry),ry);
p.push_back(vec4_t(normal,1));
}
}
}
It does make a nice spherical shading-like shading if I treat the normals as colours and blit it; is it right?
(TZ)
Sorry, I'm not familiar with those aspects of C++. Haven't used the language very much, nor recently.
This formula is often used for "fake-envmapping" effect.
double x = 2.0 * pixel_x / bitmap_size - 1.0;
double y = 2.0 * pixel_y / bitmap_size - 1.0;
double r2 = x*x + y*y;
if (r2 < 1)
{
// Inside the circle
double z = sqrt(1 - r2);
.. here the normal is (x, y, z) ...
}
Obviously you're limited to assuming all the points are on one half of the sphere or similar, because of the missing dimension. Past that, it's pretty simple.
The middle of the circle has a normal facing precisely in or out, perpendicular to the plane the circle is drawn on.
Each point on the edge of the circle is facing away from the middle, and thus you can calculate the normal for that.
For any point between the middle and the edge, you use the distance from the middle, and some simple trig (which eludes me at the moment). A lerp is roughly accurate at some points, but not quite what you need, since it's a curve. Simple curve though, and you know the beginning and end values, so figuring them out should only take a simple equation.
I think I get what you're trying to do: generate a grid of depth data for an image. Sort of like ray-tracing a sphere.
In that case, you want a Ray-Sphere Intersection test:
http://www.siggraph.org/education/materials/HyperGraph/raytrace/rtinter1.htm
Your rays will be simple perpendicular rays, based off your U/V coordinates (times two, since your sphere has a diameter of 2). This will give you the front-facing points on the sphere.
From there, calculate normals as below (point - origin, the radius is already 1 unit).
Ripped off from the link above:
You have to combine two equations:
Ray: R(t) = R0 + t * Rd , t > 0 with R0 = [X0, Y0, Z0] and Rd = [Xd, Yd, Zd]
Sphere: S = the set of points[xs, ys, zs], where (xs - xc)2 + (ys - yc)2 + (zs - zc)2 = Sr2
To do this, calculate your ray (x * pixel / width, y * pixel / width, z: 1), then:
A = Xd^2 + Yd^2 + Zd^2
B = 2 * (Xd * (X0 - Xc) + Yd * (Y0 - Yc) + Zd * (Z0 - Zc))
C = (X0 - Xc)^2 + (Y0 - Yc)^2 + (Z0 - Zc)^2 - Sr^2
Plug into quadratic equation:
t0, t1 = (- B + (B^2 - 4*C)^1/2) / 2
Check discriminant (B^2 - 4*C), and if real root, the intersection is:
Ri = [xi, yi, zi] = [x0 + xd * ti , y0 + yd * ti, z0 + zd * ti]
And the surface normal is:
SN = [(xi - xc)/Sr, (yi - yc)/Sr, (zi - zc)/Sr]
Boiling it all down:
So, since we're talking unit values, and rays that point straight at Z (no x or y component), we can boil down these equations greatly:
Ray:
X0 = 2 * pixelX / width
Y0 = 2 * pixelY / height
Z0 = 0
Xd = 0
Yd = 0
Zd = 1
Sphere:
Xc = 1
Yc = 1
Zc = 1
Factors:
A = 1 (unit ray)
B
= 2 * (0 + 0 + (0 - 1))
= -2 (no x/y component)
C
= (X0 - 1) ^ 2 + (Y0 - 1) ^ 2 + (0 - 1) ^ 2 - 1
= (X0 - 1) ^ 2 + (Y0 - 1) ^ 2
Discriminant
= (-2) ^ 2 - 4 * 1 * C
= 4 - 4 * C
From here:
If discriminant < 0:
Z = ?, Normal = ?
Else:
t = (2 + (discriminant) ^ 1 / 2) / 2
If t < 0 (hopefully never or always the case)
t = -t
Then:
Z: t
Nx: Xi - 1
Ny: Yi - 1
Nz: t - 1
Boiled farther still:
Intuitively it looks like C (X^2 + Y^2) and the square-root are the most prominent figures here. If I had a better recollection of my math (in particular, transformations on exponents of sums), then I'd bet I could derive this down to what Tom Zych gave you. Since I can't, I'll just leave it as above.

Intersection on circle of vector originating inside circle

I have a circle. Inside the circle is a point. I have a vector originating at this point. I'd like to know what point on the circle this vector intersects. Here is a drawing:
http://n4te.com/temp/circle.png http://n4te.com/temp/circle.png
The red dot is the point I am trying to determine.
I know these things: the center of the circle, the origin of the vector, and the direction of the vector.
I know this is basic stuff, but I'm still having trouble. Most of the Googlings bring me to line-circle collision, which is related but not quite the same. Thanks for any help you can provide!
Elementary vector algebra.
O — center of circle (vector)
r — its radius (scalar)
A — origin of ray (vector)
k — direction of ray (vector)
Solve (A + kt - O)² = r² for scalar t, choose positive root, and A + kt is your point.
Further explanation:
. is dot product, ² for a vector is dot product of the vector with itself. Expand LHS
(A + kt - O)² = (A - O)² + 2(k.(A - O))t + k²t².
The quadratic is k²t² + 2(k.(A - O))t + (A - O)² - r² = 0. In terms of your variables, this becomes (rayVX² + rayVY²)t² + 2(rayVX(rayX - circleX) + rayVY(rayY - circleY))t + (rayX - circleX)² + (rayY - circleY)² - r² = 0.
Much thanks to Anton Tykhyy for his detailed answer. This was the resulting Java code:
float xDiff = rayX - circleX;
float yDiff = rayY - circleY;
float a = rayVX * rayVX + rayVY * rayVY;
float b = 2 * (rayVX * (rayX - circleX) + rayVY * (rayY - circleY));
float c = xDiff * xDiff + yDiff * yDiff - r * r;
float disc = b * b - 4 * a * c;
if (disc >= 0) {
float t = (-b + (float)Math.sqrt(disc)) / (2 * a);
float x = rayX + rayVX * t;
float y = rayY + rayVY * t;
// Do something with point.
}

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