public unsafe void Test(ref ConvexHullWide a, ref ConvexHullWide b, ref Vector <float> speculativeMargin, ref Vector3Wide offsetB, ref QuaternionWide orientationA, ref QuaternionWide orientationB, int pairCount, out Convex4ContactManifoldWide manifold) { Matrix3x3Wide.CreateFromQuaternion(orientationA, out var rA); Matrix3x3Wide.CreateFromQuaternion(orientationB, out var rB); Matrix3x3Wide.MultiplyByTransposeWithoutOverlap(rA, rB, out var bLocalOrientationA); Matrix3x3Wide.TransformByTransposedWithoutOverlap(offsetB, rB, out var localOffsetB); Vector3Wide.Negate(localOffsetB, out var localOffsetA); Vector3Wide.Length(localOffsetA, out var centerDistance); Vector3Wide.Scale(localOffsetA, Vector <float> .One / centerDistance, out var initialNormal); var useInitialFallback = Vector.LessThan(centerDistance, new Vector <float>(1e-8f)); initialNormal.X = Vector.ConditionalSelect(useInitialFallback, Vector <float> .Zero, initialNormal.X); initialNormal.Y = Vector.ConditionalSelect(useInitialFallback, Vector <float> .One, initialNormal.Y); initialNormal.Z = Vector.ConditionalSelect(useInitialFallback, Vector <float> .Zero, initialNormal.Z); var hullSupportFinder = default(ConvexHullSupportFinder); ManifoldCandidateHelper.CreateInactiveMask(pairCount, out var inactiveLanes); a.EstimateEpsilonScale(inactiveLanes, out var aEpsilonScale); b.EstimateEpsilonScale(inactiveLanes, out var bEpsilonScale); var epsilonScale = Vector.Min(aEpsilonScale, bEpsilonScale); var depthThreshold = -speculativeMargin; DepthRefiner <ConvexHull, ConvexHullWide, ConvexHullSupportFinder, ConvexHull, ConvexHullWide, ConvexHullSupportFinder> .FindMinimumDepth( b, a, localOffsetA, bLocalOrientationA, ref hullSupportFinder, ref hullSupportFinder, initialNormal, inactiveLanes, 1e-5f *epsilonScale, depthThreshold, out var depth, out var localNormal, out var closestOnB); inactiveLanes = Vector.BitwiseOr(inactiveLanes, Vector.LessThan(depth, depthThreshold)); //Not every lane will generate contacts. Rather than requiring every lane to carefully clear all contactExists states, just clear them up front. manifold.Contact0Exists = default; manifold.Contact1Exists = default; manifold.Contact2Exists = default; manifold.Contact3Exists = default; if (Vector.LessThanAll(inactiveLanes, Vector <int> .Zero)) { //No contacts generated. return; } Matrix3x3Wide.TransformByTransposedWithoutOverlap(localNormal, bLocalOrientationA, out var localNormalInA); Vector3Wide.Negate(localNormalInA, out var negatedLocalNormalInA); Vector3Wide.Scale(localNormal, depth, out var negatedOffsetToClosestOnA); Vector3Wide.Subtract(closestOnB, negatedOffsetToClosestOnA, out var closestOnA); Vector3Wide.Subtract(closestOnA, localOffsetA, out var aToClosestOnA); Matrix3x3Wide.TransformByTransposedWithoutOverlap(aToClosestOnA, bLocalOrientationA, out var closestOnAInA); //To find the contact manifold, we'll clip the capsule axis against the face as usual, but we're dealing with potentially //distinct convex hulls. Rather than vectorizing over the different hulls, we vectorize within each hull. Helpers.FillVectorWithLaneIndices(out var slotOffsetIndices); var boundingPlaneEpsilon = 1e-3f * epsilonScale; for (int slotIndex = 0; slotIndex < pairCount; ++slotIndex) { if (inactiveLanes[slotIndex] < 0) { continue; } ref var aSlot = ref a.Hulls[slotIndex]; ref var bSlot = ref b.Hulls[slotIndex];
static void TestEndpointNormal(ref Vector3Wide offsetA, ref Vector3Wide capsuleAxis, ref Vector <float> capsuleHalfLength, ref Vector3Wide endpoint, ref BoxWide box, out Vector <float> depth, out Vector3Wide normal) { Vector3Wide clamped; clamped.X = Vector.Min(box.HalfWidth, Vector.Max(-box.HalfWidth, endpoint.X)); clamped.Y = Vector.Min(box.HalfHeight, Vector.Max(-box.HalfHeight, endpoint.Y)); clamped.Z = Vector.Min(box.HalfLength, Vector.Max(-box.HalfLength, endpoint.Z)); Vector3Wide.Subtract(endpoint, clamped, out normal); Vector3Wide.Length(normal, out var length); var inverseLength = Vector <float> .One / length; Vector3Wide.Scale(normal, inverseLength, out normal); //The dot between the offset from B to A and the normal gives us the center offset. //The dot between the capsule axis and normal gives us the (unscaled) extent of the capsule along the normal. //The depth is (boxExtentAlongNormal + capsuleExtentAlongNormal) - separationAlongNormal. Vector3Wide.Dot(offsetA, normal, out var baN); Vector3Wide.Dot(capsuleAxis, normal, out var daN); depth = Vector.Abs(normal.X) * box.HalfWidth + Vector.Abs(normal.Y) * box.HalfHeight + Vector.Abs(normal.Z) * box.HalfLength + Vector.Abs(daN * capsuleHalfLength) - Vector.Abs(baN); //If the endpoint doesn't generate a valid normal due to containment, ignore the depth result. depth = Vector.ConditionalSelect(Vector.GreaterThan(length, new Vector <float>(1e-10f)), depth, new Vector <float>(float.MaxValue)); }
public void Test(ref SphereWide a, ref ConvexHullWide b, ref Vector <float> speculativeMargin, ref Vector3Wide offsetB, ref QuaternionWide orientationB, int pairCount, out Convex1ContactManifoldWide manifold) { Matrix3x3Wide.CreateFromQuaternion(orientationB, out var hullOrientation); Matrix3x3Wide.TransformByTransposedWithoutOverlap(offsetB, hullOrientation, out var localOffsetB); Vector3Wide.Negate(localOffsetB, out var localOffsetA); Matrix3x3Wide.CreateIdentity(out var identity); Vector3Wide.Length(localOffsetA, out var centerDistance); Vector3Wide.Scale(localOffsetA, Vector <float> .One / centerDistance, out var initialNormal); var useInitialFallback = Vector.LessThan(centerDistance, new Vector <float>(1e-8f)); initialNormal.X = Vector.ConditionalSelect(useInitialFallback, Vector <float> .Zero, initialNormal.X); initialNormal.Y = Vector.ConditionalSelect(useInitialFallback, Vector <float> .One, initialNormal.Y); initialNormal.Z = Vector.ConditionalSelect(useInitialFallback, Vector <float> .Zero, initialNormal.Z); var hullSupportFinder = default(ConvexHullSupportFinder); var sphereSupportFinder = default(SphereSupportFinder); ManifoldCandidateHelper.CreateInactiveMask(pairCount, out var inactiveLanes); b.EstimateEpsilonScale(inactiveLanes, out var hullEpsilonScale); var epsilonScale = Vector.Min(a.Radius, hullEpsilonScale); DepthRefiner <ConvexHull, ConvexHullWide, ConvexHullSupportFinder, Sphere, SphereWide, SphereSupportFinder> .FindMinimumDepth( b, a, localOffsetA, identity, ref hullSupportFinder, ref sphereSupportFinder, initialNormal, inactiveLanes, 1e-5f *epsilonScale, -speculativeMargin, out var depth, out var localNormal, out var closestOnHull); Matrix3x3Wide.TransformWithoutOverlap(closestOnHull, hullOrientation, out var hullToContact); Matrix3x3Wide.TransformWithoutOverlap(localNormal, hullOrientation, out manifold.Normal); Vector3Wide.Add(hullToContact, offsetB, out manifold.OffsetA); manifold.FeatureId = Vector <int> .Zero; manifold.Depth = depth; manifold.ContactExists = Vector.GreaterThanOrEqual(manifold.Depth, -speculativeMargin); }
public void Test(ref SphereWide a, ref BoxWide b, ref Vector3Wide offsetB, ref QuaternionWide orientationA, ref QuaternionWide orientationB, out Vector <int> intersected, out Vector <float> distance, out Vector3Wide closestA, out Vector3Wide normal) { //Clamp the position of the sphere to the box. Matrix3x3Wide.CreateFromQuaternion(orientationB, out var orientationMatrixB); //Note that we're working with localOffsetB, which is the offset from A to B, even though conceptually we want to be operating on the offset from B to A. //Those offsets differ only by their sign, so are equivalent due to the symmetry of the box. The negation is left implicit. Matrix3x3Wide.TransformByTransposedWithoutOverlap(offsetB, orientationMatrixB, out var localOffsetB); Vector3Wide clampedLocalOffsetB; clampedLocalOffsetB.X = Vector.Min(Vector.Max(localOffsetB.X, -b.HalfWidth), b.HalfWidth); clampedLocalOffsetB.Y = Vector.Min(Vector.Max(localOffsetB.Y, -b.HalfHeight), b.HalfHeight); clampedLocalOffsetB.Z = Vector.Min(Vector.Max(localOffsetB.Z, -b.HalfLength), b.HalfLength); //Implicit negation to make the normal point from B to A, following convention. Vector3Wide.Subtract(clampedLocalOffsetB, localOffsetB, out var localNormal); Vector3Wide.Length(localNormal, out var innerDistance); var inverseDistance = Vector <float> .One / innerDistance; Vector3Wide.Scale(localNormal, inverseDistance, out localNormal); Matrix3x3Wide.TransformWithoutOverlap(localNormal, orientationMatrixB, out normal); var negativeRadius = -a.Radius; Vector3Wide.Scale(normal, negativeRadius, out closestA); distance = innerDistance - a.Radius; intersected = Vector.LessThanOrEqual(distance, Vector <float> .Zero); }
public void Test(ref SphereWide a, ref CapsuleWide b, ref Vector <float> speculativeMargin, ref Vector3Wide offsetB, ref QuaternionWide orientationB, int pairCount, out Convex1ContactManifoldWide manifold) { //The contact for a sphere-capsule pair is based on the closest point of the sphere center to the capsule internal line segment. QuaternionWide.TransformUnitXY(orientationB, out var x, out var y); Vector3Wide.Dot(y, offsetB, out var t); t = Vector.Min(b.HalfLength, Vector.Max(-b.HalfLength, -t)); Vector3Wide.Scale(y, t, out var capsuleLocalClosestPointOnLineSegment); Vector3Wide.Add(offsetB, capsuleLocalClosestPointOnLineSegment, out var sphereToInternalSegment); Vector3Wide.Length(sphereToInternalSegment, out var internalDistance); //Note that the normal points from B to A by convention. Here, the sphere is A, the capsule is B, so the normalization requires a negation. var inverseDistance = new Vector <float>(-1f) / internalDistance; Vector3Wide.Scale(sphereToInternalSegment, inverseDistance, out manifold.Normal); var normalIsValid = Vector.GreaterThan(internalDistance, Vector <float> .Zero); //If the center of the sphere is on the internal line segment, then choose a direction on the plane defined by the capsule's up vector. //We computed one such candidate earlier. Note that we could usually get away with choosing a completely arbitrary direction, but //going through the extra effort to compute a true local horizontal direction avoids some nasty corner case surprises if a user is trying //to do something like manually resolving collisions or other query-based logic. //A cheaper option would be to simply use the y axis as the normal. That's known to be suboptimal, but if we don't guarantee minimum penetration depth, that's totally fine. //My guess is that computing x will be so cheap as to be irrelevant. Vector3Wide.ConditionalSelect(normalIsValid, manifold.Normal, x, out manifold.Normal); manifold.Depth = a.Radius + b.Radius - internalDistance; manifold.FeatureId = Vector <int> .Zero; //The contact position relative to object A (the sphere) is computed as the average of the extreme point along the normal toward the opposing shape on each shape, averaged. //For capsule-sphere, this can be computed from the normal and depth. var negativeOffsetFromSphere = manifold.Depth * 0.5f - a.Radius; Vector3Wide.Scale(manifold.Normal, negativeOffsetFromSphere, out manifold.OffsetA); manifold.ContactExists = Vector.GreaterThan(manifold.Depth, -speculativeMargin); }
public void Test(ref SphereWide a, ref BoxWide b, ref Vector <float> speculativeMargin, ref Vector3Wide offsetB, ref QuaternionWide orientationB, int pairCount, out Convex1ContactManifoldWide manifold) { //Clamp the position of the sphere to the box. Matrix3x3Wide.CreateFromQuaternion(orientationB, out var orientationMatrixB); //Note that we're working with localOffsetB, which is the offset from A to B, even though conceptually we want to be operating on the offset from B to A. //Those offsets differ only by their sign, so are equivalent due to the symmetry of the box. The negation is left implicit. Matrix3x3Wide.TransformByTransposedWithoutOverlap(offsetB, orientationMatrixB, out var localOffsetB); Vector3Wide clampedLocalOffsetB; clampedLocalOffsetB.X = Vector.Min(Vector.Max(localOffsetB.X, -b.HalfWidth), b.HalfWidth); clampedLocalOffsetB.Y = Vector.Min(Vector.Max(localOffsetB.Y, -b.HalfHeight), b.HalfHeight); clampedLocalOffsetB.Z = Vector.Min(Vector.Max(localOffsetB.Z, -b.HalfLength), b.HalfLength); //Implicit negation to make the normal point from B to A, following convention. Vector3Wide.Subtract(clampedLocalOffsetB, localOffsetB, out var outsideNormal); Vector3Wide.Length(outsideNormal, out var distance); var inverseDistance = Vector <float> .One / distance; Vector3Wide.Scale(outsideNormal, inverseDistance, out outsideNormal); var outsideDepth = a.Radius - distance; //If the sphere center is inside the box, then the shortest local axis to exit must be chosen. var depthX = b.HalfWidth - Vector.Abs(localOffsetB.X); var depthY = b.HalfHeight - Vector.Abs(localOffsetB.Y); var depthZ = b.HalfLength - Vector.Abs(localOffsetB.Z); var insideDepth = Vector.Min(depthX, Vector.Min(depthY, depthZ)); //Only one axis may have a nonzero component. var useX = Vector.Equals(insideDepth, depthX); var useY = Vector.AndNot(Vector.Equals(insideDepth, depthY), useX); var useZ = Vector.OnesComplement(Vector.BitwiseOr(useX, useY)); Vector3Wide insideNormal; //A faster sign test would be nice. insideNormal.X = Vector.ConditionalSelect(useX, Vector.ConditionalSelect(Vector.LessThan(localOffsetB.X, Vector <float> .Zero), new Vector <float>(1f), new Vector <float>(-1f)), Vector <float> .Zero); insideNormal.Y = Vector.ConditionalSelect(useY, Vector.ConditionalSelect(Vector.LessThan(localOffsetB.Y, Vector <float> .Zero), new Vector <float>(1f), new Vector <float>(-1f)), Vector <float> .Zero); insideNormal.Z = Vector.ConditionalSelect(useZ, Vector.ConditionalSelect(Vector.LessThan(localOffsetB.Z, Vector <float> .Zero), new Vector <float>(1f), new Vector <float>(-1f)), Vector <float> .Zero); insideDepth += a.Radius; var useInside = Vector.Equals(distance, Vector <float> .Zero); Vector3Wide.ConditionalSelect(useInside, insideNormal, outsideNormal, out var localNormal); Matrix3x3Wide.TransformWithoutOverlap(localNormal, orientationMatrixB, out manifold.Normal); manifold.Depth = Vector.ConditionalSelect(useInside, insideDepth, outsideDepth); manifold.FeatureId = Vector <int> .Zero; //The contact position relative to object A (the sphere) is computed as the average of the extreme point along the normal toward the opposing shape on each shape, averaged. //For capsule-sphere, this can be computed from the normal and depth. var negativeOffsetFromSphere = manifold.Depth * 0.5f - a.Radius; Vector3Wide.Scale(manifold.Normal, negativeOffsetFromSphere, out manifold.OffsetA); manifold.ContactExists = Vector.GreaterThan(manifold.Depth, -speculativeMargin); }
public void Test(ref SphereWide a, ref SphereWide b, ref Vector3Wide offsetB, ref QuaternionWide orientationA, ref QuaternionWide orientationB, out Vector <int> intersected, out Vector <float> distance, out Vector3Wide closestA, out Vector3Wide normal) { Vector3Wide.Length(offsetB, out var centerDistance); //Note the negative 1. By convention, the normal points from B to A. var inverseDistance = new Vector <float>(-1f) / centerDistance; Vector3Wide.Scale(offsetB, inverseDistance, out normal); distance = centerDistance - a.Radius - b.Radius; var negativeRadiusA = -a.Radius; Vector3Wide.Scale(normal, negativeRadiusA, out closestA); intersected = Vector.LessThanOrEqual(distance, Vector <float> .Zero); }
static void TestVertexAxis(ref BoxWide box, ref Vector3Wide offsetA, ref Vector3Wide capsuleAxis, ref Vector <float> capsuleHalfLength, out Vector <float> depth, out Vector3Wide normal, out Vector3Wide closestA) { //The available feature pairs between the capsule axis and box are: //Box edge - capsule endpoints: handled by capsule endpoint clamping //Box edge - capsule axis: handled explicitly by the edge cases //Box face - capsule endpoints: handled by capsule endpoint clamping //Box face - capsule axis: redundant with edge-axis or face-endpoint //Box vertex - capsule endpoints: handled by capsule endpoint clamping //Box vertex - capsule axis: unhandled //So here, we need to identify the maximum separating axis caused by vertices. //We can safely ignore cases that are handled by the endpoint clamp, too. //A brute force approach could test every vertex-axis offset as a normal, but more cleverness is possible. //Note that this case requires that the capsule axis be in the voronoi region of a vertex. That is only possible if the offset from the box origin to the capsule axis //supports an extreme point at the vertex. //(That extreme point relationship may also be met in cases of intersection, but that's fine- this distance tester is not concerned with intersection beyond a boolean result.) //closest point on axis to origin = offsetA - (offsetA * capsuleAxis) * capsuleAxis Vector3Wide.Dot(offsetA, capsuleAxis, out var dot); var clampedDot = Vector.Min(capsuleHalfLength, Vector.Max(-capsuleHalfLength, dot)); Vector3Wide.Scale(capsuleAxis, clampedDot, out var axisOffset); Vector3Wide.Subtract(offsetA, axisOffset, out var closestOnAxis); Vector3Wide vertex; vertex.X = Vector.ConditionalSelect(Vector.LessThan(closestOnAxis.X, Vector <float> .Zero), -box.HalfWidth, box.HalfWidth); vertex.Y = Vector.ConditionalSelect(Vector.LessThan(closestOnAxis.Y, Vector <float> .Zero), -box.HalfHeight, box.HalfHeight); vertex.Z = Vector.ConditionalSelect(Vector.LessThan(closestOnAxis.Z, Vector <float> .Zero), -box.HalfLength, box.HalfLength); //closest point on axis to vertex: ((vertex - offsetA) * capsuleAxis) * capsuleAxis + offsetA - vertex Vector3Wide.Subtract(vertex, offsetA, out var capsuleCenterToVertex); Vector3Wide.Dot(capsuleCenterToVertex, capsuleAxis, out var vertexDot); Vector3Wide.Scale(capsuleAxis, vertexDot, out var vertexAxisOffset); Vector3Wide.Add(vertexAxisOffset, offsetA, out closestA); Vector3Wide.Subtract(closestA, vertex, out var vertexToClosestOnCapsule); Vector3Wide.Length(vertexToClosestOnCapsule, out var length); var inverseLength = Vector <float> .One / length; Vector3Wide.Scale(vertexToClosestOnCapsule, inverseLength, out normal); //The normal is perpendicular to the capsule axis by construction, so no need to include the capsule length extent. depth = Vector.Abs(normal.X) * box.HalfWidth + Vector.Abs(normal.Y) * box.HalfHeight + Vector.Abs(normal.Z) * box.HalfLength - Vector.Abs(offsetA.X * normal.X + offsetA.Y * normal.Y + offsetA.Z * normal.Z); //Ignore degenerate cases. Worst outcome is that it reports intersection, which is pretty reasonable. depth = Vector.ConditionalSelect(Vector.LessThan(length, new Vector <float>(1e-10f)), new Vector <float>(float.MaxValue), depth); }
public void Test(ref CapsuleWide a, ref CapsuleWide b, ref Vector3Wide offsetB, ref QuaternionWide orientationA, ref QuaternionWide orientationB, out Vector <int> intersected, out Vector <float> distance, out Vector3Wide closestA, out Vector3Wide normal) { //Compute the closest points between the two line segments. No clamping to begin with. //We want to minimize distance = ||(a + da * ta) - (b + db * tb)||. //Taking the derivative with respect to ta and doing some algebra (taking into account ||da|| == ||db|| == 1) to solve for ta yields: //ta = (da * (b - a) + (db * (a - b)) * (da * db)) / (1 - ((da * db) * (da * db)) QuaternionWide.TransformUnitXY(orientationA, out var xa, out var da); QuaternionWide.TransformUnitY(orientationB, out var db); Vector3Wide.Dot(da, offsetB, out var daOffsetB); Vector3Wide.Dot(db, offsetB, out var dbOffsetB); Vector3Wide.Dot(da, db, out var dadb); //Note potential division by zero when the axes are parallel. Arbitrarily clamp; near zero values will instead produce extreme values which get clamped to reasonable results. var ta = (daOffsetB - dbOffsetB * dadb) / Vector.Max(new Vector <float>(1e-15f), Vector <float> .One - dadb * dadb); //tb = ta * (da * db) - db * (b - a) var tb = ta * dadb - dbOffsetB; //We cannot simply clamp the ta and tb values to the capsule line segments. Instead, project each line segment onto the other line segment, clamping against the target's interval. //That new clamped projected interval is the valid solution space on that line segment. We can clamp the t value by that interval to get the correctly bounded solution. //The projected intervals are: //B onto A: +-BHalfLength * (da * db) + da * offsetB //A onto B: +-AHalfLength * (da * db) - db * offsetB var absdadb = Vector.Abs(dadb); var bOntoAOffset = b.HalfLength * absdadb; var aOntoBOffset = a.HalfLength * absdadb; var aMin = Vector.Max(-a.HalfLength, Vector.Min(a.HalfLength, daOffsetB - bOntoAOffset)); var aMax = Vector.Min(a.HalfLength, Vector.Max(-a.HalfLength, daOffsetB + bOntoAOffset)); var bMin = Vector.Max(-b.HalfLength, Vector.Min(b.HalfLength, -aOntoBOffset - dbOffsetB)); var bMax = Vector.Min(b.HalfLength, Vector.Max(-b.HalfLength, aOntoBOffset - dbOffsetB)); ta = Vector.Min(Vector.Max(ta, aMin), aMax); tb = Vector.Min(Vector.Max(tb, bMin), bMax); Vector3Wide.Scale(da, ta, out closestA); Vector3Wide.Scale(db, tb, out var closestB); Vector3Wide.Add(closestB, offsetB, out closestB); Vector3Wide.Subtract(closestA, closestB, out normal); Vector3Wide.Length(normal, out distance); var inverseDistance = Vector <float> .One / distance; Vector3Wide.Scale(normal, inverseDistance, out normal); Vector3Wide.Scale(normal, a.Radius, out var aOffset); Vector3Wide.Subtract(closestA, aOffset, out closestA); distance = distance - a.Radius - b.Radius; intersected = Vector.LessThanOrEqual(distance, Vector <float> .Zero); }
public void Test(ref SphereWide a, ref SphereWide b, ref Vector <float> speculativeMargin, ref Vector3Wide offsetB, out Convex1ContactManifoldWide manifold) { Vector3Wide.Length(offsetB, out var centerDistance); //Note the negative 1. By convention, the normal points from B to A. var inverseDistance = new Vector <float>(-1f) / centerDistance; Vector3Wide.Scale(offsetB, inverseDistance, out manifold.Normal); var normalIsValid = Vector.GreaterThan(centerDistance, Vector <float> .Zero); //Arbitrarily choose the (0,1,0) if the two spheres are in the same position. Any unit length vector is equally valid. manifold.Normal.X = Vector.ConditionalSelect(normalIsValid, manifold.Normal.X, Vector <float> .Zero); manifold.Normal.Y = Vector.ConditionalSelect(normalIsValid, manifold.Normal.Y, Vector <float> .One); manifold.Normal.Z = Vector.ConditionalSelect(normalIsValid, manifold.Normal.Z, Vector <float> .Zero); manifold.Depth = a.Radius + b.Radius - centerDistance; //The contact position relative to object A is computed as the average of the extreme point along the normal toward the opposing sphere on each sphere, averaged. var negativeOffsetFromA = manifold.Depth * 0.5f - a.Radius; Vector3Wide.Scale(manifold.Normal, negativeOffsetFromA, out manifold.OffsetA); manifold.ContactExists = Vector.GreaterThan(manifold.Depth, -speculativeMargin); }
public void Test(ref SphereWide a, ref CapsuleWide b, ref Vector3Wide offsetB, ref QuaternionWide orientationA, ref QuaternionWide orientationB, out Vector <int> intersected, out Vector <float> distance, out Vector3Wide closestA, out Vector3Wide normal) { //The contact for a sphere-capsule pair is based on the closest point of the sphere center to the capsule internal line segment. QuaternionWide.TransformUnitXY(orientationB, out var x, out var y); Vector3Wide.Dot(y, offsetB, out var t); t = Vector.Min(b.HalfLength, Vector.Max(-b.HalfLength, -t)); Vector3Wide.Scale(y, t, out var capsuleLocalClosestPointOnLineSegment); Vector3Wide.Add(offsetB, capsuleLocalClosestPointOnLineSegment, out var sphereToInternalSegment); Vector3Wide.Length(sphereToInternalSegment, out var internalDistance); //Note that the normal points from B to A by convention. Here, the sphere is A, the capsule is B, so the normalization requires a negation. var inverseDistance = new Vector <float>(-1f) / internalDistance; Vector3Wide.Scale(sphereToInternalSegment, inverseDistance, out normal); var surfaceOffset = -a.Radius; Vector3Wide.Scale(normal, surfaceOffset, out closestA); distance = internalDistance - a.Radius - b.Radius; intersected = Vector.LessThanOrEqual(distance, Vector <float> .Zero); }
public void Test(Random random, int innerIterations) { QuaternionWide q; q.X = new Vector <float>((float)random.NextDouble() * 2 - 1); q.Y = new Vector <float>((float)random.NextDouble() * 2 - 1); q.Z = new Vector <float>((float)random.NextDouble() * 2 - 1); q.W = new Vector <float>((float)random.NextDouble() * 2 - 1); QuaternionWide.Normalize(q, out q); for (int i = 0; i < innerIterations; ++i) { Matrix3x3Wide.CreateFromQuaternion(q, out var r); QuaternionWide.CreateFromRotationMatrix(r, out var qTest); #if DEBUG const float epsilon = 1e-6f; Vector3Wide.Length(r.X, out var lengthX); Vector3Wide.Length(r.Y, out var lengthY); Vector3Wide.Length(r.Z, out var lengthZ); Debug.Assert( Vector.LessThanAll(Vector.Abs(Vector <float> .One - lengthX), new Vector <float>(epsilon)) && Vector.LessThanAll(Vector.Abs(Vector <float> .One - lengthY), new Vector <float>(epsilon)) && Vector.LessThanAll(Vector.Abs(Vector <float> .One - lengthZ), new Vector <float>(epsilon))); if (qTest.X[0] * q.X[0] < 0) { QuaternionWide.Negate(qTest, out qTest); } Debug.Assert( Vector.LessThanAll(Vector.Abs(qTest.X - q.X), new Vector <float>(epsilon)) && Vector.LessThanAll(Vector.Abs(qTest.Y - q.Y), new Vector <float>(epsilon)) && Vector.LessThanAll(Vector.Abs(qTest.Z - q.Z), new Vector <float>(epsilon)) && Vector.LessThanAll(Vector.Abs(qTest.W - q.W), new Vector <float>(epsilon))); #endif } }
public void Test(ref SphereWide a, ref TriangleWide b, ref Vector3Wide offsetB, ref QuaternionWide orientationA, ref QuaternionWide orientationB, out Vector <int> intersected, out Vector <float> distance, out Vector3Wide closestA, out Vector3Wide normal) { //Note that we're borrowing a lot here from the SphereTriangleCollisionTask. Could share more if you find yourself needing to change things dramatically. //Main difficulty in fully sharing is that sweep tests do not honor one sidedness, so some of the conditions change. //Work in the local space of the triangle, since it's quicker to transform the sphere position than the vertices of the triangle. Matrix3x3Wide.CreateFromQuaternion(orientationB, out var rB); Matrix3x3Wide.TransformByTransposedWithoutOverlap(offsetB, rB, out var localOffsetB); Vector3Wide.Subtract(b.B, b.A, out var ab); Vector3Wide.Subtract(b.C, b.A, out var ac); //localOffsetA = -localOffsetB, so pa = triangle.A + localOffsetB. Vector3Wide.Add(b.A, localOffsetB, out var pa); Vector3Wide.CrossWithoutOverlap(ab, ac, out var localTriangleNormal); Vector3Wide.Dot(localTriangleNormal, pa, out var paN); //EdgeAB plane test: (pa x ab) * (ab x ac) >= 0 //EdgeAC plane test: (ac x pa) * (ab x ac) >= 0 //Note that these are scaled versions of the barycentric coordinates. //To normalize them such that the weights of a point within the triangle equal 1, we just need to divide by dot(ab x ac, ab x ac). //In other words, to test the third edge plane, we can ensure that the unnormalized weights are both positive and sum to a value less than dot(ab x ac, ab x ac). //If a point is outside of an edge plane, we know that it's not in the face region or any other edge region. It could, however, be in an adjacent vertex region. //Vertex cases can be handled by clamping an edge case. //Further, note that for any query location, it is sufficient to only test one edge even if the point is outside two edge planes. If it's outside two edge planes, //that just means it's going to be on the shared vertex, so a clamped edge test captures the correct closest point. //So, at each edge, if the point is outside the plane, cache the edge. The last edge registering an outside result will be tested. //(pa x ab) * (ab x ac) = (pa * ab) * (ab * ac) - (pa * ac) * (ab * ab) //(ac x pa) * (ab x ac) = (ac * ab) * (pa * ac) - (ac * ac) * (pa * ab) //(ab x ac) * (ab x ac) = (ab * ab) * (ac * ac) - (ab * ac) * (ab * ac) Vector3Wide.Dot(pa, ab, out var abpa); Vector3Wide.Dot(ab, ac, out var abac); Vector3Wide.Dot(ac, pa, out var acpa); Vector3Wide.Dot(ac, ac, out var acac); Vector3Wide.Dot(ab, ab, out var abab); var edgePlaneTestAB = abpa * abac - acpa * abab; var edgePlaneTestAC = abac * acpa - acac * abpa; var triangleNormalLengthSquared = abab * acac - abac * abac; var edgePlaneTestBC = triangleNormalLengthSquared - edgePlaneTestAB - edgePlaneTestAC; var outsideAB = Vector.LessThan(edgePlaneTestAB, Vector <float> .Zero); var outsideAC = Vector.LessThan(edgePlaneTestAC, Vector <float> .Zero); var outsideBC = Vector.LessThan(edgePlaneTestBC, Vector <float> .Zero); var outsideAnyEdge = Vector.BitwiseOr(outsideAB, Vector.BitwiseOr(outsideAC, outsideBC)); Vector3Wide localClosestOnTriangle; var negativeOne = new Vector <int>(-1); if (Vector.EqualsAny(outsideAnyEdge, negativeOne)) { //At least one lane detected a point outside of the triangle. Choose one edge which is outside as the representative. Vector3Wide.ConditionalSelect(outsideAC, ac, ab, out var edgeDirection); Vector3Wide.Subtract(b.C, b.B, out var bc); Vector3Wide.ConditionalSelect(outsideBC, bc, edgeDirection, out edgeDirection); Vector3Wide.ConditionalSelect(outsideBC, b.B, b.A, out var edgeStart); Vector3Wide.Add(localOffsetB, edgeStart, out var negativeEdgeStartToP); //This does some partially redundant work if the edge is AB or AC, but given that we didn't have bcbc or bcpb, it's fine. Vector3Wide.Dot(negativeEdgeStartToP, edgeDirection, out var negativeOffsetDotEdge); Vector3Wide.Dot(edgeDirection, edgeDirection, out var edgeDotEdge); var edgeScale = Vector.Max(Vector <float> .Zero, Vector.Min(Vector <float> .One, -negativeOffsetDotEdge / edgeDotEdge)); Vector3Wide.Scale(edgeDirection, edgeScale, out var pointOnEdge); Vector3Wide.Add(edgeStart, pointOnEdge, out pointOnEdge); Vector3Wide.ConditionalSelect(outsideAnyEdge, pointOnEdge, localClosestOnTriangle, out localClosestOnTriangle); } if (Vector.EqualsAny(outsideAnyEdge, Vector <int> .Zero)) { //p + N * (pa * N) / ||N||^2 = N * (pa * N) / ||N||^2 - (-p) var nScale = paN / triangleNormalLengthSquared; Vector3Wide.Scale(localTriangleNormal, nScale, out var offsetToPlane); Vector3Wide.Subtract(offsetToPlane, localOffsetB, out var pointOnFace); Vector3Wide.ConditionalSelect(outsideAnyEdge, localClosestOnTriangle, pointOnFace, out localClosestOnTriangle); } //normal = normalize(localOffsetA - localClosestOnTriangle) = (localOffsetB + localClosestOnTriangle) / (-||localOffsetB + localClosestOnTriangle||) Vector3Wide.Add(localOffsetB, localClosestOnTriangle, out var localNormal); Vector3Wide.Length(localNormal, out var localNormalLength); Vector3Wide.Scale(localNormal, new Vector <float>(-1f) / localNormalLength, out localNormal); Matrix3x3Wide.TransformWithoutOverlap(localNormal, rB, out normal); Vector3Wide.Scale(normal, -a.Radius, out closestA); distance = localNormalLength - a.Radius; intersected = Vector.LessThanOrEqual(distance, Vector <float> .Zero); }
public void Test(ref SphereWide a, ref TriangleWide b, ref Vector <float> speculativeMargin, ref Vector3Wide offsetB, ref QuaternionWide orientationB, out Convex1ContactManifoldWide manifold) { //Work in the local space of the triangle, since it's quicker to transform the sphere position than the vertices of the triangle. Matrix3x3Wide.CreateFromQuaternion(orientationB, out var rB); Matrix3x3Wide.TransformByTransposedWithoutOverlap(offsetB, rB, out var localOffsetB); Vector3Wide.Subtract(b.B, b.A, out var ab); Vector3Wide.Subtract(b.C, b.A, out var ac); //localOffsetA = -localOffsetB, so pa = triangle.A + localOffsetB. Vector3Wide.Add(b.A, localOffsetB, out var pa); Vector3Wide.CrossWithoutOverlap(ab, ac, out var localTriangleNormal); Vector3Wide.Dot(localTriangleNormal, pa, out var paN); var collidingWithSolidSide = Vector.GreaterThan(paN, Vector <float> .Zero); if (Vector.EqualsAll(collidingWithSolidSide, Vector <int> .Zero)) { //No lanes can generate contacts due to the triangle's one sidedness. manifold.ContactExists = Vector <int> .Zero; return; } //EdgeAB plane test: (pa x ab) * (ab x ac) >= 0 //EdgeAC plane test: (ac x pa) * (ab x ac) >= 0 //Note that these are scaled versions of the barycentric coordinates. //To normalize them such that the weights of a point within the triangle equal 1, we just need to divide by dot(ab x ac, ab x ac). //In other words, to test the third edge plane, we can ensure that the unnormalized weights are both positive and sum to a value less than dot(ab x ac, ab x ac). //If a point is outside of an edge plane, we know that it's not in the face region or any other edge region. It could, however, be in an adjacent vertex region. //Vertex cases can be handled by clamping an edge case. //Further, note that for any query location, it is sufficient to only test one edge even if the point is outside two edge planes. If it's outside two edge planes, //that just means it's going to be on the shared vertex, so a clamped edge test captures the correct closest point. //So, at each edge, if the point is outside the plane, cache the edge. The last edge registering an outside result will be tested. //(pa x ab) * (ab x ac) = (pa * ab) * (ab * ac) - (pa * ac) * (ab * ab) //(ac x pa) * (ab x ac) = (ac * ab) * (pa * ac) - (ac * ac) * (pa * ab) //(ab x ac) * (ab x ac) = (ab * ab) * (ac * ac) - (ab * ac) * (ab * ac) Vector3Wide.Dot(pa, ab, out var abpa); Vector3Wide.Dot(ab, ac, out var abac); Vector3Wide.Dot(ac, pa, out var acpa); Vector3Wide.Dot(ac, ac, out var acac); Vector3Wide.Dot(ab, ab, out var abab); var edgePlaneTestAB = abpa * abac - acpa * abab; var edgePlaneTestAC = abac * acpa - acac * abpa; var triangleNormalLengthSquared = abab * acac - abac * abac; var edgePlaneTestBC = triangleNormalLengthSquared - edgePlaneTestAB - edgePlaneTestAC; var outsideAB = Vector.LessThan(edgePlaneTestAB, Vector <float> .Zero); var outsideAC = Vector.LessThan(edgePlaneTestAC, Vector <float> .Zero); var outsideBC = Vector.LessThan(edgePlaneTestBC, Vector <float> .Zero); var outsideAnyEdge = Vector.BitwiseOr(outsideAB, Vector.BitwiseOr(outsideAC, outsideBC)); Vector3Wide localClosestOnTriangle; var negativeOne = new Vector <int>(-1); if (Vector.EqualsAny(Vector.BitwiseAnd(collidingWithSolidSide, outsideAnyEdge), negativeOne)) { //At least one lane detected a point outside of the triangle. Choose one edge which is outside as the representative. Vector3Wide.ConditionalSelect(outsideAC, ac, ab, out var edgeDirection); Vector3Wide.Subtract(b.C, b.B, out var bc); Vector3Wide.ConditionalSelect(outsideBC, bc, edgeDirection, out edgeDirection); Vector3Wide.ConditionalSelect(outsideBC, b.B, b.A, out var edgeStart); Vector3Wide.Add(localOffsetB, edgeStart, out var negativeEdgeStartToP); //This does some partially redundant work if the edge is AB or AC, but given that we didn't have bcbc or bcpb, it's fine. Vector3Wide.Dot(negativeEdgeStartToP, edgeDirection, out var negativeOffsetDotEdge); Vector3Wide.Dot(edgeDirection, edgeDirection, out var edgeDotEdge); var edgeScale = Vector.Max(Vector <float> .Zero, Vector.Min(Vector <float> .One, -negativeOffsetDotEdge / edgeDotEdge)); Vector3Wide.Scale(edgeDirection, edgeScale, out var pointOnEdge); Vector3Wide.Add(edgeStart, pointOnEdge, out pointOnEdge); Vector3Wide.ConditionalSelect(outsideAnyEdge, pointOnEdge, localClosestOnTriangle, out localClosestOnTriangle); } if (Vector.EqualsAny(Vector.AndNot(collidingWithSolidSide, outsideAnyEdge), negativeOne)) { //p + N * (pa * N) / ||N||^2 = N * (pa * N) / ||N||^2 - (-p) var nScale = paN / triangleNormalLengthSquared; Vector3Wide.Scale(localTriangleNormal, nScale, out var offsetToPlane); Vector3Wide.Subtract(offsetToPlane, localOffsetB, out var pointOnFace); Vector3Wide.ConditionalSelect(outsideAnyEdge, localClosestOnTriangle, pointOnFace, out localClosestOnTriangle); } manifold.FeatureId = Vector.ConditionalSelect(outsideAnyEdge, Vector <int> .Zero, new Vector <int>(MeshReduction.FaceCollisionFlag)); //We'll be using the contact position to perform boundary smoothing; in order to find other triangles, the contact position has to be on the mesh surface. Matrix3x3Wide.TransformWithoutOverlap(localClosestOnTriangle, rB, out manifold.OffsetA); Vector3Wide.Add(manifold.OffsetA, offsetB, out manifold.OffsetA); Vector3Wide.Length(manifold.OffsetA, out var distance); //Note the normal is calibrated to point from B to A. var normalScale = new Vector <float>(-1) / distance; Vector3Wide.Scale(manifold.OffsetA, normalScale, out manifold.Normal); manifold.Depth = a.Radius - distance; //In the event that the sphere's center point is touching the triangle, the normal is undefined. In that case, the 'correct' normal would be the triangle's normal. //However, given that this is a pretty rare degenerate case and that we already treat triangle backfaces as noncolliding, we'll treat zero distance as a backface non-collision. manifold.ContactExists = Vector.BitwiseAnd( Vector.GreaterThan(distance, Vector <float> .Zero), Vector.BitwiseAnd( Vector.GreaterThanOrEqual(paN, Vector <float> .Zero), Vector.GreaterThanOrEqual(manifold.Depth, -speculativeMargin))); }
public unsafe void Test(ref BoxWide a, ref ConvexHullWide b, ref Vector <float> speculativeMargin, ref Vector3Wide offsetB, ref QuaternionWide orientationA, ref QuaternionWide orientationB, int pairCount, out Convex4ContactManifoldWide manifold) { Matrix3x3Wide.CreateFromQuaternion(orientationA, out var boxOrientation); Matrix3x3Wide.CreateFromQuaternion(orientationB, out var hullOrientation); Matrix3x3Wide.MultiplyByTransposeWithoutOverlap(boxOrientation, hullOrientation, out var hullLocalBoxOrientation); Matrix3x3Wide.TransformByTransposedWithoutOverlap(offsetB, hullOrientation, out var localOffsetB); Vector3Wide.Negate(localOffsetB, out var localOffsetA); Vector3Wide.Length(localOffsetA, out var centerDistance); Vector3Wide.Scale(localOffsetA, Vector <float> .One / centerDistance, out var initialNormal); var useInitialFallback = Vector.LessThan(centerDistance, new Vector <float>(1e-8f)); initialNormal.X = Vector.ConditionalSelect(useInitialFallback, Vector <float> .Zero, initialNormal.X); initialNormal.Y = Vector.ConditionalSelect(useInitialFallback, Vector <float> .One, initialNormal.Y); initialNormal.Z = Vector.ConditionalSelect(useInitialFallback, Vector <float> .Zero, initialNormal.Z); var hullSupportFinder = default(ConvexHullSupportFinder); var boxSupportFinder = default(BoxSupportFinder); ManifoldCandidateHelper.CreateInactiveMask(pairCount, out var inactiveLanes); b.EstimateEpsilonScale(inactiveLanes, out var hullEpsilonScale); var epsilonScale = Vector.Min(Vector.Max(a.HalfWidth, Vector.Max(a.HalfHeight, a.HalfLength)), hullEpsilonScale); var depthThreshold = -speculativeMargin; DepthRefiner <ConvexHull, ConvexHullWide, ConvexHullSupportFinder, PhysicsBox, BoxWide, BoxSupportFinder> .FindMinimumDepth( b, a, localOffsetA, hullLocalBoxOrientation, ref hullSupportFinder, ref boxSupportFinder, initialNormal, inactiveLanes, 1e-5f *epsilonScale, depthThreshold, out var depth, out var localNormal, out var closestOnHull); inactiveLanes = Vector.BitwiseOr(inactiveLanes, Vector.LessThan(depth, depthThreshold)); //Not every lane will generate contacts. Rather than requiring every lane to carefully clear all contactExists states, just clear them up front. manifold = default; manifold.Contact0Exists = default; manifold.Contact1Exists = default; manifold.Contact2Exists = default; manifold.Contact3Exists = default; if (Vector.LessThanAll(inactiveLanes, Vector <int> .Zero)) { //No contacts generated. return; } //Identify the box face. Matrix3x3Wide.TransformByTransposedWithoutOverlap(localNormal, hullLocalBoxOrientation, out var localNormalInA); Vector3Wide.Abs(localNormalInA, out var absLocalNormalInA); var useX = Vector.BitwiseAnd(Vector.GreaterThan(absLocalNormalInA.X, absLocalNormalInA.Y), Vector.GreaterThan(absLocalNormalInA.X, absLocalNormalInA.Z)); var useY = Vector.AndNot(Vector.GreaterThan(absLocalNormalInA.Y, absLocalNormalInA.Z), useX); Vector3Wide.ConditionalSelect(useX, hullLocalBoxOrientation.X, hullLocalBoxOrientation.Z, out var boxFaceNormal); Vector3Wide.ConditionalSelect(useY, hullLocalBoxOrientation.Y, boxFaceNormal, out boxFaceNormal); Vector3Wide.ConditionalSelect(useX, hullLocalBoxOrientation.Y, hullLocalBoxOrientation.X, out var boxFaceX); Vector3Wide.ConditionalSelect(useY, hullLocalBoxOrientation.Z, boxFaceX, out boxFaceX); Vector3Wide.ConditionalSelect(useX, hullLocalBoxOrientation.Z, hullLocalBoxOrientation.Y, out var boxFaceY); Vector3Wide.ConditionalSelect(useY, hullLocalBoxOrientation.X, boxFaceY, out boxFaceY); var negateFace = Vector.ConditionalSelect(useX, Vector.GreaterThan(localNormalInA.X, Vector <float> .Zero), Vector.ConditionalSelect(useY, Vector.GreaterThan(localNormalInA.Y, Vector <float> .Zero), Vector.GreaterThan(localNormalInA.Z, Vector <float> .Zero))); Vector3Wide.ConditionallyNegate(negateFace, ref boxFaceNormal); //Winding is important; flip the face bases if necessary. Vector3Wide.ConditionallyNegate(Vector.OnesComplement(negateFace), ref boxFaceX); var boxFaceHalfWidth = Vector.ConditionalSelect(useX, a.HalfHeight, Vector.ConditionalSelect(useY, a.HalfLength, a.HalfWidth)); var boxFaceHalfHeight = Vector.ConditionalSelect(useX, a.HalfLength, Vector.ConditionalSelect(useY, a.HalfWidth, a.HalfHeight)); var boxFaceNormalOffset = Vector.ConditionalSelect(useX, a.HalfWidth, Vector.ConditionalSelect(useY, a.HalfHeight, a.HalfLength)); Vector3Wide.Scale(boxFaceNormal, boxFaceNormalOffset, out var boxFaceCenterOffset); Vector3Wide.Add(boxFaceCenterOffset, localOffsetA, out var boxFaceCenter); Vector3Wide.Scale(boxFaceX, boxFaceHalfWidth, out var boxFaceXOffset); Vector3Wide.Scale(boxFaceY, boxFaceHalfHeight, out var boxFaceYOffset); Vector3Wide.Subtract(boxFaceCenter, boxFaceXOffset, out var v0); Vector3Wide.Add(boxFaceCenter, boxFaceXOffset, out var v1); Vector3Wide.Subtract(v0, boxFaceYOffset, out var v00); Vector3Wide.Add(v0, boxFaceYOffset, out var v01); Vector3Wide.Subtract(v1, boxFaceYOffset, out var v10); Vector3Wide.Add(v1, boxFaceYOffset, out var v11); //To find the contact manifold, we'll clip the box edges against the hull face as usual, but we're dealing with potentially //distinct convex hulls. Rather than vectorizing over the different hulls, we vectorize within each hull. Helpers.FillVectorWithLaneIndices(out var slotOffsetIndices); var boundingPlaneEpsilon = 1e-3f * epsilonScale; //There can be no more than 8 contacts (provided there are no numerical errors); 2 per box edge. var candidates = stackalloc ManifoldCandidateScalar[8]; for (int slotIndex = 0; slotIndex < pairCount; ++slotIndex) { if (inactiveLanes[slotIndex] < 0) { continue; } ref var hull = ref b.Hulls[slotIndex]; ConvexHullTestHelper.PickRepresentativeFace(ref hull, slotIndex, ref localNormal, closestOnHull, slotOffsetIndices, ref boundingPlaneEpsilon, out var slotFaceNormal, out var slotLocalNormal, out var bestFaceIndex); //Test each face edge plane against the box face. //Note that we do not use the faceNormal x edgeOffset edge plane, but rather edgeOffset x localNormal. //The faces are wound counterclockwise in right handed coordinates. //X is 00->10; Y is 10->11; Z is 11->01; W is 01->00. ref var v00Slot = ref GatherScatter.GetOffsetInstance(ref v00, slotIndex);
public unsafe void Test(ref TriangleWide a, ref ConvexHullWide b, ref Vector <float> speculativeMargin, ref Vector3Wide offsetB, ref QuaternionWide orientationA, ref QuaternionWide orientationB, int pairCount, out Convex4ContactManifoldWide manifold) { Matrix3x3Wide.CreateFromQuaternion(orientationA, out var triangleOrientation); Matrix3x3Wide.CreateFromQuaternion(orientationB, out var hullOrientation); Matrix3x3Wide.MultiplyByTransposeWithoutOverlap(triangleOrientation, hullOrientation, out var hullLocalTriangleOrientation); Matrix3x3Wide.TransformByTransposedWithoutOverlap(offsetB, hullOrientation, out var localOffsetB); Vector3Wide.Negate(localOffsetB, out var localOffsetA); TriangleWide triangle; Matrix3x3Wide.TransformWithoutOverlap(a.A, hullLocalTriangleOrientation, out triangle.A); Matrix3x3Wide.TransformWithoutOverlap(a.B, hullLocalTriangleOrientation, out triangle.B); Matrix3x3Wide.TransformWithoutOverlap(a.C, hullLocalTriangleOrientation, out triangle.C); Vector3Wide.Add(triangle.A, triangle.B, out var centroid); Vector3Wide.Add(triangle.C, centroid, out centroid); Vector3Wide.Scale(centroid, new Vector <float>(1f / 3f), out centroid); Vector3Wide.Subtract(triangle.A, centroid, out triangle.A); Vector3Wide.Subtract(triangle.B, centroid, out triangle.B); Vector3Wide.Subtract(triangle.C, centroid, out triangle.C); Vector3Wide.Subtract(centroid, localOffsetB, out var localTriangleCenter); Vector3Wide.Subtract(triangle.B, triangle.A, out var triangleAB); Vector3Wide.Subtract(triangle.C, triangle.B, out var triangleBC); Vector3Wide.Subtract(triangle.A, triangle.C, out var triangleCA); //We'll be using B-local triangle vertices quite a bit, so cache them. Vector3Wide.Add(triangle.A, localTriangleCenter, out var triangleA); Vector3Wide.Add(triangle.B, localTriangleCenter, out var triangleB); Vector3Wide.Add(triangle.C, localTriangleCenter, out var triangleC); Vector3Wide.CrossWithoutOverlap(triangleAB, triangleCA, out var triangleNormal); Vector3Wide.Length(triangleNormal, out var triangleNormalLength); Vector3Wide.Scale(triangleNormal, Vector <float> .One / triangleNormalLength, out triangleNormal); //Check if the hull's position is within the triangle and below the triangle plane. If so, we can ignore it. Vector3Wide.Dot(triangleNormal, localTriangleCenter, out var hullToTriangleCenterDot); var hullBelowPlane = Vector.GreaterThanOrEqual(hullToTriangleCenterDot, Vector <float> .Zero); Vector <int> hullInsideAndBelowTriangle; Vector3Wide.CrossWithoutOverlap(triangleAB, triangleNormal, out var edgePlaneAB); Vector3Wide.CrossWithoutOverlap(triangleBC, triangleNormal, out var edgePlaneBC); Vector3Wide.CrossWithoutOverlap(triangleCA, triangleNormal, out var edgePlaneCA); if (Vector.LessThanAny(hullBelowPlane, Vector <int> .Zero)) { //Is the hull position within the triangle bounds? Vector3Wide.Dot(edgePlaneAB, triangleA, out var abPlaneTest); Vector3Wide.Dot(edgePlaneBC, triangleB, out var bcPlaneTest); Vector3Wide.Dot(edgePlaneCA, triangleC, out var caPlaneTest); hullInsideAndBelowTriangle = Vector.BitwiseAnd( Vector.BitwiseAnd(hullBelowPlane, Vector.LessThanOrEqual(abPlaneTest, Vector <float> .Zero)), Vector.BitwiseAnd(Vector.LessThanOrEqual(bcPlaneTest, Vector <float> .Zero), Vector.LessThanOrEqual(caPlaneTest, Vector <float> .Zero))); } else { hullInsideAndBelowTriangle = Vector <int> .Zero; } ManifoldCandidateHelper.CreateInactiveMask(pairCount, out var inactiveLanes); a.EstimateEpsilonScale(out var triangleEpsilonScale); b.EstimateEpsilonScale(inactiveLanes, out var hullEpsilonScale); var epsilonScale = Vector.Min(triangleEpsilonScale, hullEpsilonScale); inactiveLanes = Vector.BitwiseOr(inactiveLanes, Vector.LessThan(triangleNormalLength, epsilonScale * 1e-6f)); inactiveLanes = Vector.BitwiseOr(inactiveLanes, hullInsideAndBelowTriangle); //Not every lane will generate contacts. Rather than requiring every lane to carefully clear all contactExists states, just clear them up front. manifold.Contact0Exists = default; manifold.Contact1Exists = default; manifold.Contact2Exists = default; manifold.Contact3Exists = default; if (Vector.LessThanAll(inactiveLanes, Vector <int> .Zero)) { //No contacts generated. return; } //Note the use of the triangle center as the initial normal rather than the localOffsetA. //Triangles are not guaranteed to be centered on their center of mass, and the DepthRefiner //will converge to a depth which does not oppose the so-far best normal- which, on the early iterations, //could be the initial normal. Vector3Wide.Length(localTriangleCenter, out var centerDistance); Vector3Wide.Scale(localTriangleCenter, Vector <float> .One / centerDistance, out var initialNormal); var useInitialFallback = Vector.LessThan(centerDistance, new Vector <float>(1e-10f)); initialNormal.X = Vector.ConditionalSelect(useInitialFallback, Vector <float> .Zero, initialNormal.X); initialNormal.Y = Vector.ConditionalSelect(useInitialFallback, Vector <float> .One, initialNormal.Y); initialNormal.Z = Vector.ConditionalSelect(useInitialFallback, Vector <float> .Zero, initialNormal.Z); //Check if the extreme point of the hull toward the triangle along its face normal lies inside the triangle. //If it is, then there's no need for depth refinement. Vector <int> triangleNormalIsMinimal; var hullSupportFinder = default(ConvexHullSupportFinder); DepthRefiner.SimplexWithWitness simplex; var triangleSupportFinder = default(PretransformedTriangleSupportFinder); //Create a simplex entry for the direction from the hull center to triangle center. DepthRefiner.FindSupport(b, triangle, localTriangleCenter, hullLocalTriangleOrientation, ref hullSupportFinder, ref triangleSupportFinder, initialNormal, inactiveLanes, out simplex.A.Support, out simplex.A.SupportOnA); Vector3Wide.Dot(simplex.A.Support, initialNormal, out var depth); simplex.A.Exists = Vector.OnesComplement(inactiveLanes); //Create a simplex entry for the triangle face normal. Vector3Wide.Negate(triangleNormal, out var negatedTriangleNormal); hullSupportFinder.ComputeLocalSupport(b, negatedTriangleNormal, inactiveLanes, out var hullSupportAlongTriangleNormal); simplex.B.SupportOnA = hullSupportAlongTriangleNormal; Vector3Wide.Subtract(simplex.B.SupportOnA, localTriangleCenter, out simplex.B.Support); Vector3Wide.Dot(simplex.B.Support, negatedTriangleNormal, out var triangleFaceDepth); var useTriangleFace = Vector.LessThan(triangleFaceDepth, depth); Vector3Wide.ConditionalSelect(useTriangleFace, negatedTriangleNormal, initialNormal, out initialNormal); depth = Vector.ConditionalSelect(useTriangleFace, triangleFaceDepth, depth); simplex.B.Exists = simplex.A.Exists; simplex.C.Exists = default; //Check if the extreme point on the hull is contained within the bounds of the triangle face. If it is, there is no need for a full depth refinement. Vector3Wide.Subtract(triangleA, hullSupportAlongTriangleNormal, out var closestToA); Vector3Wide.Subtract(triangleB, hullSupportAlongTriangleNormal, out var closestToB); Vector3Wide.Subtract(triangleC, hullSupportAlongTriangleNormal, out var closestToC); Vector3Wide.Dot(edgePlaneAB, closestToA, out var extremeABPlaneTest); Vector3Wide.Dot(edgePlaneBC, closestToB, out var extremeBCPlaneTest); Vector3Wide.Dot(edgePlaneCA, closestToC, out var extremeCAPlaneTest); triangleNormalIsMinimal = Vector.BitwiseAnd(Vector.LessThanOrEqual(extremeABPlaneTest, Vector <float> .Zero), Vector.BitwiseAnd(Vector.LessThanOrEqual(extremeBCPlaneTest, Vector <float> .Zero), Vector.LessThanOrEqual(extremeCAPlaneTest, Vector <float> .Zero))); var depthThreshold = -speculativeMargin; var skipDepthRefine = Vector.BitwiseOr(triangleNormalIsMinimal, inactiveLanes); Vector3Wide localNormal, closestOnHull; if (Vector.EqualsAny(skipDepthRefine, Vector <int> .Zero)) { DepthRefiner.FindMinimumDepth( b, triangle, localTriangleCenter, hullLocalTriangleOrientation, ref hullSupportFinder, ref triangleSupportFinder, ref simplex, initialNormal, depth, skipDepthRefine, 1e-5f * epsilonScale, depthThreshold, out var refinedDepth, out var refinedNormal, out var refinedClosestOnHull); Vector3Wide.ConditionalSelect(skipDepthRefine, hullSupportAlongTriangleNormal, refinedClosestOnHull, out closestOnHull); Vector3Wide.ConditionalSelect(skipDepthRefine, initialNormal, refinedNormal, out localNormal); depth = Vector.ConditionalSelect(skipDepthRefine, depth, refinedDepth); } else { //No depth refine ran; the extreme point prepass did everything we needed. Just use the initial normal. localNormal = initialNormal; closestOnHull = hullSupportAlongTriangleNormal; } Vector3Wide.Dot(triangleNormal, localNormal, out var triangleNormalDotLocalNormal); inactiveLanes = Vector.BitwiseOr(inactiveLanes, Vector.BitwiseOr(Vector.GreaterThanOrEqual(triangleNormalDotLocalNormal, Vector <float> .Zero), Vector.LessThan(depth, depthThreshold))); if (Vector.LessThanAll(inactiveLanes, Vector <int> .Zero)) { //No contacts generated. return; } //To find the contact manifold, we'll clip the triangle edges against the hull face as usual, but we're dealing with potentially //distinct convex hulls. Rather than vectorizing over the different hulls, we vectorize within each hull. Helpers.FillVectorWithLaneIndices(out var slotOffsetIndices); var boundingPlaneEpsilon = 1e-3f * epsilonScale; //There can be no more than 6 contacts (provided there are no numerical errors); 2 per triangle edge. var candidates = stackalloc ManifoldCandidateScalar[6]; for (int slotIndex = 0; slotIndex < pairCount; ++slotIndex) { if (inactiveLanes[slotIndex] < 0) { continue; } ref var hull = ref b.Hulls[slotIndex]; ConvexHullTestHelper.PickRepresentativeFace(ref hull, slotIndex, ref localNormal, closestOnHull, slotOffsetIndices, ref boundingPlaneEpsilon, out var slotFaceNormal, out var slotLocalNormal, out var bestFaceIndex); //Test each triangle edge against the hull face. //Note that we do not use the faceNormal x edgeOffset edge plane, but rather edgeOffset x localNormal. //The faces are wound counterclockwise. //Note that the triangle edges are packed into a Vector4. Historically, there were some minor codegen issues with Vector3. //May not matter anymore, but it costs ~nothing to use a dead slot. ref var aSlot = ref GatherScatter.GetOffsetInstance(ref triangleA, slotIndex);
public void Test( ref CapsuleWide a, ref CapsuleWide b, ref Vector <float> speculativeMargin, ref Vector3Wide offsetB, ref QuaternionWide orientationA, ref QuaternionWide orientationB, int pairCount, out Convex2ContactManifoldWide manifold) { //Compute the closest points between the two line segments. No clamping to begin with. //We want to minimize distance = ||(a + da * ta) - (b + db * tb)||. //Taking the derivative with respect to ta and doing some algebra (taking into account ||da|| == ||db|| == 1) to solve for ta yields: //ta = (da * (b - a) + (db * (a - b)) * (da * db)) / (1 - ((da * db) * (da * db)) QuaternionWide.TransformUnitXY(orientationA, out var xa, out var da); QuaternionWide.TransformUnitY(orientationB, out var db); Vector3Wide.Dot(da, offsetB, out var daOffsetB); Vector3Wide.Dot(db, offsetB, out var dbOffsetB); Vector3Wide.Dot(da, db, out var dadb); //Note potential division by zero when the axes are parallel. Arbitrarily clamp; near zero values will instead produce extreme values which get clamped to reasonable results. var ta = (daOffsetB - dbOffsetB * dadb) / Vector.Max(new Vector <float>(1e-15f), Vector <float> .One - dadb * dadb); //tb = ta * (da * db) - db * (b - a) var tb = ta * dadb - dbOffsetB; //We cannot simply clamp the ta and tb values to the capsule line segments. Instead, project each line segment onto the other line segment, clamping against the target's interval. //That new clamped projected interval is the valid solution space on that line segment. We can clamp the t value by that interval to get the correctly bounded solution. //The projected intervals are: //B onto A: +-BHalfLength * (da * db) + da * offsetB //A onto B: +-AHalfLength * (da * db) - db * offsetB var absdadb = Vector.Abs(dadb); var bOntoAOffset = b.HalfLength * absdadb; var aOntoBOffset = a.HalfLength * absdadb; var aMin = Vector.Max(-a.HalfLength, Vector.Min(a.HalfLength, daOffsetB - bOntoAOffset)); var aMax = Vector.Min(a.HalfLength, Vector.Max(-a.HalfLength, daOffsetB + bOntoAOffset)); var bMin = Vector.Max(-b.HalfLength, Vector.Min(b.HalfLength, -aOntoBOffset - dbOffsetB)); var bMax = Vector.Min(b.HalfLength, Vector.Max(-b.HalfLength, aOntoBOffset - dbOffsetB)); ta = Vector.Min(Vector.Max(ta, aMin), aMax); tb = Vector.Min(Vector.Max(tb, bMin), bMax); Vector3Wide.Scale(da, ta, out var closestPointOnA); Vector3Wide.Scale(db, tb, out var closestPointOnB); Vector3Wide.Add(closestPointOnB, offsetB, out closestPointOnB); //Note that normals are calibrated to point from B to A by convention. Vector3Wide.Subtract(closestPointOnA, closestPointOnB, out manifold.Normal); Vector3Wide.Length(manifold.Normal, out var distance); var inverseDistance = Vector <float> .One / distance; Vector3Wide.Scale(manifold.Normal, inverseDistance, out manifold.Normal); //In the event that the line segments are touching, the normal doesn't exist and we need an alternative. Any direction along the local horizontal (XZ) plane of either capsule //is valid. (Normals along the local Y axes are not guaranteed to be as quick of a path to separation due to nonzero line length.) var normalIsValid = Vector.GreaterThan(distance, new Vector <float>(1e-7f)); Vector3Wide.ConditionalSelect(normalIsValid, manifold.Normal, xa, out manifold.Normal); //In the event that the two capsule axes are coplanar, we accept the whole interval as a source of contact. //As the axes drift away from coplanarity, the accepted interval rapidly narrows to zero length, centered on ta and tb. //We rate the degree of coplanarity based on the angle between the capsule axis and the plane defined by the opposing segment and contact normal: //sin(angle) = dot(da, (db x normal)/||db x normal||) //Finally, note that we are dealing with extremely small angles, and for small angles sin(angle) ~= angle, //and also that fade behavior is completely arbitrary, so we can directly use squared angle without any concern. //angle^2 ~= dot(da, (db x normal))^2 / ||db x normal||^2 //Note that if ||db x normal|| is zero, then any da should be accepted as being coplanar because there is no restriction. ConditionalSelect away the discontinuity. Vector3Wide.CrossWithoutOverlap(db, manifold.Normal, out var planeNormal); Vector3Wide.LengthSquared(planeNormal, out var planeNormalLengthSquared); Vector3Wide.Dot(da, planeNormal, out var numeratorUnsquared); var squaredAngle = Vector.ConditionalSelect(Vector.LessThan(planeNormalLengthSquared, new Vector <float>(1e-10f)), Vector <float> .Zero, numeratorUnsquared * numeratorUnsquared / planeNormalLengthSquared); //Convert the squared angle to a lerp parameter. For squared angle from 0 to lowerThreshold, we should use the full interval (1). From lowerThreshold to upperThreshold, lerp to 0. const float lowerThresholdAngle = 0.01f; const float upperThresholdAngle = 0.05f; const float lowerThreshold = lowerThresholdAngle * lowerThresholdAngle; const float upperThreshold = upperThresholdAngle * upperThresholdAngle; var intervalWeight = Vector.Max(Vector <float> .Zero, Vector.Min(Vector <float> .One, (new Vector <float>(upperThreshold) - squaredAngle) * new Vector <float>(1f / (upperThreshold - lowerThreshold)))); //If the line segments intersect, even if they're coplanar, we would ideally stick to using a single point. Would be easy enough, //but we don't bother because it's such a weird and extremely temporary corner case. Not really worth handling. var weightedTa = ta - ta * intervalWeight; aMin = intervalWeight * aMin + weightedTa; aMax = intervalWeight * aMax + weightedTa; Vector3Wide.Scale(da, aMin, out manifold.OffsetA0); Vector3Wide.Scale(da, aMax, out manifold.OffsetA1); //In the coplanar case, there are two points. We need a method of computing depth which gives a reasonable result to the second contact. //Note that one of the two contacts should end up with a distance equal to the previously computed segment distance, so we're doing some redundant work here. //It's just easier to do that extra work than it would be to track which endpoint contributed the lower distance. //Unproject the final interval endpoints from a back onto b. //dot(offsetB + db * tb0, da) = ta0 //tb0 = (ta0 - daOffsetB) / dadb //distance0 = dot(a0 - (offsetB + tb0 * db), normal) //distance1 = dot(a1 - (offsetB + tb1 * db), normal) Vector3Wide.Dot(db, manifold.Normal, out var dbNormal); Vector3Wide.Subtract(manifold.OffsetA0, offsetB, out var offsetB0); Vector3Wide.Subtract(manifold.OffsetA1, offsetB, out var offsetB1); //Note potential division by zero. In that case, treat both projected points as the closest point. (Handled by the conditional select that chooses the previously computed distance.) var inverseDadb = Vector <float> .One / dadb; var projectedTb0 = Vector.Max(bMin, Vector.Min(bMax, (aMin - daOffsetB) * inverseDadb)); var projectedTb1 = Vector.Max(bMin, Vector.Min(bMax, (aMax - daOffsetB) * inverseDadb)); Vector3Wide.Dot(offsetB0, manifold.Normal, out var b0Normal); Vector3Wide.Dot(offsetB1, manifold.Normal, out var b1Normal); var capsulesArePerpendicular = Vector.LessThan(Vector.Abs(dadb), new Vector <float>(1e-7f)); var distance0 = Vector.ConditionalSelect(capsulesArePerpendicular, distance, b0Normal - dbNormal * projectedTb0); var distance1 = Vector.ConditionalSelect(capsulesArePerpendicular, distance, b1Normal - dbNormal * projectedTb1); var combinedRadius = a.Radius + b.Radius; manifold.Depth0 = combinedRadius - distance0; manifold.Depth1 = combinedRadius - distance1; //Apply the normal offset to the contact positions. var negativeOffsetFromA0 = manifold.Depth0 * 0.5f - a.Radius; var negativeOffsetFromA1 = manifold.Depth1 * 0.5f - a.Radius; Vector3Wide.Scale(manifold.Normal, negativeOffsetFromA0, out var normalPush0); Vector3Wide.Scale(manifold.Normal, negativeOffsetFromA1, out var normalPush1); Vector3Wide.Add(manifold.OffsetA0, normalPush0, out manifold.OffsetA0); Vector3Wide.Add(manifold.OffsetA1, normalPush1, out manifold.OffsetA1); manifold.FeatureId0 = Vector <int> .Zero; manifold.FeatureId1 = Vector <int> .One; var minimumAcceptedDepth = -speculativeMargin; manifold.Contact0Exists = Vector.GreaterThanOrEqual(manifold.Depth0, minimumAcceptedDepth); manifold.Contact1Exists = Vector.BitwiseAnd( Vector.GreaterThanOrEqual(manifold.Depth1, minimumAcceptedDepth), Vector.GreaterThan(aMax - aMin, new Vector <float>(1e-7f) * a.HalfLength)); //TODO: Since we added in the complexity of 2 contact support, this is probably large enough to benefit from working in the local space of one of the capsules. //Worth looking into later. }