Bläddra i källkod

unit test: add unit tests for bounds and light filter / sort

Kirill Vainer 10 år sedan
förälder
incheckning
81b5c48fb0

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

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

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

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