using FeMM.Grasshopper.Helpers;
using Grasshopper.Kernel;
using Rhino;
using Rhino.Geometry;
using Rhino.Geometry.Intersect;
using System;
using System.Collections.Generic;

namespace FeMM.Grasshopper.Components.Tools
{
    public class RemeshComponent : GH_Component
    {
        /// <summary>
        /// Initializes a new instance of the NodeComponent class.
        /// </summary>
        public RemeshComponent()
          : base("Remesh", "R", "Remesh an existing mesh", CategoryNameConstants.CATEGORY_FEMM, CategoryNameConstants.SUBCATEGORY_TOOLS)
        {
        }

        /// <summary>
        /// Registers all the input parameters for this component.
        /// </summary>
        protected override void RegisterInputParams(GH_InputParamManager pManager)
        {
            pManager.AddMeshParameter("Mesh", "M", "The mesh to remesh", GH_ParamAccess.item);
            pManager.AddNumberParameter("Target length", "TL", "The target length of the mesh", GH_ParamAccess.item);
            pManager.AddNumberParameter("Max distance", "MD", "Max distance to consider two points collinear", GH_ParamAccess.item, 100 * RhinoDoc.ActiveDoc.ModelAbsoluteTolerance);
            pManager.AddPlaneParameter("Plane", "P", "The plane where the geometry will be remeshed", GH_ParamAccess.item);
            pManager[pManager.ParamCount - 1].Optional = true;
        }

        /// <summary>
        /// Registers all the output parameters for this component.
        /// </summary>
        protected override void RegisterOutputParams(GH_OutputParamManager pManager)
        {
            pManager.AddGenericParameter("Mesh", "M", "The remeshed mesh", GH_ParamAccess.item);
        }

