Forráskód Böngészése

testcase and fix for issue #1459 (BoundingSphere merge yields NaNs) (#1465)

* testcase and fix for issue #1459 (BoundingSphere merge yields NaNs)

* Vector3f: fix overflow/underflow bugs in length() and distance() methods
Stephen Gold 4 éve
szülő
commit
6b829c69e5

+ 2 - 2
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 @@ public class BoundingSphere extends BoundingVolume {
         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 {

+ 25 - 3
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 final class Vector3f implements Savable, Cloneable, java.io.Serializable
      * @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 final class Vector3f implements Savable, Cloneable, java.io.Serializable
      * @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;
     }
 
     /**

+ 66 - 0
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));
+    }
+}

+ 19 - 5
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 class Vector3fTest {
         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 class Vector3fTest {
 
     @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