コード例 #1
0
        // Remove all contacts where the angle between the contact normal and the triangle normal
        // is less than the given angle. The limit angle is given as cos(angle) (= dot product).
        private static void RemoveBadContacts(bool swapped, ContactSet testContactSet, Vector3 triangleNormal, float cosMinAngle)
        {
            // Note: We assume that we have only one contact per set.
            if (testContactSet.Count > 0)
            {
                if (!swapped)
                {
                    if (Vector3.Dot(testContactSet[0].Normal, triangleNormal) < cosMinAngle)
                    {
                        foreach (var contact in testContactSet)
                        {
                            contact.Recycle();
                        }

                        testContactSet.Clear();
                    }
                }
                else
                {
                    if (Vector3.Dot(-testContactSet[0].Normal, triangleNormal) < cosMinAngle)
                    {
                        foreach (var contact in testContactSet)
                        {
                            contact.Recycle();
                        }

                        testContactSet.Clear();
                    }
                }
            }
        }
コード例 #2
0
        public override void ComputeCollision(ContactSet contactSet, CollisionQueryType type)
        {
            if (type == CollisionQueryType.ClosestPoints)
            {
                // Closest point queries.
                _closestPointsAlgorithm.ComputeCollision(contactSet, type);
                if (contactSet.HaveContact)
                {
                    // Penetration.

                    // Remember the closest point info in case we run into troubles.
                    // Get the last contact. We assume that this is the newest. In most cases there will
                    // only be one contact in the contact set.
                    Contact fallbackContact = (contactSet.Count > 0)
                                    ? contactSet[contactSet.Count - 1]
                                    : null;

                    // Call the contact query algorithm.
                    _contactAlgorithm.ComputeCollision(contactSet, CollisionQueryType.Contacts);

                    if (!contactSet.HaveContact)
                    {
                        // Problem!
                        // The closest-point algorithm reported contact. The contact algorithm didn't find a contact.
                        // This can happen, for example, because of numerical inaccuracies in GJK vs. MPR.
                        // Keep the result of the closest-point computation, but decrease the penetration depth
                        // to indicate separation.
                        if (fallbackContact != null)
                        {
                            Debug.Assert(fallbackContact.PenetrationDepth == 0);
                            fallbackContact.PenetrationDepth = -Math.Min(10 * Numeric.EpsilonF, CollisionDetection.Epsilon);

                            foreach (var contact in contactSet)
                            {
                                if (contact != fallbackContact)
                                {
                                    contact.Recycle();
                                }
                            }

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

                        contactSet.HaveContact = false;
                    }
                }
            }
            else
            {
                // Boolean or contact queries.
                _contactAlgorithm.ComputeCollision(contactSet, type);
            }
        }
コード例 #3
0
            public override void ComputeCollision(ContactSet contactSet, CollisionQueryType type)
            {
                contactSet.Clear();
                contactSet.Add(ContactHelper.CreateContact(contactSet, new Vector3(1, 2, 3), Vector3.UnitZ, 1, false));

                if (type == CollisionQueryType.Contacts)
                {
                    contactSet.Add(ContactHelper.CreateContact(contactSet, new Vector3(2, 2, 3), Vector3.UnitZ, 1.2f, false));
                }

                contactSet.HaveContact = true;
            }
コード例 #4
0
        /// <summary>
        /// Performs a collision query to update the contact information in the contact set.
        /// </summary>
        /// <param name="contactSet">The contact set.</param>
        /// <param name="deltaTime">
        /// The time step size in seconds. (The elapsed simulation time since the contact set was
        /// updated the last time.)
        /// </param>
        /// <remarks>
        /// <para>
        /// This method updates contact information stored in the given contact set. This method is
        /// usually faster than <see cref="GetContacts"/> because the information in
        /// <paramref name="contactSet"/> is reused and updated.
        /// </para>
        /// <para>
        /// The life time counter of persistent contacts is increased.
        /// </para>
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="contactSet"/> is <see langword="null"/>.
        /// </exception>
        public void UpdateContacts(ContactSet contactSet, float deltaTime)
        {
            if (contactSet == null)
            {
                throw new ArgumentNullException("contactSet");
            }

            // Remove separated contacts - the contact set could contain separated
            // closest points of a GetClosestPoints query.
            ContactHelper.RemoveSeparatedContacts(contactSet);

            // Update cached information.
            ContactHelper.UpdateContacts(contactSet, deltaTime, CollisionDetection.ContactPositionTolerance);

            // We do not compute the detailed contact information for triggers.
            bool ignoreContactInfo = (contactSet.ObjectA.Type == CollisionObjectType.Trigger ||
                                      contactSet.ObjectB.Type == CollisionObjectType.Trigger);

            // We have to make the full contact query if one object is a ray with StopsAtFirstHit because
            // we need the PenetrationDepth for sorting the hits.
            if (contactSet.ObjectA.IsRayThatStopsAtFirstHit || contactSet.ObjectB.IsRayThatStopsAtFirstHit)
            {
                ignoreContactInfo = false;
            }

            if (ignoreContactInfo)
            {
                // Check cached flag. If necessary, make only boolean check.
                if (contactSet.HaveContact == false || contactSet.Count == 0)
                {
                    ComputeCollision(contactSet, CollisionQueryType.Boolean);
                }
            }
            else
            {
                // Compute new contact info.
                ComputeCollision(contactSet, CollisionQueryType.Contacts);
            }

            if (contactSet.HaveContact == false)
            {
                // No contact: Remove old contacts if objects do not touch.
                foreach (var contact in contactSet)
                {
                    contact.Recycle();
                }

                contactSet.Clear();
            }
            else
            {
                // Reduce ray cast results to 1 contact.
                if (contactSet.ObjectA.IsRay || contactSet.ObjectB.IsRay)
                {
                    ContactHelper.ReduceRayHits(contactSet);
                }

                // We have contact: Call contact filter.
                if (CollisionDetection.ContactFilter != null)
                {
                    CollisionDetection.ContactFilter.Filter(contactSet);
                }
            }

            CheckResult(contactSet, false);
            contactSet.IsValid = true;
        }
コード例 #5
0
            public override void ComputeCollision(ContactSet contactSet, CollisionQueryType type)
            {
                contactSet.Clear();
                contactSet.Add(ContactHelper.CreateContact(contactSet, new Vector3F(1, 2, 3), Vector3F.UnitZ, 1, false));

                if (type == CollisionQueryType.Contacts)
                  contactSet.Add(ContactHelper.CreateContact(contactSet, new Vector3F(2, 2, 3), Vector3F.UnitZ, 1.2f, false));

                contactSet.HaveContact = true;
            }
コード例 #6
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);
        }
