From 81b5c48fb014eb127eef356e5eb40b242898a34d Mon Sep 17 00:00:00 2001 From: Kirill Vainer Date: Sun, 13 Sep 2015 22:11:11 -0400 Subject: [PATCH] unit test: add unit tests for bounds and light filter / sort --- .../jme3/collision/BoundingCollisionTest.java | 166 ++++++++++++++++ .../com/jme3/collision/CollisionUtil.java | 80 ++++++++ .../java/com/jme3/light/LightFilterTest.java | 179 ++++++++++++++++++ .../java/com/jme3/light/LightSortTest.java | 115 +++++++++++ 4 files changed, 540 insertions(+) create mode 100644 jme3-core/src/test/java/com/jme3/collision/BoundingCollisionTest.java create mode 100644 jme3-core/src/test/java/com/jme3/collision/CollisionUtil.java create mode 100644 jme3-core/src/test/java/com/jme3/light/LightFilterTest.java create mode 100644 jme3-core/src/test/java/com/jme3/light/LightSortTest.java diff --git a/jme3-core/src/test/java/com/jme3/collision/BoundingCollisionTest.java b/jme3-core/src/test/java/com/jme3/collision/BoundingCollisionTest.java new file mode 100644 index 000000000..7c3104292 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/collision/BoundingCollisionTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2009-2015 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.collision; + +import static com.jme3.collision.CollisionUtil.*; + +import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingSphere; +import com.jme3.math.FastMath; +import com.jme3.math.Ray; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Quad; +import org.junit.Test; + +/** + * Tests collision detection between bounding volumes. + * + * @author Kirill Vainer + */ +public class BoundingCollisionTest { + + @Test + public void testBoxBoxCollision() { + BoundingBox box1 = new BoundingBox(Vector3f.ZERO, 1, 1, 1); + BoundingBox box2 = new BoundingBox(Vector3f.ZERO, 1, 1, 1); + checkCollision(box1, box2, 1); + + // Put it at the very edge - should still intersect. + box2.setCenter(new Vector3f(2f, 0f, 0f)); + checkCollision(box1, box2, 1); + + // Put it a wee bit farther - no intersection expected + box2.setCenter(new Vector3f(2f + FastMath.ZERO_TOLERANCE, 0, 0)); + checkCollision(box1, box2, 0); + + // Check the corners. + box2.setCenter(new Vector3f(2f, 2f, 2f)); + checkCollision(box1, box2, 1); + + box2.setCenter(new Vector3f(2f, 2f, 2f + FastMath.ZERO_TOLERANCE)); + checkCollision(box1, box2, 0); + } + + @Test + public void testSphereSphereCollision() { + BoundingSphere sphere1 = new BoundingSphere(1, Vector3f.ZERO); + BoundingSphere sphere2 = new BoundingSphere(1, Vector3f.ZERO); + checkCollision(sphere1, sphere2, 1); + + // Put it at the very edge - should still intersect. + sphere2.setCenter(new Vector3f(2f, 0f, 0f)); + checkCollision(sphere1, sphere2, 1); + + // Put it a wee bit farther - no intersection expected + sphere2.setCenter(new Vector3f(2f + FastMath.ZERO_TOLERANCE, 0, 0)); + checkCollision(sphere1, sphere2, 0); + } + + @Test + public void testBoxSphereCollision() { + BoundingBox box1 = new BoundingBox(Vector3f.ZERO, 1, 1, 1); + BoundingSphere sphere2 = new BoundingSphere(1, Vector3f.ZERO); + checkCollision(box1, sphere2, 1); + + // Put it at the very edge - for sphere vs. box, it will not intersect + sphere2.setCenter(new Vector3f(2f, 0f, 0f)); + checkCollision(box1, sphere2, 0); + + // Put it a wee bit closer - should intersect. + sphere2.setCenter(new Vector3f(2f - FastMath.ZERO_TOLERANCE, 0, 0)); + checkCollision(box1, sphere2, 1); + + // Test if the algorithm converts the sphere + // to a box before testing the collision (incorrect) + float sqrt3 = FastMath.sqrt(3); + + sphere2.setCenter(Vector3f.UNIT_XYZ.mult(2)); + sphere2.setRadius(sqrt3); + checkCollision(box1, sphere2, 0); + + // Make it a wee bit larger. + sphere2.setRadius(sqrt3 + FastMath.ZERO_TOLERANCE); + checkCollision(box1, sphere2, 1); + } + + @Test + public void testBoxRayCollision() { + BoundingBox box = new BoundingBox(Vector3f.ZERO, 1, 1, 1); + Ray ray = new Ray(Vector3f.ZERO, Vector3f.UNIT_Z); + + // XXX: seems incorrect, ray inside box should only generate + // one result... + checkCollision(box, ray, 2); + + ray.setOrigin(new Vector3f(0, 0, -5)); + checkCollision(box, ray, 2); + + // XXX: is this right? the ray origin is on the box's side.. + ray.setOrigin(new Vector3f(0, 0, 2f)); + checkCollision(box, ray, 0); + + ray.setOrigin(new Vector3f(0, 0, -2f)); + checkCollision(box, ray, 2); + + // parallel to the edge, touching the side + ray.setOrigin(new Vector3f(0, 1f, -2f)); + checkCollision(box, ray, 2); + + // still parallel, but not touching the side + ray.setOrigin(new Vector3f(0, 1f + FastMath.ZERO_TOLERANCE, -2f)); + checkCollision(box, ray, 0); + } + + @Test + public void testBoxTriangleCollision() { + BoundingBox box = new BoundingBox(Vector3f.ZERO, 1, 1, 1); + Geometry geom = new Geometry("geom", new Quad(1, 1)); + checkCollision(box, geom, 2); // Both triangles intersect + + // The box touches the edges of the triangles. + box.setCenter(new Vector3f(-1f, 0, 0)); + checkCollision(box, geom, 2); + + // Move it slightly farther.. + box.setCenter(new Vector3f(-1f - FastMath.ZERO_TOLERANCE, 0, 0)); + checkCollision(box, geom, 0); + + // Parallel triangle / box side, touching + box.setCenter(new Vector3f(0, 0, -1f)); + checkCollision(box, geom, 2); + + // Not touching + box.setCenter(new Vector3f(0, 0, -1f - FastMath.ZERO_TOLERANCE)); + checkCollision(box, geom, 0); + } +} diff --git a/jme3-core/src/test/java/com/jme3/collision/CollisionUtil.java b/jme3-core/src/test/java/com/jme3/collision/CollisionUtil.java new file mode 100644 index 000000000..43d7b20f0 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/collision/CollisionUtil.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2009-2015 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.collision; + +import com.jme3.bounding.BoundingVolume; + +/** + * Utilities for testing collision. + * + * @author Kirill Vainer + */ +final class CollisionUtil { + + private static void checkCollisionBase(Collidable a, Collidable b, int expected) { + // Test bounding volume methods + if (a instanceof BoundingVolume && b instanceof BoundingVolume) { + BoundingVolume bv1 = (BoundingVolume) a; + BoundingVolume bv2 = (BoundingVolume) b; + assert bv1.intersects(bv2) == (expected != 0); + } + + // Test standard collideWith method + CollisionResults results = new CollisionResults(); + int numCollisions = a.collideWith(b, results); + assert results.size() == numCollisions; + assert numCollisions == expected; + + // force the results to be sorted here.. + results.getClosestCollision(); + + if (results.size() > 0) { + assert results.getCollision(0) == results.getClosestCollision(); + } + if (results.size() == 1) { + assert results.getClosestCollision() == results.getFarthestCollision(); + } + } + + /** + * Tests various collisions between the two collidables and + * the transitive property. + * + * @param a First collidable + * @param b Second collidable + * @param expect Number of expected results + */ + public static void checkCollision(Collidable a, Collidable b, int expected) { + checkCollisionBase(a, b, expected); + checkCollisionBase(b, a, expected); + } +} diff --git a/jme3-core/src/test/java/com/jme3/light/LightFilterTest.java b/jme3-core/src/test/java/com/jme3/light/LightFilterTest.java new file mode 100644 index 000000000..0d1e71988 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/light/LightFilterTest.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2009-2015 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.light; + +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.util.TempVars; +import org.junit.Before; +import org.junit.Test; + +/** + * Test light filtering for various light types. + * + * @author Kirill Vainer + */ +public class LightFilterTest { + + private DefaultLightFilter filter; + private Camera cam; + private Geometry geom; + private LightList list; + + private void checkFilteredLights(int expected) { + geom.updateGeometricState(); + filter.setCamera(cam); // setCamera resets the intersection cache + list.clear(); + filter.filterLights(geom, list); + assert list.size() == expected; + } + + @Before + public void setUp() { + filter = new DefaultLightFilter(); + + cam = new Camera(512, 512); + cam.setFrustumPerspective(45, 1, 1, 1000); + cam.setLocation(Vector3f.ZERO); + cam.lookAtDirection(Vector3f.UNIT_Z, Vector3f.UNIT_Y); + filter.setCamera(cam); + + Box box = new Box(1, 1, 1); + geom = new Geometry("geom", box); + geom.setLocalTranslation(0, 0, 10); + geom.updateGeometricState(); + list = new LightList(geom); + } + + @Test + public void testAmbientFiltering() { + geom.addLight(new AmbientLight()); + checkFilteredLights(1); // Ambient lights must never be filtered + } + + @Test + public void testDirectionalFiltering() { + geom.addLight(new DirectionalLight(Vector3f.UNIT_Y)); + checkFilteredLights(1); // Directional lights must never be filtered + } + + @Test + public void testPointFiltering() { + PointLight pl = new PointLight(Vector3f.ZERO); + geom.addLight(pl); + checkFilteredLights(1); // Infinite point lights must never be filtered + + // Light at origin does not intersect geom which is at Z=10 + pl.setRadius(1); + checkFilteredLights(0); + + // Put it closer to geom, the very edge of the sphere touches the box. + // Still not considered an intersection though. + pl.setPosition(new Vector3f(0, 0, 8f)); + checkFilteredLights(0); + + // And more close - now its an intersection. + pl.setPosition(new Vector3f(0, 0, 8f + FastMath.ZERO_TOLERANCE)); + checkFilteredLights(1); + + // Move the geometry away + geom.move(0, 0, FastMath.ZERO_TOLERANCE); + checkFilteredLights(0); + + // Test if the algorithm converts the sphere + // to a box before testing the collision (incorrect) + float sqrt3 = FastMath.sqrt(3); + + pl.setPosition(new Vector3f(2, 2, 8)); + pl.setRadius(sqrt3); + checkFilteredLights(0); + + // Make it a wee bit larger. + pl.setRadius(sqrt3 + FastMath.ZERO_TOLERANCE); + checkFilteredLights(1); + + // Rotate the camera so it is up, light is outside frustum. + cam.lookAtDirection(Vector3f.UNIT_Y, Vector3f.UNIT_Y); + checkFilteredLights(0); + } + + @Test + public void testSpotFiltering() { + SpotLight sl = new SpotLight(Vector3f.ZERO, Vector3f.UNIT_Z); + sl.setSpotRange(0); + geom.addLight(sl); + checkFilteredLights(1); // Infinite spot lights are only filtered + // if the geometry is outside the infinite cone. + + TempVars vars = TempVars.get(); + try { + // The spot is not touching the near plane of the camera yet, + // should still be culled. + sl.setSpotRange(1f - FastMath.ZERO_TOLERANCE); + assert !sl.intersectsFrustum(cam, vars); + // should be culled from the geometry's PoV + checkFilteredLights(0); + + // Now it touches the near plane. + sl.setSpotRange(1f); + // still culled from the geometry's PoV + checkFilteredLights(0); + assert sl.intersectsFrustum(cam, vars); + } finally { + vars.release(); + } + + // make it barely reach the geometry + sl.setSpotRange(9f); + checkFilteredLights(0); + + // make it reach the geometry (touching its bound) + sl.setSpotRange(9f + FastMath.ZERO_TOLERANCE); + checkFilteredLights(1); + + // rotate the cone a bit so it no longer faces the geom + sl.setDirection(new Vector3f(0.316f, 0, 0.948f).normalizeLocal()); + checkFilteredLights(0); + + // extent the range much farther + sl.setSpotRange(20); + checkFilteredLights(0); + + // Create box of size X=10 (double the extent) + // now, the spot will touch the box. + geom.setMesh(new Box(5, 1, 1)); + checkFilteredLights(1); + } +} diff --git a/jme3-core/src/test/java/com/jme3/light/LightSortTest.java b/jme3-core/src/test/java/com/jme3/light/LightSortTest.java new file mode 100644 index 000000000..593cc9d3e --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/light/LightSortTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2009-2015 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.light; + +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import org.junit.Test; + +/** + * Test light sorting (in the scene graph) for various light types. + * + * @author Kirill Vainer + */ +public class LightSortTest { + + @Test + public void testSimpleSort() { + Geometry g = new Geometry("test", new Mesh()); + LightList list = new LightList(g); + + list.add(new SpotLight(Vector3f.ZERO, Vector3f.UNIT_X)); + list.add(new PointLight(Vector3f.UNIT_X)); + list.add(new DirectionalLight(Vector3f.UNIT_X)); + list.add(new AmbientLight()); + + list.sort(true); + + assert list.get(0) instanceof AmbientLight; // Ambients always first + assert list.get(1) instanceof DirectionalLight; // .. then directionals + assert list.get(2) instanceof SpotLight; // Spot is 0 units away from geom + assert list.get(3) instanceof PointLight; // .. and point is 1 unit away. + } + + @Test + public void testSceneGraphSort() { + Node n = new Node("node"); + Geometry g = new Geometry("geom", new Mesh()); + SpotLight spot = new SpotLight(Vector3f.ZERO, Vector3f.UNIT_X); + PointLight point = new PointLight(Vector3f.UNIT_X); + DirectionalLight directional = new DirectionalLight(Vector3f.UNIT_X); + AmbientLight ambient = new AmbientLight(); + + // Some lights are on the node + n.addLight(spot); + n.addLight(point); + + // .. and some on the geometry. + g.addLight(directional); + g.addLight(ambient); + + n.attachChild(g); + n.updateGeometricState(); + + LightList list = g.getWorldLightList(); + + // check the sorting (when geom is at 0,0,0) + assert list.get(0) instanceof AmbientLight; + assert list.get(1) instanceof DirectionalLight; + assert list.get(2) instanceof SpotLight; + assert list.get(3) instanceof PointLight; + + // move the geometry closer to the point light + g.setLocalTranslation(Vector3f.UNIT_X); + n.updateGeometricState(); + + assert list.get(0) instanceof AmbientLight; + assert list.get(1) instanceof DirectionalLight; + assert list.get(2) instanceof PointLight; + assert list.get(3) instanceof SpotLight; + + // now move the point light away from the geometry + // and the spot light closer + + // XXX: doesn't work! jME can't detect that the light moved! +// point.setPosition(Vector3f.ZERO); +// spot.setPosition(Vector3f.UNIT_X); +// n.updateGeometricState(); +// +// assert list.get(0) instanceof AmbientLight; +// assert list.get(1) instanceof DirectionalLight; +// assert list.get(2) instanceof SpotLight; +// assert list.get(3) instanceof PointLight; + } +}