001/** 002 * SubrangeSurface -- A Surface that is a subrange on another parametric surface. 003 * 004 * Copyright (C) 2013-2015, 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 Lesser 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; 019 020import geomss.geom.nurbs.*; 021import jahuwaldt.js.param.Parameter; 022import jahuwaldt.tools.math.MathTools; 023import java.text.MessageFormat; 024import java.util.List; 025import java.util.Objects; 026import static java.util.Objects.requireNonNull; 027import javax.measure.converter.ConversionException; 028import javax.measure.quantity.Dimensionless; 029import javax.measure.quantity.Length; 030import javax.measure.unit.SI; 031import javax.measure.unit.Unit; 032import javax.swing.event.ChangeEvent; 033import javax.swing.event.ChangeListener; 034import javolution.context.ObjectFactory; 035import javolution.context.StackContext; 036import javolution.lang.Immutable; 037import javolution.text.Text; 038import javolution.text.TextBuilder; 039import javolution.xml.XMLFormat; 040import javolution.xml.stream.XMLStreamException; 041 042/** 043 * A subrange or trimmed {@link Surface} that is defined by a set of four 2D boundary 044 * curves that define the 4 parametric edges of the trimmed surface on the child surface. 045 * 046 * <p> Modified by: Joseph A. Huwaldt </p> 047 * 048 * @author Joseph A. Huwaldt, Date: April 8, 2013 049 * @version November 27, 2015 050 */ 051@SuppressWarnings({"serial", "CloneableImplementsClone"}) 052public class SubrangeSurface extends AbstractSurface<SubrangeSurface> implements Subrange<Surface> { 053 054 /** 055 * The number of points used when estimating the twist vector and when estimating the 056 * min/max bounding box for this surface. 057 */ 058 private static final int NPTS = 21; 059 060 /** 061 * The parametric geometry object that this surface is located on. 062 */ 063 private Surface _child; 064 065 /** 066 * The parametric surface defining the parametric range for this surface. 067 */ 068 private Surface _u; 069 070 /** 071 * Reference to a change listener for this object's child geometry. 072 */ 073 private ChangeListener _childChangeListener = new MyChangeListener(this); 074 075 /** 076 * The minimum bounding point for this subrange surface. 077 */ 078 private transient Point _boundsMin; 079 080 /** 081 * The maximum bounding point for this subrange surface. 082 */ 083 private transient Point _boundsMax; 084 085 // The following are cached to improve performance. 086 private transient double _s = -1, _t = -1; 087 private transient double _uA = 1; // uA = uS*uT (product of parametric arc lengths along local s & t directions. 088 private Boolean _isDegenerate; 089 090 /** 091 * Returns a {@link SubrangeSurface} instance referring to the specified 092 * {@link Surface} and the supplied 2D surface in parametric space which maps the 093 * SubrangeSurface to the child Surface. The mapping surface in parametric space must 094 * be in units of meters and be in the range 0,0 to 1,1. 095 * 096 * @param child The {@link Surface} that this surface is subranged onto (may not be 097 * <code>null</code>). 098 * @param par A 2D surface in parametric space (between 0,0 and 1,1 in meters) that 099 * maps the parametric positions from the subrange surface to the child 100 * surface. If <code>null</code> is passed, the full parametric boundary 101 * of the child surface is used. 102 * @return the subrange surface having the specified mapping to the child surface. 103 * @throws DimensionException if the input parametric space surface does not have a 104 * parametric dimension equal to 2. 105 */ 106 public static SubrangeSurface newInstance(Surface child, Surface par) { 107 requireNonNull(child, MessageFormat.format(RESOURCES.getString("paramNullErr"), "child")); 108 if (par == null) 109 return SubrangeSurface.newInstance(child, 0, 0, 1, 1); 110 111 // Handle the user inputting a subrange surface on the child surface. 112 if (par instanceof SubrangeSurface && ((SubrangeSurface)par).getChild() == child) 113 par = ((SubrangeSurface)par).getParPosition(); 114 115 int pDim = 2; 116 if (par.getPhyDimension() != pDim) 117 throw new DimensionException(RESOURCES.getString("scIncParDim")); 118 119 // Convert the parametric surface to METER units (in case it isn't already). 120 par = par.to(SI.METER); 121 122 SubrangeSurface obj = FACTORY.object(); 123 obj._child = child; 124 obj._u = par; 125 obj.calcBoundsMinMax(); 126 127 if (!(par instanceof Immutable)) 128 par.addChangeListener(obj._childChangeListener); 129 if (!(child instanceof Immutable)) 130 child.addChangeListener(obj._childChangeListener); 131 132 return obj; 133 } 134 135 /** 136 * Returns a {@link SubrangeSurface} instance referring to the specified 137 * {@link Surface} and the supplied boundary location curves in parametric space. The 138 * boundary curves in parametric space must be in units of meters and be in the range 139 * 0,0 to 1,1. The start points of the s0,t0 curves must be coincident. The start of 140 * the s1 curve and end of the t0 curve must be coincident. The end of the s1 curve 141 * and end of the t1 curve must be coincident. And, the end of the s0 curve and start 142 * of the t1 curve must be coincident. The corner points of the parametric curves are 143 * checked for proper coincidence. 144 * 145 * @param child The {@link Surface} that this surface is subranged onto (may not be 146 * <code>null</code>). 147 * @param s0 A 2D curve of the parametric position (between 0,0 and 1,1 in meters) 148 * along each parametric dimension or a subrange curve on the child 149 * surface that serves as the s=0 boundary for the subrange surface. 150 * @param t0 A 2D curve of the parametric position (between 0,0 and 1,1 in meters) 151 * along each parametric dimension or a subrange curve on the child 152 * surface that serves as the t=0 boundary for the subrange surface. 153 * @param s1 A 2D curve of the parametric position (between 0,0 and 1,1 in meters) 154 * along each parametric dimension that serves as the s=1 boundary for 155 * the subrange surface. 156 * @param t1 A 2D curve of the parametric position (between 0,0 and 1,1 in meters) 157 * along each parametric dimension or a subrange curve on the child 158 * surface that serves as the t=1 boundary for the subrange surface. 159 * @param tol A tolerance (in parameter space) for how closely the corner points 160 * must match. 161 * @return the subrange surface having the specified boundary curves. 162 * @throws DimensionException if the input boundary curves are not subranges on child 163 * and their parametric dimensions are not equal to 2. 164 */ 165 public static SubrangeSurface newInstance(Surface child, Curve s0, Curve t0, Curve s1, Curve t1, double tol) { 166 requireNonNull(child, MessageFormat.format(RESOURCES.getString("paramNullErr"), "child")); 167 requireNonNull(s0, MessageFormat.format(RESOURCES.getString("paramNullErr"), "s0")); 168 requireNonNull(s1, MessageFormat.format(RESOURCES.getString("paramNullErr"), "s1")); 169 requireNonNull(t0, MessageFormat.format(RESOURCES.getString("paramNullErr"), "t0")); 170 requireNonNull(t1, MessageFormat.format(RESOURCES.getString("paramNullErr"), "t1")); 171 172 // Handle the user inputting subrange curves on the child surface. 173 if (s0 instanceof SubrangeCurve && ((SubrangeCurve)s0).getChild() == child) 174 s0 = ((SubrangeCurve)s0).getParPosition(); 175 if (s1 instanceof SubrangeCurve && ((SubrangeCurve)s1).getChild() == child) 176 s1 = ((SubrangeCurve)s1).getParPosition(); 177 if (t0 instanceof SubrangeCurve && ((SubrangeCurve)t0).getChild() == child) 178 t0 = ((SubrangeCurve)t0).getParPosition(); 179 if (t1 instanceof SubrangeCurve && ((SubrangeCurve)t1).getChild() == child) 180 t1 = ((SubrangeCurve)t1).getParPosition(); 181 182 int pDim = child.getParDimension(); 183 if (s0.getPhyDimension() != pDim) 184 throw new DimensionException(RESOURCES.getString("scIncParDim")); 185 if (s1.getPhyDimension() != pDim) 186 throw new DimensionException(RESOURCES.getString("scIncParDim")); 187 if (t0.getPhyDimension() != pDim) 188 throw new DimensionException(RESOURCES.getString("scIncParDim")); 189 if (t1.getPhyDimension() != pDim) 190 throw new DimensionException(RESOURCES.getString("scIncParDim")); 191 192 // Convert the parametric curves to METER units (in case they aren't already). 193 s0 = s0.to(SI.METER); 194 s1 = s1.to(SI.METER); 195 t0 = t0.to(SI.METER); 196 t1 = t1.to(SI.METER); 197 198 // Convert the boundary curves into a parametric surface. 199 NurbsSurface par = SurfaceFactory.createTFISurface(s0, t0, s1, t1, Parameter.valueOf(tol, SI.METER)); 200 SubrangeSurface obj = FACTORY.object(); 201 obj._child = child; 202 obj._u = par; 203 obj.calcBoundsMinMax(); 204 205 if (!(child instanceof Immutable)) 206 child.addChangeListener(obj._childChangeListener); 207 208 return obj; 209 } 210 211 /** 212 * Returns a SubrangeSurface on the surface <code>child</code> that covers the range 213 * of parametric positions from <code>s0,t0</code> to <code>s1,t1</code>. 214 * 215 * @param child The {@link Surface} object that this surface is subranged onto (may 216 * not be <code>null</code>). 217 * @param s0 The parametric position on the child surface that should form the s=0 218 * edge of the subrange surface. 219 * @param t0 The parametric position on the child surface that should form the t=0 220 * edge of the subrange surface. 221 * @param s1 The parametric position on the child surface that should form the s=1 222 * edge of the subrange surface. 223 * @param t1 The parametric position on the child surface that should form the t=1 224 * edge of the subrange surface. 225 * @return the subrange surface having the specified range of parametric values on the 226 * child surface. 227 */ 228 public static SubrangeSurface newInstance(Surface child, double s0, double t0, double s1, double t1) { 229 requireNonNull(child, MessageFormat.format(RESOURCES.getString("paramNullErr"), "child")); 230 231 // Form the desired boundary parametric curves. 232 Point p00 = Point.valueOf(s0, t0); 233 Point p01 = Point.valueOf(s0, t1); 234 Point p11 = Point.valueOf(s1, t1); 235 Point p10 = Point.valueOf(s1, t0); 236 Curve s0crv = LineSeg.valueOf(p00, p01); 237 Curve t0crv = LineSeg.valueOf(p00, p10); 238 Curve s1crv = LineSeg.valueOf(p10, p11); 239 Curve t1crv = LineSeg.valueOf(p01, p11); 240 241 return SubrangeSurface.newInstance(child, s0crv, t0crv, s1crv, t1crv, GTOL); 242 } 243 244 // A change listener that re-calculates the subrange bounds as well as 245 // passing the child's change event on to any listeners. 246 private static class MyChangeListener extends ForwardingChangeListener { 247 248 private final SubrangeSurface target; 249 250 public MyChangeListener(SubrangeSurface geom) { 251 super(geom); 252 this.target = geom; 253 } 254 255 @Override 256 public void stateChanged(ChangeEvent e) { 257 // Re-calculate the bounds of this subrange curve. 258 target.calcBoundsMinMax(); 259 super.stateChanged(e); 260 } 261 } 262 263 /** 264 * Returns the child object this point is subranged onto. 265 */ 266 @Override 267 public ParametricGeometry getChild() { 268 return _child; 269 } 270 271 /** 272 * Returns a 2D surface in parametric space which maps this surface's parametric 273 * positions to the child surface. 274 * 275 * @return A 2D surface in parametric space that maps this surface's parameters to the 276 * child surface. 277 */ 278 @Override 279 public Surface getParPosition() { 280 return _u; 281 } 282 283 /** 284 * Sets the range of parametric positions on the child object that this surface refers 285 * to. The parametric surface must be 2D, in units of meters and be in the range 0,0 286 * to 1,1. 287 * 288 * @param par The mapping of parametric positions (0,0 to 1,1) from this surface to 289 * the child surface. May not be null. 290 */ 291 @Override 292 public void setParPosition(Surface par) { 293 requireNonNull(par, MessageFormat.format(RESOURCES.getString("paramNullErr"), "par")); 294 295 // Handle the user inputting a subrange surface on the child surface. 296 if (par instanceof SubrangeSurface && ((SubrangeSurface)par).getChild() == _child) 297 par = ((SubrangeSurface)par).getParPosition(); 298 299 int pDim = 2; 300 if (par.getPhyDimension() != pDim) 301 throw new DimensionException(RESOURCES.getString("scIncParDim")); 302 303 // Convert the parametric surface to METER units (in case it isn't already). 304 par = par.to(SI.METER); 305 306 // Change the parametric surface for this SubrangeSurface. 307 if (!(_u instanceof Immutable)) 308 _u.removeChangeListener(_childChangeListener); 309 310 _u = par; 311 312 if (!(par instanceof Immutable)) 313 par.addChangeListener(_childChangeListener); 314 315 calcBoundsMinMax(); 316 _s = -1; 317 _t = -1; 318 _uA = 1; 319 _isDegenerate = null; 320 321 this.fireChangeEvent(); 322 } 323 324 /** 325 * Returns the number of child-elements that make up this geometry element. This 326 * implementation returns the size of the child object. 327 * 328 * @return The number of child-elements that make up this geometry element. 329 */ 330 @Override 331 public int size() { 332 return _child.size(); 333 } 334 335 /** 336 * Return the T=0 Boundary for this surface as a curve. 337 * 338 * @return The T=0 Boundary for this surface as a curve. 339 */ 340 @Override 341 public Curve getT0Curve() { 342 return SubrangeCurve.newInstance(_child, _u.getT0Curve()); 343 } 344 345 /** 346 * Return the T=1 Boundary for this surface as a curve. 347 * 348 * @return The T=1 Boundary for this surface as a curve. 349 */ 350 @Override 351 public Curve getT1Curve() { 352 return SubrangeCurve.newInstance(_child, _u.getT1Curve()); 353 } 354 355 /** 356 * Return the S=0 Boundary for this surface as a curve. 357 * 358 * @return The S=0 Boundary for this surface as a curve. 359 */ 360 @Override 361 public Curve getS0Curve() { 362 return SubrangeCurve.newInstance(_child, _u.getS0Curve()); 363 } 364 365 /** 366 * Return the S=1 Boundary for this surface as a curve. 367 * 368 * @return The S=1 Boundary for this surface as a curve. 369 */ 370 @Override 371 public Curve getS1Curve() { 372 return SubrangeCurve.newInstance(_child, _u.getS1Curve()); 373 } 374 375 /** 376 * Calculate a point on the surface for the given parametric position on the surface. 377 * 378 * @param s 1st parametric dimension distance to calculate a point for (0.0 to 1.0 379 * inclusive). 380 * @param t 2nd parametric dimension distance to calculate a point for (0.0 to 1.0 381 * inclusive). 382 * @return The calculated point on the surface at the specified parameter values. 383 * @throws IllegalArgumentException if there is any problem with the parameter values. 384 */ 385 @Override 386 public Point getRealPoint(double s, double t) { 387 validateParameter(s, t); 388 Point sT = _u.getRealPoint(s, t); // Convert from 0-1 to parametric position range of subrange. 389 return _child.getRealPoint(sT); 390 } 391 392 /** 393 * Calculate all the derivatives from <code>0</code> to <code>grade</code> with 394 * respect to parametric s-position on the surface for the given parametric position 395 * on the surface, <code>d^{grade}p(s,t)/d^{grade}s</code>. 396 * <p> 397 * Example:<br> 398 * 1st derivative (grade = 1), this returns <code>[p(s,t), dp(s,t)/ds]</code>;<br> 399 * 2nd derivative (grade = 2), this returns <code>[p(s,t), dp(s,t)/ds, d^2p(s,t)/d^2s]</code>; etc. 400 * </p> 401 * 402 * @param s 1st parametric dimension distance to calculate derivative for (0.0 to 403 * 1.0 inclusive). 404 * @param t 2nd parametric dimension distance to calculate derivative for (0.0 to 405 * 1.0 inclusive). 406 * @param grade The maximum grade to calculate the uA-derivatives for (1=1st 407 * derivative, 2=2nd derivative, etc) 408 * @param scaled Pass <code>true</code> for properly scaled derivatives or 409 * <code>false</code> if the magnitude of the derivative vector is not 410 * required -- this sometimes results in faster calculation times. 411 * @return A list of s-derivatives up to the specified grade of the surface at the 412 * specified parametric position. 413 * @throws IllegalArgumentException if the grade is < 0 or the parameter values are 414 * invalid. 415 */ 416 @Override 417 public List<Vector<Length>> getSDerivatives(double s, double t, int grade, boolean scaled) { 418 419 Point sT = _u.getRealPoint(s, t); // Convert from 0-1 to parametric position range of subrange. 420 421 // Calculate the derivatives of the child object. 422 List<List<Vector<Length>>> dersLst = _child.getDerivatives(sT, grade); 423 Point origin = Point.valueOf(dersLst.get(0).get(0)); 424 425 // Have to account for derivatives in 2-dimensions. 426 // Get the direction cosines of the parametric surface S tangent vector. 427 Vector<Dimensionless> utangent = _u.getSDerivative(s, t, 1).toUnitVector(); 428 double ks = utangent.get(Vector.X).getValue(); 429 double kt = utangent.get(Vector.Y).getValue(); 430 431 // Get the surface derivatives in the s and t directions. 432 List<Vector<Length>> dP_dss = dersLst.get(0); 433 List<Vector<Length>> dP_dts = dersLst.get(1); 434 435 // Create the output array and put in the surface/curve point. 436 List<Vector<Length>> ders = dP_dss; 437 438 // Use the direction cosines to combine the surface s & t direction 439 // derivatives into the parametric surface's s direction: dP_dps = ks*dP_dss + kt*dP_dts 440 for (int i = 1; i <= grade; ++i) 441 ders.set(i, dP_dss.get(i).times(ks).plus(dP_dts.get(i).times(kt))); 442 443 if (scaled) { 444 // Scale all the derivatives by the ratio of the parametric distance in the s & t 445 // directions to the underlying surface parametric range (uS*uT/(1*1)). 446 double uA = calcParametricArcLengthProduct(s, t); 447 double f = 1; 448 for (int i = 1; i <= grade; ++i) { 449 f *= uA; 450 ders.set(i, ders.get(i).times(f)); 451 } 452 } 453 454 // Change the derivative vector origin to be subranges of this surface. 455 for (int i = 0; i <= grade; ++i) 456 ders.get(i).setOrigin(origin); 457 458 return ders; 459 } 460 461 /** 462 * Calculate all the derivatives from <code>0</code> to <code>grade</code> with 463 * respect to parametric t-position on the surface for the given parametric position 464 * on the surface, <code>d^{grade}p(s,t)/d^{grade}t</code>. 465 * <p> 466 * Example:<br> 467 * 1st derivative (grade = 1), this returns <code>[p(s,t), dp(s,t)/dt]</code>;<br> 468 * 2nd derivative (grade = 2), this returns <code>[p(s,t), dp(s,t)/dt, d^2p(s,t)/d^2t]</code>; etc. 469 * </p> 470 * 471 * @param s 1st parametric dimension distance to calculate derivative for (0.0 to 472 * 1.0 inclusive). 473 * @param t 2nd parametric dimension distance to calculate derivative for (0.0 to 474 * 1.0 inclusive). 475 * @param grade The maximum grade to calculate the v-derivatives for (1=1st 476 * derivative, 2=2nd derivative, etc) 477 * @param scaled Pass <code>true</code> for properly scaled derivatives or 478 * <code>false</code> if the magnitude of the derivative vector is not 479 * required -- this sometimes results in faster calculation times. 480 * @return A list of t-derivatives up to the specified grade of the surface at the 481 * specified parametric position. 482 * @throws IllegalArgumentException if the grade is < 0 or the parameter values are 483 * invalid. 484 */ 485 @Override 486 public List<Vector<Length>> getTDerivatives(double s, double t, int grade, boolean scaled) { 487 488 Point sT = _u.getRealPoint(s, t); // Convert from 0-1 to parametric position range of subrange. 489 490 // Calculate the derivatives of the child object. 491 List<List<Vector<Length>>> dersLst = _child.getDerivatives(sT, grade); 492 Point origin = Point.valueOf(dersLst.get(0).get(0)); 493 494 // Have to account for derivatives in 2-dimensions. 495 // Get the direction cosines of the parametric surface T tangent vector. 496 Vector<Dimensionless> utangent = _u.getTDerivative(s, t, 1).toUnitVector(); 497 double ks = utangent.get(Vector.X).getValue(); 498 double kt = utangent.get(Vector.Y).getValue(); 499 500 // Get the surface derivatives in the s and t directions. 501 List<Vector<Length>> dP_dss = dersLst.get(0); 502 List<Vector<Length>> dP_dts = dersLst.get(1); 503 504 // Create the output array and put in the surface/curve point. 505 List<Vector<Length>> ders = dP_dss; 506 507 // Use the direction cosines to combine the surface s & t direction 508 // derivatives into the parametric surface's s direction: dP_dps = ks*dP_dss + kt*dP_dts 509 for (int i = 1; i <= grade; ++i) 510 ders.set(i, dP_dss.get(i).times(ks).plus(dP_dts.get(i).times(kt))); 511 512 if (scaled) { 513 // Scale all the derivatives by the ratio of the parametric distance in the s & t 514 // directions to the underlying surface parametric range (uS*uT/(1*1)). 515 double uA = calcParametricArcLengthProduct(s, t); 516 double f = 1; 517 for (int i = 1; i <= grade; ++i) { 518 f *= uA; 519 ders.set(i, ders.get(i).times(f)); 520 } 521 } 522 523 // Change the derivative vector origin to be subranges of this surface. 524 for (int i = 0; i <= grade; ++i) 525 ders.get(i).setOrigin(origin); 526 527 return ders; 528 } 529 530 /** 531 * Method that calculates and returns the product of the local parametric arc lengths 532 * in the s & t directions: uA = uS*uT. 533 */ 534 private double calcParametricArcLengthProduct(double s, double t) { 535 if (MathTools.isApproxEqual(s, _s, TOL_ST) && MathTools.isApproxEqual(t, _t, TOL_ST)) 536 return _uA; 537 538 StackContext.enter(); 539 try { 540 SubrangeCurve scrv = _u.getSCurve(s); 541 double uS = scrv.getArcLength(GTOL).getValue(); 542 SubrangeCurve tcrv = _u.getTCurve(t); 543 double uT = tcrv.getArcLength(GTOL).getValue(); 544 _uA = uS * uT; 545 _s = s; 546 _t = t; 547 } finally { 548 StackContext.exit(); 549 } 550 551 return _uA; 552 } 553 554 /** 555 * Calculate the twist vector (d^2P/(ds*dt) = d(dP/ds)/dt) for this surface at the 556 * specified position on this surface. 557 * 558 * @param s 1st parametric dimension distance to calculate twist vector for (0.0 to 559 * 1.0 inclusive). 560 * @param t 2nd parametric dimension distance to calculate twist vector for (0.0 to 561 * 1.0 inclusive). 562 * @return The twist vector of this surface at the specified parametric position. 563 * @throws IllegalArgumentException if the parameter values are invalid. 564 */ 565 @Override 566 public Vector<Length> getTwistVector(double s, double t) { 567 validateParameter(s, t); 568 569 StackContext.enter(); 570 try { 571 // Create a point string to hold the derivatives in the s-direction. 572 PointString<Point> sDers = PointString.newInstance(); 573 574 // Space out some points to use for exracting derivatives along a constant-s line. 575 List<Double> spacing = GridSpacing.linear(NPTS); 576 577 // Insert in the input t position to the list. 578 for (int i = 1; i < NPTS; ++i) { 579 if (spacing.get(i) > t) { 580 if (spacing.get(i - 1) != t) 581 spacing.add(i, t); 582 break; 583 } 584 } 585 586 // Compute the s-direction derivatives at each of the t-positions for the input "s" position. 587 for (Double tpos : spacing) { 588 Vector<Length> der = this.getSDerivative(s, tpos, 1); 589 sDers.add(Point.valueOf(der)); 590 } 591 592 // Fit a curve to the derivative string and find the t-direction derivative at the input "t" position. 593 BasicNurbsCurve tCrv = CurveFactory.fitPoints(3, sDers); 594 Vector<Length> tvec = tCrv.getSDerivative(t, 1); 595 tvec.setOrigin(getRealPoint(s, t)); 596 597 return StackContext.outerCopy(tvec); 598 599 } finally { 600 StackContext.exit(); 601 } 602 } 603 604 /** 605 * Return a new surface that is identical to this one, but with the S-parameterization 606 * reversed. 607 * 608 * @return A new surface that is identical to this one, but with the 609 * S-parameterization reversed. 610 * @see #reverseT 611 */ 612 @Override 613 public SubrangeSurface reverseS() { 614 Surface u = _u.reverseS(); 615 616 SubrangeSurface srf = SubrangeSurface.newInstance(_child, u); 617 copyState(srf); // Copy over the super-class state for this surface to the new one. 618 619 return srf; 620 } 621 622 /** 623 * Return a new surface that is identical to this one, but with the T-parameterization 624 * reversed. 625 * 626 * @return A new surface that is identical to this one, but with the 627 * T-parameterization reversed. 628 * @see #reverseS 629 */ 630 @Override 631 public SubrangeSurface reverseT() { 632 Surface u = _u.reverseT(); 633 634 SubrangeSurface srf = SubrangeSurface.newInstance(_child, u); 635 copyState(srf); // Copy over the super-class state for this surface to the new one. 636 637 return srf; 638 } 639 640 /** 641 * Return a new surface that is identical to this one but with the transpose of the 642 * parameterization of this surface. The S and T directions will be swapped. 643 * 644 * @return A new surface that is identical to this one but with the transpose of the 645 * parameterization of this surface. 646 * @see #reverseT 647 * @see #reverseS 648 */ 649 @Override 650 public SubrangeSurface transpose() { 651 Surface u = _u.transpose(); 652 653 SubrangeSurface srf = SubrangeSurface.newInstance(_child, u); 654 copyState(srf); // Copy over the super-class state for this surface to the new one. 655 656 return srf; 657 } 658 659 /** 660 * Split this {@link SubrangeSurface} at the specified parametric S-position returning 661 * a list containing two new surfaces (a lower surface with smaller S-parametric 662 * positions than "s" and an upper surface with larger S-parametric positions). 663 * 664 * @param s The S-parametric position where this surface should be split (must not be 665 * 0 or 1!). 666 * @return A list containing two surfaces: 0 == the lower surface, 1 == the upper 667 * surface. 668 */ 669 @Override 670 public GeomList<SubrangeSurface> splitAtS(double s) { 671 validateParameter(s, 0); 672 if (parNearEnds(s, TOL_ST)) 673 throw new IllegalArgumentException(MessageFormat.format( 674 RESOURCES.getString("canNotSplitAtEnds"), "surface")); 675 676 // Split the parametric space surface. 677 GeomList<Surface> par = _u.splitAtS(s); 678 679 // Split the surface. 680 SubrangeSurface srfL = SubrangeSurface.newInstance(_child, par.get(0)); 681 SubrangeSurface srfU = SubrangeSurface.newInstance(_child, par.get(1)); 682 683 // Create the output list. 684 GeomList<SubrangeSurface> output = GeomList.valueOf(srfL, srfU); 685 return output; 686 } 687 688 /** 689 * Split this {@link SubrangeSurface} at the specified parametric T-position returning 690 * a list containing two new surfaces (a lower surface with smaller T-parametric 691 * positions than "t" and an upper surface with larger T-parametric positions). 692 * 693 * @param t The T-parametric position where this surface should be split (must not be 694 * 0 or 1!). 695 * @return A list containing two surfaces: 0 == the lower surface, 1 == the upper 696 * surface. 697 */ 698 @Override 699 public GeomList<SubrangeSurface> splitAtT(double t) { 700 validateParameter(0, t); 701 if (parNearEnds(t, TOL_ST)) 702 throw new IllegalArgumentException(MessageFormat.format( 703 RESOURCES.getString("canNotSplitAtEnds"), "surface")); 704 705 // Split the parametric space surface. 706 GeomList<Surface> par = _u.splitAtT(t); 707 708 // Split the surface. 709 SubrangeSurface srfL = SubrangeSurface.newInstance(_child, par.get(0)); 710 SubrangeSurface srfU = SubrangeSurface.newInstance(_child, par.get(1)); 711 712 // Create the output list. 713 GeomList<SubrangeSurface> output = GeomList.valueOf(srfL, srfU); 714 return output; 715 } 716 717 /** 718 * Returns transformed version of this element. The returned object implements 719 * {@link GeomTransform} and contains this element as a child. 720 * 721 * @param transform The transformation to apply to this geometry. May not be null. 722 * @return A new triangle that is identical to this one with the specified 723 * transformation applied. 724 * @throws DimensionException if this point is not 3D. 725 */ 726 @Override 727 public SubrangeSurface getTransformed(GTransform transform) { 728 requireNonNull(transform); 729 return SubrangeSurface.newInstance((Surface)_child.getTransformed(transform), _u); 730 } 731 732 /** 733 * Return the coordinate point representing the minimum bounding box corner of this 734 * geometry element (e.g.: min X, min Y, min Z). 735 * 736 * @return The minimum bounding box coordinate for this geometry element. 737 * @throws IndexOutOfBoundsException if this list contains no elements. 738 */ 739 @Override 740 public Point getBoundsMin() { 741 return _boundsMin; 742 } 743 744 /** 745 * Return the coordinate point representing the maximum bounding box corner (e.g.: max 746 * X, max Y, max Z). 747 * 748 * @return The maximum bounding box coordinate for this geometry element. 749 * @throws IndexOutOfBoundsException if this list contains no elements. 750 */ 751 @Override 752 public Point getBoundsMax() { 753 return _boundsMax; 754 } 755 756 /** 757 * Calculate the minimum & maximum bounding box corner points of this geometry 758 * element. 759 */ 760 private void calcBoundsMinMax() { 761 762 StackContext.enter(); 763 try { 764 // Space out some points on the subrange surface. 765 List<Double> spacing = GridSpacing.linear(NPTS); 766 PointArray<SubrangePoint> parr = _u.extractGrid(GridRule.PAR, spacing, spacing, spacing, spacing); 767 PointArray<Point> arr = PointArray.newInstance(); 768 for (int i = 0; i < NPTS; ++i) { 769 PointString<Point> str = PointString.newInstance(); 770 for (int j = 0; j < NPTS; ++j) { 771 Point ppnt = parr.get(i, j).copyToReal(); 772 str.add(_child.getRealPoint(ppnt)); 773 } 774 arr.add(str); 775 } 776 777 // Get the bounds of the grid of points. 778 _boundsMin = StackContext.outerCopy(arr.getBoundsMin()); 779 _boundsMax = StackContext.outerCopy(arr.getBoundsMax()); 780 781 } finally { 782 StackContext.exit(); 783 } 784 } 785 786 /** 787 * Returns the number of physical dimensions of the geometry element. This 788 * implementation always returns the physical dimension of the underlying 789 * {@link Surface} objects. 790 * 791 * @return The number of physical dimensions of the geometry element. 792 */ 793 @Override 794 public int getPhyDimension() { 795 return _child.getPhyDimension(); 796 } 797 798 /** 799 * Return the equivalent of this surface converted to the specified number of physical 800 * dimensions. If the number of dimensions is greater than this element, then zeros 801 * are added to the additional dimensions. If the number of dimensions is less than 802 * this element, then the extra dimensions are simply dropped (truncated). If the new 803 * dimensions are the same as the dimension of this element, then this element is 804 * simply returned. 805 * 806 * @param newDim The dimension of the surface to return. 807 * @return The equivalent of this surface converted to the new dimensions. 808 */ 809 @Override 810 public SubrangeSurface toDimension(int newDim) { 811 if (getPhyDimension() == newDim) 812 return this; 813 814 return SubrangeSurface.newInstance(_child.toDimension(newDim), _u); 815 } 816 817 /** 818 * Return a NURBS surface representation of this surface to within the specified 819 * tolerance. 820 * 821 * @param tol The greatest possible difference between this surface and the NURBS 822 * representation returned. May not be null. 823 * @return A NURBS surface that represents this surface to within the specified 824 * tolerance. 825 */ 826 @Override 827 public NurbsSurface toNurbs(Parameter<Length> tol) { 828 requireNonNull(tol); 829 StackContext.enter(); 830 try { 831 PointArray<SubrangePoint> arr = this.gridToTolerance(tol); 832 833 // Fit a cubic NURBS surface to the points. 834 int sdeg = 3, tdeg = 3; 835 if (arr.size() <= tdeg) 836 tdeg = arr.size() - 1; 837 if (arr.get(0).size() <= sdeg) 838 sdeg = arr.get(0).size() - 1; 839 BasicNurbsSurface surface = SurfaceFactory.fitPoints(sdeg, tdeg, arr); 840 copyState(surface); // Copy over the super-class state for this surface to the new one. 841 842 return StackContext.outerCopy(surface); 843 844 } finally { 845 StackContext.exit(); 846 } 847 } 848 849 /** 850 * Returns the unit in which this surface is stated. 851 * 852 * @return The unit in which this surface is stated. 853 */ 854 @Override 855 public Unit<Length> getUnit() { 856 return _child.getUnit(); 857 } 858 859 /** 860 * Returns the equivalent to this surface but stated in the specified unit. 861 * 862 * @param unit the length unit of the surface to be returned. 863 * @return an equivalent to this surface but stated in the specified unit. 864 * @throws ConversionException if the the input unit is not a length unit. 865 */ 866 @Override 867 public SubrangeSurface to(Unit<Length> unit) throws ConversionException { 868 if (unit.equals(getUnit())) 869 return this; 870 871 // Convert the curves. 872 SubrangeSurface srf = SubrangeSurface.newInstance(_child.to(unit), _u); 873 874 return srf; 875 } 876 877 /** 878 * Return <code>true</code> if this SubrangeSurface contains valid and finite 879 * numerical components. A value of <code>false</code> will be returned if any of the 880 * member curves are not valid. 881 * 882 * @return true if this SubrangeSurface contains valid and finite numerical 883 * components. 884 */ 885 @Override 886 public boolean isValid() { 887 return _child.isValid() && _u.isValid(); 888 } 889 890 /** 891 * Return <code>true</code> if this surface is degenerate (i.e.: has area less than 892 * the specified tolerance squared). 893 * 894 * @param tol The tolerance for determining if this surface is degenerate. May not be 895 * null. 896 * @return true if this surface is degenerate. 897 */ 898 @Override 899 public boolean isDegenerate(Parameter<Length> tol) { 900 requireNonNull(tol); 901 if (_isDegenerate != null) 902 return _isDegenerate; 903 904 _isDegenerate = _u.isDegenerate(tol); 905 if (_isDegenerate) 906 return true; 907 _isDegenerate = _child.isDegenerate(tol); 908 return _isDegenerate; 909 } 910 911 /** 912 * Compares the specified object with this <code>SubrangeSurface</code> for equality. 913 * Returns true if and only if both surfaces are of the same type, have the same child 914 * surface, and both contain the same boundary curves in the same order. 915 * 916 * @param obj the object to compare with. 917 * @return <code>true</code> if this surface is identical to that surface; 918 * <code>false</code> otherwise. 919 */ 920 @Override 921 public boolean equals(Object obj) { 922 if (this == obj) 923 return true; 924 if ((obj == null) || (obj.getClass() != this.getClass())) 925 return false; 926 927 SubrangeSurface that = (SubrangeSurface)obj; 928 return this._child.equals(that._child) 929 && this._u.equals(that._u) 930 && super.equals(obj); 931 } 932 933 /** 934 * Returns the hash code for this <code>SubrangeSurface</code>. 935 * 936 * @return the hash code value. 937 */ 938 @Override 939 public int hashCode() { 940 return 31*super.hashCode() + Objects.hash(_u, _child); 941 } 942 943 /** 944 * Return a copy of this object with any transformations or subranges removed 945 * (applied). This method is not yet implemented and current returns a new 946 * SubrangeSurface with the child surface and subrange curves copied to real. 947 * 948 * @return A copy of this object with any transformations or subranges removed. 949 */ 950 @Override 951 public SubrangeSurface copyToReal() { 952 // TODO: Add support for this. 953 //throw new UnsupportedOperationException( RESOURCES.getString("scCopy2RealNotSupported") ); 954 return SubrangeSurface.newInstance((Surface)_child.copyToReal(), (Surface)_u.copyToReal()); 955 } 956 957 /** 958 * Returns a copy of this <code>SubrangeSurface</code> instance 959 * {@link javolution.context.AllocatorContext allocated} by the calling thread 960 * (possibly on the stack). 961 * 962 * @return an identical and independent copy of this object. 963 */ 964 @Override 965 public SubrangeSurface copy() { 966 return copyOf(this); 967 } 968 969 /** 970 * Returns the text representation of this geometry element. 971 * 972 * @return The text representation of this geometry element. 973 */ 974 @Override 975 public Text toText() { 976 TextBuilder tmp = TextBuilder.newInstance(); 977 String className = this.getClass().getName(); 978 tmp.append(className.substring(className.lastIndexOf(".") + 1)); 979 980 tmp.append(": {child = {\n"); 981 tmp.append(_child.toText()); 982 tmp.append("}\n"); 983 tmp.append(", u = {\n"); 984 tmp.append(_u.toText()); 985 tmp.append("}\n"); 986 987 tmp.append("}"); 988 Text txt = tmp.toText(); 989 TextBuilder.recycle(tmp); 990 return txt; 991 } 992 993 /** 994 * Holds the default XML representation for this object. 995 */ 996 @SuppressWarnings("FieldNameHidesFieldInSuperclass") 997 protected static final XMLFormat<SubrangeSurface> XML = new XMLFormat<SubrangeSurface>(SubrangeSurface.class) { 998 999 @Override 1000 public SubrangeSurface newInstance(Class<SubrangeSurface> cls, XMLFormat.InputElement xml) throws XMLStreamException { 1001 return FACTORY.object(); 1002 } 1003 1004 @Override 1005 public void read(XMLFormat.InputElement xml, SubrangeSurface obj) throws XMLStreamException { 1006 AbstractSurface.XML.read(xml, obj); // Call parent read. 1007 1008 Surface par = xml.get("ParPos"); 1009 obj._u = par; 1010 Surface child = xml.get("Child"); 1011 obj._child = child; 1012 if (!(par instanceof Immutable)) 1013 par.addChangeListener(obj._childChangeListener); 1014 if (!(child instanceof Immutable)) 1015 child.addChangeListener(obj._childChangeListener); 1016 obj.calcBoundsMinMax(); 1017 1018 } 1019 1020 @Override 1021 public void write(SubrangeSurface obj, XMLFormat.OutputElement xml) throws XMLStreamException { 1022 AbstractSurface.XML.write(obj, xml); // Call parent write. 1023 1024 xml.add(obj._u, "ParPos"); 1025 xml.add(obj._child, "Child"); 1026 1027 } 1028 }; 1029 1030 ////////////////////// 1031 // Factory Creation // 1032 ////////////////////// 1033 /** 1034 * Do not allow the default constructor to be used except by subclasses. 1035 */ 1036 protected SubrangeSurface() { } 1037 1038 private static final ObjectFactory<SubrangeSurface> FACTORY = new ObjectFactory<SubrangeSurface>() { 1039 @Override 1040 protected SubrangeSurface create() { 1041 return new SubrangeSurface(); 1042 } 1043 1044 @Override 1045 protected void cleanup(SubrangeSurface obj) { 1046 obj.reset(); 1047 if (!(obj._u instanceof Immutable)) 1048 obj._u.removeChangeListener(obj._childChangeListener); 1049 obj._u = null; 1050 if (!(obj._child instanceof Immutable)) 1051 obj._child.removeChangeListener(obj._childChangeListener); 1052 obj._child = null; 1053 obj._boundsMin = null; 1054 obj._boundsMax = null; 1055 obj._isDegenerate = null; 1056 obj._s = -1; 1057 obj._t = -1; 1058 obj._uA = 1; 1059 } 1060 }; 1061 1062 /** 1063 * Recycles a SubrangeSurface instance immediately (on the stack when executing in a 1064 * StackContext). 1065 * 1066 * @param instance The instance to be recycled. 1067 */ 1068 public static void recycle(SubrangeSurface instance) { 1069 FACTORY.recycle(instance); 1070 } 1071 1072 @SuppressWarnings("unchecked") 1073 private static SubrangeSurface copyOf(SubrangeSurface original) { 1074 SubrangeSurface obj = FACTORY.object(); 1075 obj._child = original._child.copy(); 1076 obj._u = original._u.copy(); 1077 obj._boundsMax = original._boundsMax.copy(); 1078 obj._boundsMin = original._boundsMin.copy(); 1079 if (!(obj._u instanceof Immutable)) 1080 obj._u.addChangeListener(obj._childChangeListener); 1081 if (!(obj._child instanceof Immutable)) 1082 obj._child.addChangeListener(obj._childChangeListener); 1083 original.copyState(obj); 1084 return obj; 1085 } 1086 1087}