public TilemapTileDrawSource(Tilemap tilemap, Point2 origin, Grid<bool> area)
 {
     if (tilemap == null) throw new ArgumentNullException("tilemap");
     this.tilemap = tilemap;
     this.origin = origin;
     this.area = new Grid<bool>(area);
 }
        public override bool Convert(ConvertOperation convert)
        {
            // If we already have a renderer in the result set, consider generating
            // a tilemap to be not the right course of action.
            if (convert.Result.OfType<ICmpRenderer>().Any())
                return false;

            List<object> results = new List<object>();
            List<Tileset> availData = convert.Perform<Tileset>().ToList();

            // Generate objects
            foreach (Tileset tileset in availData)
            {
                if (convert.IsObjectHandled(tileset)) continue;

                // Retrieve previously generated GameObjects and Tilemaps for re-use
                GameObject gameobj = convert.Result.OfType<GameObject>().FirstOrDefault();
                Tilemap tilemap = convert.Result.OfType<Tilemap>().FirstOrDefault();
                TilemapRenderer tilemapRenderer = convert.Result.OfType<TilemapRenderer>().FirstOrDefault();
                if (tilemap == null && gameobj != null) tilemap = gameobj.GetComponent<Tilemap>();

                // Create a new Tilemap (and TilemapRenderer) if none did exist before
                if (tilemap == null)
                {
                    tilemap = new Tilemap();
                    TilemapsSetupUtility.SetupTilemap(tilemap, tileset);

                    // Add a renderer for this Tilemap to the result list, if there was none before
                    if (tilemapRenderer == null)
                    {
                        results.Add(new TilemapRenderer());
                    }
                }

                // Configure the Tilemap according to the Tileset we're converting
                tilemap.Tileset = tileset;

                // Add the Tilemap to our result set
                results.Add(tilemap);
                convert.SuggestResultName(tilemap, tileset.Name);
                convert.MarkObjectHandled(tileset);
            }

            convert.AddResult(results);
            return false;
        }
        /// <summary>
        /// Prepares the specified <see cref="Tilemap"/> for user editing using the specified size.
        /// </summary>
        /// <param name="tilemap"></param>
        /// <param name="tilesetRef"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        /// <param name="isUpperLayer"></param>
        public static void SetupTilemap(Tilemap tilemap, ContentRef<Tileset> tilesetRef, int width, int height, bool isUpperLayer)
        {
            Tileset tileset = tilesetRef.Res;

            // Determine the first tile index that matches the layer type.
            int fillTileIndex = GetDefaultTileIndex(tileset, isUpperLayer);

            // Resize the Tilemap and fill it with the first visually non-empty tile.
            tilemap.Tileset = tileset;
            tilemap.Resize(width, height);
            tilemap.BeginUpdateTiles().Fill(
                new Tile(fillTileIndex),
                0,
                0,
                tilemap.Size.X,
                tilemap.Size.Y);
            tilemap.EndUpdateTiles();
        }
 /// <summary>
 /// Prepares the specified <see cref="Tilemap"/> for user editing using the default size.
 /// </summary>
 /// <param name="tilemap"></param>
 /// <param name="tilesetRef"></param>
 public static void SetupTilemap(Tilemap tilemap, ContentRef<Tileset> tilesetRef)
 {
     SetupTilemap(tilemap, tilesetRef, DefaultTilemapSize.X, DefaultTilemapSize.Y, false);
 }
        private void DualityEditorApp_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (e.SameObjects) return;
            if (!e.AffectedCategories.HasFlag(ObjectSelection.Category.GameObjCmp))
                return;

            // Tilemap selection changed
            Tilemap newSelection = TilemapsEditorSelectionParser.QuerySelectedTilemap();
            if (this.selectedTilemap != newSelection)
            {
                this.selectedTilemap = newSelection;
                if (this.Mouseover)
                    this.OnMouseMove();
                this.Invalidate();
                this.UpdateActionToolButtons();
            }
        }
 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();
 }
        protected override void OnEnterState()
        {
            base.OnEnterState();

            // Initialize available tools, actions and the toolbar containing them
            this.InitAvailableActions();
            this.InitAvailableTools();
            this.InitToolButtons();

            // Register events
            DualityEditorApp.SelectionChanged += this.DualityEditorApp_SelectionChanged;
            DualityEditorApp.ObjectPropertyChanged += this.DualityEditorApp_ObjectPropertyChanged;
            DualityEditorApp.UpdatingEngine += this.DualityEditorApp_UpdatingEngine;
            Scene.Entered += this.Scene_Entered;

            // Initial update
            this.UpdateTilemapToolButtons();
            this.UpdateActionToolButtons();
            this.selectedTilemap = TilemapsEditorSelectionParser.QuerySelectedTilemap();

            // If we're already focused when entering the state, publish the currently selected tilemap tool
            if (this.Focused)
                this.SelectToolInInspector();
        }
        void ITilemapToolEnvironment.PerformEditTiles(EditTilemapActionType actionType, Tilemap tilemap, Point2 pos, Grid<bool> brush, ITileDrawSource source, Point2 sourceOffset)
        {
            Grid<Tile> drawPatch = new Grid<Tile>(brush.Width, brush.Height);
            source.FillTarget(drawPatch, sourceOffset);

            UndoRedoManager.Do(new EditTilemapAction(
                tilemap,
                actionType,
                pos,
                drawPatch,
                brush,
                this.actionTool.Settings.UseAutoTiling));
        }
        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();
            }
        }
