Editor brush for a tilemapper, implements a 3x3 "complex" autotile brush that computes an 8-neighbor bitmask and maps it to one of 47 sprite indices using a static lookup table. It determines which neighboring cells to update and returns visibility/collider defaults for tiles.
using Sandbox;
using System.Collections.Generic;
using Saandy.Tilemapper;
namespace Saandy.Editor.Tilemapper;
/// <summary>
/// 47-tile 3x3 blob autotile brush.
/// This is the "complex" brush: full 8-neighbor mask input, but reduced to the 47 useful blob shapes.
/// It distinguishes isolated 1x1 tiles, narrow caps/lines, edges, outer corners, and inner corners.
/// </summary>
public class TileBrush3x3Complex : TileBrush
{
public override string Name { get; protected set; } = "3x3 Complex Brush";
// Lookup uses this bit order:
// 1 2 4
// 8 X 16
// 32 64 128
//
// 255 = surrounded/full -> tile 0
// 0 = isolated -> tile 46
private static readonly ushort[] TileLookup =
{
46, 46, 44, 44, 46, 46, 44, 44, 45, 45, 39, 38, 45, 45, 39, 38,
43, 43, 41, 41, 43, 43, 40, 40, 33, 33, 31, 30, 33, 33, 29, 28,
46, 46, 44, 44, 46, 46, 44, 44, 45, 45, 39, 38, 45, 45, 39, 38,
43, 43, 41, 41, 43, 43, 40, 40, 33, 33, 31, 30, 33, 33, 29, 28,
42, 42, 32, 32, 42, 42, 32, 32, 37, 37, 27, 25, 37, 37, 27, 25,
35, 35, 19, 19, 35, 35, 18, 18, 23, 23, 15, 14, 23, 23, 13, 12,
42, 42, 32, 32, 42, 42, 32, 32, 36, 36, 26, 24, 36, 36, 26, 24,
35, 35, 19, 19, 35, 35, 18, 18, 21, 21, 7, 6, 21, 21, 5, 4,
46, 46, 44, 44, 46, 46, 44, 44, 45, 45, 39, 38, 45, 45, 39, 38,
43, 43, 41, 41, 43, 43, 40, 40, 33, 33, 31, 30, 33, 33, 29, 28,
46, 46, 44, 44, 46, 46, 44, 44, 45, 45, 39, 38, 45, 45, 39, 38,
43, 43, 41, 41, 43, 43, 40, 40, 33, 33, 31, 30, 33, 33, 29, 28,
42, 42, 32, 32, 42, 42, 32, 32, 37, 37, 27, 25, 37, 37, 27, 25,
34, 34, 17, 17, 34, 34, 16, 16, 22, 22, 11, 10, 22, 22, 9, 8,
42, 42, 32, 32, 42, 42, 32, 32, 36, 36, 26, 24, 36, 36, 26, 24,
34, 34, 17, 17, 34, 34, 16, 16, 20, 20, 3, 2, 20, 20, 1, 0
};
public override ushort GetSpriteIndexToSet( TileMap tilemap, TilesetResource tileset, int x, int y )
{
int bitmask = GetBitmask( tilemap, x, y );
return TileLookup[bitmask];
}
public override (bool IsVisible, bool UseCollider) GetDefaultTileFlags( ushort spriteIndex )
{
return (true, true);
}
private int GetBitmask( TileMap tilemap, int x, int y )
{
ushort centerTilesetId = tilemap.GetTilesetIdAt( x, y );
if ( centerTilesetId == 0 )
return 0;
int mask = 0;
// The lookup table/template is authored with visual Up as +Y in tilemap cell space.
// The old version used y - 1 for Up, which vertically mirrored every top/bottom
// edge and corner piece in the 3x3 complex brush.
// Top row / visual up.
if ( IsSameTileset( tilemap, centerTilesetId, x - 1, y + 1 ) )
mask |= 1; // UpLeft
if ( IsSameTileset( tilemap, centerTilesetId, x, y + 1 ) )
mask |= 2; // Up
if ( IsSameTileset( tilemap, centerTilesetId, x + 1, y + 1 ) )
mask |= 4; // UpRight
// Middle row.
if ( IsSameTileset( tilemap, centerTilesetId, x - 1, y ) )
mask |= 8; // Left
if ( IsSameTileset( tilemap, centerTilesetId, x + 1, y ) )
mask |= 16; // Right
// Bottom row / visual down.
if ( IsSameTileset( tilemap, centerTilesetId, x - 1, y - 1 ) )
mask |= 32; // DownLeft
if ( IsSameTileset( tilemap, centerTilesetId, x, y - 1 ) )
mask |= 64; // Down
if ( IsSameTileset( tilemap, centerTilesetId, x + 1, y - 1 ) )
mask |= 128; // DownRight
return mask;
}
private static bool IsSameTileset( TileMap tilemap, ushort centerTilesetId, int x, int y )
{
return tilemap.IsSameTilesetId( centerTilesetId, tilemap.GetTilesetIdAt( x, y ) );
}
protected override IEnumerable<Vector2Int> GetAffectedTiles( Vector2Int cell )
{
// Order does not affect the update result, but keep this listed visually top-to-bottom
// to match the bitmask comments above.
yield return new Vector2Int( cell.x - 1, cell.y + 1 );
yield return new Vector2Int( cell.x, cell.y + 1 );
yield return new Vector2Int( cell.x + 1, cell.y + 1 );
yield return new Vector2Int( cell.x - 1, cell.y );
yield return new Vector2Int( cell.x + 1, cell.y );
yield return new Vector2Int( cell.x - 1, cell.y - 1 );
yield return new Vector2Int( cell.x, cell.y - 1 );
yield return new Vector2Int( cell.x + 1, cell.y - 1 );
}
}