More extensible camera (changes in text)

Post and discuss features you'd like to see in the BEPUphysics library.
Post Reply
jimmyfo
Posts: 12
Joined: Sun Dec 25, 2011 7:27 pm

More extensible camera (changes in text)

Post by jimmyfo » Tue Sep 24, 2013 4:36 pm

Hi Norbo, after Microsoft stopped support for XNA, I went to try unity for a while - thoroughly put off by it (in a lot of ways their phsyics really frustrated me), I came back and saw you had made the dependency free version the main branch. Awesome! I grabbed the latest source and went through it - I noticed the camera class was vastly changed and I went ahead and made a bunch of changes to allow users to sort of "plug and play" custom camera behaviour. If you like, I'm giving all the code here and you can integrate it into the branch if you'd like :)

1: Create a folder in the BepuPhsyicsDemos solution called "CameraComponents"
2: In that folder, create the class "CameraVariables.cs" with the following code:

Code: Select all

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BEPUphysicsDemos.CameraComponents
{
    /// <summary>
    /// Different modes of camera movement
    /// </summary>
    public enum CameraMode
    {
        /// <summary>
        /// Non-sprung chase camera, can move the character.
        /// </summary>
        ChaseStiff,
        /// <summary>
        /// First person viewpoint, can move the character.
        /// </summary>
        FirstPersonMovement,
        /// <summary>
        /// A free moving, physics independent camera
        /// </summary>
        Free,
        /// <summary>
        /// Static distance, offset, independent camera, can move the character, is obstruction aware.
        /// </summary>
        Offset,
    }
}
3: Move the existing "Camera.cs" class into that folder
4: Here is the biggest change. I've rearranged the entire camera class to support multiple camera classes by changing the "CameraMode" - a user can just define their own mode and create the function. Notice that code has been rearranged a bit, and the camera movement code from the "CharacterControllerInput" class has been moved into here. Also, instead of just turning on/off chase camera, we use a "ChangeCameraMode" function. To get this to work, just copy and paste into your "Camera.cs"

Code: Select all

using System;
using BEPUphysics;
using BEPUphysics.BroadPhaseEntries;
using BEPUphysics.CollisionRuleManagement;
using BEPUphysics.Entities;
using BEPUutilities;
using Microsoft.Xna.Framework.Input;
using BEPUphysicsDemos.AlternateMovement.Character;

namespace BEPUphysicsDemos.CameraComponents
{
    /// <summary>
    /// Simple camera class for moving around the demos area.
    /// </summary>
    public class Camera
    {
        #region Fields

        #region Chase Camera Mode

        /// <summary>
        /// Chase camera: how high to offset the camera along the entity's up vector.
        /// </summary>
        private float cameraVerticalOffset = 5.0f;

        //The following are used for the chase camera only.
        /// <summary>
        /// Entity to follow around and point at.
        /// </summary>
        private Entity entityToFollow;

        /// <summary>
        /// Offset vector from the center of the target chase entity to look at.
        /// </summary>
        private Vector3 offsetFromChaseTarget;

        /// <summary>
        /// Whether or not to transform the offset vector with the rotation of the entity.
        /// </summary>
        private bool transformOffset;

        /// <summary>
        /// Distance away from the target entity to try to maintain.  The distance will be shorter at times if the ray hits an object.
        /// </summary>
        private float distanceToTarget;

        /// <summary>
        /// Offset distance away from hit point when chasing and the ray intersects.
        /// </summary>
        private const float chaseCameraMargin = 1f;

        #endregion

        #region Private Variables to Reduce Declarations

        /// <summary>
        /// Hold variable for what we are looking at with cameras.
        /// </summary>
        private Vector3 lookAt;

        /// <summary>
        /// Hold variable for our world matrix backwards.
        /// </summary>
        private Vector3 backwards;

        /// <summary>
        /// Hold variable for our camera offset.
        /// </summary>
        private Vector3 offset;

        /// <summary>
        /// Hold variable to find the earliest ray hit that isn't the chase target to position the camera appropriately.
        /// </summary>
        private RayCastResult result;

