Ejemplo n.º 1
0
        /// <summary>
        /// Raycasts a single body. NOTE: For performance reasons terrain and trianglemeshshape aren't checked
        /// against rays (rays are of infinite length). They are checked against segments
        /// which start at rayOrigin and end in rayOrigin + rayDirection.
        /// </summary>
        #region public override bool Raycast(RigidBody body, JVector rayOrigin, JVector rayDirection, out JVector normal, out double fraction)
        public override bool Raycast(RigidBody body, JVector rayOrigin, JVector rayDirection, out JVector normal, out double fraction)
        {
            fraction = double.MaxValue; normal = JVector.Zero;

            if (!body.BoundingBox.RayIntersect(ref rayOrigin, ref rayDirection))
            {
                return(false);
            }

            if (body.Shape is Multishape)
            {
                Multishape ms = (body.Shape as Multishape).RequestWorkingClone();

                JVector tempNormal; double tempFraction;
                bool    multiShapeCollides = false;

                JVector transformedOrigin; JVector.Subtract(ref rayOrigin, ref body.position, out transformedOrigin);
                JVector.Transform(ref transformedOrigin, ref body.invOrientation, out transformedOrigin);
                JVector transformedDirection; JVector.Transform(ref rayDirection, ref body.invOrientation, out transformedDirection);

                int msLength = ms.Prepare(ref transformedOrigin, ref transformedDirection);

                for (int i = 0; i < msLength; i++)
                {
                    ms.SetCurrentShape(i);

                    if (GJKCollide.Raycast(ms, ref body.orientation, ref body.invOrientation, ref body.position,
                                           ref rayOrigin, ref rayDirection, out tempFraction, out tempNormal))
                    {
                        if (tempFraction < fraction)
                        {
                            if (useTerrainNormal && ms is TerrainShape)
                            {
                                (ms as TerrainShape).CollisionNormal(out tempNormal);
                                JVector.Transform(ref tempNormal, ref body.orientation, out tempNormal);
                                tempNormal.Negate();
                            }
                            else if (useTriangleMeshNormal && ms is TriangleMeshShape)
                            {
                                (ms as TriangleMeshShape).CollisionNormal(out tempNormal);
                                JVector.Transform(ref tempNormal, ref body.orientation, out tempNormal);
                                tempNormal.Negate();
                            }

                            normal             = tempNormal;
                            fraction           = tempFraction;
                            multiShapeCollides = true;
                        }
                    }
                }

                ms.ReturnWorkingClone();
                return(multiShapeCollides);
            }
            else
            {
                return(GJKCollide.Raycast(body.Shape, ref body.orientation, ref body.invOrientation, ref body.position,
                                          ref rayOrigin, ref rayDirection, out fraction, out normal));
            }
        }
