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