Editor/Tileset/TilesetTools/Tools/PaintTileTool.cs
using Editor;
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;
namespace SpriteTools.TilesetTool.Tools;
/// <summary>
/// Used to paint tiles on the selected layer.
/// </summary>
[Title( "Paint" )]
[Icon( "brush" )]
[Alias( "tileset-tools.paint-tool" )]
[Group( "1" )]
[Order( 0 )]
public class PaintTileTool : BaseTileTool
{
public PaintTileTool ( TilesetTool parent ) : base( parent ) { }
/// <summary>
/// The size of the Brush when Painting.
/// </summary>
[Group( "Paint Tool" ), Property, Range( 1, 12 ), Step( 1 )]
public int BrushSize
{
get => _brushSize;
set
{
_brushSize = value;
lastTilePos = -999999;
}
}
private int _brushSize = 1;
/// <summary>
/// Whether the Brush is round or square.
/// </summary>
[Group( "Paint Tool" ), Property]
public bool IsRound
{
get => _isRound;
set
{
_isRound = value;
lastTilePos = -999999;
}
}
private bool _isRound = false;
/// <summary>
/// If enabled, Autotiles of different types will attempt to connect with each other.
/// </summary>
[Group( "Paint Tool" ), Property, ShowIf( nameof( this.CanSeeAutotileSettings ), true )]
public bool MergeDifferentAutotiles
{
get => ShouldMergeAutotiles;
set
{
ShouldMergeAutotiles = value;
}
}
private bool CanSeeAutotileSettings => AutotileWidget.Instance?.Brush is not null;
Vector2Int lastTilePos;
bool isPainting = false;
public override void OnUpdate ()
{
if ( !CanUseTool() ) return;
if ( Parent.SelectedComponent.Transform is null ) return;
var pos = GetGizmoPos();
var tile = Parent.SelectedTile;
var tilePos = (Vector2Int)( ( pos - Parent.SelectedComponent.WorldPosition ) / Parent.SelectedLayer.TilesetResource.GetTileSize() );
Parent._sceneObject.Transform = new Transform( pos, Rotation.Identity, 1 );
Parent._sceneObject.RenderingEnabled = true;
if ( tilePos != lastTilePos )
{
UpdateTilePositions();
}
if ( Gizmo.IsLeftMouseDown )
{
if ( _componentUndoScope is null )
{
_componentUndoScope = SceneEditorSession.Active.UndoScope( "Paint Tile" ).WithComponentChanges( Parent.SelectedComponent ).Push();
}
var brush = AutotileBrush;
if ( brush is not null )
{
Place( tilePos );
}
else if ( tile.Size.x > 1 || tile.Size.y > 1 )
{
for ( int x = 0; x < tile.Size.x; x++ )
{
var ux = x;
var xx = x;
if ( Parent.Settings.HorizontalFlip ) ux = tile.Size.x - x - 1;
for ( int y = 0; y < tile.Size.y; y++ )
{
var uy = y;
var yy = -y;
var offsetPos = new Vector2Int( xx, yy );
if ( Parent.Settings.Angle == 90 )
offsetPos = new Vector2Int( -offsetPos.y, offsetPos.x );
else if ( Parent.Settings.Angle == 180 )
offsetPos = new Vector2Int( -offsetPos.x, -offsetPos.y );
else if ( Parent.Settings.Angle == 270 )
offsetPos = new Vector2Int( offsetPos.y, -offsetPos.x );
Parent.PlaceTile( tilePos + offsetPos, tile.Id, new Vector2Int( ux, uy ), false );
}
}
Parent.SelectedComponent.IsDirty = true;
}
else
{
Place( tilePos );
}
isPainting = true;
}
else if ( isPainting )
{
_componentUndoScope?.Dispose();
_componentUndoScope = null;
isPainting = false;
}
// if (Parent?.SelectedLayer?.AutoTilePositions is not null)
// {
// var tileSize = Parent.SelectedLayer.TilesetResource.GetTileSize();
// using (Gizmo.Scope("test", Transform.Zero))
// {
// Gizmo.Draw.Color = Color.Red.WithAlpha(0.1f);
// foreach (var group in Parent.SelectedLayer.AutoTilePositions)
// {
// var brush = group.Key;
// foreach (var position in group.Value)
// {
// Gizmo.Draw.WorldText(Parent.SelectedLayer.GetAutotileBitmask(brush, position).ToString(),
// new Transform(
// Parent.SelectedComponent.WorldPosition + (Vector3)((Vector2)position * tileSize) + (Vector3)(tileSize * 0.5f) + Vector3.Up * 200,
// Rotation.Identity,
// 0.3f
// ),
// "Poppins", 24
// );
// }
// }
// }
// }
}
void UpdateTilePositions ()
{
var pos = GetGizmoPos();
var brush = AutotileBrush;
var tile = Parent.SelectedTile;
if ( tile is null ) return;
var tilePos = (Vector2Int)( ( pos - Parent.SelectedComponent.WorldPosition ) / Parent.SelectedLayer.TilesetResource.GetTileSize() );
List<(Vector2Int, Vector2Int)> positions = new();
if ( brush is null && ( tile.Size.x > 1 || tile.Size.y > 1 ) )
{
for ( int i = 0; i < tile.Size.x; i++ )
{
for ( int j = 0; j < tile.Size.y; j++ )
{
positions.Add( (new Vector2Int( i, -j ), tile.Position + new Vector2Int( i, j )) );
}
}
}
else if ( IsRound )
{
var size = ( BrushSize - 0.9f ) * 2;
var center = new Vector2Int( (int)( size / 2f ), (int)( size / 2f ) );
for ( int i = 0; i < size * 2; i++ )
{
for ( int j = 0; j < size * 2; j++ )
{
var offset = new Vector2Int( i, j ) - center;
if ( offset.LengthSquared <= ( size / 2 ) * ( size / 2 ) )
{
positions.Add( (offset, tile.Position) );
}
}
}
}
else
{
Vector2Int startPos = new Vector2Int( -BrushSize / 2, -BrushSize / 2 );
for ( int i = 0; i < BrushSize; i++ )
{
for ( int j = 0; j < BrushSize; j++ )
{
positions.Add( (new Vector2Int( i, j ) + startPos, tile.Position) );
}
}
}
// Set autobrush tiles if necessary
if ( brush is not null )
{
if ( brush.AutotileType == AutotileType.Bitmask2x2Edge )
{
List<Vector2Int> tilesToAdd = new();
foreach ( var ppos in positions )
{
bool touchingX = false;
bool touchingY = false;
var up = ppos.Item1.WithY( ppos.Item1.y + 1 );
var down = ppos.Item1.WithY( ppos.Item1.y - 1 );
var left = ppos.Item1.WithX( ppos.Item1.x - 1 );
var right = ppos.Item1.WithX( ppos.Item1.x + 1 );
foreach ( var ppos2 in positions )
{
if ( !touchingX && ( ppos2.Item1 == left || ppos2.Item1 == right ) )
{
touchingX = true;
}
if ( !touchingY && ( ppos2.Item1 == up || ppos2.Item1 == down ) )
{
touchingY = true;
}
if ( touchingX && touchingY ) break;
}
if ( touchingX && touchingY ) continue;
var upLeft = up.WithX( left.x );
var upRight = up.WithX( right.x );
var downLeft = down.WithX( left.x );
var downRight = down.WithX( right.x );
if ( !tilesToAdd.Contains( up ) ) tilesToAdd.Add( up );
if ( !tilesToAdd.Contains( down ) ) tilesToAdd.Add( down );
if ( !tilesToAdd.Contains( left ) ) tilesToAdd.Add( left );
if ( !tilesToAdd.Contains( right ) ) tilesToAdd.Add( right );
if ( !tilesToAdd.Contains( upLeft ) ) tilesToAdd.Add( upLeft );
if ( !tilesToAdd.Contains( upRight ) ) tilesToAdd.Add( upRight );
if ( !tilesToAdd.Contains( downLeft ) ) tilesToAdd.Add( downLeft );
if ( !tilesToAdd.Contains( downRight ) ) tilesToAdd.Add( downRight );
}
foreach ( var toAddPos in tilesToAdd )
{
if ( !positions.Contains( (toAddPos, tile.Position) ) )
positions.Add( (toAddPos, tile.Position) );
}
}
}
UpdateTilePositions( positions.Select( x => (Vector2)x.Item1 ).ToList() );
lastTilePos = tilePos;
}
void Place ( Vector2Int tilePos )
{
var brush = AutotileBrush;
var tile = Parent.SelectedTile;
foreach ( var position in Parent._sceneObject.MultiTilePositions )
{
if ( brush is null )
{
Parent.PlaceTile( tilePos + position.Item1, tile.Id, 0 );
}
else
{
Parent.PlaceAutotile( ( position.Item3 == Guid.Empty ) ? brush.Id : position.Item3, tilePos + position.Item1, false );
}
}
if ( brush is not null )
{
foreach ( var position in Parent._sceneObject.MultiTilePositions )
{
var brushId = ( position.Item3 == Guid.Empty ) ? brush.Id : position.Item3;
Parent.SelectedLayer.UpdateAutotile( brushId, tilePos + position.Item1, false, shouldMerge: MergeDifferentAutotiles );
}
}
return;
}
[Shortcut( "tileset-tools.paint-tool", "b", typeof( SceneViewportWidget ) )]
public static void ActivateSubTool ()
{
if ( EditorToolManager.CurrentModeName != nameof( TilesetTool ) ) return;
EditorToolManager.SetSubTool( nameof( PaintTileTool ) );
}
}