001/** 002 * SubrangePoint -- A GeomPoint that is a subrange on a parametric object such as a curve 003 * or surface. 004 * 005 * Copyright (C) 2009-2017, 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 jahuwaldt.js.param.Parameter; 022import jahuwaldt.js.param.Vector3D; 023import java.text.MessageFormat; 024import java.util.Objects; 025import static java.util.Objects.requireNonNull; 026import javax.measure.converter.ConversionException; 027import javax.measure.quantity.Length; 028import javax.measure.unit.Unit; 029import javax.swing.event.ChangeListener; 030import javolution.context.ObjectFactory; 031import javolution.context.StackContext; 032import javolution.lang.Immutable; 033import javolution.xml.XMLFormat; 034import javolution.xml.stream.XMLStreamException; 035import org.jscience.mathematics.vector.Float64Vector; 036 037/** 038 * A {@link GeomPoint} element that refers to a {@link ParametricGeometry} object such as 039 * a {@link Curve} or {@link Surface} and a parametric position on that parametric object. 040 * 041 * <p> Modified by: Joseph A. Huwaldt </p> 042 * 043 * @author Joseph A. Huwaldt, Date: May 28, 2009 044 * @version October 14, 2017 045 */ 046@SuppressWarnings({"serial", "CloneableImplementsClone"}) 047public final class SubrangePoint extends GeomPoint implements Subrange<GeomPoint> { 048 049 /** 050 * Tolerance allowed on parametric spacing being == 0 or == 1. 051 */ 052 protected static final double TOL_ST = Parameter.EPS; 053 054 /** 055 * The parametric distance along the parametric geometry where this point is located. 056 */ 057 private GeomPoint _u; 058 059 /** 060 * The parametric geometry object that this point is located on. 061 */ 062 private ParametricGeometry _child; 063 064 /** 065 * Reference to a change listener for this object's child geometry. 066 */ 067 private ChangeListener _childChangeListener = new ForwardingChangeListener(this); 068 069 /** 070 * Returns a {@link SubrangePoint} instance referring to the specified {@link Curve} 071 * and parametric location. 072 * 073 * @param child The {@link Curve} object that this point is subranged onto (may not be 074 * <code>null</code>). 075 * @param s The parametric s-position (between 0 and 1) along curve where the 076 * point is located (may not be <code>null</code>). 077 * @return the subrange point having the specified values. 078 */ 079 public static SubrangePoint newInstance(Curve child, double s) { 080 if (s < 0 || s > 1) 081 throw new DimensionException( 082 MessageFormat.format(RESOURCES.getString("crvInvalidSValue"), s)); 083 return newInstance(child, Point.valueOf(s)); 084 } 085 086 /** 087 * Returns a {@link SubrangePoint} instance referring to the specified {@link Surface} 088 * and parametric location. 089 * 090 * @param child The {@link Surface} object that this point is subranged onto (may not 091 * be <code>null</code>). 092 * @param s The parametric s-position (between 0 and 1) along surface where the 093 * point is located. 094 * @param t The parametric t-position (between 0 and 1) along surface where the 095 * point is located. 096 * @return the subrange point having the specified values. 097 */ 098 public static SubrangePoint newInstance(Surface child, double s, double t) { 099 if (s < -TOL_ST || s > 1 + TOL_ST) 100 throw new IllegalArgumentException( 101 MessageFormat.format(RESOURCES.getString("srfInvalidSValue"), s)); 102 if (t < -TOL_ST || t > 1 + TOL_ST) 103 throw new IllegalArgumentException( 104 MessageFormat.format(RESOURCES.getString("srfInvalidTValue"), t)); 105 return newInstance(child, Point.valueOf(s, t)); 106 } 107 108 /** 109 * Returns a {@link SubrangePoint} instance referring to the specified 110 * {@link ParametricGeometry} and parametric location. 111 * 112 * @param child The {@link ParametricGeometry} object that this point is subranged 113 * onto (may not be <code>null</code>). 114 * @param par The parametric position (between 0 and 1) along each parametric 115 * dimension where the point is located (may not be <code>null</code>). 116 * @return the subrange point having the specified values. 117 * @throws DimensionException if the input child object's parametric dimension is not 118 * compatible with the input parametric distance point. 119 */ 120 public static SubrangePoint newInstance(ParametricGeometry child, GeomPoint par) { 121 requireNonNull(child, MessageFormat.format(RESOURCES.getString("paramNullErr"), "child")); 122 requireNonNull(par, MessageFormat.format(RESOURCES.getString("paramNullErr"), "par")); 123 if (par.getPhyDimension() != child.getParDimension()) 124 throw new DimensionException(RESOURCES.getString("scIncParDim")); 125 126 SubrangePoint obj = FACTORY.object(); 127 obj._u = par; 128 obj._child = child; 129 130 if (!(par instanceof Immutable)) 131 par.addChangeListener(obj._childChangeListener); 132 if (!(child instanceof Immutable)) 133 child.addChangeListener(obj._childChangeListener); 134 135 return obj; 136 } 137 138 /** 139 * Returns the parametric position on the child object that this point refers to. 140 */ 141 @Override 142 public GeomPoint getParPosition() { 143 return _u; 144 } 145 146 /** 147 * Sets the parametric position on the child object that this point refers to. 148 * 149 * @param par The parametric position (between 0 and 1) along each parametric 150 * dimension where the point is located (may not be <code>null</code>). 151 */ 152 @Override 153 public void setParPosition(GeomPoint par) { 154 requireNonNull(par, MessageFormat.format(RESOURCES.getString("paramNullErr"),"par")); 155 if (par.getPhyDimension() != _child.getParDimension()) 156 throw new DimensionException(RESOURCES.getString("scIncParDim")); 157 158 if (!(_u instanceof Immutable)) 159 _u.removeChangeListener(_childChangeListener); 160 161 _u = par; 162 163 if (!(par instanceof Immutable)) 164 par.addChangeListener(_childChangeListener); 165 166 fireChangeEvent(); 167 } 168 169 /** 170 * Returns the child object this point is subranged onto. 171 */ 172 @Override 173 public ParametricGeometry getChild() { 174 return _child; 175 } 176 177 /** 178 * Return a copy of the child object transformed into a concrete {@link GeomPoint} 179 * object (information on the parametric position on the child parametric geometry is 180 * removed). 181 * 182 * @return A copy of this object with the subrange removed (applied). 183 */ 184 @Override 185 public Point copyToReal() { 186 return _child.getRealPoint(_u); 187 } 188 189 /** 190 * Recycles a <code>SubrangePoint</code> instance immediately (on the stack when 191 * executing in a <code>StackContext</code>). 192 * 193 * @param instance The instance to be recycled. 194 */ 195 public static void recycle(SubrangePoint instance) { 196 FACTORY.recycle(instance); 197 } 198 199 /** 200 * Return an immutable version of this point. 201 * 202 * @return An immutable version of this point. 203 */ 204 @Override 205 public Point immutable() { 206 return Point.valueOf(this); 207 } 208 209 /** 210 * Returns the number of physical dimensions of the geometry element. 211 * 212 * @return The number of physical dimensions of the geometry element. 213 */ 214 @Override 215 public int getPhyDimension() { 216 return _child.getPhyDimension(); 217 } 218 219 /** 220 * Returns the value of a coordinate in this point as a <code>double</code>, stated in 221 * this point's {@link #getUnit unit}. 222 * 223 * @param i the dimension index. 224 * @return the value of the Parameter at <code>i</code>. 225 * @throws IndexOutOfBoundsException <code>(i < 0) || (i ≥ getPhyDimension())</code> 226 */ 227 @Override 228 public double getValue(int i) { 229 StackContext.enter(); 230 try { 231 Point pTrans = copyToReal(); 232 double value = pTrans.getValue(i); 233 return value; 234 } finally { 235 StackContext.exit(); 236 } 237 } 238 239 /** 240 * Returns the value of a coordinate in this point as a <code>double</code>, stated in 241 * the specified unit. 242 * 243 * @param i the dimension index. 244 * @param unit the unit to return the value in. May not be null. 245 * @return the value of the Parameter at <code>i</code> in the specified unit. 246 * @throws IndexOutOfBoundsException <code>(i < 0) || (i ≥ getPhyDimension())</code> 247 */ 248 @Override 249 public double getValue(int i, Unit<Length> unit) { 250 StackContext.enter(); 251 try { 252 Point pTrans = copyToReal(); 253 double value = pTrans.getValue(i, requireNonNull(unit)); 254 return value; 255 } finally { 256 StackContext.exit(); 257 } 258 } 259 260 /** 261 * Returns the square of the Euclidean norm, magnitude, or length value of the vector 262 * from the origin to this point (the dot product of the origin-to-this-point vector 263 * and itself). This is slightly faster than calling <code>normValue</code> if the 264 * squared value is all that is needed. 265 * 266 * @return <code>this.normSq().getValue()</code>. 267 * @see #normValue() 268 */ 269 @Override 270 public double normSqValue() { 271 StackContext.enter(); 272 try { 273 Point pTrans = copyToReal(); 274 double norm2V = pTrans.normSqValue(); 275 return norm2V; 276 } finally { 277 StackContext.exit(); 278 } 279 } 280 281 /** 282 * Returns the sum of this point with the one specified. The unit of the output point 283 * will be the units of this point. 284 * 285 * @param that the point to be added. May not be null. 286 * @return <code>this + that</code>. 287 * @throws DimensionException if point dimensions are different. 288 */ 289 @Override 290 public Point plus(GeomPoint that) { 291 Point pTrans = copyToReal(); 292 Point point = pTrans.plus(that); 293 return point; 294 } 295 296 /** 297 * Adds the specified parameter to each component of this point. The unit of the 298 * output point will be the units of this point. 299 * 300 * @param that the parameter to be added to each component of this point. May not be 301 * null. 302 * @return <code>this + that</code>. 303 */ 304 @Override 305 public Point plus(Parameter<Length> that) { 306 Point pTrans = copyToReal(); 307 Point point = pTrans.plus(that); 308 return point; 309 } 310 311 /** 312 * Returns the difference between this point and the one specified. The unit of the 313 * output point will be the units of this point. 314 * 315 * @param that the point to be subtracted from this point. May not be null. 316 * @return <code>this - that</code>. 317 * @throws DimensionException if point dimensions are different. 318 */ 319 @Override 320 public Point minus(GeomPoint that) { 321 Point pTrans = copyToReal(); 322 Point point = pTrans.minus(that); 323 return point; 324 } 325 326 /** 327 * Subtracts the specified parameter from each component of this point. The unit of 328 * the output point will be the units of this point. 329 * 330 * @param that the parameter to be subtracted from each component of this point. May 331 * not be null. 332 * @return <code>this - that</code>. 333 */ 334 @Override 335 public Point minus(Parameter<Length> that) { 336 Point pTrans = copyToReal(); 337 Point point = pTrans.minus(that); 338 return point; 339 } 340 341 /** 342 * Returns the negation of this point (all the values of each dimension negated). 343 * 344 * @return <code>-this</code> 345 */ 346 @Override 347 public Point opposite() { 348 Point pTrans = copyToReal(); 349 Point point = pTrans.opposite(); 350 return point; 351 } 352 353 /** 354 * Returns the product of this point with the specified coefficient. 355 * 356 * @param k the coefficient multiplier. 357 * @return <code>this ยท k</code> 358 */ 359 @Override 360 public Point times(double k) { 361 Point pTrans = copyToReal(); 362 Point point = pTrans.times(k); 363 return point; 364 } 365 366 /** 367 * Return <code>true</code> if this point contains valid and finite numerical 368 * components. A value of <code>false</code> will be returned if any of the coordinate 369 * values are NaN or Inf. 370 * 371 * @return true if this point contains valid and finite numerical components. 372 */ 373 @Override 374 public boolean isValid() { 375 return _u.isValid() && _child.isValid(); 376 } 377 378 /** 379 * Returns a copy of this SubrangePoint instance 380 * {@link javolution.context.AllocatorContext allocated} by the calling thread 381 * (possibly on the stack). 382 * 383 * @return an identical and independent copy of this point. 384 */ 385 @Override 386 public SubrangePoint copy() { 387 return SubrangePoint.copyOf(this); 388 } 389 390 /** 391 * Returns the unit in which the {@link #getValue values} in this point are stated in. 392 * 393 * @return The unit in which the coordinate values in this point are stated in. 394 */ 395 @Override 396 public Unit<Length> getUnit() { 397 return _child.getUnit(); 398 } 399 400 /** 401 * Returns the equivalent to this point but stated in the specified unit. 402 * 403 * @param unit the length unit of the point to be returned. May not be null. 404 * @return an equivalent to this point but stated in the specified unit. 405 * @throws ConversionException if the the input unit is not a length unit. 406 */ 407 @Override 408 public GeomPoint to(Unit<Length> unit) throws ConversionException { 409 if (unit.equals(getUnit())) 410 return this; 411 return copyToReal().to(unit); 412 } 413 414 /** 415 * Returns a Vector3D representation of this transformed point if possible. 416 * 417 * @return A Vector3D that is equivalent to this transformed point 418 * @throws DimensionException if this point has any number of dimensions other than 3. 419 */ 420 @Override 421 public Vector3D<Length> toVector3D() { 422 Point pTrans = copyToReal(); 423 Vector3D<Length> V = pTrans.toVector3D(); 424 return V; 425 } 426 427 /** 428 * Returns the values stored in this transformed point, stated in this point's 429 * {@link #getUnit unit}, as a Float64Vector. 430 * 431 * @return A Float64Vector containing the coordinate values in this point. 432 */ 433 @Override 434 public Float64Vector toFloat64Vector() { 435 StackContext.enter(); 436 try { 437 Point pTrans = copyToReal(); 438 Float64Vector V = pTrans.toFloat64Vector(); 439 return V; 440 } finally { 441 StackContext.exit(); 442 } 443 } 444 445 /** 446 * Return the equivalent of this point converted to the specified number of physical 447 * dimensions. If the number of dimensions is greater than this element, then zeros 448 * are added to the additional dimensions. If the number of dimensions is less than 449 * this element, then the extra dimensions are simply dropped (truncated). If the new 450 * dimensions are the same as the dimension of this element, then this element is 451 * simply returned. NOTE: The returned point is no longer a SubrangePoint. 452 * 453 * @param newDim The dimension of the point to return. 454 * @return The equivalent of this point converted to the new dimensions. 455 */ 456 @Override 457 public GeomPoint toDimension(int newDim) { 458 if (newDim == this.getPhyDimension()) 459 return this; 460 461 GeomPoint newP = copyToReal().toDimension(newDim); 462 return newP; 463 } 464 465 /** 466 * Returns transformed version of this element. The returned object implements 467 * {@link GeomTransform} and contains this element as a child. 468 * 469 * @param transform The transformation to apply to this geometry. May not be null. 470 * @return A new triangle that is identical to this one with the specified 471 * transformation applied. 472 * @throws DimensionException if this point is not 3D. 473 */ 474 @Override 475 public GeomPointTrans getTransformed(GTransform transform) { 476 return GeomPointTrans.newInstance(this, requireNonNull(transform)); 477 } 478 479 /** 480 * Compares this SubrangePoint against the specified object for strict equality (same 481 * values and same units). 482 * 483 * @param obj the object to compare with. 484 * @return <code>true</code> if this point is identical to that point; 485 * <code>false</code> otherwise. 486 */ 487 @Override 488 public boolean equals(Object obj) { 489 if (this == obj) 490 return true; 491 if ((obj == null) || (obj.getClass() != this.getClass())) 492 return false; 493 494 SubrangePoint that = (SubrangePoint)obj; 495 return this._u.equals(that._u) 496 && this._child.equals(that._child) 497 && super.equals(obj); 498 } 499 500 /** 501 * Returns the hash code for this parameter. 502 * 503 * @return the hash code value. 504 */ 505 @Override 506 public int hashCode() { 507 return 31*super.hashCode() + Objects.hash(_u, _child); 508 } 509 510 /** 511 * Holds the default XML representation for this object. 512 */ 513 @SuppressWarnings("FieldNameHidesFieldInSuperclass") 514 protected static final XMLFormat<SubrangePoint> XML = new XMLFormat<SubrangePoint>(SubrangePoint.class) { 515 516 @Override 517 public SubrangePoint newInstance(Class<SubrangePoint> cls, InputElement xml) throws XMLStreamException { 518 return FACTORY.object(); 519 } 520 521 @Override 522 public void read(InputElement xml, SubrangePoint obj) throws XMLStreamException { 523 GeomPoint.XML.read(xml, obj); // Call parent read. 524 525 GeomPoint par = xml.get("ParPos"); 526 obj._u = par; 527 ParametricGeometry child = xml.get("Child"); 528 obj._child = child; 529 530 if (!(par instanceof Immutable)) 531 par.addChangeListener(obj._childChangeListener); 532 if (!(child instanceof Immutable)) 533 child.addChangeListener(obj._childChangeListener); 534 } 535 536 @Override 537 public void write(SubrangePoint obj, OutputElement xml) throws XMLStreamException { 538 GeomPoint.XML.write(obj, xml); // Call parent write. 539 540 xml.add(obj._u, "ParPos"); 541 xml.add(obj._child, "Child"); 542 543 } 544 }; 545 546 /////////////////////// 547 // Factory creation. // 548 /////////////////////// 549 private SubrangePoint() { } 550 551 @SuppressWarnings("unchecked") 552 private static final ObjectFactory<SubrangePoint> FACTORY = new ObjectFactory<SubrangePoint>() { 553 @Override 554 protected SubrangePoint create() { 555 return new SubrangePoint(); 556 } 557 558 @Override 559 protected void cleanup(SubrangePoint obj) { 560 obj.reset(); 561 if (!(obj._u instanceof Immutable)) 562 obj._u.removeChangeListener(obj._childChangeListener); 563 obj._u = null; 564 if (!(obj._child instanceof Immutable)) 565 obj._child.removeChangeListener(obj._childChangeListener); 566 obj._child = null; 567 } 568 }; 569 570 @SuppressWarnings("unchecked") 571 private static SubrangePoint copyOf(SubrangePoint original) { 572 SubrangePoint obj = FACTORY.object(); 573 obj._u = original._u.copy(); 574 obj._child = original._child.copy(); 575 if (!(obj._u instanceof Immutable)) 576 obj._u.addChangeListener(obj._childChangeListener); 577 if (!(obj._child instanceof Immutable)) 578 obj._child.addChangeListener(obj._childChangeListener); 579 original.copyState(obj); 580 return obj; 581 } 582 583}