diff --git a/jme3-core/src/main/java/com/jme3/math/FastMath.java b/jme3-core/src/main/java/com/jme3/math/FastMath.java index 2993c7eed0..d7b533fbd9 100644 --- a/jme3-core/src/main/java/com/jme3/math/FastMath.java +++ b/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. * * Redistribution and use in source and binary forms, with or without @@ -40,9 +40,10 @@ * @author Various * @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 */ @@ -489,10 +490,8 @@ public static float acos(float fValue) { if (fValue < 1.0f) { return (float) Math.acos(fValue); } - return 0.0f; } - return PI; } @@ -511,10 +510,8 @@ public static float asin(float fValue) { if (fValue < 1.0f) { return (float) Math.asin(fValue); } - return HALF_PI; } - return -HALF_PI; } @@ -844,35 +841,111 @@ public static float determinant(double m00, double m01, double m02, } /** - * 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() { 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() { 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 * Y as up) and stores the results in the store var. @@ -883,8 +956,7 @@ public static int nextRandomInt() { * @param store storage for the result (modified if not null) * @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) { store = new Vector3f(); } @@ -906,8 +978,7 @@ public static Vector3f sphericalToCartesian(Vector3f sphereCoords, * @return the Cartesian coordinates: x=distance from origin, y=longitude in * 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) { store = new Vector3f(); } @@ -936,8 +1007,7 @@ public static Vector3f cartesianToSpherical(Vector3f cartCoords, * @param store storage for the result (modified if not null) * @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) { store = new Vector3f(); } @@ -959,8 +1029,7 @@ public static Vector3f sphericalToCartesianZ(Vector3f sphereCoords, * @return the Cartesian coordinates: x=distance from origin, y=latitude in * 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) { store = new Vector3f(); } @@ -982,12 +1051,9 @@ public static Vector3f cartesianZToSpherical(Vector3f cartCoords, /** * 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) */ public static float normalize(float val, float min, float max) { @@ -1143,5 +1209,4 @@ public static int toMultipleOf(int n, int p) { return ((n - 1) | (p - 1)) + 1; } - } diff --git a/jme3-examples/src/main/java/jme3test/math/TestRandomPoints.java b/jme3-examples/src/main/java/jme3test/math/TestRandomPoints.java new file mode 100644 index 0000000000..ef264b95d1 --- /dev/null +++ b/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; + } + +}