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,     // Pulling apart
        Compression, // Pushing together
        Shear         // Sliding past each other
    }
    public class StrainAnalysis
    {
        public float Magnitude;      // Total magnitude of the strain
        public StrainType Type;      // The dominant type of force
        public float NormalRate;    // Rate of convergence/divergence
        public float ShearRate;     // Rate of sliding
    }

    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)
    {
        // 1. Normalize the input point to ensure it represents the normal vector
        // (If your sphere is not at 0,0,0, use the vector from center to point)
        Vector3 normal = pointOnSphere.Normalized();

        // 2. Generate a random vector
        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)
        );

        // 3. Calculate the tangent using Cross Product
        // Cross Product of (randomVec, normal) gives a vector perpendicular to both.
        // Since it is perpendicular to the normal, it is tangential to the sphere.
        Vector3 tangent = randomVec.Cross(normal);

        // 4. Edge Case Handling
        // If the random vector happens to be parallel to the normal, 
        // the cross product will result in a zero vector.
        if (tangent.Dot(tangent) < 1e-6f)
        {
            // If parallel, force a non-zero vector by modifying the input slightly
            // or simply recurse (try again). Here we force a modification for safety.
            randomVec = new Vector3(0, 1, 0); 
            tangent = randomVec.Cross(normal);
        }

        // 5. Normalize to ensure unit length
        Vector3 normalizedTangent = tangent.Normalized();

        // 6. Scale by the desired speed
        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();

        // 1. Geometry and Relative Velocity
        Vector3 edge = p2 - p1;
        float edgeLength = edge.Length();

        // If points are identical, strain is zero
        if (edgeLength < float.Epsilon)
        {
            result.Magnitude = 0;
            result.Type = StrainType.Shear; // Default
            return result;
        }

        Vector3 relVelocity = v2 - v1;
        float relVelMag = relVelocity.Length();

        // 2. Calculate Components
        // Normal component: How much they are moving parallel to the edge (Pulling apart/Pushing together)
        // We project relVelocity onto edge vector
        float dot = relVelocity.Dot(edge);
        result.NormalRate = dot / edgeLength;

        // Shear component: How much they are moving perpendicular to the edge (Sliding)
        // Formula: v_tangent = sqrt(|v|^2 - (v_normal)^2)
        // Note: We use the magnitude of the relVelocity for the subtraction to avoid float errors
        float normalRateSq = result.NormalRate * result.NormalRate;
        float shearRateSq = relVelMag * relVelMag - normalRateSq;
        result.ShearRate = (shearRateSq > 0) ? (float)Math.Sqrt(shearRateSq) : 0;

        // 3. Determine Magnitude
        // Total strain = sqrt(normal^2 + shear^2)
        result.Magnitude = (float)Math.Sqrt(normalRateSq + shearRateSq);

        // 4. Classification Logic
        // Compare the absolute values to see which force is "dominant"
        float absNormal = Math.Abs(result.NormalRate);
        float absShear = Math.Abs(result.ShearRate);

        if (absNormal > absShear)
        {
            // Dominant force is edge-wise
            result.Type = result.NormalRate > 0 
                ? StrainType.Tension   // Moving apart
                : StrainType.Compression; // Moving together
        }
        else
        {
            // Dominant force is lateral (sliding)
            result.Type = StrainType.Shear;
        }

        return result;
    }
    
    public static StrainAnalysis AverageStrainList(List<StrainAnalysis> strains)
    {
        if (strains == null || strains.Count == 0)
        {
            return new StrainAnalysis(); // Return default if empty
        }

        int count = strains.Count;

        float sumMagnitude = 0;
        float sumNormalRate = 0;
        float sumShearRate = 0;

        // Counters for the Type
        int tensionCount = 0;
        int compressionCount = 0;
        int shearCount = 0;

        foreach (var s in strains)
        {
            sumMagnitude += s.Magnitude;
            sumNormalRate += s.NormalRate;
            sumShearRate += s.ShearRate;

            // Count occurrences of each type
            switch (s.Type)
            {
                case StrainType.Tension:
                    tensionCount++;
                    break;
                case StrainType.Compression:
                    compressionCount++;
                    break;
                case StrainType.Shear:
                    shearCount++;
                    break;
            }
        }

        // Calculate numerical averages
        float avgMagnitude = sumMagnitude / count;
        float avgNormalRate = sumNormalRate / count;
        float avgShearRate = sumShearRate / count;

        // Determine the most common StrainType (Mode)
        StrainType averageType = StrainType.Shear; // Default
        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);
    }
}