        /// <summary>
        /// FirstPersonMovement: Whether or not to smooth out character steps and other discontinuous motion.
        /// </summary>
        public bool UseCameraSmoothing = true;

        #endregion

        /// <summary>
        /// Gets the game associated with the camera.
        /// </summary>
        public DemosGame Game
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets the position of the camera.
        /// </summary>
        public Vector3 Position { get; set; }

        /// <summary>
        /// Gets or sets the speed at which the camera moves.
        /// </summary>
        public float Speed { get; set; }

        /// <summary>
        /// Gets or sets the projection matrix of the camera.
        /// </summary>
        public Matrix ProjectionMatrix { get; set; }

        /// <summary>
        /// Gets the view matrix of the camera.
        /// </summary>
        public Matrix ViewMatrix
        {
            get { return Matrix.CreateViewRH(Position, viewDirection, lockedUp); }
        }

        /// <summary>
        /// Gets the world transformation of the camera.
        /// </summary>
        public Matrix WorldMatrix
        {
            get { return Matrix.CreateWorldRH(Position, viewDirection, lockedUp); }
        }

        private Vector3 viewDirection;
        /// <summary>
        /// Gets or sets the view direction of the camera.
        /// </summary>
        public Vector3 ViewDirection
        {
            get { return viewDirection; }
            set
            {
                Vector3.Normalize(ref value, out value);
                //Validate the input. A temporary violation of the maximum pitch is permitted as it will be fixed as the user looks around.
                //However, we cannot allow a view direction parallel to the locked up direction.
                float dot;
                Vector3.Dot(ref value, ref lockedUp, out dot);
                if (Math.Abs(dot) > 1 - Toolbox.BigEpsilon)
                    throw new ArgumentException("The view direction must not be aligned with the locked up direction.");
                viewDirection = value;
            }
        }

        private float maximumPitch = MathHelper.PiOver2 * 0.99f;
        /// <summary>
        /// Gets or sets how far the camera can look up or down in radians.
        /// </summary>
        public float MaximumPitch
        {
            get { return maximumPitch; }
            set
            {
                if (value < 0)
                    throw new ArgumentException("Maximum pitch corresponds to pitch magnitude; must be positive.");
                if (value >= MathHelper.PiOver2)
                    throw new ArgumentException("Maximum pitch must be less than Pi/2.");
                maximumPitch = value;
            }
        }

        private Vector3 lockedUp;
        /// <summary>
        /// Gets or sets the current locked up vector of the camera.
        /// </summary>
        public Vector3 LockedUp
        {
            get { return lockedUp; }
            set
            {
                var oldUp = lockedUp;
                Vector3.Normalize(ref value, out lockedUp);
                //Move the view direction with the transform. This helps guarantee that the view direction won't end up aligned with the up vector.
                Quaternion rotation;
                Quaternion.GetQuaternionBetweenNormalizedVectors(ref oldUp, ref lockedUp, out rotation);
                Quaternion.Transform(ref viewDirection, ref rotation, out viewDirection);

            }
        }

        /// <summary>
        /// Whether or not to use the default free-flight camera controls.
        /// Set to false when using vehicles or character controllers.
        /// </summary>
        public CameraMode cameraMode = CameraMode.Free;

        /// <summary>
        /// FirstPersonMovement: controller to use
        /// </summary>
        public CharacterController characterController;

        /// <summary>
        /// FirstPersonMovement: Offset from the position of the character to the 'eyes' while the character is standing.
        /// </summary>
        public float StandingCameraOffset = .7f;

        /// <summary>
        /// FirstPersonMovement: Offset from the position of the character to the 'eyes' while the character is crouching.
        /// </summary>
        public float CrouchingCameraOffset = .4f;

        #endregion

        #region Initialization