Ejemplo n.º 2
0
        private void DetectSoftRigid(RigidBody rigidBody, SoftBody softBody)
        {
            if (rigidBody.Shape is Multishape)
            {
                Multishape ms = (rigidBody.Shape as Multishape);
                ms = ms.RequestWorkingClone();

                JBBox transformedBoundingBox = softBody.BoundingBox;
                transformedBoundingBox.InverseTransform(ref rigidBody.position, ref rigidBody.orientation);

                int msLength = ms.Prepare(ref transformedBoundingBox);

                List <int> detected = potentialTriangleLists.GetNew();
                softBody.dynamicTree.Query(detected, ref rigidBody.boundingBox);

                foreach (int i in detected)
                {
                    SoftBody.Triangle t = softBody.dynamicTree.GetUserData(i);

                    JVector point, normal;
                    float   penetration;
                    bool    result;

                    for (int e = 0; e < msLength; e++)
                    {
                        ms.SetCurrentShape(e);

                        result = XenoCollide.Detect(ms, t, ref rigidBody.orientation, ref JMatrix.InternalIdentity,
                                                    ref rigidBody.position, ref JVector.InternalZero, out point, out normal, out penetration);

                        if (result)
                        {
                            int minIndex = FindNearestTrianglePoint(softBody, i, ref point);

                            if (this.RaisePassedNarrowphase(rigidBody, softBody.points[minIndex],
                                                            ref point, ref normal, penetration))
                            {
                                RaiseCollisionDetected(rigidBody,
                                                       softBody.points[minIndex], ref point, ref point, ref normal, penetration);
                            }
                        }
                    }
                }

                detected.Clear(); potentialTriangleLists.GiveBack(detected);
                ms.ReturnWorkingClone();
            }
            else
            {
                List <int> detected = potentialTriangleLists.GetNew();
                softBody.dynamicTree.Query(detected, ref rigidBody.boundingBox);

                foreach (int i in detected)
                {
                    SoftBody.Triangle t = softBody.dynamicTree.GetUserData(i);

                    JVector point, normal;
                    float   penetration;
                    bool    result;

                    result = XenoCollide.Detect(rigidBody.Shape, t, ref rigidBody.orientation, ref JMatrix.InternalIdentity,
                                                ref rigidBody.position, ref JVector.InternalZero, out point, out normal, out penetration);

                    if (result)
                    {
                        int minIndex = FindNearestTrianglePoint(softBody, i, ref point);

                        if (this.RaisePassedNarrowphase(rigidBody, softBody.points[minIndex],
                                                        ref point, ref normal, penetration))
                        {
                            RaiseCollisionDetected(rigidBody,
                                                   softBody.points[minIndex], ref point, ref point, ref normal, penetration);
                        }
                    }
                }

                detected.Clear();
                potentialTriangleLists.GiveBack(detected);
            }
        }
