Browse Source

Merge pull request #2450 from capdevon/capdevon-FastMath

FastMath: new utility methods
Ryan McDonough 3 months ago
parent
commit
b7f11186d4

+ 99 - 34
jme3-core/src/main/java/com/jme3/math/FastMath.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2009-2024 jMonkeyEngine
+ * Copyright (c) 2009-2025 jMonkeyEngine
  * All rights reserved.
  * All rights reserved.
  *
  *
  * Redistribution and use in source and binary forms, with or without
  * Redistribution and use in source and binary forms, with or without
@@ -40,9 +40,10 @@ import java.util.Random;
  * @author Various
  * @author Various
  * @version $Id: FastMath.java,v 1.45 2007/08/26 08:44:20 irrisor Exp $
  * @version $Id: FastMath.java,v 1.45 2007/08/26 08:44:20 irrisor Exp $
  */
  */
-final public class FastMath {
-    private FastMath() {
-    }
+public final class FastMath {
+
+    private FastMath() {}
+
     /**
     /**
      * A "close to zero" double epsilon value for use
      * A "close to zero" double epsilon value for use
      */
      */
@@ -489,10 +490,8 @@ final public class FastMath {
             if (fValue < 1.0f) {
             if (fValue < 1.0f) {
                 return (float) Math.acos(fValue);
                 return (float) Math.acos(fValue);
             }
             }
-
             return 0.0f;
             return 0.0f;
         }
         }
-
         return PI;
         return PI;
     }
     }
 
 
@@ -511,10 +510,8 @@ final public class FastMath {
             if (fValue < 1.0f) {
             if (fValue < 1.0f) {
                 return (float) Math.asin(fValue);
                 return (float) Math.asin(fValue);
             }
             }
-
             return HALF_PI;
             return HALF_PI;
         }
         }
-
         return -HALF_PI;
         return -HALF_PI;
     }
     }
 
 
@@ -844,35 +841,111 @@ final public class FastMath {
     }
     }
 
 
     /**
     /**
-     * Returns a random float between 0 and 1.
+     * Generates a pseudorandom {@code float} in the range [0.0, 1.0).
      *
      *
-     * @return a random float between 0 (inclusive) and 1 (exclusive)
+     * @return A random {@code float} value.
      */
      */
     public static float nextRandomFloat() {
     public static float nextRandomFloat() {
         return rand.nextFloat();
         return rand.nextFloat();
     }
     }
 
 
     /**
     /**
-     * Returns a random integer between min and max.
+     * Generates a pseudorandom {@code float} in the range [min, max)
      *
      *
-     * @param min the desired minimum value
-     * @param max the desired maximum value
-     * @return a random int between min (inclusive) and max (inclusive)
+     * @param min The lower bound (inclusive).
+     * @param max The upper bound (exclusive).
+     * @return A random {@code float} value within the specified range.
      */
      */
-    public static int nextRandomInt(int min, int max) {
-        return (int) (nextRandomFloat() * (max - min + 1)) + min;
+    public static float nextRandomFloat(float min, float max) {
+        return min + (max - min) * nextRandomFloat();
     }
     }
 
 
     /**
     /**
-     * Choose a pseudo-random, uniformly-distributed integer value from
-     * the shared generator.
+     * Generates a pseudorandom, uniformly-distributed {@code int} value.
      *
      *
-     * @return the next integer value
+     * @return The next pseudorandom {@code int} value.
      */
      */
     public static int nextRandomInt() {
     public static int nextRandomInt() {
         return rand.nextInt();
         return rand.nextInt();
     }
     }
 
 
