コード例 #1
0
        /// <summary>
        /// Reduces the number of contacts in the contact set to 1 contact.
        /// </summary>
        /// <param name="contactSet">The contact set.</param>
        /// <remarks>
        /// The contact with the biggest penetration depth is kept.
        /// </remarks>
        internal static void ReduceClosestPoints(ContactSet contactSet)
        {
            // Reduce to 1 contact.
            int numberOfContacts = contactSet.Count;

            if (numberOfContacts > 1)
            {
                // Keep the contact with the deepest penetration depth.
                Contact bestContact = contactSet[0];
                for (int i = 1; i < numberOfContacts; i++)
                {
                    if (contactSet[i].PenetrationDepth > bestContact.PenetrationDepth)
                    {
                        bestContact = contactSet[i];
                    }
                }

                // Throw away other contacts.
                foreach (var contact in contactSet)
                {
                    if (contact != bestContact)
                    {
                        contact.Recycle();
                    }
                }

                contactSet.Clear();
                contactSet.Add(bestContact);
            }

            Debug.Assert(contactSet.Count == 0 || contactSet.Count == 1);

            // If we HaveContact but the contact shows a separation, delete contact.
            // This can happen for TriangleMesh vs. TriangleMesh because the triangle mesh algorithm
            // filters contacts if they have a bad normal. It can happen that all contacts are filtered
            // and only a separated contact remains in the contact set.
            if (contactSet.HaveContact && contactSet.Count > 0 && contactSet[0].PenetrationDepth < 0)
            {
                contactSet[0].Recycle();
                contactSet.Clear();
            }
        }
コード例 #2
0
        /// <summary>
        /// Performs more collision tests while slightly rotating one collision object.
        /// </summary>
        /// <param name="collisionDetection">The collision detection.</param>
        /// <param name="contactSet">
        /// The contact set; must contain at least 1 <see cref="Contact"/>.
        /// </param>
        /// <param name="perturbB">
        /// if set to <see langword="true"/> collision object B will be rotated; otherwise collision
        /// object A will be rotated.
        /// </param>
        /// <param name="testMethod">The test method that is called to compute contacts.</param>
        /// <remarks>
        /// This method rotates one object 3 times and calls contact computation for the new
        /// orientations. It is recommended to call this method only when the contact set has 1 new
        /// contact.
        /// </remarks>
        internal static void TestWithPerturbations(CollisionDetection collisionDetection, ContactSet contactSet, bool perturbB, Action <ContactSet> testMethod)
        {
            Debug.Assert(contactSet != null);
            Debug.Assert(contactSet.Count > 0 && contactSet.HaveContact || !contactSet.IsPerturbationTestAllowed);
            Debug.Assert(testMethod != null);

            // Make this test only if there is 1 contact.
            // If there are 0 contacts, we assume that the contact pair is separated.
            // If there are more than 3 contacts, then we already have a lot of contacts to work with, no
            // need to search for more.
            if (!contactSet.HaveContact || contactSet.Count == 0 || contactSet.Count >= 4 || !contactSet.IsPerturbationTestAllowed)
            {
                return;
            }

            // Get data of object that will be rotated.
            var collisionObject = (perturbB) ? contactSet.ObjectB : contactSet.ObjectA;
            var geometricObject = collisionObject.GeometricObject;
            var pose            = geometricObject.Pose;

            // Get normal, pointing to the test object.
            var normal = contactSet[0].Normal;

            if (!perturbB)
            {
                normal = -normal;
            }

            var contactPosition = contactSet[0].Position;
            var centerToContact = contactPosition - pose.Position;

            // Compute a perturbation angle proportional to the dimension of the object.
            var radius = geometricObject.Aabb.Extent.Length;
            var angle  = collisionDetection.ContactPositionTolerance / radius;

            // axis1 is in the contact tangent plane, orthogonal to normal.
            var axis1 = Vector3F.Cross(normal, centerToContact);

            // If axis1 is zero then normal and centerToContact are collinear. This happens
            // for example for spheres or cone tips against flat faces. In these cases we assume
            // that there will be max. 1 contact.
            if (axis1.IsNumericallyZero)
            {
                return;
            }

            var axis1Local = pose.ToLocalDirection(axis1);
            var rotation   = Matrix33F.CreateRotation(axis1Local, -angle);

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

            testGeometricObject.Shape = geometricObject.Shape;
            testGeometricObject.Scale = geometricObject.Scale;
            testGeometricObject.Pose  = new Pose(pose.Position, pose.Orientation * rotation);

            var testCollisionObject = ResourcePools.TestCollisionObjects.Obtain();

            testCollisionObject.SetInternal(collisionObject, testGeometricObject);

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

            testContactSet.IsPerturbationTestAllowed = false;       // Avoid recursive perturbation tests!
            testContactSet.PreferredNormal           = contactSet.PreferredNormal;

            // Compute next contacts.
            testMethod(testContactSet);

            if (testContactSet.Count > 0)
            {
                // axis2 is in the contact tangent plane, orthogonal to axis1.
                var axis2      = Vector3F.Cross(axis1, normal);
                var axis2Local = pose.ToLocalDirection(axis2);

                var rotation2 = Matrix33F.CreateRotation(axis2Local, -angle);
                testGeometricObject.Pose = new Pose(pose.Position, pose.Orientation * rotation2);

                // Compute next contacts.
                testMethod(testContactSet);

                // Invert rotation2.
                rotation2.Transpose();
                testGeometricObject.Pose = new Pose(pose.Position, pose.Orientation * rotation2);

                // Compute next contacts.
                testMethod(testContactSet);
            }

            // Set HaveContact. It is reset when a perturbation separates the objects.
            testContactSet.HaveContact = true;

            // TODO: Test if we need this:
            // The contact world positions are not really correct because one object was rotated.
            // UpdateContacts recomputes the world positions from the local positions.
            UpdateContacts(testContactSet, 0, collisionDetection.ContactPositionTolerance);

            // Merge contacts of testContactSet into contact set, but do not change existing contacts.
            foreach (var contact in testContactSet)
            {
                // We call TryMerge() to see if the contact is similar to an existing contact.
                bool exists = TryMergeWithNearestContact(
                    contactSet,
                    contact,
                    collisionDetection.ContactPositionTolerance,
                    false); // The existing contact must no be changed!

                if (exists)
                {
                    // We can throw away the new contact because a similar is already in the contact set.
                    contact.Recycle();
                }
                else
                {
                    // Add new contact.
                    contactSet.Add(contact);
                }
            }

            // Recycle temporary objects.
            testContactSet.Recycle();
            ResourcePools.TestCollisionObjects.Recycle(testCollisionObject);
            testGeometricObject.Recycle();
        }
