001/**
002 * BasicNurbsCurve -- A basic implementation of NURBS curve.
003 *
004 * Copyright (C) 2009-2015 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.Point;
024import geomss.geom.Vector;
025import jahuwaldt.js.math.BinomialCoef;
026import jahuwaldt.tools.math.MathTools;
027import java.text.MessageFormat;
028import java.util.List;
029import java.util.Objects;
030import static java.util.Objects.isNull;
031import static java.util.Objects.nonNull;
032import javax.measure.converter.ConversionException;
033import javax.measure.quantity.Length;
034import javax.measure.unit.Unit;
035import javolution.context.ArrayFactory;
036import javolution.context.ObjectFactory;
037import javolution.context.StackContext;
038import javolution.lang.MathLib;
039import javolution.lang.ValueType;
040import javolution.text.Text;
041import javolution.text.TextBuilder;
042import javolution.util.FastTable;
043import javolution.xml.XMLFormat;
044import javolution.xml.stream.XMLStreamException;
045
046/**
047 * A basic implementation of a parametric NURBS curve.
048 * 
049 * <p> Modified by: Joseph A. Huwaldt </p>
050 *
051 * @author Samuel Gerber, Date: May 14, 2009, Version 1.0.
052 * @version November 28, 2015
053 */
054@SuppressWarnings({"serial", "CloneableImplementsClone"})
055public final class BasicNurbsCurve extends NurbsCurve implements ValueType {
056
057    private static final double TOL = 1.0e-14;          //  Toleranace for avoiding 0/0 in binomial.
058
059    private FastTable<ControlPoint> _cpoly;
060    private KnotVector _uKnots;
061
062    /*
063     *  References:
064     *  1.) Michael E. Mortenson, "Geometric Modeling, Third Edition", ISBN:9780831132989, 2008.
065     *  2.) Piegl, L., Tiller, W., The Nurbs Book, 2nd Edition, Springer-Verlag, Berlin, 1997.
066     */
067    
068    /**
069     * Create a NURBS curve from the given control points, knots and degree.
070     *
071     * @param cps    Array of control points.  May not be null.
072     * @param degree Degree of the NURBS curve
073     * @param uK     Knot values.  May not be null.
074     * @return A new NURBS curve
075     * @throws IllegalArgumentException if the knot vector is not valid.
076     */
077    public static BasicNurbsCurve newInstance(ControlPoint cps[], int degree, double... uK) {
078        if (cps.length < 1)
079            throw new IllegalArgumentException(RESOURCES.getString("noControlPointsErr"));
080
081        if (uK.length != degree + cps.length + 1)
082            throw new IllegalArgumentException(
083                    MessageFormat.format(RESOURCES.getString("crvWrongNumKnotsErr"),
084                            uK.length, degree + cps.length + 1));
085
086        //  Convert the units of the input control points.
087        Unit<Length> refUnit = cps[0].getUnit();
088        FastTable<ControlPoint> cpList = FastTable.newInstance();
089        int length = cps.length;
090        for (int i = 0; i < length; ++i) {
091            cpList.add(cps[i].to(refUnit));
092        }
093        KnotVector knots = KnotVector.newInstance(degree, uK);
094
095        //  Build the object.
096        BasicNurbsCurve o = FACTORY.object();
097        o._cpoly = cpList;
098        o._uKnots = knots;
099
100        return o;
101    }
102
103    /**
104     * Generate a NURBS curve from the given control points and the given knot vector.
105     *
106     * @param cpList List of control points.  May not be null.
107     * @param uKnots Knot vector for the curve.  May not be null.
108     * @return A new NURBS curve.
109     * @throws IllegalArgumentException if the knot vector is not consistent with the
110     * number of control points.
111     */
112    public static BasicNurbsCurve newInstance(List<ControlPoint> cpList, KnotVector uKnots) {
113        if (cpList.size() < 1)
114            throw new IllegalArgumentException(RESOURCES.getString("noControlPointsErr"));
115
116        if (uKnots.length() != uKnots.getDegree() + cpList.size() + 1)
117            throw new IllegalArgumentException(
118                    MessageFormat.format(RESOURCES.getString("crvWrongNumKnotsErr"),
119                            uKnots.length(), uKnots.getDegree() + cpList.size() + 1));
120
121        //  Convert the units of the input control points.
122        Unit<Length> refUnit = cpList.get(0).getUnit();
123        FastTable<ControlPoint> nCpList = FastTable.newInstance();
124        int size = cpList.size();
125        for (int i = 0; i < size; ++i) {
126            ControlPoint cp = cpList.get(i);
127            nCpList.add(cp.to(refUnit));
128        }
129
130        //  Build the object.
131        BasicNurbsCurve o = FACTORY.object();
132        o._cpoly = nCpList;
133        o._uKnots = uKnots;
134
135        return o;
136    }
137
138    /**
139     * Returns the number of child-elements that make up this geometry element. This
140     * implementation returns the number of control points in this NURBS curve.
141     *
142     * @return The number of control points in this curve.
143     */
144    @Override
145    public int size() {
146        return _cpoly.size();
147    }
148
149    /**
150     * Returns the number of physical dimensions of the geometry element.
151     *
152     * @return The number of physical dimensions of this geometry element.
153     */
154    @Override
155    public int getPhyDimension() {
156        return _cpoly.get(0).getPhyDimension();
157    }
158
159    /**
160     * Return a list of control points for this curve.
161     *
162     * @return the ordered control points
163     */
164    @Override
165    public List<ControlPoint> getControlPoints() {
166        List<ControlPoint> list = FastTable.newInstance();
167        list.addAll(_cpoly);
168        return list;
169    }
170
171    /**
172     * Return the knot vector of this curve.
173     *
174     * @return The knot vector.
175     */
176    @Override
177    public KnotVector getKnotVector() {
178        return _uKnots;
179    }
180
181    /**
182     * Return the degree of the NURBS curve.
183     *
184     * @return degree of curve
185     */
186    @Override
187    public int getDegree() {
188        return _uKnots.getDegree();
189    }
190
191    /**
192     * Calculate a point on the curve for the given parametric distance along the curve,
193     * <code>p(s)</code>.
194     *
195     * @param s parametric distance to calculate a point for (0.0 to 1.0 inclusive).
196     * @return the calculated point
197     */
198    @Override
199    public Point getRealPoint(double s) {
200        validateParameter(s);
201
202        //  End-point interpolation: C(0) = P0, and C(1) = Pn.
203        if (parNearStart(s, TOL_S))         //  if (s == 0)
204            return _cpoly.get(0).getPoint();
205        else if (parNearEnd(s, TOL_S))      //  if (s == 1)
206            return _cpoly.get(_cpoly.size() - 1).getPoint();
207
208        StackContext.enter();
209        try {
210            //  Algorithm: A4.1, Ref. 2.
211            /*
212             This routine calculates the following:
213             P(s) = sum_{i=1}^{N} ( Pi*Wi*Nik(s) ) / sum_{i=1}^{N} ( Wi*Nik(s) )
214             */
215            Unit<Length> unit = getUnit();
216            int span = _uKnots.findSpan(s);
217            int degree = _uKnots.getDegree();
218            int sind = span - degree;
219
220            //  Get the basis function values (Nik).
221            double[] Nk = _uKnots.basisFunctions(span, s);
222
223            //  Apply the basis functions to each degree of the curve.
224            //  Calculate "cw" point as sum_{i=1}^N (Pi*Wi*Nik(s)) and "cw" weight as sum_{i=1}^N (Wi*Ni,k(s)).
225            ControlPoint cw = ControlPoint.newInstance(getPhyDimension(), unit);
226            for (int i = 0; i <= degree; i++) {
227                ControlPoint tmp = _cpoly.get(sind + i).applyWeight();
228                tmp = tmp.times(Nk[i]);
229                cw = cw.plus(tmp);
230            }
231
232            //  Convert the control point to a geometric point.
233            //  The "cw" weight is the sum of Wi*Ni,k calculated above.
234            Point outPoint = cw.getPoint();
235            if (outPoint.normValue() > MathTools.SQRT_EPS)
236                outPoint = outPoint.divide(cw.getWeight());
237            outPoint = outPoint.to(unit);
238
239            return StackContext.outerCopy(outPoint);
240        } finally {
241            StackContext.exit();
242        }
243    }
244
245    /**
246     * Calculate all the derivatives from <code>0</code> to <code>grade</code> with
247     * respect to parametric distance on the curve for the given parametric distance along
248     * the curve, <code>d^{grade}p(s)/d^{grade}s</code>.
249     * <p>
250     * Example:<br>
251     * 1st derivative (grade = 1), this returns <code>[p(s), dp(s)/ds]</code>;<br>
252     * 2nd derivative (grade = 2), this returns <code>[p(s), dp(s)/ds, d^2p(s)/d^2s]</code>; etc.
253     * </p>
254     *
255     * @param s     Parametric distance to calculate derivatives for (0.0 to 1.0
256     *              inclusive).
257     * @param grade The maximum grade to calculate the derivatives for (1=1st derivative,
258     *              2=2nd derivative, etc)
259     * @return A list of derivatives up to the specified grade of the curve at the
260     *         specified parametric position.
261     * @throws IllegalArgumentException if the grade is &lt; 0.
262     */
263    @Override
264    public List<Vector<Length>> getSDerivatives(double s, int grade) {
265        validateParameter(s);
266        s = roundParNearEnds(s);
267
268        if (grade < 0)
269            throw new IllegalArgumentException(RESOURCES.getString("gradeLTZeroErr"));
270
271        //  Uses Algorithm: A4.2, Ref. 2, pg. 127
272        //  First find unweighted derivatives of a B-Spline
273        //  Algorithm: A3.2, Ref. 2, pg. 93.
274        int dim = getPhyDimension();
275        int degree = _uKnots.getDegree();
276        int span = _uKnots.findSpan(s);
277        int sind = span - degree;
278        int ds = MathLib.min(grade, degree);
279        int gradeP1 = grade + 1;
280
281        Unit<Length> unit = getUnit();
282        Vector[] cOut = Vector.allocateArray(gradeP1);   //  Created outside of StackContext.
283        StackContext.enter();
284        try {
285            //  Get the basis function derivative values for a B-Spline (unweighted derivatives).
286            double[][] Nik = _uKnots.basisFunctionDerivatives(span, s, ds);
287
288            //  Calculate z = sum_i^N ( Wi*Nik ) for each basis function derivative and
289            //  p = sum_i^N (Pi*Wi*Nik) for each control point and basis function derivative.
290            int order = degree + 1;
291            double[] z = ArrayFactory.DOUBLES_FACTORY.array(gradeP1);
292            Point[] p = Point.allocateArray(gradeP1);
293            for (int k = order; k <= grade; ++k) {
294                z[k] = 0.;
295                p[k] = Point.newInstance(dim, unit);
296            }
297            for (int k = 0; k <= ds; ++k) {
298                z[k] = 0.;
299                p[k] = Point.newInstance(dim, unit);
300                for (int i = 0; i < order; i++) {
301                    ControlPoint cpi = _cpoly.get(sind + i);
302                    double Wi = cpi.getWeight();
303                    double WiNik = Wi * Nik[k][i];
304                    z[k] += WiNik;
305                    p[k] = p[k].plus(cpi.getPoint().times(WiNik));
306                }
307            }
308
309            //  Compute the weighted derivatives by stepping through a binomial expansion for each derivative.
310            //    Algorithm: A4.2, Ref. 2, pg. 127      
311            Point[] c = binomial(p, z, gradeP1);
312
313            //  Copy the final points out of the StackContext.
314            for (int i = gradeP1-1; i >= 0; --i) {
315                cOut[i] = StackContext.outerCopy(c[i].to(unit).toGeomVector());
316            }
317
318        } finally {
319            StackContext.exit();
320        }
321
322        //  Convert to a list of Vector objets.
323        FastTable<Vector<Length>> output = FastTable.newInstance();
324        Point origin = Point.valueOf(cOut[0]);
325        for (int i = 0; i <= grade; ++i) {
326            Vector<Length> v = cOut[i];
327            v.setOrigin(origin);
328            output.add(v);
329        }
330
331        Vector.recycleArray(cOut);
332
333        return output;
334    }
335
336    /**
337     * Compute the weighted derivatives by stepping through a binomial expansion for each
338     * derivative. e.g.:
339     * <pre>
340     *   surface point, c(0) = p(0)/z(0)
341     *      1st derivative, c(1) = (p(1) - c(0)*z(1))/z(0),
342     *      2nd derivative, c(2) = (p(2) - c(0)*z(2) - 2*c(1)*z(1))/z(0),
343     *      3rd derivative, c(3) = (p(3) - c(0)*z(3) - 3*c(1)*z(2) - 3*c(2)*z(1))/z(0),
344     *      etc
345     * </pre>
346     *
347     * @param pnts    The list of weighted control points: p[0..grade+1].
348     * @param z       The derivative weights (Wi*Nik): z[0..grade+1].
349     * @param gradeP1 The maximum grade to calculate the v-derivatives for (1=1st
350     *                derivative, 2=2nd derivative, etc) + 1.
351     */
352    private static Point[] binomial(Point[] pnts, double[] z, int gradeP1) {
353
354        BinomialCoef bin = BinomialCoef.newInstance(gradeP1);   //  Get the binomial coefficients.
355
356        Point[] c = Point.allocateArray(gradeP1);
357        Point ZERO = Point.newInstance(pnts[0].getPhyDimension(), pnts[0].getUnit());
358        double z0inv = 1.0 / z[0];
359        for (int k = 0; k < gradeP1; ++k) {
360            Point v = pnts[k];
361            for (int i = k; i > 0; --i) {
362                v = v.minus(c[k - i].times(bin.get(k, i) * z[i]));
363            }
364            if (v.normValue() > TOL)        //  Handles 0/0 == 0.
365                c[k] = v.times(z0inv);
366            else
367                c[k] = ZERO;  //  v.times(0);
368        }
369
370        return c;
371    }
372
373    /**
374     * Return the coordinate point representing the minimum bounding box corner of this
375     * geometry element (e.g.: min X, min Y, min Z).
376     *
377     * @return The minimum bounding box coordinate for this geometry element.
378     * @throws IndexOutOfBoundsException if this list contains no geometry.
379     */
380    @Override
381    public Point getBoundsMin() {
382
383        Point minPoint = _cpoly.get(0).getBoundsMin();
384
385        int size = _cpoly.size();
386        for (int i = 0; i < size; ++i) {
387            ControlPoint cp = _cpoly.get(i);
388            minPoint = minPoint.min(cp.getBoundsMin());
389        }
390
391        return minPoint;
392    }
393
394    /**
395     * Return the coordinate point representing the maximum bounding box corner (e.g.: max
396     * X, max Y, max Z).
397     *
398     * @return The maximum bounding box coordinate for this geometry element.
399     * @throws IndexOutOfBoundsException if this list contains no elements.
400     */
401    @Override
402    public Point getBoundsMax() {
403
404        Point maxPoint = _cpoly.get(0).getBoundsMax();
405
406        int size = _cpoly.size();
407        for (int i = 0; i < size; ++i) {
408            ControlPoint cp = _cpoly.get(i);
409            maxPoint = maxPoint.max(cp.getBoundsMax());
410        }
411
412        return maxPoint;
413    }
414
415    /**
416     * Returns the unit in which the control points in this curve are stated.
417     *
418     * @return The unit that this curves points are stated in.
419     */
420    @Override
421    public Unit<Length> getUnit() {
422        return _cpoly.get(0).getPoint().getUnit();
423    }
424
425    /**
426     * Returns the equivalent to this curve but stated in the specified unit.
427     *
428     * @param unit The length unit of the curve to be returned. May not be null.
429     * @return An equivalent to this curve but stated in the specified unit.
430     * @throws ConversionException if the the input unit is not a length unit.
431     */
432    @Override
433    public BasicNurbsCurve to(Unit<Length> unit) throws ConversionException {
434        if (unit.equals(getUnit()))
435            return this;
436
437        //  Convert the control points.
438        FastTable<ControlPoint> nCpoly = FastTable.newInstance();
439        int size = _cpoly.size();
440        for (int i = 0; i < size; ++i) {
441            ControlPoint cp = _cpoly.get(i);
442            nCpoly.add(cp.to(unit));
443        }
444
445        BasicNurbsCurve curve = BasicNurbsCurve.newInstance(nCpoly, getKnotVector());
446        return copyState(curve);    //  Copy over the super-class state for this object to the new one.
447    }
448
449    /**
450     * Return the equivalent of this curve converted to the specified number of physical
451     * dimensions. If the number of dimensions is greater than this element, then zeros
452     * are added to the additional dimensions. If the number of dimensions is less than
453     * this element, then the extra dimensions are simply dropped (truncated). If the new
454     * dimensions are the same as the dimension of this element, then this element is
455     * simply returned.
456     *
457     * @param newDim The dimension of the curve to return.
458     * @return The equivalent of this curve converted to the new dimensions.
459     */
460    @Override
461    public BasicNurbsCurve toDimension(int newDim) {
462        if (getPhyDimension() == newDim)
463            return this;
464
465        FastTable<ControlPoint> newCpLst = FastTable.newInstance();
466        int size = _cpoly.size();
467        for (int i = 0; i < size; ++i) {
468            ControlPoint cp = _cpoly.get(i);
469            Point pnt = cp.getPoint().toDimension(newDim);
470            ControlPoint newCp = ControlPoint.valueOf(pnt, cp.getWeight());
471            newCpLst.add(newCp);
472        }
473
474        BasicNurbsCurve newCrv = BasicNurbsCurve.newInstance(newCpLst, getKnotVector());
475
476        return copyState(newCrv);   //  Copy over the super-class state for this object to the new one.
477    }
478
479    /**
480     * Returns a copy of this {@link BasicNurbsCurve} instance
481     * {@link javolution.context.AllocatorContext allocated} by the calling thread
482     * (possibly on the stack).
483     *
484     * @return an identical and independent copy of this object.
485     */
486    @Override
487    public BasicNurbsCurve copy() {
488        return copyOf(this);
489    }
490
491    /**
492     * Return a copy of this object with any transformations or subranges removed
493     * (applied).
494     *
495     * @return A copy of this object with any transformations or subranges removed
496     *         (applied).
497     */
498    @Override
499    public BasicNurbsCurve copyToReal() {
500        return copy();
501    }
502
503    /**
504     * Return an immutable version of this NURBS curve. This implementation simply returns
505     * this BasicNurbsCurve instance.
506     *
507     * @return an immutable version of this curve.
508     */
509    @Override
510    public BasicNurbsCurve immutable() {
511        return this;
512    }
513    
514    /**
515     * Resets the internal state of this object to its default values.
516     */
517    @Override
518    public void reset() {
519        _cpoly.reset();
520        super.reset();
521    }
522
523    /**
524     * Compares this BasicNurbsCurve against the specified object for strict equality
525     * (same values and same units).
526     *
527     * @param obj the object to compare with.
528     * @return <code>true</code> if this object is identical to that object;
529     *         <code>false</code> otherwise.
530     */
531    @Override
532    public boolean equals(Object obj) {
533        if (this == obj)
534            return true;
535        if ((obj == null) || (obj.getClass() != this.getClass()))
536            return false;
537
538        BasicNurbsCurve that = (BasicNurbsCurve)obj;
539        return this._cpoly.equals(that._cpoly)
540                && this._uKnots.equals(that._uKnots)
541                && super.equals(obj);
542    }
543
544    /**
545     * Returns the hash code for this parameter.
546     *
547     * @return the hash code value.
548     */
549    @Override
550    public int hashCode() {
551        return 31*super.hashCode() + Objects.hash(_cpoly, _uKnots);
552    }
553
554    /**
555     * Returns the text representation of this geometry element that consists of the name
556     * followed by the control point values, followed by the knot vector. For example:
557     * <pre>
558     *   {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}}}
559     * </pre>
560     * If there is no name, then the output looks like this:
561     * <pre>
562     *   {{{{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}}}
563     * </pre>
564     *
565     * @return the text representation of this geometry element.
566     */
567    @Override
568    public Text toText() {
569        TextBuilder tmp = TextBuilder.newInstance();
570        tmp.append('{');
571        String nameStr = getName();
572        boolean hasName = nonNull(nameStr);
573        if (hasName) {
574            tmp.append(nameStr);
575            tmp.append(" = {");
576        }
577        int size = _cpoly.size();
578        for (int i = 0; i < size; i++) {
579            tmp.append(_cpoly.get(i).toText());
580            if (i != size - 1) {
581                tmp.append(", ");
582            }
583        }
584        tmp.append("},");
585
586        tmp.append(_uKnots.toText());
587
588        if (hasName)
589            tmp.append('}');
590        tmp.append('}');
591        Text txt = tmp.toText();
592        TextBuilder.recycle(tmp);
593        return txt;
594    }
595
596    /**
597     * Recycles a <code>BasicNurbsCurve</code> instance immediately (on the stack when
598     * executing in a StackContext).
599     *
600     * @param instance The instance to be recycled.
601     */
602    public static void recycle(BasicNurbsCurve instance) {
603        FACTORY.recycle(instance);
604    }
605
606    /**
607     * Holds the default XML representation for this object.
608     */
609    @SuppressWarnings("FieldNameHidesFieldInSuperclass")
610    protected static final XMLFormat<BasicNurbsCurve> XML = new XMLFormat<BasicNurbsCurve>(BasicNurbsCurve.class) {
611        @Override
612        public BasicNurbsCurve newInstance(Class<BasicNurbsCurve> cls, InputElement xml) throws XMLStreamException {
613            return FACTORY.object();
614        }
615
616        @SuppressWarnings("unchecked")
617        @Override
618        public void read(InputElement xml, BasicNurbsCurve obj) throws XMLStreamException {
619            NurbsCurve.XML.read(xml, obj);     // Call parent read.
620
621            FastTable<ControlPoint> cpList = xml.get("CPoly", FastTable.class);
622            KnotVector knots = xml.get("Knots", KnotVector.class);
623
624            if (isNull(cpList) || cpList.size() < 1)
625                throw new XMLStreamException(RESOURCES.getString("noControlPointsErr"));
626
627            if (knots.length() != knots.getDegree() + cpList.size() + 1)
628                throw new XMLStreamException(
629                        MessageFormat.format(RESOURCES.getString("crvWrongNumKnotsErr"),
630                                knots.length(), knots.getDegree() + cpList.size() + 1));
631
632            //  Convert the units of the input control points.
633            Unit<Length> refUnit = cpList.get(0).getUnit();
634            FastTable<ControlPoint> nCpList = FastTable.newInstance();
635            int size = cpList.size();
636            for (int i = 0; i < size; ++i) {
637                ControlPoint cp = cpList.get(i);
638                nCpList.add(cp.to(refUnit));
639            }
640            obj._cpoly = nCpList;
641            obj._uKnots = knots;
642
643        }
644
645        @Override
646        public void write(BasicNurbsCurve obj, OutputElement xml) throws XMLStreamException {
647            NurbsCurve.XML.write(obj, xml);    // Call parent write.
648
649            xml.add(obj._cpoly, "CPoly", FastTable.class);
650            xml.add(obj._uKnots, "Knots", KnotVector.class);
651
652        }
653    };
654
655    ///////////////////////
656    // Factory creation. //
657    ///////////////////////
658    private BasicNurbsCurve() { }
659
660    @SuppressWarnings("unchecked")
661    private static final ObjectFactory<BasicNurbsCurve> FACTORY = new ObjectFactory<BasicNurbsCurve>() {
662        @Override
663        protected BasicNurbsCurve create() {
664            return new BasicNurbsCurve();
665        }
666
667        @Override
668        protected void cleanup(BasicNurbsCurve obj) {
669            obj.reset();
670            obj._cpoly = null;
671            obj._uKnots = null;
672        }
673    };
674
675    @SuppressWarnings("unchecked")
676    private static BasicNurbsCurve copyOf(BasicNurbsCurve original) {
677        BasicNurbsCurve o = FACTORY.object();
678        FastTable<ControlPoint> newCPList = FastTable.newInstance();
679        int size = original._cpoly.size();
680        for (int i = 0; i < size; ++i) {
681            ControlPoint cp = original._cpoly.get(i);
682            newCPList.add(cp.copy());
683        }
684        o._cpoly = newCPList;
685        o._uKnots = original._uKnots.copy();
686        original.copyState(o);
687        return o;
688    }
689
690}