|
|
|
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; }
|
|
|
|
|
|
|
|
|
|
|
|
Material _editToolMaterial;
|
|
|
|
public override void _Ready()
|
|
|
|
{
|
|
|
|
_editToolMaterial = new StandardMaterial3D {
|
|
|
|
VertexColorUseAsAlbedo = true,
|
|
|
|
ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded,
|
|
|
|
DepthDrawMode = BaseMaterial3D.DepthDrawModeEnum.Disabled,
|
|
|
|
NoDepthTest = true,
|
|
|
|
};
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
/// <summary> Transforms a 3D position local to the equivalent tile position. </summary>
|
|
|
|
public TilePos ToTilePos(Vector3 localPos)
|
|
|
|
=> new(RoundToInt(localPos.X / TileSize + Size.X / 2.0f),
|
|
|
|
RoundToInt(localPos.Z / TileSize + Size.Y / 2.0f));
|
|
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|