blob: e70f869d38fc521874946cee799143a7e88ec17f [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 org.apache.commons.geometry.core.Transform;
import org.apache.commons.geometry.core.partitioning.EmbeddingHyperplane;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
import org.apache.commons.geometry.core.partitioning.HyperplaneLocation;
/** Class representing a line in two dimensional Euclidean space. This
* class should only be used for testing purposes.
*/
public class TestLine implements EmbeddingHyperplane<TestPoint2D, TestPoint1D> {
/** Line pointing along the positive x-axis. */
public static final TestLine X_AXIS = new TestLine(0, 0, 1, 0);
/** Line pointing along the positive y-axis. */
public static final TestLine Y_AXIS = new TestLine(0, 0, 0, 1);
/** X value of the normalized line direction. */
private final double directionX;
/** Y value of the normalized line direction. */
private final double directionY;
/** The distance between the origin and the line. */
private final double originOffset;
/** Construct a line from two points. The line points in the direction from
* {@code p1} to {@code p2}.
* @param p1 first point
* @param p2 second point
*/
public TestLine(final TestPoint2D p1, final TestPoint2D p2) {
this(p1.getX(), p1.getY(), p2.getX(), p2.getY());
}
/** Construct a line from two point, given by their components.
* @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 TestLine(final double x1, final double y1, final double x2, final double y2) {
double vecX = x2 - x1;
double vecY = y2 - y1;
double norm = norm(vecX, vecY);
vecX /= norm;
vecY /= norm;
if (!Double.isFinite(vecX) || !Double.isFinite(vecY)) {
throw new IllegalStateException("Unable to create line between points: (" +
x1 + ", " + y1 + "), (" + x2 + ", " + y2 + ")");
}
this.directionX = vecX;
this.directionY = vecY;
this.originOffset = signedArea(vecX, vecY, x1, y1);
}
/** Get the line origin, meaning the projection of the 2D origin onto the line.
* @return line origin
*/
public TestPoint2D getOrigin() {
return toSpace(0);
}
/** Get the x component of the line direction.
* @return x component of the line direction.
*/
public double getDirectionX() {
return directionX;
}
/** Get the y component of the line direction.
* @return y component of the line direction.
*/
public double getDirectionY() {
return directionY;
}
/** {@inheritDoc} */
@Override
public double offset(TestPoint2D point) {
return originOffset - signedArea(directionX, directionY, point.getX(), point.getY());
}
/** {@inheritDoc} */
@Override
public HyperplaneLocation classify(TestPoint2D point) {
final double offset = offset(point);
final double cmp = PartitionTestUtils.PRECISION.compare(offset, 0.0);
if (cmp == 0) {
return HyperplaneLocation.ON;
}
return cmp < 0 ? HyperplaneLocation.MINUS : HyperplaneLocation.PLUS;
}
/** {@inheritDoc} */
@Override
public boolean contains(TestPoint2D point) {
return classify(point) == HyperplaneLocation.ON;
}
/** Get the location of the given 2D point in the 1D space of the line.
* @param point point to project into the line's 1D space
* @return location of the point in the line's 1D space
*/
public double toSubspaceValue(TestPoint2D point) {
return (directionX * point.getX()) + (directionY * point.getY());
}
/** {@inheritDoc} */
@Override
public TestPoint1D toSubspace(TestPoint2D point) {
return new TestPoint1D(toSubspaceValue(point));
}
/** Get the 2D location of the given 1D location in the line's 1D space.
* @param abscissa location in the line's 1D space.
* @return the location of the given 1D point in 2D space
*/
public TestPoint2D toSpace(final double abscissa) {
if (Double.isInfinite(abscissa)) {
int dirXCmp = PartitionTestUtils.PRECISION.sign(directionX);
int dirYCmp = PartitionTestUtils.PRECISION.sign(directionY);
double x;
if (dirXCmp == 0) {
// vertical line
x = getOrigin().getX();
}
else {
x = (dirXCmp < 0 ^ abscissa < 0) ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
}
double y;
if (dirYCmp == 0) {
// horizontal line
y = getOrigin().getY();
}
else {
y = (dirYCmp < 0 ^ abscissa < 0) ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
}
return new TestPoint2D(x, y);
}
final double ptX = (abscissa * directionX) + (-originOffset * directionY);
final double ptY = (abscissa * directionY) + (originOffset * directionX);
return new TestPoint2D(ptX, ptY);
}
/** {@inheritDoc} */
@Override
public TestPoint2D toSpace(TestPoint1D point) {
return toSpace(point.getX());
}
/** {@inheritDoc} */
@Override
public TestPoint2D project(final TestPoint2D point) {
return toSpace(toSubspaceValue(point));
}
/** {@inheritDoc} */
@Override
public TestLine reverse() {
TestPoint2D pt = getOrigin();
return new TestLine(pt.getX(), pt.getY(), pt.getX() - directionX, pt.getY() - directionY);
}
/** {@inheritDoc} */
@Override
public TestLine transform(Transform<TestPoint2D> transform) {
TestPoint2D p1 = transform.apply(toSpace(0));
TestPoint2D p2 = transform.apply(toSpace(1));
return new TestLine(p1, p2);
}
/** {@inheritDoc} */
@Override
public boolean similarOrientation(Hyperplane<TestPoint2D> other) {
final TestLine otherLine = (TestLine) other;
final double dot = (directionX * otherLine.directionX) + (directionY * otherLine.directionY);
return dot >= 0.0;
}
/** {@inheritDoc} */
@Override
public TestLineSegment span() {
return new TestLineSegment(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, this);
}
/** Get the intersection point of the instance and another line.
* @param other other line
* @return intersection point of the instance and the other line
* or null if there is no unique intersection point (ie, the lines
* are parallel or coincident)
*/
public TestPoint2D intersection(final TestLine other) {
final double area = signedArea(directionX, directionY, other.directionX, other.directionY);
if (PartitionTestUtils.PRECISION.eqZero(area)) {
// lines are parallel
return null;
}
final double x = ((other.directionX * originOffset) +
(-directionX * other.originOffset)) / area;
final double y = ((other.directionY * originOffset) +
(-directionY * other.originOffset)) / area;
return new TestPoint2D(x, y);
}
/** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getSimpleName())
.append("[origin= ")
.append(getOrigin())
.append(", direction= (")
.append(directionX)
.append(", ")
.append(directionY)
.append(")]");
return sb.toString();
}
/** Compute the signed area of the parallelogram with sides defined by the given
* vectors.
* @param x1 x coordinate of first vector
* @param y1 y coordinate of first vector
* @param x2 x coordinate of second vector
* @param y2 y coordinate of second vector
* @return the signed are of the parallelogram with side defined by the given
* vectors
*/
private static double signedArea(final double x1, final double y1,
final double x2, final double y2) {
return (x1 * y2) + (-y1 * x2);
}
/** Compute the Euclidean norm.
* @param x x coordinate value
* @param y y coordinate value
* @return Euclidean norm
*/
public static double norm(final double x, final double y) {
return Math.sqrt((x * x) + (y * y));
}
}