Finding the coordinates x y z of the center of a surface - ruby

I have a surface in a 3D space, and I need to calculate the coordinates of the center of that surface. The surface is a polygon.
I found this formula:
X1 += SUM[(xi + xi+1 ) * (xi* yi+1 - xi+1 * yi )]/6/area
Y1 += SUM[(yi + yi+1) * (xi* yi+1 - xi+1 * yi )]/6/area
But it only works for 2D. It gives x and y values. In my case I need 3 coordinates, x y z.
How can I do that ?
I need the center of the surfaces selected, but they may have any shape as they are a polygon.
Thanks a lot.

It's easy to prove that the centroid of the projection of a polygon on any plane is the projection of the centroid of that polygon on that plane.
So just calculate the centroid of the projections of the polygon on x-y plane and y-z plane, and you'll get the coordinate of the centroid of that polygon.
#!/usr/bin/env ruby
Point = Struct.new(:x, :y, :z)
def centroid(vertices, dimensions: [:x, :y])
area = 0
centroid_x, centroid_y = 0, 0
vertices.count.times do |i|
v1, v2 = vertices[i, 2]
v2 ||= vertices[0]
x1, y1 = dimensions.map{|d| v1.send(d)}
x2, y2 = dimensions.map{|d| v2.send(d)}
a = x1 * y2 - x2 * y1
area += a
centroid_x += (x1 + x2) * a
centroid_y += (y1 + y2) * a
end
area *= 0.5
centroid_x /= (6.0 * area)
centroid_y /= (6.0 * area)
[centroid_x, centroid_y]
end
vertices = [
Point.new(1, 0, 0),
Point.new(0, 2, 0),
Point.new(0, 0, 3)
]
p centroid(vertices, dimensions: [:x, :y])
p centroid(vertices, dimensions: [:y, :z])
p centroid(vertices, dimensions: [:z, :x])
prints
[0.3333333333333333, 0.6666666666666666]
[0.6666666666666666, 1.0]
[1.0, 0.3333333333333333]

#Aetherus it didn't work for my example:
X1 = 0
Y1 = 0
Y11 = 0
Z1 = 0
for i in 0..d.vertices.size-2
X1 += (d.vertices[i].position[0] + d.vertices[i+1].position[0]) * (d.vertices[i].position[0] * d.vertices[i+1].position[1] - d.vertices[i+1].position[0] * d.vertices[i].position[1])
Y1 += (d.vertices[i].position[1] + d.vertices[i+1].position[1]) * (d.vertices[i].position[0] * d.vertices[i+1].position[1] - d.vertices[i+1].position[0] * d.vertices[i].position[1])
Y11 += (d.vertices[i].position[1] + d.vertices[i+1].position[1]) * (d.vertices[i].position[1] * d.vertices[i+1].position[2] - d.vertices[i+1].position[1] * d.vertices[i].position[2])
Z1 += (d.vertices[i].position[2] + d.vertices[i+1].position[2]) * (d.vertices[i].position[1] * d.vertices[i+1].position[2] - d.vertices[i+1].position[1] * d.vertices[i].position[2])
end
x=X1/(6.0*(d.area))
y=Y1/(6.0*(d.area))
y1=Y11/(6.0*(d.area))
z=Z1/(6.0*(d.area))
UI.messagebox("x1 #{x} Y1 #{y} \n y11 #{y1} z1 #{z}")
With "d" is my polygon the xy is for x-y projection and y1z for y-z projection.

Related

Algorithm for a simple circular orbit in 3D [duplicate]

