コード例 #1
0
        public override void ComputeCollision(ContactSet contactSet, CollisionQueryType type)
        {
            // Note: When comparing this implementation with the GJK (see Gjk.cs), be aware that the
            // GJK implementation computes the CSO as the Minkowski difference A-B whereas the MPR uses
            // B-A. Both representations of the CSO are equivalent, we just have to invert the vectors
            // here and there. (B-A was chosen because the original description of the MPR used B-A.)

            if (type == CollisionQueryType.ClosestPoints)
            {
                throw new GeometryException("MPR cannot handle closest-point queries. Use GJK instead.");
            }

            CollisionObject  collisionObjectA = contactSet.ObjectA;
            IGeometricObject geometricObjectA = collisionObjectA.GeometricObject;
            ConvexShape      shapeA           = geometricObjectA.Shape as ConvexShape;
            Vector3F         scaleA           = geometricObjectA.Scale;
            Pose             poseA            = geometricObjectA.Pose;

            CollisionObject  collisionObjectB = contactSet.ObjectB;
            IGeometricObject geometricObjectB = collisionObjectB.GeometricObject;
            ConvexShape      shapeB           = geometricObjectB.Shape as ConvexShape;
            Vector3F         scaleB           = geometricObjectB.Scale;
            Pose             poseB            = geometricObjectB.Pose;

            if (shapeA == null || shapeB == null)
            {
                throw new ArgumentException("The contact set must contain convex shapes.", "contactSet");
            }

            // Assume no contact.
            contactSet.HaveContact = false;

            Vector3F v0;

            if (contactSet.IsPreferredNormalAvailable && type == CollisionQueryType.Contacts)
            {
                // Set v0, so to shoot into preferred direction.
                v0 = contactSet.PreferredNormal;

                // Perform only 1 MPR iteration.
                DoMpr(type, contactSet, v0);
                return;
            }

            // Find first point v0 (which determines the ray direction).
            // Inner point in CSO (Minkowski difference B-A).
            Vector3F v0A = poseA.ToWorldPosition(shapeA.InnerPoint * scaleA);
            Vector3F v0B = poseB.ToWorldPosition(shapeB.InnerPoint * scaleB);

            v0 = v0B - v0A;

            // If v0 == origin then we have contact.
            if (v0.IsNumericallyZero)
            {
                // The inner points overlap. Probably there are two objects centered on the same point.
                contactSet.HaveContact = true;
                if (type == CollisionQueryType.Boolean)
                {
                    return;
                }

                // Choose a v0 different from Zero. Any direction is ok.
                // The point should still be in the Minkowski difference.
                v0.X = CollisionDetection.Epsilon / 10;
            }

            // Call MPR in iteration until the MPR ray has converged.
            int       iterationCount = 0;
            const int iterationLimit = 10;
            Vector3F  oldMprRay;

            // Use a temporary contact set.
            var testContactSet = ContactSet.Create(collisionObjectA, collisionObjectB);

            testContactSet.IsPerturbationTestAllowed = contactSet.IsPerturbationTestAllowed;
            testContactSet.PreferredNormal           = contactSet.PreferredNormal;

            Contact oldContact = null;

            do
            {
                oldMprRay = v0;
                if (iterationCount == 0)
                {
                    oldMprRay.TryNormalize();
                }

                // Call MPR. v0 of the next iteration is simply -returned portal normal.

                Debug.Assert(testContactSet.Count == 0 || testContactSet.Count == 1, "testContactSet in MPR should have 0 or 1 contacts.");
                Debug.Assert(testContactSet.Count == 0 || testContactSet[0] == oldContact);
                testContactSet.Clear();

                // Because of numerical problems (for example with long thin ellipse vs. capsule)
                // it is possible that the last iteration was a contact but in this iteration
                // no contact is found. Therefore we also reset the HaveContact flag to avoid
                // an end result where HaveContact is set but no Contact is in the ContactSet.
                testContactSet.HaveContact = false;
                v0 = -DoMpr(type, testContactSet, v0);

                if (testContactSet.Count > 0)
                {
                    var newContact = testContactSet[0];
                    if (oldContact != null)
                    {
                        if (oldContact.PenetrationDepth < newContact.PenetrationDepth)
                        {
                            // The new penetration depth is larger then the old penetration depth.
                            // In this case we keep the old contact.
                            // This can happen for nearly parallel boxes. First we get a good contact.
                            // Then we get a contact another side. Normal has changed 90°. The new
                            // penetration depth can be nearly the whole box side length :-(.
                            newContact.Recycle();
                            testContactSet[0] = oldContact;
                            break;
                        }
                    }

                    if (newContact != oldContact)
                    {
                        if (oldContact != null)
                        {
                            oldContact.Recycle();
                        }

                        oldContact = newContact;
                    }
                }

                iterationCount++;
            } while (testContactSet.HaveContact && // Separation? - No contact which we could refine.
                     iterationCount < iterationLimit && // Iteration limit reached?
                     v0 != Vector3F.Zero &&        // Is normal useful to go on?
                     !Vector3F.AreNumericallyEqual(-v0, oldMprRay, CollisionDetection.Epsilon));
            // Normal hasn't converged yet?

            if (testContactSet.Count > 0)
            {
                // Recycle oldContact if not used.
                if (testContactSet[0] != oldContact)
                {
                    if (oldContact != null)
                    {
                        oldContact.Recycle();
                        oldContact = null;
                    }
                }
            }

            if (CollisionDetection.FullContactSetPerFrame &&
                type == CollisionQueryType.Contacts &&
                testContactSet.Count > 0 &&
                contactSet.Count < 3)
            {
                // Try to find full contact set.
                var wrapper = TestMethodWrappers.Obtain();
                wrapper.OriginalMethod = _doMprMethod;
                wrapper.V0             = testContactSet[0].Normal; // The MPR ray will point along the normal of the first contact.

                ContactHelper.TestWithPerturbations(
                    CollisionDetection,
                    testContactSet,
                    true,
                    wrapper.Method);

                TestMethodWrappers.Recycle(wrapper);
            }

            contactSet.HaveContact = testContactSet.HaveContact;
            ContactHelper.Merge(contactSet, testContactSet, type, CollisionDetection.ContactPositionTolerance);

            // Recycle temporary objects.
            testContactSet.Recycle();
        }
コード例 #2
0
        private void ComputeLineVsOther(ContactSet contactSet, CollisionQueryType type, bool objectAIsLine)
        {
            CollisionObject  collisionObjectA = contactSet.ObjectA;
            CollisionObject  collisionObjectB = contactSet.ObjectB;
            IGeometricObject geometricObjectA = collisionObjectA.GeometricObject;
            IGeometricObject geometricObjectB = collisionObjectB.GeometricObject;
            Shape            shapeA           = geometricObjectA.Shape;
            Shape            shapeB           = geometricObjectB.Shape;

            Debug.Assert(
                shapeA is LineShape && !(shapeB is LineShape) ||
                shapeB is LineShape && !(shapeA is LineShape),
                "LineAlgorithm.ComputeLineVsOther should only be called for a line and another shape.");

            CollisionObject  lineCollisionObject;
            IGeometricObject lineGeometricObject;
            IGeometricObject otherGeometricObject;
            LineShape        lineShape;
            Shape            otherShape;

            if (objectAIsLine)
            {
                lineCollisionObject  = collisionObjectA;
                lineGeometricObject  = geometricObjectA;
                lineShape            = (LineShape)shapeA;
                otherGeometricObject = geometricObjectB;
                otherShape           = shapeB;
            }
            else
            {
                lineCollisionObject  = collisionObjectB;
                lineGeometricObject  = geometricObjectB;
                lineShape            = (LineShape)shapeB;
                otherGeometricObject = geometricObjectA;
                otherShape           = shapeA;
            }

            // Apply scaling to line.
            Line     line      = new Line(lineShape);
            Vector3F lineScale = lineGeometricObject.Scale;

            line.Scale(ref lineScale);

            // Step 1: Get any bounding sphere that encloses the other object.
            Aabb     aabb   = otherGeometricObject.Aabb;
            Vector3F center = (aabb.Minimum + aabb.Maximum) / 2;
            float    radius = (aabb.Maximum - aabb.Minimum).Length; // A large safe radius. (Exact size does not matter.)

            // Step 2: Get the closest point of line vs. center.
            // All computations in local space of the line.
            Vector3F closestPointOnLine;
            Pose     linePose = lineGeometricObject.Pose;

            GeometryHelper.GetClosestPoint(line, linePose.ToLocalPosition(center), out closestPointOnLine);

            // Step 3: Crop the line to a line segment that will contain the closest point.
            var lineSegment = ResourcePools.LineSegmentShapes.Obtain();

            lineSegment.Start = closestPointOnLine - line.Direction * radius;
            lineSegment.End   = closestPointOnLine + line.Direction * radius;

            // Use temporary test objects.
            var testGeometricObject = TestGeometricObject.Create();

            testGeometricObject.Shape = lineSegment;
            testGeometricObject.Scale = Vector3F.One;
            testGeometricObject.Pose  = linePose;

            var testCollisionObject = ResourcePools.TestCollisionObjects.Obtain();

            testCollisionObject.SetInternal(lineCollisionObject, testGeometricObject);

            var testContactSet = objectAIsLine ? ContactSet.Create(testCollisionObject, collisionObjectB)
                                         : ContactSet.Create(collisionObjectA, testCollisionObject);

            testContactSet.IsPerturbationTestAllowed = contactSet.IsPerturbationTestAllowed;

            // Step 4: Call another collision algorithm.
            CollisionAlgorithm collisionAlgorithm = CollisionDetection.AlgorithmMatrix[lineSegment, otherShape];

            // Step 5: Manually chosen preferred direction for MPR.
            // For the MPR we choose the best ray direction ourselves. The ray should be normal
            // to the line, otherwise MPR could try to push the line segment out of the other object
            // in the line direction - this cannot work for infinite lines.
            // Results without a manual MPR ray were ok for normal cases. Problems were only observed
            // for cases where the InnerPoints overlap or for deep interpenetrations.
            Vector3F v0A = geometricObjectA.Pose.ToWorldPosition(shapeA.InnerPoint * geometricObjectA.Scale);
            Vector3F v0B = geometricObjectB.Pose.ToWorldPosition(shapeB.InnerPoint * geometricObjectB.Scale);
            Vector3F n   = v0B - v0A; // This is the default MPR ray direction.

            // Make n normal to the line.
            n = n - Vector3F.ProjectTo(n, linePose.ToWorldDirection(lineShape.Direction));
            if (!n.TryNormalize())
            {
                n = lineShape.Direction.Orthonormal1;
            }

            testContactSet.PreferredNormal = n;
            collisionAlgorithm.ComputeCollision(testContactSet, type);

            if (testContactSet.HaveContact)
            {
                contactSet.HaveContact = true;
            }

            ContactHelper.Merge(contactSet, testContactSet, type, CollisionDetection.ContactPositionTolerance);

            // Recycle temporary objects.
            testContactSet.Recycle();
            ResourcePools.TestCollisionObjects.Recycle(testCollisionObject);
            testGeometricObject.Recycle();
            ResourcePools.LineSegmentShapes.Recycle(lineSegment);
        }
