private Point2 GetTileAtLocalPos(ICmpTilemapRenderer renderer, Point localPos, TilePickMode pickMode)
        {
            Component component = renderer as Component;
            Transform transform = component.GameObj.Transform;

            // Determine where the cursor is hovering in various coordinate systems
            Vector3 worldCursorPos = this.CameraComponent.GetSpaceCoord(new Vector3(localPos.X, localPos.Y, transform.Pos.Z));
            Vector2 localCursorPos = transform.GetLocalPoint(worldCursorPos.Xy);

            // Determine tile coordinates of the cursor
            return renderer.GetTileAtLocalPos(localCursorPos, pickMode);
        }
        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();
        }
 protected override void OnMouseLeave(EventArgs e)
 {
     base.OnMouseLeave(e);
     this.hoveredTile = InvalidTile;
     this.hoveredRenderer = null;
     if (this.actionTool == this.toolNone)
     {
         this.activeTool = this.toolNone;
         this.activeTilemap = null;
         this.activeAreaOrigin = InvalidTile;
         this.activeArea.ResizeClear(0, 0);
     }
     this.UpdateCursor();
     this.Invalidate();
 }
        private void UpdateHoverState(Point cursorPos)
        {
            Point2 lastHoveredTile = this.hoveredTile;
            ICmpTilemapRenderer lastHoveredRenderer = this.hoveredRenderer;

            // Reset hover data
            this.hoveredTile = InvalidTile;
            this.hoveredRenderer = null;

            // Early-out, if the cursor isn't even inside the CamView area - unless the user is performing a cursor action
            if (!this.View.ClientRectangle.Contains(cursorPos) && this.actionTool == this.toolNone)
            {
                if (lastHoveredTile != this.hoveredTile || lastHoveredRenderer != this.hoveredRenderer)
                    this.Invalidate();
                return;
            }

            // Early-out, if a camera action claims the cursor
            if (this.CamActionRequiresCursor)
            {
                if (lastHoveredTile != this.hoveredTile || lastHoveredRenderer != this.hoveredRenderer)
                    this.Invalidate();
                return;
            }

            // While doing an action, it's either the selected tilemap or none. No picking, just determine the hovered tile
            bool performingAction = this.actionTool != this.toolNone;
            if (performingAction)
            {
                this.hoveredTile = this.GetTileAtLocalPos(this.activeRenderer, cursorPos, TilePickMode.Free);
                this.hoveredRenderer = this.activeRenderer;
            }
            // Otherwise, perform a tile-based picking operation
            else
            {
                List<ICmpTilemapRenderer> visibleRenderers;
                bool pureZSortPicking = !(this.overrideTool ?? this.selectedTool).PickPreferSelectedLayer || this.selectedTilemap == null;

                // Determine which renderers we're able to see right now and sort them by their Z values
                visibleRenderers = this.QueryVisibleTilemapRenderers().ToList();
                visibleRenderers.StableSort((a, b) =>
                {
                    // When prefered by the editing tool, the currently edited tilemap always prevails in picking checks
                    if (!pureZSortPicking && a.ActiveTilemap != b.ActiveTilemap)
                    {
                        if (a.ActiveTilemap == this.selectedTilemap)
                            return -1;
                        else if (b.ActiveTilemap == this.selectedTilemap)
                            return 1;
                    }

                    // Otherwise, do regular Z sorting
                    return
                        ((a as Component).GameObj.Transform.Pos.Z + a.BaseDepthOffset >
                        (b as Component).GameObj.Transform.Pos.Z + b.BaseDepthOffset)
                        ? 1 : -1;
                });

                // Eliminate all tilemap renderers without a tile hit, so we remain with only the renderers under the cursor.
                for (int i = visibleRenderers.Count - 1; i >= 0; i--)
                {
                    ICmpTilemapRenderer renderer = visibleRenderers[i];
                    Point2 tileCursorPos = this.GetTileAtLocalPos(renderer, cursorPos, TilePickMode.Reject);
                    if (tileCursorPos == InvalidTile)
                    {
                        visibleRenderers.RemoveAt(i);
                        continue;
                    }
                }

                // Iterate over the remaining tilemap renderers to find out which one prevails
                for (int i = 0; i < visibleRenderers.Count; i++)
                {
                    ICmpTilemapRenderer renderer = visibleRenderers[i];
                    Component component = renderer as Component;
                    Point2 tileCursorPos = this.GetTileAtLocalPos(renderer, cursorPos, TilePickMode.Reject);

                    // If the hovered tile is transparent, don't treat it as a hit unless it's the bottom renderer
                    bool isBottomRenderer = (i == visibleRenderers.Count - 1);
                    if (pureZSortPicking && !isBottomRenderer)
                    {
                        Tilemap tilemap = renderer.ActiveTilemap;
                        int tileIndex = (tilemap != null) ? tilemap.Tiles[tileCursorPos.X, tileCursorPos.Y].Index : -1;
                        if (tileIndex == -1 || this.IsTileTransparent(tilemap.Tileset.Res, tileIndex))
                        {
                            tileCursorPos = InvalidTile;
                        }
                    }

                    // If we're hovering a tile of the current renderer, we're done
                    if (tileCursorPos != InvalidTile)
                    {
                        // If it's locked and not selected, treat it as blocking but don't allow editing
                        bool isLocked = DesignTimeObjectData.Get(component.GameObj).IsLocked;
                        if (isLocked)
                        {
                            bool isSelected = false;
                            IEnumerable<Tilemap> selectedTilemaps = TilemapsEditorSelectionParser.QuerySelectedTilemaps();
                            foreach (Tilemap tilemap in selectedTilemaps)
                            {
                                if (renderer.ActiveTilemap == tilemap)
                                {
                                    isSelected = true;
                                    break;
                                }
                            }
                            if (!isSelected) break;
                        }

                        // Set the hovered renderer and stop picking
                        this.hoveredTile = tileCursorPos;
                        this.hoveredRenderer = renderer;
                        break;
                    }
                }
            }

            // If we're not doing an action, let our action begin tile just follow around
            if (this.actionTool == this.toolNone)
                this.actionBeginTile = this.hoveredTile;

            // If something changed, redraw the view
            if (lastHoveredTile != this.hoveredTile || lastHoveredRenderer != this.hoveredRenderer)
            {
                this.Invalidate();
            }
        }
        private void UpdateActiveState()
        {
            TilemapTool lastActiveTool = this.activeTool;
            Tilemap lastActiveTilemap = this.activeTilemap;
            ICmpTilemapRenderer lastActiveRenderer = this.activeRenderer;

            // If an action is currently being performed, that action will always be the active tool
            if (this.actionTool != this.toolNone)
            {
                this.activeTool = this.actionTool;
            }
            // Otherwise, determine what action the cursor would do right now
            else
            {
                // Determine the active tool dynamically based on user input state
                if (this.hoveredRenderer == null)
                    this.activeTool = this.toolNone;
                else if (this.selectedTilemap == null)
                    this.activeTool = this.toolSelect;
                else if (this.hoveredRenderer.ActiveTilemap != this.selectedTilemap)
                    this.activeTool = this.toolSelect;
                else
                    this.activeTool = this.overrideTool ?? this.selectedTool;

                // Keep in mind on what renderer and tilemap belong to the currently active tool
                this.activeTilemap = (this.hoveredRenderer != null) ? this.hoveredRenderer.ActiveTilemap : null;
                this.activeRenderer = this.hoveredRenderer;
            }

            // Determine the area that is affected by the current action
            this.activeTool.UpdatePreview();

            // If our highlighted action changed, redraw view and update the cursor
            if (lastActiveTool != this.activeTool || lastActiveTilemap != this.activeTilemap || lastActiveRenderer != this.activeRenderer)
            {
                this.UpdateCursor();
                this.Invalidate();
            }
        }
