I am trying to find curves that overlap with themselves in the image below, where all curves are projected onto the xy-plane. Even though the six curves within the red rectangle may seem like straight lines, they actually contain a kink where the curve is folded. (The starting points are denoted in red, while the ending points are shown in blue. The points at kinks are marked with arrows.)
My goal is to find any self-overlapping curves in this example. Once I have identified these curves, I would like to pinpoint the exact location where the curve folds so I can break them at that point. Do you have any suggestions for me?
I’m looking for C# code blocks, because this will be part of larger functions.
Thank you. It’s an interesting plugin, but I’m looking for a logic that I can apply in C#. I was curious if the plugin would work for my attached curves for testing. It didn’t work for these self-overlapping curves. I need to isolate curves that are folded and overlapped, and then locate the kink point where the curve folds.
I used the Intersection.CurveSelf method to detect intersection events, but only the last curve returned a non-zero count. All other curves had 0 intersection events.
I don’t think there is a method for that. I coded a recursive search along the curve, using dot product to find the locations where the curve changes direction.
Here’s a quick version, where I’m checking whether the X coordinate is increasing. If the ‘next X’ is smaller than the previous X coordinate, then it intersects (i.e. turns back). This solution relies on the fact that the curves have the same orientation. If you flip the curve, then it does not work. So harmonise the curve directions, if needed.
Evaluate each sharp corner in your curve and store parameters where angle flip.
Shatter your curve with that.
For nurbs curves, move the control points randomly with vectors that are perpendicular to the projection line, resulting curve will works fine with .ExtremeParameters method. Parameters found this way will apply also to your projected curve.
Uh, yes, if you have access to pre-projection curves, find parameters where curve change direction there, and apply same parameters to the projected curves. Much simpler.
I didn’t know about the ExtremeParameters method. Checking curve tangent direction changes between all curve local extrema might achieve what I’m trying to do. I’ll test the logic tomorrow based on this method. Thanks so much for the idea!
I also appreciate everyone who replied to my post. All solutions are great, but @maje90 's suggestion is closest to what I want to do.
I’ll probably need to test more to refine the code, but I could get what I wanted. I’m sharing my attempt to get non overlapping curves from self-overlapping curves for anyone interested in.
using System.Linq;
/// <summary>
/// This class will be instantiated on demand by the Script component.
/// </summary>
public class Script_Instance : GH_ScriptInstance
{
#region Utility functions
/// <summary>Print a String to the [Out] Parameter of the Script component.</summary>
/// <param name="text">String to print.</param>
private void Print(string text) { /* Implementation hidden. */ }
/// <summary>Print a formatted String to the [Out] Parameter of the Script component.</summary>
/// <param name="format">String format.</param>
/// <param name="args">Formatting parameters.</param>
private void Print(string format, params object[] args) { /* Implementation hidden. */ }
/// <summary>Print useful information about an object instance to the [Out] Parameter of the Script component. </summary>
/// <param name="obj">Object instance to parse.</param>
private void Reflect(object obj) { /* Implementation hidden. */ }
/// <summary>Print the signatures of all the overloads of a specific method to the [Out] Parameter of the Script component. </summary>
/// <param name="obj">Object instance to parse.</param>
private void Reflect(object obj, string method_name) { /* Implementation hidden. */ }
#endregion
#region Members
/// <summary>Gets the current Rhino document.</summary>
private readonly RhinoDoc RhinoDocument;
/// <summary>Gets the Grasshopper document that owns this script.</summary>
private readonly GH_Document GrasshopperDocument;
/// <summary>Gets the Grasshopper script component that owns this script.</summary>
private readonly IGH_Component Component;
/// <summary>
/// Gets the current iteration count. The first call to RunScript() is associated with Iteration==0.
/// Any subsequent call within the same solution will increment the Iteration count.
/// </summary>
private readonly int Iteration;
#endregion
/// <summary>
/// This procedure contains the user code. Input parameters are provided as regular arguments,
/// Output parameters as ref arguments. You don't have to assign output parameters,
/// they will have a default value.
/// </summary>
private void RunScript(Curve curve, ref object nonOverlapCrv)
{
// All local extreme curve parameters
List < double > extremes = curve.ExtremeParameters(curve.TangentAtStart).ToList();
// Add curve start and end parameters, if they're not included in all local extrema
int n = extremes.Count;
double tStart = curve.Domain.T0;
if (extremes[0] != tStart)
{
extremes.Insert(0, tStart);
n++; // Update the count
}
double tEnd = curve.Domain.T1;
if (extremes[n - 1] != tEnd)
{
extremes.Add(tEnd);
n++; // Update the count
}
// Set the maximum loop
int max = n;
// Initialize parameters
double t;
Curve crv = curve;
List < Curve > crvs = new List<Curve>{crv};
// Looping count
int count = 0;
// Looping the SelfOverlap function
for (int i = 0; i < max; i++)
{
if (SelfOverlap(crv, out t))
{
Curve[] splitCrvs = crv.Split(t);
if (splitCrvs != null)
{
crvs = splitCrvs.ToList();
crvs = crvs.OrderByDescending(c => c.GetLength()).ToList();
crv = crvs[0];
}
}
else
{
break;
}
// Update the looping count
count++;
}
// Use a different method to split the curve, if the first method is failed.
if (count == max && n > 2 && curve.IsClosed)
{
// Get all points at extrema
List<Point3d> extPts = extremes.Select(param => curve.PointAt(param)).ToList();
// Average point of all points at each extreme parameters
Point3d averagePt = extPts.Any() ? new Point3d(extPts.Average(p => p.X), extPts.Average(p => p.Y), extPts.Average(p => p.Z)) : Point3d.Unset;
// Get a list of distances between each extreme point and the average point in descending order
List<Tuple<double, int>> distances = extPts.Select((pt, index) => Tuple.Create(averagePt.DistanceTo(pt), index)).OrderByDescending(tuple => tuple.Item1).ToList();
// Split curve at two extreme points that are farthest apart from the average point
Curve[] splitCrvs1 = curve.Split(extremes[distances[0].Item2]);
if (splitCrvs1 != null)
{
crvs = splitCrvs1.ToList();
crvs = crvs.OrderByDescending(c => c.GetLength()).ToList();
crv = crvs[0];
Curve[] splitCrvs2 = crv.Split(extremes[distances[1].Item2]);
if (splitCrvs2 != null)
{
crvs = splitCrvs2.ToList();
crvs = crvs.OrderByDescending(c => c.GetLength()).ToList();
crv = crvs[0];
}
}
else
{
Curve[] splitCrvs2 = crv.Split(extremes[distances[1].Item2]);
if (splitCrvs2 != null)
{
crvs = splitCrvs2.ToList();
crvs = crvs.OrderByDescending(c => c.GetLength()).ToList();
crv = crvs[0];
}
}
}
// Output
nonOverlapCrv = crv;
}
// <Custom additional code>
// Function to check if a curve has self-overlap
public bool SelfOverlap(Curve curve, out double t)
{
// All local extreme curve parameters
List < double > extremes = curve.ExtremeParameters(curve.TangentAtStart).ToList();
// Add curve start and end parameters, if they're not included in all local extrema
int n = extremes.Count;
double tStart = curve.Domain.T0;
if (extremes[0] != tStart)
{
extremes.Insert(0, tStart);
n++; // Update the count
}
double tEnd = curve.Domain.T1;
if (extremes[n - 1] != tEnd)
{
extremes.Add(tEnd);
n++; // Update the count
}
// Initialize parameters
bool selfOverlap = false;
t = -1;
// Loop all local extrema searching for the tangent direction change, only when more than two extreme values exist.
if (n > 2)
{
// Vector to compare in dot product
Vector3d vec1 = curve.TangentAtStart;
Vector3d vec2 = curve.TangentAtEnd;
for (int i = 0; i < n; i++)
{
// Set the first tangent vector between the previous parameter and the current one
if (i > 0)
{
double t1 = (extremes[i - 1] + extremes[i]) / 2;
vec1 = curve.TangentAt(t1);
}
// Set the second tangent vector between the current parameter and the next one
if (i != n - 1)
{
double t2 = (extremes[i] + extremes[i + 1]) / 2;
vec2 = curve.TangentAt(t2);
}
// Dot product
double dotProd = Vector3d.Multiply(vec1, vec2);
// Set the boolean value and the curve parameter at the kink, when two tangent vectors are in different directions.
if (dotProd < 0)
{
selfOverlap = true;
t = extremes[i];
break;
}
}
}
// Result
return selfOverlap;
}
// </Custom additional code>
}