using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Plex.Engine.GraphicsSubsystem { /// /// Encapsulates a and and contains methods for easily rendering various objects using those encapsulated objects. This class cannot be inherited. /// /// /// The class employs scissor testing in all of its draw calls. This makes it so that any data rendering outside the scissor rectangle (defined by the , , and properties) will be clipped and not rendered to the screen. /// Also, apart from the and properties of the graphics context, any X/Y coordinate pairs are relative to the coordinates of the scissor rectangle. So, the coordinates (0,5) refer to +0,+5. /// /// /// /// /// /// public sealed class GraphicsContext { private static Texture2D white = null; /// /// Retrieves the sprite batch associated with this graphics context. /// public SpriteBatch Batch { get { return _spritebatch; } } /// /// Retrieves the graphics device associated with this graphics context. /// public GraphicsDevice Device { get { return _graphicsDevice; } } /// /// Gets or sets the X coordinate of the scissor rectangle. /// public int X { get { return Device.ScissorRectangle.X; } set { Device.ScissorRectangle = new Rectangle(value, Y, Width, Height); } } /// /// Gets or sets the Y coordinate of the scissor rectangle. /// public int Y { get { return Device.ScissorRectangle.Y; } set { Device.ScissorRectangle = new Rectangle(X, value, Width, Height); } } /// /// Gets or sets the width of the scissor rectangle. /// public int Width { get { return Device.ScissorRectangle.Width; } set { Device.ScissorRectangle = new Rectangle(X, Y, value, Height); } } /// /// Gets or sets the height of the scissor rectangle. /// public int Height { get { return Device.ScissorRectangle.Height; } set { Device.ScissorRectangle = new Rectangle(X, Y, Width, value); } } /// /// Draw an outlined polygon. /// /// The color of the polygon's outlines /// The various X and Y coordinates relative to the scissor rectangle of the polygon. The size of this array must be a multiple of 2. /// The array does not have a length which is a multiple of 2. public void DrawPolygon(Color c, params int[] locs) { if ((locs.Length % 2) != 0) throw new Exception("The locs argument count must be a multiple of 2."); for(int i = 0; i < locs.Length; i+= 2) { int x = locs[i]; int y = locs[i + 1]; int x1 = locs[0]; int y1 = locs[1]; if (i < locs.Length - 2) { x1 = locs[i + 2]; y1 = locs[i + 3]; } DrawLine(x, y, x1, y1, 1, c); } } private GraphicsDevice _graphicsDevice; private SpriteBatch _spritebatch; /// /// Creates a new instance of the class. /// /// The graphics device where rendering will take place. /// The sprite batch to associate with the graphics context. /// The starting X coordinate of the scissor rectangle. /// The starting Y coordinate of the scissor rectangle. /// The starting width of the scissor rectangle. /// The starting height of the scissor rectangle. public GraphicsContext(GraphicsDevice device, SpriteBatch batch, int x, int y, int width, int height) { if (device == null || batch == null) throw new ArgumentNullException(); _graphicsDevice = device; _spritebatch = batch; if(white == null) { white = new Texture2D(_graphicsDevice, 1, 1); white.SetData(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }); } Width = width; Height = height; X = x; Y = y; } /// /// Clears the canvas with the specified color. /// /// The color to render public void Clear(Color c) { DrawRectangle(0, 0, Width, Height, c); } /// /// Draw a line between two separate points on the canvas /// /// The X coordinate of the first point /// The Y coordinate of the first point /// The X coordinate of the second point /// The Y coordinate of the second point /// The thickness of the line /// The line's texture public void DrawLine(int x, int y, int x1, int y1, int thickness, Texture2D tex2) { DrawLine(x, y, x1, y1, thickness, tex2, Color.White); } /// /// Draw a line with a tint between two separate points on the canvas /// /// The X coordinate of the first point /// The Y coordinate of the first point /// The X coordinate of the second point /// The Y coordinate of the second point /// The thickness of the line /// The line's texture /// The tint of the texture public void DrawLine(int x, int y, int x1, int y1, int thickness, Texture2D tex2, Color tint) { if (tint.A == 0) return; //no sense rendering if you CAN'T SEE IT x += X; y += Y; x1 += X; y1 += Y; int distance = (int)Vector2.Distance(new Vector2(x, y), new Vector2(x1, y1)); float rotation = GetRotation(x, y, x1, y1); _spritebatch.Draw(tex2, new Rectangle(x, y, distance, thickness), null, tint, rotation, Vector2.Zero, SpriteEffects.None, 0); } /// /// Draw a line with a tint between two separate points on the canvas /// /// The X coordinate of the first point /// The Y coordinate of the first point /// The X coordinate of the second point /// The Y coordinate of the second point /// The thickness of the line /// The color of the line public void DrawLine(int x, int y, int x1, int y1, int thickness, Color color) { if (color.A == 0) return; //no sense rendering if you CAN'T SEE IT x += X; y += Y; x1 += X; y1 += Y; int distance = (int)Vector2.Distance(new Vector2(x, y), new Vector2(x1, y1)); float rotation = GetRotation(x, y, x1, y1); _spritebatch.Draw(white, new Rectangle(x, y, distance, thickness), null, color, rotation, Vector2.Zero, SpriteEffects.None, 0); } /// /// Draw a rectangle with the specified color to the canvas. /// /// The X coordinate of the rectangle /// The Y coordinate of the rectangle /// The width of the rectangle /// The height of the rectangle /// The color of the rectangle public void DrawRectangle(int x, int y, int width, int height, Color color) { if (color.A == 0) return; //no sense rendering if you CAN'T SEE IT x += X; y += Y; _spritebatch.Draw(white, new Rectangle(x, y, width, height), color); } /// /// Begin a draw call. /// public void BeginDraw() { _spritebatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, Device.DepthStencilState, RasterizerState); } /// /// End the current draw call. /// public void EndDraw() { _spritebatch.End(); } /// /// Draw a circle to the canvas. /// /// The X coordinate of the circle /// The Y coordinate of the circle /// The radius of the circle /// The color of the circle public void DrawCircle(int x, int y, int radius, Color color) { if (color.A == 0) return; //no sense rendering if you CAN'T SEE IT float step = (float) Math.PI / (radius * 4); var rect = new Rectangle(x, y, radius, 1); for (float theta = 0; theta < 2 * Math.PI; theta += step) _spritebatch.Draw(white, rect, null, color, theta, Vector2.Zero, SpriteEffects.None, 0); } /// /// Draw a rectangle with the specified texture and tint to the canvas. /// /// The X coordinate of the rectangle /// The Y coordinate of the rectangle /// The width of the rectangle /// The height of the rectangle /// The texture of the rectangle /// The tint of the rectangle public void DrawRectangle(int x, int y, int width, int height, Texture2D tex2, ImageLayout layout = ImageLayout.Stretch) { DrawRectangle(x, y, width, height, tex2, Color.White, layout); } /// /// Retrieves a new preferred to be used by the graphics context. /// public readonly RasterizerState RasterizerState = new RasterizerState { ScissorTestEnable = true, MultiSampleAntiAlias = true }; /// /// Draw a rectangle with the specified texture, tint and to the canvas. /// /// The X coordinate of the rectangle /// The Y coordinate of the rectangle /// The width of the rectangle /// The height of the rectangle /// The texture of the rectangle /// The tint of the texture /// The layout of the texture /// Whether the rectangle should be opaque regardless of the texture data or tint's alpha value. /// Whether the texture data is already pre-multiplied. public void DrawRectangle(int x, int y, int width, int height, Texture2D tex2, Color tint, ImageLayout layout = ImageLayout.Stretch, bool opaque = false, bool premultiplied=true) { if (tint.A == 0) return; //no sense rendering if you CAN'T SEE IT if (tex2 == null) return; x += X; y += Y; _spritebatch.End(); var state = SamplerState.LinearClamp; if (layout == ImageLayout.Tile) state = SamplerState.LinearWrap; if (opaque) { _spritebatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, state, Device.DepthStencilState, RasterizerState); } else { if (premultiplied) { _spritebatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, state, Device.DepthStencilState, RasterizerState); } else { _spritebatch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied, state, Device.DepthStencilState, RasterizerState); } } switch (layout) { case ImageLayout.Tile: _spritebatch.Draw(tex2, new Vector2(x,y), new Rectangle(0, 0, width, height), Color.White, 0, Vector2.Zero, 1f, SpriteEffects.None, 0); break; case ImageLayout.Stretch: _spritebatch.Draw(tex2, new Rectangle(x, y, width, height), tint); break; case ImageLayout.None: _spritebatch.Draw(tex2, new Rectangle(x, y, tex2.Width, tex2.Height), tint); break; case ImageLayout.Center: _spritebatch.Draw(tex2, new Rectangle(x+((width - tex2.Width) / 2), y+((height - tex2.Height) / 2), tex2.Width, tex2.Height), tint); break; case ImageLayout.Zoom: float scale = Math.Min(width / (float)tex2.Width, height / (float)tex2.Height); var scaleWidth = (int)(tex2.Width * scale); var scaleHeight = (int)(tex2.Height * scale); _spritebatch.Draw(tex2, new Rectangle(x+(((int)width - scaleWidth) / 2), y+(((int)height - scaleHeight) / 2), scaleWidth, scaleHeight), tint); break; ; } _spritebatch.End(); BeginDraw(); } /// /// Measure a string. Note that this method is a stub and just calls . This stub will be removed soon. /// /// The text to measure /// The font to measure with /// The maximum width text can be before it is wrapped /// The wrap mode of the text /// The size of the text in pixels public static Vector2 MeasureString(string text, SpriteFont font, int wrapWidth = int.MaxValue, WrapMode wrapMode = WrapMode.Words) { return TextRenderer.MeasureText(text, font, wrapWidth, wrapMode); } /// /// Draw a string of text. /// /// The text to render /// The X coordinate of the text /// The Y coordinate of the text /// The color of the text /// The font of the text /// The alignment of the text /// The maximum width text can be before it is wrapped. /// The wrap mode of the text public void DrawString(string text, int x, int y, Color color, SpriteFont font, TextAlignment alignment, int wrapWidth = int.MaxValue, WrapMode wrapMode = WrapMode.Words) { x += X; y += Y; if (color.A == 0) return; //no sense rendering if you CAN'T SEE IT if (string.IsNullOrEmpty(text)) return; TextRenderer.DrawText(this, x, y, text, font, color, wrapWidth, alignment, wrapMode); } private float GetRotation(float x, float y, float x2, float y2) { float adj = x - x2; float opp = y - y2; return (float) Math.Atan2(opp, adj) - (float) Math.PI; } } public enum ImageLayout { Tile, Stretch, None, Zoom, Center } }