        /// <summary>
        /// Creates a camera.
        /// </summary>
        /// <param name="game">Game used with the camera.</param>
        /// <param name="position">Initial position of the camera.</param>
        /// <param name="speed">Speed of the camera per second.</param>
        /// <param name="pitch">Initial pitch angle of the camera.</param>
        /// <param name="yaw">Initial yaw value of the camera.</param>
        /// <param name="projectionMatrix">Projection matrix used.</param>
        public Camera(DemosGame game, Vector3 position, float speed, float pitch, float yaw, Matrix projectionMatrix)
        {
            this.Game = game;
            Position = position;
            Speed = speed;
            Yaw(yaw);
            Pitch(pitch);
            ProjectionMatrix = projectionMatrix;

            rayCastFilter = RayCastFilter;
        }

        #endregion

        #region Support

        /// <summary>
        /// Sets up all the information required by the chase camera.
        /// </summary>
        /// <param name="target">Target to follow.</param>
        /// <param name="offset">Offset from the center of the entity target to point at.</param>
        /// <param name="transform">Whether or not to transform the offset with the target entity's rotation.</param>
        /// <param name="distance">Distance from the target position to try to maintain.</param>
        public void ChangeCameraMode(CameraMode mode, Entity target, Vector3 offset, bool transform, float distance)
        {
            cameraMode = mode;
            entityToFollow = target;
            offsetFromChaseTarget = offset;
            transformOffset = transform;
            distanceToTarget = distance;

            switch (mode)
            {
                case CameraMode.ChaseStiff:
                    // This changes our camera's up vector to allow full screen rotation. Comment out to keep in original up.
                    LockedUp = target.BufferedStates.InterpolatedStates.OrientationMatrix.Up;
                    break;
                case CameraMode.FirstPersonMovement:
                    lockedUp = Vector3.Up;
                    LockedUp = Vector3.Up;
                    break;
                case CameraMode.Free:
                    lockedUp = Vector3.Up;
                    LockedUp = Vector3.Up;
                    break;
                case CameraMode.Offset:
                    lockedUp = Vector3.Up;
                    LockedUp = Vector3.Up;
                    break;
            }
        }

        //The raycast filter limits the results retrieved from the Space.RayCast while in chase camera mode.
        Func<BroadPhaseEntry, bool> rayCastFilter;
        bool RayCastFilter(BroadPhaseEntry entry)
        {
            return entry != entityToFollow.CollisionInformation && (entry.CollisionRules.Personal <= CollisionRule.Normal);
        }

        /// <summary>
        /// Moves the camera forward.
        /// </summary>
        /// <param name="distance">Distance to move.</param>
        public void MoveForward(float distance)
        {
            Position += WorldMatrix.Forward * distance;
        }

        /// <summary>
        /// Moves the camera to the right.
        /// </summary>
        /// <param name="distance">Distance to move.</param>
        public void MoveRight(float distance)
        {
            Position += WorldMatrix.Right * distance;
        }

        /// <summary>
        /// Moves the camera up.
        /// </summary>
        /// <param name="distance">Distanec to move.</param>
        public void MoveUp(float distance)
        {
            Position += new Vector3(0, distance, 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();
        }

        /// <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();
        }

        #endregion

        #region Update

