Add methods to FastMath

For my own games, I’ve created some math utilities which might be worth incorporating into FastMath. Here’s how it might look:

--- HEAD
+++ Modified In Working Tree
@@ -957,4 +957,199 @@
                 | ((((f & 0x7f800000) - 0x38000000) >> 13) & 0x7c00)
                 | ((f >> 13) & 0x03ff));
     }
+
+    /**
+     * Compute the circle function sqrt(1 - x^2) for a single-precision value.
+     * Double precision arithmetic is used to reduce the risk of overflow.
+     *
+     * @param abscissa input (≤1, ≥-1)
+     * @return positive ordinate of the unit circle at the abscissa (≤1,
+     * ≥0)
+     */
+    public static float circle(float abscissa) {
+        if (!(abscissa >= -1f && abscissa <= 1f)) {
+            throw new IllegalArgumentException(
+                    "abscissa should be between -1 and 1, inclusive");
+        }
+
+        double x = (double) abscissa;
+        float y = (float) Math.sqrt(1.0 - x * x);
+
+        assert y >= 0f : y;
+        assert y <= 1f : y;
+        return y;
+    }
+
+    /**
+     * Clamp the magnitude of a single-precision value.
+     *
+     * @param fValue input value to be clamped
+     * @param maxMagnitude limit of the clamp (≥0)
+     * @return value between -maxMagnitude and +maxMagnitude inclusive which is
+     * closest to fValue
+     * @see #clamp(float,float,float)
+     */
+    public static float clamp(float fValue, float maxMagnitude) {
+        if (!(maxMagnitude >= 0f)) {
+            throw new IllegalArgumentException("limit should not be negative");
+        }
+
+        return clamp(fValue, -maxMagnitude, maxMagnitude);
+    }
+
+    /**
+     * Cube a single-precision value.
+     *
+     * @param fValue input value to be cubed
+     * @return fValue raised to the third power
+     * @see #cubeRoot(float)
+     */
+    public static float cube(float fValue) {
+        return fValue * fValue * fValue;
+    }
+
+    /**
+     * Extract the cube root of a single-precision value. Unlike
+     * #pow(float,float), this method works on negative values.
+     *
+     * @param fValue input cube to be extracted (may be negative)
+     * @return cube root of fValue
+     * @see #cube(float)
+     * @see #pow(float,float)
+     * @see Math#cbrt(double)
+     */
+    public static float cubeRoot(float fValue) {
+        float magnitude = abs(fValue);
+        float exponent = ONE_THIRD;
+        float rootMagnitude = pow(magnitude, exponent);
+        float result = copysign(rootMagnitude, fValue);
+
+        return result;
+    }
+
+    /**
+     * Fade polynomial for Perlin noise. Double precision arithmetic is used to
+     * reduce rounding error.
+     *
+     * @param t input value (≤1, ≥0)
+     * @return 6*t^5 - 15*t^4 + 10*t^3 (≤1, ≥0)
+     */
+    public static float fade(float t) {
+        if (!(t >= 0f && t <= 1f)) {
+            throw new IllegalArgumentException(
+                    "input value should be between 0 and 1, inclusive");
+        }
+
+        double tt = (double) t;
+        double ff = tt * tt * tt * (10.0 + tt * (-15.0 + 6.0 * tt));
+        float result = (float) ff;
+
+        assert result >= 0f : result;
+        assert result <= 1f : result;
+        return result;
+    }
+
+    /**
+     * Compute the hypotenuse of a right triangle using the Pythagorean Theorem.
+     * This method accepts negative arguments.
+     *
+     * @param legA length of the 1st leg (may be negative)
+     * @param legB length of the 2nd leg (may be negative)
+     * @return length of the hypotenuse (≥0)
+     */
+    public static float hypotenuse(float legA, float legB) {
+        double x = (double) legA;
+        double y = (double) legB;
+        double sumSquares = x * x + y * y;
+        float result = (float) Math.sqrt(sumSquares);
+
+        assert result >= 0f : result;
+        return result;
+    }
+
+    /**
+     * Test whether an integer value is odd.
+     *
+     * @param iValue input value to be tested
+     * @return true if x is odd, false if it's even
+     */
+    public static boolean isOdd(int iValue) {
+        boolean result = (iValue % 2) != 0;
+        return result;
+    }
+
+    /**
+     * Find the max of three single-precision values.
+     *
+     * @param a 1st input value
+     * @param b 2nd input value
+     * @param c 3rd input value
+     * @return greatest of the three values
+     */
+    public static float max(float a, float b, float c) {
+        if (a >= b && a >= c) {
+            return a;
+        } else if (b >= c) {
+            return b;
+        } else {
+            return c;
+        }
+    }
+
+    /**
+     * Compute the least non-negative value congruent with a single-precision
+     * value with respect to the specified modulus.
+     *
+     * @param fValue input value
+     * @param modulus (>0)
+     * @return fValue MOD modulus (<modulus, ≥0)
+     */
+    public static float modulo(float fValue, float modulus) {
+        if (!(modulus > 0f)) {
+            throw new IllegalArgumentException("modulus should be positive");
+        }
+
+        float result = (fValue % modulus + modulus) % modulus;
+
+        assert result >= 0f : result;
+        assert result < modulus : result;
+        return result;
+    }
+
+    /**
+     * Compute the least non-negative value congruent with an integer value with
+     * respect to the specified modulus.
+     *
+     * @param iValue input value
+     * @param modulus (>0)
+     * @return iValue MOD modulus (<modulus, ≥0)
+     */
+    public static int modulo(int iValue, int modulus) {
+        if (modulus <= 0) {
+            throw new IllegalArgumentException("modulus should be positive");
+        }
+
+        int result = (iValue % modulus + modulus) % modulus;
+
+        assert result >= 0f : result;
+        assert result < modulus : result;
+        return result;
+    }
+
+    /**
+     * Standardize a rotation angle to the range [-Pi, Pi).
+     *
+     * @param angle input (in radians)
+     * @return standardized angle (in radians, <Pi, ≥-Pi)
+     */
+    public static float standardizeAngle(float angle) {
+        float result = modulo(angle, TWO_PI);
+        if (result >= PI) {
+            result -= TWO_PI;
+        }
+
+        assert result >= -PI : result;
+        assert result < PI : result;
+        return result;
+    }
 }

