Ejemplo n.º 1
0
        private static void DrawTileHighlights(Canvas canvas, ICmpTilemapRenderer renderer, Point2 origin, IReadOnlyGrid<bool> highlight, ColorRgba fillTint, ColorRgba outlineTint, TileHighlightMode mode, List<Vector2[]> outlineCache = null)
        {
            if (highlight.Width == 0 || highlight.Height == 0) return;

            // Generate strippled line texture if not available yet
            if (strippledLineTex == null)
            {
                PixelData pixels = new PixelData(8, 1);
                for (int i = 0; i < pixels.Width / 2; i++)
                    pixels[i, 0] = ColorRgba.White;
                for (int i = pixels.Width / 2; i < pixels.Width; i++)
                    pixels[i, 0] = ColorRgba.TransparentWhite;

                using (Pixmap pixmap = new Pixmap(pixels))
                {
                    strippledLineTex = new Texture(pixmap,
                        TextureSizeMode.Default,
                        TextureMagFilter.Nearest,
                        TextureMinFilter.Nearest,
                        TextureWrapMode.Repeat,
                        TextureWrapMode.Repeat,
                        TexturePixelFormat.Rgba);
                }
            }

            BatchInfo defaultMaterial = new BatchInfo(DrawTechnique.Alpha, canvas.State.Material.MainColor);
            BatchInfo strippleMaterial = new BatchInfo(DrawTechnique.Alpha, canvas.State.Material.MainColor, strippledLineTex);
            bool uncertain = (mode & TileHighlightMode.Uncertain) != 0;
            bool selection = (mode & TileHighlightMode.Selection) != 0;

            Component component = renderer as Component;
            Transform transform = component.GameObj.Transform;
            Tilemap tilemap = renderer.ActiveTilemap;
            Tileset tileset = tilemap != null ? tilemap.Tileset.Res : null;
            Vector2 tileSize = tileset != null ? tileset.TileSize : Tileset.DefaultTileSize;
            Rect localRect = renderer.LocalTilemapRect;

            // Determine the object's local coordinate system (rotated, scaled) in world space
            Vector2 worldAxisX = Vector2.UnitX;
            Vector2 worldAxisY = Vector2.UnitY;
            MathF.TransformCoord(ref worldAxisX.X, ref worldAxisX.Y, transform.Angle, transform.Scale);
            MathF.TransformCoord(ref worldAxisY.X, ref worldAxisY.Y, transform.Angle, transform.Scale);

            Vector2 localOriginPos = tileSize * origin;
            Vector2 worldOriginPos = localOriginPos.X * worldAxisX + localOriginPos.Y * worldAxisY;

            canvas.PushState();
            {
                // Configure the canvas so our shapes are properly rotated and scaled
                canvas.State.TransformHandle = -localRect.TopLeft;
                canvas.State.TransformAngle = transform.Angle;
                canvas.State.TransformScale = new Vector2(transform.Scale);

                // Fill all highlighted tiles that are currently visible
                {
                    canvas.State.SetMaterial(defaultMaterial);
                    canvas.State.ColorTint = fillTint * ColorRgba.White.WithAlpha(selection ? 0.2f : 0.375f);

                    // Determine tile visibility
                    Vector2 worldTilemapOriginPos = localRect.TopLeft;
                    MathF.TransformCoord(ref worldTilemapOriginPos.X, ref worldTilemapOriginPos.Y, transform.Angle, transform.Scale);
                    TilemapCulling.TileInput cullingIn = new TilemapCulling.TileInput
                    {
                        // Remember: All these transform values are in world space
                        TilemapPos = transform.Pos + new Vector3(worldTilemapOriginPos) + new Vector3(worldOriginPos),
                        TilemapScale = transform.Scale,
                        TilemapAngle = transform.Angle,
                        TileCount = new Point2(highlight.Width, highlight.Height),
                        TileSize = tileSize
                    };
                    TilemapCulling.TileOutput cullingOut = TilemapCulling.GetVisibleTileRect(canvas.DrawDevice, cullingIn);
                    int renderedTileCount = cullingOut.VisibleTileCount.X * cullingOut.VisibleTileCount.Y;

                    // Draw all visible highlighted tiles
                    {
                        Point2 tileGridPos = cullingOut.VisibleTileStart;
                        Vector2 renderStartPos = worldOriginPos + tileGridPos.X * tileSize.X * worldAxisX + tileGridPos.Y * tileSize.Y * worldAxisY;;
                        Vector2 renderPos = renderStartPos;
                        Vector2 tileXStep = worldAxisX * tileSize.X;
                        Vector2 tileYStep = worldAxisY * tileSize.Y;
                        int lineMergeCount = 0;
                        int totalRects = 0;
                        for (int tileIndex = 0; tileIndex < renderedTileCount; tileIndex++)
                        {
                            bool current = highlight[tileGridPos.X, tileGridPos.Y];
                            if (current)
                            {
                                // Try to merge consecutive rects in the same line to reduce drawcalls / CPU load
                                bool hasNext = (tileGridPos.X + 1 < highlight.Width) && ((tileGridPos.X + 1 - cullingOut.VisibleTileStart.X) < cullingOut.VisibleTileCount.X);
                                bool next = hasNext ? highlight[tileGridPos.X + 1, tileGridPos.Y] : false;
                                if (next)
                                {
                                    lineMergeCount++;
                                }
                                else
                                {
                                    totalRects++;
                                    canvas.FillRect(
                                        transform.Pos.X + renderPos.X - lineMergeCount * tileXStep.X,
                                        transform.Pos.Y + renderPos.Y - lineMergeCount * tileXStep.Y,
                                        transform.Pos.Z,
                                        tileSize.X * (1 + lineMergeCount),
                                        tileSize.Y);
                                    lineMergeCount = 0;
                                }
                            }

                            tileGridPos.X++;
                            renderPos += tileXStep;
                            if ((tileGridPos.X - cullingOut.VisibleTileStart.X) >= cullingOut.VisibleTileCount.X)
                            {
                                tileGridPos.X = cullingOut.VisibleTileStart.X;
                                tileGridPos.Y++;
                                renderPos = renderStartPos;
                                renderPos += tileYStep * (tileGridPos.Y - cullingOut.VisibleTileStart.Y);
                            }
                        }
                    }
                }

                // Draw highlight area outlines, unless flagged as uncertain
                if (!uncertain)
                {
                    // Determine the outlines of individual highlighted tile patches
                    if (outlineCache == null) outlineCache = new List<Vector2[]>();
                    if (outlineCache.Count == 0)
                    {
                        GetTileAreaOutlines(highlight, tileSize, ref outlineCache);
                    }

                    // Draw outlines around all highlighted tile patches
                    canvas.State.SetMaterial(selection ? strippleMaterial : defaultMaterial);
                    canvas.State.ColorTint = outlineTint;
                    foreach (Vector2[] outline in outlineCache)
                    {
                        // For strippled-line display, determine total length of outline
                        if (selection)
                        {
                            float totalLength = 0.0f;
                            for (int i = 1; i < outline.Length; i++)
                            {
                                totalLength += (outline[i - 1] - outline[i]).Length;
                            }
                            canvas.State.TextureCoordinateRect = new Rect(totalLength / strippledLineTex.PixelWidth, 1.0f);
                        }

                        // Draw the outline
                        canvas.DrawPolygon(
                            outline,
                            transform.Pos.X + worldOriginPos.X,
                            transform.Pos.Y + worldOriginPos.Y,
                            transform.Pos.Z);
                    }
                }

                // If this is an uncertain highlight, i.e. not actually reflecting the represented action,
                // draw a gizmo to indicate this for the user.
                if (uncertain)
                {
                    Vector2 highlightSize = new Vector2(highlight.Width * tileSize.X, highlight.Height * tileSize.Y);
                    Vector2 highlightCenter = highlightSize * 0.5f;

                    Vector3 circlePos = transform.Pos + new Vector3(worldOriginPos + worldAxisX * highlightCenter + worldAxisY * highlightCenter);
                    float circleRadius = MathF.Min(tileSize.X, tileSize.Y) * 0.2f;

                    canvas.State.SetMaterial(defaultMaterial);
                    canvas.State.ColorTint = outlineTint;
                    canvas.FillCircle(
                        circlePos.X,
                        circlePos.Y,
                        circlePos.Z,
                        circleRadius);
                }
            }
            canvas.PopState();
        }