        /// <summary>
        /// Updates the state of the camera.
        /// </summary>
        /// <param name="dt">Time since the last frame in seconds.</param>
        /// <param name="keyboardInput">Input for this frame from the keyboard.</param>
        /// <param name="mouseInput">Input for this frame from the mouse.</param>
        /// <param name="gamePadInput">Input for this frame from the game pad.</param>
#if !WINDOWS
        public void Update(float dt, KeyboardState keyboardInput, GamePadState gamePadInput)
        {
            if (cameraMode != CameraMode.ChaseStiff)
            {
                Yaw += gamePadInput.ThumbSticks.Right.X * -1.5f * dt;
                Pitch += gamePadInput.ThumbSticks.Right.Y * 1.5f * dt;
            }   

#else
        public void Update(float dt, KeyboardState keyboardInput, MouseState mouseInput, GamePadState gamePadInput)
        {
            //Only turn if the mouse is controlled by the game.
            if (!Game.IsMouseVisible &&
                cameraMode != CameraMode.ChaseStiff)
            {
                Yaw((200 - mouseInput.X) * dt * .12f);
                Pitch((200 - mouseInput.Y) * dt * .12f);
            }
#endif
            switch (cameraMode)
            {
                case CameraMode.ChaseStiff:
                    UpdateCameraChaseStiff();
                    break;
                case CameraMode.FirstPersonMovement:
                    UpdateCameraFirstPersonMovement(dt);
                    break;
                case CameraMode.Free:
#if !WINDOWS
                    UpdateCameraFree(dt, keyboardInput, gamepadInput);
#else
                    UpdateCameraFree(dt, keyboardInput, mouseInput, gamePadInput);
#endif
                    break;
                case CameraMode.Offset:
                    UpdateCameraOffset();
                    break;
            }
        }

#if !WINDOWS
        /// <summary>
        /// Freely moving camera.
        /// </summary>
        public void UpdateCameraFree(float dt, KeyboardState keyboardInput, GamePadState gamePadInput)
        {
#else
        /// <summary>
        /// Freely moving camera.
        /// </summary>
        /// <param name="dt"></param>
        /// <param name="keyboardInput"></param>
        /// <param name="mouseInput"></param>
        /// <param name="gamePadInput"></param>
        public void UpdateCameraFree(float dt, KeyboardState keyboardInput, MouseState mouseInput, GamePadState gamePadInput)
        {
#endif
            //Only move around if the camera has control over its own position.
            float distance = Speed * dt;
#if !WINDOWS
            MoveForward(gamePadInput.ThumbSticks.Left.Y * distance);
            MoveRight(gamePadInput.ThumbSticks.Left.X * distance);
            if (gamePadInput.IsButtonDown(Buttons.LeftStick))
                MoveUp(distance);
            if (gamePadInput.IsButtonDown(Buttons.RightStick))
                MoveUp(-distance);
#endif
            if (keyboardInput.IsKeyDown(Keys.E))
                MoveForward(distance);
            if (keyboardInput.IsKeyDown(Keys.D))
                MoveForward(-distance);
            if (keyboardInput.IsKeyDown(Keys.S))
                MoveRight(-distance);
            if (keyboardInput.IsKeyDown(Keys.F))
                MoveRight(distance);
            if (keyboardInput.IsKeyDown(Keys.A))
                MoveUp(distance);
            if (keyboardInput.IsKeyDown(Keys.Z))
                MoveUp(-distance);
        }

        /// <summary>
        /// Taken from the character controller input class
        /// </summary>
        private void UpdateCameraFirstPersonMovement(float dt)
        {
            //Note that the character controller's update method is not called here; this is because it is handled within its owning space.
            //This method's job is simply to tell the character to move around based on the Camera and input.

            ////Rotate the camera of the character based on the support velocity, if a support with velocity exists.
            ////This can be very disorienting in some cases; that's why it is off by default!
            //if (CharacterController.SupportFinder.HasSupport)
            //{
            //    SupportData? data;
            //    if (CharacterController.SupportFinder.HasTraction)
            //        data = CharacterController.SupportFinder.TractionData;
            //    else
            //        data = CharacterController.SupportFinder.SupportData;
            //    EntityCollidable support = data.Value.SupportObject as EntityCollidable;
            //    if (support != null && !support.Entity.IsDynamic) //Having the view turned by dynamic entities is extremely confusing for the most part.
            //    {
            //        float dot = Vector3.Dot(support.Entity.AngularVelocity, CharacterController.Body.OrientationMatrix.Up);
            //        Camera.Yaw += dot * dt;
            //    }
            //}

            if (UseCameraSmoothing)
            {
                //First, find where the camera is expected to be based on the last position and the current velocity.
                //Note: if the character were a free-floating 6DOF character, this would need to include an angular velocity contribution.
                //And of course, the camera orientation would be based on the character's orientation.
                Position = Position + characterController.Body.LinearVelocity * dt;
                //Now compute where it should be according the physical body of the character.
                Vector3 up = characterController.Body.OrientationMatrix.Up;
                Vector3 bodyPosition = characterController.Body.BufferedStates.InterpolatedStates.Position;
                Vector3 goalPosition = bodyPosition + up * (characterController.StanceManager.CurrentStance == Stance.Standing ? StandingCameraOffset : CrouchingCameraOffset);

                //Usually, the camera position and the goal will be very close, if not matching completely.
                //However, if the character steps or has its position otherwise modified, then they will not match.
                //In this case, we need to correct the camera position.

                //To do this, first note that we can't correct infinite errors.  We need to define a bounding region that is relative to the character
                //in which the camera can interpolate around.  The most common discontinuous motions are those of upstepping and downstepping.
                //In downstepping, the character can teleport up to the character's MaximumStepHeight downwards.
                //In upstepping, the character can teleport up to the character's MaximumStepHeight upwards, and the body's CollisionMargin horizontally.
                //Picking those as bounds creates a constraining cylinder.

                Vector3 error = goalPosition - Position;
                float verticalError = Vector3.Dot(error, up);
                Vector3 horizontalError = error - verticalError * up;
                //Clamp the vertical component of the camera position within the bounding cylinder.
                if (verticalError > characterController.StepManager.MaximumStepHeight)
                {
                    Position -= up * (characterController.StepManager.MaximumStepHeight - verticalError);
                    verticalError = characterController.StepManager.MaximumStepHeight;
                }
                else if (verticalError < -characterController.StepManager.MaximumStepHeight)
                {
                    Position -= up * (-characterController.StepManager.MaximumStepHeight - verticalError);
                    verticalError = -characterController.StepManager.MaximumStepHeight;
                }
                //Clamp the horizontal distance too.
                float horizontalErrorLength = horizontalError.LengthSquared();
                float margin = characterController.Body.CollisionInformation.Shape.CollisionMargin;
                if (horizontalErrorLength > margin * margin)
                {
                    Vector3 previousHorizontalError = horizontalError;
                    Vector3.Multiply(ref horizontalError, margin / (float)Math.Sqrt(horizontalErrorLength), out horizontalError);
                    Position -= horizontalError - previousHorizontalError;
                }
                //Now that the error/camera position is known to lie within the constraining cylinder, we can perform a smooth correction.

                //This removes a portion of the error each frame.
                //Note that this is not framerate independent.  If fixed time step is not enabled,
                //a different smoothing method should be applied to the final error values.
                //float errorCorrectionFactor = .3f;

                //This version is framerate independent, although it is more expensive.
                float errorCorrectionFactor = (float)(1 - Math.Pow(.000000001, dt));
                Position += up * (verticalError * errorCorrectionFactor);
                Position += horizontalError * errorCorrectionFactor;


            }
            else
            {
                Position = characterController.Body.Position + (characterController.StanceManager.CurrentStance == Stance.Standing ? StandingCameraOffset : CrouchingCameraOffset) * characterController.Body.OrientationMatrix.Up;
            }
        }

        /// <summary>
        /// Chase camera locked to a set distance from the chase object.
        /// </summary>
        private void UpdateCameraChaseStiff()
        {
            // CHASE STIFF
            if (transformOffset)
                offset = Matrix3x3.Transform(offsetFromChaseTarget, entityToFollow.BufferedStates.InterpolatedStates.OrientationMatrix);
            else
                offset = offsetFromChaseTarget;
            lookAt = entityToFollow.BufferedStates.InterpolatedStates.Position + offset;

            // Set camera behind the look at
            Position = lookAt + (entityToFollow.BufferedStates.InterpolatedStates.OrientationMatrix.Backward * 20);

            // This changes our camera's up vector to allow full screen rotation. Comment out to keep in original up.
            LockedUp = entityToFollow.BufferedStates.InterpolatedStates.OrientationMatrix.Up;

            // elevate camera by up vector
            Position += (entityToFollow.BufferedStates.InterpolatedStates.OrientationMatrix.Up * cameraVerticalOffset);

            // TEMP test offset when intersecting object
            Vector3 dir = Vector3.Normalize(Position - lookAt);
            float dist = Vector3.Distance(Position, lookAt);
            if (entityToFollow.Space.RayCast(new Ray(lookAt, dir), dist, rayCastFilter, out result))
            {
                Position = result.HitData.Location; //lookAt + (Math.Max(result.HitData.T - chaseCameraMargin, 0)) * backwards; //Put the camera just before any hit spot.
            }

            // set our view direction
            viewDirection = Vector3.Normalize(lookAt - Position);
        }

        /// <summary>
        /// Offset, freely rotatable camera at a fixed distance.
        /// </summary>
        private void UpdateCameraOffset()
        {
            // OFFSET MODE - TODO reset up to 0,1,0
            if (transformOffset)
                offset = Matrix3x3.Transform(offsetFromChaseTarget, entityToFollow.BufferedStates.InterpolatedStates.OrientationMatrix);
            else
                offset = offsetFromChaseTarget;
            lookAt = entityToFollow.BufferedStates.InterpolatedStates.Position + offset;
            backwards = -viewDirection;

            //Find the earliest ray hit that isn't the chase target to position the camera appropriately.
            RayCastResult result;
            if (entityToFollow.Space.RayCast(new Ray(lookAt, backwards), distanceToTarget, rayCastFilter, out result))
            {
                Position = lookAt + (Math.Max(result.HitData.T - chaseCameraMargin, 0)) * backwards; //Put the camera just before any hit spot.
            }
            else
                Position = lookAt + (Math.Max(distanceToTarget - chaseCameraMargin, 0)) * backwards;
        }

        #endregion
    }
}
5: Obviously, we need to clean up classes that call the camera. Our first is to copy/paste the new CharacterControllerInput

Code: Select all

using BEPUphysics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Diagnostics;
using BEPUphysics.BroadPhaseEntries.MobileCollidables;
using ConversionHelper;
using BEPUutilities;
using BEPUphysicsDemos.CameraComponents;

namespace BEPUphysicsDemos.AlternateMovement.Character
{
    /// <summary>
    /// Handles input and movement of a character in the game.
    /// Acts as a simple 'front end' for the bookkeeping and math of the character controller.
    /// </summary>
    public class CharacterControllerInput
    {
        /// <summary>
        /// Camera to use for input.
        /// </summary>
        public Camera Camera;

