Find Arc/Circle equation given three points in space (3D) - python

Given 3 points in space (3D): A = (x1, y1, z1), B = (x2, y2, z2) C = (x3, y3, z3); then how to find the center and radius of the circle (arc) that passes through these three points, i.e. find circle equation? Using Python and Numpy here is my initial code
import numpy as np
A = np.array([x1, y1, z1])
B = np.array([x2, y2, z2])
C = np.array([x3, y3, z3])
#Find vectors connecting the three points and the length of each vector
AB = B - A
BC = C - B
AC = C - A
# Triangle Lengths
a = np.linalg.norm(AB)
b = np.linalg.norm(BC)
c = np.linalg.norm(AC)
From the Circumradius definition, the radius can be found using:
R = (a * b * c) / np.sqrt(2.0 * a**2 * b**2 +
2.0 * b**2 * c**2 +
2.0 * c**2 * a**2 -
a**4 - b**4 - c**4)
However, I am having problems finding the Cartesian coordinates of the center. One possible solution is to use the "Barycentric Coordinates" of the triangle points to find the trilinear coordinates of the circumcenter (Circumcenter).
First (using this source) we find the circumcenter barcyntric coordinates:
#barcyntric coordinates of center
b1 = a**2 * (b**2 + c**2 - a**2)
b2 = b**2 * (c**2 + a**2 - b**2)
b3 = c**2 * (a**2 + b**2 - c**2)
Then the Cartesian coordinates of the center (P) would be:
Px = (b1 * A[0]) + (b2 * B[0]) + (b3 * C[0])
Py = (b1 * A[1]) + (b2 * B[1]) + (b3 * C[1])
Pz = (b1 * A[2]) + (b2 * B[2]) + (b3 * C[2])
However, the barcyntric coordinates values above do not seem to be correct. When solved with an example of known values, the radius is correct, but the coordinates of the center are not.
Example: For these three points:
A = np.array([2.0, 1.5, 0.0])
B = np.array([6.0, 4.5, 0.0])
C = np.array([11.75, 6.25, 0.0])
The radius and center coordinates are:
R = 15.899002930062595
P = [13.4207317073, -9.56097560967, 0]
Any ideas on how to find the center coordinates?

There are two issues with your code.
The first is in the naming convention. For all the formulas you are using to hold, the side of length a has to be the one opposite the point A, and similarly for b and B and c and C. You can solve that by computing them as:
a = np.linalg.norm(C - B)
b = np.linalg.norm(C - A)
c = np.linalg.norm(B - A)
The second has to do with the note in your source for the barycentric coordinates of the circumcenter: not necessarily homogeneous. That is, they need not be normalized in any way, and the formula you are using to compute the Cartesian coordinates from the barycentric ones is only valid when they add up to one.
Fortunately, you only need to divide the resulting Cartesian coordinates by b1 + b2 + b3 to get the result you are after. Streamlining a little bit your code for efficiency, I get the results you expect:
>>> A = np.array([2.0, 1.5, 0.0])
>>> B = np.array([6.0, 4.5, 0.0])
>>> C = np.array([11.75, 6.25, 0.0])
>>> a = np.linalg.norm(C - B)
>>> b = np.linalg.norm(C - A)
>>> c = np.linalg.norm(B - A)
>>> s = (a + b + c) / 2
>>> R = a*b*c / 4 / np.sqrt(s * (s - a) * (s - b) * (s - c))
>>> b1 = a*a * (b*b + c*c - a*a)
>>> b2 = b*b * (a*a + c*c - b*b)
>>> b3 = c*c * (a*a + b*b - c*c)
>>> P = np.column_stack((A, B, C)).dot(np.hstack((b1, b2, b3)))
>>> P /= b1 + b2 + b3
>>> R
15.899002930062531
>>> P
array([ 13.42073171, -9.56097561, 0. ])

