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