        /// <summary>
        /// Physics representation of the character.
        /// </summary>
        public CharacterController CharacterController;

        /// <summary>
        /// Whether or not to use the character controller's input.
        /// </summary>
        public bool IsActive = true;

        /// <summary>
        /// Owning space of the character.
        /// </summary>
        public Space Space { get; private set; }


        /// <summary>
        /// Constructs the character and internal physics character controller.
        /// </summary>
        /// <param name="owningSpace">Space to add the character to.</param>
        /// <param name="cameraToUse">Camera to attach to the character.</param>
        public CharacterControllerInput(Space owningSpace, Camera cameraToUse)
        {
            // Controller to move around with this input
            CharacterController = new CharacterController();

            // Assign to camera to use in movement
            cameraToUse.characterController = CharacterController;

            // Track our space
            Space = owningSpace;
            Space.Add(CharacterController);

            Camera = cameraToUse;
            Deactivate();
        }

        /// <summary>
        /// Gives the character control over the Camera and movement input.
        /// </summary>
        public void Activate()
        {
            if (!IsActive)
            {
                IsActive = true;
                Space.Add(CharacterController);

                Camera.ChangeCameraMode(CameraMode.FirstPersonMovement, null, Vector3.Zero, false, 0.0f);
                CharacterController.Body.Position = Camera.Position - new Vector3(0, Camera.StandingCameraOffset, 0);
            }
        }

