Browse Source

Adds support for light porbe blending and Oriented box light probes with corrected parallax

Nehon 7 years ago
parent
commit
83aef82d92
23 changed files with 1153 additions and 486 deletions
  1. 10 0
      jme3-core/src/main/java/com/jme3/bounding/Intersection.java
  2. 4 9
      jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java
  3. 21 0
      jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java
  4. 22 8
      jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java
  5. 7 3
      jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java
  6. 114 63
      jme3-core/src/main/java/com/jme3/light/LightProbe.java
  7. 0 214
      jme3-core/src/main/java/com/jme3/light/LightProbeBlendingProcessor.java
  8. 249 0
      jme3-core/src/main/java/com/jme3/light/OrientedBoxProbeArea.java
  9. 0 107
      jme3-core/src/main/java/com/jme3/light/PoiLightProbeLightFilter.java
  10. 1 6
      jme3-core/src/main/java/com/jme3/light/PointLight.java
  11. 33 0
      jme3-core/src/main/java/com/jme3/light/ProbeArea.java
  12. 102 0
      jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java
  13. 77 0
      jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java
  14. 44 28
      jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java
  15. 26 16
      jme3-core/src/main/java/com/jme3/scene/debug/WireFrustum.java
  16. 133 26
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag
  17. 4 0
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md
  18. 0 0
      jme3-examples/src/main/java/jme3test/collision/Main.java
  19. 0 0
      jme3-examples/src/main/java/jme3test/light/DlsfError.java
  20. 1 1
      jme3-examples/src/main/java/jme3test/light/TestConeVSFrustum.java
  21. 301 0
      jme3-examples/src/main/java/jme3test/light/TestObbVsBounds.java
  22. 2 2
      jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java
  23. 2 3
      jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java

+ 10 - 0
jme3-core/src/main/java/com/jme3/bounding/Intersection.java

@@ -34,6 +34,7 @@ package com.jme3.bounding;
 import com.jme3.math.FastMath;
 import com.jme3.math.Plane;
 import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
 import com.jme3.util.TempVars;
 import static java.lang.Math.max;
 import static java.lang.Math.min;
@@ -107,6 +108,15 @@ public final class Intersection {
         }
     }
 
+    public static boolean intersect(Camera camera, Vector3f center,float radius){
+        for (int i = 5; i >= 0; i--) {
+            if (camera.getWorldPlane(i).pseudoDistance(center) <= -radius) {
+                return false;
+            }
+        }
+        return true;
+    }
+
 //    private boolean axisTest(float a, float b, float fa, float fb, Vector3f v0, Vector3f v1, )
 //    private boolean axisTestX01(float a, float b, float fa, float fb,
 //                             Vector3f center, Vector3f ext,

+ 4 - 9
jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java

@@ -38,14 +38,9 @@ import com.jme3.environment.util.EnvMapUtils;
 import com.jme3.light.LightProbe;
 import com.jme3.math.ColorRGBA;
 import com.jme3.math.Vector3f;
-import com.jme3.renderer.Camera;
-import com.jme3.renderer.RenderManager;
-import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.*;
 import com.jme3.scene.Spatial;
-import com.jme3.texture.FrameBuffer;
-import com.jme3.texture.Image;
-import com.jme3.texture.Texture2D;
-import com.jme3.texture.TextureCubeMap;
+import com.jme3.texture.*;
 import com.jme3.texture.image.ColorSpace;
 import com.jme3.util.BufferUtils;
 import com.jme3.util.MipMapGenerator;
@@ -119,7 +114,7 @@ public class EnvironmentCamera extends BaseAppState {
     private final List<SnapshotJob> jobs = new ArrayList<SnapshotJob>();
 
     /**
-     * Creates an EnvironmentCamera with a size of 128
+     * Creates an EnvironmentCamera with a size of 256
      */
     public EnvironmentCamera() {
     }
@@ -322,7 +317,7 @@ public class EnvironmentCamera extends BaseAppState {
         final Camera offCamera = new Camera(mapSize, mapSize);
         offCamera.setLocation(worldPos);
         offCamera.setAxes(axisX, axisY, axisZ);
-        offCamera.setFrustumPerspective(90f, 1f, 1, 1000);
+        offCamera.setFrustumPerspective(90f, 1f, 0.1f, 1000);
         offCamera.setLocation(position);
         return offCamera;
     }

+ 21 - 0
jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java

@@ -32,6 +32,7 @@
 package com.jme3.environment;
 
 import com.jme3.app.Application;
+import com.jme3.asset.AssetManager;
 import com.jme3.environment.generation.*;
 import com.jme3.environment.util.EnvMapUtils;
 import com.jme3.light.LightProbe;
@@ -204,6 +205,26 @@ public class LightProbeFactory {
         }
     }
 
+    /**
+     * For debuging porpose only
+     * Will return a Node meant to be added to a GUI presenting the 2 cube maps in a cross pattern with all the mip maps.
+     *
+     * @param manager the asset manager
+     * @return a debug node
+     */
+    public static Node getDebugGui(AssetManager manager, LightProbe probe) {
+        if (!probe.isReady()) {
+            throw new UnsupportedOperationException("This EnvProbe is not ready yet, try to test isReady()");
+        }
+
+        Node debugNode = new Node("debug gui probe");
+        Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(probe.getPrefilteredEnvMap(), manager);
+        debugNode.attachChild(debugPfemCm);
+        debugPfemCm.setLocalTranslation(520, 0, 0);
+
+        return debugNode;
+    }
+
     /**
      * An inner class to keep the state of a generation process
      */

+ 22 - 8
jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java

@@ -34,9 +34,8 @@ package com.jme3.environment.util;
 import com.jme3.app.Application;
 import com.jme3.app.state.BaseAppState;
 import com.jme3.bounding.BoundingSphere;
+import com.jme3.light.*;
 import com.jme3.material.Material;
-import com.jme3.light.LightProbe;
-import com.jme3.light.Light;
 import com.jme3.renderer.RenderManager;
 import com.jme3.scene.Geometry;
 import com.jme3.scene.Node;
