diff --git a/src/main/java/pythagoras/f/AffineTransform.java b/src/main/java/pythagoras/f/AffineTransform.java index 5357087..2b2223a 100644 --- a/src/main/java/pythagoras/f/AffineTransform.java +++ b/src/main/java/pythagoras/f/AffineTransform.java @@ -96,7 +96,7 @@ public class AffineTransform extends AbstractTransform // compute the difference; if it's small enough, we're done float d00 = n00 - o00, d10 = n10 - o10; float d01 = n01 - o01, d11 = n11 - o11; - if (d00*d00 + d10*d10 + d01*d01 + d11*d11 < FloatMath.EPSILON) { + if (d00*d00 + d10*d10 + d01*d01 + d11*d11 < MathUtil.EPSILON) { break; } } @@ -340,8 +340,8 @@ public class AffineTransform extends AbstractTransform @Override public String toString () { - return "affine [" + FloatMath.toString(m00) + " " + FloatMath.toString(m01) + " " + - FloatMath.toString(m10) + " " + FloatMath.toString(m11) + " " + translation() + "]"; + return "affine [" + MathUtil.toString(m00) + " " + MathUtil.toString(m01) + " " + + MathUtil.toString(m10) + " " + MathUtil.toString(m11) + " " + translation() + "]"; } // we don't publicize this because it might encourage someone to do something stupid like diff --git a/src/main/java/pythagoras/f/FloatMath.java b/src/main/java/pythagoras/f/FloatMath.java index 676dd43..acde20c 100644 --- a/src/main/java/pythagoras/f/FloatMath.java +++ b/src/main/java/pythagoras/f/FloatMath.java @@ -5,28 +5,17 @@ package pythagoras.f; /** - * Utility methods and constants for single-precision floating point math. + * Utility methods and constants for single-precision floating point math. Extends {@link MathUtil} + * with shim methods that call through to {@link Math} and convert the results to float. */ -public class FloatMath +public class FloatMath extends MathUtil { /** The ratio of a circle's circumference to its diameter. */ public static final float PI = (float)Math.PI; - /** The circle constant, Tau (τ) http://tauday.com/ */ - public static final float TAU = (float)(Math.PI * 2); - - /** Twice Pi. */ - public static final float TWO_PI = TAU; - - /** Pi times one half. */ - public static final float HALF_PI = (float)(Math.PI * 0.5); - /** The base value of the natural logarithm. */ public static final float E = (float)Math.E; - /** A small number. */ - public static final float EPSILON = 0.00001f; - /** * Computes and returns the sine of the given angle. * @@ -197,24 +186,6 @@ public class FloatMath return (float)Math.floor(v); } - /** - * A cheaper version of {@link Math#round} that doesn't handle the special cases. - */ - public static int round (float v) - { - return (v < 0f) ? (int)(v - 0.5f) : (int)(v + 0.5f); - } - - /** - * Returns the floor of v as an integer without calling the relatively expensive - * {@link Math#floor}. - */ - public static int ifloor (float v) - { - int iv = (int)v; - return (v < 0f) ? ((iv == v || iv == Integer.MIN_VALUE) ? iv : (iv - 1)) : iv; - } - /** * Returns the ceiling of v. * @@ -224,193 +195,4 @@ public class FloatMath { return (float)Math.ceil(v); } - - /** - * Returns the ceiling of v as an integer without calling the relatively expensive - * {@link Math#ceil}. - */ - public static int iceil (float v) - { - int iv = (int)v; - return (v > 0f) ? ((iv == v || iv == Integer.MAX_VALUE) ? iv : (iv + 1)) : iv; - } - - /** - * Clamps a value to the range [lower, upper]. - */ - public static float clamp (float v, float lower, float upper) - { - return Math.min(Math.max(v, lower), upper); - } - - /** - * Rounds a value to the nearest multiple of a target. - */ - public static float roundNearest (float v, float target) - { - target = Math.abs(target); - if (v >= 0) { - return target * floor((v + 0.5f * target) / target); - } else { - return target * ceil((v - 0.5f * target) / target); - } - } - - /** - * Checks whether the value supplied is in [lower, upper]. - */ - public static boolean isWithin (float v, float lower, float upper) - { - return v >= lower && v <= upper; - } - - /** - * Returns a random value according to the normal distribution with the provided mean and - * standard deviation. - * - * @param normal a normally distributed random value. - * @param mean the desired mean. - * @param stddev the desired standard deviation. - */ - public static float normal (float normal, float mean, float stddev) - { - return stddev*normal + mean; - } - - /** - * Returns a random value according to the exponential distribution with the provided mean. - * - * @param random a uniformly distributed random value. - * @param mean the desired mean. - */ - public static float exponential (float random, float mean) - { - return -log(1f - random) * mean; - } - - /** - * Linearly interpolates between two angles, taking the shortest path around the circle. - * This assumes that both angles are in [-pi, +pi]. - */ - public static float lerpa (float a1, float a2, float t) - { - float ma1 = mirrorAngle(a1), ma2 = mirrorAngle(a2); - float d = Math.abs(a2 - a1), md = Math.abs(ma1 - ma2); - return (d < md) ? lerp(a1, a2, t) : mirrorAngle(lerp(ma1, ma2, t)); - } - - /** - * Linearly interpolates between v1 and v2 by the parameter t. - */ - public static float lerp (float v1, float v2, float t) - { - return v1 + t*(v2 - v1); - } - - /** - * Determines whether two values are "close enough" to equal. - */ - public static boolean epsilonEquals (float v1, float v2) - { - return Math.abs(v1 - v2) < EPSILON; - } - - /** - * Returns the (shortest) distance between two angles, assuming that both angles are in - * [-pi, +pi]. - */ - public static float angularDistance (float a1, float a2) - { - float ma1 = mirrorAngle(a1), ma2 = mirrorAngle(a2); - return Math.min(Math.abs(a1 - a2), Math.abs(ma1 - ma2)); - } - - /** - * Returns the (shortest) difference between two angles, assuming that both angles are in - * [-pi, +pi]. - */ - public static float angularDifference (float a1, float a2) - { - float ma1 = mirrorAngle(a1), ma2 = mirrorAngle(a2); - float diff = a1 - a2, mdiff = ma2 - ma1; - return (Math.abs(diff) < Math.abs(mdiff)) ? diff : mdiff; - } - - /** - * Returns an angle in the range [-pi, pi]. - */ - public static float normalizeAngle (float a) - { - while (a < -PI) { - a += TWO_PI; - } - while (a > PI) { - a -= TWO_PI; - } - return a; - } - - /** - * Returns an angle in the range [0, 2pi]. - */ - public static float normalizeAnglePositive (float a) - { - while (a < 0f) { - a += TWO_PI; - } - while (a > TWO_PI) { - a -= TWO_PI; - } - return a; - } - - /** - * Returns the mirror angle of the specified angle (assumed to be in [-pi, +pi]). - */ - public static float mirrorAngle (float a) - { - return (a > 0f ? PI : -PI) - a; - } - - /** - * Sets the number of decimal places to show when formatting values. By default, they are - * formatted to three decimal places. - */ - public static void setToStringDecimalPlaces (int places) { - if (places < 0) throw new IllegalArgumentException("Decimal places must be >= 0."); - TO_STRING_DECIMAL_PLACES = places; - } - - /** - * Formats the supplied floating point value, truncated to the currently configured number of - * decimal places. The value is also always preceded by a sign (e.g. +1.0 or -0.5). - */ - public static String toString (float value) - { - StringBuilder buf = new StringBuilder(); - if (value >= 0) buf.append("+"); - else { - buf.append("-"); - value = -value; - } - int ivalue = (int)value; - buf.append(ivalue); - if (TO_STRING_DECIMAL_PLACES > 0) { - buf.append("."); - for (int ii = 0; ii < TO_STRING_DECIMAL_PLACES; ii++) { - value = (value - ivalue) * 10; - ivalue = (int)value; - buf.append(ivalue); - } - // trim trailing zeros - for (int ii = 0; ii < TO_STRING_DECIMAL_PLACES-1; ii++) { - if (buf.charAt(buf.length()-1) == '0') { - buf.setLength(buf.length()-1); - } - } - } - return buf.toString(); - } - - protected static int TO_STRING_DECIMAL_PLACES = 3; } diff --git a/src/main/java/pythagoras/f/MathUtil.java b/src/main/java/pythagoras/f/MathUtil.java new file mode 100644 index 0000000..16e62cc --- /dev/null +++ b/src/main/java/pythagoras/f/MathUtil.java @@ -0,0 +1,230 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.f; + +/** + * Math utility methods. + */ +public class MathUtil +{ + /** A small number. */ + public static final float EPSILON = 0.00001f; + + /** The circle constant, Tau (τ) http://tauday.com/ */ + public static final float TAU = (float)(Math.PI * 2); + + /** Twice Pi. */ + public static final float TWO_PI = TAU; + + /** Pi times one half. */ + public static final float HALF_PI = (float)(Math.PI * 0.5); + + /** + * A cheaper version of {@link Math#round} that doesn't handle the special cases. + */ + public static int round (float v) + { + return (v < 0f) ? (int)(v - 0.5f) : (int)(v + 0.5f); + } + + /** + * Returns the floor of v as an integer without calling the relatively expensive + * {@link Math#floor}. + */ + public static int ifloor (float v) + { + int iv = (int)v; + return (v < 0f) ? ((iv == v || iv == Integer.MIN_VALUE) ? iv : (iv - 1)) : iv; + } + + /** + * Returns the ceiling of v as an integer without calling the relatively expensive + * {@link Math#ceil}. + */ + public static int iceil (float v) + { + int iv = (int)v; + return (v > 0f) ? ((iv == v || iv == Integer.MAX_VALUE) ? iv : (iv + 1)) : iv; + } + + /** + * Clamps a value to the range [lower, upper]. + */ + public static float clamp (float v, float lower, float upper) + { + return Math.min(Math.max(v, lower), upper); + } + + /** + * Rounds a value to the nearest multiple of a target. + */ + public static float roundNearest (float v, float target) + { + target = Math.abs(target); + if (v >= 0) { + return target * FloatMath.floor((v + 0.5f * target) / target); + } else { + return target * FloatMath.ceil((v - 0.5f * target) / target); + } + } + + /** + * Checks whether the value supplied is in [lower, upper]. + */ + public static boolean isWithin (float v, float lower, float upper) + { + return v >= lower && v <= upper; + } + + /** + * Returns a random value according to the normal distribution with the provided mean and + * standard deviation. + * + * @param normal a normally distributed random value. + * @param mean the desired mean. + * @param stddev the desired standard deviation. + */ + public static float normal (float normal, float mean, float stddev) + { + return stddev*normal + mean; + } + + /** + * Returns a random value according to the exponential distribution with the provided mean. + * + * @param random a uniformly distributed random value. + * @param mean the desired mean. + */ + public static float exponential (float random, float mean) + { + return -FloatMath.log(1f - random) * mean; + } + + /** + * Linearly interpolates between two angles, taking the shortest path around the circle. + * This assumes that both angles are in [-pi, +pi]. + */ + public static float lerpa (float a1, float a2, float t) + { + float ma1 = mirrorAngle(a1), ma2 = mirrorAngle(a2); + float d = Math.abs(a2 - a1), md = Math.abs(ma1 - ma2); + return (d < md) ? lerp(a1, a2, t) : mirrorAngle(lerp(ma1, ma2, t)); + } + + /** + * Linearly interpolates between v1 and v2 by the parameter t. + */ + public static float lerp (float v1, float v2, float t) + { + return v1 + t*(v2 - v1); + } + + /** + * Determines whether two values are "close enough" to equal. + */ + public static boolean epsilonEquals (float v1, float v2) + { + return Math.abs(v1 - v2) < EPSILON; + } + + /** + * Returns the (shortest) distance between two angles, assuming that both angles are in + * [-pi, +pi]. + */ + public static float angularDistance (float a1, float a2) + { + float ma1 = mirrorAngle(a1), ma2 = mirrorAngle(a2); + return Math.min(Math.abs(a1 - a2), Math.abs(ma1 - ma2)); + } + + /** + * Returns the (shortest) difference between two angles, assuming that both angles are in + * [-pi, +pi]. + */ + public static float angularDifference (float a1, float a2) + { + float ma1 = mirrorAngle(a1), ma2 = mirrorAngle(a2); + float diff = a1 - a2, mdiff = ma2 - ma1; + return (Math.abs(diff) < Math.abs(mdiff)) ? diff : mdiff; + } + + /** + * Returns an angle in the range [-pi, pi]. + */ + public static float normalizeAngle (float a) + { + while (a < -FloatMath.PI) { + a += TWO_PI; + } + while (a > FloatMath.PI) { + a -= TWO_PI; + } + return a; + } + + /** + * Returns an angle in the range [0, 2pi]. + */ + public static float normalizeAnglePositive (float a) + { + while (a < 0f) { + a += TWO_PI; + } + while (a > TWO_PI) { + a -= TWO_PI; + } + return a; + } + + /** + * Returns the mirror angle of the specified angle (assumed to be in [-pi, +pi]). + */ + public static float mirrorAngle (float a) + { + return (a > 0f ? FloatMath.PI : -FloatMath.PI) - a; + } + + /** + * Sets the number of decimal places to show when formatting values. By default, they are + * formatted to three decimal places. + */ + public static void setToStringDecimalPlaces (int places) { + if (places < 0) throw new IllegalArgumentException("Decimal places must be >= 0."); + TO_STRING_DECIMAL_PLACES = places; + } + + /** + * Formats the supplied floating point value, truncated to the currently configured number of + * decimal places. The value is also always preceded by a sign (e.g. +1.0 or -0.5). + */ + public static String toString (float value) + { + StringBuilder buf = new StringBuilder(); + if (value >= 0) buf.append("+"); + else { + buf.append("-"); + value = -value; + } + int ivalue = (int)value; + buf.append(ivalue); + if (TO_STRING_DECIMAL_PLACES > 0) { + buf.append("."); + for (int ii = 0; ii < TO_STRING_DECIMAL_PLACES; ii++) { + value = (value - ivalue) * 10; + ivalue = (int)value; + buf.append(ivalue); + } + // trim trailing zeros + for (int ii = 0; ii < TO_STRING_DECIMAL_PLACES-1; ii++) { + if (buf.charAt(buf.length()-1) == '0') { + buf.setLength(buf.length()-1); + } + } + } + return buf.toString(); + } + + protected static int TO_STRING_DECIMAL_PLACES = 3; +} diff --git a/src/main/java/pythagoras/f/NonUniformTransform.java b/src/main/java/pythagoras/f/NonUniformTransform.java index 3ad2b58..c022507 100644 --- a/src/main/java/pythagoras/f/NonUniformTransform.java +++ b/src/main/java/pythagoras/f/NonUniformTransform.java @@ -165,7 +165,7 @@ public class NonUniformTransform extends AbstractTransform float ntx = (otx*cosa - oty*sina) * scaleX + tx(); float nty = (otx*sina + oty*cosa) * scaleY + ty(); - float nrotation = FloatMath.normalizeAngle(rotation + other.rotation()); + float nrotation = MathUtil.normalizeAngle(rotation + other.rotation()); float nscaleX = scaleX * other.scaleX(); float nscaleY = scaleY * other.scaleY(); return new NonUniformTransform(nscaleX, nscaleY, nrotation, ntx, nty); @@ -181,7 +181,7 @@ public class NonUniformTransform extends AbstractTransform float sina = FloatMath.sin(other.rotation()), cosa = FloatMath.cos(other.rotation()); float ntx = (tx*cosa - ty*sina) * other.scaleX() + other.tx(); float nty = (tx*sina + ty*cosa) * other.scaleY() + other.ty(); - float nrotation = FloatMath.normalizeAngle(other.rotation() + rotation); + float nrotation = MathUtil.normalizeAngle(other.rotation() + rotation); float nscaleX = other.scaleX() * scaleX; float nscaleY = other.scaleY() * scaleY; return new NonUniformTransform(nscaleX, nscaleY, nrotation, ntx, nty); @@ -193,11 +193,11 @@ public class NonUniformTransform extends AbstractTransform return other.lerp(this, -t); // TODO: is this correct? } - float ntx = FloatMath.lerpa(tx, other.tx(), t); - float nty = FloatMath.lerpa(ty, other.ty(), t); - float nrotation = FloatMath.lerpa(rotation, other.rotation(), t); - float nscaleX = FloatMath.lerp(scaleX, other.scaleX(), t); - float nscaleY = FloatMath.lerp(scaleY, other.scaleY(), t); + float ntx = MathUtil.lerpa(tx, other.tx(), t); + float nty = MathUtil.lerpa(ty, other.ty(), t); + float nrotation = MathUtil.lerpa(rotation, other.rotation(), t); + float nscaleX = MathUtil.lerp(scaleX, other.scaleX(), t); + float nscaleY = MathUtil.lerp(scaleY, other.scaleY(), t); return new NonUniformTransform(nscaleX, nscaleY, nrotation, ntx, nty); } diff --git a/src/main/java/pythagoras/f/Points.java b/src/main/java/pythagoras/f/Points.java index 6222d24..7ffe273 100644 --- a/src/main/java/pythagoras/f/Points.java +++ b/src/main/java/pythagoras/f/Points.java @@ -25,7 +25,7 @@ public class Points * Returns the Euclidean distance between the specified two points. */ public static float distance (float x1, float y1, float x2, float y2) { - return (float)Math.sqrt(distanceSq(x1, y1, x2, y2)); + return FloatMath.sqrt(distanceSq(x1, y1, x2, y2)); } /** Transforms a point as specified, storing the result in the point provided. @@ -59,6 +59,6 @@ public class Points * +x-y, -x-y, etc. */ public static String pointToString (float x, float y) { - return FloatMath.toString(x) + FloatMath.toString(y); + return MathUtil.toString(x) + MathUtil.toString(y); } } diff --git a/src/main/java/pythagoras/f/RigidTransform.java b/src/main/java/pythagoras/f/RigidTransform.java index 25551ff..aabd26b 100644 --- a/src/main/java/pythagoras/f/RigidTransform.java +++ b/src/main/java/pythagoras/f/RigidTransform.java @@ -114,7 +114,7 @@ public class RigidTransform extends AbstractTransform Vector nt = other.translation(); nt.rotateAndAdd(rotation, translation(), nt); - float nrotation = FloatMath.normalizeAngle(rotation + other.rotation()); + float nrotation = MathUtil.normalizeAngle(rotation + other.rotation()); return new RigidTransform(nrotation, nt.x, nt.y); } @@ -126,7 +126,7 @@ public class RigidTransform extends AbstractTransform Vector nt = translation(); nt.rotateAndAdd(other.rotation(), other.translation(), nt); - float nrotation = FloatMath.normalizeAngle(other.rotation() + rotation); + float nrotation = MathUtil.normalizeAngle(other.rotation() + rotation); return new RigidTransform(nrotation, nt.x, nt.y); } @@ -136,7 +136,7 @@ public class RigidTransform extends AbstractTransform return other.lerp(this, -t); // TODO: is this correct? } Vector nt = translation().lerpLocal(other.translation(), t); - return new RigidTransform(FloatMath.lerpa(rotation, other.rotation(), t), nt.x, nt.y); + return new RigidTransform(MathUtil.lerpa(rotation, other.rotation(), t), nt.x, nt.y); } @Override // from Transform diff --git a/src/main/java/pythagoras/f/UniformTransform.java b/src/main/java/pythagoras/f/UniformTransform.java index 2b46a6a..cd94244 100644 --- a/src/main/java/pythagoras/f/UniformTransform.java +++ b/src/main/java/pythagoras/f/UniformTransform.java @@ -136,7 +136,7 @@ public class UniformTransform extends AbstractTransform Vector nt = other.translation(); nt.rotateScaleAndAdd(rotation, scale, translation(), nt); - float nrotation = FloatMath.normalizeAngle(rotation + other.rotation()); + float nrotation = MathUtil.normalizeAngle(rotation + other.rotation()); float nscale = scale * other.uniformScale(); return new UniformTransform(nscale, nrotation, nt.x, nt.y); } @@ -150,7 +150,7 @@ public class UniformTransform extends AbstractTransform Vector nt = translation(); nt.rotateScaleAndAdd(other.rotation(), other.uniformScale(), other.translation(), nt); - float nrotation = FloatMath.normalizeAngle(other.rotation() + rotation); + float nrotation = MathUtil.normalizeAngle(other.rotation() + rotation); float nscale = other.uniformScale() * scale; return new UniformTransform(nscale, nrotation, nt.x, nt.y); } @@ -162,8 +162,8 @@ public class UniformTransform extends AbstractTransform } Vector nt = translation().lerpLocal(other.translation(), t); - float nrotation = FloatMath.lerpa(rotation, other.rotation(), t); - float nscale = FloatMath.lerp(scale, other.uniformScale(), t); + float nrotation = MathUtil.lerpa(rotation, other.rotation(), t); + float nscale = MathUtil.lerp(scale, other.uniformScale(), t); return new UniformTransform(nscale, nrotation, nt.x, nt.y); } diff --git a/src/main/java/pythagoras/f/Vectors.java b/src/main/java/pythagoras/f/Vectors.java index 9575995..9f7c4e4 100644 --- a/src/main/java/pythagoras/f/Vectors.java +++ b/src/main/java/pythagoras/f/Vectors.java @@ -74,6 +74,6 @@ public class Vectors * +x-y, -x-y, etc. */ public static String vectorToString (float x, float y) { - return FloatMath.toString(x) + FloatMath.toString(y); + return MathUtil.toString(x) + MathUtil.toString(y); } }