Ejemplo n.º 3
0
        private void DetectRigidRigid(RigidBody body1, RigidBody body2)
        {
            bool b1IsMulti = (body1.Shape is Multishape);
            bool b2IsMulti = (body2.Shape is Multishape);

            JVector point, normal;
            float   penetration;

            if (!b1IsMulti && !b2IsMulti)
            {
                if (XenoCollide.Detect(body1.Shape, body2.Shape, ref body1.orientation,
                                       ref body2.orientation, ref body1.position, ref body2.position,
                                       out point, out normal, out penetration))
                {
                    if (this.RaisePassedNarrowphase(body1, body2, ref point, ref normal, penetration))
                    {
                        JVector point1, point2;
                        FindSupportPoints(body1, body2, body1.Shape, body2.Shape, ref point, ref normal, out point1, out point2);
                        RaiseCollisionDetected(body1, body2, ref point1, ref point2, ref normal, penetration);
                    }
                }
            }
            else if (b1IsMulti && b2IsMulti)
            {
                Multishape ms1 = (body1.Shape as Multishape);
                Multishape ms2 = (body2.Shape as Multishape);

                ms1 = ms1.RequestWorkingClone();
                ms2 = ms2.RequestWorkingClone();

                JBBox transformedBoundingBox = body2.boundingBox;
                transformedBoundingBox.InverseTransform(ref body1.position, ref body1.orientation);

                int ms1Length = ms1.Prepare(ref transformedBoundingBox);

                transformedBoundingBox = body1.boundingBox;
                transformedBoundingBox.InverseTransform(ref body2.position, ref body2.orientation);

                int ms2Length = ms2.Prepare(ref transformedBoundingBox);

                if (ms1Length == 0 || ms2Length == 0)
                {
                    ms1.ReturnWorkingClone();
                    ms2.ReturnWorkingClone();
                    return;
                }

                for (int i = 0; i < ms1Length; i++)
                {
                    ms1.SetCurrentShape(i);

                    for (int e = 0; e < ms2Length; e++)
                    {
                        ms2.SetCurrentShape(e);

                        if (XenoCollide.Detect(ms1, ms2, ref body1.orientation,
                                               ref body2.orientation, ref body1.position, ref body2.position,
                                               out point, out normal, out penetration))
                        {
                            if (this.RaisePassedNarrowphase(body1, body2, ref point, ref normal, penetration))
                            {
                                JVector point1, point2;
                                FindSupportPoints(body1, body2, ms1, ms2, ref point, ref normal, out point1, out point2);
                                RaiseCollisionDetected(body1, body2, ref point1, ref point2, ref normal, penetration);
                            }
                        }
                    }
                }

                ms1.ReturnWorkingClone();
                ms2.ReturnWorkingClone();
            }
            else
            {
                RigidBody b1, b2;

                if (body2.Shape is Multishape)
                {
                    b1 = body2; b2 = body1;
                }
                else
                {
                    b2 = body2; b1 = body1;
                }

                Multishape ms = (b1.Shape as Multishape);

                ms = ms.RequestWorkingClone();

                JBBox transformedBoundingBox = b2.boundingBox;
                transformedBoundingBox.InverseTransform(ref b1.position, ref b1.orientation);

                int msLength = ms.Prepare(ref transformedBoundingBox);

                if (msLength == 0)
                {
                    ms.ReturnWorkingClone();
                    return;
                }

                for (int i = 0; i < msLength; i++)
                {
                    ms.SetCurrentShape(i);

                    if (XenoCollide.Detect(ms, b2.Shape, ref b1.orientation,
                                           ref b2.orientation, ref b1.position, ref b2.position,
                                           out point, out normal, out penetration))
                    {
                        if (this.RaisePassedNarrowphase(b1, b2, ref point, ref normal, penetration))
                        {
                            JVector point1, point2;
                            FindSupportPoints(b1, b2, ms, b2.Shape, ref point, ref normal, out point1, out point2);

                            if (useTerrainNormal && ms is TerrainShape)
                            {
                                (ms as TerrainShape).CollisionNormal(out normal);
                                JVector.Transform(ref normal, ref b1.orientation, out normal);
                            }
                            else if (useTriangleMeshNormal && ms is TriangleMeshShape)
                            {
                                (ms as TriangleMeshShape).CollisionNormal(out normal);
                                JVector.Transform(ref normal, ref b1.orientation, out normal);
                            }

                            RaiseCollisionDetected(b1, b2, ref point1, ref point2, ref normal, penetration);
                        }
                    }
                }

                ms.ReturnWorkingClone();
            }
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Adds a body to the fluid. Only bodies which where added
        /// to the fluidvolume gets affected by buoyancy forces.
        /// </summary>
        /// <param name="body">The body which should be added.</param>
        /// <param name="subdivisions">The object is subdivided in smaller objects
        /// for which buoyancy force is calculated. The more subdivisons the better
        /// the results. Note that the total number of subdivisions is subdivisions³.</param>
        public void Add(RigidBody body, int subdivisions)
        {
            List <JVector> massPoints = new List <JVector>();
            JVector        testVector;

            JVector diff = body.Shape.BoundingBox.Max - body.Shape.BoundingBox.Min;

            if (diff.IsNearlyZero())
            {
                throw new InvalidOperationException("BoundingBox volume of the shape is zero.");
            }

            Multishape ms     = body.Shape as Multishape;
            int        values = 0;

            if (ms != null)
            {
                JBBox largeBox = JBBox.LargeBox;
                values = ms.Prepare(ref largeBox);
            }

            for (int i = 0; i < subdivisions; i++)
            {
                for (int e = 0; e < subdivisions; e++)
                {
                    for (int k = 0; k < subdivisions; k++)
                    {
                        testVector.X = body.Shape.BoundingBox.Min.X + (diff.X / (float)(subdivisions - 1)) * ((float)i);
                        testVector.Y = body.Shape.BoundingBox.Min.Y + (diff.Y / (float)(subdivisions - 1)) * ((float)e);
                        testVector.Z = body.Shape.BoundingBox.Min.Z + (diff.Z / (float)(subdivisions - 1)) * ((float)k);

                        JMatrix ident = JMatrix.Identity;
                        JVector zero  = JVector.Zero;

                        if (ms != null)
                        {
                            for (int j = 0; j < values; j++)
                            {
                                ms.SetCurrentShape(j);


                                if (GJKCollide.Pointcast(body.Shape, ref ident,
                                                         ref zero, ref testVector))
                                {
                                    massPoints.Add(testVector);
                                }
                            }
                        }
                        else
                        {
                            if (GJKCollide.Pointcast(body.Shape, ref ident,
                                                     ref zero, ref testVector))
                            {
                                massPoints.Add(testVector);
                            }
                        }
                    }
                }
            }

            samples.Add(body.Shape, massPoints.ToArray());
            bodies.Add(body);
        }
Ejemplo n.º 5
0
        private void DetectRigidRigid(RigidBody body1, RigidBody body2)
        {
            bool b1IsMulti = (body1.Shape is Multishape);
            bool b2IsMulti = (body2.Shape is Multishape);

            bool speculative = speculativeContacts ||
                               (body1.EnableSpeculativeContacts || body2.EnableSpeculativeContacts);

            JVector point, normal;
            float   penetration;

            if (!b1IsMulti && !b2IsMulti)
            {
                if (XenoCollide.Detect(body1.Shape, body2.Shape, ref body1.orientation,
                                       ref body2.orientation, ref body1.position, ref body2.position,
                                       out point, out normal, out penetration))
                {
                    JVector point1, point2;
                    FindSupportPoints(body1, body2, body1.Shape, body2.Shape, ref point, ref normal, out point1, out point2);
                    RaiseCollisionDetected(body1, body2, ref point1, ref point2, ref normal, penetration);
                }
                else if (speculative)
                {
                    JVector hit1, hit2;

                    if (GJKCollide.ClosestPoints(body1.Shape, body2.Shape, ref body1.orientation, ref body2.orientation,
                                                 ref body1.position, ref body2.position, out hit1, out hit2, out normal))
                    {
                        JVector delta = hit2 - hit1;

                        if (delta.LengthSquared() < (body1.sweptDirection - body2.sweptDirection).LengthSquared())
                        {
                            penetration = delta * normal;

                            if (penetration < 0.0f)
                            {
                                RaiseCollisionDetected(body1, body2, ref hit1, ref hit2, ref normal, penetration);
                            }
                        }
                    }
                }
            }
            else if (b1IsMulti && b2IsMulti)
            {
                Multishape ms1 = (body1.Shape as Multishape);
                Multishape ms2 = (body2.Shape as Multishape);

                ms1 = ms1.RequestWorkingClone();
                ms2 = ms2.RequestWorkingClone();

                JBBox transformedBoundingBox = body2.boundingBox;
                transformedBoundingBox.InverseTransform(ref body1.position, ref body1.orientation);

                int ms1Length = ms1.Prepare(ref transformedBoundingBox);

                transformedBoundingBox = body1.boundingBox;
                transformedBoundingBox.InverseTransform(ref body2.position, ref body2.orientation);

                int ms2Length = ms2.Prepare(ref transformedBoundingBox);

                if (ms1Length == 0 || ms2Length == 0)
                {
                    ms1.ReturnWorkingClone();
                    ms2.ReturnWorkingClone();
                    return;
                }

                for (int i = 0; i < ms1Length; i++)
                {
                    ms1.SetCurrentShape(i);

                    for (int e = 0; e < ms2Length; e++)
                    {
                        ms2.SetCurrentShape(e);

                        if (XenoCollide.Detect(ms1, ms2, ref body1.orientation,
                                               ref body2.orientation, ref body1.position, ref body2.position,
                                               out point, out normal, out penetration))
                        {
                            JVector point1, point2;
                            FindSupportPoints(body1, body2, ms1, ms2, ref point, ref normal, out point1, out point2);
                            RaiseCollisionDetected(body1, body2, ref point1, ref point2, ref normal, penetration);
                        }
                        else if (speculative)
                        {
                            JVector hit1, hit2;

                            if (GJKCollide.ClosestPoints(ms1, ms2, ref body1.orientation, ref body2.orientation,
                                                         ref body1.position, ref body2.position, out hit1, out hit2, out normal))
                            {
                                JVector delta = hit2 - hit1;

                                if (delta.LengthSquared() < (body1.sweptDirection - body2.sweptDirection).LengthSquared())
                                {
                                    penetration = delta * normal;

                                    if (penetration < 0.0f)
                                    {
                                        RaiseCollisionDetected(body1, body2, ref hit1, ref hit2, ref normal, penetration);
                                    }
                                }
                            }
                        }
                    }
                }

                ms1.ReturnWorkingClone();
                ms2.ReturnWorkingClone();
            }
            else
            {
                RigidBody b1, b2;

                if (body2.Shape is Multishape)
                {
                    b1 = body2; b2 = body1;
                }
                else
                {
                    b2 = body2; b1 = body1;
                }

                Multishape ms = (b1.Shape as Multishape);

                ms = ms.RequestWorkingClone();

                JBBox transformedBoundingBox = b2.boundingBox;
                transformedBoundingBox.InverseTransform(ref b1.position, ref b1.orientation);

                int msLength = ms.Prepare(ref transformedBoundingBox);

                if (msLength == 0)
                {
                    ms.ReturnWorkingClone();
                    return;
                }

                for (int i = 0; i < msLength; i++)
                {
                    ms.SetCurrentShape(i);

                    if (XenoCollide.Detect(ms, b2.Shape, ref b1.orientation,
                                           ref b2.orientation, ref b1.position, ref b2.position,
                                           out point, out normal, out penetration))
                    {
                        JVector point1, point2;
                        FindSupportPoints(b1, b2, ms, b2.Shape, ref point, ref normal, out point1, out point2);

                        if (useTerrainNormal && ms is TerrainShape)
                        {
                            (ms as TerrainShape).CollisionNormal(out normal);
                            JVector.Transform(ref normal, ref b1.orientation, out normal);
                        }
                        else if (useTriangleMeshNormal && ms is TriangleMeshShape)
                        {
                            (ms as TriangleMeshShape).CollisionNormal(out normal);
                            JVector.Transform(ref normal, ref b1.orientation, out normal);
                        }

                        RaiseCollisionDetected(b1, b2, ref point1, ref point2, ref normal, penetration);
                    }
                    else if (speculative)
                    {
                        JVector hit1, hit2;

                        if (GJKCollide.ClosestPoints(ms, b2.Shape, ref b1.orientation, ref b2.orientation,
                                                     ref b1.position, ref b2.position, out hit1, out hit2, out normal))
                        {
                            JVector delta = hit2 - hit1;

                            if (delta.LengthSquared() < (body1.sweptDirection - body2.sweptDirection).LengthSquared())
                            {
                                penetration = delta * normal;

                                if (penetration < 0.0f)
                                {
                                    RaiseCollisionDetected(b1, b2, ref hit1, ref hit2, ref normal, penetration);
                                }
                            }
                        }
                    }
                }

                ms.ReturnWorkingClone();
            }
        }
Ejemplo n.º 6
0
        //int sampleCount = 50;
        //float distance = 4.0f;
        public void UpdateAmbientOcclusion(Model model, Group occluders, string mapName)
        {
#if false // \todo fix
            GeometryMesh mesh = model.Batch.MeshSource as GeometryMesh;
            if (mesh == null)
            {
                return;
            }
            Geometry geometry = mesh.Geometry;

            model.UpdateOctree();
            var shape = new TriangleMeshShape(model.Octree);
            shape.SphericalExpansion = 0.0f;

#if VISUALIZE_RAYS
            sceneManager.DebugLineRenderer.Begin();
#endif

            var pointLocations   = geometry.PointAttributes.Find <Vector3>("point_locations");
            var polygonCentroids = geometry.PolygonAttributes.Find <Vector3>("polygon_centroids");
            var pointNormals     = geometry.PointAttributes.Find <Vector3>(mapName);
            var cornerNormals    = geometry.CornerAttributes.Contains <Vector3>("corner_normals") ? geometry.CornerAttributes.Find <Vector3>("corner_normals") : null;
            var cornerColors     = geometry.CornerAttributes.FindOrCreate <Vector4>("corner_colors");
            cornerColors.Clear();
            int badCount = 0;
            foreach (Polygon polygon in geometry.Polygons)
            {
                foreach (Corner corner in polygon.Corners)
                {
                    Point point = corner.Point;

                    //  Start from corner position slightly moved towards polygon center
                    Vector3 initialEye = Vector3.Mix(pointLocations[point], polygonCentroids[polygon], 0.001f);
                    Vector3 normal;

                    if (
                        (cornerNormals != null) &&
                        (cornerNormals.ContainsKey(corner) == true)
                        )
                    {
                        normal = cornerNormals[corner];
                    }
                    else
                    {
                        normal = pointNormals[point];
                    }

                    JMatrix orientation    = JMatrix.Identity;
                    JMatrix invOrientation = JMatrix.Identity;
                    Vector3 modelPosition  = Vector3.Zero;

                    //  Slightly up from surface
                    initialEye += normal * 0.01f;

                    int visibility = sampleCount;

                    JVector position = Conversions.JVector(modelPosition);
                    JVector direction;
                    JVector jnormal;

                    Vector3 eye    = initialEye;
                    JVector origin = Conversions.JVector(eye);

                    //  Sample a few directions
                    for (int c = 0; c < sampleCount; ++c)
                    {
                        //  Get a random direction until it points up from the surface
                        Vector3 sampleDirection;
                        float   dot;
                        do
                        {
                            sampleDirection = Random();
                            dot             = Vector3.Dot(normal, sampleDirection);
                        }while(dot < 0.25f);

                        //  Scale
                        direction = Conversions.JVector(sampleDirection * distance);

                        bool hit = false;
                        {
                            float tempFraction;

                            if (shape is Multishape)
                            {
                                Multishape ms = (shape as Multishape).RequestWorkingClone();
                                JVector    tempNormal;
                                int        msLength = ms.Prepare(ref origin, ref direction);

                                for (int i = 0; i < msLength; i++)
                                {
                                    ms.SetCurrentShape(i);

                                    if (
                                        GJKCollide.Raycast(
                                            ms, ref orientation, ref invOrientation, ref position,
                                            ref origin, ref direction, out tempFraction, out tempNormal
                                            )
                                        )
                                    {
                                        hit = true;
                                        break;
                                    }
                                }

                                ms.ReturnWorkingClone();
                            }
                            else
                            {
                                hit = GJKCollide.Raycast(
                                    shape, ref orientation, ref invOrientation, ref position,
                                    ref origin, ref direction, out tempFraction, out jnormal
                                    );
                            }
                        }


                        if (hit)
                        {
                            visibility -= 1;
                        }

                        Vector3 root = new Vector3(origin.X, origin.Y, origin.Z);
                        Vector3 tip  = root + 0.02f * new Vector3(direction.X, direction.Y, direction.Z);
#if VISUALIZE_RAYS
                        sceneManager.DebugLineRenderer.Line(
                            root,
                            Vector4.Zero,
                            tip,
                            (hit ? Vector4.UnitX : Vector4.UnitY)
                            );
#endif
                    }
                    float visibilityFloat = (float)(visibility) / (float)(sampleCount);
                    cornerColors[corner] = new Vector4(visibilityFloat, visibilityFloat, visibilityFloat, 1.0f);
                }
            }
            mesh.Geometry.ComputePolygonCentroids();
            mesh.Geometry.ComputePolygonNormals();
            mesh.Geometry.SmoothNormalize("corner_normals", "polygon_normals", (2.0f * (float)Math.PI));
            mesh.Geometry.SmoothAverage("corner_colors", mapName);
            mesh.BuildMeshFromGeometry(BufferUsageHint.StaticDraw, NormalStyle.CornerNormals);
            //UpdateMeshCornerColors(mesh);
            Debug.WriteLine("bad count = " + badCount.ToString());

#if VISUALIZE_RAYS
            sceneManager.DebugLineRenderer.End();
            sceneManager.DebugFrame = model.Frame;
#endif

            //model.Name = "AmbientOcclusion(" + model.Name + ")";
#endif
        }
Ejemplo n.º 7
0
        private void DetectRigidRigid(RigidBody body1, RigidBody body2)
        {
            // CUSTOM: Added custom detection callbacks (primarily to accommodate actor movement on surfaces).
            var callback1 = body1.ShouldGenerateContact;
            var callback2 = body2.ShouldGenerateContact;

            bool b1IsMulti = (body1.Shape is Multishape);
            bool b2IsMulti = (body2.Shape is Multishape);

            bool speculative = speculativeContacts ||
                               (body1.AreSpeculativeContactsEnabled || body2.AreSpeculativeContactsEnabled);

            JVector point, normal;
            float   penetration;

            if (!b1IsMulti && !b2IsMulti)
            {
                // CUSTOM: Added these callbacks (two rigid bodies).
                if ((callback1 != null && !callback1(body2, null)) || (callback2 != null && !callback2(body1, null)))
                {
                    return;
                }

                if (XenoCollide.Detect(body1.Shape, body2.Shape, ref body1.orientation,
                                       ref body2.orientation, ref body1.position, ref body2.position,
                                       out point, out normal, out penetration))
                {
                    JVector point1, point2;
                    FindSupportPoints(body1, body2, body1.Shape, body2.Shape, ref point, ref normal, out point1, out point2);
                    RaiseCollisionDetected(body1, body2, ref point1, ref point2, ref normal, penetration);
                }
                else if (speculative)
                {
                    JVector hit1, hit2;

                    if (GJKCollide.ClosestPoints(body1.Shape, body2.Shape, ref body1.orientation, ref body2.orientation,
                                                 ref body1.position, ref body2.position, out hit1, out hit2, out normal))
                    {
                        JVector delta = hit2 - hit1;

                        if (delta.LengthSquared() < (body1.sweptDirection - body2.sweptDirection).LengthSquared())
                        {
                            penetration = delta * normal;

                            if (penetration < 0.0f)
                            {
                                RaiseCollisionDetected(body1, body2, ref hit1, ref hit2, ref normal, penetration);
                            }
                        }
                    }
                }
            }
            else if (b1IsMulti && b2IsMulti)
            {
                Multishape ms1 = (body1.Shape as Multishape);
                Multishape ms2 = (body2.Shape as Multishape);

                ms1 = ms1.RequestWorkingClone();
                ms2 = ms2.RequestWorkingClone();

                JBBox transformedBoundingBox = body2.boundingBox;
                transformedBoundingBox.InverseTransform(ref body1.position, ref body1.orientation);

                int ms1Length = ms1.Prepare(ref transformedBoundingBox);

                transformedBoundingBox = body1.boundingBox;
                transformedBoundingBox.InverseTransform(ref body2.position, ref body2.orientation);

                int ms2Length = ms2.Prepare(ref transformedBoundingBox);

                if (ms1Length == 0 || ms2Length == 0)
                {
                    ms1.ReturnWorkingClone();
                    ms2.ReturnWorkingClone();
                    return;
                }

                for (int i = 0; i < ms1Length; i++)
                {
                    ms1.SetCurrentShape(i);

                    for (int e = 0; e < ms2Length; e++)
                    {
                        ms2.SetCurrentShape(e);

                        if (XenoCollide.Detect(ms1, ms2, ref body1.orientation,
                                               ref body2.orientation, ref body1.position, ref body2.position,
                                               out point, out normal, out penetration))
                        {
                            JVector point1, point2;
                            FindSupportPoints(body1, body2, ms1, ms2, ref point, ref normal, out point1, out point2);
                            RaiseCollisionDetected(body1, body2, ref point1, ref point2, ref normal, penetration);
                        }
                        else if (speculative)
                        {
                            JVector hit1, hit2;

                            if (GJKCollide.ClosestPoints(ms1, ms2, ref body1.orientation, ref body2.orientation,
                                                         ref body1.position, ref body2.position, out hit1, out hit2, out normal))
                            {
                                JVector delta = hit2 - hit1;

                                if (delta.LengthSquared() < (body1.sweptDirection - body2.sweptDirection).LengthSquared())
                                {
                                    penetration = delta * normal;

                                    if (penetration < 0.0f)
                                    {
                                        RaiseCollisionDetected(body1, body2, ref hit1, ref hit2, ref normal, penetration);
                                    }
                                }
                            }
                        }
                    }
                }

                ms1.ReturnWorkingClone();
                ms2.ReturnWorkingClone();
            }
            else
            {
                RigidBody b1, b2;

                if (body2.Shape is Multishape)
                {
                    // CUSTOM: Swapped callbacks here as well.
                    b1 = body2;
                    b2 = body1;

                    // A proper swap here (using a temporary variable) isn't necessary since, by this point, the other
                    // callback (attached to the static body) is intentionally not used.
                    callback2 = callback1;
                }
                else
                {
                    b2 = body2;
                    b1 = body1;
                }

                Multishape ms = (b1.Shape as Multishape);

                ms = ms.RequestWorkingClone();

                JBBox transformedBoundingBox = b2.boundingBox;
                transformedBoundingBox.InverseTransform(ref b1.position, ref b1.orientation);

                int msLength = ms.Prepare(ref transformedBoundingBox);

                if (msLength == 0)
                {
                    ms.ReturnWorkingClone();
                    return;
                }

                for (int i = 0; i < msLength; i++)
                {
                    ms.SetCurrentShape(i);

                    // CUSTOM: Added this callback (to allow specific triangle collisions to be ignored).
                    bool shouldCollideWith = ms is TriangleMeshShape tMesh && (callback2 == null ||
                                                                               callback2(b1, tMesh.CurrentTriangle));

                    if (shouldCollideWith && XenoCollide.Detect(ms, b2.Shape, ref b1.orientation,
                                                                ref b2.orientation, ref b1.position, ref b2.position,
                                                                out point, out normal, out penetration))
                    {
                        JVector[] triangle = null;
                        FindSupportPoints(b1, b2, ms, b2.Shape, ref point, ref normal, out var point1, out var point2);

                        if (useTerrainNormal && ms is TerrainShape)
                        {
                            (ms as TerrainShape).CollisionNormal(out normal);
                            JVector.Transform(ref normal, ref b1.orientation, out normal);
                        }
                        else if (useTriangleMeshNormal)
                        {
                            tMesh    = ms as TriangleMeshShape;
                            triangle = tMesh.CurrentTriangle;
                            tMesh.CollisionNormal(out normal);
                            JVector.Transform(ref normal, ref b1.orientation, out normal);
                        }

                        RaiseCollisionDetected(b1, b2, ref point1, ref point2, ref normal, triangle, penetration);
                    }
                    else if (speculative)
                    {
                        JVector hit1, hit2;

                        if (GJKCollide.ClosestPoints(ms, b2.Shape, ref b1.orientation, ref b2.orientation,
                                                     ref b1.position, ref b2.position, out hit1, out hit2, out normal))
                        {
                            JVector delta = hit2 - hit1;

                            if (delta.LengthSquared() < (body1.sweptDirection - body2.sweptDirection).LengthSquared())
                            {
                                penetration = delta * normal;

                                if (penetration < 0.0f)
                                {
                                    RaiseCollisionDetected(b1, b2, ref hit1, ref hit2, ref normal, penetration);
                                }
                            }
                        }
                    }
                }

                ms.ReturnWorkingClone();
            }
        }