using SFML.Graphics;
using System.Collections.Generic;
namespace Otter {
///
/// Base abstract class used for anything that can be rendered.
///
public abstract class Graphic {
#region Private Fields
Color color;
protected VertexArray SFMLVertices = new VertexArray(PrimitiveType.Quads);
protected Drawable SFMLDrawable;
float shakeX;
float shakeY;
public List Transforms = new List() { new Transformation() };
Dictionary transformByString = new Dictionary();
protected float
RepeatSizeX,
RepeatSizeY;
protected bool roundRendering = true;
#endregion
#region Public Fields
public Transformation Transform {
get {
return Transforms[0];
}
}
public Transformation AddTransform(Transformation transform) {
Transforms.Add(transform);
return transform;
}
public Transformation AddTransform(Vector2 translation, Vector2 scale, Vector2 origin, float rotation) {
return AddTransform(new Transformation(translation, scale, origin, rotation));
}
public Transformation AddTransform() {
return AddTransform(Vector2.Zero, Vector2.One, Vector2.Zero, 0);
}
public Transformation RemoveTransformation(Transformation transform) {
Transforms.Remove(transform);
return transform;
}
public Transformation PopTransformation() {
if (Transforms.Count > 1) {
var t = Transforms[Transforms.Count - 1];
Transforms.Remove(t);
return t;
}
return null;
}
///
/// Determines if the Graphic is rendered relative to its Entity.
///
public bool Relative = true;
///
/// The X position of the Graphic.
///
public float X {
get {
return Transform.Translation.X;
}
set {
Transform.Translation.X = value;
}
}
///
/// The Y position of the Graphic.
///
public float Y {
get {
return Transform.Translation.Y;
}
set {
Transform.Translation.Y = value;
}
}
///
/// Determines if the Graphic will render.
///
public bool Visible = true;
///
/// The scroll factor for the x position. Used for parallax like effects. Values lower than 1
/// will scroll slower than the camera (appear to be further away) and values higher than 1
/// will scroll faster than the camera (appear to be closer.)
///
public float ScrollX = 1;
///
/// The scroll factor for the y position. Used for parallax like effects. Values lower than 1
/// will scroll slower than the camera (appear to be further away) and values higher than 1
/// will scroll faster than the camera (appear to be closer.)
///
public float ScrollY = 1;
///
/// The horizontal scale of the graphic. Used in the final transformation.
///
public float ScaleX {
get {
return Transform.Scale.X;
}
set {
Transform.Scale.X = value;
}
}
///
/// The vertical scale of the graphic. Used in the final transformation.
///
public float ScaleY {
get {
return Transform.Scale.Y;
}
set {
Transform.Scale.Y = value;
}
}
///
/// The angle of rotation of the graphic. Used in the final transformation.
///
public float Angle {
get {
return Transform.Rotation;
}
set {
Transform.Rotation = value;
}
}
///
/// The X origin point to scale and rotate the graphic with. Used in the final transformation.
///
public float OriginX {
get {
return Transform.Origin.X;
}
set {
Transform.Origin.X = value;
}
}
///
/// The Y origin point to scale and rotate the graphic with. Used in the final transformation.
///
public float OriginY {
get {
return Transform.Origin.Y;
}
set {
Transform.Origin.Y = value;
}
}
///
/// The horizontal amount to randomly offset the graphic by each frame.
///
public float ShakeX;
///
/// The vertial amount to randomly offset the graphic by each frame.
///
public float ShakeY;
///
/// If true the graphic will always update its drawable.
///
protected bool Dynamic;
///
/// The region to render the Texture with.
///
public Rectangle TextureRegion;
///
/// The Rectangle to render an AtlasTexture with.
///
public Rectangle AtlasRegion;
///
/// The shader to be applied to this graphic.
///
public Shader Shader;
///
/// The name of the graphic.
///
public string Name = "Graphic";
///
/// The blend mode to be applied to this graphic.
///
public BlendMode Blend = BlendMode.Alpha;
///
/// Determines if the image should be rendered multiple times horizontally.
///
public bool RepeatX;
///
/// Determines if the image should be rendered multiple times vertically.
///
public bool RepeatY;
#endregion
#region Public Properties
///
/// The base color of the Graphic. Multiplies the vertices of the graphic by this color.
///
public Color Color {
get {
if (color == null) color = Color.White;
return color;
}
set {
// Get rid of graphic reference in old color
if (color != null) {
color.Graphic = null;
}
color = new Color(value); // 2015/2/12: testing copying the color because of a bug
// Set reference so that NeedsUpdate will be activated on changes
color.Graphic = this;
NeedsUpdate = true;
}
}
///
/// The texture that the graphic is using.
///
public virtual Texture Texture { get; private set; }
///
/// The base transparency of the graphic. A shortcut to access the base color's Alpha.
///
public float Alpha {
get {
return Color.A;
}
set {
Color.A = value;
NeedsUpdate = true;
}
}
///
/// The width of the Graphic.
///
public int Width { get; protected set; }
///
/// The height of the Graphic.
///
public int Height { get; protected set; }
///
/// The width in pixels of the image after applying the X scale.
///
public float ScaledWidth {
get {
return Width * ScaleX;
}
set {
ScaleX = value / Width;
}
}
///
/// The height in pixels of the image after applying the Y scale.
///
public float ScaledHeight {
get {
return Height * ScaleY;
}
set {
ScaleY = value / Height;
}
}
///
/// Smooth the texture of a sprite image while scaling it.
///
public virtual bool Smooth {
get {
if (Texture != null) return Texture.Smooth;
return false;
}
set {
if (Texture != null) Texture.Smooth = value;
}
}
///
/// Set both ScrollX and ScrollY.
///
public float Scroll {
set { ScrollX = value; ScrollY = value; }
get { return (ScrollX + ScrollY) / 2f; }
}
///
/// Half of the width.
///
public float HalfWidth { get { return Width / 2f; } }
///
/// Half of the height.
///
public float HalfHeight { get { return Height / 2f; } }
///
/// Sets both the ScaleX and ScaleY at the same time.
///
public float Scale {
set { ScaleX = value; ScaleY = value; }
}
///
/// Sets both RepeatX and RepeatY at the same time.
///
public bool Repeat {
set { RepeatX = value; RepeatY = value; }
}
///
/// A shortcut to set both ShakeX and ShakeY.
///
public float Shake {
set { ShakeX = value; ShakeY = value; }
}
///
/// The X position of the left side of the Graphic.
///
public float Left {
get { return X - OriginX; }
}
///
/// The Y position of the top of the Graphic.
///
public float Top {
get { return Y - OriginY; }
}
///
/// The X position of the right side of the Graphic.
///
public float Right {
get { return Left + Width; }
}
///
/// The Y position of the bottom of the Graphic.
///
public float Bottom {
get { return Top + Height; }
}
///
/// The X position of the left of the Texture.
///
public int TextureLeft {
get {
return AtlasRegion.Left + TextureRegion.Left;
}
}
///
/// The X position of the right of the Texture.
///
public int TextureRight {
get {
return TextureLeft + TextureRegion.Width;
}
}
///
/// The Y position of the top of the Texture.
///
public int TextureTop {
get {
return AtlasRegion.Top + TextureRegion.Top;
}
}
///
/// The Y position of the bottom of the Texture.
///
public int TextureBottom {
get {
return TextureTop + TextureRegion.Height;
}
}
#endregion
#region Private Methods
protected void Append(float x, float y, Color color, float u, float v) {
SFMLVertices.Append(x, y, color, u, v);
}
protected void Append(float x, float y, Color color = null) {
SFMLVertices.Append(x, y, color);
}
///
/// Updates the internal SFML data for rendering.
///
protected virtual void UpdateDrawable() {
if (!Dynamic) {
NeedsUpdate = false;
}
}
protected virtual void TextureChanged() {
}
protected virtual void SFMLRender(Drawable drawable, float x = 0, float y = 0) {
RenderStates renderStates;
if (Texture != null) {
renderStates = new RenderStates(Texture.SFMLTexture);
}
else {
renderStates = RenderStates.Default;
}
renderStates.BlendMode = SFMLBlendMode(Blend);
if (Shader != null) {
renderStates.Shader = Shader.SFMLShader;
}
// This is really bad x_x lol
renderStates.Transform.Translate(x - OriginX, y - OriginY);
foreach (var t in Transforms) {
if (t != Transform) {
renderStates.Transform.Translate(t.X, t.Y);
}
renderStates.Transform.Rotate(-t.Angle, t.OriginX, t.OriginY);
renderStates.Transform.Scale(t.ScaleX, t.ScaleY, t.OriginX, t.OriginY);
}
if (Batchable) {
Draw.Batchable((VertexArray)drawable, renderStates);
return;
}
Draw.Drawable(drawable, renderStates);
}
#endregion
#region Public Methods
///
/// Removes the shader from the graphic.
///
public virtual void ClearShader() {
Shader = null;
}
///
/// Set the position of the Graphic.
///
/// The X Position.
/// The Y Position.
public void SetPosition(float x, float y) {
X = x;
Y = y;
}
///
/// Set the position of the Graphic.
///
/// The Graphic to get the position from.
public void SetPosition(Graphic g, float offsetX = 0, float offsetY = 0) {
SetPosition(g.X + offsetX, g.Y + offsetY);
}
///
/// Set the position of the Graphic.
///
/// The Vector2 to get the position from.
public void SetPosition(Vector2 xy) {
SetPosition(xy.X, xy.Y);
}
///
/// Set the origin of the Graphic.
///
/// The X origin.
/// The Y origin.
public void SetOrigin(float x, float y) {
OriginX = x;
OriginY = y;
}
///
/// Set the origin of the Graphic.
///
/// The X,Y position of the origin.
public void SetOrigin(Vector2 xy) {
SetOrigin(xy.X, xy.Y);
}
///
/// Set the Texture that the Graphic is using (if it is using one.)
///
/// The path to the Texture to use.
public void SetTexture(string path) {
SetTexture(new Texture(path));
}
///
/// Set the Texture that the Graphic is using (if it is using one.)
///
/// The Texture to use.
public void SetTexture(Texture texture) {
Texture = texture;
TextureRegion = texture.Region;
TextureChanged();
NeedsUpdate = true;
}
///
/// Set the Texture that the Graphic is using (if it is using one.)
///
/// The AtlasTexture to use.
public void SetTexture(AtlasTexture atlasTexture) {
Texture = atlasTexture.Texture;
AtlasRegion = atlasTexture.Region;
TextureRegion.Width = atlasTexture.Width;
TextureRegion.Height = atlasTexture.Height;
NeedsUpdate = true;
}
///
/// Update the graphic.
///
public virtual void Update() {
shakeX = Rand.Float(-ShakeX * 0.5f, ShakeX * 0.5f);
shakeY = Rand.Float(-ShakeY * 0.5f, ShakeY * 0.5f);
}
///
/// Centers the graphic origin.
///
public virtual void CenterOrigin() {
OriginX = HalfWidth;
OriginY = HalfHeight;
}
///
/// Centers the graphic origin while retaining its relative position.
///
public virtual void CenterOriginZero() {
float ox, oy;
ox = OriginX;
oy = OriginY;
CenterOrigin();
ox = OriginX - ox;
oy = OriginY - oy;
X += ox;
Y += oy;
}
///
/// Draws the graphic.
///
/// the x offset to draw the image from
/// the y offset to draw the image from
public virtual void Render(float x = 0, float y = 0) {
if (!Visible) return;
UpdateDrawableIfNeeded();
float renderX = X + x + shakeX;
float renderY = Y + y + shakeY;
// Rounding here to fix 1 pixel offset problems (textures wrap around?)
if (roundRendering) {
renderX = Util.Round(renderX);
renderY = Util.Round(renderY);
}
if (ScrollX != 1) {
renderX = X + Draw.Target.CameraX * (1 - ScrollX) + x;
}
if (ScrollY != 1) {
renderY = Y + Draw.Target.CameraY * (1 - ScrollY) + y;
}
float
screenX = renderX - Draw.Target.CameraX,
screenY = renderY - Draw.Target.CameraY;
float
zoom = Draw.Target.CameraZoom,
w = Draw.Target.Width + OriginX,
h = Draw.Target.Height + OriginY;
float
repeatLeft = Draw.Target.CameraX - (w / zoom - w) * 0.5f,
repeatTop = Draw.Target.CameraY - (h / zoom - h) * 0.5f,
repeatRight = Draw.Target.CameraX + (w / zoom + w) * 0.5f,
repeatBottom = Draw.Target.CameraY + (h / zoom + w) * 0.5f;
RepeatSizeX = repeatRight - repeatLeft + OriginX;
RepeatSizeY = repeatBottom - repeatTop + OriginY;
Drawable drawable;
if (SFMLDrawable == null) {
drawable = SFMLVertices;
}
else {
drawable = SFMLDrawable;
}
if (!RepeatX && !RepeatY) {
SFMLRender(drawable, renderX, renderY);
}
else if (RepeatX && !RepeatY) {
while (renderX > repeatLeft) {
renderX -= ScaledWidth;
}
while (renderX < repeatRight) {
SFMLRender(drawable, renderX, renderY);
renderX += ScaledWidth;
}
}
else if (!RepeatX && RepeatY) {
while (renderY > repeatTop) {
renderY -= ScaledHeight;
}
while (renderY < repeatBottom) {
SFMLRender(drawable, renderX, renderY);
renderY += Height;
}
}
else if (RepeatX && RepeatY) {
float startX = renderX;
while (renderY > repeatTop) {
renderY -= ScaledHeight;
}
while (renderY < repeatBottom) {
while (renderX > repeatLeft) {
renderX -= ScaledWidth;
}
while (renderX < repeatRight) {
SFMLRender(drawable, renderX, renderY);
renderX += ScaledWidth;
}
renderY += ScaledHeight;
}
}
}
#endregion
#region Internal
///
/// Determines if the graphic's core drawable will have to be updated before it's rendered.
///
internal bool NeedsUpdate = true; // Needs update on first update
internal bool Batchable = false;
internal void UpdateDrawableIfNeeded() {
if (NeedsUpdate) {
UpdateDrawable();
}
}
internal SFML.Graphics.BlendMode SFMLBlendMode(BlendMode blend) {
switch (blend) {
case BlendMode.Alpha: return SFML.Graphics.BlendMode.Alpha;
case BlendMode.Add: return SFML.Graphics.BlendMode.Add;
case BlendMode.Multiply: return SFML.Graphics.BlendMode.Multiply;
case BlendMode.None: return SFML.Graphics.BlendMode.None;
}
return SFML.Graphics.BlendMode.None;
}
#endregion
}
#region Enum
///
/// The blendmodes that can be used for graphic rendering.
///
public enum BlendMode {
Alpha,
Add,
Multiply,
None,
Null
}
#endregion
}