001/**
002 * NurbsCurve -- Implementation and interface in common to all NURBS curves.
003 *
004 * Copyright (C) 2009-2025, by Joseph A. Huwaldt. All rights reserved.
005 *
006 * This library is free software; you can redistribute it and/or modify it under the terms
007 * of the GNU Lesser General Public License as published by the Free Software Foundation;
008 * either version 2.1 of the License, or (at your option) any later version.
009 *
010 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
012 * PARTICULAR PURPOSE. See the GNU Library General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public License along with
015 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place -
016 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html
017 *
018 * This source file is based on, but heavily modified from, the LGPL jgeom (Geometry
019 * Library for Java) code by Samuel Gerber, Copyright (C) 2005.
020 */
021package geomss.geom.nurbs;
022
023import geomss.geom.*;
024import jahuwaldt.js.param.Parameter;
025import jahuwaldt.tools.math.BracketRoot1D;
026import jahuwaldt.tools.math.Evaluatable1D;
027import jahuwaldt.tools.math.MathTools;
028import jahuwaldt.tools.math.RootException;
029import java.text.MessageFormat;
030import java.util.ArrayList;
031import java.util.Collections;
032import java.util.List;
033import static java.util.Objects.nonNull;
034import static java.util.Objects.requireNonNull;
035import java.util.logging.Level;
036import java.util.logging.Logger;
037import javax.measure.quantity.Area;
038import javax.measure.quantity.Dimensionless;
039import javax.measure.quantity.Length;
040import javax.measure.unit.Unit;
041import javolution.context.ObjectFactory;
042import javolution.context.StackContext;
043import javolution.lang.MathLib;
044import javolution.text.Text;
045import javolution.text.TextBuilder;
046import javolution.util.FastList;
047import javolution.util.FastMap;
048import javolution.util.FastTable;
049import javolution.xml.XMLFormat;
050import javolution.xml.stream.XMLStreamException;
051import org.jscience.mathematics.number.Float64;
052import org.jscience.mathematics.vector.Float64Vector;
053
054/**
055 * The interface and implementation in common to all NURBS curves.
056 *
057 * <p> Modified by: Joseph A. Huwaldt </p>
058 *
059 * @author Joseph A. Huwaldt, Date: May 28, 2009
060 * @version February 17, 2025
061 */
062@SuppressWarnings({"serial", "CloneableImplementsClone"})
063public abstract class NurbsCurve extends AbstractCurve<NurbsCurve> {
064
065    /**
066     * Return a list of control points for this curve.
067     *
068     * @return the ordered control points
069     */
070    public abstract List<ControlPoint> getControlPoints();
071
072    /**
073     * Return the knot vector of this curve.
074     *
075     * @return The knot vector.
076     */
077    public abstract KnotVector getKnotVector();
078
079    /**
080     * Return the degree of the NURBS curve.
081     *
082     * @return degree of curve
083     */
084    public abstract int getDegree();
085
086    /**
087     * Return an immutable version of this NURBS curve.
088     *
089     * @return an immutable version of this curve.
090     */
091    public abstract BasicNurbsCurve immutable();
092    
093    /**
094     * Return a new curve that is identical to this one, but with the parameterization
095     * reversed.
096     *
097     * @return A new curve that is identical to this one but with the parameterization
098     *         reversed.
099     */
100    @Override
101    public NurbsCurve reverse() {
102        List<ControlPoint> oldCPs = getControlPoints();
103
104        //  Reverse the order of the control points.
105        FastTable<ControlPoint> cps = FastTable.newInstance();
106        for (int i = oldCPs.size() - 1; i >= 0; --i)
107            cps.add(oldCPs.get(i));
108        FastTable.recycle((FastTable)oldCPs);
109
110        //  Reverse the order of the knot vector.
111        KnotVector kv = getKnotVector().reverse();
112
113        //  Return a new curve.
114        BasicNurbsCurve curve = BasicNurbsCurve.newInstance(cps, kv);
115        copyState(curve);   //  Copy over the super-class state for this curve to the new one.
116
117        FastTable.recycle(cps);
118        return curve;
119    }
120
121    /**
122     * Create and return a new {@link BasicNurbsCurve NURBS curve} that is geometrically
123     * identical to this one but with a new knot inserted at the specified parametric
124     * location. The parameterization of the new curve is identical to this curve, but the
125     * new curve will have a new knot at the specified location and a new set of control
126     * points.
127     *
128     * @param s             The parametric location where the knot is to be inserted.
129     * @param numInsertions The number of times that the knot should be inserted at the
130     *                      specified position.
131     * @return A new NURBS curve that is identical to this one, but with a new knot
132     *         inserted at the specified location.
133     */
134    public BasicNurbsCurve insertKnot(double s, int numInsertions) {
135        //  Algorithm A5.1, pg. 151, Piegl, L., Tiller, W., The Nurbs Book, 2nd Edition, Springer-Verlag, Berlin, 1997.
136
137        validateParameter(s);
138        if (numInsertions < 1)
139            throw new IllegalArgumentException(RESOURCES.getString("numInsertionsLE0Err"));
140
141        StackContext.enter();
142        try {
143            //  Construct the new knot vector by inserting the specified parametric position
144            //  into the list of knots.
145            KnotVector SP = getKnotVector();
146            int numKnots = SP.length();
147            int p = SP.getDegree();
148            int order = p + 1;
149            int r = numInsertions;
150
151            //  Find where in the knot vector the new knot lies.
152            int k = 0;
153            for (int i = 0; i < numKnots; ++i) {
154                if (SP.getValue(i) > s) {
155                    k = i - 1;
156                    break;
157                }
158            }
159
160            //  Determine the multiplicity of this knot.
161            int sm = 0;
162            if (s <= SP.getValue(k))
163                sm = SP.findMultiplicity(k);
164
165            if ((r + sm) > order)
166                r = order - sm;
167
168            if (r <= 0)
169                return StackContext.outerCopy((BasicNurbsCurve)this.copyToReal());
170
171            //  Load the new knot vector.
172            FastTable<Float64> SQList = FastTable.newInstance();
173            for (int i = 0; i <= k; ++i) {
174                SQList.add(SP.get(i));
175            }
176
177            for (int i = 0; i < r; ++i) {
178                SQList.add(Float64.valueOf(s));
179            }
180
181            for (int i = k + 1; i < numKnots; ++i) {
182                SQList.add(SP.get(i));
183            }
184
185            KnotVector SQ = KnotVector.newInstance(p, Float64Vector.valueOf(SQList));
186
187            //  Save unaltered control points.
188            List<ControlPoint> Pw = getControlPoints();
189            int np = Pw.size();
190            FastTable<ControlPoint> Qw = FastTable.newInstance();
191            Qw.setSize(np + r);
192
193            for (int i = 0; i <= k - p; ++i) {
194                Qw.set(i, Pw.get(i));
195            }
196
197            for (int i = k - sm; i < np; ++i) {
198                Qw.set(i + r, Pw.get(i));
199            }
200
201            //  Temporary storage for altered control points.
202            FastTable<ControlPoint> Rw = FastTable.newInstance();
203            Rw.setSize(order);
204            for (int i = 0; i <= p - sm; ++i) {
205                Rw.set(i, Pw.get(k - p + i).applyWeight());     //  Project them from NURBS to B-Spline.
206            }
207
208            //  Insert the knot r times.
209            int L = 0;
210            for (int j = 1; j <= r; ++j) {
211                L = k - p + j;
212                int pmjms = p - j - sm;
213                for (int i = 0; i <= pmjms; ++i) {
214                    //  Defining new control points in terms of old.
215                    //  alpha = (s - SP(L+i))/(SP(i+k+1) - SP(L+i))
216                    //  Rw(i) = alpha*Rw[i+1] + (1.0 - alpha)*Rw[i]
217                    double alpha = (s - SP.getValue(L + i)) / (SP.getValue(i + k + 1) - SP.getValue(L + i));
218
219                    ControlPoint Rwi = Rw.get(i);
220                    Rwi = Rwi.times(1.0 - alpha);
221                    ControlPoint Rwip1 = Rw.get(i + 1);
222                    Rwip1 = Rwip1.times(alpha);
223                    Rwip1 = Rwip1.plus(Rwi);
224
225                    //  Store the new control point.
226                    Rw.set(i, Rwip1);
227                }
228
229                //  Store the new control points (converting back from B-Spline to NURBS).
230                Qw.set(L, Rw.get(0).getHomogeneous());
231                if (pmjms > 0)
232                    Qw.set(k + r - j - sm, Rw.get(pmjms).getHomogeneous());
233
234            }
235
236            //  Load the remaining control points.
237            for (int i = L + 1; i < k - sm; ++i) {
238                Qw.set(i, Rw.get(i - L).getHomogeneous());  //  Converting back from B-Spline to NURBS.
239            }
240
241            //  Construct and return a new curve.
242            BasicNurbsCurve curve = BasicNurbsCurve.newInstance(Qw, SQ);
243            return StackContext.outerCopy(curve);
244
245        } finally {
246            StackContext.exit();
247        }
248
249    }
250
251    /**
252     * Create and return a new {@link BasicNurbsCurve NURBS curve} that is geometrically
253     * identical to this one but with the specified list of knots inserted into it's knot
254     * vector. The parameterization of the new curve is identical to this curve, but the
255     * new curve will have the new knots at the specified locations and a new set of
256     * control points. This is more efficient than repeatedly calling insertKnot() for
257     * multiple knot insertions.
258     *
259     * @param newKnots The parametric locations where the knots are to be inserted.
260     * @return A new NURBS curve that is identical to this one, but with the new knots
261     *         inserted at the specified locations.
262     */
263    public BasicNurbsCurve refineKnotVector(double[] newKnots) {
264        return refineKnotVector(Float64Vector.valueOf(newKnots));
265    }
266
267    /**
268     * Create and return a new {@link BasicNurbsCurve NURBS curve} that is geometrically
269     * identical to this one but with the specified list of knots inserted into it's knot
270     * vector. The parameterization of the new curve is identical to this curve, but the
271     * new curve will have the new knots at the specified locations and a new set of
272     * control points. This is more efficient than repeatedly calling insertKnot() for
273     * multiple knot insertions.
274     *
275     * @param newKnots The parametric locations where the knots are to be inserted. May
276     *                 not be null.
277     * @return A new NURBS curve that is identical to this one, but with the new knots
278     *         inserted at the specified locations.
279     */
280    public BasicNurbsCurve refineKnotVector(Float64Vector newKnots) {
281        //  Algorithm A5.4, pg. 164, Piegl, L., Tiller, W., The Nurbs Book, 2nd Edition, Springer-Verlag, Berlin, 1997.
282
283        //  Check on the validity of the new knots.
284        int r = newKnots.getDimension() - 1;
285        double sOld = 0;
286        for (int i = 0; i <= r; ++i) {
287            double s = newKnots.getValue(i);
288            validateParameter(s);
289            if (s < sOld)
290                throw new IllegalArgumentException(RESOURCES.getString("knotsNotIncreasingErr"));
291            sOld = s;
292        }
293
294        StackContext.enter();
295        try {
296            //  Get the existing knot vector.
297            KnotVector U = getKnotVector();
298            int p = U.getDegree();
299
300            //  Get the existing control points.
301            List<ControlPoint> Pw = getControlPoints();
302            int n = Pw.size() - 1;
303
304            int m = n + p + 1;
305
306            //  Allocate storage for the new control points.
307            FastTable<ControlPoint> Qw = FastTable.newInstance();
308            Qw.setSize(n + r + 2);
309
310            //  Allocate storage for the new knot vector.
311            FastTable<Float64> UbarList = FastTable.newInstance();
312            UbarList.setSize(m + r + 2);
313
314            int a = U.findSpan(newKnots.getValue(0));
315            int b = U.findSpan(newKnots.getValue(r));
316            ++b;
317
318            //  Copy over the unaltered control points.
319            for (int j = 0; j <= a - p; ++j) {
320                Qw.set(j, Pw.get(j));
321            }
322            for (int j = b - 1; j <= n; ++j) {
323                Qw.set(j + r + 1, Pw.get(j));
324            }
325
326            //  Copy over the unaltered knots.
327            for (int j = 0; j <= a; ++j) {
328                UbarList.set(j, U.get(j));
329            }
330            for (int j = b + p; j <= m; ++j) {
331                UbarList.set(j + r + 1, U.get(j));
332            }
333
334            int i = b + p - 1;
335            int k = b + p + r;
336            for (int j = r; j >= 0; --j) {
337                while (newKnots.getValue(j) <= U.getValue(i) && i > a) {
338                    Qw.set(k - p - 1, Pw.get(i - p - 1));
339                    UbarList.set(k, U.get(i));
340                    --k;
341                    --i;
342                }
343
344                Qw.set(k - p - 1, Qw.get(k - p));
345
346                for (int l = 1; l <= p; ++l) {
347                    int ind = k - p + l;
348                    int indm1 = ind - 1;
349                    int kpl = k + l;
350                    double Ubarkpl = UbarList.get(kpl).doubleValue();
351                    double alpha = Ubarkpl - newKnots.getValue(j);
352                    if (MathTools.isApproxZero(alpha))
353                        Qw.set(indm1, Qw.get(ind));
354                    else {
355                        alpha /= Ubarkpl - U.getValue(i - p + l);
356
357                        //  Qw[ind-1] = alpha*Qw[ind-1] + (1.0-alpha)*Qw[ind];
358                        ControlPoint Qwind = Qw.get(ind).applyWeight();
359                        ControlPoint Qwindm1 = Qw.get(indm1).applyWeight();
360                        Qwindm1 = Qwindm1.times(alpha);
361                        Qwind = Qwind.times(1.0 - alpha);
362                        Qwind = Qwind.plus(Qwindm1);
363                        Qw.set(indm1, Qwind.getHomogeneous());
364                    }
365                }
366
367                UbarList.set(k, newKnots.get(j));
368                --k;
369            }
370
371            //  Construct and return a new curve.
372            KnotVector Ubar = KnotVector.newInstance(p, Float64Vector.valueOf(UbarList));
373            BasicNurbsCurve curve = BasicNurbsCurve.newInstance(Qw, Ubar);
374            return StackContext.outerCopy(curve);
375
376        } finally {
377            StackContext.exit();
378        }
379
380    }
381
382    /**
383     * Return a {@link BasicNurbsCurve NURBS curve} that is geometrically identical to
384     * this one but with the specified list of knots merged into it's knot vector. The
385     * parameterization of the new curve is identical to this curve, but the new curve
386     * will have any new knots at the specified locations added and a new set of control
387     * points. If the input knot list matches the knot list of this curve, then this curve
388     * is returned.
389     *
390     * @param Um The knot vector to merge with this curve's knot vector. May not be null.
391     * @return A NURBS curve that is identical to this one, but with the merger of the
392     *         input knot vector and this curve's knot vector.
393     */
394    public NurbsCurve mergeKnotVector(Float64Vector Um) {
395
396        StackContext.enter();
397        try {
398            //  Get this curve's knot vector.
399            KnotVector U = getKnotVector();
400            int Usize = U.length();
401            int Umsize = Um.getDimension();
402
403            //  Find the knots that need inserting.
404            FastTable<Float64> Iknots = FastTable.newInstance();
405
406            boolean done = false;
407            int ia = 0, ib = 0;
408            while (!done) {
409                double Umv = Um.getValue(ib);
410                double Uv = U.getValue(ia);
411                if (MathLib.abs(Umv - Uv) <= TOL_S) {   //  Umv == Uv
412                    ++ib;
413                    ++ia;
414                } else {
415                    Iknots.add(Float64.valueOf(Umv));
416                    ++ib;
417                }
418                done = (ia >= Usize || ib >= Umsize);
419            }
420
421            //  Check to see if any knots need inserting.
422            if (Iknots.isEmpty()) {
423                return this;
424            }
425
426            BasicNurbsCurve newCrv = this.refineKnotVector(Float64Vector.valueOf(Iknots));
427            return StackContext.outerCopy(newCrv);
428
429        } finally {
430            StackContext.exit();
431        }
432
433    }
434
435    /**
436     * Attempts to remove the knot with the specified index from this NURBS curve the
437     * specified number of times. If the knot can not be removed without changing the
438     * shape of the curve, then a reference to this curve is returned:
439     * <code>if (crv == crv.removeKnot(...)) { no knots removed }</code>.
440     *
441     * @param index       The index of the knot to be removed (degree + 1 &le; index &lt;
442     *                    knot vector length - degree).
443     * @param numRemovals The number of times that the knot at "index" should be removed
444     *                    (1 &le; num &le; multiplicity of the knot). If numRemovals is &gt;
445     *                    multiplicity of the knot, then numRemovals is set to the
446     *                    multiplicity of the knot.
447     * @param tolerance   The knot(s) specified will be removed as long as the deviation
448     *                    of the curve is everywhere less than this tolerance. May not be
449     *                    null.
450     * @return A new NURBS curve that is identical to this one (to within the specified
451     *         tolerance), but with the specified knot removed or a reference to this
452     *         curve if the knot could not be removed.
453     */
454    public NurbsCurve removeKnot(int index, int numRemovals, Parameter<Length> tolerance) {
455        //  Algorithm: A5.8, pg. 185, Piegl, L., Tiller, W., The Nurbs Book, 2nd Edition, Springer-Verlag, Berlin, 1997.
456        requireNonNull(tolerance);
457        
458        KnotVector Ukv = getKnotVector();
459        int degree = Ukv.getDegree();
460        int order = degree + 1;
461        int m = Ukv.length();
462
463        //  Check to make sure the knot to be removed is a legal one.
464        if (index < order || index >= m - degree)
465            throw new IllegalArgumentException(
466                    MessageFormat.format(RESOURCES.getString("illegalKnotIndexForRemoval"),
467                            index, order, m - degree));
468        if (numRemovals < 0)
469            throw new IllegalArgumentException(RESOURCES.getString("negNumKnotsRemoveErr"));
470        if (numRemovals == 0)
471            return this;
472
473        StackContext.enter();
474        try {
475            //  Check if this is a multiple knot.
476            //  If it is, reset "index" to be the largest index for the multiple knot.
477            //  Want: Ukv(index) != Ukv(index+1)
478            while (index < m - degree - 1 && Ukv.getValue(index + 1) == Ukv.getValue(index)) {
479                ++index;
480            }
481            double u = Ukv.getValue(index);
482
483            //  Determine multiplicity.
484            int s = Ukv.findMultiplicity(index);
485            if (numRemovals > s)
486                numRemovals = s;
487
488            //  Get the original control points.
489            List<ControlPoint> Pw = getControlPoints();
490            int n = Pw.size();
491
492            //  Determine an appropriate and meaningful tolerance for knot removal.
493            double tol = tolerance.getValue(getUnit());
494            boolean isRational = false;
495            double wmin = Double.MAX_VALUE;
496            double Pmax = -Double.MAX_VALUE;
497            for (int i = 0; i < n; ++i) {
498                ControlPoint Pwi = Pw.get(i);
499                double w = Pwi.getWeight();
500                wmin = MathLib.min(wmin, w);
501                if (MathTools.isApproxEqual(w, 1))
502                    isRational = true;
503                Pmax = MathLib.max(Pmax, Pwi.normValue());
504            }
505            if (isRational)
506                tol = tol * (1 + Pmax) / wmin;
507
508            //  Allocate storage for calculated control points.
509            FastTable<ControlPoint> temp = FastTable.newInstance();
510            temp.setSize(2 * degree + 1);
511
512            //  First control point out.
513            int fout = (2 * index - s - degree) / 2;
514            int last = index - s;
515            int first = index - degree;
516            int i, j, t;
517
518            //  This loop is Eqn. 5.28
519            for (t = 0; t < numRemovals; ++t) {
520                //  Diff. in index between temp and Pw.
521                int off = first - 1;
522                temp.set(0, Pw.get(off).applyWeight());
523                ControlPoint cp = Pw.get(last + 1).applyWeight();
524                temp.set(last + 1 - off, cp);
525                i = first;
526                j = last;
527                int ii = 1;
528                int jj = last - off;
529                boolean remflag = false;
530
531                while (j - i > t) {
532                    //  Compute new control points for one removal step.
533                    double alfi = (u - Ukv.getValue(i)) / (Ukv.getValue(i + order + t) - Ukv.getValue(i));
534                    double alfj = (u - Ukv.getValue(j - t)) / (Ukv.getValue(j + order) - Ukv.getValue(j - t));
535                    ControlPoint Pwi = Pw.get(i).applyWeight();
536                    ControlPoint Pwj = Pw.get(j).applyWeight();
537
538                    //  temp[ii] = (Pw[i] - (1.0-alfi)*temp[ii-1])/alfi;
539                    ControlPoint tmpCP = temp.get(ii - 1);
540                    tmpCP = tmpCP.times(1.0 - alfi);
541                    Pwi = Pwi.minus(tmpCP);
542                    Pwi = Pwi.divide(alfi);
543                    temp.set(ii, Pwi);
544
545                    //  temp[jj] = (Pw[j] - alfj*temp[jj+1])/(1 - alfi);
546                    tmpCP = temp.get(jj + 1);
547                    tmpCP = tmpCP.times(alfj);
548                    Pwj = Pwj.minus(tmpCP);
549                    Pwj = Pwj.divide(1.0 - alfj);
550                    temp.set(jj, Pwj);
551
552                    ++i;
553                    ++ii;
554                    --j;
555                    --jj;
556                }
557
558                //  Check if knot removable.
559                if (j - i < t) {
560                    if (temp.get(ii - 1).getHomogeneous().distance(temp.get(jj + 1).getHomogeneous()) <= tol)
561                        remflag = true;
562                } else {
563                    double alfi = (u - Ukv.getValue(i)) / (Ukv.getValue(i + order + t) - Ukv.getValue(i));
564                    //  tmp = alfi*temp[ii+t+1] + (1.0-alfi)*temp[ii-1];
565                    ControlPoint tmp2 = temp.get(ii - 1);
566                    tmp2 = tmp2.times(1.0 - alfi);
567                    ControlPoint tmp = temp.get(ii + t + 1);
568                    tmp = tmp.times(alfi);
569                    tmp = tmp.plus(tmp2);
570                    if (Pw.get(i).distance(tmp.getHomogeneous()) <= tol)
571                        remflag = true;
572                }
573                if (!remflag)
574                    //  Can not remove any more knots.
575                    break;  //  Get out of the for-loop.
576                else {
577                    //  Successfull removal.  Save new control points.
578                    i = first;
579                    j = last;
580
581                    while (j - i > t) {
582                        Pw.set(i, temp.get(i - off).getHomogeneous());
583                        Pw.set(j, temp.get(j - off).getHomogeneous());
584                        ++i;
585                        --j;
586                    }
587                }
588                --first;
589                ++last;
590            }   //  end of for-loop.
591
592            if (t == 0) {
593                //  No knots removed.
594                return this;
595            }
596
597            //  Construct a new knot vector by shifting the old knots over.
598            FastTable<Float64> newKV = FastTable.newInstance();
599            int end = index + 1 - t;
600            for (int k = 0; k < end; ++k) {
601                newKV.add(Ukv.get(k));
602            }
603            for (int k = index + 1; k < m; ++k) {
604                newKV.add(Ukv.get(k));
605            }
606            KnotVector kv = KnotVector.newInstance(degree, Float64Vector.valueOf(newKV));
607
608            //  Pj thru Pi will be overwritten
609            j = fout;
610            i = j;
611            for (int k = 1; k < t; ++k) {
612                if ((k % 2) == 1)   //  k modulo 2
613                    ++i;
614                else
615                    --j;
616            }
617
618            //  Construct a new control point list.
619            FastTable<ControlPoint> newCPs = FastTable.newInstance();
620            for (int k = 0; k < j; ++k) {
621                newCPs.add(Pw.get(k));
622            }
623            for (int k = i + 1; k < n; ++k) {
624                newCPs.add(Pw.get(k));
625            }
626
627            //  Construct a new curve.
628            BasicNurbsCurve crv = BasicNurbsCurve.newInstance(newCPs, kv);
629            return StackContext.outerCopy(crv);
630
631        } finally {
632            StackContext.exit();
633        }
634
635    }
636
637    /**
638     * Return the total arc length of this curve.
639     *
640     * @param eps The desired fractional accuracy on the arc-length.
641     * @return The total arc length of the curve.
642     */
643    @Override
644    public Parameter<Length> getArcLength(double eps) {
645
646        if (getDegree() == 1)
647            //  A polygon is a special case that can be dealt with easily.
648            return getCPPolyLength();
649
650        //  Otherwise, just use the standard approach.
651        return getArcLength(0, 1, eps);
652    }
653
654    /**
655     * Return the length of the control point polygon for this curve.
656     */
657    private Parameter<Length> getCPPolyLength() {
658
659        StackContext.enter();
660        try {
661            Parameter<Length> length = Parameter.ZERO_LENGTH.to(getUnit());
662            List<ControlPoint> Pw = getControlPoints();
663
664            int size = Pw.size();
665            Point Pim1 = Pw.get(0).getPoint();
666            for (int i = 1; i < size; ++i) {
667                Point Pi = Pw.get(i).getPoint();
668                length = length.plus(Pim1.distance(Pi));
669                Pim1 = Pi;
670            }
671
672            return StackContext.outerCopy(length);
673
674        } finally {
675            StackContext.exit();
676        }
677    }
678
679    /**
680     * Return the arc length of a segment of this curve.
681     *
682     * @param s1  The starting parametric distance along the curve to begin the arc-length
683     *            calculation from.
684     * @param s2  The ending parametric distance along the curve to end the arc-length
685     *            calculation at.
686     * @param eps The desired fractional accuracy on the arc-length.
687     * @return The arc length of the specified segment of the curve.
688     */
689    @Override
690    public Parameter<Length> getArcLength(double s1, double s2, double eps) {
691        validateParameter(s1);
692        validateParameter(s2);
693
694        //  Method:  Find arc-lengths of each Bezier segment and add them up.
695        //           This avoids problems with discontinuities and improves accuracy.
696        //           Adjusts eps value for each segment assuming "s" is proportional to arc-length
697        //           which is not exactly true (also applied a factor of 2 conservatism).
698        
699        //  Create a list of Bezier segment starting parametric positions ( and the last end position, 1).
700        FastTable<Double> bParams = getSegmentParameters();
701
702        //  Find the arc-length of each segment but the last in turn and sum them up.
703        Parameter<Length> L = Parameter.ZERO_LENGTH.to(getUnit());
704        int nSegs = bParams.size() - 1;
705        double epsf = 2 * (s2 - s1);
706        double sprev = s1;
707        for (int i = 1; i < nSegs; ++i) {
708            double si = bParams.get(i);
709            if (si > s1 && si < s2) {
710                L = L.plus(super.getArcLength(sprev, si, eps / (si - sprev) * 0.5));
711                sprev = si;
712                epsf = 1;
713            }
714        }
715
716        //  Add in the last segment.
717        L = L.plus(super.getArcLength(sprev, s2, eps * epsf / (s2 - sprev) * 0.5));
718
719        return L;
720    }
721
722    /**
723     * Return a string of points that are gridded onto the curve with the number of points
724     * and spacing chosen to result in straight line segments between the points that have
725     * mid-points that are all within the specified tolerance of this curve.
726     *
727     * @param tol The maximum distance that a straight line between gridded points may
728     *            deviate from this curve. May not be null.
729     * @return A string of subrange points gridded onto the curve.
730     */
731    @Override
732    public PointString<SubrangePoint> gridToTolerance(Parameter<Length> tol) {
733        if (getDegree() > 1)
734            return super.gridToTolerance(tol);
735        
736        //  A more efficient algorithm is possible for degree=1 (linear) curves.
737        //  Since a linear curve consists of straight line segments between knots,
738        //  we can start with the knots and then "thin to tolerance" from there.
739        
740        if (isDegenerate(requireNonNull(tol))) {
741            //  If this curve is a single point, just return that point twice.
742            PointString<SubrangePoint> str = PointString.newInstance();
743            str.add(getPoint(0));
744            str.add(getPoint(1));
745            return str;
746        }
747        
748        //  Create a list of Bezier segment starting parametric positions ( and the last end position, 1).
749        FastTable<Double> bParams = getSegmentParameters();
750        
751        //  Convert the parametric positions into SubrangePoints.
752        PointString<SubrangePoint> pntList = PointString.newInstance();
753        int numPnts = bParams.size();
754        for (int i = 0; i < numPnts; ++i) {
755            Double s = bParams.get(i);
756            pntList.add(getPoint(s));
757        }
758        
759        //  Loop over the points and remove any that are closer than tol to line segments
760        //  between adjacent points.
761        Parameter<Area> tol2 = tol.times(tol);
762        for (int i = numPnts - 2; i >= 1; --i) {
763            SubrangePoint pi = pntList.get(i);
764            double s = pi.getParPosition().getValue(0);
765
766            //  Calculate a point, pa, along the line between p(i+1) and p(i-1)
767            //  at the position of "s" (or pi).
768            SubrangePoint pip1 = pntList.get(i + 1);
769            SubrangePoint pim1 = pntList.get(i - 1);
770            Point pa = interpSPoint(pim1, pip1, s);
771
772            //  Compute the distance between the point at "s" and the line seg point
773            //  and compare with the tolerance.
774            boolean distLTtol = pa.distanceSq(pi).isLessThan(tol2);
775            if (distLTtol) {
776                //  Remove this point from the list.
777                SubrangePoint pnt = pntList.remove(i);
778                SubrangePoint.recycle(pnt);
779            }
780        }
781        
782        return pntList;
783    }
784    
785    /**
786     * Guess an initial set of points to use when gridding to tolerance. The initial
787     * points must include the start and end of the curve as well as any discontinuities
788     * in the curve. Other than that, they should be distributed as best as possible to
789     * indicate areas of higher curvature, but should be generated as efficiently as
790     * possible. The guessed points MUST be monotonically increasing from 0 to 1.
791     *
792     * @param tol2 The square of the maximum distance that a straight line
793     *             between gridded points may deviate from this curve.
794     * @return A FastList of subrange points to use as a starting point for the grid to
795     *         tolerance algorithms.
796     */
797    @Override
798    protected FastList<SubrangePoint> guessGrid2TolPoints(Parameter<Area> tol2) {
799        if (getPhyDimension() < 2)
800            return super.guessGrid2TolPoints(tol2);
801
802        //  Extract the boundaries of Bezier segments and place grid points at those.
803        //  Find the control point that is furthest from a straight line from the start to
804        //  the end of each Bezier segment and place a grid point that that approximate location.
805        FastTable<Double> parS = FastTable.newInstance();
806        StackContext.enter();
807        try {
808            //  Create a list of Bezier segment starting parametric positions ( and the last end position, 1).
809            FastTable<Double> bParams = getSegmentParameters();
810
811            //  Create a list of Bezier segments for the curve.
812            GeomList<BasicNurbsCurve> segs = CurveUtils.decomposeToBezier(this);
813
814            //  Loop over the segment starting positions.
815            int nSegs = segs.size();
816            for (int i = 0; i < nSegs; ++i) {
817                //  Always add the start point for each segment to the output points.
818                Double s0 = bParams.get(i);
819                parS.add(s0);
820
821                //  Get this Bezier segment.
822                BasicNurbsCurve seg = segs.get(i);
823
824                //  Get this segment's control points.
825                FastTable<ControlPoint> cps = (FastTable<ControlPoint>)seg.getControlPoints();
826
827                //  Find the control point that is furthest away from a line segment between the 1st and last CP.
828                Point p0 = cps.get(0).getPoint();
829                Point p1 = cps.getLast().getPoint();
830                Unit<Length> unit = getUnit();
831                double Lv = p1.distance(p0).getValue(unit);
832                if (!MathTools.isApproxZero(Lv)) {
833                    Vector<Dimensionless> u = p1.minus(p0).toGeomVector().toUnitVector();
834                    double dmax2 = 0;
835                    double sdmax = 0.5;
836                    int numCP = cps.size() - 1;
837                    for (int cpIdx = 1; cpIdx < numCP; ++cpIdx) {
838                        Point cpi = cps.get(cpIdx).getPoint();
839                        Point pi = GeomUtil.pointLineClosest(cpi, p0, u);
840                        double dist2 = cpi.distanceSqValue(pi);
841                        if (dist2 > dmax2) {
842                            //  Store maximum distance (squared).
843                            dmax2 = dist2;
844                            //  Approx. parametric location of maximum point.
845                            sdmax = pi.distance(p0).getValue(unit) / Lv;
846                        }
847                    }
848
849                    //  Add the location of the maximum lateral offset to the starting points.
850                    if (dmax2 > tol2.getValue(unit.pow(2).asType(Area.class))) {
851                        //  Convert from segment to full curve parametric position.
852                        Double s1 = bParams.get(i + 1);
853                        sdmax = segmentPos2Parent(sdmax, s0, s1);
854                        parS.add(sdmax);
855                    }
856                }
857            }
858
859            //  Add the last point to the list.
860            parS.add(bParams.getLast());
861
862        } finally {
863            StackContext.exit();
864        }
865
866        //  Convert the parametric positions into SubrangePoints.
867        FastList<SubrangePoint> pntList = FastList.newInstance();
868        int nSegs = parS.size();
869        for (int i = 0; i < nSegs; ++i) {
870            Double s = parS.get(i);
871            pntList.add(getPoint(s));
872        }
873        FastTable.recycle(parS);
874
875        return pntList;
876    }
877
878    /**
879     * Return a list of brackets that surround possible closest/farthest points. Each of
880     * the brackets will be refined using a root solver in order to find the actual
881     * closest or farthest points.
882     *
883     * @param point   The point to find the closest/farthest point on this curve to/from.
884     * @param closest Set to <code>true</code> to return the closest point or
885     *                <code>false</code> to return the farthest point.
886     * @param eval    A function that calculates the dot product of (p(s)-q) and ps(s) on
887     *                the curve.
888     * @return A list of brackets that each surround a potential closest/farthest point.
889     */
890    @Override
891    protected List<BracketRoot1D> guessClosestFarthest(GeomPoint point, boolean closest,
892            Evaluatable1D eval) {
893        requireNonNull(point);
894        requireNonNull(eval);
895        
896        //  Inspired by: Ma, Hewitt, "Point inversion and projection for NURBS curve and surface", CAGD, 2003.
897        //  But not quite as complex (or efficient or robust).
898        //  Extract the curve degree.
899        int degree = getDegree();
900        int order = degree + 1;
901
902        //  Create a list of Bezier segment starting parametric positions ( and the last end position, 1).
903        FastTable<Double> bParams = getSegmentParameters();
904
905        //  Decompose the curve into Bezier segments.
906        GeomList<BasicNurbsCurve> segs = CurveUtils.decomposeToBezier(this);
907
908        //  Create a list of Bezier end parameters that may be closest/farthest points.
909        FastTable<Double> closestEnds = FastTable.newInstance();
910
911        //  Create a list of segments to sub-divide into more segments.
912        FastTable<Integer> segIndexes = FastTable.newInstance();
913
914        //  Loop over all the Bezier segments.
915        int size = segs.size();
916        for (int i = 0; i < size; ++i) {
917            BasicNurbsCurve seg = segs.get(i);
918            List<ControlPoint> segCP = seg.getControlPoints();
919
920            //  Determine if this segment is a "valid" segment (simple and convex).
921            if (CurveUtils.isSimpleConvexPolygon(segCP)) {
922                //  Determine if the end-points are closest to the target point.
923                if (CurveUtils.isBezierEndPointNearest(point, segCP)) {
924                    //  One of the ends is closest.
925                    double s = bParams.get(i);  //  Start of segment "i".
926                    closestEnds.add(s);
927                    s = bParams.get(i + 1);     //  End of segment "i".
928                    closestEnds.add(s);
929
930                } else {
931                    //  An interior point is likely closest.
932                    segIndexes.add(i);
933                }
934
935            } else {
936                //  Handle invalid segments by just searching for interior brackets.
937                segIndexes.add(i);
938            }
939
940            FastTable.recycle((FastTable)segCP);
941        }
942        GeomList.recycle(segs);
943
944        //  Look for brackets in each of the Bezier segments identified.
945        FastTable<BracketRoot1D> brackets = findInteriorBrackets(order + 1, segIndexes, bParams, eval);
946
947        //  Finally, look for brackets near the end points of other segments.
948        brackets.addAll(findEndBrackets(closestEnds, point, closest, eval));
949
950        //  Look for and remove overlapping brackets that are bracketing the same root.
951        removeOverlappingBrackets(brackets, eval);
952
953        //  Cleanup before leaving.
954        FastTable.recycle(closestEnds);
955        FastTable.recycle(segIndexes);
956        FastTable.recycle(bParams);
957
958        return brackets;
959    }
960
961    /**
962     * Return a list of brackets that surround possible plane intersection points. Each of
963     * the brackets will be refined using a root solver in order to find the actual
964     * intersection points.
965     *
966     * @param plane The plane to find the intersection points on this curve to/from.
967     * @param eval  A function that calculates the <code>n dot (p(s) - pp)</code> on the
968     *              curve.
969     * @return A list of brackets that each surround a potential intersection point.
970     *         Returns an empty list if there are no intersections found.
971     */
972    @Override
973    protected List<BracketRoot1D> guessIntersect(GeomPlane plane, Evaluatable1D eval) {
974        requireNonNull(plane);
975        requireNonNull(eval);
976        
977        //  Get the Bezier segment start parameters.
978        FastTable<Double> bParams = getSegmentParameters();
979
980        //  Extract the curve degree.
981        int degree = getDegree();
982        int numPoints = degree + 3;
983
984        //  Find multiple intersection points (roots) by sub-dividing each Bezier segment into pieces and
985        //  bracketing any roots that occur in any of those segments.
986        FastTable<BracketRoot1D> outputs = FastTable.newInstance();
987        try {
988            double s0 = bParams.get(0);
989            double fp = eval.function(s0);
990            int numSegs = bParams.size() - 1;
991            for (int j = 0; j < numSegs; ++j) {
992                //  Get the next segment end.
993                double s1 = bParams.get(j + 1);
994
995                //  Search for brackets between s0 and s1.
996                double dx = (s1 - s0) / numPoints;
997                if (dx != 0) {
998                    double x = s0;
999                    double xp = x;
1000                    for (int i = 0; i < numPoints; ++i) {
1001                        x += dx;
1002                        if (x > s1)
1003                            x = s1;
1004                        double fc = eval.function(x);
1005                        if (fc * fp <= 0.0) {
1006                            //  Create a bracket (while dealing with roundoff)
1007                            double xpt = xp - MathTools.epsilon(xp);
1008                            if (xpt < 0.0)
1009                                xpt = 0.0;
1010                            double xt = x + MathTools.epsilon(x);
1011                            if (xt > 1.0)
1012                                xt = 1.0;
1013                            outputs.add(new BracketRoot1D(xpt, xt));
1014                        }
1015                        xp = x;
1016                        fp = fc;
1017                    }
1018                }
1019
1020                //  Set the next segment start to this segment end.
1021                s0 = s1;
1022            }
1023        } catch (RootException err) {
1024            Logger.getLogger(NurbsCurve.class.getName()).log(Level.WARNING,
1025                    "Failed to bracket plane intersection points.", err);
1026        }
1027
1028        return outputs;
1029    }
1030
1031    /**
1032     * Return a list of brackets that surround possible surface intersection points. Each
1033     * of the brackets will be refined using a root solver in order to find the actual
1034     * intersection points.
1035     *
1036     * @param surface The surface to find the intersection points on this curve to/from.
1037     * @param eval    A function that calculates the <code>n dot (p(s) - pp)</code> on the
1038     *                curve.
1039     * @return A list of brackets that each surround a potential intersection point.
1040     *         Returns an empty list if there are no intersections found.
1041     */
1042    @Override
1043    protected List<BracketRoot1D> guessIntersect(Surface surface, Evaluatable1D eval) {
1044
1045        //  Get the Bezier segment start parameters.
1046        FastTable<Double> bParams = getSegmentParameters();
1047        int numSegs = bParams.size() - 1;
1048
1049        //  Determine the number of points per segment.
1050        int numPoints = getNumPointsPerSegment();
1051
1052        //  Don't allow an excessive number of bracket tests.
1053        if (numPoints * numSegs > 50) {
1054            numPoints = 50 / numSegs;
1055            if (numPoints == 0)
1056                numPoints = 1;
1057        }
1058
1059        //  Find multiple intersection points (roots) by sub-dividing curve into pieces and
1060        //  bracketing any roots that occur in any of those segments.
1061        FastTable<BracketRoot1D> brackets = FastTable.newInstance();
1062        try {
1063            double fp = eval.function(bParams.get(0));
1064            for (int i = 0; i < numSegs; ++i) {
1065                double s0 = bParams.get(i);
1066                double s1 = bParams.get(i + 1);
1067                fp = findNextBrackets(eval, s0, s1, numPoints, fp, brackets);
1068            }
1069
1070        } catch (RootException err) {
1071            Logger.getLogger(NurbsCurve.class.getName()).log(Level.WARNING,
1072                    "Failed to bracket surface intersection points.", err);
1073        }
1074
1075        return brackets;
1076    }
1077
1078    /**
1079     * Given a function, <code>func</code>, defined on the interval <code>x1</code> to
1080     * <code>x2</code>, this routine subdivides the interval into <code>n</code> equally
1081     * spaced segments, and searches for zero crossings of the function. Brackets around
1082     * any zero crossings found are placed in the input "brackets" list. The function
1083     * value at x1 is also input to eliminate unnecessary function evaluations when
1084     * calling this function in a loop.
1085     *
1086     * @param func     The function that is being search for zero crossings.
1087     * @param x1       The start of the interval to be searched.
1088     * @param x2       The end of the interval to be searched.
1089     * @param n        The number segments to divide the interval into.
1090     * @param fx1      The function value at x1. This is used to eliminate a redundant
1091     *                 function evaluation when this function is called repeatedly in a
1092     *                 loop.
1093     * @param brackets An existing list that has any new brackets added to it.
1094     * @return The value of the function evaluated at x2.
1095     * @throws RootException if the Evaluatable1D throws a exception.
1096     */
1097    private static double findNextBrackets(Evaluatable1D func, double x1, double x2, int n,
1098            double fx1, List<BracketRoot1D> brackets) throws RootException {
1099
1100        double dx = (x2 - x1) / n;
1101        if (MathTools.isApproxZero(dx))
1102            return fx1;
1103
1104        double x = x1;
1105        double fp = fx1;
1106        for (int i = 0; i < n; ++i) {
1107            x += dx;
1108            if (x > x2)
1109                x = x2;
1110            double fc = func.function(x);
1111            if (fc * fp <= 0.0) {
1112                brackets.add(new BracketRoot1D(x - dx, x));
1113            }
1114            fp = fc;
1115        }
1116
1117        return fp;
1118    }
1119
1120    /**
1121     * Bracket any closest/farthest points interior to the listed segments by
1122     * sub-dividing.
1123     *
1124     * @param numSubSegs The number of segments to divide the Bezier segment into for root
1125     *                   bracketing.
1126     * @param segIndexes The indexes for the Bezier segments to be searched for
1127     *                   closest/farthest points.
1128     * @param knotList   A list containing the parametric positions of the start of each
1129     *                   Bezier segment.
1130     * @param eval       A function that calculates the dot product of (p(s)-q) and ps(s)
1131     *                   on the curve.
1132     * @return A list of bracketed closest/farthest points in the segments indicated by
1133     *         segIndexes.
1134     */
1135    private static FastTable<BracketRoot1D> findInteriorBrackets(int numSubSegs, List<Integer> segIndexes, List<Double> knotList, Evaluatable1D eval) {
1136
1137        FastTable<BracketRoot1D> brackets = FastTable.newInstance();
1138        for (int i : segIndexes) {
1139            double s1 = knotList.get(i);        //  S for start of segment "i".
1140            double s2 = knotList.get(i + 1);    //  S for end of segment "i".
1141            try {
1142                brackets.addAll(BracketRoot1D.findBrackets(eval, s1, s2, numSubSegs));
1143            } catch (RootException err) {
1144                Logger.getLogger(NurbsCurve.class.getName()).log(Level.WARNING,
1145                        "Failed to find interior bracket points.", err);
1146            }
1147        }
1148
1149        return brackets;
1150    }
1151
1152    /**
1153     * Bracket any closest/farthest points at the end of the closest/farthest Bezier
1154     * segment end.
1155     *
1156     * @param segEnds A list of ends of Bezier segments to search for the closest/farthest
1157     *                point.
1158     * @param point   The point to find the closest/farthest point on this curve to/from.
1159     * @param closest Set to <code>true</code> to return the closest point or
1160     *                <code>false</code> to return the farthest point.
1161     * @param eval    A function that calculates the dot product of (p(s)-q) and ps(s) on
1162     *                the curve.
1163     * @return A list of brackets surrounding the closest/farthest end point of the
1164     *         supplied Bezier segment ends.
1165     */
1166    private List<BracketRoot1D> findEndBrackets(List<Double> segEnds, GeomPoint point, boolean closest,
1167            Evaluatable1D eval) {
1168
1169        int size = segEnds.size();
1170        if (size > 0) {
1171            //  Find the closest/farthest segment end-point.  The other's don't matter.
1172            double sOpt = segEnds.get(0);
1173            StackContext.enter();
1174            try {
1175                double dOpt = getRealPoint(sOpt).distanceValue(point);
1176                double oldS = -1;
1177                for (int i = 1; i < size; ++i) {
1178                    double s = segEnds.get(i);
1179                    if (s != oldS) {
1180                        double dist = getRealPoint(s).distanceValue(point);
1181                        if (closest) {
1182                            if (dist < dOpt) {
1183                                dOpt = dist;
1184                                sOpt = s;
1185                            }
1186                        } else {
1187                            if (dist > dOpt) {
1188                                dOpt = dist;
1189                                sOpt = s;
1190                            }
1191                        }
1192                    }
1193                    oldS = s;
1194                }
1195            } finally {
1196                StackContext.exit();
1197            }
1198
1199            //  Bracket around the closest end-point.
1200            try {
1201                return bracketNear(eval, sOpt);
1202            } catch (RootException err) {
1203                Logger.getLogger(NurbsCurve.class.getName()).log(Level.WARNING,
1204                        "Failed to find end bracket points.", err);
1205            }
1206        }
1207
1208        //  Return an empty list (must be ArrayList as that is what "Roots.bracket" returns).
1209        return new ArrayList();
1210    }
1211
1212    /**
1213     * Remove any overlapping brackets that are covering the same closest/farthest point.
1214     *
1215     * @param brackets A list of root brackets. The list will be modified to delete any
1216     *                 brackets that are both bracketing the same root.
1217     * @param eval     A function that calculates the dot product of (p(s)-q) and ps(s) on
1218     *                 the curve.
1219     */
1220    private void removeOverlappingBrackets(List<BracketRoot1D> brackets, Evaluatable1D eval) {
1221
1222        //  Look for and remove overlapping brackets that are bracketing the same root.
1223        int size = brackets.size();
1224        if (size > 1) {
1225            try {
1226                //  First sort the brackets into ascending order of the start of each bracket.
1227                Collections.sort(brackets);
1228
1229                --size;
1230                for (int i = 0; i < size; ++i) {
1231                    BracketRoot1D b = brackets.get(i);
1232                    BracketRoot1D bp1 = brackets.get(i + 1);
1233                    double x1 = b.x2;
1234                    double x2 = bp1.x1;
1235                    if (x1 > x2) {
1236                        //  We have an overlapping bracket pair.
1237                        //  Do they bound the same root?
1238                        double f1 = eval.function(x1);
1239                        double f2 = eval.function(x2);
1240                        if (f1 * f2 <= 0) {
1241                            //  Both bracket the same root.
1242
1243                            //  Modify the 1st bracket to more closely cover the root.
1244                            b.x1 = x2;
1245                            b.x2 = x1;
1246
1247                            //  Delete the 2nd bracket.
1248                            brackets.remove(i + 1);
1249                            --size;
1250                        }
1251                    }
1252                }
1253            } catch (RootException err) {
1254                Logger.getLogger(NurbsCurve.class.getName()).log(Level.WARNING,
1255                        "Evaluation function failed while removing overlapping brackets.", err);
1256            }
1257        }
1258    }
1259
1260    /**
1261     * Returns transformed version of this element. The returned object implements
1262     * {@link geomss.geom.GeomTransform} and contains this element as a child.
1263     *
1264     * @param transform The transformation to apply to this geometry. May not be null.
1265     * @return A new triangle that is identical to this one with the specified
1266     *         transformation applied.
1267     * @throws DimensionException if this point is not 3D.
1268     */
1269    @Override
1270    public NurbsCurveTrans getTransformed(GTransform transform) {
1271        return NurbsCurveTrans.newInstance(this, requireNonNull(transform));
1272    }
1273
1274    /**
1275     * Split this {@link NurbsCurve} at the specified parametric position returning a list
1276     * containing two curves (a lower curve with smaller parametric positions than "s" and
1277     * an upper curve with larger parametric positions).
1278     *
1279     * @param s The parametric position where this curve should be split (must not be 0 or
1280     *          1!).
1281     * @return A list containing two curves: 0 == the lower curve, 1 == the upper curve.
1282     */
1283    @Override
1284    public GeomList<NurbsCurve> splitAt(double s) {
1285        validateParameter(s);
1286        if (parNearEnds(s, TOL_S))
1287            throw new IllegalArgumentException(
1288                    MessageFormat.format(RESOURCES.getString("canNotSplitAtEnds"), "curve"));
1289
1290        GeomList<NurbsCurve> output = GeomList.newInstance();   //  Outside of StackContext.
1291        StackContext.enter();
1292        try {
1293            KnotVector U = getKnotVector();
1294            int degree = U.getDegree();
1295            int span = U.findSpan(s);
1296
1297            //  Find the multiplicity of the split parameter.
1298            int sIndex;
1299            if (MathLib.abs(s - U.getValue(span)) <= TOL_S)
1300                sIndex = U.findMultiplicity(span);
1301            else
1302                sIndex = 0;
1303
1304            //  Number of times to insert the knot required to split the curve.
1305            int t = degree + 1 - sIndex;
1306
1307            //  Insert the knot where we are splitting the curve "t" times.
1308            BasicNurbsCurve cTmp = insertKnot(s, t);
1309            KnotVector kvTmp = cTmp.getKnotVector();
1310            List<ControlPoint> cpTmp = cTmp.getControlPoints();
1311
1312            span = kvTmp.findSpan(s) - degree;      //  span is now the beginning of the upper curve.
1313
1314            //  Create a knot vector for the lower curve.
1315            FastTable<Float64> kvList = FastTable.newInstance();
1316            int size = span + degree + 1;
1317            for (int i = 0; i < size; ++i) {
1318                double snew = kvTmp.getValue(i) / s;            //  Scale to range 0-1.
1319                //  Watch for roundoff
1320                if (snew <= TOL_S)
1321                    snew = 0;
1322                else if (snew >= 1 - TOL_S)
1323                    snew = 1;
1324                kvList.add(Float64.valueOf(snew));
1325            }
1326            KnotVector kv = KnotVector.newInstance(degree, Float64Vector.valueOf(kvList));
1327
1328            //  Create a ControlPoint list for the lower curve.
1329            FastTable<ControlPoint> cpList = FastTable.newInstance();
1330            size = span;
1331            for (int i = 0; i < size; ++i) {
1332                cpList.add(cpTmp.get(i));
1333            }
1334
1335            //  Create the lower curve.
1336            BasicNurbsCurve cl = BasicNurbsCurve.newInstance(cpList, kv);
1337
1338            //  Create a knot vector for the upper curve.
1339            double oms = 1 - s;
1340            kvList.clear();
1341            size = kvTmp.length();
1342            for (int i = span; i < size; ++i) {
1343                double snew = (kvTmp.getValue(i) - s) / oms;    //  Scale to range 0-1.
1344                //  Watch for roundoff.
1345                if (snew <= TOL_S)
1346                    snew = 0;
1347                else if (snew >= 1 - TOL_S)
1348                    snew = 1;
1349                kvList.add(Float64.valueOf(snew));
1350            }
1351            kv = KnotVector.newInstance(degree, Float64Vector.valueOf(kvList));
1352
1353            //  Create a ControlPoint list for the upper curve.
1354            cpList.clear();
1355            size = cpTmp.size();
1356            for (int i = span; i < size; ++i) {
1357                cpList.add(cpTmp.get(i));
1358            }
1359
1360            //  Create the upper curve.
1361            BasicNurbsCurve cu = BasicNurbsCurve.newInstance(cpList, kv);
1362
1363            //  Put in the output list (which is outside the StackContext).
1364            output.add(StackContext.outerCopy(cl));
1365            output.add(StackContext.outerCopy(cu));
1366
1367        } finally {
1368            StackContext.exit();
1369        }
1370
1371        return output;
1372    }
1373
1374    /**
1375     * Return the intersection between a line segment and this curve.
1376     *
1377     * @param line The line segment to intersect. May not be null.
1378     * @param tol  Tolerance (in physical space) to refine the point positions to. May not be null.
1379     * @return A list containing two PointString instances each containing zero or more
1380     *         subrange points, on this curve and the input line segment respectively,
1381     *         made by the intersection of this curve with the specified line segment. If
1382     *         no intersections are found a list of two empty PointStrings are returned.
1383     */
1384    @Override
1385    public GeomList<PointString<SubrangePoint>> intersect(LineSegment line, Parameter<Length> tol) {
1386        requireNonNull(line);
1387        requireNonNull(tol);
1388        
1389        //  First check to see if the line segment even comes near the curve.
1390        GeomList<PointString<SubrangePoint>> output = GeomList.newInstance();
1391        output.add(PointString.newInstance());
1392        output.add(PointString.newInstance());
1393        if (!GeomUtil.lineSegBoundsIntersect(line.getStart(), line.getEnd(), this))
1394            return output;
1395
1396        //  Check for a degenerate curve case.
1397        if (isDegenerate(tol)) {
1398            //  Just see if the start point on this curve intersects that curve (is on that curve).
1399            SubrangePoint p1 = getPoint(0);
1400            Point p1r = p1.copyToReal();
1401
1402            //  Find the closest point on the input curve.
1403            SubrangePoint p2 = line.getClosest(p1r, GTOL);
1404
1405            if (p1r.distance(p2).isLessThan(tol)) {
1406                //  Add the points to the lists of intersections being collected.
1407                output.get(0).add(p1);
1408                output.get(1).add(p2);
1409            }
1410
1411            return output;
1412        }
1413
1414        //  Create a line segment-curve intersector object.
1415        LineSegNurbsCurveIntersect intersector = LineSegNurbsCurveIntersect.newInstance(this, tol, line, output);
1416
1417        //  Intersect the line segment and curve (intersections are added to "output").
1418        output = intersector.intersect();
1419
1420        //  Clean up.
1421        LineSegNurbsCurveIntersect.recycle(intersector);
1422
1423        return output;
1424    }
1425
1426    /**
1427     * Return a NURBS curve representation of this curve to within the specified
1428     * tolerance. This implementation returns a reference to this curve itself since it is
1429     * already a NURBS curve and the tolerance parameter is ignored.
1430     *
1431     * @param tol Ignored. May pass <code>null</code>.
1432     * @return A reference to this NURBS curve is returned.
1433     */
1434    @Override
1435    public NurbsCurve toNurbs(Parameter<Length> tol) {
1436        return this;
1437    }
1438
1439    /**
1440     * Return a NURBS curve representation of this curve. This implementation returns a
1441     * reference to this curve itself since it is already a NURBS curve.
1442     *
1443     * @return A reference to this NURBS curve is returned.
1444     */
1445    public NurbsCurve toNurbs() {
1446        return this;
1447    }
1448
1449    /**
1450     * Return <code>true</code> if this NurbsCurve contains valid and finite numerical
1451     * components. A value of <code>false</code> will be returned if any of the control
1452     * point values or knots are NaN or Inf.
1453     *
1454     * @return true if this curve contains valid and finite values.
1455     */
1456    @Override
1457    public boolean isValid() {
1458
1459        //  Check the knot vector.
1460        if (!getKnotVector().isValid())
1461            return false;
1462
1463        //  Check the control points.
1464        List<ControlPoint> controlPoints = getControlPoints();
1465        int size = controlPoints.size();
1466        for (int i = 0; i < size; ++i) {
1467            ControlPoint cp = controlPoints.get(i);
1468            if (!cp.isValid())
1469                return false;
1470        }
1471
1472        return true;
1473    }
1474
1475    /**
1476     * Return <code>true</code> if this curve is degenerate (i.e.: has length less than
1477     * the specified tolerance).
1478     *
1479     * @param tol The tolerance for determining if this curve is degenerate. May not be null.
1480     * @return true if this curve is degenerate.
1481     * @see #getArcLength
1482     */
1483    @Override
1484    public boolean isDegenerate(Parameter<Length> tol) {
1485        requireNonNull(tol);
1486        
1487        //  For a NURBS curve this is much faster than computing
1488        //  the length of the curve using getArcLength() and then comparing
1489        //  with tol.
1490        Parameter<Length> cLength = getCPPolyLength();
1491        return cLength.compareTo(tol) <= 0;
1492    }
1493
1494    /**
1495     * Returns <code>true</code> if this curve is a line to within the specified
1496     * tolerance.
1497     *
1498     * @param tol The tolerance for determining if this curve is a line. If null is
1499     *            passed, then exact linearity is required.
1500     * @return true if this curve is a line.
1501     */
1502    @Override
1503    public boolean isLine(Parameter<Length> tol) {
1504
1505        //  Make some simple checks on linearity first.
1506        if (getDegree() == 1) {
1507            StackContext.enter();
1508            try {
1509
1510                //  Get the control points.
1511                List<ControlPoint> controlPoints = getControlPoints();
1512
1513                //  First check for an obvious case.
1514                int numCp = controlPoints.size();
1515                if (numCp <= 2) {
1516                    return true;
1517                }
1518
1519                //  Are all the weights equal to 1?
1520                boolean isLinear = true;
1521                int size = controlPoints.size();
1522                for (int i = 0; i < size; ++i) {
1523                    ControlPoint cp = controlPoints.get(i);
1524                    if (MathTools.isApproxEqual(cp.getWeight(), 1)) {
1525                        isLinear = false;
1526                        break;
1527                    }
1528                }
1529
1530                //  If degree == 1, weights == 1, and all the control points are co-linear,
1531                //  then the curve is a line.
1532                if (isLinear) {
1533                    Point cp0 = controlPoints.get(0).getPoint();
1534                    Point cp1 = controlPoints.get(1).getPoint();
1535                    Vector<Dimensionless> u = cp1.minus(cp0).toGeomVector().toUnitVector();
1536                    for (int i = 2; i < numCp; ++i) {
1537                        Point cp = controlPoints.get(i).getPoint();
1538                        Parameter<Length> d = GeomUtil.pointLineDistance(cp, cp0, u);
1539                        if (d.isGreaterThan(tol)) {
1540                            isLinear = false;
1541                            break;
1542                        }
1543                    }
1544                    if (isLinear)
1545                        return true;
1546                }
1547
1548            } finally {
1549                StackContext.exit();
1550            }
1551        }
1552
1553        //  If simple checks fail, try more complex tests for linearity.
1554        return super.isLine(tol);
1555    }
1556
1557    /**
1558     * Return <code>true</code> if this curve is planar or <code>false</code> if it is
1559     * not.
1560     *
1561     * @param tol The geometric tolerance to use in determining if the curve is planar.
1562     *            May not be null.
1563     * @return true if this curve is planar.
1564     */
1565    @Override
1566    public boolean isPlanar(Parameter<Length> tol) {
1567        //  If the curve is less than 3D, then it must be planar.
1568        int numDims = getPhyDimension();
1569        if (numDims <= 2)
1570            return true;
1571
1572        //  Is this curve degenerate?
1573        if (isDegenerate(requireNonNull(tol)))
1574            return true;
1575
1576        //  Is the curve linear?
1577        if (isLine(tol))
1578            return true;
1579
1580        //  The curve is planar if the control points are co-planar.
1581        StackContext.enter();
1582        try {
1583
1584            //  Get the control points.
1585            List<ControlPoint> cps = getControlPoints();
1586
1587            //  If there are only two or three control points, then the curve is planar by definition
1588            //  (it is a line or triangle).
1589            int numCP = cps.size();
1590            if (numCP <= 3)
1591                return true;
1592
1593            //  Form a vector with the 1st two control points.
1594            Point p0 = cps.get(0).getPoint();
1595            Point p1 = cps.get(1).getPoint();
1596            Vector<Length> v1 = p1.toGeomVector().minus(p0.toGeomVector());
1597
1598            //  Form a vector with the 2nd and 3rd control points.
1599            Point p2 = cps.get(2).getPoint();
1600            Vector<Length> v2 = p2.toGeomVector().minus(p1.toGeomVector());
1601
1602            //  Cross product of these two vectors defines the plane.
1603            Point ZERO_POINT = Point.newInstance(numDims);
1604            Vector<Dimensionless> nhat = v1.cross(v2).toUnitVector();
1605            nhat.setOrigin(ZERO_POINT);
1606
1607            //  Find deviation from a plane for each point by projecting a vector from p1 to pi
1608            //  onto the normal formed by the cross product of the 1st two vectors.
1609            //  If the projection is anything other than zero, the points and tangent
1610            //  vectors are not in the same plane.
1611            for (int i = 3; i < numCP; ++i) {
1612                Point pi = cps.get(i).getPoint();
1613                Vector<Length> p1pi = pi.minus(p1).toGeomVector();
1614                if (!p1pi.mag().isApproxZero()) {
1615                    Parameter proj = p1pi.dot(nhat);
1616                    if (proj.isLargerThan(tol))
1617                        return false;
1618                }
1619            }
1620
1621        } finally {
1622            StackContext.exit();
1623        }
1624
1625        return true;
1626    }
1627
1628    /**
1629     * Return a list containing parameters at the start of logical segments of this curve.
1630     * The first element in this list must always be 0.0 and the last element 1.0. These
1631     * should be good places to break the curve up into pieces for analysis, but otherwise
1632     * are arbitrary.
1633     * <p>
1634     * This implementation returns the parameters at the start of each Bezier segment of
1635     * this NURBS curve.
1636     * </p>
1637     *
1638     * @return A list of parameters at the start and end of each Bezier segment.
1639     */
1640    @Override
1641    protected FastTable<Double> getSegmentParameters() {
1642        return CurveUtils.getBezierStartParameters(this);
1643    }
1644
1645    /**
1646     * Return the number of points per segment that should be used when analyzing curve
1647     * segments by certain methods.
1648     * <p>
1649     * This implementation always returns 2*(p + 1) where p is the degree of the curve.
1650     * </p>
1651     *
1652     * @return The number of points per segment that should be used when analyzing this
1653     *         curve.
1654     */
1655    @Override
1656    protected int getNumPointsPerSegment() {
1657        return 2 * (getDegree() + 1);
1658    }
1659
1660    /**
1661     * Returns the text representation of this geometry element that consists of the name
1662     * followed by the control point values, followed by the knot vector. For example:
1663     * <pre>
1664     *   {aCurve = {{{1 ft, 0 ft}, 1.0}, {{0 ft, 1 ft}, 0.25}, {{-1 ft, 0 ft}, 1.0}},{degree=2,{0.0, 0.0, 0.0, 1.0, 1.0, 1.0}}}
1665     * </pre>
1666     * If there is no name, then the output looks like this:
1667     * <pre>
1668     *   {{{{1 ft, 0 ft}, 1.0}, {{0 ft, 1 ft}, 0.25}, {{-1 ft, 0 ft}, 1.0}},{degree=2,{0.0, 0.0, 0.0, 1.0, 1.0, 1.0}}}
1669     * </pre>
1670     *
1671     * @return the text representation of this geometry element.
1672     */
1673    @Override
1674    public Text toText() {
1675        TextBuilder tmp = TextBuilder.newInstance();
1676        tmp.append('{');
1677        String nameStr = getName();
1678        boolean hasName = nonNull(nameStr);
1679        if (hasName) {
1680            tmp.append(nameStr);
1681            tmp.append(" = {");
1682        }
1683        List<ControlPoint> cpList = getControlPoints();
1684        int size = cpList.size();
1685        for (int i = 0; i < size; i++) {
1686            tmp.append(cpList.get(i).toText());
1687            if (i != size - 1) {
1688                tmp.append(", ");
1689            }
1690        }
1691        tmp.append("},");
1692
1693        tmp.append(getKnotVector().toText());
1694
1695        if (hasName)
1696            tmp.append('}');
1697        tmp.append('}');
1698        Text txt = tmp.toText();
1699        TextBuilder.recycle(tmp);
1700        return txt;
1701    }
1702
1703    /**
1704     * A class used to intersect an line segment and this NURBS curve.
1705     */
1706    private static class LineSegNurbsCurveIntersect {
1707
1708        private NurbsCurve _thisCurve;
1709        private Parameter<Length> _tol;
1710        private LineSegment _line;
1711        private GeomPoint _p0;
1712        private GeomPoint _p1;
1713        private Vector<Length> _Lu;
1714        private GeomList<PointString<SubrangePoint>> _output;
1715
1716        public static LineSegNurbsCurveIntersect newInstance(NurbsCurve thisCurve, Parameter<Length> tol,
1717                LineSegment line, GeomList<PointString<SubrangePoint>> output) {
1718
1719            //  Make sure the line and curve have the same dimensions.
1720            int numDims = thisCurve.getPhyDimension();
1721            if (line.getPhyDimension() > numDims)
1722                throw new IllegalArgumentException(MessageFormat.format(
1723                        RESOURCES.getString("sameOrFewerDimErr"), "line", "curve"));
1724            else if (line.getPhyDimension() < numDims)
1725                line = line.toDimension(numDims);
1726
1727            if (thisCurve instanceof GeomTransform)
1728                thisCurve = thisCurve.copyToReal();
1729            if (line instanceof GeomTransform)
1730                line = line.copyToReal();
1731
1732            //  Create an instance of this class and fill in the class variables.
1733            LineSegNurbsCurveIntersect o = FACTORY.object();
1734            o._thisCurve = thisCurve;
1735            o._tol = tol;
1736            o._line = line;
1737            o._p0 = line.getStart();
1738            o._p1 = line.getEnd();
1739            o._Lu = line.getDerivativeVector().immutable();
1740            o._output = output;
1741
1742            return o;
1743        }
1744
1745        /**
1746         * Method that actually carries out the intersection adding the intersection
1747         * points to the list of lists provided in the constructor.
1748         *
1749         * @return A list containing two PointString instances each containing zero or
1750         *         more subrange points, on this curve and the input line segment
1751         *         respectively, made by the intersection of this curve with the specified
1752         *         line segment. If no intersections are found a list of two empty
1753         *         PointStrings are returned.
1754         */
1755        public GeomList<PointString<SubrangePoint>> intersect() {
1756
1757            //  Decompose the NurbsCurve into a set of Bezier curve segments.
1758            GeomList<BasicNurbsCurve> segs = CurveUtils.decomposeToBezier(_thisCurve);
1759
1760            //  Create a list of segment starting par. positions (and the last end position, 1).
1761            FastTable<Double> bParams = _thisCurve.getSegmentParameters();
1762
1763            //  Work with each segment separately.
1764            int numSegs = segs.size();
1765            for (int i = 0; i < numSegs; ++i) {
1766                NurbsCurve curveSeg = segs.get(i);
1767                if (GeomUtil.lineSegBoundsIntersect(_p0, _p1, curveSeg)) {
1768                    double s0 = bParams.get(i);
1769                    double s1 = bParams.get(i + 1);
1770                    divideAndConquerLineSeg(curveSeg, s0, s1);
1771                }
1772            }
1773
1774            //  Clean up.
1775            FastTable.recycle(bParams);
1776            GeomList.recycle(segs);
1777
1778            return _output;
1779        }
1780
1781        /**
1782         * Uses a recursive "Divide and Conquer" approach to intersecting a line segment
1783         * with a curve. On each call, the following happens:
1784         * <pre>
1785         *      1) The curve is checked to see if it is approx. a straight line.
1786         *          1b) If it is, then a line-line intersection is performed, the
1787         *              intersection point added to the output list and the method exited.
1788         *      2) The curve is divided into two halves.
1789         *          2b) Each half is tested to see if it's bounding box is intersected by the line.
1790         *              If it is, then this method is called with that segment recursively.
1791         *              Otherwise, the method is exited.
1792         * </pre>
1793         * A number of class variables are used to pass information to this
1794         * recursion:
1795         * <pre>
1796         *      _tol  The tolerance to use when determining if the curve is locally linear.
1797         *      _line The line segment instance we are intersecting the curve with.
1798         *      _p0   A point at the start of the line being intersected with this curve.
1799         *      _p1   A point at the end of the line being intersected with this curve.
1800         *      _Lu   A vector indicating the direction of the line.
1801         *      _output A list used to collect the output subrange intersection points.
1802         * </pre>
1803         *
1804         * @param crv The curve segment being checked for intersection with the line.
1805         * @param s0  The parametric position of the start of this curve segment on the
1806         *            master curve.
1807         * @param s1  The parametric position of the end of this curve segment on the
1808         *            master curve.
1809         */
1810        private void divideAndConquerLineSeg(NurbsCurve crv, double s0, double s1) {
1811
1812            //  Check to see if the input curve is within tolerance of a straight line.
1813            Point p0 = crv.getRealPoint(0);
1814            Point p1 = crv.getRealPoint(1);
1815            Point pAvg = p0.plus(p1).divide(2);
1816            Point pm = crv.getRealPoint(0.5);
1817            if (pAvg.distance(pm).isLessThan(_tol)) {
1818                //  The input curve segment is nearly a straight line.
1819
1820                //  Intersect the input line and the line approximating the curve.
1821                Vector<Length> crvDir = p1.minus(p0).toGeomVector();
1822                MutablePoint sLn = MutablePoint.newInstance(2);
1823                int numDims = p0.getPhyDimension();
1824                Unit<Length> unit = p0.getUnit();
1825                MutablePoint pi1 = MutablePoint.newInstance(numDims, unit);
1826                MutablePoint pi2 = MutablePoint.newInstance(numDims, unit);
1827                IntersectType type = GeomUtil.lineLineIntersect(_p0, _Lu, p0, crvDir, _tol, sLn, pi1, pi2);
1828                Vector.recycle(crvDir);
1829
1830                //  Make sure the intersection points fall on the line segments.
1831                double sl_1 = sLn.getValue(0);
1832                if (sl_1 > 1) {
1833                    pi1.set(_line.getEnd());
1834                    sl_1 = 1;
1835                } else if (sl_1 < 0) {
1836                    pi1.set(_line.getStart());
1837                    sl_1 = 0;
1838                }
1839                double sl_2 = sLn.getValue(1);
1840                if (sl_2 > 1) {
1841                    pi2.set(crv.getRealPoint(1));
1842                    sl_2 = 1;
1843                } else if (sl_2 < 0) {
1844                    pi2.set(crv.getRealPoint(0));
1845                    sl_2 = 0;
1846                }
1847                if (pi1.distance(pi2).isLessThan(_tol)) {
1848                    //  Add an intersection point on each curve to the output lists.
1849                    _output.get(1).add(_line.getPoint(sl_1));
1850                    double s = s0 + (s1 - s0) * sl_2;   //  Convert from segment par. pos. to full curve pos.
1851                    s = roundParNearEnds(s);            //  Deal with roundoff issues.
1852                    _output.get(0).add(_thisCurve.getPoint(s));
1853                }
1854
1855                //  Clean up.
1856                MutablePoint.recycle(sLn);
1857                MutablePoint.recycle(pi1);
1858                MutablePoint.recycle(pi2);
1859
1860            } else {
1861                //  Sub-divide the curve into two segments.
1862                GeomList<NurbsCurve> segments = crv.splitAt(0.5);
1863
1864                //  Check for possible intersections on the left segment.
1865                NurbsCurve seg = segments.get(0);
1866                if (GeomUtil.lineSegBoundsIntersect(_p0, _p1, seg)) {
1867                    //  May be an intersection.
1868                    double sl = s0;
1869                    double sh = (s0 + s1) / 2;
1870
1871                    //  Recurse down to a finer level of detail.
1872                    divideAndConquerLineSeg(seg, sl, sh);
1873                }
1874                BasicNurbsCurve.recycle((BasicNurbsCurve)seg);
1875
1876                //  Check for possible intersections on the right segment.
1877                seg = segments.get(1);
1878                if (GeomUtil.lineSegBoundsIntersect(_p0, _p1, seg)) {
1879                    //  May be an intersection.
1880                    double sl = (s0 + s1) / 2;
1881                    double sh = s1;
1882
1883                    //  Recurse down to a finer level of detail.
1884                    divideAndConquerLineSeg(seg, sl, sh);
1885                }
1886                BasicNurbsCurve.recycle((BasicNurbsCurve)seg);
1887
1888                //  Clean up.
1889                GeomList.recycle(segments);
1890            }
1891
1892        }
1893
1894        public static void recycle(LineSegNurbsCurveIntersect instance) {
1895            FACTORY.recycle(instance);
1896        }
1897
1898        private LineSegNurbsCurveIntersect() { }
1899
1900        private static final ObjectFactory<LineSegNurbsCurveIntersect> FACTORY = new ObjectFactory<LineSegNurbsCurveIntersect>() {
1901            @Override
1902            protected LineSegNurbsCurveIntersect create() {
1903                return new LineSegNurbsCurveIntersect();
1904            }
1905
1906            @Override
1907            protected void cleanup(LineSegNurbsCurveIntersect obj) {
1908                obj._thisCurve = null;
1909                obj._tol = null;
1910                obj._line = null;
1911                obj._p0 = null;
1912                obj._p1 = null;
1913                obj._Lu = null;
1914                obj._output = null;
1915            }
1916        };
1917    }
1918
1919    /**
1920     * Holds the default XML representation for this object.
1921     */
1922    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
1923    protected static final XMLFormat<NurbsCurve> XML = new XMLFormat<NurbsCurve>(NurbsCurve.class) {
1924
1925        @Override
1926        public void read(XMLFormat.InputElement xml, NurbsCurve obj) throws XMLStreamException {
1927            String name = xml.getAttribute("name", (String)null);
1928            if (nonNull(name))
1929                obj.setName(name);
1930            FastMap userData = xml.get("UserData", FastMap.class);
1931            if (nonNull(userData)) {
1932                obj.putAllUserData(userData);
1933            }
1934        }
1935
1936        @Override
1937        public void write(NurbsCurve obj, XMLFormat.OutputElement xml) throws XMLStreamException {
1938            String name = obj.getName();
1939            if (nonNull(name))
1940                xml.setAttribute("name", name);
1941            FastMap userData = (FastMap)obj.getAllUserData();
1942            if (!userData.isEmpty())
1943                xml.add(userData, "UserData", FastMap.class);
1944        }
1945    };
1946
1947}