コード例 #7
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();
              }
        }
コード例 #8
0
        public static void Merge(ContactSet target, ContactSet newContacts, CollisionQueryType type, float contactPositionTolerance)
        {
            Debug.Assert(target != null);
              Debug.Assert(newContacts != null);
              Debug.Assert(contactPositionTolerance >= 0, "The contact position tolerance must be greater than or equal to 0");

              int numberOfNewContacts = newContacts.Count;
              for (int i = 0; i < numberOfNewContacts; i++)
            Merge(target, newContacts[i], type, contactPositionTolerance);

              newContacts.Clear();
        }
コード例 #9
0
        public override void ComputeCollision(ContactSet contactSet, CollisionQueryType type)
        {
            // Ray vs. convex has at max 1 contact.
              Debug.Assert(contactSet.Count <= 1);

              // Object A should be the ray.
              // Object B should be the convex.
              IGeometricObject rayObject = contactSet.ObjectA.GeometricObject;
              IGeometricObject convexObject = contactSet.ObjectB.GeometricObject;

              // Swap object if necessary.
              bool swapped = (convexObject.Shape is RayShape);
              if (swapped)
            MathHelper.Swap(ref rayObject, ref convexObject);

              RayShape rayShape = rayObject.Shape as RayShape;
              ConvexShape convexShape = convexObject.Shape as ConvexShape;

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

              // Call line segment vs. convex for closest points queries.
              if (type == CollisionQueryType.ClosestPoints)
              {
            // Find point on ray closest to the convex shape.

            // Call GJK.
            _gjk.ComputeCollision(contactSet, type);
            if (contactSet.HaveContact == false)
              return;

            // Otherwise compute 1 contact ...
            // GJK result is invalid for penetration.
            foreach (var contact in contactSet)
              contact.Recycle();

            contactSet.Clear();
              }

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

              // Get transformations.
              Vector3F rayScale = rayObject.Scale;
              Vector3F convexScale = convexObject.Scale;
              Pose convexPose = convexObject.Pose;
              Pose rayPose = rayObject.Pose;

              // See Raycasting paper of van den Bergen or Bullet.
              // Note: Compute in local space of convex (object B).

              // Scale ray and transform ray to local space of convex.
              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 convexPose);      // Transform ray to local space of convex.

              var simplex = GjkSimplexSolver.Create();
              try
              {
            Vector3F s = ray.Origin;                  // source
            Vector3F r = ray.Direction * ray.Length;  // ray
            float λ = 0;                              // ray parameter
            Vector3F x = s;                           // hit spot (on ray)
            Vector3F n = new Vector3F();              // normal
            Vector3F v = x - convexShape.GetSupportPoint(ray.Direction, convexScale);
                                                  // v = x - arbitrary point. Vector used for support mapping.
            float distanceSquared = v.LengthSquared;  // ||v||²
            int iterationCount = 0;

            while (distanceSquared > Numeric.EpsilonF && iterationCount < MaxNumberOfIterations)
            {
              iterationCount++;
              Vector3F p = convexShape.GetSupportPoint(v, convexScale); // point on convex
              Vector3F w = x - p;                                       // simplex/Minkowski difference point

              float vDotW = Vector3F.Dot(v, w);       // v∙w
              if (vDotW > 0)
              {
            float vDotR = Vector3F.Dot(v, r);     // v∙r
            if (vDotR >= 0)                       // TODO: vDotR >= - Epsilon^2 ?
              return;                             // No Hit.

            λ = λ - vDotW / vDotR;
            x = s + λ * r;
            simplex.Clear(); // Configuration space obstacle (CSO) is translated whenever x is updated.
            w = x - p;
            n = v;
              }

              simplex.Add(w, x, p);
              simplex.Update();
              v = simplex.ClosestPoint;
              distanceSquared = (simplex.IsValid && !simplex.IsFull) ? v.LengthSquared : 0;
            }

            // We have a contact if the hit is inside the ray length.
            contactSet.HaveContact = (0 <= λ && λ <= 1);

            if (type == CollisionQueryType.Boolean || (type == CollisionQueryType.Contacts && !contactSet.HaveContact))
            {
              // HaveContact queries can exit here.
              // GetContacts queries can exit here if we don't have a contact.
              return;
            }

            float penetrationDepth = λ * ray.Length;

            Debug.Assert(contactSet.HaveContact, "Separation was not detected by GJK above.");

            // Convert back to world space.
            Vector3F position = rayWorld.Origin + rayWorld.Direction * penetrationDepth;
            n = convexPose.ToWorldDirection(n);
            if (!n.TryNormalize())
              n = Vector3F.UnitY;

            if (swapped)
              n = -n;

            // Update contact set.
            Contact contact = ContactHelper.CreateContact(contactSet, position, -n, penetrationDepth, true);
            ContactHelper.Merge(contactSet, contact, type, CollisionDetection.ContactPositionTolerance);
              }
              finally
              {
            simplex.Recycle();
              }
        }
