001/**
002 * KnotVector -- A collection of knot values used in a NURBS curve.
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 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 slightly modified from, the LGPL jgeom (Geometry
019 * Library for Java) by Samuel Gerber, Copyright (C) 2005.
020 */
021package geomss.geom.nurbs;
022
023import java.text.MessageFormat;
024import java.util.List;
025import java.util.ResourceBundle;
026import javolution.context.ArrayFactory;
027import javolution.context.ObjectFactory;
028import javolution.context.StackContext;
029import javolution.lang.ValueType;
030import javolution.text.Text;
031import javolution.text.TextBuilder;
032import javolution.util.FastTable;
033import javolution.xml.XMLFormat;
034import javolution.xml.XMLSerializable;
035import javolution.xml.stream.XMLStreamException;
036import org.jscience.mathematics.number.Float64;
037import org.jscience.mathematics.vector.Float64Vector;
038
039/**
040 * An immutable collection of knot values that are associated with a NURBS curve or
041 * surface.
042 * 
043 * <p> Modified by: Joseph A. Huwaldt </p>
044 * 
045 * @author Samuel Gerber, Date: May 14, 2009, Version 1.0.
046 * @version February 17, 2025
047 */
048@SuppressWarnings("serial")
049public class KnotVector implements Cloneable, XMLSerializable, ValueType {
050
051    /*
052     * References:
053     *  1.) Piegl, L., Tiller, W., The Nurbs Book, 2nd Edition, Springer-Verlag, Berlin, 1997.
054     */
055    
056    /**
057     * The resource bundle for this package.
058     */
059    private static final ResourceBundle RESOURCES = geomss.geom.AbstractGeomElement.RESOURCES;
060
061    private boolean _open;          //  Indicates if the knot vector is open or closed.
062    private Float64Vector _knots;   //  The vector of knot values.
063    private int _degree;            //  The degree of the curve.
064    private int _nu;
065
066    /**
067     * Create a KnotVector from the given knot values of the desired degree.
068     *
069     * @param degree degree of NURBS curve
070     * @param knots knot values. May not be null.
071     * @return A KnotVector consisting of the given knots and degree.
072     * @throws IllegalArgumentException if the knot vector is not valid.
073     */
074    public static KnotVector newInstance(int degree, Float64Vector knots) {
075        int numKnots = knots.getDimension();
076        for (int i = 1; i < numKnots; i++) {
077            if (knots.getValue(i - 1) > knots.getValue(i)) {
078                throw new IllegalArgumentException(RESOURCES.getString("knotsNotIncreasingErr"));
079            }
080        }
081        for (int i = 0; i < numKnots; ++i) {
082            double kv = knots.getValue(i);
083            if (kv > 1.0 || kv < 0.0) {
084                throw new IllegalArgumentException(
085                        MessageFormat.format(RESOURCES.getString("invalidKnotValue"), kv));
086            }
087        }
088
089        KnotVector o = FACTORY.object();
090
091        o._knots = knots;
092        o._degree = degree;
093        o._nu = knots.getDimension() - degree - 2;
094
095        //Check if it is an open knot vector
096        o._open = determineIfOpen(o);
097
098        return o;
099    }
100
101    /**
102     * Method that determines if the specified KnotVector is open or closed and returns
103     * true if it is open.
104     */
105    private static boolean determineIfOpen(KnotVector o) {
106        Float64Vector knots = o._knots;
107        int degree = o._degree;
108
109        boolean open = true;
110        for (int k = 0; k < degree && open == true; k++) {
111            if (knots.getValue(k) != knots.getValue(k + 1)) {
112                open = false;
113            }
114        }
115        int m = knots.getDimension() - 1;
116        for (int k = m; k > m - degree && open == true; k--) {
117            if (knots.getValue(k) != knots.getValue(k - 1)) {
118                open = false;
119            }
120        }
121
122        return open;
123    }
124
125    /**
126     * Create a KnotVector from the given list of knot values of the desired degree.
127     *
128     * @param degree degree of NURBS curve
129     * @param knots A list of knot values.  May not be null.
130     * @return A KnotVector consisting of the given knots and degree.
131     * @throws IllegalArgumentException if the knot vector is not valid.
132     */
133    public static KnotVector newInstance(int degree, List<Double> knots) throws IllegalArgumentException {
134        FastTable<Float64> kvList = FastTable.newInstance();
135        for (double value : knots) {
136            kvList.add(Float64.valueOf(value));
137        }
138        Float64Vector kv = Float64Vector.valueOf(kvList);
139        return newInstance(degree, kv);
140    }
141
142    /**
143     * Create a KnotVector from the given knot values of the desired degree.
144     *
145     * @param degree degree of NURBS curve
146     * @param knots knot values.  May not be null.
147     * @return A KnotVector consisting of the given knots and degree.
148     * @throws IllegalArgumentException if the knot vector is not valid.
149     */
150    public static KnotVector newInstance(int degree, double... knots) throws IllegalArgumentException {
151        return newInstance(degree, Float64Vector.valueOf(knots));
152    }
153
154    /**
155     * Return a knot vector that can be used to create a Bezier curve segment of the
156     * specified degree using the BasicNurbsCurve class.
157     *
158     * @param degree The degree of the knot vector to return.
159     * @return A knot vector appropriate for creating a Bezier curve segment.
160     */
161    public static KnotVector bezierKnotVector(int degree) {
162
163        StackContext.enter();
164        try {
165            FastTable<Float64> floatLst = FastTable.newInstance();
166            for (int i = 0; i <= degree; ++i) {
167                floatLst.add(Float64.ZERO);
168            }
169            for (int i = 0; i <= degree; ++i) {
170                floatLst.add(Float64.ONE);
171            }
172            KnotVector kvBezier = KnotVector.newInstance(degree, Float64Vector.valueOf(floatLst));
173            FastTable.recycle(floatLst);
174
175            return StackContext.outerCopy(kvBezier);
176        } finally {
177            StackContext.exit();
178        }
179    }
180
181    /**
182     * Returns the span (position of corresponding knot values in knot vector) a given
183     * parameter value belongs to.
184     *
185     * @param s parameter value to find the span for
186     * @return Position of span.
187     */
188    public int findSpan(double s) {
189        //  Algorithm A2.1 from Ref. 1.
190        if (s >= _knots.getValue(_nu + 1)) {
191            return _nu;
192        }
193
194        int low = _degree;
195        int high = _nu + 1;
196        int mid = (low + high) / 2;
197        while ((s < _knots.getValue(mid) || s >= _knots.getValue(mid + 1)) && low < high) {
198            if (s < _knots.getValue(mid)) {
199                high = mid;
200            } else {
201                low = mid;
202            }
203            mid = (low + high) / 2;
204        }
205
206        return mid;
207    }
208
209    /**
210     * Returns the basis function values for the given parameter value (Nik(s)). This
211     * function first calculates the span which is needed in order to calculate the basis
212     * functions values.
213     *
214     * @param s Parameter value to calculate basis functions for.
215     * @return basis function values. WARNING: the returned array will likely be longer
216     * than [0..degree], so do NOT depend on array.length in any iterations over the
217     * array!. The additional array elements will contain garbage and should not be used.
218     * The returned array was allocated using
219     * javolution.context.ArrayFactory.DOUBLES_FACTORY and could be recycled by the user
220     * when no longer needed.
221     */
222    public double[] basisFunctions(double s) {
223        return basisFunctions(findSpan(s), s);
224    }
225
226    /**
227     * Returns the unweighted basis function values for the given parameter value
228     * (Nik(s)), when the span that the parameter value lies in is already known.
229     *
230     * @param span The span <code>s</code> lies in
231     * @param s The parameter value to calculate basis functions for.
232     * @return basis function values. WARNING: the returned array will likely be longer
233     * than [0..degree], so do NOT depend on array.length in any iterations over the
234     * array!. The additional array elements will contain garbage and should not be used.
235     * The returned array was allocated using
236     * javolution.context.ArrayFactory.DOUBLES_FACTORY and could be recycled by the user
237     * when no longer needed.
238     */
239    public double[] basisFunctions(int span, double s) {
240        //  Algorithm A2.2 from Ref. 1.
241        int degree = _degree;
242        int order = degree + 1;
243        double res[] = ArrayFactory.DOUBLES_FACTORY.array(order);
244        res[0] = 1;
245        double left[] = ArrayFactory.DOUBLES_FACTORY.array(order);
246        left[0] = 0;
247        double right[] = ArrayFactory.DOUBLES_FACTORY.array(order);
248        right[0] = 0;
249
250        for (int j = 1; j < order; j++) {
251            left[j] = s - _knots.getValue(span + 1 - j);
252            right[j] = _knots.getValue(span + j) - s;
253            double saved = 0;
254            for (int r = 0; r < j; r++) {
255                int jmr = j - r;
256                int rp1 = r + 1;
257                double tmp = res[r] / (right[rp1] + left[jmr]);
258                res[r] = saved + right[rp1] * tmp;
259                saved = left[jmr] * tmp;
260            }
261            res[j] = saved;
262        }
263
264        //  Clean up before leaving.
265        ArrayFactory.DOUBLES_FACTORY.recycle(left);
266        ArrayFactory.DOUBLES_FACTORY.recycle(right);
267
268        return res;
269    }
270
271    /**
272     * Calculates all the derivatives of all the unweighted basis functions from
273     * <code>0</code> up to the given grade, <code>d^{grade}Nik(s)/d^{grade}s</code>.
274     * <p>
275     * Examples:<br>
276     * 1st derivative (grade = 1), this returns <code>[Nik(s), dNik(s)/ds]</code>;<br>
277     * 2nd derivative (grade = 2), this returns <code>[Nik(s), dNik(s)/ds, d^2Nik(s)/d^2s]</code>; etc.
278     * </p>
279     *
280     * @param span The span <code>s</code> lies in
281     * @param s The parameter value to calculate basis functions for.
282     * @param grade The grade to calculate the derivatives for (1=1st derivative, 2=2nd
283     * derivative, etc).
284     * @return Basis function derivative values. WARNING: the returned array is recycled
285     * and will likely be longer than [0..grade+1][0..degree+1], so do NOT depend on
286     * array.length in any iterations over the array!. The additional array elements will
287     * contain garbage and should not be used. The returned array could be recycled by
288     * calling <code>KnotVector.recycle2DArray(arr)</code> when no longer needed.
289     * @throws IllegalArgumentException if the grade is &lt; 0.
290     * @see #basisFunctions
291     * @see #recycle2DArray
292     */
293    public double[][] basisFunctionDerivatives(int span, double s, int grade) {
294        //  Algorithm: A2.3, Ref. 1, pg. 72.
295        if (grade < 0) {
296            throw new IllegalArgumentException(RESOURCES.getString("gradeLTZeroErr"));
297        }
298        int degree = _degree;
299        int order = degree + 1;
300        int gradeP1 = grade + 1;
301
302        //  double[][] ders = new double[grade+1][degree+1];
303        double[][] ders = CurveUtils.allocate2DArray(gradeP1, order);           //  Outside of StackContext.
304
305        StackContext.enter();
306        try {
307            //  double[][] nds = new double[degree + 1][degree + 1];
308            double[][] nds = new2DZeroedArray(order, order);
309            nds[0][0] = 1.0;
310
311            //  double[] left = new double[degree + 1];
312            //  double[] right = new double[degree + 1];
313            double[] left = ArrayFactory.DOUBLES_FACTORY.array(order);
314            left[0] = 0;
315            double[] right = ArrayFactory.DOUBLES_FACTORY.array(order);
316            right[0] = 0;
317
318            for (int j = 1; j < order; j++) {
319                left[j] = s - _knots.getValue(span + 1 - j);
320                right[j] = _knots.getValue(span + j) - s;
321                double[] ndsj = nds[j];
322                double saved = 0.0;
323                for (int r = 0; r < j; r++) {
324                    int jmr = j - r;
325                    int rp1 = r + 1;
326                    //  Lower triangle
327                    ndsj[r] = right[rp1] + left[jmr];         //  nds[j][r] = right[rp1] + left[jmr];
328                    double temp = nds[r][j - 1] / ndsj[r];    //  temp = nds[r][j - 1] / nds[j][r];
329                    //  Upper triangle
330                    nds[r][j] = saved + right[rp1] * temp;
331                    saved = left[jmr] * temp;
332                }
333                ndsj[j] = saved;      //  nds[j][j] = saved;
334            }
335
336            //  Load the basis functions.
337            for (int j = order-1; j >= 0; --j) {
338                ders[0][j] = nds[j][degree];
339            }
340
341            //  double[][] a = new double[2][degree + 1];
342            double[][] a = new2DZeroedArray(2, order);
343
344            //  This section computes the derivatives (Ref. 1, Eqn. 2.9).
345            for (int r = 0; r < order; r++) {
346                int s1 = 0, s2 = 1; //  Alternate rows in array a
347                a[0][0] = 1.0;
348
349                //  Loop to compute the kth derivative.
350                for (int k = 1; k < gradeP1; k++) {
351                    double[] as1 = a[s1];
352                    double[] as2 = a[s2];
353
354                    double d = 0.0;
355                    int rk = r - k;
356                    int pk = degree - k;
357                    int pkp1 = pk + 1;
358                    double[] ndspkp1 = nds[pkp1];
359                    if (r >= k) {
360                        as2[0] = as1[0] / ndspkp1[rk];    //  a[s2][0] = a[s1][0] / nds[pkp1][rk];
361                        d = as2[0] * nds[rk][pk];
362                    }
363                    int j1, j2;
364                    if (rk >= -1)
365                        j1 = 1;
366                    else
367                        j1 = -rk;
368
369                    if (r - 1 <= pk)
370                        j2 = k - 1;
371                    else
372                        j2 = degree - r;
373
374                    for (int j = j1; j <= j2; j++) {
375                        int rkpj = rk + j;
376                        as2[j] = (as1[j] - as1[j - 1]) / ndspkp1[rkpj]; // a[s2][j] = (a[s1][j] - a[s1][j - 1]) / nds[pkp1][rkpj];
377                        d += as2[j] * nds[rkpj][pk];
378                    }
379                    if (r <= pk) {
380                        as2[k] = -as1[k - 1] / ndspkp1[r];    //  a[s2][k] = -a[s1][k - 1] / nds[pkp1][r];
381                        d += as2[k] * nds[r][pk];
382                    }
383                    ders[k][r] = d;
384
385                    //  Switch rows.
386                    j1 = s1;
387                    s1 = s2;
388                    s2 = j1;
389                }
390
391            }   //  Next r
392
393            //  Multiply through by the correct factors (Ref. 1, Eq. 2.9).
394            int r = degree;
395            for (int k = 1; k < gradeP1; k++) {
396                @SuppressWarnings("MismatchedReadAndWriteOfArray")
397                double[] dersk = ders[k];
398                for (int j = order-1; j >= 0; --j) {
399                    dersk[j] *= r;      //  ders[k][j] *= r;
400                }
401                r *= (degree - k);
402            }
403
404        } finally {
405            StackContext.exit();
406        }
407
408        return ders;
409    }
410
411    /**
412     * Allocate a 2D array using factory methods and fill it with zeros.
413     */
414    private static double[][] new2DZeroedArray(int rows, int cols) {
415        double[][] arr = CurveUtils.allocate2DArray(rows, cols);
416        //  Factory allocated arrays are not necessarily zeroed.
417        double[] arr0 = arr[0];
418        for (int j = cols-1; j >= 0; --j) {
419            arr0[j] = 0.;
420        }
421        for (int i = 1; i < rows; ++i) {
422            System.arraycopy(arr0, 0, arr[i], 0, cols);
423        }
424        return arr;
425    }
426
427    /**
428     * Return the length of the knot vector (nu).
429     *
430     * @return The length of the knot vector.
431     */
432    public int getNu() {
433        return _nu;
434    }
435
436    /**
437     * Return the number of elements in the knot vector.
438     *
439     * @return The number of elements in the knot vector.
440     */
441    public int length() {
442        return _knots.getDimension();
443    }
444
445    /**
446     * Return the knot values as an vector of <code>Float64</code> values.
447     *
448     * @return the vector of knot values
449     */
450    public Float64Vector getAll() {
451        return _knots;
452    }
453
454    /**
455     * Return the knot at the specified index.
456     *
457     * @param i Index to get knot value for
458     * @return the knot value at index <code>i</code>
459     */
460    public Float64 get(int i) {
461        return _knots.get(i);
462    }
463
464    /**
465     * Return the knot value at a specific index as a <code>double</code>.
466     *
467     * @param i Index to get knot value for
468     * @return the knot value at index <code>i</code> returned as a <code>double</code>.
469     */
470    public double getValue(int i) {
471        return _knots.getValue(i);
472    }
473
474    /**
475     * Return the degree of the KnotVector
476     *
477     * @return Degree of the KnotVector
478     */
479    public int getDegree() {
480        return _degree;
481    }
482
483    /**
484     * Return the number of segments in the knot vector.
485     * 
486     * @return The number of segments in the knot vector.
487     */
488    public int getNumberOfSegments() {
489        int seg = 0;
490        double u = _knots.getValue(0);
491        int size = _knots.getDimension();
492        for (int i = 1; i < size; i++) {
493            double kv = _knots.getValue(i);
494            if (u != kv) {
495                seg++;
496                u = kv;
497            }
498        }
499        return seg;
500    }
501
502    /**
503     * Return <code>true</code> if the knot vector is open and <code>false</code> if it is
504     * closed.
505     *
506     * @return true if the knot vector is open.
507     */
508    public boolean isOpen() {
509        return _open;
510    }
511
512    /**
513     * Return <code>true</code> if this KnotVector contains valid and finite numerical
514     * components. A value of <code>false</code> will be returned if any of the knot
515     * values are NaN or Inf.
516     *
517     * @return true if the KnotVector contains valid and finite values.
518     */
519    public boolean isValid() {
520        int length = length();
521        for (int i = 0; i < length; ++i) {
522            Float64 value = _knots.get(i);
523            if (value.isNaN() || value.isInfinite()) {
524                return false;
525            }
526        }
527        return true;
528    }
529
530    /**
531     * Find the multiplicity of the knot with the specified index in this knot vector.
532     *
533     * @param index the index of the knot to observe (the largest index of a repeated
534     * series of knots).
535     * @return the multiplicity of the knot
536     */
537    public int findMultiplicity(int index) {
538        int s = 1;
539        int order = getDegree() + 1;
540        for (int i = index; i > order; --i) {
541            if (getValue(i) <= getValue(i - 1)) {
542                ++s;
543            } else {
544                return s;
545            }
546        }
547        return s;
548    }
549
550    /**
551     * Return a copy of this knot vector with the parameterization reversed.
552     *
553     * @return A copy of this KnotVector with the parameterization reversed.
554     */
555    public KnotVector reverse() {
556        StackContext.enter();
557        try {
558            FastTable<Float64> values = FastTable.newInstance();
559            for (int i = length() - 1; i >= 0; --i) {
560                values.add(Float64.ONE.minus(get(i)));      //  values.add(1.0 - getValue(i));
561            }
562            KnotVector kv = KnotVector.newInstance(getDegree(), Float64Vector.valueOf(values));
563            return StackContext.outerCopy(kv);
564        } finally {
565            StackContext.exit();
566        }
567    }
568
569    /**
570     * Returns a copy of this KnotVector instance
571     * {@link javolution.context.AllocatorContext allocated} by the calling thread
572     * (possibly on the stack).
573     *
574     * @return an identical and independent copy of this point.
575     */
576    @Override
577    public KnotVector copy() {
578        return copyOf(this);
579    }
580
581    /**
582     * Returns a copy of this KnotVector instance
583     * {@link javolution.context.AllocatorContext allocated} by the calling thread
584     * (possibly on the stack).
585     *
586     * @return an identical and independent copy of this point.
587     * @throws java.lang.CloneNotSupportedException Never thrown.
588     */
589    @Override
590    @SuppressWarnings("CloneDoesntCallSuperClone")
591    public Object clone() throws CloneNotSupportedException {
592        return copy();
593    }
594
595    /**
596     * Compares this ControlPoint against the specified object for strict equality (same
597     * values and same units).
598     *
599     * @param obj the object to compare with.
600     * @return <code>true</code> if this point is identical to that point;
601     * <code>false</code> otherwise.
602     */
603    @Override
604    public boolean equals(Object obj) {
605        if (this == obj) {
606            return true;
607        }
608        if ((obj == null) || (obj.getClass() != this.getClass())) {
609            return false;
610        }
611
612        KnotVector that = (KnotVector)obj;
613        return this._degree == that._degree
614                && this._knots.equals(that._knots);
615    }
616
617    /**
618     * Returns the hash code for this parameter.
619     *
620     * @return the hash code value.
621     */
622    @Override
623    public int hashCode() {
624        int hash = 7;
625
626        int var_code = _degree;
627        hash = hash * 31 + var_code;
628
629        var_code = _knots.hashCode();
630        hash = hash * 31 + var_code;
631
632        return hash;
633    }
634
635    /**
636     * Returns the text representation of this knot vector that consists of the degree
637     * followed by the knot values. For example:
638     * <pre>
639     *   {degree=2,{0.0, 0.0, 0.0, 1.0, 1.0, 1.0}}
640     * </pre>
641     *
642     * @return the text representation of this geometry element.
643     */
644    public Text toText() {
645        TextBuilder tmp = TextBuilder.newInstance();
646        tmp.append("{degree=");
647        tmp.append(_degree);
648        tmp.append(",");
649        tmp.append(_knots.toText());
650        tmp.append('}');
651        Text txt = tmp.toText();
652        TextBuilder.recycle(tmp);
653        return txt;
654    }
655
656    /**
657     * Returns the string representation of this knot vector that consists of the degree
658     * followed by the knot values. For example:
659     * <pre>
660     *   {degree=2,{0.0, 0.0, 0.0, 1.0, 1.0, 1.0}}
661     * </pre>
662     *
663     * @return the text representation of this geometry element.
664     */
665    @Override
666    public String toString() {
667        return toText().toString();
668    }
669
670    /**
671     * Recycle any 2D array of doubles that was created by this classes factory methods.
672     *
673     * @param arr The array to be recycled. The array must have been created by this class
674     * or by CurveUtils.allocate2DArray()!
675     */
676    public static void recycle2DArray(double[][] arr) {
677        CurveUtils.recycle2DArray(arr);
678    }
679
680    /**
681     * Holds the default XML representation for this object.
682     */
683    protected static final XMLFormat<KnotVector> XML = new XMLFormat<KnotVector>(KnotVector.class) {
684
685        @Override
686        public KnotVector newInstance(Class<KnotVector> cls, InputElement xml) throws XMLStreamException {
687            return FACTORY.object();
688        }
689
690        @Override
691        public void read(InputElement xml, KnotVector obj) throws XMLStreamException {
692            int degree = xml.getAttribute("degree", 1);
693
694            FastTable<Float64> valueList = FastTable.newInstance();
695            while (xml.hasNext()) {
696                Float64 value = xml.getNext();
697                valueList.add(value);
698            }
699
700            Float64Vector knots = Float64Vector.valueOf(valueList);
701            int numKnots = knots.getDimension();
702
703            //  Check for valid inputs.
704            for (int i = 1; i < numKnots; i++) {
705                if (knots.getValue(i - 1) > knots.getValue(i)) {
706                    throw new XMLStreamException(RESOURCES.getString("knotsNotIncreasingErr"));
707                }
708            }
709            for (int i = 0; i < numKnots; ++i) {
710                double kv = knots.getValue(i);
711                if (kv > 1.0 || kv < 0.0) {
712                    throw new XMLStreamException(
713                            MessageFormat.format(RESOURCES.getString("invalidKnotValue"), kv));
714                }
715            }
716
717            obj._degree = degree;
718            obj._knots = knots;
719            obj._nu = numKnots - degree - 2;
720
721            obj._open = determineIfOpen(obj);
722        }
723
724        @Override
725        public void write(KnotVector obj, OutputElement xml) throws XMLStreamException {
726
727            xml.setAttribute("degree", obj._degree);
728            int size = obj._knots.getDimension();
729            for (int i = 0; i < size; ++i) {
730                xml.add(obj._knots.get(i));
731            }
732
733        }
734    };
735
736    ///////////////////////
737    // Factory creation. //
738    ///////////////////////
739    protected KnotVector() { }
740
741    @SuppressWarnings("unchecked")
742    private static final ObjectFactory<KnotVector> FACTORY = new ObjectFactory<KnotVector>() {
743        @Override
744        protected KnotVector create() {
745            return new KnotVector();
746        }
747    };
748
749    @SuppressWarnings("unchecked")
750    private static KnotVector copyOf(KnotVector original) {
751        KnotVector o = FACTORY.object();
752        o._open = original._open;
753        o._knots = original._knots.copy();
754        o._degree = original._degree;
755        o._nu = original._nu;
756        return o;
757    }
758}