        /// <summary>
        /// Returns input control to the Camera.
        /// </summary>
        public void Deactivate()
        {
            if (IsActive)
            {
                IsActive = false;
                Camera.cameraMode = CameraMode.Free;
                Space.Remove(CharacterController);
            }
        }


        /// <summary>
        /// Handles the input and movement of the character.
        /// </summary>
        /// <param name="dt">Time since last frame in simulation seconds.</param>
        /// <param name="previousKeyboardInput">The last frame's keyboard state.</param>
        /// <param name="keyboardInput">The current frame's keyboard state.</param>
        /// <param name="previousGamePadInput">The last frame's gamepad state.</param>
        /// <param name="gamePadInput">The current frame's keyboard state.</param>
        public void Update(float dt, KeyboardState previousKeyboardInput, KeyboardState keyboardInput, GamePadState previousGamePadInput, GamePadState gamePadInput)
        {
            if (IsActive)
            {
                Vector2 totalMovement = Vector2.Zero;

#if XBOX360
                totalMovement += new Vector2(gamePadInput.ThumbSticks.Left.X, gamePadInput.ThumbSticks.Left.Y);

                CharacterController.HorizontalMotionConstraint.SpeedScale = Math.Min(totalMovement.Length(), 1); //Don't trust the game pad to output perfectly normalized values.
                CharacterController.HorizontalMotionConstraint.MovementDirection = totalMovement;
                
                CharacterController.StanceManager.DesiredStance = gamePadInput.IsButtonDown(Buttons.RightStick) ? Stance.Crouching : Stance.Standing;

                //Jumping
                if (previousGamePadInput.IsButtonUp(Buttons.LeftStick) && gamePadInput.IsButtonDown(Buttons.LeftStick))
                {
                    CharacterController.Jump();
                }
#else

                //Collect the movement impulses.
                
                if (keyboardInput.IsKeyDown(Keys.E))
                {
                    totalMovement += new Vector2(0, 1);
                }
                if (keyboardInput.IsKeyDown(Keys.D))
                {
                    totalMovement += new Vector2(0, -1);
                }
                if (keyboardInput.IsKeyDown(Keys.S))
                {
                    totalMovement += new Vector2(-1, 0);
                }
                if (keyboardInput.IsKeyDown(Keys.F))
                {
                    totalMovement += new Vector2(1, 0);
                }
                if (totalMovement == Vector2.Zero)
                    CharacterController.HorizontalMotionConstraint.MovementDirection = Vector2.Zero;
                else
                    CharacterController.HorizontalMotionConstraint.MovementDirection = Vector2.Normalize(totalMovement);


                CharacterController.StanceManager.DesiredStance = keyboardInput.IsKeyDown(Keys.Z) ? Stance.Crouching : Stance.Standing;

                //Jumping
                if (previousKeyboardInput.IsKeyUp(Keys.A) && keyboardInput.IsKeyDown(Keys.A))
                {
                    CharacterController.Jump();
                }
#endif
                CharacterController.ViewDirection = Camera.WorldMatrix.Forward;

            }
        }
    }
}
6: For any Active/Deactive functions, we need to use the new functions - here is an example from the tankinput.cs file

Code: Select all

