| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.commons.geometry.core.partition.test; |
| |
| import java.util.Arrays; |
| import java.util.List; |
| |
| import org.apache.commons.geometry.core.RegionLocation; |
| import org.apache.commons.geometry.core.Transform; |
| import org.apache.commons.geometry.core.partitioning.ConvexSubHyperplane; |
| import org.apache.commons.geometry.core.partitioning.Hyperplane; |
| import org.apache.commons.geometry.core.partitioning.Split; |
| import org.apache.commons.geometry.core.partitioning.SubHyperplane; |
| |
| /** Class representing a line segment in two dimensional Euclidean space. This |
| * class should only be used for testing purposes. |
| */ |
| public class TestLineSegment implements ConvexSubHyperplane<TestPoint2D> { |
| /** Abscissa of the line segment start point. */ |
| private final double start; |
| |
| /** Abscissa of the line segment end point. */ |
| private final double end; |
| |
| /** The underlying line for the line segment. */ |
| private final TestLine line; |
| |
| /** Construct a line segment between two points. |
| * @param start start point |
| * @param end end point |
| */ |
| public TestLineSegment(final TestPoint2D start, final TestPoint2D end) { |
| this.line = new TestLine(start, end); |
| |
| final double startValue = line.toSubspaceValue(start); |
| final double endValue = line.toSubspaceValue(end); |
| |
| this.start = Math.min(startValue, endValue); |
| this.end = Math.max(startValue, endValue); |
| } |
| |
| /** Construct a line segment between two points. |
| * @param x1 x coordinate of first point |
| * @param y1 y coordinate of first point |
| * @param x2 x coordinate of second point |
| * @param y2 y coordinate of second point |
| */ |
| public TestLineSegment(final double x1, final double y1, final double x2, final double y2) { |
| this(new TestPoint2D(x1, y1), new TestPoint2D(x2, y2)); |
| } |
| |
| /** Construct a line segment based on an existing line. |
| * @param start abscissa of the line segment start point |
| * @param end abscissa of the line segment end point |
| * @param line the underyling line |
| */ |
| public TestLineSegment(final double start, final double end, final TestLine line) { |
| this.start = Math.min(start, end); |
| this.end = Math.max(start, end); |
| this.line = line; |
| } |
| |
| /** Get the start abscissa value. |
| * @return |
| */ |
| public double getStart() { |
| return start; |
| } |
| |
| /** Get the end abscissa value. |
| * @return |
| */ |
| public double getEnd() { |
| return end; |
| } |
| |
| /** Get the start point of the line segment. |
| * @return the start point of the line segment |
| */ |
| public TestPoint2D getStartPoint() { |
| return line.toSpace(start); |
| } |
| |
| /** Get the end point of the line segment. |
| * @return the end point of the line segment |
| */ |
| public TestPoint2D getEndPoint() { |
| return line.toSpace(end); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public TestLine getHyperplane() { |
| return line; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean isFull() { |
| return start < end && Double.isInfinite(start) && Double.isInfinite(end); |
| |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean isEmpty() { |
| return PartitionTestUtils.PRECISION.eqZero(getSize()); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean isInfinite() { |
| return Double.isInfinite(getSize()); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean isFinite() { |
| return !isInfinite(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public double getSize() { |
| return Math.abs(start - end); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public RegionLocation classify(TestPoint2D point) { |
| if (line.contains(point)) { |
| final double value = line.toSubspaceValue(point); |
| |
| final int startCmp = PartitionTestUtils.PRECISION.compare(value, start); |
| final int endCmp = PartitionTestUtils.PRECISION.compare(value, end); |
| |
| if (startCmp == 0 || endCmp == 0) { |
| return RegionLocation.BOUNDARY; |
| } |
| else if (startCmp > 0 && endCmp < 0) { |
| return RegionLocation.INSIDE; |
| } |
| } |
| |
| return RegionLocation.OUTSIDE; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public TestPoint2D closest(TestPoint2D point) { |
| double value = line.toSubspaceValue(point); |
| value = Math.max(Math.min(value, end), start); |
| |
| return line.toSpace(value); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public List<ConvexSubHyperplane<TestPoint2D>> toConvex() { |
| return Arrays.asList(this); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public TestLineSegment reverse() { |
| TestLine rLine = line.reverse(); |
| return new TestLineSegment(-end, -start, rLine); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public Split<TestLineSegment> split(Hyperplane<TestPoint2D> splitter) { |
| final TestLine splitterLine = (TestLine) splitter; |
| |
| if (isInfinite()) { |
| return splitInfinite(splitterLine); |
| } |
| return splitFinite(splitterLine); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public SubHyperplane.Builder<TestPoint2D> builder() { |
| return new TestLineSegmentCollectionBuilder(line); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public ConvexSubHyperplane<TestPoint2D> transform(Transform<TestPoint2D> transform) { |
| if (!isInfinite()) { |
| // simple case; just transform the points directly |
| TestPoint2D p1 = transform.apply(getStartPoint()); |
| TestPoint2D p2 = transform.apply(getEndPoint()); |
| |
| return new TestLineSegment(p1, p2); |
| } |
| |
| // determine how the line has transformed |
| TestPoint2D p0 = transform.apply(line.toSpace(0)); |
| TestPoint2D p1 = transform.apply(line.toSpace(1)); |
| |
| TestLine tLine = new TestLine(p0, p1); |
| double translation = tLine.toSubspaceValue(p0); |
| double scale = tLine.toSubspaceValue(p1); |
| |
| double tStart = (start * scale) + translation; |
| double tEnd = (end * scale) + translation; |
| |
| return new TestLineSegment(tStart, tEnd, tLine); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public String toString() { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append(this.getClass().getSimpleName()) |
| .append("[start= ") |
| .append(getStartPoint()) |
| .append(", end= ") |
| .append(getEndPoint()) |
| .append("]"); |
| |
| return sb.toString(); |
| } |
| |
| /** Method used to split the instance with the given line when the instance has |
| * infinite size. |
| * @param splitter the splitter line |
| * @return the split convex subhyperplane |
| */ |
| private Split<TestLineSegment> splitInfinite(TestLine splitter) { |
| final TestPoint2D intersection = splitter.intersection(line); |
| |
| if (intersection == null) { |
| // the lines are parallel |
| final double originOffset = splitter.offset(line.getOrigin()); |
| |
| final int sign = PartitionTestUtils.PRECISION.sign(originOffset); |
| if (sign < 0) { |
| return new Split<TestLineSegment>(this, null); |
| } |
| else if (sign > 0) { |
| return new Split<TestLineSegment>(null, this); |
| } |
| return new Split<TestLineSegment>(null, null); |
| } |
| else { |
| // the lines intersect |
| final double intersectionAbscissa = line.toSubspaceValue(intersection); |
| |
| TestLineSegment startSegment = null; |
| TestLineSegment endSegment = null; |
| |
| if (start < intersectionAbscissa) { |
| startSegment = new TestLineSegment(start, intersectionAbscissa, line); |
| } |
| if (intersectionAbscissa < end) { |
| endSegment = new TestLineSegment(intersectionAbscissa, end, line); |
| } |
| |
| final double startOffset = splitter.offset(line.toSpace(intersectionAbscissa - 1)); |
| final double startCmp = PartitionTestUtils.PRECISION.sign(startOffset); |
| |
| final TestLineSegment minus = (startCmp > 0) ? endSegment: startSegment; |
| final TestLineSegment plus = (startCmp > 0) ? startSegment : endSegment; |
| |
| return new Split<TestLineSegment>(minus, plus); |
| } |
| } |
| |
| /** Method used to split the instance with the given line when the instance has |
| * finite size. |
| * @param splitter the splitter line |
| * @return the split convex subhyperplane |
| */ |
| private Split<TestLineSegment> splitFinite(TestLine splitter) { |
| |
| final double startOffset = splitter.offset(line.toSpace(start)); |
| final double endOffset = splitter.offset(line.toSpace(end)); |
| |
| final int startCmp = PartitionTestUtils.PRECISION.sign(startOffset); |
| final int endCmp = PartitionTestUtils.PRECISION.sign(endOffset); |
| |
| // startCmp | endCmp | result |
| // -------------------------------- |
| // 0 | 0 | hyper |
| // 0 | < 0 | minus |
| // 0 | > 0 | plus |
| // < 0 | 0 | minus |
| // < 0 | < 0 | minus |
| // < 0 | > 0 | SPLIT |
| // > 0 | 0 | plus |
| // > 0 | < 0 | SPLIT |
| // > 0 | > 0 | plus |
| |
| if (startCmp == 0 && endCmp == 0) { |
| // the entire line segment is directly on the splitter line |
| return new Split<TestLineSegment>(null, null); |
| } |
| else if (startCmp < 1 && endCmp < 1) { |
| // the entire line segment is on the minus side |
| return new Split<TestLineSegment>(this, null); |
| } |
| else if (startCmp > -1 && endCmp > -1) { |
| // the entire line segment is on the plus side |
| return new Split<TestLineSegment>(null, this); |
| } |
| |
| // we need to split the line |
| final TestPoint2D intersection = splitter.intersection(line); |
| final double intersectionAbscissa = line.toSubspaceValue(intersection); |
| |
| final TestLineSegment startSegment = new TestLineSegment(start, intersectionAbscissa, line); |
| final TestLineSegment endSegment = new TestLineSegment(intersectionAbscissa, end, line); |
| |
| final TestLineSegment minus = (startCmp > 0) ? endSegment: startSegment; |
| final TestLineSegment plus = (startCmp > 0) ? startSegment : endSegment; |
| |
| return new Split<TestLineSegment>(minus, plus); |
| } |
| } |