blob: b368dfa5745d43057ec107372b4cf199c1bf6884 [file] [log] [blame]
/*
* 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);
}
}