317 lines
12 KiB
C#
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();
|
|
}
|
|
}
|