blob: b8e124f0cbb7c7ac81c6b4c52dc3e4bf071ef941 [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.euclidean.oned;
import org.apache.commons.geometry.core.internal.DoubleFunction1N;
import org.apache.commons.geometry.euclidean.AbstractAffineTransformMatrix;
import org.apache.commons.geometry.euclidean.exception.NonInvertibleTransformException;
import org.apache.commons.geometry.euclidean.internal.Vectors;
import org.apache.commons.numbers.core.Precision;
/** Class using a matrix to represent affine transformations in 1 dimensional Euclidean space.
*
* <p>Instances of this class use a 2x2 matrix for all transform operations.
* The last row of this matrix is always set to the values <code>[0 1]</code> and so
* is not stored. Hence, the methods in this class that accept or return arrays always
* use arrays containing 2 elements, instead of 4.
* </p>
*/
public final class AffineTransformMatrix1D extends AbstractAffineTransformMatrix<Vector1D>
implements Transform1D {
/** The number of internal matrix elements. */
private static final int NUM_ELEMENTS = 2;
/** String used to start the transform matrix string representation. */
private static final String MATRIX_START = "[ ";
/** String used to end the transform matrix string representation. */
private static final String MATRIX_END = " ]";
/** String used to separate elements in the matrix string representation. */
private static final String ELEMENT_SEPARATOR = ", ";
/** Shared transform set to the identity matrix. */
private static final AffineTransformMatrix1D IDENTITY_INSTANCE = new AffineTransformMatrix1D(1, 0);
/** Transform matrix entry <code>m<sub>0,0</sub></code>. */
private final double m00;
/** Transform matrix entry <code>m<sub>0,1</sub></code>. */
private final double m01;
/**
* Simple constructor; sets all internal matrix elements.
* @param m00 matrix entry <code>m<sub>0,0</sub></code>
* @param m01 matrix entry <code>m<sub>0,1</sub></code>
*/
private AffineTransformMatrix1D(final double m00, final double m01) {
this.m00 = m00;
this.m01 = m01;
}
/** Return a 2 element array containing the variable elements from the
* internal transformation matrix. The elements are in row-major order.
* The array indices map to the internal matrix as follows:
* <pre>
* [
* arr[0], arr[1],
* 0 1
* ]
* </pre>
* @return 2 element array containing the variable elements from the
* internal transformation matrix
*/
public double[] toArray() {
return new double[] {
m00, m01
};
}
/** {@inheritDoc} */
@Override
public Vector1D apply(final Vector1D vec) {
final double x = vec.getX();
final double resultX = (m00 * x) + m01;
return Vector1D.of(resultX);
}
/** {@inheritDoc}
* @see #applyDirection(Vector1D)
*/
@Override
public Vector1D applyVector(final Vector1D vec) {
return applyVector(vec, Vector1D::of);
}
/** {@inheritDoc}
* @see #applyVector(Vector1D)
*/
@Override
public Vector1D.Unit applyDirection(final Vector1D vec) {
return applyVector(vec, Vector1D.Unit::from);
}
/** {@inheritDoc} */
@Override
public double determinant() {
return m00;
}
/** {@inheritDoc}
*
* <p>This simply returns the current instance.</p>
*/
@Override
public AffineTransformMatrix1D toMatrix() {
return this;
}
/** Get a new transform containing the result of applying a translation logically after
* the transformation represented by the current instance. This is achieved by
* creating a new translation transform and pre-multiplying it with the current
* instance. In other words, the returned transform contains the matrix
* <code>B * A</code>, where <code>A</code> is the current matrix and <code>B</code>
* is the matrix representing the given translation.
* @param translation vector containing the translation values for each axis
* @return a new transform containing the result of applying a translation to
* the current instance
*/
public AffineTransformMatrix1D translate(final Vector1D translation) {
return translate(translation.getX());
}
/** Get a new transform containing the result of applying a translation logically after
* the transformation represented by the current instance. This is achieved by
* creating a new translation transform and pre-multiplying it with the current
* instance. In other words, the returned transform contains the matrix
* <code>B * A</code>, where <code>A</code> is the current matrix and <code>B</code>
* is the matrix representing the given translation.
* @param x translation in the x direction
* @return a new transform containing the result of applying a translation to
* the current instance
*/
public AffineTransformMatrix1D translate(final double x) {
return new AffineTransformMatrix1D(m00, m01 + x);
}
/** Get a new transform containing the result of applying a scale operation
* logically after the transformation represented by the current instance.
* This is achieved by creating a new scale transform and pre-multiplying it with the current
* instance. In other words, the returned transform contains the matrix
* <code>B * A</code>, where <code>A</code> is the current matrix and <code>B</code>
* is the matrix representing the given scale operation.
* @param scaleFactor vector containing scale factors for each axis
* @return a new transform containing the result of applying a scale operation to
* the current instance
*/
public AffineTransformMatrix1D scale(final Vector1D scaleFactor) {
return scale(scaleFactor.getX());
}
/** Get a new transform containing the result of applying a scale operation
* logically after the transformation represented by the current instance.
* This is achieved by creating a new scale transform and pre-multiplying it with the current
* instance. In other words, the returned transform contains the matrix
* <code>B * A</code>, where <code>A</code> is the current matrix and <code>B</code>
* is the matrix representing the given scale operation.
* @param x scale factor
* @return a new transform containing the result of applying a scale operation to
* the current instance
*/
public AffineTransformMatrix1D scale(final double x) {
return new AffineTransformMatrix1D(m00 * x, m01 * x);
}
/** Get a new transform created by multiplying this instance by the argument.
* This is equivalent to the expression {@code A * M} where {@code A} is the
* current transform matrix and {@code M} is the given transform matrix. In
* terms of transformations, applying the returned matrix is equivalent to
* applying {@code M} and <em>then</em> applying {@code A}. In other words,
* the rightmost transform is applied first.
*
* @param m the transform to multiply with
* @return the result of multiplying the current instance by the given
* transform matrix
*/
public AffineTransformMatrix1D multiply(final AffineTransformMatrix1D m) {
return multiply(this, m);
}
/** Get a new transform created by multiplying the argument by this instance.
* This is equivalent to the expression {@code M * A} where {@code A} is the
* current transform matrix and {@code M} is the given transform matrix. In
* terms of transformations, applying the returned matrix is equivalent to
* applying {@code A} and <em>then</em> applying {@code M}. In other words,
* the rightmost transform is applied first.
*
* @param m the transform to multiply with
* @return the result of multiplying the given transform matrix by the current
* instance
*/
public AffineTransformMatrix1D premultiply(final AffineTransformMatrix1D m) {
return multiply(m, this);
}
/** Get a new transform representing the inverse of the current instance.
* @return inverse transform
* @throws NonInvertibleTransformException if the transform matrix cannot be inverted
*/
public AffineTransformMatrix1D inverse() {
final double det = this.m00;
if (!Vectors.isRealNonZero(det)) {
throw new NonInvertibleTransformException("Transform is not invertible; matrix determinant is " + det);
}
validateElementForInverse(m01);
final double invDet = 1.0 / det;
final double c00 = invDet;
final double c01 = -(this.m01 * invDet);
return new AffineTransformMatrix1D(c00, c01);
}
/** {@inheritDoc} */
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = (result * prime) + Double.hashCode(m00);
result = (result * prime) + Double.hashCode(m01);
return result;
}
/**
* Return true if the given object is an instance of {@link AffineTransformMatrix1D}
* and all matrix element values are exactly equal.
* @param obj object to test for equality with the current instance
* @return true if all transform matrix elements are exactly equal; otherwise false
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof AffineTransformMatrix1D)) {
return false;
}
final AffineTransformMatrix1D other = (AffineTransformMatrix1D) obj;
return Precision.equals(this.m00, other.m00) &&
Precision.equals(this.m01, other.m01);
}
/** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append(MATRIX_START)
.append(m00)
.append(ELEMENT_SEPARATOR)
.append(m01)
.append(MATRIX_END);
return sb.toString();
}
/** Multiplies the given vector by the scaling component of this transform.
* The computed coordinate is passed to the given factory function.
* @param <T> factory output type
* @param vec the vector to transform
* @param factory the factory instance that will be passed the transformed coordinate
* @return the factory return value
*/
private <T> T applyVector(final Vector1D vec, final DoubleFunction1N<T> factory) {
final double resultX = m00 * vec.getX();
return factory.apply(resultX);
}
/** Get a new transform with the given matrix elements. The array must contain 2 elements.
* The first element in the array represents the scale factor for the transform and the
* second represents the translation.
* @param arr 2-element array containing values for the variable entries in the
* transform matrix
* @return a new transform initialized with the given matrix values
* @throws IllegalArgumentException if the array does not have 2 elements
*/
public static AffineTransformMatrix1D of(final double... arr) {
if (arr.length != NUM_ELEMENTS) {
throw new IllegalArgumentException("Dimension mismatch: " + arr.length + " != " + NUM_ELEMENTS);
}
return new AffineTransformMatrix1D(arr[0], arr[1]);
}
/** Get the transform representing the identity matrix. This transform does not
* modify point or vector values when applied.
* @return transform representing the identity matrix
*/
public static AffineTransformMatrix1D identity() {
return IDENTITY_INSTANCE;
}
/** Get a transform representing the given translation.
* @param translation vector containing translation values for each axis
* @return a new transform representing the given translation
*/
public static AffineTransformMatrix1D createTranslation(final Vector1D translation) {
return createTranslation(translation.getX());
}
/** Get a transform representing the given translation.
* @param x translation in the x direction
* @return a new transform representing the given translation
*/
public static AffineTransformMatrix1D createTranslation(final double x) {
return new AffineTransformMatrix1D(1, x);
}
/** Get a transform representing a scale operation.
* @param factor vector containing the scale factor
* @return a new transform representing a scale operation
*/
public static AffineTransformMatrix1D createScale(final Vector1D factor) {
return createScale(factor.getX());
}
/** Get a transform representing a scale operation.
* @param factor scale factor
* @return a new transform representing a scale operation
*/
public static AffineTransformMatrix1D createScale(final double factor) {
return new AffineTransformMatrix1D(factor, 0);
}
/** Multiply two transform matrices together.
* @param a first transform
* @param b second transform
* @return the transform computed as {@code a x b}
*/
private static AffineTransformMatrix1D multiply(final AffineTransformMatrix1D a,
final AffineTransformMatrix1D b) {
// calculate the matrix elements
final double c00 = a.m00 * b.m00;
final double c01 = (a.m00 * b.m01) + a.m01;
return new AffineTransformMatrix1D(c00, c01);
}
/** Checks that the given matrix element is valid for use in calculation of
* a matrix inverse. Throws a {@link NonInvertibleTransformException} if not.
* @param element matrix entry to check
* @throws NonInvertibleTransformException if the element is not valid for use
* in calculating a matrix inverse, ie if it is NaN or infinite.
*/
private static void validateElementForInverse(final double element) {
if (!Double.isFinite(element)) {
throw new NonInvertibleTransformException("Transform is not invertible; invalid matrix element: " +
element);
}
}
}