/// <summary> /// Initializes a new instance of the TerrainComponent class. /// </summary> /// <param name="terrain">The terrain quad tree.</param> /// <param name="isCollidable">Indicates whether the terrain can be collided with.</param> /// <param name="pathNodes">The pathfinding nodes of the terrain.</param> public TerrainComponent( ClipQuadTree<TerrainData> terrain, bool isCollidable, Dictionary<Point, LinkedPathNode> pathNodes) { this.Terrain = terrain; this.IsCollidable = isCollidable; this.PathNodes = pathNodes; this.Fixtures = new Dictionary<Square, Fixture>(); }
/// <summary> /// Create a terrain object from the given terrain bitmap. /// </summary> /// <param name="bitmap">The bitmap defining the terrain.</param> /// <param name="currentTime">The current time used as the terrain creation time.</param> /// <returns>The terrain object.</returns> public ClipQuadTree<TerrainData> CreateTerrainQuadTree(Texture2D bitmap, TimeSpan currentTime) { // Create the quad tree var quadTree = new ClipQuadTree<TerrainData>( new Square(0, 0, this.GetUpperPowerOf2(bitmap.Height > bitmap.Width ? bitmap.Height : bitmap.Width))); // Read the bitmap data var bitmapData = new Color[bitmap.Width * bitmap.Height]; bitmap.GetData<Color>(bitmapData); // Populate the terrain's quad tree from the bitmap data this.PopulateQuadTree(quadTree, bitmapData, bitmap.Width, currentTime); // Decorate the light fronts on the quad tree var lightFrontDecorator = new LightFrontDecorator(quadTree); lightFrontDecorator.Decorate(); return quadTree; }
/// <summary> /// Get the light fronts for the given terrain node. /// </summary> /// <param name="terrainNode">The terrain node.</param> /// <returns>Array of light fronts.</returns> private Edge[] GetNodeLightFronts(ClipQuadTree<TerrainData> terrainNode) { var lightFronts = new List<Edge>(); // Test each point 1 pixel outside of this quad on the top/bottom sides int startAboveX = -1; int startBelowX = -1; for (int x = terrainNode.Bounds.X; x < terrainNode.Bounds.Right; x++) { if (this.IsEmptyTerrain(x, terrainNode.Bounds.Y - 1)) { if (startAboveX == -1) { startAboveX = x; } } else { if (startAboveX != -1) { lightFronts.Add(new Edge(startAboveX, terrainNode.Bounds.Y, x - 1, terrainNode.Bounds.Y)); startAboveX = -1; } } if (this.IsEmptyTerrain(x, terrainNode.Bounds.Bottom)) { if (startBelowX == -1) { startBelowX = x; } } else { if (startBelowX != -1) { lightFronts.Add( new Edge(startBelowX, terrainNode.Bounds.Bottom - 1, x - 1, terrainNode.Bounds.Bottom - 1)); startBelowX = -1; } } } // Add uncompleted fronts if (startAboveX != -1) { lightFronts.Add( new Edge(startAboveX, terrainNode.Bounds.Y, terrainNode.Bounds.Right - 1, terrainNode.Bounds.Y)); } // Add uncompleted fronts if (startBelowX != -1) { lightFronts.Add( new Edge(startBelowX, terrainNode.Bounds.Bottom - 1, terrainNode.Bounds.Right - 1, terrainNode.Bounds.Bottom - 1)); } // Test each point 1 pixel outside of this quad on the left/right sides int startLeftY = -1; int startRightY = -1; for (int y = terrainNode.Bounds.Y; y < terrainNode.Bounds.Bottom; y++) { if (this.IsEmptyTerrain(terrainNode.Bounds.X - 1, y)) { if (startLeftY == -1) { startLeftY = y; } } else { if (startLeftY != -1) { lightFronts.Add( new Edge(terrainNode.Bounds.X, startLeftY, terrainNode.Bounds.X, y - 1)); startLeftY = -1; } } if (this.IsEmptyTerrain(terrainNode.Bounds.Right, y)) { if (startRightY == -1) { startRightY = y; } } else { if (startRightY != -1) { lightFronts.Add( new Edge(terrainNode.Bounds.Right - 1, startRightY, terrainNode.Bounds.Right - 1, y - 1)); startRightY = -1; } } } // Add uncompleted fronts if (startLeftY != -1) { lightFronts.Add( new Edge(terrainNode.Bounds.X, startLeftY, terrainNode.Bounds.X, terrainNode.Bounds.Bottom - 1)); } // Add uncompleted fronts if (startRightY != -1) { lightFronts.Add( new Edge(terrainNode.Bounds.Right - 1, startRightY, terrainNode.Bounds.Right - 1, terrainNode.Bounds.Bottom - 1)); } return lightFronts.ToArray(); }
/// <summary> /// Initializes a new instance of the LightFrontDecorator class. /// </summary> /// <param name="quadTree">The terrain quad tree.</param> public LightFrontDecorator(ClipQuadTree<TerrainData> quadTree) { this.QuadTree = quadTree; }
/// <summary> /// Initializes a new instance of the PathBuilder class. /// </summary> /// <param name="quadTree">The terrain quad tree.</param> /// <param name="maxSpanLength">The maximum length (in nodes) of a span.</param> /// <param name="minJumpHeight">The minimum height (in nodes) that is required for a span to be considered a /// jump.</param> public PathBuilder(ClipQuadTree<TerrainData> quadTree, int maxSpanLength, int minJumpHeight) { this.QuadTree = quadTree; this.MaxSpanLength = maxSpanLength; this.MinJumpHeight = minJumpHeight; }
/// <summary> /// Draw the TerrainComponent sprites. /// </summary> /// <param name="spriteBatch">The sprite batch.</param> /// <param name="cameraTranslateX">The camera x translation value.</param> /// <param name="cameraTranslateY">The camera y translation value.</param> /// <param name="cameraScaleX">The camera x scale value.</param> /// <param name="cameraScaleY">The camera y scale value.</param> private void DrawTerrainComponents( SpriteBatch spriteBatch, Matrix transform, ClipQuadTree<TerrainData>[] terrainBlocks, TerrainComponent cTerrain) { // Begin the sprite batch with the camera transform spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointClamp, null, null, null, transform); foreach (ClipQuadTree<TerrainData> terrainBlock in terrainBlocks) { // Don't draw anything if no terrain exists here TerrainMaterial material = terrainBlock.Data.Material; if (material == TerrainMaterial.None) { continue; } // Calculate the bounds of this terrain block in on-screen coordinates var screenBounds = new Rectangle( terrainBlock.Bounds.X, terrainBlock.Bounds.Y, terrainBlock.Bounds.Length, terrainBlock.Bounds.Length); // Tile the terrain within the bounds this.DrawTiledTerrain(spriteBatch, material, screenBounds); } // Draw fringe sprites for each terrain block foreach (ClipQuadTree<TerrainData> terrainBlock in terrainBlocks) { // Don't draw anything if no terrain exists here TerrainMaterial material = terrainBlock.Data.Material; if (material == TerrainMaterial.None) { continue; } // Draw the fringe tiles this.DrawTerrainFringe(spriteBatch, material, terrainBlock.Bounds, cTerrain.PathNodes); } /* // DEBUG: Draw path components Texture2D debugTexture = new Texture2D(this.graphics, 1, 1); debugTexture.SetData<Color>(new Color[] { Color.White }); foreach (PathComponent path in this.EntityManager.GetComponents(typeof(PathComponent))) { for (int i = 0; i < path.Nodes.Length - 1; i++) { PathNode p1 = path.Nodes[i]; PathNode p2 = path.Nodes[i + 1]; int width = Math.Abs(p2.X - p1.X); if (width == 0) width = 1; int height = Math.Abs(p2.Y - p1.Y); if (height == 0) height = 1; spriteBatch.Draw( debugTexture, new Rectangle(p1.X, p1.Y, width, height), (p1.Type == PathNodeType.Normal) ? Color.Red : Color.Yellow); } } */ spriteBatch.End(); }
/// <summary> /// Draw the lighting. /// </summary> /// <param name="spriteBatch">The sprite batch.</param> /// <param name="cameraTranslateX">The camera x translation value.</param> /// <param name="cameraTranslateY">The camera y translation value.</param> /// <param name="cameraScaleX">The camera x scale value.</param> /// <param name="cameraScaleY">The camera y scale value.</param> private void DrawLighting(SpriteBatch spriteBatch, Matrix transform, ClipQuadTree<TerrainData>[] terrainBlocks) { // TODO: Make this a configurable variable const int lightLength = 125; const int lightEdgeStep = 1; int halfLightLength = lightLength / 2; // Begin the sprite batch with the camera transform spriteBatch.Begin( SpriteSortMode.Deferred, this.blendStateAdditiveMax, null, null, null, null, transform); // Draw light-front sprites Rectangle destRect; Rectangle srcRect = this.resources.GetSpriteRectangle("light", "lightfront", "radial"); foreach (ClipQuadTree<TerrainData> terrainBlock in terrainBlocks) { foreach (Edge light in terrainBlock.Data.LightFronts) { // Draw light for the first point destRect = new Rectangle( light.Point1.X - halfLightLength, light.Point1.Y - halfLightLength, lightLength, lightLength); spriteBatch.Draw(this.resources.SpriteSheet, destRect, srcRect, Color.White); if (light.Orientation != EdgeOrientation.Point) { // Draw light for the last point destRect = new Rectangle( light.Point2.X - halfLightLength, light.Point2.Y - halfLightLength, lightLength, lightLength); spriteBatch.Draw(this.resources.SpriteSheet, destRect, srcRect, Color.White); // Draw light for points along the edge if (light.Orientation == EdgeOrientation.Horizontal) { if (light.Point1.X < light.Point2.X) { for (int x = light.Point1.X; x < light.Point2.X; x += lightEdgeStep) { destRect = new Rectangle( x - halfLightLength, light.Point1.Y - halfLightLength, lightLength, lightLength); spriteBatch.Draw(this.resources.SpriteSheet, destRect, srcRect, Color.White); } } else { for (int x = light.Point2.X; x < light.Point1.X; x += lightEdgeStep) { destRect = new Rectangle( x - halfLightLength, light.Point1.Y - halfLightLength, lightLength, lightLength); spriteBatch.Draw(this.resources.SpriteSheet, destRect, srcRect, Color.White); } } } else if (light.Orientation == EdgeOrientation.Vertical) { if (light.Point1.Y < light.Point2.Y) { for (int y = light.Point1.Y; y < light.Point2.Y; y += lightEdgeStep) { destRect = new Rectangle( light.Point1.X - halfLightLength, y - halfLightLength, lightLength, lightLength); spriteBatch.Draw(this.resources.SpriteSheet, destRect, srcRect, Color.White); } } else { for (int y = light.Point2.Y; y < light.Point1.Y; y += lightEdgeStep) { destRect = new Rectangle( light.Point1.X - halfLightLength, y - halfLightLength, lightLength, lightLength); spriteBatch.Draw(this.resources.SpriteSheet, destRect, srcRect, Color.White); } } } } } } // Draw ambient light foreach (ClipQuadTree<TerrainData> terrainBlock in terrainBlocks) { // If this block is empty (ie. is just air), then it is fully lit if (terrainBlock.Data.State == TerrainState.Empty) { var blockBounds = new Rectangle( terrainBlock.Bounds.X, terrainBlock.Bounds.Y, terrainBlock.Bounds.Length, terrainBlock.Bounds.Length); spriteBatch.Draw(this.whiteTexture, blockBounds, Color.White); } } spriteBatch.End(); }
/// <summary> /// Draw the game. /// </summary> /// <param name="spriteBatch">The sprite batch.</param> /// <param name="cameraTranslateX">The camera x translation value.</param> /// <param name="cameraTranslateY">The camera y translation value.</param> /// <param name="cameraScaleX">The camera x scale value.</param> /// <param name="cameraScaleY">The camera y scale value.</param> private void DrawGame( SpriteBatch spriteBatch, Matrix terrainTransform, Matrix spriteTransform, ClipQuadTree<TerrainData>[] terrainBlocks, TerrainComponent cTerrain) { // Draw the terrain this.DrawTerrainComponents(spriteBatch, terrainTransform, terrainBlocks, cTerrain); // Draw the sprites this.DrawSpriteComponents(spriteBatch, spriteTransform); }
/// <summary> /// Populate the quad tree quadrants in the given bounds from the bit map data. /// <para /> /// This function will be recursively called the on the four sub-quadrants of this quadrant. If all of the sub- /// quandrants share the same material, then no data is added to the quad tree and the material is returned. /// The data will be set at a higher level in the quad tree hierarchy. The reason for this is because if the /// parent quadrant is also fully uniform, then any quadrants below it (this one) would have be removed, /// wasting processor time. /// <para /> /// If any of the sub-quadrants are of a fully uniform material (yet not *all* sub-quadrants like in the above /// case) then the data for those sub-quadrants is set in the quad tree here, making those sub-quadrants leaf /// nodes (an example of this could be Q1 and Q2 returning TerrainMaterial.None, Q3 returning /// TerrainMaterial.Mud, and Q4 returning null since it is a 'mixed' quadrant and the data is set at a lower /// level. In this situation Q1, Q2 and Q3 would have their data set at this level). /// </summary> /// <param name="quadTree">The quad tree to populate.</param> /// <param name="bounds">The bounds of the quadrant whose sub-quandrants are being populated.</param> /// <param name="bitmapData">The bit map color data.</param> /// <param name="bitmapWidth">The width in pixels of the bitmap image.</param> /// <param name="currentTime">The current time used as the terrain creation time.</param> /// <returns>The material value if all sub-quadrants in this bound are filled with the same material; Null if /// the sub-quadrants have various materials.</returns> private TerrainMaterial? PopulateQuadrants( ClipQuadTree<TerrainData> quadTree, Square bounds, Color[] bitmapData, int bitmapWidth, TimeSpan currentTime) { if (bounds.Length > 1) { // Get the sub-quadrant bounds of this quadrant Square topLeftBounds = bounds.GetTopLeftQuadrant(); Square topRightBounds = bounds.GetTopRightQuadrant(); Square bottomLeftBounds = bounds.GetBottomLeftQuadrant(); Square bottomRightBounds = bounds.GetBottomRightQuadrant(); // Populate the sub quadrants or if they are uniform get the single material they contain TerrainMaterial? topLeftTerrain = this.PopulateQuadrants(quadTree, topLeftBounds, bitmapData, bitmapWidth, currentTime); TerrainMaterial? topRightTerrain = this.PopulateQuadrants(quadTree, topRightBounds, bitmapData, bitmapWidth, currentTime); TerrainMaterial? bottomLeftTerrain = this.PopulateQuadrants(quadTree, bottomLeftBounds, bitmapData, bitmapWidth, currentTime); TerrainMaterial? bottomRightTerrain = this.PopulateQuadrants(quadTree, bottomRightBounds, bitmapData, bitmapWidth, currentTime); // Check if all of the quadrants are uniform and all have the same material if (topLeftTerrain.HasValue && topLeftTerrain == topRightTerrain && topRightTerrain == bottomLeftTerrain && bottomLeftTerrain == bottomRightTerrain) { // All quadrants have the same material. Return this to create the quadrant at a higher level return topLeftTerrain; } else { // The quadrants do not all share the same material. However, they may themselves be uniform in // which case their quadrant values need to be set in the quad tree if (topLeftTerrain.HasValue) { // Quadrant has a uniform color quadTree.SetData( new TerrainData(topLeftTerrain.Value, currentTime), topLeftBounds, null); } if (topRightTerrain.HasValue) { // Quadrant has a uniform color quadTree.SetData( new TerrainData(topRightTerrain.Value, currentTime), topRightBounds, null); } if (bottomLeftTerrain.HasValue) { // Quadrant has a uniform color quadTree.SetData( new TerrainData(bottomLeftTerrain.Value, currentTime), bottomLeftBounds, null); } if (bottomRightTerrain.HasValue) { // Quadrant has a uniform color quadTree.SetData( new TerrainData(bottomRightTerrain.Value, currentTime), bottomRightBounds, null); } // Return null since there isnt a single color shared between all the quadrants return null; } } else { // Get the terrain material at this pixel if (bounds.X < bitmapWidth) { int dataIndex = bounds.X + (bounds.Y * bitmapWidth); if (dataIndex < bitmapData.Length) { return TerrainMaterialConverter.GetValue(bitmapData[dataIndex]); } else { // The index is out of bounds for this quadrant. This will happen if the bitmap image is not a // square with a power-of-2 length. Just return TerrainMaterial.None return TerrainMaterial.None; } } else { // The index is out of bounds for this quadrant. This will happen if the bitmap image is not a // square with a power-of-2 length. Just return TerrainMaterial.None return TerrainMaterial.None; } } }
/// <summary> /// Populate the quad tree from the bit map data. /// </summary> /// <param name="quadTree">The quad tree to populate.</param> /// <param name="bitmapData">The bit map color data.</param> /// <param name="bitmapWidth">The width in pixels of the bitmap image.</param> /// <param name="currentTime">The current time used as the terrain creation time.</param> private void PopulateQuadTree( ClipQuadTree<TerrainData> quadTree, Color[] bitmapData, int bitmapWidth, TimeSpan currentTime) { // Create the quadrants TerrainMaterial? material = this.PopulateQuadrants(quadTree, quadTree.Bounds, bitmapData, bitmapWidth, currentTime); if (material.HasValue) { TerrainData data = new TerrainData(material.Value, currentTime); // The entire terrain quad tree is of a single material, so set the root value with no leaves quadTree.SetData(data, quadTree.Bounds, null); } }