コード例 #10
0
        /// <summary>
        /// Updates the contact information in the given contact set.
        /// </summary>
        /// <param name="contactSet">The contact set containing the last known contacts.</param>
        /// <param name="deltaTime">
        /// The time step size in seconds. (The elapsed simulation time since 
        /// <see cref="UpdateClosestPoints"/> or <see cref="UpdateContacts"/> was last called for this
        /// contact set.)
        /// </param>
        /// <remarks>
        /// If two objects move, the contact information will usually change and has to be updated.
        /// Using the contact set containing the last known contacts, this method can compute the new
        /// contacts faster than <see cref="GetContacts"/> if the poses of the objects haven't changed
        /// drastically.
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="contactSet"/> is <see langword="null"/>.
        /// </exception>
        public void UpdateContacts(ContactSet contactSet, float deltaTime)
        {
            if (contactSet == null)
            throw new ArgumentNullException("contactSet");

              // Broad phase AABB check and collision filtering
              if (HaveAabbContact(contactSet.ObjectA, contactSet.ObjectB))
              {
            // Narrow phase
            AlgorithmMatrix[contactSet].UpdateContacts(contactSet, deltaTime);
              }
              else
              {
            foreach (var contact in contactSet)
              contact.Recycle();

            contactSet.Clear();
            contactSet.HaveContact = false;
              }
        }
コード例 #11
0
        // Remove all contacts where the angle between the contact normal and the triangle normal
        // is less than the given angle. The limit angle is given as cos(angle) (= dot product).
        private static void RemoveBadContacts(bool swapped, ContactSet testContactSet, Vector3F triangleNormal, float cosMinAngle)
        {
            // Note: We assume that we have only one contact per set.
              if (testContactSet.Count > 0)
              {
            if (!swapped)
            {
              if (Vector3F.Dot(testContactSet[0].Normal, triangleNormal) < cosMinAngle)
              {
            foreach (var contact in testContactSet)
              contact.Recycle();

            testContactSet.Clear();
              }
            }
            else
            {
              if (Vector3F.Dot(-testContactSet[0].Normal, triangleNormal) < cosMinAngle)
              {
            foreach (var contact in testContactSet)
              contact.Recycle();

            testContactSet.Clear();
              }
            }
              }
        }
コード例 #12
0
        public override void ComputeCollision(ContactSet contactSet, CollisionQueryType type)
        {
            // Ray vs. box has at max 1 contact.
            Debug.Assert(contactSet.Count <= 1);

            // Object A should be the ray.
            // Object B should be the box.
            IGeometricObject rayObject = contactSet.ObjectA.GeometricObject;
            IGeometricObject boxObject = contactSet.ObjectB.GeometricObject;

            // Swap objects if necessary.
            bool swapped = (boxObject.Shape is RayShape);

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

            RayShape rayShape = rayObject.Shape as RayShape;
            BoxShape boxShape = boxObject.Shape as BoxShape;

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

            // Call line segment vs. box for closest points queries.
            if (type == CollisionQueryType.ClosestPoints)
            {
                // Find point on ray closest to the box.

                // Call GJK.
                _gjk.ComputeCollision(contactSet, type);
                if (contactSet.HaveContact == false)
                {
                    return;
                }

                // Otherwise compute 1 contact ...
                // GJK result is invalid for penetration.
                foreach (var contact in contactSet)
                {
                    contact.Recycle();
                }

                contactSet.Clear();
            }

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

            // Get transformations.
            Vector3F rayScale = rayObject.Scale;
            Vector3F boxScale = Vector3F.Absolute(boxObject.Scale);
            Pose     rayPose  = rayObject.Pose;
            Pose     boxPose  = boxObject.Pose;

            // Apply scale to box.
            Vector3F boxExtent = boxShape.Extent * boxScale;

            // See SOLID and Bergen: "Collision Detection in Interactive 3D Environments", p. 75.
            // Note: Compute in box local space.

            // Apply scale to ray and transform to local space of box.
            Ray rayWorld = new Ray(rayShape);

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

            ray.ToLocal(ref boxPose);     // Transform ray from world to local space.

            uint startOutcode = GeometryHelper.GetOutcode(boxExtent, ray.Origin);
            uint endOutcode   = GeometryHelper.GetOutcode(boxExtent, ray.Origin + ray.Direction * ray.Length);

            if ((startOutcode & endOutcode) != 0)
            {
                // A face of the box is a separating plane.
                return;
            }

            // Assertion: The ray can intersect with the box but may not...
            float    λEnter     = 0;                          // ray parameter where ray enters box
            float    λExit      = 1;                          // ray parameter where ray exits box
            uint     bit        = 1;
            Vector3F r          = ray.Direction * ray.Length; // ray vector
            Vector3F halfExtent = 0.5f * boxExtent;           // Box half-extent vector.
            Vector3F normal     = Vector3F.Zero;              // normal vector

            for (int i = 0; i < 3; i++)
            {
                if ((startOutcode & bit) != 0)
                {
                    // Intersection is an entering point.
                    float λ = (-ray.Origin[i] - halfExtent[i]) / r[i];
                    if (λEnter < λ)
                    {
                        λEnter    = λ;
                        normal    = new Vector3F();
                        normal[i] = 1;
                    }
                }
                else if ((endOutcode & bit) != 0)
                {
                    // Intersection is an exciting point.
                    float λ = (-ray.Origin[i] - halfExtent[i]) / r[i];
                    if (λExit > λ)
                    {
                        λExit = λ;
                    }
                }
                bit <<= 1;
                if ((startOutcode & bit) != 0)
                {
                    // Intersection is an entering point.
                    float λ = (-ray.Origin[i] + halfExtent[i]) / r[i];
                    if (λEnter < λ)
                    {
                        λEnter    = λ;
                        normal    = new Vector3F();
                        normal[i] = -1;
                    }
                }
                else if ((endOutcode & bit) != 0)
                {
                    // Intersection is an exciting point.
                    float λ = (-ray.Origin[i] + halfExtent[i]) / r[i];
                    if (λExit > λ)
                    {
                        λExit = λ;
                    }
                }
                bit <<= 1;
            }

            if (λEnter <= λExit)
            {
                // The ray intersects the box.
                contactSet.HaveContact = true;

                if (type == CollisionQueryType.Boolean)
                {
                    return;
                }

                float penetrationDepth = λEnter * ray.Length;

                if (normal == Vector3F.Zero)
                {
                    normal = Vector3F.UnitX;
                }

                // Create contact info.
                Vector3F position = rayWorld.Origin + rayWorld.Direction * penetrationDepth;
                normal = boxPose.ToWorldDirection(normal);
                if (swapped)
                {
                    normal = -normal;
                }

                // Update contact set.
                Contact contact = ContactHelper.CreateContact(contactSet, position, normal, penetrationDepth, true);
                ContactHelper.Merge(contactSet, contact, type, CollisionDetection.ContactPositionTolerance);
            }
        }
