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> plateVerticeCandidates = new Dictionary>(); Dictionary plates = new Dictionary(); private int stage = -1; MeshDataTool mdt = new MeshDataTool(); public class PlateDataOld(List vertices = null, bool isLand = false, Vector3? dir = null, Color color = new()) { public List Vertices = vertices ?? new List(); 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("./Icosphere"); if (MeshInstance.GetSurfaceOverrideMaterial(0) is ShaderMaterial shaderMaterial) { shaderMaterial.SetShaderParameter("mode", 1); } } public void InitializeGeneration() { plateVerticeCandidates = new(); plates = new(); var MeshInstance = GetNode("./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 isLandList = new List(); 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("./Icosphere"); if (MeshInstance.GetSurfaceOverrideMaterial(0) is ShaderMaterial shaderMaterial) { shaderMaterial.SetShaderParameter("mode", 1); } } if (GetNode("./Icosphere").Mesh is ArrayMesh mesh) { switch (stage) { case 0: // Gen initial tectonic plates if (plateVerticeCandidates.Count == 0) stage = 1; List toRemove = []; foreach ((int plateIndex, List 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("./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 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 GetNeighboringVertices(int vertex, bool blackOnly = true) { var verts = mdt.GetVertexEdges(vertex).AsEnumerable().SelectMany(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(); } }