001/** 002 * AbstractCurve -- Represents the implementation in common to all curves in nD space. 003 * 004 * Copyright (C) 2009-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 jahuwaldt.js.param.Parameter; 021import jahuwaldt.tools.math.*; 022import static jahuwaldt.tools.math.MathTools.isApproxEqual; 023import java.text.MessageFormat; 024import java.util.Collections; 025import java.util.Comparator; 026import java.util.List; 027import static java.util.Objects.isNull; 028import static java.util.Objects.requireNonNull; 029import java.util.logging.Level; 030import java.util.logging.Logger; 031import javax.measure.quantity.Area; 032import javax.measure.quantity.Dimensionless; 033import javax.measure.quantity.Length; 034import javax.measure.unit.SI; 035import javax.measure.unit.Unit; 036import javolution.context.ObjectFactory; 037import javolution.context.StackContext; 038import javolution.lang.MathLib; 039import javolution.util.FastList; 040import javolution.util.FastTable; 041 042/** 043 * The interface and implementation in common to all curves. 044 * 045 * <p> Modified by: Joseph A. Huwaldt </p> 046 * 047 * @author Joseph A. Huwaldt, Date: May 15, 2009 048 * @version February 18, 2025 049 * 050 * @param <T> The sub-type of this AbstractCurve. 051 */ 052@SuppressWarnings({"serial", "CloneableImplementsClone"}) 053public abstract class AbstractCurve<T extends AbstractCurve> extends AbstractGeomElement<T> implements Curve<T> { 054 055 /* 056 * Reference 1: Michael E. Mortenson, "Geometric Modeling, Third Edition", ISBN:9780831132989, 2008. 057 */ 058 059 /** 060 * Generic/default tolerance for use in root finders, etc. 061 */ 062 public static final double GTOL = 1e-6; 063 064 /** 065 * Tolerance allowed on parametric spacing being == 0 or == 1. 066 */ 067 protected static final double TOL_S = Parameter.EPS; 068 069 /** 070 * Returns the number of parametric dimensions of the geometry element. This 071 * implementation always returns 1. 072 */ 073 @Override 074 public int getParDimension() { 075 return 1; 076 } 077 078 /** 079 * Checks the input parameter for validity (in the range 0 to 1 for example) and 080 * throws an exception if it is not valid. 081 * 082 * @param s parametric distance (0.0 to 1.0 inclusive). 083 * @throws IllegalArgumentException if there is any problem with the parameter value. 084 */ 085 protected void validateParameter(double s) { 086 if (s < -TOL_S || s > 1 + TOL_S) 087 throw new IllegalArgumentException( 088 MessageFormat.format(RESOURCES.getString("crvInvalidSValue"), s)); 089 } 090 091 /** 092 * Checks the input parameter for validity (in the range 0 to 1 for example) and 093 * throws an exception if it is not valid. 094 * 095 * @param s parametric distance. Must be a 1-dimensional point with a value in the 096 * range 0 to 1.0. Units are ignored. 097 * @throws IllegalArgumentException if there is any problem with the parameter value. 098 */ 099 protected void validateParameter(GeomPoint s) { 100 if (isNull(s) || s.getPhyDimension() != 1) 101 throw new IllegalArgumentException(MessageFormat.format( 102 RESOURCES.getString("invalidParamDim"), "Curve", 1)); 103 validateParameter(s.getValue(0)); 104 } 105 106 /** 107 * If the input parameter is within tol of 0 or 1, or outside the bounds of 0 or 1, 108 * then the output value will be set to exactly 0 or 1. Otherwise the input value will 109 * be returned. 110 * 111 * @param s The parameter to check for approximate end values. 112 * @return The input value or 0 and 1 if the input value is within tolerance of the 113 * ends. 114 */ 115 protected static final double roundParNearEnds(double s) { 116 return (s < TOL_S ? 0. : (s > 1. - TOL_S ? 1. : s)); 117 } 118 119 /** 120 * Return true if the input parameter is within tolerance of or less than the start of the 121 * parameter range (s ≤ tol). 122 * 123 * @param s The parameter to check. 124 * @param tol The tolerance to use for the check. 125 * @return true if the parameter is within tolerance of zero. 126 */ 127 protected static final boolean parNearStart(double s, double tol) { 128 return s <= tol; 129 } 130 131 /** 132 * Return true if the input parameter is within tolerance of or greater than the end 133 * of the parameter range (s ≥ 1.0 - tol). 134 * 135 * @param s The parameter to check. 136 * @param tol The tolerance to use for the check. 137 * @return true if the parameter is within tolerance of 1.0. 138 */ 139 protected static final boolean parNearEnd(double s, double tol) { 140 return s >= 1.0 - tol; 141 } 142 143 /** 144 * Return true if the input parameter is within tolerance of or outside of the bounds 145 * of either the end of the parameter range (s ≤ tol || s ≥ 1.0 - tol). 146 * 147 * @param s The parameter to check. 148 * @param tol The tolerance to use for the check. 149 * @return true if the parameter is within tolerance of 0.0 or 1.0. 150 */ 151 protected static final boolean parNearEnds(double s, double tol) { 152 return s <= tol || s >= 1.0 - tol; 153 } 154 155 /** 156 * Split this curve at the specified parametric position returning a list containing 157 * two curves (a lower curve with smaller parametric positions than "s" and an upper 158 * curve with larger parametric positions). 159 * 160 * @param s The parametric position where this curve should be split (must not be 0 or 161 * 1!). Must be a 1-dimensional point with a value in the range 0 to 1.0, 162 * non-inclusive. Units are ignored. 163 * @return A list containing two curves: 0 == the lower curve, 1 == the upper curve. 164 */ 165 @Override 166 public GeomList<T> splitAt(GeomPoint s) { 167 return splitAt(s.getValue(0)); 168 } 169 170 /** 171 * Return a subrange point on the curve for the given parametric distance along the 172 * curve. 173 * 174 * @param s parametric distance to calculate a point for (0.0 to 1.0 inclusive). 175 * @return The subrange point or <code>null</code> if the parameter is not in a valid 176 * range. 177 */ 178 @Override 179 public SubrangePoint getPoint(double s) { 180 validateParameter(s); 181 s = roundParNearEnds(s); 182 return SubrangePoint.newInstance(this, Point.valueOf(s)); 183 } 184 185 /** 186 * Return a subrange point on the curve for the given parametric distance along the 187 * curve. 188 * 189 * @param s parametric distance to calculate a point for. Must be a 1-dimensional 190 * point with a value in the range 0 to 1.0. Units are ignored. 191 * @return The subrange point 192 */ 193 @Override 194 public SubrangePoint getPoint(GeomPoint s) { 195 validateParameter(s); 196 return SubrangePoint.newInstance(this, s); 197 } 198 199 /** 200 * Calculate a point on the curve for the given parametric distance along the curve. 201 * 202 * @param s parametric distance to calculate a point for. Must be a 1-dimensional 203 * point with a value in the range 0 to 1.0. Units are ignored. 204 * @return calculated point 205 */ 206 @Override 207 public Point getRealPoint(GeomPoint s) { 208 validateParameter(s); 209 return getRealPoint(s.getValue(0)); 210 } 211 212 /** 213 * Calculate all the derivatives from <code>0</code> to <code>grade</code> with 214 * respect to parametric position(s) on a parametric object for the given parametric 215 * position on the object, <code>d^{grade}p(s)/d^{grade}s</code>. 216 * <p> 217 * Example:<br> 218 * 1st derivative (grade = 1), this returns <code>[p(s), dp(s)/ds]</code>;<br> 219 * 2nd derivative (grade = 2), this returns <code>[p(s), dp(s)/ds, d^2p(s)/d^2s]</code>; etc. 220 * </p> 221 * 222 * @param s parametric position to calculate the derivatives for. Must match the 223 * parametric dimension of this parametric surface and have each value in 224 * the range 0 to 1.0. Units are ignored. 225 * @param grade The maximum grade to calculate the derivatives for (1=1st derivative, 226 * 2=2nd derivative, etc) 227 * @return A list of lists of derivatives (one list for each parametric dimension). 228 * Each list contains derivatives up to the specified grade at the specified 229 * parametric position. Example: for a surface list element 0 is the array of 230 * derivatives in the 1st parametric dimension (s), then 2nd list element is 231 * the array of derivatives in the 2nd parametric dimension (t). 232 * @throws IllegalArgumentException if the grade is < 0. 233 */ 234 @Override 235 public List<List<Vector<Length>>> getDerivatives(GeomPoint s, int grade) { 236 validateParameter(s); 237 238 List<List<Vector<Length>>> list = FastTable.newInstance(); 239 list.add(getSDerivatives(s.getValue(0), grade)); 240 241 return list; 242 } 243 244 /** 245 * Calculate all the derivatives from <code>0</code> to <code>grade</code> with 246 * respect to parametric distance on the curve for the given parametric distance along 247 * the curve, <code>d^{grade}p(s)/d^{grade}s</code>. 248 * <p> 249 * Example:<br> 250 * 1st derivative (grade = 1), this returns <code>[p(s), dp(s)/ds]</code>;<br> 251 * 2nd derivative (grade = 2), this returns <code>[p(s), dp(s)/ds, d^2p(s)/d^2s]</code>; etc. 252 * </p> 253 * 254 * @param s parametric distance to calculate the derivatives for. Must be a 255 * 1-dimensional point with a value in the range 0 to 1.0. Units are 256 * ignored. 257 * @param grade The maximum grade to calculate the derivatives for (1=1st derivative, 258 * 2=2nd derivative, etc) 259 * @return A list of derivatives up to the specified grade of the curve at the 260 * specified parametric position. 261 * @throws IllegalArgumentException if the grade is < 0. 262 */ 263 @Override 264 public List<Vector<Length>> getSDerivatives(GeomPoint s, int grade) { 265 validateParameter(s); 266 return getSDerivatives(s.getValue(0), grade); 267 } 268 269 /** 270 * Calculate a derivative with respect to parametric distance of the given grade on 271 * the curve for the given parametric distance along the curve, 272 * <code>d^{grade}p(s)/d^{grade}s</code>. 273 * <p> 274 * Example:<br> 275 * 1st derivative (grade = 1), this returns <code>dp(s)/ds</code>; <br> 276 * 2nd derivative (grade = 2), this returns <code>d^2p(s)/d^2s</code>; etc. 277 * </p> 278 * <p> 279 * Note: Cartesian space derivatives can be calculated as follows: 280 * <pre> 281 * derivative = curve.getSDerivative(s, 1); 282 * dy/dx = (dy/ds)/(dx/ds) = derivative.getValue(1)/derivative.getValue(0), 283 * dy/dz = (dy/ds)/(dz/ds) = derivative.getValue(1)/derivative.getValue(2), 284 * etc 285 * </pre> 286 * 287 * @param s Parametric distance to calculate a derivative for (0.0 to 1.0 288 * inclusive). 289 * @param grade The grade to calculate the derivative for (1=1st derivative, 2=2nd 290 * derivative, etc) 291 * @return The specified derivative of the curve at the specified parametric position. 292 * @throws IllegalArgumentException if the grade is < 0. 293 */ 294 @Override 295 public Vector<Length> getSDerivative(double s, int grade) { 296 validateParameter(s); 297 StackContext.enter(); 298 try { 299 300 List<Vector<Length>> ders = getSDerivatives(s, grade); 301 Vector<Length> derivative = ders.get(grade); 302 return StackContext.outerCopy(derivative); 303 304 } finally { 305 StackContext.exit(); 306 } 307 } 308 309 /** 310 * Return the tangent vector for the given parametric distance along the curve. This 311 * vector contains the normalized 1st derivative of the curve with respect to 312 * parametric distance in each component direction: dx/ds/|dp(s)/ds|, 313 * dy/ds/|dp(s)/ds|, dz/ds/|dp(s)/ds|, etc. 314 * <p> 315 * Note: Cartesian space derivatives can be calculated as follows: 316 * <pre> 317 * tangent = curve.getTangent(s); 318 * dy/dx = (dy/ds)/(dx/ds) = tangent.getValue(1)/tangent.getValue(0), 319 * dy/dz = (dy/ds)/(dz/ds) = tangent.getValue(1)/tangent.getValue(2), 320 * etc 321 * </pre> 322 * 323 * @param s Parametric distance to calculate a tangent vector for (0.0 to 1.0 324 * inclusive). 325 * @return The tangent vector of the curve at the specified parametric position. 326 */ 327 @Override 328 public Vector<Dimensionless> getTangent(double s) { 329 validateParameter(s); 330 StackContext.enter(); 331 try { 332 333 // Ref. 1, Eqn. 12.1. 334 List<Vector<Length>> ders = getSDerivatives(s, 1); 335 Vector<Dimensionless> derivative = ders.get(1).toUnitVector(); 336 337 return StackContext.outerCopy(derivative); 338 339 } finally { 340 StackContext.exit(); 341 } 342 } 343 344 /** 345 * Return the tangent vector for the given parametric distance along the curve. This 346 * vector contains the normalized 1st derivative of the curve with respect to 347 * parametric distance in each component direction: dx/ds/|dp(s)/ds|, 348 * dy/ds/|dp(s)/ds|, dz/ds/|dp(s)/ds|, etc. 349 * <p> 350 * Note: Cartesian space derivatives can be calculated as follows: 351 * <pre> 352 * tangent = curve.getTangent(s); 353 * dy/dx = (dy/ds)/(dx/ds) = tangent.getValue(1)/tangent.getValue(0), 354 * dy/dz = (dy/ds)/(dz/ds) = tangent.getValue(1)/tangent.getValue(2), 355 * etc 356 * </pre> 357 * 358 * @param s Parametric distance to calculate a tangent vector for. Must be a 359 * 1-dimensional point with a value in the range 0 to 1.0. Units are ignored. 360 * @return The tangent vector of the curve at the specified parametric position. 361 */ 362 @Override 363 public Vector<Dimensionless> getTangent(GeomPoint s) { 364 validateParameter(s); 365 return getTangent(s.getValue(0)); 366 } 367 368 /** 369 * Return the principal normal vector for the given parametric distance, 370 * <code>s</code>, along the curve. This vector is normal to the curve, lies in the 371 * normal plane (is perpendicular to the tangent vector), and points toward the center 372 * of curvature at the specified point. 373 * 374 * @param s Parametric distance to calculate the principal normal vector for (0.0 to 375 * 1.0 inclusive). 376 * @return The principal normal vector of the curve at the specified parametric 377 * position. 378 * @throws IllegalArgumentException if the curve does not have a 2nd derivative with 379 * respect to <code>s</code> (if the curve is locally a straight line segment -- this 380 * means there are an infinite number of possible principal normal vectors). 381 */ 382 @Override 383 public Vector<Dimensionless> getPrincipalNormal(double s) { 384 validateParameter(s); 385 386 StackContext.enter(); 387 try { 388 // Ref. 1, Eqn. 12.15. 389 List<Vector<Length>> ders = getSDerivatives(s, 2); 390 Vector<Length> piu = ders.get(1); 391 Vector<Length> piuu = ders.get(2); 392 393 if (piuu.mag().isApproxZero()) 394 throw new IllegalArgumentException(RESOURCES.getString("undefinedPrincipleNormal")); 395 396 // ni = k/|k|; k = piuu - piuu DOT piu / |piu|^2 * piu; 397 Parameter piuMag = piu.mag(); 398 Parameter piuuDotpiu = piuu.dot(piu); 399 Vector k = piu.times(piuuDotpiu.divide(piuMag).divide(piuMag)); 400 k = piuu.minus(k); 401 402 return StackContext.outerCopy(k.toUnitVector()); 403 404 } finally { 405 StackContext.exit(); 406 } 407 } 408 409 /** 410 * Return the principal normal vector for the given parametric distance, 411 * <code>s</code>, along the curve. This vector is normal to the curve, lies in the 412 * normal plane (is perpendicular to the tangent vector), and points toward the center 413 * of curvature at the specified point. 414 * 415 * @param s Parametric distance to calculate the principal normal vector for. Must be 416 * a 1-dimensional point with a value in the range 0 to 1.0. Units are 417 * ignored. 418 * @return The principal normal vector of the curve at the specified parametric 419 * position. 420 * @throws IllegalArgumentException if the curve does not have a 2nd derivative with 421 * respect to <code>s</code> (if the curve is locally a straight line segment -- this 422 * means there are an infinite number of possible principal normal vectors). 423 */ 424 @Override 425 public Vector<Dimensionless> getPrincipalNormal(GeomPoint s) { 426 validateParameter(s); 427 return getPrincipalNormal(s.getValue(0)); 428 } 429 430 /** 431 * Return the binormal vector for the given parametric distance along the curve. This 432 * vector is normal to the curve at <code>s</code>, lies in the normal plane (is 433 * perpendicular to the tangent vector), and is perpendicular to the principal normal 434 * vector. Together, the tangent vector, principal normal vector, and binormal vector 435 * form a useful orthogonal frame. 436 * 437 * @param s Parametric distance to calculate the binormal vector for (0.0 to 1.0 438 * inclusive). 439 * @return The binormal vector of the curve at the specified parametric position. 440 * @throws IllegalArgumentException if the curve does not have a 2nd derivative with 441 * respect to <code>s</code> (if the curve is locally a straight line segment). 442 * @throws DimensionException if the curve does not have at least 3 physical 443 * dimensions. 444 */ 445 @Override 446 public Vector<Dimensionless> getBinormal(double s) throws DimensionException { 447 validateParameter(s); 448 if (getPhyDimension() < 3) 449 throw new DimensionException(RESOURCES.getString("undefinedBinormal")); 450 451 StackContext.enter(); 452 try { 453 454 // Ref. 1, Eqn. 12.18. 455 // bi = ti X ni; 456 Vector ni = getPrincipalNormal(s); 457 Vector ti = getTangent(s); 458 Vector bi = ti.cross(ni); 459 return StackContext.outerCopy(bi); 460 461 } finally { 462 StackContext.exit(); 463 } 464 } 465 466 /** 467 * Return the binormal vector for the given parametric distance along the curve. This 468 * vector is normal to the curve at <code>s</code>, lies in the normal plane (is 469 * perpendicular to the tangent vector), and is perpendicular to the principal normal 470 * vector. Together, the tangent vector, principal normal vector, and binormal vector 471 * form a useful orthogonal frame. 472 * 473 * @param s Parametric distance to calculate the binormal vector for. Must be a 474 * 1-dimensional point with a value in the range 0 to 1.0. Units are ignored. 475 * @return The binormal vector of the curve at the specified parametric position. 476 * @throws IllegalArgumentException if the curve does not have a 2nd derivative with 477 * respect to <code>s</code> (if the curve is locally a straight line segment). 478 * @throws DimensionException if the curve does not have at least 3 physical 479 * dimensions. 480 */ 481 @Override 482 public Vector<Dimensionless> getBinormal(GeomPoint s) throws DimensionException { 483 validateParameter(s); 484 return getBinormal(s.getValue(0)); 485 } 486 487 /** 488 * Return the curvature (kappa = 1/rho; where rho = the radius of curvature) of the 489 * curve at the parametric position <code>s</code>. The curvature vector (vector from 490 * p(s) to the center of curvature) can be constructed from: <code>k = rhoi*ni</code> 491 * or <code>k = curve.getPrincipalNormal(s).divide(curve.getCurvature(s));</code> 492 * 493 * @param s Parametric distance to calculate the curvature for (0.0 to 1.0 inclusive). 494 * @return The curvature of the curve at the specified parametric position in units of 495 * 1/Length. 496 */ 497 @Override 498 public Parameter getCurvature(double s) { 499 validateParameter(s); 500 int dim = getPhyDimension(); 501 if (dim < 2) 502 return Parameter.valueOf(0, getUnit().inverse()); 503 504 StackContext.enter(); 505 try { 506 // Ref. 1, Eqn. 12.24. 507 List<Vector<Length>> ders = getSDerivatives(s, 2); 508 Vector<Length> piu = ders.get(1); 509 Vector<Length> piuu = ders.get(2); 510 511 Parameter piuMag = piu.mag(); 512 if (piuu.mag().isApproxZero() || piuMag.isApproxZero()) 513 return StackContext.outerCopy(Parameter.valueOf(0, getUnit().inverse())); 514 515 Parameter kappa; 516 517 if (dim > 2) { 518 // kappa = |piu X piuu| / |piu|^3 519 Vector piuXpiuu = piu.cross(piuu); 520 kappa = piuXpiuu.mag().divide(piuMag.pow(3)); 521 522 } else { 523 Parameter xu = piu.get(0); 524 Parameter yu = piu.get(1); 525 Parameter xuu = piuu.get(0); 526 Parameter yuu = piuu.get(1); 527 528 // kappa = |xu*yuu - xuu*yu|/(xu^2 + yu^2)^(3/2) = |xu*yuu - xuu*yu|/|piu|^3 529 Parameter num = xu.times(yuu).minus(xuu.times(yu)).abs(); 530 kappa = num.divide(piuMag.pow(3)); 531 } 532 533 kappa = kappa.to(getUnit().inverse()); 534 return StackContext.outerCopy(kappa); 535 536 } finally { 537 StackContext.exit(); 538 } 539 } 540 541 /** 542 * Return the curvature (kappa = 1/rho; where rho = the radius of curvature) of the 543 * curve at the parametric position <code>s</code>. The curvature vector (vector from 544 * p(s) to the center of curvature) can be constructed from: <code>k = rhoi*ni</code> 545 * or <code>k = curve.getPrincipalNormal(s).divide(curve.getCurvature(s));</code> 546 * 547 * @param s Parametric distance to calculate the curvature for. Must be a 548 * 1-dimensional point with a value in the range 0 to 1.0. Units are ignored. 549 * @return The curvature of the curve at the specified parametric position in units of 550 * 1/Length. 551 */ 552 @Override 553 public Parameter getCurvature(GeomPoint s) { 554 validateParameter(s); 555 return getCurvature(s.getValue(0)); 556 } 557 558 /** 559 * Return the variation of curvature or rate of change of curvature (VOC or 560 * dKappa(s)/ds) at the parametric position <code>s</code>. 561 * 562 * @param s Parametric distance to calculate the variation of curvature for (0.0 to 563 * 1.0 inclusive). 564 * @return The variation of curvature of the curve at the specified parametric 565 * position in units of 1/Length. 566 */ 567 @Override 568 public Parameter getVariationOfCurvature(double s) { 569 validateParameter(s); 570 int dim = getPhyDimension(); 571 if (dim < 2) 572 return Parameter.valueOf(0, getUnit().inverse()); 573 574 // Ref: Moreton, H.P., "Minimum Curvature Variation Curves, Networks, 575 // and Surfaces for Fair Free-Form Shape Design", Dissertation, 576 // University of California Berkely, 1992, pg. 79. 577 578 StackContext.enter(); 579 try { 580 // Get the needed derivatives. 581 List<Vector<Length>> ders = getSDerivatives(s, 3); 582 Vector<Length> piu = ders.get(1); 583 Vector<Length> piuu = ders.get(2); 584 Vector<Length> piuuu = ders.get(3); 585 586 Parameter piuMag = piu.mag(); 587 if (piuMag.isApproxZero()) 588 return StackContext.outerCopy(Parameter.valueOf(0, getUnit().inverse())); 589 590 Parameter dKds; 591 if (dim > 2) { 592 // u = piu x piuu 593 Vector u = piu.cross(piuu); 594 595 // kappa = (piu X piuu) / |piu|^3 596 Vector kappa = u.divide(piuMag.pow(3)); 597 598 // du = piu x piuuu 599 Vector du = piu.cross(piuuu); 600 601 // v = |piu|^3 = (piu dot piu)^(3/2) 602 Parameter v = piuMag.pow(3); 603 604 // dv = 3*|piu|*(piu dot piuu) 605 Parameter dv = piu.dot(piuu).times(piuMag).times(3); 606 607 // dKv/ds = (v*du - u*dv)/v^2 608 Vector num = du.times(v).minus(u.times(dv)); 609 Vector dKvds = num.divide(v.pow(2)); 610 611 // dK/ds = (kappa/|kappa|) dot dKv/ds 612 dKds = dKvds.dot(kappa.toUnitVector()); 613 614 } else { 615 Parameter xu = piu.get(0); 616 Parameter yu = piu.get(1); 617 Parameter xuu = piuu.get(0); 618 Parameter yuu = piuu.get(1); 619 Parameter xuuu = piuuu.get(0); 620 Parameter yuuu = piuuu.get(1); 621 622 // u = xu*yuu - xuu*yu 623 Parameter u = xu.times(yuu).minus(xuu.times(yu)); 624 625 // du = xu*yuuu - xuuu*yu 626 Parameter du = xu.times(yuuu).minus(xuuu.times(yu)); 627 628 // v = |piu|^3 = (piu dot piu)^(3/2) 629 Parameter v = piuMag.pow(3); 630 631 // dv = 3*|piu|*(piu dot piuu) 632 Parameter dv = xu.times(xuu).plus(yu.times(yuu)).times(piuMag).times(3); 633 634 // dK/ds = (v*du - u*dv)/v^2 635 dKds = v.times(du).minus(u.times(dv)).divide(v.pow(2)); 636 } 637 638 Parameter voc = dKds.to(getUnit().inverse()); 639 return StackContext.outerCopy(voc); 640 641 } finally { 642 StackContext.exit(); 643 } 644 } 645 646 /** 647 * Return the variation of curvature or rate of change of curvature (VOC or 648 * dKappa(s)/ds) at the parametric position <code>s</code>. 649 * 650 * @param s Parametric distance to calculate the variation of curvature for. Must be a 651 * 1-dimensional point with a value in the range 0 to 1.0. Units are ignored. 652 * @return The variation of curvature of the curve at the specified parametric 653 * position in units of 1/Length. 654 */ 655 @Override 656 public Parameter getVariationOfCurvature(GeomPoint s) { 657 validateParameter(s); 658 return getVariationOfCurvature(s.getValue(0)); 659 } 660 661 /** 662 * Return the torsion of the curve at the parametric position <code>s</code>. The 663 * torsion is a measure of the rotation or twist about the tangent vector. 664 * 665 * @param s Parametric distance to calculate the torsion for (0.0 to 1.0 inclusive). 666 * @return The torsion of the curve at the specified parametric position in units of 667 * 1/Length. 668 */ 669 @Override 670 public Parameter getTorsion(double s) { 671 validateParameter(s); 672 int dim = getPhyDimension(); 673 if (dim < 3) 674 return Parameter.valueOf(0, getUnit().inverse()); 675 676 StackContext.enter(); 677 try { 678 // Ref. 1, Eqn. 12.30. 679 List<Vector<Length>> ders = getSDerivatives(s, 3); 680 Vector<Length> piu = ders.get(1); 681 Vector<Length> piuu = ders.get(2); 682 Vector<Length> piuuu = ders.get(3); 683 684 if (piuuu.mag().isApproxZero() || piuu.mag().isApproxZero() 685 || piu.mag().isApproxZero()) 686 return StackContext.outerCopy(Parameter.valueOf(0, getUnit().inverse())); 687 688 // tau = piu DOT (piuu X piuuu) / |piu X piuu|^2 689 Parameter piuXpiuu = piu.cross(piuu).mag(); 690 Parameter tau = piu.dot(piuu.cross(piuuu)).divide(piuXpiuu.times(piuXpiuu)); 691 692 return StackContext.outerCopy(tau); 693 694 } finally { 695 StackContext.exit(); 696 } 697 } 698 699 /** 700 * Return the torsion of the curve at the parametric position <code>s</code>. The 701 * torsion is a measure of the rotation or twist about the tangent vector. 702 * 703 * @param s Parametric distance to calculate the torsion for. Must be a 1-dimensional 704 * point with a value in the range 0 to 1.0. Units are ignored. 705 * @return The torsion of the curve at the specified parametric position in units of 706 * 1/Length. 707 */ 708 @Override 709 public Parameter getTorsion(GeomPoint s) { 710 validateParameter(s); 711 return getTorsion(s.getValue(0)); 712 } 713 714 /** 715 * Return the total arc length of this curve. 716 * 717 * @param eps The desired fractional accuracy on the arc-length. 718 * @return The total arc length of the curve. 719 */ 720 @Override 721 public Parameter<Length> getArcLength(double eps) { 722 return getArcLength(0, 1, eps); 723 } 724 725 /** 726 * Return the arc length of a segment of this curve. 727 * 728 * @param s1 The starting parametric distance along the curve to begin the arc-length 729 * calculation from. 730 * @param s2 The ending parametric distance along the curve to end the arc-length 731 * calculation at. 732 * @param eps The desired fractional accuracy on the arc-length. 733 * @return The arc length of the specified segment of the curve. 734 */ 735 @Override 736 public Parameter<Length> getArcLength(double s1, double s2, double eps) { 737 validateParameter(s1); 738 validateParameter(s2); 739 740 // If s1 and s2 are equal, then we know the answer already. 741 if (isApproxEqual(s1, s2, TOL_S)) 742 return Parameter.ZERO_LENGTH.to(getUnit()); 743 744 // Make sure that s2 is > s1. 745 if (s1 > s2) { 746 double temp = s1; 747 s1 = s2; 748 s2 = temp; 749 } 750 751 ArcLengthEvaluatable arcLengthEvaluator = ArcLengthEvaluatable.newInstance(this); 752 double length = 0; 753 try { 754 755 // Integrate to find the arc length: L = int_s1^s2{ sqrt(ps(s) DOT ps(s)) ds} 756 length = Quadrature.adaptLobatto(arcLengthEvaluator, s1, s2, eps); 757 758 } catch (IntegratorException | RootException e) { 759 // Fall back on Gaussian Quadrature. 760 Logger.getLogger(AbstractCurve.class.getName()).log(Level.WARNING, 761 "Trying Gaussian Quadrature in getArcLength()..."); 762 try { 763 length = Quadrature.gaussLegendre_Wx1N20(arcLengthEvaluator, s1, s2); 764 765 } catch (RootException err) { 766 Logger.getLogger(AbstractCurve.class.getName()).log(Level.SEVERE, 767 "Could not find arc-length.", err); 768 } 769 770 } finally { 771 ArcLengthEvaluatable.recycle(arcLengthEvaluator); 772 } 773 774 Parameter<Length> L = Parameter.valueOf(length, getUnit()); 775 776 return L; 777 } 778 779 /** 780 * Return the arc length of a segment of this curve. 781 * 782 * @param s1 The starting parametric distance along the curve to begin the arc-length 783 * calculation from. Must be a 1-dimensional point with a value in the 784 * range 0 to 1.0. Units are ignored. 785 * @param s2 The ending parametric distance along the curve to end the arc-length 786 * calculation at. Must be a 1-dimensional point with a value in the range 787 * 0 to 1.0. Units are ignored. 788 * @param eps The desired fractional accuracy on the arc-length. 789 * @return The arc length of the specified segment of the curve. 790 */ 791 @Override 792 public Parameter<Length> getArcLength(GeomPoint s1, GeomPoint s2, double eps) { 793 return getArcLength(s1.getValue(0), s2.getValue(0), eps); 794 } 795 796 /** 797 * Return a subrange point at the position on the curve with the specified arc-length. 798 * If the requested arc-length is ≤ 0, the start of the curve is returned. If the 799 * requested arc length is > the total arc length of the curve, then the end point 800 * is returned. 801 * 802 * @param targetArcLength The target arc length to find in meters. 803 * @param tol Fractional tolerance (in parameter space) to refine the 804 * point position to. 805 * @return A subrange point on the curve at the specified arc-length position. 806 */ 807 @Override 808 public SubrangePoint getPointAtArcLength(Parameter<Length> targetArcLength, double tol) { 809 // Deal with an obvious case first. 810 if (targetArcLength.isApproxZero()) 811 return SubrangePoint.newInstance(this, Point.valueOf(0)); 812 813 double arcLengthValue = targetArcLength.getValue(SI.METER); 814 815 // Calculate the full arc-length of the curve (to GTOL tolerance). 816 double fullArcLength = getArcLength(GTOL).getValue(SI.METER); 817 818 // Is the requested arc length > than the actual arc length? 819 if (arcLengthValue > fullArcLength * (1 + GTOL)) 820 return SubrangePoint.newInstance(this, Point.valueOf(1)); 821 822 // Calculate the arc-length tolerance to use based on the requested parameter 823 // space tolerance and the full arc-length. 824 double eps = tol * fullArcLength / 100.; 825 826 return pointAtArcLength(0, arcLengthValue, tol, eps); 827 } 828 829 /** 830 * A point is found along this curve that when connected by a straight line to the 831 * given point in space, the resulting line is tangent to this curve. This method 832 * should only be used if this curve is a planar curve that is C1 (slope) continuous, 833 * and convex with respect to the point. The given point should be in the plane of the 834 * curve. If it is not, it will be projected into the plane of the curve. 835 * 836 * @param point The point that the tangent on the curve is to be found relative to. 837 * @param near The parametric distance on the curve (0-1) that serves as an initial 838 * guess at the location of the tangent point. 839 * @param tol Fractional tolerance (in parameter space) to refine the point position 840 * to. 841 * @return The point on this curve that is tangent to the curve and the supplied 842 * point. 843 */ 844 @Override 845 public SubrangePoint getTangencyPoint(GeomPoint point, double near, double tol) { 846 validateParameter(near); 847 requireNonNull(point, MessageFormat.format(RESOURCES.getString("paramNullErr"), "point")); 848 849 double minP = 0; 850 StackContext.enter(); 851 try { 852 int cDim = this.getPhyDimension(); 853 if (point.getPhyDimension() < cDim) { 854 point = point.toDimension(cDim); 855 } 856 if (point.getPhyDimension() != cDim) { 857 throw new DimensionException(RESOURCES.getString("crvPointDimensionErr")); 858 } 859 860 if (cDim > 2) { 861 // Project the input point onto the plane of the curve. 862 Point p0 = this.getRealPoint(0); // Any point in plane (on curve). 863 Vector nhat = this.getBinormal(.5); // Plane normal vector. 864 865 // Find the closest point on the plane. 866 Point projPoint = GeomUtil.pointPlaneClosest(point, p0, nhat); 867 point = projPoint; 868 } 869 870 // Create a function that evaluates: 1 - |t(s) DOT ((q - p(s))/|q - p(s)|)|. 871 // When this function is minimized, the s value may be a tangent point. 872 TangentPointEvaluatable tangentEvaluator = TangentPointEvaluatable.newInstance(this, point); 873 try { 874 // Find the minimum of the evaluator function. 875 minP = Minimization.find(tangentEvaluator, 0, near, 1, tol, null); 876 877 } catch (RootException err) { 878 Logger.getLogger(AbstractCurve.class.getName()).log(Level.SEVERE, 879 "Could not find tangency point.", err); 880 } 881 882 } finally { 883 StackContext.exit(); 884 } 885 886 SubrangePoint p = SubrangePoint.newInstance(this, Point.valueOf(minP)); 887 return p; 888 } 889 890 /** 891 * Return a string of points that are gridded onto the curve using the specified 892 * spacing and gridding rule. 893 * 894 * @param gridRule Specifies whether the subdivision spacing is applied with respect 895 * to arc-length in real space or parameter space. 896 * @param spacing A list of values used to define the subdivision spacing. 897 * <code>gridRule</code> specifies whether the subdivision spacing is 898 * applied with respect to arc-length in real space or parameter 899 * space. If the spacing is arc-length, then the values are 900 * interpreted as fractions of the arc length of the curve from 0 to 901 * 1. 902 * @return A string of subrange points gridded onto the curve at the specified 903 * parametric positions. 904 */ 905 @Override 906 public PointString<SubrangePoint> extractGrid(GridRule gridRule, List<Double> spacing) { 907 requireNonNull(gridRule); 908 requireNonNull(spacing); 909 PointString<SubrangePoint> str = PointString.newInstance(); 910 911 switch (gridRule) { 912 case PAR: 913 // Parametric spacing. 914 for (double s : spacing) { 915 if (s > 1) 916 s = 1; 917 else if (s < 0) 918 s = 0; 919 str.add(this.getPoint(s)); 920 } 921 break; 922 923 case ARC: 924 // Arc-length spacing. 925 double arcLength = this.getArcLength(GTOL).getValue(SI.METER); 926 double tol = GTOL / arcLength * 100.; // Tolerance for root finder in parameter space. 927 928 double sOld = 0; 929 for (double s : spacing) { 930 double targetArcLength = s * arcLength; 931 932 SubrangePoint p; 933 if (targetArcLength >= arcLength) 934 p = getPoint(1); 935 else if (targetArcLength <= 0) 936 p = getPoint(0); 937 938 else { 939 if (s < sOld) 940 sOld = 0; 941 p = pointAtArcLength(sOld, targetArcLength, tol, GTOL); 942 } 943 str.add(p); 944 945 sOld = p.getParPosition().getValue(0); 946 } 947 break; 948 949 default: 950 throw new IllegalArgumentException( 951 MessageFormat.format(RESOURCES.getString("crvUnsupportedGridRule"), 952 gridRule.toString())); 953 954 } 955 956 return str; 957 } 958 959 /** 960 * Return a subrange point at the position on the curve with the specified arc length. 961 * 962 * @param x1 The minimum parametric position that could have the 963 * specified arc length. 964 * @param targetArcLength The target arc length to find in meters. 965 * @param tol Fractional tolerance (in parameter space) to refine the 966 * point position to. 967 * @param eps The desired fractional accuracy on the arc-length. 968 */ 969 private SubrangePoint pointAtArcLength(double x1, double targetArcLength, double tol, double eps) { 970 971 double pos = 1; 972 StackContext.enter(); 973 try { 974 // Create a function that is used to iteratively find the target arc length. 975 ArcPosEvaluatable arcPosEvaluator = ArcPosEvaluatable.newInstance(this, targetArcLength, eps); 976 977 try { 978 // Find the arc length position using the evaluator function. 979 arcPosEvaluator.firstPass = true; 980 pos = Roots.findRoot1D(arcPosEvaluator, x1, 1, tol); 981 982 } catch (RootException err) { 983 Logger.getLogger(AbstractCurve.class.getName()).log(Level.WARNING, 984 "Could not find point at arc-length.", err); 985 pos = 1; 986 } 987 } finally { 988 StackContext.exit(); 989 } 990 991 SubrangePoint p = SubrangePoint.newInstance(this, Point.valueOf(pos)); 992 return p; 993 } 994 995 private static final int MAX_POINTS = 10000; 996 997 /** 998 * Return a string of points that are gridded onto the curve with the number of points 999 * and spacing chosen to result in straight line segments between the points that have 1000 * mid-points that are all within the specified tolerance of this curve. 1001 * 1002 * @param tol The maximum distance that a straight line between gridded points may 1003 * deviate from this curve. May not be null. 1004 * @return A string of subrange points gridded onto the curve. 1005 */ 1006 @Override 1007 public PointString<SubrangePoint> gridToTolerance(Parameter<Length> tol) { 1008 if (isDegenerate(requireNonNull(tol))) { 1009 // If this curve is a single point, just return that point twice. 1010 PointString<SubrangePoint> str = PointString.newInstance(); 1011 str.add(getPoint(0)); 1012 str.add(getPoint(1)); 1013 return str; 1014 } 1015 1016 // Start with the end-points. 1017 FastList<SubrangePoint> pntList = FastList.newInstance(); 1018 pntList.add(getPoint(0)); 1019 pntList.add(getPoint(1)); 1020 1021 // Refine the initial points using subdivision. 1022 Parameter<Area> tol2 = tol.times(tol); 1023 refineCurvePoints(pntList, tol2); 1024 1025 // Check guessed grid-to-tolerance points and add any that fall out of tolerance. 1026 FastList<SubrangePoint> guessPnts = guessGrid2TolPoints(tol2); 1027 checkGuessedGrid2TolPoints(pntList, guessPnts, tol2); 1028 FastList.recycle(guessPnts); 1029 1030 // Refine the points after any guessed points were added using subdivision. 1031 refineCurvePoints(pntList, tol2); 1032 1033 if (pntList.size() >= MAX_POINTS) 1034 Logger.getLogger(AbstractCurve.class.getName()).log(Level.WARNING, 1035 MessageFormat.format(RESOURCES.getString("maxPointsWarningMsg"), 1036 "curve", this.getID())); 1037 1038 // Convert from a FastList to a PointString. 1039 PointString<SubrangePoint> str = PointString.valueOf(null, pntList); 1040 FastList.recycle(pntList); 1041 1042 return str; 1043 } 1044 1045 /** 1046 * Use subdivision to refine the points gridded onto this curve until the 1047 * mid-points of all the segments meet the specified tolerance from the 1048 * curve. Points are added to the list wherever the segment mid-point 1049 * distances are greater than the tolerance. 1050 * 1051 * @param pntList The starting list of points to refine. 1052 * @param tol2 The square of the maximum distance that a straight line between 1053 * gridded points may deviate from this curve. May not be null. 1054 */ 1055 private void refineCurvePoints(FastList<SubrangePoint> pntList, Parameter<Area> tol2) { 1056 // Start at the head of the list. 1057 FastList.Node<SubrangePoint> current = pntList.head().getNext(); 1058 1059 // Loop until we reach the end of the list or exceed the max. number of points. 1060 FastList.Node tail = pntList.tail().getPrevious(); 1061 while (!current.equals(tail) && pntList.size() < MAX_POINTS) { 1062 1063 // Subdivide the current segment until the mid-point is less than tol away from the curve. 1064 boolean distGTtol = true; 1065 while (distGTtol) { 1066 1067 // Get the current point. 1068 SubrangePoint pc = current.getValue(); 1069 double sc = pc.getParPosition().getValue(0); 1070 1071 // Get the next point. 1072 FastList.Node<SubrangePoint> next = current.getNext(); 1073 SubrangePoint pn = next.getValue(); 1074 double sn = pn.getParPosition().getValue(0); 1075 1076 // Compute the average point half-way on a line from pc to pn. 1077 Point pa = GeomUtil.averagePoints(pc, pn); 1078 1079 // Find the parametric middle point on the curve. 1080 double sm = (sc + sn) / 2; 1081 SubrangePoint pm = getPoint(sm); 1082 1083 // Compute the distance between the middle point and the average point 1084 // and compare with the tolerance. 1085 distGTtol = pa.distanceSq(pm).isGreaterThan(tol2); 1086 if (distGTtol) 1087 // Insert the new point into the list. 1088 pntList.addBefore(next, pm); 1089 else 1090 SubrangePoint.recycle(pm); 1091 1092 } // end while() 1093 1094 // Move on to the next point. 1095 current = current.getNext(); 1096 1097 } // end while() 1098 } 1099 1100 /** 1101 * A Comparator that compares the parametric S-position of a pair of 1102 * subrange points. 1103 */ 1104 private static class SComparator implements Comparator<SubrangePoint> { 1105 1106 @Override 1107 public int compare(SubrangePoint o1, SubrangePoint o2) { 1108 double s1 = o1.getParPosition().getValue(0); 1109 double s2 = o2.getParPosition().getValue(0); 1110 return Double.compare(s1, s2); 1111 } 1112 } 1113 1114 private static final SComparator S_CMPRTR = new SComparator(); 1115 1116 /** 1117 * Check guessed grid-to-tolerance points and add them to pntList if they 1118 * are greater than the tolerance distance away from straight line segments 1119 * between the points already in pntList. 1120 * 1121 * @param pntList The existing list of gridded points on this curve. Any 1122 * points from guessPnts that are more than tol away from straight line 1123 * segments between these points are added to this list. 1124 * @param guessPnts The existing list of segment parameter derived guessed points. 1125 * @param tol2 The square of the maximum distance that a straight line between 1126 * gridded points may deviate from this curve. May not be null. 1127 */ 1128 private void checkGuessedGrid2TolPoints(FastList<SubrangePoint> pntList, 1129 FastList<SubrangePoint> guessPnts, Parameter<Area> tol2) { 1130 1131 int nSegs = guessPnts.size() - 1; 1132 for (int i=1; i < nSegs; ++i) { 1133 SubrangePoint ps = guessPnts.get(i); 1134 double s = ps.getParPosition().getValue(0); 1135 1136 // Find where this point should fall in the gridded list of points. 1137 int idx = Collections.binarySearch(pntList, ps, S_CMPRTR); 1138 if (idx < 0) { 1139 // ps is not already in the pntList, so see if it is in tolerance or not. 1140 idx = -idx - 1; 1141 1142 // Calculate a point, pa, along the line between p(i) and p(i-1) 1143 // at the position of "s". 1144 SubrangePoint pi = pntList.get(idx); 1145 SubrangePoint pim1 = pntList.get(idx-1); 1146 Point pa = interpSPoint(pim1,pi,s); 1147 1148 // Compute the distance between the point at "s" and the line seg point 1149 // and compare with the tolerance. 1150 boolean distGTtol = pa.distanceSq(ps).isGreaterThan(tol2); 1151 if (distGTtol) { 1152 // Insert the new point into the list. 1153 pntList.add(idx, ps); 1154 if (pntList.size() == MAX_POINTS) 1155 break; 1156 } else 1157 SubrangePoint.recycle(ps); 1158 } else 1159 SubrangePoint.recycle(ps); 1160 } 1161 } 1162 1163 /** 1164 * Interpolate a point at "s" between the points p1 and p2 (that must have 1165 * parametric positions surrounding "s"). 1166 * 1167 * @param p1 Point with a parametric position less than "s". 1168 * @param p2 Point with a parametric position greater than "s". 1169 * @param s The parametric position at which a point is to be interpolated 1170 * between p1 and p2. 1171 * @return The interpolated point. 1172 */ 1173 protected static Point interpSPoint(SubrangePoint p1, SubrangePoint p2, double s) { 1174 StackContext.enter(); 1175 try { 1176 double s1 = p1.getParPosition().getValue(0); 1177 double s2 = p2.getParPosition().getValue(0); 1178 Point Ds = p2.minus(p1); 1179 Point pout = p1.plus(Ds.times((s - s1) / (s2 - s1))); 1180 return StackContext.outerCopy(pout); 1181 } finally { 1182 StackContext.exit(); 1183 } 1184 } 1185 1186 /** 1187 * Guess an initial set of points to use when gridding to tolerance. The initial 1188 * points must include the start and end of the curve as well as any discontinuities 1189 * in the curve. Other than that, they should be distributed as best as possible to 1190 * indicate areas of higher curvature, but should be generated as efficiently as 1191 * possible. The guessed points MUST be monotonically increasing from 0 to 1. 1192 * <p> 1193 * The default implementation places points at the positions returned by 1194 * "getSegmentParameters()". Sub-classes should override this method if they can 1195 * provide a more efficient/robust method to locate points where there may be 1196 * curvature discontinuities or areas of high curvature. 1197 * </p> 1198 * 1199 * @param tol2 The square of the maximum distance that a straight line 1200 * between gridded points may deviate from this curve. 1201 * @return A FastList of subrange points to use as a starting point for the grid to 1202 * tolerance algorithms. 1203 */ 1204 protected FastList<SubrangePoint> guessGrid2TolPoints(Parameter<Area> tol2) { 1205 1206 // Extract the boundaries of curve segments and place grid points at those. 1207 FastList<SubrangePoint> pntList = FastList.newInstance(); 1208 1209 // Create a list of segment starting parametric positions ( and the last end position, 1). 1210 FastTable<Double> bParams = getSegmentParameters(); 1211 if (bParams.size() < 3) { 1212 // We only have 0.0 & 1.0. Insert 0.5 to ensure there is always at least one intermediate point. 1213 bParams.add(1, 0.5); 1214 } 1215 1216 // Loop over the segment starting positions. 1217 int nSegs = bParams.size(); 1218 for (int i = 0; i < nSegs; ++i) { 1219 double s0 = bParams.get(i); 1220 pntList.add(getPoint(s0)); // Add the start-point of each segment. 1221 } 1222 1223 // Cleanup before leaving. 1224 FastTable.recycle(bParams); 1225 1226 return pntList; 1227 } 1228 1229 /** 1230 * Return the intersection between a plane and this curve. 1231 * 1232 * @param plane The plane to intersect with this curve. 1233 * @param tol Fractional tolerance (in parameter space) to refine the point 1234 * positions to. 1235 * @return A PointString containing zero or more subrange points made by the 1236 * intersection of this curve with the specified plane. If no intersection is 1237 * found, an empty PointString is returned. 1238 */ 1239 @Override 1240 public PointString<SubrangePoint> intersect(GeomPlane plane, double tol) { 1241 1242 // Algorithm: Finds places on the curve where the signed distance from a curve 1243 // point to the plane is zero: n dot (p(s) - pp) = 0. 1244 // See: http://mathworld.wolfram.com/Point-PlaneDistance.html 1245 1246 // Create a function that is used to iteratively find the intersections. 1247 int numDims = getPhyDimension(); 1248 if (numDims < plane.getPhyDimension()) 1249 numDims = plane.getPhyDimension(); 1250 PlaneIntersectEvaluatable intEvaluator 1251 = PlaneIntersectEvaluatable.newInstance(this.toDimension(numDims), plane.toDimension(numDims)); 1252 1253 PointString<SubrangePoint> output = PointString.newInstance(); 1254 try { 1255 1256 // Guess brackets surrounding the intersection points (if any). 1257 List<BracketRoot1D> brackets = guessIntersect(plane, intEvaluator); 1258 1259 // Loop over all the brackets found. 1260 for (BracketRoot1D bracket : brackets) { 1261 // Find the intersections using the evaluator function. 1262 double pos = Roots.findRoot1D(intEvaluator, bracket, tol); 1263 SubrangePoint p = SubrangePoint.newInstance(this, Point.valueOf(pos)); 1264 1265 // Add the point to the list of intersections being collected. 1266 output.add(p); 1267 } 1268 1269 // Remove any near duplicates. 1270 int numPnts = output.size(); 1271 for (int i = 0; i < numPnts; ++i) { 1272 SubrangePoint pi = output.get(i); 1273 for (int j = i + 1; j < numPnts; ++j) { 1274 SubrangePoint pj = output.get(j); 1275 double d = pj.getParPosition().distanceValue(pi.getParPosition()); 1276 if (d <= tol) { 1277 output.remove(j); 1278 --numPnts; 1279 --j; 1280 } 1281 } 1282 } 1283 1284 } catch (RootException err) { 1285 Logger.getLogger(AbstractCurve.class.getName()).log(Level.SEVERE, 1286 "Could not intersect this curve witih a plane.", err); 1287 1288 } finally { 1289 PlaneIntersectEvaluatable.recycle(intEvaluator); 1290 } 1291 1292 return output; 1293 } 1294 1295 /** 1296 * Return a list of brackets that surround possible plane intersection points. Each of 1297 * the brackets will be refined using a root solver in order to find the actual 1298 * intersection points. 1299 * <p> 1300 * The default implementation sub-divides the curve into 17 equal-sized segments and 1301 * looks for a single bracket inside each segment. Sub-classes should override this 1302 * method if they can provide a more efficient/robust method to locate plane 1303 * intersection points. 1304 * </p> 1305 * 1306 * @param plane The plane to find the intersection points on this curve to/from. 1307 * @param eval A function that calculates the <code>n dot (p(s) - pp)</code> on the 1308 * curve. 1309 * @return A list of brackets that each surround a potential intersection point. 1310 * Returns an empty list if there are no intersections found. 1311 */ 1312 protected List<BracketRoot1D> guessIntersect(GeomPlane plane, Evaluatable1D eval) { 1313 1314 // Find multiple intersection points (roots) by sub-dividing curve into pieces and 1315 // bracketing any roots that occur in any of those segments. 1316 List<BracketRoot1D> brackets; 1317 try { 1318 1319 brackets = BracketRoot1D.findBrackets(requireNonNull(eval), 0, 1, getNumPointsPerSegment() * 2 + 1); 1320 1321 } catch (RootException err) { 1322 Logger.getLogger(AbstractCurve.class.getName()).log(Level.WARNING, 1323 "guessIntersect(plane, eval) failed.", err); 1324 return FastTable.newInstance(); 1325 } 1326 1327 return brackets; 1328 } 1329 1330 /** 1331 * Return the intersection between an infinite line and this curve. 1332 * 1333 * @param L0 A point on the line (origin of the line). 1334 * @param Ldir The direction vector for the line (does not have to be a unit vector). 1335 * @param tol Tolerance (in physical space) to refine the point positions to. 1336 * @return A PointString containing zero or more subrange points made by the 1337 * intersection of this curve with the specified line. If no intersection is 1338 * found, an empty PointString is returned. 1339 */ 1340 @Override 1341 public PointString<SubrangePoint> intersect(GeomPoint L0, GeomVector Ldir, Parameter<Length> tol) { 1342 requireNonNull(tol); 1343 1344 // First check to see if the line even comes near the curve. 1345 if (!GeomUtil.lineBoundsIntersect(L0, Ldir, this, null)) 1346 return PointString.newInstance(); 1347 1348 // Make sure the line and curve have the same dimensions. 1349 int numDims = getPhyDimension(); 1350 if (L0.getPhyDimension() > numDims) 1351 throw new IllegalArgumentException(MessageFormat.format( 1352 RESOURCES.getString("sameOrFewerDimErr"), "line", "curve")); 1353 else if (L0.getPhyDimension() < numDims) 1354 L0 = L0.toDimension(numDims); 1355 if (Ldir.getPhyDimension() > numDims) 1356 throw new IllegalArgumentException(MessageFormat.format( 1357 RESOURCES.getString("sameOrFewerDimErr"), "line", "curve")); 1358 else if (Ldir.getPhyDimension() < numDims) 1359 Ldir = Ldir.toDimension(numDims); 1360 1361 // Create a line-curve intersector object. 1362 LineCurveIntersect intersector = LineCurveIntersect.newInstance(this, tol, L0, Ldir); 1363 1364 // Intersect the line and curve (intersections are added to "output"). 1365 PointString<SubrangePoint> output = intersector.intersect(); 1366 1367 // Clean up. 1368 LineCurveIntersect.recycle(intersector); 1369 1370 return output; 1371 } 1372 1373 /** 1374 * Return the intersection between a line segment and this curve. 1375 * 1376 * @param line The line segment to intersect. 1377 * @param tol Tolerance (in physical space) to refine the point positions to. 1378 * @return A list containing two PointString instances each containing zero or more 1379 * subrange points, on this curve and the input line segment respectively, 1380 * made by the intersection of this curve with the specified line segment. If 1381 * no intersections are found a list of two empty PointStrings are returned. 1382 */ 1383 @Override 1384 public GeomList<PointString<SubrangePoint>> intersect(LineSegment line, Parameter<Length> tol) { 1385 requireNonNull(line); 1386 requireNonNull(tol); 1387 1388 // First check to see if the line segment even comes near the curve. 1389 GeomList<PointString<SubrangePoint>> output = GeomList.newInstance(); 1390 output.add(PointString.newInstance()); 1391 output.add(PointString.newInstance()); 1392 if (!GeomUtil.lineSegBoundsIntersect(line.getStart(), line.getEnd(), this)) 1393 return output; 1394 1395 // Check for a degenerate curve case. 1396 if (isDegenerate(tol)) { 1397 // Just see if the start point on this curve intersects that curve (is on that curve). 1398 SubrangePoint p1 = getPoint(0); 1399 Point p1r = p1.copyToReal(); 1400 1401 // Find the closest point on the surface. 1402 SubrangePoint p2 = line.getClosest(p1r, GTOL); 1403 1404 if (p1r.distance(p2).isLessThan(tol)) { 1405 // Add the points to the lists of intersections being collected. 1406 output.get(0).add(p1); 1407 output.get(1).add(p2); 1408 } 1409 1410 return output; 1411 } 1412 1413 // Make sure the line and curve have the same dimensions. 1414 int numDims = getPhyDimension(); 1415 if (line.getPhyDimension() > numDims) 1416 throw new IllegalArgumentException(MessageFormat.format( 1417 RESOURCES.getString("sameOrFewerDimErr"), "line", "curve")); 1418 else if (line.getPhyDimension() < numDims) 1419 line = line.toDimension(numDims); 1420 1421 // Create a line segment-curve intersector object. 1422 LineSegCurveIntersect intersector = LineSegCurveIntersect.newInstance(this, tol, line, output); 1423 1424 // Intersect the line segment and curve (intersections are added to "output"). 1425 output = intersector.intersect(); 1426 1427 // Clean up. 1428 LineSegCurveIntersect.recycle(intersector); 1429 1430 return output; 1431 } 1432 1433 /** 1434 * Return the intersection between another curve and this curve. 1435 * 1436 * @param curve The curve to intersect with this curve. 1437 * @param tol Tolerance (in physical space) to refine the point positions to. 1438 * @return A list containing two PointString instances each containing zero or more 1439 * subrange points, on this curve and the input curve respectively, made by 1440 * the intersection of this curve with the specified curve. If no 1441 * intersections are found a list of two empty PointStrings are returned. 1442 */ 1443 @Override 1444 public GeomList<PointString<SubrangePoint>> intersect(Curve curve, Parameter<Length> tol) { 1445 requireNonNull(curve); 1446 requireNonNull(tol); 1447 1448 // First check to see if the line segment even comes near the curve. 1449 GeomList<PointString<SubrangePoint>> output = GeomList.newInstance(); 1450 output.add(PointString.newInstance()); 1451 output.add(PointString.newInstance()); 1452 if (!GeomUtil.boundsIntersect(curve, this)) 1453 return output; 1454 1455 // Check for a degenerate curve case. 1456 if (isDegenerate(tol)) { 1457 // Just see if the start point on this curve intersects that curve (is on that curve). 1458 SubrangePoint p1 = getPoint(0); 1459 Point p1r = p1.copyToReal(); 1460 1461 // Find the closest point on the surface. 1462 SubrangePoint p2 = curve.getClosest(p1r, GTOL); 1463 1464 if (p1r.distance(p2).isLessThan(tol)) { 1465 // Add the points to the lists of intersections being collected. 1466 output.get(0).add(p1); 1467 output.get(1).add(p2); 1468 } 1469 1470 return output; 1471 } 1472 1473 // Create a curve-curve intersector object. 1474 CurveCurveIntersect intersector = CurveCurveIntersect.newInstance(this, tol, curve, output); 1475 1476 // Intersect that curve and this curve (intersections are added to "output"). 1477 output = intersector.intersect(); 1478 1479 // Clean up. 1480 CurveCurveIntersect.recycle(intersector); 1481 1482 return output; 1483 } 1484 1485 /** 1486 * Return the intersections between a surface and this curve. 1487 * 1488 * @param surface The surface to intersect with this curve. 1489 * @param tol Tolerance (in physical space) to refine the point positions to. 1490 * @return A list containing two PointString instances each containing zero or more 1491 * subrange points, on this curve and the input surface respectively, made by 1492 * the intersection of this curve with the specified surface. If no 1493 * intersections are found a list of two empty PointStrings are returned. 1494 */ 1495 @Override 1496 public GeomList<PointString<SubrangePoint>> intersect(Surface surface, Parameter<Length> tol) { 1497 requireNonNull(surface); 1498 requireNonNull(tol); 1499 1500 PointString<SubrangePoint> thisOutput = PointString.newInstance(); 1501 PointString<SubrangePoint> thatOutput = PointString.newInstance(); 1502 GeomList<PointString<SubrangePoint>> output = GeomList.newInstance(); 1503 output.add(thisOutput); 1504 output.add(thatOutput); 1505 1506 // First check to see if the curve even comes near the surface. 1507 if (!GeomUtil.boundsIntersect(this, surface)) 1508 return output; 1509 1510 // Make sure the line and curve have the same dimensions. 1511 Surface origSrf = surface; 1512 int numDims = getPhyDimension(); 1513 if (surface.getPhyDimension() > numDims) 1514 throw new IllegalArgumentException(MessageFormat.format( 1515 RESOURCES.getString("sameOrFewerDimErr"), "surface", "curve")); 1516 else if (surface.getPhyDimension() < numDims) 1517 surface = surface.toDimension(numDims); 1518 1519 // Check for a degenerate curve case. 1520 if (isDegenerate(tol)) { 1521 // Just see if the start point on the curve intersects the surface (is on the surface). 1522 SubrangePoint p1 = getPoint(0); 1523 Point p1r = p1.copyToReal(); 1524 1525 // Find the closest point on the surface. 1526 SubrangePoint p2 = origSrf.getClosest(p1r, GTOL); 1527 1528 if (p1r.distance(p2).isLessThan(tol)) { 1529 // Add the points to the lists of intersections being collected. 1530 thisOutput.add(p1); 1531 thatOutput.add(p2); 1532 } 1533 1534 return output; 1535 } 1536 1537 // Convert the inputs to the units of this surface. 1538 Unit<Length> unit = getUnit(); 1539 surface = surface.to(unit); 1540 tol = tol.to(unit); 1541 1542 // Determine the parameter tolerance to use. 1543 double arcLength = this.getArcLength(GTOL * 100).getValue(); 1544 double ptol = tol.getValue() / arcLength; // Tolerance for root finder in parameter space. 1545 if (ptol < GTOL) 1546 ptol = GTOL; 1547 1548 // Find potential intersection points (roots) by sub-dividing curve into pieces and 1549 // bracketing any roots that occur in any of those segments. Then, a 1D root finder 1550 // is used to find the bracketed roots. 1551 CurveSrfIntersectFun eval = CurveSrfIntersectFun.newInstance(this, surface, GTOL * 1000); 1552 try { 1553 1554 // Guess brackets surrounding the intersection points (if any). 1555 List<BracketRoot1D> brackets = guessIntersect(surface, eval); 1556 1557 // Increase the tolerance for actual root finding. 1558 eval.tol = GTOL; 1559 1560 // Loop over all the brackets found. 1561 for (BracketRoot1D bracket : brackets) { 1562 1563 // Find the intersections using the evaluator function. 1564 eval.firstPass = true; 1565 double pos = Roots.findRoot1D(eval, bracket, ptol); 1566 SubrangePoint p1 = SubrangePoint.newInstance(this, Point.valueOf(pos)); 1567 Point p1r = p1.copyToReal(); 1568 1569 // Find the closest point on the surface. 1570 SubrangePoint p2 = origSrf.getClosest(p1r, eval._nearS, eval._nearT, GTOL); 1571 1572 if (p1r.distance(p2).isLessThan(tol)) { 1573 // Add the points to the lists of intersections being collected. 1574 thisOutput.add(p1); 1575 thatOutput.add(p2); 1576 } 1577 } 1578 1579 } catch (RootException e) { 1580 //e.printStackTrace(); 1581 1582 } finally { 1583 CurveSrfIntersectFun.recycle(eval); 1584 } 1585 1586 return output; 1587 } 1588 1589 /** 1590 * Return a list of brackets that surround possible surface intersection points. Each 1591 * of the brackets will be refined using a root solver in order to find the actual 1592 * intersection points. 1593 * <p> 1594 * The default implementation sub-divides the curve into 17 equal-sized segments and 1595 * looks for a single bracket inside each segment. Sub-classes should override this 1596 * method if they can provide a more efficient/robust method to locate surface 1597 * intersection points. 1598 * </p> 1599 * 1600 * @param surface The surface to find the intersection points on this curve to/from. 1601 * @param eval A function that calculates the <code>n dot (p(s) - pp)</code> on the 1602 * curve. 1603 * @return A list of brackets that each surround a potential intersection point. 1604 * Returns an empty list if there are no intersections found. 1605 */ 1606 protected List<BracketRoot1D> guessIntersect(Surface surface, Evaluatable1D eval) { 1607 1608 // Find multiple intersection points (roots) by sub-dividing curve into pieces and 1609 // bracketing any roots that occur in any of those segments. 1610 List<BracketRoot1D> brackets; 1611 try { 1612 1613 brackets = BracketRoot1D.findBrackets(requireNonNull(eval), 0, 1, getNumPointsPerSegment() * 2 + 1); 1614 1615 } catch (RootException err) { 1616 Logger.getLogger(AbstractCurve.class.getName()).log(Level.WARNING, 1617 "guessIntersect(surface, eval) failed.", err); 1618 return FastTable.newInstance(); 1619 } 1620 1621 return brackets; 1622 } 1623 1624 /** 1625 * Returns the closest point on this curve to the specified point. 1626 * 1627 * @param point The point to find the closest point on this curve to. 1628 * @param tol Fractional tolerance (in parameter space) to refine the point position 1629 * to. 1630 * @return The point on this curve that is closest to the specified point. 1631 */ 1632 @Override 1633 public SubrangePoint getClosest(GeomPoint point, double tol) { 1634 return getClosestFarthest(point, tol, true); 1635 } 1636 1637 /** 1638 * Returns the closest point on this curve to the specified point near the specified 1639 * parametric position. This may not return the global closest point! 1640 * 1641 * @param point The point to find the closest point on this curve to. 1642 * @param near The parametric position where the search for the closest point should 1643 * be started. 1644 * @param tol Fractional tolerance (in parameter space) to refine the point position 1645 * to. 1646 * @return The point on this curve that is closest to the specified point near the 1647 * specified parametric position. 1648 */ 1649 @Override 1650 public SubrangePoint getClosest(GeomPoint point, double near, double tol) { 1651 return getClosestFarthestNear(point, near, tol, true); 1652 } 1653 1654 /** 1655 * Returns the closest points on this curve to the specified list of points. 1656 * 1657 * @param points The list of points to find the closest points on this curve to. 1658 * @param tol Fractional tolerance (in parameter space) to refine the point 1659 * positions to. 1660 * @return The list of {@link SubrangePoint} objects on this curve that are closest to 1661 * the specified points. 1662 */ 1663 @Override 1664 public PointString<SubrangePoint> getClosest(List<? extends GeomPoint> points, double tol) { 1665 return getClosestFarthest(points, tol, true); 1666 } 1667 1668 /** 1669 * Returns the farthest point on this curve from the specified point. 1670 * 1671 * @param point The point to find the farthest point on this curve from. 1672 * @param tol Fractional tolerance (in parameter space) to refine the point position 1673 * to. 1674 * @return The {@link SubrangePoint} on this curve that is farthest from the specified 1675 * point. 1676 */ 1677 @Override 1678 public SubrangePoint getFarthest(GeomPoint point, double tol) { 1679 return getClosestFarthest(point, tol, false); 1680 } 1681 1682 /** 1683 * Returns the farthest point on this curve from the specified point near the 1684 * specified parametric position. This may not return the global farthest point! 1685 * 1686 * @param point The point to find the farthest point on this curve from. 1687 * @param near The parametric position where the search for the farthest point should 1688 * be started. 1689 * @param tol Fractional tolerance (in parameter space) to refine the point position 1690 * to. 1691 * @return The {@link SubrangePoint} on this curve that is farthest from the specified 1692 * point. 1693 */ 1694 @Override 1695 public SubrangePoint getFarthest(GeomPoint point, double near, double tol) { 1696 return getClosestFarthestNear(point, near, tol, false); 1697 } 1698 1699 /** 1700 * Returns the farthest points on this curve to the specified list of points. 1701 * 1702 * @param points The list of points to find the farthest points on this curve to. 1703 * @param tol Fractional tolerance (in parameter space) to refine the point 1704 * positions to. 1705 * @return The list of {@link SubrangePoint} objects on this curve that are farthest 1706 * from the specified points. 1707 */ 1708 @Override 1709 public PointString<SubrangePoint> getFarthest(List<? extends GeomPoint> points, double tol) { 1710 return getClosestFarthest(points, tol, false); 1711 } 1712 1713 /** 1714 * Returns the closest/farthest points on this curve to the specified point. 1715 * 1716 * @param point The point to find the closest/farthest point on this curve from. 1717 * @param tol Fractional tolerance (in parameter space) to refine the point 1718 * position to. 1719 * @param closest Set to <code>true</code> to return the closest point or 1720 * <code>false</code> to return the farthest point. 1721 * @return The {@link SubrangePoint} on this curve that is closest/farthest from the 1722 * specified point. 1723 */ 1724 protected SubrangePoint getClosestFarthest(GeomPoint point, double tol, boolean closest) { 1725 1726 double sout = 0; 1727 StackContext.enter(); 1728 try { 1729 // Make sure the point and curve have the same dimensions. 1730 int numDims = getPhyDimension(); 1731 if (point.getPhyDimension() > numDims) { 1732 throw new IllegalArgumentException(MessageFormat.format( 1733 RESOURCES.getString("sameOrFewerDimErr"), "point", "curve")); 1734 } else if (point.getPhyDimension() < numDims) { 1735 point = point.toDimension(numDims); 1736 } 1737 1738 // Create a function that calculates the dot product of (p-q) and ps. When this 1739 // is zero, a point on the curve that forms a vector perpendicular to the curve has been found. 1740 PerpPointEvaluatable perpPointEval = PerpPointEvaluatable.newInstance(this, point); 1741 1742 // Find brackets surrounding possible closest/farthest points. 1743 List<BracketRoot1D> brackets = guessClosestFarthest(point, closest, perpPointEval); 1744 1745 // Find the closest/farthest point. 1746 SubrangePoint output = getClosestFarthest(point, tol, closest, perpPointEval, brackets); 1747 sout = output.getParPosition().getValue(0); 1748 1749 } finally { 1750 StackContext.exit(); 1751 } 1752 1753 return getPoint(sout); 1754 } 1755 1756 /** 1757 * Returns the closest/farthest points on this curve to the specified list of points. 1758 * 1759 * @param point The point to find the closest/farthest point on this curve from. 1760 * @param near The parametric position where the search for the farthest point 1761 * should be started. 1762 * @param tol Fractional tolerance (in parameter space) to refine the point 1763 * position to. 1764 * @param closest Set to <code>true</code> to return the closest point or 1765 * <code>false</code> to return the farthest point. 1766 * @return The {@link SubrangePoint} on this curve that is closest/farthest from the 1767 * specified point. 1768 */ 1769 private SubrangePoint getClosestFarthestNear(GeomPoint point, double near, double tol, boolean closest) { 1770 validateParameter(near); 1771 1772 double sout = 0; 1773 StackContext.enter(); 1774 try { 1775 // Make sure the point and curve have the same dimensions. 1776 int numDims = getPhyDimension(); 1777 if (point.getPhyDimension() > numDims) { 1778 throw new IllegalArgumentException(MessageFormat.format( 1779 RESOURCES.getString("sameOrFewerDimErr"), "point", "curve")); 1780 } else if (point.getPhyDimension() < numDims) { 1781 point = point.toDimension(numDims); 1782 } 1783 1784 // Create a function that calculates the dot product of (p-q) and ps. When this 1785 // is zero, a point on the curve that forms a vector perpendicular to the curve has been found. 1786 PerpPointEvaluatable perpPointEval = PerpPointEvaluatable.newInstance(this, point); 1787 1788 // Find the closet/farthest point near the specified parametric position. 1789 try { 1790 List<BracketRoot1D> brackets = bracketNear(perpPointEval, near); 1791 SubrangePoint output = getClosestFarthest(point, tol, closest, perpPointEval, brackets); 1792 sout = output.getParPosition().getValue(0); 1793 1794 } catch (RootException err) { 1795 Logger.getLogger(AbstractCurve.class.getName()).log(Level.WARNING, 1796 "Failed to find closest/farthest point near existing point.", err); 1797 sout = 0; 1798 } 1799 1800 } finally { 1801 StackContext.exit(); 1802 } 1803 1804 return getPoint(sout); 1805 } 1806 1807 /** 1808 * Returns the closest/farthest points on this curve to the specified list of points. 1809 * 1810 * @param points The list of points to find the closest/farthest points on this curve 1811 * to. 1812 * @param tol Fractional tolerance (in parameter space) to refine the point 1813 * positions to. 1814 * @param closest Set to <code>true</code> to return the closest point or 1815 * <code>false</code> to return the farthest point. 1816 * @return The list of {@link SubrangePoint} objects on this curve that are 1817 * closest/farthest to the specified points. 1818 */ 1819 @SuppressWarnings("null") 1820 private PointString<SubrangePoint> getClosestFarthest(List<? extends GeomPoint> points, double tol, boolean closest) { 1821 PointString<SubrangePoint> output = PointString.newInstance(); 1822 1823 // Loop over all the target points in the supplied list and find the closest curve point for each one. 1824 SubrangePoint p_old = null; 1825 int numDims = getPhyDimension(); 1826 int size = points.size(); 1827 for (int i = 0; i < size; ++i) { 1828 // Get the target point. 1829 GeomPoint q = points.get(i); 1830 1831 // Make sure the point and curve have the same dimensions. 1832 if (q.getPhyDimension() > numDims) 1833 throw new IllegalArgumentException(MessageFormat.format( 1834 RESOURCES.getString("sameOrFewerDimErr"), "points", "curve")); 1835 else if (q.getPhyDimension() < numDims) 1836 q = q.toDimension(numDims); 1837 1838 // Create a function that calculates the dot product of (p(s)-q) and ps(s). When this 1839 // is zero, a point on the curve that forms a vector with the target perpendicular to the curve has been found. 1840 PerpPointEvaluatable perpPointEval = PerpPointEvaluatable.newInstance(this, q); 1841 1842 // Find multiple closest/farthest points (roots) by sub-dividing curve into pieces and 1843 // bracketing any roots that occur in any of those segments. 1844 List<BracketRoot1D> brackets; 1845 try { 1846 if (i == 0) 1847 // Look across the entire curve. 1848 brackets = guessClosestFarthest(q, closest, perpPointEval); 1849 else 1850 // Look for brackets near the previous point. 1851 brackets = bracketNear(perpPointEval, p_old.getParPosition().getValue(0)); 1852 } catch (RootException err) { 1853 Logger.getLogger(AbstractCurve.class.getName()).log(Level.WARNING, 1854 "Could not find closest/farthest point.", err); 1855 return output; 1856 } 1857 1858 // Now use a root finder to actually find the closest/farthest point on the curve. 1859 SubrangePoint p = getClosestFarthest(q, tol, closest, perpPointEval, brackets); 1860 output.add(p); 1861 p_old = p; 1862 1863 // Recycle temporary objects. 1864 PerpPointEvaluatable.recycle(perpPointEval); 1865 } 1866 1867 return output; 1868 } 1869 1870 /** 1871 * Return a list of brackets that surround possible closest/farthest points. Each of 1872 * the brackets will be refined using a root solver in order to find the actual 1873 * closest or farthest points. 1874 * <p> 1875 * The default implementation sub-divides the curve into 21 equal-sized segments and 1876 * looks for a single bracket inside each segment. Sub-classes should override this 1877 * method if they can provide a more efficient/robust method to locate 1878 * closest/farthest points. 1879 * </p> 1880 * 1881 * @param point The point to find the closest/farthest point on this curve to/from 1882 * (guaranteed to be the same physical dimension before this method is 1883 * called). 1884 * @param closest Set to <code>true</code> to return the closest point or 1885 * <code>false</code> to return the farthest point. 1886 * @param eval A function that calculates the dot product of (p(s)-q) and ps(s) on 1887 * the curve. 1888 * @return A list of brackets that each surround a potential closest/farthest point. 1889 */ 1890 protected List<BracketRoot1D> guessClosestFarthest(GeomPoint point, boolean closest, 1891 Evaluatable1D eval) { 1892 1893 // Find multiple closest/farthest points (roots) by sub-dividing curve into pieces and 1894 // bracketing any roots that occur in any of those segments. 1895 List<BracketRoot1D> brackets; 1896 try { 1897 1898 brackets = BracketRoot1D.findBrackets(requireNonNull(eval), 0, 1, 21); 1899 1900 } catch (RootException err) { 1901 Logger.getLogger(AbstractCurve.class.getName()).log(Level.WARNING, 1902 "Failed to guess closest/farthest point.", err); 1903 return FastTable.newInstance(); 1904 } 1905 1906 return brackets; 1907 } 1908 1909 /** 1910 * Look for and bracket roots at parametric positions near the specified parametric 1911 * position value. 1912 * 1913 * @param eval A function that calculates the dot product of (p(s)-q) and ps(s) on the 1914 * curve. 1915 * @param near The parametric position on the curve to begin search for roots near. 1916 * @return A list of brackets of roots to the perpPointEval function each of which 1917 * could be the closest/farthest. 1918 * @throws jahuwaldt.tools.math.RootException if a bracket could not be found. 1919 */ 1920 protected List<BracketRoot1D> bracketNear(Evaluatable1D eval, double near) throws RootException { 1921 requireNonNull(eval); 1922 List<BracketRoot1D> brackets = null; 1923 1924 // Look near the previous parametric position by expanding the upper and lower limits until 1925 // at least one bracket is found. 1926 double old_sm = near; 1927 double old_sp = near - 0.01; 1928 if (old_sp < 0) 1929 old_sp = 0; 1930 do { 1931 double sm = old_sm - 0.1; 1932 if (sm < 0) 1933 sm = 0; 1934 double sp = old_sp + 0.1; 1935 if (sp > 1) 1936 sp = 1; 1937 // Look on either side of near. 1938 brackets = BracketRoot1D.findBrackets(eval, sm, sp, 10); 1939 old_sm = sm; 1940 old_sp = sp; 1941 } while (brackets.size() < 1 && !(MathTools.isApproxEqual(old_sp, 1) && MathTools.isApproxZero(old_sm))); 1942 1943 return brackets; 1944 } 1945 1946 /** 1947 * Returns the closest point on this curve, p, to the specified point, q. 1948 * 1949 * @param point The point, q, to find the closest point on this curve to 1950 * (guaranteed to be the same physical dimension before this 1951 * method is called). 1952 * @param tol Fractional tolerance (in parameter space) to refine the point 1953 * position to. 1954 * @param closest Set to <code>true</code> to return the closest point or 1955 * <code>false</code> to return the farthest point. 1956 * @param perpPointEval A function that calculates the dot product of (p(s)-q) and 1957 * ps(s) on the curve. 1958 * @param brackets A list of brackets of roots to the perpPointEval function each 1959 * of which could be the closest/farthest. 1960 * @return The {@link SubrangePoint} on this curve that is closest to the specified 1961 * point. 1962 */ 1963 private SubrangePoint getClosestFarthest(GeomPoint point, double tol, boolean closest, PerpPointEvaluatable perpPointEval, List<BracketRoot1D> brackets) { 1964 1965 double sout = 0; 1966 StackContext.enter(); 1967 try { 1968 // Create a list of potential closest points and add the ends of the curve to it. 1969 FastTable<SubrangePoint> potentials = FastTable.newInstance(); 1970 potentials.add(SubrangePoint.newInstance(this, 0)); 1971 potentials.add(SubrangePoint.newInstance(this, 1)); 1972 1973 try { 1974 // Loop over all the brackets found. 1975 for (BracketRoot1D bracket : brackets) { 1976 // Find the perpendicular point on the curve using the evaluator function. 1977 perpPointEval.firstPass = true; 1978 double s = Roots.findRoot1D(perpPointEval, bracket, tol); 1979 1980 // Add the point to the list of potential closest/farthest points being collected. 1981 potentials.add(SubrangePoint.newInstance(this, s)); 1982 } 1983 1984 } catch (RootException err) { 1985 Logger.getLogger(AbstractCurve.class.getName()).log(Level.WARNING, 1986 "Failed to find perpendicular point along curve.", err); 1987 1988 } finally { 1989 if (potentials.isEmpty()) { 1990 sout = 0; 1991 1992 } else { 1993 // Loop over the potentials and find the one that is the closest/farthest. 1994 SubrangePoint output = potentials.get(0); 1995 double d2Opt = point.distanceSqValue(output); 1996 int size = potentials.size(); 1997 for (int i = 1; i < size; ++i) { 1998 SubrangePoint p = potentials.get(i); 1999 double dist2 = point.distanceSqValue(p); 2000 if (closest) { 2001 if (dist2 < d2Opt) { 2002 d2Opt = dist2; 2003 output = p; 2004 } 2005 } else { 2006 if (dist2 > d2Opt) { 2007 d2Opt = dist2; 2008 output = p; 2009 } 2010 } 2011 } 2012 sout = output.getParPosition().getValue(0); 2013 } 2014 } 2015 } finally { 2016 StackContext.exit(); 2017 } 2018 2019 return getPoint(sout); 2020 } 2021 2022 /** 2023 * Returns the closest points (giving the minimum distance) between this curve and the 2024 * specified curve. If the specified curve intersects this curve, then the 1st 2025 * intersection found is returned (not all the intersections are found and there is no 2026 * guarantee about which intersection will be returned). If the specified curve is 2027 * parallel to this curve (all points are equidistant away), then any point between 2028 * the two curves could be returned as the "closest". 2029 * 2030 * @param curve The curve to find the closest points between this curve and that one. 2031 * @param tol Fractional tolerance (in parameter space) to refine the point position 2032 * to. 2033 * @return A list containing two SubrangePoint objects that represent the closest 2034 * point on this curve (index 0) and the closest point on the specified curve 2035 * (index 1). 2036 */ 2037 @Override 2038 public PointString<SubrangePoint> getClosest(Curve curve, double tol) { 2039 2040 // Make sure that curve and this curve have the same dimensions. 2041 int numDims = getPhyDimension(); 2042 if (curve.getPhyDimension() > numDims) 2043 throw new IllegalArgumentException(MessageFormat.format( 2044 RESOURCES.getString("sameOrFewerDimErr"), "input curve", "curve")); 2045 else if (curve.getPhyDimension() < numDims) 2046 curve = curve.toDimension(numDims); 2047 2048 // Create a function that calculates the dot product of (p-q) and ps where "q" is the closest 2049 // point on the target curve. When this is zero, a point on the curve that forms a 2050 // vector perpendicular to the curve has been found. 2051 PerpPointParGeomEvaluatable perpPointEval = PerpPointParGeomEvaluatable.newInstance(this, curve, tol); 2052 2053 // Find brackets surrounding possible closest/farthest points. 2054 List<BracketRoot1D> brackets = guessClosest(curve, perpPointEval); 2055 2056 // Find the closest point. 2057 PointString<SubrangePoint> output = getClosest(curve, tol, perpPointEval, brackets); 2058 2059 // Recycle temporary objects. 2060 PerpPointParGeomEvaluatable.recycle(perpPointEval); 2061 2062 return output; 2063 } 2064 2065 /** 2066 * Return a list of brackets that surround possible points on this curve that are 2067 * closest to the target parametric geometry object. Each of the brackets will be 2068 * refined using a root solver in order to find the actual closest points. 2069 * <p> 2070 * The default implementation sub-divides the curve into 21 equal-sized segments and 2071 * looks for a single bracket inside each segment. Sub-classes should override this 2072 * method if they can provide a more efficient/robust method to locate closest 2073 * points. 2074 * </p> 2075 * 2076 * @param parGeom The target parametric geometry to find the closest point on this 2077 * curve to (guaranteed to be the same physical dimension before this 2078 * method is called). 2079 * @param eval A function that calculates the dot product of (p(s)-q) and ps(s) on 2080 * the curve where "q" is the closest point on the target parametric 2081 * geometry object. 2082 * @return A list of brackets that each surround a potential closest point on this 2083 * curve. 2084 */ 2085 protected List<BracketRoot1D> guessClosest(ParametricGeometry parGeom, PerpPointParGeomEvaluatable eval) { 2086 2087 // Find multiple closest/farthest points (roots) by sub-dividing curve into pieces and 2088 // bracketing any roots that occur in any of those segments. 2089 List<BracketRoot1D> brackets; 2090 try { 2091 2092 brackets = BracketRoot1D.findBrackets(requireNonNull(eval), 0, 1, 21); 2093 2094 } catch (RootException err) { 2095 Logger.getLogger(AbstractCurve.class.getName()).log(Level.WARNING, 2096 "Failed to bracket closest points.", err); 2097 return FastTable.newInstance(); 2098 } 2099 2100 return brackets; 2101 } 2102 2103 /** 2104 * Returns a list containing the closest point on this curve, p, to the closest point 2105 * on the target parametric geometry, q. 2106 * 2107 * @param parGeom The parametric geometry to find the closest point on this 2108 * curve to (guaranteed to be the same physical dimension before 2109 * this method is called). 2110 * @param tol Fractional tolerance (in parameter space) to refine the point 2111 * position to. 2112 * @param perpPointEval A function that calculates the dot product of (p(s)-q) and 2113 * ps(s) on the curve where "q" is the closest point on the 2114 * target parametric geometry object. 2115 * @param brackets A list of brackets of roots to the perpPointEval function each 2116 * of which could be the closest/farthest. 2117 * @return A list containing SubrangePoint objects representing the closest points on 2118 * this curve (index 0) and the target parametric geometry (index 1). 2119 */ 2120 private PointString<SubrangePoint> getClosest(ParametricGeometry parGeom, double tol, PerpPointParGeomEvaluatable perpPointEval, 2121 List<BracketRoot1D> brackets) { 2122 GeomPoint thisParPos, thatParPos; 2123 2124 StackContext.enter(); 2125 try { 2126 SubrangePoint thisOutput, thatOutput; 2127 2128 // Create a list of potential closest points and add the ends of this curve to it. 2129 FastTable<SubrangePoint> potentials = FastTable.newInstance(); 2130 potentials.add(SubrangePoint.newInstance(this, 0)); 2131 potentials.add(SubrangePoint.newInstance(this, 1)); 2132 2133 try { 2134 // Loop over all the brackets found. 2135 for (BracketRoot1D bracket : brackets) { 2136 // Find the perpendicular point on this curve using the evaluator function. 2137 double s = Roots.findRoot1D(perpPointEval, bracket, tol); 2138 2139 // Add the point to the list of potential closest/farthest points being collected. 2140 potentials.add(SubrangePoint.newInstance(this, s)); 2141 } 2142 2143 } catch (RootException err) { 2144 Logger.getLogger(AbstractCurve.class.getName()).log(Level.WARNING, 2145 "Failed to find perpendicular points on curve.", err); 2146 2147 } finally { 2148 if (potentials.isEmpty()) { 2149 thisOutput = getPoint(0); 2150 thatOutput = parGeom.getClosest(thisOutput, tol); 2151 2152 } else { 2153 // Loop over the potentials and find the one that is the closest. 2154 thisOutput = potentials.get(0); 2155 thatOutput = parGeom.getClosest(thisOutput, tol); 2156 double dOpt = thatOutput.distanceValue(thisOutput); 2157 int size = potentials.size(); 2158 for (int i = 1; i < size; ++i) { 2159 SubrangePoint p = potentials.get(i); 2160 SubrangePoint q = parGeom.getClosest(p, tol); 2161 double dist = q.distanceValue(p); 2162 if (dist < dOpt) { 2163 dOpt = dist; 2164 thisOutput = p; 2165 thatOutput = q; 2166 } 2167 } 2168 } 2169 } 2170 2171 // Copy out the parametric positions of the closest points. 2172 thisParPos = StackContext.outerCopy(thisOutput.getParPosition().immutable()); 2173 thatParPos = StackContext.outerCopy(thatOutput.getParPosition().immutable()); 2174 2175 } finally { 2176 StackContext.exit(); 2177 } 2178 2179 // Turn parametric positions back into actual subrange points. 2180 PointString<SubrangePoint> output = PointString.newInstance(); 2181 output.add(SubrangePoint.newInstance(this, thisParPos)); 2182 output.add(SubrangePoint.newInstance(parGeom, thatParPos)); 2183 2184 return output; 2185 } 2186 2187 /** 2188 * Returns the closest points (giving the minimum distance) between this curve and the 2189 * specified surface. If this curve intersects the specified surface, then the 1st 2190 * intersection found is returned (not all the intersections are found and there is no 2191 * guarantee about which intersection will be returned). If this curve is parallel to 2192 * the specified surface (all closest points are equidistant away), then any point on 2193 * this curve and the closest point on the target surface will be returned as 2194 * "closest". 2195 * 2196 * @param surface The surface to find the closest points between this curve and that 2197 * surface. 2198 * @param tol Fractional tolerance (in parameter space) to refine the point 2199 * position to. 2200 * @return A list containing two SubrangePoint objects that represent the closest 2201 * point on this curve (index 0) and the closest point on the specified 2202 * surface (index 1). 2203 */ 2204 @Override 2205 public PointString<SubrangePoint> getClosest(Surface surface, double tol) { 2206 2207 // Make sure that surface and this curve have the same dimensions. 2208 int numDims = getPhyDimension(); 2209 if (surface.getPhyDimension() > numDims) 2210 throw new IllegalArgumentException(MessageFormat.format( 2211 RESOURCES.getString("sameOrFewerDimErr"), "surface", "curve")); 2212 else if (surface.getPhyDimension() < numDims) 2213 surface = surface.toDimension(numDims); 2214 2215 // Create a function that calculates the dot product of (p-q) and ps where "q" is the closest 2216 // point on the target surface. When this is zero, a point on the curve that forms a 2217 // vector perpendicular to the curve has been found. 2218 PerpPointParGeomEvaluatable perpPointEval = PerpPointParGeomEvaluatable.newInstance(this, surface, tol); 2219 2220 // Find brackets surrounding possible closest/farthest points. 2221 List<BracketRoot1D> brackets = guessClosest(surface, perpPointEval); 2222 2223 // Find the closest point. 2224 PointString<SubrangePoint> output = getClosest(surface, tol, perpPointEval, brackets); 2225 2226 // Recycle temporary objects. 2227 PerpPointParGeomEvaluatable.recycle(perpPointEval); 2228 2229 return output; 2230 } 2231 2232 /** 2233 * Returns the closest points (giving the minimum distance) between this curve and the 2234 * specified plane. If this curve intersects the specified plane, then the 1st 2235 * intersection found is returned (not all the intersections are found and there is no 2236 * guarantee about which intersection will be returned). If this curve is parallel to 2237 * the specified plane (all closest points are equidistant away), then any point on 2238 * this curve and the closest point on the target plane will be returned as "closest". 2239 * 2240 * @param plane The plane to find the closest points between this curve and that 2241 * plane. 2242 * @param tol Fractional tolerance (in parameter space) to refine the point position 2243 * to. 2244 * @return The closest point found on this curve to the specified plane. 2245 */ 2246 @Override 2247 public SubrangePoint getClosest(GeomPlane plane, double tol) { 2248 2249 // Make sure that the input plane and this curve have the same dimensions. 2250 int numDims = getPhyDimension(); 2251 if (plane.getPhyDimension() > numDims) 2252 throw new IllegalArgumentException(MessageFormat.format( 2253 RESOURCES.getString("sameOrFewerDimErr"), "plane", "curve")); 2254 else if (plane.getPhyDimension() < numDims) 2255 plane = plane.toDimension(numDims); 2256 2257 // Create a function that calculates the dot product of (p-q) and ps where "q" is the closest 2258 // point on the target plane. When this is zero, a point on the curve that forms a 2259 // vector perpendicular to the curve has been found. 2260 PerpPointPlaneEvaluatable perpPointEval = PerpPointPlaneEvaluatable.newInstance(this, plane); 2261 2262 // Find brackets surrounding possible closest/farthest points. 2263 List<BracketRoot1D> brackets = guessClosest(perpPointEval); 2264 2265 // Find the closest point. 2266 SubrangePoint output = getClosest(plane, tol, perpPointEval, brackets); 2267 2268 // Recycle temporary objects. 2269 PerpPointPlaneEvaluatable.recycle(perpPointEval); 2270 2271 return output; 2272 } 2273 2274 /** 2275 * Return a list of brackets that surround possible points on this curve that are 2276 * closest to the target geometry object. Each of the brackets will be refined using a 2277 * root solver in order to find the actual closest points. 2278 * <p> 2279 * The default implementation sub-divides the curve into 21 equal-sized segments and 2280 * looks for a single bracket inside each segment. Sub-classes should override this 2281 * method if they can provide a more efficient/robust method to locate closest 2282 * points. 2283 * </p> 2284 * 2285 * @param eval A function that calculates the dot product of (p(s)-q) and ps(s) on the 2286 * curve where "q" is the closest point on the target geometry object. 2287 * @return A list of brackets that each surround a potential closest point on this 2288 * curve. 2289 */ 2290 protected List<BracketRoot1D> guessClosest(PerpPointPlaneEvaluatable eval) { 2291 2292 // Find multiple closest/farthest points (roots) by sub-dividing curve into pieces and 2293 // bracketing any roots that occur in any of those segments. 2294 List<BracketRoot1D> brackets; 2295 try { 2296 brackets = BracketRoot1D.findBrackets(requireNonNull(eval), 0, 1, 21); 2297 2298 } catch (RootException err) { 2299 Logger.getLogger(AbstractCurve.class.getName()).log(Level.WARNING, 2300 "Failed to bracket closest points", err); 2301 return FastTable.newInstance(); 2302 } 2303 2304 return brackets; 2305 } 2306 2307 /** 2308 * Returns a list containing the closest point on this curve, p, to the closest point 2309 * on the target plane, q. 2310 * 2311 * @param plane The plane to find the closest point on this curve to 2312 * (guaranteed to be the same physical dimension before this 2313 * method is called). 2314 * @param tol Fractional tolerance (in parameter space) to refine the point 2315 * position to. 2316 * @param perpPointEval A function that calculates the dot product of (p(s)-q) and 2317 * ps(s) on the curve where "q" is the closest point on the 2318 * target plane object. 2319 * @param brackets A list of brackets of roots to the perpPointEval function each 2320 * of which could be the closest/farthest. 2321 * @return The closest point found on this curve to the specified plane. 2322 */ 2323 private SubrangePoint getClosest(GeomPlane plane, double tol, PerpPointPlaneEvaluatable perpPointEval, 2324 List<BracketRoot1D> brackets) { 2325 double sOutput; 2326 2327 StackContext.enter(); 2328 try { 2329 SubrangePoint thisOutput; 2330 2331 // Create a list of potential closest points and add the ends of this curve to it. 2332 FastTable<SubrangePoint> potentials = FastTable.newInstance(); 2333 potentials.add(SubrangePoint.newInstance(this, 0)); 2334 potentials.add(SubrangePoint.newInstance(this, 1)); 2335 2336 // Loop over all the brackets found. 2337 for (BracketRoot1D bracket : brackets) { 2338 try { 2339 // Find the perpendicular point on this curve using the evaluator function. 2340 double s = Roots.findRoot1D(perpPointEval, bracket, tol); 2341 2342 // Add the point to the list of potential closest/farthest points being collected. 2343 potentials.add(SubrangePoint.newInstance(this, s)); 2344 2345 } catch (RootException e) { 2346 // Just ignore. 2347 //e.printStackTrace(); 2348 } 2349 } 2350 2351 if (potentials.isEmpty()) { 2352 thisOutput = getPoint(0); 2353 2354 } else { 2355 // Loop over the potentials and find the one that is the closest. 2356 thisOutput = potentials.get(0); 2357 GeomPoint q = plane.getClosest(thisOutput); 2358 double dOpt = q.distanceValue(thisOutput); 2359 int size = potentials.size(); 2360 for (int i = 1; i < size; ++i) { 2361 SubrangePoint p = potentials.get(i); 2362 q = plane.getClosest(p); 2363 double dist = q.distanceValue(p); 2364 if (dist < dOpt) { 2365 dOpt = dist; 2366 thisOutput = p; 2367 } 2368 } 2369 } 2370 2371 // Get the parametric position on this curve of the closest point. 2372 sOutput = thisOutput.getParPosition().getValue(0); 2373 2374 } finally { 2375 StackContext.exit(); 2376 } 2377 2378 return getPoint(sOutput); 2379 } 2380 2381 /** 2382 * Returns the most extreme point, either minimum or maximum, in the specified 2383 * coordinate direction on this curve. This is more accurate than 2384 * <code>getBoundsMax</code> & <code>getBoundsMin</code>, but also typically takes 2385 * longer to compute. 2386 * 2387 * @param dim An index indicating the dimension to find the min/max point for (0=X, 2388 * 1=Y, 2=Z, etc). 2389 * @param max Set to <code>true</code> to return the maximum value, <code>false</code> 2390 * to return the minimum. 2391 * @param tol Fractional tolerance (in parameter space) to refine the min/max point 2392 * position to. 2393 * @return The point found on this curve that is the min or max in the specified 2394 * coordinate direction. 2395 * @see #getBoundsMin 2396 * @see #getBoundsMax 2397 */ 2398 @Override 2399 public SubrangePoint getLimitPoint(int dim, boolean max, double tol) { 2400 // Check the input dimension for consistancy. 2401 int thisDim = getPhyDimension(); 2402 if (dim < 0 || dim >= thisDim) 2403 throw new DimensionException(MessageFormat.format( 2404 RESOURCES.getString("inputDimOutOfRange"), "curve")); 2405 2406 double sout = 0; 2407 StackContext.enter(); 2408 try { 2409 // Get the bounds of the curve. 2410 Point boundsMax = getBoundsMax(); 2411 Point boundsMin = getBoundsMin(); 2412 Parameter<Length> range = boundsMax.get(dim).minus(boundsMin.get(dim)); 2413 2414 if (thisDim > 2) { 2415 // We have at least 3 dimensions. 2416 2417 // Create a vector pointing in the indicated direction. 2418 MutablePoint p = MutablePoint.newInstance(thisDim); 2419 p.set(dim, Parameter.valueOf(1, SI.METER)); 2420 Vector<Dimensionless> uv = Vector.valueOf(p).toUnitVector(); 2421 2422 // Bias the bounds off 1% in the specified dimension 2423 if (max) { 2424 p.set(boundsMax); 2425 p.set(dim, p.get(dim).plus(range.times(0.01))); 2426 } else { 2427 p.set(boundsMin); 2428 p.set(dim, p.get(dim).minus(range.times(0.01))); 2429 } 2430 2431 // Create a plane 1% of the diagonal off the bounds. 2432 Plane plane = Plane.valueOf(uv, p.immutable()); 2433 2434 // Return the closest point to the specified plane. 2435 SubrangePoint pnt = getClosest(plane, tol); 2436 sout = pnt.getParPosition().getValue(0); 2437 2438 } else if (thisDim == 2) { 2439 // We have a 2D curve. 2440 2441 // Create two points biased off the bounds in the specified direction. 2442 MutablePoint p0, p1; 2443 if (max) { 2444 p0 = MutablePoint.valueOf(boundsMax); 2445 p0.set(dim, p0.get(dim).plus(range.times(0.01))); 2446 p1 = MutablePoint.valueOf(boundsMin); 2447 p1.set(dim, p0.get(dim)); 2448 } else { 2449 p0 = MutablePoint.valueOf(boundsMin); 2450 p0.set(dim, p0.get(dim).minus(range.times(0.01))); 2451 p1 = MutablePoint.valueOf(boundsMax); 2452 p1.set(dim, p0.get(dim)); 2453 } 2454 2455 // Create a line on the desired side of the curve. 2456 LineSeg seg = LineSeg.valueOf(p0.immutable(), p1.immutable()); 2457 2458 // Find the closest point between this curve the line segment. 2459 PointString<SubrangePoint> str = getClosest(seg, tol); 2460 SubrangePoint p = str.getFirst(); 2461 sout = p.getParPosition().getValue(0); 2462 2463 } else { 2464 // We have a 1 dimensional curve. 2465 2466 // Bias the bounds off 1% in the specified dimension 2467 Point pp; 2468 if (max) { 2469 pp = boundsMax.plus(range.times(0.01)); 2470 } else { 2471 pp = boundsMin.minus(range.times(0.01)); 2472 } 2473 2474 // Return the closest point to the specified point. 2475 sout = getClosest(pp, tol).getParPosition().getValue(0); 2476 } 2477 } finally { 2478 StackContext.exit(); 2479 } 2480 2481 return getPoint(sout); 2482 } 2483 2484 /** 2485 * Return the signed area of the region enclosed or subtended by a planar curve 2486 * relative to the specified reference point. This method assumes that the curve is 2487 * planar and will give incorrect results if the curve is not planar. Also, the input 2488 * reference point is assumed to lie in the plane of the curve, it if is not, this 2489 * will give incorrect results. If the curve is closed, then the reference point is 2490 * arbitrary (as long as it is in the plane of the curve). If the curve is open, then 2491 * the area returned is that subtended by the curve with straight lines from each end 2492 * of the curve to the input point. 2493 * 2494 * @param refPnt The reference point used for computing the enclosed or subtended area 2495 * of the curve. 2496 * @param eps The desired fractional accuracy of the area calculated. 2497 * @return The area enclosed or subtended by this curve (assuming that this curve is 2498 * planar and that the reference point lies in the plane of this curve). 2499 */ 2500 @Override 2501 public Parameter<Area> getEnclosedArea(GeomPoint refPnt, double eps) { 2502 Unit<Area> areaUnit = getUnit().pow(2).asType(Area.class); 2503 2504 // If we only have 1 dimension, then there can be no area. 2505 int numDims = getPhyDimension(); 2506 if (numDims == 1) 2507 return Parameter.ZERO_AREA.to(areaUnit); 2508 2509 double area = 0; 2510 StackContext.enter(); 2511 try { 2512 // Make sure the point and curve have the same dimensions. 2513 if (refPnt.getPhyDimension() > numDims) { 2514 throw new IllegalArgumentException(MessageFormat.format( 2515 RESOURCES.getString("sameOrFewerDimErr"), "refPnt", "curve")); 2516 } else if (refPnt.getPhyDimension() < numDims) { 2517 refPnt = refPnt.toDimension(numDims); 2518 } 2519 2520 // Convert the reference point to the same units as this curve. 2521 refPnt = refPnt.to(getUnit()); 2522 2523 EnclAreaEvaluatable areaEvaluator = EnclAreaEvaluatable.newInstance(this, refPnt); 2524 try { 2525 // Integrate to find the subtended area: A = int_0^1{ p0p(t) x p'(t) ds} 2526 area = Quadrature.adaptLobatto(areaEvaluator, 0, 1, eps) * 0.5; 2527 2528 } catch (RootException | IntegratorException e) { 2529 // Fall back on Gaussian Quadrature. 2530 Logger.getLogger(AbstractCurve.class.getName()).log(Level.WARNING, 2531 "Trying Gaussian Quadrature in getEnclosedArea()..."); 2532 try { 2533 area = Quadrature.gaussLegendre_Wx1N40(areaEvaluator, 0, 1) * 0.5; 2534 2535 } catch (RootException err) { 2536 Logger.getLogger(AbstractCurve.class.getName()).log(Level.WARNING, 2537 "Could not calculate enclosed area; using 0.", err); 2538 area = 0; 2539 } 2540 } 2541 } finally { 2542 StackContext.exit(); 2543 } 2544 2545 return Parameter.valueOf(area, areaUnit); 2546 } 2547 2548 /** 2549 * Return <code>true</code> if this curve is degenerate (i.e.: has length less than 2550 * the specified tolerance). 2551 * 2552 * @param tol The tolerance for determining if this curve is degenerate. May not be 2553 * null. 2554 */ 2555 @Override 2556 public boolean isDegenerate(Parameter<Length> tol) { 2557 requireNonNull(tol); 2558 Parameter<Length> cLength = getArcLength(GTOL * 100); 2559 return cLength.compareTo(tol) <= 0; 2560 } 2561 2562 /** 2563 * Return <code>true</code> if this curve is planar or <code>false</code> if it is 2564 * not. 2565 * 2566 * @param tol The geometric tolerance to use in determining if the curve is planar. 2567 */ 2568 @Override 2569 public boolean isPlanar(Parameter<Length> tol) { 2570 // If the curve is less than 3D, then it must be planar. 2571 int numDims = getPhyDimension(); 2572 if (numDims <= 2) 2573 return true; 2574 2575 // Is this curve degenerate? 2576 if (isDegenerate(tol)) 2577 return true; 2578 2579 // Is the curve linear? 2580 if (isLine(tol)) 2581 return true; 2582 2583 StackContext.enter(); 2584 try { 2585 // Create a list of segment starting par. positions (and the last end position, 1). 2586 FastTable<Double> segParams = getSegmentParameters(); 2587 2588 // Form a plane from three points on the curve. 2589 Point p0 = getRealPoint(0); 2590 GeomVector<Dimensionless> nhat = getPlaneNormal(p0); 2591 2592 // Number of points extracted on each segment to determine type. 2593 int nPnts = getNumPointsPerSegment(); 2594 int nPntsm1 = nPnts - 1; 2595 2596 // Loop over all the segments. 2597 int numSegs = segParams.size() - 1; 2598 for (int seg = 0; seg < numSegs; ++seg) { 2599 2600 // Sample at 2*(p+1) points on each segment. 2601 double sSeg = segParams.get(seg); 2602 double sNext = segParams.get(seg + 1); 2603 double sRange = sNext - sSeg; 2604 for (int i = 0; i < nPntsm1; ++i) { 2605 double s = sSeg + (i * sRange) / nPntsm1; 2606 Point pi = getRealPoint(s); 2607 2608 // Find deviation from a plane for each point by projecting a vector from p0 to pi 2609 // onto the normal for the arbitrarily chosen points on the curve. 2610 // If the projection is anything other than zero, the points are not 2611 // in the same plane. 2612 Vector<Length> p1pi = pi.minus(p0).toGeomVector(); 2613 if (!p1pi.mag().isApproxZero()) { 2614 Parameter proj = p1pi.dot(nhat); 2615 if (proj.isLargerThan(tol)) 2616 return false; 2617 } 2618 } 2619 } 2620 2621 // Must be planar. 2622 return true; 2623 2624 } finally { 2625 StackContext.exit(); 2626 } 2627 } 2628 2629 /** 2630 * Return a normal vector for the curve from an input point and 2 arbitrarily chosen 2631 * points on a > 2D curve. If the input point is in the plane of the curve, then this 2632 * will return the normal vector for the plane of the curve. 2633 */ 2634 private Vector<Dimensionless> getPlaneNormal(GeomPoint p0) { 2635 StackContext.enter(); 2636 try { 2637 Vector p0v = p0.toGeomVector(); 2638 2639 Point p1 = getRealPoint(0.38196601125); 2640 Point p2 = getRealPoint(0.61803398875); 2641 Vector<Length> p1v = p1.toGeomVector(); 2642 Vector<Length> p2v = p2.toGeomVector(); 2643 Vector<Length> v1 = p1v.minus(p0v); 2644 Vector<Length> v2 = p2v.minus(p0v); 2645 Vector n = v1.cross(v2); 2646 Vector<Dimensionless> nhat; 2647 if (n.magValue() > GTOL) { 2648 nhat = n.toUnitVector(); 2649 nhat.setOrigin(Point.newInstance(getPhyDimension())); 2650 } else { 2651 // Try a different set of points on the curve. 2652 p1 = getRealPoint(0.25); 2653 p2 = getRealPoint(0.5); 2654 p1v = p1.toGeomVector(); 2655 p2v = p2.toGeomVector(); 2656 v1 = p1v.minus(p0v); 2657 v2 = p2v.minus(p0v); 2658 n = v1.cross(v2); 2659 if (n.magValue() > GTOL) { 2660 nhat = n.toUnitVector(); 2661 nhat.setOrigin(Point.newInstance(getPhyDimension())); 2662 } else { 2663 // Points from input curve form a straight line. 2664 nhat = GeomUtil.calcYHat(p1v.minus(p2v).toUnitVector()); 2665 nhat = v2.cross(nhat).toUnitVector(); 2666 } 2667 } 2668 2669 return StackContext.outerCopy(nhat); 2670 2671 } finally { 2672 StackContext.exit(); 2673 } 2674 } 2675 2676 /** 2677 * Returns <code>true</code> if this curve is a line to within the specified 2678 * tolerance. 2679 * 2680 * @param tol The tolerance for determining if this curve is a line. 2681 */ 2682 @Override 2683 public boolean isLine(Parameter<Length> tol) { 2684 // Reference: Piegl, L.A., Tiller, W., "Computing Offsets of NURBS Curves and Surfaces", 2685 // Computer Aided Design 31, 1999, pgs. 147-156. 2686 requireNonNull(tol); 2687 2688 StackContext.enter(); 2689 try { 2690 // Extract the curve end points. 2691 Point p0 = getRealPoint(0), p1 = getRealPoint(1); 2692 2693 // If the end points are coincident, then it can't be a line. 2694 Vector<Length> tv = p1.minus(p0).toGeomVector(); 2695 if (!tv.mag().isGreaterThan(tol)) 2696 return false; 2697 2698 // Get a unit vector for the potential line. 2699 Vector<Dimensionless> uv = tv.toUnitVector(); 2700 2701 // Create a list of segment starting par. positions (and the last end position, 1). 2702 FastTable<Double> bParams = getSegmentParameters(); 2703 2704 // Number of points extracted on each segment to determine type. 2705 int nPnts = getNumPointsPerSegment(); 2706 int nPntsm1 = nPnts - 1; 2707 2708 // Loop over all the segments. 2709 Parameter c2 = tv.dot(tv); 2710 int numSegs = bParams.size() - 1; 2711 for (int seg = 0; seg < numSegs; ++seg) { 2712 2713 // Sample at nPnts points on each segment. 2714 double sSeg = bParams.get(seg); 2715 double sNext = bParams.get(seg + 1); 2716 double sRange = sNext - sSeg; 2717 for (int i = 0; i < nPntsm1; ++i) { 2718 double s = sSeg + (i * sRange) / nPntsm1; 2719 Point pi = getRealPoint(s); 2720 2721 // Check distance of this point from infinite line p0-uv. 2722 if (GeomUtil.pointLineDistance(pi, p0, uv).isGreaterThan(tol)) 2723 return false; 2724 2725 // See if the point falls in the segment p0 to p1. 2726 Vector<Length> w = pi.minus(p0).toGeomVector(); 2727 Parameter c1 = w.dot(tv); 2728 if (c1.getValue() < 0) 2729 return false; 2730 if (c2.isLessThan(c1)) 2731 return false; 2732 } 2733 2734 } 2735 2736 // Must be a straight line. 2737 return true; 2738 2739 } finally { 2740 StackContext.exit(); 2741 } 2742 } 2743 2744 /** 2745 * Returns <code>true</code> if this curve is a circular arc to within the specified 2746 * tolerance. 2747 * 2748 * @param tol The tolerance for determining if this curve is a circular arc. 2749 */ 2750 @Override 2751 public boolean isCircular(Parameter<Length> tol) { 2752 // Reference: Piegl, L.A., Tiller, W., "Computing Offsets of NURBS Curves and Surfaces", 2753 // Computer Aided Design 31, 1999, pgs. 147-156. 2754 2755 // Can't be degenerate. 2756 if (isDegenerate(tol)) 2757 return false; 2758 2759 // Must be planar. 2760 if (!isPlanar(tol)) 2761 return false; 2762 2763 StackContext.enter(); 2764 try { 2765 // Create a list of segment starting par. positions (and the last end position, 1). 2766 FastTable<Double> bParams = getSegmentParameters(); 2767 2768 // Get the curvature and principal normal at the start of the curve. 2769 Parameter k0 = getCurvature(0); 2770 if (k0.isApproxZero()) 2771 return false; 2772 Vector n0 = getPrincipalNormal(0); 2773 2774 // Compute the center of the circle. 2775 Point pc0 = Point.valueOf(n0.divide(k0)).plus(n0.getOrigin()); 2776 2777 // Number of points extracted on each segment to determine type. 2778 int nPnts = getNumPointsPerSegment(); 2779 int nPntsm1 = nPnts - 1; 2780 2781 // Loop over all the segments. 2782 int numSegs = bParams.size() - 1; 2783 for (int seg = 0; seg < numSegs; ++seg) { 2784 2785 // Sample at nPnts points on each segment. 2786 double sSeg = bParams.get(seg); 2787 double sNext = bParams.get(seg + 1); 2788 double sRange = sNext - sSeg; 2789 for (int i = 0; i < nPntsm1; ++i) { 2790 double s = sSeg + (i * sRange) / nPntsm1; 2791 2792 // Get the local curvature and principal normal vector. 2793 Parameter k = getCurvature(s); 2794 if (k.isApproxZero()) 2795 return false; 2796 Vector n = getPrincipalNormal(s); 2797 2798 // Compute the local center of curvature. 2799 Point pc = Point.valueOf(n.divide(k)).plus(n.getOrigin()); 2800 Parameter<Length> dist = pc0.distance(pc); 2801 if (dist.isGreaterThan(tol)) 2802 return false; 2803 } 2804 } 2805 2806 // The curve must be circular. 2807 return true; 2808 2809 } finally { 2810 StackContext.exit(); 2811 } 2812 } 2813 2814 /** 2815 * Return a list containing parameters at the start of logical segments of this curve. 2816 * The first element in this list must always be 0.0 and the last element 1.0. These 2817 * should be good places to break the curve up into pieces for analysis, but otherwise 2818 * are arbitrary. Subclasses should override this method to provide better segment 2819 * starting parameters. 2820 * 2821 * The default implementation always splits the curve into two pieces and returns the 2822 * following parameters [0, 0.50, 1]. 2823 * 2824 * @return A list containing parameters at the start of logical segments of this 2825 * curve. 2826 */ 2827 protected FastTable<Double> getSegmentParameters() { 2828 FastTable<Double> knots = FastTable.newInstance(); 2829 knots.add(0.0); 2830 knots.add(0.5); 2831 knots.add(1.0); 2832 return knots; 2833 } 2834 2835 /** 2836 * Return the number of points per segment that should be used when analyzing curve 2837 * segments by certain methods. Subclasses should override this method to provide a 2838 * more appropriate number of points per segment. 2839 * 2840 * The default implementation always returns 8. 2841 * 2842 * @return The number of points per segment that should be used when analyzing curve 2843 * segments by certain methods. 2844 */ 2845 protected int getNumPointsPerSegment() { 2846 return 8; // 2*(3 + 1) 2847 } 2848 2849 /** 2850 * Converts the input parametric position on a curve segment with the specified range 2851 * of parametric positions into a parametric position on the parent curve. 2852 * 2853 * @param s The parametric position on the curve segment to be converted. 2854 * @param s0 The starting parametric position of the curve segment on the parent 2855 * curve. 2856 * @param s1 The ending parametric position of the curve segment on the parent curve. 2857 * @return The input segment parametric position converted to the parent curve. 2858 */ 2859 protected static double segmentPos2Parent(double s, double s0, double s1) { 2860 double s_out = s0 + (s1 - s0) * s; 2861 if (s_out > 1) { // Deal with roundoff issues. 2862 s_out = 1; 2863 } else if (s_out < 0) { 2864 s_out = 0; 2865 } 2866 return s_out; 2867 } 2868 2869 /** 2870 * An Evaulatable1D that returns the magnitude of the tangent vector at the specified 2871 * parametric location. This is used to find the arc length of a curve. This is 2872 * <code>f(x)</code> in: 2873 * <code>L = int_0^1 {f(x)dx} = int_0^1 { sqrt(ps(s) DOT ps(s)) ds }</code>. 2874 */ 2875 private static class ArcLengthEvaluatable extends AbstractEvaluatable1D { 2876 2877 private Curve _curve; 2878 2879 public static ArcLengthEvaluatable newInstance(Curve curve) { 2880 if (curve instanceof GeomTransform) 2881 curve = curve.copyToReal(); 2882 ArcLengthEvaluatable o = FACTORY.object(); 2883 o._curve = curve; 2884 return o; 2885 } 2886 2887 @Override 2888 public double function(double s) throws RootException { 2889 StackContext.enter(); 2890 try { 2891 Vector<Length> ps = _curve.getSDerivative(s, 1); // The dimensional tangent vector. 2892 double psv = ps.normValue(); // sqrt(ps DOT ps) = |ps| 2893 return psv; 2894 } finally { 2895 StackContext.exit(); 2896 } 2897 } 2898 2899 public static void recycle(ArcLengthEvaluatable instance) { 2900 FACTORY.recycle(instance); 2901 } 2902 2903 private ArcLengthEvaluatable() { } 2904 2905 private static final ObjectFactory<ArcLengthEvaluatable> FACTORY = new ObjectFactory<ArcLengthEvaluatable>() { 2906 @Override 2907 protected ArcLengthEvaluatable create() { 2908 return new ArcLengthEvaluatable(); 2909 } 2910 }; 2911 2912 } 2913 2914 /** 2915 * An Evaulatable1D that is used to find a point on a curve that has a tangent vector 2916 * that points to the input point. 2917 */ 2918 private static class TangentPointEvaluatable extends AbstractEvaluatable1D { 2919 2920 private Curve _curve; 2921 private Point _q; 2922 2923 public static TangentPointEvaluatable newInstance(Curve curve, GeomPoint q) { 2924 if (curve instanceof GeomTransform) 2925 curve = curve.copyToReal(); 2926 TangentPointEvaluatable o = FACTORY.object(); 2927 o._curve = curve; 2928 o._q = q.immutable(); 2929 return o; 2930 } 2931 2932 /** 2933 * Calculates: 1 - ((q - p(s))/|q - p(s)|) DOT t(s) where t(s) is the tangent 2934 * vector on the curve at s, p(s) is the point on the curve at s and q is the 2935 * point we are finding a tangent on the curve to. 2936 */ 2937 @Override 2938 public double function(double s) throws RootException { 2939 StackContext.enter(); 2940 try { 2941 2942 Vector<Dimensionless> t = _curve.getTangent(s); 2943 Point p = _curve.getRealPoint(s); 2944 Vector pq = _q.minus(p).toGeomVector(); 2945 double tDOTpq = 0; 2946 if (!pq.norm().isApproxZero()) { 2947 pq = pq.toUnitVector(); 2948 tDOTpq = t.dot(pq).getValue(); 2949 } 2950 return 1.0 - MathLib.abs(tDOTpq); 2951 2952 } finally { 2953 StackContext.exit(); 2954 } 2955 } 2956 2957 public static void recycle(TangentPointEvaluatable instance) { 2958 FACTORY.recycle(instance); 2959 } 2960 2961 private TangentPointEvaluatable() { } 2962 2963 private static final ObjectFactory<TangentPointEvaluatable> FACTORY = new ObjectFactory<TangentPointEvaluatable>() { 2964 @Override 2965 protected TangentPointEvaluatable create() { 2966 return new TangentPointEvaluatable(); 2967 } 2968 }; 2969 } 2970 2971 /** 2972 * An Evaulatable1D that is used to find a point on a curve that has the specified arc 2973 * length. 2974 */ 2975 private static class ArcPosEvaluatable extends AbstractEvaluatable1D { 2976 2977 private Curve _curve; 2978 private double _eps; 2979 public boolean firstPass; 2980 2981 /** 2982 * Return a new ArcPosEvaluatable function. 2983 * 2984 * @param curve The curve to find the arc-length along. 2985 * @param targetLength The target length, in meters, to search for. 2986 * @param eps The desired fractional accuracy on the arc-length. 2987 */ 2988 @SuppressWarnings("null") 2989 public static ArcPosEvaluatable newInstance(Curve curve, double targetLength, double eps) { 2990 if (curve instanceof GeomTransform) 2991 curve = curve.copyToReal(); 2992 2993 ArcPosEvaluatable o = FACTORY.object(); 2994 o._curve = curve.to(SI.METER); // Convert everything to meters. 2995 o._eps = eps; 2996 o.setInitialValue(targetLength); 2997 o.firstPass = false; 2998 2999 return o; 3000 } 3001 3002 /** 3003 * Calculates: arcLength(s) - targetArcLength. When this is zero, then the arc 3004 * length position has been found. 3005 */ 3006 @Override 3007 public double function(double s) throws RootException { 3008 // Don't compute anything if the 1st pass flag is set. 3009 if (firstPass) { 3010 firstPass = false; 3011 return 0; 3012 } 3013 StackContext.enter(); 3014 try { 3015 3016 double arcLength = _curve.getArcLength(0, s, _eps).getValue(); 3017 return arcLength - getInitialValue(); 3018 3019 } finally { 3020 StackContext.exit(); 3021 } 3022 } 3023 3024 public static void recycle(ArcPosEvaluatable instance) { 3025 FACTORY.recycle(instance); 3026 } 3027 3028 private ArcPosEvaluatable() { } 3029 3030 private static final ObjectFactory<ArcPosEvaluatable> FACTORY = new ObjectFactory<ArcPosEvaluatable>() { 3031 @Override 3032 protected ArcPosEvaluatable create() { 3033 return new ArcPosEvaluatable(); 3034 } 3035 }; 3036 } 3037 3038 /** 3039 * An Evaulatable1D that is used to find the intersection of a curve and a plane. 3040 */ 3041 private static class PlaneIntersectEvaluatable implements Evaluatable1D { 3042 3043 private Curve _curve; 3044 private GeomPlane _plane; 3045 public boolean firstPass; 3046 3047 public static PlaneIntersectEvaluatable newInstance(Curve curve, GeomPlane plane) { 3048 if (curve instanceof GeomTransform) 3049 curve = curve.copyToReal(); 3050 3051 // Create the object. 3052 PlaneIntersectEvaluatable o = FACTORY.object(); 3053 o._curve = curve; 3054 o._plane = plane; 3055 o.firstPass = false; 3056 3057 return o; 3058 } 3059 3060 /** 3061 * Calculates: <code>n dot (p(s) - pp)</code> which is the signed distance from 3062 * the point to the plane. When this is zero, then an intersection has been found. 3063 */ 3064 @Override 3065 public double function(double s) throws RootException { 3066 // Don't compute anything if the 1st pass flag is set. 3067 if (firstPass) { 3068 return 0; 3069 } 3070 3071 StackContext.enter(); 3072 try { 3073 3074 Point ps = _curve.getRealPoint(s); 3075 GeomPoint pp = _plane.getRefPoint(); 3076 Vector<Length> psmpp = ps.minus(pp).toGeomVector(); 3077 GeomVector n = _plane.getNormal(); 3078 3079 Parameter d = n.dot(psmpp); 3080 3081 return d.getValue(); 3082 3083 } finally { 3084 StackContext.exit(); 3085 } 3086 } 3087 3088 /** 3089 * Calculates: <code>d(n dot (p(s) - pp))/ds</code> which is the slope of the 3090 * signed distance from the point to the plane wrt to s. 3091 */ 3092 @Override 3093 public double derivative(double s) throws RootException { 3094 // Don't compute anything if the 1st pass flag is set. 3095 if (firstPass) { 3096 firstPass = false; 3097 return 0; 3098 } 3099 3100 StackContext.enter(); 3101 try { 3102 3103 Vector dpds = _curve.getSDerivative(s, 1); 3104 GeomVector n = _plane.getNormal(); 3105 3106 Parameter d = n.dot(dpds); 3107 3108 return d.getValue(); 3109 3110 } finally { 3111 StackContext.exit(); 3112 } 3113 } 3114 3115 public static void recycle(PlaneIntersectEvaluatable instance) { 3116 FACTORY.recycle(instance); 3117 } 3118 3119 private PlaneIntersectEvaluatable() { } 3120 3121 private static final ObjectFactory<PlaneIntersectEvaluatable> FACTORY = new ObjectFactory<PlaneIntersectEvaluatable>() { 3122 @Override 3123 protected PlaneIntersectEvaluatable create() { 3124 return new PlaneIntersectEvaluatable(); 3125 } 3126 }; 3127 } 3128 3129 /** 3130 * An Evaulatable1D that is used to find a parametric position on a curve that forms a 3131 * vector with the target point that is perpendicular to the curves tangent vector at 3132 * that position. Such a perpendicular vector indicates a point that is locally either 3133 * the closest or furthest away from the target point. This is then used with a 1D 3134 * root finder to isolate all the perpendicular points on the curve. 3135 */ 3136 protected static class PerpPointEvaluatable implements Evaluatable1D { 3137 3138 private Curve _curve; 3139 private Vector<Length> _point; 3140 public boolean firstPass; 3141 private Vector<Length> _pmq; 3142 private List<Vector<Length>> _ders; 3143 3144 public static PerpPointEvaluatable newInstance(Curve curve, GeomPoint point) { 3145 if (curve instanceof GeomTransform) 3146 curve = curve.copyToReal(); 3147 3148 PerpPointEvaluatable o = FACTORY.object(); 3149 o._curve = curve; 3150 o._point = point.toGeomVector(); 3151 o.firstPass = false; 3152 3153 return o; 3154 } 3155 3156 /** 3157 * Calculates (p(s) - q) DOT ps(s) (dot product between the vector formed by the 3158 * target point and a point on the curve at "s" and the tangent vector at "s". 3159 * Where this is zero, a perpendicular point has been found. A perpendicular point 3160 * is either the closest or furthest away from the target point in a local region. 3161 * 3162 * @param s The parametric position on the curve being evaluated. 3163 */ 3164 @Override 3165 public double function(double s) { 3166 // Don't compute anything if the 1st pass flag is set. 3167 if (firstPass) { 3168 return 0; 3169 } 3170 3171 // Get derivatives up to 2nd order. These are also used in derivative(). 3172 _ders = _curve.getSDerivatives(s, 2); 3173 3174 // Calculate: p(s) - q. This is also used in derivative(). 3175 Vector<Length> p = _ders.get(0); 3176 _pmq = p.minus(_point); 3177 if (_pmq.mag().isApproxZero()) 3178 return 0; 3179 3180 Vector ps = _ders.get(1); 3181 Parameter cosa = _pmq.dot(ps); // Projections of (p-q) onto tangent vector. 3182 3183 return cosa.getValue(); 3184 } 3185 3186 /** 3187 * Calculates: <code>d((p(s) - q) DOT ps(s))/ds</code> which is the slope of the 3188 * projection of the target point to curve point vector onto the tangent vector. 3189 * 3190 * @param s The parametric position on the curve being evaluated. 3191 */ 3192 @Override 3193 public double derivative(double s) { 3194 // Don't compute anything if the 1st pass flag is set. 3195 if (firstPass) { 3196 firstPass = false; 3197 return 0; 3198 } 3199 3200 /* d(fg) = df*g + f*dg 3201 d(p(s) - q)/ds = ps(s); d(ps(s))/ds = pss(s) 3202 d((p(s) - q) DOT ps(s))/ds = ps(s) DOT ps(s) + (p(s) - q) DOT pss(s) 3203 ((p(s) - q) DOT ps(s))/ds = |ps(s)|^2 + (p(s) - q) DOT pss(s) 3204 Derivatives and p(s) - q calculated in "function()". 3205 */ 3206 Vector ps = _ders.get(1); 3207 Vector pss = _ders.get(2); 3208 FastTable.recycle((FastTable)_ders); 3209 3210 Parameter value = ps.dot(ps).plus(_pmq.dot(pss)); 3211 3212 Vector.recycle(_pmq); 3213 Vector.recycle(ps); 3214 Vector.recycle(pss); 3215 3216 return value.getValue(); 3217 } 3218 3219 public static void recycle(PerpPointEvaluatable instance) { 3220 FACTORY.recycle(instance); 3221 } 3222 3223 private PerpPointEvaluatable() { } 3224 3225 private static final ObjectFactory<PerpPointEvaluatable> FACTORY = new ObjectFactory<PerpPointEvaluatable>() { 3226 @Override 3227 protected PerpPointEvaluatable create() { 3228 return new PerpPointEvaluatable(); 3229 } 3230 3231 @Override 3232 protected void cleanup(PerpPointEvaluatable obj) { 3233 obj._curve = null; 3234 obj._point = null; 3235 obj._pmq = null; 3236 obj._ders = null; 3237 } 3238 }; 3239 3240 } 3241 3242 /** 3243 * An Evaulatable1D that is used to find a parametric position on a curve that forms a 3244 * vector with the target point (which is the closest point on the target curve or 3245 * surface) that is perpendicular to the curve's tangent vector at that position. Such 3246 * a perpendicular vector indicates a point that is locally either the closest or 3247 * furthest away from the target point. This is then used with a 1D root finder to 3248 * isolate all the potential nearest/farthest points on the two curves. 3249 */ 3250 protected static class PerpPointParGeomEvaluatable extends AbstractEvaluatable1D { 3251 3252 private Curve _thisCurve; 3253 private ParametricGeometry _thatParGeom = null; 3254 private double _tol; 3255 public boolean firstPass; 3256 3257 public static PerpPointParGeomEvaluatable newInstance(Curve thisCurve, ParametricGeometry thatParGeom, double tol) { 3258 if (thisCurve instanceof GeomTransform) 3259 thisCurve = thisCurve.copyToReal(); 3260 3261 PerpPointParGeomEvaluatable o = FACTORY.object(); 3262 o._thisCurve = thisCurve; 3263 o._thatParGeom = thatParGeom; 3264 o._tol = tol; 3265 o.firstPass = false; 3266 3267 return o; 3268 } 3269 3270 /** 3271 * Calculates (p(s) - q) DOT ps(s) (dot product between the vector formed by the 3272 * target point (closest point on target parametric geometry) and a point on this 3273 * curve at "s" and the tangent vector at "s". Where this is zero, a perpendicular 3274 * point has been found. A perpendicular point is either the closest or furthest 3275 * away from the target point in a local region. 3276 * 3277 * @param s The parametric position on the curve being evaluated. 3278 * @return The dot product between the vector formed by the target point and a 3279 * point on this curve. 3280 */ 3281 @Override 3282 public double function(double s) { 3283 // Don't compute anything if the 1st pass flag is set. 3284 if (firstPass) { 3285 firstPass = false; 3286 return 0; 3287 } 3288 3289 StackContext.enter(); 3290 try { 3291 // Get derivatives up to 2nd order. 3292 FastTable<Vector> ders = (FastTable)_thisCurve.getSDerivatives(s, 2); 3293 3294 // Extract the point on this curve at "s". 3295 Vector<Length> p = ders.get(0); 3296 Point pnt = Point.valueOf(p); 3297 3298 // Get the closest point on the target parametric geometry. 3299 Vector<Length> q = _thatParGeom.getClosest(pnt, _tol).toGeomVector(); 3300 3301 // Calculate: p(s) - q. 3302 Vector<Length> pmq = p.minus(q); 3303 if (pmq.mag().isApproxZero()) { 3304 return 0; 3305 } 3306 3307 Vector ps = ders.get(1); 3308 Parameter cosa = pmq.dot(ps); // Projection of (p-q) onto tangent vector. 3309 3310 return cosa.getValue(); 3311 3312 } finally { 3313 StackContext.exit(); 3314 } 3315 } 3316 3317 public static void recycle(PerpPointParGeomEvaluatable instance) { 3318 FACTORY.recycle(instance); 3319 } 3320 3321 private PerpPointParGeomEvaluatable() { } 3322 3323 private static final ObjectFactory<PerpPointParGeomEvaluatable> FACTORY = new ObjectFactory<PerpPointParGeomEvaluatable>() { 3324 @Override 3325 protected PerpPointParGeomEvaluatable create() { 3326 return new PerpPointParGeomEvaluatable(); 3327 } 3328 }; 3329 3330 } 3331 3332 /** 3333 * An Evaulatable1D that is used to find a parametric position on a curve that forms a 3334 * vector with the target point (which is the closest point on the target plane) that 3335 * is perpendicular to the curve's tangent vector at that position. Such a 3336 * perpendicular vector indicates a point that is locally either the closest or 3337 * furthest away from the target point. This is then used with a 1D root finder to 3338 * isolate all the potential nearest/farthest points on the curve to the plane. 3339 */ 3340 protected static class PerpPointPlaneEvaluatable extends AbstractEvaluatable1D { 3341 3342 private Curve _thisCurve; 3343 private GeomPlane _thatPlane = null; 3344 public boolean firstPass; 3345 3346 public static PerpPointPlaneEvaluatable newInstance(Curve thisCurve, GeomPlane thatPlane) { 3347 if (thisCurve instanceof GeomTransform) 3348 thisCurve = thisCurve.copyToReal(); 3349 3350 PerpPointPlaneEvaluatable o = FACTORY.object(); 3351 o._thisCurve = thisCurve; 3352 o._thatPlane = thatPlane; 3353 o.firstPass = false; 3354 3355 return o; 3356 } 3357 3358 /** 3359 * Calculates (p(s) - q) DOT ps(s) (dot product between the vector formed by the 3360 * target point (closest point on target plane) and a point on this curve at "s" 3361 * and the tangent vector at "s". Where this is zero, a perpendicular point has 3362 * been found. A perpendicular point is either the closest or furthest away from 3363 * the target point in a local region. 3364 * 3365 * @param s The parametric position on this curve being evaluated. 3366 * @return The dot product between the vector formed by the target point and a 3367 * point on this curve 3368 */ 3369 @Override 3370 public double function(double s) { 3371 // Don't compute anything if the 1st pass flag is set. 3372 if (firstPass) { 3373 firstPass = false; 3374 return 0; 3375 } 3376 3377 StackContext.enter(); 3378 try { 3379 // Get derivatives up to 2nd order. 3380 FastTable<Vector> ders = (FastTable)_thisCurve.getSDerivatives(s, 2); 3381 3382 // Extract the point on this curve at "s". 3383 Vector<Length> p = ders.get(0); 3384 Point pnt = Point.valueOf(p); 3385 3386 // Get the closest point on the target plane geometry. 3387 Vector<Length> q = _thatPlane.getClosest(pnt).toGeomVector(); 3388 3389 // Calculate: p(s) - q. 3390 Vector<Length> pmq = p.minus(q); 3391 if (pmq.mag().isApproxZero()) { 3392 return 0; 3393 } 3394 3395 Vector ps = ders.get(1); 3396 Parameter cosa = pmq.dot(ps); // Projection of (p-q) onto tangent vector. 3397 3398 return cosa.getValue(); 3399 3400 } finally { 3401 StackContext.exit(); 3402 } 3403 } 3404 3405 public static void recycle(PerpPointPlaneEvaluatable instance) { 3406 FACTORY.recycle(instance); 3407 } 3408 3409 private PerpPointPlaneEvaluatable() { } 3410 3411 private static final ObjectFactory<PerpPointPlaneEvaluatable> FACTORY = new ObjectFactory<PerpPointPlaneEvaluatable>() { 3412 @Override 3413 protected PerpPointPlaneEvaluatable create() { 3414 return new PerpPointPlaneEvaluatable(); 3415 } 3416 }; 3417 3418 } 3419 3420 /** 3421 * A class used to intersect an infinite line and a curve. 3422 */ 3423 private static class LineCurveIntersect { 3424 3425 private static final int MAXDEPTH = 20; 3426 private Curve _thisCurve; 3427 private Parameter<Length> _tol; 3428 private Point _L0; 3429 private Vector<Length> _Ldir; 3430 private FastTable<Double> _sParams; 3431 3432 @SuppressWarnings("null") 3433 public static LineCurveIntersect newInstance(Curve thisCurve, 3434 Parameter<Length> tol, GeomPoint L0, GeomVector<Length> Ldir) { 3435 3436 // The magnitude of Ldir doesn't matter, but it can not be "Dimensionless". 3437 if (Ldir.getUnit().equals(Dimensionless.UNIT)) { 3438 Ldir = Vector.valueOf(Ldir.toFloat64Vector(), SI.METER); 3439 } 3440 3441 if (thisCurve instanceof GeomTransform) 3442 thisCurve = thisCurve.copyToReal(); 3443 3444 // Create an instance of this class and fill in the class data. 3445 LineCurveIntersect o = FACTORY.object(); 3446 o._thisCurve = thisCurve; 3447 o._tol = tol.to(thisCurve.getUnit()); 3448 o._L0 = L0.immutable().to(thisCurve.getUnit()); 3449 o._Ldir = Ldir.immutable(); 3450 o._sParams = FastTable.newInstance(); 3451 3452 return o; 3453 } 3454 3455 /** 3456 * Method that actually carries out the intersection adding the intersection 3457 * points to the list provided in the constructor. 3458 * 3459 * @return A PointString containing zero or more subrange points made by the 3460 * intersection of the curve with the specified line. If no intersection 3461 * is found, an empty PointString is returned. 3462 */ 3463 public PointString<SubrangePoint> intersect() { 3464 divideAndConquerLine(1, _thisCurve, 0, 1); 3465 3466 PointString<SubrangePoint> output = PointString.newInstance(); 3467 int size = _sParams.size(); 3468 for (int i = 0; i < size; ++i) { 3469 double s = _sParams.get(i); 3470 output.add(SubrangePoint.newInstance(_thisCurve, s)); 3471 } 3472 FastTable.recycle(_sParams); 3473 _sParams = null; 3474 3475 return output; 3476 } 3477 3478 /** 3479 * Uses a recursive "Divide and Conquer" approach to intersecting an infinite line 3480 * with a curve. On each call, the following happens: 3481 * <pre> 3482 * 1) The curve is checked to see if it is approx. a straight line. 3483 * 1b) If it is, then a line-line intersection is performed, the 3484 * intersection point added to the output list and the method exited. 3485 * 2) The curve is divided into two halves. 3486 * 2b) Each half is tested to see if it's bounding box is intersected by the line. 3487 * If it is, then this method is called with that segment recursively. 3488 * Otherwise, the method is exited. 3489 * </pre> 3490 * 3491 * A number of class variables are used to pass information to this recursion: 3492 * <pre> 3493 * _tol The tolerance to use when determining if the curve is locally linear. 3494 * _L0 A point on the line being intersected with this curve (origin of the line). 3495 * _Ldir A vector indicating the direction of the line. 3496 * _sParams A list used to collect the output subrange intersection point parametric locations. 3497 * </pre> 3498 * 3499 * @param crv The curve segment being checked for intersection with the line. 3500 * @param s0 The parametric position of the start of this curve segment on the 3501 * master curve. 3502 * @param s1 The parametric position of the end of this curve segment on the 3503 * master curve. 3504 */ 3505 private void divideAndConquerLine(int depth, Curve crv, double s0, double s1) { 3506 StackContext.enter(); 3507 try { 3508 // Check to see if the input curve is within tolerance of a straight line. 3509 Point p0 = crv.getRealPoint(0); 3510 Point p1 = crv.getRealPoint(1); 3511 Point pAvg = p0.plus(p1).divide(2); 3512 Point pm = crv.getRealPoint(0.5); 3513 if (depth > MAXDEPTH || pAvg.distance(pm).isLessThan(_tol)) { 3514 // The input curve segment is nearly a straight line. 3515 3516 // Intersect the input line and the line approximating the curve. 3517 Vector<Length> crvDir = p1.minus(p0).toGeomVector(); 3518 MutablePoint sLn = MutablePoint.newInstance(2); 3519 int numDims = p0.getPhyDimension(); 3520 Unit<Length> unit = p0.getUnit(); 3521 MutablePoint pi1 = MutablePoint.newInstance(numDims, unit); 3522 MutablePoint pi2 = MutablePoint.newInstance(numDims, unit); 3523 GeomUtil.lineLineIntersect(_L0, _Ldir, p0, crvDir, _tol, sLn, pi1, pi2); 3524 3525 if (pi1.distance(pi2).isLessThan(_tol)) { 3526 // Add an intersection point to the list. 3527 double sln = sLn.getValue(1); 3528 // Convert from segment par. pos. to full curve pos. 3529 double s = segmentPos2Parent(sln, s0, s1); 3530 _sParams.add(s); 3531 } 3532 3533 } else { 3534 // Sub-divide the curve into two segments. 3535 GeomList<Curve> segments = crv.splitAt(0.5); 3536 3537 // Check for possible intersections on the left segment. 3538 Curve seg = segments.get(0); 3539 if (GeomUtil.lineBoundsIntersect(_L0, _Ldir, seg, null)) { 3540 // May be an intersection. 3541 double sl = s0; 3542 double sh = (s0 + s1) / 2.0; 3543 3544 // Recurse down to a finer level of detail. 3545 divideAndConquerLine(depth + 1, seg, sl, sh); 3546 } 3547 3548 // Check for possible intersections on the right segment. 3549 seg = segments.get(1); 3550 if (GeomUtil.lineBoundsIntersect(_L0, _Ldir, seg, null)) { 3551 // May be an intersection. 3552 double sl = (s0 + s1) / 2.0; 3553 double sh = s1; 3554 3555 // Recurse down to a finer level of detail. 3556 divideAndConquerLine(depth + 1, seg, sl, sh); 3557 } 3558 } 3559 3560 } finally { 3561 StackContext.exit(); 3562 } 3563 } 3564 3565 public static void recycle(LineCurveIntersect instance) { 3566 FACTORY.recycle(instance); 3567 } 3568 3569 private LineCurveIntersect() { } 3570 3571 private static final ObjectFactory<LineCurveIntersect> FACTORY = new ObjectFactory<LineCurveIntersect>() { 3572 @Override 3573 protected LineCurveIntersect create() { 3574 return new LineCurveIntersect(); 3575 } 3576 3577 @Override 3578 protected void cleanup(LineCurveIntersect obj) { 3579 obj._thisCurve = null; 3580 obj._tol = null; 3581 obj._L0 = null; 3582 obj._Ldir = null; 3583 obj._sParams = null; 3584 } 3585 }; 3586 3587 } 3588 3589 /** 3590 * A class used to intersect an line segment and this curve. 3591 */ 3592 private static class LineSegCurveIntersect { 3593 3594 private static final int MAXDEPTH = 20; 3595 private Curve _thisCurve; 3596 private Parameter<Length> _tol; 3597 private LineSegment _line; 3598 private Point _p0; 3599 private Point _p1; 3600 private Vector<Length> _Lu; 3601 private GeomList<PointString<SubrangePoint>> _output; 3602 private FastTable<Double> _crvS, _lineS; 3603 3604 @SuppressWarnings("null") 3605 public static LineSegCurveIntersect newInstance(Curve thisCurve, Parameter<Length> tol, 3606 LineSegment line, GeomList<PointString<SubrangePoint>> output) { 3607 3608 if (thisCurve instanceof GeomTransform) 3609 thisCurve = thisCurve.copyToReal(); 3610 if (line instanceof GeomTransform) 3611 line = line.copyToReal(); 3612 Unit<Length> unit = thisCurve.getUnit(); 3613 3614 // Create an instance of this class and fill in the class variables. 3615 LineSegCurveIntersect o = FACTORY.object(); 3616 o._thisCurve = thisCurve; 3617 o._tol = tol.to(unit); 3618 o._line = line.to(unit); 3619 o._p0 = line.getStart().immutable().to(unit); 3620 o._p1 = line.getEnd().immutable().to(unit); 3621 o._Lu = line.getDerivativeVector().immutable().to(unit); 3622 o._output = output; 3623 o._crvS = FastTable.newInstance(); 3624 o._lineS = FastTable.newInstance(); 3625 3626 return o; 3627 } 3628 3629 /** 3630 * Method that actually carries out the intersection adding the intersection 3631 * points to the list of lists provided in the constructor. 3632 * 3633 * @return A list containing two PointString instances each containing zero or 3634 * more subrange points, on this curve and the input line segment 3635 * respectively, made by the intersection of this curve with the specified 3636 * line segment. If no intersections are found a list of two empty 3637 * PointStrings are returned. 3638 */ 3639 public GeomList<PointString<SubrangePoint>> intersect() { 3640 divideAndConquerLineSeg(1, _thisCurve, 0, 1); 3641 3642 // Convert stored parametric positions to subrange points on the curves. 3643 int size = _crvS.size(); 3644 for (int i = 0; i < size; ++i) { 3645 double s = _crvS.get(i); 3646 _output.get(0).add(SubrangePoint.newInstance(_thisCurve, s)); 3647 s = _lineS.get(i); 3648 _output.get(1).add(SubrangePoint.newInstance(_line, s)); 3649 } 3650 FastTable.recycle(_crvS); 3651 _crvS = null; 3652 FastTable.recycle(_lineS); 3653 _lineS = null; 3654 3655 return _output; 3656 } 3657 3658 /** 3659 * Uses a recursive "Divide and Conquer" approach to intersecting a line segment 3660 * with a curve. On each call, the following happens: 3661 * <pre> 3662 * 1) The curve is checked to see if it is approx. a straight line. 3663 * 1b) If it is, then a line-line intersection is performed, the 3664 * intersection point added to the output list and the method exited. 3665 * 2) The curve is divided into two halves. 3666 * 2b) Each half is tested to see if it's bounding box is intersected by the line. 3667 * If it is, then this method is called with that segment recursively. 3668 * Otherwise, the method is exited. 3669 * </pre> 3670 * 3671 * A number of class variables are used to pass information to this recursion: 3672 * <pre> 3673 * _tol The tolerance to use when determining if the curve is locally linear. 3674 * _line The line segment instance we are intersecting the curve with. 3675 * _p0 A point at the start of the line being intersected with this curve. 3676 * _p1 A point at the end of the line being intersected with this curve. 3677 * _Lu A vector indicating the direction of the line. 3678 * _crvS A list used to collect the output subrange intersection point parametric 3679 * locations on this curve. 3680 * _lineS A list used to collect the output subrange intersection point parametric 3681 * locations on the line segment curve. 3682 * </pre> 3683 * 3684 * @param crv The curve segment being checked for intersection with the line. 3685 * @param s0 The parametric position of the start of this curve segment on the 3686 * master curve. 3687 * @param s1 The parametric position of the end of this curve segment on the 3688 * master curve. 3689 */ 3690 private void divideAndConquerLineSeg(int depth, Curve crv, double s0, double s1) { 3691 StackContext.enter(); 3692 try { 3693 // Check to see if the input curve is within tolerance of a straight line. 3694 Point p0 = crv.getRealPoint(0); 3695 Point p1 = crv.getRealPoint(1); 3696 Point pAvg = p0.plus(p1).divide(2); 3697 Point pm = crv.getRealPoint(0.5); 3698 if (depth > MAXDEPTH || pAvg.distance(pm).isLessThan(_tol)) { 3699 // The input curve segment is nearly a straight line. 3700 3701 // Intersect the input line and the line approximating the curve. 3702 Vector<Length> crvDir = p1.minus(p0).toGeomVector(); 3703 MutablePoint sLn = MutablePoint.newInstance(2); 3704 int numDims = p0.getPhyDimension(); 3705 Unit<Length> unit = p0.getUnit(); 3706 MutablePoint pi1 = MutablePoint.newInstance(numDims, unit); 3707 MutablePoint pi2 = MutablePoint.newInstance(numDims, unit); 3708 GeomUtil.lineLineIntersect(_p0, _Lu, p0, crvDir, _tol, sLn, pi1, pi2); 3709 3710 // Make sure the intersection points fall on the line segments. 3711 double sl_1 = sLn.getValue(0); 3712 if (sl_1 > 1) { 3713 pi1.set(_line.getEnd()); 3714 sl_1 = 1; 3715 } else if (sl_1 < 0) { 3716 pi1.set(_line.getStart()); 3717 sl_1 = 0; 3718 } 3719 double sl_2 = sLn.getValue(1); 3720 if (sl_2 > 1) { 3721 pi2.set(crv.getRealPoint(1)); 3722 sl_2 = 1; 3723 } else if (sl_2 < 0) { 3724 pi2.set(crv.getRealPoint(0)); 3725 sl_2 = 0; 3726 } 3727 Parameter<Length> dist = pi1.distance(pi2); 3728 if (dist.isLessThan(_tol)) { 3729 // Add an intersection point on each curve to the output lists. 3730 _lineS.add(sl_1); 3731 // Convert from segment par. pos. to full curve pos. 3732 double s = segmentPos2Parent(sl_2, s0, s1); 3733 _crvS.add(s); 3734 } 3735 3736 } else { 3737 // Sub-divide the curve into two segments. 3738 GeomList<Curve> segments = crv.splitAt(0.5); 3739 3740 // Check for possible intersections on the left segment. 3741 Curve seg = segments.get(0); 3742 if (GeomUtil.lineSegBoundsIntersect(_p0, _p1, seg)) { 3743 // May be an intersection. 3744 double sl = s0; 3745 double sh = (s0 + s1) / 2; 3746 3747 // Recurse down to a finer level of detail. 3748 divideAndConquerLineSeg(depth + 1, seg, sl, sh); 3749 } 3750 3751 // Check for possible intersections on the right segment. 3752 seg = segments.get(1); 3753 if (GeomUtil.lineSegBoundsIntersect(_p0, _p1, seg)) { 3754 // May be an intersection. 3755 double sl = (s0 + s1) / 2; 3756 double sh = s1; 3757 3758 // Recurse down to a finer level of detail. 3759 divideAndConquerLineSeg(depth + 1, seg, sl, sh); 3760 } 3761 } 3762 3763 } finally { 3764 StackContext.exit(); 3765 } 3766 } 3767 3768 public static void recycle(LineSegCurveIntersect instance) { 3769 FACTORY.recycle(instance); 3770 } 3771 3772 private LineSegCurveIntersect() { } 3773 3774 private static final ObjectFactory<LineSegCurveIntersect> FACTORY = new ObjectFactory<LineSegCurveIntersect>() { 3775 @Override 3776 protected LineSegCurveIntersect create() { 3777 return new LineSegCurveIntersect(); 3778 } 3779 3780 @Override 3781 protected void cleanup(LineSegCurveIntersect obj) { 3782 obj._thisCurve = null; 3783 obj._tol = null; 3784 obj._line = null; 3785 obj._p0 = null; 3786 obj._p1 = null; 3787 obj._Lu = null; 3788 obj._output = null; 3789 obj._crvS = null; 3790 obj._lineS = null; 3791 } 3792 }; 3793 } 3794 3795 /** 3796 * A class used to intersect an line segment and this curve. 3797 */ 3798 private static class CurveCurveIntersect { 3799 3800 private static final int MAXDEPTH = 20; 3801 private AbstractCurve _thisCurve; 3802 private Parameter<Length> _tol; 3803 private Curve _thatCurve; 3804 private GeomList<PointString<SubrangePoint>> _output; 3805 private FastTable<Double> _thisS, _thatS; 3806 3807 public static CurveCurveIntersect newInstance(AbstractCurve thisCurve, Parameter<Length> tol, 3808 Curve thatCurve, GeomList<PointString<SubrangePoint>> output) { 3809 3810 // Make sure the line and curve have the same dimensions. 3811 int numDims = thisCurve.getPhyDimension(); 3812 if (thatCurve.getPhyDimension() > numDims) 3813 throw new IllegalArgumentException(MessageFormat.format( 3814 RESOURCES.getString("sameOrFewerDimErr"), "target curve", "curve")); 3815 else if (thatCurve.getPhyDimension() < numDims) 3816 thatCurve = thatCurve.toDimension(numDims); 3817 3818 if (thisCurve instanceof GeomTransform) 3819 thisCurve = (AbstractCurve)thisCurve.copyToReal(); 3820 if (thatCurve instanceof GeomTransform) 3821 thatCurve = thatCurve.copyToReal(); 3822 3823 // Create an instance of this class and fill in the class variables. 3824 CurveCurveIntersect o = FACTORY.object(); 3825 o._thisCurve = thisCurve; 3826 o._tol = tol; 3827 o._thatCurve = thatCurve; 3828 o._output = output; 3829 o._thisS = FastTable.newInstance(); 3830 o._thatS = FastTable.newInstance(); 3831 3832 return o; 3833 } 3834 3835 /** 3836 * Method that actually carries out the intersection adding the intersection 3837 * points to the list of lists provided in the constructor. 3838 * 3839 * @return A list containing two PointString instances each containing zero or 3840 * more subrange points, on this curve and the input line segment 3841 * respectively, made by the intersection of this curve with the specified 3842 * line segment. If no intersections are found a list of two empty 3843 * PointStrings are returned. 3844 */ 3845 public GeomList<PointString<SubrangePoint>> intersect() { 3846 divideAndConquerCurveSeg(1, _thisCurve, 0, 1, _thatCurve, 0, 1); 3847 3848 // Convert stored parametric positions to subrange points on the curves. 3849 int size = _thisS.size(); 3850 for (int i = 0; i < size; ++i) { 3851 double s = _thisS.get(i); 3852 _output.get(0).add(SubrangePoint.newInstance(_thisCurve, s)); 3853 s = _thatS.get(i); 3854 _output.get(1).add(SubrangePoint.newInstance(_thatCurve, s)); 3855 } 3856 FastTable.recycle(_thisS); 3857 _thisS = null; 3858 FastTable.recycle(_thatS); 3859 _thatS = null; 3860 3861 return _output; 3862 } 3863 3864 /** 3865 * Uses a recursive "Divide and Conquer" approach to intersecting a curve segment 3866 * with another curve. On each call, the following happens: 3867 * <pre> 3868 * 1) The curve is checked to see if it is approx. a straight line. 3869 * 1b) If it is, then a line-line intersection is performed, the 3870 * intersection point added to the output list and the method exited. 3871 * 2) The curve is divided into two halves. 3872 * 2b) Each half is tested to see if it's bounding box is intersected by the line. 3873 * If it is, then this method is called with that segment recursively. 3874 * Otherwise, the method is exited. 3875 * </pre> 3876 * 3877 * A number of class variables are used to pass information to this recursion: 3878 * <pre> 3879 * _tol The tolerance to use when determining if the curve is locally linear. 3880 * _line The line segment instance we are intersecting the curve with. 3881 * _p0 A point at the start of the line being intersected with this curve. 3882 * _p1 A point at the end of the line being intersected with this curve. 3883 * _Lu A vector indicating the direction of the line. 3884 * _output A list used to collect the output subrange intersection points. 3885 * </pre> 3886 * 3887 * @param crv1 The 1st curve segment being checked for intersection with "that" 3888 * curve. 3889 * @param s0_1 The parametric position of the start of the 1st curve segment on 3890 * the master "this" curve. 3891 * @param s1_1 The parametric position of the end of the 1st curve segment on the 3892 * master "this" curve. 3893 * @param crv2 The 2nd curve segment being checked for intersection with "this" 3894 * curve. 3895 * @param s0_2 The parametric position of the start of the 2nd curve segment on 3896 * the master "that" curve. 3897 * @param s1_2 The parametric position of the end of the 2nd curve segment on the 3898 * master "that" curve. 3899 */ 3900 private void divideAndConquerCurveSeg(int depth, Curve crv1, double s0_1, double s1_1, 3901 Curve crv2, double s0_2, double s1_2) { 3902 3903 StackContext.enter(); 3904 try { 3905 // Check to see if the 1st input curve is within tolerance of a straight line. 3906 Point p0 = crv1.getRealPoint(0); 3907 Point p1 = crv1.getRealPoint(1); 3908 Point pAvg = p0.plus(p1).divide(2); 3909 Point pm = crv1.getRealPoint(0.5); 3910 if (depth > MAXDEPTH || pAvg.distance(pm).isLessThan(_tol)) { 3911 // The 1st curve segment is nearly a straight line. 3912 LineSeg line1 = LineSeg.valueOf(crv1); 3913 3914 // Intersect the line segment with crv2. 3915 GeomList<PointString<SubrangePoint>> strs = crv2.intersect(line1, _tol); 3916 if (!strs.get(0).isEmpty()) { 3917 // Intersections were found. 3918 // Loop over the intersection points. 3919 int numPnts = strs.get(0).size(); 3920 for (int i = 0; i < numPnts; ++i) { 3921 SubrangePoint pnt1 = strs.get(1).get(i); 3922 SubrangePoint pnt2 = strs.get(0).get(i); 3923 3924 // Convert from segment par. pos. to full curve pos. 3925 double s1 = pnt1.getParPosition().getValue(0); 3926 double s = segmentPos2Parent(s1, s0_1, s1_1); 3927 _thisS.add(s); 3928 3929 double s2 = pnt2.getParPosition().getValue(0); 3930 s = segmentPos2Parent(s2, s0_2, s1_2); 3931 _thatS.add(s); 3932 } 3933 } 3934 3935 return; 3936 } 3937 3938 // Check to see if the 2nd input curve is within tolerance of a straight line. 3939 p0 = crv2.getRealPoint(0); 3940 p1 = crv2.getRealPoint(1); 3941 pAvg = p0.plus(p1).divide(2); 3942 pm = crv2.getRealPoint(0.5); 3943 if (pAvg.distance(pm).isLessThan(_tol)) { 3944 // The 2nd curve segment is nearly a straight line. 3945 LineSeg line2 = LineSeg.valueOf(crv2); 3946 3947 // Intersect the line segment with crv1. 3948 GeomList<PointString<SubrangePoint>> strs = crv1.intersect(line2, _tol); 3949 if (!strs.get(0).isEmpty()) { 3950 // Intersections were found. 3951 // Loop over the intersection points. 3952 int numPnts = strs.get(0).size(); 3953 for (int i = 0; i < numPnts; ++i) { 3954 SubrangePoint pnt1 = strs.get(0).get(i); 3955 SubrangePoint pnt2 = strs.get(1).get(i); 3956 3957 // Convert from segment par. pos. to full curve pos. 3958 double s1 = pnt1.getParPosition().getValue(0); 3959 double s = segmentPos2Parent(s1, s0_1, s1_1); 3960 _thisS.add(s); 3961 3962 double s2 = pnt2.getParPosition().getValue(0); 3963 s = segmentPos2Parent(s2, s0_2, s1_2); 3964 _thatS.add(s); 3965 } 3966 } 3967 3968 } else { 3969 // Sub-divide the input curves into two segments each. 3970 GeomList<Curve> crv1Segs = crv1.splitAt(0.5); 3971 GeomList<Curve> crv2Segs = crv2.splitAt(0.5); 3972 3973 // Check for possible intersections on the left segment of curve 1. 3974 Curve seg1 = crv1Segs.get(0); 3975 Curve seg2 = crv2Segs.get(0); 3976 if (GeomUtil.boundsIntersect(seg1, seg2)) { 3977 // May be an intersection. 3978 double sl_1 = s0_1; 3979 double sh_1 = (s0_1 + s1_1) / 2; 3980 double sl_2 = s0_2; 3981 double sh_2 = (s0_2 + s1_2) / 2; 3982 3983 // Recurse down to a finer level of detail. 3984 divideAndConquerCurveSeg(depth + 1, seg1, sl_1, sh_1, seg2, sl_2, sh_2); 3985 } 3986 3987 seg2 = crv2Segs.get(1); 3988 if (GeomUtil.boundsIntersect(seg1, seg2)) { 3989 // May be an intersection. 3990 double sl_1 = s0_1; 3991 double sh_1 = (s0_1 + s1_1) / 2; 3992 double sl_2 = (s0_2 + s1_2) / 2; 3993 double sh_2 = s1_2; 3994 3995 // Recurse down to a finer level of detail. 3996 divideAndConquerCurveSeg(depth + 1, seg1, sl_1, sh_1, seg2, sl_2, sh_2); 3997 } 3998 3999 // Check for possible intersections on the right segment of curve 1. 4000 seg1 = crv1Segs.get(1); 4001 seg2 = crv2Segs.get(0); 4002 if (GeomUtil.boundsIntersect(seg1, seg2)) { 4003 // May be an intersection. 4004 double sl_1 = (s0_1 + s1_1) / 2; 4005 double sh_1 = s1_1; 4006 double sl_2 = s0_2; 4007 double sh_2 = (s0_2 + s1_2) / 2; 4008 4009 // Recurse down to a finer level of detail. 4010 divideAndConquerCurveSeg(depth + 1, seg1, sl_1, sh_1, seg2, sl_2, sh_2); 4011 } 4012 4013 seg2 = crv2Segs.get(1); 4014 if (GeomUtil.boundsIntersect(seg1, seg2)) { 4015 // May be an intersection. 4016 double sl_1 = (s0_1 + s1_1) / 2; 4017 double sh_1 = s1_1; 4018 double sl_2 = (s0_2 + s1_2) / 2; 4019 double sh_2 = s1_2; 4020 4021 // Recurse down to a finer level of detail. 4022 divideAndConquerCurveSeg(depth + 1, seg1, sl_1, sh_1, seg2, sl_2, sh_2); 4023 } 4024 4025 } 4026 4027 } finally { 4028 StackContext.exit(); 4029 } 4030 } 4031 4032 public static void recycle(CurveCurveIntersect instance) { 4033 FACTORY.recycle(instance); 4034 } 4035 4036 private CurveCurveIntersect() { } 4037 4038 private static final ObjectFactory<CurveCurveIntersect> FACTORY = new ObjectFactory<CurveCurveIntersect>() { 4039 @Override 4040 protected CurveCurveIntersect create() { 4041 return new CurveCurveIntersect(); 4042 } 4043 4044 @Override 4045 protected void cleanup(CurveCurveIntersect obj) { 4046 obj._thisCurve = null; 4047 obj._tol = null; 4048 obj._output = null; 4049 } 4050 }; 4051 } 4052 4053 /** 4054 * An Evaluatable1D function that calculates the signed distance between a point along 4055 * a curve and a point on a surface. This is used by a 1D root finder to drive that 4056 * distance to zero. 4057 */ 4058 private static class CurveSrfIntersectFun extends AbstractEvaluatable1D { 4059 4060 private Surface _srf; 4061 private Curve _thisCurve; 4062 public double tol; 4063 public double _nearS, _nearT; 4064 public boolean firstPass; 4065 4066 public static CurveSrfIntersectFun newInstance(Curve thisCurve, Surface surface, double tol) { 4067 4068 if (surface instanceof GeomTransform) 4069 surface = (Surface)surface.copyToReal(); 4070 if (thisCurve instanceof GeomTransform) 4071 thisCurve = thisCurve.copyToReal(); 4072 4073 CurveSrfIntersectFun o = FACTORY.object(); 4074 o._srf = surface; 4075 o._thisCurve = thisCurve; 4076 o._nearS = -1; 4077 o._nearT = 0; 4078 o.tol = tol; 4079 o.firstPass = false; 4080 4081 return o; 4082 } 4083 4084 /** 4085 * Function that calculates the signed distance between a point on an curve, Q(u), 4086 * and the closest point on a parametric surface, P(s,t). 4087 * 4088 * @param u Parametric distance along the curve. 4089 * @return The signed distance between the point on the curve at u and the closest 4090 * point on the surface. 4091 */ 4092 @Override 4093 public double function(double u) throws RootException { 4094 // Don't compute anything if the 1st pass flag is set. 4095 if (firstPass) { 4096 firstPass = false; 4097 return 0; 4098 } 4099 4100 StackContext.enter(); 4101 try { 4102 // Compute the point on the curve at u. 4103 Point Qu = _thisCurve.getRealPoint(u); 4104 4105 // Find the closest point on the surface near "Qu". 4106 SubrangePoint Pst; 4107 if (_nearS < 0) 4108 Pst = _srf.getClosest(Qu, tol); 4109 else 4110 Pst = _srf.getClosest(Qu, _nearS, _nearT, tol); 4111 GeomPoint st = Pst.getParPosition(); 4112 _nearS = st.getValue(0); 4113 _nearT = st.getValue(1); 4114 4115 // Find the signed distance between Pst and Qu. 4116 Vector<Dimensionless> nhat = _srf.getNormal(_nearS, _nearT); 4117 Vector<Length> dP = Qu.toGeomVector().minus(Pst.toGeomVector()); 4118 double dPsign = dP.dot(nhat).getValue(); // Projection of dP onto nhat. 4119 dPsign = dPsign >= 0 ? 1 : -1; 4120 double d = dP.normValue(); // Unsigned distance. 4121 d = d * dPsign; // Signed distance. 4122 4123 return d; 4124 4125 } finally { 4126 StackContext.exit(); 4127 } 4128 } 4129 4130 public static void recycle(CurveSrfIntersectFun instance) { 4131 FACTORY.recycle(instance); 4132 } 4133 4134 private CurveSrfIntersectFun() { } 4135 4136 @SuppressWarnings("unchecked") 4137 private static final ObjectFactory<CurveSrfIntersectFun> FACTORY = new ObjectFactory<CurveSrfIntersectFun>() { 4138 @Override 4139 protected CurveSrfIntersectFun create() { 4140 return new CurveSrfIntersectFun(); 4141 } 4142 4143 @Override 4144 protected void cleanup(CurveSrfIntersectFun obj) { 4145 obj._srf = null; 4146 obj._thisCurve = null; 4147 } 4148 }; 4149 } 4150 4151 /** 4152 * An Evaulatable1D that returns the increment in area subtended by a curve tangent 4153 * vector at the specified parametric location. This is used to find the enclosed area 4154 * of a curve. This is <code>f(s)</code> in: 4155 * <code>A = int_0^1 { f(s) ds } = int_0^1 { ((p(s)-p0) x p'(s)) ds }</code>. 4156 */ 4157 private static class EnclAreaEvaluatable extends AbstractEvaluatable1D { 4158 4159 private AbstractCurve _curve; 4160 private Vector<Dimensionless> _nhat; 4161 private Vector<Length> _p0; 4162 private boolean _is2D; 4163 private boolean _isZero; 4164 public int iterations; 4165 4166 @SuppressWarnings("null") 4167 public static EnclAreaEvaluatable newInstance(AbstractCurve curve, GeomPoint p0) { 4168 if (curve instanceof GeomTransform) 4169 curve = (AbstractCurve)curve.copyToReal(); 4170 if (p0 instanceof GeomTransform) 4171 p0 = p0.copyToReal(); 4172 4173 EnclAreaEvaluatable o = FACTORY.object(); 4174 o._curve = curve; 4175 o._p0 = p0.toGeomVector(); 4176 o._is2D = curve.getPhyDimension() == 2; 4177 o._isZero = false; 4178 4179 if (!o._is2D) { 4180 if (curve.isLine(Parameter.valueOf(1e-10, SI.METER))) { 4181 // For straight line curves, form normal from curve end points. 4182 Vector<Length> v1 = curve.getRealPoint(0).toGeomVector().minus(o._p0); 4183 Vector<Length> v2 = curve.getRealPoint(1).toGeomVector().minus(o._p0); 4184 Vector n = v1.cross(v2); 4185 if (n.magValue() <= 1e-10) { 4186 // The target point is colinear with the line segment. The enclosed area is always zero. 4187 o._isZero = true; 4188 } else 4189 o._nhat = n.toUnitVector(); 4190 4191 } else 4192 o._nhat = curve.getPlaneNormal(p0); 4193 } 4194 o.iterations = 0; 4195 4196 return o; 4197 } 4198 4199 @Override 4200 public double function(double s) throws RootException { 4201 ++iterations; 4202 4203 // If the area must be zero, then don't compute anything. 4204 if (_isZero) 4205 return 0; 4206 4207 StackContext.enter(); 4208 try { 4209 // Extract the curve point (p) and velocity (p') at s. 4210 List<Vector<Length>> ders = _curve.getSDerivatives(s, 1); 4211 Vector<Length> p = ders.get(0); 4212 Vector<Length> pp = ders.get(1); 4213 4214 // Form the p0p vector. 4215 Vector<Length> p0p = p.minus(_p0); 4216 4217 // Calculate fs = p0p(s) x p'(s) 4218 double fs; 4219 if (_is2D) { 4220 // In 2D u x v = det(u, v) = ux*vy - uy*vx 4221 fs = p0p.getValue(0) * pp.getValue(1) - p0p.getValue(1) * pp.getValue(0); 4222 4223 } else { 4224 Vector p0pxpp = p0p.cross(pp); 4225 4226 // Determine the sign by projecting the p0p x pp vector onto the 4227 // plane normal vector for the curve. 4228 fs = p0pxpp.dot(_nhat).getValue(); 4229 } 4230 4231 return fs; 4232 4233 } finally { 4234 StackContext.exit(); 4235 } 4236 } 4237 4238 public static void recycle(EnclAreaEvaluatable instance) { 4239 FACTORY.recycle(instance); 4240 } 4241 4242 private EnclAreaEvaluatable() { } 4243 4244 private static final ObjectFactory<EnclAreaEvaluatable> FACTORY = new ObjectFactory<EnclAreaEvaluatable>() { 4245 @Override 4246 protected EnclAreaEvaluatable create() { 4247 return new EnclAreaEvaluatable(); 4248 } 4249 4250 @Override 4251 protected void cleanup(EnclAreaEvaluatable obj) { 4252 obj._curve = null; 4253 obj._nhat = null; 4254 obj._p0 = null; 4255 } 4256 }; 4257 4258 } 4259 4260}