コード例 #13
0
        public override void ComputeCollision(ContactSet contactSet, CollisionQueryType type)
        {
            // Ray vs. box has at max 1 contact.
              Debug.Assert(contactSet.Count <= 1);

              // Object A should be the ray.
              // Object B should be the box.
              IGeometricObject rayObject = contactSet.ObjectA.GeometricObject;
              IGeometricObject boxObject = contactSet.ObjectB.GeometricObject;

              // Swap objects if necessary.
              bool swapped = (boxObject.Shape is RayShape);
              if (swapped)
            MathHelper.Swap(ref rayObject, ref boxObject);

              RayShape rayShape = rayObject.Shape as RayShape;
              BoxShape boxShape = boxObject.Shape as BoxShape;

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

              // Call line segment vs. box for closest points queries.
              if (type == CollisionQueryType.ClosestPoints)
              {
            // Find point on ray closest to the box.

            // Call GJK.
            _gjk.ComputeCollision(contactSet, type);
            if (contactSet.HaveContact == false)
              return;

            // Otherwise compute 1 contact ...
            // GJK result is invalid for penetration.
            foreach (var contact in contactSet)
              contact.Recycle();

            contactSet.Clear();
              }

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

              // Get transformations.
              Vector3F rayScale = rayObject.Scale;
              Vector3F boxScale = Vector3F.Absolute(boxObject.Scale);
              Pose rayPose = rayObject.Pose;
              Pose boxPose = boxObject.Pose;

              // Apply scale to box.
              Vector3F boxExtent = boxShape.Extent * boxScale;

              // See SOLID and Bergen: "Collision Detection in Interactive 3D Environments", p. 75.
              // Note: Compute in box local space.

              // Apply scale to ray and transform to local space of box.
              Ray rayWorld = new Ray(rayShape);
              rayWorld.Scale(ref rayScale);       // Apply scale to ray.
              rayWorld.ToWorld(ref rayPose);      // Transform ray to world space.
              Ray ray = rayWorld;
              ray.ToLocal(ref boxPose);           // Transform ray from world to local space.

              uint startOutcode = GeometryHelper.GetOutcode(boxExtent, ray.Origin);
              uint endOutcode = GeometryHelper.GetOutcode(boxExtent, ray.Origin + ray.Direction * ray.Length);

              if ((startOutcode & endOutcode) != 0)
              {
            // A face of the box is a separating plane.
            return;
              }

              // Assertion: The ray can intersect with the box but may not...
              float λEnter = 0;                         // ray parameter where ray enters box
              float λExit = 1;                          // ray parameter where ray exits box
              uint bit = 1;
              Vector3F r = ray.Direction * ray.Length;  // ray vector
              Vector3F halfExtent = 0.5f * boxExtent;   // Box half-extent vector.
              Vector3F normal = Vector3F.Zero;          // normal vector
              for (int i = 0; i < 3; i++)
              {
            if ((startOutcode & bit) != 0)
            {
              // Intersection is an entering point.
              float λ = (-ray.Origin[i] - halfExtent[i]) / r[i];
              if (λEnter < λ)
              {
            λEnter = λ;
            normal = new Vector3F();
            normal[i] = 1;
              }
            }
            else if ((endOutcode & bit) != 0)
            {
              // Intersection is an exciting point.
              float λ = (-ray.Origin[i] - halfExtent[i]) / r[i];
              if (λExit > λ)
            λExit = λ;
            }
            bit <<= 1;
            if ((startOutcode & bit) != 0)
            {
              // Intersection is an entering point.
              float λ = (-ray.Origin[i] + halfExtent[i]) / r[i];
              if (λEnter < λ)
              {
            λEnter = λ;
            normal = new Vector3F();
            normal[i] = -1;
              }
            }
            else if ((endOutcode & bit) != 0)
            {
              // Intersection is an exciting point.
              float λ = (-ray.Origin[i] + halfExtent[i]) / r[i];
              if (λExit > λ)
            λExit = λ;
            }
            bit <<= 1;
              }

              if (λEnter <= λExit)
              {
            // The ray intersects the box.
            contactSet.HaveContact = true;

            if (type == CollisionQueryType.Boolean)
              return;

            float penetrationDepth = λEnter * ray.Length;

            if (normal == Vector3F.Zero)
              normal = Vector3F.UnitX;

            // Create contact info.
            Vector3F position = rayWorld.Origin + rayWorld.Direction * penetrationDepth;
            normal = boxPose.ToWorldDirection(normal);
            if (swapped)
              normal = -normal;

            // Update contact set.
            Contact contact = ContactHelper.CreateContact(contactSet, position, normal, penetrationDepth, true);
            ContactHelper.Merge(contactSet, contact, type, CollisionDetection.ContactPositionTolerance);
              }
        }
