Getting OBB-Triangle collision detection right in OpenGL engine - c#

I want to implement terrain collision. I have a class called GeoTerrain. Its constructor takes a picture as parameter and then builds a terrain out of triangles with vertices high for each light pixel and low for each dark pixel.
This puts me in a position where I have to implement a proper collision detection for the OBBs when they hit the terrain. But my temporary solution is far from robust.
Here is my current approach:
Each surface triangle has its own little hitbox that consists of 6 vertices (the 3 vertices on the triangle surface and three additional vertices that basically are the 3 surface vertices but shifted down the surface normal (so that they are underneath the visible surface).
Why: Because I do not know how to otherwise calculate the minimum translation vector without having a hitbox volume.
Sorry for the long code - I tried to comment as much as necessary:
// This method resides in the Hitbox class.
// The hitbox instance then checks against the other object (parameter):
private IntersectionObject TestIntersectionTerrain(GeoTerrain terra)
{
Vector3 mtv = new Vector3(0, 0, 0);
Vector3 mtvTotal = new Vector3(0, 0, 0);
float shape1Min, shape1Max, shape2Min, shape2Max;
// Select all triangles within reach of the OBB hitbox:
List<GeoTriangle> triangles = terra.GetTrianglesForHitbox(this);
// Loop through all triangles and check collision
// (cannot be more than 8 triangles, right now)
foreach (GeoTriangle triangle in triangles)
{
bool bothOverlap = false;
mtv = Vector3.Zero;
bool error;
bool breakDone = false;
float mtvDistance = float.MaxValue;
float mtvDirection = 1;
// loop through all hitbox normals (three normals):
for (int i = 0; i < mNormals.Length; i++)
{
error = false;
// project the current vertices of objects' hitbox and triangle hitbox to find
// both shapes' minimum and maximum:
SATtest(CurrentHitBoxNormals[i], CurrentHitBoxVertices, out shape1Min, out shape1Max);
SATtest(CurrentHitBoxNormals[i], triangle.VerticesHitbox, out shape2Min, out shape2Max);
if (!Overlaps(shape1Min, shape1Max, shape2Min, shape2Max))
{
bothOverlap = false;
breakDone = true;
break;
}
else
{
// calculate MTV:
CalculateOverlap(
CurrentHitBoxNormals[i], // normals (here, the meshes' hitbox normals)
ref shape1Min,
ref shape1Max,
ref shape2Min,
ref shape2Max,
out error,
ref mtvDistance,
ref mtv,
ref mtvDirection,
mCenterTranslated, // object's hitbox volume center (world space)
triangle.CenterHitbox); // triangle's hitbox volume center (world space)
}
// do the same but now for the triangle's normal
// (right now this unnecessarily also gets looped 3 times):
SATtest(triangle.Normal, CurrentHitBoxVertices, out shape1Min, out shape1Max);
SATtest(triangle.Normal, triangle.VerticesHitbox, out shape2Min, out shape2Max);
if (!Overlaps(shape1Min, shape1Max, shape2Min, shape2Max))
{
bothOverlap = false;
breakDone = true;
}
else
{
CalculateOverlap(triangle.Normal, ref shape1Min, ref shape1Max, ref shape2Min, ref shape2Max,
out error, ref mtvDistance, ref mtv, ref mtvDirection, mCenterTranslated, triangle.CenterHitbox);
bothOverlap = true;
}
}
if (bothOverlap && !breakDone && mtv != Vector3.Zero)
{
// add the current mtv to the total MTV (of all triangles)
// but only add more to it, if the current MTV has a bigger shift in
// one direction than the previous MTVs:
mtvTotal.X = Math.Abs(mtv.X) > Math.Abs(mtvTotal.X) ? mtv.X : mtvTotal.X;
mtvTotal.Y = Math.Abs(mtv.Y) > Math.Abs(mtvTotal.Y) ? mtv.Y : mtvTotal.Y;
mtvTotal.Z = Math.Abs(mtv.Z) > Math.Abs(mtvTotal.Z) ? mtv.Z : mtvTotal.Z;
}
}
if (mtvTotal != Vector3.Zero)
{
IntersectionObject o = new IntersectionObject();
o.IntersectingGameObject = terra.Owner;
o.MinimumTranslationVector = mtvTotal;
o.MeshNumber = 0;
o.MeshName = terra.Owner.Name;
return o;
}
else
{
return null;
}
}
private void CalculateOverlap(Vector3 axis, ref float shape1Min, ref float shape1Max, ref float shape2Min, ref float shape2Max, out bool error, ref float mtvDistance, ref Vector3 mtv, ref float mtvDirection, Vector3 posA, Vector3 posB)
{
float d0 = (shape2Max - shape1Min);
float d1 = (shape1Max - shape2Min);
float intersectionDepthScaled = (shape1Min < shape2Min)? (shape1Max - shape2Min) : (shape1Min - shape2Max);
float axisLengthSquared = Vector3.Dot(axis, axis);
if (axisLengthSquared < 1.0e-8f)
{
error = true;
return;
}
float intersectionDepthSquared = (intersectionDepthScaled * intersectionDepthScaled) / axisLengthSquared;
error = false;
if (intersectionDepthSquared < mtvDistance || mtvDistance < 0)
{
mtvDistance = intersectionDepthSquared;
mtv = axis * (intersectionDepthScaled / axisLengthSquared);
float notSameDirection = Vector3.Dot(posA - posB, mtv);
mtvDirection = notSameDirection < 0 ? -1.0f : 1.0f;
mtv = mtv * mtvDirection;
}
}
The problem is: it works, but not on the edges. With my approach (invisible hitbox below triangle surface) the player sometimes hits an invisible wall because the MTV tells him to move up (ok) and sideways as well (not ok). Mathematically this is correct, because if the player sinks into the fake triangle volume, the mtv may of course decide to shift him back on more than one axis if need be.
Is there any way to get the MTV from just checking against a flat triangle? I found several links that tell me "if" there is an intersection. But I found no understandable solution for getting the MTV. I think I need to find out by how much the player has to be shifted back along the surface normal?
EDIT (2019-02-12 22:00)
I updated the code according to the first answer. But I still get strange behaviour when applying the calculated MTV. This is my current algorithm:
My updated collision detection algorithm approach is so far:
get a list of the triangles that are close to the player
get the OBBs vertices and normals
loop through that triangle list and do for each triangle (maximum of ~8 right now):
use triangle's normal and project triangle's vertices and OBB's vertices.
use OBB's normal #1 and project triangle's vertices and OBB's vertices.
use OBB's normal #2 and project triangle's vertices and OBB's vertices.
use OBB's normal #3 and project triangle's vertices and OBB's vertices.
cross the vectors you told me (step 5 to 13) and project triangle's vertices and OBB's vertices on the normalised version of these vectors
Each step is only done if the previous step resulted in an overlap. The overlapping length is then stored in an array (13 cells, one for each step)
If every projection resulted in an overlap, look for the mtv by calculating the minimum overlap and the corresponding axis vector.
Multiply this vector by the overlap and use it as the MTV.
Is that about right?

The SAT (Separation Axis Theorem) algorithm will give you the minimum translation vector quite easily, for any kind of convex shapes, that can also include triangles, quads, flat polygons.
In a brute-force manner, The SAT works in two steps, a couple more to get the MTV.
Find the list of minimum separation axes to tests against.
for example, for an OBB, you will need to consider three directions for
the faces, and three directions for the edges.
Project the two convex shapes on the separation axis, to get the projection intervals.
for example, for each shape, dot product all their vertices with the axis
direction.
The min and max values will give you the intervals for each shape.
if the two intervals do not intersect, the objects are spearate and do not intersect.
if all intervals intersect on every spearation axis, the objects intersect.
to find the MTV, find the minimum overlap among all the speration axis intervals.
the interval overlap, combined with the separation axis direction, will give you the MTV.
For convex polyhedras, the separation axes to test against are quite simple. They are :
the surface normals for each shape, call them A and B.
the cross product of every edges of A against every edges of B.
There are optimisations, for examples, there is no need to test axes with
opposite direction, or similar direction.
as an example, a OBB will need to be tested for three face normals, and three edge directions.
NOTE: that the MTV may not always be the best solution to your problem, especially when we talk about terrains.

Related

How to find the area that two triangles are in contact

Given the positions of the vertices, and the surface normals. How can I calculate the area(which may be 0) that the 2 triangles are in contact? These triangles are also in 3D space so if they aren't lined up properly, just jammed into each other, the contact area should be 0.
(In C#)
This is not a trivial problem, so let's break it down into steps.
Check if the two triangles are coplanar, otherwise the area is 0.
Project the triangles onto a 2D surface
Calculate the intersection polygon
Calculate the area
1. Checking for coplanarity
For the two triangles to be coplanar, all vertices of one triangle must lie in the plane determined by the other one.
Using the algorithm described here we can check for every vertex whether that is the case, but due to the fact that floating point numbers are not perfectly precise, you will need to define some error theshold to determine what still counts as coplanar.
Assuming va and vb are the vectors of the triangles A and B respectively, the code could look something like this.
(Note: I have never worked with Unity and am writing all of the code from memory, so please excuse if it isn't 100% correct).
public static bool AreTrianglesCoplanar(Vector3[] va, Vector3[] vb) {
// make sure these are actually triangles
Debug.Assert(va.Length == 3);
Debug.Assert(vb.Length == 3);
// calculate the (scaled) normal of triangle A
var normal = Vector3.Cross(va[1] - va[0], va[2] - va[0]);
// iterate all vertices of triangle B
for(var vertex in vb) {
// calculate the dot product between the normal and the vector va[0] --> vertex
// the dot product will be 0 (or very small) if the angle between these vectors
// is a right angle (90°)
float dot = Vector3.Dot(normal, vertex - va[0]).
// the error threshold
const float epsilon = 0.001f;
// if the dot product is above the threshold, the vertex lies outside the plane
// in that case the two triangles are not coplanar
if(Math.Abs(dot) > epsilon)
return false;
}
return true;
}
2. Projecting the triangles into 2D space
We now know that all six vertices are in the same 2D plane embedded into 3D space, but all of our vertex coordinates are still three-dimensional. So the next step would be to project our points into a 2D coordinate system, such that their relative position is preserved.
This answer explains the math pretty well.
First, we need to find a set of three vectors forming an orthonormal basis (they must be orthoginal to each other and of length 1).
One of them is just the plane's normal vector, so we need two more vectors that are orthogonal to the first, and also orthogonal to each other.
By definition, all vectors in the plane defined by our triangles are orthogonal to the normal vector, so we can just pick one (for example the vector from va[0] to va[1]) and normalize it.
The third vector has to be orthogonal to both of the others, we can find such a vector by taking the cross product of the previous two.
We also need to choose a point in the plane as our origin point, for example va[0].
With all of these parameters, and using the formula from the linked amswer, we can determine our new projected (x, y) coordinates (t_1 and t_2 from the other answer). Note that -- because all of our points lie in the plane defining that normal vector -- the third coordinate (called s in the other answer) will always be (close to) zero.
public static void ProjectTo2DPlane(
Vector3[] va, Vector3[] vb
out Vector2[] vaProjected, out Vector2[] vbProjecte
) {
// calculate the three coordinate system axes
var normal = Vector3.Cross(va[1] - va[0], va[2] - va[0]).normalized;
var e1 = Vector3.Normalize(va[1] - va[0]);
var e2 = Vector3.Cross(normal, e1);
// select an origin point
var origin = va[0];
// projection function we will apply to every vertex
Vector2 ProjectVertex(Vector3 vertex) {
float s = Dot(normal, vertex - origin);
float t1 = Dot(e1, vertex - origin);
float t2 = Dot(e2, vertex - origin);
// sanity check: this should be close to zero
// (otherwise the point is outside the plane)
Debug.Assert(Math.Abs(s) < 0.001);
return new Vector2(t1, t2);
}
// project the vertices using Linq
vaProjected = va.Select(ProjectVertex).ToArray();
vbProjected = vb.Select(ProjectVertex).ToArray();
}
Sanity check:
The vertex va[0] should be projected to (0, 0).
The vertex va[1] should be projected to (*, 0), so somewhere on the new x axis.
3. / 4. Calculating the intersection area in 2D
This answer this answer mentions the algorithms necessary for the last step.
The Sutherland-Hodgman algorithm successively clips one triangles with each side of the other. The result of this will be either a triangle, a quadrilateral or an empty polygon.
Finally, the shoelace formula can be used to calculate the area of the resulting clipping polygon.
Bringing it all together
Assuming you implemented the two functions CalculateIntersecionPolygon and CalculatePolygonArea, the final intersection area could be calculated like this:
public static float CalculateIntersectionArea(Mesh triangleA, Mesh triangleB) {
var verticesA = triangleA.GetVertices();
var verticesB = triangleB.GetVertices();
if(!AreTrianglesCoplanar(verticesA, verticesB))
return 0f; // the triangles are not coplanar
ProjectTo2DPlane(verticesA, verticesB, out Vector2[] projectedA, out Vector2[] projectedB);
CalculateIntersecionPolygon(projectedA, projectedB, out List<Vector2> intersection);
if(intersection.Count == 0)
return 0f; // the triangles didn't overlap
return CalculatePolygonArea(intersection);
}

Detect if a vector2 is between two other vector2

as the title suggests I am trying different ways but without solving.
I would need to check if the mouse is between two 2d points in PlayMode.
I thought of using rect to build a rectangle between two vector2 on the fly and check with rect.contains if the mouse position is inside the rectangle. This works if the rectangle is horizontal but the points on the screen are not aligned and it does not seem to me that there is a way to create an oblique rectangle.
I tried to calculate the distance between the two points, to create the rectangle and to move its center to rotate it but it doesn't seem to work. Also it seems that there is no way to display the rectangle on the screen and therefore it is difficult to understand if it retains its original shape after changing some parameters.
Do you have any suggestions? But I need the detection area to be rectangular and not on a single line. Using LineCast it is possible to solve but the detected area is too narrow.
I thought of a rectangle between the two vectors. Imagine a one-pixel line connecting the two vectors. Now increase the thickness of the line up to 10/20 pixels so that the mouse is detected a little before and a little after the original line. Maybe I'm wrong but I see the new rect as a good solution but as far as I looked at all its properties I can't understand how to build it between two vectors without too many lines of code.
You can do some vector math to:
Determine if the test point is not "outside" of the two points.
Calculate the distance from the line segment your point is, and if that's equal or below some threshold, then consider it to be "near" the segment between the two points.
If it's "near" the segment and not "outside" the two points, then consider it to be "between" the points.
For example:
Vector3 point1 = new Vector2(10f, 10f);
Vector3 point2 = new Vector2(50f, 50f);
float closeThreshold = 5f;
Vector3 testPoint = new Vector2(20f, 21f);
Vector3 segmentDirection = (point2-point1).normalized;
Vector3 point1ToTest = testPoint - point1;
Vector3 testToPoint2 = point2 - testPoint;
// determine if testPoint is not "outside" the points
if ( Vector3.Dot(segmentDirection, point1ToTest.normalized) >= 0f
&& Vector3.Dot(segmentDirection, testToPoint2.normalized) >= 0f)
{
Vector3 closestPointOnSegment = point1
+ segmentDirection * Vector3.Dot(point1ToTest, segmentDirection);
// determine if testPoint is close enough to the line segment point1->point2
if ((closestPointOnSegment - testPoint).magnitude <= closeThreshold)
{
// testPoint is "between" point1 and point2
}
}
This will work with Vector3 or Vector2, which will safely convert to Vector3 as shown above.
If you are only concerned with if the point is exactly between the two positions, take the directions from the point you're testing to either end of the line, and if the dot product between those directions is -1, then the point is between them:
Vector3 point1 = new Vector2(-1f,-1f);
Vector3 point2 = new Vector2(1f,1f);
Vector3 testPoint = new Vector2(0f,0f);
Vector3 testTo1 = (point1-testPoint).normalized;
Vector3 testTo2 = (point2-testPoint).normalized;
if (Mathf.Approximately(-1f, Vector3.Dot(testTo1, testTo2)))
{
// testPoint is between point1 and point2
}
This will work with Vector3 or Vector2, which will safely convert to Vector3 as above.

Finding points on a cylinder in 3d room c#

Example Image here
I am trying to find a way to calculate points on my cylinders top circle surface. My situation looks like this, I have a vector which is defining my cylinders direction in 3d room. Then I already calculated me a perpendicular vector with
Vector3.Cross(vector1, vector2)
Now I use the diameter/2 to calculate the point which is lying on the edge of the circular top surface of my cylinder. Now I want to rotate my vector always 90 degrees in order to get 4 points on the edge of the surface. All the 4 vectors defining them should be perpendicular to the cylinders direction. Can you help me how I can rotate the first perpendicular to achieve this?
I already tried:
Matrix4x4.CreateFromAxisAngle(vectorcylinderdirection, radiant)
Then I calculated again cross product but it doesnt work like I want to.
Edit:
public static void calculatePontsOnCylinder()
{
//Calculate Orthogonal Vector to Direction
Vector3 tCylinderDirection = new Vector3(1, 0, 0);
Vector3 tOrthogonal = Vector3.Cross(tCylinderDirection, new Vector3(-tCylinderDirection.Z,tCylinderDirection.X,tCylinderDirection.Y));
Vector3 tNormOrthogonal = Vector3.Normalize(tOrthogonal);
//Calculate point on surface circle of cylinder
//10mm radius
int tRadius = 10;
Vector3 tPointFinder = tNormOrthogonal * tRadius;
//tPointFinder add the cylinder start point
//not yet implemented
//now i need to rotate the vector always 90 degrees to find the 3 other points on the circular top surface of the cylinder
//don't know how to do this
// I thought this should do it
Matrix4x4.CreateFromAxisAngle(tCylinderDirection, (float)DegreeToRadian(90));
}
private static double DegreeToRadian(double angle)
{
return Math.PI * angle / 180.0;
}
In the picture you can see a example, the vector1 is what I need, always rotated 90 degrees and vector2 would be my cylinder direction vector
I possibly have found the correct formula:
Vector3 tFinal = Vector3.Multiply((float)Math.Cos(DegreeToRadian(90)), tPointFinder) + Vector3.Multiply((float)Math.Sin(DegreeToRadian(90)), Vector3.Cross(tCylinderDirection, tPointFinder));
Vector3 tFinal180 = Vector3.Multiply((float)Math.Cos(DegreeToRadian(180)), tPointFinder) + Vector3.Multiply((float)Math.Sin(DegreeToRadian(180)), Vector3.Cross(tCylinderDirection, tPointFinder));
Vector3 tFinal270= Vector3.Multiply((float)Math.Cos(DegreeToRadian(270)), tPointFinder) + Vector3.Multiply((float)Math.Sin(DegreeToRadian(270)), Vector3.Cross(tCylinderDirection, tPointFinder));
Interesting is that if I try it with (1,1,0) as cylinder direction it gives me correct directions but the length is different for 90 degrees and 270.
Here is the code that should solve your problem assuming that the input requirements are satisfied.
float zCutPlaneLocation = 20; // should not get bigger than cylinder length
float cylinderRadius = 100;
Vector3 cylinderCenter = new Vector3(0, 0, 0); // or whatever you got as cylinder center point, given as Vector3 since Point type is not defined
// will return 360 points on cylinder edge, corresponding to this z section (cut plane),
// another z section will give another 360 points and so on
List<Vector3> cylinderRotatedPointsIn3D = new List<Vector3>();
for (int angleToRotate = 0; angleToRotate < 360; angleToRotate++)
{
cylinderRotatedPointsIn3D.Add(GetRotatedPoint(zCutPlaneLocation, angleToRotate, cylinderRadius, cylinderCenter));
}
....
private static Vector3 GetRotatedPoint(
float zLocation, double rotationAngleInRadian, float cylinderRadius, Vector3 cylinderCenter)
{
Vector2 cylinderCenterInSection = new Vector2(cylinderCenter.X, cylinderCenter.Y);
float xOfRotatedPoint = cylinderRadius * (float)Math.Cos(rotationAngleInRadian);
float yOfRotatedPoint = cylinderRadius * (float)Math.Sin(rotationAngleInRadian);
Vector2 rotatedVector = new Vector2(xOfRotatedPoint, yOfRotatedPoint);
Vector2 rotatedSectionPointOnCylinder = rotatedVector + cylinderCenterInSection;
Vector3 rotatedPointOnCylinderIn3D = new Vector3(
rotatedSectionPointOnCylinder.X,
rotatedSectionPointOnCylinder.Y,
zLocation + cylinderCenter.Z);
return rotatedPointOnCylinderIn3D;
}
I just created a console app for this. First part of code should be added in main method.
Working with those matrices seems is not that easy. Also I am not sure if your solution works ok for any kind of angle.
Here the idea is that the rotated points from cylinder are calculated in a section of the cylinder so in 2D than the result is moved in 3D by just adding the z where the Z section was made on cylinder. I suppose that world axis and cylinder axis are on the same directions. Also if your cylinder gets along (increases) on the X axis, instead of Z axis as in example just switch in code the Z with X.
I attached also a picture for more details. This should work if you have the cylinder center, radius, rotation angle and you know the length of the cylinder so that you create valid Z sections on cylinder. This could get tricky for clockwise/counter clock wise cases but lets see how it works for you.
If you want to handle this with matrices or whatever else I think that you will end up having this kind of result. So I think that you cannot have "all" the rotated points in just a list for the entire cylinder surface, they would depend on something like the rotated points of a Z section on the cylinder.

how to make the transform.up of a gameobject be colliear with a vector, without changing it's Y rotation?

So... I'll try to be as clear as possible, if I let something unclear please let me know.
I have a vector that comes from origin and go to a point in space, and I have an object that I want it's transform.up (Or Y vector) to be colinear with this vector, but the Y rotation of this object is driven by another factor, and I dont want to change it.
So far, what I'm trying to do is project this vector in the local XY and local ZY planes and measure the angles and apply rotation:
float xInclination = Mathf.Atan(Vector3.ProjectOnPlane(orbitMomemtumVector, transform.right).z / Vector3.ProjectOnPlane(orbitMomemtumVector, transform.right).y)*Mathf.Rad2Deg;
float yInclination = Mathf.Atan(initialPos.z / initialPos.x) * Mathf.Rad2Deg;
float zInclination = -Mathf.Atan(Vector3.ProjectOnPlane(orbitMomemtumVector, transform.forward).x / Vector3.ProjectOnPlane(orbitMomemtumVector, transform.forward).y)*Mathf.Rad2Deg;
if (initialPos.x < 0 && initialPos.z > 0)
{
yInclination = 180f - Mathf.Abs(yInclination);
argumentPeriapsis = argumentPeriapsis - yInclination;
}
else if (initialPos.x < 0 && initialPos.z < 0)
{
yInclination = 180f + Mathf.Abs(yInclination);
argumentPeriapsis = argumentPeriapsis - yInclination;
}
else
{
argumentPeriapsis = argumentPeriapsis - yInclination;
}
transform.rotation = Quaternion.Euler(xInclination, (float)argumentPeriapsis, zInclination);
This image shows the problem, I need the Y arrow to be collinear with the blue line
Let me be clear on this, don't use Euler angles in 3d space. In fact, avoid them in 2d games as well. Unless your object truly rotates on a single axis, and you never have to get the angle between two rotations, or lerp your rotations, don't use them.
What you want is Quaternion.LookRotation(A, B).
A being a vector to which Z will be colinear, X being orthogonal to the plane defined by A and B, and Y belonging to that plane.
Followup:
To match other axis to A, there are multiple solutions. First would be to simply apply the lookRotation to a parent object, while the child object is rotated inside to match whatever rotation you want. You can also edit your entire mesh to do so.
The other way is to simply apply another rotation, so that both combined get your desired result, like so:
Quaternion zMatched = Quaternion.LookRotation(zAxisTarget, direction)
Quaternion yMatched = zMatched * Quaternion.AngleAxis(90f, Vector3.right);
transform.rotation = yMatched;
This will rotate the object so that the y axis becomes collinear to the previous z axis.
This is however nor perfect. If you reach this point, you should consider building your own clearer solution based on combining AngleAxis results. But it works well enough.

Triangle - Triangle Intersection Test

I'd like to know if there is out there some tutorial or guide to understand and implement a Triangle-Triangle intersection test in a 3D Environment. (i don't need to know precise where the intersection happened, but only that an intersection has occurred)
I was going to implement it following a theoric pdf, but i'm pretty stuck at the
Compute plane equation of triangle 2.
Reject as trivial if all points of triangle 1 are on same side.
Compute plane equation of triangle 1.
Reject as trivial if all points of triangle 2 are on same side.
Compute intersection line and project onto largest axis.
Compute the intervals for each triangle.
Intersect the intervals.
point 5 of this guide. I don't really know what's asking (all 5,6 and 7). XD
As i don't have high knowledge in mathematics (well, i know as far the couple of exams at the university has given me (i'm a raw programmer XD)), please try to be as simple as possible with me. :D (I tried to search on google, but most of the links point to some 4-5 pages full of formulas I don't really care to know and I don't understand.)
Thanks for the help
You said:
I'd like to know if there is out there
some tutorial or guide to understand
and implement a Triangle-Triangle
intersection test in a 3D Environment.
And then you said:
most of the links point to some 4-5
pages full of formulas I don't really
care to know
I note that those two statements totally contradict each other. So which is it? Do you want to understand how triangle-triangle intersection works, or do you just want an implementation that works but you don't understand it?
It's not like all those web pages are full of unnecessary math. All the math is necessary to understanding how the intersection algorithm works. Start at the beginning and learn how all of it works.
Steps 5, 6 and 7 are straightforward to understand once you know what the words mean. The intersection line is the line made by the intersection of the two planes. Each triangle lies in a plane. There are three cases:
the planes are parallel and do not intersect. The triangles obviously do not intersect.
the planes are the same plane. The triangles might meet or might not.
the planes are two different planes that meet at a line. If the triangles intersect, they obviously must intersect on that line.
Suppose we're in the third case. Compute the segment of the intersection line which is contained in the first triangle. Compute the segment of the intersection line which is in the second triangle. The question is now "do these segments overlap?"
You can work that out by projecting the segments onto a convenient axis, and seeing whether the line segments on that axis overlap. Basically, it works like this: imagine you are shining a light on the line segments, such that their shadows fall onto an axis. If the shadows on the axis intersect then the line segments must intersect. If there is a gap between the shadows on the axis, then clearly there must be a gap between the line segments, and therefore the triangles do not intersect.
If you want to understand how this works then there's no getting around the fact that you are going to need to understand all this stuff -- all the algebra that works out how planes intersect and how projects onto an axis work. It's all necessary. And all that stuff is basic building blocks out of which more complex transformations, projections, and so on, will be built, so understand the basics thoroughly if you want to go farther.
My answer is simple ... this problem is hard in an arbitrary coordinate system, so Change it to something that makes the problem easy. The Matrix class in xna has a CreateLookAt function which can be used to create a useful transformation on all verticies.
The following example is not optimized, it is written only for understanding of the solution. The Exceptions and their corresponding if statements can all be removed, along with a few vector transformations.
public static bool CheckColision(Vector3 t1a, Vector3 t1b, Vector3 t1c, Vector3 t2a, Vector3 t2b, Vector3 t2c)
{//rotates each edge of the first triangle to the Z axis and checks the second triangle against it then repeats with the second one against the first, and lastly checks to see if all points of the second triangle are on the same side as the first
if(! CheckColisionLookAt(t1a, t1b, t1c, t2a, t2b, t2c))
return false;
if (!CheckColisionLookAt(t1b, t1c, t1a, t2a, t2b, t2c))
return false;
if (!CheckColisionLookAt(t1c, t1a, t1b, t2a, t2b, t2c))
return false;
if (!CheckColisionLookAt(t2a, t2b, t2c, t1a, t1b, t1c))
return false;
if (!CheckColisionLookAt(t2b, t2c, t2a, t1a, t1b, t1c))
return false;
if (!CheckColisionLookAt(t2c, t2a, t2b, t1a, t1b, t1c))
return false;
return CheckColisionAllOnOneSide(t1a, t1b, t1c, t2a, t2b, t2c);
}
public static bool CheckColisionAllOnOneSide(Vector3 t1a, Vector3 t1b, Vector3 t1c, Vector3 t2a, Vector3 t2b, Vector3 t2c)
{//simply performs a transformation to check if all points on one triangle are on the same side of the other triangle
Matrix m = Matrix.CreateLookAt(t1a, t1b, t1c - t1a);
t2a = Vector3.Transform(t2a, m);
t2b = Vector3.Transform(t2b, m);
t2c = Vector3.Transform(t2c, m);
if (t2a.X < 0 && t2b.X < 0 && t2c.X < 0)
return false;
if (0 < t2a.X && 0 < t2b.X && 0 < t2c.X)
return false;
return true;
}
public static bool CheckColisionLookAt(Vector3 t1a, Vector3 t1b, Vector3 t1c, Vector3 t2a, Vector3 t2b, Vector3 t2c)
{//performs a transformation and checks if all points of the one triangle are under the other triangle after the transformation
Matrix m = Matrix.CreateLookAt(t1a, t1b, t1c - t1a);
t1a = Vector3.Transform(t1a, m);// (0, 0, 0)
if ( ZERRO < Math.Abs(t1a.X)|| ZERRO < Math.Abs(t1a.Y) || ZERRO < Math.Abs(t1a.Z))
throw new Exception();
t1b = Vector3.Transform(t1b, m);// (0, 0, maxZ)
if (ZERRO < Math.Abs(t1a.X) || ZERRO < Math.Abs(t1a.Y))
throw new Exception();
t1c = Vector3.Transform(t1c, m);// (0, maxY, someZ)
if (ZERRO < Math.Abs(t1a.X))
throw new Exception();
t2a = Vector3.Transform(t2a, m);
t2b = Vector3.Transform(t2b, m);
t2c = Vector3.Transform(t2c, m);
if (t2a.Y < 0 && t2b.Y < 0 && t2c.Y < 0)
return false;
return true;
}
Here's a website that contains references to a lot of intersections:
Real-Time Rendering Object/Object Intersection Page
Here's what they list for Tri/Tri:
Möller jgt 2(2);Held jgt 2(4);GTweb;Möller;GPG p.393;GTCG p.539;TGS;RTCD p.155,172;Shen jgt 8(1);Guigue jgt 8(1);SoftSurfer; Real-Time Rendering, 2nd Edition p.590; Real-Time Rendering, Third Edition p.757
I suppose that you have the x,y coordinates for the triangle vertices.
eg.
for Triangle A:
1. Side A1: xa1, ya1
2. Side A2: xa2, ya2
3. Side A3: xa3, ya3
for Triangle B:
1. Side B1: xb1, yb1
2. Side B2: xb2, yb2
3. Side B3: xb3, yb3
The triangles intersect if any combination of their lines intercect. Meaning if A1 intersects B1 or B2 or B3, or if A2 intersects B1 or B2 or B3, or if A3 intersects B1 or B2 or B3.
So you need the algorithm that decects if two lines intersect. Here is the easiest example I found: http://www.mathopenref.com/coordintersection.html
The method you posted looks like it is using something akin to this algorithm to detect if convex polygons intersect, based on the Separating Axis Theorem. It's not very difficult to understand.
If a line, called a separating axis, can be drawn between two polygons, they do not intersect. Each edge of each polygon is a candidate separating axis. The polygons are projected onto a vector perpendicular to that axis, and the 1D extents are tested for overlap. If there is no 1D overlap, the current edge is a separating axis and the two polygons do not intersect. If there is 1D overlap, the results are inconclusive until all candidate edges have been tested, at which point it is concluded that the two polygons do intersect. Note that two polygons are allowed to share an edge.

Categories