Example #1
0
        /// <summary>
        /// Rotates the view direction up or down relative to the locked up vector.
        /// </summary>
        /// <param name="radians">Amount to rotate.</param>
        public void Pitch(float radians)
        {
            //Do not allow the new view direction to violate the maximum pitch.
            float dot;

            Vector3.Dot(ref viewDirection, ref lockedUp, out dot);

            //While this could be rephrased in terms of dot products alone, converting to actual angles can be more intuitive.
            //Consider +Pi/2 to be up, and -Pi/2 to be down.
            float currentPitch = (float)Math.Acos(MathHelper.Clamp(-dot, -1, 1)) - MathHelper.PiOver2;
            //Compute our new pitch by clamping the current + change.
            float newPitch      = MathHelper.Clamp(currentPitch + radians, -maximumPitch, maximumPitch);
            float allowedChange = newPitch - currentPitch;

            //Compute and apply the rotation.
            Vector3 pitchAxis;

            Vector3.Cross(ref viewDirection, ref lockedUp, out pitchAxis);
            //This is guaranteed safe by all interaction points stopping viewDirection from being aligned with lockedUp.
            pitchAxis.Normalize();
            Matrix3x3 rotation;

            Matrix3x3.CreateFromAxisAngle(ref pitchAxis, allowedChange, out rotation);
            Matrix3x3.Transform(ref viewDirection, ref rotation, out viewDirection);

            //Avoid drift by renormalizing.
            viewDirection.Normalize();
        }
Example #2
0
        /// <summary>
        /// Rotates the camera around its locked up vector.
        /// </summary>
        /// <param name="radians">Amount to rotate.</param>
        public void Yaw(float radians)
        {
            //Rotate around the up vector.
            Matrix3x3 rotation;

            Matrix3x3.CreateFromAxisAngle(ref lockedUp, radians, out rotation);
            Matrix3x3.Transform(ref viewDirection, ref rotation, out viewDirection);

            //Avoid drift by renormalizing.
            viewDirection.Normalize();
        }