コード例 #14
0
        /// <summary>
        /// Performs a collision query to update the contact information in the contact set.
        /// </summary>
        /// <param name="contactSet">The contact set.</param>
        /// <param name="deltaTime">
        /// The time step size in seconds. (The elapsed simulation time since the contact set was
        /// updated the last time.)
        /// </param>
        /// <remarks>
        /// <para>
        /// This method updates contact information stored in the given contact set. This method is 
        /// usually faster than <see cref="GetContacts"/> because the information in 
        /// <paramref name="contactSet"/> is reused and updated.
        /// </para>
        /// <para>
        /// The life time counter of persistent contacts is increased.
        /// </para>
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="contactSet"/> is <see langword="null"/>.
        /// </exception>
        public void UpdateContacts(ContactSet contactSet, float deltaTime)
        {
            if (contactSet == null)
            throw new ArgumentNullException("contactSet");

              // Remove separated contacts - the contact set could contain separated
              // closest points of a GetClosestPoints query.
              ContactHelper.RemoveSeparatedContacts(contactSet);

              // Update cached information.
              ContactHelper.UpdateContacts(contactSet, deltaTime, CollisionDetection.ContactPositionTolerance);

              // We do not compute the detailed contact information for triggers.
              bool ignoreContactInfo = (contactSet.ObjectA.Type == CollisionObjectType.Trigger
                                || contactSet.ObjectB.Type == CollisionObjectType.Trigger);

              // We have to make the full contact query if one object is a ray with StopsAtFirstHit because
              // we need the PenetrationDepth for sorting the hits.
              if (contactSet.ObjectA.IsRayThatStopsAtFirstHit || contactSet.ObjectB.IsRayThatStopsAtFirstHit)
            ignoreContactInfo = false;

              if (ignoreContactInfo)
              {
            // Check cached flag. If necessary, make only boolean check.
            if (contactSet.HaveContact == false || contactSet.Count == 0)
              ComputeCollision(contactSet, CollisionQueryType.Boolean);
              }
              else
              {
            // Compute new contact info.
            ComputeCollision(contactSet, CollisionQueryType.Contacts);
              }

              if (contactSet.HaveContact == false)
              {
            // No contact: Remove old contacts if objects do not touch.
            foreach (var contact in contactSet)
              contact.Recycle();

            contactSet.Clear();
              }
              else
              {
            // Reduce ray cast results to 1 contact.
            if (contactSet.ObjectA.IsRay || contactSet.ObjectB.IsRay)
              ContactHelper.ReduceRayHits(contactSet);

            // We have contact: Call contact filter.
            if (CollisionDetection.ContactFilter != null)
              CollisionDetection.ContactFilter.Filter(contactSet);
              }

              CheckResult(contactSet, false);
              contactSet.IsValid = true;
        }