As an extension to the original problem: Now suppose we have an arc of known length (e.g. 1 unit) extending from point A as defined above (away from point B). How to find the 3D coordinates of the end point (N)? The new point N lies on the same circle passing through points A, B & C.
This can be solved by first finding the angle between the two vectors PA & PN:
# L = Arc Length
theta = L / R
Now all we need to do is rotate the vector PA (Radius) by this angle theta in the correct direction. In order to do that, we need the 3D rotation matrix. For that we use the Euler–Rodrigues formula:
def rotation_matrix_3d(axis, theta):
axis = axis / np.linalg.norm(axis)
a = np.cos(theta / 2.0)
b, c, d = axis * np.sin(theta / 2.0)
rot = np.array([[a*a+b*b-c*c-d*d, 2*(b*c-a*d), 2*(b*d+a*c)],
[ 2*(b*c+a*d), a*a+c*c-b*b-d*d, 2*(c*d-a*b)],
[ 2*(b*d-a*c), 2*(c*d+a*b), a*a+d*d-b*b-c*c]])
return rot
Next, we need to find the rotation axis to rotate PA around. This can be achieved by finding the axis normal to the plane of the circle passing through A, B & C. The coordinates of point N can then be found from the center of the circle.
PA = P - A
PB = P - B
xx = np.cross(PB, PA)
r3d = rotation_matrix_3d(xx, theta)
PN = np.dot(r3d, PA)
N = P - PN
as an example, we want to find coordinates of point N that are 3 degrees away from point A, the coordinates would be
N = (1.43676498, 0.8871264, 0.)
Which is correct! (manually verified using CAD program)

Related

Three spheres intersection (trilateration) with SymPy

Is there a way to get a solution to three spheres intersection (trilateration) with SymPy? sympy.geometry doesn't have a sphere object, so a direct approach is not feasible. Can SymPy solve a system of non-linear equations as shown at Trilateration and the Intersection of Three Spheres?
Yes. There are different ways but e.g.:
In [21]: x, y, z = symbols('x, y, z', real=True)
In [22]: eq1 = (x-1)**2 + (y-2)**2 + (z-3)**2 - 1
In [23]: eq2 = (x-1)**2 + (y-S(5)/2)**2 + (z-3)**2 - 1
In [24]: eq3 = (x-S.Half)**2 + (y-S(5)/2)**2 + (z-3)**2 - 1
In [25]: solve([eq1, eq2, eq3], [x, y, z])
Out[25]:
⎡⎛ √14⎞ ⎛ √14 ⎞⎤
⎢⎜3/4, 9/4, 3 - ───⎟, ⎜3/4, 9/4, ─── + 3⎟⎥
⎣⎝ 4 ⎠ ⎝ 4 ⎠⎦
This post is in case someone benefits from what I have posted below.
One can actually solve this problem somewhat more geometrically, rather than solving systems of quadratic equations. The approach below can handle three spheres given as centers and radii or as spheres represented by three quadratic equations. Even if the three spheres are given as three quadratic equations of three variables, one can extract the coefficients and obtain the radii and the coordinates of the centers of the three spheres. Then, the two intersection points can be found geometrically, by applying the law of cosine a few times.
def norm_2(v):
return v.dot(v)
def norm(v):
return np.sqrt(norm_2(v))
def extract_center_and_radius(sphere_coefficients):
'''
sphere_equation should be
x^2 + y^2 + z^2 + a1*x + a2*y + a3*z + a4 = 0
so sphere equation should be parametrized
as a list of 4 coefficients
[a1, a2, a3, a4]
'''
a = np.array(sphere_coefficients)
center = - a[0:3] / 2
radius = np.sqrt(center.dot(center) - a[3])
return center, radius
def intersecton_3_spheres(sphere1, sphere2, sphere3):
'''
spherei = (center, radius) is a tuple or a list,
with first entry an np.array representing
the coordinates of the center,
the second being the radius
'''
A, rA = sphere1
B, rB = sphere2
C, rC = sphere3
AB = B-A
AC = C-A
l_AB = norm(AB)
l_AC = norm(AC)
l_BC = norm(C-B)
x2 = (l_AB**2 + l_AC**2 - l_BC**2) / (2 * l_AB)
y2 = np.sqrt(l_AC**2 - x2**2)
x3 = (l_AB**2 + rA**2 - rB**2) / (2*l_AB)
y3 = (l_AC**2 + rA**2 - rC**2) / 2
y3 = (y3 - x2*x3) / y2
z3 = np.sqrt(rA**2 - x3**2 - y3**2)
n = np.cross(AB, AC)
n = n / norm(n)
AB = AB / l_AB
b = np.cross(n, AB)
return A + x3*AB + y3*b + z3*n, A + x3*AB + y3*b - z3*n
def intersecton_of_3_spheres(sphere1, sphere2, sphere3):
'''
the input is three lists of 4 coefficients each
e.g. [a1, a2, a3, a4] which describe the coefficients
of three sphere equations
x^2 + y^2 + z^2 + a1*x + a2*y + a3*z + a4 = 0
'''
center_radius_1 = extract_center_and_radius(sphere1)
center_radius_2 = extract_center_and_radius(sphere2)
center_radius_3 = extract_center_and_radius(sphere3)
return intersecton_3_spheres(center_radius_1, center_radius_2, center_radius_3)

