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