When running a performance profiler (VS2017), I find that XMVector3Dot shows up as taking some time (it's part of my code that does collision detection). I find that by replacing the usage of XMVECTOR with XMFLOAT3 and manually calculating a dot product (the same reasoning applies to other vector operations), that the speed of my algorithm is faster. I understand that XMVECTORs are of course needed when suppling the GPU with vectors etc, this is what the GPU understands, but is it expected that when calculating on the CPU that it's faster to manually calculate a dot product with XMFLOAT3s instead of XMVECTORs?
Efficient use of SIMD requires a number of techniques, primarily keeping your computation vectorized for as long as you can. If you have to convert back and forth between vectorized and scalar, the performance benefits of SIMD are lost.
Dot-product takes two vectors and returns a scalar value. To make it easier to keep computations vectorized, XMVector3Dot returns the scalar value 'splatted' across the vector. If you are just extracting one of the components and going back to scalar computations, then your algorithm is likely not well vectorized and you would in fact be better off doing dot product as a scalar operation.
DirectXMath includes a collision header with various tests that follow the SIMD best practices. For example:
inline XMVECTOR PointOnPlaneInsideTriangle(FXMVECTOR P, FXMVECTOR V0, FXMVECTOR V1, GXMVECTOR V2)
{
// Compute the triangle normal.
XMVECTOR N = XMVector3Cross( XMVectorSubtract( V2, V0 ), XMVectorSubtract( V1, V0 ) );
// Compute the cross products of the vector from the base of each edge to
// the point with each edge vector.
XMVECTOR C0 = XMVector3Cross( XMVectorSubtract( P, V0 ), XMVectorSubtract( V1, V0 ) );
XMVECTOR C1 = XMVector3Cross( XMVectorSubtract( P, V1 ), XMVectorSubtract( V2, V1 ) );
XMVECTOR C2 = XMVector3Cross( XMVectorSubtract( P, V2 ), XMVectorSubtract( V0, V2 ) );
// If the cross product points in the same direction as the normal the the
// point is inside the edge (it is zero if is on the edge).
XMVECTOR Zero = XMVectorZero();
XMVECTOR Inside0 = XMVectorGreaterOrEqual( XMVector3Dot( C0, N ), Zero );
XMVECTOR Inside1 = XMVectorGreaterOrEqual( XMVector3Dot( C1, N ), Zero );
XMVECTOR Inside2 = XMVectorGreaterOrEqual( XMVector3Dot( C2, N ), Zero );
// If the point inside all of the edges it is inside.
return XMVectorAndInt( XMVectorAndInt( Inside0, Inside1 ), Inside2 );
}
Instead of doing a scalar conversion an then comparison, it uses vectorized comparisons.
The DirectXMath collision code also avoids dynamic branches. Modern CPUs have a lot of computational power so doing more work without dynamic branches or accessing memory is often faster. For example, here is the sphere-triangle test:
inline bool BoundingSphere::Intersects( FXMVECTOR V0, FXMVECTOR V1, FXMVECTOR V2 ) const
{
// Load the sphere.
XMVECTOR vCenter = XMLoadFloat3( &Center );
XMVECTOR vRadius = XMVectorReplicatePtr( &Radius );
// Compute the plane of the triangle (has to be normalized).
XMVECTOR N = XMVector3Normalize( XMVector3Cross( XMVectorSubtract( V1, V0 ), XMVectorSubtract( V2, V0 ) ) );
// Assert that the triangle is not degenerate.
assert( !XMVector3Equal( N, XMVectorZero() ) );
// Find the nearest feature on the triangle to the sphere.
XMVECTOR Dist = XMVector3Dot( XMVectorSubtract( vCenter, V0 ), N );
// If the center of the sphere is farther from the plane of the triangle than
// the radius of the sphere, then there cannot be an intersection.
XMVECTOR NoIntersection = XMVectorLess( Dist, XMVectorNegate( vRadius ) );
NoIntersection = XMVectorOrInt( NoIntersection, XMVectorGreater( Dist, vRadius ) );
// Project the center of the sphere onto the plane of the triangle.
XMVECTOR Point = XMVectorNegativeMultiplySubtract( N, Dist, vCenter );
// Is it inside all the edges? If so we intersect because the distance
// to the plane is less than the radius.
XMVECTOR Intersection = DirectX::Internal::PointOnPlaneInsideTriangle( Point, V0, V1, V2 );
// Find the nearest point on each edge.
XMVECTOR RadiusSq = XMVectorMultiply( vRadius, vRadius );
// Edge 0,1
Point = DirectX::Internal::PointOnLineSegmentNearestPoint( V0, V1, vCenter );
// If the distance to the center of the sphere to the point is less than
// the radius of the sphere then it must intersect.
Intersection = XMVectorOrInt( Intersection, XMVectorLessOrEqual( XMVector3LengthSq( XMVectorSubtract( vCenter, Point ) ), RadiusSq ) );
// Edge 1,2
Point = DirectX::Internal::PointOnLineSegmentNearestPoint( V1, V2, vCenter );
// If the distance to the center of the sphere to the point is less than
// the radius of the sphere then it must intersect.
Intersection = XMVectorOrInt( Intersection, XMVectorLessOrEqual( XMVector3LengthSq( XMVectorSubtract( vCenter, Point ) ), RadiusSq ) );
// Edge 2,0
Point = DirectX::Internal::PointOnLineSegmentNearestPoint( V2, V0, vCenter );
// If the distance to the center of the sphere to the point is less than
// the radius of the sphere then it must intersect.
Intersection = XMVectorOrInt( Intersection, XMVectorLessOrEqual( XMVector3LengthSq( XMVectorSubtract( vCenter, Point ) ), RadiusSq ) );
return XMVector4EqualInt( XMVectorAndCInt( Intersection, NoIntersection ), XMVectorTrueInt() );
}
For your algorithm, you should either (a) make it fully vectorized or (b) stick with a scalar dot-product.
Related
I have this one triangle with arbitrary vertices positioned in a 3D space.
I have that finding the centroid of such triangle is easy by doing:
float centroid[3] = { 0, 0, 0 };
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
centroid[i] += points[j][i];
}
centroid[i] /= 3.0;
}
It's also easy to find the normal for it with something called plane equation:
crossProduct(points[1] - points[0], points[2] - points[0]);
There is a very simple method for moving the vertices away from the centroid, but that is too linear. I can only move the pointers back and forth.
What is the formula that I need to be able to freely move the vertices in a pseudo X/Y axis that is formed from the perspective of the triangle normal?
For reference, I'm using C++ and QT for the vectors and matrices. I'm rendering with basic OpenGL.
To build coordinate axes in triangle plane, you can use axis pseudoX from centroid to any vertex and perpendicular axis pseudoY = pseudoX.cross.Normal.
The choice of vertex as base vector seems rather natural. If you want to add some randomness, rotate this pseudoX by arbitrary angle and generate new pseudoY as cross product again.
Another method to generate vector in that plane - from normal only. Choose normal component with the largest magnitude, negate it and exchange with component with the second magnitude, make the smallest component zero. For example, if
|ny|>=|nz|>=|nx|
Vec = (0, nz, -ny)
note that Vec.dot.Normal = 0, so Vec lies in triangle plane
geometry.faces accessible only for new THREE.BoxGeometry. Then I try to use THREE.BoxBufferGeometry I can't change color for faces.
Not working:
var geometry = new THREE.BoxBufferGeometry( 100, 100, 100 );
for ( var i = 0; i < geometry.faces.length; i ++ ) {
geometry.faces[ i ].color.setHex( Math.random() * 0xffffff );
}
Working:
var geometry = new THREE.BoxGeometry( 100, 100, 100 );
for ( var i = 0; i < geometry.faces.length; i ++ ) {
geometry.faces[ i ].color.setHex( Math.random() * 0xffffff );
}
BufferGeometries in three.js are fundamentally different from the regular Geometries. They are not oriented towards ease of manipulation but rather towards how meshes need to be delivered to the WebGL API.
That being said, there is no explicit notion of "faces" for BufferGeometries, they are implicit. A BufferGeometry consists of a number of attributes (for background see here), one of them is the position-attribute.
In a regular BufferGeometry (as opposed to "indexed"), the faces are stored as sequences of three vertices within that attribute (something like [x1, y1, z1, x2, y2, z2, x3, ...], so for the first face position[0] is the x-component of the first vertex and position[8] is the z-component of the third vertex). All other attributes use a similar indexing-scheme. If you define an attribute color for the geometry, you can control the face-colors by writing the same color-value at the positions of all three face-vertices (so in this example a color-attribute with [r, g, b, r, g, b, r, g, b, ...] would assign the same rgb-value to the three vertices of the first triangle).
Indexed geometries are different: Instead of repeating the vertices for all triangles, every vertex is stored only once. An additional attribute index is used to connect the vertices into triangles. So an index-attribute might look like this: [0, 1, 2, 0, 2, 3, ...] which reads as "construct first triangle from vertices at positions 0, 1 and 2" and so on.
As this is a very efficient way of storing geometries, this technique is used with most (maybe even all) of the builtin geometries in three.js.
With indexed geometries it is not possible to have colors per face-vertex because the vertex must have the same color everywhere it is used. You can however use bufferGeometry.toNonIndexed() to convert an indexed geometry into a regular one.
All necesarry here https://threejs.org/docs/index.html#api/en/core/BufferGeometry
See examples: Mesh with non-indexed faces, Mesh with indexed faces...
and i think that little example be more useful:
const geometry = new THREE.BoxBufferGeometry( 100, 100, 100 );
const colorsAttr = geometry.attributes.position.clone();
// Faces will be colored by vertex colors
geometry.setAttribute('color', colorsAttr);
const material = new THREE.MeshBasicMaterial({
vertexColors: THREE.VertexColors
});
const cube = new THREE.Mesh( geometry, material );
I have two cartesian coordinate systems with known unit vectors:
System A(x_A,y_A,z_A)
and
System B(x_B,y_B,z_B)
Both systems share the same origin (0,0,0). I'm trying to calculate a quaternion, so that vectors in system B can be expressed in system A.
I am familiar with the mathematical concept of quaternions. I have already implemented the required math from here: http://content.gpwiki.org/index.php/OpenGL%3aTutorials%3aUsing_Quaternions_to_represent_rotation
One possible solution could be to calculate Euler angles and use them for 3 quaternions. Multiplying them would lead to a final one, so that I could transform my vectors:
v(A) = q*v(B)*q_conj
But this would incorporate Gimbal Lock again, which was the reason NOT to use Euler angles in the beginning.
Any idead how to solve this?
You can calculate the quaternion representing the best possible transformation from one coordinate system to another by the method described in this paper:
Paul J. Besl and Neil D. McKay
"Method for registration of 3-D shapes", Sensor Fusion IV: Control Paradigms and Data Structures, 586 (April 30, 1992); http://dx.doi.org/10.1117/12.57955
The paper is not open access but I can show you the Python implementation:
def get_quaternion(lst1,lst2,matchlist=None):
if not matchlist:
matchlist=range(len(lst1))
M=np.matrix([[0,0,0],[0,0,0],[0,0,0]])
for i,coord1 in enumerate(lst1):
x=np.matrix(np.outer(coord1,lst2[matchlist[i]]))
M=M+x
N11=float(M[0][:,0]+M[1][:,1]+M[2][:,2])
N22=float(M[0][:,0]-M[1][:,1]-M[2][:,2])
N33=float(-M[0][:,0]+M[1][:,1]-M[2][:,2])
N44=float(-M[0][:,0]-M[1][:,1]+M[2][:,2])
N12=float(M[1][:,2]-M[2][:,1])
N13=float(M[2][:,0]-M[0][:,2])
N14=float(M[0][:,1]-M[1][:,0])
N21=float(N12)
N23=float(M[0][:,1]+M[1][:,0])
N24=float(M[2][:,0]+M[0][:,2])
N31=float(N13)
N32=float(N23)
N34=float(M[1][:,2]+M[2][:,1])
N41=float(N14)
N42=float(N24)
N43=float(N34)
N=np.matrix([[N11,N12,N13,N14],\
[N21,N22,N23,N24],\
[N31,N32,N33,N34],\
[N41,N42,N43,N44]])
values,vectors=np.linalg.eig(N)
w=list(values)
mw=max(w)
quat= vectors[:,w.index(mw)]
quat=np.array(quat).reshape(-1,).tolist()
return quat
This function returns the quaternion that you were looking for. The arguments lst1 and lst2 are lists of numpy.arrays where every array represents a 3D vector. If both lists are of length 3 (and contain orthogonal unit vectors), the quaternion should be the exact transformation. If you provide longer lists, you get the quaternion that is minimizing the difference between both point sets.
The optional matchlist argument is used to tell the function which point of lst2 should be transformed to which point in lst1. If no matchlist is provided, the function assumes that the first point in lst1 should match the first point in lst2 and so forth...
A similar function for sets of 3 Points in C++ is the following:
#include <Eigen/Dense>
#include <Eigen/Geometry>
using namespace Eigen;
/// Determine rotation quaternion from coordinate system 1 (vectors
/// x1, y1, z1) to coordinate system 2 (vectors x2, y2, z2)
Quaterniond QuaternionRot(Vector3d x1, Vector3d y1, Vector3d z1,
Vector3d x2, Vector3d y2, Vector3d z2) {
Matrix3d M = x1*x2.transpose() + y1*y2.transpose() + z1*z2.transpose();
Matrix4d N;
N << M(0,0)+M(1,1)+M(2,2) ,M(1,2)-M(2,1) , M(2,0)-M(0,2) , M(0,1)-M(1,0),
M(1,2)-M(2,1) ,M(0,0)-M(1,1)-M(2,2) , M(0,1)+M(1,0) , M(2,0)+M(0,2),
M(2,0)-M(0,2) ,M(0,1)+M(1,0) ,-M(0,0)+M(1,1)-M(2,2) , M(1,2)+M(2,1),
M(0,1)-M(1,0) ,M(2,0)+M(0,2) , M(1,2)+M(2,1) ,-M(0,0)-M(1,1)+M(2,2);
EigenSolver<Matrix4d> N_es(N);
Vector4d::Index maxIndex;
N_es.eigenvalues().real().maxCoeff(&maxIndex);
Vector4d ev_max = N_es.eigenvectors().col(maxIndex).real();
Quaterniond quat(ev_max(0), ev_max(1), ev_max(2), ev_max(3));
quat.normalize();
return quat;
}
What language are you using? If c++, feel free to use my open source library:
http://sourceforge.net/p/transengine/code/HEAD/tree/transQuaternion/
The short of it is, you'll need to convert your vectors to quaternions, do your calculations, and then convert your quaternion to a transformation matrix.
Here's a code snippet:
Quaternion from vector:
cQuat nTrans::quatFromVec( Vec vec ) {
float angle = vec.v[3];
float s_angle = sin( angle / 2);
float c_angle = cos( angle / 2);
return (cQuat( c_angle, vec.v[0]*s_angle, vec.v[1]*s_angle,
vec.v[2]*s_angle )).normalized();
}
And for the matrix from quaternion:
Matrix nTrans::matFromQuat( cQuat q ) {
Matrix t;
q = q.normalized();
t.M[0][0] = ( 1 - (2*q.y*q.y + 2*q.z*q.z) );
t.M[0][1] = ( 2*q.x*q.y + 2*q.w*q.z);
t.M[0][2] = ( 2*q.x*q.z - 2*q.w*q.y);
t.M[0][3] = 0;
t.M[1][0] = ( 2*q.x*q.y - 2*q.w*q.z);
t.M[1][1] = ( 1 - (2*q.x*q.x + 2*q.z*q.z) );
t.M[1][2] = ( 2*q.y*q.z + 2*q.w*q.x);
t.M[1][3] = 0;
t.M[2][0] = ( 2*q.x*q.z + 2*q.w*q.y);
t.M[2][1] = ( 2*q.y*q.z - 2*q.w*q.x);
t.M[2][2] = ( 1 - (2*q.x*q.x + 2*q.y*q.y) );
t.M[2][3] = 0;
t.M[3][0] = 0;
t.M[3][1] = 0;
t.M[3][2] = 0;
t.M[3][3] = 1;
return t;
}
I just ran into this same problem. I was on the track to a solution, but I got stuck.
So, you'll need TWO vectors which are known in both coordinate systems. In my case, I have 2 orthonormal vectors in the coordinate system of a device (gravity and magnetic field), and I want to find the quaternion to rotate from device coordinates to global orientation (where North is positive Y, and "up" is positive Z). So, in my case, I've measured the vectors in the device coordinate space, and I'm defining the vectors themselves to form the orthonormal basis for the global system.
With that said, consider the axis-angle interpretation of quaternions, there is some vector V about which the device's coordinates can be rotated by some angle to match the global coordinates. I'll call my (negative) gravity vector G, and magnetic field M (both are normalized).
V, G and M all describe points on the unit sphere.
So do Z_dev and Y_dev (the Z and Y bases for my device's coordinate system).
The goal is to find a rotation which maps G onto Z_dev and M onto Y_dev.
For V to rotate G onto Z_dev the distance between the points defined by G and V must be the same as the distance between the points defined by V and Z_dev. In equations:
|V - G| = |V - Z_dev|
The solution to this equation forms a plane (all points equidistant to G and Z_dev). But, V is constrained to be unit-length, which means the solution is a ring centered on the origin -- still an infinite number of points.
But, the same situation is true of Y_dev, M and V:
|V - M| = |V - Y_dev|
The solution to this is also a ring centered on the origin. These rings have two intersection points, where one is the negative of the other. Either is a valid axis of rotation (the angle of rotation will just be negative in one case).
Using the two equations above, and the fact that each of these vectors is unit length you should be able to solve for V.
Then you just have to find the angle to rotate by, which you should be able to do using the vectors going from V to your corresponding bases (G and Z_dev for me).
Ultimately, I got gummed up towards the end of the algebra in solving for V.. but either way, I think everything you need is here -- maybe you'll have better luck than I did.
Define 3x3 matrices A and B as you gave them, so the columns of A are x_A,x_B, and x_C and the columns of B are similarly defined. Then the transformation T taking coordinate system A to B is the solution TA = B, so T = BA^{-1}. From the rotation matrix T of the transformation you can calculate the quaternion using standard methods.
You need to express the orientation of B, with respect to A as a quaternion Q. Then any vector in B can be transformed to a vector in A e.g. by using a rotation matrix R derived from Q. vectorInA = R*vectorInB.
There is a demo script for doing this (including a nice visualization) in the Matlab/Octave library available on this site: http://simonbox.info/index.php/blog/86-rocket-news/92-quaternions-to-model-rotations
You can compute what you want using only quaternion algebra.
Given two unit vectors v1 and v2 you can directly embed them into quaternion algebra and get the corresponding pure quaternions q1 and q2. The rotation quaternion Q that align the two vectors such that:
Q q1 Q* = q2
is given by:
Q = q1 (q1 + q2)/(||q1 + q2||)
The above product is the quaternion product.
At first I figured you sum the vertices and scale by 1/3 to find the origin then take the largest distance from the vertex to the origin. This results in a sphere that contains the triangle, but it isn't necessarily the smallest.
Is there a known method for determining the smallest sphere to fully encapsulate an arbitrary triangle in 3D?
Used the answers here and wikipedia to come up with something in c++ that works for me, I hope this helps someone!
static Sphere makeMinimumBoundingSphere(const Vec3 &p1, const Vec3 &p2, const Vec3 &p3) {
Sphere s;
// Calculate relative distances
float A = (p1 - p2).distance();
float B = (p2 - p3).distance();
float C = (p3 - p1).distance();
// Re-orient triangle (make A longest side)
const Vec3 *a = &p3, *b = &p1, *c = &p2;
if (B < C) swap(B, C), swap(b, c);
if (A < B) swap(A, B), swap(a, b);
// If obtuse, just use longest diameter, otherwise circumscribe
if ((B*B) + (C*C) <= (A*A)) {
s.radius = A / 2.f;
s.position = (*b + *c) / 2.f;
} else {
// http://en.wikipedia.org/wiki/Circumscribed_circle
precision cos_a = (B*B + C*C - A*A) / (B*C*2);
s.radius = A / (sqrt(1 - cos_a*cos_a)*2.f);
Vec3 alpha = *a - *c, beta = *b - *c;
s.position = (beta * alpha.dot(alpha) - alpha * beta.dot(beta)).cross(alpha.cross(beta)) /
(alpha.cross(beta).dot(alpha.cross(beta)) * 2.f) + *c;
}
return s;
}
The smallest sphere to encapsulate the triangle is just the circumsribed cirlce extended into the third dimension.
Update: Scratch that, of course it isn't. It's the sphere that you get if you rotate the smallest circle around its diameter. The reason being that for any containing sphere that has its origin out of the plane of the triangle there is a smaller one that has its origin on the plane (by projecting the origin orthogonally onto the plane).
You are trying to find the smallest enclosing ball MB(P) of a point set P, so you could use an algorithm as implemented here https://github.com/hbf/miniball. (Note: "ball" and "sphere" are synonyms in this context.)
However, this is overkill in your case, since the point set P at hand contains exactly 3 points (the vertices of the triangle). In this particular case, you can use the fact that the smallest enclosing ball MB(P) of P={p,q,r} equals either:
B(p,q) if r is contained in B(p,q), or
B(p,r) if q is contained in B(p,r), or
B(q,r) if p is contained in B(q,r), or
B(p,q,r) otherwise.
Here, B(x,y) is the smallest ball containing the points x,y and B(x,y,z) is the smallest ball containing the points x,y,z on the boundary. B(x,y) and B(x,y,z) can be computed by solving a linear system of equations.
Note: I am the author of https://github.com/hbf/miniball.
Assuming that the sphere is simply a trivial extension of a circle (2-D) into 3-D (using both the same center point and the same radius), I believe what you are looking for is called circumscribed circle of a triangle.
Apparently I didn't consider the case of an obtuse triangle which if you have the vertices (points) of the triangle on the circle, then the circle is not the smallest bounding circle (and thus smallest bounding sphere).
Now I believe that you are looking for the minimum bounding sphere, which is a known and studied problem in mathematics, and computer graphics. "Smallest Enclosing Circle Problem" is a description of an O( n^{2} ) and a linear O(n) algorithms.
And as far as I know the minimal bounding circle does produce the minimal bounding sphere, using the same parameters (center point and radius) projected into three dimensions.
I am trying to determine whether a line segment (i.e. between two points) intersects a sphere. I am not interested in the position of the intersection, just whether or not the segment intersects the sphere surface. Does anyone have any suggestions as to what the most efficient algorithm for this would be? (I'm wondering if there are any algorithms that are simpler than the usual ray-sphere intersection algorithms, since I'm not interested in the intersection position)
If you are only interested if knowing if it intersects or not then your basic algorithm will look like this...
Consider you have the vector of your ray line, A -> B.
You know that the shortest distance between this vector and the centre of the sphere occurs at the intersection of your ray vector and a vector which is at 90 degrees to this which passes through the centre of the sphere.
You hence have two vectors, the equations of which fully completely defined. You can work out the intersection point of the vectors using linear algebra, and hence the length of the line (or more efficiently the square of the length of the line) and test if this is less than the radius (or the square of the radius) of your sphere.
I don't know what the standard way of doing it is, but if you only want to know IF it intersects, here is what I would do.
General rule ... avoid doing sqrt() or other costly operations. When possible, deal with the square of the radius.
Determine if the starting point is inside the radius of the sphere. If you know that this is never the case, then skip this step. If you are inside, your ray will intersect the sphere.
From here on, your starting point is outside the sphere.
Now, imagine the small box that will fit sphere. If you are outside that box, check the x-direction, y-direction and z-direction of the ray to see if it will intersect the side of the box that your ray starts at. This should be a simple sign check, or comparison against zero. If you are outside the and moving away from it, you will never intersect it.
From here on, you are in the more complicated phase. Your starting point is between the imaginary box and the sphere. You can get a simplified expression using calculus and geometry.
The gist of what you want to do is determine if the shortest distance between your ray and the sphere is less than radius of the sphere.
Let your ray be represented by (x0 + it, y0 + jt, z0 + kt), and the centre of your sphere be at (xS, yS, zS). So, we want to find t such that it would give the shortest of (xS - x0 - it, yS - y0 - jt, zS - z0 - kt).
Let x = xS - x0, y = yX - y0, z = zS - z0, D = magnitude of the vector squared
D = x^2 -2*xit + (i*t)^2 + y^2 - 2*yjt + (j*t)^2 + z^2 - 2*zkt + (k*t)^2
D = (i^2 + j^2 + k^2)t^2 - (xi + yj + zk)*2*t + (x^2 + y^2 + z^2)
dD/dt = 0 = 2*t*(i^2 + j^2 + k^2) - 2*(xi + yj + z*k)
t = (xi + yj + z*k) / (i^2 + j^2 + k^2)
Plug t back into the equation for D = .... If the result is less than or equal the square of the sphere's radius, you have an intersection. If it is greater, then there is no intersection.
This page has an exact solution for this problem. Essentially, you are substituting the equation for the line into the equation for the sphere, then computes the discriminant of the resulting quadratic. The values of the discriminant indicate intersection.
Are you still looking for an answer 13 years later? Here is a complete and simple solution
Assume the following:
the line segment is defined by endpoints as 3D vectors v1 and v2
the sphere is centered at vc with radius r
Ne define the three side lengths of a triangle ABC as:
A = v1-vc
B = v2-vc
C = v1-v2
If |A| < r or |B| < r, then we're done; the line segment intersects the sphere
After doing the check above, if the angle between A and B is acute, then we're done; the line segment does not intersect the sphere.
If neither of these conditions are met, then the line segment may or may not intersect the sphere. To find out, we just need to find H, which is the height of the triangle ABC taking C as the base. First we need φ, the angle between A and C:
φ = arccos( dot(A,C) / (|A||C|) )
and then solve for H:
sin(φ) = H/|A|
===> H = |A|sin(φ) = |A| sqrt(1 - (dot(A,C) / (|A||C|))^2)
and we are done. The result is
if H < r, then the line segment intersects the sphere
if H = r, then the line segment is tangent to the sphere
if H > r, then the line segment does not intersect the sphere
Here that is in Python:
import numpy as np
def unit_projection(v1, v2):
'''takes the dot product between v1, v2 after normalization'''
u1 = v1 / np.linalg.norm(v1)
u2 = v2 / np.linalg.norm(v2)
return np.dot(u1, u2)
def angle_between(v1, v2):
'''computes the angle between vectors v1 and v2'''
return np.arccos(np.clip(unit_projection(v1, v2), -1, 1))
def check_intersects_sphere(xa, ya, za, xb, yb, zb, xc, yc, zc, radius):
'''checks if a line segment intersects a sphere'''
v1 = np.array([xa, ya, za])
v2 = np.array([xb, yb, zb])
vc = np.array([xc, yc, zc])
A = v1 - vc
B = v2 - vc
C = v1 - v2
if(np.linalg.norm(A) < radius or np.linalg.norm(B) < radius):
return True
if(angle_between(A, B) < np.pi/2):
return False
H = np.linalg.norm(A) * np.sqrt(1 - unit_projection(A, C)**2)
if(H < radius):
return True
if(H >= radius):
return False
Note that I have written this so that it returns False when either endpoint is on the surface of the sphere, or when the line segment is tangent to the sphere, because it serves my purposes better.
This might be essentially what user Cruachan suggested. A comment there suggests that other answers are "too elaborate". There might be a more elegant way to implement this that uses more compact linear algebra operations and identities, but I suspect that the amount of actual compute required boils down to something like this. If someone sees somewhere to save some effort please do let us know.
Here is a test of the code. The figure below shows several trial line segments originating from a position (-1, 1, 1) , with a unit sphere at (1,1,1). Blue line segments have intersected, red have not.
And here is another figure which verifies that line segments that stop just short of the sphere's surface do not intersect, even if the infinite ray that they belong to does:
Here is the code that generates the image:
import matplotlib.pyplot as plt
radius = 1
xc, yc, zc = 1, 1, 1
xa, ya, za = xc-2, yc, zc
nx, ny, nz = 4, 4, 4
xx = np.linspace(xc-2, xc+2, nx)
yy = np.linspace(yc-2, yc+2, ny)
zz = np.linspace(zc-2, zc+2, nz)
n = nx * ny * nz
XX, YY, ZZ = np.meshgrid(xx, yy, zz)
xb, yb, zb = np.ravel(XX), np.ravel(YY), np.ravel(ZZ)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for i in range(n):
if(xb[i] == xa): continue
intersects = check_intersects_sphere(xa, ya, za, xb[i], yb[i], zb[i], xc, yc, zc, radius)
color = ['r', 'b'][int(intersects)]
s = [0.3, 0.7][int(intersects)]
ax.plot([xa, xb[i]], [ya, yb[i]], [za, zb[i]], '-o', color=color, ms=s, lw=s, alpha=s/0.7)
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
x = np.outer(np.cos(u), np.sin(v)) + xc
y = np.outer(np.sin(u), np.sin(v)) + yc
z = np.outer(np.ones(np.size(u)), np.cos(v)) + zc
ax.plot_surface(x, y, z, rstride=4, cstride=4, color='k', linewidth=0, alpha=0.25, zorder=0)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.tight_layout()
plt.show()
you sorta have to work that the position anyway if you want accuracy. The only way to improve speed algorithmically is to switch from ray-sphere intersection to ray-bounding-box intersection.
Or you could go deeper and try and improve sqrt and other inner function calls
http://wiki.cgsociety.org/index.php/Ray_Sphere_Intersection