+    /**
+     * Generates a pseudorandom {@code int} in the range [min, max] (inclusive).
+     *
+     * @param min The lower bound (inclusive).
+     * @param max The upper bound (inclusive).
+     * @return A random {@code int} value within the specified range.
+     */
+    public static int nextRandomInt(int min, int max) {
+        return (int) (nextRandomFloat() * (max - min + 1)) + min;
+    }
+
+    /**
+     * Returns a random point on the surface of a sphere with radius 1.0
+     *
+     * @return A new {@link Vector3f} representing a random point on the surface of the unit sphere.
+     */
+    public static Vector3f onUnitSphere() {
+
+        float u = nextRandomFloat();
+        float v = nextRandomFloat();
+
+        // azimuthal angle: The angle between x-axis in radians [0, 2PI]
+        float theta = FastMath.TWO_PI * u;
+        // polar angle: The angle between z-axis in radians [0, PI]
+        float phi = (float) Math.acos(2f * v - 1f);
+
+        float cosPolar = FastMath.cos(phi);
+        float sinPolar = FastMath.sin(phi);
+        float cosAzim = FastMath.cos(theta);
+        float sinAzim = FastMath.sin(theta);
+
+        return new Vector3f(cosAzim * sinPolar, sinAzim * sinPolar, cosPolar);
+    }
+
+    /**
+     * Returns a random point inside or on a sphere with radius 1.0
+     * This method uses spherical coordinates combined with a cubed-root radius.
+     *
+     * @return A new {@link Vector3f} representing a random point within the unit sphere.
+     */
+    public static Vector3f insideUnitSphere() {
+        float u = nextRandomFloat();
+        // Azimuthal angle [0, 2PI]
+        float theta = FastMath.TWO_PI * nextRandomFloat();
+        // Polar angle [0, PI] for uniform surface distribution
+        float phi = FastMath.acos(2f * nextRandomFloat() - 1f);
+
+        // For uniform distribution within the volume, radius R should be such that R^3 is uniformly distributed.
+        // So, R = cbrt(random_uniform_0_to_1)
+        float radius = (float) Math.cbrt(u);
+
+        float sinPhi = FastMath.sin(phi);
+        float x = radius * sinPhi * FastMath.cos(theta);
+        float y = radius * sinPhi * FastMath.sin(theta);
+        float z = radius * FastMath.cos(phi);
+
+        return new Vector3f(x, y, z);
+    }
+
+    /**
+     * Returns a random point inside or on a circle with radius 1.0.
+     * This method uses polar coordinates combined with a square-root radius.
+     *
+     * @return A new {@link Vector2f} representing a random point within the unit circle.
+     */
+    public static Vector2f insideUnitCircle() {
+        // Angle [0, 2PI]
+        float angle = FastMath.TWO_PI * nextRandomFloat();
+        // For uniform distribution, R^2 is uniform
+        float radius = FastMath.sqrt(nextRandomFloat());
+
+        float x = radius * FastMath.cos(angle);
+        float y = radius * FastMath.sin(angle);
+
+        return new Vector2f(x, y);
+    }
+
     /**
     /**
      * Converts a point from Spherical coordinates to Cartesian (using positive
      * Converts a point from Spherical coordinates to Cartesian (using positive
      * Y as up) and stores the results in the store var.
      * Y as up) and stores the results in the store var.
@@ -883,8 +956,7 @@ final public class FastMath {
      * @param store storage for the result (modified if not null)
      * @param store storage for the result (modified if not null)
      * @return the Cartesian coordinates (either store or a new vector)
      * @return the Cartesian coordinates (either store or a new vector)
      */
      */