Exemple #10
0
        private void RetrieveSourceTilemaps()
        {
            Tilemap localTilemap = this.GameObj.GetComponent<Tilemap>();

            this.referenceTilemap = null;
            this.sourceTilemaps = new Tilemap[this.source.Length];
            for (int i = 0; i < this.sourceTilemaps.Length; i++)
            {
                this.sourceTilemaps[i] =
                    this.source[i].SourceTilemap ??
                    localTilemap;
                if (this.referenceTilemap == null && this.sourceTilemaps[i] != null)
                    this.referenceTilemap = this.sourceTilemaps[i];
            }
        }
Exemple #11
0
 private static Point2 GetTileCount(Tilemap[] tilemaps)
 {
     Point2 count = new Point2(int.MaxValue, int.MaxValue);
     for (int i = 0; i < tilemaps.Length; i++)
     {
         if (tilemaps[i] == null) continue;
         count.X = Math.Min(count.X, tilemaps[i].Size.X);
         count.Y = Math.Min(count.Y, tilemaps[i].Size.Y);
     }
     return count;
 }
Exemple #12
0
 private static TileInfo[][] GetRawTileData(Tilemap[] tilemaps)
 {
     TileInfo[][] tileData = new TileInfo[tilemaps.Length][];
     for (int i = 0; i < tilemaps.Length; i++)
     {
         if (tilemaps[i] == null) continue;
         if (tilemaps[i].Tileset.Res == null) continue;
         tileData[i] = tilemaps[i].Tileset.Res.TileData.Data;
     }
     return tileData;
 }
Exemple #13
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 = this.colorTint;

            // 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;
            }

            float renderBaseOffset = this.offset + this.tileDepthOffset * depthPerTile + originDepthOffset;

            // Prepare vertex generation data
            Vector2 tileXStep    = cullingOut.XAxisWorld * cullingIn.TileSize.X;
            Vector2 tileYStep    = cullingOut.YAxisWorld * cullingIn.TileSize.Y;
            Vector3 renderPos    = cullingOut.RenderOriginWorld;
            float   renderOffset = renderBaseOffset;
            Point2  tileGridPos  = cullingOut.VisibleTileStart;

            // Reserve the required space for vertex data in our locally cached buffer
            const int MaxVerticesPerBatch = 65532;

            if (this.vertices == null)
            {
                this.vertices = new RawList <VertexC1P3T2>();
            }
            this.vertices.Count = Math.Min(renderedTileCount * 4, MaxVerticesPerBatch);
            VertexC1P3T2[] vertexData = this.vertices.Data;

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

            TileInfo[] tileData            = tileset.TileData.Data;
            int        submittedTileCount  = 0;
            int        submittedBatchCount = 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;
                        vertexData[vertexBaseIndex + 0].DepthOffset = renderOffset + 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;
                        vertexData[vertexBaseIndex + 1].DepthOffset = renderOffset + 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;
                        vertexData[vertexBaseIndex + 2].DepthOffset = renderOffset + 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;
                        vertexData[vertexBaseIndex + 3].DepthOffset = renderOffset + 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].DepthOffset += depthPerTile;
                            vertexData[vertexBaseIndex + 3].DepthOffset += 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.RenderOriginWorld;
                    renderPos.X += tileYStep.X * (tileGridPos.Y - cullingOut.VisibleTileStart.Y);
                    renderPos.Y += tileYStep.Y * (tileGridPos.Y - cullingOut.VisibleTileStart.Y);
                    renderOffset = renderBaseOffset + tileGridPos.Y * depthPerTile;
                }

                // If we reached the maximum number of vertices per batch, submit early and restart
                if (vertexBaseIndex >= MaxVerticesPerBatch)
                {
                    device.AddVertices(
                        material,
                        VertexMode.Quads,
                        vertexData,
                        vertexBaseIndex);
                    vertexBaseIndex = 0;
                    submittedBatchCount++;
                }
            }

            // Submit the final batch will all remaining vertices
            if (vertexBaseIndex > 0)
            {
                device.AddVertices(
                    material,
                    VertexMode.Quads,
                    vertexData,
                    vertexBaseIndex);
                submittedBatchCount++;
            }

            Profile.AddToStat(@"Duality\Stats\Render\Tilemaps\NumTiles", renderedTileCount);
            Profile.AddToStat(@"Duality\Stats\Render\Tilemaps\NumVertices", submittedTileCount * 4);
            Profile.AddToStat(@"Duality\Stats\Render\Tilemaps\NumBatches", submittedBatchCount);
        }
 public TilemapChangedEventArgs(Tilemap tilemap, int x, int y, int width, int height)
     : base(tilemap)
 {
     this.pos = new Point2(x, y);
     this.size = new Point2(width, height);
 }
