protected override bool ConfigureTriangle(int i, TriangleShape localTriangleShape, out TriangleIndices indices) { int triangleIndex = overlappedTriangles.Elements[i]; mesh.Mesh.Data.GetTriangle(triangleIndex, out localTriangleShape.vA, out localTriangleShape.vB, out localTriangleShape.vC); localTriangleShape.sidedness = mesh.sidedness; localTriangleShape.collisionMargin = 0; indices = new TriangleIndices { A = mesh.Mesh.Data.indices[triangleIndex], B = mesh.Mesh.Data.indices[triangleIndex + 1], C = mesh.Mesh.Data.indices[triangleIndex + 2] }; return true; }
//Relies on the triangle being located in the local space of the convex object. The convex transform is used to transform the //contact points back from the convex's local space into world space. ///<summary> /// Generates a contact between the triangle and convex. ///</summary> /// <param name="triangle">Triangle to test the convex against. The input triangle should be transformed into the local space of the convex.</param> ///<param name="contactList">Contact between the shapes, if any.</param> ///<returns>Whether or not the shapes are colliding.</returns> public override bool GenerateContactCandidates(TriangleShape triangle, out TinyStructList<ContactData> contactList) { switch (state) { case CollisionState.Plane: return DoPlaneTest(triangle, out contactList); case CollisionState.ExternalSeparated: return DoExternalSeparated(triangle, out contactList); case CollisionState.ExternalNear: return DoExternalNear(triangle, out contactList); case CollisionState.Deep: return DoDeepContact(triangle, out contactList); default: contactList = new TinyStructList<ContactData>(); return false; } }
protected override bool ConfigureLocalTriangle(int i, TriangleShape localTriangleShape, out TriangleIndices indices) { int triangleIndex = overlappedTriangles.Elements[i]; var data = mesh.Mesh.Data; localTriangleShape.vA = data.vertices[data.indices[triangleIndex]]; localTriangleShape.vB = data.vertices[data.indices[triangleIndex + 1]]; localTriangleShape.vC = data.vertices[data.indices[triangleIndex + 2]]; localTriangleShape.sidedness = mesh.sidedness; localTriangleShape.collisionMargin = 0; indices = new TriangleIndices { A = data.indices[triangleIndex], B = data.indices[triangleIndex + 1], C = data.indices[triangleIndex + 2] }; return true; }
//TODO: Having a specialized triangle-triangle pair test would be nice. Even if it didn't use an actual triangle-triangle test, certain assumptions could still make it speedier and more elegant. //"Closest points between triangles" + persistent manifolding would probably be the best approach (a lot faster than the triangle-convex general case anyway). public override bool GenerateContactCandidates(TriangleShape triangle, out TinyStructList<ContactData> contactList) { if (base.GenerateContactCandidates(triangle, out contactList)) { //The triangle-convex pair test has already rejected contacts whose normals would violate the first triangle's sidedness. //However, since it's a vanilla triangle-convex test, it doesn't know about the sidedness of the other triangle! var shape = ((TriangleShape)convex); Vector3 normal; //Lots of recalculating ab-bc! Vector3 ab, ac; Vector3.Subtract(ref shape.vB, ref shape.vA, out ab); Vector3.Subtract(ref shape.vC, ref shape.vA, out ac); Vector3.Cross(ref ab, ref ac, out normal); var sidedness = shape.sidedness; if (sidedness != TriangleSidedness.DoubleSided) { for (int i = contactList.Count - 1; i >= 0; i--) { ContactData item; contactList.Get(i, out item); float dot; Vector3.Dot(ref item.Normal, ref normal, out dot); if (sidedness == TriangleSidedness.Clockwise) { if (dot < 0) { contactList.RemoveAt(i); } } else { if (dot > 0) { contactList.RemoveAt(i); } } } } return contactList.Count > 0; } return false; }
protected override bool ConfigureLocalTriangle(int i, TriangleShape localTriangleShape, out TriangleIndices indices) { TerrainVertexIndices a, b, c; terrain.Shape.GetLocalIndices(overlappedTriangles[i], out a, out b, out c); int terrainWidth = terrain.Shape.Heights.GetLength(0); indices.A = a.ToSequentialIndex(terrainWidth); indices.B = b.ToSequentialIndex(terrainWidth); indices.C = c.ToSequentialIndex(terrainWidth); terrain.Shape.GetLocalPosition(a.ColumnIndex, a.RowIndex, out localTriangleShape.vA); terrain.Shape.GetLocalPosition(b.ColumnIndex, b.RowIndex, out localTriangleShape.vB); terrain.Shape.GetLocalPosition(c.ColumnIndex, c.RowIndex, out localTriangleShape.vC); localTriangleShape.collisionMargin = 0; localTriangleShape.sidedness = terrain.sidedness; //Unlike other 'instanced' geometries, terrains are almost always axis aligned in some way and/or have low triangle density relative to what they are colliding with. //Instead of performing additional tests, just assume that it's a fairly regular situation. return true; }
protected override bool ConfigureLocalTriangle(int i, TriangleShape localTriangleShape, out TriangleIndices indices) { MeshBoundingBoxTreeData data = mesh.Shape.TriangleMesh.Data; int triangleIndex = overlappedTriangles.Elements[i]; localTriangleShape.sidedness = mesh.sidedness; localTriangleShape.collisionMargin = 0; indices = new TriangleIndices { A = data.indices[triangleIndex], B = data.indices[triangleIndex + 1], C = data.indices[triangleIndex + 2] }; localTriangleShape.vA = data.vertices[indices.A]; localTriangleShape.vB = data.vertices[indices.B]; localTriangleShape.vC = data.vertices[indices.C]; return true; }
///<summary> /// Initializes the pair tester. ///</summary> ///<param name="convex">Convex shape to use.</param> ///<param name="triangle">Triangle shape to use.</param> public override void Initialize(ConvexShape convex, TriangleShape triangle) { this.convex = convex; this.triangle = triangle; }
///<summary> /// Initializes the pair tester. ///</summary> ///<param name="convex">Convex shape to use.</param> ///<param name="triangle">Triangle shape to use.</param> public abstract void Initialize(ConvexShape convex, TriangleShape triangle);
/// <summary> /// Gets the triangle region in which the contact resides. /// </summary> /// <param name="triangle">Triangle to compare the contact against.</param> /// <param name="contact">Contact to check.</param> /// <returns>Region in which the contact resides.</returns> public abstract VoronoiRegion GetRegion(TriangleShape triangle, ref ContactData contact);
//Relies on the triangle being located in the local space of the convex object. The convex transform is used to transform the //contact points back from the convex's local space into world space. ///<summary> /// Generates a contact between the triangle and convex. ///</summary> ///<param name="contactList">Contact between the shapes, if any.</param> ///<returns>Whether or not the shapes are colliding.</returns> public override bool GenerateContactCandidates(TriangleShape triangle, out TinyStructList<ContactData> contactList) { contactList = new TinyStructList<ContactData>(); System.Numerics.Vector3 ab, ac; Vector3Ex.Subtract(ref triangle.vB, ref triangle.vA, out ab); Vector3Ex.Subtract(ref triangle.vC, ref triangle.vA, out ac); System.Numerics.Vector3 triangleNormal; Vector3Ex.Cross(ref ab, ref ac, out triangleNormal); if (triangleNormal.LengthSquared() < Toolbox.Epsilon * .01f) { //If the triangle is degenerate, use the offset between its center and the sphere. Vector3Ex.Add(ref triangle.vA, ref triangle.vB, out triangleNormal); Vector3Ex.Add(ref triangleNormal, ref triangle.vC, out triangleNormal); Vector3Ex.Multiply(ref triangleNormal, 1 / 3f, out triangleNormal); if (triangleNormal.LengthSquared() < Toolbox.Epsilon * .01f) triangleNormal = Toolbox.UpVector; //Alrighty then! Pick a random direction. } float dot; Vector3Ex.Dot(ref triangleNormal, ref triangle.vA, out dot); switch (triangle.sidedness) { case TriangleSidedness.DoubleSided: if (dot < 0) Vector3Ex.Negate(ref triangleNormal, out triangleNormal); //Normal must face outward. break; case TriangleSidedness.Clockwise: if (dot > 0) return false; //Wrong side, can't have a contact pointing in a reasonable direction. break; case TriangleSidedness.Counterclockwise: if (dot < 0) return false; //Wrong side, can't have a contact pointing in a reasonable direction. break; } System.Numerics.Vector3 closestPoint; //Could optimize this process a bit. The 'point' being compared is always zero. Additionally, since the triangle normal is available, //there is a little extra possible optimization. lastRegion = Toolbox.GetClosestPointOnTriangleToPoint(ref triangle.vA, ref triangle.vB, ref triangle.vC, ref Toolbox.ZeroVector, out closestPoint); float lengthSquared = closestPoint.LengthSquared(); float marginSum = triangle.collisionMargin + sphere.collisionMargin; if (lengthSquared <= marginSum * marginSum) { var contact = new ContactData(); if (lengthSquared < Toolbox.Epsilon) { //Super close to the triangle. Normalizing would be dangerous. Vector3Ex.Negate(ref triangleNormal, out contact.Normal); contact.Normal.Normalize(); contact.PenetrationDepth = marginSum; contactList.Add(ref contact); return true; } lengthSquared = (float)Math.Sqrt(lengthSquared); Vector3Ex.Divide(ref closestPoint, lengthSquared, out contact.Normal); contact.PenetrationDepth = marginSum - lengthSquared; contact.Position = closestPoint; contactList.Add(ref contact); return true; } return false; }
public override VoronoiRegion GetRegion(TriangleShape triangle, ref ContactData contact) { return lastRegion; }
protected override bool ConfigureTriangle(int i, TriangleShape localTriangleShape, out TriangleIndices indices) { MeshBoundingBoxTreeData data = mesh.Shape.TriangleMesh.Data; int triangleIndex = overlappedTriangles.Elements[i]; data.GetTriangle(triangleIndex, out localTriangleShape.vA, out localTriangleShape.vB, out localTriangleShape.vC); AffineTransform.Transform(ref localTriangleShape.vA, ref mesh.worldTransform, out localTriangleShape.vA); AffineTransform.Transform(ref localTriangleShape.vB, ref mesh.worldTransform, out localTriangleShape.vB); AffineTransform.Transform(ref localTriangleShape.vC, ref mesh.worldTransform, out localTriangleShape.vC); //In instanced meshes, the bounding box we found in local space could collect more triangles than strictly necessary. //By doing a second pass, we should be able to prune out quite a few of them. BoundingBox triangleAABB; Toolbox.GetTriangleBoundingBox(ref localTriangleShape.vA, ref localTriangleShape.vB, ref localTriangleShape.vC, out triangleAABB); bool toReturn; triangleAABB.Intersects(ref convex.boundingBox, out toReturn); if (!toReturn) { indices = new TriangleIndices(); return false; } localTriangleShape.sidedness = mesh.sidedness; localTriangleShape.collisionMargin = 0; indices = new TriangleIndices() { A = data.indices[triangleIndex], B = data.indices[triangleIndex + 1], C = data.indices[triangleIndex + 2] }; return true; }
protected override bool ConfigureTriangle(int i, TriangleShape localTriangleShape, out TriangleIndices indices) { indices = overlappedTriangles.Elements[i]; terrain.Shape.GetTriangle(ref indices, ref terrain.worldTransform, out localTriangleShape.vA, out localTriangleShape.vB, out localTriangleShape.vC); localTriangleShape.collisionMargin = 0; //Calibrate the sidedness of the triangle such that it's always facing up. //TODO: There's quite a bit of redundancy in here with other systems. Vector3 AB, AC, normal; Vector3.Subtract(ref localTriangleShape.vB, ref localTriangleShape.vA, out AB); Vector3.Subtract(ref localTriangleShape.vC, ref localTriangleShape.vA, out AC); Vector3.Cross(ref AB, ref AC, out normal); Vector3 terrainUp = new Vector3(terrain.worldTransform.LinearTransform.M21, terrain.worldTransform.LinearTransform.M22, terrain.worldTransform.LinearTransform.M23); float dot; Vector3.Dot(ref terrainUp, ref normal, out dot); if (dot > 0) { localTriangleShape.sidedness = TriangleSidedness.Clockwise; } else { localTriangleShape.sidedness = TriangleSidedness.Counterclockwise; } //Unlike other 'instanced' geometries, terrains are almost always axis aligned in some way and/or have low triangle density relative to what they are colliding with. //Instead of performing additional tests, just assume that it's a fairly regular situation. return true; }
void TryToEscape(TriangleShape triangle, ref System.Numerics.Vector3 position) { if (++escapeAttempts == EscapeAttemptPeriod && GetVoronoiRegion(triangle, ref position) == VoronoiRegion.ABC) { escapeAttempts = 0; state = CollisionState.Plane; } }
private bool DoDeepContact(TriangleShape triangle, out TinyStructList<ContactData> contactList) { //Find the origin to triangle center offset. System.Numerics.Vector3 center; Vector3Ex.Add(ref triangle.vA, ref triangle.vB, out center); Vector3Ex.Add(ref center, ref triangle.vC, out center); Vector3Ex.Multiply(ref center, 1f / 3f, out center); ContactData contact; contactList = new TinyStructList<ContactData>(); if (MPRToolbox.AreLocalShapesOverlapping(convex, triangle, ref center, ref Toolbox.RigidIdentity)) { float dot; System.Numerics.Vector3 triangleNormal, ab, ac; Vector3Ex.Subtract(ref triangle.vB, ref triangle.vA, out ab); Vector3Ex.Subtract(ref triangle.vC, ref triangle.vA, out ac); Vector3Ex.Cross(ref ab, ref ac, out triangleNormal); float lengthSquared = triangleNormal.LengthSquared(); if (lengthSquared < Toolbox.Epsilon * .01f) { //Degenerate triangle! That's no good. //Just use the direction pointing from A to B, "B" being the triangle. That direction is center - origin, or just center. MPRToolbox.LocalSurfaceCast(convex, triangle, ref Toolbox.RigidIdentity, ref center, out contact.PenetrationDepth, out contact.Normal, out contact.Position); } else { //Normalize the normal. Vector3Ex.Divide(ref triangleNormal, (float)Math.Sqrt(lengthSquared), out triangleNormal); //TODO: This tests all three edge axes with a full MPR raycast. That's not really necessary; the correct edge normal should be discoverable, resulting in a single MPR raycast. //Find the edge directions that will be tested with MPR. System.Numerics.Vector3 AO, BO, CO; System.Numerics.Vector3 AB, BC, CA; Vector3Ex.Subtract(ref center, ref triangle.vA, out AO); Vector3Ex.Subtract(ref center, ref triangle.vB, out BO); Vector3Ex.Subtract(ref center, ref triangle.vC, out CO); Vector3Ex.Subtract(ref triangle.vB, ref triangle.vA, out AB); Vector3Ex.Subtract(ref triangle.vC, ref triangle.vB, out BC); Vector3Ex.Subtract(ref triangle.vA, ref triangle.vC, out CA); //We don't have to worry about degenerate triangles here because we've already handled that possibility above. System.Numerics.Vector3 ABnormal, BCnormal, CAnormal; //Project the center onto the edge to find the direction from the center to the edge AB. Vector3Ex.Dot(ref AO, ref AB, out dot); Vector3Ex.Multiply(ref AB, dot / AB.LengthSquared(), out ABnormal); Vector3Ex.Subtract(ref AO, ref ABnormal, out ABnormal); ABnormal.Normalize(); //Project the center onto the edge to find the direction from the center to the edge BC. Vector3Ex.Dot(ref BO, ref BC, out dot); Vector3Ex.Multiply(ref BC, dot / BC.LengthSquared(), out BCnormal); Vector3Ex.Subtract(ref BO, ref BCnormal, out BCnormal); BCnormal.Normalize(); //Project the center onto the edge to find the direction from the center to the edge BC. Vector3Ex.Dot(ref CO, ref CA, out dot); Vector3Ex.Multiply(ref CA, dot / CA.LengthSquared(), out CAnormal); Vector3Ex.Subtract(ref CO, ref CAnormal, out CAnormal); CAnormal.Normalize(); MPRToolbox.LocalSurfaceCast(convex, triangle, ref Toolbox.RigidIdentity, ref ABnormal, out contact.PenetrationDepth, out contact.Normal); //Check to see if the normal is facing in the proper direction, considering that this may not be a two-sided triangle. Vector3Ex.Dot(ref triangleNormal, ref contact.Normal, out dot); if ((triangle.sidedness == TriangleSidedness.Clockwise && dot > 0) || (triangle.sidedness == TriangleSidedness.Counterclockwise && dot < 0)) { //Normal was facing the wrong way. //Instead of ignoring it entirely, correct the direction to as close as it can get by removing any component parallel to the triangle normal. System.Numerics.Vector3 previousNormal = contact.Normal; Vector3Ex.Dot(ref contact.Normal, ref triangleNormal, out dot); System.Numerics.Vector3 p; Vector3Ex.Multiply(ref contact.Normal, dot, out p); Vector3Ex.Subtract(ref contact.Normal, ref p, out contact.Normal); float length = contact.Normal.LengthSquared(); if (length > Toolbox.Epsilon) { //Renormalize the corrected normal. Vector3Ex.Divide(ref contact.Normal, (float)Math.Sqrt(length), out contact.Normal); Vector3Ex.Dot(ref contact.Normal, ref previousNormal, out dot); contact.PenetrationDepth *= dot; } else { contact.PenetrationDepth = float.MaxValue; contact.Normal = new System.Numerics.Vector3(); } } System.Numerics.Vector3 candidateNormal; float candidateDepth; MPRToolbox.LocalSurfaceCast(convex, triangle, ref Toolbox.RigidIdentity, ref BCnormal, out candidateDepth, out candidateNormal); //Check to see if the normal is facing in the proper direction, considering that this may not be a two-sided triangle. Vector3Ex.Dot(ref triangleNormal, ref candidateNormal, out dot); if ((triangle.sidedness == TriangleSidedness.Clockwise && dot > 0) || (triangle.sidedness == TriangleSidedness.Counterclockwise && dot < 0)) { //Normal was facing the wrong way. //Instead of ignoring it entirely, correct the direction to as close as it can get by removing any component parallel to the triangle normal. System.Numerics.Vector3 previousNormal = candidateNormal; Vector3Ex.Dot(ref candidateNormal, ref triangleNormal, out dot); System.Numerics.Vector3 p; Vector3Ex.Multiply(ref candidateNormal, dot, out p); Vector3Ex.Subtract(ref candidateNormal, ref p, out candidateNormal); float length = candidateNormal.LengthSquared(); if (length > Toolbox.Epsilon) { //Renormalize the corrected normal. Vector3Ex.Divide(ref candidateNormal, (float)Math.Sqrt(length), out candidateNormal); Vector3Ex.Dot(ref candidateNormal, ref previousNormal, out dot); candidateDepth *= dot; } else { contact.PenetrationDepth = float.MaxValue; contact.Normal = new System.Numerics.Vector3(); } } if (candidateDepth < contact.PenetrationDepth) { contact.Normal = candidateNormal; contact.PenetrationDepth = candidateDepth; } MPRToolbox.LocalSurfaceCast(convex, triangle, ref Toolbox.RigidIdentity, ref CAnormal, out candidateDepth, out candidateNormal); //Check to see if the normal is facing in the proper direction, considering that this may not be a two-sided triangle. Vector3Ex.Dot(ref triangleNormal, ref candidateNormal, out dot); if ((triangle.sidedness == TriangleSidedness.Clockwise && dot > 0) || (triangle.sidedness == TriangleSidedness.Counterclockwise && dot < 0)) { //Normal was facing the wrong way. //Instead of ignoring it entirely, correct the direction to as close as it can get by removing any component parallel to the triangle normal. System.Numerics.Vector3 previousNormal = candidateNormal; Vector3Ex.Dot(ref candidateNormal, ref triangleNormal, out dot); System.Numerics.Vector3 p; Vector3Ex.Multiply(ref candidateNormal, dot, out p); Vector3Ex.Subtract(ref candidateNormal, ref p, out candidateNormal); float length = candidateNormal.LengthSquared(); if (length > Toolbox.Epsilon) { //Renormalize the corrected normal. Vector3Ex.Divide(ref candidateNormal, (float)Math.Sqrt(length), out candidateNormal); Vector3Ex.Dot(ref candidateNormal, ref previousNormal, out dot); candidateDepth *= dot; } else { contact.PenetrationDepth = float.MaxValue; contact.Normal = new System.Numerics.Vector3(); } } if (candidateDepth < contact.PenetrationDepth) { contact.Normal = candidateNormal; contact.PenetrationDepth = candidateDepth; } //Try the depth along the positive triangle normal. //If it's clockwise, this direction is unnecessary (the resulting normal would be invalidated by the onesidedness of the triangle). if (triangle.sidedness != TriangleSidedness.Clockwise) { MPRToolbox.LocalSurfaceCast(convex, triangle, ref Toolbox.RigidIdentity, ref triangleNormal, out candidateDepth, out candidateNormal); if (candidateDepth < contact.PenetrationDepth) { contact.Normal = candidateNormal; contact.PenetrationDepth = candidateDepth; } } //Try the depth along the negative triangle normal. //If it's counterclockwise, this direction is unnecessary (the resulting normal would be invalidated by the onesidedness of the triangle). if (triangle.sidedness != TriangleSidedness.Counterclockwise) { Vector3Ex.Negate(ref triangleNormal, out triangleNormal); MPRToolbox.LocalSurfaceCast(convex, triangle, ref Toolbox.RigidIdentity, ref triangleNormal, out candidateDepth, out candidateNormal); if (candidateDepth < contact.PenetrationDepth) { contact.Normal = candidateNormal; contact.PenetrationDepth = candidateDepth; } } } MPRToolbox.RefinePenetration(convex, triangle, ref Toolbox.RigidIdentity, contact.PenetrationDepth, ref contact.Normal, out contact.PenetrationDepth, out contact.Normal, out contact.Position); //It's possible for the normal to still face the 'wrong' direction according to one sided triangles. if (triangle.sidedness != TriangleSidedness.DoubleSided) { Vector3Ex.Dot(ref triangleNormal, ref contact.Normal, out dot); if (dot < 0) { //Skip the add process. goto InnerSphere; } } contact.Id = -1; if (contact.PenetrationDepth < convex.collisionMargin + triangle.collisionMargin) { state = CollisionState.ExternalNear; //If it's emerged from the deep contact, we can go back to using the preferred GJK method. } contactList.Add(ref contact); } InnerSphere: if (TryInnerSphereContact(triangle, out contact)) { contactList.Add(ref contact); } if (contactList.Count > 0) return true; state = CollisionState.ExternalSeparated; return false; }
public override VoronoiRegion GetRegion(TriangleShape triangle, ref ContactData contact) { //Deep contact can produce non-triangle normals while still being within the triangle. //To solve this problem, find the voronoi region to which the contact belongs using its normal. //The voronoi region will be either the most extreme vertex, or the edge that includes //the first and second most extreme vertices. //If the normal dotted with an extreme edge direction is near 0, then it belongs to the edge. //Otherwise, it belongs to the vertex. //MPR tends to produce 'approximate' normals, though. //Use a fairly forgiving epsilon. float dotA, dotB, dotC; Vector3Ex.Dot(ref triangle.vA, ref contact.Normal, out dotA); Vector3Ex.Dot(ref triangle.vB, ref contact.Normal, out dotB); Vector3Ex.Dot(ref triangle.vC, ref contact.Normal, out dotC); //Since normal points from convex to triangle always, reverse dot signs. dotA = -dotA; dotB = -dotB; dotC = -dotC; float faceEpsilon = .01f; const float edgeEpsilon = .01f; float edgeDot; System.Numerics.Vector3 edgeDirection; if (dotA > dotB && dotA > dotC) { //A is extreme. if (dotB > dotC) { //B is second most extreme. if (Math.Abs(dotA - dotC) < faceEpsilon) { //The normal is basically a face normal. This can happen at the edges occasionally. return VoronoiRegion.ABC; } else { Vector3Ex.Subtract(ref triangle.vB, ref triangle.vA, out edgeDirection); Vector3Ex.Dot(ref edgeDirection, ref contact.Normal, out edgeDot); if (edgeDot * edgeDot < edgeDirection.LengthSquared() * edgeEpsilon) return VoronoiRegion.AB; else return VoronoiRegion.A; } } else { //C is second most extreme. if (Math.Abs(dotA - dotB) < faceEpsilon) { //The normal is basically a face normal. This can happen at the edges occasionally. return VoronoiRegion.ABC; } else { Vector3Ex.Subtract(ref triangle.vC, ref triangle.vA, out edgeDirection); Vector3Ex.Dot(ref edgeDirection, ref contact.Normal, out edgeDot); if (edgeDot * edgeDot < edgeDirection.LengthSquared() * edgeEpsilon) return VoronoiRegion.AC; else return VoronoiRegion.A; } } } else if (dotB > dotC) { //B is extreme. if (dotC > dotA) { //C is second most extreme. if (Math.Abs(dotB - dotA) < faceEpsilon) { //The normal is basically a face normal. This can happen at the edges occasionally. return VoronoiRegion.ABC; } else { Vector3Ex.Subtract(ref triangle.vC, ref triangle.vB, out edgeDirection); Vector3Ex.Dot(ref edgeDirection, ref contact.Normal, out edgeDot); if (edgeDot * edgeDot < edgeDirection.LengthSquared() * edgeEpsilon) return VoronoiRegion.BC; else return VoronoiRegion.B; } } else { //A is second most extreme. if (Math.Abs(dotB - dotC) < faceEpsilon) { //The normal is basically a face normal. This can happen at the edges occasionally. return VoronoiRegion.ABC; } else { Vector3Ex.Subtract(ref triangle.vA, ref triangle.vB, out edgeDirection); Vector3Ex.Dot(ref edgeDirection, ref contact.Normal, out edgeDot); if (edgeDot * edgeDot < edgeDirection.LengthSquared() * edgeEpsilon) return VoronoiRegion.AB; else return VoronoiRegion.B; } } } else { //C is extreme. if (dotA > dotB) { //A is second most extreme. if (Math.Abs(dotC - dotB) < faceEpsilon) { //The normal is basically a face normal. This can happen at the edges occasionally. return VoronoiRegion.ABC; } else { Vector3Ex.Subtract(ref triangle.vA, ref triangle.vC, out edgeDirection); Vector3Ex.Dot(ref edgeDirection, ref contact.Normal, out edgeDot); if (edgeDot * edgeDot < edgeDirection.LengthSquared() * edgeEpsilon) return VoronoiRegion.AC; else return VoronoiRegion.C; } } else { //B is second most extreme. if (Math.Abs(dotC - dotA) < faceEpsilon) { //The normal is basically a face normal. This can happen at the edges occasionally. return VoronoiRegion.ABC; } else { Vector3Ex.Subtract(ref triangle.vB, ref triangle.vC, out edgeDirection); Vector3Ex.Dot(ref edgeDirection, ref contact.Normal, out edgeDot); if (edgeDot * edgeDot < edgeDirection.LengthSquared() * edgeEpsilon) return VoronoiRegion.BC; else return VoronoiRegion.C; } } } }
private bool DoExternalNear(TriangleShape triangle, out TinyStructList<ContactData> contactList) { System.Numerics.Vector3 closestA, closestB; //Don't bother trying to do any clever caching. The continually transforming simplex makes it very rarely useful. //TODO: Initialize the simplex of the GJK method using the 'true' center of the triangle. //If left unmodified, the simplex that is used in GJK will just be a point at 0,0,0, which of course is at the origin. //This causes an instant-out, always. Not good! //By giving the contributing simplex the average centroid, it has a better guess. System.Numerics.Vector3 triangleCentroid; Vector3Ex.Add(ref triangle.vA, ref triangle.vB, out triangleCentroid); Vector3Ex.Add(ref triangleCentroid, ref triangle.vC, out triangleCentroid); Vector3Ex.Multiply(ref triangleCentroid, .33333333f, out triangleCentroid); var initialSimplex = new CachedSimplex { State = SimplexState.Point, LocalSimplexB = { A = triangleCentroid } }; if (GJKToolbox.GetClosestPoints(convex, triangle, ref Toolbox.RigidIdentity, ref Toolbox.RigidIdentity, ref initialSimplex, out closestA, out closestB)) { state = CollisionState.Deep; return DoDeepContact(triangle, out contactList); } System.Numerics.Vector3 displacement; Vector3Ex.Subtract(ref closestB, ref closestA, out displacement); float distanceSquared = displacement.LengthSquared(); float margin = convex.collisionMargin + triangle.collisionMargin; contactList = new TinyStructList<ContactData>(); if (distanceSquared < margin * margin) { //Try to generate a contact. var contact = new ContactData(); //Determine if the normal points in the appropriate direction given the sidedness of the triangle. if (triangle.sidedness != TriangleSidedness.DoubleSided) { System.Numerics.Vector3 triangleNormal, ab, ac; Vector3Ex.Subtract(ref triangle.vB, ref triangle.vA, out ab); Vector3Ex.Subtract(ref triangle.vC, ref triangle.vA, out ac); Vector3Ex.Cross(ref ab, ref ac, out triangleNormal); float dot; Vector3Ex.Dot(ref triangleNormal, ref displacement, out dot); if (triangle.sidedness == TriangleSidedness.Clockwise && dot > 0) return false; if (triangle.sidedness == TriangleSidedness.Counterclockwise && dot < 0) return false; } //Displacement is from A to B. point = A + t * AB, where t = marginA / margin. if (margin > Toolbox.Epsilon) //This can be zero! It would cause a NaN if unprotected. Vector3Ex.Multiply(ref displacement, convex.collisionMargin / margin, out contact.Position); //t * AB else contact.Position = new System.Numerics.Vector3(); Vector3Ex.Add(ref closestA, ref contact.Position, out contact.Position); //A + t * AB. contact.Normal = displacement; float distance = (float)Math.Sqrt(distanceSquared); Vector3Ex.Divide(ref contact.Normal, distance, out contact.Normal); contact.PenetrationDepth = margin - distance; contactList.Add(ref contact); TryToEscape(triangle, ref contact.Position); return true; } //Too far to make a contact- move back to separation. state = CollisionState.ExternalSeparated; return false; }
///<summary> /// Initializes the pair tester. ///</summary> ///<param name="convex">Convex shape to use.</param> ///<param name="triangle">Triangle shape to use.</param> public override void Initialize(ConvexShape convex, TriangleShape triangle) { this.sphere = (SphereShape)convex; this.triangle = triangle; }
private bool DoExternalSeparated(TriangleShape triangle, out TinyStructList<ContactData> contactList) { if (GJKToolbox.AreShapesIntersecting(convex, triangle, ref Toolbox.RigidIdentity, ref Toolbox.RigidIdentity, ref localSeparatingAxis)) { state = CollisionState.ExternalNear; return DoExternalNear(triangle, out contactList); } TryToEscape(); contactList = new TinyStructList<ContactData>(); return false; }
//Relies on the triangle being located in the local space of the convex object. The convex transform is used to transform the //contact points back from the convex's local space into world space. ///<summary> /// Generates a contact between the triangle and convex. ///</summary> /// <param name="triangle">Triangle to test</param> ///<param name="contactList">Contact between the shapes, if any.</param> ///<returns>Whether or not the shapes are colliding.</returns> public abstract bool GenerateContactCandidates(TriangleShape triangle, out TinyStructList<ContactData> contactList);
private bool DoPlaneTest(TriangleShape triangle, out TinyStructList<ContactData> contactList) { //Find closest point between object and plane. System.Numerics.Vector3 reverseNormal; System.Numerics.Vector3 ab, ac; Vector3Ex.Subtract(ref triangle.vB, ref triangle.vA, out ab); Vector3Ex.Subtract(ref triangle.vC, ref triangle.vA, out ac); Vector3Ex.Cross(ref ac, ref ab, out reverseNormal); //Convex position dot normal is ALWAYS zero. The thing to look at is the plane's 'd'. //If the distance along the normal is positive, then the convex is 'behind' that normal. float dotA; Vector3Ex.Dot(ref triangle.vA, ref reverseNormal, out dotA); contactList = new TinyStructList<ContactData>(); switch (triangle.sidedness) { case TriangleSidedness.DoubleSided: if (dotA < 0) { //The reverse normal is pointing towards the convex. //It needs to point away from the convex so that the direction //will get the proper extreme point. Vector3Ex.Negate(ref reverseNormal, out reverseNormal); dotA = -dotA; } break; case TriangleSidedness.Clockwise: //if (dotA < 0) //{ // //The reverse normal is pointing towards the convex. // return false; //} break; case TriangleSidedness.Counterclockwise: //if (dotA > 0) //{ // //The reverse normal is pointing away from the convex. // return false; //} //The reverse normal is pointing towards the convex. //It needs to point away from the convex so that the direction //will get the proper extreme point. Vector3Ex.Negate(ref reverseNormal, out reverseNormal); dotA = -dotA; break; } System.Numerics.Vector3 extremePoint; convex.GetLocalExtremePointWithoutMargin(ref reverseNormal, out extremePoint); //See if the extreme point is within the face or not. //It might seem like the easy "depth" test should come first, since a barycentric //calculation takes a bit more time. However, transferring from plane to depth is 'rare' //(like all transitions), and putting this test here is logically closer to its requirements' //computation. if (GetVoronoiRegion(triangle, ref extremePoint) != VoronoiRegion.ABC) { state = CollisionState.ExternalSeparated; return DoExternalSeparated(triangle, out contactList); } float dotE; Vector3Ex.Dot(ref extremePoint, ref reverseNormal, out dotE); float t = (dotA - dotE) / reverseNormal.LengthSquared(); System.Numerics.Vector3 offset; Vector3Ex.Multiply(ref reverseNormal, t, out offset); //Compare the distance from the plane to the convex object. float distanceSquared = offset.LengthSquared(); float marginSum = triangle.collisionMargin + convex.collisionMargin; //TODO: Could just normalize early and avoid computing point plane before it's necessary. //Exposes a sqrt but... if (t <= 0 || distanceSquared < marginSum * marginSum) { //The convex object is in the margin of the plane. //All that's left is to create the contact. var contact = new ContactData(); //Displacement is from A to B. point = A + t * AB, where t = marginA / margin. if (marginSum > Toolbox.Epsilon) //This can be zero! It would cause a NaN is unprotected. Vector3Ex.Multiply(ref offset, convex.collisionMargin / marginSum, out contact.Position); //t * AB else contact.Position = new System.Numerics.Vector3(); Vector3Ex.Add(ref extremePoint, ref contact.Position, out contact.Position); //A + t * AB. float normalLength = reverseNormal.Length(); Vector3Ex.Divide(ref reverseNormal, normalLength, out contact.Normal); float distance = normalLength * t; contact.PenetrationDepth = marginSum - distance; if (contact.PenetrationDepth > marginSum) { //Check to see if the inner sphere is touching the plane. //This does not override other tests; there can be more than one contact from a single triangle. ContactData alternateContact; if (TryInnerSphereContact(triangle, out alternateContact))// && alternateContact.PenetrationDepth > contact.PenetrationDepth) { contactList.Add(ref alternateContact); } //The convex object is stuck deep in the plane! //The most problematic case for this is when //an object is right on top of a cliff. //The lower, vertical triangle may occasionally detect //a contact with the object, but would compute an extremely //deep depth if the normal plane test was used. //Verify that the depth is correct by trying another approach. CollisionState previousState = state; state = CollisionState.ExternalNear; TinyStructList<ContactData> alternateContacts; if (DoExternalNear(triangle, out alternateContacts)) { alternateContacts.Get(0, out alternateContact); if (alternateContact.PenetrationDepth + .01f < contact.PenetrationDepth) //Bias against the subtest's result, since the plane version will probably have a better position. { //It WAS a bad contact. contactList.Add(ref alternateContact); //DoDeepContact (which can be called from within DoExternalNear) can generate two contacts, but the second contact would just be an inner sphere (which we already generated). //DoExternalNear can only generate one contact. So we only need the first contact! //TODO: This is a fairly fragile connection between the two stages. Consider robustifying. (Also, the TryInnerSphereContact is done twice! This process is very rare for marginful pairs, though) } else { //Well, it really is just that deep. contactList.Add(ref contact); state = previousState; } } else { //If the external near test finds that there was no collision at all, //just return to plane testing. If the point turns up outside the face region //next time, the system will adapt. state = previousState; return false; } } else { contactList.Add(ref contact); } return true; } return false; }
/// <summary> /// Returns a resource to the pool. /// </summary> /// <param name="triangle">Triangle to return.</param> public static void GiveBack(TriangleShape triangle) { triangle.collisionMargin = 0; triangle.sidedness = TriangleSidedness.DoubleSided; SubPoolTriangleShape.GiveBack(triangle); }
///<summary> /// Determines what voronoi region a given point is in. ///</summary> ///<param name="p">Point to test.</param> ///<returns>Voronoi region containing the point.</returns> private VoronoiRegion GetVoronoiRegion(TriangleShape triangle, ref System.Numerics.Vector3 p) { //The point we are comparing against the triangle is 0,0,0, so instead of storing an "A->P" vector, //just use -A. //Same for B->, C->P... System.Numerics.Vector3 ab, ac, ap; Vector3Ex.Subtract(ref triangle.vB, ref triangle.vA, out ab); Vector3Ex.Subtract(ref triangle.vC, ref triangle.vA, out ac); Vector3Ex.Subtract(ref p, ref triangle.vA, out ap); //Check to see if it's outside A. float APdotAB, APdotAC; Vector3Ex.Dot(ref ap, ref ab, out APdotAB); Vector3Ex.Dot(ref ap, ref ac, out APdotAC); if (APdotAC <= 0f && APdotAB <= 0) { //It is A! return VoronoiRegion.A; } //Check to see if it's outside B. float BPdotAB, BPdotAC; System.Numerics.Vector3 bp; Vector3Ex.Subtract(ref p, ref triangle.vB, out bp); Vector3Ex.Dot(ref ab, ref bp, out BPdotAB); Vector3Ex.Dot(ref ac, ref bp, out BPdotAC); if (BPdotAB >= 0f && BPdotAC <= BPdotAB) { //It is B! return VoronoiRegion.B; } //Check to see if it's outside AB. float vc = APdotAB * BPdotAC - BPdotAB * APdotAC; if (vc <= 0 && APdotAB > 0 && BPdotAB < 0) //Note > and < instead of => <=; avoids possibly division by zero { return VoronoiRegion.AB; } //Check to see if it's outside C. float CPdotAB, CPdotAC; System.Numerics.Vector3 cp; Vector3Ex.Subtract(ref p, ref triangle.vC, out cp); Vector3Ex.Dot(ref ab, ref cp, out CPdotAB); Vector3Ex.Dot(ref ac, ref cp, out CPdotAC); if (CPdotAC >= 0f && CPdotAB <= CPdotAC) { //It is C! return VoronoiRegion.C; } //Check if it's outside AC. float vb = CPdotAB * APdotAC - APdotAB * CPdotAC; if (vb <= 0f && APdotAC > 0f && CPdotAC < 0f) //Note > instead of >= and < instead of <=; prevents bad denominator { return VoronoiRegion.AC; } //Check if it's outside BC. float va = BPdotAB * CPdotAC - CPdotAB * BPdotAC; if (va <= 0f && (BPdotAC - BPdotAB) > 0f && (CPdotAB - CPdotAC) > 0f)//Note > instead of >= and < instead of <=; prevents bad denominator { return VoronoiRegion.BC; } //On the face of the triangle. return VoronoiRegion.ABC; }
protected override bool ConfigureLocalTriangle(int i, TriangleShape localTriangleShape, out TriangleIndices indices) { var data = mesh.Shape.TriangleMesh.Data; int triangleIndex = overlappedTriangles.Elements[i]; TriangleSidedness sidedness; switch (mesh.Shape.solidity) { case MobileMeshSolidity.Clockwise: sidedness = TriangleSidedness.Clockwise; break; case MobileMeshSolidity.Counterclockwise: sidedness = TriangleSidedness.Counterclockwise; break; case MobileMeshSolidity.DoubleSided: sidedness = TriangleSidedness.DoubleSided; break; default: sidedness = mesh.Shape.SidednessWhenSolid; break; } localTriangleShape.sidedness = sidedness; localTriangleShape.collisionMargin = 0; indices = new TriangleIndices { A = data.indices[triangleIndex], B = data.indices[triangleIndex + 1], C = data.indices[triangleIndex + 2] }; localTriangleShape.vA = data.vertices[indices.A]; localTriangleShape.vB = data.vertices[indices.B]; localTriangleShape.vC = data.vertices[indices.C]; return true; }
private bool TryInnerSphereContact(TriangleShape triangle, out ContactData contact) { System.Numerics.Vector3 closestPoint; Toolbox.GetClosestPointOnTriangleToPoint(ref triangle.vA, ref triangle.vB, ref triangle.vC, ref Toolbox.ZeroVector, out closestPoint); float length = closestPoint.LengthSquared(); float minimumRadius = convex.MinimumRadius * (MotionSettings.CoreShapeScaling + .01f); if (length < minimumRadius * minimumRadius) { System.Numerics.Vector3 triangleNormal, ab, ac; Vector3Ex.Subtract(ref triangle.vB, ref triangle.vA, out ab); Vector3Ex.Subtract(ref triangle.vC, ref triangle.vA, out ac); Vector3Ex.Cross(ref ab, ref ac, out triangleNormal); float dot; Vector3Ex.Dot(ref closestPoint, ref triangleNormal, out dot); if ((triangle.sidedness == TriangleSidedness.Clockwise && dot > 0) || (triangle.sidedness == TriangleSidedness.Counterclockwise && dot < 0)) { //Normal was facing the wrong way. contact = new ContactData(); return false; } length = (float)Math.Sqrt(length); contact.Position = closestPoint; if (length > Toolbox.Epsilon) //Watch out for NaN's! { Vector3Ex.Divide(ref closestPoint, length, out contact.Normal); } else { //The direction is undefined. Use the triangle's normal. //One sided triangles can only face in the appropriate direction. float normalLength = triangleNormal.LengthSquared(); if (triangleNormal.LengthSquared() > Toolbox.Epsilon) { Vector3Ex.Divide(ref triangleNormal, (float)Math.Sqrt(normalLength), out triangleNormal); if (triangle.sidedness == TriangleSidedness.Clockwise) contact.Normal = triangleNormal; else Vector3Ex.Negate(ref triangleNormal, out contact.Normal); } else { //Degenerate triangle! contact = new ContactData(); return false; } } //Compute the actual depth of the contact. //This is conservative; the minimum radius is guaranteed to be no larger than the shape itself. //But that's ok- this is strictly a deep contact protection scheme. Other contacts will make the objects separate. contact.PenetrationDepth = convex.MinimumRadius - length; contact.Id = -1; return true; } contact = new ContactData(); return false; }
/// <summary> /// Returns a resource to the pool. /// </summary> /// <param name="triangle">Triangle to return.</param> public static void GiveBack(TriangleShape triangle) { if (SubPoolTriangleShape == null) SubPoolTriangleShape = new UnsafeResourcePool<TriangleShape>(); triangle.collisionMargin = 0; triangle.sidedness = TriangleSidedness.DoubleSided; SubPoolTriangleShape.GiveBack(triangle); }
protected override bool ConfigureTriangle(int i, TriangleShape localTriangleShape, out TriangleIndices indices) { MeshBoundingBoxTreeData data = mesh.Shape.TriangleMesh.Data; int triangleIndex = overlappedTriangles.Elements[i]; data.GetTriangle(triangleIndex, out localTriangleShape.vA, out localTriangleShape.vB, out localTriangleShape.vC); AffineTransform transform; AffineTransform.CreateFromRigidTransform(ref mesh.worldTransform, out transform); AffineTransform.Transform(ref localTriangleShape.vA, ref transform, out localTriangleShape.vA); AffineTransform.Transform(ref localTriangleShape.vB, ref transform, out localTriangleShape.vB); AffineTransform.Transform(ref localTriangleShape.vC, ref transform, out localTriangleShape.vC); //In instanced meshes, the bounding box we found in local space could collect more triangles than strictly necessary. //By doing a second pass, we should be able to prune out quite a few of them. BoundingBox triangleAABB; Toolbox.GetTriangleBoundingBox(ref localTriangleShape.vA, ref localTriangleShape.vB, ref localTriangleShape.vC, out triangleAABB); bool toReturn; triangleAABB.Intersects(ref convex.boundingBox, out toReturn); if (!toReturn) { indices = new TriangleIndices(); return false; } TriangleSidedness sidedness; switch (mesh.Shape.solidity) { case MobileMeshSolidity.Clockwise: sidedness = TriangleSidedness.Clockwise; break; case MobileMeshSolidity.Counterclockwise: sidedness = TriangleSidedness.Counterclockwise; break; case MobileMeshSolidity.DoubleSided: sidedness = TriangleSidedness.DoubleSided; break; default: sidedness = mesh.Shape.SidednessWhenSolid; break; } localTriangleShape.sidedness = sidedness; localTriangleShape.collisionMargin = 0; indices = new TriangleIndices() { A = data.indices[triangleIndex], B = data.indices[triangleIndex + 1], C = data.indices[triangleIndex + 2] }; return true; }