コード例 #3
0
        public override void ComputeCollision(ContactSet contactSet, CollisionQueryType type)
        {
            // Object B should be the transformed shape.
            CollisionObject objectA = contactSet.ObjectA;
            CollisionObject objectB = contactSet.ObjectB;

            // Swap objects if necessary.
            bool swapped = !(objectB.GeometricObject.Shape is TransformedShape);

            if (swapped)
            {
                MathHelper.Swap(ref objectA, ref objectB);
            }

            IGeometricObject geometricObjectB = objectB.GeometricObject;
            TransformedShape transformedShape = geometricObjectB.Shape as TransformedShape;

            // Check if collision objects shapes are correct.
            if (transformedShape == null)
            {
                throw new ArgumentException("The contact set must contain a transformed shape.", "contactSet");
            }

            // Assume no contact.
            contactSet.HaveContact = false;

            // Currently object B has the following structure:
            //
            //  CollisionObject           objectB
            //    GeometricObject           geometricObjectB
            //      Pose                      poseB
            //      Scale                     scaleB
            //      TransformedShape          transformedShape
            //        GeometricObject             childGeometricObject
            //          Pose                      childPose
            //          Scale                     childScale
            //          Shape                     childShape
            //
            // To compute the collisions we temporarily remove the TransformedShape:
            // We replace the original geometric object with a test geometric object and combine the
            // transformations:
            //
            //  CollisionObject           testObjectB
            //    GeometricObject           testGeometricObjectB
            //      Pose                      poseB * childPose
            //      Shape                     childShape
            //      Scale                     scaleB * childScale

            Pose     poseB  = geometricObjectB.Pose;
            Vector3F scaleB = geometricObjectB.Scale;

            // Apply scale to pose and test geometric object.
            // (Note: The scaling is either uniform or the transformed object has no local rotation.
            // Therefore, we only need to apply the scale of the parent to the scale and translation of
            // the child. We can ignore the rotation.)
            IGeometricObject childGeometricObject = transformedShape.Child;
            Pose             childPose            = childGeometricObject.Pose;

            // Non-uniform scaling is not supported for rotated child objects.
            if ((scaleB.X != scaleB.Y || scaleB.Y != scaleB.Z) && childPose.HasRotation)
            {
                throw new NotSupportedException("Computing collisions for transformed shapes with local rotations and non-uniform scaling is not supported.");
            }

            childPose.Position *= scaleB;                 // Apply scaling to local translation.

            var testGeometricObjectB = TestGeometricObject.Create();

            testGeometricObjectB.Shape = childGeometricObject.Shape;
            testGeometricObjectB.Scale = scaleB * childGeometricObject.Scale; // Apply scaling to local scale.
            testGeometricObjectB.Pose  = poseB * childPose;

            var testCollisionObjectB = ResourcePools.TestCollisionObjects.Obtain();

            testCollisionObjectB.SetInternal(objectB, testGeometricObjectB);

            var testContactSet = swapped ? ContactSet.Create(testCollisionObjectB, objectA)
                                   : ContactSet.Create(objectA, testCollisionObjectB);

            testContactSet.IsPerturbationTestAllowed = contactSet.IsPerturbationTestAllowed;

            // Transform contacts into space of child and copy them into the testContactSet.
            //int numberOfOldContacts = contactSet.Count;
            //for (int i = 0; i < numberOfOldContacts; i++)
            //{
            //  Contact contact = contactSet[i];
            //  if (swapped)
            //    contact.PositionALocal = childPose.ToLocalPosition(contact.PositionALocal);
            //  else
            //    contact.PositionBLocal = childPose.ToLocalPosition(contact.PositionBLocal);
            //
            //  testContactSet.Add(contact);
            //}

            // Compute collision.
            var collisionAlgorithm = CollisionDetection.AlgorithmMatrix[objectA, testCollisionObjectB];

            collisionAlgorithm.ComputeCollision(testContactSet, type);

            if (testContactSet.HaveContact)
            {
                contactSet.HaveContact = true;
            }

            // Transform contacts into space of parent TransformShape.
            int numberOfNewContacts = testContactSet.Count;

            for (int i = 0; i < numberOfNewContacts; i++)
            {
                Contact contact = testContactSet[i];
                if (swapped)
                {
                    contact.PositionALocal = childPose.ToWorldPosition(contact.PositionALocal);
                }
                else
                {
                    contact.PositionBLocal = childPose.ToWorldPosition(contact.PositionBLocal);
                }
            }

            // Merge new contacts to contactSet.
            // (If testContactSet contains all original contacts (see commented out part above), we can
            // simply clear contactSet and copy all contacts of testContactSet.)
            //contactSet.Clear();
            //foreach (Contact contact in testContactSet)
            //  contactSet.Add(contact);
            ContactHelper.Merge(contactSet, testContactSet, type, CollisionDetection.ContactPositionTolerance);

            // Recycle temporary objects.
            testContactSet.Recycle();
            ResourcePools.TestCollisionObjects.Recycle(testCollisionObjectB);
            testGeometricObjectB.Recycle();
        }
コード例 #4
0
        public override void ComputeCollision(ContactSet contactSet, CollisionQueryType type)
        {
            if (type == CollisionQueryType.ClosestPoints)
            {
                // Just use normal composite shape algorithm.
                _compositeAlgorithm.ComputeCollision(contactSet, type);
                return;
            }

            Debug.Assert(type != CollisionQueryType.ClosestPoints, "Closest point queries should have already been handled!");

            // Composite = A, Ray = B
            IGeometricObject compositeObject = contactSet.ObjectA.GeometricObject;
            IGeometricObject rayObject       = contactSet.ObjectB.GeometricObject;

            // Object A should be the composite, swap objects if necessary.
            bool swapped = (compositeObject.Shape is RayShape);

            if (swapped)
            {
                MathHelper.Swap(ref rayObject, ref compositeObject);
            }

            RayShape       rayShape       = rayObject.Shape as RayShape;
            CompositeShape compositeShape = compositeObject.Shape as CompositeShape;

            // Check if shapes are correct.
            if (rayShape == null || compositeShape == null)
            {
                throw new ArgumentException("The contact set must contain a ray and a composite shape.", "contactSet");
            }

            // Assume no contact.
            contactSet.HaveContact = false;

            // Get transformations.
            Vector3F rayScale       = rayObject.Scale;
            Pose     rayPose        = rayObject.Pose;
            Vector3F compositeScale = compositeObject.Scale;
            Pose     compositePose  = compositeObject.Pose;

            // Check if transforms are supported.
            // Same check for object B.
            if (compositeShape != null &&
                (compositeScale.X != compositeScale.Y || compositeScale.Y != compositeScale.Z) &&
                compositeShape.Children.Any(child => child.Pose.HasRotation)) // Note: Any() creates garbage, but non-uniform scalings should not be used anyway.
            {
                throw new NotSupportedException("Computing collisions for composite shapes with non-uniform scaling and rotated children is not supported.");
            }

            // ----- A few fixed objects which are reused to avoid GC garbage.
            var testCollisionObject = ResourcePools.TestCollisionObjects.Obtain();
            var testGeometricObject = TestGeometricObject.Create();

            // Create a test contact set and initialize with dummy objects.
            // (The actual collision objects are set below.)
            var testContactSet = ContactSet.Create(testCollisionObject, contactSet.ObjectB); // Dummy arguments! They are changed later.

            // Scale ray and transform ray to local unscaled space of composite.
            Ray rayWorld = new Ray(rayShape);

            rayWorld.Scale(ref rayScale);  // Scale ray.
            rayWorld.ToWorld(ref rayPose); // Transform ray to world space.
            Ray ray = rayWorld;

            ray.ToLocal(ref compositePose); // Transform ray to local space of composite.
            var inverseCompositeScale = Vector3F.One / compositeScale;

            ray.Scale(ref inverseCompositeScale);

            try
            {
                if (compositeShape.Partition != null)
                {
                    #region ----- Composite with BVH vs. * -----

                    foreach (var childIndex in compositeShape.Partition.GetOverlaps(ray))
                    {
                        if (type == CollisionQueryType.Boolean && contactSet.HaveContact)
                        {
                            break; // We can abort early.
                        }
                        AddChildContacts(
                            contactSet,
                            swapped,
                            childIndex,
                            type,
                            testContactSet,
                            testCollisionObject,
                            testGeometricObject);
                    }
                    #endregion
                }
                else
                {
                    #region ----- Composite vs. *-----

                    var rayDirectionInverse = new Vector3F(
                        1 / ray.Direction.X,
                        1 / ray.Direction.Y,
                        1 / ray.Direction.Z);

                    float epsilon = Numeric.EpsilonF * (1 + compositeObject.Aabb.Extent.Length);

                    // Go through list of children and find contacts.
                    int numberOfChildGeometries = compositeShape.Children.Count;
                    for (int i = 0; i < numberOfChildGeometries; i++)
                    {
                        IGeometricObject child = compositeShape.Children[i];

                        if (GeometryHelper.HaveContact(child.Shape.GetAabb(child.Scale, child.Pose), ray.Origin, rayDirectionInverse, ray.Length, epsilon))
                        {
                            AddChildContacts(
                                contactSet,
                                swapped,
                                i,
                                type,
                                testContactSet,
                                testCollisionObject,
                                testGeometricObject);

                            // We have contact and stop for boolean queries.
                            if (contactSet.HaveContact && type == CollisionQueryType.Boolean)
                            {
                                break;
                            }
                        }
                    }
                    #endregion
                }
            }
            finally
            {
                Debug.Assert(compositeObject.Shape == compositeShape, "Shape was altered and not restored.");

                testContactSet.Recycle();
                ResourcePools.TestCollisionObjects.Recycle(testCollisionObject);
                testGeometricObject.Recycle();
            }
        }
コード例 #5
0
        public void ConstructorException3()
        {
            CollisionObject a = new CollisionObject();

            ContactSet.Create(a, a);
        }
