net.algart.math.geometry.Orthonormal3DBasis Maven / Gradle / Ivy
Show all versions of algart Show documentation
/*
* The MIT License (MIT)
*
* Copyright (c) 2007-2024 Daniel Alievsky, AlgART Laboratory (http://algart.net)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.algart.math.geometry;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.random.RandomGenerator;
/**
* Right orthonormal basis in 3D Euclidean space: 3 orthogonal unit vectors i, j, k.
*
* This class is immutable and thread-safe:
* there are no ways to modify settings of the created instance.
*
* @author Daniel Alievsky
*/
public final class Orthonormal3DBasis {
/**
* While creating a new basis, two vectors are considered to be "almost collinear"
* if the sine of the angle between them is less or about {@link #COLLINEARITY_EPSILON}.
*/
public static final double COLLINEARITY_EPSILON = 1e-7;
/**
* The square of {@link #COLLINEARITY_EPSILON}.
*/
public static final double COLLINEARITY_EPSILON_SQR = COLLINEARITY_EPSILON * COLLINEARITY_EPSILON;
private static final int REVIVING_COUNT = 32;
// - must be not too big: every rotation can replace |r| to |r|^2, so 50-100 iteration can lead to overflow
/**
* Minimal allowed length of the vectors (ix
,iy
,iz
) and
* (jx
,jy
,jz
), passed to creation methods
* {@link #newBasis(double, double, double, double, double, double, boolean)},
* {@link #newSomeBasis(double, double, double)}.
*/
public static final double MIN_ALLOWED_LENGTH = 1e-100;
private static final AtomicInteger globalCallCount = new AtomicInteger();
/**
* Default basis: I=(1,0,0), J=(0,1,0), K=(0,0,1).
*/
public static final Orthonormal3DBasis DEFAULT =
new Orthonormal3DBasis(1, 0, 0, 0, 1, 0, 0, 0, 1, 0);
private final double ix, iy, iz, jx, jy, jz, kx, ky, kz;
private final int counter;
private Orthonormal3DBasis(
double ix, double iy, double iz,
double jx, double jy, double jz,
double kx, double ky, double kz,
int counter) {
if ((globalCallCount.incrementAndGet() & 0x3FF) == 0) {
final double iSqr = ix * ix + iy * iy + iz * iz;
final double jSqr = jx * jx + jy * jy + jz * jz;
final double kSqr = kx * kx + ky * ky + kz * kz;
final double ij = ix * jx + iy * jy + iz * jz;
final double ik = ix * kx + iy * ky + iz * kz;
final double jk = jx * kx + jy * ky + jz * kz;
//System.out.println("|I|=" + Math.sqrt(iSqr) + ", |J|=" + Math.sqrt(jSqr) + ", |K|=" + Math.sqrt(kSqr)
//+ "; IJ=" + ij + ", IK=" + ik + ", JK=" + jk + " I=(" + ix + "," + iy + "," + iz
//+ "),J=(" + jx + "," + jy + "," +jz
//+ "),K=(" + kx + "," + ky + "," + kz + ")");
if (iSqr < 0.98 || iSqr > 1.02)
throw new AssertionError("Internal error! |I| == " + Math.sqrt(iSqr) + " != 1.0");
if (jSqr < 0.98 || jSqr > 1.02)
throw new AssertionError("Internal error! |J| == " + Math.sqrt(jSqr) + " != 1.0");
if (ij < -0.02 || ij > 0.02)
throw new AssertionError("Internal error! IJ == " + ij + " != 0.0");
if (kSqr < 0.9 || kSqr > 1.1)
throw new AssertionError("Internal error! |K| == " + Math.sqrt(kSqr) + " != 1.0");
if (ik < -0.1 || ik > 0.1)
throw new AssertionError("Internal error! IK == " + ik + " != 0.0");
if (jk < -0.1 || jk > 0.1)
throw new AssertionError("Internal error! JK == " + jk + " != 0.0");
}
this.ix = ix;
this.iy = iy;
this.iz = iz;
this.jx = jx;
this.jy = jy;
this.jz = jz;
this.kx = kx;
this.ky = ky;
this.kz = kz;
this.counter = counter;
}
private Orthonormal3DBasis(double ix, double iy, double iz, double jx, double jy, double jz, int counter) {
this(ix, iy, iz, jx, jy, jz,
iy * jz - iz * jy,
iz * jx - ix * jz,
ix * jy - iy * jx,
counter);
// vector K = [IJ]
}
/**
* Creates new basis, where i vector has components
* (ix
/d, iy
/d, iz
/d),
* d=sqrt
(ix2+iy2+iz2). Other vectors j and k
* will have some values, depending on the implementation.
*
* @param ix x-component of new i vector (maybe, multiplied by some d constant).
* @param iy y-component of new i vector (maybe, multiplied by some d constant).
* @param iz z-component of new i vector (maybe, multiplied by some d constant).
* @return new right orthonormal basis with given direction of i vector.
* @throws IllegalArgumentException if the length sqrt
(ix2+iy2+iz2)
* of the passed vector (ix
,iy
,iz
) is zero or
* too small (< {@link #MIN_ALLOWED_LENGTH}).
*/
public static Orthonormal3DBasis newSomeBasis(double ix, double iy, double iz) {
final double xAbs = ix >= 0.0 ? ix : -ix;
final double yAbs = iy >= 0.0 ? iy : -iy;
final double zAbs = iz >= 0.0 ? iz : -iz;
if (yAbs < zAbs) {
if (xAbs < yAbs) {
// So, normalized vector I=(ix,iy,iz)/sqrt(ix^2+iy^2+iz^2) is in sectors 45 degree
// around axes Y and Z, strongly not collinear with (1,0,0):
// CollinearityException in the following getInstance is impossible
return newBasis(ix, iy, iz, 1.0, 0.0, 0.0, true);
} else {
// and so on...
return newBasis(ix, iy, iz, 0.0, 1.0, 0.0, true);
}
} else {
if (xAbs < zAbs) {
return newBasis(ix, iy, iz, 1.0, 0.0, 0.0, true);
} else {
return newBasis(ix, iy, iz, 0.0, 0.0, 1.0, true);
}
}
}
/**
* Creates new basis, where i vector has components
* (ix
/d1, iy
/d1, iz
/d1),
* d1=sqrt
(ix2+iy2+iz2),
* j vector has components
* (jx
/d2, jy
/d2, jz
/d2),
* d2=sqrt
(jx2+jy2+jz2),
* k vector is chosen automatically to provide right orthonormal basis.
*
* If the passed vectors (ix
,iy
,iz
) and
* (jx
,jy
,jz
)
* are not orthogonal, the vector (jx
,jy
,jz
) is automatically corrected,
* before all other calculations, to become orthogonal to i, and the plane ij is preserved
* while this correction.
*
*
If the passed vectors are collinear or almost collinear (with very little angle difference,
* about 10−8..10−6 radians or something like this),
* or if the length of one of the passed vectors is less than {@link #MIN_ALLOWED_LENGTH},
* then behaviour depends on exceptionOnCollinearity
argument. If it is true
,
* the method throws {@link CollinearityException}. In other case, the method ignores the passed
* vector (jx
,jy
,jz
) and returns some basis according the passed vector
* (ix
,iy
,iz
), as {@link #newSomeBasis(double, double, double)} method.
*
* @param ix x-component of new i vector
* (maybe, multiplied by some d1 constant).
* @param iy y-component of new i vector
* (maybe, multiplied by some d1 constant).
* @param iz z-component of new i vector
* (maybe, multiplied by some d1 constant).
* @param jx x-component of new j vector
* (maybe, multiplied by some d2 constant).
* @param jy y-component of new j vector
* (maybe, multiplied by some d2 constant).
* @param jz z-component of new j vector
* (maybe, multiplied by some d2 constant).
* @param exceptionOnCollinearity whether exception is thrown for collinear vector pair.
* @return new right orthonormal basis with given direction of i vector
* and the direction of j vector, chosen according the arguments (see above).
* @throws IllegalArgumentException if the length sqrt
(ix2+iy2+iz2)
* of the passed vector (ix
,iy
,iz
) or
* the length sqrt
(jx2+jy2+jz2)
* of the passed vector (jx
,jy
,jz
)
* is zero or too small (< {@link #MIN_ALLOWED_LENGTH}).
* @throws CollinearityException if the passed two vectors are almost collinear and
* exceptionOnCollinearity==true
.
*/
public static Orthonormal3DBasis newBasis(
final double ix, final double iy, final double iz,
final double jx, final double jy, final double jz,
final boolean exceptionOnCollinearity)
throws CollinearityException {
final double lengthI = length(ix, iy, iz);
if (lengthI < MIN_ALLOWED_LENGTH) {
throw new IllegalArgumentException("Zero or too short I vector (" + ix + ", " + iy + ", " + iz + ")"
+ " (vectors with length <" + MIN_ALLOWED_LENGTH + " are not allowed)");
}
final double lengthJ = length(jx, jy, jz);
if (lengthJ < MIN_ALLOWED_LENGTH) {
throw new IllegalArgumentException("Zero or too short J vector (" + jx + ", " + jy + ", " + jz + ")"
+ " (vectors with length <" + MIN_ALLOWED_LENGTH + " are not allowed)");
}
double mult = 1.0 / lengthI;
final double newIx = ix * mult;
final double newIy = iy * mult;
final double newIz = iz * mult;
mult = 1.0 / lengthJ;
double newJx = jx * mult;
double newJy = jy * mult;
double newJz = jz * mult;
final double ij = newIx * newJx + newIy * newJy + newIz * newJz;
if (ij != 0.0) {
newJx -= newIx * ij;
newJy -= newIy * ij;
newJz -= newIz * ij; // correct if (ij)!=0: J = J-I(ij)
final double correctedLengthJ = length(newJx, newJy, newJz);
if (correctedLengthJ < COLLINEARITY_EPSILON) {
if (exceptionOnCollinearity) {
throw new CollinearityException("Passed I vector (" + ix + ", " + iy + ", " + iz + ") and "
+ "J vector (" + jx + ", " + jy + ", " + jz + ") are collinear or almost collinear");
} else {
return newSomeBasis(ix, iy, iz);
}
}
mult = 1.0 / correctedLengthJ;
newJx *= mult;
newJy *= mult;
newJz *= mult; // correct again
}
return new Orthonormal3DBasis(newIx, newIy, newIz, newJx, newJy, newJz, 0);
}
/**
* Analogue of {@link #newBasis(double, double, double, double, double, double, boolean)
* getBasis(ix, iy, iz, jx, jy, jz, true}}, but instead of throwing exceptions this method
* just returns Optional.empty()
.
*
*
In other words, this method returns Optional.empty()
when
* the length sqrt
(ix2+iy2+iz2)
* of the passed vector (ix
,iy
,iz
) or
* the length sqrt
(jx2+jy2+jz2)
* of the passed vector (jx
,jy
,jz
)
* is zero or too small (< {@link #MIN_ALLOWED_LENGTH}),
* and also this method returns Optional.empty()
when
* the passed two vectors are almost collinear.
* In all other cases, this method is equivalent
* to Optional.of({@link #newBasis(double, double, double, double, double, double, boolean)
* getBasis(ix, iy, iz, jx, jy, jz, true/false)})
(the last argument is not important).
* This method never throws any exceptions.
*
* @param ix x-component of new i vector
* (maybe, multiplied by some d1 constant).
* @param iy y-component of new i vector
* (maybe, multiplied by some d1 constant).
* @param iz z-component of new i vector
* (maybe, multiplied by some d1 constant).
* @param jx x-component of new j vector
* (maybe, multiplied by some d2 constant).
* @param jy y-component of new j vector
* (maybe, multiplied by some d2 constant).
* @param jz z-component of new j vector
* (maybe, multiplied by some d2 constant).
* @return new right orthonormal basis with given direction of i vector
* and the direction of j vector, chosen according the arguments
* (see {@link #newBasis(double, double, double, double, double, double, boolean)}),
* or empty value in a case of problems.
*/
public static Optional optBasis(
final double ix, final double iy, final double iz,
final double jx, final double jy, final double jz) {
final double lengthI = length(ix, iy, iz);
if (lengthI < MIN_ALLOWED_LENGTH) {
return Optional.empty();
}
final double lengthJ = length(jx, jy, jz);
if (lengthJ < MIN_ALLOWED_LENGTH) {
return Optional.empty();
}
double mult = 1.0 / lengthI;
final double newIx = ix * mult;
final double newIy = iy * mult;
final double newIz = iz * mult;
mult = 1.0 / lengthJ;
double newJx = jx * mult;
double newJy = jy * mult;
double newJz = jz * mult;
final double ij = newIx * newJx + newIy * newJy + newIz * newJz;
if (ij != 0.0) {
newJx -= newIx * ij;
newJy -= newIy * ij;
newJz -= newIz * ij; // correct if (ij)!=0: J = J-I(ij)
final double correctedLengthJ = length(newJx, newJy, newJz);
if (correctedLengthJ < COLLINEARITY_EPSILON) {
return Optional.empty();
}
mult = 1.0 / correctedLengthJ;
newJx *= mult;
newJy *= mult;
newJz *= mult; // correct again
}
return Optional.of(new Orthonormal3DBasis(newIx, newIy, newIz, newJx, newJy, newJz, 0));
}
/**
* Creates a pseudorandom basis, which orientation is uniformly distributed in the space.
* The orientation is chosen with help of random.nextDouble()
method.
*
* @param random random generator used to create the basis.
* @return new right orthonormal basis with random orientation.
*/
public static Orthonormal3DBasis newRandomBasis(RandomGenerator random) {
for (; ; ) {
final double ix = 2 * random.nextDouble() - 1.0;
final double iy = 2 * random.nextDouble() - 1.0;
final double iz = 2 * random.nextDouble() - 1.0;
final double distanceSqr = ix * ix + iy * iy + iz * iz;
if (distanceSqr >= 0.01 && distanceSqr < 1.0) {
// Note: the second check is necessary to provide uniform distribution in a sphere (not in a cube).
return newRandomBasis(random, ix, iy, iz);
}
}
}
/**
* Creates a pseudorandom basis, where i vector has components
* (ix
/d, iy
/d, iz
/d),
* d=sqrt
(ix2+iy2+iz2).
* Directions of vector pair j, k are uniformly distributed in the plane, perpendicular to
* i vector. These directions are chosen with help of random.nextDouble()
method.
*
* @param ix x-component of new i vector (maybe, multiplied by some d constant).
* @param iy y-component of new i vector (maybe, multiplied by some d constant).
* @param iz z-component of new i vector (maybe, multiplied by some d constant).
* @param random random generator used to create the basis.
* @return new randomly oriented right orthonormal basis with given direction of i vector.
* @throws IllegalArgumentException if the length sqrt
(ix2+iy2+iz2)
* of the passed vector (ix
,iy
,iz
) is zero or
* too small (< {@link #MIN_ALLOWED_LENGTH}).
*/
public static Orthonormal3DBasis newRandomBasis(RandomGenerator random, double ix, double iy, double iz) {
return Orthonormal3DBasis.newSomeBasis(ix, iy, iz).rotateJK(2 * Math.PI * random.nextDouble());
}
/**
* Returns x-component of i vector.
*
* @return x-component of i vector.
*/
public double ix() {
return ix;
}
/**
* Returns y-component of i vector.
*
* @return y-component of i vector.
*/
public double iy() {
return iy;
}
/**
* Returns z-component of i vector.
*
* @return z-component of i vector.
*/
public double iz() {
return iz;
}
/**
* Returns x-component of j vector.
*
* @return x-component of j vector.
*/
public double jx() {
return jx;
}
/**
* Returns y-component of j vector.
*
* @return y-component of j vector.
*/
public double jy() {
return jy;
}
/**
* Returns z-component of j vector.
*
* @return z-component of j vector.
*/
public double jz() {
return jz;
}
/**
* Returns x-component of k vector.
*
* @return x-component of k vector.
*/
public double kx() {
return kx;
}
/**
* Returns y-component of j vector.
*
* @return y-component of j vector.
*/
public double ky() {
return ky;
}
/**
* Returns z-component of j vector.
*
* @return z-component of j vector.
*/
public double kz() {
return kz;
}
/**
* Returns i * {@link #ix()} + j * {@link #jx()} + k * {@link #kx()}
*
* @param i projection of some vector p to the basis vector i.
* @param j projection of some vector p to the basis vector j.
* @param k projection of some vector p to the basis vector k.
* @return x-component of such p vector.
*/
public double x(double i, double j, double k) {
return i * ix + j * jx + k * kx;
}
/**
* Returns i * {@link #iy()} + j * {@link #jy()} + k * {@link #ky()}
*
* @param i projection of some vector p to the basis vector i.
* @param j projection of some vector p to the basis vector j.
* @param k projection of some vector p to the basis vector k.
* @return y-component of such p vector.
*/
public double y(double i, double j, double k) {
return i * iy + j * jy + k * ky;
}
/**
* Returns i * {@link #iz()} + j * {@link #jz()} + k * {@link #kz()}
*
* @param i projection of some vector p to the basis vector i.
* @param j projection of some vector p to the basis vector j.
* @param k projection of some vector p to the basis vector k.
* @return z-component of such p vector.
*/
public double z(double i, double j, double k) {
return i * iz + j * jz + k * kz;
}
/**
* Returns new basis (i', j', k'), where
* i'=j, j'=k, k'=i.
*
* @return new basis (j, k, i).
*/
public Orthonormal3DBasis jki() {
return new Orthonormal3DBasis(jx, jy, jz, kx, ky, kz, ix, iy, iz, counter);
}
/**
* Returns new basis (i', j', k'), where
* i'=k, j'=i, k'=j.
*
* @return new basis (k, i, j).
*/
public Orthonormal3DBasis kij() {
return new Orthonormal3DBasis(kx, ky, kz, ix, iy, iz, jx, jy, jz, counter);
}
public Orthonormal3DBasis rotateJK(double angleInRadians) {
if (angleInRadians == 0.0) {
return this;
}
final double cos = Math.cos(angleInRadians);
final double sin = Math.sin(angleInRadians);
final double newJx = cos * jx + sin * kx,
newJy = cos * jy + sin * ky,
newJz = cos * jz + sin * kz;
return new Orthonormal3DBasis(ix, iy, iz, newJx, newJy, newJz, counter + 1).reviveIfNecessary();
}
public Orthonormal3DBasis rotateKI(double angleInRadians) {
if (angleInRadians == 0.0) {
return this;
}
final double cos = Math.cos(angleInRadians);
final double sin = Math.sin(angleInRadians);
final double newIx = -sin * kx + cos * ix,
newIy = -sin * ky + cos * iy,
newIz = -sin * kz + cos * iz;
return new Orthonormal3DBasis(newIx, newIy, newIz, jx, jy, jz, counter + 1).reviveIfNecessary();
}
public Orthonormal3DBasis rotateIJ(double angleInRadians) {
if (angleInRadians == 0.0) {
return this;
}
final double cos = Math.cos(angleInRadians);
final double sin = Math.sin(angleInRadians);
final double newIx = cos * ix + sin * jx,
newIy = cos * iy + sin * jy,
newIz = cos * iz + sin * jz,
newJx = -sin * ix + cos * jx,
newJy = -sin * iy + cos * jy,
newJz = -sin * iz + cos * jz;
return new Orthonormal3DBasis(newIx, newIy, newIz, newJx, newJy, newJz, counter + 1)
.reviveIfNecessary();
}
public Orthonormal3DBasis rotate(double angleXYInRadians, double angleYZInRadians, double angleZXInRadians) {
double ix = this.ix, iy = this.iy, iz = this.iz;
double jx = this.jx, jy = this.jy, jz = this.jz;
if (angleYZInRadians != 0.0) {
final double cos = Math.cos(angleYZInRadians);
final double sin = Math.sin(angleYZInRadians);
final double newIy = iz * sin + iy * cos;
final double newIz = iz * cos - iy * sin;
final double newJy = jz * sin + jy * cos;
final double newJz = jz * cos - jy * sin;
iy = newIy;
iz = newIz;
jy = newJy;
jz = newJz;
}
if (angleZXInRadians != 0.0) {
final double cos = Math.cos(angleZXInRadians);
final double sin = Math.sin(angleZXInRadians);
final double newIz = ix * sin + iz * cos;
final double newIx = ix * cos - iz * sin;
final double newJz = jx * sin + jz * cos;
final double newJx = jx * cos - jz * sin;
iz = newIz;
ix = newIx;
jz = newJz;
jx = newJx;
}
if (angleXYInRadians != 0.0) {
final double cos = Math.cos(angleXYInRadians);
final double sin = Math.sin(angleXYInRadians);
final double newIx = iy * sin + ix * cos;
final double newIy = iy * cos - ix * sin;
final double newJx = jy * sin + jx * cos;
final double newJy = jy * cos - jx * sin;
ix = newIx;
iy = newIy;
jx = newJx;
jy = newJy;
}
return new Orthonormal3DBasis(ix, iy, iz, jx, jy, jz, counter + 1).reviveIfNecessary();
}
public Orthonormal3DBasis rotate(double directionX, double directionY, double directionZ, double angle) {
final double cos = Math.cos(angle);
final double sin = Math.sin(angle);
double mult = 1.0 / Math.sqrt(directionX * directionX + directionY * directionY + directionZ * directionZ);
directionX *= mult;
directionY *= mult;
directionZ *= mult;
final double id = ix * directionX + iy * directionY + iz * directionZ;
final double idx = id * directionX, idy = id * directionY, idz = id * directionZ;
double ax = ix - idx;
double ay = iy - idy;
double az = iz - idz;
double aLength = Math.sqrt(ax * ax + ay * ay + az * az);
mult = 1.0 / aLength;
ax *= mult;
ay *= mult;
az *= mult;
double bx = directionY * az - directionZ * ay;
double by = directionZ * ax - directionX * az;
double bz = directionX * ay - directionY * ax;
// (a,b,g) is a basis, i = id*g + aLength*a
double newAx = cos * ax + sin * bx;
double newAy = cos * ay + sin * by;
double newAz = cos * az + sin * bz;
if ((mult = Math.abs(newAx * newAx + newAy * newAy + newAz * newAz - 1.0)) > 0.01)
throw new AssertionError("Internal error (|newA| = " + mult + " != 1) in rotate method");
final double newIx = idx + aLength * newAx;
final double newIy = idy + aLength * newAy;
final double newIz = idz + aLength * newAz;
final double jd = jx * directionX + jy * directionY + jz * directionZ;
final double jdx = jd * directionX, jdy = jd * directionY, jdz = jd * directionZ;
ax = jx - jdx;
ay = jy - jdy;
az = jz - jdz;
aLength = Math.sqrt(ax * ax + ay * ay + az * az);
mult = 1.0 / aLength;
ax *= mult;
ay *= mult;
az *= mult;
bx = directionY * az - directionZ * ay;
by = directionZ * ax - directionX * az;
bz = directionX * ay - directionY * ax;
// (a,b,g) is a basis, j = jd*g + aLength*a
newAx = cos * ax + sin * bx;
newAy = cos * ay + sin * by;
newAz = cos * az + sin * bz;
if ((mult = Math.abs(newAx * newAx + newAy * newAy + newAz * newAz - 1.0)) > 0.01)
throw new AssertionError("Internal error (|newA| = " + mult + " != 1) in rotate method");
final double newJx = jdx + aLength * newAx;
final double newJy = jdy + aLength * newAy;
final double newJz = jdz + aLength * newAz;
if ((mult = Math.abs(newIx * newIx + newIy * newIy + newIz * newIz - 1.0)) > 0.01)
throw new AssertionError("Internal error (|newI| = " + mult + " != 1) in rotate method");
if ((mult = Math.abs(newJx * newJx + newJy * newJy + newJz * newJz - 1.0)) > 0.01)
throw new AssertionError("Internal error (|newJ| = " + mult + " != 1) in rotate method");
if ((mult = Math.abs(newIx * newJx + newIy * newJy + newIz * newJz)) > 0.01)
throw new AssertionError("Internal error (newI * newJ = " + mult + " != 0) in rotate method");
return new Orthonormal3DBasis(newIx, newIy, newIz, newJx, newJy, newJz, counter + 1)
.reviveIfNecessary();
}
public Orthonormal3DBasis multiply(Orthonormal3DBasis other) {
Objects.requireNonNull(other, "Null other basis");
return new Orthonormal3DBasis(
// new I: ix*other.I+iy*other.J+iz*other.K
ix * other.ix + iy * other.jx + iz * other.kx,
ix * other.iy + iy * other.jy + iz * other.ky,
ix * other.iz + iy * other.jz + iz * other.kz,
// new J: jx*other.I+jy*other.J+jz*other.K
jx * other.ix + jy * other.jx + jz * other.kx,
jx * other.iy + jy * other.jy + jz * other.ky,
jx * other.iz + jy * other.jz + jz * other.kz,
counter + 1).reviveIfNecessary();
}
public Orthonormal3DBasis inverse() {
return new Orthonormal3DBasis(
ix, jx, kx,
iy, jy, ky,
iz, jz, kz,
counter + 1).reviveIfNecessary();
}
public double distanceSquare(Orthonormal3DBasis other) {
Objects.requireNonNull(other, "Null other basis");
return lengthSquare(other.ix - ix, other.iy - iy, other.iz - iz)
+ lengthSquare(other.jx - jx, other.jy - jy, other.jz - jz)
+ lengthSquare(other.kx - kx, other.ky - ky, other.kz - kz);
}
/**
* Returns a brief string description of this object.
*
* The result of this method may depend on implementation.
*
* @return a brief string description of this object.
*/
public String toString() {
return "I=(" + ix + "," + iy + "," + iz
+ "),J=(" + jx + "," + jy + "," + jz
+ "),K=(" + kx + "," + ky + "," + kz + ")";
}
/**
* Indicates whether some other object is an instance of this class, representing the same basis.
* The corresponding coordinates of vectors are compared as in Double.equals
method,
* i.e. they are converted to long
values by Double.doubleToLongBits
method
* and the results are compared.
*
* @param o the object to be compared for equality with this instance.
* @return true
if and only if the specified object is an instance of {@link Orthonormal3DBasis},
* representing the same right orthonormal basis as this object.
*/
public boolean equals(Object o) {
if (!(o instanceof Orthonormal3DBasis that)) {
return false;
}
return (Double.doubleToLongBits(that.ix) == Double.doubleToLongBits(ix)
&& Double.doubleToLongBits(that.iy) == Double.doubleToLongBits(iy)
&& Double.doubleToLongBits(that.iz) == Double.doubleToLongBits(iz)
&& Double.doubleToLongBits(that.jx) == Double.doubleToLongBits(jx)
&& Double.doubleToLongBits(that.jy) == Double.doubleToLongBits(jy)
&& Double.doubleToLongBits(that.jz) == Double.doubleToLongBits(jz));
}
/**
* Returns the hash code of this object.
*
* @return the hash code of this object.
*/
public int hashCode() {
int result = 0;
result = 37 * result + hashCode(ix);
result = 37 * result + hashCode(iy);
result = 37 * result + hashCode(iz);
result = 37 * result + hashCode(jx);
result = 37 * result + hashCode(jy);
result = 37 * result + hashCode(jz);
return result;
}
/**
* Returns the square of the length of 3D segment with the given projections to the axes.
* Equivalent to x * x + y * y + z * z
.
*
* @param x x-projection of the segment.
* @param y y-projection of the segment.
* @param z z-projection of the segment.
* @return the square of the segment length.
*/
public static double lengthSquare(double x, double y, double z) {
return x * x + y * y + z * z;
}
/**
* Returns the length of 3D segment with the given projections to the axes.
* Equivalent to Math.sqrt({@link #lengthSquare(double, double, double) lengthSquare}(x, y, z)).
.
*
* @param x x-projection of the segment.
* @param y y-projection of the segment.
* @param z z-projection of the segment.
* @return the segment length.
*/
public static double length(double x, double y, double z) {
return Math.sqrt(x * x + y * y + z * z);
}
/**
* Analog ot {@link #lengthSquare(double, double, double)} for integer components.
* Equivalent to x * x + y * y + z * z
.
*
*
Note: this method does not check possible overflow! To avoid overflow, we recommend never
* pass here the components of the segment, which precise length ≥231*√2.
* If you can guarantee that overflow is impossible, this method works faster than
* {@link #lengthSquare(double, double, double)}.
*
* @param x x-projection of the segment.
* @param y y-projection of the segment.
* @param z z-projection of the segment.
* @return the square of the segment length.
*/
public static long lengthSquareInteger(long x, long y, long z) {
return x * x + y * y + z * z;
}
/**
* Returns the scalar product of a and b vectors:
* ax * bx + ay * by + az * bz
.
*
* @param ax x-component of the vector a.
* @param ay y-component of the vector a.
* @param az z-component of the vector a.
* @param bx x-component of the vector b.
* @param by y-component of the vector b.
* @param bz z-component of the vector b.
* @return scalar product of two vectors.
*/
public static double scalarProduct(double ax, double ay, double az, double bx, double by, double bz) {
return ax * bx + ay * by + az * bz;
}
private Orthonormal3DBasis reviveIfNecessary() {
if (counter <= REVIVING_COUNT) {
return this;
}
return newBasis(ix, iy, iz, jx, jy, jz, false);
}
private static int hashCode(double value) {
return Double.hashCode(value);
}
}