HomogeneousPoint3D.java

/*
 * Copyright (C) 2017 Alberto Irurueta Carro (alberto@irurueta.com)
 *
 * Licensed 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 com.irurueta.geometry;

import java.io.Serializable;
import java.util.Objects;

/**
 * Subclass of Point3D defining an homogeneous 3D point.
 * An homogeneous 3D point is defined by four coordinates: (x,y,z,w), where
 * x, y and z are the horizontal, vertical and depth coordinates, respectively,
 * and w is a normalization (homogenization) factor. Homogeneous 3D points at
 * infinity are expressed using w=0 (x,y,z,0) where (x,y,z) describe the
 * direction of the 3D point towards infinity.
 * Inhomogeneous 3D points can be transformed into homogeneous 3D points by
 * setting the w coordinate to one (w=1, not at infinity) as follows:
 * Inhomogeneous 3D point (x,y,z) -< Homogeneous 3D point (x,y,z,1).
 */
public class HomogeneousPoint3D extends Point3D implements Serializable {

    /**
     * Default threshold to consider a point is located at infinity.
     */
    private static final double DEFAULT_INFINITY_THRESHOLD = 1e-10;

    /**
     * Machine precision.
     */
    private static final double PRECISION = 1e-12;

    /**
     * Defines the X coordinate of an homogeneous 2D point.
     */
    private double x;

    /**
     * Defines the Y coordinate of an homogeneous 2D point.
     */
    private double y;

    /**
     * Defines the Z coordinate of an homogeneous 2D point.
     */
    private double z;

    /**
     * Defines the W coordinate of an homogeneous 2D point.
     */
    private double w;

    /**
     * Determines whether this point is already normalized.
     */
    private boolean normalized;

    /**
     * Empty constructor.
     */
    public HomogeneousPoint3D() {
        super();
        x = y = z = 0.0;
        w = 1.0;
        normalized = false;
    }

    /**
     * Constructor of this class. This constructor sets a new homogeneous
     * v array containing the coordinates X, Y, Z and W of the given point.
     *
     * @param v Array of length 4 containing the 3D coordinates of an
     *          homogeneous point.
     * @throws IllegalArgumentException Raised when the size of the array is
     *                                  different of 4.
     */
    public HomogeneousPoint3D(final double[] v) {
        super();
        setCoordinates(v);
    }