Do any of these seem worth adding?

I was going to complain of bloat but it seems the FastMath class is far past bloat by now and is lying on a beach waiting for its guts to explode. (Readers: google beached whales in case the reference misses… you won’t be disappointed)

You will probably have to explain a lot better why modulo is different than %, though. Also, the old school micro-optimizer in me wants to use a conditional to take out the extra divide… but I guess divides are not as expensive as in the olden days. :wink:

fade() does seem really specific. What is a good general use-case?

1 Like

As you’re doubtless aware, modulo differs from remainder for negative values of the first argument. For instance, modulo(-1, 4) == 3 while -1 % 4 == -1. I’ll add comments to this effect if you wish.

I believe that in modern pipelined CPUs, unpredictable branches are more expensive than division, but I haven’t tested this belief, and my computer architecture training dates back to the 1980s, so I may be mistaken.

So far, the my only use of fade() has been for Perlin noise, but I anticipate it might be useful for other interpolations where you want both the first and second derivatives to be continuous everywhere. Motion paths are one example.

Since I already have these methods in my utilities plugin, I’m not attached to any of them being added to FastMath. You might say I’m testing the water. If there’s no support, we can forget this.

@sgold said: As you're doubtless aware, modulo differs from remainder for negative values of the first argument. For instance, modulo(-1, 4) == 3 while -1 % 4 == -1. I'll add comments to this effect if you wish.

Yeah, that was my only point… to improve the docs. After all, we likely have people using the engine that may not even have the skills to know that there is a % operator.

1 Like

@pspeed: I value your suggestions.