PageRenderTime 43ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/Rendering/Basics/Drawing/BillboardManager.cs

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