コード例 #15
0
        private void AddTriangleContacts(ContactSet contactSet,
                                         bool swapped,
                                         int triangleIndex,
                                         CollisionQueryType type,
                                         ContactSet testContactSet,
                                         CollisionObject testCollisionObject,
                                         TestGeometricObject testGeometricObject,
                                         TriangleShape testTriangle)
        {
            // Object A should be the triangle mesh.
            CollisionObject  collisionObjectA = (swapped) ? contactSet.ObjectB : contactSet.ObjectA;
            CollisionObject  collisionObjectB = (swapped) ? contactSet.ObjectA : contactSet.ObjectB;
            IGeometricObject geometricObjectA = collisionObjectA.GeometricObject;
            var      triangleMeshShape        = ((TriangleMeshShape)geometricObjectA.Shape);
            Triangle triangle = triangleMeshShape.Mesh.GetTriangle(triangleIndex);
            Pose     poseA    = geometricObjectA.Pose;
            Vector3F scaleA   = geometricObjectA.Scale;

            // Find collision algorithm.
            CollisionAlgorithm collisionAlgorithm = CollisionDetection.AlgorithmMatrix[typeof(TriangleShape), collisionObjectB.GeometricObject.Shape.GetType()];

            // Apply scaling.
            testTriangle.Vertex0 = triangle.Vertex0 * scaleA;
            testTriangle.Vertex1 = triangle.Vertex1 * scaleA;
            testTriangle.Vertex2 = triangle.Vertex2 * scaleA;

            // Set the shape temporarily to the current triangles.
            testGeometricObject.Shape = testTriangle;
            testGeometricObject.Scale = Vector3F.One;
            testGeometricObject.Pose  = poseA;

            testCollisionObject.SetInternal(collisionObjectA, testGeometricObject);

            // Make a temporary contact set.
            // (Object A and object B should have the same order as in contactSet; otherwise we couldn't
            // simply merge them.)
            Debug.Assert(testContactSet.Count == 0, "testContactSet needs to be cleared.");
            if (swapped)
            {
                testContactSet.Reset(collisionObjectB, testCollisionObject);
            }
            else
            {
                testContactSet.Reset(testCollisionObject, collisionObjectB);
            }

            if (type == CollisionQueryType.Boolean)
            {
                collisionAlgorithm.ComputeCollision(testContactSet, CollisionQueryType.Boolean);
                contactSet.HaveContact = contactSet.HaveContact || testContactSet.HaveContact;
            }
            else
            {
                // No perturbation test. Most triangle mesh shapes are either complex and automatically
                // have more contacts. Or they are complex and will not be used for stacking
                // where full contact sets would be needed.
                testContactSet.IsPerturbationTestAllowed = false;

                // TODO: Copy separating axis info and similar things into triangleContactSet.
                // But currently this info is not used in the queries.

                // For closest points: 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);
                contactSet.HaveContact = contactSet.HaveContact || testContactSet.HaveContact;

                if (testContactSet.HaveContact && testContactSet.Count > 0 && !triangleMeshShape.IsTwoSided)
                {
                    // To compute the triangle normal in world space we take the normal of the unscaled
                    // triangle and transform the normal with: (M^-1)^T = 1 / scale
                    Vector3F triangleNormalLocal = Vector3F.Cross(triangle.Vertex1 - triangle.Vertex0, triangle.Vertex2 - triangle.Vertex0) / scaleA;
                    Vector3F triangleNormal      = poseA.ToWorldDirection(triangleNormalLocal);
                    if (triangleNormal.TryNormalize())
                    {
                        var preferredNormal = swapped ? -triangleNormal : triangleNormal;

                        // ----- Remove bad normal.
                        // Triangles are double sided, but meshes are single sided.
                        // --> Remove contacts where the contact normal points into the wrong direction.
                        ContactHelper.RemoveBadContacts(testContactSet, preferredNormal, -Numeric.EpsilonF);

                        if (testContactSet.Count > 0 && triangleMeshShape.EnableContactWelding)
                        {
                            var contactDotTriangle = Vector3F.Dot(testContactSet[0].Normal, preferredNormal);
                            if (contactDotTriangle < WeldingLimit)
                            {
                                // Bad normal. Perform welding.

                                Vector3F contactPositionOnTriangle = swapped
                                                       ? testContactSet[0].PositionBLocal / scaleA
                                                       : testContactSet[0].PositionALocal / scaleA;

                                Vector3F neighborNormal;
                                float    triangleDotNeighbor;
                                GetNeighborNormal(triangleIndex, triangle, contactPositionOnTriangle, triangleNormal, triangleMeshShape, poseA, scaleA, out neighborNormal, out triangleDotNeighbor);

                                if (triangleDotNeighbor < float.MaxValue && Numeric.IsLess(contactDotTriangle, triangleDotNeighbor))
                                {
                                    // Normal is not in allowed range.
                                    // Test again in triangle normal direction.

                                    Contact c0 = testContactSet[0];
                                    testContactSet.RemoveAt(0);

                                    testContactSet.Clear();
                                    testContactSet.PreferredNormal = preferredNormal;
                                    collisionAlgorithm.ComputeCollision(testContactSet, queryType);
                                    testContactSet.PreferredNormal = Vector3F.Zero;

                                    if (testContactSet.Count > 0)
                                    {
                                        Contact c1 = testContactSet[0];
                                        float   contact1DotTriangle = Vector3F.Dot(c1.Normal, preferredNormal);

                                        // We use c1 instead of c0 if it has lower penetration depth (then it is simply
                                        // better). Or we use c1 if the penetration depth increase is in an allowed range
                                        // and c1 has a normal in the allowed range.
                                        if (c1.PenetrationDepth < c0.PenetrationDepth ||
                                            Numeric.IsGreaterOrEqual(contact1DotTriangle, triangleDotNeighbor) &&
                                            c1.PenetrationDepth < c0.PenetrationDepth + CollisionDetection.ContactPositionTolerance)
                                        {
                                            c0.Recycle();
                                            c0 = c1;
                                            testContactSet.RemoveAt(0);
                                            contactDotTriangle = contact1DotTriangle;
                                        }
                                    }

                                    if (Numeric.IsLess(contactDotTriangle, triangleDotNeighbor))
                                    {
                                        // Clamp contact to allowed normal:
                                        // We keep the contact position on the mesh and the penetration depth. We set
                                        // a new normal and compute the other related values for this normal.
                                        if (!swapped)
                                        {
                                            var positionAWorld = c0.PositionAWorld;
                                            c0.Normal = neighborNormal;
                                            var positionBWorld = positionAWorld - c0.Normal * c0.PenetrationDepth;
                                            c0.Position       = (positionAWorld + positionBWorld) / 2;
                                            c0.PositionBLocal = testContactSet.ObjectB.GeometricObject.Pose.ToLocalPosition(positionBWorld);
                                        }
                                        else
                                        {
                                            var positionBWorld = c0.PositionBWorld;
                                            c0.Normal = -neighborNormal;
                                            var positionAWorld = positionBWorld + c0.Normal * c0.PenetrationDepth;
                                            c0.Position       = (positionAWorld + positionBWorld) / 2;
                                            c0.PositionALocal = testContactSet.ObjectA.GeometricObject.Pose.ToLocalPosition(positionAWorld);
                                        }
                                    }

                                    c0.Recycle();
                                }
                            }
                        }
                    }
                }

                #region ----- Merge testContactSet into contactSet -----

                if (testContactSet.Count > 0)
                {
                    // Set the shape feature of the new contacts.
                    int numberOfContacts = testContactSet.Count;
                    for (int i = 0; i < numberOfContacts; i++)
                    {
                        Contact contact = testContactSet[i];
                        //if (contact.Lifetime.Ticks == 0) // Currently, this check is not necessary because triangleSet does not contain old contacts.
                        //{
                        if (swapped)
                        {
                            contact.FeatureB = triangleIndex;
                        }
                        else
                        {
                            contact.FeatureA = triangleIndex;
                        }
                        //}
                    }

                    // Merge the contact info.
                    ContactHelper.Merge(contactSet, testContactSet, type, CollisionDetection.ContactPositionTolerance);
                }
                #endregion
            }
        }
