Editor/Brush/TileBrush3x3Complex.cs

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.

File Access
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 );
	}
}