diff --git a/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java b/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java index 60947d7eac..d643598848 100644 --- a/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java +++ b/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -615,7 +615,7 @@ private BoundingVolume merge(float temp_radius, Vector3f temp_center, if (rCenter == null) { rVal.setCenter(rCenter = new Vector3f()); } - if (length > RADIUS_EPSILON) { + if (length > RADIUS_EPSILON && Float.isFinite(length)) { float coeff = (length + radiusDiff) / (2.0f * length); rCenter.set(center.addLocal(diff.multLocal(coeff))); } else { diff --git a/jme3-core/src/main/java/com/jme3/math/Vector3f.java b/jme3-core/src/main/java/com/jme3/math/Vector3f.java index c1d52b40a2..4a954f4887 100644 --- a/jme3-core/src/main/java/com/jme3/math/Vector3f.java +++ b/jme3-core/src/main/java/com/jme3/math/Vector3f.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -435,7 +435,18 @@ public boolean isUnitVector() { * @return the length or magnitude of the vector. */ public float length() { - return FastMath.sqrt(lengthSquared()); + /* + * Use double-precision arithmetic to reduce the chance of overflow + * (when lengthSquared > Float.MAX_VALUE) or underflow (when + * lengthSquared is < Float.MIN_VALUE). + */ + double xx = x; + double yy = y; + double zz = z; + double lengthSquared = xx * xx + yy * yy + zz * zz; + float result = (float) Math.sqrt(lengthSquared); + + return result; } /** @@ -470,7 +481,18 @@ public float distanceSquared(Vector3f v) { * @return the distance between the two vectors. */ public float distance(Vector3f v) { - return FastMath.sqrt(distanceSquared(v)); + /* + * Use double-precision arithmetic to reduce the chance of overflow + * (when distanceSquared > Float.MAX_VALUE) or underflow (when + * distanceSquared is < Float.MIN_VALUE). + */ + double dx = x - v.x; + double dy = y - v.y; + double dz = z - v.z; + double distanceSquared = dx * dx + dy * dy + dz * dz; + float result = (float) Math.sqrt(distanceSquared); + + return result; } /** diff --git a/jme3-core/src/test/java/com/jme3/bounding/TestBoundingSphere.java b/jme3-core/src/test/java/com/jme3/bounding/TestBoundingSphere.java new file mode 100644 index 0000000000..7f75ec6550 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/bounding/TestBoundingSphere.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bounding; + +import com.jme3.math.Vector3f; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test cases for the BoundingSphere class. + * + * @author Stephen Gold + */ +public class TestBoundingSphere { + + /** + * Verify that an infinite bounding sphere can be merged with a very + * eccentric bounding box without producing NaNs. This was issue #1459 at + * GitHub. + */ + @Test + public void testIssue1459() { + Vector3f boxCenter = new Vector3f(-92f, 3.3194322e29f, 674.89886f); + BoundingBox boundingBox = new BoundingBox(boxCenter, + 1.0685959f, 3.3194322e29f, 2.705017f); + + Vector3f sphCenter = new Vector3f(0f, 0f, 0f); + float radius = Float.POSITIVE_INFINITY; + BoundingSphere boundingSphere = new BoundingSphere(radius, sphCenter); + + boundingSphere.mergeLocal(boundingBox); + + Vector3f copyCenter = new Vector3f(); + boundingSphere.getCenter(copyCenter); + Assert.assertTrue(Vector3f.isValidVector(copyCenter)); + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/Vector3fTest.java b/jme3-core/src/test/java/com/jme3/math/Vector3fTest.java index 136f634745..c03719746e 100644 --- a/jme3-core/src/test/java/com/jme3/math/Vector3fTest.java +++ b/jme3-core/src/test/java/com/jme3/math/Vector3fTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -235,12 +235,16 @@ public void testCrossLocal() { assertEquals(-0.0f, retval.z, 0.0f); } + /** + * Verify that distance() doesn't always overflow when distanceSquared > + * Float.MAX_VALUE . + */ @Test public void testDistance() { final Vector3f target = new Vector3f(3.86405e+18f, 3.02146e+23f, 0.171875f); final Vector3f v = new Vector3f(-2.0f, -1.61503e+19f, 0.171875f); - - assertEquals(Float.POSITIVE_INFINITY, target.distance(v), 0.0f); + + assertEquals(3.0216215e23f, target.distance(v), 0f); } @Test @@ -540,11 +544,21 @@ public void testIsValidVector() { @Test public void testLength() { - assertEquals(0.0f, new Vector3f(1.88079e-37f, 0.0f, 1.55077e-36f).length(), 0.0f); + /* + * avoid underflow when lengthSquared is < Float.MIN_VALUE + */ + assertEquals(1.5621336e-36f, + new Vector3f(1.88079e-37f, 0.0f, 1.55077e-36f).length(), 0f); + assertEquals(Float.NaN, new Vector3f(Float.NaN, 0.0f, 1.55077e-36f).length(), 0.0f); assertEquals(Float.POSITIVE_INFINITY, new Vector3f(Float.POSITIVE_INFINITY, 0.0f, 1.0f).length(), 0.0f); + assertEquals(4.0124f, new Vector3f(1.9f, 3.2f, 1.5f).length(), 0.001f); - assertEquals(Float.POSITIVE_INFINITY, new Vector3f(1.8e37f, 1.8e37f, 1.5e36f).length(), 0.0f); + /* + * avoid overflow when lengthSquared > Float.MAX_VALUE + */ + assertEquals(2.5499999e37f, + new Vector3f(1.8e37f, 1.8e37f, 1.5e36f).length(), 0.0f); } @Test