/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}