Ejemplo n.º 2
0
        public override void Draw(IDrawDevice device)
        {
            // Determine basic working data
            Tilemap tilemap = this.ActiveTilemap;
            Tileset tileset = tilemap != null ? tilemap.Tileset.Res : null;
            Point2 tileCount = tilemap != null ? tilemap.Size : new Point2(1, 1);
            Vector2 tileSize = tileset != null ? tileset.TileSize : Tileset.DefaultTileSize;

            // Early-out, if insufficient
            if (tilemap == null) return;
            if (tileset == null) return;

            // Determine the total size and origin of the rendered Tilemap
            Vector2 renderTotalSize = tileCount * tileSize;
            Vector2 renderOrigin = Vector2.Zero;
            this.origin.ApplyTo(ref renderOrigin, ref renderTotalSize);
            MathF.TransformCoord(ref renderOrigin.X, ref renderOrigin.Y, this.GameObj.Transform.Angle, this.GameObj.Transform.Scale);

            // Determine Tile visibility
            TilemapCulling.TileInput cullingIn = new TilemapCulling.TileInput
            {
                // Remember: All these transform values are in world space
                TilemapPos = this.GameObj.Transform.Pos + new Vector3(renderOrigin),
                TilemapScale = this.GameObj.Transform.Scale,
                TilemapAngle = this.GameObj.Transform.Angle,
                TileCount = tileCount,
                TileSize = tileSize
            };
            TilemapCulling.TileOutput cullingOut = TilemapCulling.GetVisibleTileRect(device, cullingIn);
            int renderedTileCount = cullingOut.VisibleTileCount.X * cullingOut.VisibleTileCount.Y;

            // Determine rendering parameters
            Material material = (tileset != null ? tileset.RenderMaterial : null) ?? Material.Checkerboard.Res;
            ColorRgba mainColor = material.MainColor * this.colorTint;

            // Reserve the required space for vertex data in our locally cached buffer
            if (this.vertices == null) this.vertices = new RawList<VertexC1P3T2>();
            this.vertices.Count = renderedTileCount * 4;
            VertexC1P3T2[] vertexData = this.vertices.Data;

            // Determine and adjust data for Z offset generation
            float depthPerTile = -cullingIn.TileSize.Y * cullingIn.TilemapScale * this.tileDepthScale;

            if (this.tileDepthMode == TileDepthOffsetMode.Flat)
                depthPerTile = 0.0f;

            float originDepthOffset = Rect.Align(this.origin, 0, 0, 0, tileCount.Y * depthPerTile).Y;

            if (this.tileDepthMode == TileDepthOffsetMode.World)
                originDepthOffset += (this.GameObj.Transform.Pos.Y / (float)tileSize.Y) * depthPerTile;

            cullingOut.RenderOriginView.Z += this.offset + this.tileDepthOffset * depthPerTile + originDepthOffset;

            // Prepare vertex generation data
            Vector2 tileXStep = cullingOut.XAxisView * cullingIn.TileSize.X;
            Vector2 tileYStep = cullingOut.YAxisView * cullingIn.TileSize.Y;
            Vector3 renderPos = cullingOut.RenderOriginView;
            Point2 tileGridPos = cullingOut.VisibleTileStart;

            // Prepare vertex data array for batch-submitting
            IReadOnlyGrid<Tile> tiles = tilemap.Tiles;
            TileInfo[] tileData = tileset.TileData.Data;
            int submittedTileCount = 0;
            int vertexBaseIndex = 0;
            for (int tileIndex = 0; tileIndex < renderedTileCount; tileIndex++)
            {
                Tile tile = tiles[tileGridPos.X, tileGridPos.Y];
                if (tile.Index < tileData.Length)
                {
                    Rect uvRect = tileData[tile.Index].TexCoord0;
                    bool visualEmpty = tileData[tile.Index].IsVisuallyEmpty;
                    int tileBaseOffset = tileData[tile.Index].DepthOffset;
                    float localDepthOffset = (tile.DepthOffset + tileBaseOffset) * depthPerTile;

                    if (!visualEmpty)
                    {
                        vertexData[vertexBaseIndex + 0].Pos.X = renderPos.X;
                        vertexData[vertexBaseIndex + 0].Pos.Y = renderPos.Y;
                        vertexData[vertexBaseIndex + 0].Pos.Z = renderPos.Z + localDepthOffset;
                        vertexData[vertexBaseIndex + 0].TexCoord.X = uvRect.X;
                        vertexData[vertexBaseIndex + 0].TexCoord.Y = uvRect.Y;
                        vertexData[vertexBaseIndex + 0].Color = mainColor;

                        vertexData[vertexBaseIndex + 1].Pos.X = renderPos.X + tileYStep.X;
                        vertexData[vertexBaseIndex + 1].Pos.Y = renderPos.Y + tileYStep.Y;
                        vertexData[vertexBaseIndex + 1].Pos.Z = renderPos.Z + localDepthOffset + depthPerTile;
                        vertexData[vertexBaseIndex + 1].TexCoord.X = uvRect.X;
                        vertexData[vertexBaseIndex + 1].TexCoord.Y = uvRect.Y + uvRect.H;
                        vertexData[vertexBaseIndex + 1].Color = mainColor;

                        vertexData[vertexBaseIndex + 2].Pos.X = renderPos.X + tileXStep.X + tileYStep.X;
                        vertexData[vertexBaseIndex + 2].Pos.Y = renderPos.Y + tileXStep.Y + tileYStep.Y;
                        vertexData[vertexBaseIndex + 2].Pos.Z = renderPos.Z + localDepthOffset + depthPerTile;
                        vertexData[vertexBaseIndex + 2].TexCoord.X = uvRect.X + uvRect.W;
                        vertexData[vertexBaseIndex + 2].TexCoord.Y = uvRect.Y + uvRect.H;
                        vertexData[vertexBaseIndex + 2].Color = mainColor;

                        vertexData[vertexBaseIndex + 3].Pos.X = renderPos.X + tileXStep.X;
                        vertexData[vertexBaseIndex + 3].Pos.Y = renderPos.Y + tileXStep.Y;
                        vertexData[vertexBaseIndex + 3].Pos.Z = renderPos.Z + localDepthOffset;
                        vertexData[vertexBaseIndex + 3].TexCoord.X = uvRect.X + uvRect.W;
                        vertexData[vertexBaseIndex + 3].TexCoord.Y = uvRect.Y;
                        vertexData[vertexBaseIndex + 3].Color = mainColor;

                        bool vertical = tileData[tile.Index].IsVertical;
                        if (vertical)
                        {
                            vertexData[vertexBaseIndex + 0].Pos.Z += depthPerTile;
                            vertexData[vertexBaseIndex + 3].Pos.Z += depthPerTile;
                        }

                        submittedTileCount++;
                        vertexBaseIndex += 4;
                    }
                }

                tileGridPos.X++;
                renderPos.X += tileXStep.X;
                renderPos.Y += tileXStep.Y;
                if ((tileGridPos.X - cullingOut.VisibleTileStart.X) >= cullingOut.VisibleTileCount.X)
                {
                    tileGridPos.X = cullingOut.VisibleTileStart.X;
                    tileGridPos.Y++;
                    renderPos = cullingOut.RenderOriginView;
                    renderPos.X += tileYStep.X * (tileGridPos.Y - cullingOut.VisibleTileStart.Y);
                    renderPos.Y += tileYStep.Y * (tileGridPos.Y - cullingOut.VisibleTileStart.Y);
                    renderPos.Z += tileGridPos.Y * depthPerTile;
                }
            }

            // Submit all the vertices as one draw batch
            device.AddVertices(
                material,
                VertexMode.Quads,
                vertexData,
                submittedTileCount * 4);

            Profile.AddToStat(@"Duality\Stats\Render\Tilemaps\NumTiles", renderedTileCount);
            Profile.AddToStat(@"Duality\Stats\Render\Tilemaps\NumVertices", submittedTileCount * 4);
        }
