/*
* Copyright (c) 2015 NOVA, All rights reserved.
* This library is free software, licensed under GNU Lesser General Public License version 3
*
* This file is part of NOVA.
*
* NOVA is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NOVA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NOVA. If not, see <http://www.gnu.org/licenses/>.
*/package nova.core.util.math;
import nova.core.util.Direction;
import org.apache.commons.math3.geometry.euclidean.threed.Rotation;
import org.apache.commons.math3.geometry.euclidean.threed.RotationOrder;
import org.apache.commons.math3.util.FastMath;
/**
* A rotation utility class.
* @author Calclavia
*/
public class RotationUtil {
private RotationUtil() {
}
/**
* Rotation order that is used by defualt in Nova.
*/
public static final RotationOrder DEFAULT_ORDER = RotationOrder.YXZ;
private static int[][] relativeMatrix = new int[][] {
{ 3, 2, 1, 0, 5, 4 },
{ 4, 5, 0, 1, 2, 3 },
{ 0, 1, 3, 2, 5, 4 },
{ 0, 1, 2, 3, 4, 5 },
{ 0, 1, 4, 5, 3, 2 },
{ 0, 1, 5, 4, 2, 3 }
};
private static int[] sideRotMap = new int[] {
3, 4, 2, 5,
3, 5, 2, 4,
1, 5, 0, 4,
1, 4, 0, 5,
1, 2, 0, 3,
1, 3, 0, 2 };
private static int[] rotSideMap = new int[] {
-1, -1, 2, 0, 1, 3,
-1, -1, 2, 0, 3, 1,
2, 0, -1, -1, 3, 1,
2, 0, -1, -1, 1, 3,
2, 0, 1, 3, -1, -1,
2, 0, 3, 1, -1, -1 };
/**
* Rotate pi/2 * this offset for [side] about y axis before rotating to the side for the rotation indicies to line up
*/
public static int[] sideRotOffsets = new int[] { 0, 2, 2, 0, 1, 3 };
/**
* Rotates a relative side into a Direction global size.
* @param s - The current face we are on (0-6)
* @param r - The rotation to be applied (0-3)
* @return The Direction ordinal from 0-5.
*/
public static int rotateSide(int s, int r) {
return sideRotMap[s << 2 | r];
}
/**
* Reverse of rotateSide
*/
public static int rotationTo(int s1, int s2) {
if ((s1 & 6) == (s2 & 6)) {
throw new IllegalArgumentException("Faces " + s1 + " and " + s2 + " are opposites");
}
return rotSideMap[s1 * 6 + s2];
}
/**
* Finds the direction relative to a base direction.
* @param front The direction in which this block is facing/front. Use a number between 0 and
* 5. Default is 3.
* @param side The side you are trying to find. A number between 0 and 5.
* @return The side relative to the facing direction.
*/
public static Direction getRelativeSide(Direction front, Direction side) {
if (front != Direction.UNKNOWN && side != Direction.UNKNOWN) {
return Direction.fromOrdinal(relativeMatrix[front.ordinal()][side.ordinal()]);
}
return Direction.UNKNOWN;
}
/**
* Wrapper function that simply calls {@code slerp(a, b, t, true)}.
* <p>
* See {@link #slerp(Rotation, Rotation, double, boolean)} for details.
*/
public static Rotation slerp(Rotation a, Rotation b, double t) {
return slerp(a, b, t, true);
}
/**
* Returns the slerp interpolation of Rotations {@code a} and {@code b}, at
* time {@code t}.
* <p>
* {@code t} should range in {@code [0,1]}. Result is a when {@code t=0 } and
* {@code b} when {@code t=1}.
* <p>
* When {@code allowFlip} is true (default) the slerp interpolation will
* always use the "shortest path" between the Rotations' orientations, by
* "flipping" the source Rotation if needed.
* @param a the first Rotation
* @param b the second Rotation
* @param t the t interpolation parameter
* @param allowFlip tells whether or not the interpolation allows axis flip
*/
public static Rotation slerp(Rotation a, Rotation b, double t, boolean allowFlip) {
// Warning: this method should not normalize the Rotation
double cosAngle = dotProduct(a, b);
double c1, c2;
// Linear interpolation for close orientations
if ((1.0 - FastMath.abs(cosAngle)) < 0.01) {
c1 = 1.0f - t;
c2 = t;
} else {
// Spherical interpolation
double angle = FastMath.acos(FastMath.abs(cosAngle));
double sinAngle = FastMath.sin(angle);
c1 = FastMath.sin(angle * (1.0f - t)) / sinAngle;
c2 = FastMath.sin(angle * t) / sinAngle;
}
// Use the shortest path
if (allowFlip && (cosAngle < 0.0)) {
c1 = -c1;
}
return new Rotation(c1 * a.getQ1() + c2 * b.getQ1(), c1 * a.getQ2() + c2 * b.getQ2(), c1 * a.getQ3()
+ c2 * b.getQ3(), c1 * a.getQ0() + c2 * b.getQ0(), false);
}
/**
* Returns the "dot" product of this Quaternion and {@code b}:
* <p>
* {@code this.x * b.x + this.y * b.y + this.z * b.z + this.w * b.w}
* @param b the Quaternion
*/
public static double dotProduct(Rotation a, Rotation b) {
return a.getQ0() * b.getQ0() + a.getQ1() * b.getQ1() + a.getQ2() * b.getQ2() + a.getQ3() * b.getQ3();
}
}