예제 #1
0
        public void MultiplyTransposed()
        {
            var m = RandomHelper.Random.NextMatrix33F(1, 10);
            var v = RandomHelper.Random.NextVector3F(1, 10);

            Assert.AreEqual(m.Transposed * v, Matrix33F.MultiplyTransposed(m, v));
        }
예제 #2
0
        public override void ComputeCollision(ContactSet contactSet, CollisionQueryType type)
        {
            // Invoke GJK for closest points.
            if (type == CollisionQueryType.ClosestPoints)
            {
                throw new GeometryException("This collision algorithm cannot handle closest-point queries. Use GJK instead.");
            }

            CollisionObject  collisionObjectA = contactSet.ObjectA;
            CollisionObject  collisionObjectB = contactSet.ObjectB;
            IGeometricObject geometricObjectA = collisionObjectA.GeometricObject;
            IGeometricObject geometricObjectB = collisionObjectB.GeometricObject;
            BoxShape         boxA             = geometricObjectA.Shape as BoxShape;
            BoxShape         boxB             = geometricObjectB.Shape as BoxShape;

            // Check if collision objects shapes are correct.
            if (boxA == null || boxB == null)
            {
                throw new ArgumentException("The contact set must contain box shapes.", "contactSet");
            }

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

            // We perform the separating axis test in the local space of A.
            // The following variables are in local space of A.

            // Center of box B.
            Vector3F cB = poseA.ToLocalPosition(poseB.Position);
            // Orientation matrix of box B
            Matrix33F mB = poseA.Orientation.Transposed * poseB.Orientation;
            // Absolute of mB.
            Matrix33F aMB = Matrix33F.Absolute(mB);

            // Half extent vectors of the boxes.
            Vector3F eA = 0.5f * boxA.Extent * scaleA;
            Vector3F eB = 0.5f * boxB.Extent * scaleB;

            // ----- Separating Axis tests
            // If the boxes are separated, we immediately return.
            // For the case of interpenetration, we store the smallest penetration depth.
            float    smallestPenetrationDepth = float.PositiveInfinity;
            int      separatingAxisNumber     = 0;
            Vector3F normal           = Vector3F.UnitX;
            bool     isNormalInverted = false;

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

            #region ----- Case 1: Separating Axis: (1, 0, 0) -----
            float separation = Math.Abs(cB.X) - (eA.X + eB.X * aMB.M00 + eB.Y * aMB.M01 + eB.Z * aMB.M02);
            if (separation > 0)
            {
                return;
            }

            if (type != CollisionQueryType.Boolean && -separation < smallestPenetrationDepth)
            {
                normal = Vector3F.UnitX;
                smallestPenetrationDepth = -separation;
                isNormalInverted         = cB.X < 0;
                separatingAxisNumber     = 1;
            }
            #endregion

            #region ----- Case 2: Separating Axis: (0, 1, 0) -----
            separation = Math.Abs(cB.Y) - (eA.Y + eB.X * aMB.M10 + eB.Y * aMB.M11 + eB.Z * aMB.M12);
            if (separation > 0)
            {
                return;
            }

            if (type != CollisionQueryType.Boolean && -separation < smallestPenetrationDepth)
            {
                normal = Vector3F.UnitY;
                smallestPenetrationDepth = -separation;
                isNormalInverted         = cB.Y < 0;
                separatingAxisNumber     = 2;
            }
            #endregion

            #region ----- Case 3: Separating Axis: (0, 0, 1) -----
            separation = Math.Abs(cB.Z) - (eA.Z + eB.X * aMB.M20 + eB.Y * aMB.M21 + eB.Z * aMB.M22);
            if (separation > 0)
            {
                return;
            }

            if (type != CollisionQueryType.Boolean && -separation < smallestPenetrationDepth)
            {
                normal = Vector3F.UnitZ;
                smallestPenetrationDepth = -separation;
                isNormalInverted         = cB.Z < 0;
                separatingAxisNumber     = 3;
            }
            #endregion

            #region ----- Case 4: Separating Axis: OrientationB * (1, 0, 0) -----
            float expression = cB.X * mB.M00 + cB.Y * mB.M10 + cB.Z * mB.M20;
            separation = Math.Abs(expression) - (eB.X + eA.X * aMB.M00 + eA.Y * aMB.M10 + eA.Z * aMB.M20);
            if (separation > 0)
            {
                return;
            }

            if (type != CollisionQueryType.Boolean && -separation < smallestPenetrationDepth)
            {
                normal = mB.GetColumn(0);
                smallestPenetrationDepth = -separation;
                isNormalInverted         = expression < 0;
                separatingAxisNumber     = 4;
            }
            #endregion

            #region ----- Case 5: Separating Axis: OrientationB * (0, 1, 0) -----
            expression = cB.X * mB.M01 + cB.Y * mB.M11 + cB.Z * mB.M21;
            separation = Math.Abs(expression) - (eB.Y + eA.X * aMB.M01 + eA.Y * aMB.M11 + eA.Z * aMB.M21);
            if (separation > 0)
            {
                return;
            }

            if (type != CollisionQueryType.Boolean && -separation < smallestPenetrationDepth)
            {
                normal = mB.GetColumn(1);
                smallestPenetrationDepth = -separation;
                isNormalInverted         = expression < 0;
                separatingAxisNumber     = 5;
            }
            #endregion

            #region ----- Case 6: Separating Axis: OrientationB * (0, 0, 1) -----
            expression = cB.X * mB.M02 + cB.Y * mB.M12 + cB.Z * mB.M22;
            separation = Math.Abs(expression) - (eB.Z + eA.X * aMB.M02 + eA.Y * aMB.M12 + eA.Z * aMB.M22);
            if (separation > 0)
            {
                return;
            }

            if (type != CollisionQueryType.Boolean && -separation < smallestPenetrationDepth)
            {
                normal = mB.GetColumn(2);
                smallestPenetrationDepth = -separation;
                isNormalInverted         = expression < 0;
                separatingAxisNumber     = 6;
            }
            #endregion

            // The next 9 tests are edge-edge cases. The normal vector has to be normalized
            // to get the right penetration depth.
            // normal = Normalize(edgeA x edgeB)
            Vector3F separatingAxis;
            float    length;

            #region ----- Case 7: Separating Axis: (1, 0, 0) x (OrientationB * (1, 0, 0)) -----
            expression = cB.Z * mB.M10 - cB.Y * mB.M20;
            separation = Math.Abs(expression) - (eA.Y * aMB.M20 + eA.Z * aMB.M10 + eB.Y * aMB.M02 + eB.Z * aMB.M01);
            if (separation > 0)
            {
                return;
            }

            if (type != CollisionQueryType.Boolean)
            {
                separatingAxis = new Vector3F(0, -mB.M20, mB.M10);
                length         = separatingAxis.Length;
                separation    /= length;
                if (-separation < smallestPenetrationDepth)
                {
                    normal = separatingAxis / length;
                    smallestPenetrationDepth = -separation;
                    isNormalInverted         = expression < 0;
                    separatingAxisNumber     = 7;
                }
            }
            #endregion

            #region ----- Case 8: Separating Axis: (1, 0, 0) x (OrientationB * (0, 1, 0)) -----
            expression = cB.Z * mB.M11 - cB.Y * mB.M21;
            separation = Math.Abs(expression) - (eA.Y * aMB.M21 + eA.Z * aMB.M11 + eB.X * aMB.M02 + eB.Z * aMB.M00);
            if (separation > 0)
            {
                return;
            }

            if (type != CollisionQueryType.Boolean)
            {
                separatingAxis = new Vector3F(0, -mB.M21, mB.M11);
                length         = separatingAxis.Length;
                separation    /= length;
                if (-separation < smallestPenetrationDepth)
                {
                    normal = separatingAxis / length;
                    smallestPenetrationDepth = -separation;
                    isNormalInverted         = expression < 0;
                    separatingAxisNumber     = 8;
                }
            }
            #endregion

            #region ----- Case 9: Separating Axis: (1, 0, 0) x (OrientationB * (0, 0, 1)) -----
            expression = cB.Z * mB.M12 - cB.Y * mB.M22;
            separation = Math.Abs(expression) - (eA.Y * aMB.M22 + eA.Z * aMB.M12 + eB.X * aMB.M01 + eB.Y * aMB.M00);
            if (separation > 0)
            {
                return;
            }

            if (type != CollisionQueryType.Boolean)
            {
                separatingAxis = new Vector3F(0, -mB.M22, mB.M12);
                length         = separatingAxis.Length;
                separation    /= length;
                if (-separation < smallestPenetrationDepth)
                {
                    normal = separatingAxis / length;
                    smallestPenetrationDepth = -separation;
                    isNormalInverted         = expression < 0;
                    separatingAxisNumber     = 9;
                }
            }
            #endregion

            #region ----- Case 10: Separating Axis: (0, 1, 0) x (OrientationB * (1, 0, 0)) -----
            expression = cB.X * mB.M20 - cB.Z * mB.M00;
            separation = Math.Abs(expression) - (eA.X * aMB.M20 + eA.Z * aMB.M00 + eB.Y * aMB.M12 + eB.Z * aMB.M11);
            if (separation > 0)
            {
                return;
            }

            if (type != CollisionQueryType.Boolean)
            {
                separatingAxis = new Vector3F(mB.M20, 0, -mB.M00);
                length         = separatingAxis.Length;
                separation    /= length;
                if (-separation < smallestPenetrationDepth)
                {
                    normal = separatingAxis / length;
                    smallestPenetrationDepth = -separation;
                    isNormalInverted         = expression < 0;
                    separatingAxisNumber     = 10;
                }
            }
            #endregion

            #region ----- Case 11: Separating Axis: (0, 1, 0) x (OrientationB * (0, 1, 0)) -----
            expression = cB.X * mB.M21 - cB.Z * mB.M01;
            separation = Math.Abs(expression) - (eA.X * aMB.M21 + eA.Z * aMB.M01 + eB.X * aMB.M12 + eB.Z * aMB.M10);
            if (separation > 0)
            {
                return;
            }

            if (type != CollisionQueryType.Boolean)
            {
                separatingAxis = new Vector3F(mB.M21, 0, -mB.M01);
                length         = separatingAxis.Length;
                separation    /= length;
                if (-separation < smallestPenetrationDepth)
                {
                    normal = separatingAxis / length;
                    smallestPenetrationDepth = -separation;
                    isNormalInverted         = expression < 0;
                    separatingAxisNumber     = 11;
                }
            }
            #endregion

            #region ----- Case 12: Separating Axis: (0, 1, 0) x (OrientationB * (0, 0, 1)) -----
            expression = cB.X * mB.M22 - cB.Z * mB.M02;
            separation = Math.Abs(expression) - (eA.X * aMB.M22 + eA.Z * aMB.M02 + eB.X * aMB.M11 + eB.Y * aMB.M10);
            if (separation > 0)
            {
                return;
            }

            if (type != CollisionQueryType.Boolean)
            {
                separatingAxis = new Vector3F(mB.M22, 0, -mB.M02);
                length         = separatingAxis.Length;
                separation    /= length;
                if (-separation < smallestPenetrationDepth)
                {
                    normal = separatingAxis / length;
                    smallestPenetrationDepth = -separation;
                    isNormalInverted         = expression < 0;
                    separatingAxisNumber     = 12;
                }
            }
            #endregion

            #region ----- Case 13: Separating Axis: (0, 0, 1) x (OrientationB * (1, 0, 0)) -----
            expression = cB.Y * mB.M00 - cB.X * mB.M10;
            separation = Math.Abs(expression) - (eA.X * aMB.M10 + eA.Y * aMB.M00 + eB.Y * aMB.M22 + eB.Z * aMB.M21);
            if (separation > 0)
            {
                return;
            }

            if (type != CollisionQueryType.Boolean)
            {
                separatingAxis = new Vector3F(-mB.M10, mB.M00, 0);
                length         = separatingAxis.Length;
                separation    /= length;
                if (-separation < smallestPenetrationDepth)
                {
                    normal = separatingAxis / length;
                    smallestPenetrationDepth = -separation;
                    isNormalInverted         = expression < 0;
                    separatingAxisNumber     = 13;
                }
            }
            #endregion

            #region ----- Case 14: Separating Axis: (0, 0, 1) x (OrientationB * (0, 1, 0)) -----
            expression = cB.Y * mB.M01 - cB.X * mB.M11;
            separation = Math.Abs(expression) - (eA.X * aMB.M11 + eA.Y * aMB.M01 + eB.X * aMB.M22 + eB.Z * aMB.M20);
            if (separation > 0)
            {
                return;
            }

            if (type != CollisionQueryType.Boolean)
            {
                separatingAxis = new Vector3F(-mB.M11, mB.M01, 0);
                length         = separatingAxis.Length;
                separation    /= length;
                if (-separation < smallestPenetrationDepth)
                {
                    normal = separatingAxis / length;
                    smallestPenetrationDepth = -separation;
                    isNormalInverted         = expression < 0;
                    separatingAxisNumber     = 14;
                }
            }
            #endregion

            #region ----- Case 15: Separating Axis: (0, 0, 1) x (OrientationB * (0, 0, 1)) -----
            expression = cB.Y * mB.M02 - cB.X * mB.M12;
            separation = Math.Abs(expression) - (eA.X * aMB.M12 + eA.Y * aMB.M02 + eB.X * aMB.M21 + eB.Y * aMB.M20);
            if (separation > 0)
            {
                return;
            }

            if (type != CollisionQueryType.Boolean)
            {
                separatingAxis = new Vector3F(-mB.M12, mB.M02, 0);
                length         = separatingAxis.Length;
                separation    /= length;
                if (-separation < smallestPenetrationDepth)
                {
                    normal = separatingAxis / length;
                    smallestPenetrationDepth = -separation;
                    isNormalInverted         = expression < 0;
                    separatingAxisNumber     = 15;
                }
            }
            #endregion

            // We have a contact.
            contactSet.HaveContact = true;

            // HaveContact queries can exit here.
            if (type == CollisionQueryType.Boolean)
            {
                return;
            }

            // Lets find the contact info.
            Debug.Assert(smallestPenetrationDepth >= 0, "The smallest penetration depth should be greater than or equal to 0.");

            if (isNormalInverted)
            {
                normal = -normal;
            }

            // Transform normal from local space of A to world space.
            Vector3F normalWorld = poseA.ToWorldDirection(normal);

            if (separatingAxisNumber > 6)
            {
                // The intersection was detected by an edge-edge test.
                // Get the intersecting edges.
                // Separating axes:
                //  7 = x edge on A, x edge on B
                //  8 = x edge on A, y edge on B
                //  9 = x edge on A, Z edge on B
                // 10 = y edge on A, x edge on B
                // ...
                // 15 = z edge on A, z edge on B
                var edgeA = boxA.GetEdge((separatingAxisNumber - 7) / 3, normal, scaleA);
                var edgeB = boxB.GetEdge((separatingAxisNumber - 7) % 3, Matrix33F.MultiplyTransposed(mB, -normal), scaleB);
                edgeB.Start = mB * edgeB.Start + cB;
                edgeB.End   = mB * edgeB.End + cB;

                Vector3F position;
                Vector3F dummy;
                GeometryHelper.GetClosestPoints(edgeA, edgeB, out position, out dummy);
                position = position - normal * (smallestPenetrationDepth / 2); // Position is between the positions of the box surfaces.

                // Convert back position from local space of A to world space;
                position = poseA.ToWorldPosition(position);

                Contact contact = ContactHelper.CreateContact(contactSet, position, normalWorld, smallestPenetrationDepth, false);
                ContactHelper.Merge(contactSet, contact, type, CollisionDetection.ContactPositionTolerance);
            }
            else if (1 <= separatingAxisNumber && separatingAxisNumber <= 6)
            {
                // The intersection was detected by a face vs. * test.
                // The separating axis is perpendicular to a face.

                #region ----- Credits -----
                // The face vs. * test is based on the algorithm of the Bullet Continuous Collision
                // Detection and Physics Library. DigitalRune Geometry contains a new and improved
                // implementation of the original algorithm.
                //
                // The box-box detector in Bullet contains the following remarks:
                //
                //    Box-Box collision detection re-distributed under the ZLib license with permission from Russell L. Smith
                //    Original version is from Open Dynamics Engine, Copyright (C) 2001,2002 Russell L. Smith.
                //    All rights reserved.  Email: [email protected]   Web: www.q12.org
                //
                //    Bullet Continuous Collision Detection and Physics Library
                //    Copyright (c) 2003-2006 Erwin Coumans  http://continuousphysics.com/Bullet/
                //
                //    This software is provided 'as-is', without any express or implied warranty.
                //    In no event will the authors be held liable for any damages arising from the use of this software.
                //    Permission is granted to anyone to use this software for any purpose,
                //    including commercial applications, and to alter it and redistribute it freely,
                //    subject to the following restrictions:
                //
                //    1. The origin of this software must not be misrepresented; you must not claim that you wrote the
                //       original software. If you use this software in a product, an acknowledgment in the product
                //       documentation would be appreciated but is not required.
                //    2. Altered source versions must be plainly marked as such, and must not be misrepresented as being
                //       the original software.
                //    3. This notice may not be removed or altered from any source distribution.
                #endregion

                // We define the face perpendicular to the separating axis to be the "reference face".
                // The face of the other box closest to the reference face is called the "incident face".
                // Accordingly, we will call the box containing the reference face the "reference box" and
                // the box containing the incident face the "incident box".
                //
                // We will transform the incident face into the 2D space of reference face. Then we will
                // clip the incident face against the reference face. The polygon resulting from the
                // intersection will be transformed back into world space and the points of the polygon will
                // be the candidates for the contact points.

                Pose     poseR;      // Pose of reference box.
                Pose     poseI;      // Pose of incident box.
                Vector3F boxExtentR; // Half extent of reference box.
                Vector3F boxExtentI; // Half extent of incident box.

                // Contact normal (= normal of reference face) in world space.
                if (separatingAxisNumber <= 3)
                {
                    poseR            = poseA;
                    poseI            = poseB;
                    boxExtentR       = eA;
                    boxExtentI       = eB;
                    isNormalInverted = false;
                }
                else
                {
                    poseR            = poseB;
                    poseI            = poseA;
                    boxExtentR       = eB;
                    boxExtentI       = eA;
                    normalWorld      = -normalWorld;
                    isNormalInverted = true;
                }

                // Contact normal in local space of incident box.
                Vector3F normalI = poseI.ToLocalDirection(normalWorld);

                Vector3F absNormal = normalI;
                absNormal.Absolute();

                Vector3F xAxisInc, yAxisInc; // The basis of the incident-face space.
                float    absFaceOffsetI;     // The offset of the incident face to the center of the box.
                Vector2F faceExtentI;        // The half extent of the incident face.
                Vector3F faceNormal;         // The normal of the incident face in world space.
                float    faceDirection;      // A value indicating the direction of the incident face.

                // Find the largest component of the normal. The largest component indicates which face is
                // the incident face.
                switch (Vector3F.Absolute(normalI).IndexOfLargestComponent)
                {
                case 0:
                    faceExtentI.X  = boxExtentI.Y;
                    faceExtentI.Y  = boxExtentI.Z;
                    absFaceOffsetI = boxExtentI.X;
                    faceNormal     = poseI.Orientation.GetColumn(0);
                    xAxisInc       = poseI.Orientation.GetColumn(1);
                    yAxisInc       = poseI.Orientation.GetColumn(2);
                    faceDirection  = normalI.X;
                    break;

                case 1:
                    faceExtentI.X  = boxExtentI.X;
                    faceExtentI.Y  = boxExtentI.Z;
                    absFaceOffsetI = boxExtentI.Y;
                    faceNormal     = poseI.Orientation.GetColumn(1);
                    xAxisInc       = poseI.Orientation.GetColumn(0);
                    yAxisInc       = poseI.Orientation.GetColumn(2);
                    faceDirection  = normalI.Y;
                    break;

                // case 2:
                default:
                    faceExtentI.X  = boxExtentI.X;
                    faceExtentI.Y  = boxExtentI.Y;
                    absFaceOffsetI = boxExtentI.Z;
                    faceNormal     = poseI.Orientation.GetColumn(2);
                    xAxisInc       = poseI.Orientation.GetColumn(0);
                    yAxisInc       = poseI.Orientation.GetColumn(1);
                    faceDirection  = normalI.Z;
                    break;
                }

                // Compute center of incident face relative to the center of the reference box in world space.
                float    faceOffset    = (faceDirection < 0) ? absFaceOffsetI : -absFaceOffsetI;
                Vector3F centerOfFaceI = faceNormal * faceOffset + poseI.Position - poseR.Position;

                // (Note: We will use the center of the incident face to compute the points of the incident
                // face and transform the points into the reference-face frame. The center of the incident
                // face is relative to the center of the reference box. We could also get center of the
                // incident face relative to the center of the reference face. But since we are projecting
                // the points from 3D to 2D this does not matter.)

                Vector3F xAxisR, yAxisR; // The basis of the reference-face space.
                float    faceOffsetR;    // The offset of the reference face to the center of the box.
                Vector2F faceExtentR;    // The half extent of the reference face.
                switch (separatingAxisNumber)
                {
                case 1:
                case 4:
                    faceExtentR.X = boxExtentR.Y;
                    faceExtentR.Y = boxExtentR.Z;
                    faceOffsetR   = boxExtentR.X;
                    xAxisR        = poseR.Orientation.GetColumn(1);
                    yAxisR        = poseR.Orientation.GetColumn(2);
                    break;

                case 2:
                case 5:
                    faceExtentR.X = boxExtentR.X;
                    faceExtentR.Y = boxExtentR.Z;
                    faceOffsetR   = boxExtentR.Y;
                    xAxisR        = poseR.Orientation.GetColumn(0);
                    yAxisR        = poseR.Orientation.GetColumn(2);
                    break;

                // case 3:
                // case 6:
                default:
                    faceExtentR.X = boxExtentR.X;
                    faceExtentR.Y = boxExtentR.Y;
                    faceOffsetR   = boxExtentR.Z;
                    xAxisR        = poseR.Orientation.GetColumn(0);
                    yAxisR        = poseR.Orientation.GetColumn(1);
                    break;
                }

                // Compute the center of the incident face in the reference-face frame.
                // We can simply project centerOfFaceI onto the x- and y-axis of the reference
                // face.
                Vector2F centerOfFaceIInR;

                //centerOfFaceIInR.X = Vector3F.Dot(centerOfFaceI, xAxisR);
                // ----- Optimized version:
                centerOfFaceIInR.X = centerOfFaceI.X * xAxisR.X + centerOfFaceI.Y * xAxisR.Y + centerOfFaceI.Z * xAxisR.Z;

                //centerOfFaceIInR.Y = Vector3F.Dot(centerOfFaceI, yAxisR);
                // ----- Optimized version:
                centerOfFaceIInR.Y = centerOfFaceI.X * yAxisR.X + centerOfFaceI.Y * yAxisR.Y + centerOfFaceI.Z * yAxisR.Z;

                // Now, we have the center of the incident face in reference-face coordinates.
                // To compute the corners of the incident face in reference-face coordinates, we need
                // transform faceExtentI (the half extent vector of the incident face) from the incident-
                // face frame to the reference-face frame to compute the corners.
                //
                // The reference-face frame has the basis
                //   mR = (xAxisR, yAxisR, ?)
                //
                // The incident-face frame has the basis
                //   mI = (xAxisI, yAxisI, ?)
                //
                // Rotation from incident-face frame to reference-face frame is
                //   mIToR = mR^-1 * mI
                //
                // The corner offsets in incident-face space is are vectors (x, y, 0). To transform these
                // vectors from incident-face space to reference-face space we need to calculate:
                //   mIToR * v
                //
                // Since the z-components are 0 and we are only interested in the resulting x, y coordinates
                // in reference-space when can reduce the rotation to a 2 x 2 matrix. (The other components
                // are not needed.)

                // ----- Optimized version: (Original on the right)
                Matrix22F mIToR;
                mIToR.M00 = xAxisR.X * xAxisInc.X + xAxisR.Y * xAxisInc.Y + xAxisR.Z * xAxisInc.Z; // mIToR.M00 = Vector3F.Dot(xAxisR, xAxisInc);
                mIToR.M01 = xAxisR.X * yAxisInc.X + xAxisR.Y * yAxisInc.Y + xAxisR.Z * yAxisInc.Z; // mIToR.M01 = Vector3F.Dot(xAxisR, yAxisInc);
                mIToR.M10 = yAxisR.X * xAxisInc.X + yAxisR.Y * xAxisInc.Y + yAxisR.Z * xAxisInc.Z; // mIToR.M10 = Vector3F.Dot(yAxisR, xAxisInc);
                mIToR.M11 = yAxisR.X * yAxisInc.X + yAxisR.Y * yAxisInc.Y + yAxisR.Z * yAxisInc.Z; // mIToR.M11 = Vector3F.Dot(yAxisR, yAxisInc);

                // The corner offsets in incident-face space are:
                //  (-faceExtentI.X, -faceExtentI.Y) ... left, bottom corner
                //  ( faceExtentI.X, -faceExtentI.Y) ... right, bottom corner
                //  ( faceExtentI.X,  faceExtentI.Y) ... right, top corner
                //  (-faceExtentI.X,  faceExtentI.Y) ... left, top corner
                //
                // Instead of transforming each corner offset, we can optimize the computation: Do the
                // matrix-vector multiplication once, keep the intermediate products, apply the sign
                // of the components when adding the intermediate results.

                float           k1   = mIToR.M00 * faceExtentI.X; // Products of matrix-vector multiplication.
                float           k2   = mIToR.M01 * faceExtentI.Y;
                float           k3   = mIToR.M10 * faceExtentI.X;
                float           k4   = mIToR.M11 * faceExtentI.Y;
                List <Vector2F> quad = DigitalRune.ResourcePools <Vector2F> .Lists.Obtain();

                quad.Add(new Vector2F(centerOfFaceIInR.X - k1 - k2, centerOfFaceIInR.Y - k3 - k4));
                quad.Add(new Vector2F(centerOfFaceIInR.X + k1 - k2, centerOfFaceIInR.Y + k3 - k4));
                quad.Add(new Vector2F(centerOfFaceIInR.X + k1 + k2, centerOfFaceIInR.Y + k3 + k4));
                quad.Add(new Vector2F(centerOfFaceIInR.X - k1 + k2, centerOfFaceIInR.Y - k3 + k4));

                // Clip incident face (quadrilateral) against reference face (rectangle).
                List <Vector2F> contacts2D = ClipQuadrilateralAgainstRectangle(faceExtentR, quad);

                // Transform contact points back to world space and compute penetration depths.
                int             numberOfContacts = contacts2D.Count;
                List <Vector3F> contacts3D       = DigitalRune.ResourcePools <Vector3F> .Lists.Obtain();

                List <float> penetrationDepths = DigitalRune.ResourcePools <float> .Lists.Obtain();

                Matrix22F mRToI = mIToR.Inverse;
                for (int i = numberOfContacts - 1; i >= 0; i--)
                {
                    Vector2F contact2DR = contacts2D[i];                           // Contact in reference-face space.
                    Vector2F contact2DI = mRToI * (contact2DR - centerOfFaceIInR); // Contact in incident-face space.

                    // Transform point in incident-face space to world (relative to center of reference box).
                    // contact3D = mI * (x, y, 0) + centerOfFaceI
                    Vector3F contact3D;
                    contact3D.X = xAxisInc.X * contact2DI.X + yAxisInc.X * contact2DI.Y + centerOfFaceI.X;
                    contact3D.Y = xAxisInc.Y * contact2DI.X + yAxisInc.Y * contact2DI.Y + centerOfFaceI.Y;
                    contact3D.Z = xAxisInc.Z * contact2DI.X + yAxisInc.Z * contact2DI.Y + centerOfFaceI.Z;

                    // Compute penetration depth.

                    //float penetrationDepth = faceOffsetR - Vector3F.Dot(normalWorld, contact3D);
                    // ----- Optimized version:
                    float penetrationDepth = faceOffsetR - (normalWorld.X * contact3D.X + normalWorld.Y * contact3D.Y + normalWorld.Z * contact3D.Z);

                    if (penetrationDepth >= 0)
                    {
                        // Valid contact.
                        contacts3D.Add(contact3D);
                        penetrationDepths.Add(penetrationDepth);
                    }
                    else
                    {
                        // Remove bad contacts from the 2D contacts.
                        // (We might still need the 2D contacts, if we need to reduce the contacts.)
                        contacts2D.RemoveAt(i);
                    }
                }

                numberOfContacts = contacts3D.Count;
                if (numberOfContacts == 0)
                {
                    return; // Should never happen.
                }
                // Revert normal back to original direction.
                normal = (isNormalInverted) ? -normalWorld : normalWorld;

                // Note: normal ........ contact normal pointing from box A to B.
                //       normalWorld ... contact normal pointing from reference box to incident box.

                if (numberOfContacts <= MaxNumberOfContacts)
                {
                    // Add all contacts to contact set.
                    for (int i = 0; i < numberOfContacts; i++)
                    {
                        float penetrationDepth = penetrationDepths[i];

                        // Position is between the positions of the box surfaces.
                        Vector3F position = contacts3D[i] + poseR.Position + normalWorld * (penetrationDepth / 2);

                        Contact contact = ContactHelper.CreateContact(contactSet, position, normal, penetrationDepth, false);
                        ContactHelper.Merge(contactSet, contact, type, CollisionDetection.ContactPositionTolerance);
                    }
                }
                else
                {
                    // Reduce number of contacts, keep the contact with the max penetration depth.
                    int   indexOfDeepest      = 0;
                    float maxPenetrationDepth = penetrationDepths[0];
                    for (int i = 1; i < numberOfContacts; i++)
                    {
                        float penetrationDepth = penetrationDepths[i];
                        if (penetrationDepth > maxPenetrationDepth)
                        {
                            maxPenetrationDepth = penetrationDepth;
                            indexOfDeepest      = i;
                        }
                    }

                    List <int> indicesOfContacts = ReduceContacts(contacts2D, indexOfDeepest, MaxNumberOfContacts);

                    // Add selected contacts to contact set.
                    numberOfContacts = indicesOfContacts.Count;
                    for (int i = 0; i < numberOfContacts; i++)
                    {
                        int   index            = indicesOfContacts[i];
                        float penetrationDepth = penetrationDepths[index];

                        // Position is between the positions of the box surfaces.
                        Vector3F position = contacts3D[index] + poseR.Position + normalWorld * (penetrationDepth / 2);

                        Contact contact = ContactHelper.CreateContact(contactSet, position, normal, penetrationDepths[index], false);
                        ContactHelper.Merge(contactSet, contact, type, CollisionDetection.ContactPositionTolerance);
                    }

                    DigitalRune.ResourcePools <int> .Lists.Recycle(indicesOfContacts);
                }

                DigitalRune.ResourcePools <Vector2F> .Lists.Recycle(contacts2D);

                DigitalRune.ResourcePools <Vector3F> .Lists.Recycle(contacts3D);

                DigitalRune.ResourcePools <float> .Lists.Recycle(penetrationDepths);
            }
        }
