You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
5.0 KiB
152 lines
5.0 KiB
[Tool] |
|
public partial class Terrain |
|
: StaticBody3D |
|
{ |
|
[Export] public Vector2I Size { get; set; } = new(64, 64); |
|
[Export] public float TileSize { get; set; } = 2.0f; |
|
|
|
[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; } |
|
|
|
|
|
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"); |
|
var shape = GetOrCreateShape("CollisionShape"); |
|
|
|
mesh.ClearSurfaces(); |
|
mesh.SurfaceBegin(Mesh.PrimitiveType.Triangles); |
|
var points = new List<Vector3>(); |
|
|
|
void AddPoint(Vector3 pos, Vector2 uv) { |
|
mesh.SurfaceSetUV(uv); |
|
mesh.SurfaceAddVertex(pos); |
|
points.Add(pos); |
|
} |
|
|
|
void AddTriangle(Vector3 v1, Vector2 uv1, |
|
Vector3 v2, Vector2 uv2, |
|
Vector3 v3, Vector2 uv3) { |
|
var dir = (v3 - v1).Cross(v2 - v1); |
|
mesh.SurfaceSetNormal(dir.Normalized()); |
|
AddPoint(v1, uv1); |
|
AddPoint(v2, uv2); |
|
AddPoint(v3, uv3); |
|
} |
|
|
|
// 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); |
|
|
|
// Randomly select two different textures and one blend texture. |
|
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)); |
|
} |
|
} |
|
|
|
mesh.SurfaceEnd(); |
|
mesh.SurfaceSetMaterial(0, Material); |
|
|
|
shape.Data = [.. points]; |
|
} |
|
|
|
|
|
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); |
|
if (meshInstance == null) { |
|
meshInstance = new(){ Name = name, Mesh = new ImmediateMesh() }; |
|
AddChild(meshInstance); |
|
meshInstance.Owner = this; |
|
} |
|
return (ImmediateMesh)meshInstance.Mesh; |
|
} |
|
|
|
public ConcavePolygonShape3D GetOrCreateShape(string name) |
|
{ |
|
var collisionShape = (CollisionShape3D)GetNodeOrNull(name); |
|
if (collisionShape == null) { |
|
collisionShape = new(){ Name = name, Shape = new ConcavePolygonShape3D() }; |
|
AddChild(collisionShape); |
|
collisionShape.Owner = this; |
|
} |
|
return (ConcavePolygonShape3D)collisionShape.Shape; |
|
} |
|
}
|
|
|