-    public static Vector3f sphericalToCartesian(Vector3f sphereCoords,
-            Vector3f store) {
+    public static Vector3f sphericalToCartesian(Vector3f sphereCoords, Vector3f store) {
         if (store == null) {
         if (store == null) {
             store = new Vector3f();
             store = new Vector3f();
         }
         }
@@ -906,8 +978,7 @@ final public class FastMath {
      * @return the Cartesian coordinates: x=distance from origin, y=longitude in
      * @return the Cartesian coordinates: x=distance from origin, y=longitude in
      * radians, z=latitude in radians (either store or a new vector)
      * radians, z=latitude in radians (either store or a new vector)
      */
      */
-    public static Vector3f cartesianToSpherical(Vector3f cartCoords,
-            Vector3f store) {
+    public static Vector3f cartesianToSpherical(Vector3f cartCoords, Vector3f store) {
         if (store == null) {
         if (store == null) {
             store = new Vector3f();
             store = new Vector3f();
         }
         }
@@ -936,8 +1007,7 @@ final public class FastMath {
      * @param store storage for the result (modified if not null)
      * @param store storage for the result (modified if not null)
      * @return the Cartesian coordinates (either store or a new vector)
      * @return the Cartesian coordinates (either store or a new vector)
      */
      */
-    public static Vector3f sphericalToCartesianZ(Vector3f sphereCoords,
-            Vector3f store) {
+    public static Vector3f sphericalToCartesianZ(Vector3f sphereCoords, Vector3f store) {
         if (store == null) {
         if (store == null) {
             store = new Vector3f();
             store = new Vector3f();
         }
         }
@@ -959,8 +1029,7 @@ final public class FastMath {
      * @return the Cartesian coordinates: x=distance from origin, y=latitude in
      * @return the Cartesian coordinates: x=distance from origin, y=latitude in
      * radians, z=longitude in radians (either store or a new vector)
      * radians, z=longitude in radians (either store or a new vector)
      */
      */
-    public static Vector3f cartesianZToSpherical(Vector3f cartCoords,
-            Vector3f store) {
+    public static Vector3f cartesianZToSpherical(Vector3f cartCoords, Vector3f store) {
         if (store == null) {
         if (store == null) {
             store = new Vector3f();
             store = new Vector3f();
         }
         }
@@ -982,12 +1051,9 @@ final public class FastMath {
     /**
     /**
      * Takes a value and expresses it in terms of min to max.
      * Takes a value and expresses it in terms of min to max.
      *
      *
-     * @param val -
-     *            the angle to normalize (in radians)
-     * @param min
-     *            the lower limit of the range
-     * @param max
-     *            the upper limit of the range
+     * @param val the angle to normalize (in radians)
+     * @param min the lower limit of the range
+     * @param max the upper limit of the range
      * @return the normalized angle (also in radians)
      * @return the normalized angle (also in radians)
      */
      */
     public static float normalize(float val, float min, float max) {
     public static float normalize(float val, float min, float max) {
@@ -1143,5 +1209,4 @@ final public class FastMath {
         return ((n - 1) | (p - 1)) + 1;
         return ((n - 1) | (p - 1)) + 1;
     }
     }
 
 
-
 }
 }

+ 84 - 0
jme3-examples/src/main/java/jme3test/math/TestRandomPoints.java

@@ -0,0 +1,84 @@
+package jme3test.math;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.debug.Arrow;
+import com.jme3.scene.debug.Grid;
+import com.jme3.scene.debug.WireSphere;
+import com.jme3.scene.shape.Sphere;
+
+/**
+ * @author capdevon
+ */
+public class TestRandomPoints extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestRandomPoints app = new TestRandomPoints();
+        app.start();
+    }
+
+    private float radius = 5;
+
+    @Override
+    public void simpleInitApp() {
+        configureCamera();
+        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+
+        Geometry grid = makeShape("DebugGrid", new Grid(21, 21, 2), ColorRGBA.LightGray);
+        grid.center().move(0, 0, 0);
+        rootNode.attachChild(grid);
+
+        Geometry bsphere = makeShape("BoundingSphere", new WireSphere(radius), ColorRGBA.Red);
+        rootNode.attachChild(bsphere);
+
+        for (int i = 0; i < 100; i++) {
+            Vector2f v = FastMath.insideUnitCircle().multLocal(radius);
+            Arrow arrow = new Arrow(Vector3f.UNIT_Y.negate());
+            Geometry geo = makeShape("Arrow." + i, arrow, ColorRGBA.Green);
+            geo.setLocalTranslation(new Vector3f(v.x, 0, v.y));
+            rootNode.attachChild(geo);
+        }
+
+        for (int i = 0; i < 100; i++) {
+            Vector3f v = FastMath.insideUnitSphere().multLocal(radius);
+            Geometry geo = makeShape("Sphere." + i, new Sphere(16, 16, 0.05f), ColorRGBA.Blue);
+            geo.setLocalTranslation(v);
+            rootNode.attachChild(geo);
+        }
+
+        for (int i = 0; i < 100; i++) {
+            Vector3f v = FastMath.onUnitSphere().multLocal(radius);
+            Geometry geo = makeShape("Sphere." + i, new Sphere(16, 16, 0.06f), ColorRGBA.Cyan);
+            geo.setLocalTranslation(v);
+            rootNode.attachChild(geo);
+        }
+
+        for (int i = 0; i < 100; i++) {
+            float value = FastMath.nextRandomFloat(-5, 5);
+            System.out.println(value);
+        }
+    }
+
+    private void configureCamera() {
+        flyCam.setMoveSpeed(15f);
+        flyCam.setDragToRotate(true);
+
+        cam.setLocation(Vector3f.UNIT_XYZ.mult(12));
+        cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
+    }
+
+    private Geometry makeShape(String name, Mesh mesh, ColorRGBA color) {
+        Geometry geo = new Geometry(name, mesh);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", color);
+        geo.setMaterial(mat);
+        return geo;
+    }
+
+}