コード例 #3
0
        /// <summary>
        /// Reduces the number of contacts in the contact set to 1 contact.
        /// </summary>
        /// <param name="contactSet">
        /// The contact set. One shape in the contact set must be a <see cref="RayShape"/>!
        /// </param>
        /// <remarks>
        /// The best ray hit is kept.
        /// </remarks>
        internal static void ReduceRayHits(ContactSet contactSet)
        {
            Debug.Assert(
                contactSet.ObjectA.GeometricObject.Shape is RayShape ||
                contactSet.ObjectB.GeometricObject.Shape is RayShape,
                "ReduceRayHits was called for a contact set without a ray.");

            // For separated contacts keep the contact with the smallest separation.
            // If we have contact, keep the contact with the SMALLEST penetration (= shortest ray length)
            // and remove all invalid contacts (with separation).
            bool    haveContact          = contactSet.HaveContact;
            float   bestPenetrationDepth = haveContact ? float.PositiveInfinity : float.NegativeInfinity;
            Contact bestContact          = null;
            int     numberOfContacts     = contactSet.Count;

            for (int i = 0; i < numberOfContacts; i++)
            {
                Contact contact          = contactSet[i];
                float   penetrationDepth = contact.PenetrationDepth;

                if (haveContact)
                {
                    // Search for positive and smallest penetration depth.
                    if (penetrationDepth >= 0 && penetrationDepth < bestPenetrationDepth)
                    {
                        bestContact          = contact;
                        bestPenetrationDepth = penetrationDepth;
                    }
                }
                else
                {
                    // Search for negative and largest penetration depth (Separation!)
                    Debug.Assert(penetrationDepth < 0, "HaveContact is false, but contact shows penetration.");
                    if (penetrationDepth > bestPenetrationDepth)
                    {
                        bestContact          = contact;
                        bestPenetrationDepth = penetrationDepth;
                    }
                }
            }

            // Keep best contact.
            // Note: In some situations HaveContact is true, but the contact set does not contains any
            // contacts with penetration. This happen, for example, when testing a ray inside a triangle
            // mesh. The TriangleMeshAlgorithm automatically filters contacts with bad normals.
            // When HaveContact is false, we should always have a contact (closest point).

            // Throw away other contacts.
            foreach (var contact in contactSet)
            {
                if (contact != bestContact)
                {
                    contact.Recycle();
                }
            }

            contactSet.Clear();
            if (bestContact != null)
            {
                contactSet.Add(bestContact);
            }
        }