/Rendering/Cameras/LookAtCamera.cs
C# | 503 lines | 298 code | 54 blank | 151 comment | 6 complexity | ca26169872b6728d425c2648e779351c MD5 | raw file
Possible License(s): Apache-2.0
- //#define USE_COMMANDS
-
- using System.IO;
- using Delta.Engine;
- using Delta.InputSystem;
- using Delta.InputSystem.Devices;
- using Delta.Utilities;
- using Delta.Utilities.Datatypes;
- using Delta.Utilities.Helpers;
- using NUnit.Framework;
-
- namespace Delta.Rendering.Cameras
- {
- /// <summary>
- /// 3D camera that support setting of position and target.
- /// </summary>
- /// <remarks>
- /// With this camera you can apply behaviour of looking at given target or
- /// move around given target.
- /// </remarks>
- public class LookAtCamera : BaseCamera
- {
- #region Constants
-
- #region ImplementationVersion
- /// <summary>
- /// The current version of the implementation of this Camera class.
- /// </summary>
- private const int ImplementationVersion = 1;
- #endregion
-
- #region MPS
- /// <summary>
- /// The Move speed Per Second.
- /// </summary>
- private const float MPS = 100.0f;
- #endregion
-
- #region MouseSpeedFactor
- /// <summary>
- /// Speed multiplier for mouse control
- /// </summary>
- private const float MouseSpeedFactor = 2.75f;
- #endregion
-
- #region KeyboardSpeedFactor
- /// <summary>
- /// Speed multiplier for keyboard control
- /// </summary>
- private const float KeyboardSpeedFactor = 1.0f;
- #endregion
-
- #region MinPitchRotation
- /// <summary>
- /// Limit for looking down
- /// </summary>
- private const float MinPitchRotation = -90 + 1; //2;
- #endregion
-
- #region MaxPitchRotation
- /// <summary>
- /// Limit for looking up
- /// </summary>
- private const float MaxPitchRotation = +90 - 1; //2;
- #endregion
-
- #region DefaultLookAtCamPosition
- /// <summary>
- /// A default position which can be used if the current camera position
- /// value isn't known (in the constructor).
- /// </summary>
- public static readonly Vector DefaultLookAtCamPosition =
- new Vector(0.0f, -5.0f, 5.0f);
- #endregion
-
- #region DefaultTargetPosition
- /// <summary>
- /// A position to a default target twhich can be used if the current camera
- /// target position value isn't known (in the constructor).
- /// </summary>
- public static readonly Vector DefaultTargetPosition = Vector.Zero;
- #endregion
-
- #endregion
-
- #region Public
-
- #region Position (override)
- /// <summary>
- /// The camera position.
- /// </summary>
- public override Vector Position
- {
- get
- {
- return position;
- }
- set
- {
- // At first directly set the new position
- position = value;
- SetPositionValues();
- }
- }
- #endregion
-
- #region Target (override)
- /// <summary>
- /// Target
- /// </summary>
- public override Vector Target
- {
- get
- {
- return target;
- }
- set
- {
- // Set the new target (position)
- target = value;
- SetPositionValues();
- }
- }
- #endregion
-
- #region Distance (override)
- /// <summary>
- /// The distance between target position and camera position
- /// </summary>
- public override float Distance
- {
- get
- {
- return base.Distance;
- }
- set
- {
- base.Distance = value;
- UpdatePosition();
- } // set
- }
-
- // Distance
- #endregion
-
- #endregion
-
- #region Private
- /// <summary>
- /// Used to be able to reset the position to constructor time
- /// </summary>
- private Vector defaultPosition;
-
- /// <summary>
- /// Used to be able to reset the target to constructor time
- /// </summary>
- private Vector defaultTarget;
-
- /// <summary>
- /// Flag determining if shift is pressed.
- /// Used to make the speed multiplier slower.
- /// </summary>
- private bool shiftPressed;
-
- /// <summary>
- /// Flag determining if control is pressed.
- /// Used to make the speed multiplier faster.
- /// </summary>
- private bool ctrlPressed;
-
- #region SpeedMultiplier
- /// <summary>
- /// Speed multiplier for every movement
- /// </summary>
- private float SpeedMultiplier
- {
- get
- {
- return shiftPressed
- ? SlowMultiplier
- : (ctrlPressed
- ? FastMultiplier
- : 1.0f);
- }
- }
- #endregion
-
- #region MoveSpeed
- /// <summary>
- /// Movespeed per second over time.
- /// </summary>
- private static float MoveSpeed
- {
- get
- {
- return MPS * Time.Delta;
- } // get
- }
- #endregion
-
- #endregion
-
- #region Constructor
- /// <summary>
- /// Create look at camera
- /// </summary>
- /// <param name="setPosition">Camera initial position.</param>
- public LookAtCamera(Vector setPosition)
- : this(setPosition, DefaultTargetPosition)
- {
- }
-
- /// <summary>
- /// Create look at camera
- /// </summary>
- /// <param name="setPosition">Camera initial position.</param>
- /// <param name="setTarget">Camera initial target.</param>
- public LookAtCamera(Vector setPosition, Vector setTarget)
- : base(setPosition)
- {
- target = setTarget;
- SetPositionValues();
-
- // Store defaults
- defaultPosition = setPosition;
- defaultTarget = setTarget;
- shiftPressed = false;
- ctrlPressed = false;
- }
- #endregion
-
- #region UpdateViewMatrix (override)
- /// <summary>
- /// Update view matrix
- /// </summary>
- protected override void UpdateViewMatrix()
- {
- // Create a standard look at matrix
- viewMatrix = Matrix.CreateLookAt(Position, Target, UpVector);
- }
- #endregion
-
- #region SetupInputCommands (override)
- /// <summary>
- /// Setup the connection between input commands and camera movement.
- /// </summary>
- protected override void SetupInputCommands()
- {
- // Make sure the commands are used (to allow quitting with Escape)
- if (Input.Commands == null)
- {
- return;
- }
-
- }
- #endregion
-
- #region Zoom
- /// <summary>
- /// Zoom in or out. if respectFPS is set to true, Time.Delta will be taken
- /// into account. E.g.: set this to true for hold-able buttons.
- /// </summary>
- /// <param name="zoomIn">if set to <c>true</c> [zoom in].</param>
- public void Zoom(bool zoomIn, bool respectFps)
- {
- if (IsLocked)
- {
- return;
- }
- float zoomSpeed = MathHelper.Max(Distance, 0.1f) * 0.1f *
- SpeedMultiplier * (respectFps
- ? (Time.Delta * 10f)
- : 1f);
- // Limit zoomSpeed, else we get into millions too quickly
- zoomSpeed = MathHelper.Min(zoomSpeed, 100000 * Time.Delta);
- Distance += zoomIn
- ? -zoomSpeed
- : zoomSpeed;
- }
- #endregion
-
-
- #region RotateHorizontal
- /// <summary>
- /// Rotate horizontal
- /// </summary>
- /// <param name="left">if set to <c>true</c> [left].</param>
- public void RotateHorizontal(bool left)
- {
- if (IsLocked)
- {
- return;
- }
- rotation.X +=
- MPS * 0.05f * KeyboardSpeedFactor *
- (left
- ? -SpeedMultiplier
- : SpeedMultiplier) * (Time.Delta * 10f);
- UpdatePosition();
- }
- #endregion
-
- #region RotateVertical
- /// <summary>
- /// Rotate vertical
- /// </summary>
- /// <param name="up">if set to <c>true</c> [up].</param>
- public void RotateVertical(bool up)
- {
- if (IsLocked)
- {
- return;
- }
- rotation.Y +=
- MPS * 0.05f * KeyboardSpeedFactor *
- (up
- ? SpeedMultiplier
- : -SpeedMultiplier) * (Time.Delta * 10f);
- UpdatePosition();
- }
- #endregion
-
- #region ResetCamera
- /// <summary>
- /// Resets camera position/rotation to values at constructor time
- /// </summary>
- public void ResetCamera()
- {
- position = defaultPosition;
- target = defaultTarget;
- SetPositionValues();
-
- UpdatePosition();
- }
- #endregion
-
- #region UpdatePosition
- /// <summary>
- /// Update position
- /// </summary>
- private void UpdatePosition()
- {
- // Always clamp the up-down rotation so we don't rotate "over" the
- // up-vector to avoid the "flipping problem"
- rotation.Y = MathHelper.Clamp(rotation.Y, MinPitchRotation,
- MaxPitchRotation);
-
- Matrix rotationMatrix = Matrix.Identity;
- // Matrix.CreateRotationY(Z) - We don't need here the "Roll" rotation
- Matrix xRotation = Matrix.CreateRotationX(rotation.Y);
- Matrix zRotation = Matrix.CreateRotationZ(rotation.X);
- Matrix.Multiply(ref xRotation, ref zRotation, ref rotationMatrix);
-
- // We have to invert here the look vector, because to compute the
- // position we have to "go" from the target to the position instead of
- // "looking" case which is the other case (-> look from position to the
- // target)
- Vector lookVector = new Vector(0f, Distance, 0f);
- Vector.TransformNormal(ref lookVector, ref rotationMatrix, ref position);
- Vector.Add(ref position, ref target, out position);
- }
- #endregion
-
- #region SetPositionValues
- /// <summary>
- /// Set position values
- /// Used by the Position and Target properties.
- /// </summary>
- private void SetPositionValues()
- {
- // and we have to clear the current rotation now, because the new
- // position is already "final"
-
- // Now we still need to know the new direction we have to look to the
- // (old) target
- Vector.Subtract(ref target, ref position, out lookDirection);
- // and store the new distance to (in base because we want to avoid the
- // unnecessary UpdatePosition() call from Distance here)
- distance = lookDirection.Length;
- lookDirection.Normalize();
-
- // Position.Z = sin(Rotation.X) -> see trigonometrical functions
- // Note: we consider at the moment only the x rotation, because we don't
- // have any rotation on y or z (with default values)
- rotation.Y = MathHelper.Asin(-LookDirection.Z);
- rotation.X = -MathHelper.Atan(-LookDirection.X, -lookDirection.Y);
- }
- #endregion
-
- #region ISaveLoadBinary Members
-
- #region Save (override)
- /// <summary>
- /// Override saving data from BinaryStream.
- /// </summary>
- /// <param name="dataWriter">Binary writer used for writing data.</param>
- public override void Save(BinaryWriter dataWriter)
- {
- base.Save(dataWriter);
-
- // Save the implementation version first
- dataWriter.Write(ImplementationVersion);
-
- // and then all required values
- defaultPosition.Save(dataWriter);
- defaultTarget.Save(dataWriter);
- }
- #endregion
-
- #region Load (override)
- /// <summary>
- /// Override loading data from BinaryStream.
- /// </summary>
- /// <param name="reader">Binary reader used for reading data.</param>
- public override void Load(BinaryReader reader)
- {
- base.Load(reader);
-
- // First read the implementation version
- int version = reader.ReadInt32();
- switch (version)
- {
- // Version 1
- case 1:
- // Now just read the saved values
- defaultPosition = new Vector(reader);
- defaultTarget = new Vector(reader);
- break;
-
- default:
- Log.InvalidVersionWarning(GetType().Name + ": " + Name,
- version, ImplementationVersion);
- break;
- } // switch
- }
- #endregion
-
- #endregion
-
- #region Tests
- /// <summary>
- /// Tests
- /// </summary>
- internal class LookAtCameraTests
- {
- #region TestSaveAndLoad (LongRunning)
- /// <summary>
- /// Test save and load functionality of the Graph class
- /// </summary>
- [Test, Category("LongRunning")]
- public void TestSaveAndLoad()
- {
- // Creation of the graph
- LookAtCamera lookAtCamera = new LookAtCamera(new Vector(3, -6, 8))
- {
- // BaseCamera
- LookDirection = new Vector(-1, 2, -1),
- Target = new Vector(3, 4, 1),
- Rotation = new Vector(3, 1, 0),
- FieldOfView = 65,
- FarPlane = 205,
- NearPlane = 0.15f,
- AlwaysNeedsUpdate = true,
- IsLocked = true,
- // LookAtCamera
- defaultPosition = new Vector(0, -5, 9),
- defaultTarget = new Vector(3, 3, 1),
- };
-
- // Saving
- MemoryStream savedStream = new MemoryStream();
- BinaryWriter writer = new BinaryWriter(savedStream);
- lookAtCamera.Save(writer);
- writer.Flush();
- writer = null;
-
- // Loading
- savedStream.Position = 0;
- BinaryReader reader = new BinaryReader(savedStream);
- LookAtCamera loadedCamera = new LookAtCamera(Vector.Zero);
- loadedCamera.Load(reader);
-
- // Checking
- // BaseCamera values
- Assert.Equal(loadedCamera.LookDirection, lookAtCamera.LookDirection);
- Assert.Equal(loadedCamera.Target, lookAtCamera.Target);
- Assert.Equal(loadedCamera.Rotation, lookAtCamera.Rotation);
- Assert.Equal(loadedCamera.FieldOfView, lookAtCamera.FieldOfView);
- Assert.Equal(loadedCamera.FarPlane, lookAtCamera.FarPlane);
- Assert.Equal(loadedCamera.NearPlane, lookAtCamera.NearPlane);
- Assert.Equal(loadedCamera.AlwaysNeedsUpdate,
- lookAtCamera.AlwaysNeedsUpdate);
- Assert.Equal(loadedCamera.IsLocked, lookAtCamera.IsLocked);
- // LookAtCamera values
- Assert.Equal(loadedCamera.defaultPosition,
- lookAtCamera.defaultPosition);
- Assert.Equal(loadedCamera.defaultTarget, lookAtCamera.defaultTarget);
- }
- #endregion
- }
- #endregion
- }
- }