blob: 46605c13d8a2000aed6b62cc0aae29a76ebb38ef [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.lucene.spatial3d.geom;
/**
* A 3d vector in space, not necessarily
* going through the origin.
*
* @lucene.experimental
*/
public class Vector {
/**
* Values that are all considered to be essentially zero have a magnitude
* less than this.
*/
public static final double MINIMUM_RESOLUTION = 1.0e-12;
/**
* Angular version of minimum resolution.
*/
public static final double MINIMUM_ANGULAR_RESOLUTION = Math.PI * MINIMUM_RESOLUTION;
/**
* For squared quantities, the bound is squared too.
*/
public static final double MINIMUM_RESOLUTION_SQUARED = MINIMUM_RESOLUTION * MINIMUM_RESOLUTION;
/**
* For cubed quantities, cube the bound.
*/
public static final double MINIMUM_RESOLUTION_CUBED = MINIMUM_RESOLUTION_SQUARED * MINIMUM_RESOLUTION;
/** The x value */
public final double x;
/** The y value */
public final double y;
/** The z value */
public final double z;
/**
* Gram-Schmidt convergence envelope is a bit smaller than we really need because we don't want the math to fail afterwards in
* other places.
*/
private static final double MINIMUM_GRAM_SCHMIDT_ENVELOPE = MINIMUM_RESOLUTION * 0.5;
/**
* Construct from (U.S.) x,y,z coordinates.
*@param x is the x value.
*@param y is the y value.
*@param z is the z value.
*/
public Vector(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
/**
* Construct a vector that is perpendicular to
* two other (non-zero) vectors. If the vectors are parallel,
* IllegalArgumentException will be thrown.
* Produces a normalized final vector.
*
* @param A is the first vector
* @param BX is the X value of the second
* @param BY is the Y value of the second
* @param BZ is the Z value of the second
*/
public Vector(final Vector A, final double BX, final double BY, final double BZ) {
// We're really looking at two vectors and computing a perpendicular one from that.
this(A.x, A.y, A.z, BX, BY, BZ);
}
/**
* Construct a vector that is perpendicular to
* two other (non-zero) vectors. If the vectors are parallel,
* IllegalArgumentException will be thrown.
* Produces a normalized final vector.
*
* @param AX is the X value of the first
* @param AY is the Y value of the first
* @param AZ is the Z value of the first
* @param BX is the X value of the second
* @param BY is the Y value of the second
* @param BZ is the Z value of the second
*/
public Vector(final double AX, final double AY, final double AZ, final double BX, final double BY, final double BZ) {
// We're really looking at two vectors and computing a perpendicular one from that.
// We can think of this as having three points -- the origin, and two points that aren't the origin.
// Normally, we can compute the perpendicular vector this way:
// x = u2v3 - u3v2
// y = u3v1 - u1v3
// z = u1v2 - u2v1
// Sometimes that produces a plane that does not contain the original three points, however, due to
// numerical precision issues. Then we continue making the answer more precise using the
// Gram-Schmidt process: https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process
// Compute the naive perpendicular
final double thisX = AY * BZ - AZ * BY;
final double thisY = AZ * BX - AX * BZ;
final double thisZ = AX * BY - AY * BX;
final double magnitude = magnitude(thisX, thisY, thisZ);
if (magnitude == 0.0) {
throw new IllegalArgumentException("Degenerate/parallel vector constructed");
}
final double inverseMagnitude = 1.0/magnitude;
double normalizeX = thisX * inverseMagnitude;
double normalizeY = thisY * inverseMagnitude;
double normalizeZ = thisZ * inverseMagnitude;
// For a plane to work, the dot product between the normal vector
// and the points needs to be less than the minimum resolution.
// This is sometimes not true for points that are very close. Therefore
// we need to adjust
int i = 0;
while (true) {
final double currentDotProdA = AX * normalizeX + AY * normalizeY + AZ * normalizeZ;
final double currentDotProdB = BX * normalizeX + BY * normalizeY + BZ * normalizeZ;
if (Math.abs(currentDotProdA) < MINIMUM_GRAM_SCHMIDT_ENVELOPE && Math.abs(currentDotProdB) < MINIMUM_GRAM_SCHMIDT_ENVELOPE) {
break;
}
// Converge on the one that has largest dot product
final double currentVectorX;
final double currentVectorY;
final double currentVectorZ;
final double currentDotProd;
if (Math.abs(currentDotProdA) > Math.abs(currentDotProdB)) {
currentVectorX = AX;
currentVectorY = AY;
currentVectorZ = AZ;
currentDotProd = currentDotProdA;
} else {
currentVectorX = BX;
currentVectorY = BY;
currentVectorZ = BZ;
currentDotProd = currentDotProdB;
}
// Adjust
normalizeX = normalizeX - currentDotProd * currentVectorX;
normalizeY = normalizeY - currentDotProd * currentVectorY;
normalizeZ = normalizeZ - currentDotProd * currentVectorZ;
// Normalize
final double correctedMagnitude = magnitude(normalizeX, normalizeY, normalizeZ);
final double inverseCorrectedMagnitude = 1.0 / correctedMagnitude;
normalizeX = normalizeX * inverseCorrectedMagnitude;
normalizeY = normalizeY * inverseCorrectedMagnitude;
normalizeZ = normalizeZ * inverseCorrectedMagnitude;
//This is probably not needed as the method seems to converge
//quite quickly. But it is safer to have a way out.
if (i++ > 10) {
throw new IllegalArgumentException("Plane could not be constructed! Could not find a normal vector.");
}
}
this.x = normalizeX;
this.y = normalizeY;
this.z = normalizeZ;
}
/**
* Construct a vector that is perpendicular to
* two other (non-zero) vectors. If the vectors are parallel,
* IllegalArgumentException will be thrown.
* Produces a normalized final vector.
*
* @param A is the first vector
* @param B is the second
*/
public Vector(final Vector A, final Vector B) {
this(A, B.x, B.y, B.z);
}
/** Compute a magnitude of an x,y,z value.
*/
public static double magnitude(final double x, final double y, final double z) {
return Math.sqrt(x*x + y*y + z*z);
}
/**
* Compute a normalized unit vector based on the current vector.
*
* @return the normalized vector, or null if the current vector has
* a magnitude of zero.
*/
public Vector normalize() {
double denom = magnitude();
if (denom < MINIMUM_RESOLUTION)
// Degenerate, can't normalize
return null;
double normFactor = 1.0 / denom;
return new Vector(x * normFactor, y * normFactor, z * normFactor);
}
/**
* Evaluate the cross product of two vectors against a point.
* If the dot product of the resultant vector resolves to "zero", then
* return true.
* @param A is the first vector to use for the cross product.
* @param B is the second vector to use for the cross product.
* @param point is the point to evaluate.
* @return true if we get a zero dot product.
*/
public static boolean crossProductEvaluateIsZero(final Vector A, final Vector B, final Vector point) {
// Include Gram-Schmidt in-line so we avoid creating objects unnecessarily
// Compute the naive perpendicular
final double thisX = A.y * B.z - A.z * B.y;
final double thisY = A.z * B.x - A.x * B.z;
final double thisZ = A.x * B.y - A.y * B.x;
final double magnitude = magnitude(thisX, thisY, thisZ);
if (magnitude == 0.0) {
return true;
}
final double inverseMagnitude = 1.0/magnitude;
double normalizeX = thisX * inverseMagnitude;
double normalizeY = thisY * inverseMagnitude;
double normalizeZ = thisZ * inverseMagnitude;
// For a plane to work, the dot product between the normal vector
// and the points needs to be less than the minimum resolution.
// This is sometimes not true for points that are very close. Therefore
// we need to adjust
int i = 0;
while (true) {
final double currentDotProdA = A.x * normalizeX + A.y * normalizeY + A.z * normalizeZ;
final double currentDotProdB = B.x * normalizeX + B.y * normalizeY + B.z * normalizeZ;
if (Math.abs(currentDotProdA) < MINIMUM_GRAM_SCHMIDT_ENVELOPE && Math.abs(currentDotProdB) < MINIMUM_GRAM_SCHMIDT_ENVELOPE) {
break;
}
// Converge on the one that has largest dot product
final double currentVectorX;
final double currentVectorY;
final double currentVectorZ;
final double currentDotProd;
if (Math.abs(currentDotProdA) > Math.abs(currentDotProdB)) {
currentVectorX = A.x;
currentVectorY = A.y;
currentVectorZ = A.z;
currentDotProd = currentDotProdA;
} else {
currentVectorX = B.x;
currentVectorY = B.y;
currentVectorZ = B.z;
currentDotProd = currentDotProdB;
}
// Adjust
normalizeX = normalizeX - currentDotProd * currentVectorX;
normalizeY = normalizeY - currentDotProd * currentVectorY;
normalizeZ = normalizeZ - currentDotProd * currentVectorZ;
// Normalize
final double correctedMagnitude = magnitude(normalizeX, normalizeY, normalizeZ);
final double inverseCorrectedMagnitude = 1.0 / correctedMagnitude;
normalizeX = normalizeX * inverseCorrectedMagnitude;
normalizeY = normalizeY * inverseCorrectedMagnitude;
normalizeZ = normalizeZ * inverseCorrectedMagnitude;
//This is probably not needed as the method seems to converge
//quite quickly. But it is safer to have a way out.
if (i++ > 10) {
throw new IllegalArgumentException("Plane could not be constructed! Could not find a normal vector.");
}
}
return Math.abs(normalizeX * point.x + normalizeY * point.y + normalizeZ * point.z) < MINIMUM_RESOLUTION;
}
/**
* Do a dot product.
*
* @param v is the vector to multiply.
* @return the result.
*/
public double dotProduct(final Vector v) {
return this.x * v.x + this.y * v.y + this.z * v.z;
}
/**
* Do a dot product.
*
* @param x is the x value of the vector to multiply.
* @param y is the y value of the vector to multiply.
* @param z is the z value of the vector to multiply.
* @return the result.
*/
public double dotProduct(final double x, final double y, final double z) {
return this.x * x + this.y * y + this.z * z;
}
/**
* Determine if this vector, taken from the origin,
* describes a point within a set of planes.
*
* @param bounds is the first part of the set of planes.
* @param moreBounds is the second part of the set of planes.
* @return true if the point is within the bounds.
*/
public boolean isWithin(final Membership[] bounds, final Membership... moreBounds) {
// Return true if the point described is within all provided bounds
//System.err.println(" checking if "+this+" is within bounds");
for (final Membership bound : bounds) {
if (bound != null && !bound.isWithin(this)) {
//System.err.println(" NOT within "+bound);
return false;
}
}
for (final Membership bound : moreBounds) {
if (bound != null && !bound.isWithin(this)) {
//System.err.println(" NOT within "+bound);
return false;
}
}
//System.err.println(" is within");
return true;
}
/**
* Translate vector.
*/
public Vector translate(final double xOffset, final double yOffset, final double zOffset) {
return new Vector(x - xOffset, y - yOffset, z - zOffset);
}
/**
* Rotate vector counter-clockwise in x-y by an angle.
*/
public Vector rotateXY(final double angle) {
return rotateXY(Math.sin(angle), Math.cos(angle));
}
/**
* Rotate vector counter-clockwise in x-y by an angle, expressed as sin and cos.
*/
public Vector rotateXY(final double sinAngle, final double cosAngle) {
return new Vector(x * cosAngle - y * sinAngle, x * sinAngle + y * cosAngle, z);
}
/**
* Rotate vector counter-clockwise in x-z by an angle.
*/
public Vector rotateXZ(final double angle) {
return rotateXZ(Math.sin(angle), Math.cos(angle));
}
/**
* Rotate vector counter-clockwise in x-z by an angle, expressed as sin and cos.
*/
public Vector rotateXZ(final double sinAngle, final double cosAngle) {
return new Vector(x * cosAngle - z * sinAngle, y, x * sinAngle + z * cosAngle);
}
/**
* Rotate vector counter-clockwise in z-y by an angle.
*/
public Vector rotateZY(final double angle) {
return rotateZY(Math.sin(angle), Math.cos(angle));
}
/**
* Rotate vector counter-clockwise in z-y by an angle, expressed as sin and cos.
*/
public Vector rotateZY(final double sinAngle, final double cosAngle) {
return new Vector(x, z * sinAngle + y * cosAngle, z * cosAngle - y * sinAngle);
}
/**
* Compute the square of a straight-line distance to a point described by the
* vector taken from the origin.
* Monotonically increasing for arc distances up to PI.
*
* @param v is the vector to compute a distance to.
* @return the square of the linear distance.
*/
public double linearDistanceSquared(final Vector v) {
double deltaX = this.x - v.x;
double deltaY = this.y - v.y;
double deltaZ = this.z - v.z;
return deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
}
/**
* Compute the square of a straight-line distance to a point described by the
* vector taken from the origin.
* Monotonically increasing for arc distances up to PI.
*
* @param x is the x part of the vector to compute a distance to.
* @param y is the y part of the vector to compute a distance to.
* @param z is the z part of the vector to compute a distance to.
* @return the square of the linear distance.
*/
public double linearDistanceSquared(final double x, final double y, final double z) {
double deltaX = this.x - x;
double deltaY = this.y - y;
double deltaZ = this.z - z;
return deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
}
/**
* Compute the straight-line distance to a point described by the
* vector taken from the origin.
* Monotonically increasing for arc distances up to PI.
*
* @param v is the vector to compute a distance to.
* @return the linear distance.
*/
public double linearDistance(final Vector v) {
return Math.sqrt(linearDistanceSquared(v));
}
/**
* Compute the straight-line distance to a point described by the
* vector taken from the origin.
* Monotonically increasing for arc distances up to PI.
*
* @param x is the x part of the vector to compute a distance to.
* @param y is the y part of the vector to compute a distance to.
* @param z is the z part of the vector to compute a distance to.
* @return the linear distance.
*/
public double linearDistance(final double x, final double y, final double z) {
return Math.sqrt(linearDistanceSquared(x, y, z));
}
/**
* Compute the square of the normal distance to a vector described by a
* vector taken from the origin.
* Monotonically increasing for arc distances up to PI/2.
*
* @param v is the vector to compute a distance to.
* @return the square of the normal distance.
*/
public double normalDistanceSquared(final Vector v) {
double t = dotProduct(v);
double deltaX = this.x * t - v.x;
double deltaY = this.y * t - v.y;
double deltaZ = this.z * t - v.z;
return deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
}
/**
* Compute the square of the normal distance to a vector described by a
* vector taken from the origin.
* Monotonically increasing for arc distances up to PI/2.
*
* @param x is the x part of the vector to compute a distance to.
* @param y is the y part of the vector to compute a distance to.
* @param z is the z part of the vector to compute a distance to.
* @return the square of the normal distance.
*/
public double normalDistanceSquared(final double x, final double y, final double z) {
double t = dotProduct(x, y, z);
double deltaX = this.x * t - x;
double deltaY = this.y * t - y;
double deltaZ = this.z * t - z;
return deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
}
/**
* Compute the normal (perpendicular) distance to a vector described by a
* vector taken from the origin.
* Monotonically increasing for arc distances up to PI/2.
*
* @param v is the vector to compute a distance to.
* @return the normal distance.
*/
public double normalDistance(final Vector v) {
return Math.sqrt(normalDistanceSquared(v));
}
/**
* Compute the normal (perpendicular) distance to a vector described by a
* vector taken from the origin.
* Monotonically increasing for arc distances up to PI/2.
*
* @param x is the x part of the vector to compute a distance to.
* @param y is the y part of the vector to compute a distance to.
* @param z is the z part of the vector to compute a distance to.
* @return the normal distance.
*/
public double normalDistance(final double x, final double y, final double z) {
return Math.sqrt(normalDistanceSquared(x, y, z));
}
/**
* Compute the magnitude of this vector.
*
* @return the magnitude.
*/
public double magnitude() {
return magnitude(x,y,z);
}
/**
* Compute whether two vectors are numerically identical.
* @param otherX is the other vector X.
* @param otherY is the other vector Y.
* @param otherZ is the other vector Z.
* @return true if they are numerically identical.
*/
public boolean isNumericallyIdentical(final double otherX, final double otherY, final double otherZ) {
final double deltaX = x - otherX;
final double deltaY = y - otherY;
final double deltaZ = z - otherZ;
return deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ < MINIMUM_RESOLUTION_SQUARED;
}
/**
* Compute whether two vectors are numerically identical.
* @param other is the other vector.
* @return true if they are numerically identical.
*/
public boolean isNumericallyIdentical(final Vector other) {
final double deltaX = x - other.x;
final double deltaY = y - other.y;
final double deltaZ = z - other.z;
return deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ < MINIMUM_RESOLUTION_SQUARED;
}
/**
* Compute whether two vectors are parallel.
* @param otherX is the other vector X.
* @param otherY is the other vector Y.
* @param otherZ is the other vector Z.
* @return true if they are parallel.
*/
public boolean isParallel(final double otherX, final double otherY, final double otherZ) {
final double thisX = y * otherZ - z * otherY;
final double thisY = z * otherX - x * otherZ;
final double thisZ = x * otherY - y * otherX;
return thisX * thisX + thisY * thisY + thisZ * thisZ < MINIMUM_RESOLUTION_SQUARED;
}
/**
* Compute whether two vectors are numerically identical.
* @param other is the other vector.
* @return true if they are parallel.
*/
public boolean isParallel(final Vector other) {
final double thisX = y * other.z - z * other.y;
final double thisY = z * other.x - x * other.z;
final double thisZ = x * other.y - y * other.x;
return thisX * thisX + thisY * thisY + thisZ * thisZ < MINIMUM_RESOLUTION_SQUARED;
}
/** Compute the desired magnitude of a unit vector projected to a given
* planet model.
* @param planetModel is the planet model.
* @param x is the unit vector x value.
* @param y is the unit vector y value.
* @param z is the unit vector z value.
* @return a magnitude value for that (x,y,z) that projects the vector onto the specified ellipsoid.
*/
static double computeDesiredEllipsoidMagnitude(final PlanetModel planetModel, final double x, final double y, final double z) {
return 1.0 / Math.sqrt(x*x*planetModel.inverseXYScalingSquared + y*y*planetModel.inverseXYScalingSquared + z*z*planetModel.inverseZScalingSquared);
}
/** Compute the desired magnitude of a unit vector projected to a given
* planet model. The unit vector is specified only by a z value.
* @param planetModel is the planet model.
* @param z is the unit vector z value.
* @return a magnitude value for that z value that projects the vector onto the specified ellipsoid.
*/
static double computeDesiredEllipsoidMagnitude(final PlanetModel planetModel, final double z) {
return 1.0 / Math.sqrt((1.0-z*z)*planetModel.inverseXYScalingSquared + z*z*planetModel.inverseZScalingSquared);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Vector))
return false;
Vector other = (Vector) o;
return (other.x == x && other.y == y && other.z == z);
}
@Override
public int hashCode() {
int result;
long temp;
temp = Double.doubleToLongBits(x);
result = (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(y);
result = 31 * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(z);
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public String toString() {
return "[X=" + x + ", Y=" + y + ", Z=" + z + "]";
}
}