/// <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;
 }
Example #5
0
 /// <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;
 }
Example #6
0
        /// <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();
        }
Example #7
0
        /// <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();
        }
Example #8
0
        /// <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;
                }
            }
        }
Example #10
0
        /// <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);
            }
        }