I want to draw a circle with a specified angle of inclination in 3D space using Python. Similar to the image below:
Image
I can already draw circles in 2D. I modified my program by referring to the link below:
Masking a 3D numpy array with a tilted disc
import numpy as np
import matplotlib.pyplot as plt
r = 5.0
a, b, c = (0.0, 0.0, 0.0)
angle = np.pi / 6 # "tilt" of the circle
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_xlim(-10,10)
ax.set_ylim(-10,10)
ax.set_zlim(-10,10)
phirange = np.linspace(0, 2 * np.pi, 300) #to make a full circle
x = a + r * np.cos(phirange)
y = b + r * np.sin(phirange)
z= c
ax.plot(x, y, z )
plt.show()
Now I can draw the circle in 3D space, but I can't get the circle to tilt at the angle I want.
I tried to modify the code in the Z part, the circle can be tilted, but not the result I want.
z = c + r * np.cos(phirange) * np.sin(angle)
Result image:
Do the X and Y parts also need to be modified? What should I do?
update: the circle tilt with other axis
Let i = (1, 0, 0), j = (0, 1, 0). Those are the direction vectors of the x-axis and y-axis, respectively. Those two vectors form an orthonormal basis of the horizontal plane. Here "orthonormal" means the two vectors are orthogonal and both have length 1.
A circle on the horizontal plane with centre C and radius r consists in all points that can be written as C + r * (cos(theta) * i + sin(theta) * j), for all values of theta in range [0, 2 pi]. Note that this works with i and j, but it would have worked equally with any other orthonormal basis of the horizontal plane.
A circle in any other plane can be described exactly the same way, by replacing i and j with two vectors that form an orthonormal basis of that plane.
According to your image, the "tilted plane at angle tilt" has the following orthonormal basis:
a = (cos(tilt), 0, sin(tilt))
b = (0, 1, 0)
You can check that these are two vectors in your plane, that they are orthogonal and that they both have norm 1. Thus they are indeed an orthonormal basis of your plane.
Therefore a circle in your plane, with centre C and radius r, can be described as all the points C + r * (cos(theta) * a + sin(theta) * b), where theta is in range [0, 2 pi].
In terms of x,y,z, this translates into the following system of three parametric equations:
x = x_C + r * cos(theta) * x_a + r * sin(theta) * x_b
y = y_C + r * cos(theta) * y_a + r * sin(theta) * y_b
z = z_C + r * cos(theta) * z_a + r * sin(theta) * z_b
This simplifies a lot, because x_b, y_a, z_b are all equal to 0:
x = x_C + r * cos(theta) * x_a # + sin(theta) * x_b, but x_b == 0
y = y_C + r * sin(theta) * y_b # + cos(theta) * y_a, but y_a == 0
z = z_C + r * cos(theta) * z_a # + sin(theta) * z_b, but z_b == 0
Replacing x_a, y_b and z_a by their values:
x = x_C + r * cos(tilt) * cos(theta)
y = y_C + r * sin(theta)
z = z_C + r * sin(tilt) * cos(theta)
In python:
import numpy as np
import matplotlib.pyplot as plt
# parameters of circle
r = 5.0 # radius
x_C, y_C, z_C = (0.0, 0.0, 0.0) # centre
tilt = np.pi / 6 # tilt of plane around y-axis
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_xlim(-10,10)
ax.set_ylim(-10,10)
ax.set_zlim(-10,10)
theta = np.linspace(0, 2 * np.pi, 300) #to make a full circle
x = x_C + r * np.cos(tilt) * np.cos(theta)
y = y_C + r * np.sin(theta)
z = z_C + r * np.sin(tilt) * np.cos(theta)
ax.plot(x, y, z )
plt.show()

How do you rectify a 3D planar polygon?

