Triangle - Triangle Intersection Test - c#

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.

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);
}

How to find the smallest sector of a circle that covers a polygon?

Given the circle C: (O, r) and the polygon P, how can I find the smallest sector of C that covers P?
Assume that the radius of the circle is large enough, so the main problem is to find the initial and final angles of the sector.
I tried to draw rays from center of circle toward each of angles of the polygon and check if the ray overlaps the polygon. But there might be more than two rays that only touch the polygon. I can not rely on the selection of unique rays based on their direction angle, due to double precision. So finding min and max angles in the list of touched rays is useless. Beside that, I have problem with choosing either of sectors created by two terminal angles, since initial angle can be greater than final angle when computed by atan2.
So what is the proper way to find such a sector?
Edit:
Three example polygons (in WKT format):
POLYGON((52.87404 30.85613, 42.55699 28.46292, 41.54373 24.319989, 53.57623 21.300564, 62.94891 28.46292, 49.39652 27.550071, 52.87404 30.85613))
POLYGON((52.94294 30.920592, 42.55699 28.46292, 43.61965 35.545578, 55.85037 34.862696, 59.12524 36.621547, 47.68664 39.877048, 35.69973 36.198265, 37.30512 29.196711, 31.09762 28.46292, 41.54373 24.319989, 53.57623 21.300564, 62.94891 28.46292, 49.39652 27.550071, 52.94294 30.920592))
POLYGON((52.94294 30.920592, 42.55699 28.46292, 43.61965 35.545578, 52.45594 37.266299, 59.30560 29.196711, 64.12177 33.290489, 58.81733 36.554277, 47.68664 39.877048, 35.69973 36.198265, 37.30512 29.196711, 31.09762 28.46292, 41.54373 24.319989, 53.57623 21.300564, 62.94891 28.46292, 49.39652 27.550071, 52.94294 30.920592))
Center and radius of circle for all examples:
O: (45, 30)
r: 25
For starters we can handle your data as point cloud (polygon vertexes) p[i] and some circle defined by center p0 and radius r. If your point cloud is entirely inside circle you can ignore the radius.
We can exploit atan2 however to avoid problems with crossing and sector selection we do not enlarge min/max bounds as usual for standard cartesian BBOX computation instead:
compute atan2 angle for each point and remember it in array a[]
sort a[]
find biggest distance between consequent angles in a[]
Do not forget that angle difference can be |Pi| tops so if it is more you need to +/- 2*PI. Also handle a[] as cyclic buffer.
This is my simple C++/VCL attempt:
//---------------------------------------------------------------------------
float p0[]={52.87404,30.856130,42.55699,28.46292,41.54373,24.319989,53.57623,21.300564,62.94891,28.46292,49.39652,27.550071,52.87404,30.85613,};
float p1[]={52.94294,30.920592,42.55699,28.46292,43.61965,35.545578,55.85037,34.862696,59.12524,36.621547,47.68664,39.877048,35.69973,36.198265,37.30512,29.196711,31.09762,28.46292,41.54373,24.319989,53.57623,21.300564,62.94891,28.46292,49.39652,27.550071,52.94294,30.920592,};
float p2[]={52.94294,30.920592,42.55699,28.46292,43.61965,35.545578,52.45594,37.266299,59.30560,29.196711,64.12177,33.290489,58.81733,36.554277,47.68664,39.877048,35.69973,36.198265,37.30512,29.196711,31.09762,28.46292,41.54373,24.319989,53.57623,21.300564,62.94891,28.46292,49.39652,27.550071,52.94294,30.920592,};
float x0=45.0,y0=30.0,R=25.0;
//---------------------------------------------------------------------------
template <class T> void sort_asc_bubble(T *a,int n)
{
int i,e; T a0,a1;
for (e=1;e;n--) // loop until no swap occurs
for (e=0,a0=a[0],a1=a[1],i=1;i<n;a0=a1,i++,a1=a[i])// proces unsorted part of array
if (a0>a1) // condition if swap needed
{ a[i-1]=a1; a[i]=a0; a1=a0; e=1; } // swap and allow to process array again
}
//---------------------------------------------------------------------------
void get_sector(float x0,float y0,float r,float *p,int n,float &a0,float &a1)
{
// x0,y0 circle center
// r circle radius
// p[n] polyline vertexes
// a0,a1 output angle range a0<=a1
int i,j,m=n>>1;
float x,y,*a;
a=new float[m];
// process points and compute angles
for (j=0,i=0;i<n;j++)
{
x=p[i]-x0; i++;
y=p[i]-y0; i++;
a[j]=atan2(y,x);
}
// sort by angle
sort_asc_bubble(a,m);
// get max distance
a0=a[m-1]; a1=a[0]; x=a1-a0;
while (x<-M_PI) x+=2.0*M_PI;
while (x>+M_PI) x-=2.0*M_PI;
if (x<0.0) x=-x;
for (j=1;j<m;j++)
{
y=a[j]-a[j-1];
while (y<-M_PI) y+=2.0*M_PI;
while (y>+M_PI) y-=2.0*M_PI;
if (y<0.0) y=-y;
if (y>x){ a0=a[j-1]; a1=a[j]; x=y; }
}
}
//---------------------------------------------------------------------------
void TMain::draw()
{
int i,n;
float x,y,r,*p,a0=0.0,a1=0.0;
float ax,ay,bx,by;
float zoom=7.0;
p=p0; n=sizeof(p0)/sizeof(p0[0]);
// p=p1; n=sizeof(p1)/sizeof(p1[0]);
// p=p2; n=sizeof(p2)/sizeof(p2[0]);
get_sector(x0,y0,R,p,n,a0,a1);
// clear buffer
bmp->Canvas->Brush->Color=clBlack;
bmp->Canvas->FillRect(TRect(0,0,xs,ys));
// circle
x=x0; y=y0; r=R;
ax=x+R*cos(a0);
ay=y+R*sin(a0);
bx=x+R*cos(a1);
by=y+R*sin(a1);
x*=zoom; y*=zoom; r*=zoom;
ax*=zoom; ay*=zoom;
bx*=zoom; by*=zoom;
bmp->Canvas->Pen->Color=clBlue;
bmp->Canvas->Brush->Color=TColor(0x00101010);
bmp->Canvas->Ellipse(x-r,y-r,x+r,y+r);
bmp->Canvas->Pen->Color=clAqua;
bmp->Canvas->Brush->Color=TColor(0x00202020);
bmp->Canvas->Pie(x-r,y-r,x+r,y+r,ax,ay,bx,by);
// PCL
r=2.0;
bmp->Canvas->Pen->Color=clAqua;
bmp->Canvas->Brush->Color=clAqua;
for (i=0;i<n;)
{
x=p[i]; i++;
y=p[i]; i++;
x*=zoom; y*=zoom;
bmp->Canvas->Ellipse(x-r,y-r,x+r,y+r);
}
// render backbuffer
Main->Canvas->Draw(0,0,bmp);
}
//---------------------------------------------------------------------------
You can ignore the void TMain::draw() function its just example of usage and this is the preview:
However as you have polygon (lines) to avoid wrong results You have two simple options:
sample lines with more than just 2 points
this way the angular gap should be bigger than distances between points in the point cloud. So if you sample lines with enough points the result will be correct. However wrongly selected number of points per line will lead to wrong result in edge cases. On the other hand implementing this is just simple DDA interpolation added to current code.
convert to handling angle intervals instead of angles a[]
so for each line compute angular interval <a0,a1> with predefined poygon winding rule (so CW or CCW but consistent). And instead of array a[] you would have ordered list of intervals where you would either insert new interval or merge with existing if overlap. This approach is safe but merging angular intervals is not that easy. If the input data is polyline (like yours) that means each next line start from the previous line endpoint so you can ignore the list of intervals and just enlarge the single one instead but still you need to handle the enlargement correctly which is not trivial.
[Edit1] using the first approach the updated function look like this:
void get_sector_pol(float x0,float y0,float r,float *p,int n,float &a0,float &a1)
{
// x0,y0 circle center
// r circle radius
// p[n] point cloud
// a0,a1 output angle range a0<=a1
int i,j,k,N=10,m=(n>>1)*N;
float ax,ay,bx,by,x,y,dx,dy,*a,_N=1.0/N;
a=new float[m];
// process points and compute angles
bx=p[n-2]-x0; i++;
by=p[n-1]-y0; i++;
for (j=0,i=0;i<n;)
{
ax=bx; ay=by;
bx=p[i]-x0; i++;
by=p[i]-y0; i++;
dx=_N*(bx-ax); x=ax;
dy=_N*(by-ay); y=ay;
for (k=0;k<N;k++,x+=dx,y+=dy,j++) a[j]=atan2(y,x);
}
// sort by angle
sort_asc_bubble(a,m);
// get max distance
a0=a[m-1]; a1=a[0]; x=a1-a0;
while (x<-M_PI) x+=2.0*M_PI;
while (x>+M_PI) x-=2.0*M_PI;
if (x<0.0) x=-x;
for (j=1;j<m;j++)
{
y=a[j]-a[j-1];
while (y<-M_PI) y+=2.0*M_PI;
while (y>+M_PI) y-=2.0*M_PI;
if (y<0.0) y=-y;
if (y>x){ a0=a[j-1]; a1=a[j]; x=y; }
}
}
As you can see its almost the same just simple DDA is added to the first loop win N points per line. Here preview for the second polygon which fails with just point cloud approach:

