package edu.gatech.cs2340.trydent.math;
import java.lang.reflect.Array;
import javafx.scene.transform.Transform;
import edu.gatech.cs2340.trydent.math.geom.Rectangle;
/**
* Library of advanced math tools, primarily linear algebra.
* <p>
* To be used by advanced developers only.
*
* @author Garrett Malmquist
*/
public class MathTools {
// We don't want people instantiating this class.
private MathTools() {
}
/**
* Takes an angle in degrees and wraps it to the range 0 - 360.
*
* @param theta
* angle in degrees to wrap if necessary
* @return the wrapped angle in degrees (between 0 and 360)
*/
public static double wrapAngle(double theta) {
if (theta < 0)
return 360.0 + (theta % 360.0);
return theta % 360.0;
}
/**
* Performs a linear interpolation from the first angle to the second at
* time t, going either clockwise or counter-clockwise depending on which
* distance is shorter.
*
* @param thetaA
* starting angle (at t=0)
* @param thetaB
* ending angle (at t=1)
* @param t
* interpolation parameter, where t=0 returns thetaA, and t=1
* returns thetaB.
* @return the interpolated angle, wrapped to be between 0 and 360.
*/
public static double degreeLerp(double thetaA, double thetaB, double t) {
thetaA = wrapAngle(thetaA);
thetaB = wrapAngle(thetaB);
double small = thetaA;
double large = thetaB;
if (thetaA > thetaB) {
small = thetaB;
large = thetaA;
t = 1.0 - t;
}
if (large - small > 180) {
// Quicker to go over the 0 = 360 degree barrier.
large -= 360;
}
return wrapAngle((1.0 - t) * small + t * large);
}
/**
* Performs an (n-1) dimensional Bezier interpolation over n points.
* (Advanced functionality).
*
* @param t
* interpolation parameter - t=0.0 will return the 1st point,
* t=1.0 will return the last point.
* @param points
* One or more points to interpolate between.
* @param <T>
* type of point
* @return the interpolated point
*/
@SuppressWarnings("unchecked")
public static <T extends BaseVector<?>> T bezier(double t, T... points) {
if (points.length == 0)
throw new IllegalArgumentException("Must input at least one point!");
if (points.length == 1)
return (T) points[0].copy();
// Strictly speaking this base case could be omitted without loss of
// functionality,
// but it makes things a bit more efficient.
if (points.length == 2)
return (T) points[0].copy().lerp(t, points[1]);
T[] left = (T[]) Array.newInstance(points[0].getClass(), points.length - 1);
T[] right = (T[]) Array.newInstance(points[0].getClass(), points.length - 1);
for (int i = 0; i < points.length - 1; i++) {
left[i] = points[i];
right[i] = points[i + 1];
}
return (T) bezier(t, left).lerp(t, bezier(t, right));
}
/**
* Modified lerp that passes through point 'start' at time t=a and point
* 'end' at time t=b. (Advanced functionality).
*
* @param <T>
* the type of the point to interpolate (inferred from other
* arguments)
* @param a
* starting time
* @param start
* starting point
* @param b
* ending time
* @param end
* ending point
* @param t
* interpolation parameter time
* @return interpolated point at time t
*/
@SuppressWarnings("unchecked")
public static <T extends BaseVector<?>> T lerp(double a, T start, double b, T end, double t) {
return (T) start.copy().lerp((t - a) / (b - a), end);
}
/**
* Linear interpolation from start to end at time t. (Advanced
* functionality).
*
* @param <T>
* the type of the point to interpolate (inferred from the other
* arguments)
* @param start
* start point at t=0
* @param end
* end point at t=1
* @param t
* interpolation parameter
* @return interpolated point
*/
@SuppressWarnings("unchecked")
public static <T extends BaseVector<?>> T lerp(T start, T end, double t) {
return (T) start.copy().lerp(t, end);
}
/**
* Scalar hermite interpolation. (Advanced functionality).
* <p>
* {@code P(t) = (2t^3-3t^2+1)P0+(t^3-2t^2+t)T0+(-2t^3+3t^2)P1+(t^3-t^2)T1 }
*
* @param p0
* first point
* @param t0
* first tangent
* @param p1
* second point
* @param t1
* second tangent
* @param t
* interpolation parameter (P(t=0) = p0, P(t=1) = p1)
* @return interpolated floating point value
*/
public static double hermite(double p0, double t0, double p1, double t1, double t) {
double t2 = t * t;
double t3 = t * t * t;
return (2 * t3 - 3 * t2 + 1) * p0 + (t3 - 2 * t2 + t) * t0 + (-2 * t3 + 3 * t2) * p1 + (t3 - t2) * t1;
}
/**
* Vector hermite interpolation. (Advanced functionality).
* <p>
* {@code P(t) = (2t^3-3t^2+1)P0+(t^3-2t^2+t)T0+(-2t^3+3t^2)P1+(t^3-t^2)T1 }
*
* @param <T>
* the type of the point to interpolate (inferred from the other
* arguments)
* @param p0
* first point
* @param t0
* first tangent
* @param p1
* second point
* @param t1
* second tangent
* @param t
* interpolation parameter (P(t=0) = p0, P(t=1) = p1)
* @return interpolated point
*/
@SuppressWarnings("unchecked")
public static <T extends BaseVector<?>> T hermite(T p0, BaseVector<?> t0, T p1, BaseVector<?> t1, double t) {
T point = (T) p0.copy();
for (int i = 0; i < point.getComponentCount(); i++) {
point.setComponent(i,
hermite(p0.getComponent(i), t0.getComponent(i), p1.getComponent(i), t1.getComponent(i), t));
}
return point;
}
/**
* Returns the 2D rotation of the matrix in degrees.
*
* @param matrix
* rotation matrix from which to extract the rotation about the
* z-axis
* @return the rotation about the Z axis in degrees
*/
public static double getRotation(Transform matrix) {
// [ xx xy xz tx ] = [ Sx*c -Sy*s ... tx ]
// [ yx yy yz ty ] [ Sx*s Sy*c ... ty ]
// [ zx zy zz tz ] [ ... ... ... tz ]
// [ 0 0 0 1 ] [ 0 0 0 1 ]
// yx = scaleX * sin(theta)
// xx = scaleX * cos(theta)
return Math.toDegrees(Math.atan2(matrix.getMyx(), matrix.getMxx()));
}
/**
* Returns the X scale of the matrix.
*
* @param matrix
* matrix to extract the x scale from
* @return the x scaling of the transformation matrix
*/
public static double getScaleX(Transform matrix) {
// [ xx xy xz tx ] = [ Sx*c -Sy*s ... tx ]
// [ yx yy yz ty ] [ Sx*s Sy*c ... ty ]
// [ zx zy zz tz ] [ ... ... ... tz ]
// [ 0 0 0 1 ] [ 0 0 0 1 ]
double theta = Math.toRadians(getRotation(matrix));
return matrix.getMxx() / Math.cos(theta);
}
/**
* Returns the Y scale of the matrix.
*
* @param matrix
* matrix to extract the y scale from
* @return the y-scale of the matrix
*/
public static double getScaleY(Transform matrix) {
// [ xx xy xz tx ] = [ Sx*c -Sy*s ... tx ]
// [ yx yy yz ty ] [ Sx*s Sy*c ... ty ]
// [ zx zy zz tz ] [ ... ... ... tz ]
// [ 0 0 0 1 ] [ 0 0 0 1 ]
double theta = Math.toRadians(getRotation(matrix));
return matrix.getMyy() / Math.cos(theta);
}
/**
* Returns the x, y scale of the transform.
*
* @param matrix
* the matrix from which to extract the x,y scaling
* @return the (x,y) scale of the matrix as a Scale object
*/
public static Scale getScale(Transform matrix) {
return new Scale(getScaleX(matrix), getScaleY(matrix));
}
/**
* Returns the translation of the matrix.
*
* @param matrix
* the matrix to extract the translation from
* @return a position representation the translation of the matrix
*/
public static Position getTranslation(Transform matrix) {
return new Position(matrix.getTx(), matrix.getTy());
}
/**
* Returns true if the rectangles (x0, y0, w0, h0) and (x1, y1, w1, h1) intersect or contain each other.
* @param x0 Left coordinate of rectangle 0.
* @param y0 Top coordinate of rectangle 0.
* @param w0 Width of rectangle 0.
* @param h0 Height of rectangle 0.
* @param x1 Left coordinate of rectangle 1.
* @param y1 Top coordinate of rectangle 1.
* @param w1 Width of rectangle 1.
* @param h1 Height of rectangle 1.
* @return Whether the rectangles intersect.
*/
public static boolean doRectanglesIntersect(double x0, double y0, double w0, double h0, double x1, double y1,
double w1, double h1) {
// Test left and top edge.
if (x0 > x1 + w1) return false;
if (y0 > y1 + h1) return false;
// Test right and bottom edge.
if (x1 > x0 + w0) return false;
if (y1 > y0 + h0) return false;
return true;
}
/**
* Returns true if the rectangles r0 and r1 intersect or contain each other.
* @param r0 the first rectangle.
* @param r1 the second rectangle.
* @return Whether the rectangles intersect.
*/
public static boolean doRectanglesIntersect(Rectangle r0, Rectangle r1) {
return doRectanglesIntersect(
r0.getLeft(), r0.getTop(), r0.getWidth(), r0.getHeight(),
r1.getLeft(), r1.getTop(), r1.getWidth(), r1.getHeight()
);
}
}