public void FillTarget(Grid<Tile> target, Point2 offset) { Point2 patternSize = new Point2(this.pattern.Width, this.pattern.Height); // Adjust the offset to positive values only if (offset.X < 0) offset.X += (1 + (-offset.X / patternSize.X)) * patternSize.X; if (offset.Y < 0) offset.Y += (1 + (-offset.Y / patternSize.Y)) * patternSize.Y; // Apply the source tiles to the target area for (int y = 0; y < target.Height; y++) { for (int x = 0; x < target.Width; x++) { Point2 wrapped = new Point2( (x + offset.X) % patternSize.X, (y + offset.Y) % patternSize.Y); if (this.shape[wrapped.X, wrapped.Y]) target[x, y] = this.pattern[wrapped.X, wrapped.Y]; else target[x, y] = new Tile(); } } }
void ISerializeExplicit.ReadData(IDataReader reader) { // Determine which Tile data version we're dealing with int version; try { reader.ReadValue("version", out version); } catch (Exception) { version = Serialize_Version_Unknown; } // Read the compressed data and unpack it if (version >= Serialize_MinVersion && version <= Serialize_MaxVersion) { byte[] compressedData; reader.ReadValue("data", out compressedData); using (MemoryStream compressedStream = new MemoryStream(compressedData)) using (GZipStream stream = new GZipStream(compressedStream, CompressionMode.Decompress)) using (BinaryReader streamReader = new BinaryReader(stream)) { int width = streamReader.ReadInt32(); int height = streamReader.ReadInt32(); Tile[] rawData = new Tile[width * height]; switch (version) { case Serialize_Version_ZippedBytes: this.ReadBinVersionFirst(streamReader, rawData); break; case Serialize_Version_DepthOffset: this.ReadBinVersionDepth(streamReader, rawData); break; case Serialize_Version_AutoTile: this.ReadBinVersionAutoTile(streamReader, rawData); break; } this.tiles = new Grid<Tile>(width, height, rawData); } } else { throw new NotSupportedException(string.Format( "Unknown TilemapData serialization version '{0}'. Can't load tilemap data.", version)); } }
private void ReadBinVersionFirst(BinaryReader reader, Tile[] target) { for (int i = 0; i < target.Length; i++) { target[i].BaseIndex = reader.ReadInt32(); target[i].Index = target[i].BaseIndex; } }
private void ReadBinVersionAutoTile(BinaryReader reader, Tile[] target) { for (int i = 0; i < target.Length; i++) { target[i].BaseIndex = reader.ReadInt32(); target[i].DepthOffset = reader.ReadInt16(); target[i].AutoTileCon = (TileConnection)reader.ReadByte(); target[i].Index = target[i].BaseIndex; } }
/// <summary> /// Resolves the <see cref="Index"/> values of the specified <see cref="Tile"/> grid area, given the grid's raw data block. /// </summary> /// <param name="tileGridData"></param> /// <param name="beginX"></param> /// <param name="beginY"></param> /// <param name="width"></param> /// <param name="height"></param> /// <param name="stride"></param> /// <param name="tilesetRes"></param> public static void ResolveIndices(Tile[] tileGridData, int stride, int beginX, int beginY, int width, int height, ContentRef<Tileset> tileset) { if (tileset.Res == null) throw new ArgumentNullException("tileset"); if (!tileset.Res.Compiled) throw new InvalidOperationException("The specified Tileset needs to be compiled first."); Tileset tilesetRes = tileset.Res; TileInfo[] tileData = tilesetRes.TileData.Data; int tileCount = tilesetRes.TileCount; for (int y = beginY; y < beginY + height; y++) { for (int x = beginX; x < beginX + width; x++) { int i = y * stride + x; int baseIndex = MathF.Clamp(tileGridData[i].BaseIndex, 0, tileCount - 1); int autoTileIndex = tileData[baseIndex].AutoTileLayer - 1; TilesetAutoTileInfo autoTile = autoTileIndex > -1 ? tilesetRes.AutoTileData[autoTileIndex] : null; tileGridData[i].ResolveIndex(autoTile); } } }
public override void Do() { if (this.oldData == null) { this.oldData = new Grid<Tile>[this.tilemaps.Length]; for (int i = 0; i < this.tilemaps.Length; i++) { this.oldData[i] = new Grid<Tile>(this.tilemaps[i].BeginUpdateTiles()); this.tilemaps[i].EndUpdateTiles(0, 0, 0, 0); } } for (int i = 0; i < this.tilemaps.Length; i++) { // Determine the tile which should fill the new area Tile fillTile = new Tile(TilemapsSetupUtility.GetDefaultTileIndex( this.tilemaps[i].Tileset.Res, true)); // Determine the up to four regions that we will add int leftAdd = 0; int rightAdd = 0; int topAdd = 0; int bottomAdd = 0; { if (this.origin == Alignment.Right || this.origin == Alignment.TopRight || this.origin == Alignment.BottomRight) leftAdd = this.newSize.X - this.tilemaps[i].Size.X; else if ( this.origin == Alignment.Center || this.origin == Alignment.Top || this.origin == Alignment.Bottom) leftAdd = (this.newSize.X - this.tilemaps[i].Size.X) / 2; if (this.origin == Alignment.Bottom || this.origin == Alignment.BottomLeft || this.origin == Alignment.BottomRight) topAdd = this.newSize.Y - this.tilemaps[i].Size.Y; else if ( this.origin == Alignment.Center || this.origin == Alignment.Left || this.origin == Alignment.Right) topAdd = (this.newSize.Y - this.tilemaps[i].Size.Y) / 2; rightAdd = (this.newSize.X - this.tilemaps[i].Size.X) - leftAdd; bottomAdd = (this.newSize.Y - this.tilemaps[i].Size.Y) - topAdd; } // Resize the tilemap this.tilemaps[i].Resize(this.newSize.X, this.newSize.Y, this.origin); // If we have a non-default filling tile, use it if (!object.Equals(fillTile, default(Tile))) { Grid<Tile> data = this.tilemaps[i].BeginUpdateTiles(); if (topAdd > 0) data.Fill(fillTile, 0, 0, data.Width, topAdd); if (leftAdd > 0) data.Fill(fillTile, 0, 0, leftAdd, data.Height); if (bottomAdd > 0) data.Fill(fillTile, 0, data.Height - bottomAdd, data.Width, bottomAdd); if (rightAdd > 0) data.Fill(fillTile, data.Width - rightAdd, 0, rightAdd, data.Height); this.tilemaps[i].EndUpdateTiles(0, 0, 0, 0); } } this.OnNotifyPropertyChanged(); }
/// <summary> /// Resolves the <see cref="Index"/> values of the specified tile array segment. /// </summary> /// <param name="tiles"></param> /// <param name="index"></param> /// <param name="count"></param> /// <param name="tileset"></param> public static void ResolveIndices(Tile[] tiles, int index, int count, ContentRef<Tileset> tileset) { ResolveIndices(tiles, 0, index, 0, count, 1, tileset); }
private static bool _FloodFill_TilesEqual(Tile baseTile, Tile otherTile) { return baseTile.BaseIndex == otherTile.BaseIndex; }
private static bool _FloodFill_IsCandidate(Grid<bool> fillBuffer, Grid<Tile> tiles, Point2 pos, Tile baseTile) { return !fillBuffer[pos.X, pos.Y] && _FloodFill_TilesEqual(baseTile, tiles[pos.X, pos.Y]); }
private static Point2 _FloodFillTiles_FindTopLeft(Grid<bool> fillBuffer, Grid<Tile> tiles, Point2 pos, Tile baseTile) { // Find the topleft-most connected matching tile while(true) { Point2 origin = pos; while (pos.Y != 0 && _FloodFill_IsCandidate(fillBuffer, tiles, new Point2(pos.X, pos.Y - 1), baseTile)) pos.Y--; while (pos.X != 0 && _FloodFill_IsCandidate(fillBuffer, tiles, new Point2(pos.X - 1, pos.Y), baseTile)) pos.X--; if (pos == origin) break; } return pos; }
private static bool _FloodFillTiles(Grid<bool> fillBuffer, Grid<Tile> tiles, Point2 pos, Tile baseTile, ref int maxTileCount, ref Point2 fillAreaTopLeft, ref Point2 fillAreaSize) { // Adjust the fill area to the position we'll be starting to fill fillAreaSize.X += Math.Max(fillAreaTopLeft.X - pos.X, 0); fillAreaSize.Y += Math.Max(fillAreaTopLeft.Y - pos.Y, 0); fillAreaTopLeft.X = Math.Min(fillAreaTopLeft.X, pos.X); fillAreaTopLeft.Y = Math.Min(fillAreaTopLeft.Y, pos.Y); // Since the top and left of the current tile are blocking the fill operation, proceed down and right int lastRowLength = 0; do { Point2 rowPos = pos; int rowLength = 0; bool firstRow = (lastRowLength == 0); // Narrow scan line width on the left when necessary if (!firstRow && !_FloodFill_IsCandidate(fillBuffer, tiles, pos, baseTile)) { while (lastRowLength > 1) { pos.X++; lastRowLength--; if (_FloodFill_IsCandidate(fillBuffer, tiles, pos, baseTile)) break; } rowPos.X = pos.X; } // Expand scan line width to the left when necessary else { for (; pos.X != 0 && _FloodFill_IsCandidate(fillBuffer, tiles, new Point2(pos.X - 1, pos.Y), baseTile); rowLength++, lastRowLength++) { pos.X--; fillBuffer[pos.X, pos.Y] = true; // Adjust the fill area fillAreaSize.X += Math.Max(fillAreaTopLeft.X - pos.X, 0); fillAreaTopLeft.X = Math.Min(fillAreaTopLeft.X, pos.X); // If something above the current scan line is free, handle it recursively if (pos.Y != 0 && _FloodFill_IsCandidate(fillBuffer, tiles, new Point2(pos.X, pos.Y - 1), baseTile)) { // Find the topleft-most tile to start with Point2 targetPos = new Point2(pos.X, pos.Y - 1); targetPos = _FloodFillTiles_FindTopLeft(fillBuffer, tiles, targetPos, baseTile); if (!_FloodFillTiles(fillBuffer, tiles, targetPos, baseTile, ref maxTileCount, ref fillAreaTopLeft, ref fillAreaSize)) return false; } } } // Fill the current row for (; rowPos.X < tiles.Width && _FloodFill_IsCandidate(fillBuffer, tiles, rowPos, baseTile); rowLength++, rowPos.X++) { fillBuffer[rowPos.X, rowPos.Y] = true; } maxTileCount -= rowLength; if (maxTileCount < 0) return false; // Adjust the fill area fillAreaSize.X = Math.Max(fillAreaSize.X, rowPos.X - fillAreaTopLeft.X); // If the current row is shorter than the previous, see if there are // disconnected pixels below the (filled) previous row left to handle if (rowLength < lastRowLength) { for (int end = pos.X + lastRowLength; ++rowPos.X < end; ) { // Recursively handle the disconnected below-bottom pixels of the last row if (_FloodFill_IsCandidate(fillBuffer, tiles, rowPos, baseTile)) { if (!_FloodFillTiles(fillBuffer, tiles, rowPos, baseTile, ref maxTileCount, ref fillAreaTopLeft, ref fillAreaSize)) return false; } } } // If the current row is longer than the previous, see if there are // top pixels above this one that are disconnected from the last row else if (rowLength > lastRowLength && pos.Y != 0) { for (int prevRowX = pos.X + lastRowLength; ++prevRowX < rowPos.X; ) { // Recursively handle the disconnected pixels of the last row if (_FloodFill_IsCandidate(fillBuffer, tiles, new Point2(prevRowX, pos.Y - 1), baseTile)) { // Find the topleft-most tile to start with Point2 targetPos = new Point2(prevRowX, pos.Y - 1); targetPos = _FloodFillTiles_FindTopLeft(fillBuffer, tiles, targetPos, baseTile); if (!_FloodFillTiles(fillBuffer, tiles, targetPos, baseTile, ref maxTileCount, ref fillAreaTopLeft, ref fillAreaSize)) return false; } } } lastRowLength = rowLength; pos.Y++; // Adjust the fill area fillAreaSize.Y = Math.Max(fillAreaSize.Y, pos.Y - fillAreaTopLeft.Y); } while (lastRowLength != 0 && pos.Y < tiles.Height); return true; }
/// <summary> /// Sets a single tile at the specified position. /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="tile"></param> public void SetTile(int x, int y, Tile tile) { this.tileData.Tiles[x, y] = tile; this.OnTilesChanged(x, y, 1, 1); }