コード例 #6
0
        public override void ComputeCollision(ContactSet contactSet, CollisionQueryType type)
        {
            // Object A should be the height field.
            CollisionObject heightFieldCollisionObject = contactSet.ObjectA;
            CollisionObject otherCollisionObject       = contactSet.ObjectB;

            // Swap objects if necessary.
            bool swapped = !(heightFieldCollisionObject.GeometricObject.Shape is HeightField);

            if (swapped)
            {
                MathHelper.Swap(ref heightFieldCollisionObject, ref otherCollisionObject);
            }

            IGeometricObject heightFieldGeometricObject = heightFieldCollisionObject.GeometricObject;
            IGeometricObject otherGeometricObject       = otherCollisionObject.GeometricObject;
            HeightField      heightField = heightFieldGeometricObject.Shape as HeightField;
            Shape            otherShape  = otherGeometricObject.Shape;

            // Check if collision object shapes are correct.
            if (heightField == null)
            {
                throw new ArgumentException("The contact set must contain a height field.", "contactSet");
            }

            if (heightField.UseFastCollisionApproximation && type != CollisionQueryType.ClosestPoints)
            {
                // If other object is convex, use the new fast collision detection algorithm.
                ConvexShape convex = otherShape as ConvexShape;
                if (convex != null)
                {
                    ComputeCollisionFast(
                        contactSet,
                        type,
                        heightFieldGeometricObject,
                        otherGeometricObject,
                        heightField,
                        convex,
                        swapped);
                    return;
                }
            }



            Vector3 scaleHeightField = heightFieldGeometricObject.Scale;
            Vector3 scaleOther       = otherGeometricObject.Scale;
            Pose    heightFieldPose  = heightFieldGeometricObject.Pose;

            // We do not support negative scaling. It is not clear what should happen when y is
            // scaled with a negative factor and triangle orders would be wrong... Not worth the trouble.
            if (scaleHeightField.X < 0 || scaleHeightField.Y < 0 || scaleHeightField.Z < 0)
            {
                throw new NotSupportedException("Computing collisions for height fields with a negative scaling is not supported.");
            }

            // Get height field and basic info.
            Vector3 heightFieldUpAxis = heightFieldPose.ToWorldDirection(Vector3.UnitY);
            int     arrayLengthX      = heightField.NumberOfSamplesX;
            int     arrayLengthZ      = heightField.NumberOfSamplesZ;

            Debug.Assert(arrayLengthX > 1 && arrayLengthZ > 1, "A height field should contain at least 2 x 2 elements (= 1 cell).");
            float cellWidthX = heightField.WidthX * scaleHeightField.X / (arrayLengthX - 1);
            float cellWidthZ = heightField.WidthZ * scaleHeightField.Z / (arrayLengthZ - 1);

            // The search-space is the rectangular region on the height field where the closest points
            // must lie in. For contacts we do not have to search neighbor cells. For closest-point
            // queries and separation we have to search neighbor cells.
            // We compute the search-space using a current maximum search distance.
            float   currentSearchDistance = 0;
            Contact guessedClosestPair    = null;

            if (!contactSet.HaveContact && type == CollisionQueryType.ClosestPoints)
            {
                // Make a guess for the closest pair using SupportMapping or InnerPoints.
                bool isOverHole;
                guessedClosestPair = GuessClosestPair(contactSet, swapped, out isOverHole);
                if (isOverHole)
                {
                    // Guesses over holes are useless. --> Check the whole terrain.
                    currentSearchDistance = heightFieldGeometricObject.Aabb.Extent.Length;
                }
                else if (guessedClosestPair.PenetrationDepth < 0)
                {
                    currentSearchDistance = -guessedClosestPair.PenetrationDepth;
                }
                else
                {
                    contactSet.HaveContact = true;
                }
            }
            else
            {
                // Assume no contact.
                contactSet.HaveContact = false;
            }

            // Get AABB of the other object in local space of the height field.
            Aabb aabbOfOther = otherShape.GetAabb(scaleOther, heightFieldPose.Inverse * otherGeometricObject.Pose);

            float originX = heightField.OriginX * scaleHeightField.X;
            float originZ = heightField.OriginZ * scaleHeightField.Z;

            // ----- Compute the cell indices of the search-space.
            // Estimate start and end indices from our search distance.
            int xIndexStartEstimated = (int)((aabbOfOther.Minimum.X - currentSearchDistance - originX) / cellWidthX);
            int xIndexEndEstimated   = (int)((aabbOfOther.Maximum.X + currentSearchDistance - originX) / cellWidthX);
            int zIndexStartEstimated = (int)((aabbOfOther.Minimum.Z - currentSearchDistance - originZ) / cellWidthZ);
            int zIndexEndEstimated   = (int)((aabbOfOther.Maximum.Z + currentSearchDistance - originZ) / cellWidthZ);

            // Clamp indices to valid range.
            int xIndexMax = arrayLengthX - 2;
            int zIndexMax = arrayLengthZ - 2;

            int xIndexStart = Math.Max(xIndexStartEstimated, 0);
            int xIndexEnd   = Math.Min(xIndexEndEstimated, xIndexMax);
            int zIndexStart = Math.Max(zIndexStartEstimated, 0);
            int zIndexEnd   = Math.Min(zIndexEndEstimated, zIndexMax);

            // Find collision algorithm for MinkowskiSum vs. other object's shape.
            CollisionAlgorithm collisionAlgorithm = CollisionDetection.AlgorithmMatrix[typeof(ConvexShape), otherShape.GetType()];

            int numberOfContactsInLastFrame = contactSet.Count;



            // Create several temporary test objects:
            // Instead of the original height field geometric object, we test against a shape for each
            // height field triangle. For the test shape we "extrude" the triangle under the height field.
            // To create the extrusion we "add" a line segment to the triangle using a Minkowski sum.
            // TODO: We can make this faster with a special shape that knows that the child poses are Identity (instead of the standard MinkowskiSumShape).
            // This special shape could compute its InnerPoint without applying the poses.

            var triangleShape = ResourcePools.TriangleShapes.Obtain();
            // (Vertices will be set in the loop below.)

            var triangleGeometricObject = TestGeometricObject.Create();

            triangleGeometricObject.Shape = triangleShape;

            var lineSegment = ResourcePools.LineSegmentShapes.Obtain();

            lineSegment.Start = Vector3.Zero;
            lineSegment.End   = -heightField.Depth * Vector3.UnitY;

            var lineSegmentGeometricObject = TestGeometricObject.Create();

            lineSegmentGeometricObject.Shape = lineSegment;

            var extrudedTriangleShape = TestMinkowskiSumShape.Create();

            extrudedTriangleShape.ObjectA = triangleGeometricObject;
            extrudedTriangleShape.ObjectB = lineSegmentGeometricObject;

            var extrudedTriangleGeometricObject = TestGeometricObject.Create();

            extrudedTriangleGeometricObject.Shape = extrudedTriangleShape;
            extrudedTriangleGeometricObject.Pose  = heightFieldPose;

            var testCollisionObject = ResourcePools.TestCollisionObjects.Obtain();

            testCollisionObject.SetInternal(heightFieldCollisionObject, extrudedTriangleGeometricObject);

            var testContactSet = swapped ? ContactSet.Create(contactSet.ObjectA, testCollisionObject)
                                   : ContactSet.Create(testCollisionObject, contactSet.ObjectB);

            testContactSet.IsPerturbationTestAllowed = false;

            // We compute closest points with a preferred normal direction: the height field up-axis.

            // Loop over the cells in the search space.
            // (The inner loop can reduce the search space. Therefore, when we increment the indices
            // xIndex and zIndex we also check if the start indices have changed.)
            for (int xIndex = xIndexStart; xIndex <= xIndexEnd; xIndex = Math.Max(xIndexStart, xIndex + 1))
            {
                for (int zIndex = zIndexStart; zIndex <= zIndexEnd; zIndex = Math.Max(zIndexStart, zIndex + 1))
                {
                    // Test the two cell triangles.
                    for (int triangleIndex = 0; triangleIndex < 2; triangleIndex++)
                    {
                        // Get triangle 0 or 1.
                        var triangle = heightField.GetTriangle(xIndex, zIndex, triangleIndex != 0);

                        var triangleIsHole = Numeric.IsNaN(triangle.Vertex0.Y * triangle.Vertex1.Y * triangle.Vertex2.Y);
                        if (triangleIsHole)
                        {
                            continue;
                        }

                        triangleShape.Vertex0 = triangle.Vertex0 * scaleHeightField;
                        triangleShape.Vertex1 = triangle.Vertex1 * scaleHeightField;
                        triangleShape.Vertex2 = triangle.Vertex2 * scaleHeightField;

                        if (type == CollisionQueryType.Boolean)
                        {
                            collisionAlgorithm.ComputeCollision(testContactSet, CollisionQueryType.Boolean);
                            contactSet.HaveContact = contactSet.HaveContact || testContactSet.HaveContact;
                            if (contactSet.HaveContact)
                            {
                                // We can stop tests here for boolean queries.
                                // Update end indices to exit the outer loops.
                                xIndexEnd = -1;
                                zIndexEnd = -1;
                                break;
                            }
                        }
                        else
                        {
                            Debug.Assert(testContactSet.Count == 0, "testContactSet needs to be cleared.");

                            // If we know that we have a contact, then we can make a faster contact query
                            // instead of a closest-point query.
                            CollisionQueryType queryType = (contactSet.HaveContact) ? CollisionQueryType.Contacts : type;

                            collisionAlgorithm.ComputeCollision(testContactSet, queryType);

                            if (testContactSet.HaveContact)
                            {
                                contactSet.HaveContact = true;

                                if (testContactSet.Count > 0)
                                {
                                    // Get neighbor triangle.

                                    // To compute the triangle normal we take the normal of the unscaled triangle and transform
                                    // the normal with: (M^-1)^T = 1 / scale
                                    // Note: We cannot use the scaled vertices because negative scalings change the
                                    // face-order of the vertices.
                                    Vector3 triangleNormal = triangle.Normal / scaleHeightField;
                                    triangleNormal = heightFieldPose.ToWorldDirection(triangleNormal);
                                    triangleNormal.TryNormalize();

                                    // Assuming the last contact is the newest. (With closest-point queries
                                    // and the CombinedCollisionAlgo, testContactSet[0] could be a (not so useful)
                                    // closest-point result, and testContactSet[1] the better contact query result.)
                                    var testContact   = testContactSet[testContactSet.Count - 1];
                                    var contactNormal = swapped ? -testContact.Normal : testContact.Normal;
                                    if (Vector3.Dot(contactNormal, triangleNormal) < WeldingLimit)
                                    {
                                        // Contact normal deviates by more than the welding limit. --> Check the contact.

                                        // If we do not find a neighbor, we assume the neighbor has the same normal.
                                        var neighborNormal = triangleNormal;



                                        // Get barycentric coordinates of contact position.
                                        Vector3 contactPositionOnHeightField = swapped ? testContact.PositionBLocal / scaleHeightField : testContact.PositionALocal / scaleHeightField;
                                        float   u, v, w;
                                        // TODO: GetBaryCentricFromPoint computes the triangle normal, which we already know - optimize.
                                        GeometryHelper.GetBarycentricFromPoint(triangle, contactPositionOnHeightField, out u, out v, out w);

                                        // If one coordinate is near 0, the contact is near an edge.
                                        if (u < 0.05f || v < 0.05f || w < 0.05f)
                                        {
                                            if (triangleIndex == 0)
                                            {
                                                if (u < v && u < w)
                                                {
                                                    neighborNormal = heightFieldPose.ToWorldDirection(heightField.GetTriangle(xIndex, zIndex, true).Normal / scaleHeightField);
                                                    neighborNormal.TryNormalize();
                                                }
                                                else if (v < w)
                                                {
                                                    if (zIndex > 0)
                                                    {
                                                        neighborNormal = heightFieldPose.ToWorldDirection(heightField.GetTriangle(xIndex, zIndex - 1, true).Normal / scaleHeightField);
                                                        neighborNormal.TryNormalize();
                                                    }
                                                    else
                                                    {
                                                        // The contact is at the border of the whole height field. Set a normal which disables all bad contact filtering.
                                                        neighborNormal = new Vector3(float.NaN);
                                                    }
                                                }
                                                else
                                                {
                                                    if (xIndex > 0)
                                                    {
                                                        neighborNormal = heightFieldPose.ToWorldDirection(heightField.GetTriangle(xIndex - 1, zIndex, true).Normal / scaleHeightField);
                                                        neighborNormal.TryNormalize();
                                                    }
                                                    else
                                                    {
                                                        neighborNormal = new Vector3(float.NaN);
                                                    }
                                                }
                                            }
                                            else
                                            {
                                                if (u < v && u < w)
                                                {
                                                    if (xIndex + 2 < arrayLengthX)
                                                    {
                                                        neighborNormal = heightFieldPose.ToWorldDirection(heightField.GetTriangle(xIndex + 1, zIndex, false).Normal / scaleHeightField);
                                                        neighborNormal.TryNormalize();
                                                    }
                                                    else
                                                    {
                                                        neighborNormal = new Vector3(float.NaN);
                                                    }
                                                }
                                                else if (v < w)
                                                {
                                                    neighborNormal = heightFieldPose.ToWorldDirection(heightField.GetTriangle(xIndex, zIndex, false).Normal / scaleHeightField);
                                                    neighborNormal.TryNormalize();
                                                }
                                                else
                                                {
                                                    if (zIndex + 2 < arrayLengthZ)
                                                    {
                                                        neighborNormal = heightFieldPose.ToWorldDirection(heightField.GetTriangle(xIndex, zIndex + 1, true).Normal / scaleHeightField);
                                                        neighborNormal.TryNormalize();
                                                    }
                                                    else
                                                    {
                                                        neighborNormal = new Vector3(float.NaN);
                                                    }
                                                }
                                            }
                                        }


                                        // Contact normals in the range triangleNormal - neighborNormal are allowed.
                                        // Others, especially vertical contacts in slopes or horizontal normals are not
                                        // allowed.
                                        var cosMinAngle = Vector3.Dot(neighborNormal, triangleNormal) - CollisionDetection.Epsilon;
                                        RemoveBadContacts(swapped, testContactSet, triangleNormal, cosMinAngle);

                                        // If we have no contact yet, we retry with a preferred normal identical to the up axis.
                                        // (Note: contactSet.Count will be > 0 for closest-point queries but will
                                        // probably constraint separated contacts and not real contacts.)
                                        if (testContactSet.Count == 0 &&
                                            (contactSet.Count == 0 || type == CollisionQueryType.ClosestPoints))
                                        {
                                            testContactSet.PreferredNormal = (swapped) ? -heightFieldUpAxis : heightFieldUpAxis;
                                            collisionAlgorithm.ComputeCollision(testContactSet, CollisionQueryType.Contacts);
                                            testContactSet.PreferredNormal = Vector3.Zero;
                                            RemoveBadContacts(swapped, testContactSet, triangleNormal, cosMinAngle);
                                        }

                                        // If we have no contact yet, we retry with a preferred normal identical to the triangle normal.
                                        // But only if the triangle normal differs significantly from the up axis.
                                        if (testContactSet.Count == 0 &&
                                            (contactSet.Count == 0 || type == CollisionQueryType.ClosestPoints) &&
                                            Vector3.Dot(heightFieldUpAxis, triangleNormal) < WeldingLimit)
                                        {
                                            testContactSet.PreferredNormal = (swapped) ? -triangleNormal : triangleNormal;
                                            collisionAlgorithm.ComputeCollision(testContactSet, CollisionQueryType.Contacts);
                                            testContactSet.PreferredNormal = Vector3.Zero;
                                            RemoveBadContacts(swapped, testContactSet, triangleNormal, cosMinAngle);
                                        }
                                    }
                                }
                            }

                            if (testContactSet.Count > 0)
                            {
                                // Remember separation distance for later.
                                float separationDistance = -testContactSet[0].PenetrationDepth;

                                // Set the shape feature of the new contacts.
                                // The features is the height field triangle index (see HeightField documentation).
                                int numberOfContacts = testContactSet.Count;
                                for (int i = 0; i < numberOfContacts; i++)
                                {
                                    Contact contact      = testContactSet[i];
                                    int     featureIndex = (zIndex * (arrayLengthX - 1) + xIndex) * 2 + triangleIndex;
                                    if (swapped)
                                    {
                                        contact.FeatureB = featureIndex;
                                    }
                                    else
                                    {
                                        contact.FeatureA = featureIndex;
                                    }
                                }

                                // Merge the contact info. (Contacts in testContactSet are recycled!)
                                ContactHelper.Merge(contactSet, testContactSet, type, CollisionDetection.ContactPositionTolerance);



                                // Update search space if possible.
                                // The best search distance is 0. For separation we can use the current smallest
                                // separation as search distance. As soon as we have a contact, we set the
                                // search distance to 0.
                                if (currentSearchDistance > 0 &&                  // No need to update search space if search distance is already 0.
                                    (contactSet.HaveContact || // If we have a contact, we set the search distance to 0.
                                     separationDistance < currentSearchDistance)) // If we have closer separation, we use this.
                                {
                                    // Note: We only check triangleContactSet[0] in the if condition.
                                    // triangleContactSet could contain several contacts, but we don't bother with
                                    // this special case.

                                    // Update search distance.
                                    if (contactSet.HaveContact)
                                    {
                                        currentSearchDistance = 0;
                                    }
                                    else
                                    {
                                        currentSearchDistance = Math.Max(0, separationDistance);
                                    }

                                    // Update search space indices.
                                    xIndexStartEstimated = (int)((aabbOfOther.Minimum.X - currentSearchDistance - originX) / cellWidthX);
                                    xIndexEndEstimated   = (int)((aabbOfOther.Maximum.X + currentSearchDistance - originX) / cellWidthX);
                                    zIndexStartEstimated = (int)((aabbOfOther.Minimum.Z - currentSearchDistance - originZ) / cellWidthZ);
                                    zIndexEndEstimated   = (int)((aabbOfOther.Maximum.Z + currentSearchDistance - originZ) / cellWidthZ);

                                    xIndexStart = Math.Max(xIndexStart, xIndexStartEstimated);
                                    xIndexEnd   = Math.Min(xIndexEndEstimated, xIndexMax);
                                    zIndexStart = Math.Max(zIndexStart, zIndexStartEstimated);
                                    zIndexEnd   = Math.Min(zIndexEndEstimated, zIndexMax);
                                }
                            }
                        }
                    }
                }
            }

            // Recycle temporary objects.
            testContactSet.Recycle();
            ResourcePools.TestCollisionObjects.Recycle(testCollisionObject);
            extrudedTriangleGeometricObject.Recycle();
            extrudedTriangleShape.Recycle();
            lineSegmentGeometricObject.Recycle();
            ResourcePools.LineSegmentShapes.Recycle(lineSegment);
            triangleGeometricObject.Recycle();
            ResourcePools.TriangleShapes.Recycle(triangleShape);



            if (contactSet.Count == 0 &&
                (contactSet.HaveContact && type == CollisionQueryType.Contacts || type == CollisionQueryType.ClosestPoints))
            {
                // ----- Bad contact info:
                // We should have contact data because this is either a contact query and the objects touch
                // or this is a closest-point query.
                // Use our guess as the contact info.
                Contact closestPair = guessedClosestPair;
                bool    isOverHole  = false;
                if (closestPair == null)
                {
                    closestPair = GuessClosestPair(contactSet, swapped, out isOverHole);
                }

                // Guesses over holes are useless. :-(
                if (!isOverHole)
                {
                    ContactHelper.Merge(contactSet, closestPair, type, CollisionDetection.ContactPositionTolerance);
                }
            }



            if (CollisionDetection.FullContactSetPerFrame &&
                type == CollisionQueryType.Contacts &&
                numberOfContactsInLastFrame == 0 &&
                contactSet.Count > 0 &&
                contactSet.Count < 4)
            {
                // Try to find full contact set.
                // TODO: This can be optimized by not doing the whole overhead of ComputeCollision again.
                ContactHelper.TestWithPerturbations(
                    CollisionDetection,
                    contactSet,
                    !swapped, // Perturb objectB not the height field.
                    _computeContactsMethod);
            }
        }