示例#6
0
        public override void Perform(IEnumerable <Tilemap> objEnum)
        {
            // Since resizing is usually done for all tilemaps in a layered setup,
            // but users will often have only one of them selected due to previous
            // editing operations, check for layered setups and deal with them.
            List <Tilemap> tilemaps = objEnum.ToList();

            if (tilemaps.Count == 1)
            {
                Tilemap    baseTilemap = tilemaps[0];
                GameObject baseObject  = baseTilemap.GameObj;
                Scene      baseScene   = (baseObject != null) ? baseObject.ParentScene : null;

                // Prerequisite: We are operating on a tilemap with a proper parent and
                // parent scene available. Otherwise, there is nothing we could do.
                if (baseScene != null)
                {
                    // Since a tilemap is just a data container without any relation to a
                    // scene or layered setups, we will check tilemap renderers instead.
                    // Only they can provide the required information for determining layered setups.
                    ICmpTilemapRenderer[] allRenderers  = baseScene.FindComponents <ICmpTilemapRenderer>().ToArray();
                    ICmpTilemapRenderer   baseRenderer  = allRenderers.FirstOrDefault(r => r.ActiveTilemap == baseTilemap);
                    Transform             baseTransform = (baseRenderer != null) ? (baseRenderer as Component).GameObj.Transform : null;
                    if (baseTransform != null)
                    {
                        foreach (ICmpTilemapRenderer renderer in allRenderers)
                        {
                            if (renderer.ActiveTilemap == null)
                            {
                                continue;
                            }
                            if (tilemaps.Contains(renderer.ActiveTilemap))
                            {
                                continue;
                            }

                            Transform transform = (renderer as Component).GameObj.Transform;
                            if (baseTransform.Pos.Xy != transform.Pos.Xy)
                            {
                                continue;
                            }
                            if (baseTransform.Angle != transform.Angle)
                            {
                                continue;
                            }
                            if (baseTransform.Scale != transform.Scale)
                            {
                                continue;
                            }

                            if (baseTilemap.Size != renderer.ActiveTilemap.Size)
                            {
                                continue;
                            }
                            if (baseRenderer.LocalTilemapRect != renderer.LocalTilemapRect)
                            {
                                continue;
                            }

                            tilemaps.Add(renderer.ActiveTilemap);
                        }
                    }
                }
            }

            // Open the resize dialog. It will handle all the rest.
            TilemapResizeDialog resizeDialog = new TilemapResizeDialog();

            resizeDialog.Tilemaps = tilemaps;
            resizeDialog.ShowDialog(DualityEditorApp.MainForm);
        }