PageRenderTime 185ms CodeModel.GetById 80ms app.highlight 15ms RepoModel.GetById 86ms app.codeStats 0ms

/Rendering/Cameras/LookAtCamera.cs

#
C# | 503 lines | 298 code | 54 blank | 151 comment | 6 complexity | ca26169872b6728d425c2648e779351c MD5 | raw file
  1//#define USE_COMMANDS
  2
  3using System.IO;
  4using Delta.Engine;
  5using Delta.InputSystem;
  6using Delta.InputSystem.Devices;
  7using Delta.Utilities;
  8using Delta.Utilities.Datatypes;
  9using Delta.Utilities.Helpers;
 10using NUnit.Framework;
 11
 12namespace Delta.Rendering.Cameras
 13{
 14	/// <summary>
 15	/// 3D camera that support setting of position and target.
 16	/// </summary>
 17	/// <remarks>
 18	/// With this camera you can apply behaviour of looking at given target or
 19	/// move around given target.
 20	/// </remarks>
 21	public class LookAtCamera : BaseCamera
 22	{
 23		#region Constants
 24
 25		#region ImplementationVersion
 26		/// <summary>
 27		/// The current version of the implementation of this Camera class.
 28		/// </summary>
 29		private const int ImplementationVersion = 1;
 30		#endregion
 31
 32		#region MPS
 33		/// <summary>
 34		/// The Move speed Per Second.
 35		/// </summary>
 36		private const float MPS = 100.0f;
 37		#endregion
 38
 39		#region MouseSpeedFactor
 40		/// <summary>
 41		/// Speed multiplier for mouse control
 42		/// </summary>
 43		private const float MouseSpeedFactor = 2.75f;
 44		#endregion
 45
 46		#region KeyboardSpeedFactor
 47		/// <summary>
 48		/// Speed multiplier for keyboard control
 49		/// </summary>
 50		private const float KeyboardSpeedFactor = 1.0f;
 51		#endregion
 52
 53		#region MinPitchRotation
 54		/// <summary>
 55		/// Limit for looking down
 56		/// </summary>
 57		private const float MinPitchRotation = -90 + 1; //2;
 58		#endregion
 59
 60		#region MaxPitchRotation
 61		/// <summary>
 62		/// Limit for looking up
 63		/// </summary>
 64		private const float MaxPitchRotation = +90 - 1; //2;
 65		#endregion
 66
 67		#region DefaultLookAtCamPosition
 68		/// <summary>
 69		/// A default position which can be used if the current camera position
 70		/// value isn't known (in the constructor).
 71		/// </summary>
 72		public static readonly Vector DefaultLookAtCamPosition =
 73			new Vector(0.0f, -5.0f, 5.0f);
 74		#endregion
 75
 76		#region DefaultTargetPosition
 77		/// <summary>
 78		/// A position to a default target twhich can be used if the current camera
 79		/// target position value isn't known (in the constructor).
 80		/// </summary>
 81		public static readonly Vector DefaultTargetPosition = Vector.Zero;
 82		#endregion
 83
 84		#endregion
 85
 86		#region Public
 87
 88		#region Position (override)
 89		/// <summary>
 90		/// The camera position.
 91		/// </summary>
 92		public override Vector Position
 93		{
 94			get
 95			{
 96				return position;
 97			}
 98			set
 99			{
100				// At first directly set the new position
101				position = value;
102				SetPositionValues();
103			}
104		}
105		#endregion
106
107		#region Target (override)
108		/// <summary>
109		/// Target
110		/// </summary>
111		public override Vector Target
112		{
113			get
114			{
115				return target;
116			}
117			set
118			{
119				// Set the new target (position)
120				target = value;
121				SetPositionValues();
122			}
123		}
124		#endregion
125
126		#region Distance (override)
127		/// <summary>
128		/// The distance between target position and camera position
129		/// </summary>
130		public override float Distance
131		{
132			get
133			{
134				return base.Distance;
135			}
136			set
137			{
138				base.Distance = value;
139				UpdatePosition();
140			} // set
141		}
142
143		// Distance
144		#endregion
145
146		#endregion
147
148		#region Private
149		/// <summary>
150		/// Used to be able to reset the position to constructor time
151		/// </summary>
152		private Vector defaultPosition;
153
154		/// <summary>
155		/// Used to be able to reset the target to constructor time
156		/// </summary>
157		private Vector defaultTarget;
158
159		/// <summary>
160		/// Flag determining if shift is pressed.
161		/// Used to make the speed multiplier slower.
162		/// </summary>
163		private bool shiftPressed;
164
165		/// <summary>
166		/// Flag determining if control is pressed.
167		/// Used to make the speed multiplier faster.
168		/// </summary>
169		private bool ctrlPressed;
170
171		#region SpeedMultiplier
172		/// <summary>
173		/// Speed multiplier for every movement
174		/// </summary>
175		private float SpeedMultiplier
176		{
177			get
178			{
179				return shiftPressed
180				       	? SlowMultiplier
181				       	: (ctrlPressed
182				       	   	? FastMultiplier
183				       	   	: 1.0f);
184			}
185		}
186		#endregion
187
188		#region MoveSpeed
189		/// <summary>
190		/// Movespeed per second over time.
191		/// </summary>
192		private static float MoveSpeed
193		{
194			get
195			{
196				return MPS * Time.Delta;
197			} // get
198		}
199		#endregion
200
201		#endregion
202
203		#region Constructor
204		/// <summary>
205		/// Create look at camera
206		/// </summary>
207		/// <param name="setPosition">Camera initial position.</param>
208		public LookAtCamera(Vector setPosition)
209			: this(setPosition, DefaultTargetPosition)
210		{
211		}
212
213		/// <summary>
214		/// Create look at camera
215		/// </summary>
216		/// <param name="setPosition">Camera initial position.</param>
217		/// <param name="setTarget">Camera initial target.</param>
218		public LookAtCamera(Vector setPosition, Vector setTarget)
219			: base(setPosition)
220		{
221			target = setTarget;
222			SetPositionValues();
223
224			// Store defaults
225			defaultPosition = setPosition;
226			defaultTarget = setTarget;
227			shiftPressed = false;
228			ctrlPressed = false;
229		}
230		#endregion
231
232		#region UpdateViewMatrix (override)
233		/// <summary>
234		/// Update view matrix
235		/// </summary>
236		protected override void UpdateViewMatrix()
237		{
238			// Create a standard look at matrix
239			viewMatrix = Matrix.CreateLookAt(Position, Target, UpVector);
240		}
241		#endregion
242
243		#region SetupInputCommands (override)
244		/// <summary>
245		/// Setup the connection between input commands and camera movement.
246		/// </summary>
247		protected override void SetupInputCommands()
248		{
249			// Make sure the commands are used (to allow quitting with Escape)
250			if (Input.Commands == null)
251			{
252				return;
253			}
254
255		}
256		#endregion
257
258		#region Zoom
259		/// <summary>
260		/// Zoom in or out. if respectFPS is set to true, Time.Delta will be taken
261		/// into account. E.g.: set this to true for hold-able buttons.
262		/// </summary>
263		/// <param name="zoomIn">if set to <c>true</c> [zoom in].</param>
264		public void Zoom(bool zoomIn, bool respectFps)
265		{
266			if (IsLocked)
267			{
268				return;
269			}
270			float zoomSpeed = MathHelper.Max(Distance, 0.1f) * 0.1f *
271			                  SpeedMultiplier * (respectFps
272			                                     	? (Time.Delta * 10f)
273			                                     	: 1f);
274			// Limit zoomSpeed, else we get into millions too quickly
275			zoomSpeed = MathHelper.Min(zoomSpeed, 100000 * Time.Delta);
276			Distance += zoomIn
277			            	? -zoomSpeed
278			            	: zoomSpeed;
279		}
280		#endregion
281
282
283		#region RotateHorizontal
284		/// <summary>
285		/// Rotate horizontal
286		/// </summary>
287		/// <param name="left">if set to <c>true</c> [left].</param>
288		public void RotateHorizontal(bool left)
289		{
290			if (IsLocked)
291			{
292				return;
293			}
294			rotation.X +=
295				MPS * 0.05f * KeyboardSpeedFactor *
296				(left
297				 	? -SpeedMultiplier
298				 	: SpeedMultiplier) * (Time.Delta * 10f);
299			UpdatePosition();
300		}
301		#endregion
302
303		#region RotateVertical
304		/// <summary>
305		/// Rotate vertical
306		/// </summary>
307		/// <param name="up">if set to <c>true</c> [up].</param>
308		public void RotateVertical(bool up)
309		{
310			if (IsLocked)
311			{
312				return;
313			}
314			rotation.Y +=
315				MPS * 0.05f * KeyboardSpeedFactor *
316				(up
317				 	? SpeedMultiplier
318				 	: -SpeedMultiplier) * (Time.Delta * 10f);
319			UpdatePosition();
320		}
321		#endregion
322
323		#region ResetCamera
324		/// <summary>
325		/// Resets camera position/rotation to values at constructor time
326		/// </summary>
327		public void ResetCamera()
328		{
329			position = defaultPosition;
330			target = defaultTarget;
331			SetPositionValues();
332
333			UpdatePosition();
334		}
335		#endregion
336
337		#region UpdatePosition
338		/// <summary>
339		/// Update position
340		/// </summary>
341		private void UpdatePosition()
342		{
343			// Always clamp the up-down rotation so we don't rotate "over" the
344			// up-vector to avoid the "flipping problem"
345			rotation.Y = MathHelper.Clamp(rotation.Y, MinPitchRotation,
346				MaxPitchRotation);
347
348			Matrix rotationMatrix = Matrix.Identity;
349			// Matrix.CreateRotationY(Z) - We don't need here the "Roll" rotation
350			Matrix xRotation = Matrix.CreateRotationX(rotation.Y);
351			Matrix zRotation = Matrix.CreateRotationZ(rotation.X);
352			Matrix.Multiply(ref xRotation, ref zRotation, ref rotationMatrix);
353
354			// We have to invert here the look vector, because to compute the
355			// position we have to "go" from the target to the position instead of
356			// "looking" case which is the other case (-> look from position to the
357			// target)
358			Vector lookVector = new Vector(0f, Distance, 0f);
359			Vector.TransformNormal(ref lookVector, ref rotationMatrix, ref position);
360			Vector.Add(ref position, ref target, out position);
361		}
362		#endregion
363
364		#region SetPositionValues
365		/// <summary>
366		/// Set position values
367		/// Used by the Position and Target properties.
368		/// </summary>
369		private void SetPositionValues()
370		{
371			// and we have to clear the current rotation now, because the new
372			// position is already "final"
373
374			// Now we still need to know the new direction we have to look to the
375			// (old) target
376			Vector.Subtract(ref target, ref position, out lookDirection);
377			// and store the new distance to (in base because we want to avoid the
378			// unnecessary UpdatePosition() call from Distance here)
379			distance = lookDirection.Length;
380			lookDirection.Normalize();
381
382			// Position.Z = sin(Rotation.X) -> see trigonometrical functions
383			// Note: we consider at the moment only the x rotation, because we don't
384			// have any rotation on y or z (with default values)
385			rotation.Y = MathHelper.Asin(-LookDirection.Z);
386			rotation.X = -MathHelper.Atan(-LookDirection.X, -lookDirection.Y);
387		}
388		#endregion
389
390		#region ISaveLoadBinary Members
391
392		#region Save (override)
393		/// <summary>
394		/// Override saving data from BinaryStream.
395		/// </summary>
396		/// <param name="dataWriter">Binary writer used for writing data.</param>
397		public override void Save(BinaryWriter dataWriter)
398		{
399			base.Save(dataWriter);
400
401			// Save the implementation version first
402			dataWriter.Write(ImplementationVersion);
403
404			// and then all required values
405			defaultPosition.Save(dataWriter);
406			defaultTarget.Save(dataWriter);
407		}
408		#endregion
409
410		#region Load (override)
411		/// <summary>
412		/// Override loading data from BinaryStream.
413		/// </summary>
414		/// <param name="reader">Binary reader used for reading data.</param>
415		public override void Load(BinaryReader reader)
416		{
417			base.Load(reader);
418
419			// First read the implementation version
420			int version = reader.ReadInt32();
421			switch (version)
422			{
423					// Version 1
424				case 1:
425					// Now just read the saved values
426					defaultPosition = new Vector(reader);
427					defaultTarget = new Vector(reader);
428					break;
429
430				default:
431					Log.InvalidVersionWarning(GetType().Name + ": " + Name,
432						version, ImplementationVersion);
433					break;
434			} // switch
435		}
436		#endregion
437
438		#endregion
439
440		#region Tests
441		/// <summary>
442		/// Tests
443		/// </summary>
444		internal class LookAtCameraTests
445		{
446			#region TestSaveAndLoad (LongRunning)
447			/// <summary>
448			/// Test save and load functionality of the Graph class
449			/// </summary>
450			[Test, Category("LongRunning")]
451			public void TestSaveAndLoad()
452			{
453				// Creation of the graph
454				LookAtCamera lookAtCamera = new LookAtCamera(new Vector(3, -6, 8))
455				{
456					// BaseCamera
457					LookDirection = new Vector(-1, 2, -1),
458					Target = new Vector(3, 4, 1),
459					Rotation = new Vector(3, 1, 0),
460					FieldOfView = 65,
461					FarPlane = 205,
462					NearPlane = 0.15f,
463					AlwaysNeedsUpdate = true,
464					IsLocked = true,
465					// LookAtCamera
466					defaultPosition = new Vector(0, -5, 9),
467					defaultTarget = new Vector(3, 3, 1),
468				};
469
470				// Saving
471				MemoryStream savedStream = new MemoryStream();
472				BinaryWriter writer = new BinaryWriter(savedStream);
473				lookAtCamera.Save(writer);
474				writer.Flush();
475				writer = null;
476
477				// Loading
478				savedStream.Position = 0;
479				BinaryReader reader = new BinaryReader(savedStream);
480				LookAtCamera loadedCamera = new LookAtCamera(Vector.Zero);
481				loadedCamera.Load(reader);
482
483				// Checking
484				// BaseCamera values
485				Assert.Equal(loadedCamera.LookDirection, lookAtCamera.LookDirection);
486				Assert.Equal(loadedCamera.Target, lookAtCamera.Target);
487				Assert.Equal(loadedCamera.Rotation, lookAtCamera.Rotation);
488				Assert.Equal(loadedCamera.FieldOfView, lookAtCamera.FieldOfView);
489				Assert.Equal(loadedCamera.FarPlane, lookAtCamera.FarPlane);
490				Assert.Equal(loadedCamera.NearPlane, lookAtCamera.NearPlane);
491				Assert.Equal(loadedCamera.AlwaysNeedsUpdate,
492					lookAtCamera.AlwaysNeedsUpdate);
493				Assert.Equal(loadedCamera.IsLocked, lookAtCamera.IsLocked);
494				// LookAtCamera values
495				Assert.Equal(loadedCamera.defaultPosition,
496					lookAtCamera.defaultPosition);
497				Assert.Equal(loadedCamera.defaultTarget, lookAtCamera.defaultTarget);
498			}
499			#endregion
500		}
501		#endregion
502	}
503}