001/** 002 * SubrangeCurve -- A Curve that is a subrange on a parametric object such as a curve or 003 * surface. 004 * 005 * Copyright (C) 2009-2015, Joseph A. Huwaldt. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or modify it under the terms 008 * of the GNU Lesser General Public License as published by the Free Software Foundation; 009 * either version 2.1 of the License, or (at your option) any later version. 010 * 011 * This library is distributed in the hope that it will be useful, but WITHOUT ANY 012 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 013 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License along with 016 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - 017 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html 018 */ 019package geomss.geom; 020 021import geomss.geom.nurbs.BasicNurbsCurve; 022import geomss.geom.nurbs.CurveFactory; 023import geomss.geom.nurbs.NurbsCurve; 024import jahuwaldt.js.param.*; 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.Unit; 033import javax.swing.event.ChangeEvent; 034import javax.swing.event.ChangeListener; 035import javolution.context.ObjectFactory; 036import javolution.context.StackContext; 037import javolution.lang.Immutable; 038import javolution.text.Text; 039import javolution.text.TextBuilder; 040import javolution.xml.XMLFormat; 041import javolution.xml.stream.XMLStreamException; 042 043/** 044 * A {@link Curve} element that refers to a {@link ParametricGeometry} object such as a 045 * {@link Curve} or {@link Surface} and a parametric curve on that parametric object. 046 * 047 * <p> Modified by: Joseph A. Huwaldt </p> 048 * 049 * @author Joseph A. Huwaldt, Date: May 28, 2009 050 * @version November 27, 2015 051 */ 052@SuppressWarnings({"serial", "CloneableImplementsClone"}) 053public final class SubrangeCurve extends AbstractCurve<SubrangeCurve> implements Subrange<Curve> { 054 055 /** 056 * The parametric distance along the parametric geometry where this curve is located. 057 */ 058 private Curve _u; 059 060 /** 061 * The parametric geometry object that this curve is located on. 062 */ 063 private ParametricGeometry _child; 064 065 /** 066 * Reference to a change listener for this object's child geometry. 067 */ 068 private ChangeListener _childChangeListener = new MyChangeListener(this); 069 070 /** 071 * The minimum bounding point for this subrange curve. 072 */ 073 private Point _boundsMin; 074 075 /** 076 * The maximum bounding point for this subrange curve. 077 */ 078 private Point _boundsMax; 079 080 /** 081 * The arc-length for the parametric curve. 082 */ 083 private double uL; 084 085 /** 086 * Returns a {@link SubrangeCurve} instance referring to the specified 087 * {@link ParametricGeometry} and parametric location. 088 * 089 * @param child The {@link ParametricGeometry} object that this curve is subranged 090 * onto (may not be <code>null</code>). 091 * @param par A curve of the parametric position (between 0 and 1) along each 092 * parametric dimension where the curve is located (may not be 093 * <code>null</code>). 094 * @return the subrange curve having the specified values. 095 * @throws DimensionException if the input child object's parametric dimension is not 096 * compatible with the input parametric distance point. 097 */ 098 public static SubrangeCurve newInstance(ParametricGeometry child, Curve par) { 099 requireNonNull(child, MessageFormat.format(RESOURCES.getString("paramNullErr"), "child")); 100 requireNonNull(par, MessageFormat.format(RESOURCES.getString("paramNullErr"), "par")); 101 int pDim = child.getParDimension(); 102 if (par.getPhyDimension() != pDim) 103 throw new DimensionException(RESOURCES.getString("scIncParDim")); 104 105 SubrangeCurve obj = FACTORY.object(); 106 obj._u = par; 107 obj._child = child; 108 if (!(par instanceof Immutable)) 109 par.addChangeListener(obj._childChangeListener); 110 if (!(child instanceof Immutable)) 111 child.addChangeListener(obj._childChangeListener); 112 obj.calcBoundsMinMax(); 113 114 return obj; 115 } 116 117 // A change listener that re-calculates the subrange bounds as well as 118 // passing the child's change event on to any listeners. 119 private static class MyChangeListener extends ForwardingChangeListener { 120 121 private final SubrangeCurve target; 122 123 public MyChangeListener(SubrangeCurve geom) { 124 super(geom); 125 this.target = geom; 126 } 127 128 @Override 129 public void stateChanged(ChangeEvent e) { 130 // Re-calculate the bounds of this subrange curve. 131 target.calcBoundsMinMax(); 132 super.stateChanged(e); 133 } 134 } 135 136 /** 137 * Returns the parametric position on the child object that this curve refers to. 138 * 139 * @return The parametric position on the child object that this curve refers to. 140 */ 141 @Override 142 public Curve getParPosition() { 143 return _u; 144 } 145 146 /** 147 * Sets the parametric position on the child object that this curve refers to. 148 * 149 * @param par The parametric position (between 0 and 1) along each parametric 150 * dimension where the curve is located (may not be <code>null</code>). 151 */ 152 @Override 153 public void setParPosition(Curve par) { 154 requireNonNull(par, MessageFormat.format(RESOURCES.getString("paramNullErr"), "par")); 155 int pDim = _child.getParDimension(); 156 if (par.getPhyDimension() != pDim) 157 throw new DimensionException(RESOURCES.getString("scIncParDim")); 158 159 if (!(_u instanceof Immutable)) 160 _u.removeChangeListener(_childChangeListener); 161 162 _u = par; 163 164 if (!(par instanceof Immutable)) 165 par.addChangeListener(_childChangeListener); 166 167 calcBoundsMinMax(); 168 fireChangeEvent(); 169 } 170 171 /** 172 * Returns the child object this point is subranged onto. 173 */ 174 @Override 175 public ParametricGeometry getChild() { 176 return _child; 177 } 178 179 /** 180 * Recycles a <code>SubrangeCurve</code> instance immediately (on the stack when 181 * executing in a <code>StackContext</code>). 182 * 183 * @param instance The instance to be recycled. 184 */ 185 public static void recycle(SubrangeCurve instance) { 186 FACTORY.recycle(instance); 187 } 188 189 /** 190 * Returns the number of child-elements that make up this geometry element. This 191 * implementation returns the size of the child object. 192 * 193 * @return The number of child-elements that make up this geometry element. 194 */ 195 @Override 196 public int size() { 197 return _child.size(); 198 } 199 200 /** 201 * Returns the number of physical dimensions of the geometry element. 202 * 203 * @return The number of physical dimensions of this element. 204 */ 205 @Override 206 public int getPhyDimension() { 207 return _child.getPhyDimension(); 208 } 209 210 /** 211 * Calculate a point on the curve for the given parametric distance along the curve, 212 * <code>p(s)</code>. 213 * 214 * @param s parametric distance to calculate a point for (0.0 to 1.0 inclusive). 215 * @return the calculated point 216 */ 217 @Override 218 public Point getRealPoint(double s) { 219 Point sT = _u.getRealPoint(s); // Convert from 0-1 to parametric position range of _u curve. 220 Point p = _child.getRealPoint(sT); 221 return p; 222 } 223 224 /** 225 * Calculate all the derivatives from <code>0</code> to <code>grade</code> with 226 * respect to parametric distance on the curve for the given parametric distance along 227 * the curve, <code>d^{grade}p(s)/d^{grade}s</code>. 228 * <p> 229 * Example:<br> 230 * 1st derivative (grade = 1), this returns <code>[p(s), dp(s)/ds]</code>;<br> 231 * 2nd derivative (grade = 2), this returns <code>[p(s), dp(s)/ds, d^2p(s)/d^2s]</code>; etc. 232 * </p> 233 * 234 * @param s Parametric distance to calculate derivatives for (0.0 to 1.0 235 * inclusive). 236 * @param grade The maximum grade to calculate the derivatives for (1=1st derivative, 237 * 2=2nd derivative, etc) 238 * @return A list of derivatives up to the specified grade of the curve at the 239 * specified parametric position. 240 * @throws IllegalArgumentException if the grade is < 0. 241 */ 242 @Override 243 public List<Vector<Length>> getSDerivatives(double s, int grade) { 244 Point sT = _u.getRealPoint(s); // Convert from 0-1 to parametric position range of _u curve. 245 246 // Calculate the derivatives of the child object. 247 List<List<Vector<Length>>> dersLst = _child.getDerivatives(sT, grade); 248 Point origin = Point.valueOf(dersLst.get(0).get(0)); 249 250 // Sort out what to output. 251 List<Vector<Length>> ders = null; 252 if (_child.getParDimension() == 1) 253 ders = dersLst.get(0); 254 255 else if (_child.getParDimension() == 2) { 256 // Have to account for derivatives in 2-dimensions. 257 258 // Get the direction cosines of the parametric curve tangent vector. 259 Vector<Dimensionless> utangent = _u.getTangent(s); 260 double ks = utangent.get(Vector.X).getValue(); 261 double kt = utangent.get(Vector.Y).getValue(); 262 263 // Get the surface derivatives in the s and t directions. 264 List<Vector<Length>> dP_dss = dersLst.get(0); 265 List<Vector<Length>> dP_dts = dersLst.get(1); 266 267 // Create the output array and put in the surface/curve point. 268 ders = dP_dss; 269 270 // Use the direction cosines to combine the surface s & t direction 271 // derivatives into the curve's s direction: dP_dsc = ks*dP_dss + kt*dP_dts 272 for (int i = 1; i <= grade; ++i) 273 ders.set(i, dP_dss.get(i).times(ks).plus(dP_dts.get(i).times(kt))); 274 275 } else 276 throw new UnsupportedOperationException( 277 MessageFormat.format(RESOURCES.getString("scUnsupportedParDim"), 278 _child.getParDimension())); 279 280 // Scale all the derivatives by the ratio of parameter arc length to the 281 // normal parameter range (uL/1). 282 double f = 1; 283 for (int i = 1; i <= grade; ++i) { 284 f *= uL; 285 ders.set(i, ders.get(i).times(f)); 286 } 287 288 // Set the derivative vector origin points. 289 for (int i = 0; i <= grade; ++i) 290 ders.get(i).setOrigin(origin); 291 292 return ders; 293 } 294 295 /** 296 * Return the coordinate point representing the minimum bounding box corner of this 297 * geometry element (e.g.: min X, min Y, min Z). 298 * 299 * @return The minimum bounding box coordinate for this geometry element. 300 * @throws IndexOutOfBoundsException if this list contains no elements. 301 */ 302 @Override 303 public Point getBoundsMin() { 304 return _boundsMin; 305 } 306 307 /** 308 * Return the coordinate point representing the maximum bounding box corner (e.g.: max 309 * X, max Y, max Z). 310 * 311 * @return The maximum bounding box coordinate for this geometry element. 312 * @throws IndexOutOfBoundsException if this list contains no elements. 313 */ 314 @Override 315 public Point getBoundsMax() { 316 return _boundsMax; 317 } 318 319 private static final int NPTS = 21; 320 321 /** 322 * Calculate the minimum & maximum bounding box corner points of this geometry 323 * element. 324 */ 325 private void calcBoundsMinMax() { 326 StackContext.enter(); 327 try { 328 // Space out some points on the subrange curve. 329 List<Double> spacing = GridSpacing.linear(NPTS); 330 PointString<SubrangePoint> pstr = _u.extractGrid(GridRule.PAR, spacing); 331 PointString<Point> str = PointString.newInstance(); 332 for (int i = 0; i < NPTS; ++i) { 333 Point ppnt = pstr.get(i).copyToReal(); 334 str.add(_child.getRealPoint(ppnt)); 335 } 336 337 // Get the bounds of the grid of points. 338 _boundsMin = StackContext.outerCopy(str.getBoundsMin()); 339 _boundsMax = StackContext.outerCopy(str.getBoundsMax()); 340 341 // Compute the arc length of the parametric curve and store it for later use. 342 uL = _u.getArcLength(GTOL * 100).getValue(); 343 344 } finally { 345 StackContext.exit(); 346 } 347 348 } 349 350 /** 351 * Return <code>true</code> if this SubrangeCurve contains valid and finite numerical 352 * components. A value of <code>false</code> will be returned if any of the coordinate 353 * values are NaN or Inf. 354 * 355 * @return true if this element contains valid and finite numerical values. 356 */ 357 @Override 358 public boolean isValid() { 359 return _u.isValid() && _child.isValid(); 360 } 361 362 /** 363 * Return <code>true</code> if this curve is degenerate (i.e.: has length less than 364 * the specified tolerance). 365 * 366 * @param tol The tolerance for determining if this curve is degenerate. May not be 367 * null. 368 * @return true if this curve is degenerate. 369 */ 370 @Override 371 public boolean isDegenerate(Parameter<Length> tol) { 372 if (_u.isDegenerate(requireNonNull(tol))) 373 return true; 374 return _child.isDegenerate(tol); 375 } 376 377 /** 378 * Returns the unit in which this curves values are stated. 379 * 380 * @return The unit that this object is stated in. 381 */ 382 @Override 383 public Unit<Length> getUnit() { 384 return _child.getUnit(); 385 } 386 387 /** 388 * Returns the equivalent to this curve but stated in the specified unit. 389 * 390 * @param unit the length unit of the curve to be returned. May not be null. 391 * @return an equivalent to this curve but stated in the specified unit. 392 * @throws ConversionException if the the input unit is not a length unit. 393 */ 394 @Override 395 public SubrangeCurve to(Unit<Length> unit) throws ConversionException { 396 if (unit.equals(getUnit())) 397 return this; 398 399 SubrangeCurve crv = SubrangeCurve.newInstance(_child.to(unit), _u); 400 return copyState(crv); 401 } 402 403 /** 404 * Return the equivalent of this curve converted to the specified number of physical 405 * dimensions. If the number of dimensions is greater than this element, then zeros 406 * are added to the additional dimensions. If the number of dimensions is less than 407 * this element, then the extra dimensions are simply dropped (truncated). If the new 408 * dimensions are the same as the dimension of this element, then this element is 409 * simply returned. 410 * 411 * @param newDim The dimension of the curve to return. 412 * @return The equivalent of this curve converted to the new dimensions. 413 */ 414 @Override 415 public SubrangeCurve toDimension(int newDim) { 416 if (getPhyDimension() == newDim) 417 return this; 418 419 SubrangeCurve crv = SubrangeCurve.newInstance(_child.toDimension(newDim), _u); 420 return copyState(crv); 421 } 422 423 /** 424 * Return a NURBS curve representation of this curve to within the specified 425 * tolerance. 426 * 427 * @param tol The greatest possible difference between this curve and the NURBS 428 * representation returned. May not be null. 429 * @return A NURBS curve that represents this curve to within the specified tolerance. 430 */ 431 @Override 432 public NurbsCurve toNurbs(Parameter<Length> tol) { 433 requireNonNull(tol); 434 StackContext.enter(); 435 try { 436 437 // Grid some points onto the curve. 438 PointString<SubrangePoint> str = this.gridToTolerance(tol); 439 440 // Fit a cubic NURBS curve to the points. 441 int deg = 3; 442 if (str.size() <= 3) 443 deg = str.size() - 1; 444 BasicNurbsCurve curve = CurveFactory.fitPoints(deg, str); 445 copyState(curve); // Copy over the super-class state for this curve to the new one. 446 447 return StackContext.outerCopy(curve); 448 449 } finally { 450 StackContext.exit(); 451 } 452 } 453 454 /** 455 * Return a new curve that is identical to this one, but with the parameterization 456 * reversed. 457 * 458 * @return A new curve identical to this one, but with the parameterization reversed. 459 */ 460 @Override 461 public SubrangeCurve reverse() { 462 SubrangeCurve crv = SubrangeCurve.newInstance(_child, _u.reverse()); 463 return copyState(crv); 464 } 465 466 /** 467 * Split this {@link SubrangeCurve} at the specified parametric position returning a 468 * list containing two curves (a lower curve with smaller parametric positions than 469 * "s" and an upper curve with larger parametric positions). 470 * 471 * @param s The parametric position where this curve should be split (must not be 0 or 472 * 1!). 473 * @return A list containing two curves: 0 == the lower curve, 1 == the upper curve. 474 */ 475 @Override 476 public GeomList<SubrangeCurve> splitAt(double s) { 477 478 // Split the parametric position curve. 479 GeomList<Curve> pCrvs = _u.splitAt(s); 480 481 // Create the output list. 482 GeomList<SubrangeCurve> output = GeomList.newInstance(); 483 484 // Create the lower curve. 485 output.add(SubrangeCurve.newInstance(_child, pCrvs.get(0))); 486 output.add(SubrangeCurve.newInstance(_child, pCrvs.get(1))); 487 488 GeomList.recycle(pCrvs); 489 490 return output; 491 } 492 493 /** 494 * Returns a copy of this {@link SubrangeCurve} instance 495 * {@link javolution.context.AllocatorContext allocated} by the calling thread 496 * (possibly on the stack). 497 * 498 * @return an identical and independent copy of this curve. 499 */ 500 @Override 501 public SubrangeCurve copy() { 502 return SubrangeCurve.copyOf(this); 503 } 504 505 /** 506 * Return a copy of this object with any transformations or subranges removed 507 * (applied). This method is not yet implemented and current returns a new 508 * SubrangeCurve with the child and subrange curve geometry copied to real. 509 * 510 * @return A copy of this object with any transformations or subranges removed 511 * (applied). 512 */ 513 @Override 514 public SubrangeCurve copyToReal() { 515 // TODO: Add support for this. 516 //throw new UnsupportedOperationException( RESOURCES.getString("scCopy2RealNotSupported") ); 517 return SubrangeCurve.newInstance((ParametricGeometry)_child.copyToReal(), _u.copyToReal()); 518 } 519 520 /** 521 * Returns transformed version of this element. The returned object implements is a 522 * new SubrangeCurve instance with the transformed version of this object's child as 523 * it's child. 524 * 525 * @param transform The transformation to apply to this geometry. May not be null. 526 * @return A new triangle that is identical to this one with the specified 527 * transformation applied. 528 * @throws DimensionException if this point is not 3D. 529 */ 530 @Override 531 public SubrangeCurve getTransformed(GTransform transform) { 532 requireNonNull(transform); 533 return SubrangeCurve.newInstance((ParametricGeometry)_child.getTransformed(transform), _u); 534 } 535 536 /** 537 * Compares this SubrangeCurve against the specified object for strict equality (same 538 * values and same units). 539 * 540 * @param obj the object to compare with. 541 * @return <code>true</code> if this point is identical to that point; 542 * <code>false</code> otherwise. 543 */ 544 @Override 545 public boolean equals(Object obj) { 546 if (this == obj) 547 return true; 548 if ((obj == null) || (obj.getClass() != this.getClass())) 549 return false; 550 551 SubrangeCurve that = (SubrangeCurve)obj; 552 return this._u.equals(that._u) 553 && this._child.equals(that._child) 554 && super.equals(obj); 555 } 556 557 /** 558 * Returns the hash code for this parameter. 559 * 560 * @return the hash code value. 561 */ 562 @Override 563 public int hashCode() { 564 return 31*super.hashCode() + Objects.hash(_u, _child); 565 } 566 567 /** 568 * Returns the text representation of this geometry element. 569 * 570 * @return A text representation of this geometry element. 571 */ 572 @Override 573 public Text toText() { 574 TextBuilder tmp = TextBuilder.newInstance(); 575 String className = this.getClass().getName(); 576 tmp.append(className.substring(className.lastIndexOf(".") + 1)); 577 578 tmp.append(": {child = {\n"); 579 tmp.append(_child.toText()); 580 tmp.append("}\n"); 581 tmp.append(", u = {\n"); 582 tmp.append(_u.toText()); 583 tmp.append("}\n"); 584 585 tmp.append("}"); 586 Text txt = tmp.toText(); 587 TextBuilder.recycle(tmp); 588 return txt; 589 } 590 591 /** 592 * Holds the default XML representation for this object. 593 */ 594 @SuppressWarnings("FieldNameHidesFieldInSuperclass") 595 protected static final XMLFormat<SubrangeCurve> XML = new XMLFormat<SubrangeCurve>(SubrangeCurve.class) { 596 597 @Override 598 public SubrangeCurve newInstance(Class<SubrangeCurve> cls, InputElement xml) throws XMLStreamException { 599 return FACTORY.object(); 600 } 601 602 @Override 603 public void read(InputElement xml, SubrangeCurve obj) throws XMLStreamException { 604 AbstractCurve.XML.read(xml, obj); // Call parent read. 605 606 Curve par = xml.get("ParPos"); 607 obj._u = par; 608 ParametricGeometry child = xml.get("Child"); 609 obj._child = child; 610 if (!(par instanceof Immutable)) 611 par.addChangeListener(obj._childChangeListener); 612 if (!(child instanceof Immutable)) 613 child.addChangeListener(obj._childChangeListener); 614 obj.calcBoundsMinMax(); 615 616 } 617 618 @Override 619 public void write(SubrangeCurve obj, OutputElement xml) throws XMLStreamException { 620 AbstractCurve.XML.write(obj, xml); // Call parent write. 621 622 xml.add(obj._u, "ParPos"); 623 xml.add(obj._child, "Child"); 624 625 } 626 }; 627 628 /////////////////////// 629 // Factory creation. // 630 /////////////////////// 631 private SubrangeCurve() { } 632 633 @SuppressWarnings("unchecked") 634 private static final ObjectFactory<SubrangeCurve> FACTORY = new ObjectFactory<SubrangeCurve>() { 635 @Override 636 protected SubrangeCurve create() { 637 return new SubrangeCurve(); 638 } 639 640 @Override 641 protected void cleanup(SubrangeCurve obj) { 642 obj.reset(); 643 if (!(obj._u instanceof Immutable)) 644 obj._u.removeChangeListener(obj._childChangeListener); 645 obj._u = null; 646 if (!(obj._child instanceof Immutable)) 647 obj._child.removeChangeListener(obj._childChangeListener); 648 obj._child = null; 649 obj._boundsMax = null; 650 obj._boundsMin = null; 651 } 652 }; 653 654 @SuppressWarnings("unchecked") 655 private static SubrangeCurve copyOf(SubrangeCurve original) { 656 SubrangeCurve obj = FACTORY.object(); 657 obj._u = original._u.copy(); 658 obj._child = original._child.copy(); 659 obj._boundsMax = original._boundsMax.copy(); 660 obj._boundsMin = original._boundsMin.copy(); 661 if (!(obj._u instanceof Immutable)) 662 obj._u.addChangeListener(obj._childChangeListener); 663 if (!(obj._child instanceof Immutable)) 664 obj._child.addChangeListener(obj._childChangeListener); 665 original.copyState(obj); 666 return obj; 667 } 668 669}