using FeMM.Grasshopper.Helpers;
using Grasshopper.Kernel;
using Rhino.Geometry;
using System;
using System.Collections.Generic;
using System.Linq;
namespace FeMM.Grasshopper.Components.Tools
{
public class SurfaceSplitterComponent : GH_Component
{
/// <summary>
/// Initializes a new instance of the SuperElementComponent class.
/// </summary>
public SurfaceSplitterComponent()
: base("Surface splitter curves", "SSC", "Get surface splitter curves based on internal points/lines", CategoryNameConstants.CATEGORY_FEMM, CategoryNameConstants.SUBCATEGORY_TOOLS)
{
}
/// <summary>
/// Registers all the input parameters for this component.
/// </summary>
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddBrepParameter("Surface", "S", "Surface to split", GH_ParamAccess.item);
pManager.AddPointParameter("Points", "Ps", "Points internal to surface", GH_ParamAccess.list);
pManager[pManager.ParamCount - 1].Optional = true;
pManager.AddLineParameter("Lines", "Ls", "Lines internal to surface", GH_ParamAccess.list);
pManager[pManager.ParamCount - 1].Optional = true;
pManager.AddNumberParameter("Tolerance", "T", "On border tolerance", GH_ParamAccess.item);
}
/// <summary>
/// Registers all the output parameters for this component.
/// </summary>
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddCurveParameter("Cuttings", "Cs", "The cutting curves", 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)
{
Brep surface = null;
var points = new List<Point3d>();
var lines = new List<Line>();
double toll = 0;
if (!DA.GetData(0, ref surface))
return;
DA.GetDataList(1, points);
DA.GetDataList(2, lines);
if (!DA.GetData(3, ref toll))
return;
try
{
if (surface.Faces.Count != 1)
{
throw new NotSupportedException("Brep composed by more than 1 face not admitted");
}
BrepFace bf = surface.Faces[0];
if (!bf.TryGetPlane(out Plane plane))
{
throw new NotSupportedException("Brep composed by more than 1 face not admitted");
}
Transform toPlane = Transform.PlaneToPlane(plane, Plane.WorldXY);
var edges = new List<Edge>();
if (!bf.OuterLoop.To3dCurve().TryGetPolyline(out Polyline polyline))
{
throw new NotSupportedException("Brep exteral/internal perimeter must be polylines");
}
if (!polyline.IsClosed)
polyline.Add(polyline[0]);
Point2d nextP = Point2d.Unset;
polyline.Transform(toPlane);
//see area sign
var poly = new Maffeis.Geometry.Polygon2d();
for (int i = 0; i < polyline.Count; i++)
{
Point3d p = polyline[i];
poly.Add(p.X, p.Y);
}
if (!poly.IsRightHandOrdered())
{
//outer, must be right oriented (left orto internal)
polyline.Reverse();
}
for (int i = 0; i < polyline.Count - 1; i++)
{
Point2d p;
if (nextP == Point2d.Unset)
{
p = new Point2d(polyline[i].X, polyline[i].Y);
}
else
{
p = nextP;
}
nextP = new Point2d(polyline[i + 1].X, polyline[i + 1].Y);
edges.Add(new Edge(p, nextP));
}
//adding internal nodes
var nodes = new List<Node>();
for (int i = 0; i < points.Count; i++)
{
var planed = new Point3d(points[i]);
planed.Transform(toPlane);
var point = new Point2d(planed.X, planed.Y);
nodes.Add(new Node(point, edges));
}
//adding internal lines nodes
for (int i = 0; i < lines.Count; i++)
{
Line l = lines[i];
var planedFrom = new Point3d(l.From);
var planedTo = new Point3d(l.To);
planedFrom.Transform(toPlane);
planedTo.Transform(toPlane);
var pointFrom = new Point2d(planedFrom.X, planedFrom.Y);
var pointTo = new Point2d(planedTo.X, planedTo.Y);
var nodeFrom = new Node(pointFrom, edges);
var nodeTo = new Node(pointTo, edges);
nodeFrom.ForcedNext = nodeTo;
nodeTo.ForcedNext = nodeFrom;
nodes.Add(nodeFrom);
nodes.Add(nodeTo);
}
var sortedNodes = new List<KeyValuePair<double, Node>>();
for (int i = 0; i < nodes.Count; i++)
{
sortedNodes.Add(new KeyValuePair<double, Node>(nodes[i].NextEdges[0].Key, nodes[i]));
}
sortedNodes.Sort(new KvpComparer());
double squareTol = Maffeis.Utilities.Maths.ErrorPropagation.DefaultProductSquareTolerance(toll);
var sequences = new List<Polyline>();
do
{
Node last = null;
if (sortedNodes.Count == 0)
break;
last = sortedNodes[0].Value;
sortedNodes.RemoveAt(0);
if (last == null)
break;
var sequence = new List<Node> { last };
do
{
Node nearest = null;
if (last.ForcedNext == null)
{
//finding nearest nodes that do not cut edges
double minDistance = double.MaxValue;
for (int i = 0; i < sortedNodes.Count; i++)
{
Node other = sortedNodes[i].Value;
if (other != null && other != last && !last.IntersectEdge(edges, other))
{
bool add;
double dist = (other.Pos - last.Pos).Length;
//see if have a good angle
if (sequence.Count < 2)
{
add = true;
}
else
{
add = IsGoodAngle(sequence[sequence.Count - 2].Pos, sequence[sequence.Count - 1].Pos, other.Pos);
}
if (add)
{
//see if intersect something other
for (int j = 0; j < sequence.Count - 2; j++)
{
if (IsSegmentiIntersecanti(sequence[j].Pos, sequence[j + 1].Pos, sequence[sequence.Count - 1].Pos, other.Pos))
{
add = false;
break;
}
}
if (add && (nearest == null || minDistance > dist))
{
nearest = other;
minDistance = dist;
}
}
}
}
}
else
{
if (sequence.Count < 2 || last.ForcedNext != sequence[sequence.Count - 2]) nearest = last.ForcedNext;
}
if (nearest == null)
{
//ended sequence
var seq = new Polyline();
//start point
Point2d pointOnFirstEdge = Point2d.Unset;
for (int i = 0; i < sequence[0].NextEdges.Count; i++)
{
pointOnFirstEdge = sequence[0].NextEdges[i].Value.Key;
if ((pointOnFirstEdge - sequence[0].Pos).SquareLength > squareTol)
{
bool add = true;
if (sequence.Count > 1)
{
add = IsGoodAngle(pointOnFirstEdge, sequence[0].Pos, sequence[1].Pos);
}
if (add)
{
seq.Add(pointOnFirstEdge.X, pointOnFirstEdge.Y, 0);
break;
}
}
else
{
//no need to add
pointOnFirstEdge = sequence[0].Pos;
break;
}
}
for (int i = 0; i < sequence.Count; i++)
{
seq.Add(sequence[i].Pos.X, sequence[i].Pos.Y, 0);
}
for (int i = 0; i < sequence[sequence.Count - 1].NextEdges.Count; i++)
{
Point2d pointOnLastEdge = sequence[sequence.Count - 1].NextEdges[i].Value.Key;
if ((pointOnLastEdge - sequence[sequence.Count - 1].Pos).SquareLength > squareTol)
{
if ((pointOnLastEdge - pointOnFirstEdge).SquareLength > squareTol)
{
bool add = true;
if (sequence.Count > 1)
{
add = IsGoodAngle(sequence[sequence.Count - 2].Pos, sequence[sequence.Count - 1].Pos, pointOnLastEdge);
}
if (add)
{
seq.Add(pointOnLastEdge.X, pointOnLastEdge.Y, 0);
break;
}
}
}
else
{
//no eed to add
break;
}
}
sequences.Add(seq);
break;
}
else
{
sequence.Add(nearest);
last = nearest;
for (int i = sortedNodes.Count - 1; i > -1; i--)
{
if (sortedNodes[i].Value == nearest)
{
sortedNodes.RemoveAt(i);
break;
}
}
//see if the node intersect other sequences
bool endSequence = false;
for (int j = 0; j < sequences.Count; j++)
{
Polyline seq = sequences[j];
for (int i = 0; i < seq.Count - 1; i++)
{
var a0 = new Point2d(seq[i].X, seq[i].Y);
var a1 = new Point2d(seq[i + 1].X, seq[i + 1].Y);
Vector2d dir = a1 - a0;
double length = dir.Length;
dir.Unitize();
Point2d onLineP = GetNearest(last.Pos, a0, a1, dir, length);
if ((onLineP - last.Pos).SquareLength < squareTol)
{
endSequence = true;
break;
}
}
if (endSequence) break;
}
if (endSequence)
{
//ended sequence
var seq = new Polyline();
//end the sequence because touch other nodes. sequence interrupt here
//start point
Point2d pointOnFirstEdge = sequence[0].NextEdges[0].Value.Key;
if ((pointOnFirstEdge - sequence[0].Pos).SquareLength > squareTol)
{
seq.Add(pointOnFirstEdge.X, pointOnFirstEdge.Y, 0);
}
for (int i = 0; i < sequence.Count; i++)
{
seq.Add(sequence[i].Pos.X, sequence[i].Pos.Y, 0);
}
sequences.Add(seq);
break;
}
}
} while (true == true);
} while (true == true);
Transform inverse = Transform.PlaneToPlane(Plane.WorldXY, plane);
var curves = new List<PolylineCurve>();
for (int i = 0; i < sequences.Count; i++)
{
Polyline pl = sequences[i];
var plT = new Polyline(pl);
plT.Transform(inverse);
curves.Add(new PolylineCurve(plT));
}
DA.SetDataList(0, curves);
}
catch (Exception ex)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, ex.Message);
return;
}
}
private class KvpComparer : IComparer<KeyValuePair<double, Node>>
{
public int Compare(KeyValuePair<double, Node> x, KeyValuePair<double, Node> y)
{
return x.Key.CompareTo(y.Key);
}
}
private static bool IsSegmentiIntersecanti(Point2d a0, Point2d a1, Point2d b0, Point2d b1)
{
Vector2d a0a1 = a1 - a0;
Vector2d a0b1 = b1 - a0;
Vector2d a0b0 = b0 - a0;
if (GetVectZ(a0a1, a0b1) * GetVectZ(a0a1, a0b0) < 0)
{
Vector2d b0b1, b0a1, b0a0;
b0b1 = b1 - b0;
b0a1 = a1 - b0;
b0a0 = a0 - b0;
return GetVectZ(b0b1, b0a1) * GetVectZ(b0b1, b0a0) < 0;
}
else
{
return false;
}
}
private static double GetVectZ(Vector2d first, Vector2d second)
{
return first.X * second.Y - first.Y * second.X;
}
private static bool IsGoodAngle(Point2d p0, Point2d p1, Point2d p2)
{
Vector2d lastDir = p1 - p0;
lastDir.Unitize();
Vector2d nextDir = p2 - p1;
nextDir.Unitize();
double scalar = lastDir * nextDir;
return scalar > -0.7 && scalar < 0.95;
}
private static Point2d GetNearest(Point2d p, Point2d a0, Point2d a1, Vector2d direction, double length)
{
double position = (p - a0) * direction;
Point2d result;
if (position > 0 && position < length)
{
//internal
result = a0 + position * direction;
}
else if (position <= 0)
{
result = a0;
}
else
{
result = a1;
}
return result;
}
private class Edge
{
public readonly Point2d IniP;
public readonly Point2d EndP;
public readonly Vector2d Direction;
public readonly Vector2d InternalOrto;//internal direction always in left side
public readonly double Length;
public Edge(Point2d iniP, Point2d endP)
{
IniP = iniP;
EndP = endP;
Direction = EndP - IniP;
Direction.Unitize();
InternalOrto = new Vector2d(-Direction.Y, Direction.X);
Length = (EndP - IniP).Length;
}
}
private class Node
{
public readonly Point2d Pos;
public readonly List<KeyValuePair<double, KeyValuePair<Point2d, Edge>>> NextEdges;
public Node ForcedNext;
public Node(Point2d pos, IEnumerable<Edge> edges)
{
Pos = pos;
NextEdges = null;
ForcedNext = null;
Edge[] array = [.. edges];
for (int i = 0; i < array.Length; i++)
{
Edge e = array[i];
Point2d nearestP = GetNearest(pos, e.IniP, e.EndP, e.Direction, e.Length);
//try find intersection with oter edges
bool intersect = false;
for (int j = 0; j < array.Length; j++)
{
Edge other = array[j];
if (other != e)
{
if (IsSegmentiIntersecanti(pos, nearestP, other.IniP, other.EndP))
{
intersect = true;
break;
}
}
}
if (!intersect)
{
NextEdges ??= [];
NextEdges.Add(new KeyValuePair<double, KeyValuePair<Point2d, Edge>>((nearestP - pos).Length, new KeyValuePair<Point2d, Edge>(nearestP, e)));
}
}
if (NextEdges == null)
throw new NotSupportedException("Point without next edges");
NextEdges.Sort(new KvpComparer());
}
public bool IntersectEdge(List<Edge> edges, Node other)
{
for (int i = 0; i < edges.Count; i++)
{
if (IsSegmentiIntersecanti(Pos, other.Pos, edges[i].IniP, edges[i].EndP))
{
return true;
}
}
return false;
}
private class KvpComparer : IComparer<KeyValuePair<double, KeyValuePair<Point2d, Edge>>>
{
public int Compare(KeyValuePair<double, KeyValuePair<Point2d, Edge>> x, KeyValuePair<double, KeyValuePair<Point2d, Edge>> y)
{
return x.Key.CompareTo(y.Key);
}
}
}
public override GH_Exposure Exposure => GH_Exposure.quarternary;
/// <summary>
/// Provides an Icon for the component.
/// </summary>
protected override System.Drawing.Bitmap Icon => Properties.Resources.SuperElementIcon;
/// <summary>
/// Gets the unique ID for this component. Do not change this ID after release.
/// </summary>
public override Guid ComponentGuid => new("0C1C9946-28D3-4B06-BFB3-148E57349469");
}
}