@@ -68,7 +67,7 @@ public class LightsDebugState extends BaseAppState {
     @Override
     protected void initialize(Application app) {
         debugNode = new Node("Environment debug Node");
-        Sphere s = new Sphere(16, 16, 1);
+        Sphere s = new Sphere(16, 16, 0.15f);
         debugGeom = new Geometry("debugEnvProbe", s);
         debugMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/reflect.j3md");
         debugGeom.setMaterial(debugMaterial);
@@ -80,6 +79,16 @@ public class LightsDebugState extends BaseAppState {
 
     @Override
     public void update(float tpf) {
+        if(!isEnabled()){
+            return;
+        }
+        updateLights(scene);
+        debugNode.updateLogicalState(tpf);
+        debugNode.updateGeometricState();
+        cleanProbes();
+    }
+
+    public void updateLights(Spatial scene) {
         for (Light light : scene.getWorldLightList()) {
             switch (light.getType()) {
 
@@ -101,16 +110,18 @@ public class LightsDebugState extends BaseAppState {
                         m.setTexture("CubeMap", probe.getPrefilteredEnvMap());
                     }
                     n.setLocalTranslation(probe.getPosition());
-                    n.getChild(1).setLocalScale(((BoundingSphere) probe.getBounds()).getRadius());
+                    n.getChild(1).setLocalScale(probe.getArea().getRadius());
                     break;
                 default:
                     break;
             }
         }
-        debugNode.updateLogicalState(tpf);
-        debugNode.updateGeometricState();
-        cleanProbes();
-
+        if( scene instanceof Node){
+            Node n = (Node)scene;
+            for (Spatial spatial : n.getChildren()) {
+                updateLights(spatial);
+            }
+        }
     }
 
     /**
@@ -138,6 +149,9 @@ public class LightsDebugState extends BaseAppState {
 
     @Override
     public void render(RenderManager rm) {
+        if(!isEnabled()){
+            return;
+        }
         rm.renderScene(debugNode, getApplication().getViewPort());
     }
 

+ 7 - 3
jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java

@@ -43,10 +43,10 @@ public final class DefaultLightFilter implements LightFilter {
 
     private Camera camera;
     private final HashSet<Light> processedLights = new HashSet<Light>();
-    private final LightProbeBlendingStrategy probeBlendStrat;
+    private LightProbeBlendingStrategy probeBlendStrat;
 
     public DefaultLightFilter() {
-        probeBlendStrat = new BasicProbeBlendingStrategy();
+        probeBlendStrat = new WeightedProbeBlendingStrategy();
     }
 
     public DefaultLightFilter(LightProbeBlendingStrategy probeBlendStrat) {
@@ -113,5 +113,9 @@ public final class DefaultLightFilter implements LightFilter {
             vars.release();
         }
     }
-    
+
+    public void setLightProbeBlendingStrategy(LightProbeBlendingStrategy strategy){
+        probeBlendStrat = strategy;
+    }
+
 }

+ 114 - 63
jme3-core/src/main/java/com/jme3/light/LightProbe.java

@@ -31,24 +31,16 @@
  */
 package com.jme3.light;
 
-import com.jme3.asset.AssetManager;
-import com.jme3.bounding.BoundingBox;
-import com.jme3.bounding.BoundingSphere;
-import com.jme3.bounding.BoundingVolume;
+import com.jme3.bounding.*;
 import com.jme3.environment.EnvironmentCamera;
 import com.jme3.environment.LightProbeFactory;
-import com.jme3.environment.util.EnvMapUtils;
-import com.jme3.export.InputCapsule;
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.export.OutputCapsule;
-import com.jme3.export.Savable;
-import com.jme3.math.Vector3f;
+import com.jme3.export.*;
+import com.jme3.math.*;
 import com.jme3.renderer.Camera;
-import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
 import com.jme3.texture.TextureCubeMap;
 import com.jme3.util.TempVars;
+
 import java.io.IOException;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -56,7 +48,7 @@ import java.util.logging.Logger;
 /**
  * A LightProbe is not exactly a light. It holds environment map information used for Image Based Lighting.
  * This is used for indirect lighting in the Physically Based Rendering pipeline.
- * 
+ *
  * A light probe has a position in world space. This is the position from where the Environment Map are rendered.
  * There are two environment data structure  held by the LightProbe :
  * - The irradiance spherical harmonics factors (used for indirect diffuse lighting in the PBR pipeline).
@@ -64,10 +56,10 @@ import java.util.logging.Logger;
  * Note that when instantiating the LightProbe, both of those structures are null.
  * To compute them see {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)}
  * and {@link EnvironmentCamera}.
- * 
- * The light probe has an area of effect that is a bounding volume centered on its position. (for now only Bounding spheres are supported).
- * 
- * A LightProbe will only be taken into account when it's marked as ready. 
+ *
+ * The light probe has an area of effect centered on its position. It can have a Spherical area or an Oriented Box area
+ *
+ * A LightProbe will only be taken into account when it's marked as ready and enabled.
  * A light probe is ready when it has valid environment map data set.
  * Note that you should never call setReady yourself.
  *
@@ -78,20 +70,25 @@ import java.util.logging.Logger;
 public class LightProbe extends Light implements Savable {
 
     private static final Logger logger = Logger.getLogger(LightProbe.class.getName());
+    public static final Matrix4f FALLBACK_MATRIX = new Matrix4f(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1);
 
     private Vector3f[] shCoeffs;
     private TextureCubeMap prefilteredEnvMap;
-    private BoundingVolume bounds = new BoundingSphere(1.0f, Vector3f.ZERO);
+    private ProbeArea area = new SphereProbeArea(Vector3f.ZERO, 1.0f);
     private boolean ready = false;
     private Vector3f position = new Vector3f();
-    private Node debugNode;
     private int nbMipMaps;
 
+    public enum AreaType{
+        Spherical,
+        OrientedBox
+    }
+
     /**
-     * Empty constructor used for serialization. 
+     * Empty constructor used for serialization.
      * You should never call it, use {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)} instead
      */
-    public LightProbe() {        
+    public LightProbe() {
     }
 
     /**
@@ -104,13 +101,59 @@ public class LightProbe extends Light implements Savable {
     }
 
     /**
-     * Sets the prefiltered environment map 
-     * @param prefileteredEnvMap the prefiltered environment map 
+     * Sets the prefiltered environment map
+     * @param prefileteredEnvMap the prefiltered environment map
      */
     public void setPrefilteredMap(TextureCubeMap prefileteredEnvMap) {
         this.prefilteredEnvMap = prefileteredEnvMap;
     }
 
+    /**
+     * Returns the data to send to the shader.
+     * This is a column major matrix that is not a classic transform matrix, it's laid out in a particular way
+     //   3x3 rot mat|
+     //      0  1  2 |  3
+     // 0 | ax bx cx | px | )
+     // 1 | ay by cy | py | probe position
+     // 2 | az bz cz | pz | )
+     // --|----------|
+     // 3 | sx sy sz   sp | -> 1/probe radius + nbMipMaps
+     //    --scale--
+     * <p>
+     * (ax, ay, az) is the pitch rotation axis
+     * (bx, by, bz) is the yaw rotation axis
+     * (cx, cy, cz) is the roll rotation axis
+     * Like in a standard 3x3 rotation matrix.
+     * It's also the valid rotation matrix of the probe in world space.
+     * Note that for the Spherical Probe area this part is a 3x3 identity matrix.
+     * <p>
+     * (px, py, pz) is the position of the center of the probe in world space
+     * Like in a valid 4x4 transform matrix.
+     * <p>
+     * (sx, sy, sy) is the extent of the probe ( the scale )
+     * In a standard transform matrix the scale is applied to the rotation matrix part.
+     * In the shader we need the rotation and the scale to be separated, doing this avoid to extract
+     * the scale from a classic transform matrix in the shader
+     * <p>
+     * (sp) is a special entry, it contains the packed number of mip maps of the probe and the inverse radius for the probe.
+     * since the inverse radius in lower than 1, it's packed in the decimal part of the float.
+     * The number of mip maps is packed in the integer part of the float.
+     * (ie: for 6 mip maps and a radius of 3, sp= 6.3333333)
+     * <p>
+     * The radius is obvious for a SphereProbeArea,
+     * but in the case of a OrientedBoxProbeArea it's the max of the extent vector's components.
+     */
+    public Matrix4f getUniformMatrix(){
+
+        Matrix4f mat = area.getUniformMatrix();
+
+        // setting the (sp) entry of the matrix
+        mat.m33 = nbMipMaps + 1f / area.getRadius();
+
+        return mat;
+    }
+
+
     @Override
     public void write(JmeExporter ex) throws IOException {
         super.write(ex);
@@ -118,7 +161,7 @@ public class LightProbe extends Light implements Savable {
         oc.write(shCoeffs, "shCoeffs", null);
         oc.write(prefilteredEnvMap, "prefilteredEnvMap", null);
         oc.write(position, "position", null);
-        oc.write(bounds, "bounds", new BoundingSphere(1.0f, Vector3f.ZERO));
+        oc.write(area, "area", new SphereProbeArea(Vector3f.ZERO, 1.0f));
         oc.write(ready, "ready", false);
         oc.write(nbMipMaps, "nbMipMaps", 0);
     }
@@ -127,10 +170,16 @@ public class LightProbe extends Light implements Savable {
     public void read(JmeImporter im) throws IOException {
         super.read(im);
         InputCapsule ic = im.getCapsule(this);
-        
+
         prefilteredEnvMap = (TextureCubeMap) ic.readSavable("prefilteredEnvMap", null);
         position = (Vector3f) ic.readSavable("position", null);
-        bounds = (BoundingVolume) ic.readSavable("bounds", new BoundingSphere(1.0f, Vector3f.ZERO));
+        area = (ProbeArea)ic.readSavable("area", null);
+        if(area == null) {
+            // retro compat
+            BoundingSphere bounds = (BoundingSphere) ic.readSavable("bounds", new BoundingSphere(1.0f, Vector3f.ZERO));
+            area = new SphereProbeArea(bounds.getCenter(), bounds.getRadius());
+        }
+        area.setCenter(position);
         nbMipMaps = ic.readInt("nbMipMaps", 0);
         ready = ic.readBoolean("ready", false);
 
@@ -146,25 +195,49 @@ public class LightProbe extends Light implements Savable {
         }
     }
 
+
     /**
      * returns the bounding volume of this LightProbe
      * @return a bounding volume.
+     * @deprecated use {@link LightProbe#getArea()}
      */
+    @Deprecated
     public BoundingVolume getBounds() {
-        return bounds;
+        return new BoundingSphere(((SphereProbeArea)area).getRadius(), ((SphereProbeArea)area).getCenter());
     }
-    
+
     /**
      * Sets the bounds of this LightProbe
-     * Note that for now only BoundingSphere is supported and this method will 
+     * Note that for now only BoundingSphere is supported and this method will
      * throw an UnsupportedOperationException with any other BoundingVolume type
      * @param bounds the bounds of the LightProbe
+     * @deprecated
      */
+    @Deprecated
     public void setBounds(BoundingVolume bounds) {
-        if( bounds.getType()!= BoundingVolume.Type.Sphere){
-            throw new UnsupportedOperationException("For not only BoundingSphere are suported for LightProbe");
+    }
+
+    public ProbeArea getArea() {
+        return area;
+    }
+
+    public void setAreaType(AreaType type){
+        switch (type){
+            case Spherical:
+                area = new SphereProbeArea(Vector3f.ZERO, 1.0f);
+                break;
+            case OrientedBox:
+                area = new OrientedBoxProbeArea(new Transform());
+                area.setCenter(position);
+                break;
+        }
+    }
+
+    public AreaType getAreaType(){
+        if(area instanceof SphereProbeArea){
+            return AreaType.Spherical;
         }
-        this.bounds = bounds;
+        return AreaType.OrientedBox;
     }
 
     /**
@@ -186,27 +259,6 @@ public class LightProbe extends Light implements Savable {
         this.ready = ready;
     }
 
-    /**
-     * For debuging porpose only
-     * Will return a Node meant to be added to a GUI presenting the 2 cube maps in a cross pattern with all the mip maps.
-     * 
-     * @param manager the asset manager
-     * @return a debug node
-     */
-    public Node getDebugGui(AssetManager manager) {
-        if (!ready) {
-            throw new UnsupportedOperationException("This EnvProbe is not ready yet, try to test isReady()");
-        }
-        if (debugNode == null) {
-            debugNode = new Node("debug gui probe");
-            Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(getPrefilteredEnvMap(), manager);
-            debugNode.attachChild(debugPfemCm);
-            debugPfemCm.setLocalTranslation(520, 0, 0);
-        }
-
-        return debugNode;
-    }
-
     public Vector3f[] getShCoeffs() {
         return shCoeffs;
     }
@@ -229,7 +281,7 @@ public class LightProbe extends Light implements Savable {
      */
     public void setPosition(Vector3f position) {
         this.position.set(position);
-        getBounds().setCenter(position);
+        area.setCenter(position);
     }
 
     public int getNbMipMaps() {
@@ -242,12 +294,17 @@ public class LightProbe extends Light implements Savable {
 
     @Override
     public boolean intersectsBox(BoundingBox box, TempVars vars) {
-        return getBounds().intersectsBoundingBox(box);
+        return area.intersectsBox(box, vars);
     }
 
     @Override
     public boolean intersectsFrustum(Camera camera, TempVars vars) {
-        return camera.contains(bounds) != Camera.FrustumIntersect.Outside;
+        return area.intersectsFrustum(camera, vars);
+    }
+
+    @Override
+    public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) {
+        return area.intersectsSphere(sphere, vars);
     }
 
     @Override
@@ -267,14 +324,8 @@ public class LightProbe extends Light implements Savable {
 
     @Override
     public String toString() {
-        return "Light Probe : " + name + " at " + position + " / " + bounds;
+        return "Light Probe : " + name + " at " + position + " / " + area;
     }
 
-    @Override
-    public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) {
-        return getBounds().intersectsSphere(sphere);
-    }
-    
-    
 
 }

+ 0 - 214
jme3-core/src/main/java/com/jme3/light/LightProbeBlendingProcessor.java

@@ -1,214 +0,0 @@
- /*
- * 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.bounding.BoundingSphere;
-import com.jme3.post.SceneProcessor;
-import com.jme3.profile.AppProfiler;
-import com.jme3.renderer.RenderManager;
-import com.jme3.renderer.ViewPort;
-import com.jme3.renderer.queue.RenderQueue;
-import com.jme3.scene.Spatial;
-import com.jme3.texture.FrameBuffer;
-import com.jme3.util.TempVars;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * this processor allows to blend several light probes maps together according to a Point of Interest.
- * This is all based on this article by Sebastien lagarde
- * https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/
- * @author Nehon
- */
-public class LightProbeBlendingProcessor implements SceneProcessor {
-    
-    private ViewPort viewPort;
-    private LightFilter prevFilter;
-    private RenderManager renderManager;
-    private LightProbe probe = new LightProbe();
-    private Spatial poi;
-    private AppProfiler prof;
-
-    public LightProbeBlendingProcessor(Spatial poi) {        
-        this.poi = poi;
-    }
-    
-    @Override
-    public void initialize(RenderManager rm, ViewPort vp) {
-        viewPort = vp;        
-        renderManager = rm;
-        prevFilter = rm.getLightFilter();
-        rm.setLightFilter(new PoiLightProbeLightFilter(this));        
-    }
-
-    @Override
-    public void reshape(ViewPort vp, int w, int h) {
-        
-    }
-
-    @Override
-    public boolean isInitialized() {
-        return viewPort != null;
-    }
-
-    @Override
-    public void preFrame(float tpf) {
-        
-    }
-    
-    /** 1. For POI take a spatial in the constructor and make all calculation against its world pos
-    *      - Alternatively compute an arbitrary POI by casting rays from the camera 
-    *        (one in the center and one for each corner and take the median point)
-    *   2. Take the 4 most weighted probes for default. Maybe allow the user to change this
-    *   3. For the inner influence radius take half of the radius for a start we'll see then how to change this.
-    *   
-    */
-    @Override
-    public void postQueue(RenderQueue rq) {
-        List<BlendFactor> blendFactors = new ArrayList<BlendFactor>();
-        float sumBlendFactors = computeBlendFactors(blendFactors);
-        
-        //Sort blend factors according to their weight
-        Collections.sort(blendFactors);        
-        
-        //normalize blend factors;
-        float normalizer = 1f / sumBlendFactors;
-        for (BlendFactor blendFactor : blendFactors) {
-            blendFactor.ndf *= normalizer;
-           // System.err.println(blendFactor);
-        }
-        
-        
-        //for now just pick the first probe.
-        if(!blendFactors.isEmpty()){
-            probe = blendFactors.get(0).lightProbe;            
-        }else{
-            probe = null;
-        }
-    }
-
-    private float computeBlendFactors(List<BlendFactor> blendFactors) {
-        float sumBlendFactors = 0;
-        for (Spatial scene : viewPort.getScenes()) {
-            for (Light light : scene.getWorldLightList()) {
-                if(light.getType() == Light.Type.Probe){
-                    LightProbe p = (LightProbe)light;
-                    TempVars vars = TempVars.get();
-                    boolean intersect = p.intersectsFrustum(viewPort.getCamera(), vars);
-                    vars.release();
-                    //check if the probe is inside the camera frustum
-                    if(intersect){
-
-                        //is the poi inside the bounds of this probe
-                        if(poi.getWorldBound().intersects(p.getBounds())){
-                            
-                            //computing the distance as we need it to check if th epoi in in the inner radius and later to compute the weight
-                            float outerRadius = ((BoundingSphere)p.getBounds()).getRadius();
-                            float innerRadius = outerRadius * 0.5f;
-                            float distance = p.getBounds().getCenter().distance(poi.getWorldTranslation());
-                            
-                            // if the poi in inside the inner range of this probe, then this probe is the only one that matters.
-                            if( distance < innerRadius ){
-                                blendFactors.clear();
-                                blendFactors.add(new BlendFactor(p, 1.0f));
-                                return 1.0f;
-                            }
-                            //else we need to compute the weight of this probe and collect it for blending
-                            float ndf = (distance - innerRadius) / (outerRadius - innerRadius);
-                            sumBlendFactors += ndf;
-                            blendFactors.add(new BlendFactor(p, ndf));
-                        }
-                    }
-                }
-            }
-        }
-        return sumBlendFactors;
-    }
-
-    @Override
-    public void postFrame(FrameBuffer out) {
-        
-    }
-
-    @Override
-    public void cleanup() {
-        viewPort = null;
-        renderManager.setLightFilter(prevFilter);
-    }
-
-    public void populateProbe(LightList lightList){
-        if(probe != null && probe.isReady()){
-            lightList.add(probe);
-        }
-    }
-
-    public Spatial getPoi() {
-        return poi;
-    }
-
-    public void setPoi(Spatial poi) {
-        this.poi = poi;
-    }
-
-    @Override
-    public void setProfiler(AppProfiler profiler) {
-        this.prof = profiler;
-    }
-
-    private class BlendFactor implements Comparable<BlendFactor>{
-        
-        LightProbe lightProbe;
-        float ndf;       
-
-        public BlendFactor(LightProbe lightProbe, float ndf) {
-            this.lightProbe = lightProbe;
-            this.ndf = ndf;
-        }
-
-        @Override
-        public String toString() {
-            return "BlendFactor{" + "lightProbe=" + lightProbe + ", ndf=" + ndf + '}';
-        }
-        
-        @Override
-        public int compareTo(BlendFactor o) {
-            if(o.ndf > ndf){
-                return -1;
-            }else if(o.ndf < ndf){
-                return 1;
-            }
-            return 0;
-        }
-        
-    }
-}

+ 249 - 0
jme3-core/src/main/java/com/jme3/light/OrientedBoxProbeArea.java

@@ -0,0 +1,249 @@
+package com.jme3.light;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.export.*;
+import com.jme3.math.*;
+import com.jme3.renderer.Camera;
+import com.jme3.util.TempVars;
+
+import java.io.IOException;
+
+public class OrientedBoxProbeArea implements ProbeArea {
+    private Transform transform = new Transform();
+
+    /**
+     * @see LightProbe#getUniformMatrix()
+     * for this Area type, the matrix is updated when the probe is transformed,
+     * and its data is used for bound checks in the light culling process.
+     */
+    private Matrix4f uniformMatrix = new Matrix4f();
+
+    public OrientedBoxProbeArea() {
+    }
+
+    public OrientedBoxProbeArea(Transform transform) {
+        transform.set(transform);
+        updateMatrix();
+    }
+
+    @Override
+    public boolean intersectsBox(BoundingBox box, TempVars vars) {
+
+        Vector3f axis1 = getScaledAxis(0, vars.vect1);
+        Vector3f axis2 = getScaledAxis(1, vars.vect2);
+        Vector3f axis3 = getScaledAxis(2, vars.vect3);
+
+        Vector3f tn = vars.vect4;
+        Plane p = vars.plane;
+        Vector3f c = box.getCenter();
+
+        p.setNormal(0, 0, -1);
+        p.setConstant(-(c.z + box.getZExtent()));
+        if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
+
+        p.setNormal(0, 0, 1);
+        p.setConstant(c.z - box.getZExtent());
+        if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
+
+
+        p.setNormal(0, -1, 0);
+        p.setConstant(-(c.y + box.getYExtent()));
+        if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
+
+        p.setNormal(0, 1, 0);
+        p.setConstant(c.y - box.getYExtent());
+        if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
+
+        p.setNormal(-1, 0, 0);
+        p.setConstant(-(c.x + box.getXExtent()));
+        if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
+
+        p.setNormal(1, 0, 0);
+        p.setConstant(c.x - box.getXExtent());
+        if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
+
+        return true;
+
+    }
+
+    @Override
+    public float getRadius() {
+        return Math.max(Math.max(transform.getScale().x, transform.getScale().y), transform.getScale().z);
+    }
+
+    @Override
+    public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) {
+
+        Vector3f closestPoint = getClosestPoint(vars, sphere.getCenter());
+        // check if the point intersects with the sphere bound
+        if (sphere.intersects(closestPoint)) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean intersectsFrustum(Camera camera, TempVars vars) {
+
+        // extract the scaled axis
+        // this allows a small optimization.
+        Vector3f axis1 = getScaledAxis(0, vars.vect1);
+        Vector3f axis2 = getScaledAxis(1, vars.vect2);
+        Vector3f axis3 = getScaledAxis(2, vars.vect3);
+
+        Vector3f tn = vars.vect4;
+
+        for (int i = 5; i >= 0; i--) {
+            Plane p = camera.getWorldPlane(i);
+            if (!insidePlane(p, axis1, axis2, axis3, tn)) return false;
+        }
+        return true;
+    }
+
+    private Vector3f getScaledAxis(int index, Vector3f store) {
+        Matrix4f u = uniformMatrix;
+        float x = 0, y = 0, z = 0, s = 1;
+        switch (index) {
+            case 0:
+                x = u.m00;
+                y = u.m10;
+                z = u.m20;
+                s = u.m30;
+                break;
+            case 1:
+                x = u.m01;
+                y = u.m11;
+                z = u.m21;
+                s = u.m31;
+                break;
+            case 2:
+                x = u.m02;
+                y = u.m12;
+                z = u.m22;
+                s = u.m32;
+        }
+        return store.set(x, y, z).multLocal(s);
+    }
+
+    private boolean insidePlane(Plane p, Vector3f axis1, Vector3f axis2, Vector3f axis3, Vector3f tn) {
+        // transform the plane normal in the box local space.
+        tn.set(axis1.dot(p.getNormal()), axis2.dot(p.getNormal()), axis3.dot(p.getNormal()));
+
+        // distance check
+        float radius = FastMath.abs(tn.x) +
+                FastMath.abs(tn.y) +
+                FastMath.abs(tn.z);
+
+        float distance = p.pseudoDistance(transform.getTranslation());
+
+        if (distance < -radius) {
+            return false;
+        }
+        return true;
+    }
+
+    private Vector3f getClosestPoint(TempVars vars, Vector3f point) {
+        // non normalized direction
+        Vector3f dir = vars.vect2.set(point).subtractLocal(transform.getTranslation());
+        // initialize the closest point with box center
+        Vector3f closestPoint = vars.vect3.set(transform.getTranslation());
+
+        //store extent in an array
+        float[] r = vars.fWdU;
+        r[0] = transform.getScale().x;
+        r[1] = transform.getScale().y;
+        r[2] = transform.getScale().z;
+
+        // computing closest point to sphere center
+        for (int i = 0; i < 3; i++) {
+            // extract the axis from the 3x3 matrix
+            Vector3f axis = getScaledAxis(i, vars.vect1);
+            // nomalize (here we just divide by the extent
+            axis.divideLocal(r[i]);
+            // distance to the closest point on this axis.
+            float d = FastMath.clamp(dir.dot(axis), -r[i], r[i]);
+            closestPoint.addLocal(vars.vect4.set(axis).multLocal(d));
+        }
+        return closestPoint;
+    }
+
+    private void updateMatrix() {
+        TempVars vars = TempVars.get();
+        Matrix3f r = vars.tempMat3;
+        Matrix4f u = uniformMatrix;
+        transform.getRotation().toRotationMatrix(r);
+
+        u.m00 = r.get(0,0);
+        u.m10 = r.get(1,0);
+        u.m20 = r.get(2,0);
+        u.m01 = r.get(0,1);
+        u.m11 = r.get(1,1);
+        u.m21 = r.get(2,1);
+        u.m02 = r.get(0,2);
+        u.m12 = r.get(1,2);
+        u.m22 = r.get(2,2);
+
+        //scale
+        u.m30 = transform.getScale().x;
+        u.m31 = transform.getScale().y;
+        u.m32 = transform.getScale().z;
+
+        //position
+        u.m03 = transform.getTranslation().x;
+        u.m13 = transform.getTranslation().y;
+        u.m23 = transform.getTranslation().z;
+
+        vars.release();
+    }
+
+    public Matrix4f getUniformMatrix() {
+        return uniformMatrix;
+    }
+
+    public Vector3f getExtent() {
+        return transform.getScale();
+    }
+
+    public void setExtent(Vector3f extent) {
+        transform.setScale(extent);
+        updateMatrix();
+    }
+
+    public Vector3f getCenter() {
+        return transform.getTranslation();
+    }
+
+    public void setCenter(Vector3f center) {
+        transform.setTranslation(center);
+        updateMatrix();
+    }
+
+    public Quaternion getRotation() {
+        return transform.getRotation();
+    }
+
+    public void setRotation(Quaternion rotation) {
+        transform.setRotation(rotation);
+        updateMatrix();
+    }
+
+    @Override
+    protected OrientedBoxProbeArea clone() throws CloneNotSupportedException {
+        return new OrientedBoxProbeArea(transform);
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        OutputCapsule oc = e.getCapsule(this);
+        oc.write(transform, "transform", new Transform());
+    }
+
+    @Override
+    public void read(JmeImporter i) throws IOException {
+        InputCapsule ic = i.getCapsule(this);
+        transform = (Transform) ic.readSavable("transform", new Transform());
+        updateMatrix();
+    }
+
+}

+ 0 - 107
jme3-core/src/main/java/com/jme3/light/PoiLightProbeLightFilter.java

@@ -1,107 +0,0 @@
-/*
- * 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.bounding.BoundingBox;
-import com.jme3.bounding.BoundingSphere;
-import com.jme3.bounding.BoundingVolume;
-import com.jme3.renderer.Camera;
-import com.jme3.scene.Geometry;
-import com.jme3.util.TempVars;
-import java.util.HashSet;
-
-public final class PoiLightProbeLightFilter implements LightFilter {
-
-    private Camera camera;
-    private final HashSet<Light> processedLights = new HashSet<Light>();
-    private final LightProbeBlendingProcessor processor;
-
-    public PoiLightProbeLightFilter(LightProbeBlendingProcessor processor) {
-        this.processor = processor;
-    }
-
-    @Override
-    public void setCamera(Camera camera) {
-        this.camera = camera;
-        for (Light light : processedLights) {
-            light.frustumCheckNeeded = true;
-        }
-    }
-
-    @Override
-    public void filterLights(Geometry geometry, LightList filteredLightList) {
-        TempVars vars = TempVars.get();
-        try {
-            LightList worldLights = geometry.getWorldLightList();
-
-            for (int i = 0; i < worldLights.size(); i++) {
-                Light light = worldLights.get(i);
-
-                if (light.getType() == Light.Type.Probe) {
-                    continue;
-                }
-
-                if (light.frustumCheckNeeded) {
-                    processedLights.add(light);
-                    light.frustumCheckNeeded = false;
-                    light.intersectsFrustum = light.intersectsFrustum(camera, vars);
-                }
-
-                if (!light.intersectsFrustum) {
-                    continue;
-                }
-
-                BoundingVolume bv = geometry.getWorldBound();
-
-                if (bv instanceof BoundingBox) {
-                    if (!light.intersectsBox((BoundingBox) bv, vars)) {
-                        continue;
-                    }
-                } else if (bv instanceof BoundingSphere) {
-                    if (!Float.isInfinite(((BoundingSphere) bv).getRadius())) {
-                        if (!light.intersectsSphere((BoundingSphere) bv, vars)) {
-                            continue;
-                        }
-                    }
-                }
-                
-                filteredLightList.add(light);
-            }
-
-            processor.populateProbe(filteredLightList);           
-
-        } finally {
-            vars.release();
-        }
-    }
-
-}

+ 1 - 6
jme3-core/src/main/java/com/jme3/light/PointLight.java

@@ -212,12 +212,7 @@ public class PointLight extends Light {
         if (this.radius == 0) {
             return true;
         } else {
-            for (int i = 5; i >= 0; i--) {
-                if (camera.getWorldPlane(i).pseudoDistance(position) <= -radius) {
-                    return false;
-                }
-            }
-            return true;
+            return Intersection.intersect(camera, position, radius);
         }
     }
     

+ 33 - 0
jme3-core/src/main/java/com/jme3/light/ProbeArea.java

@@ -0,0 +1,33 @@
+package com.jme3.light;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.export.Savable;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.util.TempVars;
+
+public interface ProbeArea extends Savable, Cloneable{
+
+    public void setCenter(Vector3f center);
+
+    public float getRadius();
+
+    public Matrix4f getUniformMatrix();
+
+    /**
+     * @see Light#intersectsBox(BoundingBox, TempVars)
+     */
+    public boolean intersectsBox(BoundingBox box, TempVars vars);
+
+    /**
+     * @see Light#intersectsSphere(BoundingSphere, TempVars)
+     */
+    public boolean intersectsSphere(BoundingSphere sphere, TempVars vars);
+
+    /**
+     * @see Light#intersectsFrustum(Camera, TempVars)
+     */
+    public abstract boolean intersectsFrustum(Camera camera, TempVars vars);
+}

+ 102 - 0
jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java

@@ -0,0 +1,102 @@
+package com.jme3.light;
+
+import com.jme3.bounding.*;
+import com.jme3.export.*;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.util.TempVars;
+
+import java.io.IOException;
+import java.util.logging.Level;
+
+public class SphereProbeArea implements ProbeArea {
+
+    private Vector3f center = new Vector3f();
+    private float radius = 1;
+    private Matrix4f uniformMatrix = new Matrix4f();
+
+    public SphereProbeArea() {
+    }
+
+    public SphereProbeArea(Vector3f center, float radius) {
+        this.center.set(center);
+        this.radius = radius;
+        updateMatrix();
+    }
+
+    public Vector3f getCenter() {
+        return center;
+    }
+
+    public void setCenter(Vector3f center) {
+        this.center.set(center);
+        updateMatrix();
+    }
+
+    public float getRadius() {
+        return radius;
+    }
+
+    public void setRadius(float radius) {
+        this.radius = radius;
+        updateMatrix();
+    }
+
+    @Override
+    public Matrix4f getUniformMatrix() {
+        return uniformMatrix;
+    }
+
+    private void updateMatrix(){
+        //position
+        uniformMatrix.m03 = center.x;
+        uniformMatrix.m13 = center.y;
+        uniformMatrix.m23 = center.z;
+
+    }
+
+    @Override
+    public boolean intersectsBox(BoundingBox box, TempVars vars) {
+        return Intersection.intersect(box, center, radius);
+    }
+
+    @Override
+    public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) {
+        return Intersection.intersect(sphere, center, radius);
+    }
+
+    @Override
+    public boolean intersectsFrustum(Camera camera, TempVars vars) {
+        return Intersection.intersect(camera, center, radius);
+    }
+
+    @Override
+    public String toString() {
+        return "SphereProbeArea{" +
+                "center=" + center +
+                ", radius=" + radius +
+                '}';
+    }
+
+    @Override
+    protected SphereProbeArea clone() throws CloneNotSupportedException {
+        return new SphereProbeArea(center, radius);
+    }
+
+    @Override
+    public void write(JmeExporter e) throws IOException {
+        OutputCapsule oc = e.getCapsule(this);
+        oc.write(center, "center", new Vector3f());
+        oc.write(radius, "radius", 1);
+    }
+
+    @Override
+    public void read(JmeImporter i) throws IOException {
+        InputCapsule ic = i.getCapsule(this);
+        center = (Vector3f) ic.readSavable("center", new Vector3f());
+        radius = ic.readFloat("radius", 1);
+        updateMatrix();
+    }
+
+}

+ 77 - 0
jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java

@@ -0,0 +1,77 @@
+/*
+ * 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.scene.Geometry;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This strategy returns the 3 closest probe from the rendered object.
+ * <p>
+ * Image based lighting will be blended between those probes in the shader according to their distance and range.
+ *
+ * @author Nehon
+ */
+public class WeightedProbeBlendingStrategy implements LightProbeBlendingStrategy {
+
+    private final static int MAX_PROBES = 3;
+    List<LightProbe> lightProbes = new ArrayList<LightProbe>();
+
+    @Override
+    public void registerProbe(LightProbe probe) {
+        lightProbes.add(probe);
+    }
+
+    @Override
+    public void populateProbes(Geometry g, LightList lightList) {
+        if (!lightProbes.isEmpty()) {
+            //The 3 first probes are the closest to the geometry since the
+            //light list is sorted according to the distance to the geom.
+            int addedProbes = 0;
+            for (LightProbe p : lightProbes) {
+                if (p.isReady() && p.isEnabled()) {
+                    lightList.add(p);
+                    addedProbes ++;
+                }
+                if (addedProbes == MAX_PROBES) {
+                    break;
+                }
+            }
+
+            //clearing the list for next pass.
+            lightProbes.clear();
+        }
+    }
+
+}

+ 44 - 28
jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java

@@ -42,17 +42,17 @@ import com.jme3.scene.Geometry;
 import com.jme3.shader.*;
 import com.jme3.util.TempVars;
 
-import java.util.EnumSet;
+import java.util.*;
 
 public final class SinglePassAndImageBasedLightingLogic extends DefaultTechniqueDefLogic {
 
     private static final String DEFINE_SINGLE_PASS_LIGHTING = "SINGLE_PASS_LIGHTING";
     private static final String DEFINE_NB_LIGHTS = "NB_LIGHTS";
-    private static final String DEFINE_INDIRECT_LIGHTING = "INDIRECT_LIGHTING";
+    private static final String DEFINE_NB_PROBES = "NB_PROBES";
     private static final RenderState ADDITIVE_LIGHT = new RenderState();
 
     private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
-    private LightProbe lightProbe = null;
+    private List<LightProbe> lightProbes = new ArrayList<>(3);
 
     static {
         ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive);
@@ -61,13 +61,13 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
 
     private final int singlePassLightingDefineId;
     private final int nbLightsDefineId;
-    private final int indirectLightingDefineId;
+    private final int nbProbesDefineId;
 
     public SinglePassAndImageBasedLightingLogic(TechniqueDef techniqueDef) {
         super(techniqueDef);
         singlePassLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_SINGLE_PASS_LIGHTING, VarType.Boolean);
         nbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_LIGHTS, VarType.Int);
-        indirectLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_INDIRECT_LIGHTING, VarType.Boolean);
+        nbProbesDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_PROBES, VarType.Int);
     }
 
     @Override
@@ -81,12 +81,9 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
         //Though the second pass should not render IBL as it is taken care of on first pass like ambient light in phong lighting.
         //We cannot change the define between passes and the old technique, and for some reason the code fails on mac (renders nothing).
         if(lights != null) {
-            lightProbe = extractIndirectLights(lights, false);
-            if (lightProbe == null) {
-                defines.set(indirectLightingDefineId, false);
-            } else {
-                defines.set(indirectLightingDefineId, true);
-            }
+            lightProbes.clear();
+            extractIndirectLights(lights, false);
+            defines.set(nbProbesDefineId, lightProbes.size());
         }
 
         return super.makeCurrent(assetManager, renderManager, rendererCaps, lights, defines);
@@ -113,35 +110,44 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
         Uniform lightData = shader.getUniform("g_LightData");
         lightData.setVector4Length(numLights * 3);//8 lights * max 3
         Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
+
+        // Matrix4f
         Uniform lightProbeData = shader.getUniform("g_LightProbeData");
-        lightProbeData.setVector4Length(1);
+        Uniform lightProbeData2 = shader.getUniform("g_LightProbeData2");
+        Uniform lightProbeData3 = shader.getUniform("g_LightProbeData3");
 
-        //TODO These 2 uniforms should be packed in an array, to be able to have several probes and blend between them.
         Uniform shCoeffs = shader.getUniform("g_ShCoeffs");
         Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap");
+        Uniform shCoeffs2 = shader.getUniform("g_ShCoeffs2");
+        Uniform lightProbePemMap2 = shader.getUniform("g_PrefEnvMap2");
+        Uniform shCoeffs3 = shader.getUniform("g_ShCoeffs3");
+        Uniform lightProbePemMap3 = shader.getUniform("g_PrefEnvMap3");
 
-        lightProbe = null;
+        lightProbes.clear();
         if (startIndex != 0) {
             // apply additive blending for 2nd and future passes
             rm.getRenderer().applyRenderState(ADDITIVE_LIGHT);
             ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
         }else{
-            lightProbe = extractIndirectLights(lightList,true);
+            extractIndirectLights(lightList,true);
             ambientColor.setValue(VarType.Vector4, ambientLightColor);
         }
 
         //If there is a lightProbe in the list we force its render on the first pass
-        if(lightProbe != null){
-            BoundingSphere s = (BoundingSphere)lightProbe.getBounds();
-            lightProbeData.setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f / s.getRadius() + lightProbe.getNbMipMaps(), 0);
-            shCoeffs.setValue(VarType.Vector3Array, lightProbe.getShCoeffs());
-            //assigning new texture indexes
-            int pemUnit = lastTexUnit++;
-            rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap());
-            lightProbePemMap.setValue(VarType.Int, pemUnit);
+        if (!lightProbes.isEmpty()) {
+            LightProbe lightProbe = lightProbes.get(0);
+            lastTexUnit = setProbeData(rm, lastTexUnit, lightProbeData, shCoeffs, lightProbePemMap, lightProbe);
+            if (lightProbes.size() > 1) {
+                lightProbe = lightProbes.get(1);
+                lastTexUnit = setProbeData(rm, lastTexUnit, lightProbeData2, shCoeffs2, lightProbePemMap2, lightProbe);
+            }
+            if (lightProbes.size() > 2) {
+                lightProbe = lightProbes.get(2);
+                setProbeData(rm, lastTexUnit, lightProbeData3, shCoeffs3, lightProbePemMap3, lightProbe);
+            }
         } else {
             //Disable IBL for this pass
-            lightProbeData.setVector4InArray(0,0,0,-1, 0);
+            lightProbeData.setValue(VarType.Matrix4, LightProbe.FALLBACK_MATRIX);
         }
 
         int lightDataIndex = 0;
@@ -222,6 +228,18 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
         return curIndex;
     }
 
+    private int setProbeData(RenderManager rm, int lastTexUnit, Uniform lightProbeData, Uniform shCoeffs, Uniform lightProbePemMap, LightProbe lightProbe) {
+
+        lightProbeData.setValue(VarType.Matrix4, lightProbe.getUniformMatrix());
+                //setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f / area.getRadius() + lightProbe.getNbMipMaps(), 0);
+        shCoeffs.setValue(VarType.Vector3Array, lightProbe.getShCoeffs());
+        //assigning new texture indexes
+        int pemUnit = lastTexUnit++;
+        rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap());
+        lightProbePemMap.setValue(VarType.Int, pemUnit);
+        return lastTexUnit;
+    }
+
     @Override
     public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
         int nbRenderedLights = 0;
