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

namespace FeMM.Grasshopper.Components.Patterning
{
    public class BordersComponent : GH_Component
    {


        /// <summary>
        /// Initializes a new instance of the BordersComponent class.
        /// </summary>
        public BordersComponent()
          : base("Borders", "Bs", "Extract the borders of a mesh, joined with a tolerance", CategoryNameConstants.CATEGORY_FEMM, CategoryNameConstants.SUBCATEGORY_PATTERNING)
        {
        }

        /// <summary>
        /// Registers all the input parameters for this component.
        /// </summary>
        protected override void RegisterInputParams(GH_InputParamManager pManager)
        {
            pManager.AddMeshParameter("Mesh", "M", "The mesh", GH_ParamAccess.item);
            pManager.AddAngleParameter("Angle", "A", "Tolerance angle of consecutive pieces of a border", GH_ParamAccess.item, 15);
            ((Param_Number)pManager[pManager.ParamCount - 1]).UseDegrees = true;
            pManager.AddBooleanParameter("Smooth", "S", "Smooth the polyline with a curve", GH_ParamAccess.item, false);
        }

        /// <summary>
        /// Registers all the output parameters for this component.
        /// </summary>
        protected override void RegisterOutputParams(GH_OutputParamManager pManager)
        {
            pManager.AddCurveParameter("Borders", "Bs", "The borders of the mesh", GH_ParamAccess.list);
        }

        /// <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 m = null;
            double angle = 0;
            bool smooth = true;

            if (!DA.GetData(0, ref m))
                return;
            if (!DA.GetData(1, ref angle))
                return;
            if (!DA.GetData(2, ref smooth))
                return;

            Param_Number angleParameter = Params.Input[1] as Param_Number;
            if (angleParameter != null)
            {
                if (angleParameter.UseDegrees)
                {
                    angle *= Math.PI / 180;
                }
            }
            else
            {
                return;
            }

            Polyline[] pls = m.GetNakedEdges();
            List<Polyline> borders = BreakAtPolylines(pls, angle);

            if (smooth)
            {
                List<Curve> curves;

                curves = [];
                foreach (Polyline pl in borders)
                {
                    List<Point3d> points;
                    points = [];
                    for (int i = 0; i < pl.Count - 1; i++)
                    {
                        points.Add(pl[i]);
                        //points.Add(0.5 * (pl[i] + pl[i + 1]));
                    }
                    points.Add(pl[pl.Count - 1]);
                    curves.Add(Curve.CreateControlPointCurve(points));
                }
                DA.SetDataList(0, curves);
            }
            else
            {
                DA.SetDataList(0, borders);
            }
        }

        internal static List<Polyline> BreakAtPolylines(Polyline[] pls, double angle)
        {
            //i don't use the follow command for the rest because it break also at start and end of the polyline
            var lines = new List<object>();
            for (int i = 0; i < pls.Length; i++)
            {
                Line[] array = pls[i].GetSegments();
                for (int j = 0; j < array.Length; j++)
                {
                    lines.Add(array[j]);
                }
            }

            var result = new List<Polyline>();

            do
            {
                var startingDir = new Vector3d();
                var endingDir = new Vector3d();
                Polyline seed = null;

                for (int i = 0; i < lines.Count; i++)
                {
                    if (lines[i] != null)
                    {
                        Line line;
                        line = (Line)lines[i];
                        seed = new Polyline(new Point3d[] { line.PointAt(0), line.PointAt(1) });
                        startingDir = line.Direction;
                        endingDir = startingDir;
                        lines[i] = null;
                        break;
                    }
                }

                if (seed == null)
                {
                    break;
                }

                do
                {
                    double TOLL = RhinoDoc.ActiveDoc.ModelAbsoluteTolerance;
                    bool exit = true;
                    for (int i = 0; i < lines.Count; i++)
                    {
                        if (lines[i] != null)
                        {
                            Line line = (Line)lines[i];
                            Point3d startPI = line.PointAt(0);
                            Point3d lastPI = line.PointAt(1);
                            Vector3d dir = line.Direction;

                            if (startPI.DistanceToSquared(seed.Last) < TOLL && dir.IsParallelTo(endingDir, angle) == 1)
                            {
                                //first attached to last of seed
                                seed.Add(lastPI);
                                endingDir = dir;
                                lines[i] = null;
                                exit = false;
                                break;
                            }
                            else if (lastPI.DistanceToSquared(seed.Last) < TOLL && dir.IsParallelTo(endingDir, angle) == -1)
                            {
                                //last attached to last of seed
                                seed.Add(startPI);
                                endingDir = -dir;
                                lines[i] = null;
                                exit = false;
                                break;
                            }
                            else if (lastPI.DistanceToSquared(seed.First) < TOLL && dir.IsParallelTo(startingDir, angle) == 1)
                            {
                                //last attached to first of seed
                                seed.Insert(0, startPI);
                                startingDir = dir;
                                lines[i] = null;
                                exit = false;
                                break;
                            }
                            else if (startPI.DistanceToSquared(seed.First) < TOLL && dir.IsParallelTo(startingDir, angle) == -1)
                            {
                                //start attached to first of seed
                                seed.Insert(0, lastPI);
                                startingDir = -dir;
                                lines[i] = null;
                                exit = false;
                                break;
                            }
                        }
                    }
                    if (exit)
                    {
                        break;
                    }

                } while (true == true);

                result.Add(seed);

            } while (true == true);

            return result;
        }

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

        /// <summary>
        /// Gets the unique ID for this component. Do not change this ID after release.
        /// </summary>
        public override Guid ComponentGuid => new("49141274-7CAD-44B1-8C31-BA5D2C9D74DC");
    }
}
303 files24 directories