using System; using System.Collections.Generic; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using Fds.Xna.Enigma.Graphics; using Microsoft.Xna.Framework.Graphics; using Fds.Xna.Enigma.Graphics.Lighting; namespace Fds.Xna.Enigma.Sandbox { public enum EditType { Terrain, Texture } public class TerrainManipulationScene : Scene { const int width = 256; const int depth = 256; Vector4[] weights = new Vector4[] { new Vector4(1, 0, 0, 0), new Vector4(0, 1, 0, 0), new Vector4(0, 0, 1, 0), new Vector4(0, 0, 0, 1), }; SpriteBatch spriteBatch; Effect effect; Texture2D[] textures; KeyboardState previousKeyboardState; MouseState previousMouseState; VertexDeclaration vertexDeclaration; Matrix world = Matrix.CreateTranslation(Vector3.Zero); VertexTerrain[] vertices = new VertexTerrain[width * depth]; Vector3 pickTarget; EditType editType = EditType.Terrain; int[] indices = new int[(width * depth * 6)]; int triangleCount; int? selctedIndex; float morphRadius = 1; float morphPower = 1; int currentTextureIndex = 0; public TerrainManipulationScene(Game game) : base(game) { } protected override void LoadContent() { base.LoadContent(); Game.IsMouseVisible = true; spriteBatch = new SpriteBatch(GraphicsDevice); vertexDeclaration = new VertexDeclaration(GraphicsDevice, VertexTerrain.VertexElements); effect = Game.Content.Load("Effects\\DeferredRendering\\DeferredTerrain"); DirectionalLight dlight = new DirectionalLight(); dlight.Direction = new Vector3(0f, -1f, 0); dlight.Color = Color.White; Lights.Add(dlight); textures = new Texture2D[5]; textures[0] = Game.Content.Load("Textures\\Sand01"); textures[1] = Game.Content.Load("Textures\\Grass01"); textures[2] = Game.Content.Load("Textures\\Rock01"); textures[3] = Game.Content.Load("Textures\\Snow01"); textures[4] = Game.Content.Load("Textures\\TerrainCursor"); Camera = new Camera(); Camera.Move(0f, 75f, -50f); Camera.Perspective(45f, 4f / 3f, 1f, 1000f); Camera.Rotate(0f, -90f, 0f); for (int z = 0; z < depth; z++) { for (int x = 0; x < width; x++) { int index = x + (z * width); vertices[index].Position = new Vector3(x + width / -2, 0, z + depth / -2); vertices[index].Normal = new Vector3(0, 1, 0); vertices[index].TextureWeight = new Vector4(1, 0, 0, 0); } } for (int z = 0; z < depth - 1; z++) { for (int x = 0; x < width - 1; x++) { indices[(x + (z * width)) * 6] = (x + z * width); indices[(x + (z * width)) * 6 + 1] = (x + z * width) + 1; indices[(x + (z * width)) * 6 + 2] = (x + (z + 1) * width); indices[(x + (z * width)) * 6 + 3] = (x + (z + 1) * width); indices[(x + (z * width)) * 6 + 4] = (x + z * width) + 1; indices[(x + (z * width)) * 6 + 5] = (x + (z + 1) * width) + 1; } } triangleCount = indices.Length / 3; } public void ProcessNormals() { for (int z = 1; z < depth - 1; z++) { for (int x = 1; x < width - 1; x++) { int index = x + (z * width); Vector3 normX = new Vector3((vertices[index - 1].Position.Y - vertices[index + 1].Position.Y) / 2, 1, 0); Vector3 normZ = new Vector3(0, 1, (vertices[x + ((z - 1) * width)].Position.Y - vertices[x + ((z + 1) * width)].Position.Y) / 2); vertices[index].Normal = normX + normZ; vertices[index].Normal.Normalize(); } } } public void SmoothTerrain(int smoothingPasses) { if (smoothingPasses < 0) { smoothingPasses = 0; } int passes = (int)smoothingPasses; VertexTerrain[] newVertices; while (passes > 0) { passes--; newVertices = new VertexTerrain[width * depth]; for (int x = 0; x < width; x++) { for (int z = 0; z < depth; z++) { int adjacentSections = 0; float sectionsTotal = 0.0f; if (x == 0 || x == width - 1 || z == 0 || z == depth - 1) { continue; } // ================================================================= if ((x - 1) > 0) // Check to left { sectionsTotal += vertices[z + ((x - 1) * depth)].Position.Y; adjacentSections++; if ((z - 1) > 0) // Check up and to the left { sectionsTotal += vertices[(z - 1) + ((x - 1) * depth)].Position.Y; adjacentSections++; } if ((z + 1) < depth) // Check down and to the left { sectionsTotal += vertices[(z + 1) + ((x - 1) * depth)].Position.Y; adjacentSections++; } } // ================================================================= // ================================================================= if ((x + 1) < width) // Check to right { sectionsTotal += vertices[z + ((x + 1) * depth)].Position.Y; adjacentSections++; if ((z - 1) > 0) // Check up and to the right { sectionsTotal += vertices[(z - 1) + ((x + 1) * depth)].Position.Y; adjacentSections++; } if ((z + 1) < depth) // Check down and to the right { sectionsTotal += vertices[(z + 1) + ((x + 1) * depth)].Position.Y; adjacentSections++; } } // ================================================================= // ================================================================= if ((z - 1) > 0) // Check above { sectionsTotal += vertices[(z - 1) + (x * depth)].Position.Y; adjacentSections++; } // ================================================================= // ================================================================= if ((z + 1) < depth) // Check below { sectionsTotal += vertices[(z + 1) + (x * depth)].Position.Y; adjacentSections++; } // ================================================================= newVertices[z + (x * depth)].Position.Y = (vertices[z + (x * depth)].Position.Y + (sectionsTotal / adjacentSections)) * 0.5f; } } // Overwrite the HeightData info with our new smoothed info for (int x = 0; x < width; x++) for (int z = 0; z < depth; z++) { vertices[z + (x * depth)].Position.Y = newVertices[z + (x * depth)].Position.Y; } } } public int? FindPickingTarget(MouseState mouseState) { Viewport viewport = GraphicsDevice.Viewport; Vector3 minSource = new Vector3(mouseState.X, mouseState.Y, viewport.MinDepth); Vector3 maxSource = new Vector3(mouseState.X, mouseState.Y, viewport.MaxDepth); Vector3 nearSource = viewport.Unproject(minSource, Camera.Projection, Camera.View, world); Vector3 farSource = viewport.Unproject(maxSource, Camera.Projection, Camera.View, world); Vector3 direction = farSource - nearSource; Ray ray = new Ray(nearSource, direction); for (int i = 0; i < vertices.Length - 3; i++) { Vector3 pos = Vector3.Transform(vertices[i].Position, world); BoundingBox bb = new BoundingBox(pos + Vector3.One * -1f, pos + Vector3.One * 1f); float? length; ray.Intersects(ref bb, out length); if (length.HasValue) { pickTarget = ray.Position + ray.Direction * length.Value; return i; } } return null; } public override void Update(GameTime gameTime) { base.Update(gameTime); float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds; KeyboardState currentKeyboardState = Keyboard.GetState(); MouseState currentMouseState = Mouse.GetState(); if (currentKeyboardState.IsKeyDown(Keys.Left)) Camera.Move(-50 * elapsed, 0, 0); if (currentKeyboardState.IsKeyDown(Keys.Right)) Camera.Move(50 * elapsed, 0, 0); if (currentKeyboardState.IsKeyDown(Keys.Up)) Camera.Move(0, 0, 50 * elapsed); if (currentKeyboardState.IsKeyDown(Keys.Down)) Camera.Move(0, 0, -50 * elapsed); if (currentKeyboardState.IsKeyDown(Keys.PageUp)) Camera.Move(0, 50 * elapsed, 0); if (currentKeyboardState.IsKeyDown(Keys.PageDown)) Camera.Move(0, -50 * elapsed, 0); if (currentKeyboardState.IsKeyDown(Keys.A)) Camera.Rotate(50 * elapsed, 0, 0); if (currentKeyboardState.IsKeyDown(Keys.D)) Camera.Rotate(-50 * elapsed, 0, 0); if (currentKeyboardState.IsKeyDown(Keys.W)) Camera.Rotate(0, 50 * elapsed, 0); if (currentKeyboardState.IsKeyDown(Keys.S)) Camera.Rotate(0, -50 * elapsed, 0); if (currentKeyboardState.IsKeyDown(Keys.OemMinus)) morphRadius = MathHelper.Clamp(morphRadius - (morphRadius * 0.5f) * elapsed, 1f, 6f); if (currentKeyboardState.IsKeyDown(Keys.OemPlus)) morphRadius = MathHelper.Clamp(morphRadius + (morphRadius * 0.5f) * elapsed, 1f, 6f); if (currentKeyboardState.IsKeyDown(Keys.Q)) morphPower = MathHelper.Clamp(morphPower - (morphPower * 0.5f) * elapsed, 1f, 30f); if (currentKeyboardState.IsKeyDown(Keys.E)) morphPower = MathHelper.Clamp(morphPower + (morphPower * 0.5f) * elapsed, 1f, 30f); if (previousKeyboardState != null) { if (currentKeyboardState.IsKeyDown(Keys.Tab) && !previousKeyboardState.IsKeyDown(Keys.Tab)) { editType = (editType == EditType.Terrain) ? EditType.Texture : EditType.Terrain; } if (currentKeyboardState.IsKeyDown(Keys.T) && !previousKeyboardState.IsKeyDown(Keys.T)) { currentTextureIndex++; if (currentTextureIndex > 3) { currentTextureIndex = 0; } } if (currentKeyboardState.IsKeyDown(Keys.F1) && !previousKeyboardState.IsKeyDown(Keys.F1)) { SmoothTerrain(1); ProcessNormals(); } if (currentKeyboardState.IsKeyDown(Keys.F2)) { SmoothTerrain(1); ProcessNormals(); } if (currentKeyboardState.IsKeyDown(Keys.F12) && !previousKeyboardState.IsKeyDown(Keys.F12)) { float min = float.MaxValue; float max = float.MinValue; for (int i = 0; i < vertices.Length; i++) { if (vertices[i].Position.Y > max) max = vertices[i].Position.Y; if (vertices[i].Position.Y < min) min = vertices[i].Position.Y; } max -= min; min = 0; if (max < 256) { max = 256; } Texture2D heightmap = new Texture2D(GraphicsDevice, width, depth, 1, TextureUsage.None, SurfaceFormat.Vector4); Vector4[] colors = new Vector4[width * depth]; for (int z = 0; z < depth; z++) { for (int x = 0; x < width; x++) { float posY = vertices[x + z * width].Position.Y; float height = MathHelper.Clamp(posY / max, 0.0f, 1f); float w = 1.0f; colors[x + z * width] = new Vector4(height, height, height, w); } } heightmap.SetData(colors); heightmap.Save("heightmap.png", ImageFileFormat.Png); } if (currentKeyboardState.IsKeyDown(Keys.F11) && !previousKeyboardState.IsKeyDown(Keys.F11)) { Texture2D colormap = new Texture2D(GraphicsDevice, width, depth, 1, TextureUsage.None, SurfaceFormat.Vector4); Vector4[] colors = new Vector4[width * depth]; for (int z = 0; z < depth; z++) { for (int x = 0; x < width; x++) { Vector4 color = vertices[x + z * width].TextureWeight; color.W = 1 - color.W; colors[x + z * width] = color; } } colormap.SetData(colors); colormap.Save("colormap.png", ImageFileFormat.Png); } } if (previousMouseState != null) { if (currentMouseState.LeftButton == ButtonState.Pressed && selctedIndex.HasValue) { for (int i = 0; i < vertices.Length; i++) { Vector3 delta = pickTarget - vertices[i].Position; float length = delta.Length(); float scalar = MathHelper.Clamp(morphRadius - (length / morphRadius), 0.0f, 1f); if (editType == EditType.Terrain) { vertices[i].Position.Y += morphPower * scalar * elapsed; } else { vertices[i].TextureWeight -= vertices[i].TextureWeight * scalar * elapsed; vertices[i].TextureWeight += weights[currentTextureIndex] * scalar * elapsed; } } if (editType == EditType.Terrain) { ProcessNormals(); } } else if (currentMouseState.RightButton == ButtonState.Pressed && selctedIndex.HasValue) { for (int i = 0; i < vertices.Length; i++) { Vector3 delta = pickTarget - vertices[i].Position; float length = delta.Length(); float scalar = MathHelper.Clamp(morphRadius - (length / morphRadius), 0.0f, 1f); vertices[i].Position.Y -= morphPower * scalar * elapsed; } if (editType == EditType.Terrain) { ProcessNormals(); } } selctedIndex = FindPickingTarget(currentMouseState); } Game.Window.Title = String.Format("Edit Type: {2} Morph Power: {0} Morph Radius: {1}", morphPower, morphRadius, Enum.GetName(typeof(EditType), editType)); previousKeyboardState = currentKeyboardState; previousMouseState = currentMouseState; } public override void Draw(GameTime gameTime) { base.Draw(gameTime); Matrix world = Matrix.CreateTranslation(Vector3.Zero); GraphicsDevice.VertexDeclaration = vertexDeclaration; GraphicsDevice.RenderState.DepthBufferEnable = true; GraphicsDevice.RenderState.CullMode = CullMode.None; GraphicsDevice.RenderState.PointSpriteEnable = false; if (selctedIndex.HasValue) { effect.Parameters["EditTexture"].SetValue(textures[4]); } else { effect.Parameters["EditTexture"].SetValue((Texture)null); } effect.Parameters["WorldViewProj"].SetValue(world * Camera.View * Camera.Projection); effect.Parameters["World"].SetValue(world); effect.Parameters["DirtTexture"].SetValue(textures[0]); effect.Parameters["GrassTexture"].SetValue(textures[1]); effect.Parameters["RockTexture"].SetValue(textures[2]); effect.Parameters["SnowTexture"].SetValue(textures[3]); effect.Parameters["Scale"].SetValue(new Vector3(1, 1, 1)); effect.Parameters["UVScale"].SetValue(new Vector2(0.05f, 0.05f)); effect.Parameters["EditMode"].SetValue(true); effect.Parameters["EditRadius"].SetValue(morphRadius); effect.Parameters["EditPosition"].SetValue(pickTarget); effect.Parameters["HeightmapSize"].SetValue(new Vector2(width, depth)); effect.Begin(); effect.CurrentTechnique.Passes[0].Begin(); GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, triangleCount); effect.CurrentTechnique.Passes[0].End(); effect.End(); } public void DrawDiagnosticInfo() { spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState); spriteBatch.Draw(textures[currentTextureIndex], new Rectangle(0, 0, 50, 50), Color.White); spriteBatch.End(); } } }