001/** 002 * BasicCSTCurve -- An implementation of a Class-Shape-Transform planar curve. 003 * 004 * Copyright (C) 2014-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 */ 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 November 28, 2015 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 } 646 647 /** 648 * Returns the unit in which this curve is stated. 649 * 650 * @return The unit that this curves points are stated in. 651 */ 652 @Override 653 public Unit<Length> getUnit() { 654 return origin.getUnit(); 655 } 656 657 /** 658 * Returns the equivalent to this curve but stated in the specified unit. 659 * 660 * @param unit the length unit of the curve to be returned. May not be null. 661 * @return an equivalent to this curve but stated in the specified unit. 662 * @throws ConversionException if the the input unit is not a length unit. 663 */ 664 @Override 665 public BasicCSTCurve to(Unit<Length> unit) throws ConversionException { 666 if (unit.equals(getUnit())) 667 return this; 668 BasicCSTCurve crv = newInstance(Cf, Sf, getOffsetTE(), origin.to(unit), chord.to(unit), xhat, yhat); 669 return copyState(crv); 670 } 671 672 /** 673 * Return the equivalent of this curve converted to the specified number of physical 674 * dimensions. If the number of dimensions is greater than this element, then zeros 675 * are added to the additional dimensions. If the number of dimensions is less than 676 * this element, then the extra dimensions are simply dropped (truncated). If the new 677 * dimensions are the same as the dimension of this element, then this element is 678 * simply returned. 679 * 680 * @param newDim The dimension of the curve to return. 681 * @return The equivalent of this curve converted to the new dimensions. 682 */ 683 @Override 684 public BasicCSTCurve toDimension(int newDim) { 685 if (getPhyDimension() == newDim) 686 return this; 687 BasicCSTCurve crv = newInstance(Cf, Sf, getOffsetTE(), origin.toDimension(newDim), chord, 688 xhat.toDimension(newDim), yhat.toDimension(newDim)); 689 return copyState(crv); 690 } 691 692 /** 693 * Return a NURBS curve representation of this curve to within the specified 694 * tolerance. 695 * 696 * @param tol The greatest possible difference between this curve and the NURBS 697 * representation returned. May not be null. 698 * @return A NURBS curve that represents this curve to within the specified tolerance. 699 */ 700 @Override 701 public NurbsCurve toNurbs(Parameter<Length> tol) { 702 StackContext.enter(); 703 try { 704 // Can this curve be represented exactly by a NURBS curve? 705 // Only integer N2 (either 0 or 1) can be represented exactly as NURBS. 706 if (MathTools.isApproxEqual(Cf.getN2(), 1.0)) { 707 if (MathTools.isApproxEqual(Cf.getN1(), 0.5)) { 708 BasicNurbsCurve curve = bluntAirfoil2Bezier(); 709 curve = matchParameterization(curve); 710 copyState(curve); 711 return StackContext.outerCopy(curve); 712 } 713 if (MathTools.isApproxEqual(Cf.getN1(), 1.0)) { 714 BasicNurbsCurve curve = sharpAirfoil2Bezier(); 715 curve = matchParameterization(curve); 716 copyState(curve); 717 return StackContext.outerCopy(curve); 718 } 719 720 } else if (MathTools.isApproxEqual(Cf.getN2(), 0)) { 721 if (MathTools.isApproxEqual(Cf.getN1(), 1.0)) { 722 BasicNurbsCurve curve = cone2Bezier(); 723 curve = matchParameterization(curve); 724 copyState(curve); 725 return StackContext.outerCopy(curve); 726 } 727 if (MathTools.isApproxEqual(Cf.getN1(), 0.0)) { 728 BasicNurbsCurve curve = rectangular2Bezier(); 729 curve = matchParameterization(curve); 730 copyState(curve); 731 return StackContext.outerCopy(curve); 732 } 733 } 734 735 // Fall-back to gridding points onto the curve and then fitting a curve through them. 736 737 // Grid some points onto the curve. 738 PointString<SubrangePoint> str = this.gridToTolerance(requireNonNull(tol)); 739 740 // Fit a cubic NURBS curve to the points. 741 int deg = Sf.getOrder() - 1; 742 if (deg < 3) 743 deg = 3; 744 if (str.size() <= deg) 745 deg = str.size() - 1; 746 BasicNurbsCurve curve = CurveFactory.fitPoints(deg, str); 747 copyState(curve); // Copy over the super-class state for this curve to the new one. 748 749 return StackContext.outerCopy(curve); 750 751 } finally { 752 StackContext.exit(); 753 } 754 } 755 756 /** 757 * Modify the input NurbsCurve to have the same parameterization as this CST curve. 758 * 759 * @param crv The NURBS curve to be modified. 760 * @return The modified NURBS curve. 761 */ 762 private BasicNurbsCurve matchParameterization(BasicNurbsCurve crv) { 763 // Get the limits of this curve's parameter mapping. 764 double s0 = pmap.getRealPoint(0).getValue(0); 765 double s1 = pmap.getRealPoint(1).getValue(0); 766 767 // Is the parameterization reversed? 768 boolean reversed = s1 < s0; 769 770 if (!reversed) { 771 if (!parNearStart(s0, TOL_S)) { 772 // s0 is not at the start of the curve. 773 774 // Find s0 on the NurbsCurve and split the NurbsCurve there. 775 SubrangePoint p = crv.getClosest(this.getRealPoint(0), s0, TOL_S); 776 double s_split = p.getParPosition().getValue(0); 777 GeomList<NurbsCurve> lst = crv.splitAt(s_split); 778 crv = (BasicNurbsCurve)lst.getLast(); 779 } 780 if (!parNearEnd(s1, TOL_S)) { 781 // s1 is not at the end of the curve. 782 783 // Find s1 on the NurbsCurve and split the NurbsCurve there. 784 SubrangePoint p = crv.getClosest(this.getRealPoint(1), s1, TOL_S); 785 double s_split = p.getParPosition().getValue(0); 786 GeomList<NurbsCurve> lst = crv.splitAt(s_split); 787 crv = (BasicNurbsCurve)lst.getFirst(); 788 } 789 790 } else { 791 // The parameterization is reversed. 792 if (!parNearStart(s1, TOL_S)) { 793 // s01 is not at the start of the curve. 794 795 // Find s1 on the NurbsCurve and split the NurbsCurve there. 796 SubrangePoint p = crv.getClosest(this.getRealPoint(1), s1, TOL_S); 797 double s_split = p.getParPosition().getValue(0); 798 GeomList<NurbsCurve> lst = crv.splitAt(s_split); 799 crv = (BasicNurbsCurve)lst.getLast(); 800 } 801 if (!parNearEnd(s0, TOL_S)) { 802 // s0 is not at the end of the curve. 803 804 // Find s0 on the NurbsCurve and split the NurbsCurve there. 805 SubrangePoint p = crv.getClosest(this.getRealPoint(0), s0, TOL_S); 806 double s_split = p.getParPosition().getValue(0); 807 GeomList<NurbsCurve> lst = crv.splitAt(s_split); 808 crv = (BasicNurbsCurve)lst.getFirst(); 809 } 810 crv = (BasicNurbsCurve)crv.reverse(); 811 } 812 813 return crv; 814 } 815 816 /** 817 * An exact conversion to a Bezier curve is possible for a blunt-nosed airfoil CST curve. 818 * 819 * @return A NURBS curve that represents this airfoil curve exactly. 820 */ 821 private BasicNurbsCurve bluntAirfoil2Bezier() { 822 // Ref.: Marshall, D.D., "Creating Exact Bezier Representations of CST Shapes", 823 // 21st AIAA Computational Fluid Dynamics Conference Proceedings: San Diego, CA, June 24, 2013. 824 825 int n = Sf.getOrder() - 1; // Degree of the shape function. 826 int m = 2*n + 3; // Degree of output NURBS curve. 827 828 // Convert the Bezier curve shape function to a monomial of the form: S(t) = sum_{i=0}^{n} ai*t^i 829 BinomialCoef bin = BinomialCoef.newInstance(m + 1); // Get a set of binomial coefficients. 830 double[] a = shape2Monomial(Sf.getBasisFunction(), bin); 831 832 // Create the "c" coefficients for the new monomial using Equation 13 from the reference. 833 // xi = s^2 due to parameter substitution (see below), so c[2] = 1. 834 double[] c = ArrayFactory.DOUBLES_FACTORY.array(m + 1); 835 c[0] = 0; 836 c[1] = 0; 837 c[2] = 1; 838 for (int i=3; i <= m; ++i) 839 c[i] = 0; 840 841 // Create the "b" coefficients for the new monomial using Equation 11 from the reference. 842 // zeta = sqrt(t)*(1-t)*sum_{i=0}^{n}{ai*t^i} + t*DzetaTE 843 // With parameter substitution: t ==> s^2: 844 // zeta = s*[a0 + sum_{i=1}^{n}{(ai - a_(i-1))*s^(2*i) - an*s^(2*(n+1))] + s^2*DzetaTE; or simplifying: 845 // 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} 846 double[] b = ArrayFactory.DOUBLES_FACTORY.array(m + 1); 847 for (int i = 0; i <= m; ++i) { 848 if (i == 1) 849 b[i] = a[0]; 850 else if (i == 2) 851 b[i] = offsetTE_c; 852 else if (MathTools.even(i)) 853 b[i] = 0; 854 else if (i == m) 855 b[i] = -a[n]; 856 else { 857 int i2 = (i - 1) / 2; 858 b[i] = a[i2] - a[i2 - 1]; 859 } 860 } 861 862 // Form the new Bezier curve from the newly computed monomial coefficients. 863 BasicNurbsCurve newCrv = monomial2Bezier(c,b, m, bin); 864 865 return newCrv; 866 } 867 868 /** 869 * An exact conversion to a Bezier curve is possible for a sharp-nosed airfoil CST curve. 870 * 871 * @return A NURBS curve that represents this airfoil curve exactly. 872 */ 873 private BasicNurbsCurve sharpAirfoil2Bezier() { 874 // Ref.: Marshall, D.D., "Creating Exact Bezier Representations of CST Shapes", 875 // 21st AIAA Computational Fluid Dynamics Conference Proceedings: San Diego, CA, June 24, 2013. 876 877 int n = Sf.getOrder() - 1; // Degree of the shape function. 878 int m = n + 2; // Degree of output NURBS curve. 879 880 // Convert the Bezier curve shape function to a monomial of the form: S(t) = sum_{i=0}^{n} ai*t^i 881 BinomialCoef bin = BinomialCoef.newInstance(m + 1); // Get a set of binomial coefficients. 882 double[] a = shape2Monomial(Sf.getBasisFunction(), bin); 883 884 // Create the "c" coefficients for the new monomial. 885 // xi = t, to c[1] = 1. 886 double[] c = ArrayFactory.DOUBLES_FACTORY.array(m + 1); 887 c[0] = 0; 888 c[1] = 1; 889 for (int i=2; i <= m; ++i) 890 c[i] = 0; 891 892 // Create the "b" coefficients for the new monomial. 893 // zeta = t*(1 - t)*sum_{i=0}^{n}{ai*t^i} + t*DzetaTE (Eqn. 14 with xi = t) 894 // zeta = t*[a0 + sum_{i=1}^{n}{(ai - a_(i-1))*t^i - an*s^(n+1)] + t*DzetaTE; simplifying: 895 // 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} 896 double[] b = ArrayFactory.DOUBLES_FACTORY.array(m + 1); 897 for (int i = 0; i <= m; ++i) { 898 if (i == 0) 899 b[i] = 0; 900 else if (i == 1) 901 b[i] = a[0] + offsetTE_c; 902 else if (i == m) 903 b[i] = -a[n]; 904 else { 905 int i2 = i - 1; 906 b[i] = a[i2] - a[i2 - 1]; 907 } 908 } 909 910 // Form the new Bezier curve from the newly computed monomial coefficients. 911 BasicNurbsCurve newCrv = monomial2Bezier(c,b, m, bin); 912 913 return newCrv; 914 } 915 916 /** 917 * An exact conversion to a Bezier curve is possible for a cone or wedge shaped CST curve. 918 * 919 * @return A NURBS curve that represents this CST curve exactly. 920 */ 921 private BasicNurbsCurve cone2Bezier() { 922 // Ref.: Marshall, D.D., "Creating Exact Bezier Representations of CST Shapes", 923 // 21st AIAA Computational Fluid Dynamics Conference Proceedings: San Diego, CA, June 24, 2013. 924 925 int n = Sf.getOrder() - 1; // Degree of the shape function. 926 int m = n + 1; // Degree of output NURBS curve. 927 928 // Convert the Bezier curve shape function to a monomial of the form: S(t) = sum_{i=0}^{n} ai*t^i 929 BinomialCoef bin = BinomialCoef.newInstance(m + 1); // Get a set of binomial coefficients. 930 double[] a = shape2Monomial(Sf.getBasisFunction(), bin); 931 932 // Create the "c" coefficients for the new monomial. 933 // xi = t, to c[1] = 1. 934 double[] c = ArrayFactory.DOUBLES_FACTORY.array(m + 1); 935 c[0] = 0; 936 c[1] = 1; 937 for (int i=2; i <= m; ++i) 938 c[i] = 0; 939 940 // Create the "b" coefficients for the new monomial. 941 // zeta = t*sum_{i=0}^{n}{ai*t^i} + t*DzetaTE (Eqn. 15 with xi = t) 942 // zeta = (a0 + DzetaTE)*t + sum_{i=1}^{n}{ai*t^(i+1)} + an*t^m = sum_{i=0}^{n}{bi*t^i} 943 double[] b = ArrayFactory.DOUBLES_FACTORY.array(m + 1); 944 for (int i = 0; i <= m; ++i) { 945 if (i == 0) 946 b[i] = 0; 947 else if (i == 1) 948 b[i] = a[0] + offsetTE_c; 949 else if (i == m) 950 b[i] = a[n]; 951 else 952 b[i] = a[i]; 953 } 954 955 // Form the new Bezier curve from the newly computed monomial coefficients. 956 BasicNurbsCurve newCrv = monomial2Bezier(c,b, m, bin); 957 958 return newCrv; 959 } 960 961 /** 962 * An exact conversion to a Bezier curve is possible for a rectangular shaped CST curve. 963 * 964 * @return A NURBS curve that represents this CST curve exactly. 965 */ 966 private BasicNurbsCurve rectangular2Bezier() { 967 // Ref.: Marshall, D.D., "Creating Exact Bezier Representations of CST Shapes", 968 // 21st AIAA Computational Fluid Dynamics Conference Proceedings: San Diego, CA, June 24, 2013. 969 970 int n = Sf.getOrder() - 1; // Degree of the shape function. 971 int m = n; // Degree of output NURBS curve. 972 973 // Convert the Bezier curve shape function to a monomial of the form: S(t) = sum_{i=0}^{n} ai*t^i 974 BinomialCoef bin = BinomialCoef.newInstance(m + 1); // Get a set of binomial coefficients. 975 double[] a = shape2Monomial(Sf.getBasisFunction(), bin); 976 977 // Create the "c" coefficients for the new monomial. 978 // xi = t, to c[1] = 1. 979 double[] c = ArrayFactory.DOUBLES_FACTORY.array(m + 1); 980 c[0] = 0; 981 c[1] = 1; 982 for (int i=2; i <= m; ++i) 983 c[i] = 0; 984 985 // Create the "b" coefficients for the new monomial. 986 // zeta = sum_{i=0}^{n}{ai*t^i} + t*DzetaTE (Eqn. 16 with xi = t) 987 // zeta = a0 + (a1 + DzetaTE)*t + sum_{i=2}^{n}{ai*t^i} = sum_{i=0}^{n}{bi*t^i} 988 double[] b = a; 989 b[1] += offsetTE_c; 990 991 // Form the new Bezier curve from the newly computed monomial coefficients. 992 BasicNurbsCurve newCrv = monomial2Bezier(c,b, m, bin); 993 994 return newCrv; 995 } 996 997 /** 998 * Method that converts the 1D shape function Bezier curve to a monomial 999 * representation of the form: <code>S(t) = sum_{i=0}^{n}{ai*t^i}</code>. 1000 * 1001 * @param S The Bezier curve that is the basis for the shape function. 1002 * @param bin A set of binomial coefficients of at least the S.getOrder() in size. 1003 * @return The coefficients of a monomial representation of the shape function. 1004 */ 1005 private static double[] shape2Monomial(BasicNurbsCurve S, BinomialCoef bin) { 1006 1007 // Convert the Bezier curve shape function to a monomial of the form: 1008 // S(t) = sum_{i=0}^{n} {ai*t^i} 1009 // Equation 7 from reference: aj = sum_{i=0}^{j}{ (n,j) * (j,i) * (-1)^(j-i) * pi } 1010 int n = S.getDegree(); 1011 List<ControlPoint> cps = S.getControlPoints(); 1012 double[] a = ArrayFactory.DOUBLES_FACTORY.array(n + 1); 1013 for (int j = 0; j <= n; ++j) { 1014 a[j] = 0; 1015 for (int i = 0; i <= j; ++i) { 1016 double Bnj = bin.get(n, j); 1017 double Bji = bin.get(j, i); 1018 double sign = (MathTools.even(j - i) ? 1.0 : -1.0); // = (-1)^(j-i) 1019 double pi = cps.get(i).getPoint().getValue(Point.X); 1020 a[j] += Bnj * Bji * sign * pi; 1021 } 1022 } 1023 1024 return a; 1025 } 1026 1027 /** 1028 * Form a 2D Bezier curve (as a NURBS curve) from the input list of monomial 1029 * coefficients of degree "m". 1030 * 1031 * @param c An array of monomial coefficients (c.length > m) for the "X" dimension. 1032 * @param b An array of monomial coefficients (b.length > m) for the "Y" dimension. 1033 * @param m The degree of the monomial equation. 1034 * @param bin A set of binomial coefficients of at least size m+1. 1035 * @return A new 2D Bezier curve representation of the input monomial equation 1036 * coefficients. 1037 */ 1038 private BasicNurbsCurve monomial2Bezier(double[] c, double[] b, int m, BinomialCoef bin) { 1039 1040 // Form the new control points using Equation 12 from the reference: 1041 // qveci = 1/(m,n)*sum_{j=0}^{i}{ (m-j, i-j) * dvecj } 1042 // dvecj = [cj, bj] 1043 FastTable<ControlPoint> cpLst = FastTable.newInstance(); 1044 for (int i = 0; i <= m; ++i) { 1045 double invBmi = 1. / bin.get(m, i); 1046 Point qi = Point.valueOf(0, 0); 1047 for (int j = 0; j <= i; ++j) { 1048 double B = invBmi * bin.get(m - j, i - j); 1049 Point qij = Point.valueOf(c[j]*B, b[j]*B); 1050 qi = qi.plus(qij); 1051 } 1052 double ch = chord.getValue(SI.METER); 1053 double qixh = ch*qi.getValue(0); // qi in the xhat direction. 1054 double qiyh = ch*qi.getValue(1); // qi in the yhat direction. 1055 Point cpp = Point.valueOf(xhat.times(qixh).plus(yhat.times(qiyh))).to(getUnit()); 1056 ControlPoint cpi = ControlPoint.valueOf(cpp, 1); 1057 cpLst.add(cpi); 1058 } 1059 1060 // Create a Bezier curve knot vector. 1061 KnotVector bezierKV = KnotVector.bezierKnotVector(m); 1062 1063 // Construct the new Bezier curve as a NURBS curve. 1064 BasicNurbsCurve newCrv = BasicNurbsCurve.newInstance(cpLst, bezierKV); 1065 1066 return newCrv; 1067 } 1068 1069 /** 1070 * Returns a copy of this {@link BasicCSTCurve} instance 1071 * {@link javolution.context.AllocatorContext allocated} by the calling thread 1072 * (possibly on the stack). 1073 * 1074 * @return an identical and independent copy of this curve. 1075 */ 1076 @Override 1077 public BasicCSTCurve copy() { 1078 return copyOf(this); 1079 } 1080 1081 /** 1082 * Return a copy of this object with any transformations or subranges removed 1083 * (applied). 1084 * 1085 * @return A copy of this object with any transformations or subranges removed 1086 * (applied). 1087 */ 1088 @Override 1089 public BasicCSTCurve copyToReal() { 1090 return copy(); 1091 } 1092 1093 /** 1094 * Return an immutable version of this CST curve. This implementation simply returns 1095 * this BasicCSTCurve instance. 1096 * 1097 * @return an immutable version of this curve. 1098 */ 1099 @Override 1100 public BasicCSTCurve immutable() { 1101 return this; 1102 } 1103 1104 /** 1105 * Compares this BasicCSTCurve against the specified object for strict equality (same 1106 * values and same units). 1107 * 1108 * @param obj the object to compare with. 1109 * @return <code>true</code> if this point is identical to that point; 1110 * <code>false</code> otherwise. 1111 */ 1112 @Override 1113 public boolean equals(Object obj) { 1114 if (this == obj) 1115 return true; 1116 if ((obj == null) || (obj.getClass() != this.getClass())) 1117 return false; 1118 1119 BasicCSTCurve that = (BasicCSTCurve)obj; 1120 return this.offsetTE_c == that.offsetTE_c 1121 && this.Cf.equals(that.Cf) 1122 && this.Sf.equals(that.Sf) 1123 && this.chord.equals(that.chord) 1124 && this.origin.equals(that.origin) 1125 && this.xhat.equals(that.xhat) 1126 && this.yhat.equals(that.yhat) 1127 && super.equals(obj); 1128 } 1129 1130 /** 1131 * Returns the hash code for this parameter. 1132 * 1133 * @return the hash code value. 1134 */ 1135 @Override 1136 public int hashCode() { 1137 return 31*super.hashCode() 1138 + 31*makeVarCode(offsetTE_c) 1139 + Objects.hash(Cf, Sf, chord, origin, xhat, yhat); 1140 } 1141 1142 private static int makeVarCode(double value) { 1143 long bits = Double.doubleToLongBits(value); 1144 int var_code = (int)(bits ^ (bits >>> 32)); 1145 return var_code; 1146 } 1147 1148 /** 1149 * Recycles a <code>BasicCSTCurve</code> instance immediately (on the stack when 1150 * executing in a StackContext). 1151 * 1152 * @param instance The instance to be recycled. 1153 */ 1154 public static void recycle(BasicCSTCurve instance) { 1155 FACTORY.recycle(instance); 1156 } 1157 1158 /** 1159 * Holds the default XML representation for this object. 1160 */ 1161 @SuppressWarnings("FieldNameHidesFieldInSuperclass") 1162 protected static final XMLFormat<BasicCSTCurve> XML = new XMLFormat<BasicCSTCurve>(BasicCSTCurve.class) { 1163 1164 @Override 1165 public BasicCSTCurve newInstance(Class<BasicCSTCurve> cls, XMLFormat.InputElement xml) throws XMLStreamException { 1166 return FACTORY.object(); 1167 } 1168 1169 @Override 1170 public void read(XMLFormat.InputElement xml, BasicCSTCurve obj) throws XMLStreamException { 1171 double offsetTE_c = xml.getAttribute("offsetTE_c", 0.0); 1172 1173 CSTCurve.XML.read(xml, obj); // Call parent read. 1174 1175 NurbsCurve pmap = xml.get("pmap"); 1176 CSTClassFunction Cf = xml.get("Cf"); 1177 CSTShapeFunction Sf = xml.get("Sf"); 1178 Parameter<Length> chord = xml.get("Chord"); 1179 Point origin = xml.get("Origin"); 1180 Vector<Dimensionless> xhat = xml.get("xhat"); 1181 Vector<Dimensionless> yhat = xml.get("yhat"); 1182 1183 obj.pmap = pmap; 1184 obj.Cf = Cf; 1185 obj.Sf = Sf; 1186 obj.offsetTE_c = offsetTE_c; 1187 obj.chord = chord; 1188 obj.origin = origin; 1189 obj.xhat = xhat; 1190 obj.yhat = yhat; 1191 obj.calcBoundsMinMax(); 1192 1193 } 1194 1195 @Override 1196 public void write(BasicCSTCurve obj, XMLFormat.OutputElement xml) throws XMLStreamException { 1197 xml.setAttribute("offsetTE_c", obj.offsetTE_c); 1198 1199 CSTCurve.XML.write(obj, xml); // Call parent write. 1200 1201 xml.add(obj.pmap, "pmap"); 1202 xml.add(obj.Cf, "Cf"); 1203 xml.add(obj.Sf, "Sf"); 1204 xml.add(obj.chord, "Chord"); 1205 xml.add(obj.origin, "Origin"); 1206 xml.add(obj.xhat, "xhat"); 1207 xml.add(obj.yhat, "yhat"); 1208 } 1209 }; 1210 1211 /////////////////////// 1212 // Factory creation. // 1213 /////////////////////// 1214 private BasicCSTCurve() { } 1215 1216 @SuppressWarnings("unchecked") 1217 private static final ObjectFactory<BasicCSTCurve> FACTORY = new ObjectFactory<BasicCSTCurve>() { 1218 @Override 1219 protected BasicCSTCurve create() { 1220 return new BasicCSTCurve(); 1221 } 1222 1223 @Override 1224 protected void cleanup(BasicCSTCurve obj) { 1225 obj.reset(); 1226 obj.pmap = null; 1227 obj.Cf = null; 1228 obj.Sf = null; 1229 obj.chord = null; 1230 obj.origin = null; 1231 obj.xhat = null; 1232 obj.yhat = null; 1233 obj._boundsMax = null; 1234 obj._boundsMin = null; 1235 } 1236 }; 1237 1238 @SuppressWarnings("unchecked") 1239 private static BasicCSTCurve copyOf(BasicCSTCurve original) { 1240 BasicCSTCurve obj = FACTORY.object(); 1241 obj.pmap = original.pmap.copy(); 1242 obj.Cf = original.Cf.copy(); 1243 obj.Sf = original.Sf.copy(); 1244 obj.offsetTE_c = original.offsetTE_c; 1245 obj.origin = original.origin.copy(); 1246 obj.chord = original.chord.copy(); 1247 obj.xhat = original.xhat.copy(); 1248 obj.yhat = original.yhat.copy(); 1249 obj._boundsMax = original._boundsMax.copy(); 1250 obj._boundsMin = original._boundsMin.copy(); 1251 original.copyState(obj); 1252 return obj; 1253 } 1254 1255}