#region CPL License /* Nuclex Framework Copyright (C) 2002-2009 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Content; using DeviceEventHandler = System.EventHandler; namespace Nuclex.Graphics.SpecialEffects.Water { /// Simple water surface public class WaterSurface : IDisposable { /// Delegate for a method used to draw the scene /// Snapshot of the game's timing values /// Camera through which the scene is being viewed public delegate void SceneDrawDelegate(GameTime gameTime, Camera camera); /// Initializes a new water surface /// Graphics device to use for rendering /// Lesser coordinates of the region covered by water /// Greater coordinates of the region covered by water public WaterSurface( GraphicsDevice graphicsDevice, Vector2 min, Vector2 max ) : this(graphicsDevice, min, max, 1, 1) { } /// Initializes a new water surface /// Graphics device to use for rendering /// Lesser coordinates of the region covered by water /// Greater coordinates of the region covered by water /// Number segments (and texture repeats) on the X axis /// Number segments (and texture repeats) on the Y axis public WaterSurface( GraphicsDevice graphicsDevice, Vector2 min, Vector2 max, int segmentsX, int segmentsZ ) { this.graphicsDevice = graphicsDevice; this.grid = new WaterGrid(graphicsDevice, min, max, segmentsX, segmentsZ); this.reflectionCamera = new Camera(Matrix.Identity, Matrix.Identity); this.graphicsDevice.DeviceResetting += new DeviceEventHandler(graphicsDeviceResetting); this.graphicsDevice.DeviceReset += new DeviceEventHandler(graphicsDeviceReset); createRenderTarget(); } /// /// Called when graphics resources should be released. Override to /// handle component specific graphics resources /// public void Dispose() { if(this.reflectionRenderTarget != null) { this.reflectionRenderTarget.Dispose(); this.reflectionRenderTarget = null; } if(this.grid != null) { this.grid.Dispose(); this.grid = null; } } /// Selects the vertex and index buffer for the water surface public void SelectVertexAndIndexBuffer() { this.graphicsDevice.SetVertexBuffer(this.grid.VertexBuffer); this.graphicsDevice.Indices = this.grid.IndexBuffer; } /// Draws the plane making up the water surface /// Snapshot of the game's timing values /// Camera through which the scene is being viewed public void DrawWaterPlane(GameTime gameTime, Camera camera) { this.graphicsDevice.DrawIndexedPrimitives( this.grid.PrimitiveType, // primitive type to render 0, // will be added to all vertex indices in the index buffer 0, // minimum index of the vertices in the vertex buffer used in this call this.grid.VertexCount, // number of vertices used in this call 0, // where in the index buffer to start drawing this.grid.PrimitiveCount // number of primitives to draw ); } /// Updates the reflected image on the water surface /// Snapshot of the game's timing values /// Camera through which the scene is being viewed /// /// Delegate that will be called to draw the scene in its reflected state /// /// /// /// When the delegate is called, the scene should be drawn normally using /// the provided game time and camera. The view matrix of the provided camera /// will have been adjusted to draw the scene upside-down and the graphics device /// will be configured to clip off anything that's below the water surface. /// /// /// Some adjustments can be made, like rendering the reflection with reduced /// detail, cheaper effects or even leaving our parts of the scene to improve /// performance since the reflection will not be clearly displayed anyway. /// /// public void UpdateReflection( GameTime gameTime, Camera camera, SceneDrawDelegate reflectedSceneDrawer ) { // Create a matrix that undoes the view and projection transforms. We don't // involve any world matrix here because the water plane exists in // the world coordinate frame, its world matrix is always the identity matrix. Matrix viewProjection = camera.View * camera.Projection; Matrix inverseViewProjection = Matrix.Invert(viewProjection); // The water plane in world coordinates. We want to clip away everything that's // below the water surface (as only things above it should be reflected) Vector4 worldWaterPlane = new Vector4(Vector3.Down, 0.0f); // The water plane as sent through the inverse view and projection transforms. // This is neccessary because the plane will be transformed by those two // matrices when it is applied to the scene. Don't ask me why. Vector4 projectedWaterPlane = Vector4.Transform( worldWaterPlane, Matrix.Transpose(inverseViewProjection) ); this.graphicsDevice.SetRenderTarget(this.reflectionRenderTarget); try { // Set up a clipping plane that only draws those parts of the scene that // are above the water because that's the only thing we want to appear in // the reflection on the water surface. Matrix reflectionMatrix = Matrix.CreateReflection(new Plane(worldWaterPlane)); this.reflectionCamera.View = reflectionMatrix * camera.View; this.reflectionCamera.Projection = camera.Projection; reflectedSceneDrawer(gameTime, this.reflectionCamera); } finally { this.graphicsDevice.SetRenderTarget(null); } } /// Texture containing the water reflection public Texture2D ReflectionTexture { get { return this.reflectionRenderTarget; } } /// Camera which views the scene turned upside-down public Camera ReflectionCamera { get { return this.reflectionCamera; } } /// Sets up the render target for the water surface reflection private void createRenderTarget() { int width = Math.Max(graphicsDevice.PresentationParameters.BackBufferWidth, 16); int height = Math.Max(graphicsDevice.PresentationParameters.BackBufferHeight, 16); this.reflectionRenderTarget = new RenderTarget2D( graphicsDevice, width, height, false, // mipMap graphicsDevice.PresentationParameters.BackBufferFormat, graphicsDevice.PresentationParameters.DepthStencilFormat, 1, // MultisampleCount RenderTargetUsage.PlatformContents ); } /// Called when the graphics device has completed a reset /// Graphics device that has completed a reset /// Not used private void graphicsDeviceReset(object sender, EventArgs arguments) { createRenderTarget(); } /// Called when the graphics device is about to perform a reset /// Graphics device that is about to perform a reset /// Not used private void graphicsDeviceResetting(object sender, EventArgs arguments) { if(this.reflectionRenderTarget != null) { this.reflectionRenderTarget.Dispose(); this.reflectionRenderTarget = null; } } /// Camera used to draw the water reflection private Camera reflectionCamera; /// GraphicsDevice the water surface is rendered with private GraphicsDevice graphicsDevice; /// Grid containing the vertices of the water surface private WaterGrid grid; /// Render target used for the refraction and reflection textures private RenderTarget2D reflectionRenderTarget; } } // namespace Nuclex.SpecialEffects.Water