Finding the intersect between a quadratic and line

I am trying to find the intersect between a straight line and a quadratic curve, however the result I am getting appears to be imaginary although I don't see how this can be the case as I can see them intersect on real axes:
Import numpy
#quadratic coefficients
a,b,c = (-3.09363812e-04, 1.52138019e+03, -1.87044961e+09)
# y = ax^2 + bx + c
#line coefficients
m,d = (1.06446434e-03, -2.61660911e+03)
#y = mx + d
intersect = (-(b-m)+((b-m)**2 - 4*a*(c-d))**0.5)/(2*a)
print(intersect)
The output of this is 2458883.4674943495-107.95731226786134j
I am trying to find the intersect between the yellow curve over the blue points and the black dotted line
The graphed curves you presented vs. your equations are not the same, and your equations do not intersect.
I rewrote some example code for you. numpy isn't needed, and an exact solution is possible.
import math
import collections
def calculateIntersection(p, l):
b = p.B - l.slope
c = p.C - l.yInt
discriminant = b**2 - (4 * p.A * c)
if discriminant > 0.0:
# 2 points of intersection
x1 = (-b + math.sqrt(discriminant)) / (2.0 * p.A)
x2 = (-b - math.sqrt(discriminant)) / (2.0 * p.A)
return discriminant, [(x1, l.slope * x1 + l.yInt), (x2, l.slope * x2 + l.yInt)]
elif discriminant == 0.0:
# 1 point of intersection
x1 = -b / (2.0 * p.A)
return discriminant, [(x1, slope * x1 + l.yInt)]
else:
# no points of intersection
return discriminant, []
Line = collections.namedtuple('Line', 'slope yInt')
Poly = collections.namedtuple('Poly', 'A B C')
p = Poly(A=-3.09363812e-04, B=1.52138019e+03, C=-1.87044961e+09)
print(p)
l = Line(slope=1.06446434e-03, yInt=-2.61660911e+03)
print(l)
(discriminant, points) = calculateIntersection(p, l)
if (len(points) > 0):
print("Intersection: {}".format(points))
else:
print("No intersection: {}".format(discriminant))

Find point where altitude meets base (Python)