コード例 #7
0
 public void ConstructorException()
 {
     ContactSet.Create(null, new CollisionObject());
 }
コード例 #8
0
 public void ConstructorException2()
 {
     ContactSet.Create(new CollisionObject(), null);
 }
コード例 #9
0
        public override void ComputeCollision(ContactSet contactSet, CollisionQueryType type)
        {
            CollisionObject  collisionObjectA = contactSet.ObjectA;
            CollisionObject  collisionObjectB = contactSet.ObjectB;
            IGeometricObject geometricObjectA = collisionObjectA.GeometricObject;
            IGeometricObject geometricObjectB = collisionObjectB.GeometricObject;

            // Object A should be the composite, swap objects if necessary.
            // When testing CompositeShape vs. CompositeShape with BVH, object A should be the
            // CompositeShape with BVH.
            CompositeShape compositeShapeA = geometricObjectA.Shape as CompositeShape;
            CompositeShape compositeShapeB = geometricObjectB.Shape as CompositeShape;
            bool           swapped         = false;

            if (compositeShapeA == null)
            {
                // Object A is something else. Object B must be a composite shape.
                swapped = true;
            }
            else if (compositeShapeA.Partition == null &&
                     compositeShapeB != null &&
                     compositeShapeB.Partition != null)
            {
                // Object A has no BVH, object B is CompositeShape with BVH.
                swapped = true;
            }

            if (swapped)
            {
                MathHelper.Swap(ref collisionObjectA, ref collisionObjectB);
                MathHelper.Swap(ref geometricObjectA, ref geometricObjectB);
                MathHelper.Swap(ref compositeShapeA, ref compositeShapeB);
            }

            // Check if collision objects shapes are correct.
            if (compositeShapeA == null)
            {
                throw new ArgumentException("The contact set must contain a composite shape.", "contactSet");
            }

            // Assume no contact.
            contactSet.HaveContact = false;

            Vector3 scaleA = geometricObjectA.Scale;
            Vector3 scaleB = geometricObjectB.Scale;

            // Check if transforms are supported.
            if (compositeShapeA != null &&                                     // When object A is a CompositeShape
                (scaleA.X != scaleA.Y || scaleA.Y != scaleA.Z) &&              // non-uniform scaling is not supported
                compositeShapeA.Children.Any(child => child.Pose.HasRotation)) // when a child has a local rotation.
            {                                                                  // Note: Any() creates garbage, but non-uniform scalings should not be used anyway.
                throw new NotSupportedException("Computing collisions for composite shapes with non-uniform scaling and rotated children is not supported.");
            }

            // Same check for object B.
            if (compositeShapeB != null &&
                (scaleB.X != scaleB.Y || scaleB.Y != scaleB.Z) &&
                compositeShapeB.Children.Any(child => child.Pose.HasRotation)) // Note: Any() creates garbage, but non-uniform scalings should not be used anyway.
            {
                throw new NotSupportedException("Computing collisions for composite shapes with non-uniform scaling and rotated children is not supported.");
            }

            // ----- A few fixed objects which are reused to avoid GC garbage.
            var testCollisionObjectA = ResourcePools.TestCollisionObjects.Obtain();
            var testCollisionObjectB = ResourcePools.TestCollisionObjects.Obtain();

            // Create a test contact set and initialize with dummy objects.
            // (The actual collision objects are set below.)
            var testContactSet       = ContactSet.Create(testCollisionObjectA, testCollisionObjectB);
            var testGeometricObjectA = TestGeometricObject.Create();
            var testGeometricObjectB = TestGeometricObject.Create();

            try
            {
                if (compositeShapeA.Partition != null &&
                    (type != CollisionQueryType.ClosestPoints ||
                     compositeShapeA.Partition is ISupportClosestPointQueries <int>))
                {
                    if (compositeShapeB != null && compositeShapeB.Partition != null)
                    {
                        Debug.Assert(swapped == false, "Why did we swap the objects? Order of objects is fine.");

                        if (type != CollisionQueryType.ClosestPoints)
                        {
                            // ----- Boolean or Contact Query

                            // Heuristic: Test large BVH vs. small BVH.
                            Aabb  aabbOfA        = geometricObjectA.Aabb;
                            Aabb  aabbOfB        = geometricObjectB.Aabb;
                            float largestExtentA = aabbOfA.Extent.LargestComponent;
                            float largestExtentB = aabbOfB.Extent.LargestComponent;
                            IEnumerable <Pair <int> > overlaps;
                            bool overlapsSwapped = largestExtentA < largestExtentB;
                            if (overlapsSwapped)
                            {
                                overlaps = compositeShapeB.Partition.GetOverlaps(
                                    scaleB,
                                    geometricObjectB.Pose,
                                    compositeShapeA.Partition,
                                    scaleA,
                                    geometricObjectA.Pose);
                            }
                            else
                            {
                                overlaps = compositeShapeA.Partition.GetOverlaps(
                                    scaleA,
                                    geometricObjectA.Pose,
                                    compositeShapeB.Partition,
                                    scaleB,
                                    geometricObjectB.Pose);
                            }

                            foreach (var overlap in overlaps)
                            {
                                if (type == CollisionQueryType.Boolean && contactSet.HaveContact)
                                {
                                    break; // We can abort early.
                                }
                                AddChildChildContacts(
                                    contactSet,
                                    overlapsSwapped ? overlap.Second : overlap.First,
                                    overlapsSwapped ? overlap.First : overlap.Second,
                                    type,
                                    testContactSet,
                                    testCollisionObjectA,
                                    testGeometricObjectA,
                                    testCollisionObjectB,
                                    testGeometricObjectB);
                            }
                        }
                        else
                        {
                            // Closest-Point Query

                            var callback = ClosestPointsCallbacks.Obtain();
                            callback.CollisionAlgorithm   = this;
                            callback.Swapped              = false;
                            callback.ContactSet           = contactSet;
                            callback.TestCollisionObjectA = testCollisionObjectA;
                            callback.TestCollisionObjectB = testCollisionObjectB;
                            callback.TestGeometricObjectA = testGeometricObjectA;
                            callback.TestGeometricObjectB = testGeometricObjectB;
                            callback.TestContactSet       = testContactSet;

                            ((ISupportClosestPointQueries <int>)compositeShapeA.Partition)
                            .GetClosestPointCandidates(
                                scaleA,
                                geometricObjectA.Pose,
                                compositeShapeB.Partition,
                                scaleB,
                                geometricObjectB.Pose,
                                callback.HandlePair);

                            ClosestPointsCallbacks.Recycle(callback);
                        }
                    }
                    else
                    {
                        // Compute AABB of B in local space of the CompositeShape.
                        Aabb aabbBInA = geometricObjectB.Shape.GetAabb(
                            scaleB, geometricObjectA.Pose.Inverse * geometricObjectB.Pose);

                        // Apply inverse scaling to do the AABB checks in the unscaled local space of A.
                        aabbBInA.Scale(Vector3.One / scaleA);

                        if (type != CollisionQueryType.ClosestPoints)
                        {
                            // Boolean or Contact Query

                            foreach (var childIndex in compositeShapeA.Partition.GetOverlaps(aabbBInA))
                            {
                                if (type == CollisionQueryType.Boolean && contactSet.HaveContact)
                                {
                                    break; // We can abort early.
                                }
                                AddChildContacts(
                                    contactSet,
                                    swapped,
                                    childIndex,
                                    type,
                                    testContactSet,
                                    testCollisionObjectA,
                                    testGeometricObjectA);
                            }
                        }
                        else if (type == CollisionQueryType.ClosestPoints)
                        {
                            // Closest-Point Query

                            var callback = ClosestPointsCallbacks.Obtain();
                            callback.CollisionAlgorithm   = this;
                            callback.Swapped              = swapped;
                            callback.ContactSet           = contactSet;
                            callback.TestCollisionObjectA = testCollisionObjectA;
                            callback.TestCollisionObjectB = testCollisionObjectB;
                            callback.TestGeometricObjectA = testGeometricObjectA;
                            callback.TestGeometricObjectB = testGeometricObjectB;
                            callback.TestContactSet       = testContactSet;

                            ((ISupportClosestPointQueries <int>)compositeShapeA.Partition)
                            .GetClosestPointCandidates(
                                aabbBInA,
                                float.PositiveInfinity,
                                callback.HandleItem);

                            ClosestPointsCallbacks.Recycle(callback);
                        }
                    }
                }
                else
                {
                    // Compute AABB of B in local space of the composite.
                    Aabb aabbBInA = geometricObjectB.Shape.GetAabb(scaleB, geometricObjectA.Pose.Inverse * geometricObjectB.Pose);

                    // Apply inverse scaling to do the AABB checks in the unscaled local space of A.
                    aabbBInA.Scale(Vector3.One / scaleA);

                    // Go through list of children and find contacts.
                    int numberOfChildGeometries = compositeShapeA.Children.Count;
                    for (int i = 0; i < numberOfChildGeometries; i++)
                    {
                        IGeometricObject child = compositeShapeA.Children[i];

                        // NOTE: For closest-point queries we could be faster estimating a search space.
                        // See TriangleMeshAlgorithm or BVH queries.
                        // But the current implementation is sufficient. If the CompositeShape is more complex
                        // the user should be using spatial partitions anyway.

                        // For boolean or contact queries, we make an AABB test first.
                        // For closest points where we have not found a contact yet, we have to search
                        // all children.
                        if ((type == CollisionQueryType.ClosestPoints && !contactSet.HaveContact) ||
                            GeometryHelper.HaveContact(aabbBInA, child.Shape.GetAabb(child.Scale, child.Pose)))
                        {
                            // TODO: We could compute the minDistance of the child AABB and the AABB of the
                            // other shape. If the minDistance is greater than the current closestPairDistance
                            // we can ignore this pair. - This could be a performance boost.

                            // Get contacts/closest pairs of this child.
                            AddChildContacts(
                                contactSet,
                                swapped,
                                i,
                                type,
                                testContactSet,
                                testCollisionObjectA,
                                testGeometricObjectA);

                            // We have contact and stop for boolean queries.
                            if (contactSet.HaveContact && type == CollisionQueryType.Boolean)
                            {
                                break;
                            }
                        }
                    }
                }
            }
            finally
            {
                Debug.Assert(collisionObjectA.GeometricObject.Shape == compositeShapeA, "Shape was altered and not restored.");

                testContactSet.Recycle();
                ResourcePools.TestCollisionObjects.Recycle(testCollisionObjectA);
                ResourcePools.TestCollisionObjects.Recycle(testCollisionObjectB);
                testGeometricObjectB.Recycle();
                testGeometricObjectA.Recycle();
            }
        }
