Adetonics/PlanetGenerator.cs
2026-03-02 13:52:26 +02:00

317 lines
12 KiB
C#

using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
public partial class PlanetGenerator : Node3D
{
[Export] FastNoiseLite noise;
[Export] FastNoiseLite hfnoise;
[Export] private int _plateCount = 55;
[Export] private float oceanPercent = 0.6f;
Dictionary<int, List<int>> plateVerticeCandidates = new Dictionary<int, List<int>>();
Dictionary<int, PlateDataOld> plates = new Dictionary<int, PlateDataOld>();
private int stage = -1;
MeshDataTool mdt = new MeshDataTool();
public class PlateDataOld(List<VertexDataOld> vertices = null, bool isLand = false, Vector3? dir = null, Color color = new())
{
public List<VertexDataOld> Vertices = vertices ?? new List<VertexDataOld>();
public Color Color = color;
public bool IsLand = isLand;
public Vector3? Dir = dir;
}
public class VertexDataOld(int index = 0, float stress = 0f, bool isEdge = false)
{
public int Index = index;
public float Stress = stress;
public bool IsEdge = isEdge;
public bool StageProcessed = false;
public float Height = 0f;
}
public override void _Ready()
{
var MeshInstance = GetNode<MeshInstance3D>("./Icosphere");
if (MeshInstance.GetSurfaceOverrideMaterial(0) is ShaderMaterial shaderMaterial)
{
shaderMaterial.SetShaderParameter("mode", 1);
}
}
public void InitializeGeneration()
{
plateVerticeCandidates = new();
plates = new();
var MeshInstance = GetNode<MeshInstance3D>("./Icosphere");
if (MeshInstance.Mesh is ArrayMesh mesh)
{
mdt.CreateFromSurface(mesh, 0);
GD.Print($"MDTVertices: '{mdt.GetVertexCount()}', MDTFaces: '{mdt.GetFaceCount()}', MESHFaces: '{MeshInstance.Mesh.GetFaces().Length}'");
for (int i = 0; i < mdt.GetVertexCount(); i++)
{
mdt.SetVertexColor(i, Colors.Black);
}
List<bool> isLandList = new List<bool>();
isLandList.AddRange(Enumerable.Repeat(true, (int)(_plateCount * oceanPercent)));
isLandList.AddRange(Enumerable.Repeat(false, (int)(_plateCount * 1f - oceanPercent)));
isLandList = isLandList.OrderBy(l => Guid.NewGuid()).ToList();
for (int i = 0; i < _plateCount; i++)
{
bool isLand = isLandList[i];
var vertexIndex = Random.Shared.Next(0, mdt.GetVertexCount());
var color = isLand ? new Color(
0.2f,
1f,
0.2f
) : new Color(
0.2f,
0.2f,
1f
);
color.ToHsv(out float h, out float s, out float v);
h += RandF(-0.05f, 0.05f);
s += RandF(-0.2f, 0.2f);
v += RandF(-0.3f, 0.3f);
color = Color.FromHsv(h, s, v);
plateVerticeCandidates.Add(i, GetNeighboringVertices(vertexIndex).ToList());
var plateVel = new Vector3(Random.Shared.NextSingle() * 2f - 1f ,Random.Shared.NextSingle() * 2f - 1f ,Random.Shared.NextSingle() * 2f - 1f);
plateVel = plateVel.Normalized() * Random.Shared.NextSingle();
plates.Add(i, new PlateDataOld([new VertexDataOld(vertexIndex)], isLand, plateVel, color));
mdt.SetVertexColor(vertexIndex, color);
}
mesh.ClearSurfaces();
mdt.CommitToSurface(mesh);
}
}
private float RandF(float min, float max)
{
return min + (max - min) * Random.Shared.NextSingle();
}
private int iterations = 1;
public override void _Process(double delta)
{
if (Input.IsActionJustPressed("spacebar"))
{
InitializeGeneration();
stage = 0;
var MeshInstance = GetNode<MeshInstance3D>("./Icosphere");
if (MeshInstance.GetSurfaceOverrideMaterial(0) is ShaderMaterial shaderMaterial)
{
shaderMaterial.SetShaderParameter("mode", 1);
}
}
if (GetNode<MeshInstance3D>("./Icosphere").Mesh is ArrayMesh mesh)
{
switch (stage)
{
case 0: // Gen initial tectonic plates
if (plateVerticeCandidates.Count == 0)
stage = 1;
List<int> toRemove = [];
foreach ((int plateIndex, List<int> verticeCanditates) in plateVerticeCandidates)
{
if (verticeCanditates.Count == 0)
toRemove.Add(plateIndex);
int max = Random.Shared.Next(1, (int)(2 + verticeCanditates.Count / 4f));
for (int i = 0; i < max; i++)
{
iterations++;
ExpandRandomly(verticeCanditates, plateIndex);
}
plateVerticeCandidates[plateIndex] = verticeCanditates.Distinct().ToList();
}
foreach (int index in toRemove)
plateVerticeCandidates.Remove(index);
mesh.ClearSurfaces();
mdt.CommitToSurface(mesh);
break;
case 1:
foreach ((int plateIndex, PlateDataOld data) in plates)
{
foreach (VertexDataOld vertex in data.Vertices.Where(v => !v.StageProcessed).Take(50))
{
if (IsEdge(vertex.Index, plateIndex))
{
vertex.IsEdge = true;
}
vertex.StageProcessed = true;
mdt.SetVertexColor(vertex.Index, vertex.IsEdge ? Colors.Black : Colors.White);
}
}
mesh.ClearSurfaces();
mdt.CommitToSurface(mesh);
if (plates.Values.SelectMany(val => val.Vertices).All(v => v.StageProcessed))
{
foreach ((int plateIndex, PlateDataOld data) in plates)
{
foreach (VertexDataOld vertex in data.Vertices)
{
vertex.StageProcessed = false;
}
}
stage = 2;
}
break;
case 2:
foreach ((int plateIndex, PlateDataOld data) in plates)
{
foreach (VertexDataOld vertex in data.Vertices.Where(v => !v.StageProcessed && v.IsEdge).Take(10))
{
vertex.Stress = GetStress(vertex.Index, plateIndex);
vertex.StageProcessed = true;
mdt.SetVertexColor(vertex.Index, vertex.Stress > 0f ? Colors.Red : Colors.Blue);
}
}
mesh.ClearSurfaces();
mdt.CommitToSurface(mesh);
if (plates.Values.SelectMany(val => val.Vertices).Where(v => v.IsEdge).All(v => v.StageProcessed))
{
foreach ((int plateIndex, PlateDataOld data) in plates)
{
foreach (VertexDataOld vertex in data.Vertices)
{
vertex.StageProcessed = false;
}
}
stage = 3;
var MeshInstance = GetNode<MeshInstance3D>("./Icosphere");
if (MeshInstance.GetSurfaceOverrideMaterial(0) is ShaderMaterial shaderMaterial)
{
shaderMaterial.SetShaderParameter("mode", 2);
}
}
break;
case 3:
foreach ((int plateIndex, PlateDataOld data) in plates)
{
foreach (VertexDataOld vertex in data.Vertices.Where(v => !v.StageProcessed).Take(10))
{
vertex.Height = GetHeight(vertex, plateIndex);
vertex.StageProcessed = true;
mdt.SetVertexColor(vertex.Index, new Color(vertex.Height, vertex.Height, vertex.Height));
}
}
mesh.ClearSurfaces();
mdt.CommitToSurface(mesh);
if (plates.Values.SelectMany(val => val.Vertices).All(v => v.StageProcessed))
{
foreach ((int plateIndex, PlateDataOld data) in plates)
{
foreach (VertexDataOld vertex in data.Vertices)
{
vertex.StageProcessed = false;
}
}
stage = 4;
}
break;
default:
break;
}
}
}
public void ColorFace(int faceIndex, Color color)
{
mdt.SetVertexColor(mdt.GetFaceVertex(faceIndex, 0), color);
mdt.SetVertexColor(mdt.GetFaceVertex(faceIndex, 1), color);
mdt.SetVertexColor(mdt.GetFaceVertex(faceIndex, 2), color);
}
public bool IsEdge(int vertex, int plateIndex)
{
var neighbours = GetNeighboringVertices(vertex, false);
foreach (var neighbour in neighbours)
{
if (plates[plateIndex].Vertices.All(v => v.Index != neighbour))
return true;
}
return false;
}
public float GetStress(int vertex, int plateIndex)
{
PlateDataOld plate = plates[plateIndex];
var neighbours = GetNeighboringVertices(vertex, false);
float stress = 0f;
foreach ((int otherPlateIndex, PlateDataOld plateData) in plates.Where(p => p.Key != plateIndex))
{
if (plateData.Vertices.Where(v => v.IsEdge).Any(v => neighbours.Contains(v.Index)))
{
var a = plate.Dir ?? Vector3.Zero;
var b = plateData.Dir ?? Vector3.Zero;
stress += a.Dot(b);
}
}
return stress;
}
public float GetHeight(VertexDataOld vertex, int plateIndex)
{
PlateDataOld plate = plates[plateIndex];
float height = 0.5f;
if (plate.IsLand)
{
height += Mathf.Abs(vertex.Stress * 0.25f);
height += noise.GetNoise3Dv(mdt.GetVertex(vertex.Index)) * 0.15f;
height += hfnoise.GetNoise3Dv(mdt.GetVertex(vertex.Index)) * 0.05f;
}
else
{
height -= Mathf.Abs(vertex.Stress * 0.25f);
height += noise.GetNoise3Dv(mdt.GetVertex(vertex.Index)) * 0.15f;
height += hfnoise.GetNoise3Dv(mdt.GetVertex(vertex.Index)) * 0.05f;
}
return height;
}
public void ExpandRandomly(List<int> vertexCandidate, int plateIndex)
{
bool looping = true;
var index = 0;
while (looping)
{
if (vertexCandidate.Count == 0)
{
looping = false;
continue;
}
index = Random.Shared.Next(vertexCandidate.Count);
var vertex = vertexCandidate[index];
if (mdt.GetVertexColor(vertex) != Colors.Black)
{
vertexCandidate.RemoveAt(index);
continue;
}
mdt.SetVertexColor(vertex, plates[plateIndex].Color);
plates[plateIndex].Vertices.Add(new VertexDataOld(vertex));
vertexCandidate.RemoveAt(index);
var neighbours = GetNeighboringVertices(vertex).ToList();
vertexCandidate.AddRange(neighbours);
looping = false;
}
}
public List<int> GetNeighboringVertices(int vertex, bool blackOnly = true)
{
var verts = mdt.GetVertexEdges(vertex).AsEnumerable().SelectMany<int, int>(edge => [mdt.GetEdgeVertex(edge, 0), mdt.GetEdgeVertex(edge, 1)]).Distinct().Where(v => v != vertex);
if (!blackOnly)
return verts.ToList();
return verts.Where(v => mdt.GetVertexColor(v) == Colors.Black).ToList();
}
}