@@ -241,9 +259,8 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
         return;
     }
 
-    protected LightProbe extractIndirectLights(LightList lightList, boolean removeLights) {
+    protected void extractIndirectLights(LightList lightList, boolean removeLights) {
         ambientLightColor.set(0, 0, 0, 1);
-        LightProbe probe = null;
         for (int j = 0; j < lightList.size(); j++) {
             Light l = lightList.get(j);
             if (l instanceof AmbientLight) {
@@ -254,7 +271,7 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
                 }
             }
             if (l instanceof LightProbe) {
-                probe = (LightProbe)l;
+                lightProbes.add((LightProbe) l);
                 if(removeLights){
                     lightList.remove(l);
                     j--;
@@ -262,6 +279,5 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique
             }
         }
         ambientLightColor.a = 1.0f;
-        return probe;
     }
 }

+ 26 - 16
jme3-core/src/main/java/com/jme3/scene/debug/WireFrustum.java

@@ -42,29 +42,39 @@ import java.nio.FloatBuffer;
 public class WireFrustum extends Mesh {
 
     public WireFrustum(Vector3f[] points){
+        initGeom(this, points);
+    }
+
+    public static Mesh makeFrustum(Vector3f[] points){
+        Mesh m = new Mesh();
+        initGeom(m, points);
+        return m;
+    }
+
+    private static void initGeom(Mesh m, Vector3f[] points) {
         if (points != null)
-            setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points));
+            m.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points));
 
