package be.isach.ultracosmetics.util; import be.isach.ultracosmetics.UltraCosmeticsData; import be.isach.ultracosmetics.player.UltraPlayer; import be.isach.ultracosmetics.run.FallDamageManager; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.util.Vector; import java.util.Random; /** * Utility and fast math functions. * <p> * Thanks to Riven on JavaGaming.org for the basis of sin/cos/atan2/floor/ceil. * * @author Nathan Sweet and (a bit lol) iSach. */ public class MathUtils { static public final float nanoToSec = 1 / 1000000000f; static public final float FLOAT_ROUNDING_ERROR = 0.000001f; // 32 bits static public final float PI = 3.141592653589793238462643383279f; static public final float PI2 = PI * 2; static public final float SQRT_3 = 1.73205080757f; static public final float E = 2.7182818284590452354f; static private final int SIN_BITS = 14; // 16KB. Adjust for accuracy. static private final int SIN_MASK = ~(-1 << SIN_BITS); static private final int SIN_COUNT = SIN_MASK + 1; static private final float radFull = PI * 2; static private final float degFull = 360; static private final float radToIndex = SIN_COUNT / radFull; static private final float degToIndex = SIN_COUNT / degFull; /** * multiply by this to convert from radians to degrees */ static public final float radiansToDegrees = 180f / PI; static public final float radDeg = radiansToDegrees; /** * multiply by this to convert from degrees to radians */ static public final float degreesToRadians = PI / 180; static public final float degRad = degreesToRadians; static private class Sin { static final float[] table = new float[SIN_COUNT]; static { for (int i = 0; i < SIN_COUNT; i++) { table[i] = (float) Math.sin((i + 0.5f) / SIN_COUNT * radFull); } for (int i = 0; i < 360; i += 90) { table[(int) (i * degToIndex) & SIN_MASK] = (float) Math.sin(i * degreesToRadians); } } } /** * Returns the sine in radians from a lookup table. */ static public final float sin(float radians) { return Sin.table[(int) (radians * radToIndex) & SIN_MASK]; } /** * Returns the cosine in radians from a lookup table. */ static public final float cos(float radians) { return Sin.table[(int) ((radians + PI / 2) * radToIndex) & SIN_MASK]; } /** * Returns the sine in radians from a lookup table. */ static public final float sinDeg(float degrees) { return Sin.table[(int) (degrees * degToIndex) & SIN_MASK]; } /** * Returns the cosine in radians from a lookup table. */ static public final float cosDeg(float degrees) { return Sin.table[(int) ((degrees + 90) * degToIndex) & SIN_MASK]; } static private final int ATAN2_BITS = 7; // Adjust for accuracy. static private final int ATAN2_BITS2 = ATAN2_BITS << 1; static private final int ATAN2_MASK = ~(-1 << ATAN2_BITS2); static private final int ATAN2_COUNT = ATAN2_MASK + 1; static final int ATAN2_DIM = (int) Math.sqrt(ATAN2_COUNT); static private final float INV_ATAN2_DIM_MINUS_1 = 1.0f / (ATAN2_DIM - 1); static private class Atan2 { static final float[] table = new float[ATAN2_COUNT]; static { for (int i = 0; i < ATAN2_DIM; i++) { for (int j = 0; j < ATAN2_DIM; j++) { float x0 = (float) i / ATAN2_DIM; float y0 = (float) j / ATAN2_DIM; table[j * ATAN2_DIM + i] = (float) Math.atan2(y0, x0); } } } } public static boolean isInteger(Object object) { try { Integer.parseInt(object.toString()); return true; } catch (Exception exc) { return false; } } public static boolean isDouble(Object object) { try { Double.parseDouble(object.toString()); return true; } catch (Exception exc) { return false; } } /** * Returns atan2 in radians from a lookup table. */ static public final float atan2(float y, float x) { float add, mul; if (x < 0) { if (y < 0) { y = -y; mul = 1; } else { mul = -1; } x = -x; add = -PI; } else { if (y < 0) { y = -y; mul = -1; } else { mul = 1; } add = 0; } float invDiv = 1 / ((x < y ? y : x) * INV_ATAN2_DIM_MINUS_1); if (invDiv == Float.POSITIVE_INFINITY) { return ((float) Math.atan2(y, x) + add) * mul; } int xi = (int) (x * invDiv); int yi = (int) (y * invDiv); return (Atan2.table[yi * ATAN2_DIM + xi] + add) * mul; } static public Random random = new Random(); /** * Returns a random number between 0 (inclusive) and the specified value (inclusive). */ static public final int random(int range) { return random.nextInt(range + 1); } /** * Returns a random number between start (inclusive) and end (inclusive). */ static public final int random(int start, int end) { return start + random.nextInt(end - start + 1); } /** * Returns a random boolean value. */ static public final boolean randomBoolean() { return random.nextBoolean(); } /** * Returns true if a random value between 0 and 1 is less than the specified value. */ static public final boolean randomBoolean(float chance) { return MathUtils.random() < chance; } /** * Returns random number between 0.0 (inclusive) and 1.0 (exclusive). */ static public final float random() { return random.nextFloat(); } /** * Returns a random number between 0 (inclusive) and the specified value (exclusive). */ static public final float random(float range) { return random.nextFloat() * range; } /** * Returns a random number between start (inclusive) and end (exclusive). */ static public final float random(float start, float end) { return start + random.nextFloat() * (end - start); } // --- /** * Returns the next power of two. Returns the specified value if the value is already a power of two. */ static public int nextPowerOfTwo(int value) { if (value == 0) { return 1; } value--; value |= value >> 1; value |= value >> 2; value |= value >> 4; value |= value >> 8; value |= value >> 16; return value + 1; } static public boolean isPowerOfTwo(int value) { return value != 0 && (value & value - 1) == 0; } // --- static public int clamp(int value, int min, int max) { if (value < min) { return min; } if (value > max) { return max; } return value; } static public short clamp(short value, short min, short max) { if (value < min) { return min; } if (value > max) { return max; } return value; } static public float clamp(float value, float min, float max) { if (value < min) { return min; } if (value > max) { return max; } return value; } // --- static private final int BIG_ENOUGH_INT = 16 * 1024; static private final double BIG_ENOUGH_FLOOR = BIG_ENOUGH_INT; static private final double CEIL = 0.9999999; // static private final double BIG_ENOUGH_CEIL = NumberUtils // .longBitsToDouble(NumberUtils.doubleToLongBits(BIG_ENOUGH_INT + 1) - 1); static private final double BIG_ENOUGH_CEIL = 16384.999999999996; static private final double BIG_ENOUGH_ROUND = BIG_ENOUGH_INT + 0.5f; /** * Returns the largest integer less than or equal to the specified float. This method will only properly floor floats from * -(2^14) to (Float.MAX_VALUE - 2^14). */ static public int floor(float x) { return (int) (x + BIG_ENOUGH_FLOOR) - BIG_ENOUGH_INT; } /** * Returns the largest integer less than or equal to the specified float. This method will only properly floor floats that are * positive. Note this method simply casts the float to int. */ static public int floorPositive(float x) { return (int) x; } /** * Returns the smallest integer greater than or equal to the specified float. This method will only properly ceil floats from * -(2^14) to (Float.MAX_VALUE - 2^14). */ static public int ceil(float x) { return (int) (x + BIG_ENOUGH_CEIL) - BIG_ENOUGH_INT; } /** * Returns the smallest integer greater than or equal to the specified float. This method will only properly ceil floats that * are positive. */ static public int ceilPositive(float x) { return (int) (x + CEIL); } /** * Returns the closest integer to the specified float. This method will only properly round floats from -(2^14) to * (Float.MAX_VALUE - 2^14). */ static public int round(float x) { return (int) (x + BIG_ENOUGH_ROUND) - BIG_ENOUGH_INT; } /** * Returns the closest integer to the specified float. This method will only properly round floats that are positive. */ static public int roundPositive(float x) { return (int) (x + 0.5f); } /** * Returns true if the value is zero (using the default tolerance as upper bound) */ static public boolean isZero(float value) { return Math.abs(value) <= FLOAT_ROUNDING_ERROR; } /** * Returns true if the value is zero. * * @param tolerance represent an upper bound below which the value is considered zero. */ static public boolean isZero(float value, float tolerance) { return Math.abs(value) <= tolerance; } /** * Returns true if a is nearly equal to b. The function uses the default floating error tolerance. * * @param a the first value. * @param b the second value. */ static public boolean isEqual(float a, float b) { return Math.abs(a - b) <= FLOAT_ROUNDING_ERROR; } /** * Returns true if a is nearly equal to b. * * @param a the first value. * @param b the second value. * @param tolerance represent an upper bound below which the two values are considered equal. */ static public boolean isEqual(float a, float b, float tolerance) { return Math.abs(a - b) <= tolerance; } public static final Vector rotateAroundAxisX(Vector v, double angle) { double y, z, cos, sin; cos = Math.cos(angle); sin = Math.sin(angle); y = v.getY() * cos - v.getZ() * sin; z = v.getY() * sin + v.getZ() * cos; return v.setY(y).setZ(z); } public static final Vector rotateAroundAxisY(Vector v, double angle) { double x, z, cos, sin; cos = Math.cos(angle); sin = Math.sin(angle); x = v.getX() * cos + v.getZ() * sin; z = v.getX() * -sin + v.getZ() * cos; return v.setX(x).setZ(z); } public static final Vector rotateAroundAxisZ(Vector v, double angle) { double x, y, cos, sin; cos = Math.cos(angle); sin = Math.sin(angle); x = v.getX() * cos - v.getY() * sin; y = v.getX() * sin + v.getY() * cos; return v.setX(x).setY(y); } public static final Vector rotateVector(Vector v, double angleX, double angleY, double angleZ) { rotateAroundAxisX(v, angleX); rotateAroundAxisY(v, angleY); rotateAroundAxisZ(v, angleZ); return v; } public static final double angleToXAxis(Vector vector) { return Math.atan2(vector.getX(), vector.getY()); } public static Vector getRandomVector() { double x = random.nextDouble() * 2.0D - 1.0D; double y = random.nextDouble() * 2.0D - 1.0D; double z = random.nextDouble() * 2.0D - 1.0D; return new Vector(x, y, z).normalize(); } public static void applyVelocity(final Entity ent, Vector v) { if (ent.hasMetadata("NPC")) return; if (ent instanceof Player) { UltraPlayer customPlayer = UltraCosmeticsData.get().getPlugin().getPlayerManager().getUltraPlayer((Player)ent); if (!customPlayer.hasGadgetsEnabled()) return; } ent.setVelocity(v); Bukkit.getScheduler().runTaskLaterAsynchronously(UltraCosmeticsData.get().getPlugin(), new Runnable() { @Override public void run() { FallDamageManager.addNoFall(ent); } }, 5); } public static void applyVelocity(final Entity ent, Vector v, boolean ignoreGadgetsEnabled) { if (ent.hasMetadata("NPC")) return; if (!ignoreGadgetsEnabled) { if (ent instanceof Player) { UltraPlayer customPlayer = UltraCosmeticsData.get().getPlugin().getPlayerManager().getUltraPlayer((Player) ent); if (!customPlayer.hasGadgetsEnabled()) return; } } ent.setVelocity(v); Bukkit.getScheduler().runTaskLaterAsynchronously(UltraCosmeticsData.get().getPlugin(), new Runnable() { @Override public void run() { FallDamageManager.addNoFall(ent); } }, 4); } public static Vector getRandomCircleVector() { double rnd = random.nextDouble() * 2.0D * 3.141592653589793D; double x = Math.cos(rnd); double z = Math.sin(rnd); return new Vector(x, 0.0D, z); } public static Material getRandomMaterial(Material[] materials) { return materials[random.nextInt(materials.length)]; } public static double getRandomAngle() { return random.nextDouble() * 2 * Math.PI; } public static double randomDouble(double min, double max) { return Math.random() < 0.5 ? ((1 - Math.random()) * (max - min) + min) : (Math.random() * (max - min) + min); } public static float randomRangeFloat(float min, float max) { return (float) (Math.random() < 0.5 ? ((1 - Math.random()) * (max - min) + min) : (Math.random() * (max - min) + min)); } /** * get random byte between 0 and max (included). * * @param max random byte between 0 and max (included). */ public static byte randomByte(int max) { return (byte) random.nextInt(max + 1); } /** * Returns a random integer between the value min and the value max. * * @param min the minimum integer value. * @param max the maximum integer value. * @return a random integer between two values. */ public static int randomRangeInt(int min, int max) { return (int) (Math.random() < 0.5 ? ((1 - Math.random()) * (max - min) + min) : (Math.random() * (max - min) + min)); } public static double offset(Entity a, Entity b) { return offset(a.getLocation().toVector(), b.getLocation().toVector()); } public static double offset(Location a, Location b) { return offset(a.toVector(), b.toVector()); } public static double offset(Vector a, Vector b) { return a.subtract(b).length(); } }