mcneel / rhinocommon

RhinoCommon is the .NET SDK for Rhino5 / Grasshopper
http://wiki.mcneel.com/developer/rhinocommon
243 stars 93 forks source link

Possible bug in Curve.InsertKnot(double, int) #99

Open mennodeij opened 12 years ago

mennodeij commented 12 years ago

I think I may have found a bug when inserting kinks to a curve. I'm relatively new to both NURBS curves and RhinoCommon, so maybe I'm just doing things wrong.

The problem is that when I try to insert a kink (i.e. a knot of multiplicity equal to the curve degree) it sometimes does not work when an existing knot is already present that has a knot value that is very close to the new knot value.

In the code below the knot insertion does not work when the knot value is 2 - epsilon (even though the return value is true!), but it does work when the knot value is 2+epsilon (where epsilon is RhinoMath.ZeroTolerance). It also works with knot value of 2.0 by the way. So, I'm a bit puzzled whether this is a bug or not, but is seems a bit strange.

        NurbsCurve curve = new NurbsCurve(3, 6);
        curve.Points[0] = new ControlPoint(0, 0, 0);
        curve.Points[1] = new ControlPoint(0, 0, 1);
        curve.Points[2] = new ControlPoint(0, 1, 1);
        curve.Points[3] = new ControlPoint(0, 1, 0);
        curve.Points[4] = new ControlPoint(1, 1, 0);
        curve.Points[5] = new ControlPoint(1, 1, 1);

        curve.Knots[0] = 0;
        curve.Knots[1] = 0;
        curve.Knots[2] = 0;
        curve.Knots[3] = 1;
        curve.Knots[4] = 2;
        curve.Knots[5] = 3;
        curve.Knots[6] = 3;
        curve.Knots[7] = 3;

        int nKnotsStart = curve.Knots.Count;
        double t = 2.0 - RhinoMath.ZeroTolerance;
        bool ok = curve.Knots.InsertKnot(t, 3);

        // no new knot(s) inserted
        Console.WriteLine("Before {0} insertion at {1} there are {2} knots. After insertion there are {3} knots.", 
            ok ? "successful" : "failed", t, nKnotsStart, curve.Knots.Count );

        nKnotsStart = curve.Knots.Count;
        t = 2.0 + RhinoMath.ZeroTolerance;
        ok = curve.Knots.InsertKnot(t, 3);

        // new knot(s) inserted
        Console.WriteLine("Before {0} insertion at {1} there are {2} knots. After insertion there are {3} knots.",
            ok ? "successful" : "failed", t, nKnotsStart, curve.Knots.Count);
mennodeij commented 12 years ago

I understand now where this is coming from: the t-value where the knot is inserted is only checked for equality on the lower limit of a span, not on the upper limit of a span. Still not sure if this is a bug or not :-)

I added the following extension method to circumvent this problem.

/// <summary>
/// Insert a knot at given t-value. If the knot is within tolerance of an existing knot, the 
/// knot value of the existing knot is taken when inserting a new knot.
/// </summary>
/// <param name="list"></param>
/// <param name="knotValue"></param>
/// <param name="knotMultiplicity"></param>
/// <param name="tolerance"></param>
/// <returns></returns>
public static bool InsertKnotWithTolerance(this NurbsCurveKnotList list, double knotValue, int knotMultiplicity = 1, double tolerance = RhinoMath.ZeroTolerance)
{
    if (knotMultiplicity < 1)
        throw new ArgumentException("Knot multiplicity cannot be < 1. ", "knotMultiplicity");

    foreach (double knot in list)
    {
        if (Math.Abs(knot - knotValue) < tolerance)
        {
            knotValue = knot;
            break;
        }
    }

    if (knotMultiplicity > 1)
        return list.InsertKnot(knotValue, knotMultiplicity);

    return list.InsertKnot(knotValue);

}