using SFML.Graphics;
using SFML.System;
using SFML.Window;
using System;
using System.Collections.Generic;
namespace Otter {
///
/// Graphic that represents a render target. By default the game uses a master surface to
/// render the game to the window. Be aware of graphics card limiations of render textures when
/// creating surfaces.
///
public class Surface : Image {
#region Private Fields
RenderStates states;
float cameraX, cameraY, cameraAngle, cameraZoom = 1f;
RectangleShape fill;
List shaders = new List();
RenderTexture
postProcessA,
postProcessB;
bool saveNextFrame;
string saveNameFramePath;
#endregion
#region Public Fields
///
/// The color that the surface will fill with at the start of each render.
///
public Color FillColor;
///
/// Determines if the Surface will automatically clear at the start of the next render cycle.
///
public bool AutoClear = true;
///
/// Determines if the Surface will automatically set its camera to the Scene's camera.
///
public bool UseSceneCamera;
#endregion
#region Public Properties
///
/// The reference to the Game using this Surface (if it is the main Surface the game is rendering to!)
///
public Game Game { get; internal set; }
///
/// The camera X for the view of the surface.
/// Note: For the game's main surface, this is controlled by the active Scene.
///
public float CameraX {
set {
cameraX = value;
RefreshView();
}
get {
return cameraX;
}
}
///
/// The camera Y for the view of the surface.
/// Note: For the game's main surface, this is controlled by the active Scene.
///
public float CameraY {
set {
cameraY = value;
RefreshView();
}
get {
return cameraY;
}
}
///
/// The camera angle for the view of the surface.
/// Note: For the game's main surface, this is controlled by the active Scene.
///
public float CameraAngle {
set {
cameraAngle = value;
RefreshView();
}
get {
return cameraAngle;
}
}
///
/// The camera zoom for the view of the surface.
/// Note: For the game's main surface, this is controlled by the active Scene.
///
public float CameraZoom {
set {
cameraZoom = value;
if (cameraZoom <= 0) { cameraZoom = 0.0001f; } //dont be divin' by zero ya hear?
RefreshView();
}
get {
return cameraZoom;
}
}
public float CameraWidth {
get {
return Width / CameraZoom;
}
}
public float CameraHeight {
get {
return Height / CameraZoom;
}
}
///
/// The Texture the Surface has rendered to.
///
public override Texture Texture {
get {
return new Texture(renderTexture.Texture);
}
}
///
/// Convert an X position into the same position but on the Surface.
/// TODO: Make this work with scale and rotation.
///
/// The X position in the Scene.
/// The X position on the Surface.
public float SurfaceX(float x) {
return x - X + cameraX;
}
///
/// Convert a Y position into the same position but on the Surface.
/// TODO: Make this work with scale and rotation.
///
/// The Y position in the Scene.
/// The Y position on the Surface.
public float SurfaceY(float y) {
return y - Y + cameraY;
}
#endregion
#region Constructors
///
/// Creates a Surface with a specified size.
///
/// The width of the Surface to create.
/// The height of the Surface to create.
/// The default fill color of the Surface.
public Surface(int width, int height, Color color = null) {
if (width < 0) throw new ArgumentException("Width must be greater than 0.");
if (height < 0) throw new ArgumentException("Height must be greater than 0.");
if (color == null) color = Color.None;
FillColor = color;
Width = width;
Height = height;
renderTexture = new RenderTexture((uint)Width, (uint)Height);
TextureRegion = new Rectangle(0, 0, Width, Height);
ClippingRegion = TextureRegion;
renderTexture.Smooth = Texture.DefaultSmooth;
fill = new RectangleShape(new Vector2f(Width, Height)); // Using this for weird clearing bugs on some video cards
Clear();
}
///
/// Creates a Surface of a specified size.
///
/// The width of the Surface to create.
/// The height of the Surface to create.
public Surface(int width, int height) : this(width, height, Color.None) { }
///
/// Creates a Surface of a specified size.
///
/// The width and height of the Surface to create.
public Surface(int size) : this(size, size) { }
///
/// Creates a Surface of a specified size.
///
/// The width and height of the Surface to create.
/// The default fill color of the Surface.
public Surface(int size, Color color) : this(size, size, color) { }
#endregion
#region Private Methods
void UpdateShader() {
if (shaders.Count < 2) {
if (postProcessA != null) {
postProcessA.Dispose();
postProcessA = null;
}
if (postProcessB != null) {
postProcessB.Dispose();
postProcessB = null;
}
}
else if (shaders.Count == 2) {
if (postProcessA == null) {
postProcessA = new RenderTexture((uint)Width, (uint)Height);
}
if (postProcessB != null) {
postProcessB.Dispose();
postProcessB = null;
}
}
else if (shaders.Count > 2) {
if (postProcessA == null) {
postProcessA = new RenderTexture((uint)Width, (uint)Height);
}
if (postProcessB == null) {
postProcessB = new RenderTexture((uint)Width, (uint)Height);
}
}
}
void RefreshView() {
View v = new View(new FloatRect(cameraX, cameraY, Width, Height));
v.Rotation = -cameraAngle;
v.Zoom(1 / cameraZoom);
RenderTarget.SetView(v);
}
#endregion
#region Public Methods
///
/// Add a shader to be drawn on the surface. If "Shader" is set, the shader list is ignored.
///
/// The Shader to add.
public void AddShader(Shader shader) {
shaders.Add(shader);
UpdateShader();
}
///
/// Remove a shader from the surface.
///
/// The Shader to remove.
public void RemoveShader(Shader shader) {
shaders.Remove(shader);
UpdateShader();
}
///
/// Remove the top most shader on the list of shaders.
///
/// The removed Shader.
public Shader PopShader() {
if (shaders.Count == 0) return null;
var shader = shaders[shaders.Count - 1];
RemoveShader(shader);
return shader;
}
///
/// Calls the SFML Display function on the internal render texture. Should be used before
/// any sort of rendering, otherwise the texture will be upside down!
///
public void Display() {
renderTexture.Display();
SetTexture(new Texture(renderTexture.Texture));
Update();
UpdateDrawable();
}
///
/// Remove all shaders from the surface.
///
public void ClearShaders() {
shaders.Clear();
UpdateShader();
}
///
/// Replace all shaders with a single shader. This will be ignored if "Shader" is set.
///
/// The Shader to use.
public void SetShader(Shader shader) {
shaders.Clear();
shaders.Add(shader);
UpdateShader();
}
///
/// Draws a graphic to this surface.
///
/// The Graphic to draw.
/// The X position of the Graphic.
/// The Y position of the Graphic.
public void Draw(Graphic graphic, float x = 0, float y = 0) {
Surface tempSurface = Otter.Draw.Target;
Otter.Draw.SetTarget(this);
graphic.Render(x, y);
Otter.Draw.SetTarget(tempSurface);
}
///
/// Fills the surface with the specified color.
///
/// The Color to fill the Surface with.
public void Fill(Color color) {
fill.Size = new Vector2f(Width, Height);
fill.FillColor = color.SFMLColor;
fill.Position = new Vector2f(CameraX, CameraY);
renderTexture.Draw(fill); // Sometimes after 20-30 frames, game will freeze here?
}
///
/// Clears the surface with the fill color.
///
public void Clear() {
renderTexture.Clear(FillColor.SFMLColor);
}
///
/// Clears the surface with a specified color.
///
/// The Color to clear the Surface with.
public void Clear(Color color) {
renderTexture.Clear(color.SFMLColor);
}
///
/// Determines the pixel smoothing for the surface.
///
public override bool Smooth {
get {
return renderTexture.Smooth;
}
set {
renderTexture.Smooth = value;
}
}
///
/// Draw the Surface.
///
/// The X position offset.
/// The Y position offset.
public override void Render(float x = 0, float y = 0) {
Display();
SFMLDrawable = RenderShaders();
base.Render(x, y);
if (saveNextFrame) {
saveNextFrame = false;
var saveTarget = new RenderTexture((uint)Width, (uint)Height);
saveTarget.Draw(SFMLDrawable, states);
saveTarget.Display();
saveTarget.Texture.CopyToImage().SaveToFile(saveNameFramePath);
saveTarget.Dispose();
}
if (AutoClear) Clear();
}
///
/// Draw the surface directly to the game window. This will refresh the view,
/// and Display the surface, as well as clear it if AutoClear is true.
///
/// The Game to render to.
public void DrawToWindow(Game game) {
RefreshView();
Display();
Drawable drawable = RenderShaders();
game.Window.Draw(drawable, states);
if (saveNextFrame) {
saveNextFrame = false;
game.Window.Capture().SaveToFile(saveNameFramePath);
}
if (AutoClear) Clear(FillColor);
}
///
/// Draw the Surface to the Game window.
///
public void DrawToWindow() {
DrawToWindow(Game);
}
///
/// Set view of the Surface.
///
/// The X position of the view.
/// The Y position of the view.
/// The angle of the view.
/// The zoom of the view.
public void SetView(float x, float y, float angle = 0, float zoom = 1f) {
cameraX = x;
cameraY = y;
cameraAngle = angle;
cameraZoom = zoom;
RefreshView();
}
///
/// Returns a texture by getting the current render texture. I don't know if this works right yet.
///
///
public Texture GetTexture() {
return new Texture(renderTexture.Texture);
}
///
/// Saves the next completed render to a file. The supported image formats are bmp, png, tga and jpg.
/// Note that this waits until the end of the game's Render() to actually export, otherwise it will be blank!
///
///
/// The file path to save to. The type of image is deduced from the extension. If left unspecified the
/// path will be a png file of the current time in the same folder as the executable.
///
public void SaveToFile(string path = "") {
saveNextFrame = true;
if (path == "") {
path = string.Format("{0:yyyyMMddHHmmssff}.png", DateTime.Now);
}
saveNameFramePath = path;
}
///
/// Matches the view of the surface to the same view of a Scene.
///
/// The Scene to match the camera with.
public void CameraScene(Scene scene) {
SetView(scene.CameraX + X, scene.CameraY + Y, scene.CameraAngle, scene.CameraZoom);
}
///
/// Centers the camera of the surface.
///
/// The X position to be the center of the scene.
/// The Y position to be the center of the scene.
public void CenterCamera(float x, float y) {
CameraX = x - HalfWidth;
CameraY = y - HalfHeight;
}
#endregion
#region Internal
internal RenderTexture renderTexture;
internal void Draw(Drawable drawable) {
RenderTarget.Draw(drawable);
}
internal void Draw(Vertex[] vertices, RenderStates states) {
RenderTarget.Draw(vertices, PrimitiveType.Quads, states);
}
internal void Draw(Vertex[] vertices, PrimitiveType primitiveType, RenderStates states) {
RenderTarget.Draw(vertices, primitiveType, states);
}
internal void Draw(List vertices, PrimitiveType primitiveType, RenderStates states) {
Draw(vertices.ToArray(), primitiveType, states);
}
internal void Draw(List vertices, RenderStates states) {
Draw(vertices.ToArray(), states);
}
internal void Draw(Texture texture, float x, float y, float originX, float originY, int width, int height, float scaleX, float scaleY, float angle, Color color = null, BlendMode blend = BlendMode.Alpha, Shader shader = null) {
states = new RenderStates(Texture.SFMLTexture);
//states.BlendMode = (SFML.Graphics.BlendMode)Blend;
states.BlendMode = SFMLBlendMode(blend);
if (Shader != null) {
states.Shader = Shader.SFMLShader;
}
states.Transform.Translate(x - OriginX, y - OriginY);
states.Transform.Rotate(-Angle, OriginX, OriginY);
states.Transform.Scale(ScaleX, ScaleY, OriginX, OriginY);
var v = new VertexArray(PrimitiveType.Quads);
if (color == null) color = Color.White;
v.Append(x, y, color, 0, 0);
v.Append(x + width, y, color, width, 0);
v.Append(x + width, y + height, color, width, height);
v.Append(x, y + height, color, 0, height);
Draw(v, states);
}
internal void Draw(Drawable drawable, RenderStates states) {
RenderTarget.Draw(drawable, states);
}
internal RenderTarget RenderTarget {
get { return renderTexture; }
}
///
/// This goes through all the shaders and applies them between two buffers, and
/// eventually spits out the final drawable object.
///
///
Drawable RenderShaders() {
Drawable drawable = SFMLVertices;
states = new RenderStates(renderTexture.Texture);
SetTexture(new Texture(renderTexture.Texture));
states.Transform.Translate(X - OriginX, Y - OriginY);
states.Transform.Rotate(Angle, OriginX, OriginY);
states.Transform.Scale(ScaleX, ScaleY, OriginX, OriginY);
//states.BlendMode = (SFML.Graphics.BlendMode)Blend;
states.BlendMode = SFMLBlendMode(Blend);
if (Shader != null) {
states.Shader = Shader.SFMLShader;
}
else {
if (shaders.Count == 1) {
states.Shader = shaders[0].SFMLShader;
}
else if (shaders.Count == 2) {
states = new RenderStates(renderTexture.Texture);
states.Shader = shaders[0].SFMLShader;
Game.Instance.RenderCount++;
postProcessA.Draw(SFMLVertices, states);
postProcessA.Display();
states.Shader = shaders[1].SFMLShader;
drawable = new Sprite(postProcessA.Texture);
states.Transform.Rotate(Angle, OriginX, OriginY);
states.Transform.Translate(new Vector2f(X - OriginX, Y - OriginY));
states.Transform.Scale(ScaleX, ScaleY, OriginX, OriginY);
}
else if (shaders.Count > 2) {
states = new RenderStates(renderTexture.Texture);
RenderTexture nextRt, currentRt;
nextRt = postProcessB;
currentRt = postProcessA;
states.Shader = shaders[0].SFMLShader;
Game.Instance.RenderCount++;
postProcessA.Draw(SFMLVertices, states);
postProcessA.Display();
for (int i = 1; i < shaders.Count - 1; i++) {
states = RenderStates.Default;
states.Shader = shaders[i].SFMLShader;
Game.Instance.RenderCount++;
nextRt.Draw(new Sprite(currentRt.Texture), states);
nextRt.Display();
nextRt = nextRt == postProcessA ? postProcessB : postProcessA;
currentRt = currentRt == postProcessA ? postProcessB : postProcessA;
}
drawable = new Sprite(currentRt.Texture);
currentRt.Display();
states.Shader = shaders[shaders.Count - 1].SFMLShader;
states.Transform.Rotate(Angle, OriginX, OriginY);
states.Transform.Translate(new Vector2f(X - OriginX, Y - OriginY));
states.Transform.Scale(ScaleX, ScaleY, OriginX, OriginY);
}
}
return drawable;
}
#endregion
}
}