Intersection between line and triangle in 3D - c++

I have a line and a triangle somewhere in 3D space. In other words, I have 3 points ([x,y,z] each) for the triangle, and two points (also [x,y,z]) for the line.
I need to figure out a way, hopefully using C++, to figure out if the line ever crosses the triangle. A line parallel to the triangle, and with more than one point in common, should be counted as "does not intersect".
I already made some code, but it doesn't work, and I always get false even when a visual representation clearly shows an intersection.
ofVec3f P1, P2;
P1 = ray.s;
P2 = ray.s + ray.t;
ofVec3f p1, p2, p3;
p1 = face.getVertex(0);
p2 = face.getVertex(1);
p3 = face.getVertex(2);
ofVec3f v1 = p1 - p2;
ofVec3f v2 = p3 - p2;
float a, b, c, d;
a = v1.y * v2.z - v1.z * v2.y;
b = -(v1.x * v2.z - v1.z * v2.x);
c = v1.x * v2.y - v1.y * v2.x;
d = -(a * p1.x + b * p1.y + c * p1.z);
ofVec3f O = P1;
ofVec3f V = P2 - P1;
float t;
t = -(a * O.x + b * O.y + c * O.z + d) / (a * V.x + b * V.y + c * V.z);
ofVec3f p = O + V * t;
float xmin = std::min(P1.x, P2.x);
float ymin = std::min(P1.y, P2.y);
float zmin = std::min(P1.z, P2.z);
float xmax = std::max(P1.x, P2.x);
float ymax = std::max(P1.y, P2.y);
float zmax = std::max(P1.z, P2.z);
if (inside(p, xmin, xmax, ymin, ymax, zmin, zmax)) {
*result = p.length();
return true;
}
return false;
And here is the definition of inside()
bool primitive3d::inside(ofVec3f p, float xmin, float xmax, float ymin, float ymax, float zmin, float zmax) const {
if (p.x >= xmin && p.x <= xmax && p.y >= ymin && p.y <= ymax && p.z >= zmin && p.z <= zmax)
return true;
return false;
}

