After advice from krlzlx I have posted it as a new question.
From here:
3D Line Segment and Plane Intersection
I have a problem with this algorithm, I have implemented it like so:
template <class T>
class AnyCollision {
public:
std::pair<bool, T> operator()(Point3d &ray, Point3d &rayOrigin, Point3d &normal, Point3d &coord) const {
// get d value
float d = (normal.x * coord.x) + (normal.y * coord.y) + (normal.z * coord.z);
if (((normal.x * ray.x) + (normal.y * ray.y) + (normal.z * ray.z)) == 0) {
return std::make_pair(false, T());
}
// Compute the X value for the directed line ray intersecting the plane
float a = (d - ((normal.x * rayOrigin.x) + (normal.y * rayOrigin.y) + (normal.z * rayOrigin.z)) / ((normal.x * ray.x) + (normal.y * ray.y) + (normal.z * ray.z)));
// output contact point
float rayMagnitude = (sqrt(pow(ray.x, 2) + pow(ray.y, 2) + pow(ray.z, 2)));
Point3d rayNormalised((ray.x / rayMagnitude), (ray.y / rayMagnitude), (ray.z / rayMagnitude));
Point3d contact((rayOrigin.x + (rayNormalised.x * a)), (rayOrigin.y + (rayNormalised.y * a)), (rayOrigin.z + (rayNormalised.z * a))); //Make sure the ray vector is normalized
return std::make_pair(true, contact);
};
Point3d is defined as:
class Point3d {
public:
double x;
double y;
double z;
/**
* constructor
*
* 0 all elements
*/
Point3d() {
x = 0.0;
y = 0.0;
z = 0.0;
}
I am forced to use this structure, because in the larger system my component runs in it is defined like this and it cannot be changed.
My code compiles fine, but testing I get incorrect values for the point. The ratio of x, y, z is correct but the magnitude is wrong.
For example if:
rayOrigin.x = 0;
rayOrigin.y = 0;
rayOrigin.z = 0;
ray.x = 3;
ray.y = -5;
ray.z = 12;
normal.x = -3;
normal.y = 12;
normal.z = 0;
coord.x = 7;
coord.y = -5;
coord.z = 10;
I expect the point to be:
(0.63, 1.26, 1.89)
However, it is:
(3.52, -5.87, 14.09)
A magnitude of 5.09 too big.
And I also tested:
rayOrigin.x = 0;
rayOrigin.y = 0;
rayOrigin.z = 0;
ray.x = 2;
ray.y = 3;
ray.z = 3;
normal.x = 4;
normal.y = 1;
normal.z = 0;
p0.x = 2;
p0.y = 1;
p0.z = 5;
I expect the point to be:
(1.64, 2.45, 2.45)
However, it is:
(3.83761, 5.75642, 5.75642)
A magnitude of 2.34 too big?
Pseudocode (does not require vector normalization):
Diff = PlaneBaseCoordinate - RayOrigin
d = Normal.dot.Diff
e = Normal.dot.RayVector
if (e)
IntersectionPoint = RayOrigin + RayVector * d / e
otherwise
ray belongs to the plane or is parallel
Quick check:
Ray (0,0,0) (2,2,2) //to catch possible scale issues
Plane (0,1,0) (0,3,0) //plane y=1
Diff = (0,1,0)
d = 3
e = 6
IntersectionPoint = (0,0,0) + (2,2,2) * 3 / 6 = (1, 1, 1)
There is a sphere fold function, which transforms space based on the distance from origin. There are two constant parameters: fR2 and mR2. The function looks like this:
const float fR2 = 1.0;
const float mR2 = 0.25;
vec3 s_fold(in vec3 v) {
float mag = dot(v, v);
if (mag < mR2)
{
v = v * fR2 / mR2;
}
else if (mag < fR2)
{
v = v * fR2 / mag;
}
return v;
}
It works, but because of the if-else branch, it runs slowly. It is possible to get rid of the branching, if I use the step function:
float a = step(mR2 , mag);
float b = step(fR2 , mag);
float sc = dot( vec3(
1.0-a,
a*(1.0-b),
b
), vec3(
fR2 / mR2,
fR2 / mag,
1.0)
);
return sc*v;
It is now a bit faster, but I would like to optimize it further. I've found a one-line solution, but it gives me a different result:
return v*clamp(max(mR2/mag,mR2),0.0,fR2);
It is unclear to me, how is it possible to calculate it with a single clamp.
Your if-else branch is a function like res= v * F where F is one of three possible values: a constant (fR2/mR2), a (hyperbolic) variable (fR2/mag), and a constant again (1.0).
clamp(x, minVal, maxVal) does this same logic (const-var-const). So you can write:
res = v * clamp(fR2/mag, 1.0, fR2/mR2); //with fR2 >= mR2
Let's write it in other way:
res = v * fR2 * clamp(1.0/mag, 1.0/fR2, 1.0/mR2);
We can avoid two divisions because 1/mR2 <= 1/fR2 implies fR2 >= mR2
res = v * fR2 / clamp(mag, mR2, fR2);
The final code is
const float fR2 = 1.0;
const float mR2 = 0.25;
vec3 s_fold(in vec3 v) {
return v * fR2 / clamp(dot(v, v), mR2, fR2);
}
Both your solution and the posted "liner" suffer a potential division by zero when mag=0
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
I think I understand why calling glRotate(#, 0, 0, 0) results in a divide-by-zero. The rotation vector, a, is normalized: a' = a/|a| = a/0
Is that the only situation glRotate could result in a divide-by-zero? Yes, I know glRotate is deprecated. Yes, I know the matrix is on the OpenGL manual. No, I don't know linear algebra enough to confidently answer the question from the matrix. Yes, I think it would help. Yes, I asked this already in #opengl (can you tell?). And no, I didn't get an answer.
I would say yes. And I would say that you are right about the normalization step as well. The matrix shown in the OpenGL manual only consists of multiplications. And multiplying a vector would result into the same. Of course, it would do strange things if you result in a vector of (0,0,0). OpenGL states in the same manual that |x,y,z|=1 (or OpenGL will normalize).
So IF it wouldn't normalize, you would end up with a very empty matrix of:
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 1
Which will implode your object in the strangest ways. So DON'T call this function with a zero-vector. If you would like to, tell me why.
And I recommend using a library like GLM to do your matrix calculations if it gets too complicated for some simple glRotates.
Why should it divide by zero when you can check for that?:
/**
* Generate a 4x4 transformation matrix from glRotate parameters, and
* post-multiply the input matrix by it.
*
* \author
* This function was contributed by Erich Boleyn (erich#uruk.org).
* Optimizations contributed by Rudolf Opalla (rudi#khm.de).
*/
void
_math_matrix_rotate( GLmatrix *mat,
GLfloat angle, GLfloat x, GLfloat y, GLfloat z )
{
GLfloat xx, yy, zz, xy, yz, zx, xs, ys, zs, one_c, s, c;
GLfloat m[16];
GLboolean optimized;
s = (GLfloat) sin( angle * DEG2RAD );
c = (GLfloat) cos( angle * DEG2RAD );
memcpy(m, Identity, sizeof(GLfloat)*16);
optimized = GL_FALSE;
#define M(row,col) m[col*4+row]
if (x == 0.0F) {
if (y == 0.0F) {
if (z != 0.0F) {
optimized = GL_TRUE;
/* rotate only around z-axis */
M(0,0) = c;
M(1,1) = c;
if (z < 0.0F) {
M(0,1) = s;
M(1,0) = -s;
}
else {
M(0,1) = -s;
M(1,0) = s;
}
}
}
else if (z == 0.0F) {
optimized = GL_TRUE;
/* rotate only around y-axis */
M(0,0) = c;
M(2,2) = c;
if (y < 0.0F) {
M(0,2) = -s;
M(2,0) = s;
}
else {
M(0,2) = s;
M(2,0) = -s;
}
}
}
else if (y == 0.0F) {
if (z == 0.0F) {
optimized = GL_TRUE;
/* rotate only around x-axis */
M(1,1) = c;
M(2,2) = c;
if (x < 0.0F) {
M(1,2) = s;
M(2,1) = -s;
}
else {
M(1,2) = -s;
M(2,1) = s;
}
}
}
if (!optimized) {
const GLfloat mag = SQRTF(x * x + y * y + z * z);
if (mag <= 1.0e-4) {
/* no rotation, leave mat as-is */
return;
}
x /= mag;
y /= mag;
z /= mag;
/*
* Arbitrary axis rotation matrix.
*
* This is composed of 5 matrices, Rz, Ry, T, Ry', Rz', multiplied
* like so: Rz * Ry * T * Ry' * Rz'. T is the final rotation
* (which is about the X-axis), and the two composite transforms
* Ry' * Rz' and Rz * Ry are (respectively) the rotations necessary
* from the arbitrary axis to the X-axis then back. They are
* all elementary rotations.
*
* Rz' is a rotation about the Z-axis, to bring the axis vector
* into the x-z plane. Then Ry' is applied, rotating about the
* Y-axis to bring the axis vector parallel with the X-axis. The
* rotation about the X-axis is then performed. Ry and Rz are
* simply the respective inverse transforms to bring the arbitrary
* axis back to its original orientation. The first transforms
* Rz' and Ry' are considered inverses, since the data from the
* arbitrary axis gives you info on how to get to it, not how
* to get away from it, and an inverse must be applied.
*
* The basic calculation used is to recognize that the arbitrary
* axis vector (x, y, z), since it is of unit length, actually
* represents the sines and cosines of the angles to rotate the
* X-axis to the same orientation, with theta being the angle about
* Z and phi the angle about Y (in the order described above)
* as follows:
*
* cos ( theta ) = x / sqrt ( 1 - z^2 )
* sin ( theta ) = y / sqrt ( 1 - z^2 )
*
* cos ( phi ) = sqrt ( 1 - z^2 )
* sin ( phi ) = z
*
* Note that cos ( phi ) can further be inserted to the above
* formulas:
*
* cos ( theta ) = x / cos ( phi )
* sin ( theta ) = y / sin ( phi )
*
* ...etc. Because of those relations and the standard trigonometric
* relations, it is pssible to reduce the transforms down to what
* is used below. It may be that any primary axis chosen will give the
* same results (modulo a sign convention) using thie method.
*
* Particularly nice is to notice that all divisions that might
* have caused trouble when parallel to certain planes or
* axis go away with care paid to reducing the expressions.
* After checking, it does perform correctly under all cases, since
* in all the cases of division where the denominator would have
* been zero, the numerator would have been zero as well, giving
* the expected result.
*/
xx = x * x;
yy = y * y;
zz = z * z;
xy = x * y;
yz = y * z;
zx = z * x;
xs = x * s;
ys = y * s;
zs = z * s;
one_c = 1.0F - c;
/* We already hold the identity-matrix so we can skip some statements */
M(0,0) = (one_c * xx) + c;
M(0,1) = (one_c * xy) - zs;
M(0,2) = (one_c * zx) + ys;
/* M(0,3) = 0.0F; */
M(1,0) = (one_c * xy) + zs;
M(1,1) = (one_c * yy) + c;
M(1,2) = (one_c * yz) - xs;
/* M(1,3) = 0.0F; */
M(2,0) = (one_c * zx) - ys;
M(2,1) = (one_c * yz) + xs;
M(2,2) = (one_c * zz) + c;
/* M(2,3) = 0.0F; */
/*
M(3,0) = 0.0F;
M(3,1) = 0.0F;
M(3,2) = 0.0F;
M(3,3) = 1.0F;
*/
}
#undef M
matrix_multf( mat, m, MAT_FLAG_ROTATION );
}