コード例 #10
0
        // See FAST paper: "Interactive Continuous Collision Detection for Non-Convex Polyhedra", Kim Young et al.

        /// <summary>
        /// Gets the time of impact using Conservative Advancement.
        /// </summary>
        /// <param name="objectA">The object A.</param>
        /// <param name="targetPoseA">The target pose of A.</param>
        /// <param name="objectB">The object B.</param>
        /// <param name="targetPoseB">The target pose of B.</param>
        /// <param name="allowedPenetrationDepth">The allowed penetration depth.</param>
        /// <param name="collisionDetection">The collision detection.</param>
        /// <returns>
        /// The time of impact in the range [0, 1].
        /// </returns>
        /// <remarks>
        /// This algorithm does not work for concave objects.
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="objectA"/>, <paramref name="objectB"/> or
        /// <paramref name="collisionDetection"/> is <see langword="null"/>.
        /// </exception>
        internal static float GetTimeOfImpactCA(CollisionObject objectA, Pose targetPoseA,
                                                CollisionObject objectB, Pose targetPoseB, float allowedPenetrationDepth,
                                                CollisionDetection collisionDetection) // Required for collision algorithm matrix.
        {
            if (objectA == null)
            {
                throw new ArgumentNullException("objectA");
            }
            if (objectB == null)
            {
                throw new ArgumentNullException("objectB");
            }
            if (collisionDetection == null)
            {
                throw new ArgumentNullException("collisionDetection");
            }

            IGeometricObject geometricObjectA = objectA.GeometricObject;
            IGeometricObject geometricObjectB = objectB.GeometricObject;

            Pose startPoseA = geometricObjectA.Pose;
            Pose startPoseB = geometricObjectB.Pose;

            // Get angular velocity ω of object A (as magnitude + rotation axis).

            // qEnd = ∆q * qStart
            // => ∆q = qEnd * qStart.Inverse
            QuaternionF qA = QuaternionF.CreateRotation(targetPoseA.Orientation * startPoseA.Orientation.Transposed);

            // ω = ∆α / ∆t, ∆t = 1
            // => ω = ∆α
            float    ωA     = qA.Angle;                                                // Magnitude |ω|
            Vector3F ωAxisA = (!Numeric.AreEqual(qA.W, 1)) ? qA.Axis : Vector3F.UnitX; // Rotation axis of ω

            // Get angular velocity ω of object B (as magnitude + rotation axis).
            // (Same as above.)
            QuaternionF qB     = QuaternionF.CreateRotation(targetPoseB.Orientation * startPoseB.Orientation.Transposed);
            float       ωB     = qB.Angle;                                                // Magnitude |ω|
            Vector3F    ωAxisB = (!Numeric.AreEqual(qB.W, 1)) ? qB.Axis : Vector3F.UnitX; // Rotation axis of ω

            // Bounding sphere radii.
            float rMaxA = GetBoundingRadius(geometricObjectA);
            float rMaxB = GetBoundingRadius(geometricObjectB);

            // |ω| * rMax is the angular part of the projected velocity bound.
            float angularVelocityProjected = ωA * rMaxA + ωB * rMaxB;

            // Compute relative linear velocity.
            // (linearRelVel ∙ normal > 0 if objects are getting closer.)
            Vector3F linearVelocityA        = targetPoseA.Position - startPoseA.Position;
            Vector3F linearVelocityB        = targetPoseB.Position - startPoseB.Position;
            Vector3F linearVelocityRelative = linearVelocityA - linearVelocityB;

            // Abort if relative movement is zero.
            if (Numeric.IsZero(linearVelocityRelative.Length + angularVelocityProjected))
            {
                return(1);
            }

            var distanceAlgorithm = collisionDetection.AlgorithmMatrix[objectA, objectB];

            // Use temporary test objects.
            var testGeometricObjectA = TestGeometricObject.Create();

            testGeometricObjectA.Shape = geometricObjectA.Shape;
            testGeometricObjectA.Scale = geometricObjectA.Scale;
            testGeometricObjectA.Pose  = startPoseA;

            var testGeometricObjectB = TestGeometricObject.Create();

            testGeometricObjectB.Shape = geometricObjectB.Shape;
            testGeometricObjectB.Scale = geometricObjectB.Scale;
            testGeometricObjectB.Pose  = startPoseB;

            var testCollisionObjectA = ResourcePools.TestCollisionObjects.Obtain();

            testCollisionObjectA.SetInternal(objectA, testGeometricObjectA);

            var testCollisionObjectB = ResourcePools.TestCollisionObjects.Obtain();

            testCollisionObjectB.SetInternal(objectB, testGeometricObjectB);

            var testContactSet = ContactSet.Create(testCollisionObjectA, testCollisionObjectB);

            try
            {
                distanceAlgorithm.UpdateClosestPoints(testContactSet, 0);

                if (testContactSet.Count < 0)
                {
                    // No distance result --> Abort.
                    return(1);
                }

                Vector3F normal   = testContactSet[0].Normal;
                float    distance = -testContactSet[0].PenetrationDepth;

                float λ         = 0;
                float λPrevious = 0;

                for (int i = 0; i < MaxNumberOfIterations && distance > 0; i++)
                {
                    // |v∙n|
                    float linearVelocityProject = Vector3F.Dot(linearVelocityRelative, normal);

                    // |n x ω| * rMax
                    angularVelocityProjected = Vector3F.Cross(normal, ωAxisA).Length *ωA *rMaxA
                                               + Vector3F.Cross(normal, ωAxisB).Length *ωB *rMaxB;

                    // Total projected velocity.
                    float velocityProjected = linearVelocityProject + angularVelocityProjected;

                    // Abort for separating objects.
                    if (Numeric.IsLess(velocityProjected, 0))
                    {
                        break;
                    }

                    // Increase TOI.
                    float μ = (distance + allowedPenetrationDepth) / velocityProjected;
                    λ = λ + μ;

                    if (λ < 0 || λ > 1)
                    {
                        break;
                    }

                    Debug.Assert(λPrevious < λ);

                    if (λ <= λPrevious)
                    {
                        break;
                    }

                    // Get new interpolated poses.
                    Vector3F  positionA = startPoseA.Position + λ * (targetPoseA.Position - startPoseA.Position);
                    Matrix33F rotationA = Matrix33F.CreateRotation(ωAxisA, λ * ωA);
                    testGeometricObjectA.Pose = new Pose(positionA, rotationA * startPoseA.Orientation);

                    Vector3F  positionB = startPoseB.Position + λ * (targetPoseB.Position - startPoseB.Position);
                    Matrix33F rotationB = Matrix33F.CreateRotation(ωAxisB, λ * ωB);
                    testGeometricObjectB.Pose = new Pose(positionB, rotationB * startPoseB.Orientation);

                    // Get new closest point distance.
                    distanceAlgorithm.UpdateClosestPoints(testContactSet, 0);
                    if (testContactSet.Count == 0)
                    {
                        break;
                    }

                    normal   = testContactSet[0].Normal;
                    distance = -testContactSet[0].PenetrationDepth;

                    λPrevious = λ;
                }

                if (testContactSet.HaveContact && λ > 0 && λ < 1 && testContactSet.Count > 0)
                {
                    return(λ);
                    // We already have a contact that we could use.
                    // result.Contact = testContactSet[0];
                }
            }
            finally
            {
                // Recycle temporary objects.
                testContactSet.Recycle(true);
                ResourcePools.TestCollisionObjects.Recycle(testCollisionObjectA);
                ResourcePools.TestCollisionObjects.Recycle(testCollisionObjectB);
                testGeometricObjectA.Recycle();
                testGeometricObjectB.Recycle();
            }

            return(1);
        }