        /// <summary>
        /// This is the method that actually does the work.
        /// </summary>
        /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param>
        protected override void SolveInstance(IGH_DataAccess DA)
        {
            Mesh mesh = null;
            double targetLength = 0;
            double maxDistance = 0;
            Plane plane = Plane.Unset;
            if (!DA.GetData(0, ref mesh))
                return;
            if (!DA.GetData(1, ref targetLength))
                return;
            if (!DA.GetData(2, ref maxDistance))
                return;
            DA.GetData(3, ref plane);

            mesh.RebuildNormals();
            mesh.UnifyNormals();
            mesh.Normals.ComputeNormals();

            if (plane == Plane.Unset)
            {
                var normal = new Vector3d();
                foreach (Vector3d n in mesh.Normals)
                {
                    normal += n;
                }
                normal /= mesh.Normals.Count;
                plane = new Plane(mesh.Vertices[0], normal);
            }

            if (targetLength < 1000 * RhinoDoc.ActiveDoc.ModelAbsoluteTolerance)
            {
                AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Target length too small");
                return;
            }

            Polyline[] nakeds = mesh.GetNakedEdges();
            var plCurves = new List<PolylineCurve>();

            foreach (Polyline p in nakeds)
                plCurves.Add(new PolylineCurve(p));

            Curve[] joineds = Curve.JoinCurves(plCurves);
            joineds = Curve.JoinCurves(joineds);//2 is meglio che 1

            if (joineds.Length != 1 || !joineds[0].TryGetPolyline(out Polyline pl))
                throw new NotSupportedException("Mesh edge is not a close polyline");

            //Simplify polyline
            Vector3d prevDir;
            Vector3d nextDir = Vector3d.Unset;
            int startingIndex = -1;

            //finding a point on a kink
            for (int i = 0; i < pl.Count - 2; i++)
            {
                if (nextDir == Vector3d.Unset)
                {
                    prevDir = pl[i + 1] - pl[i];
                    prevDir.Unitize();
                }
                else
                {
                    prevDir = nextDir;
                }
                nextDir = pl[i + 2] - pl[i + 1];
                nextDir.Unitize();

                if (nextDir * prevDir < 0.95)
                {
                    //kink at i+1
                    startingIndex = i + 1;
                    break;
                }
            }
            if (startingIndex == -1)
                throw new NotSupportedException();

            int i0 = startingIndex;
            int iNext = i0;
            var cleanedPolyline = new Polyline { pl[startingIndex] };

            do
            {
                iNext++;

                if (iNext >= pl.Count)
                {
                    iNext = 0;
                }
                if (iNext == startingIndex)
                {
                    //closing 
                    cleanedPolyline.Add(pl[startingIndex]);
                    break;
                }
                bool add = false;
                Vector3d dir = pl[iNext] - pl[i0];
                dir.Unitize();
                if (iNext < i0)
                {
                    for (int i = i0 + 1; i < pl.Count; i++)
                    {
                        Vector3d diff = pl[i] - pl[i0];
                        Vector3d projection = diff * dir * dir;
                        Vector3d ortoProjection = diff - projection;
                        if (ortoProjection.SquareLength > maxDistance * maxDistance)
                        {
                            add = true;
                            break;
                        }
                    }

                    for (int i = 0; i < iNext; i++)
                    {
                        Vector3d diff = pl[i] - pl[i0];
                        Vector3d projection = diff * dir * dir;
                        Vector3d ortoProjection = diff - projection;
                        if (ortoProjection.SquareLength > maxDistance * maxDistance)
                        {
                            add = true;
                            break;
                        }
                    }
                }
                else
                {
                    for (int i = i0 + 1; i < iNext; i++)
                    {
                        Vector3d diff = pl[i] - pl[i0];
                        Vector3d projection = diff * dir * dir;
                        Vector3d ortoProjection = diff - projection;
                        if (ortoProjection.SquareLength > maxDistance * maxDistance)
                        {
                            add = true;
                            break;
                        }
                    }
                }
                if (add)
                {
                    if (iNext == 0)
                        iNext = pl.Count - 1;
                    else
                        iNext -= 1;

                    i0 = iNext;
                    cleanedPolyline.Add(pl[iNext]);
                }
            } while (true == true);

            //RhinoDoc.ActiveDoc.Objects.AddPolyline(cleanedPolyline);

            //adding points between cleaned points
            for (int i = cleanedPolyline.Count - 2; i > -1; i--)
            {
                Point3d origin = cleanedPolyline[i + 1];
                Vector3d diff = cleanedPolyline[i] - origin;
                if (diff.SquareLength > 1.3 * targetLength * 1.3 * targetLength)
                {
                    //adding points
                    double length = diff.Length;
                    int intervals = Convert.ToInt32(length / (1.3 * targetLength));
                    intervals++;
                    double delta = length / intervals;
                    diff.Unitize();

                    for (int j = 1; j < intervals; j++)
                        cleanedPolyline.Insert(i + 1, origin + diff * delta * j);
                }
            }

            //Flattening on plane
            var onPlane = new Polyline();
            for (int i = 0; i < cleanedPolyline.Count; i++)
                onPlane.Add(plane.ClosestPoint(cleanedPolyline[i]));

            //Creating new mesh
            MeshingParameters param = MeshingParameters.Default;
            param.MinimumEdgeLength = 0.8 * targetLength;
            param.MaximumEdgeLength = 0.9 * targetLength;

            Curve perimeter = onPlane.ToPolylineCurve();
            Mesh flattened = Mesh.CreateFromPlanarBoundary(perimeter, param, RhinoDoc.ActiveDoc.ModelAbsoluteTolerance);

            //moving to original mesh
            var projecteds = new List<Point3d>();
            for (int i = 0; i < flattened.Vertices.Count; i++)
            {
                Point3d v = (Point3d)flattened.Vertices[i];
                Point3d[] buffer = Intersection.ProjectPointsToMeshes(new Mesh[] { mesh }, new Point3d[] { v }, plane.Normal, 0);
                if (buffer.Length == 0)
                {
                    var lCurve = new LineCurve(v - plane.Normal * 1e10, v + plane.Normal * 1e10);
                    if (!lCurve.ClosestPoints(joineds[0], out Point3d pointOnThis, out Point3d pointOnOther))
                        throw new NotSupportedException();
                    projecteds.Add(pointOnOther);
                }
                else
                {
                    projecteds.Add(buffer[0]);
                }

            }

            for (int i = 0; i < flattened.Vertices.Count; i++)
                flattened.Vertices.SetVertex(i, projecteds[i]);

            flattened.Vertices.CombineIdentical(true, true);
            flattened.Faces.ConvertQuadsToTriangles();
            flattened.Weld(Math.PI);
            flattened.Normals.ComputeNormals();
            flattened.Compact();
            flattened.Smooth(1, true, true, false, true, SmoothingCoordinateSystem.Object);
            flattened.Weld(Math.PI);
            flattened.RebuildNormals();
            flattened.UnifyNormals();
            flattened.Normals.ComputeNormals();

            DA.SetData(0, flattened);
        }

        /// <summary>
        /// Provides an Icon for the component.
        /// </summary>
        protected override System.Drawing.Bitmap Icon => Properties.Resources.RemeshIcon;

        /// <summary>
        /// Gets the unique ID for this component. Do not change this ID after release.
        /// </summary>
        public override Guid ComponentGuid => new("4C4D90EE-FF74-4CF0-8FC7-B3A73E06512A");
    }
}
303 files24 directories