Example #3
0
        ///<summary>
        /// Performs the frame's configuration step.
        ///</summary>
        ///<param name="dt">Timestep duration.</param>
        public override void Update(float dt)
        {
            //Transform the axes into world space.
            basis.rotationMatrix = connectionA.orientationMatrix;
            basis.ComputeWorldSpaceAxes();
            Matrix3x3.Transform(ref localTestAxis, ref connectionB.orientationMatrix, out worldTestAxis);

            //Compute the plane normals.
            Vector3 minPlaneNormal, maxPlaneNormal;
            //Rotate basisA y axis around the basisA primary axis.
            Matrix3x3 rotation;

            Matrix3x3.CreateFromAxisAngle(ref basis.primaryAxis, minimumAngle + MathHelper.PiOver2, out rotation);
            Matrix3x3.Transform(ref Basis.xAxis, ref rotation, out minPlaneNormal);
            Matrix3x3.CreateFromAxisAngle(ref basis.primaryAxis, maximumAngle - MathHelper.PiOver2, out rotation);
            Matrix3x3.Transform(ref Basis.xAxis, ref rotation, out maxPlaneNormal);

            //Compute the errors along the two normals.
            float planePositionMin, planePositionMax;

            Vector3.Dot(ref minPlaneNormal, ref worldTestAxis, out planePositionMin);
            Vector3.Dot(ref maxPlaneNormal, ref worldTestAxis, out planePositionMax);


            float span = GetDistanceFromMinimum(maximumAngle);

            //Early out and compute the determine the plane normal.
            if (span >= MathHelper.Pi)
            {
                if (planePositionMax > 0 || planePositionMin > 0)
                {
                    //It's in a perfectly valid configuration, so skip.
                    isActiveInSolver   = false;
                    minIsActive        = false;
                    maxIsActive        = false;
                    error              = Vector2.Zero;
                    accumulatedImpulse = Vector2.Zero;
                    isLimitActive      = false;
                    return;
                }

                if (planePositionMax > planePositionMin)
                {
                    //It's quicker to escape out to the max plane than the min plane.
                    error.X = 0;
                    error.Y = -planePositionMax;
                    accumulatedImpulse.X = 0;
                    minIsActive          = false;
                    maxIsActive          = true;
                }
                else
                {
                    //It's quicker to escape out to the min plane than the max plane.
                    error.X = -planePositionMin;
                    error.Y = 0;
                    accumulatedImpulse.Y = 0;
                    minIsActive          = true;
                    maxIsActive          = false;
                }
                //There's never a non-degenerate situation where having both planes active with a span
                //greater than pi is useful.
            }
            else
            {
                if (planePositionMax > 0 && planePositionMin > 0)
                {
                    //It's in a perfectly valid configuration, so skip.
                    isActiveInSolver   = false;
                    minIsActive        = false;
                    maxIsActive        = false;
                    error              = Vector2.Zero;
                    accumulatedImpulse = Vector2.Zero;
                    isLimitActive      = false;
                    return;
                }

                if (planePositionMin <= 0 && planePositionMax <= 0)
                {
                    //Escape upward.
                    //Activate both planes.
                    error.X     = -planePositionMin;
                    error.Y     = -planePositionMax;
                    minIsActive = true;
                    maxIsActive = true;
                }
                else if (planePositionMin <= 0)
                {
                    //It's quicker to escape out to the min plane than the max plane.
                    error.X = -planePositionMin;
                    error.Y = 0;
                    accumulatedImpulse.Y = 0;
                    minIsActive          = true;
                    maxIsActive          = false;
                }
                else
                {
                    //It's quicker to escape out to the max plane than the min plane.
                    error.X = 0;
                    error.Y = -planePositionMax;
                    accumulatedImpulse.X = 0;
                    minIsActive          = false;
                    maxIsActive          = true;
                }
            }
            isLimitActive = true;


            //****** VELOCITY BIAS ******//
            //Compute the correction velocity
            float errorReduction;

            springSettings.ComputeErrorReductionAndSoftness(dt, 1 / dt, out errorReduction, out softness);

            //Compute the jacobians
            if (minIsActive)
            {
                Vector3.Cross(ref minPlaneNormal, ref worldTestAxis, out jacobianMinA);
                if (jacobianMinA.LengthSquared() < Toolbox.Epsilon)
                {
                    //The plane normal is aligned with the test axis.
                    //Use the basis's free axis.
                    jacobianMinA = basis.primaryAxis;
                }
                jacobianMinA.Normalize();
                jacobianMinB.X = -jacobianMinA.X;
                jacobianMinB.Y = -jacobianMinA.Y;
                jacobianMinB.Z = -jacobianMinA.Z;
            }
            if (maxIsActive)
            {
                Vector3.Cross(ref maxPlaneNormal, ref worldTestAxis, out jacobianMaxA);
                if (jacobianMaxA.LengthSquared() < Toolbox.Epsilon)
                {
                    //The plane normal is aligned with the test axis.
                    //Use the basis's free axis.
                    jacobianMaxA = basis.primaryAxis;
                }
                jacobianMaxA.Normalize();
                jacobianMaxB.X = -jacobianMaxA.X;
                jacobianMaxB.Y = -jacobianMaxA.Y;
                jacobianMaxB.Z = -jacobianMaxA.Z;
            }

            //Error is always positive
            if (minIsActive)
            {
                biasVelocity.X = MathHelper.Min(MathHelper.Max(0, error.X - margin) * errorReduction, maxCorrectiveVelocity);
                if (bounciness > 0)
                {
                    float relativeVelocity;
                    float dot;
                    //Find the velocity contribution from each connection
                    Vector3.Dot(ref connectionA.angularVelocity, ref jacobianMinA, out relativeVelocity);
                    Vector3.Dot(ref connectionB.angularVelocity, ref jacobianMinB, out dot);
                    relativeVelocity += dot;
                    biasVelocity.X    = MathHelper.Max(biasVelocity.X, ComputeBounceVelocity(-relativeVelocity));
                }
            }
            if (maxIsActive)
            {
                biasVelocity.Y = MathHelper.Min(MathHelper.Max(0, error.Y - margin) * errorReduction, maxCorrectiveVelocity);
                if (bounciness > 0)
                {
                    //Find the velocity contribution from each connection
                    if (maxIsActive)
                    {
                        float relativeVelocity;
                        Vector3.Dot(ref connectionA.angularVelocity, ref jacobianMaxA, out relativeVelocity);
                        float dot;
                        Vector3.Dot(ref connectionB.angularVelocity, ref jacobianMaxB, out dot);
                        relativeVelocity += dot;
                        biasVelocity.Y    = MathHelper.Max(biasVelocity.Y, ComputeBounceVelocity(-relativeVelocity));
                    }
                }
            }


            //****** EFFECTIVE MASS MATRIX ******//
            //Connection A's contribution to the mass matrix
            float   minEntryA, minEntryB;
            float   maxEntryA, maxEntryB;
            Vector3 transformedAxis;

            if (connectionA.isDynamic)
            {
                if (minIsActive)
                {
                    Matrix3x3.Transform(ref jacobianMinA, ref connectionA.inertiaTensorInverse, out transformedAxis);
                    Vector3.Dot(ref transformedAxis, ref jacobianMinA, out minEntryA);
                }
                else
                {
                    minEntryA = 0;
                }
                if (maxIsActive)
                {
                    Matrix3x3.Transform(ref jacobianMaxA, ref connectionA.inertiaTensorInverse, out transformedAxis);
                    Vector3.Dot(ref transformedAxis, ref jacobianMaxA, out maxEntryA);
                }
                else
                {
                    maxEntryA = 0;
                }
            }
            else
            {
                minEntryA = 0;
                maxEntryA = 0;
            }
            //Connection B's contribution to the mass matrix
            if (connectionB.isDynamic)
            {
                if (minIsActive)
                {
                    Matrix3x3.Transform(ref jacobianMinB, ref connectionB.inertiaTensorInverse, out transformedAxis);
                    Vector3.Dot(ref transformedAxis, ref jacobianMinB, out minEntryB);
                }
                else
                {
                    minEntryB = 0;
                }
                if (maxIsActive)
                {
                    Matrix3x3.Transform(ref jacobianMaxB, ref connectionB.inertiaTensorInverse, out transformedAxis);
                    Vector3.Dot(ref transformedAxis, ref jacobianMaxB, out maxEntryB);
                }
                else
                {
                    maxEntryB = 0;
                }
            }
            else
            {
                minEntryB = 0;
                maxEntryB = 0;
            }
            //Compute the inverse mass matrix
            //Notice that the mass matrix isn't linked, it's two separate ones.
            velocityToImpulse.X = 1 / (softness + minEntryA + minEntryB);
            velocityToImpulse.Y = 1 / (softness + maxEntryA + maxEntryB);
        }