예제 #3
0
        //public Vector3F RelativePosition
        //{
        //  get
        //  {
        //    if (BodyA == null)
        //      throw new PhysicsException("BodyA must not be null.");
        //    if (BodyB == null)
        //      throw new PhysicsException("BodyB must not be null.");

        //    // Anchor orientation in world space.
        //    Matrix33F anchorOrientationA = BodyA.Pose.Orientation * AnchorOrientationALocal;
        //    Matrix33F anchorOrientationB = BodyB.Pose.Orientation * AnchorOrientationBLocal;

        //    // Anchor orientation of B relative to A.
        //    Matrix33F relativeOrientation = anchorOrientationA.Transposed * anchorOrientationB;

        //    // The Euler angles.
        //    Vector3F angles = GetAngles(relativeOrientation);

        //    return angles;
        //  }
        //}
        #endregion


        //--------------------------------------------------------------
        #region Creation & Cleanup
        //--------------------------------------------------------------
        #endregion


        //--------------------------------------------------------------
        #region Methods
        //--------------------------------------------------------------

        /// <inheritdoc/>
        protected override void OnSetup()
        {
            // Get anchor orientations in world space.
            Matrix33F anchorOrientationA = BodyA.Pose.Orientation * AnchorOrientationALocal;
            Matrix33F anchorOrientationB = BodyB.Pose.Orientation * AnchorOrientationBLocal;

            // Get the quaternion that rotates something from anchor orientation A to
            // anchor orientation B:
            //   QB = QTotal * QA
            //   => QTotal = QB * QA.Inverse
            QuaternionF total = QuaternionF.CreateRotation(anchorOrientationB * anchorOrientationA.Transposed);

            // Compute swing axis and angle.
            Vector3F    xAxisA = anchorOrientationA.GetColumn(0);
            Vector3F    yAxisA = anchorOrientationA.GetColumn(1);
            Vector3F    xAxisB = anchorOrientationB.GetColumn(0);
            QuaternionF swing  = QuaternionF.CreateRotation(xAxisA, xAxisB);

            Vector3F swingAxis = new Vector3F(swing.X, swing.Y, swing.Z);

            if (!swingAxis.TryNormalize())
            {
                swingAxis = yAxisA;
            }

            float swingAngle = swing.Angle;

            Debug.Assert(
                0 <= swingAngle && swingAngle <= ConstantsF.Pi,
                "QuaternionF.CreateRotation(Vector3F, Vector3F) should only create rotations along the \"short arc\".");

            // The swing limits create a deformed cone. If we look onto the x-axis of A:
            // y-axis goes to the right. z-axis goes up.
            Vector3F xAxisBInAnchorA = Matrix33F.MultiplyTransposed(anchorOrientationA, xAxisB);
            float    directionY      = xAxisBInAnchorA.Y;
            float    directionZ      = xAxisBInAnchorA.Z;

            // In this plane, we have an ellipse with the formula:
            //   y²/a² + z²/b² = 1, where a and b are the ellipse radii.
            // We don't know the exact radii. We can compute them from the swing min/max angles.
            // To make it simpler, we do not use a flat ellipse. We use the swing z limit for a.
            // And the swing y limit for b.
            // We have a different ellipse for each quarter.
            float ellipseA = (directionY > 0) ? Maximum.Z : -Minimum.Z;
            float ellipseB = (directionZ > 0) ? -Minimum.Y : Maximum.Y;

            // The angles are in radians: angle = bow/radius. So our a and b are on the unit sphere.
            // This creates an elliptic thing on the unit sphere - not in a plane. We don't care because
            // we only need a smooth interpolation between the swing y and z limits.
            // No we look for the swing angle in the direction of xAxisB.
            // The next step can derived from following formulas:
            //     y²/a² + z²/b² = 1                   The ellipse formula.
            //     slope = directionZ / directionY     The direction in which we need the limit.
            //     slope = z/y                         The (y,z) is the point on the ellipse in the given direction.
            //     swingLimit = sqrt(y² + z²)          This is the distance of (y,z) from the center.
            // Since our ellipse is on a sphere, swingLimit is an angle (= bow / radius).

            float swingLimit = ellipseB;

            if (!Numeric.IsZero(directionY))
            {
                float slope           = directionZ / directionY;
                float slopeSquared    = slope * slope;
                float ellipseASquared = ellipseA * ellipseA;
                float ellipseBSquared = ellipseB * ellipseB;
                swingLimit = (float)Math.Sqrt((1 + slopeSquared) / (1 / ellipseASquared + slopeSquared / ellipseBSquared));

                // The ellipse normal would give us a better swing axis. But our computed swingAngle
                // is not correct for this axis...
                // Create a swing axis from the ellipse normal.
                //float k = ellipseASquared / ellipseBSquared * directionZ / directionY;
                //var normal = anchorOrientationA * new Vector3F(0, -k, 1).Normalized;
                //if (Vector3F.Dot(normal, swingAxis) < 0)
                //  swingAxis = -normal;
                //else
                //  swingAxis = normal;
            }

#if DEBUG
            //Debug.Assert(QuaternionF.Dot(swing, total) >= 0);
            var swingAxisALocal = Matrix33F.MultiplyTransposed(anchorOrientationA, swingAxis);
            Debug.Assert(Numeric.IsZero(swingAxisALocal.X));
#endif

            // We define our rotations like this:
            // First we twist around the x-axis of A. Then we swing.
            //   QTotal = QSwing * QTwist
            //   => QSwing.Inverse * QTotal = QTwist
            QuaternionF twist = swing.Conjugated * total;
            twist.Normalize();

            // The quaternion returns an angle in the range [0, 2π].
            float twistAngle = twist.Angle;

            // The minimum and maximum twist limits are in the range [-π, π].
            if (twistAngle > ConstantsF.Pi)
            {
                // Convert the twistAngle to the range used by the twist limits.
                twistAngle = -(ConstantsF.TwoPi - twistAngle);
                Debug.Assert(-ConstantsF.TwoPi < twistAngle && twistAngle <= ConstantsF.TwoPi);
            }

            // The axis of the twist quaternion is parallel to xAxisA.
            Vector3F twistAxis = new Vector3F(twist.X, twist.Y, twist.Z);
            if (Vector3F.Dot(twistAxis, xAxisA) < 0)
            {
                // The axis of the twist quaternion points in the opposite direction of xAxisA.
                // The twist angle need to be inverted.
                twistAngle = -twistAngle;
            }

            // Remember old states.
            LimitState oldXLimitState = _limitStates[0];
            LimitState oldYLimitState = _limitStates[1];

            // Note: All axes between xAxisA and xAxisB should be valid twist axes.
            SetupConstraint(0, twistAngle, xAxisB, Minimum[0], Maximum[0]);
            SetupConstraint(1, swingAngle, swingAxis, -swingLimit, swingLimit);

            // Warm-start the constraints if the previous limit state matches the new limit state.
            Warmstart(0, oldXLimitState);
            Warmstart(1, oldYLimitState);
        }