001/** 002 * LoftedSurface -- A surface defined by a list of cross-section curves. 003 * 004 * Copyright (C) 2010-2025, by Joseph A. Huwaldt. All rights reserved. 005 * 006 * This library is free software; you can redistribute it and/or modify it under the terms 007 * of the GNU Lesser General Public License as published by the Free Software Foundation; 008 * either version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, but WITHOUT ANY 011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 012 * PARTICULAR PURPOSE. See the GNU Library General Public License for more details. 013 * 014 * You should have received a copy of the GNU Lesser General Public License along with 015 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - 016 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html 017 */ 018package geomss.geom; 019 020import geomss.geom.nurbs.*; 021import jahuwaldt.js.param.Parameter; 022import jahuwaldt.tools.math.MathTools; 023import java.text.MessageFormat; 024import java.util.*; 025import static java.util.Objects.nonNull; 026import static java.util.Objects.requireNonNull; 027import javax.measure.converter.ConversionException; 028import javax.measure.quantity.Length; 029import javax.measure.unit.SI; 030import javax.measure.unit.Unit; 031import javax.swing.event.ChangeListener; 032import javolution.context.ArrayFactory; 033import javolution.context.ObjectFactory; 034import javolution.lang.Immutable; 035import javolution.text.Text; 036import javolution.text.TextBuilder; 037import javolution.util.FastTable; 038import javolution.xml.XMLFormat; 039import javolution.xml.stream.XMLStreamException; 040 041/** 042 * Represents a "lofted" or "skinned" surface defined from a list of defining 043 * {@link Curve curves} that each define a cross-section of the surface. Each curve 044 * defines the local parameterization in the "s" direction and the spacing between them 045 * defines the parameterization in the "t" direction. Any number of curves may be added to 046 * a lofted surface, but all curves must have the same physical dimensions. 047 * 048 * <p> Modified by: Joseph A. Huwaldt </p> 049 * 050 * @author Joseph A. Huwaldt, Date: June 24, 2010 051 * @version February 16, 2025 052 */ 053@SuppressWarnings({"serial", "CloneableImplementsClone"}) 054public class LoftedSurface extends AbstractSurface<LoftedSurface> implements GeometryList<LoftedSurface,Curve> { 055 056 // The list behind this implementation. 057 private FastTable<Curve> _crvs; 058 059 // The degree of the surface in the "t" direction. 060 private int _q = 1; 061 062 /** 063 * Reference to a change listener for this object's child curves. 064 */ 065 private final ChangeListener _childChangeListener = new ForwardingChangeListener(this); 066 067 /** 068 * Returns a new, empty, preallocated or recycled <code>LoftedSurface</code> instance 069 * (on the stack when executing in a <code>StackContext</code>), that can store a list 070 * of {@link Curve} objects. The list is initially empty and therefore the surface is 071 * initially undefined. 072 * 073 * @param q The degree of the surface across the defining curves (in the "t" 074 * direction). 075 * @return A new empty LoftedSurface. 076 */ 077 public static LoftedSurface newInstance(int q) { 078 return newInstance(null, q); 079 } 080 081 /** 082 * Returns a new, empty, preallocated or recycled <code>LoftedSurface</code> instance 083 * (on the stack when executing in a <code>StackContext</code>) with the specified 084 * name, that can store a list of {@link Curve} objects. The list is initially empty 085 * and therefore the surface is initially undefined. 086 * 087 * @param name The name to be assigned to this surface (may be <code>null</code>). 088 * @param q The degree of the surface across the defining curves (in the "t" 089 * direction). 090 * @return A new empty LoftedSurface. 091 */ 092 public static LoftedSurface newInstance(String name, int q) { 093 if (q < 1) 094 throw new IllegalArgumentException(RESOURCES.getString("tDirDegreeErr")); 095 LoftedSurface list = FACTORY.object(); 096 list._crvs = FastTable.newInstance(); 097 list._q = q; 098 list.setName(name); 099 return list; 100 } 101 102 /** 103 * Return a LoftedSurface made up of the {@link Curve} objects in the specified 104 * collection. 105 * 106 * @param name The name to be assigned to this surface (may be <code>null</code>). 107 * @param q The degree of the surface across the defining curves (in the "t" 108 * direction). 109 * @param curves A collection of curves that define the surface. May not be null. 110 * @return A new LoftedSurface made up of the curves in the specified collection. 111 */ 112 public static LoftedSurface valueOf(String name, int q, Collection<? extends Curve> curves) { 113 requireNonNull(curves); 114 LoftedSurface list = LoftedSurface.newInstance(name, q); 115 list.addAll(curves); 116 117 return list; 118 } 119 120 /** 121 * Return a LoftedSurface made up of the {@link Curve} objects in the specified 122 * collection. 123 * 124 * @param q The degree of the surface across the defining curves (in the "t" 125 * direction). 126 * @param curves A collection of curves that define the surface. May not be null. 127 * @return A new LoftedSurface made up of the curves in the specified collection. 128 */ 129 public static LoftedSurface valueOf(int q, Collection<? extends Curve> curves) { 130 131 LoftedSurface list = LoftedSurface.valueOf(null, q, curves); 132 133 return list; 134 } 135 136 /** 137 * Return a LoftedSurface made up of the {@link Curve} objects in the specified array. 138 * 139 * @param name The name to be assigned to this surface (may be <code>null</code>). 140 * @param q The degree of the surface across the defining curves (in the "t" 141 * direction). 142 * @param curves An array of curves that define the surface. May not be null. 143 * @return A new LoftedSurface made up of the curves in the specified array. 144 */ 145 public static LoftedSurface valueOf(String name, int q, Curve... curves) { 146 requireNonNull(curves); 147 LoftedSurface list = LoftedSurface.newInstance(name, q); 148 list.addAll(Arrays.asList(curves)); 149 150 return list; 151 } 152 153 /** 154 * Return a LoftedSurface made up of the {@link Curve} objects in the specified array. 155 * 156 * @param q The degree of the surface across the defining curves (in the "t" 157 * direction). 158 * @param curves An array of curves that define the surface. May not be null. 159 * @return A new LoftedSurface made up of the curves in the specified array. 160 */ 161 public static LoftedSurface valueOf(int q, Curve... curves) { 162 return LoftedSurface.valueOf(null, q, curves); 163 } 164 165 /** 166 * Return the degree of the surface in the t-direction (across the defining curves). 167 * 168 * @return degree of surface in t-direction 169 */ 170 public int getTDegree() { 171 return _q; 172 } 173 174 /** 175 * Validate that the surface is properly formed, otherwise throw an exception. 176 */ 177 private void validateSurface() { 178 if (size() < _q + 1) 179 throw new IllegalArgumentException(MessageFormat.format( 180 RESOURCES.getString("incDefiningCrvCount"), "LoftedSurface", _q + 1, size())); 181 } 182 183 /** 184 * Calculate a point on the surface for the given parametric position on the surface. 185 * 186 * @param s 1st parametric dimension distance to calculate a point for (0.0 to 1.0 187 * inclusive). 188 * @param t 2nd parametric dimension distance to calculate a point for (0.0 to 1.0 189 * inclusive). 190 * @return The calculated point on the surface at the specified parameter values. 191 * @throws IllegalArgumentException if there is any problem with the parameter values. 192 */ 193 @Override 194 public Point getRealPoint(double s, double t) { 195 validateSurface(); 196 validateParameter(s, t); 197 198 // Fit a curve through the points on each defining curve at "s". 199 BasicNurbsCurve tCrv = createTCurve(s); 200 201 // Now get the point at the desired "t" position. 202 Point p = tCrv.getRealPoint(t); 203 BasicNurbsCurve.recycle(tCrv); 204 205 return p; 206 } 207 208 /** 209 * Calculate all the derivatives from <code>0</code> to <code>grade</code> with 210 * respect to parametric s-position on the surface for the given parametric position 211 * on the surface, <code>d^{grade}p(s,t)/d^{grade}s</code>. 212 * <p> 213 * Example:<br> 214 * 1st derivative (grade = 1), this returns <code>[p(s,t), dp(s,t)/ds]</code>;<br> 215 * 2nd derivative (grade = 2), this returns <code>[p(s,t), dp(s,t)/ds, d^2p(s,t)/d^2s]</code>; etc. 216 * </p> 217 * 218 * @param s 1st parametric dimension distance to calculate derivative for (0.0 to 219 * 1.0 inclusive). 220 * @param t 2nd parametric dimension distance to calculate derivative for (0.0 to 221 * 1.0 inclusive). 222 * @param grade The maximum grade to calculate the u-derivatives for (1=1st 223 * derivative, 2=2nd derivative, etc) 224 * @param scaled Pass <code>true</code> for properly scaled derivatives or 225 * <code>false</code> if the magnitude of the derivative vector is not 226 * required -- this sometimes results in faster calculation times. 227 * @return A list of s-derivatives up to the specified grade of the surface at the 228 * specified parametric position. 229 * @throws IllegalArgumentException if the grade is < 0 or the parameter values are 230 * invalid. 231 */ 232 @Override 233 public List<Vector<Length>> getSDerivatives(double s, double t, int grade, boolean scaled) { 234 validateSurface(); 235 validateParameter(s, t); 236 if (grade < 0) 237 throw new IllegalArgumentException(RESOURCES.getString("gradeLTZeroErr")); 238 239 // Create a list of point strings, each string is for a particular grade. 240 FastTable<PointString> sDers = FastTable.newInstance(); 241 for (int i = 0; i <= grade; ++i) 242 sDers.add(PointString.newInstance()); 243 244 // Get the derivatives on each of the defining curves at the input "s" position. 245 int size = _crvs.size(); 246 for (int j = 0; j < size; ++j) { 247 Curve crv = _crvs.get(j); 248 List<Vector<Length>> ders = crv.getSDerivatives(s, grade); 249 for (int i = 0; i <= grade; ++i) { 250 Vector<Length> der = ders.get(i); 251 sDers.get(i).add(Point.valueOf(der)); 252 } 253 } 254 255 // Remove any derivative points that are approximately equal. 256 FastTable<PointString> sDers2 = FastTable.newInstance(); 257 for (int i=0; i <= grade; ++i) { 258 PointString<GeomPoint> ders = sDers.get(i); 259 ders = ders.unique(Parameter.valueOf(MathTools.SQRT_EPS, SI.METER)); 260 sDers2.add(ders); 261 } 262 FastTable.recycle(sDers); 263 sDers = sDers2; 264 265 // Create an array of output derivatives. 266 FastTable<Vector<Length>> ders = FastTable.newInstance(); 267 268 // Fit curves to each derivative string and find the derivative at the input "t" position. 269 Point origin = this.getRealPoint(s, t); 270 for (int i = 0; i <= grade; ++i) { 271 PointString<GeomPoint> sDers_i = sDers.get(i); 272 Vector<Length> der; 273 if (sDers_i.size() > 1) { 274 // There are different derivatives for each section curve. 275 // Fit a curve of appropriate degree to those derivatives and interpolate the derivative at "t". 276 int q = _q; 277 if (sDers_i.size() <= q) 278 q = sDers_i.size() - 1; 279 BasicNurbsCurve tCrv = CurveFactory.fitPoints(q, sDers_i); 280 der = tCrv.getRealPoint(t).toGeomVector(); 281 } else { 282 // There is only a single derivative in this direction across all the section curves. Use it. 283 der = sDers_i.get(0).toGeomVector(); 284 } 285 der.setOrigin(origin); 286 ders.add(der); 287 } 288 289 return ders; 290 } 291 292 /** 293 * Calculate all the derivatives from <code>0</code> to <code>grade</code> with 294 * respect to parametric t-position on the surface for the given parametric position 295 * on the surface, <code>d^{grade}p(s,t)/d^{grade}t</code>. 296 * <p> 297 * Example:<br> 298 * 1st derivative (grade = 1), this returns <code>[p(s,t), dp(s,t)/dt]</code>;<br> 299 * 2nd derivative (grade = 2), this returns <code>[p(s,t), dp(s,t)/dt, d^2p(s,t)/d^2t]</code>; etc. 300 * </p> 301 * 302 * @param s 1st parametric dimension distance to calculate derivative for (0.0 to 303 * 1.0 inclusive). 304 * @param t 2nd parametric dimension distance to calculate derivative for (0.0 to 305 * 1.0 inclusive). 306 * @param grade The maximum grade to calculate the v-derivatives for (1=1st 307 * derivative, 2=2nd derivative, etc) 308 * @param scaled Pass <code>true</code> for properly scaled derivatives or 309 * <code>false</code> if the magnitude of the derivative vector is not 310 * required -- this sometimes results in faster calculation times. 311 * @return A list of t-derivatives up to the specified grade of the surface at the 312 * specified parametric position. 313 * @throws IllegalArgumentException if the grade is < 0 or the parameter values are 314 * invalid. 315 */ 316 @Override 317 public List<Vector<Length>> getTDerivatives(double s, double t, int grade, boolean scaled) { 318 validateSurface(); 319 validateParameter(s, t); 320 if (grade < 0) 321 throw new IllegalArgumentException(RESOURCES.getString("gradeLTZeroErr")); 322 323 // Fit a curve through the points on each defining curve at "s". 324 BasicNurbsCurve tCrv = createTCurve(s); 325 326 // Get the derivatives on the t-curve at the input "t" position. 327 Point origin = this.getRealPoint(s, t); 328 List<Vector<Length>> ders = tCrv.getSDerivatives(t, grade); 329 int size = ders.size(); 330 for (int i = 0; i < size; ++i) { 331 Vector<Length> v = ders.get(i); 332 v.setOrigin(origin); 333 } 334 335 return ders; 336 } 337 338 /** 339 * Calculate the twist vector (d^2P/(ds*dt) = d(dP/ds)/dt) for this surface at the 340 * specified position on this surface. 341 * 342 * @param s 1st parametric dimension distance to calculate twist vector for (0.0 to 343 * 1.0 inclusive). 344 * @param t 2nd parametric dimension distance to calculate twist vector for (0.0 to 345 * 1.0 inclusive). 346 * @return The twist vector of this surface at the specified parametric position. 347 * @throws IllegalArgumentException if the parameter values are invalid. 348 */ 349 @Override 350 public Vector<Length> getTwistVector(double s, double t) { 351 validateSurface(); 352 validateParameter(s, t); 353 354 // Create a point string to hold the derivatives in the s-direction on each defining curve. 355 PointString<Point> sDers = PointString.newInstance(); 356 357 // Get the s-direction derivatives on each of the defining curves at the input "s" position. 358 int size = _crvs.size(); 359 for (int i = 0; i < size; ++i) { 360 Curve crv = _crvs.get(i); 361 Vector der = crv.getSDerivative(s, 1); 362 sDers.add(Point.valueOf(der)); 363 } 364 365 // Fit a curve to the derivative string and find the t-direction derivative at the input "t" position. 366 BasicNurbsCurve tCrv = CurveFactory.fitPoints(_q, sDers); 367 Vector<Length> tvec = tCrv.getSDerivative(t, 1); 368 Point origin = this.getRealPoint(s, t); 369 tvec.setOrigin(origin); 370 371 // Cleanup 372 BasicNurbsCurve.recycle(tCrv); 373 PointString.recycle(sDers); 374 375 return tvec; 376 } 377 378 /** 379 * Return the T=0 Boundary for this surface as a curve. 380 * 381 * @return The T=0 Boundary for this surface as a curve. 382 */ 383 @Override 384 public Curve getT0Curve() { 385 return _crvs.get(0); 386 } 387 388 /** 389 * Return the T=1 Boundary for this surface as a curve. 390 * 391 * @return The T=1 Boundary for this surface as a curve. 392 */ 393 @Override 394 public Curve getT1Curve() { 395 return _crvs.get(_crvs.size() - 1); 396 } 397 398 /** 399 * Return the S=0 Boundary for this surface as a curve. 400 * 401 * @return The S=0 Boundary for this surface as a curve. 402 */ 403 @Override 404 public Curve getS0Curve() { 405 return createTCurve(0); 406 } 407 408 /** 409 * Return the S=1 Boundary for this surface as a curve. 410 * 411 * @return The S=1 Boundary for this surface as a curve. 412 */ 413 @Override 414 public Curve getS1Curve() { 415 return createTCurve(1); 416 } 417 418 /** 419 * Create and return a curve, across the defining curves, at the specified "s" 420 * position. 421 */ 422 private BasicNurbsCurve createTCurve(double s) { 423 424 // Get the points on each of the defining curves at the input "s" position. 425 PointString<Point> sPnts = PointString.newInstance(); 426 int size = _crvs.size(); 427 for (int i = 0; i < size; ++i) { 428 Curve crv = _crvs.get(i); 429 sPnts.add(crv.getRealPoint(s)); 430 } 431 432 // Fit a curve through the points on each defining curve. 433 BasicNurbsCurve tCrv = CurveFactory.fitPoints(_q, sPnts); 434 PointString.recycle(sPnts); 435 436 return tCrv; 437 } 438 439 /** 440 * Return the parameterizations of each defining section in T at the specified "s" 441 * value. The returned array was created using ArrayFactor.DOUBLES_FACTORY and can be 442 * recycled. 443 */ 444 private double[] sectionParams(double s) { 445 446 // Get the points on each of the defining curves at the input "s" position. 447 PointString sPnts = PointString.newInstance(); 448 int size = _crvs.size(); 449 for (int i = 0; i < size; ++i) { 450 Curve crv = _crvs.get(i); 451 sPnts.add(crv.getRealPoint(s)); 452 } 453 454 // Parameterize the string of points. 455 double[] params = CurveFactory.parameterizeString(sPnts); 456 PointString.recycle(sPnts); 457 458 return params; 459 } 460 461 /** 462 * Returns the number of physical dimensions of the geometry element. This 463 * implementation always returns the physical dimension of the underlying 464 * {@link Curve} objects or 0 if this list has no Curve objects in it. 465 * 466 * @return The number of physical dimensions of the geometry element. 467 */ 468 @Override 469 public int getPhyDimension() { 470 if (isEmpty()) 471 return 0; 472 return get(0).getPhyDimension(); 473 } 474 475 /** 476 * Method that guesses the most likely location for the closest or farthest point on a 477 * surface and returns that location as a 2D point containing the "s" and "t" 478 * parameter values. This is called by getClosest() and getFarthest(). This is 479 * required in order for the root-finding algorithm to reliably refine the closest 480 * point to the correct location. 481 * <p> 482 * This implementation finds the closest/farthest point on each member curve and 483 * returns the parametric position of the closest of those. 484 * </p> 485 * 486 * @param point The point to find the closest point on this surface to. May not be 487 * null. 488 * @param closest Set to <code>true</code> to return the closest point or 489 * <code>false</code> to return the farthest point. 490 * @return The 2D parametric point on this surface that is closest to the specified 491 * point. 492 */ 493 @Override 494 protected GeomPoint guessClosestFarthest(GeomPoint point, boolean closest) { 495 requireNonNull(point); 496 if (size() <= 3) 497 return super.guessClosestFarthest(point, closest); 498 499 // Find the closest/furthest point on each member curve to get the parametric s-position. 500 SubrangePoint closestPnt = get(0).getClosest(point, GTOL); 501 double dOpt = point.distanceValue(closestPnt); 502 int size = size(); 503 int crvIdx = 0; 504 for (int i = 1; i < size; ++i) { 505 SubrangePoint p = get(i).getClosest(point, GTOL); 506 double dist = point.distanceValue(p); 507 if (closest) { 508 if (dist < dOpt) { 509 dOpt = dist; 510 closestPnt = p; 511 crvIdx = i; 512 } 513 } else { 514 if (dist > dOpt) { 515 dOpt = dist; 516 closestPnt = p; 517 crvIdx = i; 518 } 519 } 520 } 521 double s = closestPnt.getParPosition().getValue(0); 522 523 // Calculate the parametric t-position of the closest defining section. 524 FastTable<Double> tmp = FastTable.newInstance(); 525 Point pm1 = this.get(0).getRealPoint(s); 526 double d = 0; 527 for (int i = 1; i <= crvIdx; ++i) { 528 Point p = this.get(i).getRealPoint(s); 529 double distV = p.distanceValue(pm1); 530 d += distV; 531 tmp.add(distV); 532 pm1 = p; 533 } 534 535 double t = 0; 536 for (int i = 0; i < crvIdx; ++i) { 537 t += tmp.get(i) / d; 538 } 539 540 return Point.valueOf(s, t); 541 } 542 543 /** 544 * Return the input index normalized into the range 0 ≤ index < size(). This 545 * allows negative indexing (-1 referring to the last element in the list), but does 546 * not allow wrap-around positive indexing. 547 */ 548 private int normalizeIndex(int index) { 549 int size = size(); 550 while (index < 0) 551 index += size; 552 return index; 553 } 554 555 /** 556 * Returns an unmodifiable list view of the curves in this surface. Attempts to modify 557 * the returned collection result in an UnsupportedOperationException being thrown. 558 * 559 * @return the unmodifiable view over this list of curves. 560 */ 561 @Override 562 public List<Curve> unmodifiableList() { 563 return _crvs.unmodifiable(); 564 } 565 566 /** 567 * Returns <code>true</code> if this collection contains no elements. 568 */ 569 @Override 570 public boolean isEmpty() { 571 return _crvs.isEmpty(); 572 } 573 574 /** 575 * Returns <code>true</code> if this list actually contains any curves and 576 * <code>false</code> if this list is empty. 577 * 578 * @return true if this list actually contains geometry. 579 */ 580 @Override 581 public boolean containsGeometry() { 582 return !isEmpty(); 583 } 584 585 /** 586 * Returns the number of elements in this surface (the number of defining curves that 587 * make up this surface). If the surface contains more than Integer.MAX_VALUE 588 * elements, returns Integer.MAX_VALUE. 589 * 590 * @return the number of elements in this list of curves. 591 */ 592 @Override 593 public int size() { 594 return _crvs.size(); 595 } 596 597 /** 598 * Returns the Curve at the specified position in this surface's list of curves. 599 * 600 * @param index index of element to return (0 returns the 1st element, -1 returns the 601 * last, -2 returns the 2nd from last, etc). 602 * @return the Curve at the specified position in this surface. 603 * @throws IndexOutOfBoundsException if the given index is out of range: 604 * <code>index > size()</code> 605 */ 606 @Override 607 public Curve get(int index) { 608 index = normalizeIndex(index); 609 return _crvs.get(index); 610 } 611 612 /** 613 * Returns the first curve from this surface's list of curves. 614 * 615 * @return the first curve in this list. 616 */ 617 @Override 618 public Curve getFirst() { 619 return get(0); 620 } 621 622 /** 623 * Returns the last curve from this surface's list of curves. 624 * 625 * @return the last curve in this list. 626 */ 627 @Override 628 public Curve getLast() { 629 return get(size() - 1); 630 } 631 632 /** 633 * Returns the range of Curves in this surface from the specified start and ending 634 * indexes as a new LoftedSurface. 635 * 636 * @param first index of the first element to return (0 returns the 1st element, -1 637 * returns the last, etc). 638 * @param last index of the last element to return (0 returns the 1st element, -1 639 * returns the last, etc). 640 * @return A LoftedSurface made up of the curves in the given range from this surface. 641 * @throws IndexOutOfBoundsException if the given index is out of range: 642 * <code>index ≥ size()</code> 643 */ 644 @Override 645 public LoftedSurface getRange(int first, int last) { 646 first = normalizeIndex(first); 647 last = normalizeIndex(last); 648 649 LoftedSurface list = LoftedSurface.newInstance(getTDegree()); 650 for (int i = first; i <= last; ++i) 651 list.add(get(i)); 652 653 return list; 654 } 655 656 /** 657 * Returns the {@link Curve} with the specified name from this list. 658 * 659 * @param name The name of the curve we are looking for in the list. 660 * @return The curve matching the specified name. If the specified element name 661 * isn't found in the list, then <code>null</code> is returned. 662 */ 663 @Override 664 public Curve get(String name) { 665 666 Curve element = null; 667 int index = getIndexFromName(name); 668 if (index >= 0) 669 element = this.get(index); 670 671 return element; 672 } 673 674 /** 675 * Returns a view of the portion of this list between fromIndex, inclusive, and 676 * toIndex, exclusive. (If fromIndex and toIndex are equal, the returned list is 677 * empty.) The returned list is backed by this list, so changes in the returned list 678 * are reflected in this list, and vice-versa. The returned list supports all of the 679 * optional list operations supported by this list. 680 * 681 * This method eliminates the need for explicit range operations (of the sort that 682 * commonly exist for arrays). Any operation that expects a list can be used as a 683 * range operation by passing a subList view instead of a whole list. For example, the 684 * following idiom removes a range of values from a list: <code> 685 * list.subList(from, to).clear();</code> Similar idioms may be constructed for 686 * <code>indexOf</code> and <code>lastIndexOf</code>, and all of the algorithms in the 687 * <code>Collections</code> class can be applied to a subList. 688 * 689 * The semantics of the list returned by this method become undefined if the backing 690 * list (i.e., this list) is <i>structurally modified</i> in any way other than via 691 * the returned list (structural modifications are those that change the size of this 692 * list, or otherwise perturb it in such a fashion that iterations in progress may 693 * yield incorrect results). 694 * 695 * @param fromIndex low endpoint (inclusive) of the subList. 696 * @param toIndex high endpoint (exclusive) of the subList. 697 * @return a view of the specified range within this list. 698 * @throws IndexOutOfBoundsException if the given index is out of range: 699 * <code>index > size()</code> 700 */ 701 @Override 702 public List<Curve> subList(int fromIndex, int toIndex) { 703 fromIndex = normalizeIndex(fromIndex); 704 toIndex = normalizeIndex(toIndex); 705 return _crvs.subList(fromIndex, toIndex); 706 } 707 708 /** 709 * Returns an new {@link GeomList} with the elements in this list. 710 * 711 * @return A new GeomList with the elements in this list. 712 */ 713 @Override 714 public GeomList<Curve> getAll() { 715 GeomList<Curve> list = GeomList.newInstance(); 716 list.addAll(this); 717 return list; 718 } 719 720 /** 721 * Returns an new {@link LoftedSurface} with the curves in this surface in reverse 722 * order. This is identical to "reverseT()". 723 * 724 * @return A new LoftedSurface with the curves in this surface in reverse order. 725 * @see #reverseT 726 */ 727 @Override 728 public LoftedSurface reverse() { 729 LoftedSurface list = LoftedSurface.newInstance(_q); 730 copyState(list); 731 int size = this.size(); 732 for (int i = size - 1; i >= 0; --i) { 733 list.add(get(i)); 734 } 735 return list; 736 } 737 738 /** 739 * Returns the index in this list of the first occurrence of the specified element, or 740 * -1 if the list does not contain this element. 741 * 742 * @param element The element to search for. 743 * @return the index in this List of the first occurrence of the specified element, or 744 * -1 if the List does not contain this element. 745 */ 746 @Override 747 public int indexOf(Object element) { 748 return _crvs.indexOf(element); 749 } 750 751 /** 752 * Returns the index in this list of the last occurrence of the specified element, or 753 * -1 if the list does not contain this element. More formally, returns the highest 754 * index i such that (o==null ? get(i)==null : o.equals(get(i))), or -1 if there is no 755 * such index. 756 * 757 * @param element The element to search for. 758 * @return the index in this list of the last occurrence of the specified element, or 759 * -1 if the list does not contain this element. 760 */ 761 @Override 762 public int lastIndexOf(Object element) { 763 return _crvs.lastIndexOf(element); 764 } 765 766 /** 767 * Returns true if this collection contains the specified element. More formally, 768 * returns true if and only if this collection contains at least one element e such 769 * that (o==null ? e==null : o.equals(e)). 770 * 771 * @param o object to be checked for containment in this collection. 772 * @return <code>true</code> if this collection contains the specified element. 773 */ 774 @Override 775 public boolean contains(Object o) { 776 return _crvs.contains(o); 777 } 778 779 /** 780 * Returns true if this collection contains all of the elements in the specified 781 * collection. 782 * 783 * @param c collection to be checked for containment in this collection. 784 * @return <code>true</code> if this collection contains all of the elements in the 785 * specified collection. 786 */ 787 @Override 788 public boolean containsAll(Collection<?> c) { 789 return _crvs.containsAll(c); 790 } 791 792 /** 793 * Return the index to the 1st Curve in this list with the specified name. 794 * 795 * @param name The name of the Curve to find in this list 796 * @return The index to the named Curve or -1 if it is not found. 797 */ 798 @Override 799 public int getIndexFromName(String name) { 800 if (name == null) 801 return -1; 802 803 int result = -1; 804 int size = this.size(); 805 for (int i = 0; i < size; ++i) { 806 GeomElement element = this.get(i); 807 String eName = element.getName(); 808 if (name.equals(eName)) { 809 result = i; 810 break; 811 } 812 } 813 814 return result; 815 } 816 817 /** 818 * Inserts the specified {@link Curve} at the specified position in this list. Shifts 819 * the element currently at that position (if any) and any subsequent elements to the 820 * right (adds one to their indices). Null values are ignored. The input value must 821 * have the same physical dimensions as the other items in this list, or an exception 822 * is thrown. 823 * <p> 824 * Note: If this method is used concurrent access must be synchronized (the table is 825 * not thread-safe). 826 * </p> 827 * 828 * @param index the index at which the specified element is to be inserted. (0 returns 829 * the 1st element, -1 returns the last, -2 returns the 2nd from last, 830 * etc). 831 * @param value the element to be inserted. May not be null. 832 * @throws IndexOutOfBoundsException if <code>index > size()</code> 833 * @throws DimensionException if the input value dimensions are different from this 834 * list's dimensions. 835 */ 836 @Override 837 @SuppressWarnings("null") 838 public void add(int index, Curve value) { 839 if (!isEmpty() && value.getPhyDimension() != getPhyDimension()) 840 throw new DimensionException(MessageFormat.format( 841 RESOURCES.getString("incCrvDimension"), "curve", getPhyDimension())); 842 index = normalizeIndex(index); 843 844 _crvs.add(index, value); 845 if (!(value instanceof Immutable) && _childChangeListener != null) 846 value.addChangeListener(_childChangeListener); 847 848 fireChangeEvent(); // Notify change listeners. 849 } 850 851 /** 852 * Appends the specified {@link Curve} to the end of this surface's list of curves. 853 * Null values are ignored. The input value must have the same physical dimensions as 854 * the other items in this list, or an exception is thrown. 855 * <p> 856 * Note: If this method is used concurrent access must be synchronized (the table is 857 * not thread-safe). 858 * </p> 859 * 860 * @param value the curve to be appended to this list. May not be null. 861 * @throws DimensionException if the input element's dimensions are different from 862 * this list's dimensions. 863 */ 864 @Override 865 public boolean add(Curve value) { 866 add(size(), value); 867 return true; 868 } 869 870 /** 871 * Appends all of the elements in the specified list of arguments to this geometry 872 * element list. The input values must have the same physical dimensions as the other 873 * items in this list, or an exception is thrown. 874 * 875 * @param array elements to be inserted into this collection. May not be null. 876 * @return <code>true</code> if this collection changed as a result of the call. 877 * @throws DimensionException if the input element's dimensions are different from 878 * this list's dimensions. 879 */ 880 @Override 881 public boolean add(Curve... array) { 882 return add(size(), array); 883 } 884 885 /** 886 * Inserts all of the {@link Curve} objects in the specified list of arguments into 887 * this list at the specified position. Shifts the element currently at that position 888 * (if any) and any subsequent elements to the right (increases their indices). The 889 * new elements will appear in this list in the order that they are appeared in the 890 * array. The input values must have the same physical dimensions as the other items 891 * in this list, or an exception is thrown. 892 * 893 * @param index index at which to insert first element from the specified array. 894 * @param array elements to be inserted into this collection. May not be null. 895 * @return <code>true</code> if this collection changed as a result of the call. 896 * @throws DimensionException if the input element's dimensions are different from 897 * this list's dimensions. 898 */ 899 @Override 900 public boolean add(int index, Curve... array) { 901 return addAll(index, Arrays.asList(requireNonNull(array))); 902 } 903 904 /** 905 * Adds all of the curves in the specified collection to this surface. The behavior of 906 * this operation is undefined if the specified collection is modified while the 907 * operation is in progress. This implies that the behavior of this call is undefined 908 * if the specified collection is this collection, and this collection is nonempty. 909 * The input elements must have the same physical dimensions as the other items in 910 * this list, or an exception is thrown. 911 * 912 * @param c curves to be inserted into this surface. May not be null. 913 * @return <code>true</code> if this surface changed as a result of the call 914 * @throws DimensionException if the input element's dimensions are different from 915 * this list's dimensions. 916 */ 917 @Override 918 public boolean addAll(Collection<? extends Curve> c) { 919 return addAll(size(), c); 920 } 921 922 /** 923 * Inserts all of the curves in the specified collection into this surface at the 924 * specified position. Shifts the curve currently at that position (if any) and any 925 * subsequent curves to the right (increases their indices). The new curves will 926 * appear in this list in the order that they are returned by the specified 927 * collection's iterator. The behavior of this operation is unspecified if the 928 * specified collection is modified while the operation is in progress. Note that 929 * this will occur if the specified collection is this list, and it's nonempty. 930 * The input elements must have the same physical dimensions as the other items in 931 * this list, or an exception is thrown. 932 * 933 * @param index index at which to insert first curve from the specified collection. 934 * @param c curves to be inserted into this collection. May not be null. 935 * @return <code>true</code> if this surface changed as a result of the call 936 * @throws DimensionException if the input element's dimensions are different from 937 * this list's dimensions. 938 */ 939 @Override 940 @SuppressWarnings("null") 941 public boolean addAll(int index, Collection<? extends Curve> c) { 942 requireNonNull(c); 943 if (!isEmpty()) { 944 int dim = getPhyDimension(); 945 for (Curve crv : c) { 946 requireNonNull(crv, RESOURCES.getString("collectionElementsNullErr")); 947 if (crv.getPhyDimension() != dim) 948 throw new DimensionException(MessageFormat.format( 949 RESOURCES.getString("incCrvDimension"),"curves", getPhyDimension())); 950 } 951 } 952 index = normalizeIndex(index); 953 boolean changed = _crvs.addAll(index, c); 954 if (changed) { 955 for (Curve crv : c) { 956 if (!(crv instanceof Immutable) && _childChangeListener != null) 957 crv.addChangeListener(_childChangeListener); 958 } 959 fireChangeEvent(); // Notify change listeners. 960 } 961 return changed; 962 } 963 964 /** 965 * Appends all of the curves in the specified array to this LoftedSurface. The 966 * behavior of this operation is undefined if the specified collection is modified 967 * while the operation is in progress. The input array elements must have the same 968 * physical dimensions as the other items in this list, or an exception is thrown. 969 * 970 * @param arr curves to be appended onto this collection. May not be null. 971 * @return <code>true</code> if this collection changed as a result of the call. 972 * @throws DimensionException if the input element's dimensions are different from 973 * this list's dimensions. 974 */ 975 @Override 976 public boolean addAll(Curve[] arr) { 977 return addAll(size(), arr); 978 } 979 980 /** 981 * Inserts all of the {@link Curve} objects in the specified array into this list at 982 * the specified position. Shifts the element currently at that position (if any) and 983 * any subsequent elements to the right (increases their indices). The new elements 984 * will appear in this list in the order that they are returned by the specified 985 * collection's iterator. The input array elements must have the same physical 986 * dimensions as the other items in this list, or an exception is thrown. 987 * 988 * @param index index at which to insert first element from the specified array. 989 * @param arr elements to be inserted into this collection. May not be null. 990 * @return <code>true</code> if this collection changed as a result of the call. 991 * @throws DimensionException if the input element's dimensions are different from 992 * this list's dimensions. 993 */ 994 @Override 995 public boolean addAll(int index, Curve[] arr) { 996 return addAll(index, Arrays.asList(requireNonNull(arr))); 997 } 998 999 /** 1000 * Replaces the Curve at the specified position in this surface's list of 1001 * curves with the specified element. Null elements are ignored. The input element 1002 * must have the same physical dimensions as the other items in this list, or an 1003 * exception is thrown. 1004 * 1005 * @param index The index of the element to replace (0 returns the 1st element, -1 1006 * returns the last, -2 returns the 2nd from last, etc). 1007 * @param element The curve to be stored at the specified position. May not be null. 1008 * @return The curve previously at the specified position in this list. 1009 * @throws java.lang.IndexOutOfBoundsException - if <code>index > size()</code> 1010 * @throws DimensionException if the input element's dimensions are different from 1011 * this list's dimensions. 1012 */ 1013 @Override 1014 @SuppressWarnings("null") 1015 public Curve set(int index, Curve element) { 1016 if (!isEmpty() && element.getPhyDimension() != getPhyDimension()) 1017 throw new DimensionException(MessageFormat.format( 1018 RESOURCES.getString("incCrvDimension"), "curve", getPhyDimension())); 1019 index = normalizeIndex(index); 1020 1021 Curve old = _crvs.set(index, element); 1022 if (!(old instanceof Immutable) && _childChangeListener != null) 1023 old.removeChangeListener(_childChangeListener); 1024 if (!(element instanceof Immutable) && _childChangeListener != null) 1025 element.addChangeListener(_childChangeListener); 1026 1027 fireChangeEvent(); // Notify change listeners. 1028 1029 return old; 1030 } 1031 1032 /** 1033 * Removes from this list all the elements that are contained in the specified 1034 * collection. 1035 * 1036 * @param c collection that defines which elements will be removed from this list. May 1037 * not be null. 1038 * @return <code>true</code> if this list changed as a result of the call. 1039 */ 1040 @Override 1041 public boolean removeAll(Collection<?> c) { 1042 boolean modified = false; 1043 Iterator<?> it = iterator(); 1044 while (it.hasNext()) { 1045 if (c.contains(it.next())) { 1046 it.remove(); 1047 modified = true; 1048 } 1049 } 1050 return modified; 1051 } 1052 1053 /** 1054 * Retains only the elements in this list that are contained in the specified 1055 * collection. In other words, removes from this list all the elements that are not 1056 * contained in the specified collection. 1057 * 1058 * @param c collection that defines which elements this set will retain. May not be 1059 * null. 1060 * @return <code>true</code> if this list changed as a result of the call. 1061 */ 1062 @Override 1063 public boolean retainAll(Collection<?> c) { 1064 boolean modified = false; 1065 Iterator<Curve> it = iterator(); 1066 while (it.hasNext()) { 1067 if (!c.contains(it.next())) { 1068 it.remove(); 1069 modified = true; 1070 } 1071 } 1072 return modified; 1073 } 1074 1075 /** 1076 * Removes a single instance of the specified element from this collection, if it is 1077 * present. More formally, removes an element e such that (o==null ? e==null : 1078 * o.equals(e)), if this collection contains one or more such elements. Returns true 1079 * if this collection contained the specified element (or equivalently, if this 1080 * collection changed as a result of the call). 1081 * 1082 * @param o element to be removed from this collection, if present. 1083 * @return <code>true</code> if this collection changed as a result of the call 1084 */ 1085 @Override 1086 public boolean remove(Object o) { 1087 boolean changed = _crvs.remove(o); 1088 if (changed) { 1089 if (o instanceof AbstractGeomElement && !(o instanceof Immutable)) { 1090 ((AbstractGeomElement)o).removeChangeListener(_childChangeListener); 1091 } 1092 fireChangeEvent(); 1093 } 1094 return changed; 1095 } 1096 1097 /** 1098 * Removes the curve at the specified position in this surface's list of curves. 1099 * Shifts any subsequent curves to the left (subtracts one from their indices). 1100 * Returns the curve that was removed from the list. 1101 * 1102 * @param index the index of the curve to remove. (0 returns the 1st element, -1 1103 * returns the last, -2 returns the 2nd from last, etc). 1104 * @return the curve previously at the specified position. 1105 */ 1106 @Override 1107 @SuppressWarnings("null") 1108 public Curve remove(int index) { 1109 index = normalizeIndex(index); 1110 Curve old = _crvs.remove(index); 1111 if (!(old instanceof Immutable) && _childChangeListener != null) 1112 old.removeChangeListener(_childChangeListener); 1113 fireChangeEvent(); // Notify change listeners. 1114 return old; 1115 } 1116 1117 /** 1118 * Removes the curve with the specified name from this list. Shifts any subsequent 1119 * elements to the left (subtracts one from their indices). Returns the element that 1120 * was removed from the list. 1121 * 1122 * @param name the name of the element to remove. 1123 * @return the element previously at the specified position. 1124 */ 1125 @Override 1126 public Curve remove(String name) { 1127 1128 Curve element = null; 1129 int index = getIndexFromName(name); 1130 if (index >= 0) 1131 element = this.remove(index); 1132 1133 return element; 1134 } 1135 1136 /** 1137 * Removes all of the curves from this surface. The surface will be empty and 1138 * undefined after this call returns. 1139 */ 1140 @Override 1141 @SuppressWarnings("null") 1142 public void clear() { 1143 int size = _crvs.size(); 1144 for (int i = 0; i < size; ++i) { 1145 Curve crv = _crvs.get(i); 1146 if (!(crv instanceof Immutable) && _childChangeListener != null) 1147 crv.removeChangeListener(_childChangeListener); 1148 } 1149 _crvs.clear(); 1150 fireChangeEvent(); // Notify change listeners. 1151 } 1152 1153 /** 1154 * Returns an iterator over the curves in this surface's list of curves. 1155 * 1156 * @return an iterator over this list values. 1157 */ 1158 @Override 1159 public java.util.Iterator<Curve> iterator() { 1160 return _crvs.iterator(); 1161 } 1162 1163 /** 1164 * Returns a list iterator over the curves in this surface. 1165 * 1166 * @return an iterator over this list values. 1167 */ 1168 @Override 1169 public java.util.ListIterator<Curve> listIterator() { 1170 return _crvs.listIterator(); 1171 } 1172 1173 /** 1174 * Returns a list iterator from the specified position. 1175 * 1176 * @param index the index of first value to be returned from the list iterator (by a 1177 * call to the next method). 1178 * @return a list iterator of the values in this list starting at the specified 1179 * position in this list. 1180 */ 1181 @Override 1182 public java.util.ListIterator<Curve> listIterator(int index) { 1183 return _crvs.listIterator(index); 1184 } 1185 1186 /** 1187 * Returns an array containing all of the elements in this collection. 1188 */ 1189 @Override 1190 public Curve[] toArray() { 1191 return (Curve[])_crvs.toArray(); 1192 } 1193 1194 /** 1195 * Returns an array containing all of the elements in this collection. If the 1196 * collection fits in the specified array, it is returned therein. Otherwise, a new 1197 * array is allocated with the runtime type of the specified array and the size of 1198 * this collection. 1199 * 1200 * @param <T> The type of elements in this LoftedSurface (curve type). 1201 * @param a the array into which the elements of the collection are to be stored, if 1202 * it is big enough; otherwise, a new array of the same type is allocated 1203 * for this purpose. 1204 * @return an array containing the elements of the collection. 1205 */ 1206 @Override 1207 @SuppressWarnings("SuspiciousToArrayCall") 1208 public <T> T[] toArray(T[] a) { 1209 return _crvs.toArray(a); 1210 } 1211 1212 /** 1213 * Return a new surface that is identical to this one, but with the T-parameterization 1214 * reversed. This is identical to "reverse()". 1215 * 1216 * @return A new surface that is identical to this one, but with the 1217 * T-parameterization reversed. 1218 * @see #reverse 1219 * @see #reverseS 1220 */ 1221 @Override 1222 public LoftedSurface reverseT() { 1223 return reverse(); 1224 } 1225 1226 /** 1227 * Return a new surface that is identical to this one, but with the S-parameterization 1228 * reversed. 1229 * 1230 * @return A new surface that is identical to this one, but with the 1231 * S-parameterization reversed. 1232 * @see #reverseT 1233 */ 1234 @Override 1235 public LoftedSurface reverseS() { 1236 LoftedSurface list = LoftedSurface.newInstance(_q); 1237 copyState(list); 1238 int size = this.size(); 1239 for (int i = 0; i < size; ++i) { 1240 Curve crv = this.get(i); 1241 list.add(crv.reverse()); 1242 } 1243 return list; 1244 } 1245 1246 /** 1247 * Return a new surface that is identical to this one but with the transpose of the 1248 * parameterization of this surface. The S and T directions will be swapped. 1249 * 1250 * @return A new surface that is identical to this one but with the transpose of the 1251 * parameterization of this surface. 1252 * @see #reverseT 1253 * @see #reverseS 1254 */ 1255 @Override 1256 public LoftedSurface transpose() { 1257 LoftedSurface srf = reverseS().reverse(); 1258 return srf; 1259 } 1260 1261 /** 1262 * Split this {@link LoftedSurface} at the specified parametric S-position returning a 1263 * list containing two new surfaces (a lower surface with smaller S-parametric 1264 * positions than "s" and an upper surface with larger S-parametric positions). This 1265 * method splits all the defining section curves to create two new lofted surfaces. 1266 * 1267 * @param s The S-parametric position where this surface should be split (must not be 1268 * 0 or 1!). 1269 * @return A list containing two surfaces: 0 == the lower surface, 1 == the upper 1270 * surface. 1271 */ 1272 @Override 1273 public GeomList<LoftedSurface> splitAtS(double s) { 1274 validateSurface(); 1275 validateParameter(s, 0); 1276 if (parNearEnds(s, TOL_ST)) 1277 throw new IllegalArgumentException(MessageFormat.format( 1278 RESOURCES.getString("canNotSplitAtEnds"), "surface")); 1279 1280 LoftedSurface srfL = LoftedSurface.newInstance(_q); 1281 copyState(srfL); 1282 LoftedSurface srfU = LoftedSurface.newInstance(_q); 1283 copyState(srfU); 1284 1285 // Split each defining section. 1286 int size = this.size(); 1287 for (int i = 0; i < size; ++i) { 1288 Curve crv = this.get(i); 1289 GeomList<Curve> crvs = crv.splitAt(s); 1290 srfL.add(crvs.get(0)); 1291 srfU.add(crvs.get(1)); 1292 GeomList.recycle(crvs); 1293 } 1294 1295 // Create the output list. 1296 GeomList<LoftedSurface> output = GeomList.newInstance(); 1297 output.add(srfL, srfU); 1298 1299 return output; 1300 } 1301 1302 /** 1303 * Split this surface at the specified parametric T-position returning a list 1304 * containing two new surfaces (a lower surface with smaller T-parametric positions 1305 * than "t" and an upper surface with larger T-parametric positions). This method 1306 * interpolates a new defining section at "t" and uses the defining sections below it 1307 * and above it to create two new lofted surfaces. 1308 * 1309 * @param t The T-parametric position where this surface should be split (must not be 1310 * 0 or 1!). 1311 * @return A list containing two surfaces: 0 == the lower surface, 1 == the upper 1312 * surface. 1313 */ 1314 @Override 1315 @SuppressWarnings("null") 1316 public GeomList<LoftedSurface> splitAtT(double t) { 1317 validateSurface(); 1318 validateParameter(0, t); 1319 if (parNearEnds(t, TOL_ST)) 1320 throw new IllegalArgumentException(MessageFormat.format( 1321 RESOURCES.getString("canNotSplitAtEnds"), "surface")); 1322 1323 // Determine the parameterizations of the existing defining sections in T. 1324 double[] tParams = sectionParams(0); 1325 1326 // Find or create the split curve and find the split index. 1327 Curve splitCrv = null; 1328 int numCrvs = size(); 1329 int i = 0; 1330 for (; i < numCrvs; ++i) { 1331 Curve crv = get(i); 1332 double ct = tParams[i]; 1333 1334 // Check for an exact match to the split parameter. 1335 if (MathTools.isApproxEqual(t, ct)) { 1336 --i; 1337 splitCrv = crv; 1338 break; 1339 } 1340 1341 // Look for a parameter that just exceeds the split parameter. 1342 if (ct > t) { 1343 --i; 1344 1345 // Create an interpolating curve on this surface at t. 1346 Point p0 = Point.valueOf(0, t); 1347 Point p1 = Point.valueOf(1, t); 1348 splitCrv = SubrangeCurve.newInstance(this, CurveFactory.createLine(p0, p1)); 1349 break; 1350 } 1351 } 1352 ArrayFactory.DOUBLES_FACTORY.recycle(tParams); 1353 1354 // Create the lower surface. 1355 LoftedSurface srfL = LoftedSurface.valueOf(_q, getRange(0, i)); 1356 srfL.add(splitCrv); 1357 1358 // Create the upper surface. 1359 LoftedSurface srfU = LoftedSurface.newInstance(_q); 1360 srfU.add(splitCrv.copy()); 1361 srfU.addAll(getRange(i + 1, -1)); 1362 1363 // Create the output list. 1364 GeomList<LoftedSurface> output = GeomList.newInstance(); 1365 output.add(srfL, srfU); 1366 1367 return output; 1368 } 1369 1370 /** 1371 * Return a NURBS surface representation of this surface to within the specified 1372 * tolerance. If the curves making up this surface are NURBS curves, then the 1373 * resulting NURBS surface is an exact representation of this surface (and tol is 1374 * ignored). However, if any of the member curves are not NURBS curves, then tol is 1375 * used to make a NURBS approximation of that curve (and this surface). 1376 * 1377 * @param tol The greatest possible difference between this surface and the NURBS 1378 * representation returned. May not be null. 1379 * @return A NURBS surface that represents this surface to within the specified 1380 * tolerance. 1381 */ 1382 @Override 1383 public NurbsSurface toNurbs(Parameter<Length> tol) { 1384 validateSurface(); 1385 1386 // Go off and create a skinned NURBS surface. 1387 BasicNurbsSurface srf = SurfaceFactory.createSkinnedSurface(_crvs, _q, requireNonNull(tol)); 1388 copyState(srf); // Copy over the super-class state for this surface to the new one. 1389 1390 return srf; 1391 } 1392 1393 /** 1394 * Return <code>true</code> if this LoftedSurface contains valid and finite numerical 1395 * components. A value of <code>false</code> will be returned if any of the member 1396 * curves are not valid. 1397 * 1398 * @return true if this LoftedSurface contains valid and finite numerical components. 1399 */ 1400 @Override 1401 public boolean isValid() { 1402 // Make sure there are sufficient member curves. 1403 if (size() < _q + 1) 1404 return false; 1405 1406 // Make sure all the member curves are valid. 1407 int size = this.size(); 1408 for (int i = 0; i < size; ++i) { 1409 Curve crv = this.get(i); 1410 if (!crv.isValid()) 1411 return false; 1412 } 1413 1414 return true; 1415 } 1416 1417 /** 1418 * Return <code>true</code> if this surface is degenerate (i.e.: has area less than 1419 * the specified tolerance squared). 1420 * 1421 * @param tol The tolerance for determining if this surface is degenerate. May not be 1422 * null. 1423 * @return true if this surface is degenerate (i.e.: has area less than the specified 1424 * tolerance squared). 1425 */ 1426 @Override 1427 public boolean isDegenerate(Parameter<Length> tol) { 1428 requireNonNull(tol); 1429 1430 // A lofted surface is degenerate if all of it's member curves are degenerate. 1431 int size = this.size(); 1432 for (int i = 0; i < size; ++i) { 1433 Curve crv = this.get(i); 1434 if (!crv.isDegenerate(tol)) 1435 return false; 1436 } 1437 return true; 1438 } 1439 1440 /** 1441 * Returns a copy of this <code>LoftedSurface</code> instance 1442 * {@link javolution.context.AllocatorContext allocated} by the calling thread 1443 * (possibly on the stack). 1444 * 1445 * @return an identical and independent copy of this object. 1446 */ 1447 @Override 1448 public LoftedSurface copy() { 1449 return copyOf(this); 1450 } 1451 1452 /** 1453 * Return a copy of this object with any transformations or subranges removed 1454 * (applied). 1455 * 1456 * @return A copy of this object with any transformations or subranges removed. 1457 */ 1458 @Override 1459 public LoftedSurface copyToReal() { 1460 LoftedSurface newSrf = LoftedSurface.newInstance(_q); 1461 copyState(newSrf); 1462 1463 int size = this.size(); 1464 for (int i = 0; i < size; ++i) { 1465 Curve crv = this.get(i); 1466 Curve crv2 = crv.copyToReal(); 1467 newSrf.add(crv2); 1468 } 1469 1470 return newSrf; 1471 } 1472 1473 /** 1474 * Returns a transformed version of this element. The returned list of objects 1475 * implement {@link GeomTransform} and contains transformed versions of the contents 1476 * of this list as children. 1477 * 1478 * @param transform The transformation to apply to this geometry. May not be null. 1479 * @return A new LoftedSurface that is identical to this one with the specified 1480 * transformation applied to member curves. 1481 * @throws DimensionException if this surface is not 3D. 1482 */ 1483 @Override 1484 public LoftedSurface getTransformed(GTransform transform) { 1485 requireNonNull(transform); 1486 LoftedSurface list = LoftedSurface.newInstance(getTDegree()); 1487 copyState(list); 1488 1489 int size = _crvs.size(); 1490 for (int i = 0; i < size; ++i) { 1491 Curve element = _crvs.get(i); 1492 list._crvs.add((Curve)element.getTransformed(transform)); 1493 } 1494 return list; 1495 } 1496 1497 /** 1498 * Return the coordinate point representing the minimum bounding box corner of this 1499 * geometry element (e.g.: min X, min Y, min Z). 1500 * 1501 * @return The minimum bounding box coordinate for this geometry element. 1502 * @throws IndexOutOfBoundsException if this list contains no geometry. 1503 */ 1504 @Override 1505 public Point getBoundsMin() { 1506 if (isEmpty()) 1507 throw new IndexOutOfBoundsException(RESOURCES.getString("listNoGeometry")); 1508 1509 Point minPoint = _crvs.get(0).getBoundsMin(); 1510 int size = _crvs.size(); 1511 for (int i = 1; i < size; ++i) { 1512 GeomElement element = _crvs.get(i); 1513 minPoint = minPoint.min(element.getBoundsMin()); 1514 } 1515 1516 return minPoint; 1517 } 1518 1519 /** 1520 * Return the coordinate point representing the maximum bounding box corner (e.g.: max 1521 * X, max Y, max Z). 1522 * 1523 * @return The maximum bounding box coordinate for this geometry element. 1524 * @throws IndexOutOfBoundsException if this list contains no elements. 1525 */ 1526 @Override 1527 public Point getBoundsMax() { 1528 if (isEmpty()) 1529 throw new IndexOutOfBoundsException(RESOURCES.getString("listNoGeometry")); 1530 1531 Point maxPoint = _crvs.get(0).getBoundsMax(); 1532 int size = _crvs.size(); 1533 for (int i = 1; i < size; ++i) { 1534 GeomElement element = _crvs.get(i); 1535 maxPoint = maxPoint.max(element.getBoundsMax()); 1536 } 1537 1538 return maxPoint; 1539 } 1540 1541 /** 1542 * Returns the unit in which the <I>first</I> curve in this list is stated. If the 1543 * list contains no curves, then the default unit is returned. 1544 * 1545 * @return The unit in which the first curve in this surface is stated or the default 1546 * unit if this surface has no defining curves. 1547 */ 1548 @Override 1549 public Unit<Length> getUnit() { 1550 if (isEmpty()) 1551 return GeomUtil.getDefaultUnit(); 1552 1553 return _crvs.get(0).getUnit(); 1554 } 1555 1556 /** 1557 * Returns the equivalent to this surface but stated in the specified unit. 1558 * 1559 * @param unit the length unit of the surface to be returned. May not be null. 1560 * @return an equivalent to this surface but stated in the specified unit. 1561 * @throws ConversionException if the the input unit is not a length unit. 1562 */ 1563 @Override 1564 public LoftedSurface to(Unit<Length> unit) throws ConversionException { 1565 if (unit.equals(getUnit())) 1566 return this; 1567 1568 // Convert the curves. 1569 FastTable<Curve> nCrvs = FastTable.newInstance(); 1570 int size = _crvs.size(); 1571 for (int i = 0; i < size; ++i) { 1572 Curve crv = _crvs.get(i); 1573 nCrvs.add(crv.to(unit)); 1574 } 1575 LoftedSurface srf = LoftedSurface.valueOf(_q, nCrvs); 1576 copyState(srf); 1577 1578 return srf; 1579 } 1580 1581 /** 1582 * Return the equivalent of this surface converted to the specified number of physical 1583 * dimensions. If the number of dimensions is greater than this element, then zeros 1584 * are added to the additional dimensions. If the number of dimensions is less than 1585 * this element, then the extra dimensions are simply dropped (truncated). If the new 1586 * dimensions are the same as the dimension of this element, then this element is 1587 * simply returned. 1588 * 1589 * @param newDim The dimension of the surface to return. 1590 * @return This surface converted to the new dimensions. 1591 */ 1592 @Override 1593 public LoftedSurface toDimension(int newDim) { 1594 if (getPhyDimension() == newDim) 1595 return this; 1596 1597 // Convert the curves. 1598 FastTable<Curve> nCrvs = FastTable.newInstance(); 1599 int size = _crvs.size(); 1600 for (int i = 0; i < size; ++i) { 1601 Curve crv = _crvs.get(i); 1602 nCrvs.add(crv.toDimension(newDim)); 1603 } 1604 LoftedSurface srf = LoftedSurface.valueOf(_q, nCrvs); 1605 copyState(srf); 1606 1607 return srf; 1608 } 1609 1610 /** 1611 * Compares the specified object with this list of <code>Curve</code> objects for 1612 * equality. Returns true if and only if both collections are of the same type and 1613 * both collections contain equal elements in the same order. 1614 * 1615 * @param obj the object to compare with. 1616 * @return <code>true</code> if this list is identical to that list; 1617 * <code>false</code> otherwise. 1618 */ 1619 @Override 1620 public boolean equals(Object obj) { 1621 if (this == obj) 1622 return true; 1623 if ((obj == null) || (obj.getClass() != this.getClass())) 1624 return false; 1625 1626 LoftedSurface that = (LoftedSurface)obj; 1627 return this._crvs.equals(that._crvs) 1628 && super.equals(obj); 1629 } 1630 1631 /** 1632 * Returns the hash code for this <code>LoftedSurface</code>. 1633 * 1634 * @return the hash code value. 1635 */ 1636 @Override 1637 public int hashCode() { 1638 return 31*super.hashCode() + Objects.hash(_crvs); 1639 } 1640 1641 /** 1642 * Returns the text representation of this geometry element. 1643 * 1644 * @return The text representation of this geometry element. 1645 */ 1646 @Override 1647 public Text toText() { 1648 TextBuilder tmp = TextBuilder.newInstance(); 1649 String className = this.getClass().getName(); 1650 tmp.append(className.substring(className.lastIndexOf(".") + 1)); 1651 tmp.append(": {"); 1652 String nameStr = getName(); 1653 boolean hasName = nonNull(nameStr); 1654 if (hasName) { 1655 tmp.append(nameStr); 1656 tmp.append(" = {"); 1657 } 1658 tmp.append("\n"); 1659 int size = this.size(); 1660 for (int i = 0; i < size; ++i) { 1661 GeomElement e = this.get(i); 1662 tmp.append(e.toText()); 1663 if (i < size - 1) 1664 tmp.append(",\n"); 1665 else 1666 tmp.append("\n"); 1667 } 1668 if (hasName) 1669 tmp.append('}'); 1670 tmp.append("}"); 1671 Text txt = tmp.toText(); 1672 TextBuilder.recycle(tmp); 1673 return txt; 1674 } 1675 1676 /** 1677 * Resets the internal state of this object to its default values. Subclasses that 1678 * override this method must call <code>super.reset();</code> to ensure that the state 1679 * is reset properly. 1680 */ 1681 @Override 1682 public void reset() { 1683 clear(); 1684 _crvs.reset(); 1685 super.reset(); 1686 } 1687 1688 /** 1689 * Holds the default XML representation for this object. 1690 */ 1691 @SuppressWarnings("FieldNameHidesFieldInSuperclass") 1692 protected static final XMLFormat<LoftedSurface> XML = new XMLFormat<LoftedSurface>(LoftedSurface.class) { 1693 1694 @Override 1695 public LoftedSurface newInstance(Class<LoftedSurface> cls, XMLFormat.InputElement xml) throws XMLStreamException { 1696 LoftedSurface obj = FACTORY.object(); 1697 return obj; 1698 } 1699 1700 @Override 1701 public void read(XMLFormat.InputElement xml, LoftedSurface obj) throws XMLStreamException { 1702 int q = Integer.valueOf(xml.getAttribute("q").toString()); 1703 obj._q = q; 1704 1705 AbstractSurface.XML.read(xml, obj); // Call parent read. 1706 1707 FastTable<Curve> lst = xml.get("Contents", FastTable.class); 1708 obj._crvs = lst; 1709 } 1710 1711 @Override 1712 public void write(LoftedSurface obj, XMLFormat.OutputElement xml) throws XMLStreamException { 1713 xml.setAttribute("q", Integer.toString(obj._q)); 1714 1715 AbstractSurface.XML.write(obj, xml); // Call parent write. 1716 1717 xml.add(obj._crvs, "Contents", FastTable.class); 1718 } 1719 }; 1720 1721 ////////////////////// 1722 // Factory Creation // 1723 ////////////////////// 1724 /** 1725 * Do not allow the default constructor to be used except by subclasses. 1726 */ 1727 protected LoftedSurface() { } 1728 1729 private static final ObjectFactory<LoftedSurface> FACTORY = new ObjectFactory<LoftedSurface>() { 1730 @Override 1731 protected LoftedSurface create() { 1732 return new LoftedSurface(); 1733 } 1734 1735 @Override 1736 protected void cleanup(LoftedSurface obj) { 1737 obj.reset(); 1738 obj._crvs = null; 1739 } 1740 }; 1741 1742 /** 1743 * Recycles a case instance immediately (on the stack when executing in a 1744 * StackContext). 1745 * 1746 * @param instance The instance to be recycled. 1747 */ 1748 public static void recycle(LoftedSurface instance) { 1749 FACTORY.recycle(instance); 1750 } 1751 1752 @SuppressWarnings("unchecked") 1753 private static LoftedSurface copyOf(LoftedSurface original) { 1754 LoftedSurface obj = FACTORY.object(); 1755 obj._crvs = FastTable.newInstance(); 1756 obj._q = original._q; 1757 1758 int size = original.size(); 1759 for (int i = 0; i < size; ++i) { 1760 Curve crv = original.get(i); 1761 crv = crv.copy(); 1762 obj.add(crv); 1763 } 1764 1765 original.copyState(obj); 1766 return obj; 1767 } 1768 1769}