PageRenderTime 151ms CodeModel.GetById 60ms app.highlight 20ms RepoModel.GetById 41ms app.codeStats 7ms

/Rendering/Basics/Drawing/BillboardManager.cs

#
C# | 498 lines | 304 code | 45 blank | 149 comment | 26 complexity | 3aa5df99fe294f51d297c2a4eb3132b8 MD5 | raw file
  1using Delta.Engine;
  2using Delta.Engine.Dynamic;
  3using Delta.Rendering.Enums;
  4using Delta.Utilities.Datatypes;
  5using Delta.Utilities.Helpers;
  6
  7namespace Delta.Rendering.Basics.Drawing
  8{
  9	/// <summary>
 10	/// Class that allows drawing billboards and managing them in a more
 11	/// optimized and useful matter than just drawing out materials (which is
 12	/// also possible). Usually used from Effects system to draw 3D billboards.
 13	/// </summary>
 14	public class BillboardManager : DynamicModule
 15	{
 16		#region Instance (Static)
 17		/// <summary>
 18		/// Instance for this Billboard manager (handled only here privately)
 19		/// </summary>
 20		public static BillboardManager Instance
 21		{
 22			get
 23			{
 24				if (instance == null)
 25				{
 26					// Needs to be created via factory to make sure we only do this once
 27					instance = Factory.Create<BillboardManager>();
 28				}
 29				return instance;
 30			}
 31		}
 32		#endregion
 33
 34		#region Private
 35
 36		#region DefaultNormal (Private)
 37		/// <summary>
 38		/// The default normal vector of billboards e.g. Vector.Zero so the
 39		/// billboard will be calculated in special ways.
 40		/// </summary>
 41		private static Vector DefaultNormal = Vector.Zero;
 42		#endregion
 43
 44		#region DefaultGroundNormal (Private)
 45		/// <summary>
 46		/// The default ground normal used to calculate the billboard directly.
 47		/// </summary>
 48		private static Vector DefaultGroundNormal = Vector.UnitZ;
 49		#endregion
 50
 51		#region instance (Private)
 52		/// <summary>
 53		/// Private instance
 54		/// </summary>
 55		private static BillboardManager instance;
 56		#endregion
 57
 58		#region transformMatrix (Private)
 59		/// <summary>
 60		/// These matrices cache the last calculated transformation for billboards,
 61		/// used for billboards that only get calculated the first time and then
 62		/// reusing these matrices.
 63		/// </summary>
 64		private Matrix transformMatrix;
 65		#endregion
 66
 67		#region transformMatrixAll (Private)
 68		private Matrix transformMatrixAll;
 69		#endregion
 70
 71		#region transformMatrixGround (Private)
 72		private Matrix transformMatrixGround;
 73		#endregion
 74
 75		#region transformMatrixFront (Private)
 76		private Matrix transformMatrixFront;
 77		#endregion
 78
 79		#region transformMatrixUp (Private)
 80		private Matrix transformMatrixUp;
 81		#endregion
 82
 83		#region calculatedTransformAll (Private)
 84		/// <summary>
 85		/// Flags for the calculation caches above to notice if something needs
 86		/// to be calculated or not.
 87		/// </summary>
 88		private bool calculatedTransformAll;
 89		#endregion
 90
 91		#region calculatedTransformGround (Private)
 92		private bool calculatedTransformGround;
 93		#endregion
 94
 95		#region calculatedTransformFront (Private)
 96		private bool calculatedTransformFront;
 97		#endregion
 98
 99		#region calculatedTransformUp (Private)
100		private bool calculatedTransformUp;
101		#endregion
102
103		#region currentPosition3D (Private)
104		/// <summary>
105		/// The current 3d position of the billboard, set in the ProcessBillboard
106		/// method. This "cache" is only used so we don't have to copy over
107		/// the position every time (even with ref it costs) and to make the
108		/// method more readable by removing all the "ref position3D" stuff.
109		/// </summary>
110		private Vector currentPosition3D;
111		#endregion
112
113		#region billboardMode (Private)
114		/// <summary>
115		/// The current mode of the billboard. Same usage as position3D above.
116		/// </summary>
117		private BillboardMode billboardMode;
118		#endregion
119
120		#region points3D (Private)
121		/// <summary>
122		/// Helper for calculating billboard positions
123		/// </summary>
124		private readonly Vector[] points3D = new Vector[4];
125		#endregion
126
127		#region rotatedPoints (Private)
128		/// <summary>
129		/// Rotation points helper for the Add method with rotation. The
130		/// Rectangle.Rotate method will fill these 4 points and they will then
131		/// be filled into the vertex data stream.
132		/// </summary>
133		private readonly Point[] rotatedPoints = new Point[4];
134		#endregion
135
136		#endregion
137
138		#region Constructors
139		/// <summary>
140		/// Creates a new instance of BillboardManager.
141		/// </summary>
142		public BillboardManager()
143			: base("BillboardManager", typeof(MaterialManager))
144		{
145		}
146		#endregion
147
148		#region Draw (Public)
149		/// <summary>
150		/// Performs draw billboard stuff.
151		/// </summary>
152		/// <param name="material">The material to draw with.</param>
153		/// <param name="position3D">The position in 3D space.</param>
154		/// <param name="size">The size of the quad.</param>
155		/// <param name="rotation">The rotation.</param>
156		public void Draw(MaterialColored material, Vector position3D, Size size,
157			float rotation)
158		{
159			ProcessBillboard(material, ref position3D, ref size, rotation,
160				ref DefaultNormal);
161		}
162
163		/// <summary>
164		/// Performs draw billboard stuff.
165		/// </summary>
166		/// <param name="material">The material to draw with.</param>
167		/// <param name="position3D">The position in 3D space.</param>
168		/// <param name="size">The size of the quad.</param>
169		/// <param name="rotation">The rotation.</param>
170		/// <param name="normal">Normal to align the billboard to</param>
171		public void Draw(MaterialColored material, Vector position3D, Size size,
172			float rotation, Vector normal)
173		{
174			ProcessBillboard(material, ref position3D, ref size, rotation,
175				ref normal);
176		}
177
178		/// <summary>
179		/// Performs draw billboard stuff.
180		/// </summary>
181		/// <param name="material">The material to draw with.</param>
182		/// <param name="position3D">The position in 3D space.</param>
183		/// <param name="size">The size of the quad.</param>
184		/// <param name="rotation">The rotation.</param>
185		/// <param name="blendColorOverride">Overwritten blend color</param>
186		public void Draw(MaterialColored material, Vector position3D, Size size,
187			float rotation, Color blendColorOverride)
188		{
189			material.BlendColor = blendColorOverride;
190			ProcessBillboard(material, ref position3D, ref size, rotation,
191				ref DefaultNormal);
192		}
193		#endregion
194
195		#region DrawPlane (Public)
196		/// <summary>
197		/// Draw the material as a billboard ground plane.
198		/// </summary>
199		/// <param name="material">The material to draw with.</param>
200		public void DrawPlane(MaterialColored material)
201		{
202			DrawPlane(material, Vector.Zero, new Size(20), 0f);
203		}
204
205		/// <summary>
206		/// Draw the material as a billboard ground plane.
207		/// </summary>
208		/// <param name="material">The material to draw with.</param>
209		/// <param name="position3D">The position in 3D space.</param>
210		/// <param name="size">The size of the quad.</param>
211		/// <param name="rotation">The rotation.</param>
212		public void DrawPlane(MaterialColored material, Vector position3D,
213			Size size, float rotation)
214		{
215			material.billboardMode = BillboardMode.Ground;
216			ProcessBillboard(material, ref position3D, ref size, 0f,
217				ref DefaultGroundNormal);
218		}
219		#endregion
220
221		#region Run (Public)
222		/// <summary>
223		/// Run method from DynamicModule.
224		/// </summary>
225		public override void Run()
226		{
227			// Reset Billboard pre-calculated transformMatrices
228			calculatedTransformAll = false;
229			calculatedTransformGround = false;
230			calculatedTransformFront = false;
231			calculatedTransformUp = false;
232		}
233		#endregion
234
235		#region Methods (Private)
236
237		#region SetupBillboardTransformOnce
238		/// <summary>
239		/// Only does the actual setup if alreadyDone is set to false.
240		/// Sets alreadyDone to true afterwards
241		/// </summary>
242		/// <param name="alreadyDone">
243		/// Boolean value used and assigned after setting the transform once.
244		/// </param>
245		/// <param name="transform">
246		/// The transform calculated if alreadyDone is false.
247		/// </param>
248		private void SetupBillboardTransformOnce(ref Matrix transform,
249			ref bool alreadyDone)
250		{
251			if (alreadyDone == false)
252			{
253				SetupBillboardTransform(ref transform);
254				alreadyDone = true;
255			}
256		}
257		#endregion
258
259		#region SetupBillboardTransform
260		/// <summary>
261		/// Setup billboard transform for all upcoming billboard render actions.
262		/// Note: It does not set translation, as this must be done individually.
263		/// </summary>
264		/// <param name="transform">Transform matrix for the billboards</param>
265		private void SetupBillboardTransform(ref Matrix transform)
266		{
267			//Vector look = billboardMode.IsFlagSet(BillboardMode.Collective) ?
268			//  Vector.Cross(ScreenSpace.ViewInverse.Right, ScreenSpace.ViewInverse.Up) :
269			//  ScreenSpace.ViewInverse.Translation - position3D;
270			Vector look =
271				ScreenSpace.InternalViewInverse.Translation - currentPosition3D;
272			Vector cameraUp = ScreenSpace.InternalViewInverse.Up;
273			if (billboardMode.IsFlagSet(BillboardMode.FrontAxis))
274			{
275				cameraUp = Vector.UnitY;
276				look.Y = 0;
277			}
278			else if (billboardMode.IsFlagSet(BillboardMode.UpAxis))
279			{
280				cameraUp = Vector.UnitZ;
281				look.Z = 0;
282			}
283			else if (billboardMode.IsFlagSet(BillboardMode.Ground))
284			{
285				cameraUp = -Vector.UnitZ;
286				look = -Vector.UnitX;
287			}
288			look.Normalize();
289			Vector right = Vector.Cross(cameraUp, look);
290			Vector up = Vector.Cross(look, right);
291
292			// Setup transform Matrix
293			transform.Right = right;
294			transform.Up = look;
295			transform.Front = up;
296		}
297		#endregion
298
299		#region ApplyBillboardTransform
300		/// <summary>
301		/// Apply billboard transform to given Vector Array.
302		/// given position overrides transform.Translation
303		/// </summary>
304		/// <param name="transform">Transform matrix for the billboards</param>
305		private void ApplyBillboardTransform(ref Matrix transform)
306		{
307			transform.Translation = currentPosition3D;
308			int len = points3D.Length;
309			for (int index = 0; index < len; index++)
310			{
311				Vector tmp;
312				Vector.Transform(ref points3D[index], ref transform, out tmp);
313				points3D[index] = tmp;
314			}
315		}
316		#endregion
317
318		#region ProcessBillboard
319		/// <summary>
320		/// Process a billboard and add it to the material manager.
321		/// </summary>
322		/// <param name="material">Material to render on the billboard.</param>
323		/// <param name="position3D">The #define position of the billboard.</param>
324		/// <param name="size">The size of the billboard.</param>
325		/// <param name="rotation">The rotation of the billboard.</param>
326		/// <param name="normal">The normal vector of the billboard
327		/// (if available)</param>
328		private void ProcessBillboard(MaterialColored material,
329			ref Vector position3D, ref Size size, float rotation, ref Vector normal)
330		{
331			currentPosition3D = position3D;
332			// Check creation plane
333			billboardMode = material.billboardMode;
334			bool createXY = billboardMode.IsFlagSet(BillboardMode.Ground);
335
336			//// If the content is reduced in the atlas, remap the rendering!
337			//if (material.diffuseMap.useInnerDrawArea)
338			//{
339			//  drawArea =
340			//    drawArea.GetInnerRectangle(material.diffuseMap.innerDrawArea);
341			//}
342
343			#region Basic vertex positioning
344			// Now setup vertices based on given size at origin in XZ plane
345			if (rotation != 0.0f)
346			{
347				// Apply the rotation
348				new Rectangle(-size.WidthHalf, -size.HeightHalf, size.Width,
349					size.Height).Rotate(rotation, rotatedPoints);
350
351				// Left Upper
352				points3D[0].X = rotatedPoints[3].X;
353				points3D[0].Y = createXY
354				                	? rotatedPoints[3].Y
355				                	: 0;
356				points3D[0].Z = createXY
357				                	? 0
358				                	: rotatedPoints[3].Y;
359
360				// Right Upper
361				points3D[1].X = rotatedPoints[2].X;
362				points3D[1].Y = createXY
363				                	? rotatedPoints[2].Y
364				                	: 0;
365				points3D[1].Z = createXY
366				                	? 0
367				                	: rotatedPoints[2].Y;
368
369				// Left Lower
370				points3D[2].X = rotatedPoints[1].X;
371				points3D[2].Y = createXY
372				                	? rotatedPoints[1].Y
373				                	: 0;
374				points3D[2].Z = createXY
375				                	? 0
376				                	: rotatedPoints[1].Y;
377
378				// Right Lower
379				points3D[3].X = rotatedPoints[0].X;
380				points3D[3].Y = createXY
381				                	? rotatedPoints[0].Y
382				                	: 0;
383				points3D[3].Z = createXY
384				                	? 0
385				                	: rotatedPoints[0].Y;
386			}
387			else
388			{
389				// Left Upper
390				points3D[0].X = -size.WidthHalf;
391				points3D[0].Y = createXY
392				                	? size.HeightHalf
393				                	: 0;
394				points3D[0].Z = createXY
395				                	? 0
396				                	: size.HeightHalf;
397
398				// Right Upper
399				points3D[1].X = size.WidthHalf;
400				points3D[1].Y = createXY
401				                	? size.HeightHalf
402				                	: 0;
403				points3D[1].Z = createXY
404				                	? 0
405				                	: size.HeightHalf;
406
407				// Left Lower
408				points3D[3].X = -size.WidthHalf;
409				points3D[3].Y = createXY
410				                	? -size.HeightHalf
411				                	: 0;
412				points3D[3].Z = createXY
413				                	? 0
414				                	: -size.HeightHalf;
415
416				// Right Lower
417				points3D[2].X = size.WidthHalf;
418				points3D[2].Y = createXY
419				                	? -size.HeightHalf
420				                	: 0;
421				points3D[2].Z = createXY
422				                	? 0
423				                	: -size.HeightHalf;
424			} // else
425			#endregion
426
427			if (normal != Vector.Zero)
428			{
429				// rotate according to given normal
430				// Extract rotation axis
431				// Just exchanging frontAxis with UnitY gives strange result
432				Vector frontAxis = Vector.UnitZ;
433				Vector rotationAxis = Vector.Cross(frontAxis, normal);
434				rotationAxis.Normalize();
435				// Extract rotation angle
436				float angle = Vector.AngleBetweenVectors(frontAxis, normal);
437				// Build axis rotation matrix
438				Matrix rotationMatrix =
439					Matrix.CreateFromAxisAngle(rotationAxis, angle);
440
441				// And finally apply the rotation (and translation)
442				ApplyBillboardTransform(ref rotationMatrix);
443			}
444			else // do billboard logic
445			{
446				// Billboard logic taken from:
447				// http://nehe.gamedev.net/data/articles/article.asp?article=19
448				// Calculate look direction (into which billboard should look)
449				// Here we use pre-calculated transformMatrices and only set position
450				if (createXY)
451				{
452					SetupBillboardTransformOnce(ref transformMatrixGround,
453						ref calculatedTransformGround);
454					ApplyBillboardTransform(ref transformMatrixGround);
455				}
456				else if (billboardMode.IsFlagSet(BillboardMode.FrontAxis))
457				{
458					SetupBillboardTransformOnce(ref transformMatrixFront,
459						ref calculatedTransformFront);
460					ApplyBillboardTransform(ref transformMatrixFront);
461				}
462				else if (billboardMode.IsFlagSet(BillboardMode.UpAxis))
463				{
464					SetupBillboardTransformOnce(ref transformMatrixUp,
465						ref calculatedTransformUp);
466					ApplyBillboardTransform(ref transformMatrixUp);
467				}
468				else if (billboardMode.IsFlagSet(BillboardMode.CameraFacing))
469				{
470					SetupBillboardTransformOnce(ref transformMatrixAll,
471						ref calculatedTransformAll);
472					ApplyBillboardTransform(ref transformMatrixAll);
473				}
474				else if (billboardMode.IsFlagSet(BillboardMode.Only2D))
475				{
476					// Only translate points
477					int len = points3D.Length;
478					for (int index = 0; index < len; index++)
479					{
480						points3D[index] += position3D;
481					}
482				}
483				else // CameraFacingPrecise or unknown
484				{
485					// Calculate billboards individual
486					SetupBillboardTransform(ref transformMatrix);
487					// Apply transformation matrix
488					ApplyBillboardTransform(ref transformMatrix);
489				}
490			}
491
492			material.cachedLayer.AddBillboard(material, points3D);
493		}
494		#endregion
495
496		#endregion
497	}
498}