1) If you just want to know whether the line intersects the triangle (without needing the actual intersection point):
Let p1,p2,p3 denote your triangle
Pick two points q1,q2 on the line very far away in both directions.
Let SignedVolume(a,b,c,d) denote the signed volume of the tetrahedron a,b,c,d.
If SignedVolume(q1,p1,p2,p3) and SignedVolume(q2,p1,p2,p3) have different signs AND
SignedVolume(q1,q2,p1,p2), SignedVolume(q1,q2,p2,p3) and SignedVolume(q1,q2,p3,p1) have the same sign, then there is an intersection.
SignedVolume(a,b,c,d) = (1.0/6.0)*dot(cross(b-a,c-a),d-a)
2) Now if you want the intersection, when the test in 1) passes
write the equation of the line in parametric form: p(t) = q1 + t*(q2-q1)
Write the equation of the plane: dot(p-p1,N) = 0 where
N = cross(p2-p1, p3-p1)
Inject p(t) into the equation of the plane: dot(q1 + t*(q2-q1) - p1, N) = 0
Expand: dot(q1-p1,N) + t dot(q2-q1,N) = 0
Deduce t = -dot(q1-p1,N)/dot(q2-q1,N)
The intersection point is q1 + t*(q2-q1)
3) A more efficient algorithm
We now study the algorithm in:
Möller and Trumbore, "Fast, Minimum Storage Ray-Triangle Intersection", Journal of Graphics Tools, vol. 2,‎ 1997, p. 21–28
(see also: https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm)
The algorithm is in the end simpler (less instructions than what we did in 1) and 2)), but sightly more complicated to understand. Let us derive it step by step.
Notation:
O = origin of the ray,
D = direction vector of the ray,
A,B,C = vertices of the triangle
An arbitrary point P on the ray can be written as P = O + tD
An arbitrary point P on the triangle can be written as P = A + uE1 + vE2 where E1 = B-A and E2 = C-A, u>=0, v>=0 and (u+v)<=1
Writing both expressions of P gives:
O + tD = A + uE1 + vE2
or:
uE1 + vE2 -tD = O-A
in matrix form:
[u]
[E1|E2|-D] [v] = O-A
[t]
(where [E1|E2|-D] is the 3x3 matrix with E1,E2,-D as its columns)
Using Cramer's formula for the solution of:
[a11 a12 a13][x1] [b1]
[a12 a22 a23][x2] = [b2]
[a31 a32 a33][x3] [b3]
gives:
|b1 a12 a13| |a11 a12 a13|
x1 = |b2 a22 a23| / |a21 a22 a23|
|b3 a32 a33| |a31 a32 a33|
|a11 b1 a13| |a11 a12 a13|
x2 = |a21 b2 a23| / |a21 a22 a23|
|a31 b3 a33| |a31 a32 a33|
|a11 a12 b1| |a11 a12 a13|
x3 = |a21 a22 b2| / |a21 a22 a23|
|a31 a32 b3| |a31 a32 a33|
Now we get:
u = (O-A,E2,-D) / (E1,E2,-D)
v = (E1,O-A,-D) / (E1,E2,-D)
t = (E1,E2,O-A) / (E1,E2,-D)
where (A,B,C) denotes the determinant of the 3x3 matrix with A,B,C as its column vectors.
Now we use the following identities:
(A,B,C) = dot(A,cross(B,C)) (develop the determinant w.r.t. first column)
(B,A,C) = -(A,B,C) (swapping two vectors changes the sign)
(B,C,A) = (A,B,C) (circular permutation does not change the sign)
Now we get:
u = -(E2,O-A,D) / (D,E1,E2)
v = (E1,O-A,D) / (D,E1,E2)
t = -(O-A,E1,E2) / (D,E1,E2)
Using:
N=cross(E1,E2);
AO = O-A;
DAO = cross(D,AO)
We obtain finally the following code (here in GLSL, easy to translate to other languages):
bool intersect_triangle(
in Ray R, in vec3 A, in vec3 B, in vec3 C, out float t,
out float u, out float v, out vec3 N
) {
vec3 E1 = B-A;
vec3 E2 = C-A;
N = cross(E1,E2);
float det = -dot(R.Dir, N);
float invdet = 1.0/det;
vec3 AO = R.Origin - A;
vec3 DAO = cross(AO, R.Dir);
u = dot(E2,DAO) * invdet;
v = -dot(E1,DAO) * invdet;
t = dot(AO,N) * invdet;
return (det >= 1e-6 && t >= 0.0 && u >= 0.0 && v >= 0.0 && (u+v) <= 1.0);
}
When the function returns true, the intersection point is given by R.Origin + t * R.Dir. The barycentric coordinates of the intersection in the triangle are u, v, 1-u-v (useful for Gouraud shading or texture mapping). The nice thing is that you get them for free !
Note that the code is branchless.
It is used by some of my shaders on ShaderToy
https://www.shadertoy.com/view/tl3XRN
https://www.shadertoy.com/view/3ltSzM

#BrunoLevi: your algorithm does not seem to work, see the following python implementation:
def intersect_line_triangle(q1,q2,p1,p2,p3):
def signed_tetra_volume(a,b,c,d):
return np.sign(np.dot(np.cross(b-a,c-a),d-a)/6.0)
s1 = signed_tetra_volume(q1,p1,p2,p3)
s2 = signed_tetra_volume(q2,p1,p2,p3)
if s1 != s2:
s3 = signed_tetra_volume(q1,q2,p1,p2)
s4 = signed_tetra_volume(q1,q2,p2,p3)
s5 = signed_tetra_volume(q1,q2,p3,p1)
if s3 == s4 and s4 == s5:
n = np.cross(p2-p1,p3-p1)
t = -np.dot(q1,n-p1) / np.dot(q1,q2-q1)
return q1 + t * (q2-q1)
return None
My test code is:
q0 = np.array([0.0,0.0,1.0])
q1 = np.array([0.0,0.0,-1.0])
p0 = np.array([-1.0,-1.0,0.0])
p1 = np.array([1.0,-1.0,0.0])
p2 = np.array([0.0,1.0,0.0])
print(intersect_line_triangle(q0,q1,p0,p1,p2))
gives:
[ 0. 0. -3.]
instead of the expected
[ 0. 0. 0.]
looking at the line
t = np.dot(q1,n-p1) / np.dot(q1,q2-q1)
Subtracting p1 from the normal doesn't make sense to me, you want to project from q1 onto the plane of the triangle, so you need to project along the normal, with a distance that is proportional to the ratio of the distance from q1 to the plane and q1-q2 along the normal, right?
The following code fixes this:
n = np.cross(p2-p1,p3-p1)
t = np.dot(p1-q1,n) / np.dot(q2-q1,n)
return q1 + t * (q2-q1)

