Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 99 additions & 34 deletions jme3-core/src/main/java/com/jme3/math/FastMath.java
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -489,10 +490,8 @@ public static float acos(float fValue) {
if (fValue < 1.0f) {
return (float) Math.acos(fValue);
}

return 0.0f;
}

return PI;
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -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.
Expand All @@ -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();
}
Expand All @@ -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();
}
Expand Down Expand Up @@ -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();
}
Expand All @@ -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();
}
Expand All @@ -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) {
Expand Down Expand Up @@ -1143,5 +1209,4 @@ public static int toMultipleOf(int n, int p) {
return ((n - 1) | (p - 1)) + 1;
}


}
84 changes: 84 additions & 0 deletions jme3-examples/src/main/java/jme3test/math/TestRandomPoints.java
Original file line number Diff line number Diff line change
@@ -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;
}

}