コード例 #11
0
        // See FAST paper: "Interactive Continuous Collision Detection for Non-Convex Polyhedra", Kim Young et al.

        /// <summary>
        /// Gets the time of impact using Conservative Advancement (ignoring rotational movement).
        /// </summary>
        /// <param name="objectA">The object A.</param>
        /// <param name="targetPoseA">The target pose of A.</param>
        /// <param name="objectB">The object B.</param>
        /// <param name="targetPoseB">The target pose of B.</param>
        /// <param name="allowedPenetration">The allowed penetration depth.</param>
        /// <param name="collisionDetection">The collision detection.</param>
        /// <returns>
        /// The time of impact in the range [0, 1].
        /// </returns>
        /// <remarks>
        /// This algorithm does not work for concave objects.
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="objectA"/>, <paramref name="objectB"/> or
        /// <paramref name="collisionDetection"/> is <see langword="null"/>.
        /// </exception>
        internal static float GetTimeOfImpactLinearCA(CollisionObject objectA, Pose targetPoseA,
                                                      CollisionObject objectB, Pose targetPoseB, float allowedPenetration,
                                                      CollisionDetection collisionDetection) // Required for collision algorithm matrix.
        {
            if (objectA == null)
            {
                throw new ArgumentNullException("objectA");
            }
            if (objectB == null)
            {
                throw new ArgumentNullException("objectB");
            }
            if (collisionDetection == null)
            {
                throw new ArgumentNullException("collisionDetection");
            }

            IGeometricObject geometricObjectA = objectA.GeometricObject;
            IGeometricObject geometricObjectB = objectB.GeometricObject;

            Pose startPoseA = geometricObjectA.Pose;
            Pose startPoseB = geometricObjectB.Pose;

            // Compute relative linear velocity.
            // (linearRelVel ∙ normal > 0 if objects are getting closer.)
            Vector3 linearVelocityA        = targetPoseA.Position - startPoseA.Position;
            Vector3 linearVelocityB        = targetPoseB.Position - startPoseB.Position;
            Vector3 linearVelocityRelative = linearVelocityA - linearVelocityB;

            // Abort if relative movement is zero.
            if (Numeric.IsZero(linearVelocityRelative.Length))
            {
                return(1);
            }

            var distanceAlgorithm = collisionDetection.AlgorithmMatrix[objectA, objectB];

            // Use temporary test objects.
            var testGeometricObjectA = TestGeometricObject.Create();

            testGeometricObjectA.Shape = geometricObjectA.Shape;
            testGeometricObjectA.Scale = geometricObjectA.Scale;
            testGeometricObjectA.Pose  = startPoseA;

            var testGeometricObjectB = TestGeometricObject.Create();

            testGeometricObjectB.Shape = geometricObjectB.Shape;
            testGeometricObjectB.Scale = geometricObjectB.Scale;
            testGeometricObjectB.Pose  = startPoseB;

            var testCollisionObjectA = ResourcePools.TestCollisionObjects.Obtain();

            testCollisionObjectA.SetInternal(objectA, testGeometricObjectA);

            var testCollisionObjectB = ResourcePools.TestCollisionObjects.Obtain();

            testCollisionObjectB.SetInternal(objectB, testGeometricObjectB);

            var testContactSet = ContactSet.Create(testCollisionObjectA, testCollisionObjectB);

            try
            {
                distanceAlgorithm.UpdateClosestPoints(testContactSet, 0);

                if (testContactSet.Count < 0)
                {
                    // No closest-distance result. --> Abort.
                    return(1);
                }

                Vector3 normal   = testContactSet[0].Normal;
                float   distance = -testContactSet[0].PenetrationDepth;

                float λ         = 0;
                float λPrevious = 0;

                for (int i = 0; i < MaxNumberOfIterations && distance > 0; i++)
                {
                    // |v∙n|
                    float velocityProjected = Vector3.Dot(linearVelocityRelative, normal);

                    // Abort for separating objects.
                    if (Numeric.IsLess(velocityProjected, 0))
                    {
                        break;
                    }

                    // Increase TOI.
                    float μ = (distance + allowedPenetration) / velocityProjected;
                    λ = λ + μ;

                    if (λ < 0 || λ > 1)
                    {
                        break;
                    }

                    Debug.Assert(λPrevious < λ);

                    if (λ <= λPrevious)
                    {
                        break;
                    }

                    // Get new interpolated poses - only positions are changed.
                    Vector3 positionA = startPoseA.Position + λ * (targetPoseA.Position - startPoseA.Position);
                    testGeometricObjectA.Pose = new Pose(positionA, startPoseA.Orientation);

                    Vector3 positionB = startPoseB.Position + λ * (targetPoseB.Position - startPoseB.Position);
                    testGeometricObjectB.Pose = new Pose(positionB, startPoseB.Orientation);

                    // Get new closest point distance.
                    distanceAlgorithm.UpdateClosestPoints(testContactSet, 0);
                    if (testContactSet.Count == 0)
                    {
                        break;
                    }

                    normal   = testContactSet[0].Normal;
                    distance = -testContactSet[0].PenetrationDepth;

                    λPrevious = λ;
                }

                if (testContactSet.HaveContact && λ > 0 && λ < 1 && testContactSet.Count > 0)
                {
                    return(λ);
                    // We already have a contact that we could use.
                    // result.Contact = testContactSet[0];
                }
            }
            finally
            {
                // Recycle temporary objects.
                testContactSet.Recycle(true);
                ResourcePools.TestCollisionObjects.Recycle(testCollisionObjectA);
                ResourcePools.TestCollisionObjects.Recycle(testCollisionObjectB);
                testGeometricObjectA.Recycle();
                testGeometricObjectB.Recycle();
            }

            return(1);
        }
