001/* 002 * LineSegment -- Represents an abstract line segment in n-D space. 003 * 004 * Copyright (C) 2013-2017, Joseph A. Huwaldt. 005 * All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public License 018 * along with this program; if not, write to the Free Software 019 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 020 * Or visit: http://www.gnu.org/licenses/lgpl.html 021 */ 022package geomss.geom; 023 024import geomss.geom.nurbs.CurveFactory; 025import geomss.geom.nurbs.NurbsCurve; 026import jahuwaldt.js.param.Parameter; 027import jahuwaldt.tools.math.BracketRoot1D; 028import jahuwaldt.tools.math.Evaluatable1D; 029import jahuwaldt.tools.math.RootException; 030import java.text.MessageFormat; 031import java.util.List; 032import static java.util.Objects.nonNull; 033import static java.util.Objects.requireNonNull; 034import java.util.logging.Level; 035import java.util.logging.Logger; 036import javax.measure.converter.ConversionException; 037import javax.measure.quantity.Dimensionless; 038import javax.measure.quantity.Length; 039import javax.measure.unit.SI; 040import javax.measure.unit.Unit; 041import javolution.lang.MathLib; 042import javolution.text.Text; 043import javolution.text.TextBuilder; 044import javolution.util.FastTable; 045 046/** 047 * The interface and implementation in common to all line segments in n-dimensional space. 048 * 049 * <p> Modified by: Joseph A. Huwaldt </p> 050 * 051 * @author Joseph A. Huwaldt, Date: January 14, 2012 052 * @version January 31, 2017 053 */ 054@SuppressWarnings({"serial", "CloneableImplementsClone"}) 055public abstract class LineSegment extends AbstractCurve<LineSegment> implements PointGeometry<LineSegment> { 056 057 /** 058 * Return the starting point of the line segment. 059 * 060 * @return The starting point for this line segment. 061 */ 062 public abstract GeomPoint getStart(); 063 064 /** 065 * Return the end point of the line segment. 066 * 067 * @return The end point of this line segment. 068 */ 069 public abstract GeomPoint getEnd(); 070 071 /** 072 * Get unit direction vector for the line segment. 073 * 074 * @return A unit vector indicating the direction of this line segment. 075 */ 076 public abstract GeomVector<Dimensionless> getUnitVector(); 077 078 /** 079 * Return the dimensional direction/derivative vector for this line segment. The 080 * length of this vector is the length of the line segment, the origin is at the start 081 * point and the end of the vector is the line end. 082 * 083 * @return The dimensional derivative vector for this line segment. 084 */ 085 public abstract GeomVector<Length> getDerivativeVector(); 086 087 /** 088 * Returns the number of child-elements that make up this geometry element. This 089 * implementation always returns 2 as a LineSegment has two end points. 090 * 091 * @return The number of points in this line segment (always returns 2). 092 */ 093 @Override 094 public int size() { 095 return 2; 096 } 097 098 /** 099 * Returns the number of physical dimensions of the geometry element. 100 * 101 * @return The number of physical dimensions of this geometry element. 102 */ 103 @Override 104 public int getPhyDimension() { 105 return getStart().getPhyDimension(); 106 } 107 108 /** 109 * Return the total number of points in this geometry element. 110 * 111 * @return Always returns 2. 112 */ 113 @Override 114 public int getNumberOfPoints() { 115 return 2; 116 } 117 118 /** 119 * Split this curve at the specified parametric position returning a list containing 120 * two curves (a lower curve with smaller parametric positions than "s" and an upper 121 * curve with larger parametric positions). 122 * 123 * @param s The parametric position where this curve should be split (must not be 0 or 124 * 1!). 125 * @return A list containing two curves: 0 == the lower curve, 1 == the upper curve. 126 */ 127 @Override 128 public GeomList<LineSegment> splitAt(double s) { 129 validateParameter(s); 130 s = roundParNearEnds(s); 131 if (parNearEnds(s, TOL_S)) 132 throw new IllegalArgumentException(MessageFormat.format( 133 RESOURCES.getString("canNotSplitAtEnds"), "curve")); 134 135 // Get the point at the specified split point. 136 Point p = getRealPoint(s); 137 138 // Create the curve segments. 139 LineSeg cl = LineSeg.valueOf(getStart().immutable(), p); 140 LineSeg cu = LineSeg.valueOf(p, getEnd().immutable()); 141 142 // Create the output list. 143 GeomList output = GeomList.newInstance(); 144 output.add(cl, cu); 145 146 return output; 147 } 148 149 /** 150 * Return the curvature (kappa = 1/rho; where rho = the radius of curvature) of the 151 * curve at the parametric position <code>s</code>. This implementation always returns 152 * zero. 153 * 154 * @param s Parametric distance to calculate the curvature for (0.0 to 1.0 inclusive). 155 * @return The curvature of the curve at the specified parametric position in units of 156 * 1/Length. 157 */ 158 @Override 159 public Parameter getCurvature(double s) { 160 validateParameter(s); 161 return Parameter.valueOf(0, getUnit().inverse()); 162 } 163 164 /** 165 * Return the variation of curvature or rate of change of curvature (VOC or 166 * dKappa(s)/ds) at the parametric position <code>s</code>. The VOC for a line segment 167 * is always zero. 168 * 169 * @param s Parametric distance to calculate the variation of curvature for (0.0 to 170 * 1.0 inclusive). 171 * @return The variation of curvature of the curve at the specified parametric 172 * position in units of 1/Length. 173 */ 174 @Override 175 public Parameter getVariationOfCurvature(double s) { 176 validateParameter(s); 177 return Parameter.valueOf(0, getUnit().inverse()); 178 } 179 180 /** 181 * Return the torsion of the curve at the parametric position <code>s</code>. The 182 * torsion is a measure of the rotation or twist about the tangent vector. Torsion for 183 * a line segment is always zero. 184 * 185 * @param s Parametric distance to calculate the torsion for (0.0 to 1.0 inclusive). 186 * @return The torsion of the curve at the specified parametric position in units of 187 * 1/Length. 188 */ 189 @Override 190 public Parameter getTorsion(double s) { 191 validateParameter(s); 192 return Parameter.valueOf(0, getUnit().inverse()); 193 } 194 195 /** 196 * Return the arc length of a segment of this curve. 197 * 198 * @param s1 The starting parametric distance along the curve to begin the arc-length 199 * calculation from. 200 * @param s2 The ending parametric distance along the curve to end the arc-length 201 * calculation at. 202 * @param eps The desired fractional accuracy on the arc-length. For this curve type, 203 * this parameter is ignored and the exact arc length is returned. 204 * @return The arc length of the specified segment of the curve. 205 */ 206 @Override 207 public Parameter<Length> getArcLength(double s1, double s2, double eps) { 208 validateParameter(s1); 209 validateParameter(s2); 210 211 return getArcLength(0).times(MathLib.abs(s2 - s1)); 212 } 213 214 /** 215 * Return a subrange point at the position on the curve with the specified arc-length. 216 * If the requested arc-length is ≤ 0, the start of the curve is returned. If the 217 * requested arc length is > the total arc length of the curve, then the end point 218 * is returned. 219 * 220 * @param targetArcLength The target arc length to find in meters. May not be null. 221 * @param tol Fractional tolerance (in parameter space) to refine the 222 * point position to. For this curve, this parameter is ignored 223 * and the exact point position is always returned. 224 * @return A subrange point on the curve at the specified arc-length position. 225 */ 226 @Override 227 public SubrangePoint getPointAtArcLength(Parameter<Length> targetArcLength, double tol) { 228 // Deal with an obvious case first. 229 if (targetArcLength.isApproxZero()) 230 return SubrangePoint.newInstance(this, Point.valueOf(0)); 231 232 double arcLengthValue = targetArcLength.getValue(SI.METER); 233 234 // Calculate the full arc-length of the curve (tolerance is ignored). 235 double fullArcLength = getArcLength(0).getValue(SI.METER); 236 237 // Is the requested arc length > than the actual arc length? 238 if (arcLengthValue > fullArcLength) 239 return SubrangePoint.newInstance(this, Point.valueOf(1)); 240 241 // The parametric position is the ratio of the desired arc-length to the full arc-length. 242 double s = arcLengthValue / fullArcLength; 243 244 return getPoint(s); 245 } 246 247 /** 248 * Return a string of points that are gridded onto the curve using the specified 249 * spacing and gridding rule. 250 * 251 * @param gridRule Specifies whether the subdivision spacing is applied with respect 252 * to arc-length in real space or parameter space. May not be null. 253 * @param spacing A list of values used to define the subdivision spacing. 254 * <code>gridRule</code> specifies whether the subdivision spacing is 255 * applied with respect to arc-length in real space or parameter 256 * space. If the spacing is arc-length, then the values are 257 * interpreted as fractions of the arc length of the curve from 0 to 258 * 1. May not be null. 259 * @return A string of subrange points gridded onto the curve at the specified 260 * parametric positions. 261 */ 262 @Override 263 public PointString<SubrangePoint> extractGrid(GridRule gridRule, List<Double> spacing) { 264 265 PointString<SubrangePoint> str = PointString.newInstance(); 266 267 switch (gridRule) { 268 case PAR: 269 // Parametric spacing. 270 for (double s : spacing) { 271 if (s > 1) 272 s = 1; 273 else if (s < 0) 274 s = 0; 275 str.add(this.getPoint(s)); 276 } 277 break; 278 279 case ARC: 280 // Arc-length spacing. 281 Parameter<Length> fullArcLength = getArcLength(0); 282 for (double s : spacing) { 283 SubrangePoint p = getPointAtArcLength(fullArcLength.times(s), 0); 284 str.add(p); 285 } 286 break; 287 288 default: 289 throw new IllegalArgumentException( 290 MessageFormat.format(RESOURCES.getString("crvUnsupportedGridRule"), 291 gridRule.toString())); 292 293 } 294 295 return str; 296 } 297 298 /** 299 * Return a string of points that are gridded onto the curve with the number of points 300 * and spacing chosen to result in straight line segments between the points that have 301 * mid-points that are all within the specified tolerance of this curve. 302 * 303 * @param tol The maximum distance that a straight line between gridded points may 304 * deviate from this curve. Ignored by this implementation. 305 * @return A string of subrange points gridded onto the curve. 306 */ 307 @Override 308 public PointString<SubrangePoint> gridToTolerance(Parameter<Length> tol) { 309 310 // Create the list of points. 311 PointString str = PointString.newInstance(); 312 str.add(getPoint(0)); 313 str.add(getPoint(1)); 314 315 return str; 316 } 317 318 /** 319 * Return the intersection between a plane and this curve. 320 * 321 * @param plane The plane to intersect with this curve. May not be null. 322 * @param tol Fractional tolerance (in parameter space) to refine the point 323 * positions to. For this curve, this parameter is ignored and the exact 324 * point positions are returned. 325 * @return A PointString containing zero or one subrange points made by the 326 * intersection of this curve with the specified plane. If no intersection is 327 * found, an empty PointString is returned. 328 */ 329 @Override 330 public PointString<SubrangePoint> intersect(GeomPlane plane, double tol) { 331 requireNonNull(plane); 332 333 // Intersect the plane with an infinite line. 334 MutablePoint p = MutablePoint.newInstance(getPhyDimension(), getUnit()); 335 IntersectType type = GeomUtil.linePlaneIntersect(getStart(), getDerivativeVector(), plane, p); 336 337 // Check for no intersection. 338 PointString<SubrangePoint> str = PointString.newInstance(); 339 if (type == IntersectType.DISJOINT) 340 return str; 341 342 // Determine the parametric position of the intersection point. 343 Parameter sDir = p.minus(getStart()).toGeomVector().dot(getDerivativeVector()); 344 if (sDir.getValue() >= 0) { 345 // The intersection is not at a negative parametric position. 346 double length = getArcLength(0).getValue(); 347 double pp0_dist = p.distance(getStart()).getValue(); 348 if (pp0_dist <= length) { 349 // The intersection is not beyond the end of the line. 350 double s = pp0_dist / length; 351 str.add(getPoint(s)); 352 } 353 } 354 355 return str; 356 } 357 358 /** 359 * Return the intersection between a line segment and this curve. 360 * 361 * @param line The line segment to intersect. May not be null. 362 * @param tol Tolerance (in physical space) to refine the point positions to. A null 363 * value indicates an exact intersection is required. 364 * @return A list containing two PointString instances each containing zero or 1 365 * subrange points, on this line segment and the input line segment 366 * respectively, made by the intersection of this line segment with the 367 * specified line segment. If no intersections are found a list of two empty 368 * PointStrings are returned. 369 */ 370 @Override 371 public GeomList<PointString<SubrangePoint>> intersect(LineSegment line, Parameter<Length> tol) { 372 // This is simply a single line-line intersection problem. 373 374 // Extract the start point and tangent vector of the two lines. 375 GeomPoint p1 = this.getStart(); 376 GeomVector<Length> t1 = this.getDerivativeVector(); 377 GeomPoint p2 = line.getStart(); 378 GeomVector<Length> t2 = line.getDerivativeVector(); 379 380 // Do the line-line intersection problem. 381 MutablePoint sOut = MutablePoint.newInstance(2); 382 IntersectType type = GeomUtil.lineLineIntersect(p1, t1, p2, t2, tol, sOut, null, null); 383 384 // Collect the outputs. 385 GeomList<PointString<SubrangePoint>> output = GeomList.newInstance(); 386 output.add(PointString.newInstance()); 387 output.add(PointString.newInstance()); 388 if (type == IntersectType.DISJOINT) { 389 MutablePoint.recycle(sOut); 390 return output; 391 } 392 393 output.get(0).add(getPoint(sOut.getValue(0))); 394 output.get(1).add(getPoint(sOut.getValue(1))); 395 MutablePoint.recycle(sOut); 396 397 return output; 398 } 399 400 /** 401 * Return a list of brackets that surround possible closest/farthest points. Each of 402 * the brackets will be refined using a root solver in order to find the actual 403 * closest or farthest points. 404 * <p> 405 * This implementation sub-divides the curve into 2 equal-sized segments and looks for 406 * a single bracket inside each segment. 407 * </p> 408 * 409 * @param point The point to find the closest/farthest point on this curve to/from 410 * (guaranteed to be the same physical dimension before this method is 411 * called). Ignored by this implementation. 412 * @param closest Set to <code>true</code> to return the closest point or 413 * <code>false</code> to return the farthest point. Ignored by this 414 * implementation. 415 * @param eval A function that calculates the dot product of (p(s)-q) and ps(s) on 416 * the curve. May not be null. 417 * @return A list of brackets that each surround a potential closest/farthest point. 418 */ 419 @Override 420 protected List<BracketRoot1D> guessClosestFarthest(GeomPoint point, boolean closest, 421 Evaluatable1D eval) { 422 423 // Find multiple closest/farthest points (roots) by sub-dividing curve into pieces and 424 // bracketing any roots that occur in any of those segments. 425 List<BracketRoot1D> brackets; 426 try { 427 428 brackets = BracketRoot1D.findBrackets(requireNonNull(eval), 0, 1, 2); 429 430 } catch (RootException err) { 431 Logger.getLogger(LineSegment.class.getName()).log(Level.WARNING, 432 "Failed to bracket closest/farthest point.", err); 433 return FastTable.newInstance(); 434 } 435 436 return brackets; 437 } 438 439 /** 440 * Return a list of brackets that surround possible points on this curve that are 441 * closest to the target geometry object. Each of the brackets will be refined using a 442 * root solver in order to find the actual closest points. 443 * <p> 444 * This implementation sub-divides the curve into 2 equal-sized segments and looks for 445 * a single bracket inside each segment. 446 * </p> 447 * 448 * @param eval A function that calculates the dot product of (p(s)-q) and ps(s) on the 449 * curve where "q" is the closest point on the target geometry object. 450 * May not be null. 451 * @return A list of brackets that each surround a potential closest point on this 452 * curve. 453 */ 454 @Override 455 protected List<BracketRoot1D> guessClosest(PerpPointPlaneEvaluatable eval) { 456 457 // Find multiple closest/farthest points (roots) by sub-dividing curve into pieces and 458 // bracketing any roots that occur in any of those segments. 459 List<BracketRoot1D> brackets; 460 try { 461 462 brackets = BracketRoot1D.findBrackets(eval, 0, 1, 2); 463 464 } catch (RootException err) { 465 Logger.getLogger(LineSegment.class.getName()).log(Level.WARNING, 466 "Failed to bracket closest point.", err); 467 return FastTable.newInstance(); 468 } 469 470 return brackets; 471 } 472 473 /** 474 * Returns the most extreme point, either minimum or maximum, in the specified 475 * coordinate direction on this curve. 476 * 477 * @param dim An index indicating the dimension to find the min/max point for (0=X, 478 * 1=Y, 2=Z, etc). 479 * @param max Set to <code>true</code> to return the maximum value, <code>false</code> 480 * to return the minimum. 481 * @param tol Fractional tolerance (in parameter space) to refine the min/max point 482 * position to. 483 * @return The point found on this curve that is the min or max in the specified 484 * coordinate direction. 485 */ 486 @Override 487 public SubrangePoint getLimitPoint(int dim, boolean max, double tol) { 488 // Check the input dimension for consistancy. 489 int thisDim = getPhyDimension(); 490 if (dim < 0 || dim >= thisDim) 491 throw new DimensionException(MessageFormat.format( 492 RESOURCES.getString("inputDimOutOfRange"), "line")); 493 494 // Get the start and end points. 495 GeomPoint p0 = getStart(); 496 GeomPoint p1 = getEnd(); 497 498 // Limit point must always be one of the ends of a line segment. 499 if (max) { 500 if (p0.get(dim).isGreaterThan(p1.get(dim))) 501 return getPoint(0); // p0 502 else 503 return getPoint(1); // p1 504 505 } 506 // else min 507 if (p0.get(dim).isLessThan(p1.get(dim))) 508 return getPoint(0); // p0 509 510 return getPoint(1); // p1 511 } 512 513 /** 514 * Return the number of points per segment that should be used when analyzing curve 515 * segments by certain methods. Subclasses should override this method to provide a 516 * more appropriate number of points per segment. 517 *<p> 518 * This implementation always returns 1. 519 *</p> 520 * 521 * @return Always returns 1. 522 */ 523 @Override 524 protected int getNumPointsPerSegment() { 525 return 1; 526 } 527 528 /** 529 * Return <code>true</code> if this LineSegment contains valid and finite numerical 530 * components. A value of <code>false</code> will be returned if any of the coordinate 531 * values are NaN or Inf. 532 * 533 * @return true if this line segment contains valid and finite data. 534 */ 535 @Override 536 public boolean isValid() { 537 return getStart().isValid() && getEnd().isValid(); 538 } 539 540 /** 541 * Returns <code>true</code> if this curve is a line to within the specified 542 * tolerance. This implementation always returns <code>true</code> if the line is not 543 * degenerate. 544 * 545 * @param tol The tolerance for determining if this curve is a line. May not be null. 546 * @return Always returns <code>true</code> if the line is not degenerate. 547 */ 548 @Override 549 public boolean isLine(Parameter<Length> tol) { 550 return !isDegenerate(tol); 551 } 552 553 /** 554 * Return <code>true</code> if this curve is planar or <code>false</code> if it is 555 * not. This implementation always returns <code>true</code> 556 * 557 * @param tol The geometric tolerance to use in determining if the curve is planar. 558 * Ignored by this implementation. 559 * 560 * @return Always returns true. 561 */ 562 @Override 563 public boolean isPlanar(Parameter<Length> tol) { 564 return true; 565 } 566 567 /** 568 * Returns <code>true</code> if this curve is a circular arc to within the specified 569 * tolerance. This implementation always returns <code>false</code>. 570 * 571 * @param tol The tolerance for determining if this curve is a circular arc. 572 * Ignored by this implementation. 573 * 574 * @return Always returns false. 575 */ 576 @Override 577 public boolean isCircular(Parameter<Length> tol) { 578 return false; 579 } 580 581 /** 582 * Returns transformed version of this element. The returned object implements 583 * {@link geomss.geom.GeomTransform} and contains this element as a child. 584 * 585 * @param transform The transformation to apply to this geometry. May not be null. 586 * @return A new line segment that is identical to this one with the specified 587 * transformation applied. 588 * @throws DimensionException if this point is not 3D. 589 */ 590 @Override 591 public LineSegTrans getTransformed(GTransform transform) { 592 return LineSegTrans.newInstance(this, requireNonNull(transform)); 593 } 594 595 /** 596 * Returns the unit in which this curve is stated. 597 * 598 * @return The unit in which this curve is stated. 599 */ 600 @Override 601 public Unit<Length> getUnit() { 602 return getStart().getUnit(); 603 } 604 605 /** 606 * Return a NURBS curve representation of this line segment. An exact representation 607 * is returned and the tolerance parameter is ignored. 608 * 609 * @param tol Ignored. May pass <code>null</code>. 610 * @return A NURBS curve that exactly represents this line segment. 611 */ 612 @Override 613 public NurbsCurve toNurbs(Parameter<Length> tol) { 614 NurbsCurve curve = CurveFactory.createLine(getStart(), getEnd()); 615 copyState(curve); // Copy over the super-class state for this curve to the new one. 616 return curve; 617 } 618 619 /** 620 * Return a NURBS curve representation of this line segment. An exact representation 621 * is returned. 622 * 623 * @return A NURBS curve that exactly represents this line segment. 624 */ 625 public NurbsCurve toNurbs() { 626 return toNurbs(null); 627 } 628 629 /** 630 * Returns the text representation of this geometry element that consists of the name 631 * followed by the start and end points. For example: 632 * <pre> 633 * {aLine = {{0.0 m, 0.0 m},{1.0 , 0.0 }} 634 * </pre> 635 * If there is no name, then the output looks like this: 636 * <pre> 637 * {{0.0 m, 0.0 m},{1.0 , 0.0 }} 638 * </pre> 639 * 640 * @return the text representation of this geometry element. 641 */ 642 @Override 643 public Text toText() { 644 TextBuilder tmp = TextBuilder.newInstance(); 645 tmp.append('{'); 646 String nameStr = getName(); 647 boolean hasName = nonNull(nameStr); 648 if (hasName) { 649 tmp.append(nameStr); 650 tmp.append(" = {"); 651 } 652 653 tmp.append(getStart().toText()); 654 tmp.append(","); 655 tmp.append(getEnd().toText()); 656 657 if (hasName) 658 tmp.append('}'); 659 tmp.append('}'); 660 Text txt = tmp.toText(); 661 TextBuilder.recycle(tmp); 662 return txt; 663 } 664 665 /** 666 * Returns the equivalent to this LineSegment object but stated in the specified 667 * unit. 668 * 669 * @param unit the length unit of the line segment to be returned. May not be null. 670 * @return an equivalent to this line segment object but stated in the specified 671 * unit. 672 * @throws ConversionException if the the input unit is not a length unit. 673 */ 674 @Override 675 public abstract LineSegment to(Unit<Length> unit) throws ConversionException; 676 677}