Fixed a problem with too thin polygons considered to have infinite size.
Project: http://git-wip-us.apache.org/repos/asf/commons-math/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-math/commit/e6aae3a8 Tree: http://git-wip-us.apache.org/repos/asf/commons-math/tree/e6aae3a8 Diff: http://git-wip-us.apache.org/repos/asf/commons-math/diff/e6aae3a8 Branch: refs/heads/master Commit: e6aae3a8bffb981b8dce19642cc1ab831a428cae Parents: 893ef53 Author: Luc Maisonobe <l...@apache.org> Authored: Tue Dec 2 22:09:06 2014 +0100 Committer: Luc Maisonobe <l...@apache.org> Committed: Tue Dec 2 22:09:06 2014 +0100 ---------------------------------------------------------------------- src/changes/changes.xml | 3 + .../geometry/euclidean/twod/PolygonsSet.java | 414 +++++++++++++------ .../euclidean/twod/PolygonsSetTest.java | 60 +-- 3 files changed, 330 insertions(+), 147 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-math/blob/e6aae3a8/src/changes/changes.xml ---------------------------------------------------------------------- diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 46ad704..a4b462c 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -73,6 +73,9 @@ Users are encouraged to upgrade to this version as this release not 2. A few methods in the FastMath class are in fact slower that their counterpart in either Math or StrictMath (cf. MATH-740 and MATH-901). "> + <action dev="luc" type="fix" issue="MATH-1174" > + Fixed a problem with too thin polygons considered to have infinite size. + </action> <action dev="luc" type="add" > Boundary attributes in regions now provides the BSP tree nodes that were used to split the sub-hyperplane froming the boundary part of the facet. http://git-wip-us.apache.org/repos/asf/commons-math/blob/e6aae3a8/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/PolygonsSet.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/PolygonsSet.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/PolygonsSet.java index 532d6a0..8361831 100644 --- a/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/PolygonsSet.java +++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/twod/PolygonsSet.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import org.apache.commons.math3.exception.MathInternalError; import org.apache.commons.math3.geometry.Point; import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D; import org.apache.commons.math3.geometry.euclidean.oned.Interval; @@ -31,10 +30,9 @@ import org.apache.commons.math3.geometry.partitioning.AbstractSubHyperplane; import org.apache.commons.math3.geometry.partitioning.BSPTree; import org.apache.commons.math3.geometry.partitioning.BSPTreeVisitor; import org.apache.commons.math3.geometry.partitioning.BoundaryAttribute; +import org.apache.commons.math3.geometry.partitioning.Hyperplane; import org.apache.commons.math3.geometry.partitioning.Side; import org.apache.commons.math3.geometry.partitioning.SubHyperplane; -import org.apache.commons.math3.geometry.partitioning.utilities.AVLTree; -import org.apache.commons.math3.geometry.partitioning.utilities.OrderedTuple; import org.apache.commons.math3.util.FastMath; /** This class represents a 2D region: a set of polygons. @@ -689,19 +687,34 @@ public class PolygonsSet extends AbstractRegion<Euclidean2D, Euclidean1D> { vertices = new Vector2D[0][]; } else { - // sort the segments according to their start point - final SegmentsBuilder visitor = new SegmentsBuilder(); + // build the unconnected segments + final SegmentsBuilder visitor = new SegmentsBuilder(getTolerance()); getTree(true).visit(visitor); - final AVLTree<ComparableSegment> sorted = visitor.getSorted(); - - // identify the loops, starting from the open ones - // (their start segments are naturally at the sorted set beginning) - final ArrayList<List<ComparableSegment>> loops = new ArrayList<List<ComparableSegment>>(); - while (!sorted.isEmpty()) { - final AVLTree<ComparableSegment>.Node node = sorted.getSmallest(); - final List<ComparableSegment> loop = followLoop(node, sorted); + final List<ConnectableSegment> segments = visitor.getSegments(); + + // connect all segments, using topological criteria first + // and using Euclidean distance only as a last resort + int pending = segments.size(); + pending -= naturalFollowerConnections(segments); + if (pending > 0) { + pending -= splitEdgeConnections(segments); + } + if (pending > 0) { + pending -= closeVerticesConnections(segments); + } + + // create the segment loops + final ArrayList<List<ConnectableSegment>> loops = new ArrayList<List<ConnectableSegment>>(); + for (ConnectableSegment s = getUnprocessed(segments); s != null; s = getUnprocessed(segments)) { + final List<ConnectableSegment> loop = followLoop(s); if (loop != null) { - loops.add(loop); + if (loop.get(0).getStart() == null) { + // this is an open loop, we put it on the front + loops.add(0, loop); + } else { + // this is a closed loop, we put it on the back + loops.add(loop); + } } } @@ -709,7 +722,7 @@ public class PolygonsSet extends AbstractRegion<Euclidean2D, Euclidean1D> { vertices = new Vector2D[loops.size()][]; int i = 0; - for (final List<ComparableSegment> loop : loops) { + for (final List<ConnectableSegment> loop : loops) { if (loop.size() < 2 || (loop.size() == 2 && loop.get(0).getStart() == null && loop.get(1).getEnd() == null)) { // single infinite line @@ -764,126 +777,245 @@ public class PolygonsSet extends AbstractRegion<Euclidean2D, Euclidean1D> { } - /** Follow a boundary loop. - * @param node node containing the segment starting the loop - * @param sorted set of segments belonging to the boundary, sorted by - * start points (contains {@code node}) - * @return a list of connected sub-hyperplanes starting at - * {@code node} + /** Connect the segments using only natural follower information. + * @param segments segments complete segments list + * @return number of connections performed */ - private List<ComparableSegment> followLoop(final AVLTree<ComparableSegment>.Node node, - final AVLTree<ComparableSegment> sorted) { - - final ArrayList<ComparableSegment> loop = new ArrayList<ComparableSegment>(); - ComparableSegment segment = node.getElement(); - loop.add(segment); - final Vector2D globalStart = segment.getStart(); - Vector2D end = segment.getEnd(); - node.delete(); - - // is this an open or a closed loop ? - final boolean open = segment.getStart() == null; - - while ((end != null) && (open || (globalStart.distance((Point<Euclidean2D>) end) > getTolerance()))) { - - // search the sub-hyperplane starting where the previous one ended - AVLTree<ComparableSegment>.Node selectedNode = null; - ComparableSegment selectedSegment = null; - double selectedDistance = Double.POSITIVE_INFINITY; - final ComparableSegment lowerLeft = new ComparableSegment(end, -1.0e-10, -1.0e-10); - final ComparableSegment upperRight = new ComparableSegment(end, +1.0e-10, +1.0e-10); - for (AVLTree<ComparableSegment>.Node n = sorted.getNotSmaller(lowerLeft); - (n != null) && (n.getElement().compareTo(upperRight) <= 0); - n = n.getNext()) { - segment = n.getElement(); - final double distance = end.distance((Point<Euclidean2D>) segment.getStart()); - if (distance < selectedDistance) { - selectedNode = n; - selectedSegment = segment; - selectedDistance = distance; + private int naturalFollowerConnections(final List<ConnectableSegment> segments) { + int connected = 0; + for (final ConnectableSegment segment : segments) { + if (segment.getNext() == null) { + final BSPTree<Euclidean2D> node = segment.getNode(); + final BSPTree<Euclidean2D> end = segment.getEndNode(); + for (final ConnectableSegment candidateNext : segments) { + if (candidateNext.getPrevious() == null && + candidateNext.getNode() == end && + candidateNext.getStartNode() == node) { + // connect the two segments + segment.setNext(candidateNext); + candidateNext.setPrevious(segment); + ++connected; + break; + } } } + } + return connected; + } - if (selectedDistance > 1.0e-10) { - // this is a degenerated loop, it probably comes from a very - // tiny region with some segments smaller than the threshold, we - // simply ignore it - return null; + /** Connect the segments resulting from a line splitting a straight edge. + * @param segments segments complete segments list + * @return number of connections performed + */ + private int splitEdgeConnections(final List<ConnectableSegment> segments) { + int connected = 0; + for (final ConnectableSegment segment : segments) { + if (segment.getNext() == null) { + final Hyperplane<Euclidean2D> hyperplane = segment.getNode().getCut().getHyperplane(); + final BSPTree<Euclidean2D> end = segment.getEndNode(); + for (final ConnectableSegment candidateNext : segments) { + if (candidateNext.getPrevious() == null && + candidateNext.getNode().getCut().getHyperplane() == hyperplane && + candidateNext.getStartNode() == end) { + // connect the two segments + segment.setNext(candidateNext); + candidateNext.setPrevious(segment); + ++connected; + break; + } + } } + } + return connected; + } - end = selectedSegment.getEnd(); - loop.add(selectedSegment); - selectedNode.delete(); + /** Connect the segments using Euclidean distance. + * <p> + * This connection heuristic should be used last, as it relies + * only on a fuzzy distance criterion. + * </p> + * @param segments segments complete segments list + * @return number of connections performed + */ + private int closeVerticesConnections(final List<ConnectableSegment> segments) { + int connected = 0; + for (final ConnectableSegment segment : segments) { + if (segment.getNext() == null) { + final Vector2D end = segment.getEnd(); + for (final ConnectableSegment candidateNext : segments) { + if (candidateNext.getPrevious() == null && + Vector2D.distance(end, candidateNext.getStart()) <= getTolerance()) { + // connect the two segments + segment.setNext(candidateNext); + candidateNext.setPrevious(segment); + ++connected; + break; + } + } + } + } + return connected; + } + /** Get first unprocessed segment from a list. + * @param segments segments list + * @return first segment that has not been processed yet + * or null if all segments have been processed + */ + private ConnectableSegment getUnprocessed(final List<ConnectableSegment> segments) { + for (final ConnectableSegment segment : segments) { + if (!segment.isProcessed()) { + return segment; + } } + return null; + } - if ((loop.size() == 2) && !open) { - // this is a degenerated infinitely thin loop, we simply ignore it - return null; + /** Build the loop containing a segment. + * <p> + * The segment put in the loop will be marked as processed. + * </p> + * @param defining segment used to define the loop + * @return loop containing the segment (may be null if the loop is a + * degenerated infinitely thin 2 points loop + */ + private List<ConnectableSegment> followLoop(final ConnectableSegment defining) { + + final List<ConnectableSegment> loop = new ArrayList<ConnectableSegment>(); + loop.add(defining); + defining.setProcessed(true); + + // add segments in connection order + ConnectableSegment next = defining.getNext(); + while (next != defining && next != null) { + loop.add(next); + next.setProcessed(true); + next = next.getNext(); } - if ((end == null) && !open) { - throw new MathInternalError(); + if (next == null) { + // the loop is open and we have found its end, + // we need to find its start too + ConnectableSegment previous = defining.getPrevious(); + while (previous != null) { + loop.add(0, previous); + previous.setProcessed(true); + previous = previous.getPrevious(); + } + } else { + if (loop.size() == 2) { + // this is a degenerated infinitely thin loop, we simply ignore it + return null; + } } return loop; } - /** Private extension of Segment allowing comparison. */ - private static class ComparableSegment extends Segment implements Comparable<ComparableSegment> { + /** Private extension of Segment allowing connection. */ + private static class ConnectableSegment extends Segment { + + /** Node containing segment. */ + private final BSPTree<Euclidean2D> node; - /** Sorting key. */ - private OrderedTuple sortingKey; + /** Node whose intersection with current node defines start point. */ + private final BSPTree<Euclidean2D> startNode; + + /** Node whose intersection with current node defines end point. */ + private final BSPTree<Euclidean2D> endNode; + + /** Previous segment. */ + private ConnectableSegment previous; + + /** Next segment. */ + private ConnectableSegment next; + + /** Indicator for completely processed segments. */ + private boolean processed; /** Build a segment. * @param start start point of the segment * @param end end point of the segment * @param line line containing the segment + * @param node node containing the segment + * @param startNode node whose intersection with current node defines start point + * @param endNode node whose intersection with current node defines end point */ - public ComparableSegment(final Vector2D start, final Vector2D end, final Line line) { + public ConnectableSegment(final Vector2D start, final Vector2D end, final Line line, + final BSPTree<Euclidean2D> node, + final BSPTree<Euclidean2D> startNode, + final BSPTree<Euclidean2D> endNode) { super(start, end, line); - sortingKey = (start == null) ? - new OrderedTuple(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY) : - new OrderedTuple(start.getX(), start.getY()); + this.node = node; + this.startNode = startNode; + this.endNode = endNode; + this.previous = null; + this.next = null; + this.processed = false; } - /** Build a dummy segment. - * <p> - * The object built is not a real segment, only the sorting key is used to - * allow searching in the neighborhood of a point. This is an horrible hack ... - * </p> - * @param start start point of the segment - * @param dx abscissa offset from the start point - * @param dy ordinate offset from the start point + /** Get the node containing segment. + * @return node containing segment */ - public ComparableSegment(final Vector2D start, final double dx, final double dy) { - super(null, null, null); - sortingKey = new OrderedTuple(start.getX() + dx, start.getY() + dy); + public BSPTree<Euclidean2D> getNode() { + return node; } - /** {@inheritDoc} */ - public int compareTo(final ComparableSegment o) { - return sortingKey.compareTo(o.sortingKey); + /** Get the node whose intersection with current node defines start point. + * @return node whose intersection with current node defines start point + */ + public BSPTree<Euclidean2D> getStartNode() { + return startNode; } - /** {@inheritDoc} */ - @Override - public boolean equals(final Object other) { - if (this == other) { - return true; - } else if (other instanceof ComparableSegment) { - return compareTo((ComparableSegment) other) == 0; - } else { - return false; - } + /** Get the node whose intersection with current node defines end point. + * @return node whose intersection with current node defines end point + */ + public BSPTree<Euclidean2D> getEndNode() { + return endNode; } - /** {@inheritDoc} */ - @Override - public int hashCode() { - return getStart().hashCode() ^ getEnd().hashCode() ^ - getLine().hashCode() ^ sortingKey.hashCode(); + /** Get the previous segment. + * @return previous segment + */ + public ConnectableSegment getPrevious() { + return previous; + } + + /** Set the previous segment. + * @param previous previous segment + */ + public void setPrevious(final ConnectableSegment previous) { + this.previous = previous; + } + + /** Get the next segment. + * @return next segment + */ + public ConnectableSegment getNext() { + return next; + } + + /** Set the next segment. + * @param next previous segment + */ + public void setNext(final ConnectableSegment next) { + this.next = next; + } + + /** Set the processed flag. + * @param processed processed flag to set + */ + public void setProcessed(final boolean processed) { + this.processed = processed; + } + + /** Check if the segment has been processed. + * @return true if the segment has been processed + */ + public boolean isProcessed() { + return processed; } } @@ -891,12 +1023,18 @@ public class PolygonsSet extends AbstractRegion<Euclidean2D, Euclidean1D> { /** Visitor building segments. */ private static class SegmentsBuilder implements BSPTreeVisitor<Euclidean2D> { - /** Sorted segments. */ - private AVLTree<ComparableSegment> sorted; + /** Tolerance for close nodes connection. */ + private final double tolerance; - /** Simple constructor. */ - public SegmentsBuilder() { - sorted = new AVLTree<ComparableSegment>(); + /** Built segments. */ + private final List<ConnectableSegment> segments; + + /** Simple constructor. + * @param tolerance tolerance for close nodes connection + */ + public SegmentsBuilder(final double tolerance) { + this.tolerance = tolerance; + this.segments = new ArrayList<ConnectableSegment>(); } /** {@inheritDoc} */ @@ -908,11 +1046,12 @@ public class PolygonsSet extends AbstractRegion<Euclidean2D, Euclidean1D> { public void visitInternalNode(final BSPTree<Euclidean2D> node) { @SuppressWarnings("unchecked") final BoundaryAttribute<Euclidean2D> attribute = (BoundaryAttribute<Euclidean2D>) node.getAttribute(); + final Iterable<BSPTree<Euclidean2D>> splitters = attribute.getSplitters(); if (attribute.getPlusOutside() != null) { - addContribution(attribute.getPlusOutside(), false); + addContribution(attribute.getPlusOutside(), node, splitters, false); } if (attribute.getPlusInside() != null) { - addContribution(attribute.getPlusInside(), true); + addContribution(attribute.getPlusInside(), node, splitters, true); } } @@ -922,32 +1061,69 @@ public class PolygonsSet extends AbstractRegion<Euclidean2D, Euclidean1D> { /** Add the contribution of a boundary facet. * @param sub boundary facet + * @param node node containing segment + * @param splitters splitters for the boundary facet * @param reversed if true, the facet has the inside on its plus side */ - private void addContribution(final SubHyperplane<Euclidean2D> sub, final boolean reversed) { + private void addContribution(final SubHyperplane<Euclidean2D> sub, + final BSPTree<Euclidean2D> node, + final Iterable<BSPTree<Euclidean2D>> splitters, + final boolean reversed) { @SuppressWarnings("unchecked") final AbstractSubHyperplane<Euclidean2D, Euclidean1D> absSub = (AbstractSubHyperplane<Euclidean2D, Euclidean1D>) sub; final Line line = (Line) sub.getHyperplane(); final List<Interval> intervals = ((IntervalsSet) absSub.getRemainingRegion()).asList(); for (final Interval i : intervals) { - final Vector2D start = Double.isInfinite(i.getInf()) ? - null : (Vector2D) line.toSpace((Point<Euclidean1D>) new Vector1D(i.getInf())); - final Vector2D end = Double.isInfinite(i.getSup()) ? - null : (Vector2D) line.toSpace((Point<Euclidean1D>) new Vector1D(i.getSup())); + + // find the 2D points + final Vector2D startV = Double.isInfinite(i.getInf()) ? + null : (Vector2D) line.toSpace((Point<Euclidean1D>) new Vector1D(i.getInf())); + final Vector2D endV = Double.isInfinite(i.getSup()) ? + null : (Vector2D) line.toSpace((Point<Euclidean1D>) new Vector1D(i.getSup())); + + // recover the connectivity information + final BSPTree<Euclidean2D> startN = selectClosest(startV, splitters); + final BSPTree<Euclidean2D> endN = selectClosest(endV, splitters); + if (reversed) { - sorted.insert(new ComparableSegment(end, start, line.getReverse())); + segments.add(new ConnectableSegment(endV, startV, line.getReverse(), + node, endN, startN)); } else { - sorted.insert(new ComparableSegment(start, end, line)); + segments.add(new ConnectableSegment(startV, endV, line, + node, startN, endN)); + } + + } + } + + /** Select the node whose cut sub-hyperplane is closest to specified point. + * @param point reference point + * @param candidates candidate nodes + * @return node closest to point, or null if no node is closer than tolerance + */ + private BSPTree<Euclidean2D> selectClosest(final Vector2D point, final Iterable<BSPTree<Euclidean2D>> candidates) { + + BSPTree<Euclidean2D> selected = null; + double min = Double.POSITIVE_INFINITY; + + for (final BSPTree<Euclidean2D> node : candidates) { + final double distance = FastMath.abs(node.getCut().getHyperplane().getOffset(point)); + if (distance < min) { + selected = node; + min = distance; } } + + return min <= tolerance ? selected : null; + } - /** Get the sorted segments. - * @return sorted segments + /** Get the segments. + * @return built segments */ - public AVLTree<ComparableSegment> getSorted() { - return sorted; + public List<ConnectableSegment> getSegments() { + return segments; } } http://git-wip-us.apache.org/repos/asf/commons-math/blob/e6aae3a8/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/PolygonsSetTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/PolygonsSetTest.java b/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/PolygonsSetTest.java index c6c68a0..52bbbca 100644 --- a/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/PolygonsSetTest.java +++ b/src/test/java/org/apache/commons/math3/geometry/euclidean/twod/PolygonsSetTest.java @@ -850,34 +850,42 @@ public class PolygonsSetTest { Line[] l = { new Line(new Vector2D(0.0, 0.625000007541172), new Vector2D(1.0, 0.625000007541172), 1.0e-10), - new Line(new Vector2D(-0.19204433621902645, 0.0), - new Vector2D(-0.19204433621902645, 1.0), 1.0e-10), - new Line(new Vector2D(-0.40303524786887, 0.4248364535319128), - new Vector2D(-1.12851149797877, -0.2634107480798909), 1.0e-10), - new Line(new Vector2D(0.0, 2.0), - new Vector2D(1.0, 2.0), 1.0e-10) + new Line(new Vector2D(-0.19204433621902645, 0.0), + new Vector2D(-0.19204433621902645, 1.0), 1.0e-10), + new Line(new Vector2D(-0.40303524786887, 0.4248364535319128), + new Vector2D(-1.12851149797877, -0.2634107480798909), 1.0e-10), + new Line(new Vector2D(0.0, 2.0), + new Vector2D(1.0, 2.0), 1.0e-10) }; BSPTree<Euclidean2D> node1 = new BSPTree<Euclidean2D>(new SubLine(l[0], - new IntervalsSet(intersectionAbscissa(l[0], l[1]), - intersectionAbscissa(l[0], l[2]), - 1.0e-10)), - new BSPTree<Euclidean2D>(Boolean.TRUE), new BSPTree<Euclidean2D>(Boolean.FALSE), - null); + new IntervalsSet(intersectionAbscissa(l[0], l[1]), + intersectionAbscissa(l[0], l[2]), + 1.0e-10)), + new BSPTree<Euclidean2D>(Boolean.TRUE), + new BSPTree<Euclidean2D>(Boolean.FALSE), + null); BSPTree<Euclidean2D> node2 = new BSPTree<Euclidean2D>(new SubLine(l[1], - new IntervalsSet(intersectionAbscissa(l[1], l[2]), - intersectionAbscissa(l[1], l[3]), - 1.0e-10)), - node1, new BSPTree<Euclidean2D>(Boolean.FALSE), null); + new IntervalsSet(intersectionAbscissa(l[1], l[2]), + intersectionAbscissa(l[1], l[3]), + 1.0e-10)), + node1, + new BSPTree<Euclidean2D>(Boolean.FALSE), + null); BSPTree<Euclidean2D> node3 = new BSPTree<Euclidean2D>(new SubLine(l[2], - new IntervalsSet(intersectionAbscissa(l[2], l[3]), - Double.POSITIVE_INFINITY, 1.0e-10)), - node2, new BSPTree<Euclidean2D>(Boolean.FALSE), null); + new IntervalsSet(intersectionAbscissa(l[2], l[3]), + Double.POSITIVE_INFINITY, 1.0e-10)), + node2, + new BSPTree<Euclidean2D>(Boolean.FALSE), + null); BSPTree<Euclidean2D> node4 = - new BSPTree<Euclidean2D>(l[3].wholeHyperplane(), node3, new BSPTree<Euclidean2D>(Boolean.FALSE), null); + new BSPTree<Euclidean2D>(l[3].wholeHyperplane(), + node3, + new BSPTree<Euclidean2D>(Boolean.FALSE), + null); PolygonsSet set = new PolygonsSet(node4, 1.0e-10); Assert.assertEquals(0, set.getVertices().length); @@ -1062,9 +1070,9 @@ public class PolygonsSetTest { RegionFactory<Euclidean2D>().difference(set1.copySelf(), set2.copySelf()); - Vector2D[][] verticies = set.getVertices(); - Assert.assertTrue(verticies[0][0] != null); - Assert.assertEquals(1, verticies.length); + Vector2D[][] vertices = set.getVertices(); + Assert.assertTrue(vertices[0][0] != null); + Assert.assertEquals(1, vertices.length); } @Test @@ -1167,12 +1175,8 @@ public class PolygonsSetTest { private SubHyperplane<Euclidean2D> buildHalfLine(Vector2D start, Vector2D end, boolean startIsVirtual) { Line line = new Line(start, end, 1.0e-10); - double lower = startIsVirtual - ? Double.NEGATIVE_INFINITY - : (line.toSubSpace(start)).getX(); - double upper = startIsVirtual - ? (line.toSubSpace(end)).getX() - : Double.POSITIVE_INFINITY; + double lower = startIsVirtual ? Double.NEGATIVE_INFINITY : (line.toSubSpace(start)).getX(); + double upper = startIsVirtual ? (line.toSubSpace(end)).getX() : Double.POSITIVE_INFINITY; return new SubLine(line, new IntervalsSet(lower, upper, 1.0e-10)); }