-        setBuffer(Type.Index, 2,
+        m.setBuffer(Type.Index, 2,
                 new short[]{
-                     0, 1,
-                     1, 2,
-                     2, 3,
-                     3, 0,
+                        0, 1,
+                        1, 2,
+                        2, 3,
+                        3, 0,
 
-                     4, 5,
-                     5, 6,
-                     6, 7,
-                     7, 4,
+                        4, 5,
+                        5, 6,
+                        6, 7,
+                        7, 4,
 
-                     0, 4,
-                     1, 5,
-                     2, 6,
-                     3, 7,
+                        0, 4,
+                        1, 5,
+                        2, 6,
+                        3, 7,
                 }
         );
-        getBuffer(Type.Index).setUsage(Usage.Static);
-        setMode(Mode.Lines);
+        m.getBuffer(Type.Index).setUsage(Usage.Static);
+        m.setMode(Mode.Lines);
     }
 
     public void update(Vector3f[] points){

+ 133 - 26
jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag

@@ -3,7 +3,6 @@
 #import "Common/ShaderLib/Parallax.glsllib"
 #import "Common/ShaderLib/Lighting.glsllib"
 
-
 varying vec2 texCoord;
 #ifdef SEPARATE_TEXCOORD
   varying vec2 texCoord2;
@@ -12,7 +11,6 @@ varying vec2 texCoord;
 varying vec4 Color;
 
 uniform vec4 g_LightData[NB_LIGHTS];
-
 uniform vec3 g_CameraPosition;
 
 uniform float m_Roughness;
@@ -21,11 +19,20 @@ uniform float m_Metallic;
 varying vec3 wPosition;    
 
 
-#ifdef INDIRECT_LIGHTING
-//  uniform sampler2D m_IntegrateBRDF;
+#if NB_PROBES >= 1
   uniform samplerCube g_PrefEnvMap;
   uniform vec3 g_ShCoeffs[9];
-  uniform vec4 g_LightProbeData;
+  uniform mat4 g_LightProbeData;
+#endif
+#if NB_PROBES >= 2
+  uniform samplerCube g_PrefEnvMap2;
+  uniform vec3 g_ShCoeffs2[9];
+  uniform mat4 g_LightProbeData2;
+#endif
+#if NB_PROBES == 3
+  uniform samplerCube g_PrefEnvMap3;
+  uniform vec3 g_ShCoeffs3[9];
+  uniform mat4 g_LightProbeData3;
 #endif
 
 #ifdef BASECOLORMAP
@@ -87,6 +94,91 @@ varying vec3 wNormal;
 uniform float m_AlphaDiscardThreshold;
 #endif
 
+float renderProbe(vec3 viewDir, vec3 normal, vec3 norm, float Roughness, vec4 diffuseColor, vec4 specularColor, float ndotv, vec3 ao, mat4 lightProbeData,vec3 shCoeffs[9],samplerCube prefEnvMap, inout vec3 color ){
+
+    // lightProbeData is a mat4 with this layout
+    //   3x3 rot mat|
+    //      0  1  2 |  3
+    // 0 | ax bx cx | px | )
+    // 1 | ay by cy | py | probe position
+    // 2 | az bz cz | pz | )
+    // --|----------|
+    // 3 | sx sy sz   sp | -> 1/probe radius + nbMipMaps
+    //    --scale--
+    // parallax fix for spherical / obb bounds and probe blending from
+    // from https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/
+    vec3 rv = reflect(-viewDir, normal);
+    vec4 probePos = lightProbeData[3];
+    float invRadius = fract( probePos.w);
+    float nbMipMaps = probePos.w - invRadius;
+    vec3 direction = wPosition - probePos.xyz;
+    float ndf = 0.0;
+
+    if(lightProbeData[0][3] != 0.0){
+        // oriented box probe
+        mat3 wToLocalRot = mat3(lightProbeData);
+        wToLocalRot = inverse(wToLocalRot);
+        vec3 scale = vec3(lightProbeData[0][3], lightProbeData[1][3], lightProbeData[2][3]);
+        #if NB_PROBES >= 2
+            // probe blending
+            // compute fragment position in probe local space
+            vec3 localPos = wToLocalRot * wPosition;
+            localPos -= probePos.xyz;
+            // compute normalized distance field
+            vec3 localDir = abs(localPos);
+            localDir /= scale;
+            ndf = max(max(localDir.x, localDir.y), localDir.z);
+        #endif
+        // parallax fix
+        vec3 rayLs = wToLocalRot * rv;
+        rayLs /= scale;
+
+        vec3 positionLs = wPosition - probePos.xyz;
+        positionLs = wToLocalRot * positionLs;
+        positionLs /= scale;
+
+        vec3 unit = vec3(1.0);
+        vec3 firstPlaneIntersect = (unit - positionLs) / rayLs;
+        vec3 secondPlaneIntersect = (-unit - positionLs) / rayLs;
+        vec3 furthestPlane = max(firstPlaneIntersect, secondPlaneIntersect);
+        float distance = min(min(furthestPlane.x, furthestPlane.y), furthestPlane.z);
+
+        vec3 intersectPositionWs = wPosition + rv * distance;
+        rv = intersectPositionWs - probePos.xyz;
+
+    } else {
+        // spherical probe
+        // paralax fix
+        rv = invRadius * direction + rv;
+
+        #if NB_PROBES >= 2
+            // probe blending
+            float dist = sqrt(dot(direction, direction));
+            ndf = dist * invRadius;
+        #endif
+    }
+
+    vec3 indirectDiffuse = vec3(0.0);
+    vec3 indirectSpecular = vec3(0.0);
+    indirectDiffuse = sphericalHarmonics(normal.xyz, shCoeffs) * diffuseColor.rgb;
+    vec3 dominantR = getSpecularDominantDir( normal, rv.xyz, Roughness * Roughness );
+    indirectSpecular = ApproximateSpecularIBLPolynomial(prefEnvMap, specularColor.rgb, Roughness, ndotv, dominantR, nbMipMaps);
+
+    #ifdef HORIZON_FADE
+        //horizon fade from http://marmosetco.tumblr.com/post/81245981087
+        float horiz = dot(rv, norm);
+        float horizFadePower = 1.0 - Roughness;
+        horiz = clamp( 1.0 + horizFadePower * horiz, 0.0, 1.0 );
+        horiz *= horiz;
+        indirectSpecular *= vec3(horiz);
+    #endif
+
+    vec3 indirectLighting = (indirectDiffuse + indirectSpecular) * ao;
+
+    color = indirectLighting * step( 0.0, probePos.w);
+    return ndf;
+}
+
 void main(){
     vec2 newTexCoord;
     vec3 viewDir = normalize(g_CameraPosition - wPosition);
@@ -250,32 +342,47 @@ void main(){
         gl_FragColor.rgb += directLighting * fallOff;
     }
 
-    #ifdef INDIRECT_LIGHTING
-        vec3 rv = reflect(-viewDir.xyz, normal.xyz);
-        //prallax fix for spherical bounds from https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/
-        // g_LightProbeData.w is 1/probe radius + nbMipMaps, g_LightProbeData.xyz is the position of the lightProbe.
-        float invRadius = fract( g_LightProbeData.w);
-        float nbMipMaps = g_LightProbeData.w - invRadius;
-        rv = invRadius * (wPosition - g_LightProbeData.xyz) +rv;
+    #if NB_PROBES >= 1
+        vec3 color1 = vec3(0.0);
+        vec3 color2 = vec3(0.0);
+        vec3 color3 = vec3(0.0);
+        float weight1 = 1.0;
+        float weight2 = 0.0;
+        float weight3 = 0.0;
+
+        float ndf = renderProbe(viewDir, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData, g_ShCoeffs, g_PrefEnvMap, color1);
+        #if NB_PROBES >= 2
+            float ndf2 = renderProbe(viewDir, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData2, g_ShCoeffs2, g_PrefEnvMap2, color2);
+        #endif
+        #if NB_PROBES == 3
+            float ndf3 = renderProbe(viewDir, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData3, g_ShCoeffs3, g_PrefEnvMap3, color3);
+        #endif
 
-         //horizon fade from http://marmosetco.tumblr.com/post/81245981087
-        float horiz = dot(rv, norm);
-        float horizFadePower = 1.0 - Roughness;
-        horiz = clamp( 1.0 + horizFadePower * horiz, 0.0, 1.0 );
-        horiz *= horiz;
+         #if NB_PROBES >= 2
+            float invNdf =  max(1.0 - ndf,0.0);
+            float invNdf2 =  max(1.0 - ndf2,0.0);
+            float sumNdf = ndf + ndf2;
+            float sumInvNdf = invNdf + invNdf2;
+            #if NB_PROBES == 3
+                float invNdf3 = max(1.0 - ndf3,0.0);
+                sumNdf += ndf3;
+                sumInvNdf += invNdf3;
+                weight3 =  ((1.0 - (ndf3 / sumNdf)) / (NB_PROBES - 1)) *  (invNdf3 / sumInvNdf);
+            #endif
 
-        vec3 indirectDiffuse = vec3(0.0);
-        vec3 indirectSpecular = vec3(0.0);
-        indirectDiffuse = sphericalHarmonics(normal.xyz, g_ShCoeffs) * diffuseColor.rgb;
-        vec3 dominantR = getSpecularDominantDir( normal, rv.xyz, Roughness*Roughness );
-        indirectSpecular = ApproximateSpecularIBLPolynomial(g_PrefEnvMap, specularColor.rgb, Roughness, ndotv, dominantR, nbMipMaps);
-        indirectSpecular *= vec3(horiz);
+            weight1 = ((1.0 - (ndf / sumNdf)) / (NB_PROBES - 1)) *  (invNdf / sumInvNdf);
+            weight2 = ((1.0 - (ndf2 / sumNdf)) / (NB_PROBES - 1)) *  (invNdf2 / sumInvNdf);
 
-        vec3 indirectLighting = (indirectDiffuse + indirectSpecular) * ao;
+            float weightSum = weight1 + weight2 + weight3;
+
+            weight1 /= weightSum;
+            weight2 /= weightSum;
+            weight3 /= weightSum;
+        #endif
+        gl_FragColor.rgb += color1 * clamp(weight1,0.0,1.0) + color2 * clamp(weight2,0.0,1.0) + color3 * clamp(weight3,0.0,1.0);
 
-        gl_FragColor.rgb = gl_FragColor.rgb + indirectLighting * step( 0.0, g_LightProbeData.w);
     #endif
- 
+
     #if defined(EMISSIVE) || defined (EMISSIVEMAP)
         #ifdef EMISSIVEMAP
             vec4 emissive = texture2D(m_EmissiveMap, newTexCoord);

+ 4 - 0
jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md

@@ -62,6 +62,9 @@ MaterialDef PBR Lighting {
         //Set to true to activate Steep Parallax mapping
         Boolean SteepParallax
 
+        //Horizon fade
+        Boolean HorizonFade
+
         // Set to Use Lightmap
         Texture2D LightMap
 
@@ -157,6 +160,7 @@ MaterialDef PBR Lighting {
             AO_MAP: LightMapAsAOMap
             NUM_MORPH_TARGETS: NumberOfMorphTargets
             NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
+            HORIZON_FADE: HorizonFade
         }
     }
 

+ 0 - 0
jme3-examples/src/main/java/jme3test/collision/Main.java


+ 0 - 0
jme3-examples/src/main/java/jme3test/light/DlsfError.java


+ 1 - 1
jme3-examples/src/main/java/jme3test/light/TestConeVSFrustum.java

@@ -102,7 +102,7 @@ public class TestConeVSFrustum extends SimpleApplication {
 
         float radius = FastMath.tan(spotLight.getSpotOuterAngle()) * spotLight.getSpotRange();
 
-        Cylinder cylinder = new Cylinder(5, 16, 0, radius, spotLight.getSpotRange(), true, false);
+        Cylinder cylinder = new Cylinder(5, 16, 0.01f, radius, spotLight.getSpotRange(), true, false);
         geom = new Geometry("light", cylinder);
         geom.setMaterial(new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"));
         geom.getMaterial().setColor("Diffuse", ColorRGBA.White);

+ 301 - 0
jme3-examples/src/main/java/jme3test/light/TestObbVsBounds.java

@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2009-2012 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 jme3test.light;
+
+import com.jme3.app.ChaseCameraAppState;
+import com.jme3.app.SimpleApplication;
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.export.binary.BinaryExporter;
+import com.jme3.input.KeyInput;
+import com.jme3.input.MouseInput;
+import com.jme3.input.controls.*;
+import com.jme3.light.*;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.*;
+import com.jme3.scene.debug.Grid;
+import com.jme3.scene.debug.WireFrustum;
+import com.jme3.scene.shape.*;
+import com.jme3.shadow.ShadowUtil;
+import com.jme3.util.TempVars;
+
+import java.io.File;
+import java.io.IOException;
+
+public class TestObbVsBounds extends SimpleApplication {
+
+    private Node ln;
+    private BoundingBox aabb = new BoundingBox();
+    private BoundingSphere sphere = new BoundingSphere(10, new Vector3f(-30, 0, -60));
+
+    private final static float MOVE_SPEED = 60;
+    private Vector3f tmp = new Vector3f();
+    private Quaternion tmpQuat = new Quaternion();
+    private boolean moving, shift;
+    private boolean panning;
+
+    private OrientedBoxProbeArea area = new OrientedBoxProbeArea();
+    private Camera frustumCam;
+
+    private Geometry areaGeom;
+    private Geometry frustumGeom;
+    private Geometry aabbGeom;
+    private Geometry sphereGeom;
+
+    public static void main(String[] args) {
+        TestObbVsBounds app = new TestObbVsBounds();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+        frustumCam = cam.clone();
+        frustumCam.setFrustumFar(25);
+        makeCamFrustum();
+        aabb.setCenter(20, 10, -60);
+        aabb.setXExtent(10);
+        aabb.setYExtent(5);
+        aabb.setZExtent(3);
+        makeBoxWire(aabb);
+        makeSphereWire(sphere);
+
+        rootNode.addLight(new DirectionalLight());
+        AmbientLight al = new AmbientLight();
+        al.setColor(ColorRGBA.White.mult(0.2f));
+        rootNode.addLight(al);
+
+        Grid grid = new Grid(50, 50, 5);
+        Geometry gridGeom = new Geometry("grid", grid);
+        gridGeom.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"));
+        gridGeom.getMaterial().setColor("Color", ColorRGBA.Gray);
+        rootNode.attachChild(gridGeom);
+        gridGeom.setLocalTranslation(-125, -25, -125);
+
+        area.setCenter(Vector3f.ZERO);
+        area.setExtent(new Vector3f(4, 8, 5));
+        makeAreaGeom();
+
+        ln = new Node("lb");
+        ln.setLocalRotation(new Quaternion(-0.18826798f, -0.38304946f, -0.12780227f, 0.895261f));
+        ln.attachChild(areaGeom);
+        ln.setLocalScale(4,8,5);
+        rootNode.attachChild(ln);
+
+        inputManager.addMapping("click", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
+        inputManager.addMapping("shift", new KeyTrigger(KeyInput.KEY_LSHIFT), new KeyTrigger(KeyInput.KEY_RSHIFT));
+        inputManager.addMapping("middleClick", new MouseButtonTrigger(MouseInput.BUTTON_MIDDLE));
+        inputManager.addMapping("up", new MouseAxisTrigger(MouseInput.AXIS_Y, false));
+        inputManager.addMapping("down", new MouseAxisTrigger(MouseInput.AXIS_Y, true));
+        inputManager.addMapping("left", new MouseAxisTrigger(MouseInput.AXIS_X, true));
+        inputManager.addMapping("right", new MouseAxisTrigger(MouseInput.AXIS_X, false));
+
+
+        final Node camTarget = new Node("CamTarget");
+        rootNode.attachChild(camTarget);
+
+        ChaseCameraAppState chaser = new ChaseCameraAppState();
+        chaser.setTarget(camTarget);
+        chaser.setMaxDistance(150);
+        chaser.setDefaultDistance(70);
+        chaser.setDefaultHorizontalRotation(FastMath.HALF_PI);
+        chaser.setMinVerticalRotation(-FastMath.PI);
+        chaser.setMaxVerticalRotation(FastMath.PI * 2);
+        chaser.setToggleRotationTrigger(new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
+        stateManager.attach(chaser);
+        flyCam.setEnabled(false);
+
+        inputManager.addListener(new AnalogListener() {
+            public void onAnalog(String name, float value, float tpf) {
+                Spatial s = null;
+                float mult = 1;
+                if (moving) {
+                    s = ln;
+                }
+                if (panning) {
+                    s = camTarget;
+                    mult = -1;
+                }
+                if ((moving || panning) && s != null) {
+                    if (shift) {
+                        if (name.equals("left")) {
+                            tmp.set(cam.getDirection());
+                            s.rotate(tmpQuat.fromAngleAxis(value, tmp));
+                        }
+                        if (name.equals("right")) {
+                            tmp.set(cam.getDirection());
+                            s.rotate(tmpQuat.fromAngleAxis(-value, tmp));
+                        }
+                    } else {
+                        value *= MOVE_SPEED * mult;
+                        if (name.equals("up")) {
+                            tmp.set(cam.getUp()).multLocal(value);
+                            s.move(tmp);
+                        }
+                        if (name.equals("down")) {
+                            tmp.set(cam.getUp()).multLocal(-value);
+                            s.move(tmp);
+                        }
+                        if (name.equals("left")) {
+                            tmp.set(cam.getLeft()).multLocal(value);
+                            s.move(tmp);
+                        }
+                        if (name.equals("right")) {
+                            tmp.set(cam.getLeft()).multLocal(-value);
+                            s.move(tmp);
+                        }
+                    }
+                }
+            }
+        }, "up", "down", "left", "right");
+
+        inputManager.addListener(new ActionListener() {
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if (name.equals("click")) {
+                    if (isPressed) {
+                        moving = true;
+                    } else {
+                        moving = false;
+                    }
+                }
+                if (name.equals("middleClick")) {
+                    if (isPressed) {
+                        panning = true;
+                    } else {
+                        panning = false;
+                    }
+                }
+                if (name.equals("shift")) {
+                    if (isPressed) {
+                        shift = true;
+                    } else {
+                        shift = false;
+                    }
+                }
+            }
+        }, "click", "middleClick", "shift");
+
+    }
+
+    public void makeAreaGeom() {
+
+        Vector3f[] points = new Vector3f[8];
+
+        for (int i = 0; i < points.length; i++) {
+            points[i] = new Vector3f();
+        }
+
+        points[0].set(-1, -1, 1);
+        points[1].set(-1, 1, 1);
+        points[2].set(1, 1, 1);
+        points[3].set(1, -1, 1);
+
+        points[4].set(-1, -1, -1);
+        points[5].set(-1, 1, -1);
+        points[6].set(1, 1, -1);
+        points[7].set(1, -1, -1);
+
+        Mesh box = WireFrustum.makeFrustum(points);
+        areaGeom = new Geometry("light", (Mesh)box);
+        areaGeom.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"));
+        areaGeom.getMaterial().setColor("Color", ColorRGBA.White);
+    }
+
+    public void makeCamFrustum() {
+        Vector3f[] points = new Vector3f[8];
+        for (int i = 0; i < 8; i++) {
+            points[i] = new Vector3f();
+        }
+        ShadowUtil.updateFrustumPoints2(frustumCam, points);
+        WireFrustum frustumShape = new WireFrustum(points);
+        frustumGeom = new Geometry("frustum", frustumShape);
+        frustumGeom.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"));
+        rootNode.attachChild(frustumGeom);
+    }
+
+    public void makeBoxWire(BoundingBox box) {
+        Vector3f[] points = new Vector3f[8];
+        for (int i = 0; i < 8; i++) {
+            points[i] = new Vector3f();
+        }
+        points[0].set(-1, -1, 1);
+        points[1].set(-1, 1, 1);
+        points[2].set(1, 1, 1);
+        points[3].set(1, -1, 1);
+
+        points[4].set(-1, -1, -1);
+        points[5].set(-1, 1, -1);
+        points[6].set(1, 1, -1);
+        points[7].set(1, -1, -1);
+
+        WireFrustum frustumShape = new WireFrustum(points);
+        aabbGeom = new Geometry("box", frustumShape);
+        aabbGeom.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"));
+        aabbGeom.getMaterial().getAdditionalRenderState().setWireframe(true);
+        aabbGeom.setLocalTranslation(box.getCenter());
+        aabbGeom.setLocalScale(box.getXExtent(), box.getYExtent(), box.getZExtent());
+        rootNode.attachChild(aabbGeom);
+    }
+
+    public void makeSphereWire(BoundingSphere sphere) {
+
+        sphereGeom = new Geometry("box", new Sphere(16, 16, 10));
+        sphereGeom.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"));
+        sphereGeom.getMaterial().getAdditionalRenderState().setWireframe(true);
+        sphereGeom.setLocalTranslation(sphere.getCenter());
+        rootNode.attachChild(sphereGeom);
+    }
+
+
+    @Override
+    public void simpleUpdate(float tpf) {
+
+        area.setCenter(ln.getLocalTranslation());
+        area.setRotation(ln.getLocalRotation());
+
+        TempVars vars = TempVars.get();
+        boolean intersectBox = area.intersectsBox(aabb, vars);
+        boolean intersectFrustum = area.intersectsFrustum(frustumCam, vars);
+        boolean intersectSphere = area.intersectsSphere(sphere, vars);
+        vars.release();
+
+        boolean intersect = intersectBox || intersectFrustum || intersectSphere;
+
+        areaGeom.getMaterial().setColor("Color", intersect ? ColorRGBA.Green : ColorRGBA.White);
+        sphereGeom.getMaterial().setColor("Color", intersectSphere ? ColorRGBA.Cyan : ColorRGBA.White);
+        frustumGeom.getMaterial().setColor("Color", intersectFrustum ? ColorRGBA.Cyan : ColorRGBA.White);
+        aabbGeom.getMaterial().setColor("Color", intersectBox ? ColorRGBA.Cyan : ColorRGBA.White);
+
+    }
+}

+ 2 - 2
jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java

@@ -1,7 +1,6 @@
 package jme3test.light.pbr;
 
 import com.jme3.app.SimpleApplication;
-import com.jme3.bounding.BoundingSphere;
 import com.jme3.environment.EnvironmentCamera;
 import com.jme3.environment.LightProbeFactory;
 import com.jme3.environment.generation.JobProgressAdapter;
@@ -10,6 +9,7 @@ import com.jme3.input.KeyInput;
 import com.jme3.input.controls.ActionListener;
 import com.jme3.input.controls.KeyTrigger;
 import com.jme3.light.LightProbe;
+import com.jme3.light.SphereProbeArea;
 import com.jme3.material.Material;
 import com.jme3.math.*;
 import com.jme3.scene.*;
@@ -127,7 +127,7 @@ public class RefEnv extends SimpleApplication {
                     rootNode.getChild(0).setCullHint(Spatial.CullHint.Dynamic);
                 }
             });
-            ((BoundingSphere) probe.getBounds()).setRadius(100);
+            ((SphereProbeArea) probe.getArea()).setRadius(100);
             rootNode.addLight(probe);
 
         }

+ 2 - 3
jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java

@@ -42,8 +42,7 @@ import com.jme3.input.ChaseCamera;
 import com.jme3.input.KeyInput;
 import com.jme3.input.controls.ActionListener;
 import com.jme3.input.controls.KeyTrigger;
-import com.jme3.light.DirectionalLight;
-import com.jme3.light.LightProbe;
+import com.jme3.light.*;
 import com.jme3.material.Material;
 import com.jme3.math.*;
 import com.jme3.post.FilterPostProcessor;
@@ -205,7 +204,7 @@ public class TestPBRLighting extends SimpleApplication {
                     tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager);
                 }
             });
-            ((BoundingSphere) probe.getBounds()).setRadius(100);
+            ((SphereProbeArea) probe.getArea()).setRadius(100);
             rootNode.addLight(probe);
             //getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe);