Initial Commit

This commit is contained in:
Aada 2026-03-02 15:04:15 +02:00
commit cf76e5c9b2
43 changed files with 1779 additions and 0 deletions

139
src/Main.cs Normal file
View file

@ -0,0 +1,139 @@
#nullable enable
using Godot;
using System;
using System.Globalization;
using System.Linq;
using Godot.Collections;
public partial class Main : Control
{
private bool _moving = false;
[Export]
private Node3D _yawNode;
[Export]
private Node3D _pitchNode;
[Export]
private Camera3D _cameraNode;
[Export] private float _moveSensitivity = 1f/500f;
[Export] private float _zoomSensitivity = 1f;
[Export] private MeshInstance3D _meshInstance;
[Export] private Node3D World;
[Export] private TextureRect _textureRect;
private PlanetHelper.VertexData? _vertex = null;
private PlanetHelper.PlateData? _plate = null;
private PlanetHelper _planetHelper;
public override void _Ready()
{
_planetHelper = new PlanetHelper(_meshInstance, _textureRect);
UpdateStats();
}
private const float RayLength = 1000.0f;
public override void _Input(InputEvent @event)
{
if (@event is InputEventMouseButton mouseEvent)
{
if (mouseEvent.ButtonIndex == MouseButton.Left)
{
_moving = mouseEvent.Pressed;
}
if (mouseEvent.ButtonIndex == MouseButton.WheelUp)
{
_cameraNode.Position += new Vector3(0, 0, _zoomSensitivity);
}
if (mouseEvent.ButtonIndex == MouseButton.WheelDown)
{
_cameraNode.Position -= new Vector3(0, 0, _zoomSensitivity);
}
}
else if (@event is InputEventMouseMotion motionEvent && _moving)
{
_yawNode.RotateY(-motionEvent.ScreenRelative.X * _moveSensitivity);
_pitchNode.RotateX(-motionEvent.ScreenRelative.Y * _moveSensitivity);
}
}
public void Tab(int tab)
{
if (tab == 1)
{
Projector.GatherPoints(_planetHelper);
_textureRect.Texture = Projector.Render(_planetHelper);
}
}
public override void _Process(double delta)
{
if (Input.IsActionJustPressed("mouse_secondary"))
{
var from = _cameraNode.ProjectRayOrigin(GetViewport().GetMousePosition());
var to = from + _cameraNode.ProjectRayNormal(GetViewport().GetMousePosition()) * RayLength;
var result = World.GetWorld3D().DirectSpaceState.IntersectRay(PhysicsRayQueryParameters3D.Create(from, to));
if (result.Count > 0)
{
Vector3? pos = result["position"].AsVector3();
if (pos != null)
{
GD.Print($"Hit: '{pos}'");
var closest = _planetHelper.Octree.SearchNearest(pos ?? Vector3.Zero)?.Id;
if (closest != null)
{
_vertex = _planetHelper.Vertices.Single(v => v.Id == closest);
if (_planetHelper.Plates.Count > 0 && _vertex.PlateId != -1)
_plate = _planetHelper.Plates[_vertex.PlateId];
else
_plate = null;
UpdateStats();
}
}
}
}
if (Input.IsActionJustPressed("spacebar"))
{
_planetHelper.Advance = true;
}
if (Input.IsActionJustPressed("enter"))
{
_planetHelper.AutoRun = true;
}
_planetHelper.Process();
}
public void UpdateStats()
{
if (_vertex != null)
{
var height = -9000f * (0.5f - _vertex.Height) * 2f;
GetNode<Label>("%PointHeight").Text = $"{(height > 0 ? "+" : "")}{height:0000}M";
GetNode<Label>("%PointId").Text = $"{_vertex.Id:0000000}";
}
else
{
GetNode<Label>("%PointHeight").Text = "0000M";
GetNode<Label>("%PointId").Text = "0000000";
}
if (_plate != null)
{
GetNode<Label>("%PlateId").Text = $"{_plate.Id:00}";
GetNode<Label>("%IsLandform").Text = $"{(_plate.IsLandform ? "Y" : "N")}";
var area = (int)((float)_plate.Vertices.Count / _planetHelper.Vertices.Count * 100f);
GetNode<Label>("%Area").Text = $"{area:00}%";
}
else
{
GetNode<Label>("%PlateId").Text = "00";
GetNode<Label>("%IsLandform").Text = "U";
}
}
public void MakeGo()
{
_planetHelper = new PlanetHelper(_meshInstance, _textureRect);
}
}

1
src/Main.cs.uid Normal file
View file

@ -0,0 +1 @@
uid://bhpic251bgvgk

144
src/Oct.cs Normal file
View file

@ -0,0 +1,144 @@
#nullable enable
using System.Collections.Generic;
using Godot;
namespace adatonic;
public class Node
{
public Node()
{
}
public Node(int id, Vector3 pos)
{
Id = id;
Position = pos;
}
public int Id;
public Vector3 Position;
}
public class Oct
{
private Vector3 Start;
private Vector3 Extent;
Oct?[]? Trees = null;
private Node? Node = null;
public Oct()
{
Start = -Vector3.One;
Extent = Vector3.One * 2f;
}
public Oct(Vector3 start, Vector3 extent)
{
Start = start;
Extent = extent;
}
public void Insert(Node node)
{
if (!IsInside(node.Position))
{
GD.Print($"Failed to insert to Octree - Point out of bounds!");
return;
}
if (Node == null && Trees == null)
{
Node = node;
return;
}
Trees ??= new Oct?[8];
if (Node != null)
{
int octOld = WhichSubOct(Node.Position);
Trees[octOld] ??= new Oct(GetSubStart(octOld), Extent * 0.5f);
Trees[octOld]?.Insert(Node);
Node = null;
}
int oct = WhichSubOct(node.Position);
Trees[oct] ??= new Oct(GetSubStart(oct), Extent * 0.5f);
Trees[oct]?.Insert(node);
}
public Node? SearchNearest(Vector3 position)
{
Node? best = null;
float bestDist = float.MaxValue;
SearchNearest(position, ref best, ref bestDist);
return best;
}
private void SearchNearest(Vector3 position, ref Node? best, ref float bestDist)
{
if (Node != null)
{
float dist = position.DistanceSquaredTo(Node.Position);
if (dist < bestDist)
{
bestDist = dist;
best = Node;
}
}
if (Trees == null)
return;
int first = WhichSubOct(position);
Trees[first]?.SearchNearest(position, ref best, ref bestDist);
for (int i = 0; i < 8; i++)
{
if (i == first || Trees[i] == null)
continue;
float boxDist = Trees[i]!.DistanceToBox(position);
if (boxDist < bestDist)
{
Trees[i]!.SearchNearest(position, ref best, ref bestDist);
}
}
}
private float DistanceToBox(Vector3 p)
{
Vector3 min = Start;
Vector3 max = Start + Extent;
float dx = Mathf.Max(Mathf.Max(min.X - p.X, 0), p.X - max.X);
float dy = Mathf.Max(Mathf.Max(min.Y - p.Y, 0), p.Y - max.Y);
float dz = Mathf.Max(Mathf.Max(min.Z - p.Z, 0), p.Z - max.Z);
return dx * dx + dy * dy + dz * dz;
}
public int WhichSubOct(Vector3 position)
{
bool left = position.X < Start.X + Extent.X * 0.5f;
bool bottom = position.Y < Start.Y + Extent.Y * 0.5f;
bool near = position.Z < Start.Z + Extent.Z * 0.5f;
return (left ? 1 : 0) + (bottom ? 2 : 0) + (near ? 4 : 0);
}
public Vector3 GetSubStart(int oct)
{
Vector3 start = Vector3.Zero;
bool left = (oct & (1 << 0)) != 0;
bool bottom = (oct & (1 << 1)) != 0;
bool near = (oct & (1 << 2)) != 0;
start.X += left ? Start.X : Start.X + Extent.X * 0.5f;
start.Y += bottom ? Start.Y : Start.Y + Extent.Y * 0.5f;
start.Z += near ? Start.Z : Start.Z + Extent.Z * 0.5f;
return start;
}
public bool IsInside(Vector3 position)
{
return position > Start && position < Start + Extent;
}
}

1
src/Oct.cs.uid Normal file
View file

@ -0,0 +1 @@
uid://c4pay0n8iktfa

703
src/PlanetHelper.cs Normal file
View file

@ -0,0 +1,703 @@
using System;
using System.Collections.Generic;
using Godot;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using adatonic;
using Node = adatonic.Node;
using Timer = System.Timers.Timer;
public class PlanetHelper
{
public static float RandF(float min, float max)
{
return min + (max - min) * Random.Shared.NextSingle();
}
public class PlateData(int Id = 0, Color Color = new(), bool IsLandform = false, List<int> Vertices = null)
{
public int Id { get; set; } = Id;
public Color Color { get; set; } = Color;
public bool IsLandform { get; set; } = IsLandform;
public List<int> Vertices { get; set; } = Vertices;
public int CenterVertexId = -1;
public float PlateExpansion { get; set; } = RandF(0.5f, 2f);
public Vector3 Dir { get; set; } = Vector3.Zero;
}
public class VertexData(int Id = 0, int PlateId = 0, List<int> Neighbours = null, bool StageComplete = false)
{
public int Id { get; set; } = Id;
public int PlateId { get; set; } = PlateId;
public List<StrainAnalysis> StrainSamples { get; set; } = new();
public List<int> Neighbours { get; set; } = Neighbours;
public bool StageComplete { get; set; } = StageComplete;
public bool IsEdge = false;
public bool IsTypeEdge = false;
public float EdgeDistance = -1f;
public float Height = 0f;
}
public enum StrainType
{
Tension,
Compression,
Shear
}
public class StrainAnalysis
{
public float Magnitude;
public StrainType Type;
public float NormalRate;
public float ShearRate;
}
private bool StageComplete = true;
private int _plateCount = 14;
private float _landRatio = 0.4f;
public List<PlateData> Plates = new List<PlateData>();
public List<VertexData> Vertices = new List<VertexData>();
public double StageHangTime = 1.0;
public bool AutoRun = false;
public bool Advance = false;
public int TesselationLevel = 4;
Stopwatch _generationStopwatch = new Stopwatch();
private FastNoiseLite _continentalNoise;
private FastNoiseLite _mountainNoise;
private FastNoiseLite _hfNoise;
public enum GenerationStage
{
NotStarted,
Initialization,
PlateGeneration,
BorderSearch,
EdgeDistanceCalculation,
EdgeStressCalculation,
SpreadStress,
HeightCalculation,
Completed,
}
private bool _waiting = false;
public GenerationStage Stage = GenerationStage.NotStarted;
public GenerationStage StopStage = GenerationStage.Completed;
private MeshInstance3D _meshInstance;
private TextureRect _textureRect;
private ArrayMesh _arrayMesh;
public MeshDataTool Mdt;
public Oct Octree = new Oct();
public PlanetHelper(MeshInstance3D meshInstance, TextureRect textureRect)
{
_meshInstance = meshInstance;
_arrayMesh = meshInstance.Mesh as ArrayMesh;
_textureRect = textureRect;
_continentalNoise = new FastNoiseLite();
_mountainNoise = new FastNoiseLite();
_hfNoise = new FastNoiseLite();
Mdt = new MeshDataTool();
Mdt.CreateFromSurface(_arrayMesh, 0);
for (int i = 0; i < Mdt.GetVertexCount(); i++)
{
Octree.Insert(new Node(i, Mdt.GetVertex(i) * 0.001f));
Mdt.SetVertexColor(i, Colors.Black);
}
}
public void InitializeGeneration()
{
Plates = new();
Vertices = new();
Mdt.CreateFromSurface(_arrayMesh, 0);
for (int i = 0; i < Mdt.GetVertexCount(); i++)
{
// Init to black
Mdt.SetVertexColor(i, Colors.Black);
Vertices.Add(new VertexData(i, -1,GetNeighboringVertices(i, false).OrderBy(v => Guid.NewGuid()).ToList()));
}
// Initialize Plates
for (int i = 0; i < _plateCount; i++)
{
// Get a random un-assigned vertex.
VertexData vertex = Vertices.Where(v => v.PlateId == -1).OrderBy(v => Guid.NewGuid()).First();
vertex.PlateId = i;
var color = new Color(RandF(0f, 1f), RandF(0f, 1f), RandF(0f, 1f));
ColorVertex(vertex.Id, color);
PlateData plate = new PlateData(i, color, false, [vertex.Id]);
plate.Dir = GetRandomTangentialVelocity(Mdt.GetVertex(vertex.Id), RandF(0f, 1f));
Plates.Add(plate);
}
CompleteStage();
}
public IEnumerable<int> GetNeighboringVertices(int vertexId, bool blackOnly = true)
{
if (Stage != GenerationStage.Initialization)
{
if (blackOnly)
return Vertices[vertexId].Neighbours.Where(n => Vertices[n].PlateId == -1);
return Vertices[vertexId].Neighbours;
}
var verts = Mdt.GetVertexEdges(vertexId).AsEnumerable().SelectMany<int, int>(edge => [Mdt.GetEdgeVertex(edge, 0), Mdt.GetEdgeVertex(edge, 1)]).Distinct().Where(v => v != vertexId);
if (!blackOnly)
return verts.Except([vertexId]);
return verts.Where(v => Mdt.GetVertexColor(v) == Colors.Black).Except([vertexId]);
}
public Vector3 GetRandomTangentialVelocity(Vector3 pointOnSphere, float speed)
{
Vector3 normal = pointOnSphere.Normalized();
Random rand = new Random();
Vector3 randomVec = new Vector3(
(float)(rand.NextDouble() - 0.5), // Range -0.5 to 0.5
(float)(rand.NextDouble() - 0.5),
(float)(rand.NextDouble() - 0.5)
);
Vector3 tangent = randomVec.Cross(normal);
if (tangent.Dot(tangent) < 1e-6f)
{
randomVec = new Vector3(0, 1, 0);
tangent = randomVec.Cross(normal);
}
Vector3 normalizedTangent = tangent.Normalized();
return normalizedTangent * speed;
}
public void ToggleAutoRun()
{
AutoRun = !AutoRun;
}
public void ToggleAdvance()
{
Advance = !Advance;
}
public void AdvanceStage()
{
Advance = false;
if (_waiting)
return;
Timer timer = new(Mathf.Clamp(StageHangTime, 0.1, 10.0));
timer.Elapsed += (o, e) =>
{
GenerationStage stage = Stage + 1;
Stage = Stage == StopStage ? GenerationStage.Completed : stage;
if (stage == GenerationStage.Completed)
_generationStopwatch.Stop();
else
_generationStopwatch.Restart();
GD.Print($"Stage Started: '{Stage.ToString()}'");
_waiting = false;
StageComplete = false;
};
timer.AutoReset = false;
timer.Start();
_waiting = true;
}
public void Process()
{
if (!StageComplete)
{
switch (Stage)
{
default:
case GenerationStage.NotStarted:
break;
case GenerationStage.Completed:
break;
case GenerationStage.Initialization:
InitializeGeneration();
break;
case GenerationStage.PlateGeneration:
PlateGeneration();
break;
case GenerationStage.BorderSearch:
BorderSearch();
break;
case GenerationStage.EdgeDistanceCalculation:
EdgeDistanceCalculation();
break;
case GenerationStage.EdgeStressCalculation:
EdgeStressCalculation();
break;
case GenerationStage.SpreadStress:
SpreadStress();
break;
case GenerationStage.HeightCalculation:
HeightCalculation();
break;
}
UpdateMesh();
}
else
{
if (AutoRun || Advance)
AdvanceStage();
}
}
public void PlateGeneration()
{
var availableVerts = Vertices.Where(d => d.StageComplete == false && d.PlateId != -1).OrderBy(v => Guid.NewGuid()).ToList();
foreach (PlateData plateData in Plates)
{
var plateVerts = availableVerts.Where(d => d.PlateId == plateData.Id);
foreach (VertexData vertexData in plateVerts.Take((int)((5 + plateVerts.Count() / 4) * plateData.PlateExpansion)))
{
int expandTo = GetFreeNeighbourIndex(vertexData);
if (expandTo != -1)
{
Vertices[expandTo].PlateId = plateData.Id;
plateData.Vertices.Add(expandTo);
ColorVertex(expandTo, plateData.Color);
}
else
{
vertexData.StageComplete = true;
}
}
}
if (!availableVerts.Any())
{
foreach (VertexData vertexData in Vertices)
vertexData.StageComplete = false;
AssignOceanPlates(Plates);
CompleteStage();
}
}
public void BorderSearch()
{
var availableVerts = Vertices.Where(d => d.StageComplete == false).Take(2500).ToList();
foreach (VertexData vertexData in availableVerts)
{
// Do we have any neighbours of another plate?
var neighbours = GetNeighboringVertices(vertexData.Id, false).ToList();
if (neighbours
.Any(v => Vertices[v].PlateId != vertexData.PlateId))
{
vertexData.IsEdge = true;
vertexData.IsTypeEdge = neighbours.Any(n => Plates[Vertices[n].PlateId].IsLandform != Plates[vertexData.PlateId].IsLandform);
if (vertexData.IsTypeEdge)
vertexData.EdgeDistance = 1f;
ColorVertex(vertexData.Id, vertexData.IsTypeEdge ? Colors.White : Colors.Black);
}
else
{
ColorVertex(vertexData.Id, Plates[vertexData.PlateId].Color);
}
vertexData.StageComplete = true;
}
if (!availableVerts.Any())
{
foreach (VertexData vertexData in Vertices)
vertexData.StageComplete = false;
CompleteStage();
}
}
public void EdgeDistanceCalculation()
{
var availableVerts = Vertices.Where(d => d.StageComplete == false && d.EdgeDistance > 0f).OrderBy(v => v.EdgeDistance).Take(2500).ToList();
foreach (VertexData vertexData in availableVerts)
{
var neighbours = GetNeighboringVertices(vertexData.Id, false).ToList();
foreach (int neighbour in neighbours)
{
if (Vertices[neighbour].EdgeDistance > 0f && Vertices[neighbour].EdgeDistance < vertexData.EdgeDistance + 1f)
continue;
VertexData neighbourVert = Vertices[neighbour];
neighbourVert.EdgeDistance = vertexData.EdgeDistance + 1f;
ColorVertex(neighbourVert.Id, Plates[vertexData.PlateId].Color * 0.8f);
}
vertexData.StageComplete = true;
}
if (!availableVerts.Any())
{
float maxDistance = Vertices.Max(v => v.EdgeDistance);
foreach (VertexData vertexData in Vertices)
{
vertexData.EdgeDistance /= maxDistance;
}
foreach (PlateData plateData in Plates)
{
plateData.CenterVertexId =
Vertices.Where(v => v.PlateId == plateData.Id).MaxBy(v => v.EdgeDistance).Id;
}
foreach (VertexData vertexData in Vertices)
vertexData.StageComplete = false;
CompleteStage();
}
}
public void EdgeStressCalculation()
{
var availableVerts = Vertices.Where(d => d.StageComplete == false && d.IsEdge).Take(2500).ToList();
foreach (VertexData vertexData in availableVerts)
{
var neighbours = GetNeighboringVertices(vertexData.Id, false).ToList();
foreach (int neighbour in neighbours)
{
if (!Vertices[neighbour].IsEdge)
continue;
if (Vertices[neighbour].PlateId == vertexData.PlateId)
continue;
PlateData plateA = Plates[vertexData.PlateId];
PlateData plateB = Plates[Vertices[neighbour].PlateId];
VertexData centerA = Vertices[plateA.CenterVertexId];
VertexData centerB = Vertices[plateB.CenterVertexId];
Vector3 p1, p2;
p1 = Mdt.GetVertex(vertexData.Id).Cross(Mdt.GetVertex(centerA.Id));
p2 = Mdt.GetVertex(neighbour).Cross(Mdt.GetVertex(centerB.Id));
vertexData.StrainSamples.Add(CalculateStrainMagnitude(p1, p2, plateA.Dir, plateB.Dir));
}
vertexData.StageComplete = true;
var majorStrain = AverageStrainList(vertexData.StrainSamples);
switch (majorStrain.Type)
{
case StrainType.Compression:
ColorVertex(vertexData.Id, Colors.Red * majorStrain.Magnitude);
break;
case StrainType.Shear:
ColorVertex(vertexData.Id, Colors.Yellow * majorStrain.Magnitude);
break;
case StrainType.Tension:
ColorVertex(vertexData.Id, Colors.Blue * majorStrain.Magnitude);
break;
}
}
if (!availableVerts.Any())
{
foreach (VertexData vertexData in Vertices)
vertexData.StageComplete = false;
CompleteStage();
}
}
public void SpreadStress()
{
var availableVerts = Vertices.Where(d => d.StageComplete == false && d.IsEdge && d.StrainSamples.Any()).OrderBy(d => Mathf.Abs(d.StrainSamples.Max(s => s.Magnitude))).Take(2500).ToList();
foreach (VertexData vertexData in availableVerts)
{
var neighbours = GetNeighboringVertices(vertexData.Id, false).ToList();
var majorStrain = AverageStrainList(vertexData.StrainSamples);
foreach (int neighbour in neighbours)
{
VertexData neighbourVert = Vertices[neighbour];
neighbourVert.IsEdge = true;
var newStrain = new StrainAnalysis();
newStrain.Magnitude = majorStrain.Magnitude * 0.9f;
newStrain.Type = majorStrain.Type;
newStrain.NormalRate = majorStrain.NormalRate * 0.9f;
newStrain.ShearRate = majorStrain.ShearRate * 0.9f;
neighbourVert.StrainSamples.Add(newStrain);
var newAverage = AverageStrainList(neighbourVert.StrainSamples);;
switch (majorStrain.Type)
{
case StrainType.Compression:
ColorVertex(neighbourVert.Id, Colors.Red * newAverage.Magnitude);
break;
case StrainType.Shear:
ColorVertex(neighbourVert.Id, Colors.Yellow * newAverage.Magnitude);
break;
case StrainType.Tension:
ColorVertex(neighbourVert.Id, Colors.Blue * newAverage.Magnitude);
break;
}
}
if (neighbours.All(n => Vertices[n].IsEdge))
{
vertexData.StageComplete = true;
}
}
if (!availableVerts.Any())
{
foreach (VertexData vertexData in Vertices)
{
vertexData.StageComplete = false;
}
CompleteStage();
}
}
public void HeightCalculation()
{
var availableVerts = Vertices.Where(d => d.StageComplete == false).Take(2500).ToList();
foreach (VertexData vertexData in availableVerts)
{
PlateData plate = Plates[vertexData.PlateId];
float continentalNoise = _continentalNoise.GetNoise3Dv(GetVertexPosition(vertexData.Id));
float mountainNoise = (1.0f + _mountainNoise.GetNoise3Dv(GetVertexPosition(vertexData.Id))) * 0.5f;
float hfNoise = _hfNoise.GetNoise3Dv(GetVertexPosition(vertexData.Id));
var majorStrain = AverageStrainList(vertexData.StrainSamples);
var normalRate = -majorStrain.NormalRate * majorStrain.Magnitude * (plate.IsLandform ? 1f : 0.5f);
var edgeDistance = vertexData.EdgeDistance * (plate.IsLandform ? 1f : -1f);
float height = 0.5f;
//height *= plate.PlateExpansion;
float mult = 2f;
height += hfNoise;
height = (height + 0.5f * mult) / (1f + mult);
height += continentalNoise;
height = (height + 0.5f * mult) / (1f + mult);
height += edgeDistance * 0.25f;
height = (height + 0.5f * mult) / (1f + mult);
height += normalRate * 0.35f;
height = Mathf.Clamp(height, 0.01f, 0.99f);
ColorVertex(vertexData.Id, Colors.White * height);
vertexData.StageComplete = true;
vertexData.Height = height;
}
if (!availableVerts.Any())
{
GD.Print($"Heights - min:'{Vertices.Min(v => v.Height)}' - max:'{Vertices.Max(v => v.Height)}' - average:'{Vertices.Average(v => v.Height)}'");
ScaleValues(Vertices);
foreach (VertexData vertexData in Vertices)
ColorVertex(vertexData.Id, Colors.White * vertexData.Height);
float oceanPercentage = Vertices.Count(v => v.Height < 0.5f) / (float)Vertices.Count;
GD.Print($"Ocean Percentage:'{oceanPercentage}'");
CompleteStage();
if (_meshInstance.GetSurfaceOverrideMaterial(0) is ShaderMaterial shaderMaterial)
{
shaderMaterial.SetShaderParameter("mode", 2);
}
if (_textureRect.Material is ShaderMaterial textureShaderMaterial)
{
textureShaderMaterial.SetShaderParameter("mode", 2);
}
}
}
public void ScaleValues(List<VertexData> values)
{
float maxDistance = Vertices.Max(s => Mathf.Abs(s.Height - 0.5f));
float scale = 0.5f/maxDistance;
values.ForEach(v => v.Height = Mathf.Clamp(0.5f + (v.Height - 0.5f) * scale, 0.01f, 0.99f));
GD.Print($"Heights Post Scaling - min:'{Vertices.Min(v => v.Height)}' - max:'{Vertices.Max(v => v.Height)}' - average:'{Vertices.Average(v => v.Height)}'");
}
public StrainAnalysis CalculateStrainMagnitude(Vector3 p1, Vector3 p2, Vector3 v1, Vector3 v2)
{
StrainAnalysis result = new StrainAnalysis();
Vector3 edge = p2 - p1;
float edgeLength = edge.Length();
if (edgeLength < float.Epsilon)
{
result.Magnitude = 0;
result.Type = StrainType.Shear; // Default
return result;
}
Vector3 relVelocity = v2 - v1;
float relVelMag = relVelocity.Length();
float dot = relVelocity.Dot(edge);
result.NormalRate = dot / edgeLength;
float normalRateSq = result.NormalRate * result.NormalRate;
float shearRateSq = relVelMag * relVelMag - normalRateSq;
result.ShearRate = (shearRateSq > 0) ? (float)Math.Sqrt(shearRateSq) : 0;
result.Magnitude = (float)Math.Sqrt(normalRateSq + shearRateSq);
float absNormal = Math.Abs(result.NormalRate);
float absShear = Math.Abs(result.ShearRate);
if (absNormal > absShear)
{
result.Type = result.NormalRate > 0
? StrainType.Tension
: StrainType.Compression;
}
else
{
result.Type = StrainType.Shear;
}
return result;
}
public static StrainAnalysis AverageStrainList(List<StrainAnalysis> strains)
{
if (strains == null || strains.Count == 0)
{
return new StrainAnalysis();
}
int count = strains.Count;
float sumMagnitude = 0;
float sumNormalRate = 0;
float sumShearRate = 0;
int tensionCount = 0;
int compressionCount = 0;
int shearCount = 0;
foreach (var s in strains)
{
sumMagnitude += s.Magnitude;
sumNormalRate += s.NormalRate;
sumShearRate += s.ShearRate;
switch (s.Type)
{
case StrainType.Tension:
tensionCount++;
break;
case StrainType.Compression:
compressionCount++;
break;
case StrainType.Shear:
shearCount++;
break;
}
}
float avgMagnitude = sumMagnitude / count;
float avgNormalRate = sumNormalRate / count;
float avgShearRate = sumShearRate / count;
StrainType averageType = StrainType.Shear;
int maxCount = 0;
if (tensionCount > maxCount) { maxCount = tensionCount; averageType = StrainType.Tension; }
if (compressionCount > maxCount) { maxCount = compressionCount; averageType = StrainType.Compression; }
if (shearCount > maxCount) { maxCount = shearCount; averageType = StrainType.Shear; }
return new StrainAnalysis
{
Magnitude = avgMagnitude,
Type = averageType,
NormalRate = avgNormalRate,
ShearRate = avgShearRate
};
}
public void AssignOceanPlates(List<PlateData> areas)
{
int n = areas.Count;
double totalArea = areas.Sum(a => a.Vertices.Count * a.PlateExpansion);
double targetOcean = totalArea * _landRatio;
double bestDiff = double.MaxValue;
int bestMask = 0;
int combinations = 1 << n;
for (int mask = 0; mask < combinations; mask++)
{
int oceanArea = 0;
for (int i = 0; i < n; i++)
{
if ((mask & (1 << i)) != 0)
oceanArea += (int)(areas[i].Vertices.Count * areas[i].PlateExpansion);
}
double diff = Math.Abs(oceanArea - targetOcean);
if (diff < bestDiff)
{
bestDiff = diff;
bestMask = mask;
}
}
for (int i = 0; i < n; i++)
{
areas[i].IsLandform = (bestMask & (1 << i)) != 0;
Color color = GetInitialColor(areas[i].IsLandform);
areas[i].Color = color;
foreach (int v in areas[i].Vertices)
{
ColorVertex(v, color);
}
}
}
public int GetFreeNeighbourIndex(VertexData vertexData)
{
foreach (int neighbour in vertexData.Neighbours)
{
if (Vertices[neighbour].PlateId == -1)
return neighbour;
}
return -1;
}
public Color GetInitialColor(bool isLand)
{
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);
return color;
}
public Vector3 GetVertexPosition(int vertexId)
{
return Mdt.GetVertex(vertexId);
}
public void CompleteStage()
{
StageComplete = true;
_generationStopwatch.Stop();
if (Stage != GenerationStage.NotStarted)
GD.Print($"'{Stage.ToString()}' took '{_generationStopwatch.Elapsed}'");
}
public void ColorVertex(int id, Color color)
{
Mdt.SetVertexColor(id, color);
}
public void UpdateMesh()
{
_arrayMesh.ClearSurfaces();
Mdt.CommitToSurface(_arrayMesh);
}
}

1
src/PlanetHelper.cs.uid Normal file
View file

@ -0,0 +1 @@
uid://d2661qrqttvva

64
src/Projector.cs Normal file
View file

@ -0,0 +1,64 @@
using Godot;
using System;
using System.Linq;
using System.Threading.Tasks;
public static class Projector
{
public static int[,] Points = new int[1024,512];
private static bool _gathered = false;
public static void GatherPoints(PlanetHelper helper, int resolutionH = 2048, bool regather = false)
{
if (!regather && _gathered)
return;
Points = new int[resolutionH,resolutionH / 2];
string filename = $"user://points-{resolutionH}-{resolutionH / 2}.dat";
if (FileAccess.FileExists(filename))
{
var readfile = FileAccess.Open(filename, FileAccess.ModeFlags.Read);
for (int x = 0; x < Points.GetLength(0); x++)
{
for (int y = 0; y < Points.GetLength(1); y++)
{
Points[x, y] = (int)readfile.Get32();
}
}
readfile.Close();
return;
}
Parallel.ForEach(Enumerable.Range(0, Points.GetLength(0)), x =>
{
for (int y = 0; y < Points.GetLength(1); y++)
{
float yaw = (float)x / Points.GetLength(0) * 360f;
float pitch = (float)y / Points.GetLength(1) * 180f;
Vector3 point = Vector3.Up;
point = point.Rotated(Vector3.Forward, Mathf.DegToRad(pitch));
point = point.Rotated(Vector3.Up, Mathf.DegToRad(yaw));
int index = helper.Octree.SearchNearest(point)?.Id ?? -1;
Points[x,y] = index;
}
});
var file = FileAccess.Open(filename, FileAccess.ModeFlags.Write);
for (int x = 0; x < Points.GetLength(0); x++)
for (int y = 0; y < Points.GetLength(1); y++)
file.Store32((uint)Points[x,y]);
_gathered = true;
file.Close();
}
public static ImageTexture Render(PlanetHelper helper)
{
var image = Image.CreateEmpty(Points.GetLength(0) + 1, Points.GetLength(1) + 1, false, Image.Format.Rgb8);;
for (int x = 0; x < Points.GetLength(0); x++)
{
for (int y = 0; y < Points.GetLength(1); y++)
{
image.SetPixel(x,y, helper.Mdt.GetVertexColor(Points[x,y]));
}
}
return ImageTexture.CreateFromImage(image);
}
}

1
src/Projector.cs.uid Normal file
View file

@ -0,0 +1 @@
uid://xjfkn5o2lo8l