コード例 #12
0
        public void Merge1()
        {
            ContactSet set = ContactSet.Create(new CollisionObject(), new CollisionObject());

            // Separated contact is not merged
            Contact contact = Contact.Create();

            contact.Position         = new Vector3F();
            contact.PenetrationDepth = -1;
            ContactHelper.Merge(set, contact, CollisionQueryType.Contacts, 0.1f);
            Assert.AreEqual(0, set.Count);

            // Merge first contact.
            contact = Contact.Create();
            //contact.ApplicationData = "AppData";
            contact.Lifetime         = 10;
            contact.Position         = new Vector3F();
            contact.PenetrationDepth = 1;
            contact.UserData         = "UserData";
            ContactHelper.Merge(
                set,
                contact,
                CollisionQueryType.Contacts,
                0.1f);
            Assert.AreEqual(1, set.Count);

            // Merge next contact.
            contact                  = Contact.Create();
            contact.Position         = new Vector3F();
            contact.PenetrationDepth = 1;
            ContactHelper.Merge(
                set,
                contact,
                CollisionQueryType.Contacts,
                0.1f);
            Assert.AreEqual(1, set.Count);
            //Assert.AreEqual("AppData", set[0].ApplicationData);
            Assert.AreEqual(10, set[0].Lifetime);
            Assert.AreEqual("UserData", set[0].UserData);

            // TODO: This functionality was replaced. Write new tests.
            //// Test ray casts.
            //set.Clear();
            //((DefaultGeometry)set.ObjectA.GeometricObject).Shape = new RayShape();
            //Contact newContact = new Contact
            //                     {
            //                       Position = new Vector3F(),
            //                       PenetrationDepth = 1,
            //                       IsRayHit = true
            //                     };
            //ContactHelper.Merge(set, newContact, CollisionQueryType.ClosestPoints, 0.1f);
            //Assert.AreEqual(1, set.Count);

            //newContact = new Contact
            //             {
            //               Position = new Vector3F(1, 2, 3),
            //               PenetrationDepth = -1,
            //               IsRayHit = false
            //             };
            //ContactHelper.Merge(set, newContact, CollisionQueryType.ClosestPoints, 0.1f);
            //Assert.AreEqual(1, set.Count);
            //Assert.AreEqual(1f, set[0].PenetrationDepth);
            //Assert.AreEqual(new Vector3F(), set[0].Position);

            //newContact = new Contact
            //             {
            //               Position = new Vector3F(1, 2, 3),
            //               PenetrationDepth = 0.5f,
            //               IsRayHit = true
            //             };
            //ContactHelper.Merge(set, newContact, CollisionQueryType.ClosestPoints, 0.1f);
            //Assert.AreEqual(1, set.Count);
            //Assert.AreEqual(0.5f, set[0].PenetrationDepth);
            //Assert.AreEqual(new Vector3F(1, 2, 3), set[0].Position);

            //set.Clear();
            //set.Add(
            //  new Contact
            //        {
            //          Position = new Vector3F(0, 0, 0),
            //          PenetrationDepth = -1,
            //          IsRayHit = false,
            //        });
            //newContact = new Contact
            //             {
            //               Position = new Vector3F(1, 2, 3),
            //               PenetrationDepth = -2,
            //               IsRayHit = false
            //             };
            //ContactHelper.Merge(set, newContact, CollisionQueryType.ClosestPoints, 0.1f);
            //Assert.AreEqual(1, set.Count);
            //Assert.AreEqual(-1f, set[0].PenetrationDepth);
            //Assert.AreEqual(new Vector3F(0, 0, 0), set[0].Position);

            //newContact = new Contact
            //             {
            //               Position = new Vector3F(1, 2, 3),
            //               PenetrationDepth = -0.5f,
            //               IsRayHit = false,
            //             };
            //ContactHelper.Merge(set, newContact, CollisionQueryType.ClosestPoints, 0.1f);
            //Assert.AreEqual(1, set.Count);
            //Assert.AreEqual(-0.5f, set[0].PenetrationDepth);
            //Assert.AreEqual(new Vector3F(1, 2, 3), set[0].Position);

            //newContact = new Contact
            //             {
            //               Position = new Vector3F(3, 3, 3),
            //               PenetrationDepth = 1,
            //               IsRayHit = true
            //             };
            //ContactHelper.Merge(set, newContact, CollisionQueryType.ClosestPoints, 0.1f);
            //Assert.AreEqual(1, set.Count);
            //Assert.AreEqual(1f, set[0].PenetrationDepth);
            //Assert.AreEqual(new Vector3F(3, 3, 3), set[0].Position);

            //((DefaultGeometry)set.ObjectA.GeometricObject).Shape = new Sphere();

            //// Test closest points.
            //set.Clear();

            //ContactHelper.Merge(
            //  set,
            //  new Contact { Position = new Vector3F(), PenetrationDepth = 1 },
            //  CollisionQueryType.ClosestPoints,
            //  0.1f);
            //Assert.AreEqual(1, set.Count);
            //ContactHelper.Merge(
            //  set,
            //  new Contact { Position = new Vector3F(1, 2, 3), PenetrationDepth = 0 },
            //  CollisionQueryType.ClosestPoints,
            //  0.1f);
            //Assert.AreEqual(1, set.Count);
            //Assert.AreEqual(1, set[0].PenetrationDepth);
            //ContactHelper.Merge(
            //  set,
            //  new Contact { Position = new Vector3F(1, 2, 3), PenetrationDepth = 1.1f },
            //  CollisionQueryType.ClosestPoints,
            //  0.1f);
            //Assert.AreEqual(1, set.Count);
            //Assert.AreEqual(1.1f, set[0].PenetrationDepth);
            //Assert.AreEqual(new Vector3F(1, 2, 3), set[0].Position);

            //// Test default case with automatic reduction.
            //set.Clear();
            //ContactHelper.Merge(
            //  set,
            //  new Contact { Position = new Vector3F(), PenetrationDepth = 1 },
            //  CollisionQueryType.Contacts,
            //  0.1f);
            //ContactHelper.Merge(
            //  set,
            //  new Contact { Position = new Vector3F(1, 0, 0), PenetrationDepth = 1 },
            //  CollisionQueryType.Contacts,
            //  0.1f);
            //ContactHelper.Merge(
            //  set,
            //  new Contact { Position = new Vector3F(0, 1, 0), PenetrationDepth = 1 },
            //  CollisionQueryType.Contacts,
            //  0.1f);
            //ContactHelper.Merge(
            //  set,
            //  new Contact { Position = new Vector3F(0, 0, 1), PenetrationDepth = 1 },
            //  CollisionQueryType.Contacts,
            //  0.1f);
            //ContactHelper.Merge(
            //  set,
            //  new Contact { Position = new Vector3F(2, 0, 0), PenetrationDepth = 1 },
            //  CollisionQueryType.Contacts,
            //  0.1f);
            //Assert.AreEqual(4, set.Count);  // Reduced to 4 instead of 5.
        }