Ejemplo n.º 3
0
        public override void Draw(IDrawDevice device)
        {
            // Determine basic working data
            Tilemap tilemap   = this.ActiveTilemap;
            Tileset tileset   = tilemap != null ? tilemap.Tileset.Res : null;
            Point2  tileCount = tilemap != null ? tilemap.Size : new Point2(1, 1);
            Vector2 tileSize  = tileset != null ? tileset.TileSize : Tileset.DefaultTileSize;

            // Early-out, if insufficient
            if (tilemap == null)
            {
                return;
            }
            if (tileset == null)
            {
                return;
            }

            // Determine the total size and origin of the rendered Tilemap
            Vector2 renderTotalSize = tileCount * tileSize;
            Vector2 renderOrigin    = Vector2.Zero;

            this.origin.ApplyTo(ref renderOrigin, ref renderTotalSize);
            MathF.TransformCoord(ref renderOrigin.X, ref renderOrigin.Y, this.GameObj.Transform.Angle, this.GameObj.Transform.Scale);

            // Determine Tile visibility
            TilemapCulling.TileInput cullingIn = new TilemapCulling.TileInput
            {
                // Remember: All these transform values are in world space
                TilemapPos   = this.GameObj.Transform.Pos + new Vector3(renderOrigin),
                TilemapScale = this.GameObj.Transform.Scale,
                TilemapAngle = this.GameObj.Transform.Angle,
                TileCount    = tileCount,
                TileSize     = tileSize
            };
            TilemapCulling.TileOutput cullingOut = TilemapCulling.GetVisibleTileRect(device, cullingIn);
            int renderedTileCount = cullingOut.VisibleTileCount.X * cullingOut.VisibleTileCount.Y;

            // Determine rendering parameters
            Material  material  = (tileset != null ? tileset.RenderMaterial : null) ?? Material.Checkerboard.Res;
            ColorRgba mainColor = material.MainColor * this.colorTint;

            // Reserve the required space for vertex data in our locally cached buffer
            if (this.vertices == null)
            {
                this.vertices = new RawList <VertexC1P3T2>();
            }
            this.vertices.Count = renderedTileCount * 4;
            VertexC1P3T2[] vertexData = this.vertices.Data;

            // Determine and adjust data for Z offset generation
            float depthPerTile = -cullingIn.TileSize.Y * cullingIn.TilemapScale * this.tileDepthScale;

            if (this.tileDepthMode == TileDepthOffsetMode.Flat)
            {
                depthPerTile = 0.0f;
            }

            float originDepthOffset = Rect.Align(this.origin, 0, 0, 0, tileCount.Y * depthPerTile).Y;

            if (this.tileDepthMode == TileDepthOffsetMode.World)
            {
                originDepthOffset += (this.GameObj.Transform.Pos.Y / (float)tileSize.Y) * depthPerTile;
            }

            cullingOut.RenderOriginView.Z += this.offset + this.tileDepthOffset * depthPerTile + originDepthOffset;

            // Prepare vertex generation data
            Vector2 tileXStep   = cullingOut.XAxisView * cullingIn.TileSize.X;
            Vector2 tileYStep   = cullingOut.YAxisView * cullingIn.TileSize.Y;
            Vector3 renderPos   = cullingOut.RenderOriginView;
            Point2  tileGridPos = cullingOut.VisibleTileStart;

            // Prepare vertex data array for batch-submitting
            IReadOnlyGrid <Tile> tiles = tilemap.Tiles;

            TileInfo[] tileData           = tileset.TileData.Data;
            int        submittedTileCount = 0;
            int        vertexBaseIndex    = 0;

            for (int tileIndex = 0; tileIndex < renderedTileCount; tileIndex++)
            {
                Tile tile = tiles[tileGridPos.X, tileGridPos.Y];
                if (tile.Index < tileData.Length)
                {
                    Rect  uvRect           = tileData[tile.Index].TexCoord0;
                    bool  visualEmpty      = tileData[tile.Index].IsVisuallyEmpty;
                    int   tileBaseOffset   = tileData[tile.Index].DepthOffset;
                    float localDepthOffset = (tile.DepthOffset + tileBaseOffset) * depthPerTile;

                    if (!visualEmpty)
                    {
                        vertexData[vertexBaseIndex + 0].Pos.X      = renderPos.X;
                        vertexData[vertexBaseIndex + 0].Pos.Y      = renderPos.Y;
                        vertexData[vertexBaseIndex + 0].Pos.Z      = renderPos.Z + localDepthOffset;
                        vertexData[vertexBaseIndex + 0].TexCoord.X = uvRect.X;
                        vertexData[vertexBaseIndex + 0].TexCoord.Y = uvRect.Y;
                        vertexData[vertexBaseIndex + 0].Color      = mainColor;

                        vertexData[vertexBaseIndex + 1].Pos.X      = renderPos.X + tileYStep.X;
                        vertexData[vertexBaseIndex + 1].Pos.Y      = renderPos.Y + tileYStep.Y;
                        vertexData[vertexBaseIndex + 1].Pos.Z      = renderPos.Z + localDepthOffset + depthPerTile;
                        vertexData[vertexBaseIndex + 1].TexCoord.X = uvRect.X;
                        vertexData[vertexBaseIndex + 1].TexCoord.Y = uvRect.Y + uvRect.H;
                        vertexData[vertexBaseIndex + 1].Color      = mainColor;

                        vertexData[vertexBaseIndex + 2].Pos.X      = renderPos.X + tileXStep.X + tileYStep.X;
                        vertexData[vertexBaseIndex + 2].Pos.Y      = renderPos.Y + tileXStep.Y + tileYStep.Y;
                        vertexData[vertexBaseIndex + 2].Pos.Z      = renderPos.Z + localDepthOffset + depthPerTile;
                        vertexData[vertexBaseIndex + 2].TexCoord.X = uvRect.X + uvRect.W;
                        vertexData[vertexBaseIndex + 2].TexCoord.Y = uvRect.Y + uvRect.H;
                        vertexData[vertexBaseIndex + 2].Color      = mainColor;

                        vertexData[vertexBaseIndex + 3].Pos.X      = renderPos.X + tileXStep.X;
                        vertexData[vertexBaseIndex + 3].Pos.Y      = renderPos.Y + tileXStep.Y;
                        vertexData[vertexBaseIndex + 3].Pos.Z      = renderPos.Z + localDepthOffset;
                        vertexData[vertexBaseIndex + 3].TexCoord.X = uvRect.X + uvRect.W;
                        vertexData[vertexBaseIndex + 3].TexCoord.Y = uvRect.Y;
                        vertexData[vertexBaseIndex + 3].Color      = mainColor;

                        bool vertical = tileData[tile.Index].IsVertical;
                        if (vertical)
                        {
                            vertexData[vertexBaseIndex + 0].Pos.Z += depthPerTile;
                            vertexData[vertexBaseIndex + 3].Pos.Z += depthPerTile;
                        }

                        submittedTileCount++;
                        vertexBaseIndex += 4;
                    }
                }

                tileGridPos.X++;
                renderPos.X += tileXStep.X;
                renderPos.Y += tileXStep.Y;
                if ((tileGridPos.X - cullingOut.VisibleTileStart.X) >= cullingOut.VisibleTileCount.X)
                {
                    tileGridPos.X = cullingOut.VisibleTileStart.X;
                    tileGridPos.Y++;
                    renderPos    = cullingOut.RenderOriginView;
                    renderPos.X += tileYStep.X * (tileGridPos.Y - cullingOut.VisibleTileStart.Y);
                    renderPos.Y += tileYStep.Y * (tileGridPos.Y - cullingOut.VisibleTileStart.Y);
                    renderPos.Z += tileGridPos.Y * depthPerTile;
                }
            }

            // Submit all the vertices as one draw batch
            device.AddVertices(
                material,
                VertexMode.Quads,
                vertexData,
                submittedTileCount * 4);

            Profile.AddToStat(@"Duality\Stats\Render\Tilemaps\NumTiles", renderedTileCount);
            Profile.AddToStat(@"Duality\Stats\Render\Tilemaps\NumVertices", submittedTileCount * 4);
        }