Bezier along Terrain Surface

What’d be the best way to go, if i paint a bezier Curve (set start and endpoints) to Unity Terrain, and i want the curve to folow the ups and downs from the ground.
right now i partly achieve it like this,(need to connect the new Points from groundedPoints, as new Beziers)
int SegmentCount = Mathf.Floor(BezierLength / SegmentLength);
//Rounded to the next lower integer
var groundedPoints = new List<Vector3>();
for(int i =0; i<SegmentCount;i++){
Vector3 p = GetPoint(BezierPoints,i / SegmentCount);
p = p.RayCastDown();
//RayCasting Down to get the Point on the Terrain
if(i == 0 || i < SegmentCount -1){
groundedPoints.Add(p);
}else{
if(p.y != groundedPoints[groundedPoints.Count-1].y){
groundedPoints.Add(p);
}
}
}
it’s right now kind of not that accurate, but it, doesn’t have to be a real accurate solution.
Maybe someone can give me a hint? thanks
Firstly i would recommend using Centripetal Catmull–Rom spline because it follows points more strictly, and need less points to generate(also only draws between p1 and p2), but i dont know what you want to achieve so:
I would transform your bezier into a 2d bezier, and only work in 2d space with it, then when you draw(render it visually) you give it a Y value by using https://docs.unity3d.com/ScriptReference/Terrain.SampleHeight.html
I do this with my splines, and it gives a quite accurate spline in the end(road generation)
PLEASE NOTE!:
That the implicit Vector2 and Vector3 conversion will not fit your needs, you need to add an extension method to convert Vector3 to Vector2 :)
(Vector(x,y,z) will be Vector(x,y) but you need Vector(x,z))
Edit 1:
Codesample how to read out a terrain actual height, via Terrain.SampleHeight(); by a Vector2 coordinate that you are sure is above a terrain, if the Vector2 is not above the terrain it will give you back null or the closets terrain height to it im not sure which one atm(can't test it now) :)
public static float GetPoint_On_Terrain(Vector2 point){
float terrainHeightAtPoint = Terrain.activeTerrain.SampleHeight(new Vector3(point.x, 0, point.y));
return new Vector3(point.x, terrainHeightAtPoint,point.y);
}

Getting OBB-Triangle collision detection right in OpenGL engine

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.

Trying to accurately measure 3D distance from a 2D image

I am trying to extract out 3D distance in mm between two known points in a 2D image. I am using square AR markers in order to get the camera coordinates relative to the markers in the scene. The points are the corners of these markers.
An example is shown below:
The code is written in C# and I am using XNA. I am using AForge.net for the CoPlanar POSIT
The steps I take in order to work out the distance:
1. Mark corners on screen. Corners are represented in 2D vector form, Image centre is (0,0). Up is positive in the Y direction, right is positive in the X direction.
2. Use AForge.net Co-Planar POSIT algorithm to get pose of each marker:
float focalLength = 640; //Needed for POSIT
float halfCornerSize = 50; //Represents 1/2 an edge i.e. 50mm
AVector[] modelPoints = new AVector3[]
{
new AVector3( -halfCornerSize, 0, halfCornerSize ),
new AVector3( halfCornerSize, 0, halfCornerSize ),
new AVector3( halfCornerSize, 0, -halfCornerSize ),
new AVector3( -halfCornerSize, 0, -halfCornerSize ),
};
CoplanarPosit coPosit = new CoplanarPosit(modelPoints, focalLength);
coPosit.EstimatePose(cornersToEstimate, out marker1Rot, out marker1Trans);
3. Convert to XNA rotation/translation matrix (AForge uses OpenGL matrix form):
float yaw, pitch, roll;
marker1Rot.ExtractYawPitchRoll(out yaw, out pitch, out roll);
Matrix xnaRot = Matrix.CreateFromYawPitchRoll(-yaw, -pitch, roll);
Matrix xnaTranslation = Matrix.CreateTranslation(marker1Trans.X, marker1Trans.Y, -marker1Trans.Z);
Matrix transform = xnaRot * xnaTranslation;
4. Find 3D coordinates of the corners:
//Model corner points
cornerModel = new Vector3[]
{
new Vector3(halfCornerSize,0,-halfCornerSize),
new Vector3(-halfCornerSize,0,-halfCornerSize),
new Vector3(halfCornerSize,0,halfCornerSize),
new Vector3(-halfCornerSize,0,halfCornerSize)
};
Matrix markerTransform = Matrix.CreateTranslation(cornerModel[i].X, cornerModel[i].Y, cornerModel[i].Z);
cornerPositions3d1[i] = (markerTransform * transform).Translation;
//DEBUG: project corner onto screen - represented by brown dots
Vector3 t3 = viewPort.Project(markerTransform.Translation, projectionMatrix, viewMatrix, transform);
cornersProjected1[i].X = t3.X; cornersProjected1[i].Y = t3.Y;
5. Look at the 3D distance between two corners on a marker, this represents 100mm. Find the scaling factor needed to convert this 3D distance to 100mm. (I actually get the average scaling factor):
for (int i = 0; i < 4; i++)
{
//Distance scale;
distanceScale1 += (halfCornerSize * 2) / Vector3.Distance(cornerPositions3d1[i], cornerPositions3d1[(i + 1) % 4]);
}
distanceScale1 /= 4;
6. Finally I find the 3D distance between related corners and multiply by the scaling factor to get distance in mm:
for(int i = 0; i < 4; i++)
{
distance[i] = Vector3.Distance(cornerPositions3d1[i], cornerPositions3d2[i]) * scalingFactor;
}
The distances acquired are never truly correct. I used the cutting board as it allowed me easy calculation of what the distances should be. The above image calculated a distance of 147mm (expected 150mm) for corner 1 (red to purple). The image below shows 188mm (expected 200mm).
What is also worrying is the fact that when measuring the distance between marker corners sharing an edge on the same marker, the 3D distances obtained are never the same. Another thing I noticed is that the brown dots never seem to exactly match up with the colored dots. The colored dots are the coordinates used as input to the CoPlanar posit. The brown dots are the calculated positions from the center of the marker calculated via POSIT.
Does anyone have any idea what might be wrong here? I am pulling out my hair trying to figure it out. The code should be quite simple, I don't think I have made any obvious mistakes with the code. I am not great at maths so please point out where my basic maths might be wrong as well...
You are using way to many black boxes in your question. What is the focal length in the second step? Why go through ypr in step 3? How do you calibrate? I recommend to start over from scratch without using libraries that you do not understand.
Step 1: Create a camera model. Understand the errors, build a projection. If needed apply a 2d filter for lens distortion. This might be hard.
Step 2: Find you markers in 2d, after removing lens distortion. Make sure you know the error and that you get the center. Maybe over multiple frames.
Step 3: Un-project to 3d. After 1 and 2 this should be easy.
Step 4: ???
Step 5: Profit! (Measure distance in 3d and know your error)
I think you need to have 3D photo (two photo from a set of distance) so you can get the parallax distance from image differences

Categories