Пример #1
0
        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];
Пример #2
0
        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));
        }
Пример #3
0
        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);
        }
Пример #4
0
        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);
        }
Пример #6
0
        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);
        }
Пример #8
0
        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);
        }
Пример #9
0
        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);
        }
Пример #10
0
        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);
        }
Пример #12
0
            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
                }
            }
Пример #13
0
        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);
        }
Пример #14
0
        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)));
        }
Пример #15
0
        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);
Пример #16
0
        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);
Пример #17
0
        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.
        }