To find the intersection between a line and a triangle in 3D, follow this approach:
Compute the plane supporting the triangle,
Intersect the line with the plane supporting the triangle:
If there is no intersection, then there is no intersection with the triangle.
If there is an intersection, verify that the intersection point indeed lies in the triangle:
Each edge of the triangle together with the normal of the plane supporting the triangle determines a half-space bounding the inside of the triangle (the corresponding bounding plane can be derived from the normal and the edge vertices),
Verify that the intersection point lies on the inside of all the edge half-spaces.
Here is some sample code with detailed computations that should work:
// Compute the plane supporting the triangle (p1, p2, p3)
// normal: n
// offset: d
//
// A point P lies on the supporting plane iff n.dot(P) + d = 0
//
ofVec3f v21 = p2 - p1;
ofVec3f v31 = p3 - p1;
ofVec3f n = v21.getCrossed(v31);
float d = -n.dot(p1);
// A point P belongs to the line from P1 to P2 iff
// P = P1 + t * (P2 - P1)
//
// Find the intersection point P(t) between the line and
// the plane supporting the triangle:
// n.dot(P) + d = 0
// = n.dot(P1 + t (P2 - P1)) + d
// = n.dot(P1) + t n.dot(P2 - P1) + d
//
// t = -(n.dot(P1) + d) / n.dot(P2 - P1)
//
ofVec3f P21 = P2 - P1;
float nDotP21 = n.dot(P21);
// Ignore line parallel to (or lying in) the plane
if (fabs(nDotP21) < Epsilon)
return false;
float t = -(n.dot(P1) + d) / nDotP21;
ofVec3f P = P1 + t * P21;
// Plane bounding the inside half-space of edge (p1, p2):
// normal: n21 = n x (p2 - p1)
// offset: d21 = -n21.dot(p1)
//
// A point P is in the inside half-space iff n21.dot(P) + d21 > 0
//
// Edge (p1, p2)
ofVec3f n21 = n.cross(v21);
float d21 = -n21.dot(p1);
if (n21.dot(P) + d21 <= 0)
return false;
// Edge (p2, p3)
ofVec3f v32 = p3 - p2;
ofVec3f n32 = n.cross(v32);
float d32 = -n32.dot(p2);
if (n32.dot(P) + d32 <= 0)
return false;
// Edge (p3, p1)
ofVec3f n13 = n.cross(-v31);
float d13 = -n13.dot(p3);
if (n13.dot(P) + d13 <= 0)
return false;
return true;
Some comments on the code posted with the question:
Predefined operations of ofVec3f (.dot() and .cross() for geometric products, etc...) should be preferred when available (more readable, avoids implementation mistakes, etc...),
The code initially follows the approach above but then only checks that the intersection point is in the 3D axis-aligned bounding box of the line segment [P1, P2]. This combined with possible other errorscould explain why the results are incorrect.
One can verify that the intersection point is in the 3D axis-aligned bounding box of the (whole) triangle. While this is not enough to guarantee intersection, it can however be used to cull points clearly not intersecting and avoid further complex computations.