I have a 3D planar (all vertices lie in some plane) polygon with vertices: [(x1, y1, z1) ... (x1, y1, z1)].
I would like to transform this polygon so that I'm viewing it orthographically (as if I'm looking at it straight on).
How can this be done in Python?
I assume you have no information except for vertex coordinates.
Take three non-collinear (perhaps consequent) vertices C, A, B. Calculate normalized vector (divide by vector length)
b = (B - A) / |B - A|
then normal vector (using vector/cross multiplication)
N = b.cross.(A-C) and normalize it
un = N / |N|
and another unit vector in polygon plane
v = b.cross.n
Now we want find such matrix of affine transformations, that transforms vertex A into point (0,0,0), edge AB will be collinear with OX axis, normal will be collinear with OZ axis, vector q will be collinear with OY axis. This all means that rotated polygon will lie in OXY plane.
Mathematically: points A, u=A+b, v=A+q, n=A+un should be transformed in quadruplet (0,0,0), (1,0,0), (0,1,0), (0,0,1). In matrix form
[Ax ux vx nx] [0 1 0 0]
M * [Ay uy vy ny] = [0 0 1 0]
[Az uz vz nz] [0 0 0 1]
[1 1 1 1 ] [1 1 1 1]
or
M * S = D
Using matrix inverse
M * S * Sinv = D * Sinv
and finally
M = D * Sinv
So calculate matrix M and multiply it with every vertex coordinates. New coordinates should have zero Z-component (or very small due to numerical errors).
You can perform all described operations with numpy library with a little code
Example with specific data
Quick-made implementation in plain Python for reference
import math
def calcMatrix(ax, bx, cx, ay, by, cy, az, bz, cz):
ux, uy, uz = bx - ax, by - ay, bz - az
mag = math.sqrt(ux*ux+uy*uy +uz*uz)
ux, uy, uz = ux / mag, uy / mag, uz / mag
Cx, Cy, Cz = ax - cx, ay - cy, az - cz
nx, ny, nz = uy * Cz - uz * Cy, uz * Cx - ux * Cz, ux * Cy - uy * Cx
mag = math.sqrt(nx*nx+ny*ny+nz*nz)
nx, ny, nz = nx / mag, ny / mag, nz / mag
vx, vy, vz = uy * nz - uz * ny, uz * nx - ux * nz, ux * ny - uy * nx
denom = 1.0 / (ux*ux+uy*uy + uz*uz)
M = [[0.0]*4 for _ in range(4)]
M[3][3] = 1.0
M[0][0] = denom*(ux)
M[0][1] = denom*(uy)
M[0][2] = denom*(uz)
M[0][3] = denom*(-ax*ux-ay*uy+az*uz)
M[1][0] = denom*(vx)
M[1][1] = denom*(vy)
M[1][2] = denom*(vz)
M[1][3] = -denom*(ax*vx-ay*vy+az*vz)
M[2][0] = denom*(nx)
M[2][1] = denom*(ny)
M[2][2] = denom*(nz)
M[2][3] = denom*(-ax*nx-ay*ny+az*nz)
return M
def mult(M, vec):
res = [0]*4
for k in range(4):
for i in range(4):
res[k] += M[k][i] * vec[i]
return res
#test corners and middle point
M = calcMatrix(1, 0, 0, 0, 1, 0, 0, 0, 1)
#print(M)
p = [1, 0, 0, 1]
print(mult(M, p))
p = [0, 1, 0, 1]
print(mult(M, p))
p = [0, 0, 1, 1]
print(mult(M, p))
p = [1/3, 1/3, 1/3, 1]
print(mult(M, p))
test results:
[0.0, 0.0, 0.0, 1.0]
[1.4142135623730951, 0.0, 0.0, 1.0]
[0.7071067811865476, 1.2247448713915892, 0.0, 1.0]
[0.7071067811865476, 0.4082482904638631, 1.1102230246251565e-16, 1.0]
Find a normal n to the polygon, by means of a cross-product between two non-parallel sides. Take the cross-product of n with a vertical vector, to get an horizontal vector u. Then take the cross product of n and u to get v, and normalize the vectors. u and v are parallel to the plane of the polygon and orthogonal to each other.
Finally, for every vertex p compute the 2D coordinates (p.u, p.v) which show you the polygon in its plane.
numpy supplies the cross and dot vector functions. Also linalg.norm (or sqrt(dot(v, v))).
Here's a robust approach using NumPy (project(); the rest is test code).
import numpy
import scipy.spatial
def project(x):
# Center the plane on the origin
x = x - numpy.mean(x, axis=0)
# Compute the Singular Value Decomposition
u, s, v = numpy.linalg.svd(x)
# Return the top two principal components
return u[:, :2] # numpy.diag(s[:2])
def test():
n = 10
x = (numpy.random.rand(n, 2) # numpy.random.rand(2, 3)) + numpy.random.rand(3)
y = project(x)
print(x.shape, y.shape)
print(
numpy.max(
numpy.abs(
scipy.spatial.distance_matrix(x, x)
- scipy.spatial.distance_matrix(y, y)
)
)
)
if __name__ == "__main__":
test()
Sample output:
(10, 3) (10, 2)
5.551115123125783e-16

How to Position Objects With Same Distances

I am coding a game in Unity that number of your soldiers are increasing/decreasing by some triggers. I want to position my soldier objects like a full circle, so they will always be near each other(like same distances) even if their number is increasing or decreasing. How can I manage this?
You can start with some simple relatively ordered distribution of positions and by applying a dynamical system approach/gradient decent type iteration, you can let the positions converge to a much more structured pattern. I wrote such implementation in python, it is in vectorized form, but I also added an equivalent function with for loops, to illustrate the structure of the function. The final ordered pattern is inspired by the stable equilibrium position that a bunch of discs of same radius r would form if they are hold by springs, one for every two of them. To ease up the computations, I squared the spring tensions, thus avoiding square roots, so not exactly like the typical physics model, but close to it.
import numpy as np
import matplotlib.pyplot as plt
def Grad(pos, r):
Dq = - pos[:, :, np.newaxis] + pos[:, np.newaxis, :]
D = Dq[0,:,:]*Dq[0,:,:] + Dq[1,:,:]*Dq[1,:,:] + np.identity(Dq.shape[1])
Dq = (1 - r**2 / D) * Dq
return - Dq.sum(axis=2)
def Grad_flow(q_, r, step):
Q = q_
n_iter = 0
while True:
n_iter = n_iter + 1 # you can count the number of iterations needed to reach the equilibrium
Q_prev = Q
Q = Q - step * Grad(Q, r)
if np.sum(np.abs((Q.T).dot(Q) - (Q_prev.T).dot(Q_prev))) < 1e-5:
return Q
'''
Test:
'''
p = np.array([[-3, 3], [-1, 3], [1,3], [3,3],
[-3, 1], [-1, 1], [1,1], [3,1],
[-3,-1], [-1,-1], [1,-1], [3,-1],
[-3,-3], [-1, -3], [1, -3], [3,-3],
[-2, 1], [-1,2],[2,-2], [-2,-2],
[2,2], [2,0]]).T
r = 0.5
step = 0.01
q = Grad_flow(p, r, step)
'''
Plot:
'''
fig, axs = plt.subplots(1,1)
axs.set_aspect('equal')
axs.plot(q[0,:], q[1,:], 'ro')
axs.plot(p[0,:], p[1,:], 'bo')
plt.grid()
plt.show()
You start from the blue positions and you make them converge to the red positions:
Here is the loop version of the Grad function:
def Grad(pos, r):
grad = np.zeros(pos.shape, dtype=float)
for i in range(pos.shape[1]):
for j in range(pos.shape[1]):
if not i==j:
d_pos_0 = pos[0, i] - pos[0, j]
d_pos_1 = pos[1, i] - pos[1, j]
m = d_pos_0*d_pos_0 + d_pos_1*d_pos_1
m = 1 - r*r / m
grad[0, i] = grad[0, i] + m * d_pos_0
grad[1, i] = grad[1, i] + m * d_pos_1
return grad
Of course, all of this is a bit heuristic and I cannot promise full generality, so you have to play and select the parameters r which is half-distance between positions, iteration step-size step, the initial position p and so on.
Supposing you are working in the horizontal plane, you can define you much rotation for each of your soldiers, and the find that point in the plane converting cartesian coordinates (x, y) into polar ones (R, fi), add theta to fi and then convert back to cartesian:
// Rotate B around A by angle theta
private (float x, float y) Rotate(
(float x, float y) A,
(float x, float y) B,
float theta) {
float fi = Math.Atan2(B.y - A.y, B.x - A.x) + theta;
float R = Math.Sqrt((A.y - B.y) * (A.y - B.y) + (A.x - B.x) * (A.x - B.x));
return (A.x + R * Math.Cos(fi), A.y + R * Math.Sin(fi));
}
Another option that does exactly the same thing, but not using polar coords:
// Rotate B around A by angle theta clockwise
private (float x, float y) Rotate(
(float x, float y) A,
(float x, float y) B,
float theta)
{
float s = Math.Sin(theta);
float c = Math.Cos(theta);
// translate point back to origin:
B.x -= A.x;
B.y -= A.y;
// rotate point clockwise
float xnew = B.x * c - B.y * s;
float ynew = B.x * s + B.y * c;
// translate point back:
B.x = xnew + A.x;
B.y = ynew + A.y;
return B;
}
If you want your soldiers equally distributed in a circle you would need to calcualte the rotation angle of each just with float angle = 360 / numSoldiers;.
If your game is in 3d and you are working in the floor plane (XZ) you can change the .ys by .zs in the code.
You can also check how the algorithms work in a simple unity project cubes or in a console c# app to understand them and to check how they just perform the rotation of a vector's end point around its origin to return the rotated point. I think that is what you would need to find the points of interest for the position of your soldiers.

Finding the line along the intersection of two planes

I am trying to draw the line formed by the intersections of two planes in 3D, but I am having trouble understanding the math, which has been explained here and here.
I tried to figure it out myself, but the closest that I got to a solution was a vector pointing along the same direction as the intersection line, by using the cross product of the normals of the planes. I have no idea how to find a point on the intersection line, any point would do. I think that this method is a dead end. Here is a screenshot of this attempt:
I tried to use the solution mentioned in this question, but it has a dead link to the original explanation, and the equation didn't work for me (it has unbalanced parentheses, which I tried to correct below).
var planeA = new THREE.Plane((new THREE.Vector3(0, 0, 1)).normalize(), 100);
var planeB = new THREE.Plane((new THREE.Vector3(1, 1, 1)).normalize(), -100);
var x1 = planeA.normal.x,
y1 = planeA.normal.y,
z1 = planeA.normal.z,
d1 = planeA.constant;
var x2 = planeB.normal.x,
y2 = planeB.normal.y,
z2 = planeB.normal.z,
d2 = planeB.constant;
var point1 = new THREE.Vector3();
point1.x = 0;
point1.z = (y2 / y1) * (d1 - d2) / (z2 - z1 * y2 / y1);
point1.y = (-z1 * point1.z - d1) / y1;
var point2 = new THREE.Vector3();
point2.x = 1;
point2.z = (y2 / y1) * (x1 * point2.x + d1) - (x2 * point2.x - d2) / (z2 - z1 * y2 / y1);
point2.y = (-z1 * point2.z - x1 * point2.x - d1) / y1;
console.log(point1, point2);
output:
THREE.Vector3 {x: -1, y: NaN, z: NaN, …}
THREE.Vector3 {x: 1, y: Infinity, z: -Infinity, …}
expected output:
A point along the intersection where x = 0, and
Another point on the same line where x = 1
If someone could point me to a good explanation of how this is supposed to work, or an example of a plane-plane intersection algorithm, I would be grateful.
Here is an implementation of a solution for plane-plane intersections described at http://geomalgorithms.com/a05-_intersect-1.html . Essentially, you first use the cross product of the normals of the planes to find the direction of a line in both planes. Secondly, you use some algebra on the implicit equation of the planes (P . n + d = 0 where P is some point on the plane, n is the normal and d is the plane constant) to solve for a point which is on the intersection of the planes and also on one of the x=0, y=0 or z=0 planes. The solution is then the line described by a point and a vector. I was using three.js version 79
/*
Algorithm taken from http://geomalgorithms.com/a05-_intersect-1.html. See the
section 'Intersection of 2 Planes' and specifically the subsection
(A) Direct Linear Equation
*/
function intersectPlanes(p1, p2) {
// the cross product gives us the direction of the line at the intersection
// of the two planes, and gives us an easy way to check if the two planes
// are parallel - the cross product will have zero magnitude
var direction = new THREE.Vector3().crossVectors(p1.normal, p2.normal)
var magnitude = direction.distanceTo(new THREE.Vector3(0, 0, 0))
if (magnitude === 0) {
return null
}
// now find a point on the intersection. We use the 'Direct Linear Equation'
// method described in the linked page, and we choose which coordinate
// to set as zero by seeing which has the largest absolute value in the
// directional vector
var X = Math.abs(direction.x)
var Y = Math.abs(direction.y)
var Z = Math.abs(direction.z)
var point
if (Z >= X && Z >= Y) {
point = solveIntersectingPoint('z', 'x', 'y', p1, p2)
} else if (Y >= Z && Y >= X){
point = solveIntersectingPoint('y', 'z', 'x', p1, p2)
} else {
point = solveIntersectingPoint('x', 'y', 'z', p1, p2)
}
return [point, direction]
}
/*
This method helps finding a point on the intersection between two planes.
Depending on the orientation of the planes, the problem could solve for the
zero point on either the x, y or z axis
*/
function solveIntersectingPoint(zeroCoord, A, B, p1, p2){
var a1 = p1.normal[A]
var b1 = p1.normal[B]
var d1 = p1.constant
var a2 = p2.normal[A]
var b2 = p2.normal[B]
var d2 = p2.constant
var A0 = ((b2 * d1) - (b1 * d2)) / ((a1 * b2 - a2 * b1))
var B0 = ((a1 * d2) - (a2 * d1)) / ((a1 * b2 - a2 * b1))
var point = new THREE.Vector3()
point[zeroCoord] = 0
point[A] = A0
point[B] = B0
return point
}
var planeA = new THREE.Plane((new THREE.Vector3(0, 0, 1)).normalize(), 100)
var planeB = new THREE.Plane((new THREE.Vector3(1, 1, 1)).normalize(), -100)
var [point, direction] = intersectPlanes(planeA, planeB)
When I have problems like this, I usually let a symbolic algebra package (Mathematica in this case) deal with it. After typing
In[1]:= n1={x1,y1,z1};n2={x2,y2,z2};p={x,y,z};
In[2]:= Solve[n1.p==d1&&n2.p==d2,p]
and simplifying and substituting x=0 and x=1, I get
d2 z1 - d1 z2 d2 y1 - d1 y2
Out[5]= {{{y -> -------------, z -> ----------------}},
y2 z1 - y1 z2 -(y2 z1) + y1 z2
d2 z1 - x2 z1 - d1 z2 + x1 z2
> {{y -> -----------------------------,
y2 z1 - y1 z2
d2 y1 - x2 y1 + (-d1 + x1) y2
> z -> -----------------------------}}}
-(y2 z1) + y1 z2
It is easy to let three.js solve this for you.
If you were to express your problem in matrix notation
m * x = v
Then the solution for x is
x = inverse( m ) * v
We'll use a 4x4 matrix for m, because three.js has an inverse() method for the Matrix4 class.
var x1 = 0,
y1 = 0,
z1 = 1,
d1 = 100;
var x2 = 1,
y2 = 1,
z2 = 1,
d2 = -100;
var c = 0; // the desired value for the x-coordinate
var v = new THREE.Vector4( d1, d2, c, 1 );
var m = new THREE.Matrix4( x1, y1, z1, 0,
x2, y2, z2, 0,
1, 0, 0, 0,
0, 0, 0, 1
);
var minv = new THREE.Matrix4().getInverse( m );
v.applyMatrix4( minv );
console.log( v );
The x-component of v will be equal to c, as desired, and the y- and z-components will contain the values you are looking for. The w-component is irrelevalent.
Now, repeat for the next value of c, c = 1.
three.js r.58
Prerequisites
Recall that to represent a line we need a vector describing its direction and a point through which this line goes. This is called parameterized form:
line_point(t) = t * (point_2 - point_1) + point_1
where point_1 and point_2 are arbitrary points through which the line goes, and t is a scalar which parameterizes our line. Now we can find any point line_point(t) on the line if we put arbitrary t into the equation above.
NOTE: The term (point_2 - point_1) is nothing, but a vector describing the direction of our line, and the term point_1 is nothing, but a point through which our line goes (of course point_2) would also be fine to use too.
The Algorithm
Find the direction direction of the intersection line by taking
cross product of plane normals, i.e. direction = cross(normal_1,
normal_2).
Take any plane, for example the first one, and find any 2 distinct points
on this plane: point_1 and point_2. If we assume that the plane equation
is of the form a1 * x + b1 * y + c1 * z + d1 = 0, then to find 2
distinct points we could do the following:
y1 = 1
z1 = 0
x1 = -(b1 + d1) / a1
y2 = 0
z2 = 1
x2 = -(c1 + d1) / a1
where point_1 = (x1, y1, z1) and point_2 = (x2, y2, z2).
Now that we have 2 points, we can construct the parameterized
representation of the line lying on this first plane:
line_point(t) = t * (point_2 - point_1) + point_1, where line_point(t)
describes any point on this line, and t is just an input scalar
(frequently called parameter).
Find the intersection point intersection_point of the line
line_point(t) and the second plane a2 * x + b2 * y + c2 * z + d2 = 0 by using
the standard line-plane intersection algorithm (pay attention to the
Algebraic form section as this is all you need to implement line-plane
intersection, if you haven't done so already).
Our intersection line is now found and can be constructed in
parameterized form as usual: intersection_line_point(s) = s *
direction + intersection_point, where intersection_line_point(s)
describes any point on this intersection line, and s is parameter.
NOTE: I didn't read this algorithm anywhere, I've just devised it from the top of my head based on my knowledge of linear algebra. That doesn't mean that it doesn't work, but it might be possible that this algorithm can be optimized further.
Conditioning
When 2 normal vectors normal_1 and normal_2 are almost collinear this problem gets extremely ill-conditioned. Geometrically it means that the 2 planes are almost parallel to each other and determining the intersection line with acceptable precision becomes impossible in finite-precision arithmetic which is floating-point arithmetic in this case.

Optimally place a pie slice in a rectangle

Given a rectangle (w, h) and a pie slice with a radius less or equal to the smaller of both sides (w, h), a start angle and an end angle, how can I place the slice optimally in the rectangle so that it fills the room best (from an optical point of view, not mathematically speaking)?
I'm currently placing the pie slice's center in the center of the rectangle and use the half of the smaller of both rectangle sides as the radius. This leaves plenty of room for certain configurations.
Examples to make clear what I'm after, based on the precondition that the slice is drawn like a unit circle (i.e. 0 degrees on positive X axis, then running clock-wise):
A start angle of 0 and an end angle of PI would lead to a filled lower half of the rectangle and an empty upper half. A good solution here would be to move the center up by 1/4*h.
A start angle of 0 and an end angle of PI/2 would lead to a filled bottom right quarter of the rectangle. A good solution here would be to move the center point to the top left of the rectangle and to set the radius to the smaller of both rectangle sides.
This is fairly easy for the cases I've sketched but it becomes complicated when the start and end angles are arbitrary. I am searching for an algorithm which determines center of the slice and radius in a way that fills the rectangle best. Pseudo code would be great since I'm not a big mathematician.
The extrema of the bounding box of your arc are in the following format:
x + x0 * r = 0
x + x1 * r = w
y + y0 * r = 0
y + y1 * r = h
The values x0, x1, y0 and y1 are found by taking the minimum and maximum values of up to 7 points: any tangential points that are spanned (i.e. 0, 90, 180 and 270 degrees) and the end points of the two line segments.
Given the extrema of the axis-aligned bounding box of the arc (x0, y0), (x1, y1) the radius and center point can be calculated as follows:
r = min(w/(x1-x0), h/(y1-y0)
x = -x0 * r
y = -y0 * r
Here is an implementation written in Lua:
-- ensures the angle is in the range [0, 360)
function wrap(angle)
local x = math.fmod(angle, 2 * math.pi)
if x < 0 then
x = x + 2 * math.pi
end
return x
end
function place_arc(t0, t1, w, h)
-- find the x-axis extrema
local x0 = 1
local x1 = -1
local xlist = {}
table.insert(xlist, 0)
table.insert(xlist, math.cos(t0))
table.insert(xlist, math.cos(t1))
if wrap(t0) > wrap(t1) then
table.insert(xlist, 1)
end
if wrap(t0-math.pi) > wrap(t1-math.pi) then
table.insert(xlist, -1)
end
for _, x in ipairs(xlist) do
if x < x0 then x0 = x end
if x > x1 then x1 = x end
end
-- find the y-axis extrema
local ylist = {}
local y0 = 1
local y1 = -1
table.insert(ylist, 0)
table.insert(ylist, math.sin(t0))
table.insert(ylist, math.sin(t1))
if wrap(t0-0.5*math.pi) > wrap(t1-0.5*math.pi) then
table.insert(ylist, 1)
end
if wrap(t0-1.5*math.pi) > wrap(t1-1.5*math.pi) then
table.insert(ylist, -1)
end
for _, y in ipairs(ylist) do
if y < y0 then y0 = y end
if y > y1 then y1 = y end
end
-- calculate the maximum radius the fits in the bounding box
local r = math.min(w / (x1 - x0), h / (y1 - y0))
-- find x & y from the radius and minimum extrema
local x = -x0 * r
local y = -y0 * r
-- calculate the final axis-aligned bounding-box (AABB)
local aabb = {
x0 = x + x0 * r,
y0 = y + y0 * r,
x1 = x + x1 * r,
y1 = y + y1 * r
}
return x, y, r, aabb
end
function center_arc(x, y, aabb, w, h)
dx = (w - aabb.x1) / 2
dy = (h - aabb.y1) / 2
return x + dx, y + dy
end
t0 = math.rad(60)
t1 = math.rad(300)
w = 320
h = 240
x, y, r, aabb = place_arc(t0, t1, w, h)
x, y = center_arc(x, y, aabb, w, h)
print(x, y, r)
Example output:
Instead of pseudo code, I used python, but it should be usable. For this algorithm, I assume that startAngle < endAngle and that both are within [-2 * PI, 2 * PI]. If you want to use both within [0, 2 * PI] and let startAngle > endAngle, I would do:
if (startAngle > endAngle):
startAngle = startAngle - 2 * PI
So, the algorithm that comes to mind is to calculate the bounds of the unit arc and then scale to fit your rectangle.
The first is the harder part. You need to calculate 4 numbers:
Left: MIN(cos(angle), 0)
Right: MAX(cos(angle), 0)
Top: MIN(sin(angle),0)
Bottom: MAX(sin(angle),0)
Of course, angle is a range, so it's not as simple as this. However, you really only have to include up to 11 points in this calculation. The start angle, the end angle, and potentially, the cardinal directions (there are 9 of these going from -2 * PI to 2 * PI.) I'm going to define boundingBoxes as lists of 4 elements, ordered [left, right, top, bottom]
def IncludeAngle(boundingBox, angle)
x = cos(angle)
y = sin(angle)
if (x < boundingBox[0]):
boundingBox[0] = x
if (x > boundingBox[1]):
boundingBox[1] = x
if (y < boundingBox[2]):
boundingBox[2] = y
if (y > boundingBox[3]):
boundingBox[3] = y
def CheckAngle(boundingBox, startAngle, endAngle, angle):
if (startAngle <= angle and endAngle >= angle):
IncludeAngle(boundingBox, angle)
boundingBox = [0, 0, 0, 0]
IncludeAngle(boundingBox, startAngle)
IncludeAngle(boundingBox, endAngle)
CheckAngle(boundingBox, startAngle, endAngle, -2 * PI)
CheckAngle(boundingBox, startAngle, endAngle, -3 * PI / 2)
CheckAngle(boundingBox, startAngle, endAngle, -PI)
CheckAngle(boundingBox, startAngle, endAngle, -PI / 2)
CheckAngle(boundingBox, startAngle, endAngle, 0)
CheckAngle(boundingBox, startAngle, endAngle, PI / 2)
CheckAngle(boundingBox, startAngle, endAngle, PI)
CheckAngle(boundingBox, startAngle, endAngle, 3 * PI / 2)
CheckAngle(boundingBox, startAngle, endAngle, 2 * PI)
Now you've computed the bounding box of an arc with center of 0,0 and radius of 1. To fill the box, we're going to have to solve a linear equation:
boundingBox[0] * xRadius + xOffset = 0
boundingBox[1] * xRadius + xOffset = w
boundingBox[2] * yRadius + yOffset = 0
boundingBox[3] * yRadius + yOffset = h
And we have to solve for xRadius and yRadius. You'll note there are two radiuses here. The reason for that is that in order to fill the rectangle, we have to multiple by different amounts in the two directions. Since your algorithm asks for only one radius, we will just pick the lower of the two values.
Solving the equation gives:
xRadius = w / (boundingBox[1] - boundingBox[0])
yRadius = h / (boundingBox[2] - boundingBox[3])
radius = MIN(xRadius, yRadius)
Here, you have to check for boundingBox[1] - boundingBox[0] being 0 and set xRadius to infinity in that case. This will give the correct result as yRadius will be smaller. If you don't have an infinity available, you can just set it to 0 and in the MIN function, check for 0 and use the other value in that case. xRadius and yRadius can't both be 0 because both sin and cos would have to be 0 for all angles included above for that to be the case.
Now we have to place the center of the arc. We want it centered in both directions. Now we'll create another linear equation:
(boundingBox[0] + boundingBox[1]) / 2 * radius + x = xCenter = w/2
(boundingBox[2] + boundingBox[3]) / 2 * radius + y = yCenter = h/2
Solving for x and y, the center of the arc, gives
x = w/2 - (boundingBox[0] + boundingBox[1]) * radius / 2
y = h/2 - (boundingBox[3] + boundingBox[2]) * radius / 2
This should give you the center of the arc and the radius needed to put the largest circle in the given rectangle.
I haven't tested any of this code, so this algorithm may have huge holes, or perhaps tiny ones caused by typos. I'd love to know if this algoritm works.
edit:
Putting all of the code together gives:
def IncludeAngle(boundingBox, angle)
x = cos(angle)
y = sin(angle)
if (x < boundingBox[0]):
boundingBox[0] = x
if (x > boundingBox[1]):
boundingBox[1] = x
if (y < boundingBox[2]):
boundingBox[2] = y
if (y > boundingBox[3]):
boundingBox[3] = y
def CheckAngle(boundingBox, startAngle, endAngle, angle):
if (startAngle <= angle and endAngle >= angle):
IncludeAngle(boundingBox, angle)
boundingBox = [0, 0, 0, 0]
IncludeAngle(boundingBox, startAngle)
IncludeAngle(boundingBox, endAngle)
CheckAngle(boundingBox, startAngle, endAngle, -2 * PI)
CheckAngle(boundingBox, startAngle, endAngle, -3 * PI / 2)
CheckAngle(boundingBox, startAngle, endAngle, -PI)
CheckAngle(boundingBox, startAngle, endAngle, -PI / 2)
CheckAngle(boundingBox, startAngle, endAngle, 0)
CheckAngle(boundingBox, startAngle, endAngle, PI / 2)
CheckAngle(boundingBox, startAngle, endAngle, PI)
CheckAngle(boundingBox, startAngle, endAngle, 3 * PI / 2)
CheckAngle(boundingBox, startAngle, endAngle, 2 * PI)
if (boundingBox[1] == boundingBox[0]):
xRadius = 0
else:
xRadius = w / (boundingBox[1] - boundingBox[0])
if (boundingBox[3] == boundingBox[2]):
yRadius = 0
else:
yRadius = h / (boundingBox[3] - boundingBox[2])
if xRadius == 0:
radius = yRadius
elif yRadius == 0:
radius = xRadius
else:
radius = MIN(xRadius, yRadius)
x = w/2 - (boundingBox[0] + boundingBox[1]) * radius / 2
y = h/2 - (boundingBox[3] + boundingBox[2]) * radius / 2
edit:
One issue here is that sin[2 * PI] is not going to be exactly 0 because of rounding errors. I think the solution is to get rid of the CheckAngle calls and replace them with something like:
def CheckCardinal(boundingBox, startAngle, endAngle, cardinal):
if startAngle < cardinal * PI / 2 and endAngle > cardinal * PI / 2:
cardinal = cardinal % 4
if cardinal == 0:
boundingBox[1] = 1
if cardinal == 1:
boundingBox[3] = 1
if cardinal == 2:
boundingBox[0] = -1
if cardinal == 3:
boundingBox[2] = -1
CheckCardinal(boundingBox, startAngle, endAngle, -4)
CheckCardinal(boundingBox, startAngle, endAngle, -3)
CheckCardinal(boundingBox, startAngle, endAngle, -2)
CheckCardinal(boundingBox, startAngle, endAngle, -1)
CheckCardinal(boundingBox, startAngle, endAngle, 0)
CheckCardinal(boundingBox, startAngle, endAngle, 1)
CheckCardinal(boundingBox, startAngle, endAngle, 2)
CheckCardinal(boundingBox, startAngle, endAngle, 3)
CheckCardinal(boundingBox, startAngle, endAngle, 4)
You still need IncludeAngle(startAngle) and IncludeAngle(endAngle)
Just consider a circle and forget the filling. The bounds will either be the center of the circle, the endpoints, or the points at 0, 90, 180, or 270 degrees (if they exist in this slice). The maxima and minima of these seven points will determine your bounding rectangle.
As far as placing it in the center, calculate the average of the max and min for both the rectangle and the pie slice, and add or subtract the difference of these to whichever one you want to move.
I would divide the problem into three steps:
Find the bounding box of a unit pie slice (or if a radius is given the actual pie slice centered at (0, 0)).
Fit the bounding box in your rectangle.
Use the information about fitting the bounding box to adjust the center and radius of the pie slice.
When I have time, I may flush this out with more details.

Resources