/Rendering/Cameras/BaseCamera.cs
C# | 710 lines | 420 code | 73 blank | 217 comment | 15 complexity | 0f3a6da11b0025fc0151367f44d62b12 MD5 | raw file
Possible License(s): Apache-2.0
- using System.IO;
- using Delta.Engine;
- using Delta.Engine.Dynamic;
- using Delta.InputSystem;
- using Delta.Utilities;
- using Delta.Utilities.Datatypes;
- using Delta.Utilities.Helpers;
-
- namespace Delta.Rendering.Cameras
- {
- /// <summary>
- /// Abstract base camera class, provides some useful constants and properties
- /// that are commonly used in most camera classes. Is used as a dynamic
- /// module which updates itself directly after the input module updated, so
- /// the camera always have the newest input data to use for camera movement.
- /// </summary>
- public abstract class BaseCamera : DynamicModule, ISaveLoadBinary
- {
- #region Constants
- /// <summary>
- /// The current version of the implementation of this Camera class.
- /// </summary>
- private const int ImplementationVersion = 1;
-
- /// <summary>
- /// The near plane of the camera.
- /// </summary>
- public const float DefaultNearPlane = 0.45f; //0.75f;//1.0f;//0.1f;
-
- /// <summary>
- /// The far plane distance for the camera. See below for previous values.
- /// </summary>
- public const float DefaultFarPlane = 120.0f;
-
- /// <summary>
- /// The default FieldOfView is 60 degrees.
- /// </summary>
- public static readonly float DefaultFieldOfView =
- MathHelper.DegreeToRadians(60);
-
- /// <summary>
- /// The direction vector for the camera to show where is top.
- /// </summary>
- public static readonly Vector UpVector = Vector.UnitZ;
-
- /// <summary>
- /// Speed multiplier if "fast" key is pressed
- /// </summary>
- protected const float FastMultiplier = 5.0f;
-
- /// <summary>
- /// Speed multiplier if "slow" key is pressed
- /// </summary>
- protected const float SlowMultiplier = 0.2f;
- #endregion
-
- #region Current (Static)
- /// <summary>
- /// Currently active camera.
- /// </summary>
- public static BaseCamera Current
- {
- get
- {
- if (currentlyUsedCamera == null)
- {
- // We got no camera yet? Create one!
- currentlyUsedCamera = new LookAtCamera(new Vector(10, 10, 10));
- }
- return currentlyUsedCamera;
- }
- set
- {
- currentlyUsedCamera = value;
- }
- }
- #endregion
-
- #region Position (Public)
- /// <summary>
- /// The position of this camera.
- /// </summary>
- public virtual Vector Position
- {
- get
- {
- return position;
- }
- set
- {
- position = value;
- }
- }
- #endregion
-
- #region LookDirection (Public)
- /// <summary>
- /// The look direction
- /// </summary>
- public virtual Vector LookDirection
- {
- get
- {
- return lookDirection;
- }
- set
- {
- lookDirection = value;
- }
- }
- #endregion
-
- #region Target (Public)
- /// <summary>
- /// The camera target position
- /// </summary>
- public virtual Vector Target
- {
- get
- {
- return target;
- }
- set
- {
- target = value;
- }
- }
- #endregion
-
- #region Distance (Public)
- /// <summary>
- /// The distance between target position and camera position
- /// </summary>
- public virtual float Distance
- {
- get
- {
- // Make sure that we never have a distance of exactly 0, because
- // then the "View" matrix will be bad and "crash".
- return (distance != 0.0f)
- ? distance
- : MathHelper.Epsilon;
- }
- set
- {
- distance = value;
- }
- }
- #endregion
-
- #region Rotation (Public)
- /// <summary>
- /// Camera rotation vector in yaw pitch roll format
- /// Rotation Vector should be in degrees
- /// </summary>
- public virtual Vector Rotation
- {
- get
- {
- return rotation;
- }
- set
- {
- rotation = value;
- }
- }
- #endregion
-
- #region FieldOfView (Public)
- private float internalFieldOfView;
- /// <summary>
- /// Field of view, will cause the ProjectionMatrix to be updated the
- /// next time InternalRun is called.
- /// </summary>
- public float FieldOfView
- {
- get
- {
- return internalFieldOfView;
- }
- set
- {
- internalFieldOfView = value;
- needToUpdateProjectionMatrix = true;
- }
- }
- #endregion
-
- #region FarPlane (Public)
- private float internalFarPlane;
- /// <summary>
- /// Far plane
- /// </summary>
- public float FarPlane
- {
- get
- {
- return internalFarPlane;
- }
- set
- {
- internalFarPlane = value;
- needToUpdateProjectionMatrix = true;
- }
- }
- #endregion
-
- #region NearPlane (Public)
- private float internalNearPlane;
- /// <summary>
- /// Near plane
- /// </summary>
- public float NearPlane
- {
- get
- {
- return internalNearPlane;
- }
- set
- {
- internalNearPlane = value;
- needToUpdateProjectionMatrix = true;
- }
- }
- #endregion
-
- #region IsActive (Public)
- /// <summary>
- /// Determines if the camera is active or not.
- /// Only one camera may be active at a given time
- /// </summary>
- public bool IsActive
- {
- get
- {
- return (currentlyUsedCamera == this);
- }
- }
- #endregion
-
- #region IsLocked (Public)
- /// <summary>
- /// If set to true this camera should ignore all user input, which would
- /// otherwise be used to control the camera (via Mouse, Touch and Keys).
- /// </summary>
- public bool IsLocked
- {
- get;
- set;
- }
- #endregion
-
- #region ViewMatrix (Public)
- /// <summary>
- /// Public ViewMatrix for updating the screen properties.
- /// </summary>
- public Matrix ViewMatrix
- {
- get
- {
- return viewMatrix;
- }
- }
- #endregion
-
- #region ViewFrustum (Public)
- /// <summary>
- /// An array of 6 planes, one plane for each surface of the viewFrustum.
- /// Used order: 0=Left, 1=Right, 2=Top, 3=Bottom, 4=Near, 5=Far
- /// </summary>
- public Plane[] ViewFrustum
- {
- get;
- private set;
- }
- #endregion
-
- #region VertexLookupTable (Public)
- /// <summary>
- /// A 2 dimensional array used in checking boxes vs frustum.
- /// The first field in the array represents the 6 different planes.
- /// The second field represents X, Y, Z normal values of each field.
- /// 0 = xMax, 1 = yMax, 2 = zMax
- /// 3 = xMin, 4 = yMin, 5 = zMin.
- /// Example: If the bottom (3) plane Y (1) normal is positive (negative = 4)
- /// then VertexLookupTable[3,1] = 1.
- /// </summary>
- public int[,] VertexLookupTable
- {
- get;
- set;
- }
- #endregion
-
- #region Protected
-
- #region projectionMatrix (Protected)
- /// <summary>
- /// The projection matrix is only calculated once in the constructor
- /// and when the window resizes (because it depends on the AspectRatio).
- /// It is only used in UpdateViewMatrix, which will just set
- /// ScreenSpace.ViewProjection3D and ScreenSpace.ViewInverse.
- /// </summary>
- protected static Matrix projectionMatrix = Matrix.Identity;
- #endregion
-
- #region viewMatrix (Protected)
- /// <summary>
- /// Protected viewMatrix for updating the screen properties.
- /// </summary>
- protected static Matrix viewMatrix = Matrix.Identity;
- #endregion
-
- #region position (Protected)
- /// <summary>
- /// Position
- /// </summary>
- protected Vector position = Vector.Zero;
- #endregion
-
- #region lookDirection (Protected)
- /// <summary>
- /// Look direction
- /// </summary>
- protected Vector lookDirection = Vector.Zero;
- #endregion
-
- #region target (Protected)
- /// <summary>
- /// Target
- /// </summary>
- protected Vector target = Vector.Zero;
- #endregion
-
- #region distance (Protected)
- /// <summary>
- /// Distance
- /// </summary>
- protected float distance;
- #endregion
-
- #region rotation (Protected)
- /// <summary>
- /// Rotation
- /// </summary>
- protected Vector rotation = Vector.Zero;
- #endregion
-
- #region AlwaysNeedsUpdate (Protected)
- /// <summary>
- /// If this camera always needs to be updated.
- /// Mainly used for camera implementations which contain multiple cameras,
- /// so they are able to decide if they need to switch control.
- /// </summary>
- protected bool AlwaysNeedsUpdate
- {
- get;
- set;
- }
- #endregion
-
- #endregion
-
- #region Private
-
- #region currentlyUsedCamera (Private)
- /// <summary>
- /// Currently used camera
- /// </summary>
- private static BaseCamera currentlyUsedCamera;
- #endregion
-
- /// <summary>
- /// Helper flag to indicate we need to update the projection matrix because
- /// something like NearPlane, FarPlane or FieldOfView has changed.
- /// </summary>
- private static bool needToUpdateProjectionMatrix;
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create the camera
- /// </summary>
- /// <param name="setPosition">Camera initial position.</param>
- protected BaseCamera(Vector setPosition)
- : base("Camera", typeof(Input))
- {
- FieldOfView = DefaultFieldOfView;
- FarPlane = DefaultFarPlane;
- NearPlane = DefaultNearPlane;
-
- // At first we set the properties.
- Position = setPosition;
-
- ViewFrustum = new Plane[6];
- VertexLookupTable = new int[6,3];
-
- Application.Window.ResizeEvent += UpdateProjectionMatrix;
-
- // Update the projection matrix at first because we only update
- // it if something changed in the aspectRatio.
- UpdateProjectionMatrix();
-
- // Only update newest and last initialized camera!
- currentlyUsedCamera = this;
-
- SetupInputCommands();
- }
- #endregion
-
- #region Destructor
- /// <summary>
- /// Releases unmanaged resources and performs other cleanup operations
- /// before the <see cref="BaseCamera"/> is reclaimed by garbage collection.
- /// </summary>
- ~BaseCamera()
- {
- Dispose();
- }
- #endregion
-
- #region ISaveLoadBinary Members
- /// <summary>
- /// Load camera from BinaryStream.
- /// </summary>
- /// <param name="reader">Binary reader used for reading data.</param>
- public virtual void Load(BinaryReader reader)
- {
- // First read the implementation version
- int version = reader.ReadInt32();
- switch (version)
- {
- // Version 1
- case 1:
- // Now just read the saved values
- position.Load(reader);
- lookDirection.Load(reader);
- target.Load(reader);
- distance = reader.ReadSingle();
- rotation.Load(reader);
- FieldOfView = reader.ReadSingle();
- FarPlane = reader.ReadSingle();
- NearPlane = reader.ReadSingle();
- AlwaysNeedsUpdate = reader.ReadBoolean();
- IsLocked = reader.ReadBoolean();
- break;
-
- default:
- Log.InvalidVersionWarning(GetType().Name + ": " + Name,
- version, ImplementationVersion);
- break;
- }
- }
-
- /// <summary>
- /// Save camera data to BinaryWritter.
- /// </summary>
- /// <param name="dataWriter">Binary writer used for writing data.</param>
- public virtual void Save(BinaryWriter dataWriter)
- {
- // Save the implementation version first
- dataWriter.Write(ImplementationVersion);
-
- // and then all required values
- position.Save(dataWriter);
- lookDirection.Save(dataWriter);
- target.Save(dataWriter);
- dataWriter.Write(distance);
- rotation.Save(dataWriter);
- dataWriter.Write(FieldOfView);
- dataWriter.Write(FarPlane);
- dataWriter.Write(NearPlane);
- dataWriter.Write(AlwaysNeedsUpdate);
- dataWriter.Write(IsLocked);
- }
- #endregion
-
- #region Dispose (Public)
- /// <summary>
- /// Dispose the camera and most important remove it from the camera list.
- /// </summary>
- public override void Dispose()
- {
- // Kill module and everything that might be connected to this
- base.Dispose();
-
- if (currentlyUsedCamera == this)
- {
- currentlyUsedCamera = null;
- }
- // Remove it from event listening too
- Application.Window.ResizeEvent -= UpdateProjectionMatrix;
-
- // Also inform the module base class that this module can be removed from
- // the module list
- IsValid = false;
- }
- #endregion
-
- #region Activate (Public)
- /// <summary>
- /// Make the camera current (e.g. index zero on the camera list),
- /// so it'll be updated.
- /// </summary>
- /// <returns>
- /// True if this camera has been set as active one, false if it was active
- /// last frame.
- /// </returns>
- public bool Activate()
- {
- if (currentlyUsedCamera != this)
- {
- currentlyUsedCamera = this;
- return true;
- }
-
- return false;
- }
- #endregion
-
- #region UpdateProjectionMatrix (Public)
- /// <summary>
- /// Update projection matrix. In the base class this simply creates a
- /// normal perspective camera with the Screen aspect ratio. In overridden
- /// classes this can be changed (for example for isometric projection).
- /// </summary>
- public virtual void UpdateProjectionMatrix()
- {
- // Create a standard perspective matrix
- needToUpdateProjectionMatrix = false;
- projectionMatrix = Matrix.CreatePerspective(FieldOfView,
- ScreenSpace.AspectRatio, NearPlane, FarPlane);
- }
- #endregion
-
- #region Run (Public)
- /// <summary>
- /// Update the camera every tick directly after input updated its
- /// values so we always have the newest input data.
- /// </summary>
- public override void Run()
- {
- if (IsActive ||
- AlwaysNeedsUpdate)
- {
- InternalRun();
- }
- }
- #endregion
-
- #region Methods (Private)
-
- #region UpdateScreenProperties
- /// <summary>
- /// Update the screen properties ViewProjection3D and ViewInverse.
- /// Called from the InternalRun method.
- /// </summary>
- protected static void UpdateScreenProperties()
- {
- // Little bit ugly code, but nicely optimized :)
- Matrix.Multiply(ref viewMatrix, ref projectionMatrix,
- ref ScreenSpace.InternalViewProjection3D);
- Matrix.Invert(ref viewMatrix, ref ScreenSpace.InternalViewInverse);
- }
- #endregion
-
- #region SetupInputCommands
- /// <summary>
- /// Setup the input commands for controlling this camera.
- /// Can be derived in the camera implementing classes.
- /// Normally no input is attached.
- /// </summary>
- protected virtual void SetupInputCommands()
- {
- }
- #endregion
-
- #region UpdateViewMatrix
- /// <summary>
- /// Update view matrix of this camera.
- /// </summary>
- protected virtual void UpdateViewMatrix()
- {
- // Create a standard look at matrix
- viewMatrix =
- Matrix.CreateLookAt(position, position + LookDirection, UpVector);
- }
- #endregion
-
- #region BuildViewFrustum
- /// <summary>
- /// Build view frustum
- /// http://www.chadvernon.com/blog/resources/directx9/frustum-culling/
- /// </summary>
- protected virtual void BuildViewFrustum()
- {
- Matrix viewProjection3D = ScreenSpace.InternalViewProjection3D;
-
- // The order of left, right, near, far, top and bottom is supposedly
- // the best order to test the planes in. Very small difference either
- // way but it's better than nothing. This might change if the game is
- // top down for example, then it might be faster to check the top and
- // bottom planes earlier and to check far and near planes last.
-
- // Left plane
- ViewFrustum[0].Normal.X = viewProjection3D.M14 + viewProjection3D.M11;
- ViewFrustum[0].Normal.Y = viewProjection3D.M24 + viewProjection3D.M21;
- ViewFrustum[0].Normal.Z = viewProjection3D.M34 + viewProjection3D.M31;
- ViewFrustum[0].Distance = viewProjection3D.M44 + viewProjection3D.M41;
-
- // Right plane
- ViewFrustum[1].Normal.X = viewProjection3D.M14 - viewProjection3D.M11;
- ViewFrustum[1].Normal.Y = viewProjection3D.M24 - viewProjection3D.M21;
- ViewFrustum[1].Normal.Z = viewProjection3D.M34 - viewProjection3D.M31;
- ViewFrustum[1].Distance = viewProjection3D.M44 - viewProjection3D.M41;
-
- // Near plane
- ViewFrustum[2].Normal.X = viewProjection3D.M14 + viewProjection3D.M13;
- ViewFrustum[2].Normal.Y = viewProjection3D.M24 + viewProjection3D.M23;
- ViewFrustum[2].Normal.Z = viewProjection3D.M34 + viewProjection3D.M33;
- ViewFrustum[2].Distance = viewProjection3D.M44 + viewProjection3D.M43;
-
- // Far plane
- ViewFrustum[3].Normal.X = viewProjection3D.M14 - viewProjection3D.M13;
- ViewFrustum[3].Normal.Y = viewProjection3D.M24 - viewProjection3D.M23;
- ViewFrustum[3].Normal.Z = viewProjection3D.M34 - viewProjection3D.M33;
- ViewFrustum[3].Distance = viewProjection3D.M44 - viewProjection3D.M43;
-
- // Top plane
- ViewFrustum[4].Normal.X = viewProjection3D.M14 - viewProjection3D.M12;
- ViewFrustum[4].Normal.Y = viewProjection3D.M24 - viewProjection3D.M22;
- ViewFrustum[4].Normal.Z = viewProjection3D.M34 - viewProjection3D.M32;
- ViewFrustum[4].Distance = viewProjection3D.M44 - viewProjection3D.M42;
-
- // Bottom plane
- ViewFrustum[5].Normal.X = viewProjection3D.M14 + viewProjection3D.M12;
- ViewFrustum[5].Normal.Y = viewProjection3D.M24 + viewProjection3D.M22;
- ViewFrustum[5].Normal.Z = viewProjection3D.M34 + viewProjection3D.M32;
- ViewFrustum[5].Distance = viewProjection3D.M44 + viewProjection3D.M42;
-
- // Build the lookUpTable
- // 0 = xMax, 1 = yMax, 2 = zMax
- // 3 = xMin, 4 = yMin, 5 = zMin
- for (int i = 0; i < 6; i++)
- {
- // First we Normalize the plane
- // Normalization is only needed if we are testing against spheres,
- // currently all testing is done versus boxes so it's not needed.
- //viewFrustum[i].Normalize();
-
- // Check if the normal is >= 0 then set the index to max.
- // else we set the index to point at the min value.
- // we do this for all 3 values (X, Y, Z).
- if (ViewFrustum[i].Normal.X >= 0)
- {
- VertexLookupTable[i, 0] = 0;
- } // if
- else
- {
- VertexLookupTable[i, 0] = 3;
- } // else
- if (ViewFrustum[i].Normal.Y >= 0)
- {
- VertexLookupTable[i, 1] = 1;
- } // if
- else
- {
- VertexLookupTable[i, 1] = 4;
- } // else
- if (ViewFrustum[i].Normal.Z >= 0)
- {
- VertexLookupTable[i, 2] = 2;
- } // if
- else
- {
- VertexLookupTable[i, 2] = 5;
- } // else
- } // for
- }
- #endregion
-
- #region InternalRun
- /// <summary>
- /// Internal run method for overriding in the derived classes.
- /// </summary>
- protected virtual void InternalRun()
- {
- // The projection matrix is usually unchanged (except FarPlane,
- // NearPlane, FieldOfView or ScreenSpace.AspectRatio changes, e.g. when
- // the screen resolution changes).
- if (needToUpdateProjectionMatrix)
- {
- UpdateProjectionMatrix();
- }
-
- // The view matrix usually changes often (not all frames, but often).
- UpdateViewMatrix();
-
- // This means we also need to update the ViewProjection3D and ViewInverse
- UpdateScreenProperties();
-
- // And finally build the view frustum from that for culling!
- BuildViewFrustum();
- }
- #endregion
-
- #endregion
- }
- }