001/** 002 * BasicCSTCurve -- An implementation of a Class-Shape-Transform planar curve. 003 * 004 * Copyright (C) 2014-2019, 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 */ 018package geomss.geom.cst; 019 020import geomss.geom.*; 021import geomss.geom.nurbs.*; 022import jahuwaldt.js.math.BinomialCoef; 023import jahuwaldt.js.param.Parameter; 024import jahuwaldt.tools.math.MathTools; 025import java.text.MessageFormat; 026import java.util.List; 027import java.util.Objects; 028import static java.util.Objects.requireNonNull; 029import javax.measure.converter.ConversionException; 030import javax.measure.quantity.Dimensionless; 031import javax.measure.quantity.Length; 032import javax.measure.unit.SI; 033import javax.measure.unit.Unit; 034import javolution.context.ArrayFactory; 035import javolution.context.ObjectFactory; 036import javolution.context.StackContext; 037import javolution.lang.ValueType; 038import javolution.util.FastTable; 039import javolution.xml.XMLFormat; 040import javolution.xml.stream.XMLStreamException; 041 042/** 043 * An implementation of the Class-Shape-Transform (CST) planar curve. This curve type is 044 * useful for defining smooth aircraft component shapes with a minimum number of defining 045 * parameters. 046 * 047 * <p> Modified by: Joseph A. Huwaldt </p> 048 * 049 * @author Joseph A. Huwaldt, Date: March 14, 2014 050 * @version December 25, 2019 051 */ 052@SuppressWarnings({"serial", "CloneableImplementsClone"}) 053public final class BasicCSTCurve extends CSTCurve implements ValueType { 054 // Reference: Kulfan, B.M., Bussoletti, J.E., "'Fundamental' Parametric Geometry 055 // Representations for Aircraft Component Shapes", AIAA-2006-6948. 056 057 /** 058 * The class function for this curve. 059 */ 060 private CSTClassFunction Cf; 061 062 /** 063 * The shape function for this curve. 064 */ 065 private CSTShapeFunction Sf; 066 067 /** 068 * The trailing edge offset for this curve. 069 */ 070 private double offsetTE_c; 071 072 /** 073 * The origin of this curve (the location of the leading edge; gives the curve 074 * location). 075 */ 076 private Point origin; 077 078 /** 079 * The chord length of this curve (gives the curve scale). 080 */ 081 private Parameter<Length> chord; 082 083 /** 084 * The X-direction for the curve (gives the curve direction). 085 */ 086 private Vector<Dimensionless> xhat; 087 088 /** 089 * The Y-direction for the curve (gives the curve direction). 090 */ 091 private Vector<Dimensionless> yhat; 092 093 /** 094 * A parametric mapping between the input "s" and the "s" used in calculations. 095 */ 096 private NurbsCurve pmap; 097 098 /** 099 * The minimum bounding point for this subrange curve. 100 */ 101 private Point _boundsMin; 102 103 /** 104 * The maximum bounding point for this subrange curve. 105 */ 106 private Point _boundsMax; 107 108 /** 109 * Return a new planar {@link BasicCSTCurve} instance using the supplied input 110 * parameters. 111 * 112 * @param Cf The class function for this BasicCSTCurve. May not be null. 113 * @param Sf The shape function for this BasicCSTCurve. May not be null. 114 * @param offsetTE The trailing edge offset from the chord-line for this 115 * BasicCSTCurve. May not be null. 116 * @param origin The origin of the leading edge of this curve (gives the curve 117 * location). May not be null. 118 * @param chord The chord length for this curve (gives the curve scale). May not be 119 * null. 120 * @param xhat The chord-wise direction of this curve (the curve X-axis 121 * direction). May not be null. 122 * @param yhat The direction perpendicular to xhat for this curve (the curve 123 * Y-axis direction). May not be null. 124 * @return A new BasicCSTCurve instance 125 */ 126 public static BasicCSTCurve newInstance(CSTClassFunction Cf, CSTShapeFunction Sf, Parameter<Length> offsetTE, 127 Point origin, Parameter<Length> chord, GeomVector<Dimensionless> xhat, GeomVector<Dimensionless> yhat) { 128 requireNonNull(Cf); 129 requireNonNull(Sf); 130 requireNonNull(offsetTE); 131 132 int numDims = GeomUtil.maxPhyDimension(origin, xhat, yhat); 133 if (numDims < 2) 134 throw new DimensionException( 135 MessageFormat.format(RESOURCES.getString("dimensionNotAtLeast2"), 136 "input origin or normal", numDims)); 137 138 // Convert everything to common units and dimensions. 139 Unit<Length> unit = origin.getUnit(); 140 origin = origin.toDimension(numDims); 141 chord = chord.to(unit); 142 xhat = xhat.toDimension(numDims); 143 yhat = yhat.toDimension(numDims); 144 145 // Create an instance of this object and store all the inputs. 146 BasicCSTCurve obj = FACTORY.object(); 147 obj.pmap = CurveFactory.createLine(Point.valueOf(0), Point.valueOf(1)); 148 obj.Cf = Cf; 149 obj.Sf = Sf; 150 obj.offsetTE_c = offsetTE.divide(chord).asType(Dimensionless.class).getValue(Dimensionless.UNIT); 151 obj.origin = origin; 152 obj.chord = chord; 153 obj.xhat = xhat.toUnitVector(); 154 obj.yhat = yhat.toUnitVector(); 155 156 // Compute and store the bounds min/max values. 157 obj.calcBoundsMinMax(); 158 159 return obj; 160 } 161 162 /** 163 * Return a new 2D, planar {@link BasicCSTCurve} instance using the supplied input 164 * parameters with the origin at (0,0), a chord length of 1.0 m and with the chord 165 * aligned with the X-axis. 166 * 167 * @param Cf The class function for this BasicCSTCurve. May not be null. 168 * @param Sf The shape function for this BasicCSTCurve. May not be null. 169 * @param dZTE_c The trailing edge offset from the chord-line to chord length ratio 170 * for this BasicCSTCurve. 171 * @return A new BasicCSTCurve instance 172 */ 173 public static BasicCSTCurve newInstance(CSTClassFunction Cf, CSTShapeFunction Sf, double dZTE_c) { 174 Parameter<Length> chord = Parameter.valueOf(1, SI.METER); 175 Parameter<Length> dZTE = chord.times(dZTE_c); 176 BasicCSTCurve crv = BasicCSTCurve.newInstance(Cf, Sf, dZTE, Point.newInstance(2), chord, 177 Vector.valueOf(1, 0), Vector.valueOf(0, 1)); 178 return crv; 179 } 180 181 /** 182 * Return the class function associated with this CST curve. 183 * 184 * @return The class function associated with this CST curve. 185 */ 186 @Override 187 public CSTClassFunction getCFunction() { 188 return Cf; 189 } 190 191 /** 192 * Return the shape function associated with this CST curve. 193 * 194 * @return The shape function associated with this CST curve. 195 */ 196 @Override 197 public CSTShapeFunction getSFunction() { 198 return Sf; 199 } 200 201 /** 202 * Return the offset of the trailing edge of the curve from the chord line. 203 * 204 * @return The offset of the trailing edge of the curve from the chord line. 205 */ 206 @Override 207 public Parameter<Length> getOffsetTE() { 208 return chord.times(offsetTE_c); 209 } 210 211 /** 212 * Return the chord length of the curve. This defines the scale or size of the curve. 213 * 214 * @return The chord length of the curve. 215 */ 216 @Override 217 public Parameter<Length> getChord() { 218 return chord; 219 } 220 221 /** 222 * Return the origin of the leading edge of this curve. This defines the s=0 location 223 * of the curve in space. 224 * 225 * @return The origin of the leading edge of this curve. 226 */ 227 @Override 228 public Point getOrigin() { 229 return origin; 230 } 231 232 /** 233 * Return the chord-wise direction of this curve (the curve X-direction). 234 * 235 * @return The chord-wise direction of this curve. 236 */ 237 @Override 238 public Vector<Dimensionless> getXhat() { 239 return xhat; 240 } 241 242 /** 243 * Return the in-plane direction of this curve perpendicular to the chord-wise 244 * direction (the curve Y-direction). 245 * 246 * @return The in-plane direction of this curve perpendicular to Xhat. 247 */ 248 @Override 249 public Vector<Dimensionless> getYhat() { 250 return yhat; 251 } 252 253 /** 254 * Return a copy of this CSTCurve with the class function changed to the specified 255 * class function. 256 * 257 * @param Cf The new class function for this BasicCSTCurve. May not be null. 258 * @return A copy of this CSTCurve with the class function changed to the specified 259 * class function. 260 */ 261 public BasicCSTCurve changeCFunction(CSTClassFunction Cf) { 262 requireNonNull(Cf); 263 BasicCSTCurve obj = copy(); 264 obj.Cf = Cf; 265 obj.calcBoundsMinMax(); 266 return obj; 267 } 268 269 /** 270 * Return a copy of this CSTCurve with the shape function changed to the specified 271 * shape function. 272 * 273 * @param Sf The new shape function for this BasicCSTCurve. May not be null. 274 * @return A copy of this CSTCurve with the shape function changed to the specified 275 * shape function. 276 */ 277 public BasicCSTCurve changeSFunction(CSTShapeFunction Sf) { 278 requireNonNull(Sf); 279 BasicCSTCurve obj = copy(); 280 obj.Sf = Sf; 281 obj.calcBoundsMinMax(); 282 return obj; 283 } 284 285 /** 286 * Return a copy of this CSTCurve with the shape function Bernstein Polynomial 287 * coefficients changed to the specified coefficients. 288 * 289 * @param order The order of the shape function to use. 290 * @param Acoefs The new coefficients of the Bernstein Polynomial used to construct 291 * the shape function. If more than "order" coefficients are provided 292 * then the additional coefficients are ignored. May not be null. 293 * @return A copy of this CSTCurve with the shape function Bernstein Polynomial 294 * coefficients changed to the specified coefficients. 295 */ 296 public BasicCSTCurve changeBFCoefficients(int order, double... Acoefs) { 297 CSTShapeFunction Sfunc = CSTShapeFunction.newInstance(order, requireNonNull(Acoefs)); 298 return changeSFunction(Sfunc); 299 } 300 301 /** 302 * Return a copy of this CSTCurve with the trailing edge offset from the chord-line 303 * changed to the specified value. 304 * 305 * @param offsetTE The new trailing edge offset from the chord-line for this 306 * BasicCSTCurve. May not be null. 307 * @return A copy of this CSTCurve with the trailing edge offset changed to the 308 * specified value. 309 */ 310 public BasicCSTCurve changeOffsetTE(Parameter<Length> offsetTE) { 311 BasicCSTCurve obj = copy(); 312 obj.offsetTE_c = offsetTE.divide(obj.chord).asType(Dimensionless.class).getValue(Dimensionless.UNIT); 313 obj.calcBoundsMinMax(); 314 return obj; 315 } 316 317 /** 318 * Return a copy of this CSTCurve with the trailing edge offset from the chord-line to 319 * chord ratio changed to the specified value. 320 * 321 * @param offsetTE_c The new trailing edge offset from the chord-line to chord ratio 322 * for this BasicCSTCurve. 323 * @return A copy of this CSTCurve with the trailing edge offset changed to the 324 * specified value. 325 */ 326 public BasicCSTCurve changeOffsetTE(double offsetTE_c) { 327 BasicCSTCurve obj = copy(); 328 obj.offsetTE_c = offsetTE_c; 329 obj.calcBoundsMinMax(); 330 return obj; 331 } 332 333 /** 334 * Return a copy of this CSTCurve with the curve origin or leading-edge point changed 335 * to the specified value. 336 * 337 * @param origin The new origin of the leading edge of this curve (gives the curve 338 * location). May not be null. 339 * @return A copy of this CSTCurve with the origin changed to the specified value. 340 */ 341 public BasicCSTCurve changeOrigin(Point origin) { 342 requireNonNull(origin); 343 BasicCSTCurve obj = copy(); 344 obj.origin = origin; 345 obj.calcBoundsMinMax(); 346 return obj; 347 } 348 349 /** 350 * Return a copy of this CSTCurve with the chord length changed to the specified 351 * value. 352 * 353 * @param chord The chord length for this curve (gives the curve scale). May not be null. 354 * @return A copy of this CSTCurve with the chord length changed to the specified 355 * value. 356 */ 357 public BasicCSTCurve changeChord(Parameter<Length> chord) { 358 requireNonNull(chord); 359 BasicCSTCurve obj = copy(); 360 obj.chord = chord; 361 obj.calcBoundsMinMax(); 362 return obj; 363 } 364 365 /** 366 * Returns the number of physical dimensions of the geometry element. 367 * 368 * @return The number of physical dimensions of the geometry element. 369 */ 370 @Override 371 public int getPhyDimension() { 372 return origin.getPhyDimension(); 373 } 374 375 /** 376 * Return the order of this CST curve. 377 * 378 * @return order of this curve (degree + 1) 379 * 380 */ 381 public int getOrder() { 382 return Sf.getOrder(); 383 } 384 385 /** 386 * Return the value of this CST curve function at the specified parametric location. 387 */ 388 private double getValue(double s) { 389 double S = Sf.getValue(s); 390 double C = Cf.getValue(s); 391 return C * S + offsetTE_c * s; 392 } 393 394 /** 395 * Calculate a point on the curve for the given parametric distance along the curve, 396 * <code>p(s)</code>. 397 * 398 * @param s parametric distance to calculate a point for (0.0 to 1.0 inclusive). 399 * @return the calculated point 400 */ 401 @Override 402 public Point getRealPoint(double s) { 403 validateParameter(s); 404 405 StackContext.enter(); 406 try { 407 // Map "s" from parameter space to CST space. 408 s = pmap.getRealPoint(s).getValue(Point.X); 409 410 double ch = chord.getValue(SI.METER); 411 double aValue = ch * s; 412 double bValue = ch * getValue(s); 413 Point p = Point.valueOf(xhat.times(aValue).plus(yhat.times(bValue))).to(getUnit()); 414 p = p.plus(origin); 415 416 return StackContext.outerCopy(p); 417 } finally { 418 StackContext.exit(); 419 } 420 } 421 422 /** 423 * Calculate all the derivatives from <code>0</code> to <code>grade</code> with 424 * respect to parametric distance on the curve for the given parametric distance along 425 * the curve, <code>d^{grade}p(s)/d^{grade}s</code>. 426 * <p> 427 * Example:<br> 428 * 1st derivative (grade = 1), this returns <code>[p(s), dp(s)/ds]</code>;<br> 429 * 2nd derivative (grade = 2), this returns <code>[p(s), dp(s)/ds, d^2p(s)/d^2s]</code>; etc. 430 * </p> 431 * 432 * @param s Parametric distance to calculate derivatives for (0.0 to 1.0 433 * inclusive). 434 * @param grade The maximum grade to calculate the derivatives for (1=1st derivative, 435 * 2=2nd derivative, etc) 436 * @return A list of derivatives up to the specified grade of the curve at the 437 * specified parametric position. 438 * @throws IllegalArgumentException if the grade is < 0. 439 */ 440 @Override 441 public List<Vector<Length>> getSDerivatives(double s, int grade) { 442 validateParameter(s); 443 s = roundParNearEnds(s); 444 445 if (grade < 0) 446 throw new IllegalArgumentException(RESOURCES.getString("gradeLTZeroErr")); 447 448 // CST Terms: 1: C(s)*S(s), 2: s*offsetTE_c 449 Vector[] ders = Vector.allocateArray(grade + 1); // Created outside of StackContext. 450 StackContext.enter(); 451 try { 452 // Map "s" from user space to calculation space. 453 s = pmap.getRealPoint(s).getValue(Point.X); 454 455 // Get derivatives of the class and shape functions. 456 double[] Cders = Cf.getDerivatives(s, grade); 457 double[] Sders = Sf.getDerivatives(s, grade); 458 459 // Calculate components of the 1st term derivatives using Leibniz's Rule. 460 double[] term1 = leibnizRule(Cders, Sders, grade); 461 462 GeomVector<Length> o = origin.toGeomVector(); 463 Unit<Length> unit = getUnit(); 464 for (int n = 0; n <= grade; ++n) { 465 // Get the derivatives of the 1st term. 466 double v = term1[n]; 467 468 // Add in the derivatives of the 2nd term (and of "s"). 469 double ds = 0; // Derivate of "s". 470 if (n == 0) { 471 v += s * offsetTE_c; 472 ds = s; 473 } else if (n == 1) { 474 v += offsetTE_c; 475 ds = 1; 476 } 477 // 2nd term derivatives are 0 for all higher grades. 478 479 Vector<Dimensionless> vhat = xhat.times(ds).plus(yhat.times(v)); 480 Vector<Length> vec = vhat.times(chord).to(unit); 481 if (n == 0) 482 vec = vec.plus(o); 483 ders[n] = StackContext.outerCopy(vec); 484 } 485 } finally { 486 StackContext.exit(); 487 } 488 489 // Convert to a list of Vector objets. 490 FastTable<Vector<Length>> output = FastTable.newInstance(); 491 Point o = Point.valueOf(ders[0]); 492 for (int i = 0; i <= grade; ++i) { 493 Vector<Length> v = ders[i]; 494 v.setOrigin(o); 495 output.add(v); 496 } 497 498 Vector.recycleArray(ders); 499 500 return output; 501 } 502 503 /** 504 * Compute the derivatives of the product of two functions using the Leibniz General 505 * Rule for Differentiation: <code>d^n(f(s)*g(s))/ds^n</code>. 506 * 507 * @param f The derivatives of the function "f" from 0 up to grade. 508 * @param g The derivatives of the function "g" from 0 up to grade. 509 * @param grade The grade to which the derivative terms are to be calculated. 510 * @return An array of the derivatives, from 0 to grade, of the product of two 511 * functions: d^n(f(s)*g(s))/ds^n. The returned array was allocated using 512 * javolution.context.ArrayFactory.DOUBLES_FACTORY, could be longer than the 513 * number of derivatives requested, and could be recycled by the user when no 514 * longer needed. 515 */ 516 private static double[] leibnizRule(double[] f, double[] g, int grade) { 517 // Reference: https://en.wikipedia.org/wiki/General_Leibniz_rule 518 519 int gradeP1 = grade + 1; 520 BinomialCoef bin = BinomialCoef.newInstance(gradeP1); 521 double[] fgn = ArrayFactory.DOUBLES_FACTORY.array(gradeP1); 522 for (int n = 0; n < gradeP1; ++n) { 523 for (int k = n; k >= 0; --k) { 524 double bCoef = bin.get(n, k); 525 double fk = f[k]; 526 double gnmk = g[n - k]; 527 double der = fk * gnmk * bCoef; 528 fgn[n] += der; 529 } 530 } 531 BinomialCoef.recycle(bin); 532 533 return fgn; 534 } 535 536 /** 537 * Return the coordinate point representing the minimum bounding box corner of this 538 * geometry element (e.g.: min X, min Y, min Z). 539 * 540 * @return The minimum bounding box coordinate for this geometry element. 541 * @throws IndexOutOfBoundsException if this list contains no geometry. 542 */ 543 @Override 544 public Point getBoundsMin() { 545 return _boundsMin; 546 } 547 548 /** 549 * Return the coordinate point representing the maximum bounding box corner (e.g.: max 550 * X, max Y, max Z). 551 * 552 * @return The maximum bounding box coordinate for this geometry element. 553 * @throws IndexOutOfBoundsException if this list contains no elements. 554 */ 555 @Override 556 public Point getBoundsMax() { 557 return _boundsMax; 558 } 559 560 private static final int NPTS = 51; 561 562 /** 563 * Calculate the minimum & maximum bounding box corner points of this geometry 564 * element. This method may ONLY be called at the time that an instance of this object 565 * is created by a "newInstance()" method. 566 */ 567 private void calcBoundsMinMax() { 568 StackContext.enter(); 569 try { 570 // Space out some points on the curve. 571 List<Double> spacing = GridSpacing.linear(NPTS); 572 PointString<SubrangePoint> str = this.extractGrid(GridRule.PAR, spacing); 573 574 // Get the bounds of the grid of points. 575 _boundsMin = StackContext.outerCopy(str.getBoundsMin()); 576 _boundsMax = StackContext.outerCopy(str.getBoundsMax()); 577 } finally { 578 StackContext.exit(); 579 } 580 } 581 582 /** 583 * Return a new curve that is identical to this one, but with the parameterization 584 * reversed. This does not change the origin or axis directions, but only the curve 585 * parameterization; what was s=0 will become s=1, etc. 586 * 587 * @return A new curve identical to this one, but with the parameterization reversed. 588 */ 589 @Override 590 public CSTCurve reverse() { 591 BasicCSTCurve obj = copy(); 592 obj.pmap = this.pmap.reverse(); 593 return obj; 594 } 595 596 /** 597 * Split this curve at the specified parametric position returning a list containing 598 * two curves (a lower curve with smaller parametric positions than "s" and an upper 599 * curve with larger parametric positions). The origin, chord length, and trailing 600 * edge offset from chord-line will be identical for the the returned segments and 601 * this curve. The only change is in the parameterization. 602 * 603 * @param s The parametric position where this curve should be split (must not be 0 or 604 * 1!). 605 * @return A list containing two curves: 0 == the lower curve, 1 == the upper curve. 606 */ 607 @Override 608 public GeomList<CSTCurve> splitAt(double s) { 609 validateParameter(s); 610 if (parNearEnds(s, TOL_S)) 611 throw new IllegalArgumentException( 612 MessageFormat.format(RESOURCES.getString("canNotSplitAtEnds"), "curve")); 613 614 GeomList<CSTCurve> output = GeomList.newInstance(); 615 616 // Split the parametric mapping at "s". 617 GeomList<NurbsCurve> p_split = pmap.splitAt(s); 618 619 // Create a "lower" curve segment. 620 BasicCSTCurve cl = copy(); 621 cl.pmap = p_split.getFirst(); 622 cl.calcBoundsMinMax(); 623 BasicCSTCurve cu = copy(); 624 cu.pmap = p_split.getLast(); 625 cu.calcBoundsMinMax(); 626 627 output.add(cl); 628 output.add(cu); 629 630 return output; 631 } 632 633 /** 634 * Return <code>true</code> if this curve contains valid and finite numerical 635 * components. A value of <code>false</code> will be returned if any of the coordinate 636 * values are NaN or Inf. 637 * 638 * @return true if this element contains valid and finite numerical values. 639 */ 640 @Override 641 public boolean isValid() { 642 return origin.isValid() && xhat.isValid() && yhat.isValid() 643 && !chord.isInfinite() && !chord.isNaN() 644 && !Double.isInfinite(offsetTE_c) && !Double.isNaN(offsetTE_c) 645 && Sf.isValid(); 646 } 647 648 /** 649 * Returns the unit in which this curve is stated. 650 * 651 * @return The unit that this curves points are stated in. 652 */ 653 @Override 654 public Unit<Length> getUnit() { 655 return origin.getUnit(); 656 } 657 658 /** 659 * Returns the equivalent to this curve but stated in the specified unit. 660 * 661 * @param unit the length unit of the curve to be returned. May not be null. 662 * @return an equivalent to this curve but stated in the specified unit. 663 * @throws ConversionException if the the input unit is not a length unit. 664 */ 665 @Override 666 public BasicCSTCurve to(Unit<Length> unit) throws ConversionException { 667 if (unit.equals(getUnit())) 668 return this; 669 BasicCSTCurve crv = newInstance(Cf, Sf, getOffsetTE(), origin.to(unit), chord.to(unit), xhat, yhat); 670 return copyState(crv); 671 } 672 673 /** 674 * Return the equivalent of this curve converted to the specified number of physical 675 * dimensions. If the number of dimensions is greater than this element, then zeros 676 * are added to the additional dimensions. If the number of dimensions is less than 677 * this element, then the extra dimensions are simply dropped (truncated). If the new 678 * dimensions are the same as the dimension of this element, then this element is 679 * simply returned. 680 * 681 * @param newDim The dimension of the curve to return. 682 * @return The equivalent of this curve converted to the new dimensions. 683 */ 684 @Override 685 public BasicCSTCurve toDimension(int newDim) { 686 if (getPhyDimension() == newDim) 687 return this; 688 BasicCSTCurve crv = newInstance(Cf, Sf, getOffsetTE(), origin.toDimension(newDim), chord, 689 xhat.toDimension(newDim), yhat.toDimension(newDim)); 690 return copyState(crv); 691 } 692 693 /** 694 * Return a NURBS curve representation of this curve to within the specified 695 * tolerance. 696 * 697 * @param tol The greatest possible difference between this curve and the NURBS 698 * representation returned. May not be null. 699 * @return A NURBS curve that represents this curve to within the specified tolerance. 700 */ 701 @Override 702 public NurbsCurve toNurbs(Parameter<Length> tol) { 703 StackContext.enter(); 704 try { 705 // Can this curve be represented exactly by a NURBS curve? 706 // Only integer N2 (either 0 or 1) can be represented exactly as NURBS. 707 if (MathTools.isApproxEqual(Cf.getN2(), 1.0)) { 708 if (MathTools.isApproxEqual(Cf.getN1(), 0.5)) { 709 BasicNurbsCurve curve = bluntAirfoil2Bezier(); 710 curve = matchParameterization(curve); 711 copyState(curve); 712 return StackContext.outerCopy(curve); 713 } 714 if (MathTools.isApproxEqual(Cf.getN1(), 1.0)) { 715 BasicNurbsCurve curve = sharpAirfoil2Bezier(); 716 curve = matchParameterization(curve); 717 copyState(curve); 718 return StackContext.outerCopy(curve); 719 } 720 721 } else if (MathTools.isApproxEqual(Cf.getN2(), 0)) { 722 if (MathTools.isApproxEqual(Cf.getN1(), 1.0)) { 723 BasicNurbsCurve curve = cone2Bezier(); 724 curve = matchParameterization(curve); 725 copyState(curve); 726 return StackContext.outerCopy(curve); 727 } 728 if (MathTools.isApproxEqual(Cf.getN1(), 0.0)) { 729 BasicNurbsCurve curve = rectangular2Bezier(); 730 curve = matchParameterization(curve); 731 copyState(curve); 732 return StackContext.outerCopy(curve); 733 } 734 } 735 736 // Fall-back to gridding points onto the curve and then fitting a curve through them. 737 738 // Grid some points onto the curve. 739 PointString<SubrangePoint> str = this.gridToTolerance(requireNonNull(tol)); 740 741 // Fit a cubic NURBS curve to the points. 742 int deg = Sf.getOrder() - 1; 743 if (deg < 3) 744 deg = 3; 745 if (str.size() <= deg) 746 deg = str.size() - 1; 747 BasicNurbsCurve curve = CurveFactory.fitPoints(deg, str); 748 copyState(curve); // Copy over the super-class state for this curve to the new one. 749 750 return StackContext.outerCopy(curve); 751 752 } finally { 753 StackContext.exit(); 754 } 755 } 756 757 /** 758 * Modify the input NurbsCurve to have the same parameterization as this CST curve. 759 * 760 * @param crv The NURBS curve to be modified. 761 * @return The modified NURBS curve. 762 */ 763 private BasicNurbsCurve matchParameterization(BasicNurbsCurve crv) { 764 // Get the limits of this curve's parameter mapping. 765 double s0 = pmap.getRealPoint(0).getValue(0); 766 double s1 = pmap.getRealPoint(1).getValue(0); 767 768 // Is the parameterization reversed? 769 boolean reversed = s1 < s0; 770 771 if (!reversed) { 772 if (!parNearStart(s0, TOL_S)) { 773 // s0 is not at the start of the curve. 774 775 // Find s0 on the NurbsCurve and split the NurbsCurve there. 776 SubrangePoint p = crv.getClosest(this.getRealPoint(0), s0, TOL_S); 777 double s_split = p.getParPosition().getValue(0); 778 GeomList<NurbsCurve> lst = crv.splitAt(s_split); 779 crv = (BasicNurbsCurve)lst.getLast(); 780 } 781 if (!parNearEnd(s1, TOL_S)) { 782 // s1 is not at the end of the curve. 783 784 // Find s1 on the NurbsCurve and split the NurbsCurve there. 785 SubrangePoint p = crv.getClosest(this.getRealPoint(1), s1, TOL_S); 786 double s_split = p.getParPosition().getValue(0); 787 GeomList<NurbsCurve> lst = crv.splitAt(s_split); 788 crv = (BasicNurbsCurve)lst.getFirst(); 789 } 790 791 } else { 792 // The parameterization is reversed. 793 if (!parNearStart(s1, TOL_S)) { 794 // s01 is not at the start of the curve. 795 796 // Find s1 on the NurbsCurve and split the NurbsCurve there. 797 SubrangePoint p = crv.getClosest(this.getRealPoint(1), s1, TOL_S); 798 double s_split = p.getParPosition().getValue(0); 799 GeomList<NurbsCurve> lst = crv.splitAt(s_split); 800 crv = (BasicNurbsCurve)lst.getLast(); 801 } 802 if (!parNearEnd(s0, TOL_S)) { 803 // s0 is not at the end of the curve. 804 805 // Find s0 on the NurbsCurve and split the NurbsCurve there. 806 SubrangePoint p = crv.getClosest(this.getRealPoint(0), s0, TOL_S); 807 double s_split = p.getParPosition().getValue(0); 808 GeomList<NurbsCurve> lst = crv.splitAt(s_split); 809 crv = (BasicNurbsCurve)lst.getFirst(); 810 } 811 crv = (BasicNurbsCurve)crv.reverse(); 812 } 813 814 return crv; 815 } 816 817 /** 818 * An exact conversion to a Bezier curve is possible for a blunt-nosed airfoil CST curve. 819 * 820 * @return A NURBS curve that represents this airfoil curve exactly. 821 */ 822 private BasicNurbsCurve bluntAirfoil2Bezier() { 823 // Ref.: Marshall, D.D., "Creating Exact Bezier Representations of CST Shapes", 824 // 21st AIAA Computational Fluid Dynamics Conference Proceedings: San Diego, CA, June 24, 2013. 825 826 int n = Sf.getOrder() - 1; // Degree of the shape function. 827 int m = 2*n + 3; // Degree of output NURBS curve. 828 829 // Convert the Bezier curve shape function to a monomial of the form: S(t) = sum_{i=0}^{n} ai*t^i 830 BinomialCoef bin = BinomialCoef.newInstance(m + 1); // Get a set of binomial coefficients. 831 double[] a = shape2Monomial(Sf.getBasisFunction(), bin); 832 833 // Create the "c" coefficients for the new monomial using Equation 13 from the reference. 834 // xi = s^2 due to parameter substitution (see below), so c[2] = 1. 835 double[] c = ArrayFactory.DOUBLES_FACTORY.array(m + 1); 836 c[0] = 0; 837 c[1] = 0; 838 c[2] = 1; 839 for (int i=3; i <= m; ++i) 840 c[i] = 0; 841 842 // Create the "b" coefficients for the new monomial using Equation 11 from the reference. 843 // zeta = sqrt(t)*(1-t)*sum_{i=0}^{n}{ai*t^i} + t*DzetaTE 844 // With parameter substitution: t ==> s^2: 845 // zeta = s*[a0 + sum_{i=1}^{n}{(ai - a_(i-1))*s^(2*i) - an*s^(2*(n+1))] + s^2*DzetaTE; or simplifying: 846 // zeta = a0*s + DzetaTE*s^2 + sum_{i=1}^{n}{ (ai - a_(i-1)) * s^(2*i + 1) } - an*s^m = sum_{i=0}^{n}{bi*t^i} 847 double[] b = ArrayFactory.DOUBLES_FACTORY.array(m + 1); 848 for (int i = 0; i <= m; ++i) { 849 if (i == 1) 850 b[i] = a[0]; 851 else if (i == 2) 852 b[i] = offsetTE_c; 853 else if (MathTools.even(i)) 854 b[i] = 0; 855 else if (i == m) 856 b[i] = -a[n]; 857 else { 858 int i2 = (i - 1) / 2; 859 b[i] = a[i2] - a[i2 - 1]; 860 } 861 } 862 863 // Form the new Bezier curve from the newly computed monomial coefficients. 864 BasicNurbsCurve newCrv = monomial2Bezier(c,b, m, bin); 865 866 return newCrv; 867 } 868 869 /** 870 * An exact conversion to a Bezier curve is possible for a sharp-nosed airfoil CST curve. 871 * 872 * @return A NURBS curve that represents this airfoil curve exactly. 873 */ 874 private BasicNurbsCurve sharpAirfoil2Bezier() { 875 // Ref.: Marshall, D.D., "Creating Exact Bezier Representations of CST Shapes", 876 // 21st AIAA Computational Fluid Dynamics Conference Proceedings: San Diego, CA, June 24, 2013. 877 878 int n = Sf.getOrder() - 1; // Degree of the shape function. 879 int m = n + 2; // Degree of output NURBS curve. 880 881 // Convert the Bezier curve shape function to a monomial of the form: S(t) = sum_{i=0}^{n} ai*t^i 882 BinomialCoef bin = BinomialCoef.newInstance(m + 1); // Get a set of binomial coefficients. 883 double[] a = shape2Monomial(Sf.getBasisFunction(), bin); 884 885 // Create the "c" coefficients for the new monomial. 886 // xi = t, to c[1] = 1. 887 double[] c = ArrayFactory.DOUBLES_FACTORY.array(m + 1); 888 c[0] = 0; 889 c[1] = 1; 890 for (int i=2; i <= m; ++i) 891 c[i] = 0; 892 893 // Create the "b" coefficients for the new monomial. 894 // zeta = t*(1 - t)*sum_{i=0}^{n}{ai*t^i} + t*DzetaTE (Eqn. 14 with xi = t) 895 // zeta = t*[a0 + sum_{i=1}^{n}{(ai - a_(i-1))*t^i - an*s^(n+1)] + t*DzetaTE; simplifying: 896 // zeta = (a0 + DzetaTE)*t + sum_{i=1}^{n}{ (ai - a_(i-1))*t^(i+1) } - an*t^m = sum_{i=0}^{n}{bi*t^i} 897 double[] b = ArrayFactory.DOUBLES_FACTORY.array(m + 1); 898 for (int i = 0; i <= m; ++i) { 899 if (i == 0) 900 b[i] = 0; 901 else if (i == 1) 902 b[i] = a[0] + offsetTE_c; 903 else if (i == m) 904 b[i] = -a[n]; 905 else { 906 int i2 = i - 1; 907 b[i] = a[i2] - a[i2 - 1]; 908 } 909 } 910 911 // Form the new Bezier curve from the newly computed monomial coefficients. 912 BasicNurbsCurve newCrv = monomial2Bezier(c,b, m, bin); 913 914 return newCrv; 915 } 916 917 /** 918 * An exact conversion to a Bezier curve is possible for a cone or wedge shaped CST curve. 919 * 920 * @return A NURBS curve that represents this CST curve exactly. 921 */ 922 private BasicNurbsCurve cone2Bezier() { 923 // Ref.: Marshall, D.D., "Creating Exact Bezier Representations of CST Shapes", 924 // 21st AIAA Computational Fluid Dynamics Conference Proceedings: San Diego, CA, June 24, 2013. 925 926 int n = Sf.getOrder() - 1; // Degree of the shape function. 927 int m = n + 1; // Degree of output NURBS curve. 928 929 // Convert the Bezier curve shape function to a monomial of the form: S(t) = sum_{i=0}^{n} ai*t^i 930 BinomialCoef bin = BinomialCoef.newInstance(m + 1); // Get a set of binomial coefficients. 931 double[] a = shape2Monomial(Sf.getBasisFunction(), bin); 932 933 // Create the "c" coefficients for the new monomial. 934 // xi = t, to c[1] = 1. 935 double[] c = ArrayFactory.DOUBLES_FACTORY.array(m + 1); 936 c[0] = 0; 937 c[1] = 1; 938 for (int i=2; i <= m; ++i) 939 c[i] = 0; 940 941 // Create the "b" coefficients for the new monomial. 942 // zeta = t*sum_{i=0}^{n}{ai*t^i} + t*DzetaTE (Eqn. 15 with xi = t) 943 // zeta = (a0 + DzetaTE)*t + sum_{i=1}^{n}{ai*t^(i+1)} + an*t^m = sum_{i=0}^{n}{bi*t^i} 944 double[] b = ArrayFactory.DOUBLES_FACTORY.array(m + 1); 945 for (int i = 0; i <= m; ++i) { 946 if (i == 0) 947 b[i] = 0; 948 else if (i == 1) 949 b[i] = a[0] + offsetTE_c; 950 else if (i == m) 951 b[i] = a[n]; 952 else 953 b[i] = a[i]; 954 } 955 956 // Form the new Bezier curve from the newly computed monomial coefficients. 957 BasicNurbsCurve newCrv = monomial2Bezier(c,b, m, bin); 958 959 return newCrv; 960 } 961 962 /** 963 * An exact conversion to a Bezier curve is possible for a rectangular shaped CST curve. 964 * 965 * @return A NURBS curve that represents this CST curve exactly. 966 */ 967 private BasicNurbsCurve rectangular2Bezier() { 968 // Ref.: Marshall, D.D., "Creating Exact Bezier Representations of CST Shapes", 969 // 21st AIAA Computational Fluid Dynamics Conference Proceedings: San Diego, CA, June 24, 2013. 970 971 int n = Sf.getOrder() - 1; // Degree of the shape function. 972 int m = n; // Degree of output NURBS curve. 973 974 // Convert the Bezier curve shape function to a monomial of the form: S(t) = sum_{i=0}^{n} ai*t^i 975 BinomialCoef bin = BinomialCoef.newInstance(m + 1); // Get a set of binomial coefficients. 976 double[] a = shape2Monomial(Sf.getBasisFunction(), bin); 977 978 // Create the "c" coefficients for the new monomial. 979 // xi = t, to c[1] = 1. 980 double[] c = ArrayFactory.DOUBLES_FACTORY.array(m + 1); 981 c[0] = 0; 982 c[1] = 1; 983 for (int i=2; i <= m; ++i) 984 c[i] = 0; 985 986 // Create the "b" coefficients for the new monomial. 987 // zeta = sum_{i=0}^{n}{ai*t^i} + t*DzetaTE (Eqn. 16 with xi = t) 988 // zeta = a0 + (a1 + DzetaTE)*t + sum_{i=2}^{n}{ai*t^i} = sum_{i=0}^{n}{bi*t^i} 989 double[] b = a; 990 b[1] += offsetTE_c; 991 992 // Form the new Bezier curve from the newly computed monomial coefficients. 993 BasicNurbsCurve newCrv = monomial2Bezier(c,b, m, bin); 994 995 return newCrv; 996 } 997 998 /** 999 * Method that converts the 1D shape function Bezier curve to a monomial 1000 * representation of the form: <code>S(t) = sum_{i=0}^{n}{ai*t^i}</code>. 1001 * 1002 * @param S The Bezier curve that is the basis for the shape function. 1003 * @param bin A set of binomial coefficients of at least the S.getOrder() in size. 1004 * @return The coefficients of a monomial representation of the shape function. 1005 */ 1006 private static double[] shape2Monomial(BasicNurbsCurve S, BinomialCoef bin) { 1007 1008 // Convert the Bezier curve shape function to a monomial of the form: 1009 // S(t) = sum_{i=0}^{n} {ai*t^i} 1010 // Equation 7 from reference: aj = sum_{i=0}^{j}{ (n,j) * (j,i) * (-1)^(j-i) * pi } 1011 int n = S.getDegree(); 1012 List<ControlPoint> cps = S.getControlPoints(); 1013 double[] a = ArrayFactory.DOUBLES_FACTORY.array(n + 1); 1014 for (int j = 0; j <= n; ++j) { 1015 a[j] = 0; 1016 for (int i = 0; i <= j; ++i) { 1017 double Bnj = bin.get(n, j); 1018 double Bji = bin.get(j, i); 1019 double sign = (MathTools.even(j - i) ? 1.0 : -1.0); // = (-1)^(j-i) 1020 double pi = cps.get(i).getPoint().getValue(Point.X); 1021 a[j] += Bnj * Bji * sign * pi; 1022 } 1023 } 1024 1025 return a; 1026 } 1027 1028 /** 1029 * Form a 2D Bezier curve (as a NURBS curve) from the input list of monomial 1030 * coefficients of degree "m". 1031 * 1032 * @param c An array of monomial coefficients (c.length > m) for the "X" dimension. 1033 * @param b An array of monomial coefficients (b.length > m) for the "Y" dimension. 1034 * @param m The degree of the monomial equation. 1035 * @param bin A set of binomial coefficients of at least size m+1. 1036 * @return A new 2D Bezier curve representation of the input monomial equation 1037 * coefficients. 1038 */ 1039 private BasicNurbsCurve monomial2Bezier(double[] c, double[] b, int m, BinomialCoef bin) { 1040 1041 // Form the new control points using Equation 12 from the reference: 1042 // qveci = 1/(m,n)*sum_{j=0}^{i}{ (m-j, i-j) * dvecj } 1043 // dvecj = [cj, bj] 1044 FastTable<ControlPoint> cpLst = FastTable.newInstance(); 1045 for (int i = 0; i <= m; ++i) { 1046 double invBmi = 1. / bin.get(m, i); 1047 Point qi = Point.valueOf(0, 0); 1048 for (int j = 0; j <= i; ++j) { 1049 double B = invBmi * bin.get(m - j, i - j); 1050 Point qij = Point.valueOf(c[j]*B, b[j]*B); 1051 qi = qi.plus(qij); 1052 } 1053 double ch = chord.getValue(SI.METER); 1054 double qixh = ch*qi.getValue(0); // qi in the xhat direction. 1055 double qiyh = ch*qi.getValue(1); // qi in the yhat direction. 1056 Point cpp = Point.valueOf(xhat.times(qixh).plus(yhat.times(qiyh))).to(getUnit()); 1057 ControlPoint cpi = ControlPoint.valueOf(cpp, 1); 1058 cpLst.add(cpi); 1059 } 1060 1061 // Create a Bezier curve knot vector. 1062 KnotVector bezierKV = KnotVector.bezierKnotVector(m); 1063 1064 // Construct the new Bezier curve as a NURBS curve. 1065 BasicNurbsCurve newCrv = BasicNurbsCurve.newInstance(cpLst, bezierKV); 1066 1067 return newCrv; 1068 } 1069 1070 /** 1071 * Returns a copy of this {@link BasicCSTCurve} instance 1072 * {@link javolution.context.AllocatorContext allocated} by the calling thread 1073 * (possibly on the stack). 1074 * 1075 * @return an identical and independent copy of this curve. 1076 */ 1077 @Override 1078 public BasicCSTCurve copy() { 1079 return copyOf(this); 1080 } 1081 1082 /** 1083 * Return a copy of this object with any transformations or subranges removed 1084 * (applied). 1085 * 1086 * @return A copy of this object with any transformations or subranges removed 1087 * (applied). 1088 */ 1089 @Override 1090 public BasicCSTCurve copyToReal() { 1091 return copy(); 1092 } 1093 1094 /** 1095 * Return an immutable version of this CST curve. This implementation simply returns 1096 * this BasicCSTCurve instance. 1097 * 1098 * @return an immutable version of this curve. 1099 */ 1100 @Override 1101 public BasicCSTCurve immutable() { 1102 return this; 1103 } 1104 1105 /** 1106 * Compares this BasicCSTCurve against the specified object for strict equality (same 1107 * values and same units). 1108 * 1109 * @param obj the object to compare with. 1110 * @return <code>true</code> if this point is identical to that point; 1111 * <code>false</code> otherwise. 1112 */ 1113 @Override 1114 public boolean equals(Object obj) { 1115 if (this == obj) 1116 return true; 1117 if ((obj == null) || (obj.getClass() != this.getClass())) 1118 return false; 1119 1120 BasicCSTCurve that = (BasicCSTCurve)obj; 1121 return this.offsetTE_c == that.offsetTE_c 1122 && this.Cf.equals(that.Cf) 1123 && this.Sf.equals(that.Sf) 1124 && this.chord.equals(that.chord) 1125 && this.origin.equals(that.origin) 1126 && this.xhat.equals(that.xhat) 1127 && this.yhat.equals(that.yhat) 1128 && super.equals(obj); 1129 } 1130 1131 /** 1132 * Returns the hash code for this parameter. 1133 * 1134 * @return the hash code value. 1135 */ 1136 @Override 1137 public int hashCode() { 1138 return 31*super.hashCode() 1139 + 31*makeVarCode(offsetTE_c) 1140 + Objects.hash(Cf, Sf, chord, origin, xhat, yhat); 1141 } 1142 1143 private static int makeVarCode(double value) { 1144 long bits = Double.doubleToLongBits(value); 1145 int var_code = (int)(bits ^ (bits >>> 32)); 1146 return var_code; 1147 } 1148 1149 /** 1150 * Recycles a <code>BasicCSTCurve</code> instance immediately (on the stack when 1151 * executing in a StackContext). 1152 * 1153 * @param instance The instance to be recycled. 1154 */ 1155 public static void recycle(BasicCSTCurve instance) { 1156 FACTORY.recycle(instance); 1157 } 1158 1159 /** 1160 * Holds the default XML representation for this object. 1161 */ 1162 @SuppressWarnings("FieldNameHidesFieldInSuperclass") 1163 protected static final XMLFormat<BasicCSTCurve> XML = new XMLFormat<BasicCSTCurve>(BasicCSTCurve.class) { 1164 1165 @Override 1166 public BasicCSTCurve newInstance(Class<BasicCSTCurve> cls, XMLFormat.InputElement xml) throws XMLStreamException { 1167 return FACTORY.object(); 1168 } 1169 1170 @Override 1171 public void read(XMLFormat.InputElement xml, BasicCSTCurve obj) throws XMLStreamException { 1172 double offsetTE_c = xml.getAttribute("offsetTE_c", 0.0); 1173 1174 CSTCurve.XML.read(xml, obj); // Call parent read. 1175 1176 NurbsCurve pmap = xml.get("pmap"); 1177 CSTClassFunction Cf = xml.get("Cf"); 1178 CSTShapeFunction Sf = xml.get("Sf"); 1179 Parameter<Length> chord = xml.get("Chord"); 1180 Point origin = xml.get("Origin"); 1181 Vector<Dimensionless> xhat = xml.get("xhat"); 1182 Vector<Dimensionless> yhat = xml.get("yhat"); 1183 1184 obj.pmap = pmap; 1185 obj.Cf = Cf; 1186 obj.Sf = Sf; 1187 obj.offsetTE_c = offsetTE_c; 1188 obj.chord = chord; 1189 obj.origin = origin; 1190 obj.xhat = xhat; 1191 obj.yhat = yhat; 1192 obj.calcBoundsMinMax(); 1193 1194 } 1195 1196 @Override 1197 public void write(BasicCSTCurve obj, XMLFormat.OutputElement xml) throws XMLStreamException { 1198 xml.setAttribute("offsetTE_c", obj.offsetTE_c); 1199 1200 CSTCurve.XML.write(obj, xml); // Call parent write. 1201 1202 xml.add(obj.pmap, "pmap"); 1203 xml.add(obj.Cf, "Cf"); 1204 xml.add(obj.Sf, "Sf"); 1205 xml.add(obj.chord, "Chord"); 1206 xml.add(obj.origin, "Origin"); 1207 xml.add(obj.xhat, "xhat"); 1208 xml.add(obj.yhat, "yhat"); 1209 } 1210 }; 1211 1212 /////////////////////// 1213 // Factory creation. // 1214 /////////////////////// 1215 private BasicCSTCurve() { } 1216 1217 @SuppressWarnings("unchecked") 1218 private static final ObjectFactory<BasicCSTCurve> FACTORY = new ObjectFactory<BasicCSTCurve>() { 1219 @Override 1220 protected BasicCSTCurve create() { 1221 return new BasicCSTCurve(); 1222 } 1223 1224 @Override 1225 protected void cleanup(BasicCSTCurve obj) { 1226 obj.reset(); 1227 obj.pmap = null; 1228 obj.Cf = null; 1229 obj.Sf = null; 1230 obj.chord = null; 1231 obj.origin = null; 1232 obj.xhat = null; 1233 obj.yhat = null; 1234 obj._boundsMax = null; 1235 obj._boundsMin = null; 1236 } 1237 }; 1238 1239 @SuppressWarnings("unchecked") 1240 private static BasicCSTCurve copyOf(BasicCSTCurve original) { 1241 BasicCSTCurve obj = FACTORY.object(); 1242 obj.pmap = original.pmap.copy(); 1243 obj.Cf = original.Cf.copy(); 1244 obj.Sf = original.Sf.copy(); 1245 obj.offsetTE_c = original.offsetTE_c; 1246 obj.origin = original.origin.copy(); 1247 obj.chord = original.chord.copy(); 1248 obj.xhat = original.xhat.copy(); 1249 obj.yhat = original.yhat.copy(); 1250 obj._boundsMax = original._boundsMax.copy(); 1251 obj._boundsMin = original._boundsMin.copy(); 1252 original.copyState(obj); 1253 return obj; 1254 } 1255 1256}