@ -2,38 +2,16 @@
public partial class Terrain
: StaticBody3D
{
[Export] public Vector2I Size { get ; set ; } = new ( 6 4 , 6 4 ) ;
[Export] public float TileSize { get ; set ; } = 2.0f ;
[Export] public float TileStep { get ; set ; } = 0.5f ;
[Export] public ShaderMaterial Material { get ; set ; }
// If value at position non-existant => [ 0, 0, 0, 0 ]
// If value at position is float => [ v, v, v, v ]
// If value at position is float[] => value
[Export] public Godot . Collections . Dictionary < Vector2I , Variant > Tiles { get ; set ; }
[Export] public TerrainData Data { get ; set ; } = new ( ) ;
[Export] public ShaderMaterial Material { get ; set ; }
public override void _ Ready ( )
= > UpdateMeshAndShape ( ) ;
public Tile GetTile ( TilePos pos )
= > ( Tiles ? . TryGetValue ( pos . ToVector2I ( ) , out var result ) = = true )
? Tile . FromDictionary ( result . AsGodotDictionary ( ) ) : default ;
public void SetTile ( TilePos pos , Tile value )
{
var key = pos . ToVector2I ( ) ;
var dict = value . ToDictionary ( ) ;
if ( dict = = null ) Tiles . Remove ( key ) ;
else Tiles [ key ] = dict ;
}
public bool Contains ( TilePos pos )
= > ( pos . X > = 0 ) & & ( pos . X < Size . X )
& & ( pos . Y > = 0 ) & & ( pos . Y < Size . Y ) ;
public void UpdateMeshAndShape ( )
{
var mesh = GetOrCreateMesh ( "MeshInstance" ) ;
@ -41,7 +19,7 @@ public partial class Terrain
mesh . ClearSurfaces ( ) ;
mesh . SurfaceBegin ( Mesh . PrimitiveType . Triangles ) ;
var points = new List < Vector3 > ( ) ;
var points = new List < Vector3 > ( ) ; // for CollisionShape
void AddPoint ( Vector3 pos , Vector2 uv ) {
mesh . SurfaceSetUV ( uv ) ;
@ -60,103 +38,102 @@ public partial class Terrain
}
// TODO: Don't hardcode.
var num_textures = 4 ;
var num_blend_textures = 7 ;
var rnd = new Random ( ) ;
for ( var x = 0 ; x < Size . X ; x + + )
for ( var z = 0 ; z < Size . Y ; z + + ) {
var tile = GetTile ( new ( x , z ) ) ;
var corners = GetTileCornerPositions ( new ( x , z ) , tile ) ;
mesh . SurfaceSetColor ( new (
( float ) tile . TexturePrimary / num_textures ,
( float ) tile . TextureSecondary / num_textures ,
( float ) tile . TextureBlend / num_blend_textures
) ) ;
var sorted = new ( Corner Corner , float Height ) [ ] {
( Corner . TopLeft , tile . Height . TopLeft ) ,
( Corner . TopRight , tile . Height . TopRight ) ,
( Corner . BottomRight , tile . Height . BottomRight ) ,
( Corner . BottomLeft , tile . Height . BottomLeft ) ,
} ;
Array . Sort ( sorted , ( a , b ) = > a . Height . CompareTo ( b . Height ) ) ;
// Find the "ideal way" to split the quad for the tile into two triangles.
// This is done by finding the corner with the least variance between its neighboring corners.
var minDiff = Abs ( sorted [ 0 ] . Height - sorted [ 2 ] . Height ) ; // Difference between lowest and 3rd lowest point.
var maxDiff = Abs ( sorted [ 3 ] . Height - sorted [ 1 ] . Height ) ; // Difference between highest and 3rd highest point.
var first = sorted [ ( minDiff > maxDiff ) ? 0 : 3 ] . Corner ;
if ( first is Corner . TopLeft or Corner . BottomRight ) {
AddTriangle ( corners . TopLeft , new ( 0.0f , 0.0f ) ,
corners . TopRight , new ( 1.0f , 0.0f ) ,
corners . BottomLeft , new ( 0.0f , 1.0f ) ) ;
AddTriangle ( corners . TopRight , new ( 1.0f , 0.0f ) ,
corners . BottomRight , new ( 1.0f , 1.0f ) ,
corners . BottomLeft , new ( 0.0f , 1.0f ) ) ;
} else {
AddTriangle ( corners . TopRight , new ( 1.0f , 0.0f ) ,
corners . BottomRight , new ( 1.0f , 1.0f ) ,
corners . TopLeft , new ( 0.0f , 0.0f ) ) ;
AddTriangle ( corners . BottomRight , new ( 1.0f , 1.0f ) ,
corners . BottomLeft , new ( 0.0f , 1.0f ) ,
corners . TopLeft , new ( 0.0f , 0.0f ) ) ;
}
var stone_texture = 3 ;
// These are floats to ensure floating point division is used when calling 'SurfaceSetColor'.
var num_textures = 4.0f ;
var num_blend_textures = 7.0f ;
void SetTexture ( int primary , int secondary = 1 , int blend = 0 )
= > mesh . SurfaceSetColor ( new (
( primary - 1 ) / num_textures ,
( secondary - 1 ) / num_textures ,
blend / num_blend_textures
) ) ;
foreach ( var ( chunkPos , chunk ) in Data . Chunks ) {
var offset = TerrainChunk . ToTileOffset ( chunkPos ) ;
for ( var x = 0 ; x < TerrainChunk . Size ; x + + )
for ( var z = 0 ; z < TerrainChunk . Size ; z + + ) {
var pos = new TilePos ( x , z ) + offset ;
var tile = chunk [ pos ] ;
if ( tile . IsDefault ) continue ;
var corners = ToPositions ( pos , tile ) ;
SetTexture ( tile . TexturePrimary , tile . TextureSecondary , tile . TextureBlend ) ;
// Find the "ideal way" to split the quad for the tile into two triangles.
// This is done by finding the corner with the least variance between its neighboring corners.
var sorted = tile . Height . ToArray ( ) ; Array . Sort ( sorted ) ;
var minDiff = Abs ( sorted [ 0 ] - sorted [ 2 ] ) ; // Difference between lowest and 3rd lowest point.
var maxDiff = Abs ( sorted [ 3 ] - sorted [ 1 ] ) ; // Difference between highest and 3rd highest point.
var first = ( Corner ) sorted [ ( minDiff > maxDiff ) ? 0 : 3 ] ;
if ( first is Corner . TopLeft or Corner . BottomRight ) {
AddTriangle ( corners . TopLeft , new ( 0.0f , 0.0f ) ,
corners . TopRight , new ( 1.0f , 0.0f ) ,
corners . BottomLeft , new ( 0.0f , 1.0f ) ) ;
AddTriangle ( corners . TopRight , new ( 1.0f , 0.0f ) ,
corners . BottomRight , new ( 1.0f , 1.0f ) ,
corners . BottomLeft , new ( 0.0f , 1.0f ) ) ;
} else {
AddTriangle ( corners . TopRight , new ( 1.0f , 0.0f ) ,
corners . BottomRight , new ( 1.0f , 1.0f ) ,
corners . TopLeft , new ( 0.0f , 0.0f ) ) ;
AddTriangle ( corners . BottomRight , new ( 1.0f , 1.0f ) ,
corners . BottomLeft , new ( 0.0f , 1.0f ) ,
corners . TopLeft , new ( 0.0f , 0.0f ) ) ;
}
// Set stone texture for walls.
mesh . SurfaceSetColor ( new (
( float ) 2 / num_textures ,
( float ) 2 / num_textures ,
( float ) 0 / num_blend_textures
) ) ;
void DrawWall ( TilePos nbrPos , Side side ) {
var nbrTile = GetTile ( nbrPos ) ;
var nbrCorners = GetTileCornerPositions ( nbrPos , nbrTile ) ;
var ( c1 , c2 ) = side . GetCorners ( ) ;
var ( c3 , c4 ) = side . GetOpposite ( ) . GetCorners ( ) ;
var corner1 = corners [ c1 ] ; // "TopRight"
var corner2 = corners [ c2 ] ; // "TopLeft"
var corner3 = nbrCorners [ c3 ] ; // "BottomLeft"
var corner4 = nbrCorners [ c4 ] ; // "BottomRight"
var equal1 = IsEqualApprox ( corner1 . Y , corner4 . Y ) ;
var equal2 = IsEqualApprox ( corner2 . Y , corner3 . Y ) ;
switch ( equal1 , equal2 ) {
case ( true , true ) :
// Both corners are connected, no wall needed.
break ;
case ( true , false ) :
AddTriangle ( corner1 , new ( 1.0f , corner1 . Y / TileSize ) ,
corner3 , new ( 0.0f , corner3 . Y / TileSize ) ,
corner2 , new ( 0.0f , corner2 . Y / TileSize ) ) ;
break ;
case ( false , true ) :
AddTriangle ( corner1 , new ( 1.0f , corner1 . Y / TileSize ) ,
corner4 , new ( 1.0f , corner4 . Y / TileSize ) ,
corner2 , new ( 0.0f , corner2 . Y / TileSize ) ) ;
break ;
case ( false , false ) :
// FIXME: In some configurations this creates a shape we don't want.
// Need to find a way to detect this, and switch the way triangles make up a quad.
AddTriangle ( corner1 , new ( 1.0f , corner1 . Y / TileSize ) ,
corner4 , new ( 1.0f , corner4 . Y / TileSize ) ,
corner2 , new ( 0.0f , corner2 . Y / TileSize ) ) ;
AddTriangle ( corner2 , new ( 0.0f , corner2 . Y / TileSize ) ,
corner4 , new ( 1.0f , corner4 . Y / TileSize ) ,
corner3 , new ( 0.0f , corner3 . Y / TileSize ) ) ;
break ;
// Set stone texture for walls.
SetTexture ( stone_texture ) ;
void DrawWall ( TilePos nbrPos , Side side ) {
var nbrTile = Data . GetTileOrDefault ( nbrPos ) ;
var nbrCorners = ToPositions ( nbrPos , nbrTile ) ;
var ( c1 , c2 ) = side . GetCorners ( ) ;
var ( c3 , c4 ) = side . GetOpposite ( ) . GetCorners ( ) ;
var corner1 = corners [ c1 ] ; // "TopRight"
var corner2 = corners [ c2 ] ; // "TopLeft"
var corner3 = nbrCorners [ c3 ] ; // "BottomLeft"
var corner4 = nbrCorners [ c4 ] ; // "BottomRight"
var equal1 = IsEqualApprox ( corner1 . Y , corner4 . Y ) ;
var equal2 = IsEqualApprox ( corner2 . Y , corner3 . Y ) ;
switch ( equal1 , equal2 ) {
case ( true , true ) :
// Both corners are connected, no wall needed.
break ;
case ( true , false ) :
AddTriangle ( corner1 , new ( 1.0f , corner1 . Y / TileSize ) ,
corner3 , new ( 0.0f , corner3 . Y / TileSize ) ,
corner2 , new ( 0.0f , corner2 . Y / TileSize ) ) ;
break ;
case ( false , true ) :
AddTriangle ( corner1 , new ( 1.0f , corner1 . Y / TileSize ) ,
corner4 , new ( 1.0f , corner4 . Y / TileSize ) ,
corner2 , new ( 0.0f , corner2 . Y / TileSize ) ) ;
break ;
case ( false , false ) :
// FIXME: In some configurations this creates a shape we don't want.
// Need to find a way to detect this, and switch the way triangles make up a quad.
AddTriangle ( corner1 , new ( 1.0f , corner1 . Y / TileSize ) ,
corner4 , new ( 1.0f , corner4 . Y / TileSize ) ,
corner2 , new ( 0.0f , corner2 . Y / TileSize ) ) ;
AddTriangle ( corner2 , new ( 0.0f , corner2 . Y / TileSize ) ,
corner4 , new ( 1.0f , corner4 . Y / TileSize ) ,
corner3 , new ( 0.0f , corner3 . Y / TileSize ) ) ;
break ;
}
}
}
if ( x < Size . X - 1 ) DrawWall ( new ( x + 1 , z ) , Side . Right ) ;
if ( z < Size . Y - 1 ) DrawWall ( new ( x , z + 1 ) , Side . Bottom ) ;
}
DrawWall ( pos + ( 1 , 0 ) , Side . Right ) ;
DrawWall ( pos + ( 0 , 1 ) , Side . Bottom ) ;
}
}
mesh . SurfaceEnd ( ) ;
mesh . SurfaceSetMaterial ( 0 , Material ) ;
@ -165,20 +142,6 @@ public partial class Terrain
}
public Corners < Vector3 > GetTileCornerPositions ( TilePos pos )
= > GetTileCornerPositions ( pos , GetTile ( pos ) ) ;
public Corners < Vector3 > GetTileCornerPositions ( TilePos pos , Tile tile )
{
var half = TileSize / 2 ;
var vx = ( pos . X - Size . X / 2.0f ) * TileSize ;
var vz = ( pos . Y - Size . Y / 2.0f ) * TileSize ;
return new ( new ( vx - half , tile . Height . TopLeft , vz - half ) ,
new ( vx + half , tile . Height . TopRight , vz - half ) ,
new ( vx + half , tile . Height . BottomRight , vz + half ) ,
new ( vx - half , tile . Height . BottomLeft , vz + half ) ) ;
}
public ImmediateMesh GetOrCreateMesh ( string name )
{
var meshInstance = ( MeshInstance3D ) GetNodeOrNull ( name ) ;
@ -200,4 +163,18 @@ public partial class Terrain
}
return ( ConcavePolygonShape3D ) collisionShape . Shape ;
}
public Corners < Vector3 > ToPositions ( TilePos pos )
= > ToPositions ( pos , Data . GetTileOrDefault ( pos ) ) ;
public Corners < Vector3 > ToPositions ( TilePos pos , Tile tile )
{
var x = pos . X * TileSize ;
var z = pos . Z * TileSize ;
return new ( new ( x , tile . Height . TopLeft * TileStep , z ) ,
new ( x + TileSize , tile . Height . TopRight * TileStep , z ) ,
new ( x + TileSize , tile . Height . BottomRight * TileStep , z + TileSize ) ,
new ( x , tile . Height . BottomLeft * TileStep , z + TileSize ) ) ;
}
}