diff --git a/doc/bridge_packages/flame_tiled/tiled.md b/doc/bridge_packages/flame_tiled/tiled.md index 4a5b7cd3394..0a465023b29 100644 --- a/doc/bridge_packages/flame_tiled/tiled.md +++ b/doc/bridge_packages/flame_tiled/tiled.md @@ -26,6 +26,15 @@ The [dart] package provides a simple `Tiled` class and [flame_tiled] provides a `TiledComponent`, for the map rendering, which renders the tiles on the screen and supports rotations and flips. +Infinite maps from the Tiled editor are supported. Tile positions, `getTileData`, and object +coordinates match the editor (including negative tile indices if you painted left or above the +origin). All chunks present in the file are cached at load time; very large infinite maps may +therefore be expensive. Chunk streaming based on the camera is not implemented yet. + +Keep in mind that the size of a `TiledComponent` still comes from the map's `width` and `height`, +so tiles outside of that area (for example at negative indices) are rendered outside of the +component's bounds. `setTileData` only changes cells inside chunks that already exist in the map. + ## Tiled Editor diff --git a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/hexagonal_tile_layer.dart b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/hexagonal_tile_layer.dart index 04726a45ca2..6cd0f4b8ced 100644 --- a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/hexagonal_tile_layer.dart +++ b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/hexagonal_tile_layer.dart @@ -24,7 +24,6 @@ class HexagonalTileLayer extends FlameTileLayer { @override void cacheTiles() { - final tileData = layer.tileData!; final halfDestinationTile = destTileSize / 2; final size = destTileSize; final halfMapTile = Vector2(map.tileWidth / 2, map.tileHeight / 2); @@ -43,9 +42,7 @@ class HexagonalTileLayer extends FlameTileLayer { staggerX = size.x * 0.75; } - for (var ty = 0; ty < tileData.length; ty++) { - final tileRow = tileData[ty]; - + for (final MapEntry(key: ty, value: tileRow) in tilesByWorldRow().entries) { // Hexagonal Pointy Tiles shift left and right depending on the row if (map.staggerAxis == StaggerAxis.y) { if ((ty.isOdd && map.staggerIndex == StaggerIndex.odd) || @@ -61,12 +58,7 @@ class HexagonalTileLayer extends FlameTileLayer { // painted over. See the second pass loop after tx. final xSecondPass = []; - for (var tx = 0; tx < tileRow.length; tx++) { - final tileGid = tileRow[tx]; - if (tileGid.tile == 0) { - continue; - } - + for (final (tx, tileGid) in tileRow) { final tile = map.tileByGid(tileGid.tile)!; final tileset = map.tilesetByTileGId(tileGid.tile); final img = tile.image ?? tileset.image; @@ -125,7 +117,7 @@ class HexagonalTileLayer extends FlameTileLayer { final scos = flips.cos * scale; final ssin = flips.sin * scale; - final transform = transforms[tx][ty] = MutableRSTransform( + final transform = MutableRSTransform( scos, ssin, offsetX, @@ -133,6 +125,7 @@ class HexagonalTileLayer extends FlameTileLayer { -scos * anchorX + ssin * anchorY, -ssin * anchorX - scos * anchorY, ); + storeTransform(tx, ty, transform); // A second pass is only needed in the case of staggery. if (map.staggerAxis == StaggerAxis.x && staggerY > 0) { xSecondPass.add(TileTransform(src, transform, flips, batch)); diff --git a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/isometric_tile_layer.dart b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/isometric_tile_layer.dart index 781c5929ece..addc692ffcd 100644 --- a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/isometric_tile_layer.dart +++ b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/isometric_tile_layer.dart @@ -23,7 +23,6 @@ class IsometricTileLayer extends FlameTileLayer { @override void cacheTiles() { - final tileData = layer.tileData!; final halfDestinationTile = destTileSize / 2; final size = destTileSize; final isometricXShift = map.height * halfDestinationTile.x; @@ -34,15 +33,8 @@ class IsometricTileLayer extends FlameTileLayer { return; } - for (var ty = 0; ty < tileData.length; ty++) { - final tileRow = tileData[ty]; - - for (var tx = 0; tx < tileRow.length; tx++) { - final tileGid = tileRow[tx]; - if (tileGid.tile == 0) { - continue; - } - + for (final MapEntry(key: ty, value: tileRow) in tilesByWorldRow().entries) { + for (final (tx, tileGid) in tileRow) { final tile = map.tileByGid(tileGid.tile)!; final tileset = map.tilesetByTileGId(tileGid.tile); final img = tile.image ?? tileset.image; @@ -78,7 +70,7 @@ class IsometricTileLayer extends FlameTileLayer { final scos = flips.cos * scale; final ssin = flips.sin * scale; - transforms[tx][ty] = MutableRSTransform( + final transform = MutableRSTransform( scos, ssin, offsetX, @@ -86,10 +78,11 @@ class IsometricTileLayer extends FlameTileLayer { -scos * anchorX + ssin * anchorY, -ssin * anchorX - scos * anchorY, ); + storeTransform(tx, ty, transform); batch.addTransform( source: src, - transform: transforms[tx][ty], + transform: transform, flip: shouldFlip(flips), ); diff --git a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/orthogonal_tile_layer.dart b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/orthogonal_tile_layer.dart index 39d5f714592..9d81370428b 100644 --- a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/orthogonal_tile_layer.dart +++ b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/orthogonal_tile_layer.dart @@ -23,7 +23,6 @@ class OrthogonalTileLayer extends FlameTileLayer { @override void cacheTiles() { - final tileData = layer.tileData!; final size = destTileSize; final halfMapTile = Vector2(map.tileWidth / 2, map.tileHeight / 2); final batch = tiledAtlas.batch; @@ -31,15 +30,8 @@ class OrthogonalTileLayer extends FlameTileLayer { return; } - for (var ty = 0; ty < tileData.length; ty++) { - final tileRow = tileData[ty]; - - for (var tx = 0; tx < tileRow.length; tx++) { - final tileGid = tileRow[tx]; - if (tileGid.tile == 0) { - continue; - } - + for (final MapEntry(key: ty, value: tileRow) in tilesByWorldRow().entries) { + for (final (tx, tileGid) in tileRow) { final tile = map.tileByGid(tileGid.tile)!; final tileset = map.tilesetByTileGId(tileGid.tile); final img = tile.image ?? tileset.image; @@ -83,7 +75,7 @@ class OrthogonalTileLayer extends FlameTileLayer { final scos = flips.cos * scale; final ssin = flips.sin * scale; - transforms[tx][ty] = MutableRSTransform( + final transform = MutableRSTransform( scos, ssin, offsetX, @@ -91,10 +83,11 @@ class OrthogonalTileLayer extends FlameTileLayer { -scos * anchorX + ssin * anchorY, -ssin * anchorX - scos * anchorY, ); + storeTransform(tx, ty, transform); batch.addTransform( source: src, - transform: transforms[tx][ty], + transform: transform, flip: shouldFlip(flips), ); diff --git a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/staggered_tile_layer.dart b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/staggered_tile_layer.dart index 0d693808a69..a869a77f9eb 100644 --- a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/staggered_tile_layer.dart +++ b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/staggered_tile_layer.dart @@ -23,7 +23,6 @@ class StaggeredTileLayer extends FlameTileLayer { @override void cacheTiles() { - final tileData = layer.tileData!; final halfDestinationTile = destTileSize / 2; final size = destTileSize; final halfMapTile = Vector2(map.tileWidth / 2, map.tileHeight / 2); @@ -43,9 +42,7 @@ class StaggeredTileLayer extends FlameTileLayer { staggerX = size.x * 0.5; } - for (var ty = 0; ty < tileData.length; ty++) { - final tileRow = tileData[ty]; - + for (final MapEntry(key: ty, value: tileRow) in tilesByWorldRow().entries) { // Isometric staggered tiles shift left and right depending on the row if (map.staggerAxis == StaggerAxis.y) { if ((ty.isOdd && map.staggerIndex == StaggerIndex.odd) || @@ -61,12 +58,7 @@ class StaggeredTileLayer extends FlameTileLayer { // painted over. See the second pass loop after tx. final xSecondPass = []; - for (var tx = 0; tx < tileRow.length; tx++) { - final tileGid = tileRow[tx]; - if (tileGid.tile == 0) { - continue; - } - + for (final (tx, tileGid) in tileRow) { final tile = map.tileByGid(tileGid.tile)!; final tileset = map.tilesetByTileGId(tileGid.tile); final img = tile.image ?? tileset.image; @@ -125,7 +117,7 @@ class StaggeredTileLayer extends FlameTileLayer { final scos = flips.cos * scale; final ssin = flips.sin * scale; - final transform = transforms[tx][ty] = MutableRSTransform( + final transform = MutableRSTransform( scos, ssin, offsetX, @@ -133,6 +125,7 @@ class StaggeredTileLayer extends FlameTileLayer { -scos * anchorX + ssin * anchorY, -ssin * anchorX - scos * anchorY, ); + storeTransform(tx, ty, transform); // A second pass is only needed in the case of staggery. if (map.staggerAxis == StaggerAxis.x && staggerY > 0) { diff --git a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/tile_layer.dart b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/tile_layer.dart index f43d342266f..4de03129c70 100644 --- a/packages/flame_tiled/lib/src/renderable_layers/tile_layers/tile_layer.dart +++ b/packages/flame_tiled/lib/src/renderable_layers/tile_layers/tile_layer.dart @@ -1,3 +1,5 @@ +import 'dart:collection'; + import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; import 'package:flame/rendering.dart'; @@ -35,7 +37,20 @@ import 'package:meta/meta.dart'; abstract class FlameTileLayer extends RenderableLayer { late Paint _layerPaint; final TiledAtlas tiledAtlas; + + /// Cached transform of every tile, indexed as + /// `transforms[x - originX][y - originY]` for the Tiled tile `(x, y)`. + /// + /// It covers the layer's [TileLayer.contentBounds]. Use [transformAt] and + /// [storeTransform] instead of indexing it directly. late List> transforms; + + /// The Tiled coordinates of the tile stored at `transforms[0][0]`. + /// + /// This is `(0, 0)` for finite maps. Infinite maps can have tiles at + /// negative coordinates, in which case the origin is negative too. + int originX = 0; + int originY = 0; final animations = []; final Map animationFrames; final bool ignoreFlip; @@ -179,16 +194,70 @@ abstract class FlameTileLayer extends RenderableLayer { @override void refreshCache() { animations.clear(); - transforms = List.generate( - layer.width, - (index) => List.filled(layer.height, null), - ); + // The area that has tiles: `(0, 0, width, height)` for finite layers, the + // area covered by all chunks for infinite layers, and `null` for layers + // without any tile data. + final bounds = layer.contentBounds; + if (bounds == null) { + originX = 0; + originY = 0; + transforms = >[]; + } else { + originX = bounds.left; + originY = bounds.top; + transforms = List.generate( + bounds.width, + (_) => List.filled(bounds.height, null), + ); + } tiledAtlas.batch?.clear(); cacheTiles(); } + /// Transform for Tiled tile `(x, y)`, or `null` if that cell is empty or + /// outside this layer's cached bounds. + MutableRSTransform? transformAt(int x, int y) { + final ix = x - originX; + final iy = y - originY; + if (ix < 0 || + iy < 0 || + ix >= transforms.length || + iy >= transforms[ix].length) { + return null; + } + return transforms[ix][iy]; + } + + /// Stores the [transform] of the Tiled tile `(tx, ty)` in [transforms]. + @protected + void storeTransform(int tx, int ty, MutableRSTransform transform) { + transforms[tx - originX][ty - originY] = transform; + } + + /// Non-empty tiles of this layer grouped by Tiled row, with rows sorted by + /// `y` and each row sorted by `x`. + /// + /// Infinite layers store tiles chunk by chunk, so iterating the layer + /// directly does not visit tiles in row-major order. Overlapping tiles + /// (isometric, staggered, hexagonal and oversized tiles) need row-major + /// order to be painted like Tiled does. + @protected + SplayTreeMap> tilesByWorldRow() { + final rows = SplayTreeMap>(); + layer.forEachTile((x, y, gid) { + if (gid.tile == 0) { + return; + } + rows.putIfAbsent(y, () => []).add((x, gid)); + }); + for (final row in rows.values) { + row.sort((a, b) => a.$1.compareTo(b.$1)); + } + return rows; + } + /// We need to know the following information for each tile to render a layer /// correctly. /// diff --git a/packages/flame_tiled/lib/src/renderable_tile_map.dart b/packages/flame_tiled/lib/src/renderable_tile_map.dart index 453d519e8b7..2228ad5cb69 100644 --- a/packages/flame_tiled/lib/src/renderable_tile_map.dart +++ b/packages/flame_tiled/lib/src/renderable_tile_map.dart @@ -108,6 +108,13 @@ class RenderableTiledMap { /// Changes the Gid of the corresponding layer at the given layerId, /// if different + /// + /// {@template renderable_tile_map_set_tile} + /// [x] and [y] are the tile coordinates shown in the Tiled editor, which + /// can be negative on infinite maps. Only cells that exist in the map can + /// be changed: nothing happens for cells outside of a finite map, or + /// outside of the chunks of an infinite map. + /// {@endtemplate} void setTileData({ required int layerId, required int x, @@ -116,21 +123,16 @@ class RenderableTiledMap { }) { final layer = map.layers.firstWhereOrNull((layer) => layer.id == layerId); if (layer is TileLayer) { - final td = layer.tileData; - if (td != null) { - if (td[y][x].tile != gid.tile || - td[y][x].flips.horizontally != gid.flips.horizontally || - td[y][x].flips.vertically != gid.flips.vertically || - td[y][x].flips.diagonally != gid.flips.diagonally) { - td[y][x] = gid; - _refreshCache(); - } + if (layer.setTileAt(x, y, gid)) { + _refreshCache(); } } } /// Changes the Gid of the corresponding layer at the given position, /// if different + /// + /// {@macro renderable_tile_map_set_tile} void setTileDataByLayerIndex({ required int layerIndex, required int x, @@ -139,20 +141,19 @@ class RenderableTiledMap { }) { final layer = map.layers[layerIndex]; if (layer is TileLayer) { - final td = layer.tileData; - if (td != null) { - if (td[y][x].tile != gid.tile || - td[y][x].flips.horizontally != gid.flips.horizontally || - td[y][x].flips.vertically != gid.flips.vertically || - td[y][x].flips.diagonally != gid.flips.diagonally) { - td[y][x] = gid; - _refreshCache(); - } + if (layer.setTileAt(x, y, gid)) { + _refreshCache(); } } } /// Gets the Gid of the corresponding layer at the given layerId + /// + /// {@template renderable_tile_map_get_tile} + /// [x] and [y] are the tile coordinates shown in the Tiled editor, which + /// can be negative on infinite maps. Returns `null` for cells outside of a + /// finite map, or outside of the chunks of an infinite map. + /// {@endtemplate} Gid? getTileData({ required int layerId, required int x, @@ -160,12 +161,14 @@ class RenderableTiledMap { }) { final layer = map.layers.firstWhereOrNull((layer) => layer.id == layerId); if (layer is TileLayer) { - return layer.tileData?[y][x]; + return layer.tileAt(x, y); } return null; } /// Gets the Gid of the corresponding layer at the given position + /// + /// {@macro renderable_tile_map_get_tile} Gid? getTileDataByLayerIndex({ required int layerIndex, required int x, @@ -173,13 +176,16 @@ class RenderableTiledMap { }) { final layer = map.layers[layerIndex]; if (layer is TileLayer) { - return layer.tileData?[y][x]; + return layer.tileAt(x, y); } return null; } /// Select a group of tiles from the coordinates [x] and [y]. /// + /// [x] and [y] are the tile coordinates shown in the Tiled editor, which + /// can be negative on infinite maps. + /// /// If [all] is set to true, every renderable tile from the map is collected. /// /// If the [named] or [ids] sets are not empty, any layer with matching @@ -239,8 +245,9 @@ class RenderableTiledMap { continue; } - if (layer.transforms[x][y] != null) { - tiles.add(layer.transforms[x][y]!); + final transform = layer.transformAt(x, y); + if (transform != null) { + tiles.add(transform); } } } diff --git a/packages/flame_tiled/test/assets/empty-infinite-map.tmx b/packages/flame_tiled/test/assets/empty-infinite-map.tmx new file mode 100644 index 00000000000..1861cbf1fea --- /dev/null +++ b/packages/flame_tiled/test/assets/empty-infinite-map.tmx @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/flame_tiled/test/assets/infinite-map.tmx b/packages/flame_tiled/test/assets/infinite-map.tmx new file mode 100644 index 00000000000..9a4279ebf1b --- /dev/null +++ b/packages/flame_tiled/test/assets/infinite-map.tmx @@ -0,0 +1,158 @@ + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +132,163,130,131,131,132,130,131,131,130,163,132,132,131,130,132, +163,131,131,131,131,131,131,131,130,163,131,163,131,130,131,163, +130,163,132,130,132,130,163,131,132,163,132,130,130,132,130,132, +130,132,163,163,130,130,132,130,131,131,131,163,132,130,130,131, +131,163,130,131,131,132,130,163,131,131,130,131,163,130,131,131, +163,132,132,130,163,163,163,130,132,163,132,132,131,131,132,163, +130,163,131,130,163,130,163,132,132,163,132,131,163,163,131,132 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +132,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +163,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +132,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,131, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,132, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,163, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,130, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,131, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +163,130,131,163,130,132,130,131,132,131,163,163,163,163,130,131, +130,132,132,163,130,132,131,130,131,163,132,130,163,132,130,131, +132,131,130,132,131,130,163,130,130,131,131,130,163,130,130,131, +132,163,132,132,163,132,131,131,163,130,131,163,163,163,131,132, +130,131,132,132,130,131,132,132,132,163,130,132,130,130,132,132, +130,132,132,131,130,131,132,131,131,132,131,131,163,131,130,130, +132,163,132,132,130,130,163,131,130,163,163,131,131,130,163,131, +130,131,163,132,163,163,131,130,131,130,131,132,131,163,130,132, +163,163,130,163,163,132,132,163,163,132,163,132,131,163,131,131, +163,163,131,132,130,131,130,163,131,131,163,131,163,130,132,132, +163,130,163,132,132,131,132,131,130,132,131,163,163,130,163,131, +132,163,163,163,131,131,131,130,131,130,132,163,130,132,130,132, +132,132,131,131,130,132,130,131,131,132,130,132,163,131,132,132, +130,163,132,163,130,130,130,163,163,163,132,130,130,163,130,132, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +131,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +131,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +163,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +132,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +131,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +131,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +130,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +132,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +163,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +163,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +163,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +227,2,3,4,2,3,4,2,3,4,2,3,4,2,3,228, +259,34,35,35,35,35,35,35,35,35,101,35,35,35,35,260, +258,0,0,0,0,0,0,0,0,0,133,0,0,0,0,257, +258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,257, +258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,257, +258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,257 + + +258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,257, +258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,257, +258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,257, +258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,257, +258,0,0,0,0,0,0,0,0,0,0,0,0,0,0,257, +258,0,0,0,0,0,0,225,3,3,3,3,226,0,0,257, +258,0,0,0,0,0,0,257,35,35,35,35,258,0,0,257, +258,0,418,419,420,421,0,257,0,0,0,0,258,0,0,257, +291,3,450,451,452,453,3,292,0,0,0,0,258,0,0,257, +323,35,67,483,484,67,35,324,0,0,0,0,258,0,0,257, +0,0,0,0,0,0,0,0,0,0,0,0,258,0,0,257, +0,0,0,0,0,0,0,0,0,0,0,0,262,3,4,261, +0,0,0,0,0,0,0,0,0,0,0,0,323,35,35,324, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + diff --git a/packages/flame_tiled/test/assets/infinite_oversized_tiles_isometric.tmx b/packages/flame_tiled/test/assets/infinite_oversized_tiles_isometric.tmx new file mode 100644 index 00000000000..57a8e726d11 --- /dev/null +++ b/packages/flame_tiled/test/assets/infinite_oversized_tiles_isometric.tmx @@ -0,0 +1,158 @@ + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,2, +0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,2, +0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,2 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2, +0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,2, +0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,2, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0, +3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + diff --git a/packages/flame_tiled/test/assets/infinite_oversized_tiles_orthogonal.tmx b/packages/flame_tiled/test/assets/infinite_oversized_tiles_orthogonal.tmx new file mode 100644 index 00000000000..d999412a6cd --- /dev/null +++ b/packages/flame_tiled/test/assets/infinite_oversized_tiles_orthogonal.tmx @@ -0,0 +1,89 @@ + + + + + + + + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,131,131,131,131,131,131,131,131, +0,0,0,0,0,0,0,0,132,163,130,131,132,163,130,131, +0,0,0,0,0,0,0,0,163,131,163,131,163,131,163,131, +0,0,0,0,0,0,0,0,130,163,132,131,130,163,132,131, +0,0,0,0,0,0,0,0,131,131,131,131,131,131,131,131, +0,0,0,0,0,0,0,0,132,163,130,131,132,163,130,131, +0,0,0,0,0,0,0,0,163,131,163,131,163,131,163,131, +0,0,0,0,0,0,0,0,130,163,132,131,130,163,132,131, +0,0,0,0,0,0,0,0,131,131,131,131,131,131,131,131, +0,0,0,0,0,0,0,0,132,163,130,131,132,163,130,131, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +131,131,131,131,131,131,131,131,0,0,0,0,0,0,0,0, +132,163,130,131,132,163,130,131,0,0,0,0,0,0,0,0, +163,131,163,131,163,131,163,131,0,0,0,0,0,0,0,0, +130,163,132,131,130,163,132,131,0,0,0,0,0,0,0,0, +131,131,131,131,131,131,131,131,0,0,0,0,0,0,0,0, +132,163,130,131,132,163,130,131,0,0,0,0,0,0,0,0, +163,131,163,131,163,131,163,131,0,0,0,0,0,0,0,0, +130,163,132,131,130,163,132,131,0,0,0,0,0,0,0,0, +131,131,131,131,131,131,131,131,0,0,0,0,0,0,0,0, +132,163,130,131,132,163,130,131,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + + + +0,0,0,0,0,0,0,0,227,2,3,4,2,3,4,2, +0,0,0,0,0,0,0,0,259,35,35,35,35,35,35,35, +0,0,0,0,0,0,0,0,258,0,0,0,0,0,0,1138, +0,0,0,0,0,0,0,0,258,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,258,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,258,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,258,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,258,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,258,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,291,3,3,3,3,3,3,3, +0,0,0,0,0,0,0,0,323,35,35,35,35,35,35,35, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + +3,4,2,3,4,2,3,228,0,0,0,0,0,0,0,0, +35,35,35,35,35,35,35,260,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,257,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,257,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,257,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,257,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,257,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,257,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,257,0,0,0,0,0,0,0,0, +3,3,3,3,3,3,3,292,0,0,0,0,0,0,0,0, +35,35,35,35,35,35,35,324,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + diff --git a/packages/flame_tiled/test/goldens/infinite_map.png b/packages/flame_tiled/test/goldens/infinite_map.png new file mode 100644 index 00000000000..13d0943a38e Binary files /dev/null and b/packages/flame_tiled/test/goldens/infinite_map.png differ diff --git a/packages/flame_tiled/test/goldens/infinite_oversized_tiles_isometric.png b/packages/flame_tiled/test/goldens/infinite_oversized_tiles_isometric.png new file mode 100644 index 00000000000..0b7c6505a75 Binary files /dev/null and b/packages/flame_tiled/test/goldens/infinite_oversized_tiles_isometric.png differ diff --git a/packages/flame_tiled/test/goldens/infinite_oversized_tiles_orthogonal.png b/packages/flame_tiled/test/goldens/infinite_oversized_tiles_orthogonal.png new file mode 100644 index 00000000000..23b19bd96f2 Binary files /dev/null and b/packages/flame_tiled/test/goldens/infinite_oversized_tiles_orthogonal.png differ diff --git a/packages/flame_tiled/test/test_image_utils.dart b/packages/flame_tiled/test/test_image_utils.dart index 3f89f655b26..98093472ef7 100644 --- a/packages/flame_tiled/test/test_image_utils.dart +++ b/packages/flame_tiled/test/test_image_utils.dart @@ -19,5 +19,26 @@ Future renderMapToPng( return imageToPng(image); } +/// Renders the part of [map] inside [region], which is given in map pixels. +/// +/// Unlike [renderMapToPng] this can capture content at negative coordinates, +/// which infinite maps have when tiles are placed left of or above the origin. +Future renderMapRegionToPng( + RenderableTiledMap map, + Rect region, +) async { + final canvasRecorder = PictureRecorder(); + final canvas = Canvas(canvasRecorder); + canvas.translate(-region.left, -region.top); + map.render(canvas); + final picture = canvasRecorder.endRecording(); + + final image = await picture.toImageSafe( + region.width.toInt(), + region.height.toInt(), + ); + return imageToPng(image); +} + Future imageToPng(Image image) async => (await image.toByteData(format: ImageByteFormat.png))!.buffer.asUint8List(); diff --git a/packages/flame_tiled/test/tiled_test.dart b/packages/flame_tiled/test/tiled_test.dart index 1cd47514ea2..ad97fe4479c 100644 --- a/packages/flame_tiled/test/tiled_test.dart +++ b/packages/flame_tiled/test/tiled_test.dart @@ -1230,4 +1230,148 @@ void main() { expect(renderableTiledMap.getLayerOpacity(3), equals(0.2)); }); }); + + group('infinite maps', () { + test('empty infinite map loads without tiles', () async { + final bundle = TestAssetBundle( + imageNames: const [], + stringNames: ['empty-infinite-map.tmx'], + ); + final component = await TiledComponent.load( + 'assets/tiles/empty-infinite-map.tmx', + Vector2.all(16), + bundle: bundle, + images: Images(bundle: bundle), + ); + + expect(component.tileMap.map.infinite, isTrue); + expect(component.size, Vector2(120 * 16, 68 * 16)); + expect( + component.tileMap.getTileData(layerId: 1, x: 0, y: 0), + isNull, + ); + }); + + test('loads chunks and reads Tiled world coordinates', () async { + final bundle = TestAssetBundle( + imageNames: ['0x72_DungeonTilesetII_v1.4.png'], + stringNames: ['infinite-map.tmx'], + ); + final map = await RenderableTiledMap.fromFile( + 'assets/tiles/infinite-map.tmx', + Vector2.all(16), + bundle: bundle, + images: Images(bundle: bundle), + ); + + expect(map.map.infinite, isTrue); + + final floorTile = map.getTileData(layerId: 2, x: -16, y: -7); + expect(floorTile, isNotNull); + expect(floorTile!.tile, 132); + + expect(map.getTileData(layerId: 2, x: 1000, y: 1000), isNull); + + map.setTileData( + layerId: 2, + x: -16, + y: -7, + gid: const Gid(1, Flips.defaults()), + ); + expect(map.getTileData(layerId: 2, x: -16, y: -7)!.tile, 1); + + final stack = map.tileStack(-16, -7, all: true); + expect(stack.length, greaterThan(0)); + // Tiles at negative indices are placed at negative world positions, + // like in the Tiled editor. + expect(stack.position, Vector2((-16 + 0.5) * 16, (-7 + 0.5) * 16)); + }); + + test('TiledComponent size uses map width and height', () async { + final bundle = TestAssetBundle( + imageNames: ['0x72_DungeonTilesetII_v1.4.png'], + stringNames: ['infinite-map.tmx'], + ); + final component = await TiledComponent.load( + 'assets/tiles/infinite-map.tmx', + Vector2.all(16), + bundle: bundle, + images: Images(bundle: bundle), + ); + + expect(component.size, Vector2(120 * 16, 68 * 16)); + }); + + test('renders all chunks', () async { + final bundle = TestAssetBundle( + imageNames: ['0x72_DungeonTilesetII_v1.4.png'], + stringNames: ['infinite-map.tmx'], + ); + final map = await RenderableTiledMap.fromFile( + 'assets/tiles/infinite-map.tmx', + Vector2.all(16), + bundle: bundle, + images: Images(bundle: bundle), + ); + + // The chunks cover tiles (-32, -16) to (16, 16). + final pngData = await renderMapRegionToPng( + map, + const Rect.fromLTWH(-32 * 16, -16 * 16, 48 * 16, 32 * 16), + ); + await expectLater( + pngData, + matchesGoldenFile('goldens/infinite_map.png'), + ); + }); + + // The room straddles the border between two chunks. Its door is a 32x32 + // tile standing on the floor in front of the top wall, so it overlaps wall + // tiles of both chunks. Painting chunk by chunk instead of row by row + // would draw the wall of the right chunk over the door. + test('paints oversized tiles across chunks in row order', () async { + final bundle = TestAssetBundle( + imageNames: ['0x72_DungeonTilesetII_v1.4.png'], + stringNames: ['infinite_oversized_tiles_orthogonal.tmx'], + ); + final map = await RenderableTiledMap.fromFile( + 'assets/tiles/infinite_oversized_tiles_orthogonal.tmx', + Vector2.all(16), + bundle: bundle, + images: Images(bundle: bundle), + ); + + // The room covers tiles (-8, 0) to (7, 10). + final pngData = await renderMapRegionToPng( + map, + const Rect.fromLTWH(-8 * 16, 0, 16 * 16, 11 * 16), + ); + await expectLater( + pngData, + matchesGoldenFile('goldens/infinite_oversized_tiles_orthogonal.png'), + ); + }); + + test('renders isometric chunks around the origin', () async { + final bundle = TestAssetBundle( + imageNames: ['isometric_spritesheet.png'], + stringNames: ['infinite_oversized_tiles_isometric.tmx'], + ); + final map = await RenderableTiledMap.fromFile( + 'assets/tiles/infinite_oversized_tiles_isometric.tmx', + Vector2(64, 32), + bundle: bundle, + images: Images(bundle: bundle), + ); + + final pngData = await renderMapRegionToPng( + map, + const Rect.fromLTWH(0, -144, 384, 240), + ); + await expectLater( + pngData, + matchesGoldenFile('goldens/infinite_oversized_tiles_isometric.png'), + ); + }); + }); }