Given only the coordinates of the vertices of an acute triangle, how can I efficiently and quickly find the coordinates of the point at which the altitude from a particular vertex meets the opposite base?
A solution using only math, numpy, or scipy would be incredibly helpful.
Needed point is orthogonal projection of vertex point (say vertex C) onto the line containing opposite side (say AB).
To find projection point, get vectors for AB and AC
AB = (B - A) //in coordinates ab.x = b.x-a.x, ab.y = b.y-a.y
AC = (C - A)
and find parameter using scalar product of AB and AC
t =(AB * AC) / (AB * AB)
t =((b.x-a.x)*(c.x-a.x) + (b.y-a.y)*(c.y-a.y)) / ((b.x-a.x)*(b.x-a.x) + (b.y-a.y)*(b.y-a.y))
Projection point coordinates
P = A + AB * t
p.x = a.x + (b.x-a.x) * t
p.y = a.y + (b.y-a.y) * t
That's all
def orthoProjection(ax, ay, bx, by, cx, cy):
abx = bx - ax
aby = by - ay
acx = cx - ax
acy = cy - ay
t = (abx * acx + aby * acy) / (abx * abx + aby * aby)
px = ax + t * abx
py = ay + t * aby
return px, py
print(orthoProjection(0, 0, 4, 4, -1, 5))
>>(2.0, 2.0)
Consider the triangle with vertices at points A, B and C, and you wish to find where the altitude extending from vertex C intersects the line AB.
So first, you can determine the equation for line AB. You have points A and B (Ax, Ay; and Bx, By). Given that you can calculate the slope_AB as (By-Ay)/(Bx-Ax).
Now the format of a line is Y = MX+B where M is the slope just calculated, and B is the Y intercept, so: Y_intercept_AB = Ay - slope_AB * Ax. So the equation for AB is Y = slope_AB*X + Y_intercept_AB.
OK, so now, the slope of the altitude from C to where it intersects line AB (let's call that point D, and the altitude line CD) is the negative reciprocal of the slope of AB; so slope_CD = -(1/slope_AB).
So now, given that you have one point (C) on line CD and its slope, you can get the equation for CD the same way as you did for AB. First, find its Y-intercept: Y_intercept_CD = Cy - slope_CD * Cx
So the equation for CD is Y = slope_CD * X + Y_intercept_CD.
So now you have equations for line AB and line CD:
Y = slope_AB * X + Y_intercept_AB
Y = slope_CD * X + Y_intercept_CD
And your problem is simplified to finding where those lines intersect, which is point D.
From the above equations, since both right-hand sides are equal to Y we can set them equal to each other:
slope_AB * X + Y_intercept_AB = slope_CD * X + Y_intercept_CD
and now it's just a matter of solving for X.
slope_AB * X - slope_CD*X = Y_intercept_CD - Y_intercept_AB
(slope_AB - slope_CD)*X = Y_intercept_CD - Y_intercept_AB
X = (Y_intercept_CD - Y_intercept_AB)/(slope_AB - slope_CD)
That will give you the X-value for D (Dx). For the Y-value, use either line equation. Let's use the one for AB:
Dy = slope_AB * Dx + Y_intercept_AB
Putting it all together, assume a triangle of A=(-4, 2), B=(0, 6), C=(6, -4):
#Points A, B,C:
Ax = -4; Ay = 2
Bx = 0; By = 6
Cx = 6; Cy = -4
#Line AB:
slope_AB = (By - Ay)/(Bx - Ax)
Y_intercept_AB = Ay - slope_AB*Ax
print("AB: slope: %s, intercept: %s" % (slope_AB, Y_intercept_AB))
#Line CD:
slope_CD = -(1/slope_AB)
Y_intercept_CD = Cy - slope_CD*Cx
print("CD: slope: %s, intercept: %s" % (slope_CD, Y_intercept_CD))
#Find the intersection of the two lines AB & CD:
Dx = (Y_intercept_CD - Y_intercept_AB)/(slope_AB - slope_CD)
Dy = slope_AB*Dx + Y_intercept_AB
print("Intersection at (%s, %s)" % (Dx, Dy))
Prints:
AB: slope: 1.0, intercept: 6.0
CD: slope: -1.0, intercept: 2.0
Intersection at (-2.0, 4.0)
One more thing: this will divide-by-zero and fail where points A & B have the same X-value (because it divides by Ax-Bx, which would be zero); but it's a start.

moving arrowhead across its body

I have created the following function which gets two line points and outputs three points of an arrowhead triangle. What I need to add is a length according to which the arrowhead will be poisitioned away from the point B across the line AB. To explain it better, I would like it like this: A----->--B where the two dashes are equal to the length
def create_arrowhead(A, B):
"""
Calculate the arrowheads vertex positions according to the edge direction.
Parameters
----------
A : array
x,y Starting point of edge
B : array
x,y Ending point of edge
Returns
-------
B, v1, v2 : tuple
The point of head, the v1 xy and v2 xy points of the two base vertices of the arrowhead.
"""
w = 0.005 # half width of the triangle base
h = w * 0.8660254037844386467637 # sqrt(3)/2
mag = math.sqrt((B[0] - A[0])**2.0 + (B[1] - A[1])**2.0)
u0 = (B[0] - A[0]) / (mag)
u1 = (B[1] - A[1]) / (mag)
U = [u0, u1]
V = [-U[1], U[0]]
v1 = [B[0] - h * U[0] + w * V[0], B[1] - h * U[1] + w * V[1]]
v2 = [B[0] - h * U[0] - w * V[0], B[1] - h * U[1] - w * V[1]]
return (B, v1, v2)
I finally found the solution. By multiplying a variable of distance with the unit vector's components and subtracting this result from B's coordinates. This will create a point on the line AB with distance d from B.
def create_arrowhead(A, B, d):
"""
Use trigonometry to calculate the arrowheads vertex positions according to the line direction.
Parameters
----------
A : array
x,y Starting point of line segment
B : array
x,y Ending point of line segment
Returns
-------
C, v1, v2 : tuple
The point of head with distance d from point B, the v1 xy and v2 xy points of the two base vertices of the arrowhead.
"""
w = 0.003 # Half of the triangle base width
h = w / 0.26794919243 # tan(15)
AB = [B[0] - A[0], B[1] - A[1]]
mag = math.sqrt(AB[0]**2.0 + AB[1]**2.0)
u0 = AB[0] / mag
u1 = AB[1] / mag
U = [u0, u1] # Unit vector of AB
V = [-U[1], U[0]] # Unit vector perpendicular to AB
C = [ B[0] - d * u0, B[1] - d * u1 ]
v1 = [C[0] - h * U[0] + w * V[0], C[1] - h * U[1] + w * V[1]]
v2 = [C[0] - h * U[0] - w * V[0], C[1] - h * U[1] - w * V[1]]
return (C, v1, v2)

Surface Area of a Hypar (hyperbolic paraboloid)

I've already posted this question about this topic:
Speeding up a closest point on a hyperbolic paraboloid algorithm
Given four points (p0,p1,p2,p3) to define a doubly ruled hyperbolic paraboloid, what is the best (fastest) way to compute its surface area using python's numpy module?
This is more maths than programming, so you may want to check with the folks at math.stackexchange. But, from the answer to your previous question, the surface can be parametrized as:
s = p0 + u * (p1 - p0) + v * (p3 - p0) + u * v * (p2 - p3 - p1 + p0) =
p0 + u * a + v * b + u * v * c
with the area limited by your four points being 0 <= u <= 1 and 0 <= v <= 1.
You can get two vectors tangent to the surface by differentiation:
t1 = ds/du = a + v * c
t2 = ds/dv = b + u * c
And you can get a vector, perpendicular to the other two, with a norm equal to the area of the parallelogram described by them, taking their cross product:
A = t1 x t2 = a x b + u * a x c + v * c x b
It is tempting to simply go ahead and integrate A, but it is its norm you want to integrate, not the vector itself. I have tried feeding that to Mathematica, to see if it would come up with some nice, closed form solution, but it's been going for several minutes now without arriving anywhere. So you may as well do things numerically:
def integrate_hypar(p0, p1, p2, p3, n=100):
a = p1 - p0
b = p3 - p0
c = p2 - p3 - p1 + p0
delta = 1 / n
u = np.linspace(0,1, num=n, endpoint=False) + delta / 2
axb = np.cross(a, b)
axc = np.cross(a, c)
cxb = np.cross(c, b)
diff_areas = (axb + u[:, None, None] * axc +
u[:, None] * cxb) * delta * delta
diff_areas *= diff_areas
diff_areas = np.sum(diff_areas, axis=-1)
diff_areas = np.sqrt(diff_areas)
return np.sum(diff_areas)
With the data points from your other question, I get:
p0 = np.array([1.15, 0.62, -1.01])
p1 = np.array([1.74, 0.86, -0.88])
p2 = np.array([1.79, 0.40, -1.46])
p3 = np.array([0.91, 0.79, -1.84])
>>> integrate_hypar(p0, p1, p2, p3)
0.54825122958719719

Categories

Resources