Detect self-overlapping curves

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.)


overlapped curves.gh (13.1 KB)

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.

Have you tried Make2d?

Hello,

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.

overlapped curves_C#.gh (16.8 KB)

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.

overlapped curves_comment.gh (19.6 KB)

2 Likes

I have absolutely not the time to try it out, but woudln’t a marching merchant and k-means combined work for this task?

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.

1 Like


overlapped curves re.gh (11.9 KB)
This works only with a single nurbs curve segment.

1 Like

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> 
}

overlapped curves_solved.gh (13.7 KB)

2 Likes