Exemple #15
0
        /// <summary>
        /// Runs the flood fill algorithm on the specified position and writes the result into the specified variables.
        /// </summary>
        /// <param name="tilemap"></param>
        /// <param name="pos"></param>
        /// <param name="preview">If true, the algorithm will cancel when taking too long for an interactive preview.</param>
        /// <param name="floodFillArea"></param>
        /// <param name="floodFillOrigin"></param>
        /// <returns>True, if the algorithm completed. False, if it was canceled.</returns>
        private bool GetFloodFillArea(Tilemap tilemap, Point2 pos, bool preview, Grid<bool> floodFillArea, ref Point2 floodFillOrigin)
        {
            Grid<Tile> tiles = tilemap.BeginUpdateTiles();
            Point2 fillTopLeft;
            Point2 fillSize;
            bool success = FloodFillTiles(ref this.activeFillBuffer, tiles, pos, preview ? (128 * 128) : 0, out fillTopLeft, out fillSize);
            tilemap.EndUpdateTiles(0, 0, 0, 0);

            // Find the filled areas boundaries and copy it to the active area
            if (success)
            {
                floodFillOrigin = fillTopLeft;
                floodFillArea.ResizeClear(fillSize.X, fillSize.Y);
                this.activeFillBuffer.CopyTo(floodFillArea, 0, 0, -1, -1, floodFillOrigin.X, floodFillOrigin.Y);
            }

            return success;
        }
Exemple #16
0
        private void UpdateRigidBody(RigidBody body, int sectorX, int sectorY)
        {
            Sector sector = this.sectors[sectorX, sectorY];

            // Determine collision checksum
            this.tempCollisionData.Clear();
            int newChecksum = this.MergeCollisionData(sectorX, sectorY, this.tempCollisionData);

            // If it differs from our previous value, update collision shapes
            if (sector.Checksum != newChecksum)
            {
                // Clean up old shapes
                if (sector.Shapes != null)
                {
                    foreach (ShapeInfo shape in sector.Shapes)
                    {
                        body.RemoveShape(shape);
                    }
                    sector.Shapes.Clear();
                }
                else
                {
                    sector.Shapes = new List <ShapeInfo>();
                }

                // Generate new shapes
                {
                    // Determine general working data
                    Tilemap tilemap        = this.referenceTilemap;
                    Tileset tileset        = tilemap != null ? tilemap.Tileset.Res : null;
                    Vector2 tileSize       = tileset != null ? tileset.TileSize : Tileset.DefaultTileSize;
                    Point2  sectorBaseTile = new Point2(
                        sectorX * SectorSize,
                        sectorY * SectorSize);
                    Vector2 sectorBasePos = sectorBaseTile * tileSize;

                    // Clear the temporary edge map first
                    this.tempEdgeMap.Clear();

                    // Populate the edge map with fence and block geometry
                    AddFenceCollisionEdges(this.tempCollisionData, this.tempEdgeMap);
                    AddBlockCollisionEdges(this.tempCollisionData, this.tempEdgeMap, sectorBaseTile, this.tileCount);
                    if (this.solidOuterEdges)
                    {
                        AddBorderCollisionEdges(this.tempEdgeMap, sectorBaseTile, this.tileCount);
                    }

                    // Now traverse the edge map and gradually create chain / loop
                    // shapes until all edges have been used.
                    Rect localRect = Rect.Align(this.origin, 0, 0, this.tileCount.X * tileSize.X, this.tileCount.Y * tileSize.Y);
                    GenerateCollisionShapes(this.tempEdgeMap, localRect.TopLeft + sectorBasePos, tileSize, this.roundedCorners, sector.Shapes);

                    // Add all the generated shapes to the target body
                    foreach (ShapeInfo shape in sector.Shapes)
                    {
                        body.AddShape(shape);
                        shape.Friction    = this.shapeFriction;
                        shape.Restitution = this.shapeRestitution;
                    }
                }
                sector.Checksum = newChecksum;
            }

            this.sectors[sectorX, sectorY] = sector;
        }