    /**
     * Constructor of this class. This constructor sets a new homogeneous 3D
     * point using the coordinates X, Y, Z and W of the given point.
     *
     * @param x X coordinate of the given 3D point.
     * @param y Y coordinate of the given 3D point.
     * @param z Z coordinate of the given 3D point.
     * @param w W coordinate of the given 3D point.
     */
    public HomogeneousPoint3D(final double x, final double y, final double z, final double w) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
        normalized = false;
    }

    /**
     * This constructor sets a new homogeneous 3D point using as initialization
     * provided Point3D instance.
     *
     * @param point Point to initialize new instance to
     */
    public HomogeneousPoint3D(final Point3D point) {
        setCoordinates(point);
    }

    /**
     * Returns the X coordinate of the given homogeneous 3D point instance.
     *
     * @return X coordinate.
     */
    public double getX() {
        return x;
    }

    /**
     * Sets the X coordinate of this homogeneous point.
     *
     * @param x X coordinate.
     */
    public void setX(final double x) {
        this.x = x;
        normalized = false;
    }

    /**
     * Returns the Y coordinate of the given homogeneous 3D point instance.
     *
     * @return Y coordinate.
     */
    public double getY() {
        return y;
    }

    /**
     * Sets the Y coordinate of this homogeneous point.
     *
     * @param y Y coordinate.
     */
    public void setY(final double y) {
        this.y = y;
        normalized = false;
    }

    /**
     * Returns the Z coordinate of the given homogeneous 3D point instance.
     *
     * @return Z coordinate.
     */
    public double getZ() {
        return z;
    }

    /**
     * Sets the Z coordinate of this homogeneous point.
     *
     * @param z Z coordinate.
     */
    public void setZ(final double z) {
        this.z = z;
        normalized = false;
    }

    /**
     * Returns the W coordinate of the given homogeneous 3D point instance.
     *
     * @return W coordinate.
     */
    public double getW() {
        return w;
    }

    /**
     * Sets the W coordinate of this homogeneous point.
     *
     * @param w W coordinate.
     */
    public void setW(final double w) {
        this.w = w;
        normalized = false;
    }

    /**
     * Sets the coordinates of this homogeneous 3D point by using provided X,
     * Y, Z and W coordinates.
     *
     * @param x X coordinate.
     * @param y Y coordinate.
     * @param z Z coordinate.
     * @param w W coordinate.
     */
    public void setCoordinates(final double x, final double y, final double z, final double w) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
        normalized = false;
    }

    /**
     * Sets the coordinates of a 3D point using an array containing its
     * coordinates.
     *
     * @param v Array containing the coordinates of the point.
     * @throws IllegalArgumentException Raised if provided array does not have
     *                                  a valid size.
     */
    @Override
    public final void setCoordinates(final double[] v) {
        if (v.length != POINT3D_HOMOGENEOUS_COORDINATES_LENGTH) {
            throw new IllegalArgumentException();
        } else {
            x = v[0];
            y = v[1];
            z = v[2];
            w = v[3];
            normalized = false;
        }
    }

    /**
     * Sets coordinates of this instance using the coordinates of provided 3D
     * point.
     *
     * @param point Input point.
     */
    @Override
    public final void setCoordinates(final Point3D point) {
        switch (point.getType()) {
            case INHOMOGENEOUS_COORDINATES:
                final var inhomPoint = (InhomogeneousPoint3D) point;
                x = inhomPoint.getX();
                y = inhomPoint.getY();
                z = inhomPoint.getZ();
                w = 1.0;
                normalized = false;
                break;

            case HOMOGENEOUS_COORDINATES:
            default:
                final var homPoint = (HomogeneousPoint3D) point;
                x = homPoint.getX();
                y = homPoint.getY();
                z = homPoint.getZ();
                w = homPoint.getW();
                normalized = false;
                break;
        }
    }

    /**
     * Returns X homogeneous coordinate of this 3D point.
     *
     * @return X homogeneous coordinate.
     */
    @Override
    public double getHomX() {
        return getX();
    }

    /**
     * Returns Y homogeneous coordinate of this 3D point.
     *
     * @return Y homogeneous coordinate.
     */
    @Override
    public double getHomY() {
        return getY();
    }

    /**
     * Returns Z homogeneous coordinate of this 3D point.
     *
     * @return Z homogeneous coordinate.
     */
    @Override
    public double getHomZ() {
        return getZ();
    }

    /**
     * Returns W homogeneous coordinate of this 3D point.
     *
     * @return W homogeneous coordinate.
     */
    @Override
    public double getHomW() {
        return getW();
    }

    /**
     * Sets coordinates of this 3D point instance using provided homogeneous
     * coordinates.
     *
     * @param homX x homogeneous coordinate.
     * @param homY y homogeneous coordinate.
     * @param homZ z homogeneous coordinate.
     * @param homW w homogeneous coordinate.
     */
    @Override
    public void setHomogeneousCoordinates(final double homX, final double homY, final double homZ, final double homW) {
        setCoordinates(homX, homY, homZ, homW);
    }

    /**
     * Returns X inhomogeneous coordinate of this 3D point.
     *
     * @return X inhomogeneous coordinate.
     */
    @Override
    public double getInhomX() {
        return (x / w);
    }

    /**
     * Sets X inhomogeneous coordinate of this 3D point.
     *
     * @param inhomX inhomogeneous coordinate.
     */
    @Override
    public void setInhomX(final double inhomX) {
        x = inhomX * w;
        normalized = false;
    }

    /**
     * Returns Y inhomogeneous coordinate of this 3D point.
     *
     * @return Y inhomogeneous coordinate.
     */
    @Override
    public double getInhomY() {
        return (y / w);
    }

    /**
     * Sets Y inhomogeneous coordinate of this 3D point.
     *
     * @param inhomY Y inhomogeneous coordinate.
     */
    @Override
    public void setInhomY(final double inhomY) {
        y = inhomY * w;
        normalized = false;
    }

    /**
     * Returns Z inhomogeneous coordinate of this 3D point.
     *
     * @return Z inhomogeneous coordinate.
     */
    @Override
    public double getInhomZ() {
        return (z / w);
    }

    /**
     * Sets Z inhomogeneous coordinate of this 3D point.
     *
     * @param inhomZ Z inhomogeneous coordinate.
     */
    public void setInhomZ(final double inhomZ) {
        z = inhomZ * w;
        normalized = false;
    }

    /**
     * Sets coordinates of this 3D point instance using provided inhomogeneous
     * coordinates.
     *
     * @param inhomX x inhomogeneous coordinate.
     * @param inhomY y inhomogeneous coordinate.
     * @param inhomZ z inhomogeneous coordinate.
     */
    @Override
    public void setInhomogeneousCoordinates(final double inhomX, final double inhomY, final double inhomZ) {
        x = inhomX;
        y = inhomY;
        z = inhomZ;
        w = 1.0;
        normalized = false;
    }

    /**
     * Checks if provided object equals current 2D point.
     *
     * @param obj Object to compare.
     * @return True if both objects are considered to be equal, false otherwise.
     */
    @Override
    public boolean equals(final Object obj) {
        if (!(obj instanceof Point3D point)) {
            return false;
        }
        if (obj == this) {
            return true;
        }

        return equals(point);
    }

    /**
     * Returns hash code value.
     *
     * @return Hash code value.
     */
    @Override
    public int hashCode() {
        return Objects.hash(x, y, z, w);
    }

    /**
     * Checks if the homogeneous 3D point described by this instance equals the
     * input Point3D (using a comparison threshold).
     *
     * @param point     Point that will be compared to.
     * @param threshold threshold grade of tolerance to determine whether the
     *                  points are equal or not. It is used because due to machine precision, the
     *                  values might not be exactly equal (if not provided
     *                  DEFAULT_COMPARISON_THRESHOLD is used).
     * @return True if current point and input point are the same, false
     * otherwise.
     * @throws IllegalArgumentException Raised if threshold is negative.
     */
    @Override
    public boolean equals(final Point3D point, final double threshold) {
        if (point.getType() == CoordinatesType.INHOMOGENEOUS_COORDINATES) {
            return equals((InhomogeneousPoint3D) point, threshold);
        } else {
            return equals((HomogeneousPoint3D) point, threshold);
        }
    }

    /**
     * Checks if the homogeneous 3D point described by this instance equals the
     * input HomogeneousPoint2d (using a comparison threshold).
     *
     * @param point     Point that will be compared to.
     * @param threshold threshold grade of tolerance to determine whether the
     *                  points are equal or not. It is used because due to machine precision, the
     *                  values might not be exactly equal (if not provided
     *                  DEFAULT_COMPARISON_THRESHOLD is used).
     * @return True if current point and input point are the same, false
     * otherwise.
     * @throws IllegalArgumentException Raised if threshold is negative.
     */
    public boolean equals(final HomogeneousPoint3D point, final double threshold) {
        if (threshold < MIN_THRESHOLD) {
            throw new IllegalArgumentException();
        }

        normalize();
        point.normalize();

        // compute sign for the case when points have different sign
        final var signThis = (w > 0.0) ? 1.0 : -1.0;
        final var signPoint = (point.w > 0.0) ? 1.0 : -1.0;

        final var normThis = Math.sqrt(x * x + y * y + z * z + w * w) * signThis;
        final var normPoint = Math.sqrt(point.x * point.x + point.y * point.y + point.z * point.z
                + point.w * point.w) * signPoint;

        final var validX = Math.abs(x / normThis - point.x / normPoint) <= threshold;
        final var validY = Math.abs(y / normThis - point.y / normPoint) <= threshold;
        final var validZ = Math.abs(z / normThis - point.z / normPoint) <= threshold;
        final var validW = Math.abs(w / normThis - point.w / normPoint) <= threshold;

        return (validX && validY && validZ && validW);
    }

    /**
     * Checks if the homogeneous 3D point described by this instance equals the
     * input HomogeneousPoint3D (using a comparison threshold).
     *
     * @param point Point that will be compared to.
     * @return True if current point and input point are the same, false
     * otherwise.
     */
    public boolean equals(final HomogeneousPoint3D point) {
        return equals(point, DEFAULT_COMPARISON_THRESHOLD);
    }

    /**
     * Checks if the homogeneous 3D point described by this instance equals the
     * input InhomogeneousPoint3D (using a comparison threshold).
     *
     * @param point     Point that will be compared to.
     * @param threshold threshold grade of tolerance to determine whether the
     *                  points are equal or not. It is used because due to machine precision, the
     *                  values might not be exactly equal (if not provided
     *                  DEFAULT_COMPARISON_THRESHOLD is used).
     * @return True if current point and input point are the same, false
     * otherwise.
     * @throws IllegalArgumentException Raised if threshold is negative.
     */
    public boolean equals(final InhomogeneousPoint3D point, final double threshold) {
        if (threshold < MIN_THRESHOLD) {
            throw new IllegalArgumentException();
        }

        final var dX = Math.abs(point.getX() - (x / w)) <= threshold;
        final var dY = Math.abs(point.getY() - (y / w)) <= threshold;
        final var dZ = Math.abs(point.getZ() - (z / w)) <= threshold;
        return (dX && dY && dZ);
    }

    /**
     * Checks if the homogeneous 3D point described by this instance equals the
     * input InhomogeneousPoint3D (using a comparison threshold).
     *
     * @param point Point that will be compared to.
     * @return True if current point and input point are the same, false
     * otherwise.
     */
    public boolean equals(final InhomogeneousPoint3D point) {
        return equals(point, DEFAULT_COMPARISON_THRESHOLD);
    }

    /**
     * Checks whether this Point3D is at infinity or not.
     *
     * @return True if the point is at infinity. False otherwise.
     */
    @Override
    public boolean isAtInfinity() {
        return isAtInfinity(DEFAULT_INFINITY_THRESHOLD);
    }

    /**
     * Checks whether this homogeneous 3D point is at infinity or not. An
     * homogeneous 3D point is at infinity when W coordinates are equal or close
     * to zero.
     *
     * @param threshold Grade of tolerance to determine whether the point is at
     *                  infinity or not. It is used because due to machine precision, the values
     *                  might not be exactly equal.
     * @return True if point is at infinity, false otherwise.
     * @throws IllegalArgumentException Raised if threshold is negative.
     */
    public boolean isAtInfinity(final double threshold) {
        if (threshold < MIN_THRESHOLD) {
            throw new IllegalArgumentException();
        }

        return (Math.abs(w) <= threshold);
    }

    /**
     * Returns the type of coordinates used to represent a Point3D.
     *
     * @return Type of coordinates of this 3D point.
     */
    @Override
    public CoordinatesType getType() {
        return CoordinatesType.HOMOGENEOUS_COORDINATES;
    }

    /**
     * Method to normalize a 3D point by dividing all homogeneous components by
     * its norm. This only applies to homogeneous 3D points, because they are
     * defined up to scale.
     */
    @Override
    public void normalize() {
        if (!normalized) {
            var norm = Math.sqrt(x * x + y * y + z * z + w * w);
            if (norm > PRECISION) {
                x /= norm;
                y /= norm;
                z /= norm;
                w /= norm;
                normalized = true;
            }
        }
    }

    /**
     * Returns boolean indicating whether this point has already been normalized.
     *
     * @return True if normalized, false otherwise.
     */
    @Override
    public boolean isNormalized() {
        return normalized;
    }

    /**
     * Converts this instance into an inhomogeneous 3D point and returns the
     * result as a new inhomogeneous 3D point instance.
     *
     * @return Converts and returns this point as an inhomogeneous 3D point.
     */
    public InhomogeneousPoint3D toInhomogeneous() {
        return new InhomogeneousPoint3D(x / w, y / w, z / w);
    }

    /**
     * Returns an array containing the coordinates of this Point2D.
     *
     * @return Array containing coordinates of this Point2D.
     */
    @Override
    public double[] asArray() {
        final var out = new double[POINT3D_HOMOGENEOUS_COORDINATES_LENGTH];
        asArray(out);
        return out;
    }

    /**
     * Uses provided array to store the coordinates of this HomogeneousPoint2D.
     *
     * @param array Array where coordinates will be stored.
     * @throws IllegalArgumentException Raised if length of array is not 3.
     */
    @Override
    public void asArray(final double[] array) {
        if (array.length != POINT3D_HOMOGENEOUS_COORDINATES_LENGTH) {
            throw new IllegalArgumentException();
        }
        array[0] = x;
        array[1] = y;
        array[2] = z;
        array[3] = w;
    }
}