コード例 #13
0
        public override void ComputeCollision(ContactSet contactSet, CollisionQueryType type)
        {
            CollisionObject  collisionObjectA = contactSet.ObjectA;
            CollisionObject  collisionObjectB = contactSet.ObjectB;
            IGeometricObject geometricObjectA = collisionObjectA.GeometricObject;
            IGeometricObject geometricObjectB = collisionObjectB.GeometricObject;

            // Object A should be the triangle mesh, swap objects if necessary.
            // When testing TriangleMeshShape vs. TriangleMeshShape with BVH, object A should be the
            // TriangleMeshShape with BVH - except when the TriangleMeshShape with BVH is a lot smaller
            // than the other TriangleMeshShape. (See tests below.)
            TriangleMeshShape triangleMeshShapeA = geometricObjectA.Shape as TriangleMeshShape;
            TriangleMeshShape triangleMeshShapeB = geometricObjectB.Shape as TriangleMeshShape;
            bool swapped = false;

            // Check if collision objects shapes are correct.
            if (triangleMeshShapeA == null && triangleMeshShapeB == null)
            {
                throw new ArgumentException("The contact set must contain a triangle mesh.", "contactSet");
            }

            Pose     poseA  = geometricObjectA.Pose;
            Pose     poseB  = geometricObjectB.Pose;
            Vector3F scaleA = geometricObjectA.Scale;
            Vector3F scaleB = geometricObjectB.Scale;

            // First we assume that we do not have a contact.
            contactSet.HaveContact = false;

            // ----- Use temporary test objects.
            var testCollisionObjectA = ResourcePools.TestCollisionObjects.Obtain();
            var testCollisionObjectB = ResourcePools.TestCollisionObjects.Obtain();

            // Create a test contact set and initialize with dummy objects.
            // (The actual collision objects are set below.)
            var testContactSet       = ContactSet.Create(testCollisionObjectA, testCollisionObjectB);
            var testGeometricObjectA = TestGeometricObject.Create();
            var testGeometricObjectB = TestGeometricObject.Create();
            var testTriangleShapeA   = ResourcePools.TriangleShapes.Obtain();
            var testTriangleShapeB   = ResourcePools.TriangleShapes.Obtain();

            try
            {
                if (triangleMeshShapeA != null &&
                    triangleMeshShapeA.Partition != null &&
                    triangleMeshShapeB != null &&
                    triangleMeshShapeB.Partition != null &&
                    (type != CollisionQueryType.ClosestPoints ||
                     triangleMeshShapeA.Partition is ISupportClosestPointQueries <int>))
                {
                    #region ----- TriangleMesh with BVH vs. TriangleMesh with BVH -----

                    Debug.Assert(swapped == false, "Why did we swap the objects? Order of objects is fine.");

                    // Find collision algorithm for triangle vs. triangle (used in AddTriangleTriangleContacts()).
                    if (_triangleTriangleAlgorithm == null)
                    {
                        _triangleTriangleAlgorithm = CollisionDetection.AlgorithmMatrix[typeof(TriangleShape), typeof(TriangleShape)];
                    }

                    if (type != CollisionQueryType.ClosestPoints)
                    {
                        // ----- Boolean or Contact Query

                        // Heuristic: Test large BVH vs. small BVH.
                        Aabb  aabbOfA        = geometricObjectA.Aabb;
                        Aabb  aabbOfB        = geometricObjectB.Aabb;
                        float largestExtentA = aabbOfA.Extent.LargestComponent;
                        float largestExtentB = aabbOfB.Extent.LargestComponent;
                        IEnumerable <Pair <int> > overlaps;
                        bool overlapsSwapped = largestExtentA < largestExtentB;
                        if (overlapsSwapped)
                        {
                            overlaps = triangleMeshShapeB.Partition.GetOverlaps(
                                scaleB,
                                geometricObjectB.Pose,
                                triangleMeshShapeA.Partition,
                                scaleA,
                                geometricObjectA.Pose);
                        }
                        else
                        {
                            overlaps = triangleMeshShapeA.Partition.GetOverlaps(
                                scaleA,
                                geometricObjectA.Pose,
                                triangleMeshShapeB.Partition,
                                scaleB,
                                geometricObjectB.Pose);
                        }

                        foreach (var overlap in overlaps)
                        {
                            if (type == CollisionQueryType.Boolean && contactSet.HaveContact)
                            {
                                break; // We can abort early.
                            }
                            AddTriangleTriangleContacts(
                                contactSet,
                                overlapsSwapped ? overlap.Second : overlap.First,
                                overlapsSwapped ? overlap.First : overlap.Second,
                                type,
                                testContactSet,
                                testCollisionObjectA,
                                testGeometricObjectA,
                                testTriangleShapeA,
                                testCollisionObjectB,
                                testGeometricObjectB,
                                testTriangleShapeB);
                        }
                    }
                    else
                    {
                        // ----- Closest-Point Query
                        var callback = ClosestPointCallbacks.Obtain();
                        callback.CollisionAlgorithm   = this;
                        callback.Swapped              = false;
                        callback.ContactSet           = contactSet;
                        callback.TestContactSet       = testContactSet;
                        callback.TestCollisionObjectA = testCollisionObjectA;
                        callback.TestCollisionObjectB = testCollisionObjectB;
                        callback.TestGeometricObjectA = testGeometricObjectA;
                        callback.TestGeometricObjectB = testGeometricObjectB;
                        callback.TestTriangleA        = testTriangleShapeA;
                        callback.TestTriangleB        = testTriangleShapeB;

                        ((ISupportClosestPointQueries <int>)triangleMeshShapeA.Partition)
                        .GetClosestPointCandidates(
                            scaleA,
                            geometricObjectA.Pose,
                            triangleMeshShapeB.Partition,
                            scaleB,
                            geometricObjectB.Pose,
                            callback.HandlePair);

                        ClosestPointCallbacks.Recycle(callback);
                    }
                    #endregion
                }
                else
                {
                    Aabb  aabbOfA        = geometricObjectA.Aabb;
                    Aabb  aabbOfB        = geometricObjectB.Aabb;
                    float largestExtentA = aabbOfA.Extent.LargestComponent;
                    float largestExtentB = aabbOfB.Extent.LargestComponent;

                    // Choose which object should be A.
                    if (triangleMeshShapeA == null)
                    {
                        // A is no TriangleMesh. B must be a TriangleMesh.
                        swapped = true;
                    }
                    else if (triangleMeshShapeB == null)
                    {
                        // A is a TriangleMesh and B is no TriangleMesh.
                    }
                    else if (triangleMeshShapeA.Partition != null)
                    {
                        // A is a TriangleMesh with BVH and B is a TriangleMesh.
                        // We want to test TriangleMesh with BVH vs. * - unless the TriangleMesh with BVH is a lot
                        // smaller than the TriangleMesh.
                        if (largestExtentA * 2 < largestExtentB)
                        {
                            swapped = true;
                        }
                    }
                    else if (triangleMeshShapeB.Partition != null)
                    {
                        // A is a TriangleMesh and B is a TriangleMesh with BVH.
                        // We want to test TriangleMesh with BVH vs. * - unless the TriangleMesh BVH is a lot
                        // smaller than the TriangleMesh.
                        if (largestExtentB * 2 >= largestExtentA)
                        {
                            swapped = true;
                        }
                    }
                    else
                    {
                        // A and B are normal triangle meshes. A should be the larger object.
                        if (largestExtentA < largestExtentB)
                        {
                            swapped = true;
                        }
                    }

                    if (swapped)
                    {
                        // Swap all variables.
                        MathHelper.Swap(ref collisionObjectA, ref collisionObjectB);
                        MathHelper.Swap(ref geometricObjectA, ref geometricObjectB);
                        MathHelper.Swap(ref aabbOfA, ref aabbOfB);
                        MathHelper.Swap(ref largestExtentA, ref largestExtentB);
                        MathHelper.Swap(ref triangleMeshShapeA, ref triangleMeshShapeB);
                        MathHelper.Swap(ref poseA, ref poseB);
                        MathHelper.Swap(ref scaleA, ref scaleB);
                    }

                    if (triangleMeshShapeB == null &&
                        type != CollisionQueryType.ClosestPoints &&
                        largestExtentA * 2 < largestExtentB)
                    {
                        // B is a very large object and no triangle mesh.
                        // Make a AABB vs. Shape of B test for quick rejection.
                        BoxShape testBoxShape = ResourcePools.BoxShapes.Obtain();
                        testBoxShape.Extent        = aabbOfA.Extent;
                        testGeometricObjectA.Shape = testBoxShape;
                        testGeometricObjectA.Scale = Vector3F.One;
                        testGeometricObjectA.Pose  = new Pose(aabbOfA.Center);

                        testCollisionObjectA.SetInternal(collisionObjectA, testGeometricObjectA);

                        Debug.Assert(testContactSet.Count == 0, "testContactSet needs to be cleared.");
                        testContactSet.Reset(testCollisionObjectA, collisionObjectB);

                        CollisionAlgorithm collisionAlgorithm = CollisionDetection.AlgorithmMatrix[testContactSet];
                        collisionAlgorithm.ComputeCollision(testContactSet, CollisionQueryType.Boolean);

                        ResourcePools.BoxShapes.Recycle(testBoxShape);

                        if (!testContactSet.HaveContact)
                        {
                            contactSet.HaveContact = false;
                            return;
                        }
                    }

                    if (triangleMeshShapeA.Partition != null &&
                        (type != CollisionQueryType.ClosestPoints ||
                         triangleMeshShapeA.Partition is ISupportClosestPointQueries <int>))
                    {
                        #region ----- TriangleMesh BVH vs. * -----

                        // Get AABB of B in local space of A.
                        var aabbBInA = geometricObjectB.Shape.GetAabb(scaleB, poseA.Inverse * poseB);

                        // Apply inverse scaling to do the AABB-tree checks in the unscaled local space of A.
                        aabbBInA.Scale(Vector3F.One / scaleA);

                        if (type != CollisionQueryType.ClosestPoints)
                        {
                            // Boolean or Contact Query
                            foreach (var triangleIndex in triangleMeshShapeA.Partition.GetOverlaps(aabbBInA))
                            {
                                if (type == CollisionQueryType.Boolean && contactSet.HaveContact)
                                {
                                    break; // We can abort early.
                                }
                                AddTriangleContacts(
                                    contactSet,
                                    swapped,
                                    triangleIndex,
                                    type,
                                    testContactSet,
                                    testCollisionObjectA,
                                    testGeometricObjectA,
                                    testTriangleShapeA);
                            }
                        }
                        else if (type == CollisionQueryType.ClosestPoints)
                        {
                            // Closest-Point Query

                            var callback = ClosestPointCallbacks.Obtain();
                            callback.CollisionAlgorithm   = this;
                            callback.Swapped              = swapped;
                            callback.ContactSet           = contactSet;
                            callback.TestContactSet       = testContactSet;
                            callback.TestCollisionObjectA = testCollisionObjectA;
                            callback.TestCollisionObjectB = testCollisionObjectB;
                            callback.TestGeometricObjectA = testGeometricObjectA;
                            callback.TestGeometricObjectB = testGeometricObjectB;
                            callback.TestTriangleA        = testTriangleShapeA;
                            callback.TestTriangleB        = testTriangleShapeB;

                            ((ISupportClosestPointQueries <int>)triangleMeshShapeA.Partition)
                            .GetClosestPointCandidates(
                                aabbBInA,
                                float.PositiveInfinity,
                                callback.HandleItem);

                            ClosestPointCallbacks.Recycle(callback);
                        }
                        #endregion
                    }
                    else
                    {
                        #region ----- TriangleMesh vs. * -----

                        // Find an upper bound for the distance we have to search.
                        // If object are in contact or we make contact/boolean query, then the distance is 0.
                        float closestPairDistance;
                        if (contactSet.HaveContact || type != CollisionQueryType.ClosestPoints)
                        {
                            closestPairDistance = 0;
                        }
                        else
                        {
                            // Make first guess for closest pair: inner point of B to inner point of mesh.
                            Vector3F innerPointA = poseA.ToWorldPosition(geometricObjectA.Shape.InnerPoint * scaleA);
                            Vector3F innerPointB = poseB.ToWorldPosition(geometricObjectB.Shape.InnerPoint * scaleB);
                            closestPairDistance = (innerPointB - innerPointA).Length + CollisionDetection.Epsilon;
                        }

                        // The search-space is a space where the closest points must lie in.
                        Vector3F minimum         = aabbOfB.Minimum - new Vector3F(closestPairDistance);
                        Vector3F maximum         = aabbOfB.Maximum + new Vector3F(closestPairDistance);
                        Aabb     searchSpaceAabb = new Aabb(minimum, maximum);

                        // Test all triangles.
                        ITriangleMesh triangleMeshA     = triangleMeshShapeA.Mesh;
                        int           numberOfTriangles = triangleMeshA.NumberOfTriangles;
                        for (int i = 0; i < numberOfTriangles; i++)
                        {
                            // TODO: GetTriangle is performed twice! Here and in AddTriangleContacts() below!
                            Triangle triangle = triangleMeshA.GetTriangle(i);

                            testTriangleShapeA.Vertex0 = triangle.Vertex0 * scaleA;
                            testTriangleShapeA.Vertex1 = triangle.Vertex1 * scaleA;
                            testTriangleShapeA.Vertex2 = triangle.Vertex2 * scaleA;

                            // Make AABB test with search space.
                            if (GeometryHelper.HaveContact(searchSpaceAabb, testTriangleShapeA.GetAabb(poseA)))
                            {
                                // IMPORTANT: Info in triangleShape is destroyed in this method!
                                // Triangle is given to the method so that method does not allocate garbage.
                                AddTriangleContacts(
                                    contactSet,
                                    swapped,
                                    i,
                                    type,
                                    testContactSet,
                                    testCollisionObjectA,
                                    testGeometricObjectA,
                                    testTriangleShapeA);

                                // We have contact and stop for boolean queries.
                                if (contactSet.HaveContact && type == CollisionQueryType.Boolean)
                                {
                                    break;
                                }

                                if (closestPairDistance > 0 && contactSet.HaveContact ||
                                    contactSet.Count > 0 && -contactSet[contactSet.Count - 1].PenetrationDepth < closestPairDistance)
                                {
                                    // Reduce search space
                                    // Note: contactSet can contain several contacts. We assume that the last contact
                                    // is the newest one and check only this.
                                    if (contactSet.Count > 0)
                                    {
                                        closestPairDistance = Math.Max(0, -contactSet[contactSet.Count - 1].PenetrationDepth);
                                    }
                                    else
                                    {
                                        closestPairDistance = 0;
                                    }

                                    searchSpaceAabb.Minimum = aabbOfB.Minimum - new Vector3F(closestPairDistance);
                                    searchSpaceAabb.Maximum = aabbOfB.Maximum + new Vector3F(closestPairDistance);
                                }
                            }
                        }
                        #endregion
                    }
                }
            }
            finally
            {
                testContactSet.Recycle();
                ResourcePools.TestCollisionObjects.Recycle(testCollisionObjectA);
                ResourcePools.TestCollisionObjects.Recycle(testCollisionObjectB);
                testGeometricObjectB.Recycle();
                testGeometricObjectA.Recycle();
                ResourcePools.TriangleShapes.Recycle(testTriangleShapeB);
                ResourcePools.TriangleShapes.Recycle(testTriangleShapeA);
            }
        }