PageRenderTime 268ms CodeModel.GetById 121ms app.highlight 61ms RepoModel.GetById 80ms app.codeStats 0ms

/Rendering/Cameras/BaseCamera.cs

#
C# | 710 lines | 420 code | 73 blank | 217 comment | 15 complexity | 0f3a6da11b0025fc0151367f44d62b12 MD5 | raw file
  1using System.IO;
  2using Delta.Engine;
  3using Delta.Engine.Dynamic;
  4using Delta.InputSystem;
  5using Delta.Utilities;
  6using Delta.Utilities.Datatypes;
  7using Delta.Utilities.Helpers;
  8
  9namespace Delta.Rendering.Cameras
 10{
 11	/// <summary>
 12	/// Abstract base camera class, provides some useful constants and properties
 13	/// that are commonly used in most camera classes. Is used as a dynamic
 14	/// module which updates itself directly after the input module updated, so
 15	/// the camera always have the newest input data to use for camera movement.
 16	/// </summary>
 17	public abstract class BaseCamera : DynamicModule, ISaveLoadBinary
 18	{
 19		#region Constants
 20		/// <summary>
 21		/// The current version of the implementation of this Camera class.
 22		/// </summary>
 23		private const int ImplementationVersion = 1;
 24
 25		/// <summary>
 26		/// The near plane of the camera.
 27		/// </summary>
 28		public const float DefaultNearPlane = 0.45f; //0.75f;//1.0f;//0.1f;
 29
 30		/// <summary>
 31		/// The far plane distance for the camera. See below for previous values.
 32		/// </summary>
 33		public const float DefaultFarPlane = 120.0f;
 34
 35		/// <summary>
 36		/// The default FieldOfView is 60 degrees.
 37		/// </summary>
 38		public static readonly float DefaultFieldOfView =
 39			MathHelper.DegreeToRadians(60);
 40
 41		/// <summary>
 42		/// The direction vector for the camera to show where is top.
 43		/// </summary>
 44		public static readonly Vector UpVector = Vector.UnitZ;
 45
 46		/// <summary>
 47		/// Speed multiplier if "fast" key is pressed
 48		/// </summary>
 49		protected const float FastMultiplier = 5.0f;
 50
 51		/// <summary>
 52		/// Speed multiplier if "slow" key is pressed
 53		/// </summary>
 54		protected const float SlowMultiplier = 0.2f;
 55		#endregion
 56
 57		#region Current (Static)
 58		/// <summary>
 59		/// Currently active camera.
 60		/// </summary>
 61		public static BaseCamera Current
 62		{
 63			get
 64			{
 65				if (currentlyUsedCamera == null)
 66				{
 67					// We got no camera yet? Create one!
 68					currentlyUsedCamera = new LookAtCamera(new Vector(10, 10, 10));
 69				}
 70				return currentlyUsedCamera;
 71			}
 72			set
 73			{
 74				currentlyUsedCamera = value;
 75			}
 76		}
 77		#endregion
 78
 79		#region Position (Public)
 80		/// <summary>
 81		/// The position of this camera.
 82		/// </summary>
 83		public virtual Vector Position
 84		{
 85			get
 86			{
 87				return position;
 88			}
 89			set
 90			{
 91				position = value;
 92			}
 93		}
 94		#endregion
 95
 96		#region LookDirection (Public)
 97		/// <summary>
 98		/// The look direction
 99		/// </summary>
100		public virtual Vector LookDirection
101		{
102			get
103			{
104				return lookDirection;
105			}
106			set
107			{
108				lookDirection = value;
109			}
110		}
111		#endregion
112
113		#region Target (Public)
114		/// <summary>
115		/// The camera target position
116		/// </summary>
117		public virtual Vector Target
118		{
119			get
120			{
121				return target;
122			}
123			set
124			{
125				target = value;
126			}
127		}
128		#endregion
129
130		#region Distance (Public)
131		/// <summary>
132		/// The distance between target position and camera position
133		/// </summary>
134		public virtual float Distance
135		{
136			get
137			{
138				// Make sure that we never have a distance of exactly 0, because
139				// then the "View" matrix will be bad and "crash".
140				return (distance != 0.0f)
141				       	? distance
142				       	: MathHelper.Epsilon;
143			}
144			set
145			{
146				distance = value;
147			}
148		}
149		#endregion
150
151		#region Rotation (Public)
152		/// <summary>
153		/// Camera rotation vector in yaw pitch roll format
154		/// Rotation Vector should be in degrees
155		/// </summary>
156		public virtual Vector Rotation
157		{
158			get
159			{
160				return rotation;
161			}
162			set
163			{
164				rotation = value;
165			}
166		}
167		#endregion
168
169		#region FieldOfView (Public)
170		private float internalFieldOfView;
171		/// <summary>
172		/// Field of view, will cause the ProjectionMatrix to be updated the
173		/// next time InternalRun is called.
174		/// </summary>
175		public float FieldOfView
176		{
177			get
178			{
179				return internalFieldOfView;
180			}
181			set
182			{
183				internalFieldOfView = value;
184				needToUpdateProjectionMatrix = true;
185			}
186		}
187		#endregion
188
189		#region FarPlane (Public)
190		private float internalFarPlane;
191		/// <summary>
192		/// Far plane
193		/// </summary>
194		public float FarPlane
195		{
196			get
197			{
198				return internalFarPlane;
199			}
200			set
201			{
202				internalFarPlane = value;
203				needToUpdateProjectionMatrix = true;
204			}
205		}
206		#endregion
207
208		#region NearPlane (Public)
209		private float internalNearPlane;
210		/// <summary>
211		/// Near plane
212		/// </summary>
213		public float NearPlane
214		{
215			get
216			{
217				return internalNearPlane;
218			}
219			set
220			{
221				internalNearPlane = value;
222				needToUpdateProjectionMatrix = true;
223			}
224		}
225		#endregion
226
227		#region IsActive (Public)
228		/// <summary>
229		/// Determines if the camera is active or not.
230		/// Only one camera may be active at a given time
231		/// </summary>
232		public bool IsActive
233		{
234			get
235			{
236				return (currentlyUsedCamera == this);
237			}
238		}
239		#endregion
240
241		#region IsLocked (Public)
242		/// <summary>
243		/// If set to true this camera should ignore all user input, which would
244		/// otherwise be used to control the camera (via Mouse, Touch and Keys).
245		/// </summary>
246		public bool IsLocked
247		{
248			get;
249			set;
250		}
251		#endregion
252
253		#region ViewMatrix (Public)
254		/// <summary>
255		/// Public ViewMatrix for updating the screen properties.
256		/// </summary>
257		public Matrix ViewMatrix
258		{
259			get
260			{
261				return viewMatrix;
262			}
263		}
264		#endregion
265
266		#region ViewFrustum (Public)
267		/// <summary>
268		/// An array of 6 planes, one plane for each surface of the viewFrustum.
269		/// Used order: 0=Left, 1=Right, 2=Top, 3=Bottom, 4=Near, 5=Far
270		/// </summary>
271		public Plane[] ViewFrustum
272		{
273			get;
274			private set;
275		}
276		#endregion
277
278		#region VertexLookupTable (Public)
279		/// <summary>
280		/// A 2 dimensional array used in checking boxes vs frustum.
281		/// The first field in the array represents the 6 different planes.
282		/// The second field represents X, Y, Z normal values of each field.
283		/// 0 = xMax, 1 = yMax, 2 = zMax
284		/// 3 = xMin, 4 = yMin, 5 = zMin.
285		/// Example: If the bottom (3) plane Y (1) normal is positive (negative = 4)
286		/// then VertexLookupTable[3,1] = 1.
287		/// </summary>
288		public int[,] VertexLookupTable
289		{
290			get;
291			set;
292		}
293		#endregion
294
295		#region Protected
296
297		#region projectionMatrix (Protected)
298		/// <summary>
299		/// The projection matrix is only calculated once in the constructor
300		/// and when the window resizes (because it depends on the AspectRatio).
301		/// It is only used in UpdateViewMatrix, which will just set
302		/// ScreenSpace.ViewProjection3D and ScreenSpace.ViewInverse.
303		/// </summary>
304		protected static Matrix projectionMatrix = Matrix.Identity;
305		#endregion
306
307		#region viewMatrix (Protected)
308		/// <summary>
309		/// Protected viewMatrix for updating the screen properties.
310		/// </summary>
311		protected static Matrix viewMatrix = Matrix.Identity;
312		#endregion
313
314		#region position (Protected)
315		/// <summary>
316		/// Position
317		/// </summary>
318		protected Vector position = Vector.Zero;
319		#endregion
320
321		#region lookDirection (Protected)
322		/// <summary>
323		/// Look direction
324		/// </summary>
325		protected Vector lookDirection = Vector.Zero;
326		#endregion
327
328		#region target (Protected)
329		/// <summary>
330		/// Target
331		/// </summary>
332		protected Vector target = Vector.Zero;
333		#endregion
334
335		#region distance (Protected)
336		/// <summary>
337		/// Distance
338		/// </summary>
339		protected float distance;
340		#endregion
341
342		#region rotation (Protected)
343		/// <summary>
344		/// Rotation
345		/// </summary>
346		protected Vector rotation = Vector.Zero;
347		#endregion
348
349		#region AlwaysNeedsUpdate (Protected)
350		/// <summary>
351		/// If this camera always needs to be updated.
352		/// Mainly used for camera implementations which contain multiple cameras,
353		/// so they are able to decide if they need to switch control.
354		/// </summary>
355		protected bool AlwaysNeedsUpdate
356		{
357			get;
358			set;
359		}
360		#endregion
361
362		#endregion
363
364		#region Private
365
366		#region currentlyUsedCamera (Private)
367		/// <summary>
368		/// Currently used camera
369		/// </summary>
370		private static BaseCamera currentlyUsedCamera;
371		#endregion
372
373		/// <summary>
374		/// Helper flag to indicate we need to update the projection matrix because
375		/// something like NearPlane, FarPlane or FieldOfView has changed.
376		/// </summary>
377		private static bool needToUpdateProjectionMatrix;
378
379		#endregion
380
381		#region Constructors
382		/// <summary>
383		/// Create the camera
384		/// </summary>
385		/// <param name="setPosition">Camera initial position.</param>
386		protected BaseCamera(Vector setPosition)
387			: base("Camera", typeof(Input))
388		{
389			FieldOfView = DefaultFieldOfView;
390			FarPlane = DefaultFarPlane;
391			NearPlane = DefaultNearPlane;
392
393			// At first we set the properties.
394			Position = setPosition;
395
396			ViewFrustum = new Plane[6];
397			VertexLookupTable = new int[6,3];
398
399			Application.Window.ResizeEvent += UpdateProjectionMatrix;
400
401			// Update the projection matrix at first because we only update
402			// it if something changed in the aspectRatio.
403			UpdateProjectionMatrix();
404
405			// Only update newest and last initialized camera!
406			currentlyUsedCamera = this;
407
408			SetupInputCommands();
409		}
410		#endregion
411
412		#region Destructor
413		/// <summary>
414		/// Releases unmanaged resources and performs other cleanup operations
415		/// before the <see cref="BaseCamera"/> is reclaimed by garbage collection.
416		/// </summary>
417		~BaseCamera()
418		{
419			Dispose();
420		}
421		#endregion
422
423		#region ISaveLoadBinary Members
424		/// <summary>
425		/// Load camera from BinaryStream.
426		/// </summary>
427		/// <param name="reader">Binary reader used for reading data.</param>
428		public virtual void Load(BinaryReader reader)
429		{
430			// First read the implementation version
431			int version = reader.ReadInt32();
432			switch (version)
433			{
434					// Version 1
435				case 1:
436					// Now just read the saved values
437					position.Load(reader);
438					lookDirection.Load(reader);
439					target.Load(reader);
440					distance = reader.ReadSingle();
441					rotation.Load(reader);
442					FieldOfView = reader.ReadSingle();
443					FarPlane = reader.ReadSingle();
444					NearPlane = reader.ReadSingle();
445					AlwaysNeedsUpdate = reader.ReadBoolean();
446					IsLocked = reader.ReadBoolean();
447					break;
448
449				default:
450					Log.InvalidVersionWarning(GetType().Name + ": " + Name,
451						version, ImplementationVersion);
452					break;
453			}
454		}
455
456		/// <summary>
457		/// Save camera data to BinaryWritter.
458		/// </summary>
459		/// <param name="dataWriter">Binary writer used for writing data.</param>
460		public virtual void Save(BinaryWriter dataWriter)
461		{
462			// Save the implementation version first
463			dataWriter.Write(ImplementationVersion);
464
465			// and then all required values
466			position.Save(dataWriter);
467			lookDirection.Save(dataWriter);
468			target.Save(dataWriter);
469			dataWriter.Write(distance);
470			rotation.Save(dataWriter);
471			dataWriter.Write(FieldOfView);
472			dataWriter.Write(FarPlane);
473			dataWriter.Write(NearPlane);
474			dataWriter.Write(AlwaysNeedsUpdate);
475			dataWriter.Write(IsLocked);
476		}
477		#endregion
478
479		#region Dispose (Public)
480		/// <summary>
481		/// Dispose the camera and most important remove it from the camera list.
482		/// </summary>
483		public override void Dispose()
484		{
485			// Kill module and everything that might be connected to this
486			base.Dispose();
487
488			if (currentlyUsedCamera == this)
489			{
490				currentlyUsedCamera = null;
491			}
492			// Remove it from event listening too
493			Application.Window.ResizeEvent -= UpdateProjectionMatrix;
494
495			// Also inform the module base class that this module can be removed from
496			// the module list
497			IsValid = false;
498		}
499		#endregion
500
501		#region Activate (Public)
502		/// <summary>
503		/// Make the camera current (e.g. index zero on the camera list),
504		/// so it'll be updated.
505		/// </summary>
506		/// <returns>
507		/// True if this camera has been set as active one, false if it was active
508		/// last frame.
509		/// </returns>
510		public bool Activate()
511		{
512			if (currentlyUsedCamera != this)
513			{
514				currentlyUsedCamera = this;
515				return true;
516			}
517
518			return false;
519		}
520		#endregion
521
522		#region UpdateProjectionMatrix (Public)
523		/// <summary>
524		/// Update projection matrix. In the base class this simply creates a
525		/// normal perspective camera with the Screen aspect ratio. In overridden
526		/// classes this can be changed (for example for isometric projection).
527		/// </summary>
528		public virtual void UpdateProjectionMatrix()
529		{
530			// Create a standard perspective matrix
531			needToUpdateProjectionMatrix = false;
532			projectionMatrix = Matrix.CreatePerspective(FieldOfView,
533				ScreenSpace.AspectRatio, NearPlane, FarPlane);
534		}
535		#endregion
536
537		#region Run (Public)
538		/// <summary>
539		/// Update the camera every tick directly after input updated its
540		/// values so we always have the newest input data.
541		/// </summary>
542		public override void Run()
543		{
544			if (IsActive ||
545			    AlwaysNeedsUpdate)
546			{
547				InternalRun();
548			}
549		}
550		#endregion
551
552		#region Methods (Private)
553
554		#region UpdateScreenProperties
555		/// <summary>
556		/// Update the screen properties ViewProjection3D and ViewInverse.
557		/// Called from the InternalRun method.
558		/// </summary>
559		protected static void UpdateScreenProperties()
560		{
561			// Little bit ugly code, but nicely optimized :)
562			Matrix.Multiply(ref viewMatrix, ref projectionMatrix,
563				ref ScreenSpace.InternalViewProjection3D);
564			Matrix.Invert(ref viewMatrix, ref ScreenSpace.InternalViewInverse);
565		}
566		#endregion
567
568		#region SetupInputCommands
569		/// <summary>
570		/// Setup the input commands for controlling this camera.
571		/// Can be derived in the camera implementing classes.
572		/// Normally no input is attached.
573		/// </summary>
574		protected virtual void SetupInputCommands()
575		{
576		}
577		#endregion
578
579		#region UpdateViewMatrix
580		/// <summary>
581		/// Update view matrix of this camera.
582		/// </summary>
583		protected virtual void UpdateViewMatrix()
584		{
585			// Create a standard look at matrix
586			viewMatrix =
587				Matrix.CreateLookAt(position, position + LookDirection, UpVector);
588		}
589		#endregion
590
591		#region BuildViewFrustum
592		/// <summary>
593		/// Build view frustum
594		/// http://www.chadvernon.com/blog/resources/directx9/frustum-culling/
595		/// </summary>
596		protected virtual void BuildViewFrustum()
597		{
598			Matrix viewProjection3D = ScreenSpace.InternalViewProjection3D;
599
600			// The order of left, right, near, far, top and bottom is supposedly
601			// the best order to test the planes in. Very small difference either
602			// way but it's better than nothing. This might change if the game is
603			// top down for example, then it might be faster to check the top and
604			// bottom planes earlier and to check far and near planes last.
605
606			// Left plane
607			ViewFrustum[0].Normal.X = viewProjection3D.M14 + viewProjection3D.M11;
608			ViewFrustum[0].Normal.Y = viewProjection3D.M24 + viewProjection3D.M21;
609			ViewFrustum[0].Normal.Z = viewProjection3D.M34 + viewProjection3D.M31;
610			ViewFrustum[0].Distance = viewProjection3D.M44 + viewProjection3D.M41;
611
612			// Right plane
613			ViewFrustum[1].Normal.X = viewProjection3D.M14 - viewProjection3D.M11;
614			ViewFrustum[1].Normal.Y = viewProjection3D.M24 - viewProjection3D.M21;
615			ViewFrustum[1].Normal.Z = viewProjection3D.M34 - viewProjection3D.M31;
616			ViewFrustum[1].Distance = viewProjection3D.M44 - viewProjection3D.M41;
617
618			// Near plane
619			ViewFrustum[2].Normal.X = viewProjection3D.M14 + viewProjection3D.M13;
620			ViewFrustum[2].Normal.Y = viewProjection3D.M24 + viewProjection3D.M23;
621			ViewFrustum[2].Normal.Z = viewProjection3D.M34 + viewProjection3D.M33;
622			ViewFrustum[2].Distance = viewProjection3D.M44 + viewProjection3D.M43;
623
624			// Far plane
625			ViewFrustum[3].Normal.X = viewProjection3D.M14 - viewProjection3D.M13;
626			ViewFrustum[3].Normal.Y = viewProjection3D.M24 - viewProjection3D.M23;
627			ViewFrustum[3].Normal.Z = viewProjection3D.M34 - viewProjection3D.M33;
628			ViewFrustum[3].Distance = viewProjection3D.M44 - viewProjection3D.M43;
629
630			// Top plane
631			ViewFrustum[4].Normal.X = viewProjection3D.M14 - viewProjection3D.M12;
632			ViewFrustum[4].Normal.Y = viewProjection3D.M24 - viewProjection3D.M22;
633			ViewFrustum[4].Normal.Z = viewProjection3D.M34 - viewProjection3D.M32;
634			ViewFrustum[4].Distance = viewProjection3D.M44 - viewProjection3D.M42;
635
636			// Bottom plane
637			ViewFrustum[5].Normal.X = viewProjection3D.M14 + viewProjection3D.M12;
638			ViewFrustum[5].Normal.Y = viewProjection3D.M24 + viewProjection3D.M22;
639			ViewFrustum[5].Normal.Z = viewProjection3D.M34 + viewProjection3D.M32;
640			ViewFrustum[5].Distance = viewProjection3D.M44 + viewProjection3D.M42;
641
642			// Build the lookUpTable
643			// 0 = xMax, 1 = yMax, 2 = zMax
644			// 3 = xMin, 4 = yMin, 5 = zMin
645			for (int i = 0; i < 6; i++)
646			{
647				// First we Normalize the plane
648				// Normalization is only needed if we are testing against spheres,
649				// currently all testing is done versus boxes so it's not needed.
650				//viewFrustum[i].Normalize();
651
652				// Check if the normal is >= 0 then set the index to max.
653				// else we set the index to point at the min value.
654				// we do this for all 3 values (X, Y, Z).
655				if (ViewFrustum[i].Normal.X >= 0)
656				{
657					VertexLookupTable[i, 0] = 0;
658				} // if
659				else
660				{
661					VertexLookupTable[i, 0] = 3;
662				} // else
663				if (ViewFrustum[i].Normal.Y >= 0)
664				{
665					VertexLookupTable[i, 1] = 1;
666				} // if
667				else
668				{
669					VertexLookupTable[i, 1] = 4;
670				} // else
671				if (ViewFrustum[i].Normal.Z >= 0)
672				{
673					VertexLookupTable[i, 2] = 2;
674				} // if
675				else
676				{
677					VertexLookupTable[i, 2] = 5;
678				} // else
679			} // for
680		}
681		#endregion
682
683		#region InternalRun
684		/// <summary>
685		/// Internal run method for overriding in the derived classes.
686		/// </summary>
687		protected virtual void InternalRun()
688		{
689			// The projection matrix is usually unchanged (except FarPlane,
690			// NearPlane, FieldOfView or ScreenSpace.AspectRatio changes, e.g. when
691			// the screen resolution changes).
692			if (needToUpdateProjectionMatrix)
693			{
694				UpdateProjectionMatrix();
695			}
696
697			// The view matrix usually changes often (not all frames, but often).
698			UpdateViewMatrix();
699
700			// This means we also need to update the ViewProjection3D and ViewInverse
701			UpdateScreenProperties();
702
703			// And finally build the view frustum from that for culling!
704			BuildViewFrustum();
705		}
706		#endregion
707
708		#endregion
709	}
710}