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