package com.bergerkiller.bukkit.common.utils;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.craftbukkit.TrigMath;
import org.bukkit.entity.Entity;
import org.bukkit.util.Vector;
import com.bergerkiller.bukkit.common.bases.LongHash;
/**
* Multiple Math utilities to compare and calculate using Vectors and raw values
*/
public class MathUtil {
private static final int CHUNK_BITS = 4;
private static final int CHUNK_VALUES = 16;
public static final float DEGTORAD = 0.017453293F;
public static final float RADTODEG = 57.29577951F;
public static final double HALFROOTOFTWO = 0.707106781;
public static double lengthSquared(double... values) {
double rval = 0;
for (double value : values) {
rval += value * value;
}
return rval;
}
public static double length(double... values) {
return Math.sqrt(lengthSquared(values));
}
public static double distance(double x1, double y1, double x2, double y2) {
return length(x1 - x2, y1 - y2);
}
public static double distanceSquared(double x1, double y1, double x2, double y2) {
return lengthSquared(x1 - x2, y1 - y2);
}
public static double distance(double x1, double y1, double z1, double x2, double y2, double z2) {
return length(x1 - x2, y1 - y2, z1 - z2);
}
public static double distanceSquared(double x1, double y1, double z1, double x2, double y2, double z2) {
return lengthSquared(x1 - x2, y1 - y2, z1 - z2);
}
/**
* Gets a percentage and round it with a cusotm amound of decimals
*
* @param subtotal to get percentags for
* @param total to use as 100% value
* @param decimals to round with
* @return Percentage for subtotal with custom decimals
*/
public static double getPercentage(int subtotal, int total, int decimals) {
return round(getPercentage(subtotal, total), decimals);
}
/**
* Gets a percentags of 2 values
*
* @param subtotal to get percentage for
* @param total to sue as 100% value
* @return percentage
*/
public static double getPercentage(int subtotal, int total) {
return ((float) subtotal / (float) total) * 100;
}
/**
* Gets the angle difference between two angles
*
* @param angle1
* @param angle2
* @return angle difference
*/
public static int getAngleDifference(int angle1, int angle2) {
return Math.abs(wrapAngle(angle1 - angle2));
}
/**
* Gets the angle difference between two angles
*
* @param angle1
* @param angle2
* @return angle difference
*/
public static float getAngleDifference(float angle1, float angle2) {
return Math.abs(wrapAngle(angle1 - angle2));
}
/**
* Wraps the angle to be between -180 and 180 degrees
*
* @param angle to wrap
* @return [-180 > angle >= 180]
*/
public static int wrapAngle(int angle) {
int wrappedAngle = angle;
while (wrappedAngle <= -180) {
wrappedAngle += 360;
}
while (wrappedAngle > 180) {
wrappedAngle -= 360;
}
return wrappedAngle;
}
/**
* Wraps the angle to be between -180 and 180 degrees
*
* @param angle to wrap
* @return [-180 > angle >= 180]
*/
public static float wrapAngle(float angle) {
float wrappedAngle = angle;
while (wrappedAngle <= -180f) {
wrappedAngle += 360f;
}
while (wrappedAngle > 180f) {
wrappedAngle -= 360f;
}
return wrappedAngle;
}
/**
* Normalizes a 2D-vector to be the length of another 2D-vector<br>
* Calculates the normalization factor to multiply the input vector with, to get the requested length
*
* @param x axis of the vector
* @param z axis of the vector
* @param reqx axis of the length vector
* @param reqz axis of the length vector
* @return the normalization factor
*/
public static double normalize(double x, double z, double reqx, double reqz) {
return Math.sqrt(lengthSquared(reqx, reqz) / lengthSquared(x, z));
}
public static float getLookAtYaw(Entity loc, Entity lookat) {
return getLookAtYaw(loc.getLocation(), lookat.getLocation());
}
public static float getLookAtYaw(Block loc, Block lookat) {
return getLookAtYaw(loc.getLocation(), lookat.getLocation());
}
public static float getLookAtYaw(Location loc, Location lookat) {
return getLookAtYaw(lookat.getX() - loc.getX(), lookat.getZ() - loc.getZ());
}
public static float getLookAtYaw(Vector motion) {
return getLookAtYaw(motion.getX(), motion.getZ());
}
/**
* Gets the horizontal look-at angle in degrees to look into the 2D-direction specified
*
* @param dx axis of the direction
* @param dz axis of the direction
* @return the angle in degrees
*/
public static float getLookAtYaw(double dx, double dz) {
return atan2(dz, dx) - 180f;
}
/**
* Gets the pitch angle in degrees to look into the direction specified
*
* @param dX axis of the direction
* @param dY axis of the direction
* @param dZ axis of the direction
* @return look-at angle in degrees
*/
public static float getLookAtPitch(double dX, double dY, double dZ) {
return getLookAtPitch(dY, length(dX, dZ));
}
/**
* Gets the pitch angle in degrees to look into the direction specified
*
* @param dY axis of the direction
* @param dXZ axis of the direction (length of x and z)
* @return look-at angle in degrees
*/
public static float getLookAtPitch(double dY, double dXZ) {
return -atan(dY / dXZ);
}
/**
* Gets the inverse tangent of the value in degrees
*
* @param value
* @return inverse tangent angle in degrees
*/
public static float atan(double value) {
return RADTODEG * (float) TrigMath.atan(value);
}
/**
* Gets the inverse tangent angle in degrees of the rectangle vector
*
* @param y axis
* @param x axis
* @return inverse tangent 2 angle in degrees
*/
public static float atan2(double y, double x) {
return RADTODEG * (float) TrigMath.atan2(y, x);
}
/**
* Gets the floor integer value from a double value
*
* @param value to get the floor of
* @return floor value
*/
public static int floor(double value) {
int i = (int) value;
return value < (double) i ? i - 1 : i;
}
/**
* Gets the floor integer value from a float value
*
* @param value to get the floor of
* @return floor value
*/
public static int floor(float value) {
int i = (int) value;
return value < (float) i ? i - 1 : i;
}
/**
* Gets the ceiling integer value from a double value
*
* @param value to get the ceiling of
* @return ceiling value
*/
public static int ceil(double value) {
return -floor(-value);
}
/**
* Gets the ceiling integer value from a float value
*
* @param value to get the ceiling of
* @return ceiling value
*/
public static int ceil(float value) {
return -floor(-value);
}
/**
* Moves a Location into the yaw and pitch of the Location in the offset specified
*
* @param loc to move
* @param offset vector
* @return Translated Location
*/
public static Location move(Location loc, Vector offset) {
return move(loc, offset.getX(), offset.getY(), offset.getZ());
}
/**
* Moves a Location into the yaw and pitch of the Location in the offset specified
*
* @param loc to move
* @param dx offset
* @param dy offset
* @param dz offset
* @return Translated Location
*/
public static Location move(Location loc, double dx, double dy, double dz) {
Vector off = rotate(loc.getYaw(), loc.getPitch(), dx, dy, dz);
double x = loc.getX() + off.getX();
double y = loc.getY() + off.getY();
double z = loc.getZ() + off.getZ();
return new Location(loc.getWorld(), x, y, z, loc.getYaw(), loc.getPitch());
}
/**
* Rotates a 3D-vector using yaw and pitch
*
* @param yaw angle in degrees
* @param pitch angle in degrees
* @param vector to rotate
* @return Vector rotated by the angle (new instance)
*/
public static Vector rotate(float yaw, float pitch, Vector vector) {
return rotate(yaw, pitch, vector.getX(), vector.getY(), vector.getZ());
}
/**
* Rotates a 3D-vector using yaw and pitch
*
* @param yaw angle in degrees
* @param pitch angle in degrees
* @param x axis of the vector
* @param y axis of the vector
* @param z axis of the vector
* @return Vector rotated by the angle
*/
public static Vector rotate(float yaw, float pitch, double x, double y, double z) {
// Conversions found by (a lot of) testing
float angle;
angle = yaw * DEGTORAD;
double sinyaw = Math.sin(angle);
double cosyaw = Math.cos(angle);
angle = pitch * DEGTORAD;
double sinpitch = Math.sin(angle);
double cospitch = Math.cos(angle);
Vector vector = new Vector();
vector.setX((x * sinyaw) - (y * cosyaw * sinpitch) - (z * cosyaw * cospitch));
vector.setY((y * cospitch) - (z * sinpitch));
vector.setZ(-(x * cosyaw) - (y * sinyaw * sinpitch) - (z * sinyaw * cospitch));
return vector;
}
/**
* Rounds the specified value to the amount of decimals specified
*
* @param value to round
* @param decimals count
* @return value round to the decimal count specified
*/
public static double round(double value, int decimals) {
double p = Math.pow(10, decimals);
return Math.round(value * p) / p;
}
/**
* Returns 0 if the value is not-a-number
*
* @param value to check
* @return The value, or 0 if it is NaN
*/
public static double fixNaN(double value) {
return fixNaN(value, 0.0);
}
/**
* Returns the default if the value is not-a-number
*
* @param value to check
* @param def value
* @return The value, or the default if it is NaN
*/
public static double fixNaN(double value, double def) {
return Double.isNaN(value) ? def : value;
}
/**
* Converts a location value into a chunk coordinate
*
* @param loc to convert
* @return chunk coordinate
*/
public static int toChunk(double loc) {
return floor(loc / (double) CHUNK_VALUES);
}
/**
* Converts a location value into a chunk coordinate
*
* @param loc to convert
* @return chunk coordinate
*/
public static int toChunk(int loc) {
return loc >> CHUNK_BITS;
}
public static double useOld(double oldvalue, double newvalue, double peruseold) {
return oldvalue + (peruseold * (newvalue - oldvalue));
}
public static double lerp(double d1, double d2, double stage) {
if (Double.isNaN(stage) || stage > 1) {
return d2;
} else if (stage < 0) {
return d1;
} else {
return d1 * (1 - stage) + d2 * stage;
}
}
public static Vector lerp(Vector vec1, Vector vec2, double stage) {
Vector newvec = new Vector();
newvec.setX(lerp(vec1.getX(), vec2.getX(), stage));
newvec.setY(lerp(vec1.getY(), vec2.getY(), stage));
newvec.setZ(lerp(vec1.getZ(), vec2.getZ(), stage));
return newvec;
}
public static Location lerp(Location loc1, Location loc2, double stage) {
Location newloc = new Location(loc1.getWorld(), 0, 0, 0);
newloc.setX(lerp(loc1.getX(), loc2.getX(), stage));
newloc.setY(lerp(loc1.getY(), loc2.getY(), stage));
newloc.setZ(lerp(loc1.getZ(), loc2.getZ(), stage));
newloc.setYaw((float) lerp(loc1.getYaw(), loc2.getYaw(), stage));
newloc.setPitch((float) lerp(loc1.getPitch(), loc2.getPitch(), stage));
return newloc;
}
/**
* Checks whether one value is negative and the other positive, or opposite
*
* @param value1 to check
* @param value2 to check
* @return True if value1 is inverted from value2
*/
public static boolean isInverted(double value1, double value2) {
return (value1 > 0 && value2 < 0) || (value1 < 0 && value2 > 0);
}
/**
* Gets the direction of yaw and pitch angles
*
* @param yaw angle in degrees
* @param pitch angle in degrees
* @return Direction Vector
*/
public static Vector getDirection(float yaw, float pitch) {
Vector vector = new Vector();
double rotX = DEGTORAD * yaw;
double rotY = DEGTORAD * pitch;
vector.setY(-Math.sin(rotY));
double h = Math.cos(rotY);
vector.setX(-h * Math.sin(rotX));
vector.setZ(h * Math.cos(rotX));
return vector;
}
/**
* Clamps the value between -limit and limit
*
* @param value to clamp
* @param limit
* @return value, -limit or limit
*/
public static double clamp(double value, double limit) {
return clamp(value, -limit, limit);
}
/**
* Clamps the value between the min and max values
* @param value to clamp
* @param min
* @param max
* @return value, min or max
*/
public static double clamp(double value, double min, double max) {
return value < min ? min : (value > max ? max : value);
}
/**
* Clamps the value between -limit and limit
*
* @param value to clamp
* @param limit
* @return value, -limit or limit
*/
public static float clamp(float value, float limit) {
return clamp(value, -limit, limit);
}
/**
* Clamps the value between the min and max values
* @param value to clamp
* @param min
* @param max
* @return value, min or max
*/
public static float clamp(float value, float min, float max) {
return value < min ? min : (value > max ? max : value);
}
/**
* Clamps the value between -limit and limit
*
* @param value to clamp
* @param limit
* @return value, -limit or limit
*/
public static int clamp(int value, int limit) {
return clamp(value, -limit, limit);
}
/**
* Clamps the value between the min and max values
* @param value to clamp
* @param min
* @param max
* @return value, min or max
*/
public static int clamp(int value, int min, int max) {
return value < min ? min : (value > max ? max : value);
}
/**
* Turns a value negative or keeps it positive based on a boolean input
*
* @param value to work with
* @param negative - True to invert, False to keep the old value
* @return the value or inverted (-value)
*/
public static int invert(int value, boolean negative) {
return negative ? -value : value;
}
/**
* Turns a value negative or keeps it positive based on a boolean input
*
* @param value to work with
* @param negative - True to invert, False to keep the old value
* @return the value or inverted (-value)
*/
public static float invert(float value, boolean negative) {
return negative ? -value : value;
}
/**
* Turns a value negative or keeps it positive based on a boolean input
*
* @param value to work with
* @param negative - True to invert, False to keep the old value
* @return the value or inverted (-value)
*/
public static double invert(double value, boolean negative) {
return negative ? -value : value;
}
/**
* Merges two ints into a long
*
* @param msw integer
* @param lsw integer
* @return merged long value
*/
public static long toLong(int msw, int lsw) {
return longHashToLong(msw, lsw);
}
public static long longHashToLong(int msw, int lsw) {
return LongHash.toLong(msw, lsw);
}
public static int longHashMsw(long key) {
return LongHash.msw(key);
}
public static int longHashLsw(long key) {
return LongHash.lsw(key);
}
public static void setVectorLength(Vector vector, double length) {
setVectorLengthSquared(vector, Math.signum(length) * length * length);
}
public static void setVectorLengthSquared(Vector vector, double lengthsquared) {
double vlength = vector.lengthSquared();
if (Math.abs(vlength) > 0.0001) {
if (lengthsquared < 0) {
vector.multiply(-Math.sqrt(-lengthsquared / vlength));
} else {
vector.multiply(Math.sqrt(lengthsquared / vlength));
}
}
}
public static boolean isHeadingTo(BlockFace direction, Vector velocity) {
return isHeadingTo(FaceUtil.faceToVector(direction), velocity);
}
public static boolean isHeadingTo(Location from, Location to, Vector velocity) {
return isHeadingTo(new Vector(to.getX() - from.getX(), to.getY() - from.getY(), to.getZ() - from.getZ()), velocity);
}
public static boolean isHeadingTo(Vector offset, Vector velocity) {
double dbefore = offset.lengthSquared();
if (dbefore < 0.0001) {
return true;
}
Vector clonedVelocity = velocity.clone();
setVectorLengthSquared(clonedVelocity, dbefore);
return dbefore > clonedVelocity.subtract(offset).lengthSquared();
}
}