        /// <summary>
        /// Gives the vehicle control over the camera and movement input.
        /// </summary>
        public void Activate()
        {
            if (!IsActive)
            {
                IsActive = true;
                
                //Put the vehicle where the camera is.
                Vehicle.Body.Position = Camera.Position - CameraOffset;
                Vehicle.Body.LinearVelocity = Vector3.Zero;
                Vehicle.Body.AngularVelocity = Vector3.Zero;
                Vehicle.Body.Orientation = Quaternion.Identity;
                Camera.ChangeCameraMode(CameraMode.ChaseStiff, Vehicle.Body, new Vector3(0, .6f, 0), true, 10);
            }
        }

        /// <summary>
        /// Returns input control to the camera.
        /// </summary>
        public void Deactivate()
        {
            if (IsActive)
            {
                IsActive = false;
                Camera.cameraMode = CameraMode.Free;
            }
        }
7: Errors will pop up in your SphereCharacterControllerInput/ConvexTest/etc, but just change them in the same way we change them in the regular CharacterControllerInput class or the tank, as above
8: You should now be able to create your own camera update modes and functions.


Does this interest you or work for you? I ask because it's time consuming to change the camera modes every time I get a new branch :)

You may want to revisit how I use the "LockedUp" function/variable.

Thanks! Great work again!

Norbo
Site Admin
Posts: 4558
Joined: Tue Jul 04, 2006 4:45 am

Re: More extensible camera (changes in text)

Post by Norbo » Wed Sep 25, 2013 6:51 pm

I went ahead and did something similar for a new version. The Camera no longer has any control management within it. Various reusable CameraControlScheme implementations are now available.

Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest