001/* 002 * ControlPoint -- Holds the floating point coordinates of a NURBS control point in nD space. 003 * 004 * Copyright (C) 2009-2025, Joseph A. Huwaldt 005 * All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public License 018 * along with this program; if not, write to the Free Software 019 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 020 * Or visit: http://www.gnu.org/licenses/lgpl.html 021 */ 022package geomss.geom.nurbs; 023 024import geomss.geom.DimensionException; 025import geomss.geom.Point; 026import static java.util.Objects.requireNonNull; 027import java.util.ResourceBundle; 028import javax.measure.converter.ConversionException; 029import javax.measure.quantity.Length; 030import javax.measure.unit.SI; 031import javax.measure.unit.Unit; 032import javolution.context.ObjectFactory; 033import javolution.context.StackContext; 034import javolution.lang.MathLib; 035import javolution.lang.ValueType; 036import javolution.text.Text; 037import javolution.text.TextBuilder; 038import javolution.util.FastTable; 039import javolution.xml.XMLFormat; 040import javolution.xml.XMLSerializable; 041import javolution.xml.stream.XMLStreamException; 042import org.jscience.mathematics.number.Float64; 043import org.jscience.mathematics.vector.Float64Vector; 044 045/** 046 * A container that holds the coordinates of a NURBS control point in n-dimensional space. 047 * 048 * <p> Modified by: Joseph A. Huwaldt </p> 049 * 050 * @author Joseph A. Huwaldt, Date: May 14, 2009 051 * @version February 17, 2025 052 */ 053@SuppressWarnings("serial") 054public class ControlPoint implements Cloneable, XMLSerializable, ValueType { 055 056 /** 057 * The resource bundle for this package. 058 */ 059 private static final ResourceBundle RESOURCES = geomss.geom.AbstractGeomElement.RESOURCES; 060 061 /** 062 * The coordinates of the physical point represented by this control point. 063 */ 064 private Point _point; 065 066 /** 067 * The weighting factor. 068 */ 069 private double _weight; 070 071 /** 072 * Returns a {@link ControlPoint} instance of the specified dimension with zero meters 073 * for each coordinate value and zero weight. 074 * 075 * @param dim the physical dimension of the point to create (not including the 076 * weighting factor). 077 * @return the point having the specified dimension and zero meters for values. 078 */ 079 public static ControlPoint newInstance(int dim) { 080 ControlPoint P = ControlPoint.newInstance(Point.newInstance(dim), 0); 081 return P; 082 } 083 084 /** 085 * Returns a {@link ControlPoint} instance of the specified dimension and units with 086 * zero for each coordinate value and zero weight. 087 * 088 * @param dim the physical dimension of the point to create (not including the 089 * weighting factor). 090 * @param unit the unit for the point coordinate values. May not be null. 091 * @return the point having the specified dimension & unit and zero for values. 092 */ 093 public static ControlPoint newInstance(int dim, Unit<Length> unit) { 094 ControlPoint P = ControlPoint.newInstance(Point.newInstance(dim, requireNonNull(unit)), 0); 095 return P; 096 } 097 098 /** 099 * Returns a 2D {@link ControlPoint} instance holding the specified 100 * <code>double</code> values stated in meters. 101 * 102 * @param x the x value stated in meters. 103 * @param y the y value stated in meters. 104 * @param w the weighting factor for the control point. 105 * @return the point having the specified values. 106 */ 107 public static ControlPoint valueOf(double x, double y, double w) { 108 return valueOf(x, y, w, SI.METER); 109 } 110 111 /** 112 * Returns a 2D {@link ControlPoint} instance holding the specified 113 * <code>double</code> values stated in the specified units. 114 * 115 * @param x the x value stated in the specified unit. 116 * @param y the y value stated in the specified unit. 117 * @param w the weighting factor for the control point. 118 * @param unit the unit in which the coordinates are stated. May not be null. 119 * @return the point having the specified values. 120 */ 121 public static ControlPoint valueOf(double x, double y, double w, Unit<Length> unit) { 122 ControlPoint P = ControlPoint.newInstance(Point.valueOf(x, y, requireNonNull(unit)), w); 123 return P; 124 } 125 126 /** 127 * Returns a 3D {@link ControlPoint} instance holding the specified 128 * <code>double</code> values stated in meters. 129 * 130 * @param x the x value stated in meters. 131 * @param y the y value stated in meters. 132 * @param z the z value stated in meters. 133 * @param w the weighting factor for the control point. 134 * @return the point having the specified values. 135 */ 136 public static ControlPoint valueOf(double x, double y, double z, double w) { 137 return valueOf(x, y, z, w, SI.METER); 138 } 139 140 /** 141 * Returns a 3D {@link ControlPoint} instance holding the specified 142 * <code>double</code> values stated in the specified units. 143 * 144 * @param x the x value stated in the specified unit. 145 * @param y the y value stated in the specified unit. 146 * @param z the z value stated in meters. 147 * @param w the weighting factor for the control point. 148 * @param unit the unit in which the coordinates are stated. May not be null. 149 * @return the point having the specified values. 150 */ 151 public static ControlPoint valueOf(double x, double y, double z, double w, Unit<Length> unit) { 152 ControlPoint P = ControlPoint.newInstance(Point.valueOf(x, y, z, requireNonNull(unit)), w); 153 return P; 154 } 155 156 /** 157 * Returns a {@link ControlPoint} instance holding the specified <code>Float64</code> 158 * values stated in the specified units. 159 * 160 * @param vector the vector of Float64 values stated with all but the last stated in 161 * the specified unit (the last is assumed to be the weight). May not be 162 * null. 163 * @param unit the unit in which the coordinates are stated. May not be null. 164 * @return the point having the specified values. 165 */ 166 public static ControlPoint valueOf(org.jscience.mathematics.vector.Vector<Float64> vector, Unit<Length> unit) { 167 requireNonNull(unit); 168 FastTable<Float64> v = FastTable.newInstance(); 169 int dims = vector.getDimension() - 1; 170 for (int i = 0; i < dims; ++i) { 171 v.add(vector.get(i)); 172 } 173 Float64Vector f64v = Float64Vector.valueOf(v); 174 ControlPoint P = ControlPoint.newInstance(Point.valueOf(f64v, unit), vector.get(dims).doubleValue()); 175 return P; 176 } 177 178 /** 179 * Returns a {@link ControlPoint} instance holding the specified <code>Float64</code> 180 * values plus weight stated in the specified units. 181 * 182 * @param vector the vector of Float64 values representing the geometric point stated 183 * in the specified unit. May not be null. 184 * @param w the weighting factor for the control point. 185 * @param unit the unit in which the coordinates are stated. May not be null. 186 * @return the control point having the specified values. 187 */ 188 public static ControlPoint valueOf(org.jscience.mathematics.vector.Vector<Float64> vector, double w, Unit<Length> unit) { 189 requireNonNull(vector); 190 requireNonNull(unit); 191 Float64Vector f64v = Float64Vector.valueOf(vector); 192 ControlPoint P = ControlPoint.newInstance(Point.valueOf(f64v, unit), w); 193 return P; 194 } 195 196 /** 197 * Returns a {@link ControlPoint} instance holding the specified <code>double</code> 198 * values stated in the specified units. 199 * 200 * @param unit the unit in which the coordinates are stated. May not be null. 201 * @param vector the vector of coordinate values with all but the last stated in the 202 * specified unit (the last is assumed to be the weight). May not be 203 * null. 204 * @return the control point having the specified values. 205 */ 206 public static ControlPoint valueOf(Unit<Length> unit, double[] vector) { 207 requireNonNull(unit); 208 FastTable<Float64> v = FastTable.newInstance(); 209 int dims = vector.length - 1; 210 for (int i = 0; i < dims; ++i) { 211 v.add(Float64.valueOf(vector[i])); 212 } 213 Float64Vector f64v = Float64Vector.valueOf(v); 214 ControlPoint P = ControlPoint.newInstance(Point.valueOf(f64v, unit), vector[dims]); 215 return P; 216 } 217 218 /** 219 * Returns a {@link ControlPoint} instance containing the specified Point's data. 220 * 221 * @param point the Point to be copied into a new ControlPoint. May not be null. 222 * @param w the weighting factor for the control point. 223 * @return the point having the specified values. 224 */ 225 public static ControlPoint valueOf(Point point, double w) { 226 ControlPoint P = ControlPoint.newInstance(requireNonNull(point), w); 227 return P; 228 } 229 230 /** 231 * Returns a {@link ControlPoint} instance containing the specified ControlPoint's 232 * data. 233 * 234 * @param point the ControlPoint to be copied into a new ControlPoint. May not be 235 * null. 236 * @return the point having the specified values. 237 */ 238 public static ControlPoint valueOf(ControlPoint point) { 239 return copyOf(requireNonNull(point)); 240 } 241 242 /** 243 * Returns the number of physical dimensions of the geometry element. This 244 * implementation returns the dimensions of the point and does not count the weighting 245 * factor. 246 * 247 * @return The physical dimensions of the control points not counting the weighting 248 * factor. 249 */ 250 public int getPhyDimension() { 251 return _point.getPhyDimension(); 252 } 253 254 /** 255 * Returns the value of the Parameter in this point as a <code>double</code>, stated 256 * in this point's unit. 257 * 258 * @param i the dimension index. 259 * @return the value of the Parameter at <code>i</code>. 260 * @throws IndexOutOfBoundsException <code>(i < 0) || (i > 261 * getPhyDimension() - 1)</code> 262 */ 263 public double getValue(int i) { 264 return _point.getValue(i); 265 } 266 267 /** 268 * Return the Point representation of this ControlPoint. 269 * 270 * @return A Point representation of this control point. 271 */ 272 public Point getPoint() { 273 return _point; 274 } 275 276 /** 277 * Return the weight associated with this control point. 278 * 279 * @return The weighting factor associated with this control point. 280 */ 281 public double getWeight() { 282 return _weight; 283 } 284 285 /** 286 * Return a new control point that is identical to this one, but with the weight 287 * changed to the specified value. 288 * 289 * @param value The new weighting factor. 290 * @return A new ControlPoint identical to this one but with the specified weight. 291 */ 292 public ControlPoint changeWeight(double value) { 293 ControlPoint cp = ControlPoint.newInstance(_point, value); 294 return cp; 295 } 296 297 /** 298 * Return a copy of this control point with the values made homogeneous (all the 299 * geometric point values are divided by the weight). 300 * 301 * @return A new ControlPoint with the values made homogeneous. 302 */ 303 public ControlPoint getHomogeneous() { 304 Point newPoint = _point.divide(_weight); 305 return ControlPoint.newInstance(newPoint, _weight); 306 } 307 308 /** 309 * Returns a new ControlPoint with the geometric point portion of this control point 310 * multiplied by the weight. 311 * 312 * @return A new ControlPoint with the geometric point multiplied by the weight. 313 */ 314 public ControlPoint applyWeight() { 315 Point newPoint = _point.times(_weight); 316 return ControlPoint.newInstance(newPoint, _weight); 317 } 318 319 /** 320 * Returns a new control point with the sum of this point with the one specified. 321 * 322 * @param that the point to be added to this one. May not be null. 323 * @return A new ControlPoint with this + that. 324 * @throws DimensionException if point dimensions are different. 325 */ 326 public ControlPoint plus(ControlPoint that) { 327 Point pnt = _point.plus(that._point); 328 double weight = this._weight + that._weight; 329 return ControlPoint.newInstance(pnt, weight); 330 } 331 332 /** 333 * Returns a new control point with the difference between this point with the one 334 * specified. 335 * 336 * @param that the point to be subtracted from this one. May not be null. 337 * @return A new ControlPoint with this - that. 338 * @throws DimensionException if point dimensions are different. 339 */ 340 public ControlPoint minus(ControlPoint that) { 341 Point pnt = _point.minus(that._point); 342 double weight = this._weight - that._weight; 343 return ControlPoint.newInstance(pnt, weight); 344 } 345 346 /** 347 * Returns a new control point with the product of this point with the specified 348 * coefficient. 349 * 350 * @param k the coefficient multiplier. 351 * @return A new ControlPoint with this*k. 352 */ 353 public ControlPoint times(double k) { 354 Point pnt = _point.times(k); 355 double weight = _weight * k; 356 return ControlPoint.newInstance(pnt, weight); 357 } 358 359 /** 360 * Returns a new control point with this point divided by the specified divisor. 361 * 362 * @param divisor the divisor. 363 * @return A new ControlPoint with this / divisor. 364 */ 365 public ControlPoint divide(double divisor) { 366 return times(1.0 / divisor); 367 } 368 369 /** 370 * Returns the norm, magnitude, or length value of the vector from the origin to this 371 * control point geometric point (square root of the dot product of the 372 * origin-to-this-point vector and itself). 373 * 374 * @return The magnitude of the vector from the origin to this control point. 375 * geometric point. 376 */ 377 public double normValue() { 378 return _point.normValue(); 379 } 380 381 /** 382 * Return the distance (in homogeneous coordinates) between this control point and 383 * another (this treats the weight simply as another dimension). 384 * 385 * @param cp The control point to find the homogeneous distance to. May not be null. 386 * @return The homogeneous distance to the specified control point from this one. 387 */ 388 public double distance(ControlPoint cp) { 389 double d2 = 0; 390 int dim = _point.getPhyDimension(); 391 Point cpPoint = cp._point; 392 if (cpPoint.getPhyDimension() != dim) 393 throw new DimensionException(RESOURCES.getString("dimensionErr")); 394 395 for (int i = 0; i < dim; ++i) { 396 double v = _point.getValue(i) - cpPoint.getValue(i); 397 d2 += v * v; 398 } 399 double v = _weight - cp._weight; 400 d2 += v * v; 401 return MathLib.sqrt(d2); 402 } 403 404 /** 405 * Return the coordinate point representing the minimum bounding box corner (e.g.: min 406 * X, min Y, min Z). 407 * 408 * @return The minimum bounding box coordinate for this geometry element. 409 */ 410 public Point getBoundsMin() { 411 return _point; 412 } 413 414 /** 415 * Return the coordinate point representing the maximum bounding box corner (e.g.: max 416 * X, max Y, max Z). 417 * 418 * @return The maximum bounding box coordinate for this geometry element. 419 */ 420 public Point getBoundsMax() { 421 return _point; 422 } 423 424 /** 425 * Return <code>true</code> if this control point contains valid and finite numerical 426 * components. A value of <code>false</code> will be returned if any of the coordinate 427 * values are NaN or Inf (including the weight). 428 * 429 * @return true if this control point contains valid and finite values. 430 */ 431 public boolean isValid() { 432 return _point.isValid() && !(Double.isNaN(_weight) || Double.isInfinite(_weight)); 433 } 434 435 /** 436 * Returns the unit in which this control point is stated. 437 * 438 * @return The unit in which this control point is stated. 439 */ 440 public Unit<Length> getUnit() { 441 return _point.getUnit(); 442 } 443 444 /** 445 * Returns the equivalent to this control point but stated in the specified unit. 446 * 447 * @param unit The length unit of the control point to be returned. May not be null. 448 * @return An equivalent to this control point but stated in the specified unit. 449 * @throws ConversionException if the the input unit is not a length unit. 450 */ 451 public ControlPoint to(Unit<Length> unit) throws ConversionException { 452 if (unit.equals(getUnit())) 453 return this; 454 return ControlPoint.newInstance(_point.to(unit), _weight); 455 } 456 457 /** 458 * Returns the values stored in this control point, with the coordinate points stated 459 * in this point's unit and the weight tacked onto the end as a Float64Vector. 460 * 461 * @return A Float64Vector representation of this control point. 462 */ 463 public Float64Vector toFloat64Vector() { 464 StackContext.enter(); 465 try { 466 467 FastTable<Float64> v = FastTable.newInstance(); 468 Float64Vector p3dv = _point.toFloat64Vector(); 469 int numDims = p3dv.getDimension(); 470 for (int i = 0; i < numDims; ++i) { 471 v.add(p3dv.get(i)); 472 } 473 v.add(Float64.valueOf(_weight)); 474 Float64Vector f64v = Float64Vector.valueOf(v); 475 return StackContext.outerCopy(f64v); 476 477 } finally { 478 StackContext.exit(); 479 } 480 } 481 482 /** 483 * Returns the values stored in this control point as a Java array, stated in the 484 * current {@link #getUnit units} with the weight tacked onto the end of the array. 485 * 486 * @return A new array with the control point values copied into it with the 487 * weight at the end. 488 */ 489 public double[] toArray() { 490 return toArray(new double[this.getPhyDimension()+1]); 491 } 492 493 /** 494 * Returns the values stored in this control point, with the coordinate point stated in the current 495 * {@link #getUnit units} and the weight tacked onto the end, stored in the input Java array. 496 * 497 * @param array An existing array that has at least as many elements as the physical 498 * dimension of this point + 1 for the weight. 499 * @return A reference to the input array after the control point values have been copied over 500 * to it. 501 * @see #getPhyDimension() 502 */ 503 public double[] toArray(double[] array) { 504 _point.toArray(array); 505 array[_point.getPhyDimension()] = _weight; 506 return array; 507 } 508 509 /** 510 * Returns a copy of this ControlPoint instance 511 * {@link javolution.context.AllocatorContext allocated} by the calling thread 512 * (possibly on the stack). 513 * 514 * @return an identical and independent copy of this point. 515 */ 516 @Override 517 public ControlPoint copy() { 518 return copyOf(this); 519 } 520 521 /** 522 * Returns a copy of this ControlPoint instance 523 * {@link javolution.context.AllocatorContext allocated} by the calling thread 524 * (possibly on the stack). 525 * 526 * @return an identical and independent copy of this point. 527 * @throws java.lang.CloneNotSupportedException Never thrown. 528 */ 529 @Override 530 @SuppressWarnings("CloneDoesntCallSuperClone") 531 public Object clone() throws CloneNotSupportedException { 532 return copy(); 533 } 534 535 /** 536 * Compares this ControlPoint against the specified object for strict equality (same 537 * values and same units). 538 * 539 * @param obj the object to compare with. 540 * @return <code>true</code> if this point is identical to that point; 541 * <code>false</code> otherwise. 542 */ 543 @Override 544 public boolean equals(Object obj) { 545 if (this == obj) 546 return true; 547 if ((obj == null) || (obj.getClass() != this.getClass())) 548 return false; 549 550 ControlPoint that = (ControlPoint)obj; 551 return this._point.equals(that._point) && this._weight == that._weight; 552 } 553 554 /** 555 * Returns the hash code for this parameter. 556 * 557 * @return the hash code value. 558 */ 559 @Override 560 public int hashCode() { 561 int hash = 7; 562 hash = hash * 31 + makeVarCode(_weight); 563 hash = hash * 31 + _point.hashCode(); 564 return hash; 565 } 566 567 private static int makeVarCode(double value) { 568 long bits = Double.doubleToLongBits(value); 569 int var_code = (int)(bits ^ (bits >>> 32)); 570 return var_code; 571 } 572 573 /** 574 * Returns the text representation of this control point that consists of the the 575 * geometry coordinate values followed by the weighting factor. For example: 576 * <pre> 577 * {{10 ft, -3 ft, 4.56 ft}, 1.0} 578 * </pre> 579 * 580 * @return the text representation of this geometry element. 581 */ 582 public Text toText() { 583 TextBuilder tmp = TextBuilder.newInstance(); 584 tmp.append('{'); 585 tmp.append(_point.toText()); 586 tmp.append(", "); 587 tmp.append(_weight); 588 tmp.append('}'); 589 Text txt = tmp.toText(); 590 TextBuilder.recycle(tmp); 591 return txt; 592 } 593 594 /** 595 * Returns the String representation of this control point that consists of the the 596 * geometry coordinate values followed by the weighting factor. For example: 597 * <pre> 598 * {{10 ft, -3 ft, 4.56 ft}, 1.0} 599 * </pre> 600 * 601 * @return the text representation of this geometry element. 602 */ 603 @Override 604 public String toString() { 605 return toText().toString(); 606 } 607 608 /** 609 * Holds the default XML representation for this object. 610 */ 611 protected static final XMLFormat<ControlPoint> XML = new XMLFormat<ControlPoint>(ControlPoint.class) { 612 @Override 613 public ControlPoint newInstance(Class<ControlPoint> cls, InputElement xml) throws XMLStreamException { 614 return FACTORY.object(); 615 } 616 617 @Override 618 public void read(InputElement xml, ControlPoint obj) throws XMLStreamException { 619 620 double weight = xml.getAttribute("weight", 1.0); 621 obj._weight = weight; 622 623 Point point = xml.getNext(); 624 obj._point = point; 625 626 } 627 628 @Override 629 public void write(ControlPoint obj, OutputElement xml) throws XMLStreamException { 630 631 xml.setAttribute("weight", obj._weight); 632 xml.add(obj._point); 633 634 } 635 }; 636 637 /////////////////////// 638 // Factory creation. // 639 /////////////////////// 640 private ControlPoint() { 641 } 642 643 @SuppressWarnings("unchecked") 644 private static final ObjectFactory<ControlPoint> FACTORY = new ObjectFactory<ControlPoint>() { 645 @Override 646 protected ControlPoint create() { 647 return new ControlPoint(); 648 } 649 650 @Override 651 protected void cleanup(ControlPoint obj) { 652 obj._point = null; 653 } 654 }; 655 656 @SuppressWarnings("unchecked") 657 private static ControlPoint newInstance(Point point, double weight) { 658 ControlPoint o = FACTORY.object(); 659 o._point = point; 660 o._weight = weight; 661 return o; 662 } 663 664 @SuppressWarnings("unchecked") 665 private static ControlPoint copyOf(ControlPoint original) { 666 ControlPoint o = FACTORY.object(); 667 o._point = original._point.copy(); 668 o._weight = original._weight; 669 return o; 670 } 671 672}