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