コード例 #16
0
        public override void ComputeCollision(ContactSet contactSet, CollisionQueryType type)
        {
            // Ray vs. convex has at max 1 contact.
            Debug.Assert(contactSet.Count <= 1);

            // Object A should be the ray.
            // Object B should be the convex.
            IGeometricObject rayObject    = contactSet.ObjectA.GeometricObject;
            IGeometricObject convexObject = contactSet.ObjectB.GeometricObject;

            // Swap object if necessary.
            bool swapped = (convexObject.Shape is RayShape);

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

            RayShape    rayShape    = rayObject.Shape as RayShape;
            ConvexShape convexShape = convexObject.Shape as ConvexShape;

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

            // Call line segment vs. convex for closest points queries.
            if (type == CollisionQueryType.ClosestPoints)
            {
                // Find point on ray closest to the convex shape.

                // Call GJK.
                _gjk.ComputeCollision(contactSet, type);
                if (contactSet.HaveContact == false)
                {
                    return;
                }

                // Otherwise compute 1 contact ...
                // GJK result is invalid for penetration.
                foreach (var contact in contactSet)
                {
                    contact.Recycle();
                }

                contactSet.Clear();
            }

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

            // Get transformations.
            Vector3 rayScale    = rayObject.Scale;
            Vector3 convexScale = convexObject.Scale;
            Pose    convexPose  = convexObject.Pose;
            Pose    rayPose     = rayObject.Pose;

            // See Raycasting paper of van den Bergen or Bullet.
            // Note: Compute in local space of convex (object B).

            // Scale ray and transform ray to local space of convex.
            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 convexPose); // Transform ray to local space of convex.

            var simplex = GjkSimplexSolver.Create();

            try
            {
                Vector3 s = ray.Origin;                 // source
                Vector3 r = ray.Direction * ray.Length; // ray
                float   λ = 0;                          // ray parameter
                Vector3 x = s;                          // hit spot (on ray)
                Vector3 n = new Vector3();              // normal
                Vector3 v = x - convexShape.GetSupportPoint(ray.Direction, convexScale);
                // v = x - arbitrary point. Vector used for support mapping.
                float distanceSquared = v.LengthSquared(); // ||v||²
                int   iterationCount  = 0;

                while (distanceSquared > Numeric.EpsilonF && iterationCount < MaxNumberOfIterations)
                {
                    iterationCount++;
                    Vector3 p = convexShape.GetSupportPoint(v, convexScale); // point on convex
                    Vector3 w = x - p;                                       // simplex/Minkowski difference point

                    float vDotW = Vector3.Dot(v, w);                         // v∙w
                    if (vDotW > 0)
                    {
                        float vDotR = Vector3.Dot(v, r); // v∙r
                        if (vDotR >= 0)                  // TODO: vDotR >= - Epsilon^2 ?
                        {
                            return;                      // No Hit.
                        }
                        λ = λ - vDotW / vDotR;
                        x = s + λ * r;
                        simplex.Clear(); // Configuration space obstacle (CSO) is translated whenever x is updated.
                        w = x - p;
                        n = v;
                    }

                    simplex.Add(w, x, p);
                    simplex.Update();
                    v = simplex.ClosestPoint;
                    distanceSquared = (simplex.IsValid && !simplex.IsFull) ? v.LengthSquared() : 0;
                }

                // We have a contact if the hit is inside the ray length.
                contactSet.HaveContact = (0 <= λ && λ <= 1);

                if (type == CollisionQueryType.Boolean || (type == CollisionQueryType.Contacts && !contactSet.HaveContact))
                {
                    // HaveContact queries can exit here.
                    // GetContacts queries can exit here if we don't have a contact.
                    return;
                }

                float penetrationDepth = λ * ray.Length;

                Debug.Assert(contactSet.HaveContact, "Separation was not detected by GJK above.");

                // Convert back to world space.
                Vector3 position = rayWorld.Origin + rayWorld.Direction * penetrationDepth;
                n = convexPose.ToWorldDirection(n);
                if (!n.TryNormalize())
                {
                    n = Vector3.UnitY;
                }

                if (swapped)
                {
                    n = -n;
                }

                // Update contact set.
                Contact contact = ContactHelper.CreateContact(contactSet, position, -n, penetrationDepth, true);
                ContactHelper.Merge(contactSet, contact, type, CollisionDetection.ContactPositionTolerance);
            }
            finally
            {
                simplex.Recycle();
            }
        }
