diff --git a/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java b/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java index b99d4d7351..6b0e023b34 100644 --- a/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java +++ b/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,6 +46,7 @@ import java.io.IOException; import java.nio.FloatBuffer; //import com.jme.scene.TriMesh; +import java.util.Objects; /** * BoundingBox describes a bounding volume as an axis-aligned box. @@ -587,6 +588,76 @@ public BoundingVolume clone(BoundingVolume store) { return rVal; } + /** + * Tests for exact equality with the argument, distinguishing -0 from 0. If + * {@code other} is null, false is returned. Either way, the current + * instance is unaffected. + * + * @param other the object to compare (may be null, unaffected) + * @return true if {@code this} and {@code other} have identical values, + * otherwise false + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof BoundingBox)) { + return false; + } + + if (this == other) { + return true; + } + + BoundingBox otherBoundingBox = (BoundingBox) other; + if (Float.compare(xExtent, otherBoundingBox.xExtent) != 0) { + return false; + } else if (Float.compare(yExtent, otherBoundingBox.yExtent) != 0) { + return false; + } else if (Float.compare(zExtent, otherBoundingBox.zExtent) != 0) { + return false; + } else { + return super.equals(otherBoundingBox); + } + } + + /** + * Returns a hash code. If two bounding boxes have identical values, they + * will have the same hash code. The current instance is unaffected. + * + * @return a 32-bit value for use in hashing + */ + @Override + public int hashCode() { + int hash = Objects.hash(xExtent, yExtent, zExtent); + hash = 59 * hash + super.hashCode(); + + return hash; + } + + /** + * Tests for approximate equality with the specified bounding box, using the + * specified tolerance. If {@code other} is null, false is returned. Either + * way, the current instance is unaffected. + * + * @param aabb the bounding box to compare (unaffected) or null for none + * @param epsilon the tolerance for each component + * @return true if all components are within tolerance, otherwise false + */ + public boolean isSimilar(BoundingBox aabb, float epsilon) { + if (aabb == null) { + return false; + } else if (Float.compare(Math.abs(aabb.xExtent - xExtent), epsilon) > 0) { + return false; + } else if (Float.compare(Math.abs(aabb.yExtent - yExtent), epsilon) > 0) { + return false; + } else if (Float.compare(Math.abs(aabb.zExtent - zExtent), epsilon) > 0) { + return false; + } else if (!center.isSimilar(aabb.getCenter(), epsilon)) { + return false; + } + // The checkPlane field is ignored. + return true; + } + /** * toString returns the string representation of this object. * The form is: "[Center: vector xExtent: X.XX yExtent: Y.YY zExtent: 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 5b46846a23..67cf7263f8 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-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,6 +43,7 @@ import com.jme3.util.TempVars; import java.io.IOException; import java.nio.FloatBuffer; +import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; @@ -651,6 +652,68 @@ public BoundingVolume clone(BoundingVolume store) { return new BoundingSphere(radius, center.clone()); } + /** + * Tests for exact equality with the argument, distinguishing -0 from 0. If + * {@code other} is null, false is returned. Either way, the current + * instance is unaffected. + * + * @param other the object to compare (may be null, unaffected) + * @return true if {@code this} and {@code other} have identical values, + * otherwise false + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof BoundingSphere)) { + return false; + } + + if (this == other) { + return true; + } + + BoundingSphere otherBoundingSphere = (BoundingSphere) other; + if (Float.compare(radius, otherBoundingSphere.getRadius()) != 0) { + return false; + } else { + return super.equals(otherBoundingSphere); + } + } + + /** + * Returns a hash code. If two bounding boxes have identical values, they + * will have the same hash code. The current instance is unaffected. + * + * @return a 32-bit value for use in hashing + */ + @Override + public int hashCode() { + int hash = Objects.hash(radius); + hash = 59 * hash + super.hashCode(); + + return hash; + } + + /** + * Tests for approximate equality with the specified bounding sphere, using + * the specified tolerance. If {@code other} is null, false is returned. + * Either way, the current instance is unaffected. + * + * @param sphere the bounding sphere to compare (unaffected) or null for none + * @param epsilon the tolerance for each component + * @return true if all components are within tolerance, otherwise false + */ + public boolean isSimilar(BoundingSphere sphere, float epsilon) { + if (sphere == null) { + return false; + } else if (Float.compare(Math.abs(sphere.getRadius() - radius), epsilon) > 0) { + return false; + } else if (!center.isSimilar(sphere.getCenter(), epsilon)) { + return false; + } + // The checkPlane field is ignored. + return true; + } + /** * toString returns the string representation of this object. * The form is: "Radius: RRR.SSSS Center: vector". diff --git a/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java b/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java index 88cd4148ef..3a80764910 100644 --- a/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java +++ b/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,6 +40,7 @@ import com.jme3.util.TempVars; import java.io.IOException; import java.nio.FloatBuffer; +import java.util.Objects; /** * BoundingVolume defines an interface for dealing with @@ -180,6 +181,48 @@ public final BoundingVolume transform(Transform trans) { */ public abstract BoundingVolume clone(BoundingVolume store); + /** + * Tests for exact equality with the argument, distinguishing -0 from 0. If + * {@code other} is null, false is returned. Either way, the current + * instance is unaffected. + * + * @param other the object to compare (may be null, unaffected) + * @return true if {@code this} and {@code other} have identical values, + * otherwise false + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof BoundingVolume)) { + return false; + } + + if (this == other) { + return true; + } + + BoundingVolume otherBoundingVolume = (BoundingVolume) other; + if (!center.equals(otherBoundingVolume.getCenter())) { + return false; + } + // The checkPlane field is ignored. + + return true; + } + + /** + * Returns a hash code. If two bounding volumes have identical values, they + * will have the same hash code. The current instance is unaffected. + * + * @return a 32-bit value for use in hashing + */ + @Override + public int hashCode() { + int hash = Objects.hash(center); + // The checkPlane field is ignored. + + return hash; + } + public final Vector3f getCenter() { return center; } diff --git a/jme3-core/src/test/java/com/jme3/bounding/TestBoundingBox.java b/jme3-core/src/test/java/com/jme3/bounding/TestBoundingBox.java new file mode 100644 index 0000000000..7a48e553b6 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/bounding/TestBoundingBox.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2024 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 BoundingBox class. + * + * @author Stephen Gold + */ +public class TestBoundingBox { + /** + * Verify that equals() behaves as expected. + */ + @Test + public void testEquals() { + BoundingBox bb1 = new BoundingBox(new Vector3f(3f, 4f, 5f), 0f, 1f, 2f); + BoundingBox bb2 + = new BoundingBox(new Vector3f(3f, 4f, 5f), -0f, 1f, 2f); + + BoundingBox bb3 = new BoundingBox(new Vector3f(3f, 0f, 2f), 9f, 8f, 7f); + BoundingBox bb4 + = new BoundingBox(new Vector3f(3f, -0f, 2f), 9f, 8f, 7f); + + BoundingBox bb5 = new BoundingBox(new Vector3f(4f, 5f, 6f), 9f, 8f, 7f); + BoundingBox bb6 = (BoundingBox) bb5.clone(); + bb6.setCheckPlane(1); + + // Clones are equal to their base instances: + Assert.assertEquals(bb1, bb1.clone()); + Assert.assertEquals(bb2, bb2.clone()); + Assert.assertEquals(bb3, bb3.clone()); + Assert.assertEquals(bb4, bb4.clone()); + Assert.assertEquals(bb5, bb5.clone()); + Assert.assertEquals(bb6, bb6.clone()); + + Assert.assertNotEquals(bb1, bb2); // because their extents differ + Assert.assertNotEquals(bb3, bb4); // because their centers differ + Assert.assertEquals(bb5, bb6); // because check planes are ignored + } + + /** + * Verify that isSimilar() behaves as expected. + */ + @Test + public void testIsSimilar() { + BoundingBox bb1 = new BoundingBox(new Vector3f(3f, 4f, 5f), 0f, 1f, 2f); + BoundingBox bb2 + = new BoundingBox(new Vector3f(3f, 4f, 5f), 0f, 1.1f, 2f); + + BoundingBox bb3 = new BoundingBox(new Vector3f(3f, 4f, 2f), 9f, 8f, 7f); + BoundingBox bb4 + = new BoundingBox(new Vector3f(3f, 3.9f, 2f), 9f, 8f, 7f); + + BoundingBox bb5 = new BoundingBox(new Vector3f(4f, 5f, 6f), 9f, 8f, 7f); + BoundingBox bb6 = (BoundingBox) bb5.clone(); + bb6.setCheckPlane(1); + + Assert.assertFalse(bb1.isSimilar(bb2, 0.09999f)); + Assert.assertTrue(bb1.isSimilar(bb2, 0.10001f)); + + Assert.assertFalse(bb3.isSimilar(bb4, 0.09999f)); + Assert.assertTrue(bb3.isSimilar(bb4, 0.10001f)); + + Assert.assertTrue(bb5.isSimilar(bb6, 0f)); // check planes are ignored + } +} diff --git a/jme3-core/src/test/java/com/jme3/bounding/TestBoundingSphere.java b/jme3-core/src/test/java/com/jme3/bounding/TestBoundingSphere.java index 7f75ec6550..4c0e760bc2 100644 --- a/jme3-core/src/test/java/com/jme3/bounding/TestBoundingSphere.java +++ b/jme3-core/src/test/java/com/jme3/bounding/TestBoundingSphere.java @@ -41,6 +41,57 @@ * @author Stephen Gold */ public class TestBoundingSphere { + /** + * Verify that equals() behaves as expected. + */ + @Test + public void testEquals() { + BoundingSphere bs1 = new BoundingSphere(0f, new Vector3f(3f, 4f, 5f)); + BoundingSphere bs2 = new BoundingSphere(-0f, new Vector3f(3f, 4f, 5f)); + + BoundingSphere bs3 = new BoundingSphere(1f, new Vector3f(3f, 0f, 2f)); + BoundingSphere bs4 = new BoundingSphere(1f, new Vector3f(3f, -0f, 2f)); + + BoundingSphere bs5 = new BoundingSphere(2f, new Vector3f(4f, 5f, 6f)); + BoundingSphere bs6 = (BoundingSphere) bs5.clone(); + bs6.setCheckPlane(1); + + // Clones are equal to their base instances: + Assert.assertEquals(bs1, bs1.clone()); + Assert.assertEquals(bs2, bs2.clone()); + Assert.assertEquals(bs3, bs3.clone()); + Assert.assertEquals(bs4, bs4.clone()); + Assert.assertEquals(bs5, bs5.clone()); + Assert.assertEquals(bs6, bs6.clone()); + + Assert.assertNotEquals(bs1, bs2); // because their radii differ + Assert.assertNotEquals(bs3, bs4); // because their centers differ + Assert.assertEquals(bs5, bs6); // because check planes are ignored + } + + /** + * Verify that isSimilar() behaves as expected. + */ + @Test + public void testIsSimilar() { + BoundingSphere bs1 = new BoundingSphere(0f, new Vector3f(3f, 4f, 5f)); + BoundingSphere bs2 = new BoundingSphere(0.1f, new Vector3f(3f, 4f, 5f)); + + BoundingSphere bs3 = new BoundingSphere(1f, new Vector3f(3f, 4f, 2f)); + BoundingSphere bs4 = new BoundingSphere(1f, new Vector3f(3f, 3.9f, 2f)); + + BoundingSphere bs5 = new BoundingSphere(2f, new Vector3f(4f, 5f, 6f)); + BoundingSphere bs6 = (BoundingSphere) bs5.clone(); + bs6.setCheckPlane(1); + + Assert.assertFalse(bs1.isSimilar(bs2, 0.09999f)); + Assert.assertTrue(bs1.isSimilar(bs2, 0.10001f)); + + Assert.assertFalse(bs3.isSimilar(bs4, 0.09999f)); + Assert.assertTrue(bs3.isSimilar(bs4, 0.10001f)); + + Assert.assertTrue(bs5.isSimilar(bs6, 0f)); // check planes are ignored + } /** * Verify that an infinite bounding sphere can be merged with a very