#region CPL License /* Nuclex Framework Copyright (C) 2002-2009 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.Collections.Generic; using System.IO; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Content; using Nuclex.Graphics.Batching; namespace Nuclex.Graphics.Debugging { /// Game component for overlaying debugging informations on the scene /// /// /// This game component is mainly intended to debugging purposes. It will /// accept all kinds of geometry drawing commands whilst a frame is being drawn /// and will overlay this debugging geometry on everything else within the scene /// just at the end of the drawing queue. /// /// /// The DebugDrawer uses the GameComponent's DrawingOrder property to let itself /// be drawn last. If you cannot call base.Draw() in your Game class last /// (that's the line that invokes the Draw() methods of all /// DrawableGameComponents), you can also create a DebugDrawer without adding /// it to the GameComponents collection and invoke its Draw() method yourself /// at the very end of your rendering process. /// /// public class DebugDrawer : Drawable, IDebugDrawingService { /// Maximum number of vertices allowed for debugging overlays /// /// Controls the size of the vertex buffer used for storing the vertices /// of the DebugDrawer. If debugging overlays are drawn after this many /// vertices have already been generated, the drawing operations will /// silently fail and a short text message on the screen will show that /// not all debugging overlays could be drawn. /// internal const int MaximumDebugVertexCount = 8192; #region struct QueuedString /// Stores a string that is queued for rendering private struct QueuedString { /// Initialized a new queued string /// Text to be rendered /// Position at which to render the text /// Color of the text to render public QueuedString(string text, Vector2 position, Color color) { this.Text = text; this.Position = position; this.Color = color; } /// Text to be rendered public string Text; /// Position the text should be rendered at public Vector2 Position; /// Color of the rendered text public Color Color; } #endregion // struct QueuedString /// Initializes a new debug drawer component /// /// Graphics device service the debug drawer will use for rendering /// public DebugDrawer(IGraphicsDeviceService graphicsDeviceService) : base(graphicsDeviceService) { this.queuedVertices = new VertexPositionColor[MaximumDebugVertexCount]; this.queuedStrings = new List(); Reset(); } /// Resets the contents of the debug drawer /// /// Reset() will be called automatically after a frame has been rendered. /// Only use this method if you actually plan to discard everything /// added to the debug drawer so far inmidst of the drawing process. /// public void Reset() { this.triangleIndex = 0; this.lineIndex = MaximumDebugVertexCount - 1; this.overflowed = false; this.queuedStrings.Clear(); } /// Concatenated View and Projection matrices to use /// /// Update this once per frame to have your debug overlays appear in the /// right places. Simply set it to (View * Projection) of your camera. /// public Matrix ViewProjection { get { return this.viewProjection; } set { this.viewProjection = value; } } /// Loads the graphics resources of the component protected override void LoadContent() { this.contentManager = new ResourceContentManager( GraphicsDeviceServiceHelper.MakePrivateServiceProvider(GraphicsDeviceService), #if WINDOWS_PHONE Resources.Phone7DebugDrawerResources.ResourceManager #else Resources.DebugDrawerResources.ResourceManager #endif ); // The effect will be managed by our content manager and only needs to be // reloaded when the graphics device has been totally shut down or is // starting up for the first time. #if WINDOWS_PHONE this.fillEffect = new BasicEffect(GraphicsDevice); #else this.fillEffect = this.contentManager.Load("SolidColorEffect"); #endif this.drawContext = new EffectDrawContext(this.fillEffect); // Create the sprite batch we're using for text rendering this.font = this.contentManager.Load("LucidaSpriteFont"); this.fontSpriteBatch = new SpriteBatch(GraphicsDevice); // Create a new vertex buffer and its matching vertex declaration for // holding the geometry data of our debug overlays. this.batchDrawer = PrimitiveBatch.GetDefaultBatchDrawer( GraphicsDevice ); } /// Unloads the graphics resources of the component protected override void UnloadContent() { // Release the vertex buffer and vertex declaration. Because these // are not managed resources, we need to get rid of them manually even // when the graphics device is only being reset. IDisposable disposableBatchDrawer = this.batchDrawer as IDisposable; if (disposableBatchDrawer != null) { disposableBatchDrawer.Dispose(); this.batchDrawer = null; } // Release the font rendering sprite batch. this.fontSpriteBatch.Dispose(); // If the device is about to be destroyed, there's no sense in keeping // even the managed resources, so unload the entire content manager // in that case. this.fillEffect = null; this.font = null; this.contentManager.Unload(); } /// Draws a line from the starting point to the destination point /// Starting point of the line /// Destination point the line will be drawn to /// Desired color of the line public void DrawLine(Vector3 from, Vector3 to, Color color) { const int VertexCount = 2; // If we would collide with the triangles in the array or there simply // isn't enough space left, set the overflow flag and silently skip drawing int proposedStart = this.lineIndex - (VertexCount - 1); if (proposedStart < this.triangleIndex) { this.overflowed = true; return; } // Append the line vertices to our vertex array this.queuedVertices[this.lineIndex].Position = to; this.queuedVertices[this.lineIndex].Color = color; this.queuedVertices[this.lineIndex - 1].Position = from; this.queuedVertices[this.lineIndex - 1].Color = color; this.lineIndex -= VertexCount; } /// Draws a wireframe triangle between three points /// First corner point of the triangle /// Second corner point of the triangle /// Third corner point of the triangle /// Desired color of the line public void DrawTriangle(Vector3 a, Vector3 b, Vector3 c, Color color) { const int VertexCount = WireframeTriangleVertexGenerator.VertexCount; // If we would collide with the triangles in the array or there simply // isn't enough space left, set the overflow flag and silently skip drawing int proposedStart = this.lineIndex - (VertexCount - 1); if (proposedStart < this.triangleIndex) { this.overflowed = true; return; } // Generate the vertices for box' wireframe WireframeTriangleVertexGenerator.Generate( this.queuedVertices, proposedStart, a, b, c, color ); this.lineIndex -= VertexCount; } /// Draws a solid (filled) triangle between three points /// First corner point of the triangle /// Second corner point of the triangle /// Third corner point of the triangle /// Desired color of the line public void DrawSolidTriangle(Vector3 a, Vector3 b, Vector3 c, Color color) { const int VertexCount = SolidTriangleVertexGenerator.VertexCount; // If we would collide with the triangles in the array or there simply // isn't enough space left, set the overflow flag and silently skip drawing int proposedEnd = this.triangleIndex + VertexCount; if (proposedEnd > this.lineIndex) { this.overflowed = true; return; } // Generate the vertices for the faces of the box SolidTriangleVertexGenerator.Generate( this.queuedVertices, this.triangleIndex, a, b, c, color ); this.triangleIndex += VertexCount; } /// Draws a wireframe box at the specified location /// Contains the coordinates of the box lesser corner /// Contains the coordinates of the box greater corner /// Color of the wireframe to draw public void DrawBox(Vector3 min, Vector3 max, Color color) { const int VertexCount = WireframeBoxVertexGenerator.VertexCount; // If we would collide with the triangles in the array or there simply // isn't enough space left, set the overflow flag and silently skip drawing int proposedStart = this.lineIndex - (VertexCount - 1); if (proposedStart < this.triangleIndex) { this.overflowed = true; return; } // Generate the vertices for box' wireframe WireframeBoxVertexGenerator.Generate( this.queuedVertices, proposedStart, min, max, color ); this.lineIndex -= VertexCount; } /// Draws a solid (filled) box at the specified location /// Contains the coordinates of the box lesser corner /// Contains the coordinates of the box greater corner /// Desired color for the box public void DrawSolidBox(Vector3 min, Vector3 max, Color color) { const int VertexCount = SolidBoxVertexGenerator.VertexCount; // If we would collide with the triangles in the array or there simply // isn't enough space left, set the overflow flag and silently skip drawing int proposedEnd = this.triangleIndex + VertexCount; if ((proposedEnd > this.lineIndex) || (proposedEnd > MaximumDebugVertexCount)) { this.overflowed = true; return; } // Generate the vertices for the faces of the box SolidBoxVertexGenerator.Generate( this.queuedVertices, this.triangleIndex, min, max, color ); this.triangleIndex += VertexCount; } /// Draws a wireframe arrow into the scene to visualize a vector /// /// Location at which to draw the arrow (this will form the exact center of /// the drawn arrow's base) /// /// /// Direction the arrow is pointing into. The arrow's size is relative to /// the length of this vector. /// /// Color of the wireframe to draw public void DrawArrow(Vector3 origin, Vector3 direction, Color color) { const int VertexCount = WireframeArrowVertexGenerator.VertexCount; // If we would collide with the triangles in the array or there simply // isn't enough space left, set the overflow flag and silently skip drawing int proposedStart = this.lineIndex - (VertexCount - 1); if (proposedStart < this.triangleIndex) { this.overflowed = true; return; } // Generate the vertices for box' wireframe WireframeArrowVertexGenerator.Generate( this.queuedVertices, proposedStart, origin, direction, color ); this.lineIndex -= VertexCount; } /// Draws a solid arrow into the scene to visualize a vector /// /// Location at which to draw the arrow (this will form the exact center of /// the drawn arrow's base) /// /// /// Direction the arrow is pointing into. The arrow's size is relative to /// the length of this vector. /// /// Color of the arrow public void DrawSolidArrow(Vector3 origin, Vector3 direction, Color color) { const int VertexCount = SolidArrowVertexGenerator.VertexCount; // If we would collide with the triangles in the array or there simply // isn't enough space left, set the overflow flag and silently skip drawing int proposedEnd = this.triangleIndex + VertexCount; if (proposedEnd > this.lineIndex) { this.overflowed = true; return; } // Generate the vertices for the faces of the box SolidArrowVertexGenerator.Generate( this.queuedVertices, this.triangleIndex, origin, direction, color ); this.triangleIndex += VertexCount; } /// Draws text onto the screen at pixel coordinates /// /// Location on the screen, in pixels, where the text should be drawn. /// /// String to be drawn /// Color the text should have public void DrawString(Vector2 position, string text, Color color) { this.queuedStrings.Add(new QueuedString(text, position, color)); } /// Draws the debug overlays queued for this frame /// Provides a snapshot of timing values. public override void Draw(GameTime gameTime) { // Draw the lines and triangles we have queued in our vertex array drawGeometry(); // If the user has attempted to render more debugging overlays than the // DebugDrawer can handle (due to the fixed vertex buffer size), we can at // least print out a warning... if (this.overflowed) { DrawString( new Vector2(0.5f, 0.5f), "Warning: DebugDrawer is omitting some overlays", Color.Orange ); } // Draw the text on top of everything else drawStrings(); // Remove the geometry we've drawn from the buffers again //Reset(); } /// Draws the geometry (lines and triangles) queued up for this frame private void drawGeometry() { // Upload the vertices to the GPU and select their vertex declaration this.batchDrawer.Select(this.queuedVertices, this.queuedVertices.Length); // Set the view * projection matrix to use for transforming the input // vertices into screen coordinates #if WINDOWS_PHONE (this.fillEffect as BasicEffect).Projection = this.viewProjection; #else this.fillEffect.Parameters["ViewProjection"].SetValue(this.viewProjection); #endif // Draw all queued triangles if (this.triangleIndex > 0) { this.batchDrawer.Draw( 0, this.triangleIndex, PrimitiveType.TriangleList, this.drawContext ); } // Draw all queued lines if (this.lineIndex < (MaximumDebugVertexCount - 1)) { this.batchDrawer.Draw( this.lineIndex + 1, MaximumDebugVertexCount - 1 - this.lineIndex, PrimitiveType.LineList, drawContext ); } } /// Draws the strings queued up for this frame private void drawStrings() { this.fontSpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend); try { // Draw all strings that have been queued so far for (int index = 0; index < this.queuedStrings.Count; ++index) { this.fontSpriteBatch.DrawString( this.font, this.queuedStrings[index].Text, this.queuedStrings[index].Position, this.queuedStrings[index].Color ); } } finally { this.fontSpriteBatch.End(); } } /// Content manager used to load the debug drawer's effect file private ResourceContentManager contentManager; /// Effect used for drawing the debug overlays private Effect fillEffect; /// Drawing context used wit hthe batch drawer private EffectDrawContext drawContext; /// Buffer for constructing temporary vertices /// /// /// This array is filled from two sides: Triangles start at index 0 and /// are appended as normal while lines start at MaximumDebugVertexCount /// and are appended backwards. When the two list meet at the center /// of the array, the vertex list is full. /// /// /// We don't need a fancy drawing operation queue because the ordering of /// the debugging primitives does not matter. We simply draw all of the /// triangles and proceed with the lines, letting the z-buffer take care /// of the rest. /// /// private VertexPositionColor[] queuedVertices; /// The concatenated view and projection matrices private Matrix viewProjection; /// Drawer that sends the vertices to the GPU in the drawing phase private IBatchDrawer batchDrawer; /// Sprite font we're using for outputting strings to the screen private SpriteFont font; /// SpriteBatch used for font rendering private SpriteBatch fontSpriteBatch; /// Text queued to be rendered onto the scene private List queuedStrings; /// Index for the next triangle in the vertex array /// /// Counts from 0 up to MaximumDebugVertexCount, increased in multiples of 3. /// private int triangleIndex; /// Index for the next line in the vertex array /// /// Counts in reverse from MaximumDebugVertexCount to 0, /// decreased in multiples of 2. /// private int lineIndex; /// /// Flag indicating that the user has attempted to draw more primitives /// than our vertex array can hold. /// private bool overflowed; } } // namespace Nuclex.Graphics.Debugging