コード例 #17
0
        public override void ComputeCollision(ContactSet contactSet, CollisionQueryType type)
        {
            if (type == CollisionQueryType.ClosestPoints)
              {
            // Closest point queries.
            _closestPointsAlgorithm.ComputeCollision(contactSet, type);
            if (contactSet.HaveContact)
            {
              // Penetration.

              // Remember the closest point info in case we run into troubles.
              // Get the last contact. We assume that this is the newest. In most cases there will
              // only be one contact in the contact set.
              Contact fallbackContact = (contactSet.Count > 0)
                                    ? contactSet[contactSet.Count - 1]
                                    : null;

              // Call the contact query algorithm.
              _contactAlgorithm.ComputeCollision(contactSet, CollisionQueryType.Contacts);

              if (!contactSet.HaveContact)
              {
            // Problem!
            // The closest-point algorithm reported contact. The contact algorithm didn't find a contact.
            // This can happen, for example, because of numerical inaccuracies in GJK vs. MPR.
            // Keep the result of the closest-point computation, but decrease the penetration depth
            // to indicate separation.
            if (fallbackContact != null)
            {
              Debug.Assert(fallbackContact.PenetrationDepth == 0);
              fallbackContact.PenetrationDepth = -Math.Min(10 * Numeric.EpsilonF, CollisionDetection.Epsilon);

              foreach (var contact in contactSet)
                if (contact != fallbackContact)
                  contact.Recycle();

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

            contactSet.HaveContact = false;
              }
            }
              }
              else
              {
            // Boolean or contact queries.
            _contactAlgorithm.ComputeCollision(contactSet, type);
              }
        }
コード例 #18
0
    private void AddTriangleContacts(ContactSet contactSet,
                                     bool swapped,
                                     int triangleIndex,
                                     CollisionQueryType type,
                                     ContactSet testContactSet,
                                     CollisionObject testCollisionObject,
                                     TestGeometricObject testGeometricObject,
                                     TriangleShape testTriangle)
    {
      // Object A should be the triangle mesh.
      CollisionObject collisionObjectA = (swapped) ? contactSet.ObjectB : contactSet.ObjectA;
      CollisionObject collisionObjectB = (swapped) ? contactSet.ObjectA : contactSet.ObjectB;
      IGeometricObject geometricObjectA = collisionObjectA.GeometricObject;
      var triangleMeshShape = ((TriangleMeshShape)geometricObjectA.Shape);
      Triangle triangle = triangleMeshShape.Mesh.GetTriangle(triangleIndex);
      Pose poseA = geometricObjectA.Pose;
      Vector3F scaleA = geometricObjectA.Scale;

      // Find collision algorithm. 
      CollisionAlgorithm collisionAlgorithm = CollisionDetection.AlgorithmMatrix[typeof(TriangleShape), collisionObjectB.GeometricObject.Shape.GetType()];

      // Apply scaling.
      testTriangle.Vertex0 = triangle.Vertex0 * scaleA;
      testTriangle.Vertex1 = triangle.Vertex1 * scaleA;
      testTriangle.Vertex2 = triangle.Vertex2 * scaleA;

      // Set the shape temporarily to the current triangles.
      testGeometricObject.Shape = testTriangle;
      testGeometricObject.Scale = Vector3F.One;
      testGeometricObject.Pose = poseA;

      testCollisionObject.SetInternal(collisionObjectA, testGeometricObject);

      // Make a temporary contact set.
      // (Object A and object B should have the same order as in contactSet; otherwise we couldn't 
      // simply merge them.)
      Debug.Assert(testContactSet.Count == 0, "testContactSet needs to be cleared.");
      if (swapped)
        testContactSet.Reset(collisionObjectB, testCollisionObject);
      else
        testContactSet.Reset(testCollisionObject, collisionObjectB);

      if (type == CollisionQueryType.Boolean)
      {
        collisionAlgorithm.ComputeCollision(testContactSet, CollisionQueryType.Boolean);
        contactSet.HaveContact = contactSet.HaveContact || testContactSet.HaveContact;
      }
      else
      {
        // No perturbation test. Most triangle mesh shapes are either complex and automatically
        // have more contacts. Or they are complex and will not be used for stacking
        // where full contact sets would be needed.
        testContactSet.IsPerturbationTestAllowed = false;

        // TODO: Copy separating axis info and similar things into triangleContactSet. 
        // But currently this info is not used in the queries.

        // For closest points: 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);
        contactSet.HaveContact = contactSet.HaveContact || testContactSet.HaveContact;

        if (testContactSet.HaveContact && testContactSet.Count > 0 && !triangleMeshShape.IsTwoSided)
        {
          // To compute the triangle normal in world space we take the normal of the unscaled 
          // triangle and transform the normal with: (M^-1)^T = 1 / scale
          Vector3F triangleNormalLocal = Vector3F.Cross(triangle.Vertex1 - triangle.Vertex0, triangle.Vertex2 - triangle.Vertex0) / scaleA;
          Vector3F triangleNormal = poseA.ToWorldDirection(triangleNormalLocal);
          if (triangleNormal.TryNormalize())
          {
            var preferredNormal = swapped ? -triangleNormal : triangleNormal;

            // ----- Remove bad normal.
            // Triangles are double sided, but meshes are single sided.
            // --> Remove contacts where the contact normal points into the wrong direction.
            ContactHelper.RemoveBadContacts(testContactSet, preferredNormal, -Numeric.EpsilonF);

            if (testContactSet.Count > 0 && triangleMeshShape.EnableContactWelding)
            {
              var contactDotTriangle = Vector3F.Dot(testContactSet[0].Normal, preferredNormal);
              if (contactDotTriangle < WeldingLimit)
              {
                // Bad normal. Perform welding.

                Vector3F contactPositionOnTriangle = swapped
                                                       ? testContactSet[0].PositionBLocal / scaleA
                                                       : testContactSet[0].PositionALocal / scaleA;

                Vector3F neighborNormal;
                float triangleDotNeighbor;
                GetNeighborNormal(triangleIndex, triangle, contactPositionOnTriangle, triangleNormal, triangleMeshShape, poseA, scaleA, out neighborNormal, out triangleDotNeighbor);

                if (triangleDotNeighbor < float.MaxValue && Numeric.IsLess(contactDotTriangle, triangleDotNeighbor))
                {
                  // Normal is not in allowed range.
                  // Test again in triangle normal direction.

                  Contact c0 = testContactSet[0];
                  testContactSet.RemoveAt(0);

                  testContactSet.Clear();
                  testContactSet.PreferredNormal = preferredNormal;
                  collisionAlgorithm.ComputeCollision(testContactSet, queryType);
                  testContactSet.PreferredNormal = Vector3F.Zero;

                  if (testContactSet.Count > 0)
                  {
                    Contact c1 = testContactSet[0];
                    float contact1DotTriangle = Vector3F.Dot(c1.Normal, preferredNormal);

                    // We use c1 instead of c0 if it has lower penetration depth (then it is simply
                    // better). Or we use c1 if the penetration depth increase is in an allowed range
                    // and c1 has a normal in the allowed range.
                    if (c1.PenetrationDepth < c0.PenetrationDepth 
                        || Numeric.IsGreaterOrEqual(contact1DotTriangle, triangleDotNeighbor)
                           && c1.PenetrationDepth < c0.PenetrationDepth + CollisionDetection.ContactPositionTolerance)
                    {
                      c0.Recycle();
                      c0 = c1;
                      testContactSet.RemoveAt(0);
                      contactDotTriangle = contact1DotTriangle;
                    }
                  }

                  if (Numeric.IsLess(contactDotTriangle, triangleDotNeighbor))
                  {
                    // Clamp contact to allowed normal:
                    // We keep the contact position on the mesh and the penetration depth. We set
                    // a new normal and compute the other related values for this normal.
                    if (!swapped)
                    {
                      var positionAWorld = c0.PositionAWorld;
                      c0.Normal = neighborNormal;
                      var positionBWorld = positionAWorld - c0.Normal * c0.PenetrationDepth;
                      c0.Position = (positionAWorld + positionBWorld) / 2;
                      c0.PositionBLocal = testContactSet.ObjectB.GeometricObject.Pose.ToLocalPosition(positionBWorld);
                    }
                    else
                    {
                      var positionBWorld = c0.PositionBWorld;
                      c0.Normal = -neighborNormal;
                      var positionAWorld = positionBWorld + c0.Normal * c0.PenetrationDepth;
                      c0.Position = (positionAWorld + positionBWorld) / 2;
                      c0.PositionALocal = testContactSet.ObjectA.GeometricObject.Pose.ToLocalPosition(positionAWorld);
                    }
                  }

                  c0.Recycle();
                }
              }
            }
          }
        }

        #region ----- Merge testContactSet into contactSet -----

        if (testContactSet.Count > 0)
        {
          // Set the shape feature of the new contacts.
          int numberOfContacts = testContactSet.Count;
          for (int i = 0; i < numberOfContacts; i++)
          {
            Contact contact = testContactSet[i];
            //if (contact.Lifetime.Ticks == 0) // Currently, this check is not necessary because triangleSet does not contain old contacts.
            //{
            if (swapped)
              contact.FeatureB = triangleIndex;
            else
              contact.FeatureA = triangleIndex;
            //}
          }

          // Merge the contact info.
          ContactHelper.Merge(contactSet, testContactSet, type, CollisionDetection.ContactPositionTolerance);
        }
        #endregion
      }
    }