PageRenderTime 146ms CodeModel.GetById 101ms app.highlight 18ms RepoModel.GetById 22ms app.codeStats 0ms

/Rendering/Cameras/PathCamera.cs

#
C# | 494 lines | 307 code | 52 blank | 135 comment | 33 complexity | 13fb8b42470ca7ff317ea4dfd13c42d4 MD5 | raw file
  1using System;
  2using System.IO;
  3using Delta.Engine;
  4using Delta.Utilities;
  5using Delta.Utilities.Datatypes;
  6using Delta.Utilities.Helpers;
  7
  8namespace Delta.Rendering.Cameras
  9{
 10	/// <summary>
 11	/// Path camera represent a camera witch moves on a given path.
 12	/// A path consist of many positions in a 3D space. The camera stops moving,
 13	/// when it reached the last position in the given path.
 14	/// The UP-Vector is implemented in the base camera in the UpdateViewMatrix
 15	/// method. Which is the UnitZ-Vector.
 16	/// <para />
 17	/// </summary>
 18	public class PathCamera : LookAtCamera
 19	{
 20		#region Constants
 21		/// <summary>
 22		/// The current version of the implementation of this Camera class.
 23		/// </summary>
 24		private const int ImplementationVersion = 1;
 25
 26		/// <summary>
 27		/// Camera default speed.
 28		/// </summary>
 29		private const float DefaultSpeed = 10f;
 30		#endregion
 31
 32		#region LoadColladaCameraPath (Static)
 33		/// <summary>
 34		/// Loads the exported camera path (view) matrices from a Collada (file)
 35		/// data string.
 36		/// </summary>
 37		/// <param name="colladaMatricesString">Collada matrices string.</param>
 38		/// <returns>Return the loaded array of matrices.</returns>
 39		public static Matrix[] LoadColladaCameraPath(string colladaMatricesString)
 40		{
 41			float[] rawMatricesValues =
 42				colladaMatricesString.ToFloatArray();
 43
 44			// Build the matrices path from the raw path data
 45			int matricesCount = rawMatricesValues.Length / 16;
 46			Matrix[] matricesPath = new Matrix[matricesCount];
 47			// we have to scale down the exported matrix by the engine scaling
 48			// 1m = 0.01 max units (cm)
 49			Matrix correctionScaleMatrix = Matrix.CreateScale(0.01f);
 50			for (int index = 0; index < matricesCount; index++)
 51			{
 52				// The view matrix is in Collada exported as inverse view matrix,
 53				// probably because of optimization reasons of 3DsMax's viewport render
 54				Matrix deltaViewMatrix =
 55					LoadColladaMatrix(rawMatricesValues, index * 16).Inverse;
 56
 57				Matrix.Multiply(ref deltaViewMatrix, ref correctionScaleMatrix,
 58					ref matricesPath[index]);
 59			}
 60
 61			return matricesPath;
 62		}
 63		#endregion
 64
 65		#region LoadColladaMatrix (Static)
 66		/// <summary>
 67		/// Create matrix from collada float value array. The matrices in collada
 68		/// are stored differently from the xna format (row based instead of
 69		/// columns).
 70		/// </summary>
 71		/// <param name="mat">Array of floats containing this matrix, but
 72		/// not exclusively. Can contain many more matrices by using the offset
 73		/// parameter.</param>
 74		/// <param name="offset">The offset index inside the mat array.</param>
 75		/// <returns>Delta Matrix created from collada matrix data.</returns>
 76		public static Matrix LoadColladaMatrix(float[] mat, int offset)
 77		{
 78			return new Matrix(
 79				mat[offset + 0], mat[offset + 4], mat[offset + 8], mat[offset + 12],
 80				mat[offset + 1], mat[offset + 5], mat[offset + 9], mat[offset + 13],
 81				mat[offset + 2], mat[offset + 6], mat[offset + 10], mat[offset + 14],
 82				mat[offset + 3], mat[offset + 7], mat[offset + 11], mat[offset + 15]);
 83		}
 84		#endregion
 85
 86		#region LookDirection (Public)
 87		/// <summary>
 88		/// Look direction
 89		/// </summary>
 90		public override Vector LookDirection
 91		{
 92			get
 93			{
 94				return target - position;
 95			}
 96			set
 97			{
 98				// Ignored anyway
 99				lookDirection = value;
100			}
101		}
102		#endregion
103
104		#region IsFinished (Public)
105		/// <summary>
106		/// Has the camera reached the last node of the path?
107		/// </summary>
108		public bool IsFinished
109		{
110			get;
111			private set;
112		}
113		#endregion
114
115		#region Speed (Public)
116		/// <summary>
117		/// The speed of the camera, default is 10f.
118		/// </summary>
119		public float Speed
120		{
121			get;
122			set;
123		}
124		#endregion
125
126		#region Private
127
128		#region pathByMatrices (Private)
129		/// <summary>
130		/// Interpolated positions and direction as a matrix.
131		/// We get all the data from a Collada file.
132		/// </summary>
133		private readonly Matrix[] pathByMatrices;
134		#endregion
135
136		#region pathByPoints (Private)
137		/// <summary>
138		/// Path of the camera by position points.
139		/// </summary>
140		private readonly Vector[] pathByPoints;
141		#endregion
142
143		#region positionDistances (Private)
144		/// <summary>
145		/// Precalculated distances from the start to the current position,
146		/// to speed up the calculations on moving along the path.
147		/// </summary>
148		private readonly float[] positionDistances;
149		#endregion
150
151		#region targetPath (Private)
152		/// <summary>
153		/// The camera target positions path related to the camera
154		/// positions path.
155		/// This path can also be longer or shorter than the positions
156		/// path, because the current distance on the target path will be
157		/// calculated relatively to the length of the positions path.
158		/// </summary>
159		private readonly Vector[] targetPath;
160		#endregion
161
162		#region targetDistances (Private)
163		/// <summary>
164		/// Precalculated distances from the start to the current target
165		/// position, to speed up the calculations on moving along the path.
166		/// </summary>
167		private readonly float[] targetDistances;
168		#endregion
169
170		#region elapsedDistance (Private)
171		/// <summary>
172		/// The elapsed distance on the path.
173		/// </summary>
174		private float elapsedDistance;
175		#endregion
176
177		#region targetDistanceFactor (Private)
178		/// <summary>
179		/// The relative factor between the length of the camera path and
180		/// the length of the target path.
181		/// </summary>
182		private readonly float targetDistanceFactor;
183		#endregion
184
185		#endregion
186
187		#region Constructors
188		/// <summary>
189		/// Create path camera with a list of matrices.
190		/// </summary>
191		/// <param name="setPath">Matrix array containing path data.</param>
192		public PathCamera(Matrix[] setPath)
193			: this()
194		{
195			// Set the camera path (which is always valid)
196			pathByMatrices = setPath != null
197			                 	? setPath
198			                 	: new Matrix[0];
199
200			if (pathByMatrices.Length < 1)
201			{
202				Log.Warning("There is no camera path. We can't move any path " +
203				            "without at least 1 matrix.");
204				UpdateViewMatrix();
205				UpdateScreenProperties();
206			}
207
208			positionDistances = new float[pathByMatrices.Length];
209			float distanceCount = 0f;
210			for (int index = 1; index < pathByMatrices.Length; index++)
211			{
212				Matrix inv1 = Matrix.Invert(pathByMatrices[index]);
213				Matrix inv2 = Matrix.Invert(pathByMatrices[index - 1]);
214
215				distanceCount += (inv1.Translation -
216				                  inv2.Translation).Length;
217
218				positionDistances[index] = distanceCount;
219			}
220		}
221
222		/// <summary>
223		/// Create path camera with a list of vectors (positions) and
224		/// a list of target vectors (if available).
225		/// </summary>
226		/// <param name="setPath">Vector array containing
227		/// path points data.</param>
228		/// <param name="setPath">Vector array containing
229		/// target path points data.</param>
230		public PathCamera(Vector[] setPath, Vector[] setTargetPath = null)
231			: this()
232		{
233			// Set the camera path (which is always valid)
234			pathByPoints = (setPath != null)
235			               	? setPath
236			               	: new Vector[0];
237
238			positionDistances = new float[pathByPoints.Length];
239			float distanceCount = 0f;
240			for (int index = 1; index < pathByPoints.Length; index++)
241			{
242				distanceCount += (pathByPoints[index] -
243				                  pathByPoints[index - 1]).Length;
244				positionDistances[index] = distanceCount;
245			}
246
247			if (pathByPoints.Length < 2)
248			{
249				Log.Warning("There is no camera path. We can't move any path " +
250				            "without at least 2 positions.");
251				UpdateViewMatrix();
252				UpdateScreenProperties();
253			}
254
255			// Set the target path (if valid)
256			if (setTargetPath != null)
257			{
258				if (setTargetPath.Length == 0)
259				{
260					Log.Warning("The target path needs at least one position!");
261				}
262				else
263				{
264					targetPath = setTargetPath;
265
266					targetDistances = new float[targetPath.Length];
267					distanceCount = 0f;
268					for (int index = 1; index < targetPath.Length; index++)
269					{
270						distanceCount += (targetPath[index] -
271						                  targetPath[index - 1]).Length;
272						targetDistances[index] = distanceCount;
273					}
274
275					targetDistanceFactor =
276						targetDistances[targetDistances.Length - 1] /
277						positionDistances[positionDistances.Length - 1];
278				}
279			}
280		}
281
282		/// <summary>
283		/// Private helper constructor to set the default values for
284		/// all other constructors above.
285		/// </summary>
286		private PathCamera()
287			: base(DefaultLookAtCamPosition, DefaultTargetPosition)
288		{
289			// Set the default values
290			Speed = DefaultSpeed;
291			IsFinished = false;
292		}
293		#endregion
294
295		#region Save (Public)
296		/// <summary>
297		/// Override saving data from BinaryStream.
298		/// </summary>
299		/// <param name="dataWriter">Binary writer used for writing data.</param>
300		public override void Save(BinaryWriter dataWriter)
301		{
302			base.Save(dataWriter);
303
304			// Save the implementation version first
305			dataWriter.Write(ImplementationVersion);
306
307			//throw new NotImplementedException("TODO");
308			//dataWriter.Write(TargetPath.Length);
309			//for (int index = 0; index < TargetPath.Length; index++)
310			//{
311			//  TargetPath[index].Save(dataWriter);
312			//}
313		}
314		#endregion
315
316		#region Load (Public)
317		/// <summary>
318		/// Override loading data from BinaryStream.
319		/// </summary>
320		/// <param name="reader">Binary reader used for reading data.</param>
321		public override void Load(BinaryReader reader)
322		{
323			base.Load(reader);
324
325			// First read the implementation version
326			int version = reader.ReadInt32();
327			switch (version)
328			{
329					// Version 1
330				case 1:
331					//throw new NotImplementedException("TODO");
332					//int length = reader.ReadInt32();
333					//TargetPath = new Vector[length];
334
335					//for (int index = 0; index < length; index++)
336					//{
337					//  TargetPath[index].Load(reader);
338					//}
339					break;
340
341				default:
342					Log.InvalidVersionWarning(GetType().Name + ": " + Name,
343						version, ImplementationVersion);
344					break;
345			}
346		}
347		#endregion
348
349		#region Methods (Private)
350
351		#region InternalRun
352		/// <summary>
353		/// Internal run
354		/// </summary>
355		protected override void InternalRun()
356		{
357			elapsedDistance += Time.Delta * Speed;
358
359			if (pathByMatrices != null)
360			{
361				UpdateMatrixPath();
362			}
363			else if (pathByPoints != null)
364			{
365				UpdatePointsPath();
366			}
367		}
368		#endregion
369
370		#region UpdateMatrixPath
371		/// <summary>
372		/// Update the path camera according to the matrices.
373		/// </summary>
374		private void UpdateMatrixPath()
375		{
376			Matrix currentMatrix = Matrix.Identity;
377			if (elapsedDistance <= 0f)
378			{
379				currentMatrix = pathByMatrices[0];
380			}
381			else if (elapsedDistance >=
382			         positionDistances[positionDistances.Length - 1])
383			{
384				IsFinished = true;
385				currentMatrix = pathByMatrices[pathByMatrices.Length - 1];
386			}
387			else
388			{
389				int currentIndex = 0;
390				for (int index = 0; index < positionDistances.Length - 1; index++)
391				{
392					if (elapsedDistance >= positionDistances[index] &&
393					    elapsedDistance < positionDistances[index + 1])
394					{
395						currentIndex = index;
396						break;
397					}
398				}
399
400				currentMatrix = pathByMatrices[currentIndex];
401			}
402
403			viewMatrix = currentMatrix;
404			UpdateScreenProperties();
405		}
406		#endregion
407
408		#region UpdatePointsPath
409		/// <summary>
410		/// Update the path camera according to the vector positions and target
411		/// positions (if available).
412		/// </summary>
413		private void UpdatePointsPath()
414		{
415			Vector direction;
416			Vector position = GetPointPosition(pathByPoints, positionDistances,
417				elapsedDistance, out direction);
418
419			Vector target = Vector.Zero;
420			if (targetPath != null)
421			{
422				if (targetPath.Length == 1)
423				{
424					target = targetPath[0];
425				}
426				else
427				{
428					target = GetPointPosition(targetPath, targetDistances,
429						elapsedDistance * targetDistanceFactor, out direction);
430				}
431			}
432			else
433			{
434				target = position + direction;
435			}
436
437			if (elapsedDistance >= positionDistances[positionDistances.Length - 1])
438			{
439				IsFinished = true;
440			}
441
442			viewMatrix = Matrix.CreateLookAt(position,
443				target, UpVector);
444			UpdateScreenProperties();
445		}
446		#endregion
447
448		#region GetPointPosition
449		/// <summary>
450		/// Get a vector position by an array of positions, precalculated
451		/// distances (for performance) and a current distance.
452		/// <para />
453		/// Basically the logic of a 2d graph but with a 3d vector as the
454		/// result.
455		/// </summary>
456		private Vector GetPointPosition(Vector[] positions,
457			float[] distances, float distance, out Vector direction)
458		{
459			int currentIndex = 0;
460
461			if (distance >= distances[distances.Length - 1])
462			{
463				direction = positions[positions.Length - 1] -
464				            positions[positions.Length - 2];
465				return positions[positions.Length - 1];
466			}
467			else if (distance <= 0f)
468			{
469				direction = positions[1] - positions[0];
470				return positions[0];
471			}
472			else
473			{
474				for (int index = 0; index < distances.Length - 1; index++)
475				{
476					if (distance >= distances[index] &&
477					    distance < distances[index + 1])
478					{
479						currentIndex = index;
480						break;
481					}
482				}
483			}
484
485			float relativeDistance = distance - distances[currentIndex];
486			direction = positions[currentIndex + 1] - positions[currentIndex];
487			direction.Normalize();
488			return positions[currentIndex] + (relativeDistance * direction);
489		}
490		#endregion
491
492		#endregion
493	}
494}