I have a different way to do it which I found in my renderer to be far faster than the first way given by BrunoLevy. (I haven't implemented the second way)
Points A, B, C are vertexes of the triangle
O is the origin of the ray
D is the direction of the ray (doesn't need to be normalised, just closer to the origin than the triangle)
Check if the direction (D+O) is inside the tetrahedron A, B, C, O
bool SameSide(vec3 A, vec3 B, vec3 C, vec3 D, vec3 p)
{
vec3 normal = cross(B - A, C - A);
float dotD = dot(normal, D - A);
float dotP = dot(normal, p - A);
return signbit(dotD) == signbit(dotP);
}
bool LineIntersectTri(vec3 A, vec3 B, vec3 C, vec3 O, vec3 D)
{
return SameSide(A, B, C, O, O+D) &&
SameSide(B, C, O, A, O+D) &&
SameSide(C, O, A, B, O+D) &&
SameSide(O, A, B, C, O+D);
}
If D varies, and everything else stays the same (for example in a raycasting renderer) then normal and dotP don't need to be recalculated; This is why I found it so much faster
The code came from this answer https://stackoverflow.com/a/25180294/18244401

Related

Ray-bounded plane intersection

I'm trying to write a ray tracer in my freetime. Currently trying to do ray - bounded plane intersections.
My program is already working with infinite planes. I'm trying to work out the math for non-infinite planes. Tried to google, but all of the resources talk only about infinite planes.
My plane has a corner point (called position), from which two vectors (u and v) extend (their length correspond to the length of the sides). The ray has an origin and a direction.
First I calculate the intersection point with an infinite plane with the formula
t = normal * (position - origin) / (normal * direction)
The normal is calculated as a cross product of u and v.
Then with the formula
origin + direction * t
I get the intersection point itself.
The next step is checking if this point is in the bounds of the rectangle, and this is where I'm having trouble.
My idea was to get the relative vector intersection - position that is extending from the corner of the plane to the intersection point, then transform it to a new basis of u, normal and v then check if the lengths of the transformed vectors are shorter than the u and v vectors.
bool BoundedPlane::intersect(const Vec3f &origin, const Vec3f &direction, float &t) const {
t = normal * (position - origin) / (normal * direction);
Vec3f relative = (origin + direction * t) - position;
Mat3f transform{
Vec3f(u.x, normal.x, v.x),
Vec3f(u.y, normal.y, v.y),
Vec3f(u.z, normal.z, v.z)
};
Vec3f local = transform.mul(relative);
return t > 0 && local.x >= 0 && local.x <= u.x && local.z <= 0 && local.z <= v.z;
}
At the end I check if t is larger than 0, meaning the intersection is in front of the camera, and if the lengths of the vectors are inside the bounds. This gives me a weird line:
.
The plane should appear below the spheres like this:
(this used manual checking to see if it appears correctly if the numbers are right).
I'm not sure what I'm doing wrong, and if there's an easier way to check the bounds. Thanks in advance.
Edit1:
I moved the transformation matrix calculations into the constructor, so now the intersection test is:
bool BoundedPlane::intersect(const Vec3f &origin, const Vec3f &direction, float &t) const {
if (!InfinitePlane::intersect(origin, direction, t)) {
return false;
}
Vec3f local = transform.mul((origin + direction * t) - position);
return local.x >= 0 && local.x <= 1 && local.z >= 0 && local.z <= 1;
}
The transform member is the inverse of the transformation matrix.
Could I suggest another approach? Consider the frame with origin
position and basis vectors
u = { u.x, u.y, u.z }
v = { v.x, v.y, v.z }
direction = { direction.x, direction.y, direction.z}
Step 1: Form the matrix
M = {
{u.x, v.x, direction.x},
{u.y, v.y, direction.y},
{u.z, v.z, direction.z}
}
Step 2: Calculate the vector w, which is a solution to the 3 x 3 system of liner equations
M * w = origin - position, i.e.
w = inverse(M) * (origin - position);
Make sure that direction is not coplanar with u, v, otherwise there is no intersection and inverse(M) does not exist.
Step 3: if 0.0 <= w.x && w.x <= 1.0 && 0.0 <= w.y && w.y <= 1.0 then the line intersects the parallelogram spanned by the vectors u, v and the point of intersection is
w0 = { w.x, w.y , 0 };
intersection = position + M * w0;
else, the line does not intersect the parallelogram spanned by the vectors u, v
The idea of this algorithm is to consider the (non-orthonormal) frame position, u, v, direction. Then the matrix M changes everything in the coordinates of this new frame. In this frame, the line is vertical, parallel to the "z-"axis, the point origin has coordinates w, and the vertical line through w intersects the plane at w0.
Edit 1: Here is a templet formula for the inverse of a 3x3 matrix:
If original matrix M is
a b c
d e f
g h i
inverse is
(1 / det(M)) * {
{e*i - f*h, c*h - b*i, b*f - c*e},
{f*g - d*i, a*i - c*g, c*d - a*f},
{d*h - e*g, b*g - a*h, a*e - b*d},
}
where
det(M) = a*(e*i - f*h) + b*(f*g - d*i) + c*(d*h - e*h)
is the determinant of M.
So the inversion algorithm can be as follows:
Given
M = {
{a, b, c},
{d, e, f},
{g, h, i},
}
Calculate
inv_M = {
{e*i - f*h, c*h - b*i, b*f - c*e},
{f*g - d*i, a*i - c*g, c*d - a*f},
{d*h - e*g, b*g - a*h, a*e - b*d},
};
Calculate
det_M = a*inv_M[1][1] + b*inv_M[2][1] + c*inv_M[3][1];
Return inverse matrix of M
inv_M = (1/det_M) * inv_M;
Edit 2: Let's try another approach in order to speed things up.
Step 1: For each plane, determined by the point position and the two vectors u and v, precompute the following quatntities:
normal = cross(u, v);
u_dot_u = dot(u, u);
u_dot_v = dot(u, v);
v_dot_v = dot(v, v); // all these need to be computed only once for the u and v vectors
det = u_dot_u * v_dot_v - u_dot_v * u_dot_v; // again only once per u and v
Step 2: Now, for a given line with point origin and direction direction, as before, calculate the intersection point int_point with the plane spanned by u and v:
t = dot(normal, position - origin) / dot(normal, direction);
int_point = origin + t * direction;
rhs = int_point - position;
Step 3: Calcualte
u_dot_rhs = dot(u, rhs);
v_dot_rhs = dot(v, rhs);
w1 = (v_dot_v * u_dot_rhs - u_dot_v * v_dot_rhs) / det;
w2 = (- u_dot_v * u_dot_rhs + u_dot_u * v_dot_rhs) / det;
Step 4:
if (0 < = w1 && w1 <= 1 && 0 < = w2 && w2 <= 1 ){
int_point is in the parallelogram;
}
else{
int_point is not in the parallelogram;
}
So what I am doing here is basically finding the intersection point of the line origin, direction with the plane given by position, u, v and restricting myself to the plane, which allows me to work in 2D rather than 3D. I am representing
int_point = position + w1 * u + w2 * v;
rhs = int_point - position = w1 * u + w2 * v
and finding w1 and w2 by dot-multiplying of this vector expression with the basis vectors u and v, which results in a 2x2 linear system, which I am solving directly.

Computing the side planes of a 3D AABB

I have a 3D AABB defined by two sets of points a min/max.
I'd like to define the 6 planes that make up the sides of the AABB, such that any point that is within the AABB will have a positive signed-distance.
My plane definition is comprised of a normal (x,y,z) and a constant D, Corresponding to the Ax + By +Cz + D = 0 form plane equation.
struct myplane {
double nx,ny,nz;
double D;
};
Note: nx,ny, and nz are normalized.
The AABB struct is as follows:
struct myAABB {
point3d min;
point3d max;
};
I'm currently defining instances of the AABB sides like so:
myplane p0 = myplane{-1.0f, 0.0f, 0.0f,aabb.max.x);
myplane p1 = myplane{ 0.0f,-1.0f, 0.0f,aabb.max.y);
myplane p2 = myplane{ 0.0f, 0.0f,-1.0f,aabb.max.z);
myplane p3 = myplane{+1.0f, 0.0f, 0.0f,aabb.min.x);
myplane p4 = myplane{ 0.0f,+1.0f, 0.0f,aabb.min.y);
myplane p5 = myplane{ 0.0f, 0.0f,+1.0f,aabb.min.z);
where aabb is in this case is: min(-1,-1,-1) max(1,1,1)
The problem is that points in the AABB return a positive distance for the planes p0,p1 and p2, however not so for planes p3,p4 and p5, as they return negative distances which seem to indicate the points are on the other side.
For example the origin point (0,0,0) should return a positive distance of 1 for each of the planes however does not for planes p3,p4 and p5.
The signed-distance calculation being used is:
double distance(myplane& p, const point3d& v)
{
// p.normal dot v + D
return (p.nx * v.x) + (p.ny * v.y) + (p.nz * v.z) + p.D;
}
I think my equations are wrong in some way, but I can't seem to figure it out.
Signed distance from point to plane according to Chapter 2.3 of Mathematical Handbook (Korn, Korn) is
Delta = (Normal. dot. v + D) / (-Sign(D) * NormalLength)
but you don't account for D sign. Just modify function:
dt = (p.nx * v.x) + (p.ny * v.y) + (p.nz * v.z) + p.D;
return (p.D < 0) ? dt: -dt;
Xm < X < XM
is equivalent to
1.X + 0.Y + 0.Z - Xm > 0 and - 1.X + 0.Y + 0.Z + XM > 0

Create a 3D sphere and 3D box C++

I need to implement a tool to detect intersction between a 3D box and 3D Sphere in c++. Write now I find a way how to detect the intersection using that code.
inline float squared(float v) { return v * v; }
bool doesCubeIntersectSphere(vec3 C1, vec3 C2, vec3 S, float R)
{
float dist_squared = R * R;
/* assume C1 and C2 are element-wise sorted, if not, do that now */
if (S.X < C1.X) dist_squared -= squared(S.X - C1.X);
else if (S.X > C2.X) dist_squared -= squared(S.X - C2.X);
if (S.Y < C1.Y) dist_squared -= squared(S.Y - C1.Y);
else if (S.Y > C2.Y) dist_squared -= squared(S.Y - C2.Y);
if (S.Z < C1.Z) dist_squared -= squared(S.Z - C1.Z);
else if (S.Z > C2.Z) dist_squared -= squared(S.Z - C2.Z);
return dist_squared > 0;
}
What I need is an example of C++ code to create a 3D sphere using origin vector and a radius and
I need to create a 3D sphere using origin vector and a radius and a 3D box through its maximum and minimum corner vector.
I may be mistaken, but (assuming Axis-Aligned boxes):
The length of a vector from origin to corner C1 or C2 should be the radius r, right?
Explanation for my deriviation below: An Axis-Aligned-box with equal distance from center to all corners is a perfect cube. Translating such a cube to the origin puts two of the corners exactly at the line from the origin through the point {x=1,y=1,z=1}. Thus those two corners will have coordinates {d,d,d} and {-d, -d, -d}, where d is "distance" of the corner along the axises X,Y,Z. The distance to say the first corner is squaring and adding all components of the vector, and taking the square root, e.g:
|C1| = |{d,d,d}| = sqrt(d * d + d * d + d * d) = sqrt(3 * d * d)
Therefore solve:
r = sqrt(3 *d * d)
<=>
r * r = 3 * d * d
<=>
d = sqrt(r*r/3)
<=>
d = r/sqrt(3)
This needs to be translated back to the center of the Sphere, thus:
C1 = { S.x+d, S.y+d, S.z+d}
C2 = { S.x-d, S.y-d, S.z-d}
Your explanation is a little vague, so I made some assumptions. Perhaps I'm dead wrong. Anyway here is some non-tested code showing what I mean:
void makeCube(vec3 S, float R, vec3* C1, vec3* C2)
{
static const float sqrt_one_third = sqrtf(1.0f/3.0f);
float d = R * sqrt_one_third;
C1->X = S.X + d;
C1->Y = S.Y + d;
C1->Z = S.Z + d;
C2->X = S.X - d;
C2->Y = S.Y - d;
C2->Z = S.Z - d;
}

How to do ray plane intersection?

How do I calculate the intersection between a ray and a plane?
Code
This produces the wrong results.
float denom = normal.dot(ray.direction);
if (denom > 0)
{
float t = -((center - ray.origin).dot(normal)) / denom;
if (t >= 0)
{
rec.tHit = t;
rec.anyHit = true;
computeSurfaceHitFields(ray, rec);
return true;
}
}
Parameters
ray represents the ray object.
ray.direction is the direction vector.
ray.origin is the origin vector.
rec represents the result object.
rec.tHit is the value of the hit.
rec.anyHit is a boolean.
My function has access to the plane:
center and normal defines the plane
As wonce commented, you want to also allow the denominator to be negative, otherwise you will miss intersections with the front face of your plane. However, you still want a test to avoid a division by zero, which would indicate the ray being parallel to the plane. You also have a superfluous negation in your computation of t. Overall, it should look like this:
float denom = normal.dot(ray.direction);
if (abs(denom) > 0.0001f) // your favorite epsilon
{
float t = (center - ray.origin).dot(normal) / denom;
if (t >= 0) return true; // you might want to allow an epsilon here too
}
return false;
First consider the math of the ray-plane intersection:
In general one intersects the parametric form of the ray, with the implicit form of the geometry.
So given a ray of the form x = a * t + a0, y = b * t + b0, z = c * t + c0;
and a plane of the form: A x * B y * C z + D = 0;
now substitute the x, y and z ray equations into the plane equation and you will get a polynomial in t. you then solve that polynomial for the real values of t. With those values of t you can back substitute into the ray equation to get the real values of x, y and z.
Here it is in Maxima:
Note that the answer looks like the quotient of two dot products!
The normal to a plane is the first three coefficients of the plane equation A, B, and C.
You still need D to uniquely determine the plane.
Then you code that up in the language of your choice like so:
Point3D intersectRayPlane(Ray ray, Plane plane)
{
Point3D point3D;
// Do the dot products and find t > epsilon that provides intersection.
return (point3D);
}
Math
Define:
Let the ray be given parametrically by q = p + t*v for initial point p and direction vector v for t >= 0.
Let the plane be the set of points r satisfying the equation dot(n, r) + d = 0 for normal vector n = (a, b, c) and constant d. Fully expanded, the plane equation may also be written in the familiar form ax + by + cz + d = 0.
The ray-plane intersection occurs when q satisfies the plane equation. Substituting, we have:
d = -dot(n, q)
= -dot(n, p + t * v)
= -dot(n, p) + t * dot(n, v)
Rearranging:
t = -(dot(n, p) + d) / dot(n, v)
This value of t can be used to determine the intersection by plugging it back into p + t*v.
Example implementation
std::optional<vec3> intersectRayWithPlane(
vec3 p, vec3 v, // ray
vec3 n, float d // plane
) {
float denom = dot(n, v);
// Prevent divide by zero:
if (abs(denom) <= 1e-4f)
return std::nullopt;
// If you want to ensure the ray reflects off only
// the "top" half of the plane, use this instead:
//
// if (-denom <= 1e-4f)
// return std::nullopt;
float t = -(dot(n, p) + d) / dot(n, v);
// Use pointy end of the ray.
// It is technically correct to compare t < 0,
// but that may be undesirable in a raytracer.
if (t <= 1e-4)
return std::nullopt;
return p + t * v;
}
implementation of vwvan's answer
Vector3 Intersect(Vector3 planeP, Vector3 planeN, Vector3 rayP, Vector3 rayD)
{
var d = Vector3.Dot(planeP, -planeN);
var t = -(d + Vector3.Dot(rayP, planeN)) / Vector3.Dot(rayD, planeN);
return rayP + t * rayD;
}

Rotate a vector about another vector

I am writing a 3d vector class for OpenGL. How do I rotate a vector v1 about another vector v2 by an angle A?
You may find quaternions to be a more elegant and efficient solution.
After seeing this answer bumped recently, I though I'd provide a more robust answer. One that can be used without necessarily understanding the full mathematical implications of quaternions. I'm going to assume (given the C++ tag) that you have something like a Vector3 class with 'obvious' functions like inner, cross, and *= scalar operators, etc...
#include <cfloat>
#include <cmath>
...
void make_quat (float quat[4], const Vector3 & v2, float angle)
{
// BTW: there's no reason you can't use 'doubles' for angle, etc.
// there's not much point in applying a rotation outside of [-PI, +PI];
// as that covers the practical 2.PI range.
// any time graphics / floating point overlap, we have to think hard
// about degenerate cases that can arise quite naturally (think of
// pathological cancellation errors that are *possible* in seemingly
// benign operations like inner products - and other running sums).
Vector3 axis (v2);
float rl = sqrt(inner(axis, axis));
if (rl < FLT_EPSILON) // we'll handle this as no rotation:
{
quat[0] = 0.0, quat[1] = 0.0, quat[2] = 0.0, quat[3] = 1.0;
return; // the 'identity' unit quaternion.
}
float ca = cos(angle);
// we know a maths library is never going to yield a value outside
// of [-1.0, +1.0] right? Well, maybe we're using something else -
// like an approximating polynomial, or a faster hack that's a little
// rough 'around the edge' cases? let's *ensure* a clamped range:
ca = (ca < -1.0f) ? -1.0f : ((ca > +1.0f) ? +1.0f : ca);
// now we find cos / sin of a half-angle. we can use a faster identity
// for this, secure in the knowledge that 'sqrt' will be valid....
float cq = sqrt((1.0f + ca) / 2.0f); // cos(acos(ca) / 2.0);
float sq = sqrt((1.0f - ca) / 2.0f); // sin(acos(ca) / 2.0);
axis *= sq / rl; // i.e., scaling each element, and finally:
quat[0] = axis[0], quat[1] = axis[1], quat[2] = axis[2], quat[3] = cq;
}
Thus float quat[4] holds a unit quaternion that represents the axis and angle of rotation, given the original arguments (, v2, A).
Here's a routine for quaternion multiplication. SSE/SIMD can probably speed this up, but complicated transform & lighting are typically GPU-driven in most scenarios. If you remember complex number multiplication as a little weird, quaternion multiplication is more so. Complex number multiplication is a commutative operation: a*b = b*a. Quaternions don't even preserve this property, i.e., q*p != p*q :
static inline void
qmul (float r[4], const float q[4], const float p[4])
{
// quaternion multiplication: r = q * p
float w0 = q[3], w1 = p[3];
float x0 = q[0], x1 = p[0];
float y0 = q[1], y1 = p[1];
float z0 = q[2], z1 = p[2];
r[3] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1;
r[0] = w0 * x1 + x0 * w1 + y0 * z1 - z0 * y1;
r[1] = w0 * y1 + y0 * w1 + z0 * x1 - x0 * z1;
r[2] = w0 * z1 + z0 * w1 + x0 * y1 - y0 * x1;
}
Finally, rotating a 3D 'vector' v (or if you prefer, the 'point' v that the question has named v1, represented as a vector), using the quaternion: float q[4] has a somewhat strange formula: v' = q * v * conjugate(q). Quaternions have conjugates, similar to complex numbers. Here's the routine:
static inline void
qrot (float v[3], const float q[4])
{
// 3D vector rotation: v = q * v * conj(q)
float r[4], p[4];
r[0] = + v[0], r[1] = + v[1], r[2] = + v[2], r[3] = +0.0;
glView__qmul(r, q, r);
p[0] = - q[0], p[1] = - q[1], p[2] = - q[2], p[3] = q[3];
glView__qmul(r, r, p);
v[0] = r[0], v[1] = r[1], v[2] = r[2];
}
Putting it all together. Obviously you can make use of the static keyword where appropriate. Modern optimising compilers may ignore the inline hint depending on their own code generation heuristics. But let's just concentrate on correctness for now:
How do I rotate a vector v1 about another vector v2 by an angle A?
Assuming some sort of Vector3 class, and (A) in radians, we want the quaternion representing the rotation by the angle (A) about the axis v2, and we want to apply that quaternion rotation to v1 for the result:
float q[4]; // we want to find the unit quaternion for `v2` and `A`...
make_quat(q, v2, A);
// what about `v1`? can we access elements with `operator [] (int)` (?)
// if so, let's assume the memory: `v1[0] .. v1[2]` is contiguous.
// you can figure out how you want to store and manage your Vector3 class.
qrot(& v1[0], q);
// `v1` has been rotated by `(A)` radians about the direction vector `v2` ...
Is this the sort of thing that folks would like to see expanded upon in the Beta Documentation site? I'm not altogether clear on its requirements, expected rigour, etc.
This may prove useful:
double c = cos(A);
double s = sin(A);
double C = 1.0 - c;
double Q[3][3];
Q[0][0] = v2[0] * v2[0] * C + c;
Q[0][1] = v2[1] * v2[0] * C + v2[2] * s;
Q[0][2] = v2[2] * v2[0] * C - v2[1] * s;
Q[1][0] = v2[1] * v2[0] * C - v2[2] * s;
Q[1][1] = v2[1] * v2[1] * C + c;
Q[1][2] = v2[2] * v2[1] * C + v2[0] * s;
Q[2][0] = v2[0] * v2[2] * C + v2[1] * s;
Q[2][1] = v2[2] * v2[1] * C - v2[0] * s;
Q[2][2] = v2[2] * v2[2] * C + c;
v1[0] = v1[0] * Q[0][0] + v1[0] * Q[0][1] + v1[0] * Q[0][2];
v1[1] = v1[1] * Q[1][0] + v1[1] * Q[1][1] + v1[1] * Q[1][2];
v1[2] = v1[2] * Q[2][0] + v1[2] * Q[2][1] + v1[2] * Q[2][2];
Use a 3D rotation matrix.
The easiest-to-understand way would be rotating the coordinate axis so that vector v2 aligns with the Z axis, then rotate by A around the Z axis, and rotate back so that the Z axis aligns with v2.
When you have written down the rotation matrices for the three operations, you'll probably notice that you apply three matrices after each other. To reach the same effect, you can multiply the three matrices.
I found this here:
http://steve.hollasch.net/cgindex/math/rotvec.html
let
[v] = [vx, vy, vz] the vector to be rotated.
[l] = [lx, ly, lz] the vector about rotation
| 1 0 0|
[i] = | 0 1 0| the identity matrix
| 0 0 1|
| 0 lz -ly |
[L] = | -lz 0 lx |
| ly -lx 0 |
d = sqrt(lx*lx + ly*ly + lz*lz)
a the angle of rotation
then
matrix operations gives:
[v] = [v]x{[i] + sin(a)/d*[L] + ((1 - cos(a))/(d*d)*([L]x[L]))}
I wrote my own Matrix3 class and Vector3Library that implemented this vector rotation. It works absolutely perfectly. I use it to avoid drawing models outside the field of view of the camera.
I suppose this is the "use a 3d rotation matrix" approach. I took a quick look at quaternions, but have never used them, so stuck to something I could wrap my head around.