@ -3,10 +3,12 @@ using System.Runtime.InteropServices;
public partial class Terrain
public partial class Terrain
{
{
// These mirror the modes / shapes in 'terrain_editing_controls.gd'.
enum ToolMode { Height , Flatten , Paint }
enum ToolMode { Height , Flatten , Paint }
enum ToolShape { Corner , Circle , Square }
enum ToolShape { Corner , Circle , Square }
// Set by the terrain editing plugin.
// Set by the terrain editing plugin.
// Enables access to the in-editor undo/redo system.
public EditorUndoRedoManager EditorUndoRedo { get ; set ; }
public EditorUndoRedoManager EditorUndoRedo { get ; set ; }
// Dummy value to satisfy the overly careful compiler.
// Dummy value to satisfy the overly careful compiler.
@ -34,7 +36,7 @@ public partial class Terrain
var toolMode = ( ToolMode ) ( int ) controls . Get ( "tool_mode" ) ;
var toolMode = ( ToolMode ) ( int ) controls . Get ( "tool_mode" ) ;
var toolShape = ( ToolShape ) ( int ) controls . Get ( "tool_shape" ) ;
var toolShape = ( ToolShape ) ( int ) controls . Get ( "tool_shape" ) ;
var texture = ( int ) controls . Get ( "texture" ) ;
var texture = ( byte ) ( int ) controls . Get ( "texture" ) ;
var drawSize = ( int ) controls . Get ( "draw_size" ) ;
var drawSize = ( int ) controls . Get ( "draw_size" ) ;
var isRaise = ( bool ) controls . Get ( "is_raise" ) ;
var isRaise = ( bool ) controls . Get ( "is_raise" ) ;
@ -49,37 +51,37 @@ public partial class Terrain
// Offset hover tile position by corner.
// Offset hover tile position by corner.
// FIXME: This causes FLATTEN to calculate the wrong height in some cases.
// FIXME: This causes FLATTEN to calculate the wrong height in some cases.
if ( isEven ) hover . Position = hover . Corner switch {
if ( isEven ) hover . Position + = hover . Corner switch {
Corner . TopLeft = > hover . Position . Offset ( 0 , 0 ) ,
Corner . TopLeft = > ( 0 , 0 ) ,
Corner . TopRight = > hover . Position . Offset ( 1 , 0 ) ,
Corner . TopRight = > ( 1 , 0 ) ,
Corner . BottomRight = > hover . Position . Offset ( 1 , 1 ) ,
Corner . BottomRight = > ( 1 , 1 ) ,
Corner . BottomLeft = > hover . Position . Offset ( 0 , 1 ) ,
Corner . BottomLeft = > ( 0 , 1 ) ,
_ = > throw new InvalidOperationException ( ) ,
_ = > throw new InvalidOperationException ( ) ,
} ;
} ;
IEnumerable < TilePos > GetTilesInSquare ( ) {
IEnumerable < TilePos > GetTilesInSquare ( ) {
var min = hover . Position . Offset ( - radius , - radius ) ;
var min = hover . Position + ( - radius , - radius ) ;
var max = hover . Position . Offset ( + radius , + radius ) ;
var max = hover . Position + ( + radius , + radius ) ;
if ( isEven ) max = max . Offset ( - 1 , - 1 ) ;
if ( isEven ) max + = ( - 1 , - 1 ) ;
for ( var x = min . X ; x < = max . X ; x + + )
for ( var x = min . X ; x < = max . X ; x + + )
for ( var y = min . Y ; y < = max . Y ; y + + )
for ( var z = min . Z ; z < = max . Z ; z + + )
yield return new ( x , y ) ;
yield return new ( x , z ) ;
}
}
IEnumerable < TilePos > GetTilesInRadius ( ) {
IEnumerable < TilePos > GetTilesInRadius ( ) {
var center = isEven ? hover . Position . ToVector2I ( )
var center = isEven ? ( Vector2I ) hover . Position
: hover . Position . To Center( ) ;
: hover . Position . Center ;
var distanceSqr = Pow ( radius + 0.25f * ( isEven ? - 1 : 1 ) , 2 ) ;
var distanceSqr = Pow ( radius + ( isEven ? - 1 : 1 ) * 0.25f , 2 ) ;
return GetTilesInSquare ( ) . Where ( tile = >
return GetTilesInSquare ( ) . Where ( tile = >
center . DistanceSquaredTo ( tile . To Center( ) ) < distanceSqr ) ;
center . DistanceSquaredTo ( tile . Center ) < distanceSqr ) ;
}
}
// TODO: Allow click-dragging which doesn't affect already changed tiles / corners.
// TODO: Allow click-dragging which doesn't affect already changed tiles / corners.
// TODO: Use ArrayMesh instead of ImmediateMesh.
// TODO: Use ArrayMesh instead of ImmediateMesh.
// TODO: Dynamically expand terrain instead of having it be a set size.
// Holds onto all the tiles and which of their corners corners will be affected by this edit operation.
// Holds onto all the tiles and which of their corners corners will be affected by this edit operation.
var tilesToChange = new Dictionary < TilePos , Corners < bool > > ( ) ;
var tilesToChange = new Dictionary < TilePos , Corners < bool > > ( ) ;
// Don't look at this black magic. The Dictionary type should have this by default I swear!
// Don't look at this black magic. The Dictionary type should have this by default I swear!
// Basically, this returns a reference to an entry in the dictionary that can be modified directly.
// Basically, this returns a reference to an entry in the dictionary that can be modified directly.
ref Corners < bool > Tile ( TilePos position ) = > ref CollectionsMarshal . GetValueRefOrAddDefault ( tilesToChange , position , out _d ummy ) ;
ref Corners < bool > Tile ( TilePos position ) = > ref CollectionsMarshal . GetValueRefOrAddDefault ( tilesToChange , position , out _d ummy ) ;
@ -109,9 +111,9 @@ public partial class Terrain
// If the 'connected_toggle' button is active, move "connected" corners.
// If the 'connected_toggle' button is active, move "connected" corners.
// This is a simplified version of the code below that only affects the 3 neighboring corners.
// This is a simplified version of the code below that only affects the 3 neighboring corners.
if ( isConnected ) {
if ( isConnected ) {
var height = GetTile ( hover . Position ) . Height [ hover . Corner ] ;
var height = Data . GetTileOrDefault ( hover . Position ) . Height [ hover . Corner ] ;
foreach ( var neighbor in GetNeighbors ( hover . Position , hover . Corner ) ) {
foreach ( var neighbor in GetNeighbors ( hover . Position , hover . Corner ) ) {
var neighborHeight = GetTile ( neighbor . Position ) . Height [ neighbor . Corner ] ;
var neighborHeight = Data . GetTileOrDefault ( neighbor . Position ) . Height [ neighbor . Corner ] ;
if ( neighborHeight ! = height ) continue ;
if ( neighborHeight ! = height ) continue ;
Tile ( neighbor . Position ) [ neighbor . Corner ] = true ;
Tile ( neighbor . Position ) [ neighbor . Corner ] = true ;
}
}
@ -132,12 +134,12 @@ public partial class Terrain
// If the 'connected_toggle' button is active, move "connected" corners.
// If the 'connected_toggle' button is active, move "connected" corners.
// Connected corners are the ones that are at the same height as ones already being moved.
// Connected corners are the ones that are at the same height as ones already being moved.
if ( isConnected ) foreach ( var pos in tiles ) {
if ( isConnected ) foreach ( var pos in tiles ) {
var tile = GetTile ( pos ) ;
var tile = Data . GetTileOrDefault ( pos ) ;
foreach ( var corner in Enum . GetValues < Corner > ( ) ) {
foreach ( var corner in Enum . GetValues < Corner > ( ) ) {
var height = tile . Height [ corner ] ;
var height = tile . Height [ corner ] ;
foreach ( var neighbor in GetNeighbors ( pos , corner ) ) {
foreach ( var neighbor in GetNeighbors ( pos , corner ) ) {
if ( tiles . Contains ( neighbor . Position ) ) continue ;
if ( tiles . Contains ( neighbor . Position ) ) continue ;
var neighborHeight = GetTile ( neighbor . Position ) . Height [ neighbor . Corner ] ;
var neighborHeight = Data . GetTileOrDefault ( neighbor . Position ) . Height [ neighbor . Corner ] ;
if ( neighborHeight ! = height ) continue ;
if ( neighborHeight ! = height ) continue ;
Tile ( neighbor . Position ) [ neighbor . Corner ] = true ;
Tile ( neighbor . Position ) [ neighbor . Corner ] = true ;
}
}
@ -157,11 +159,11 @@ public partial class Terrain
if ( toolMode = = ToolMode . Paint ) {
if ( toolMode = = ToolMode . Paint ) {
// TODO: Support blending somehow.
// TODO: Support blending somehow.
var tilesPrevious = new List < ( TilePos , int ) > ( ) ;
var tilesPrevious = new List < ( TilePos , byte ) > ( ) ;
var tilesChanged = new List < ( TilePos , int ) > ( ) ;
var tilesChanged = new List < ( TilePos , byte ) > ( ) ;
foreach ( var ( pos , corners ) in tilesToChange ) {
foreach ( var ( pos , corners ) in tilesToChange ) {
var tile = GetTile ( pos ) ;
var tile = Data . GetTileOrDefault ( pos ) ;
tilesPrevious . Add ( ( pos , tile . TexturePrimary ) ) ;
tilesPrevious . Add ( ( pos , tile . TexturePrimary ) ) ;
tilesChanged . Add ( ( pos , texture ) ) ;
tilesChanged . Add ( ( pos , texture ) ) ;
}
}
@ -171,22 +173,28 @@ public partial class Terrain
doArgs = [ PackTextureData ( tilesChanged ) ] ;
doArgs = [ PackTextureData ( tilesChanged ) ] ;
undoArgs = [ PackTextureData ( tilesPrevious ) ] ;
undoArgs = [ PackTextureData ( tilesPrevious ) ] ;
} else {
} else {
var tilesPrevious = new List < ( TilePos , Corners < floa t> ) > ( ) ;
var tilesPrevious = new List < ( TilePos , Corners < shor t> ) > ( ) ;
var tilesChanged = new List < ( TilePos , Corners < floa t> ) > ( ) ;
var tilesChanged = new List < ( TilePos , Corners < shor t> ) > ( ) ;
const float AdjustHeight = 0.5f ;
var amount = isFlatten ? Data . GetTileOrDefault ( hover . Position ) . Height [ hover . Corner ]
var amount = isFlatten ? GetTile ( hover . Position ) . Height [ hover . Corner ]
: isRaise ? ( short ) + 1 : ( short ) - 1 ;
: isRaise ? AdjustHeight : - AdjustHeight ;
foreach ( var ( pos , corners ) in tilesToChange ) {
foreach ( var ( pos , corners ) in tilesToChange ) {
var tile = GetTile ( pos ) ;
var tile = Data . GetTileOrDefault ( pos ) ;
tilesPrevious . Add ( ( pos , tile . Height ) ) ;
tilesPrevious . Add ( ( pos , tile . Height ) ) ;
var newHeight = tile . Height ;
var newHeight = tile . Height ;
if ( corners . TopLeft ) newHeight . TopLeft = isFlatten ? amount : newHeight . TopLeft + amount ;
if ( isFlatten ) {
if ( corners . TopRight ) newHeight . TopRight = isFlatten ? amount : newHeight . TopRight + amount ;
if ( corners . TopLeft ) newHeight . TopLeft = amount ;
if ( corners . BottomRight ) newHeight . BottomRight = isFlatten ? amount : newHeight . BottomRight + amount ;
if ( corners . TopRight ) newHeight . TopRight = amount ;
if ( corners . BottomLeft ) newHeight . BottomLeft = isFlatten ? amount : newHeight . BottomLeft + amount ;
if ( corners . BottomRight ) newHeight . BottomRight = amount ;
if ( corners . BottomLeft ) newHeight . BottomLeft = amount ;
} else {
if ( corners . TopLeft ) newHeight . TopLeft + = amount ;
if ( corners . TopRight ) newHeight . TopRight + = amount ;
if ( corners . BottomRight ) newHeight . BottomRight + = amount ;
if ( corners . BottomLeft ) newHeight . BottomLeft + = amount ;
}
tilesChanged . Add ( ( pos , newHeight ) ) ;
tilesChanged . Add ( ( pos , newHeight ) ) ;
}
}
@ -222,20 +230,14 @@ public partial class Terrain
public void DoModifyTerrainHeight ( byte [ ] data )
public void DoModifyTerrainHeight ( byte [ ] data )
{
{
foreach ( var ( pos , corners ) in UnpackHeightData ( data ) ) {
foreach ( var ( pos , corners ) in UnpackHeightData ( data ) )
var tile = GetTile ( pos ) ;
Data [ pos ] . Height = corners ;
tile . Height = corners ;
SetTile ( pos , tile ) ;
}
}
}
public void DoModifyTerrainTexture ( byte [ ] data )
public void DoModifyTerrainTexture ( byte [ ] data )
{
{
foreach ( var ( pos , texture ) in UnpackTextureData ( data ) ) {
foreach ( var ( pos , texture ) in UnpackTextureData ( data ) )
var tile = GetTile ( pos ) ;
Data [ pos ] . TexturePrimary = texture ;
tile . TexturePrimary = texture ;
SetTile ( pos , tile ) ;
}
}
}
@ -254,7 +256,7 @@ public partial class Terrain
}
}
foreach ( var ( tile , visible ) in tiles ) {
foreach ( var ( tile , visible ) in tiles ) {
var positions = Get TileC orner Positions( tile ) ;
var positions = ToPositions ( tile ) ;
foreach ( var side in Enum . GetValues < Side > ( ) ) {
foreach ( var side in Enum . GetValues < Side > ( ) ) {
var ( corner1 , corner2 ) = side . GetCorners ( ) ;
var ( corner1 , corner2 ) = side . GetCorners ( ) ;
if ( ! visible [ corner1 ] & & ! visible [ corner2 ] ) continue ;
if ( ! visible [ corner1 ] & & ! visible [ corner2 ] ) continue ;
@ -274,8 +276,7 @@ public partial class Terrain
( TilePos Position , Corner Corner ) ToTilePos ( Vector3 position )
( TilePos Position , Corner Corner ) ToTilePos ( Vector3 position )
{
{
var local = ToLocal ( position ) ;
var local = ToLocal ( position ) ;
var coord = new Vector2 ( local . X , local . Z ) / TileSize + ( Size + Vector2 . One ) / 2 ;
var coord = new Vector2 ( local . X , local . Z ) / TileSize ;
var pos = TilePos . From ( coord ) ;
var corner = coord . PosMod ( 1 ) . RoundToVector2I ( ) switch {
var corner = coord . PosMod ( 1 ) . RoundToVector2I ( ) switch {
( 0 , 0 ) = > Corner . TopLeft ,
( 0 , 0 ) = > Corner . TopLeft ,
( 1 , 0 ) = > Corner . TopRight ,
( 1 , 0 ) = > Corner . TopRight ,
@ -283,26 +284,26 @@ public partial class Terrain
( 0 , 1 ) = > Corner . BottomLeft ,
( 0 , 1 ) = > Corner . BottomLeft ,
_ = > throw new InvalidOperationException ( ) ,
_ = > throw new InvalidOperationException ( ) ,
} ;
} ;
return ( pos , corner ) ;
return ( ( TilePos ) coord , corner ) ;
}
}
static readonly Dictionary < Corner , ( int X , int Y , Corner Opposite ) [ ] > _ offsetLookup = new ( ) {
static readonly Dictionary < Corner , ( int X , int Z , Corner Opposite ) [ ] > _ offsetLookup = new ( ) {
[Corner.TopLeft ] = [ ( - 1 , - 1 , Corner . BottomRight ) , ( - 1 , 0 , Corner . TopRight ) , ( 0 , - 1 , Corner . BottomLeft ) ] ,
[Corner.TopLeft ] = [ ( - 1 , - 1 , Corner . BottomRight ) , ( - 1 , 0 , Corner . TopRight ) , ( 0 , - 1 , Corner . BottomLeft ) ] ,
[Corner.TopRight ] = [ ( + 1 , - 1 , Corner . BottomLeft ) , ( + 1 , 0 , Corner . TopLeft ) , ( 0 , - 1 , Corner . BottomRight ) ] ,
[Corner.TopRight ] = [ ( + 1 , - 1 , Corner . BottomLeft ) , ( + 1 , 0 , Corner . TopLeft ) , ( 0 , - 1 , Corner . BottomRight ) ] ,
[Corner.BottomRight] = [ ( + 1 , + 1 , Corner . TopLeft ) , ( + 1 , 0 , Corner . BottomLeft ) , ( 0 , + 1 , Corner . TopRight ) ] ,
[Corner.BottomRight] = [ ( + 1 , + 1 , Corner . TopLeft ) , ( + 1 , 0 , Corner . BottomLeft ) , ( 0 , + 1 , Corner . TopRight ) ] ,
[Corner.BottomLeft ] = [ ( - 1 , + 1 , Corner . TopRight ) , ( - 1 , 0 , Corner . BottomRight ) , ( 0 , + 1 , Corner . TopLeft ) ] ,
[Corner.BottomLeft ] = [ ( - 1 , + 1 , Corner . TopRight ) , ( - 1 , 0 , Corner . BottomRight ) , ( 0 , + 1 , Corner . TopLeft ) ] ,
} ;
} ;
static IEnumerable < ( TilePos Position , Corner Corner ) > GetNeighbors ( TilePos pos , Corner corner )
static IEnumerable < ( TilePos Position , Corner Corner ) > GetNeighbors ( TilePos pos , Corner corner )
= > _ offsetLookup [ corner ] . Select ( e = > ( new TilePos ( pos . X + e . X , pos . Y + e . Y ) , e . Opposite ) ) ;
= > _ offsetLookup [ corner ] . Select ( e = > ( new TilePos ( pos . X + e . X , pos . Z + e . Z ) , e . Opposite ) ) ;
static byte [ ] PackHeightData ( IEnumerable < ( TilePos Position , Corners < floa t> Corners ) > data )
static byte [ ] PackHeightData ( IEnumerable < ( TilePos Position , Corners < shor t> Corners ) > data )
{
{
using var stream = new MemoryStream ( ) ;
using var stream = new MemoryStream ( ) ;
using var writer = new BinaryWriter ( stream ) ;
using var writer = new BinaryWriter ( stream ) ;
foreach ( var ( pos , corners ) in data ) {
foreach ( var ( pos , corners ) in data ) {
writer . Write ( pos . X ) ;
writer . Write ( pos . X ) ;
writer . Write ( pos . Y ) ;
writer . Write ( pos . Z ) ;
writer . Write ( corners . TopLeft ) ;
writer . Write ( corners . TopLeft ) ;
writer . Write ( corners . TopRight ) ;
writer . Write ( corners . TopRight ) ;
writer . Write ( corners . BottomRight ) ;
writer . Write ( corners . BottomRight ) ;
@ -311,33 +312,33 @@ public partial class Terrain
return stream . ToArray ( ) ;
return stream . ToArray ( ) ;
}
}
static IEnumerable < ( TilePos Position , Corners < floa t> Corners ) > UnpackHeightData ( byte [ ] data )
static IEnumerable < ( TilePos Position , Corners < shor t> Corners ) > UnpackHeightData ( byte [ ] data )
{
{
using var stream = new MemoryStream ( data ) ;
using var stream = new MemoryStream ( data ) ;
using var reader = new BinaryReader ( stream ) ;
using var reader = new BinaryReader ( stream ) ;
while ( stream . Position < stream . Length ) {
while ( stream . Position < stream . Length ) {
var x = reader . ReadInt32 ( ) ;
var x = reader . ReadInt32 ( ) ;
var y = reader . ReadInt32 ( ) ;
var y = reader . ReadInt32 ( ) ;
var corners = new Corners < floa t> (
var corners = new Corners < shor t> (
reader . ReadSingle ( ) , reader . ReadSingle ( ) ,
reader . ReadInt16 ( ) , reader . ReadInt16 ( ) ,
reader . ReadSingle ( ) , reader . ReadSingle ( ) ) ;
reader . ReadInt16 ( ) , reader . ReadInt16 ( ) ) ;
yield return ( new ( x , y ) , corners ) ;
yield return ( new ( x , y ) , corners ) ;
}
}
}
}
static byte [ ] PackTextureData ( IEnumerable < ( TilePos Position , int Texture ) > data )
static byte [ ] PackTextureData ( IEnumerable < ( TilePos Position , byte Texture ) > data )
{
{
using var stream = new MemoryStream ( ) ;
using var stream = new MemoryStream ( ) ;
using var writer = new BinaryWriter ( stream ) ;
using var writer = new BinaryWriter ( stream ) ;
foreach ( var ( pos , texture ) in data ) {
foreach ( var ( pos , texture ) in data ) {
writer . Write ( pos . X ) ;
writer . Write ( pos . X ) ;
writer . Write ( pos . Y ) ;
writer . Write ( pos . Z ) ;
writer . Write ( ( byte ) texture ) ;
writer . Write ( texture ) ;
}
}
return stream . ToArray ( ) ;
return stream . ToArray ( ) ;
}
}
static IEnumerable < ( TilePos Position , int Texture ) > UnpackTextureData ( byte [ ] data )
static IEnumerable < ( TilePos Position , byte Texture ) > UnpackTextureData ( byte [ ] data )
{
{
using var stream = new MemoryStream ( data ) ;
using var stream = new MemoryStream ( data ) ;
using var reader = new BinaryReader ( stream ) ;
using var reader = new BinaryReader ( stream ) ;