Bläddra i källkod

PBR has come
Merge branch 'PBRisComing'

# Conflicts:
# jme3-core/src/main/java/com/jme3/material/Material.java
# jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java

Nehon 9 år sedan
förälder
incheckning
16023fa481
95 ändrade filer med 7991 tillägg och 26 borttagningar
  1. 337 0
      jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java
  2. 291 0
      jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java
  3. 176 0
      jme3-core/src/main/java/com/jme3/environment/generation/IrradianceMapGenerator.java
  4. 59 0
      jme3-core/src/main/java/com/jme3/environment/generation/JobProgressAdapter.java
  5. 67 0
      jme3-core/src/main/java/com/jme3/environment/generation/JobProgressListener.java
  6. 265 0
      jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java
  7. 89 0
      jme3-core/src/main/java/com/jme3/environment/generation/RunnableWithProgress.java
  8. 177 0
      jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.java
  9. 243 0
      jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java
  10. 947 0
      jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java
  11. 214 0
      jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java
  12. 70 0
      jme3-core/src/main/java/com/jme3/light/BasicProbeBlendingStrategy.java
  13. 20 2
      jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java
  14. 8 1
      jme3-core/src/main/java/com/jme3/light/Light.java
  15. 266 0
      jme3-core/src/main/java/com/jme3/light/LightProbe.java
  16. 208 0
      jme3-core/src/main/java/com/jme3/light/LightProbeBlendingProcessor.java
  17. 55 0
      jme3-core/src/main/java/com/jme3/light/LightProbeBlendingStrategy.java
  18. 107 0
      jme3-core/src/main/java/com/jme3/light/PoiLightProbeLightFilter.java
  19. 7 3
      jme3-core/src/main/java/com/jme3/material/Material.java
  20. 2 2
      jme3-core/src/main/java/com/jme3/material/Technique.java
  21. 45 0
      jme3-core/src/main/java/com/jme3/material/TechniqueDef.java
  22. 4 4
      jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java
  23. 3 1
      jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java
  24. 251 0
      jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java
  25. 3 1
      jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java
  26. 1 1
      jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java
  27. 1 1
      jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java
  28. 9 0
      jme3-core/src/main/java/com/jme3/renderer/RenderManager.java
  29. 7 0
      jme3-core/src/main/java/com/jme3/scene/Geometry.java
  30. 1 1
      jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md
  31. 238 0
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag
  32. 307 0
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md
  33. 65 0
      jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert
  34. 45 0
      jme3-core/src/main/resources/Common/MatDefs/Misc/reflect.j3md
  35. 22 0
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping.j3sn
  36. 8 0
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping100.frag
  37. 8 0
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping130.frag
  38. 25 0
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect.j3sn
  39. 14 0
      jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect100.vert
  40. 12 5
      jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib
  41. 154 0
      jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib
  42. BIN
      jme3-core/src/main/resources/Common/Textures/integrateBRDF.ktx
  43. 18 3
      jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java
  44. 6 0
      jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java
  45. 354 0
      jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXLoader.java
  46. 279 0
      jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXWriter.java
  47. 46 0
      jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/PixelReader.java
  48. 60 0
      jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTdRiPixelReader.java
  49. 58 0
      jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTuRoPixelReader.java
  50. 1 1
      jme3-examples/src/main/java/jme3test/batching/TestBatchNodeCluster.java
  51. 90 0
      jme3-examples/src/main/java/jme3test/light/TestColorApp.java
  52. 131 0
      jme3-examples/src/main/java/jme3test/light/TestShadowBug.java
  53. 96 0
      jme3-examples/src/main/java/jme3test/light/TestTangentCube.java
  54. 71 0
      jme3-examples/src/main/java/jme3test/light/pbr/ConsoleProgressReporter.java
  55. 141 0
      jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java
  56. 199 0
      jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java
  57. 387 0
      jme3-examples/src/main/java/jme3test/light/pbr/TestPbrEnv.java
  58. 157 0
      jme3-examples/src/main/java/jme3test/material/TestParallaxPBR.java
  59. 180 0
      jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java
  60. 112 0
      jme3-examples/src/main/java/jme3test/texture/ktx/TestLoadKtx.java
  61. 13 0
      jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat.j3m
  62. 12 0
      jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat2.j3m
  63. 9 0
      jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat3.j3m
  64. 11 0
      jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat4.j3m
  65. BIN
      jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefDE.png
  66. BIN
      jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefM.png
  67. BIN
      jme3-testdata/src/main/resources/Models/Tank/Tank_Base_Color.png
  68. BIN
      jme3-testdata/src/main/resources/Models/Tank/Tank_Emissive.png
  69. BIN
      jme3-testdata/src/main/resources/Models/Tank/Tank_Metallic.png
  70. BIN
      jme3-testdata/src/main/resources/Models/Tank/Tank_Normal.png
  71. BIN
      jme3-testdata/src/main/resources/Models/Tank/Tank_Roughness.png
  72. 14 0
      jme3-testdata/src/main/resources/Models/Tank/tank.j3m
  73. BIN
      jme3-testdata/src/main/resources/Models/Tank/tank.j3o
  74. 3 0
      jme3-testdata/src/main/resources/Models/Tank/tank.j3odata
  75. BIN
      jme3-testdata/src/main/resources/Scenes/PBR/spheres.j3o
  76. BIN
      jme3-testdata/src/main/resources/Textures/Sky/Path.hdr
  77. 9 0
      jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWallPBR.j3m
  78. 8 0
      jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWallPBR2.j3m
  79. BIN
      jme3-testdata/src/main/resources/Textures/ktx/down-reference.ktx
  80. BIN
      jme3-testdata/src/main/resources/Textures/ktx/irrMap.ktx
  81. BIN
      jme3-testdata/src/main/resources/Textures/ktx/prefilteredMap.ktx
  82. BIN
      jme3-testdata/src/main/resources/Textures/ktx/rgb-amg-reference.ktx
  83. BIN
      jme3-testdata/src/main/resources/Textures/ktx/rgb-mipmap-reference.ktx
  84. BIN
      jme3-testdata/src/main/resources/Textures/ktx/rgba-reference.ktx
  85. BIN
      jme3-testdata/src/main/resources/Textures/ktx/up-reference.ktx
  86. 131 0
      sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbe.java
  87. 99 0
      sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbeButtonProperty.java
  88. 42 0
      sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbeProgressHandler.java
  89. 57 0
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightGizmoControl.java
  90. 68 0
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightGizmoFactory.java
  91. 59 0
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightProbeGizmoControl.java
  92. 23 0
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed.j3sn
  93. 11 0
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed100.frag
  94. 37 0
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/dashed.j3md
  95. 168 0
      sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/shape/ProbeRadiusShape.java

+ 337 - 0
jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java

@@ -0,0 +1,337 @@
+/*
+ * 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.environment;
+
+import com.jme3.app.Application;
+import com.jme3.app.state.BaseAppState;
+import com.jme3.environment.generation.JobProgressListener;
+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.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.image.ColorSpace;
+import com.jme3.util.BufferUtils;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * A 360 camera that can capture a cube map of a scene, and then generate the
+ * Prefiltered Environment cube Map and the Irradiance cube Map needed for PBR
+ * indirect lighting
+ *
+ * @see LightProbeFactory
+ * @see LightProbe
+ *
+ * @author Nehon
+ */
+public class EnvironmentCamera extends BaseAppState {
+
+    protected static Vector3f[] axisX = new Vector3f[6];
+    protected static Vector3f[] axisY = new Vector3f[6];
+    protected static Vector3f[] axisZ = new Vector3f[6];
+
+    protected Image.Format imageFormat = Image.Format.RGB16F;
+
+    //Axis for cameras
+    static {
+        //PositiveX axis(left, up, direction)
+        axisX[0] = Vector3f.UNIT_Z.mult(1f);
+        axisY[0] = Vector3f.UNIT_Y.mult(-1f);
+        axisZ[0] = Vector3f.UNIT_X.mult(1f);
+        //NegativeX
+        axisX[1] = Vector3f.UNIT_Z.mult(-1f);
+        axisY[1] = Vector3f.UNIT_Y.mult(-1f);
+        axisZ[1] = Vector3f.UNIT_X.mult(-1f);
+        //PositiveY
+        axisX[2] = Vector3f.UNIT_X.mult(-1f);
+        axisY[2] = Vector3f.UNIT_Z.mult(1f);
+        axisZ[2] = Vector3f.UNIT_Y.mult(1f);
+        //NegativeY
+        axisX[3] = Vector3f.UNIT_X.mult(-1f);
+        axisY[3] = Vector3f.UNIT_Z.mult(-1f);
+        axisZ[3] = Vector3f.UNIT_Y.mult(-1f);
+        //PositiveZ
+        axisX[4] = Vector3f.UNIT_X.mult(-1f);
+        axisY[4] = Vector3f.UNIT_Y.mult(-1f);
+        axisZ[4] = Vector3f.UNIT_Z;
+        //NegativeZ
+        axisX[5] = Vector3f.UNIT_X.mult(1f);
+        axisY[5] = Vector3f.UNIT_Y.mult(-1f);
+        axisZ[5] = Vector3f.UNIT_Z.mult(-1f);
+
+    }
+    protected Image images[];
+    protected ViewPort[] viewports;
+    protected FrameBuffer[] framebuffers;
+    protected ByteBuffer[] buffers;
+
+    protected Vector3f position = new Vector3f();
+    protected ColorRGBA backGroundColor;
+
+    protected int size = 128;
+
+    private final List<SnapshotJob> jobs = new ArrayList<SnapshotJob>();
+
+    /**
+     * Creates an EnvironmentCamera with a size of 128
+     */
+    public EnvironmentCamera() {
+    }
+
+    /**
+     * Creates an EnvironmentCamera with the given size.
+     *
+     * @param size the size of the resulting texture.
+     */
+    public EnvironmentCamera(int size) {
+        this.size = size;
+    }
+
+    /**
+     * Creates an EnvironmentCamera with the given size, and the given position
+     *
+     * @param size the size of the resulting texture.
+     * @param position the position of the camera.
+     */
+    public EnvironmentCamera(int size, Vector3f position) {
+        this.size = size;
+        this.position.set(position);
+    }
+
+    /**
+     * Creates an EnvironmentCamera with the given size, and the given position
+     *
+     * @param size the size of the resulting texture, and the given ImageFormat.
+     * @param position the position of the camera.
+     * @param imageFormat the ImageFormat to use for the resulting texture.
+     */
+    public EnvironmentCamera(int size, Vector3f position, Image.Format imageFormat) {
+        this.size = size;
+        this.position.set(position);
+        this.imageFormat = imageFormat;
+    }
+
+    /**
+     * Takes a snapshot of the surrounding scene.
+     *
+     * @param scene the scene to snapshot.
+     * @param done a callback to call when the snapshot is done.
+     */
+    public void snapshot(final Spatial scene, final JobProgressListener<TextureCubeMap> done) {
+        getApplication().enqueue(new Callable<Void>() {
+
+            @Override
+            public Void call() throws Exception {
+                SnapshotJob job = new SnapshotJob(done, scene);
+                jobs.add(job);
+                return null;
+            }
+        });
+    }
+
+    @Override
+    public void render(final RenderManager renderManager) {
+
+        if (jobs.isEmpty()) {
+            return;
+        }
+
+        final SnapshotJob job = jobs.get(0);
+
+        for (int i = 0; i < 6; i++) {
+            viewports[i].clearScenes();
+            viewports[i].attachScene(job.scene);
+            renderManager.renderViewPort(viewports[i], 0.16f);
+            buffers[i] = BufferUtils.createByteBuffer(size * size * imageFormat.getBitsPerPixel() / 8);
+            renderManager.getRenderer().readFrameBufferWithFormat(framebuffers[i], buffers[i], imageFormat);
+            images[i] = new Image(imageFormat, size, size, buffers[i], ColorSpace.Linear);
+        }
+
+        final TextureCubeMap map = EnvMapUtils.makeCubeMap(images[0], images[1], images[2], images[3], images[4], images[5], imageFormat);
+
+        job.callback.done(map);
+        map.getImage().dispose();
+        jobs.remove(0);
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    public Vector3f getPosition() {
+        return position;
+    }
+
+    /**
+     * Sets the camera position in world space.
+     *
+     * @param position the position in world space
+     */
+    public void setPosition(final Vector3f position) {
+        this.position.set(position);
+
+        if (viewports == null) {
+            return;
+        }
+
+        for (final ViewPort viewPort : viewports) {
+            viewPort.getCamera().setLocation(position);
+        }
+    }
+
+    @Override
+    protected void initialize(Application app) {
+        this.backGroundColor = app.getViewPort().getBackgroundColor();
+
+        final Camera[] cameras = new Camera[6];
+
+        Texture2D[] textures = new Texture2D[6];
+
+        viewports = new ViewPort[6];
+        framebuffers = new FrameBuffer[6];
+        buffers = new ByteBuffer[6];
+        images = new Image[6];
+
+        for (int i = 0; i < 6; i++) {
+            cameras[i] = createOffCamera(size, position, axisX[i], axisY[i], axisZ[i]);
+            viewports[i] = createOffViewPort("EnvView" + i, cameras[i]);
+            framebuffers[i] = createOffScreenFrameBuffer(size, viewports[i]);
+            textures[i] = new Texture2D(size, size, imageFormat);
+            framebuffers[i].setColorTexture(textures[i]);
+        }
+    }
+
+    @Override
+    protected void cleanup(Application app) {
+        this.backGroundColor = null;
+
+        for (final FrameBuffer frameBuffer : framebuffers) {
+            frameBuffer.dispose();
+        }
+
+        for (final Image image : images) {
+            if( image != null){
+                image.dispose();
+            }
+        }
+    }
+
+    /**
+     * returns the images format used for the generated maps.
+     *
+     * @return
+     */
+    public Image.Format getImageFormat() {
+        return imageFormat;
+    }
+
+    @Override
+    protected void onEnable() {
+    }
+
+    @Override
+    protected void onDisable() {
+    }
+
+    /**
+     * Creates an off camera
+     *
+     * @param mapSize the size
+     * @param worldPos the position
+     * @param axisX the x axis
+     * @param axisY the y axis
+     * @param axisZ tha z axis
+     * @return
+     */
+    protected Camera createOffCamera(final int mapSize, final Vector3f worldPos, final Vector3f axisX, final Vector3f axisY, final Vector3f axisZ) {
+        final Camera offCamera = new Camera(mapSize, mapSize);
+        offCamera.setLocation(worldPos);
+        offCamera.setAxes(axisX, axisY, axisZ);
+        offCamera.setFrustumPerspective(90f, 1f, 1, 1000);
+        offCamera.setLocation(position);
+        return offCamera;
+    }
+
+    /**
+     * creates an offsceen VP
+     *
+     * @param name
+     * @param offCamera
+     * @return
+     */
+    protected ViewPort createOffViewPort(final String name, final Camera offCamera) {
+        final ViewPort offView = new ViewPort(name, offCamera);
+        offView.setClearFlags(true, true, true);
+        offView.setBackgroundColor(backGroundColor);
+        return offView;
+    }
+
+    /**
+     * create an offscreen frame buffer.
+     *
+     * @param mapSize
+     * @param offView
+     * @return
+     */
+    protected FrameBuffer createOffScreenFrameBuffer(int mapSize, ViewPort offView) {
+        // create offscreen framebuffer
+        final FrameBuffer offBuffer = new FrameBuffer(mapSize, mapSize, 1);
+        offBuffer.setDepthBuffer(Image.Format.Depth);
+        offView.setOutputFrameBuffer(offBuffer);
+        return offBuffer;
+    }
+
+    /**
+     * An inner class to keep track on a snapshot job.
+     */
+    protected class SnapshotJob {
+
+        JobProgressListener<TextureCubeMap> callback;
+        Spatial scene;
+
+        public SnapshotJob(JobProgressListener callback, Spatial scene) {
+            this.callback = callback;
+            this.scene = scene;
+        }
+    }
+}

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

@@ -0,0 +1,291 @@
+/*
+ * 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.environment;
+
+import com.jme3.light.LightProbe;
+import com.jme3.environment.generation.JobProgressListener;
+import com.jme3.environment.generation.PrefilteredEnvMapFaceGenerator;
+import com.jme3.environment.generation.IrradianceMapGenerator;
+import com.jme3.environment.util.EnvMapUtils;
+import com.jme3.environment.generation.JobProgressAdapter;
+import com.jme3.app.Application;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.TextureCubeMap;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+/**
+ * This Factory allows to create LightProbes within a scene given an EnvironmentCamera.
+ * 
+ * Since the process can be long, you can provide a JobProgressListener that 
+ * will be notified of the ongoing generation process when calling the makeProbe method.
+ * 
+ * The process is the folowing : 
+ * 1. Create an EnvironmentCamera
+ * 2. give it a position in the scene
+ * 3. call {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node)}
+ * 4. add the created LightProbe to a node with the {@link Node#addLight(com.jme3.light.Light) } method.
+ * 
+ * Optionally for step 3 call {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node, com.jme3.environment.generation.JobProgressListener) }
+ * with a {@link JobProgressListener} to be notified of the progress of the generation process.
+ * 
+ * The generation will be split in several threads for faster generation. 
+ * 
+ * This class is entirely thread safe and can be called from any thread. 
+ * 
+ * Note that in case you are using a {@link JobProgressListener} all the its 
+ * method will be called inside and app.enqueu callable.
+ * This means that it's completely safe to modify the scenegraph within the 
+ * Listener method, but also means that the even will be delayed until next update loop.
+ * 
+ * @see EnvironmentCamera
+ * @author bouquet
+ */
+public class LightProbeFactory {
+
+    /**
+     * Creates a LightProbe with the giver EnvironmentCamera in the given scene.
+     * 
+     * Note that this is an assynchronous process that will run on multiple threads.
+     * The process is thread safe.
+     * The created lightProbe will only be marked as ready when the rendering process is done.
+     * 
+     * If you want to monitor the process use {@link LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Node, com.jme3.environment.generation.JobProgressListener) }
+     * 
+     *
+     * 
+     * @see LightProbe
+     * @see EnvironmentCamera
+     * @param envCam the EnvironmentCamera
+     * @param scene the Scene
+     * @return the created LightProbe
+     */
+    public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene) {
+        return makeProbe(envCam, scene, null);
+    }
+
+    /**
+     * Creates a LightProbe with the giver EnvironmentCamera in the given scene.
+     * 
+     * Note that this is an assynchronous process that will run on multiple threads.
+     * The process is thread safe.
+     * The created lightProbe will only be marked as ready when the rendering process is done.
+     *      
+     * The JobProgressListener will be notified of the progress of the generation. 
+     * Note that you can also use a {@link JobProgressAdapter}. 
+     * 
+     * @see LightProbe
+     * @see EnvironmentCamera
+     * @see JobProgressListener
+     
+     * @param envCam the EnvironmentCamera
+     * @param scene the Scene
+     * @param listener the listener of the genration progress.
+     * @return the created LightProbe
+     */
+    public static LightProbe makeProbe(final EnvironmentCamera envCam, Spatial scene, final JobProgressListener<LightProbe> listener) {
+        final LightProbe probe = new LightProbe();
+        probe.setPosition(envCam.getPosition());
+        probe.setIrradianceMap(EnvMapUtils.createIrradianceMap(envCam.getSize(), envCam.getImageFormat()));
+        probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat()));
+        envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() {
+
+            @Override
+            public void done(TextureCubeMap map) {
+                generatePbrMaps(map, probe, envCam.getApplication(), listener);
+            }
+        });
+        return probe;
+    }
+    
+     /**
+     * Updates a LightProbe with the giver EnvironmentCamera in the given scene.
+     * 
+     * Note that this is an assynchronous process that will run on multiple threads.
+     * The process is thread safe.
+     * The created lightProbe will only be marked as ready when the rendering process is done.
+     *      
+     * The JobProgressListener will be notified of the progress of the generation. 
+     * Note that you can also use a {@link JobProgressAdapter}. 
+     *      
+     * @see LightProbe
+     * @see EnvironmentCamera
+     * @see JobProgressListener
+     * 
+     * @param probe the Light probe to update
+     * @param envCam the EnvironmentCamera
+     * @param scene the Scene
+     * @param listener the listener of the genration progress.
+     * @return the created LightProbe
+     */
+    public static LightProbe updateProbe(final LightProbe probe, final EnvironmentCamera envCam, Spatial scene, final JobProgressListener<LightProbe> listener) {
+        
+        envCam.setPosition(probe.getPosition());
+        
+        probe.setReady(false);
+        
+        if(probe.getIrradianceMap() != null) {
+            probe.getIrradianceMap().getImage().dispose();
+            probe.getPrefilteredEnvMap().getImage().dispose();
+        }
+        
+        probe.setIrradianceMap(EnvMapUtils.createIrradianceMap(envCam.getSize(), envCam.getImageFormat()));
+        probe.setPrefilteredMap(EnvMapUtils.createPrefilteredEnvMap(envCam.getSize(), envCam.getImageFormat()));
+        
+        
+        envCam.snapshot(scene, new JobProgressAdapter<TextureCubeMap>() {
+
+            @Override
+            public void done(TextureCubeMap map) {
+                generatePbrMaps(map, probe, envCam.getApplication(), listener);
+            }
+        });
+        return probe;
+    }
+
+    /**
+     * Internally called to generate the maps.
+     * This method will spawn 7 thread (one for the IrradianceMap, and one for each face of the prefiltered env map).
+     * Those threads will be executed in a ScheduledThreadPoolExecutor that will be shutdown when the genration is done.
+     * 
+     * @param envMap the raw env map rendered by the env camera
+     * @param probe the LigthProbe to generate maps for
+     * @param app the Application
+     * @param listener a progress listener. (can be null if no progress reporting is needed)
+     */
+    private static void generatePbrMaps(TextureCubeMap envMap, final LightProbe probe, Application app, final JobProgressListener<LightProbe> listener) {
+        IrradianceMapGenerator irrMapGenerator;
+        PrefilteredEnvMapFaceGenerator[] pemGenerators = new PrefilteredEnvMapFaceGenerator[6];
+
+        final JobState jobState = new JobState(new ScheduledThreadPoolExecutor(7));
+
+        irrMapGenerator = new IrradianceMapGenerator(app, new JobListener(listener, jobState, probe, 6));
+        int size = envMap.getImage().getWidth();
+        irrMapGenerator.setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.Wrap, probe.getIrradianceMap());
+
+        jobState.executor.execute(irrMapGenerator);
+
+        for (int i = 0; i < pemGenerators.length; i++) {
+            pemGenerators[i] = new PrefilteredEnvMapFaceGenerator(app, i, new JobListener(listener, jobState, probe, i));
+            pemGenerators[i].setGenerationParam(EnvMapUtils.duplicateCubeMap(envMap), size, EnvMapUtils.FixSeamsMethod.Wrap, probe.getPrefilteredEnvMap());
+            jobState.executor.execute(pemGenerators[i]);
+        }
+    }
+
+    /**
+     * An inner class to keep the state of a generation process
+     */
+    private static class JobState {
+
+        double progress[] = new double[7];
+        boolean done[] = new boolean[7];
+        ScheduledThreadPoolExecutor executor;
+        boolean started = false;
+
+        public JobState(ScheduledThreadPoolExecutor executor) {
+            this.executor = executor;
+        }
+
+        boolean isDone() {
+            for (boolean d : done) {
+                if (d == false) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        float getProgress() {
+            float mean = 0;
+            for (double progres : progress) {
+                mean += progres;
+            }
+            return mean / 7f;
+        }
+    }
+
+    /**
+     * An inner JobProgressListener to controll the genration process and properly clean up when it's done
+     */
+    private static class JobListener extends JobProgressAdapter<Integer> {
+
+        JobProgressListener<LightProbe> globalListener;
+        JobState jobState;
+        LightProbe probe;
+
+        int index;
+
+        public JobListener(JobProgressListener<LightProbe> globalListener, JobState jobState, LightProbe probe, int index) {
+            this.globalListener = globalListener;
+            this.jobState = jobState;
+            this.probe = probe;
+            this.index = index;
+        }
+
+        @Override
+        public void start() {
+            if (globalListener != null && !jobState.started) {
+                jobState.started = true;
+                globalListener.start();
+            }
+        }
+
+        @Override
+        public void progress(double value) {
+            jobState.progress[index] = value;
+            if (globalListener != null) {
+                globalListener.progress(jobState.getProgress());
+            }
+        }
+
+        @Override
+        public void done(Integer result) {
+            if (globalListener != null) {
+                if (result < 6) {
+                    globalListener.step("Prefiltered env map face " + result + " generated");
+                } else {
+                    globalListener.step("Irradiance map generated");
+
+                }
+            }
+
+            jobState.done[index] = true;
+            if (jobState.isDone()) {
+                probe.setReady(true);
+                if (globalListener != null) {
+                    globalListener.done(probe);
+                }
+                jobState.executor.shutdownNow();
+            }
+        }
+    }
+}

+ 176 - 0
jme3-core/src/main/java/com/jme3/environment/generation/IrradianceMapGenerator.java

@@ -0,0 +1,176 @@
+/*
+ * 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.environment.generation;
+
+import com.jme3.environment.util.CubeMapWrapper;
+import com.jme3.environment.util.EnvMapUtils;
+import com.jme3.app.Application;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.texture.TextureCubeMap;
+import static com.jme3.environment.util.EnvMapUtils.shBandFactor;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Callable;
+
+/**
+ *
+ * Generates the Irrafiance map for PBR. This job can be lauched from a separate
+ * thread.
+ *
+ * TODO there is a lot of duplicate code here with the EnvMapUtils.
+ *
+ * @author Nehon
+ */
+//TODO there is a lot of duplicate code here with the EnvMapUtils. We should, 
+//either leverage the code from the util class either remove it and only allow 
+//parallel generation using this runnable.
+public class IrradianceMapGenerator extends RunnableWithProgress {
+
+    private int targetMapSize;
+    private EnvMapUtils.FixSeamsMethod fixSeamsMethod;
+    private TextureCubeMap sourceMap;
+    private TextureCubeMap store;
+    private final Application app;
+
+    /**
+     * Creates an Irradiance map generator. The app is needed to enqueue the
+     * call to the EnvironmentCamera when the generation is done, so that this
+     * process is thread safe.
+     *
+     * @param app the Application
+     * @param listener
+     */
+    public IrradianceMapGenerator(Application app, JobProgressListener<Integer> listener) {
+        super(listener);
+        this.app = app;
+    }
+
+    /**
+     * Fills all the genration parameters
+     *
+     * @param sourceMap the source cube map
+     * @param targetMapSize the size of the generated map (width or height in
+     * pixel)
+     * @param fixSeamsMethod the method used to fix seams as described here
+     * {@link EnvMapUtils.FixSeamsMethod}
+     *
+     * @param store The cube map to store the result in.
+     */
+    public void setGenerationParam(TextureCubeMap sourceMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
+        this.sourceMap = sourceMap;
+        this.targetMapSize = targetMapSize;
+        this.fixSeamsMethod = fixSeamsMethod;
+        this.store = store;
+        reset();
+    }
+
+    @Override
+    public void run() {
+        app.enqueue(new Callable<Void>() {
+
+            @Override
+            public Void call() throws Exception {
+                listener.start();
+                return null;
+            }
+        });
+        try {
+            Vector3f[] shCoeffs = EnvMapUtils.getSphericalHarmonicsCoefficents(sourceMap);
+            store = generateIrradianceMap(shCoeffs, targetMapSize, fixSeamsMethod, store);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        app.enqueue(new Callable<Void>() {
+
+            @Override
+            public Void call() throws Exception {
+                listener.done(6);
+                return null;
+            }
+        });
+    }
+
+    /**
+     * Generates the Irradiance map (used for image based difuse lighting) from
+     * Spherical Harmonics coefficients previously computed with
+     * {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)}
+     *
+     * @param shCoeffs the SH coeffs
+     * @param targetMapSize the size of the irradiance map to generate
+     * @param fixSeamsMethod the method to fix seams
+     * @param store
+     * @return The irradiance cube map for the given coefficients
+     */
+    public TextureCubeMap generateIrradianceMap(Vector3f[] shCoeffs, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
+        TextureCubeMap irrCubeMap = store;
+
+        setEnd(6 + 6);
+        for (int i = 0; i < 6; i++) {
+            ByteBuffer buf = BufferUtils.createByteBuffer(targetMapSize * targetMapSize * store.getImage().getFormat().getBitsPerPixel() / 8);
+            irrCubeMap.getImage().setData(i, buf);
+            progress();
+        }
+
+        Vector3f texelVect = new Vector3f();
+        ColorRGBA color = new ColorRGBA(ColorRGBA.Black);
+        float[] shDir = new float[9];
+        CubeMapWrapper envMapWriter = new CubeMapWrapper(irrCubeMap);
+        for (int face = 0; face < 6; face++) {
+
+            for (int y = 0; y < targetMapSize; y++) {
+                for (int x = 0; x < targetMapSize; x++) {
+                    EnvMapUtils.getVectorFromCubemapFaceTexCoord(x, y, targetMapSize, face, texelVect, fixSeamsMethod);
+                    EnvMapUtils.evalShBasis(texelVect, shDir);
+                    color.set(0, 0, 0, 0);
+                    for (int i = 0; i < EnvMapUtils.NUM_SH_COEFFICIENT; i++) {
+                        color.set(color.r + shCoeffs[i].x * shDir[i] * shBandFactor[i],
+                                color.g + shCoeffs[i].y * shDir[i] * shBandFactor[i],
+                                color.b + shCoeffs[i].z * shDir[i] * shBandFactor[i],
+                                1.0f);
+                    }
+
+                    //clamping the color because very low value close to zero produce artifacts
+                    color.r = Math.max(0.0001f, color.r);
+                    color.g = Math.max(0.0001f, color.g);
+                    color.b = Math.max(0.0001f, color.b);
+
+                    envMapWriter.setPixel(x, y, face, color);
+                    
+                }
+            }
+            progress();
+        }
+        return irrCubeMap;
+    }
+
+}

+ 59 - 0
jme3-core/src/main/java/com/jme3/environment/generation/JobProgressAdapter.java

@@ -0,0 +1,59 @@
+/*
+ * 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.environment.generation;
+
+/**
+ * Abstract Adapter class that implements optional methods of JobProgressListener.
+ * Extends this class instead of implementing a JobProgressListener if you need 
+ * only a subset of method implemented.
+ * 
+ * @author nehon
+ * @param <T>
+ */
+public abstract class JobProgressAdapter<T> implements JobProgressListener<T>{
+
+    @Override
+    public void progress(double value) {        
+    }
+
+    @Override
+    public void start() {
+    }
+
+    @Override
+    public void step(String message) {
+    }
+
+    @Override
+    public abstract void done(T result);
+    
+}

+ 67 - 0
jme3-core/src/main/java/com/jme3/environment/generation/JobProgressListener.java

@@ -0,0 +1,67 @@
+/*
+ * 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.environment.generation;
+
+/**
+ * An interface listener that will be notified of the progress of an asynchronous
+ * generation job.
+ *  
+ *
+ * @author nehon
+ * @param <T> The type of object generated.
+ */
+public interface JobProgressListener<T> {
+    
+    /**
+     * Called when the process starts.
+     */
+    public void start();
+    
+    /**
+     * Can be called when a step of the process has been completed with a relevant message.
+     * @param message the message stating of the paricular step completion.
+     */
+    public void step(String message);
+    
+    /**
+     * Called when the process has made some progress.
+     * @param value a value from 0 to 1 representing the percentage of completion of the process.
+     */
+    public void progress(double value);
+    
+    /**
+     * Called when the process is done.
+     * @param result the object generated by the process.
+     */
+    public void done(T result);
+    
+}

+ 265 - 0
jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java

@@ -0,0 +1,265 @@
+/*
+ * 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.environment.generation;
+
+import com.jme3.environment.util.CubeMapWrapper;
+import com.jme3.environment.util.EnvMapUtils;
+import com.jme3.app.Application;
+import com.jme3.math.ColorRGBA;
+import static com.jme3.math.FastMath.abs;
+import static com.jme3.math.FastMath.clamp;
+import static com.jme3.math.FastMath.pow;
+import static com.jme3.math.FastMath.sqrt;
+import com.jme3.math.Vector3f;
+import com.jme3.math.Vector4f;
+import com.jme3.texture.TextureCubeMap;
+import static com.jme3.environment.util.EnvMapUtils.getHammersleyPoint;
+import static com.jme3.environment.util.EnvMapUtils.getRoughnessFromMip;
+import static com.jme3.environment.util.EnvMapUtils.getSampleFromMip;
+import static com.jme3.environment.util.EnvMapUtils.getVectorFromCubemapFaceTexCoord;
+import java.util.concurrent.Callable;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * Generates one face of the prefiltered environnement map for PBR. This job can
+ * be lauched from a separate thread.
+ *
+ * TODO there is a lot of duplicate code here with the EnvMapUtils.
+ *
+ * @author Nehon
+ */
+//TODO there is a lot of duplicate code here with the EnvMapUtils. We should, 
+//either leverage the code from the util class either remove it and only allow 
+//parallel generation using this runnable.
+public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress {
+
+    private final static Logger log = Logger.getLogger(PrefilteredEnvMapFaceGenerator.class.getName());
+
+    private int targetMapSize;
+    private EnvMapUtils.FixSeamsMethod fixSeamsMethod;
+    private TextureCubeMap sourceMap;
+    private TextureCubeMap store;
+    private final Application app;
+    private int face = 0;
+    Vector4f Xi = new Vector4f();
+    Vector3f H = new Vector3f();
+    Vector3f tmp = new Vector3f();
+    ColorRGBA c = new ColorRGBA();
+    Vector3f tmp1 = new Vector3f();
+    Vector3f tmp2 = new Vector3f();
+    Vector3f tmp3 = new Vector3f();
+
+    /**
+     * Creates a pem generator for the given face. The app is needed to enqueue
+     * the call to the EnvironmentCamera when the generation is done, so that
+     * this process is thread safe.
+     *
+     * @param app the Application
+     * @param face the face to generate
+     * @param listener
+     */
+    public PrefilteredEnvMapFaceGenerator(Application app, int face, JobProgressListener<Integer> listener) {
+        super(listener);
+        this.app = app;
+        this.face = face;
+    }
+
+    
+    
+    /**
+     * Fills all the genration parameters
+     *
+     * @param sourceMap the source cube map
+     * @param targetMapSize the size of the generated map (width or height in
+     * pixel)
+     * @param fixSeamsMethod the method used to fix seams as described here
+     * {@link EnvMapUtils.FixSeamsMethod}
+     *
+     * @param store The cube map to store the result in.
+     */
+    public void setGenerationParam(TextureCubeMap sourceMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
+        this.sourceMap = sourceMap;
+        this.targetMapSize = targetMapSize;
+        this.fixSeamsMethod = fixSeamsMethod;
+        this.store = store;
+        init();
+    }
+    
+    private void init(){
+         Xi.set(0, 0, 0, 0);
+         H.set(0, 0, 0);
+         tmp.set(0, 0, 0);
+         c.set(1, 1, 1, 1);
+         tmp1.set(0, 0, 0);
+         tmp2.set(0, 0, 0);
+         tmp3.set(0, 0, 0);
+         reset();
+
+    }
+
+    @Override
+    public void run() {
+
+        app.enqueue(new Callable<Void>() {
+
+            @Override
+            public Void call() throws Exception {
+                listener.start();
+                return null;
+            }
+        });
+        store = generatePrefilteredEnvMap(sourceMap, targetMapSize, fixSeamsMethod, store);
+        app.enqueue(new Callable<Void>() {
+
+            @Override
+            public Void call() throws Exception {
+                listener.done(face);
+                return null;
+            }
+        });
+    }
+
+    /**
+     * Generates the prefiltered env map (used for image based specular
+     * lighting) With the GGX/Shlick brdf
+     * {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)}
+     * Note that the output cube map is in RGBA8 format.
+     *
+     * @param sourceEnvMap
+     * @param targetMapSize the size of the irradiance map to generate
+     * @param store
+     * @param fixSeamsMethod the method to fix seams
+     * @return The irradiance cube map for the given coefficients
+     */
+    private TextureCubeMap generatePrefilteredEnvMap(TextureCubeMap sourceEnvMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
+        TextureCubeMap pem = store;
+
+        int nbMipMap = (int) (Math.log(targetMapSize) / Math.log(2) - 1);
+
+        setEnd(nbMipMap);
+        
+
+        CubeMapWrapper sourceWrapper = new CubeMapWrapper(sourceEnvMap);
+        CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
+
+        Vector3f texelVect = new Vector3f();
+        Vector3f color = new Vector3f();
+        ColorRGBA outColor = new ColorRGBA();
+        for (int mipLevel = 0; mipLevel < nbMipMap; mipLevel++) {
+            float roughness = getRoughnessFromMip(mipLevel, nbMipMap);
+            int nbSamples = getSampleFromMip(mipLevel, nbMipMap);
+            int targetMipMapSize = (int) pow(2, nbMipMap + 1 - mipLevel);
+
+            for (int y = 0; y < targetMipMapSize; y++) {
+                for (int x = 0; x < targetMipMapSize; x++) {
+                    color.set(0, 0, 0);
+                    getVectorFromCubemapFaceTexCoord(x, y, targetMipMapSize, face, texelVect, EnvMapUtils.FixSeamsMethod.Wrap);
+                    prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, color);
+                    
+                    outColor.set(Math.max(color.x, 0.0001f), Math.max(color.y,0.0001f), Math.max(color.z, 0.0001f), 1);
+                    log.log(Level.FINE, "coords {0},{1}", new Object[]{x, y});
+                    targetWrapper.setPixel(x, y, face, mipLevel, outColor);
+
+                }
+            }
+            progress();
+        }
+
+        return pem;
+    }
+
+    private Vector3f prefilterEnvMapTexel(CubeMapWrapper envMapReader, float roughness, Vector3f N, int numSamples, Vector3f store) {
+
+        Vector3f prefilteredColor = store;
+        float totalWeight = 0.0f;
+
+        // a = roughness² and a2 = a²
+        float a2 = roughness * roughness;
+        a2 *= a2;
+        a2 *= 10;
+        for (int i = 0; i < numSamples; i++) {
+            Xi = getHammersleyPoint(i, numSamples, Xi);
+            H = importanceSampleGGX(Xi, a2, N, H);
+
+            H.normalizeLocal();
+            tmp.set(H);
+            float NoH = N.dot(tmp);
+
+            Vector3f L = tmp.multLocal(NoH * 2).subtractLocal(N);
+            float NoL = clamp(N.dot(L), 0.0f, 1.0f);
+            if (NoL > 0) {
+                envMapReader.getPixel(L, c);
+                prefilteredColor.setX(prefilteredColor.x + c.r * NoL);
+                prefilteredColor.setY(prefilteredColor.y + c.g * NoL);
+                prefilteredColor.setZ(prefilteredColor.z + c.b * NoL);
+
+                totalWeight += NoL;
+            }
+        }
+
+        return prefilteredColor.divideLocal(totalWeight);
+    }
+
+    public Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f normal, Vector3f store) {
+        if (store == null) {
+            store = new Vector3f();
+        }
+
+        float cosTheta = sqrt((1f - xi.x) / (1f + (a2 - 1f) * xi.x));
+        float sinTheta = sqrt(1f - cosTheta * cosTheta);
+
+        float sinThetaCosPhi = sinTheta * xi.z;//xi.z is cos(phi)
+        float sinThetaSinPhi = sinTheta * xi.w;//xi.w is sin(phi)
+
+        Vector3f upVector = Vector3f.UNIT_X;
+
+        if (abs(normal.z) < 0.999) {
+            upVector = Vector3f.UNIT_Y;
+        }
+
+        Vector3f tangentX = tmp1.set(upVector).crossLocal(normal).normalizeLocal();
+        Vector3f tangentY = tmp2.set(normal).crossLocal(tangentX);
+
+        // Tangent to world space
+        tangentX.multLocal(sinThetaCosPhi);
+        tangentY.multLocal(sinThetaSinPhi);
+        tmp3.set(normal).multLocal(cosTheta);
+
+        // Tangent to world space
+        store.set(tangentX).addLocal(tangentY).addLocal(tmp3);
+
+        return store;
+    }
+
+}

+ 89 - 0
jme3-core/src/main/java/com/jme3/environment/generation/RunnableWithProgress.java

@@ -0,0 +1,89 @@
+/*
+ * 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.environment.generation;
+
+/**
+ *
+ * Abstract runnable that can report its progress
+ *
+ * @author Nehon
+ */
+public abstract class RunnableWithProgress implements Runnable {
+
+    private int progress;
+    private int end;
+    protected JobProgressListener listener;
+
+    public RunnableWithProgress() {
+    }
+
+    public RunnableWithProgress(JobProgressListener listener) {
+        this.listener = listener;
+    }
+    
+
+    /**
+     * set the end step value of the process.
+     *
+     * @param end
+     */
+    protected void setEnd(int end) {
+        this.end = end;
+    }
+
+    /**
+     * return the curent progress of the process.
+     *
+     * @return
+     */
+    public double getProgress() {
+        return (double) progress / (double) end;
+    }
+
+    /**
+     * adds one progression step to the process.
+     */
+    protected void progress() {
+        progress++;
+        if (listener != null) {
+            listener.progress(getProgress());
+        }
+    }
+
+    /**
+     * resets the progression of the process.
+     */
+    protected void reset() {
+        progress = 0;
+    }
+
+}

+ 177 - 0
jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.java

@@ -0,0 +1,177 @@
+ /*
+ * 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.environment.util;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * 
+ * A debuging shape for a BoundingSphere 
+ * Consists of 3 axis aligned circles.
+ * 
+ * @author nehon
+ */
+public class BoundingSphereDebug extends Mesh {
+
+    protected int vertCount;
+    protected int triCount;
+    protected int radialSamples = 32;
+    protected boolean useEvenSlices;
+    protected boolean interior;
+    /**
+     * the distance from the center point each point falls on
+     */
+    public float radius;
+
+    public float getRadius() {
+        return radius;
+    }
+
+    public BoundingSphereDebug() {
+        setGeometryData();
+        setIndexData();
+    }
+
+    /**
+     * builds the vertices based on the radius
+     */
+    private void setGeometryData() {
+        setMode(Mode.Lines);
+
+        FloatBuffer posBuf = BufferUtils.createVector3Buffer((radialSamples + 1) * 3);
+        FloatBuffer colBuf = BufferUtils.createVector3Buffer((radialSamples + 1) * 4);
+
+        setBuffer(Type.Position, 3, posBuf);
+        setBuffer(Type.Color, 4, colBuf);
+
+        // generate geometry
+        float fInvRS = 1.0f / radialSamples;
+
+        // Generate points on the unit circle to be used in computing the mesh
+        // points on a sphere slice.
+        float[] afSin = new float[(radialSamples + 1)];
+        float[] afCos = new float[(radialSamples + 1)];
+        for (int iR = 0; iR < radialSamples; iR++) {
+            float fAngle = FastMath.TWO_PI * fInvRS * iR;
+            afCos[iR] = FastMath.cos(fAngle);
+            afSin[iR] = FastMath.sin(fAngle);
+        }
+        afSin[radialSamples] = afSin[0];
+        afCos[radialSamples] = afCos[0];
+
+        for (int iR = 0; iR <= radialSamples; iR++) {
+            posBuf.put(afCos[iR])
+                    .put(afSin[iR])
+                    .put(0);
+            colBuf.put(ColorRGBA.Blue.r)
+                    .put(ColorRGBA.Blue.g)
+                    .put(ColorRGBA.Blue.b)
+                    .put(ColorRGBA.Blue.a);
+
+        }
+        for (int iR = 0; iR <= radialSamples; iR++) {
+            posBuf.put(afCos[iR])
+                    .put(0)
+                    .put(afSin[iR]);
+            colBuf.put(ColorRGBA.Green.r)
+                    .put(ColorRGBA.Green.g)
+                    .put(ColorRGBA.Green.b)
+                    .put(ColorRGBA.Green.a);
+        }
+        for (int iR = 0; iR <= radialSamples; iR++) {
+            posBuf.put(0)
+                    .put(afCos[iR])
+                    .put(afSin[iR]);
+            colBuf.put(ColorRGBA.Yellow.r)
+                    .put(ColorRGBA.Yellow.g)
+                    .put(ColorRGBA.Yellow.b)
+                    .put(ColorRGBA.Yellow.a);
+        }
+
+        updateBound();
+        setStatic();
+    }
+
+    /**
+     * sets the indices for rendering the sphere.
+     */
+    private void setIndexData() {
+
+        // allocate connectivity
+        int nbSegments = (radialSamples) * 3;
+
+        ShortBuffer idxBuf = BufferUtils.createShortBuffer(2 * nbSegments);
+        setBuffer(Type.Index, 2, idxBuf);
+
+        int idx = 0;
+        int segDone = 0;
+        while (segDone < nbSegments) {
+            idxBuf.put((short) idx);
+            idxBuf.put((short) (idx + 1));
+            idx++;
+            segDone++;
+            if (segDone == radialSamples || segDone == radialSamples * 2) {
+                idx++;
+            }
+
+        }
+
+    }
+
+    
+    /**
+     * Convenience factory method that creates a debuging bounding sphere geometry
+     * @param assetManager the assetManager
+     * @return the bounding sphere debug geometry.
+     */
+    public static Geometry createDebugSphere(AssetManager assetManager) {
+        BoundingSphereDebug b = new BoundingSphereDebug();
+        Geometry geom = new Geometry("BoundingDebug", b);
+
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setBoolean("VertexColor", true);
+        mat.getAdditionalRenderState().setWireframe(true);
+        
+        geom.setMaterial(mat);
+        return geom;
+
+    }
+}

+ 243 - 0
jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java

@@ -0,0 +1,243 @@
+/*
+ * 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.environment.util;
+
+import com.jme3.environment.util.EnvMapUtils;
+import com.jme3.math.ColorRGBA;
+import static com.jme3.math.FastMath.pow;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.texture.Image;
+import com.jme3.texture.TextureCubeMap;
+import com.jme3.texture.image.DefaultImageRaster;
+import com.jme3.texture.image.MipMapImageRaster;
+import com.jme3.util.BufferUtils;
+
+/**
+ * Wraps a Cube map and allows to read from or write pixels into it.
+ * 
+ * It uses the ImageRaster class to tailor the read write operations.
+ * 
+ * @author Nehon
+ */
+public class CubeMapWrapper {
+
+    private MipMapImageRaster mipMapRaster;
+    private final DefaultImageRaster raster;
+    private int[] sizes;
+    private final Vector2f uvs = new Vector2f();
+    private final Image image;
+
+    /**
+     * Creates a CubeMapWrapper for the given cube map
+     * Note that the cube map must be initialized, and the mipmaps sizes should 
+     * be set if relevant for them to be readable/writable
+     * @param cubeMap the cubemap to wrap.
+     */
+    public CubeMapWrapper(TextureCubeMap cubeMap) {
+        image = cubeMap.getImage();
+        if (image.hasMipmaps()) {
+            int nbMipMaps = image.getMipMapSizes().length;
+            sizes = new int[nbMipMaps];
+            mipMapRaster = new MipMapImageRaster(image, 0);
+
+            for (int i = 0; i < nbMipMaps; i++) {
+                sizes[i] = Math.max(1, image.getWidth() >> i);
+            }
+        } else {
+            sizes = new int[1];
+            sizes[0] = image.getWidth();
+        }
+        raster = new DefaultImageRaster(image, 0,0 , false);
+    }
+
+    /**
+     * Reads a pixel from the cube map given the coordinate vector
+     * @param vector the direction vector to fetch the texel
+     * @param store the color in which to store the pixel color read.
+     * @return the color of the pixel read.
+     */
+    public ColorRGBA getPixel(Vector3f vector, ColorRGBA store) {
+
+        if (store == null) {
+            store = new ColorRGBA();
+        }
+
+        int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[0], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
+        raster.setSlice(face);
+        return raster.getPixel((int) uvs.x, (int) uvs.y, store);
+    }
+
+    /**
+     * 
+     * Reads a pixel from the cube map given the coordinate vector
+     * @param vector the direction vector to fetch the texel
+     * @param mipLevel the mip level to read from
+     * @param store the color in which to store the pixel color read.
+     * @return the color of the pixel read.
+     */
+    public ColorRGBA getPixel(Vector3f vector, int mipLevel, ColorRGBA store) {
+        if (mipMapRaster == null) {
+            throw new IllegalArgumentException("This cube map has no mip maps");
+        }
+        if (store == null) {
+            store = new ColorRGBA();
+        }
+
+        int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[mipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
+        mipMapRaster.setSlice(face);
+        mipMapRaster.setMipLevel(mipLevel);
+        return mipMapRaster.getPixel((int) uvs.x, (int) uvs.y, store);
+    }
+
+    /**
+     * Reads a pixel from the cube map given the 2D coordinates and the face to read from
+     * @param x the x tex coordinate (from 0 to width)
+     * @param y the y tex coordinate (from 0 to height)
+     * @param face the face to read from
+     * @param store the color where the result is stored.
+     * @return the color read.
+     */
+    public ColorRGBA getPixel(int x, int y, int face, ColorRGBA store) {
+        if (store == null) {
+            store = new ColorRGBA();
+        }
+        raster.setSlice(face);
+        return raster.getPixel((int) x, (int) y, store);
+    }
+
+     /**
+     * Reads a pixel from the cube map given the 2D coordinates and the face and 
+     * the mip level to read from
+     * @param x the x tex coordinate (from 0 to width)
+     * @param y the y tex coordinate (from 0 to height)
+     * @param face the face to read from
+     * @param mipLevel the miplevel to read from
+     * @param store the color where the result is stored.
+     * @return the color read.
+     */
+    public ColorRGBA getPixel(int x, int y, int face, int mipLevel, ColorRGBA store) {
+        if (mipMapRaster == null) {
+            throw new IllegalArgumentException("This cube map has no mip maps");
+        }
+        if (store == null) {
+            store = new ColorRGBA();
+        }
+        mipMapRaster.setSlice(face);
+        mipMapRaster.setMipLevel(mipLevel);
+        return mipMapRaster.getPixel((int) x, (int) y, store);
+    }
+
+    /**
+     * writes a pixel given the coordinates vector and the color.
+     * @param vector the cooredinates where to write the pixel
+     * @param color the color to write
+     */
+    public void setPixel(Vector3f vector, ColorRGBA color) {
+
+        int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[0], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
+        raster.setSlice(face);
+        raster.setPixel((int) uvs.x, (int) uvs.y, color);
+    }
+    /**
+     * writes a pixel given the coordinates vector, the mip level and the color.
+     * @param vector the cooredinates where to write the pixel
+     * @param mipLevel the miplevel to write to
+     * @param color the color to write
+     */
+    public void setPixel(Vector3f vector, int mipLevel, ColorRGBA color) {
+        if (mipMapRaster == null) {
+            throw new IllegalArgumentException("This cube map has no mip maps");
+        }
+        int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(vector, sizes[mipLevel], uvs, EnvMapUtils.FixSeamsMethod.Stretch);
+        mipMapRaster.setSlice(face);
+        mipMapRaster.setMipLevel(mipLevel);
+        mipMapRaster.setPixel((int) uvs.x, (int) uvs.y, color);
+    }
+
+    /**
+     * Writes a pixel given the 2D cordinates and the color
+     * @param x the x tex coord (from 0 to width)
+     * @param y the y tex coord (from 0 to height)
+     * @param face the face to write to
+     * @param color the color to write
+     */
+    public void setPixel(int x, int y, int face, ColorRGBA color) {
+        raster.setSlice(face);
+        raster.setPixel((int) x, (int) y, color);
+    }
+
+    /**
+     * Writes a pixel given the 2D cordinates, the mip level and the color
+     * @param x the x tex coord (from 0 to width)
+     * @param y the y tex coord (from 0 to height)
+     * @param face the face to write to
+     * @param mipLevel the mip level to write to
+     * @param color the color to write
+     */
+    public void setPixel(int x, int y, int face, int mipLevel, ColorRGBA color) {
+        if (mipMapRaster == null) {
+            throw new IllegalArgumentException("This cube map has no mip maps");
+        }
+
+        mipMapRaster.setSlice(face);
+        mipMapRaster.setMipLevel(mipLevel);
+        mipMapRaster.setPixel((int) x, (int) y, color);
+    }
+
+    /**
+     * Inits the mip maps of a cube map witht he given number of mip maps
+     * @param nbMipMaps the number of mip maps to initialize
+     */
+    public void initMipMaps(int nbMipMaps) {
+        int maxMipMap = (int) (Math.log(image.getWidth()) / Math.log(2) + 1);
+        if (nbMipMaps > maxMipMap) {
+            throw new IllegalArgumentException("Max mip map number for a " + image.getWidth() + "x" + image.getHeight() + " cube map is " + maxMipMap);
+        }
+
+        sizes = new int[nbMipMaps];
+
+        int totalSize = 0;
+        for (int i = 0; i < nbMipMaps; i++) {
+            int size = (int) pow(2, maxMipMap - 1 - i);
+            sizes[i] = size * size * image.getFormat().getBitsPerPixel() / 8;
+            totalSize += sizes[i];
+        }
+        
+        image.setMipMapSizes(sizes);        
+        image.getData().clear();
+        for (int i = 0; i < 6; i++) {
+            image.addData(BufferUtils.createByteBuffer(totalSize));
+        }
+        mipMapRaster = new MipMapImageRaster(image, 0);        
+    }
+}

+ 947 - 0
jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java

@@ -0,0 +1,947 @@
+/*
+ * 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.environment.util;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.math.Vector4f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Quad;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.TextureCubeMap;
+import com.jme3.texture.image.ColorSpace;
+import com.jme3.ui.Picture;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import static com.jme3.math.FastMath.*;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector2f;
+import com.jme3.util.TempVars;
+
+/**
+ *
+ * This class holds several utility method unseful for Physically Based
+ * Rendering. It alloaws to compute useful pre filtered maps from an env map.
+ *
+ * @author Nehon
+ */
+public class EnvMapUtils {
+
+    public final static int NUM_SH_COEFFICIENT = 9;
+    // See Peter-Pike Sloan paper for these coefficients
+    //http://www.ppsloan.org/publications/StupidSH36.pdf
+    public static float[] shBandFactor = {1.0f,
+        2.0f / 3.0f, 2.0f / 3.0f, 2.0f / 3.0f,
+        1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 4.0f};
+
+    public static enum FixSeamsMethod {
+
+        /**
+         * wrap texture coordinates
+         */
+        Wrap,
+        /**
+         * stretch texture coordinates
+         */
+        Stretch,
+        /**
+         * No seams fix
+         */
+        None;
+    }
+
+    /**
+     * Creates a cube map from 6 images
+     *
+     * @param leftImg the west side image, also called negative x (negX) or left
+     * image
+     * @param rightImg the east side image, also called positive x (posX) or
+     * right image
+     * @param downImg the bottom side image, also called negative y (negY) or
+     * down image
+     * @param upImg the up side image, also called positive y (posY) or up image
+     * @param backImg the south side image, also called positive z (posZ) or
+     * back image
+     * @param frontImg the north side image, also called negative z (negZ) or
+     * front image
+     * @param format the format of the image
+     * @return a cube map
+     */
+    public static TextureCubeMap makeCubeMap(Image rightImg, Image leftImg, Image upImg, Image downImg, Image backImg, Image frontImg, Image.Format format) {
+        Image cubeImage = new Image(format, leftImg.getWidth(), leftImg.getHeight(), null, ColorSpace.Linear);
+
+        cubeImage.addData(rightImg.getData(0));
+        cubeImage.addData(leftImg.getData(0));
+
+        cubeImage.addData(upImg.getData(0));
+        cubeImage.addData(downImg.getData(0));
+
+        cubeImage.addData(backImg.getData(0));
+        cubeImage.addData(frontImg.getData(0));
+
+        if (leftImg.getEfficentData() != null) {
+            // also consilidate efficient data
+            ArrayList<Object> efficientData = new ArrayList<Object>(6);
+            efficientData.add(rightImg.getEfficentData());
+            efficientData.add(leftImg.getEfficentData());
+            efficientData.add(upImg.getEfficentData());
+            efficientData.add(downImg.getEfficentData());
+            efficientData.add(backImg.getEfficentData());
+            efficientData.add(frontImg.getEfficentData());
+            cubeImage.setEfficentData(efficientData);
+        }
+
+        TextureCubeMap cubeMap = new TextureCubeMap(cubeImage);
+        cubeMap.setAnisotropicFilter(0);
+        cubeMap.setMagFilter(Texture.MagFilter.Bilinear);
+        cubeMap.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
+        cubeMap.setWrap(Texture.WrapMode.EdgeClamp);
+
+        return cubeMap;
+    }
+  
+    /**
+     * Make a duplicate of this cube Map. That means that it's another instant
+     * od TextureCubeMap, but the underlying buffers are duplicates of the
+     * original ones. see {@link ByteBuffer#duplicate()}
+     *
+     * Use this if you need to read from the map from multiple threads, it
+     * should garanty the thread safety. Note that if you want to write to the
+     * cube map you have to make sure that the different thread do not write to
+     * the same area of the buffer. The position, limit and mark are not an
+     * issue.
+     *
+     * @param sourceMap
+     * @return
+     */
+    public static TextureCubeMap duplicateCubeMap(TextureCubeMap sourceMap) {
+        Image srcImg = sourceMap.getImage();
+        Image cubeImage = new Image(srcImg.getFormat(), srcImg.getWidth(), srcImg.getHeight(), null, srcImg.getColorSpace());
+
+        for (ByteBuffer d : srcImg.getData()) {
+            cubeImage.addData(d.duplicate());
+        }
+
+        if (srcImg.getEfficentData() != null) {
+            // also consilidate efficient data
+            ArrayList<Object> efficientData = new ArrayList<Object>(6);
+            efficientData.add(srcImg.getEfficentData());
+            cubeImage.setEfficentData(efficientData);
+        }
+
+        TextureCubeMap cubeMap = new TextureCubeMap(cubeImage);
+        cubeMap.setAnisotropicFilter(sourceMap.getAnisotropicFilter());
+        cubeMap.setMagFilter(sourceMap.getMagFilter());
+        cubeMap.setMinFilter(sourceMap.getMinFilter());
+        cubeMap.setWrap(sourceMap.getWrap(Texture.WrapAxis.S));
+
+        return cubeMap;
+    }
+
+    /**
+     * Computes the vector coordinates, for the given x,y texture coordinates
+     * and the given cube map face.
+     *
+     * Also computes the solid angle for those coordinates and returns it.
+     *
+     * To know what the solid angle is please read this.
+     * http://www.codinglabs.net/article_physically_based_rendering.aspx
+     *
+     *
+     * Original solid angle calculation code is from Ignacio Castaño. This
+     * formula is from Manne Öhrström's thesis. It takes two coordiantes in the
+     * range [-1, 1] that define a portion of a cube face and return the area of
+     * the projection of that portion on the surface of the sphere.
+     *
+     * @param x texture coordinate from 0 to 1 in the given cube map face
+     * @param y texture coordinate from 0 to 1 in the given cube map face
+     * @param mapSize the size of the cube map
+     * @param face the face id of the cube map
+     * @param store the vector3f where the vector will be stored. don't provide
+     * null for this param
+     * @return the solid angle for the give parameters
+     */
+    static float getSolidAngleAndVector(int x, int y, int mapSize, int face, Vector3f store, FixSeamsMethod fixSeamsMethod) {
+
+        if (store == null) {
+            throw new IllegalArgumentException("the store parameter ust not be null");
+        }
+
+        /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
+         (+ 0.5f is for texel center addressing) */
+        float u = (2.0f * ((float) x + 0.5f) / (float) mapSize) - 1.0f;
+        float v = (2.0f * ((float) y + 0.5f) / (float) mapSize) - 1.0f;
+
+        getVectorFromCubemapFaceTexCoord(x, y, mapSize, face, store, fixSeamsMethod);
+
+        /* Solid angle weight approximation :
+         * U and V are the -1..1 texture coordinate on the current face.
+         * Get projected area for this texel */
+        float x0, y0, x1, y1;
+        float invRes = 1.0f / (float) mapSize;
+        x0 = u - invRes;
+        y0 = v - invRes;
+        x1 = u + invRes;
+        y1 = v + invRes;
+
+        return areaElement(x0, y0) - areaElement(x0, y1) - areaElement(x1, y0) + areaElement(x1, y1);
+    }
+
+    /**
+     * used to compute the solid angle
+     *
+     * @param x tex coordinates
+     * @param y tex coordinates
+     * @return
+     */
+    private static float areaElement(float x, float y) {
+        return (float) Math.atan2(x * y, sqrt(x * x + y * y + 1));
+    }
+
+    /**
+     *
+     * Computes the 3 component vector coordinates for the given face and coords
+     *
+     * @param x the x texture coordinate
+     * @param y the y texture coordinate
+     * @param mapSize the size of a face of the cube map
+     * @param face the face to consider
+     * @param store a vector3f where the resulting vector will be stored
+     * @param fixSeamsMethod the method to fix the seams
+     * @return
+     */
+    public static Vector3f getVectorFromCubemapFaceTexCoord(int x, int y, int mapSize, int face, Vector3f store, FixSeamsMethod fixSeamsMethod) {
+        if (store == null) {
+            store = new Vector3f();
+        }
+
+        float u;
+        float v;
+
+        if (fixSeamsMethod == FixSeamsMethod.Stretch) {
+            /* Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp		
+             * transform from [0..res - 1] to [-1 .. 1], match up edges exactly. */
+            u = (2.0f * (float) x / ((float) mapSize - 1.0f)) - 1.0f;
+            v = (2.0f * (float) y / ((float) mapSize - 1.0f)) - 1.0f;
+        } else {
+            //Done if any other fix method or no fix method is set
+            /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
+             * (+ 0.5f is for texel center addressing) */
+            u = (2.0f * ((float) x + 0.5f) / (float) (mapSize)) - 1.0f;
+            v = (2.0f * ((float) y + 0.5f) / (float) (mapSize)) - 1.0f;
+        }
+
+        if (fixSeamsMethod == FixSeamsMethod.Wrap) {
+            // Warp texel centers in the proximity of the edges.
+            float a = pow((float) mapSize, 2.0f) / pow(((float) mapSize - 1f), 3.0f);
+            u = a * pow(u, 3f) + u;
+            v = a * pow(v, 3f) + v;
+        }
+
+        //compute vector depending on the face
+        // Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp	
+        switch (face) {
+            case 0:
+                store.set(1f, -v, -u);
+                break;
+            case 1:
+                store.set(-1f, -v, u);
+                break;
+            case 2:
+                store.set(u, 1f, v);
+                break;
+            case 3:
+                store.set(u, -1f, -v);
+                break;
+            case 4:
+                store.set(u, -v, 1f);
+                break;
+            case 5:
+                store.set(-u, -v, -1.0f);
+                break;
+        }
+
+        return store.normalizeLocal();
+    }
+
+    /**
+     *
+     * Computes the texture coortinates and the face of the cube map from the
+     * given vector
+     *
+     * @param texelVect the vector to fetch texelt from the cube map
+     * @param fixSeamsMethod the method to fix the seams
+     * @param mapSize the size of one face of the cube map
+     * @param store a Vector2f where the texture coordinates will be stored
+     * @return the face from which to fetch the texel
+     */
+    public static int getCubemapFaceTexCoordFromVector(Vector3f texelVect, int mapSize, Vector2f store, FixSeamsMethod fixSeamsMethod) {
+
+        float u = 0, v = 0, bias = 0;
+        int face;
+        float absX = abs(texelVect.x);
+        float absY = abs(texelVect.y);
+        float absZ = abs(texelVect.z);
+        float max = Math.max(Math.max(absX, absY), absZ);
+        if (max == absX) {
+            face = texelVect.x > 0 ? 0 : 1;
+        } else if (max == absY) {
+            face = texelVect.y > 0 ? 2 : 3;
+        } else {
+            face = texelVect.z > 0 ? 4 : 5;
+        }
+
+        //compute vector depending on the face
+        // Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp	
+        switch (face) {
+            case 0:
+                //store.set(1f, -v, -u, 0);
+                bias = 1f / texelVect.x;
+                u = -texelVect.z;
+                v = -texelVect.y;
+                break;
+            case 1:
+                // store.set(-1f, -v, u, 0);
+                bias = -1f / texelVect.x;
+                u = texelVect.z;
+                v = -texelVect.y;
+                break;
+            case 2:
+                //store.set(u, 1f, v, 0);
+                bias = 1f / texelVect.y;
+                u = texelVect.x;
+                v = texelVect.z;
+                break;
+            case 3:
+                //store.set(u, -1f, -v, 0);
+                bias = -1f / texelVect.y;
+                u = texelVect.x;
+                v = -texelVect.z;
+                break;
+            case 4:
+                //store.set(u, -v, 1f, 0);
+                bias = 1f / texelVect.z;
+                u = texelVect.x;
+                v = -texelVect.y;
+                break;
+            case 5:
+                //store.set(-u, -v, -1.0f, 0);
+                bias = -1f / texelVect.z;
+                u = -texelVect.x;
+                v = -texelVect.y;
+                break;
+        }
+        u *= bias;
+        v *= bias;
+
+        if (fixSeamsMethod == FixSeamsMethod.Stretch) {
+            /* Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp		
+             * transform from [0..res - 1] to [-1 .. 1], match up edges exactly. */
+            u = Math.round((u + 1.0f) * ((float) mapSize - 1.0f) * 0.5f);
+            v = Math.round((v + 1.0f) * ((float) mapSize - 1.0f) * 0.5f);
+        } else {
+            //Done if any other fix method or no fix method is set
+            /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)]
+             * (+ 0.5f is for texel center addressing) */
+            u = Math.round((u + 1.0f) * ((float) mapSize) * 0.5f - 0.5f);
+            v = Math.round((v + 1.0f) * ((float) mapSize) * 0.5f - 0.5f);
+
+        }
+
+        store.set(u, v);
+        return face;
+    }
+
+    /*
+    public static void main(String... argv) {
+
+//        for (int givenFace = 0; givenFace < 6; givenFace++) {
+//
+//            //int givenFace = 1;
+//            for (int x = 0; x < 128; x++) {
+//                for (int y = 0; y < 128; y++) {
+//                    Vector3f v = EnvMapUtils.getVectorFromCubemapFaceTexCoord(x, y, 128, givenFace, null, FixSeamsMethod.None);
+//                    Vector2f uvs = new Vector2f();
+//                    int face = EnvMapUtils.getCubemapFaceTexCoordFromVector(v, 128, uvs, FixSeamsMethod.None);
+//
+//                    if ((int) uvs.x != x || (int) uvs.y != y) {
+//                        System.err.println("error " + uvs + " should be " + x + "," + y + " vect was " + v);
+//                    }
+//                    if (givenFace != face) {
+//                        System.err.println("error face: " + face + " should be " + givenFace);
+//                    }
+//                }
+//            }
+//        }
+//        System.err.println("done ");
+        int total = 0;
+        for (int i = 0; i < 6; i++) {
+            int size = (int) pow(2, 7 - i);
+            int samples = EnvMapUtils.getSampleFromMip(i, 6);
+            int iterations = (samples * size * size);
+            total += iterations;
+            float roughness = EnvMapUtils.getRoughnessFromMip(i, 6);
+            System.err.println("roughness " + i + " : " + roughness + " , map : " + size + " , samples : " + samples + " , iterations : " + iterations);
+            System.err.println("reverse " + EnvMapUtils.getMipFromRoughness(roughness, 6));
+
+        }
+        System.err.println("total " + total);
+        System.err.println(128 * 128 * 1024);
+        System.err.println("test " + EnvMapUtils.getMipFromRoughness(0.9999f, 6));
+        System.err.println("nb mip = " + (Math.log(128) / Math.log(2) - 1));
+
+    }*/
+
+    public static int getSampleFromMip(int mipLevel, int miptot) {        
+        return mipLevel==0?1:Math.min(1 << (miptot - 1 + (mipLevel) * 2 ), 8192);
+    }
+
+    public static float getRoughnessFromMip(int miplevel, int miptot) {
+        float mipScale = 1.0f;
+        float mipOffset = -0.3f;
+
+        return pow(2, (miplevel - (miptot - 1) + mipOffset) / mipScale);
+    }
+
+    public static float getMipFromRoughness(float roughness, int miptot) {
+        float mipScale = 1.0f;
+        float Lod = (float) (Math.log(roughness) / Math.log(2)) * mipScale + miptot - 1.0f;       
+
+        return (float) Math.max(0.0, Lod);
+    }
+
+    /**
+     * same as
+     * {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap, com.jme3.utils.EnvMapUtils.FixSeamsMethod)}
+     * the fix method used is {@link FixSeamsMethod#Wrap}
+     *
+     * @param cubeMap the environment cube map to compute SH for
+     * @return an array of 9 vector3f representing thos coefficients for each
+     * r,g,b channnel
+     */
+    public static Vector3f[] getSphericalHarmonicsCoefficents(TextureCubeMap cubeMap) {
+        return getSphericalHarmonicsCoefficents(cubeMap, FixSeamsMethod.Wrap);
+    }
+
+    /**
+     * Returns the Spherical Harmonics coefficients for this cube map.
+     *
+     * The method used is the one from this article :
+     * http://graphics.stanford.edu/papers/envmap/envmap.pdf
+     *
+     * Also good resources on spherical harmonics
+     * http://dickyjim.wordpress.com/2013/09/04/spherical-harmonics-for-beginners/
+     *
+     * @param cubeMap the environment cube map to compute SH for
+     * @param fixSeamsMethod method to fix seams when computing the SH
+     * coefficients
+     * @return an array of 9 vector3f representing thos coefficients for each
+     * r,g,b channnel
+     */
+    public static Vector3f[] getSphericalHarmonicsCoefficents(TextureCubeMap cubeMap, FixSeamsMethod fixSeamsMethod) {
+
+        Vector3f[] shCoef = new Vector3f[NUM_SH_COEFFICIENT];
+
+        float[] shDir = new float[9];
+        float weightAccum = 0.0f;
+        float weight;
+
+        if (cubeMap.getImage().getData(0) == null) {
+            throw new IllegalStateException("The cube map must contain Efficient data, if you rendered the cube map on the GPU plase use renderer.readFrameBuffer, to create a CPU image");
+        }
+
+        int width = cubeMap.getImage().getWidth();
+        int height = cubeMap.getImage().getHeight();
+
+        Vector3f texelVect = new Vector3f();
+        ColorRGBA color = new ColorRGBA();
+
+        CubeMapWrapper envMapReader = new CubeMapWrapper(cubeMap);
+        for (int face = 0; face < 6; face++) {
+            for (int y = 0; y < height; y++) {
+                for (int x = 0; x < width; x++) {
+
+                    weight = getSolidAngleAndVector(x, y, width, face, texelVect, fixSeamsMethod);
+
+                    evalShBasis(texelVect, shDir);
+
+                    envMapReader.getPixel(x, y, face, color);
+
+                    for (int i = 0; i < NUM_SH_COEFFICIENT; i++) {
+
+                        if (shCoef[i] == null) {
+                            shCoef[i] = new Vector3f();
+                        }
+
+                        shCoef[i].setX(shCoef[i].x + color.r * shDir[i] * weight);
+                        shCoef[i].setY(shCoef[i].y + color.g * shDir[i] * weight);
+                        shCoef[i].setZ(shCoef[i].z + color.b * shDir[i] * weight);
+                    }
+
+                    weightAccum += weight;
+                }
+            }
+        }
+
+        /* Normalization - The sum of solid angle should be equal to the solid angle of the sphere (4 PI), so
+         * normalize in order our weightAccum exactly match 4 PI. */
+        for (int i = 0; i < NUM_SH_COEFFICIENT; ++i) {
+            shCoef[i].multLocal(4.0f * PI / weightAccum);
+        }
+        return shCoef;
+    }
+
+    /**
+     * Computes SH coefficient for a given textel dir The method used is the one
+     * from this article : http://graphics.stanford.edu/papers/envmap/envmap.pdf
+     *
+     * @param texelVect
+     * @param shDir
+     */
+    public static void evalShBasis(Vector3f texelVect, float[] shDir) {
+
+        float xV = texelVect.x;
+        float yV = texelVect.y;
+        float zV = texelVect.z;
+
+        float pi = PI;
+        float sqrtPi = sqrt(pi);
+        float sqrt3Pi = sqrt(3f / pi);
+        float sqrt5Pi = sqrt(5f / pi);
+        float sqrt15Pi = sqrt(15f / pi);
+
+        float x2 = xV * xV;
+        float y2 = yV * yV;
+        float z2 = zV * zV;
+
+        shDir[0] = (1f / (2f * sqrtPi));
+        shDir[1] = -(sqrt3Pi * yV) / 2f;
+        shDir[2] = (sqrt3Pi * zV) / 2f;
+        shDir[3] = -(sqrt3Pi * xV) / 2f;
+        shDir[4] = (sqrt15Pi * xV * yV) / 2f;
+        shDir[5] = -(sqrt15Pi * yV * zV) / 2f;
+        shDir[6] = (sqrt5Pi * (-1f + 3f * z2)) / 4f;
+        shDir[7] = -(sqrt15Pi * xV * zV) / 2f;
+        shDir[8] = sqrt15Pi * (x2 - y2) / 4f;
+        
+//        shDir[0]  = (1f/(2.f*sqrtPi));
+//
+//	shDir[1]  = -(sqrt(3f/pi)*yV)/2.f;
+//	shDir[2]  = (sqrt(3/pi)*zV)/2.f;
+//	shDir[3]  = -(sqrt(3/pi)*xV)/2.f;
+//
+//	shDir[4]  = (sqrt(15f/pi)*xV*yV)/2.f;
+//	shDir[5]  = -(sqrt(15f/pi)*yV*zV)/2.f;
+//	shDir[6]  = (sqrt(5f/pi)*(-1 + 3f*z2))/4.f;
+//	shDir[7]  = -(sqrt(15f/pi)*xV*zV)/2.f;
+//	shDir[8]  = sqrt(15f/pi)*(x2 - y2)/4.f;
+
+
+    }
+
+    /**
+     * {@link EnvMapUtils#generateIrradianceMap(com.jme3.math.Vector3f[], com.jme3.texture.TextureCubeMap, int, com.jme3.utils.EnvMapUtils.FixSeamsMethod)
+     * }
+     *
+     * @param shCoeffs the spherical harmonics coefficients to use
+     * @param targetMapSize the size of the target map
+     * @return the irradiance map.
+     */
+    public static TextureCubeMap generateIrradianceMap(Vector3f[] shCoeffs, int targetMapSize) {
+        return generateIrradianceMap(shCoeffs, targetMapSize, FixSeamsMethod.Wrap, null);
+    }
+
+    /**
+     * Generates the Irradiance map (used for image based difuse lighting) from
+     * Spherical Harmonics coefficients previously computed with
+     * {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)}
+     * Note that the output cube map is in RGBA8 format.
+     *
+     * @param shCoeffs the SH coeffs
+     * @param targetMapSize the size of the irradiance map to generate
+     * @param fixSeamsMethod the method to fix seams
+     * @param store
+     * @return The irradiance cube map for the given coefficients
+     */
+    public static TextureCubeMap generateIrradianceMap(Vector3f[] shCoeffs, int targetMapSize, FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
+        TextureCubeMap irrCubeMap = store;
+        if (irrCubeMap == null) {
+            irrCubeMap = new TextureCubeMap(targetMapSize, targetMapSize, Image.Format.RGB16F);
+            irrCubeMap.setMagFilter(Texture.MagFilter.Bilinear);
+            irrCubeMap.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
+            irrCubeMap.getImage().setColorSpace(ColorSpace.Linear);
+        }
+
+        for (int i = 0; i < 6; i++) {
+            ByteBuffer buf = BufferUtils.createByteBuffer(targetMapSize * targetMapSize * irrCubeMap.getImage().getFormat().getBitsPerPixel()/8);
+            irrCubeMap.getImage().setData(i, buf);
+        }
+
+        Vector3f texelVect = new Vector3f();
+        ColorRGBA color = new ColorRGBA(ColorRGBA.Black);
+        float[] shDir = new float[9];
+        CubeMapWrapper envMapWriter = new CubeMapWrapper(irrCubeMap);
+        for (int face = 0; face < 6; face++) {
+
+            for (int y = 0; y < targetMapSize; y++) {
+                for (int x = 0; x < targetMapSize; x++) {
+                    getVectorFromCubemapFaceTexCoord(x, y, targetMapSize, face, texelVect, fixSeamsMethod);
+                    evalShBasis(texelVect, shDir);
+                    color.set(0, 0, 0, 0);
+                    for (int i = 0; i < NUM_SH_COEFFICIENT; i++) {
+                        color.set(color.r + shCoeffs[i].x * shDir[i] * shBandFactor[i],
+                                color.g + shCoeffs[i].y * shDir[i] * shBandFactor[i],
+                                color.b + shCoeffs[i].z * shDir[i] * shBandFactor[i],
+                                1.0f);
+                    }
+                    
+                    //clamping the color because very low value close to zero produce artifacts
+                    color.r = Math.max(0.0001f, color.r);
+                    color.g = Math.max(0.0001f, color.g);
+                    color.b = Math.max(0.0001f, color.b);
+                    envMapWriter.setPixel(x, y, face, color);
+                }
+            }
+        }
+        return irrCubeMap;
+    }
+
+    /**
+     * Generates the prefiltered env map (used for image based specular
+     * lighting) With the GGX/Shlick brdf
+     * {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)}
+     * Note that the output cube map is in RGBA8 format.
+     *
+     * @param sourceEnvMap
+     * @param targetMapSize the size of the irradiance map to generate
+     * @param store
+     * @param fixSeamsMethod the method to fix seams
+     * @return The irradiance cube map for the given coefficients
+     */
+    public static TextureCubeMap generatePrefilteredEnvMap(TextureCubeMap sourceEnvMap, int targetMapSize, FixSeamsMethod fixSeamsMethod, TextureCubeMap store) {
+        TextureCubeMap pem = store;
+        if (pem == null) {
+            pem = new TextureCubeMap(targetMapSize, targetMapSize, Image.Format.RGB16F);
+            pem.setMagFilter(Texture.MagFilter.Bilinear);
+            pem.setMinFilter(Texture.MinFilter.Trilinear);
+            pem.getImage().setColorSpace(ColorSpace.Linear);
+        }
+
+        int nbMipMap = (int) (Math.log(targetMapSize) / Math.log(2) - 1);
+
+        CubeMapWrapper sourceWrapper = new CubeMapWrapper(sourceEnvMap);
+        CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
+        targetWrapper.initMipMaps(nbMipMap);
+
+        Vector3f texelVect = new Vector3f();
+        Vector3f color = new Vector3f();
+        ColorRGBA outColor = new ColorRGBA();
+        for (int mipLevel = 0; mipLevel < nbMipMap; mipLevel++) {
+            System.err.println("mip level " + mipLevel);
+            float roughness = getRoughnessFromMip(mipLevel, nbMipMap);
+            int nbSamples = getSampleFromMip(mipLevel, nbMipMap);
+            int targetMipMapSize = (int) pow(2, nbMipMap + 1 - mipLevel);
+            for (int face = 0; face < 6; face++) {
+                System.err.println("face " + face);
+                for (int y = 0; y < targetMipMapSize; y++) {
+                    for (int x = 0; x < targetMipMapSize; x++) {
+                        color.set(0, 0, 0);
+                        getVectorFromCubemapFaceTexCoord(x, y, targetMipMapSize, face, texelVect, FixSeamsMethod.Wrap);
+                        prefilterEnvMapTexel(sourceWrapper, roughness, texelVect, nbSamples, color);
+                        outColor.set(color.x, color.y, color.z, 1.0f);
+                        // System.err.println("coords " + x + "," + y);
+                        targetWrapper.setPixel(x, y, face, mipLevel, outColor);
+                    }
+                }
+            }
+        }
+        return pem;
+    }
+
+    public static Vector4f getHammersleyPoint(int i, final int nbrSample, Vector4f store) {
+        if (store == null) {
+            store = new Vector4f();
+        }
+        float phi;
+        long ui = i;
+        store.setX((float) i / (float) nbrSample);
+
+        /* From http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
+         * Radical Inverse : Van der Corput */
+        ui = (ui << 16) | (ui >> 16);
+        ui = ((ui & 0x55555555) << 1) | ((ui & 0xAAAAAAAA) >>> 1);
+        ui = ((ui & 0x33333333) << 2) | ((ui & 0xCCCCCCCC) >>> 2);
+        ui = ((ui & 0x0F0F0F0F) << 4) | ((ui & 0xF0F0F0F0) >>> 4);
+        ui = ((ui & 0x00FF00FF) << 8) | ((ui & 0xFF00FF00) >>> 8);
+
+        ui = ui & 0xffffffff;
+        store.setY(2.3283064365386963e-10f * (float) (ui)); /* 0x100000000 */
+
+        phi = 2.0f * PI * store.y;
+        store.setZ(cos(phi));
+        store.setW(sin(phi));
+
+        return store;
+    }
+
+    private static Vector3f prefilterEnvMapTexel(CubeMapWrapper envMapReader, float roughness, Vector3f N, int numSamples, Vector3f store) {        
+
+        Vector3f prefilteredColor = store;
+        float totalWeight = 0.0f;
+
+        TempVars vars = TempVars.get();
+        Vector4f Xi = vars.vect4f1;
+        Vector3f H = vars.vect1;
+        Vector3f tmp = vars.vect2;
+        ColorRGBA c = vars.color;
+        // a = roughness² and a2 = a²
+        float a2 = roughness * roughness;
+        a2 *= a2;
+        a2 *= 10;
+        for (int i = 0; i < numSamples; i++) {
+            Xi = getHammersleyPoint(i, numSamples, Xi);            
+            H = importanceSampleGGX(Xi, a2, N, H, vars);
+
+            H.normalizeLocal();
+            tmp.set(H);
+            float NoH = N.dot(tmp);
+
+            Vector3f L = tmp.multLocal(NoH * 2).subtractLocal(N);
+            float NoL = clamp(N.dot(L), 0.0f, 1.0f);
+            if (NoL > 0) {
+                envMapReader.getPixel(L, c);
+                prefilteredColor.setX(prefilteredColor.x + c.r * NoL);
+                prefilteredColor.setY(prefilteredColor.y + c.g * NoL);
+                prefilteredColor.setZ(prefilteredColor.z + c.b * NoL);
+
+                totalWeight += NoL;
+            }
+        }
+        vars.release();
+        return prefilteredColor.divideLocal(totalWeight);
+    }
+
+    public static Vector3f importanceSampleGGX(Vector4f xi, float a2, Vector3f normal, Vector3f store, TempVars vars) {
+        if (store == null) {
+            store = new Vector3f();
+        }
+
+        float cosTheta = sqrt((1f - xi.x) / (1f + (a2 - 1f) * xi.x));
+        float sinTheta = sqrt(1f - cosTheta * cosTheta);
+
+        float sinThetaCosPhi = sinTheta * xi.z;//xi.z is cos(phi)
+        float sinThetaSinPhi = sinTheta * xi.w;//xi.w is sin(phi)
+
+        Vector3f upVector = Vector3f.UNIT_X;
+
+        if (abs(normal.z) < 0.999) {
+            upVector = Vector3f.UNIT_Y;
+        }
+
+        Vector3f tangentX = vars.vect3.set(upVector).crossLocal(normal).normalizeLocal();
+        Vector3f tangentY = vars.vect4.set(normal).crossLocal(tangentX);
+
+        // Tangent to world space
+        tangentX.multLocal(sinThetaCosPhi);
+        tangentY.multLocal(sinThetaSinPhi);
+        vars.vect5.set(normal).multLocal(cosTheta);
+
+        // Tangent to world space
+        store.set(tangentX).addLocal(tangentY).addLocal(vars.vect5);
+
+        return store;
+    }
+
+    /**
+     * Creates a debug Node of the given cube map to attach to the gui node
+     *
+     * the cube map is layered this way :
+     * <pre>
+     *         _____
+     *        |     |
+     *        | +Y  |
+     *   _____|_____|_____ _____
+     *  |     |     |     |     |
+     *  | -X  | +Z  | +X  | -Z  |
+     *  |_____|_____|_____|_____|
+     *        |     |
+     *        | -Y  |
+     *        |_____|
+     *
+     *</pre>
+     *
+     * @param cubeMap the cube map
+     * @param assetManager the asset Manager
+     * @return
+     */
+    public static Node getCubeMapCrossDebugView(TextureCubeMap cubeMap, AssetManager assetManager) {
+        Node n = new Node("CubeMapDebug" + cubeMap.getName());
+        int size = cubeMap.getImage().getWidth();
+        Picture[] pics = new Picture[6];
+
+        float ratio = 128f / (float) size;
+
+        for (int i = 0; i < 6; i++) {
+            pics[i] = new Picture("bla");
+            Texture2D tex = new Texture2D(new Image(cubeMap.getImage().getFormat(), size, size, cubeMap.getImage().getData(i), cubeMap.getImage().getColorSpace()));
+
+            pics[i].setTexture(assetManager, tex, true);
+            pics[i].setWidth(size);
+            pics[i].setHeight(size);
+            n.attachChild(pics[i]);
+        }
+
+        pics[0].setLocalTranslation(size, size * 2, 1);
+        pics[0].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+        pics[1].setLocalTranslation(size * 3, size * 2, 1);
+        pics[1].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+        pics[2].setLocalTranslation(size * 2, size * 3, 1);
+        pics[2].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+        pics[3].setLocalTranslation(size * 2, size, 1);
+        pics[3].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+        pics[4].setLocalTranslation(size * 2, size * 2, 1);
+        pics[4].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+        pics[5].setLocalTranslation(size * 4, size * 2, 1);
+        pics[5].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+
+        Quad q = new Quad(size * 4, size * 3);
+        Geometry g = new Geometry("bg", q);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", ColorRGBA.Black);
+        g.setMaterial(mat);
+        g.setLocalTranslation(0, 0, 0);
+
+        n.attachChild(g);
+        n.setLocalScale(ratio);
+        return n;
+    }
+
+    public static Node getCubeMapCrossDebugViewWithMipMaps(TextureCubeMap cubeMap, AssetManager assetManager) {
+        Node n = new Node("CubeMapDebug" + cubeMap.getName());
+        int size = cubeMap.getImage().getWidth();
+        int nbMips = cubeMap.getImage().getMipMapSizes().length;
+        Picture[] pics = new Picture[6*nbMips];
+
+        float ratio = 1f;// 128f / (float) size;
+
+        int offset = 0;
+        int guiOffset = 0;
+        for (int mipLevel = 0; mipLevel < nbMips; mipLevel++) {
+            size = Math.max(1, cubeMap.getImage().getWidth() >> mipLevel);
+            int dataSize = cubeMap.getImage().getMipMapSizes()[mipLevel];
+            byte[] dataArray = new byte[dataSize];
+            for (int i = 0; i < 6; i++) {
+                
+                ByteBuffer bb = cubeMap.getImage().getData(i);
+              
+                bb.rewind();
+                bb.position(offset);
+                bb.get(dataArray, 0, dataSize);
+                ByteBuffer data = BufferUtils.createByteBuffer(dataArray);
+
+                pics[i] = new Picture("bla");
+                Texture2D tex = new Texture2D(new Image(cubeMap.getImage().getFormat(), size, size, data, cubeMap.getImage().getColorSpace()));
+
+                pics[i].setTexture(assetManager, tex, true);
+                pics[i].setWidth(size);
+                pics[i].setHeight(size);
+                n.attachChild(pics[i]);
+            }
+            pics[0].setLocalTranslation(guiOffset + size, guiOffset + size * 2, 1);
+            pics[0].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+            pics[1].setLocalTranslation(guiOffset + size * 3, guiOffset + size * 2, 1);
+            pics[1].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+            pics[2].setLocalTranslation(guiOffset + size * 2, guiOffset + size * 3, 1);
+            pics[2].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+            pics[3].setLocalTranslation(guiOffset + size * 2, guiOffset + size, 1);
+            pics[3].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+            pics[4].setLocalTranslation(guiOffset + size * 2, guiOffset + size * 2, 1);
+            pics[4].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+            pics[5].setLocalTranslation(guiOffset + size * 4, guiOffset + size * 2, 1);
+            pics[5].setLocalRotation(new Quaternion().fromAngleAxis(PI, Vector3f.UNIT_Z));
+            
+            guiOffset+=size *2+1;
+            offset += dataSize;
+            
+        }
+
+        Quad q = new Quad(cubeMap.getImage().getWidth() * 4 + nbMips, guiOffset + size);
+        Geometry g = new Geometry("bg", q);
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        mat.setColor("Color", ColorRGBA.Black);
+        g.setMaterial(mat);
+        g.setLocalTranslation(0, 0, 0);
+
+        n.attachChild(g);
+        n.setLocalScale(ratio);
+        return n;
+    }
+    
+     /**
+     * initialize the Irradiancemap
+     * @param size the size of the map
+     * @param imageFormat the format of the image
+     * @return the initialized Irradiance map
+     */
+    public static TextureCubeMap createIrradianceMap(int size, Image.Format imageFormat) {
+
+        TextureCubeMap irrMap = new TextureCubeMap(size, size, imageFormat);
+        irrMap.setMagFilter(Texture.MagFilter.Bilinear);
+        irrMap.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
+        irrMap.getImage().setColorSpace(ColorSpace.Linear);
+        return irrMap;
+    }
+
+    /**
+     * initialize the pem map
+     * @param size the size of the map
+     * @param imageFormat the format of the image
+     * @return the initialized prefiltered env map
+     */
+    public static TextureCubeMap createPrefilteredEnvMap(int size, Image.Format imageFormat) {
+
+        TextureCubeMap pem = new TextureCubeMap(size, size, imageFormat);
+        pem.setMagFilter(Texture.MagFilter.Bilinear);
+        pem.setMinFilter(Texture.MinFilter.Trilinear);
+        pem.getImage().setColorSpace(ColorSpace.Linear);
+        int nbMipMap = (int) (Math.log(size) / Math.log(2) - 1);
+        CubeMapWrapper targetWrapper = new CubeMapWrapper(pem);
+        targetWrapper.initMipMaps(nbMipMap);
+        return pem;
+    }
+}

+ 214 - 0
jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java

@@ -0,0 +1,214 @@
+/*
+ * 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.environment.util;
+
+import com.jme3.app.Application;
+import com.jme3.app.state.BaseAppState;
+import com.jme3.bounding.BoundingSphere;
+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;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Sphere;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A debug state that will display LIght gizmos on screen.
+ * Still a wip and for now it only displays light probes.
+ * 
+ * @author nehon
+ */
+public class LightsDebugState extends BaseAppState {
+
+    private Node debugNode;
+    private final Map<LightProbe, Node> probeMapping = new HashMap<LightProbe, Node>();
+    private final List<LightProbe> garbage = new ArrayList<LightProbe>();
+    private Geometry debugGeom;
+    private Geometry debugBounds;
+    private Material debugMaterial;
+    private DebugMode debugMode = DebugMode.PrefilteredEnvMap;
+    private float probeScale = 1.0f;
+    private Spatial scene = null;
+    private final List<LightProbe> probes = new ArrayList<LightProbe>();
+
+    /**
+     * Debug mode for light probes
+     */
+    public enum DebugMode {
+
+        /**
+         * Displays the prefiltered env maps on the debug sphere
+         */
+        PrefilteredEnvMap,
+        /**
+         * displays the Irradiance map on the debug sphere
+         */
+        IrradianceMap
+    }
+
+    @Override
+    protected void initialize(Application app) {
+        debugNode = new Node("Environment debug Node");
+        Sphere s = new Sphere(16, 16, 1);
+        debugGeom = new Geometry("debugEnvProbe", s);
+        debugMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/reflect.j3md");
+        debugGeom.setMaterial(debugMaterial);
+        debugBounds = BoundingSphereDebug.createDebugSphere(app.getAssetManager());
+        if (scene == null) {
+            scene = app.getViewPort().getScenes().get(0);
+        }
+    }
+
+    @Override
+    public void update(float tpf) {
+        for (Light light : scene.getWorldLightList()) {
+            switch (light.getType()) {
+
+                case Probe:
+                    LightProbe probe = (LightProbe) light;
+                    probes.add(probe);
+                    Node n = probeMapping.get(probe);
+                    if (n == null) {
+                        n = new Node("DebugProbe");
+                        n.attachChild(debugGeom.clone(true));
+                        n.attachChild(debugBounds.clone(false));
+                        debugNode.attachChild(n);
+                        probeMapping.put(probe, n);
+                    }
+                    Geometry probeGeom = ((Geometry) n.getChild(0));
+                    Material m = probeGeom.getMaterial();
+                    probeGeom.setLocalScale(probeScale);
+                    if (probe.isReady()) {
+                        if (debugMode == DebugMode.IrradianceMap) {
+                            m.setTexture("CubeMap", probe.getIrradianceMap());
+                        } else {
+                            m.setTexture("CubeMap", probe.getPrefilteredEnvMap());
+                        }
+                    }
+                    n.setLocalTranslation(probe.getPosition());
+                    n.getChild(1).setLocalScale(((BoundingSphere) probe.getBounds()).getRadius());
+                    break;
+                default:
+                    break;
+            }
+        }
+        debugNode.updateLogicalState(tpf);
+        debugNode.updateGeometricState();
+        cleanProbes();
+
+    }
+
+    /**
+     * Set the scenes for wich to render light gizmos.
+     * @param scene 
+     */
+    public void setScene(Spatial scene) {
+        this.scene = scene;
+    }
+
+    private void cleanProbes() {
+        if (probes.size() != probeMapping.size()) {
+            for (LightProbe probe : probeMapping.keySet()) {
+                if (!probes.contains(probe)) {
+                    garbage.add(probe);
+                }
+            }
+            for (LightProbe probe : garbage) {
+                probeMapping.remove(probe);
+            }
+            garbage.clear();
+            probes.clear();
+        }
+    }
+
+    @Override
+    public void render(RenderManager rm) {
+        rm.renderScene(debugNode, getApplication().getViewPort());
+    }
+
+    /**
+     * 
+     * @see DebugMode
+     * @return the debug mode
+     */
+    public DebugMode getDebugMode() {
+        return debugMode;
+    }
+
+    /**
+     * sets the debug mode
+     * @see DebugMode
+     * @param debugMode the debug mode
+     */
+    public void setDebugMode(DebugMode debugMode) {
+        this.debugMode = debugMode;
+
+    }
+
+    /**
+     * returns the scale of the probe's debug sphere
+     * @return 
+     */
+    public float getProbeScale() {
+        return probeScale;
+    }
+
+    /**
+     * sets the scale of the probe's debug sphere
+     * @param probeScale 
+     */
+    public void setProbeScale(float probeScale) {
+        this.probeScale = probeScale;
+    }
+
+    @Override
+    protected void cleanup(Application app) {
+
+    }
+
+    @Override
+    protected void onEnable() {
+
+    }
+
+    @Override
+    protected void onDisable() {
+
+    }
+
+}

+ 70 - 0
jme3-core/src/main/java/com/jme3/light/BasicProbeBlendingStrategy.java

@@ -0,0 +1,70 @@
+/*
+ * 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 closest probe from the rendered object.
+ * 
+ * This is the most basic strategy : The fastest and the easiest.
+ * Though it has severe graphical draw backs as there might be very visible seams
+ * on static object and some "poping" on dynamic objects.
+ *
+ * @author Nehon
+ */
+public class BasicProbeBlendingStrategy implements LightProbeBlendingStrategy {
+
+    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 first probe is actually the closest to the geometry since the 
+            //light list is sorted according to the distance to the geom.
+            LightProbe p = lightProbes.get(0);
+            if (p.isReady()) {
+                lightList.add(p);
+            }            
+            //clearing the list for next pass.
+            lightProbes.clear();
+        }        
+    }
+
+}

+ 20 - 2
jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java

@@ -43,6 +43,15 @@ public final class DefaultLightFilter implements LightFilter {
 
     private Camera camera;
     private final HashSet<Light> processedLights = new HashSet<Light>();
+    private final LightProbeBlendingStrategy probeBlendStrat;
+
+    public DefaultLightFilter() {
+        probeBlendStrat = new BasicProbeBlendingStrategy();
+    }
+
+    public DefaultLightFilter(LightProbeBlendingStrategy probeBlendStrat) {
+        this.probeBlendStrat = probeBlendStrat;
+    }
     
     @Override
     public void setCamera(Camera camera) {
@@ -57,6 +66,7 @@ public final class DefaultLightFilter implements LightFilter {
         TempVars vars = TempVars.get();
         try {
             LightList worldLights = geometry.getWorldLightList();
+           
             for (int i = 0; i < worldLights.size(); i++) {
                 Light light = worldLights.get(i);
 
@@ -88,9 +98,17 @@ public final class DefaultLightFilter implements LightFilter {
                         }
                     }
                 }
-
-                filteredLightList.add(light);
+                
+                if (light.getType() == Light.Type.Probe) {
+                    probeBlendStrat.registerProbe((LightProbe) light);
+                } else {
+                    filteredLightList.add(light);
+                }
+                
             }
+            
+            probeBlendStrat.populateProbes(geometry, filteredLightList);
+
         } finally {
             vars.release();
         }

+ 8 - 1
jme3-core/src/main/java/com/jme3/light/Light.java

@@ -78,7 +78,14 @@ public abstract class Light implements Savable, Cloneable {
          * 
          * @see AmbientLight
          */
-        Ambient(3);
+        Ambient(3),
+        
+        /**
+         * Light probe
+         * @see LightProbe
+         */
+        Probe(4);
+                
 
         private int typeId;
 

+ 266 - 0
jme3-core/src/main/java/com/jme3/light/LightProbe.java

@@ -0,0 +1,266 @@
+/*
+ * 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.asset.AssetManager;
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.bounding.BoundingVolume;
+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.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;
+
+/**
+ * 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 maps held by the LightProbe :
+ * - The irradiance map (used for indirect diffuse lighting in the PBR pipeline).
+ * - The prefiltered environment map (used for indirect specular lighting and reflection in the PBE pipeline).
+ * Note that when instanciating the LightProbe, both those maps are null. 
+ * To render 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. 
+ * A light probe is ready when it has valid environment map data set.
+ * Note that you should never call setReady yourself.
+ *
+ * @see LightProbeFactory
+ * @see EnvironmentCamera
+ * @author nehon
+ */
+public class LightProbe extends Light implements Savable {
+
+    private TextureCubeMap irradianceMap;
+    private TextureCubeMap prefilteredEnvMap;
+    private BoundingVolume bounds = new BoundingSphere(1.0f, Vector3f.ZERO);
+    private boolean ready = false;
+    private Vector3f position = new Vector3f();
+    private Node debugNode;
+
+    /**
+     * 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() {        
+    }
+
+    /**
+     * returns the irradiance map texture of this Light probe.
+     * Note that this Texture may not have image data yet if the LightProbe is not ready
+     * @return the irradiance map 
+     */
+    public TextureCubeMap getIrradianceMap() {
+        return irradianceMap;
+    }
+
+    /**
+     * Sets the irradiance map
+     * @param irradianceMap the irradiance map
+     */
+    public void setIrradianceMap(TextureCubeMap irradianceMap) {
+        this.irradianceMap = irradianceMap;
+    }
+
+    /**
+     * returns the prefiltered environment map texture of this light probe
+     * Note that this Texture may not have image data yet if the LightProbe is not ready
+     * @return the prefiltered environment map
+     */
+    public TextureCubeMap getPrefilteredEnvMap() {
+        return prefilteredEnvMap;
+    }
+
+    /**
+     * Sets the prefiltered environment map 
+     * @param prefileteredEnvMap the prefiltered environment map 
+     */
+    public void setPrefilteredMap(TextureCubeMap prefileteredEnvMap) {
+        this.prefilteredEnvMap = prefileteredEnvMap;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(irradianceMap, "irradianceMap", null);
+        oc.write(prefilteredEnvMap, "prefilteredEnvMap", null);
+        oc.write(position, "position", null);
+        oc.write(bounds, "bounds", new BoundingSphere(1.0f, Vector3f.ZERO));
+        oc.write(ready, "ready", false);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule ic = im.getCapsule(this);
+        irradianceMap = (TextureCubeMap) ic.readSavable("irradianceMap", null);
+        prefilteredEnvMap = (TextureCubeMap) ic.readSavable("prefilteredEnvMap", null);
+        position = (Vector3f) ic.readSavable("position", this);
+        bounds = (BoundingVolume) ic.readSavable("bounds", new BoundingSphere(1.0f, Vector3f.ZERO));
+        ready = ic.readBoolean("ready", false);
+    }
+
+    /**
+     * returns the bounding volume of this LightProbe
+     * @return a bounding volume.
+     */
+    public BoundingVolume getBounds() {
+        return bounds;
+    }
+    
+    /**
+     * Sets the bounds of this LightProbe
+     * 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
+     */
+    public void setBounds(BoundingVolume bounds) {
+        if( bounds.getType()!= BoundingVolume.Type.Sphere){
+            throw new UnsupportedOperationException("For not only BoundingSphere are suported for LightProbe");
+        }
+        this.bounds = bounds;
+    }
+
+    /**
+     * return true if the LightProbe is ready, meaning the Environment maps have
+     * been loaded or rnedered and are ready to be used by a material
+     * @return the LightProbe ready state
+     */
+    public boolean isReady() {
+        return ready;
+    }
+
+    /**
+     * Don't call this method directly.
+     * It's meant to be called by additional systems that will load or render
+     * the Environment maps of the LightProbe
+     * @param ready the ready state of the LightProbe.
+     */
+    public void setReady(boolean ready) {
+        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 EnvProbeis not ready yet, try to test isReady()");
+        }
+        if (debugNode == null) {
+            debugNode = new Node("debug gui probe");
+            Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(getPrefilteredEnvMap(), manager);
+            Node debugIrrCm = EnvMapUtils.getCubeMapCrossDebugView(getIrradianceMap(), manager);
+
+            debugNode.attachChild(debugIrrCm);
+            debugNode.attachChild(debugPfemCm);
+            debugPfemCm.setLocalTranslation(520, 0, 0);
+        }
+
+        return debugNode;
+    }
+
+    /**
+     * Returns the position of the LightProbe in world space
+     * @return the wolrd space position
+     */
+    public Vector3f getPosition() {
+        return position;
+    }
+
+    /**
+     * Sets the position of the LightProbe in world space
+     * @param position the wolrd space position
+     */
+    public void setPosition(Vector3f position) {
+        this.position.set(position);
+        getBounds().setCenter(position);
+    }
+
+    @Override
+    public boolean intersectsBox(BoundingBox box, TempVars vars) {
+        return getBounds().intersectsBoundingBox(box);
+    }
+
+    @Override
+    public boolean intersectsFrustum(Camera camera, TempVars vars) {
+        return camera.contains(bounds) != Camera.FrustumIntersect.Outside;
+    }
+
+    @Override
+    protected void computeLastDistance(Spatial owner) {
+        if (owner.getWorldBound() != null) {
+            BoundingVolume bv = owner.getWorldBound();
+            lastDistance = bv.distanceSquaredTo(position);
+        } else {
+            lastDistance = owner.getWorldTranslation().distanceSquared(position);
+        }
+    }
+
+    @Override
+    public Type getType() {
+        return Type.Probe;
+    }
+
+    @Override
+    public String toString() {
+        return "Light Probe : " + name + " at " + position + " / " + bounds;
+    }
+
+    @Override
+    public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) {
+        return getBounds().intersectsSphere(sphere);
+    }
+    
+    
+
+}

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

@@ -0,0 +1,208 @@
+ /*
+ * 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.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;
+
+    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;
+    }
+    
+    
+    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;
+        }
+        
+    }
+}

+ 55 - 0
jme3-core/src/main/java/com/jme3/light/LightProbeBlendingStrategy.java

@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+/**
+ * This is the interface to implement if you want to make your own LightProbe blending strategy.
+ * The strategy sets the way multiple LightProbes will be handled for a given object.
+ *
+ * @author Nehon
+ */
+public interface LightProbeBlendingStrategy {
+    
+    /**
+     * Registers a probe with this strategy
+     * @param probe 
+     */
+    public void registerProbe(LightProbe probe);
+    /**
+     * Populates the resulting light probes into the given light list.
+     * @param g the geometry for wich the light list is computed
+     * @param lightList the result light list
+     */
+    public void populateProbes(Geometry g, LightList lightList);
+}

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

@@ -0,0 +1,107 @@
+/*
+ * 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();
+        }
+    }
+
+}

+ 7 - 3
jme3-core/src/main/java/com/jme3/material/Material.java

@@ -807,6 +807,8 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
             }
         }
 
+        //TODO HACKY HACK remove this when texture unit is handled by the uniform.
+        return unit;
     }
 
     private void updateRenderState(RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) {
@@ -959,13 +961,15 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
         renderManager.updateUniformBindings(shader);
         
         // Set material parameters
-        updateShaderMaterialParameters(renderer, shader, overrides, renderManager.getForcedMatParams());
-        
+
+        //TODO RRemove the unit when texture units are handled in the Uniform
+        int unit = updateShaderMaterialParameters(renderer, shader, overrides, renderManager.getForcedMatParams());
+
         // Clear any uniforms not changed by material.
         resetUniformsNotSetByCurrent(shader);
         
         // Delegate rendering to the technique
-        technique.render(renderManager, shader, geometry, lights);
+        technique.render(renderManager, shader, geometry, lights, unit);
     }
 
     /**

+ 2 - 2
jme3-core/src/main/java/com/jme3/material/Technique.java

@@ -161,9 +161,9 @@ public final class Technique {
      * @param geometry The geometry to render
      * @param lights Lights which influence the geometry.
      */
-    void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
+    void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
         TechniqueDefLogic logic = def.getLogic();
-        logic.render(renderManager, shader, geometry, lights);
+        logic.render(renderManager, shader, geometry, lights, lastTexUnit);
     }
     
     /**

+ 45 - 0
jme3-core/src/main/java/com/jme3/material/TechniqueDef.java

@@ -89,6 +89,16 @@ public class TechniqueDef implements Savable {
          */
         MultiPass,
 
+        /**
+         * Enable light rendering by using a single pass, and also uses Image based lighting for global lighting
+         * Usually used for PBR
+         * <p>
+         * An array of light positions and light colors is passed to the shader
+         * containing the world light list for the geometry being rendered.
+         * Also Light probes are passed to the shader.
+         */
+        SinglePassAndImageBased,
+
         /**
          * @deprecated OpenGL1 is not supported anymore
          */
@@ -112,6 +122,15 @@ public class TechniqueDef implements Savable {
         InPass,
         PostPass,
     }
+    
+    /**
+     * Define in what space the light data should be sent to the shader.
+     */
+    public enum LightSpace {
+        World,
+        View,
+        Legacy
+    }
 
     private final EnumSet<Caps> requiredCaps = EnumSet.noneOf(Caps.class);
     private String name;
@@ -139,6 +158,8 @@ public class TechniqueDef implements Savable {
     private TechniqueDefLogic logic;
 
     private ArrayList<UniformBinding> worldBinds;
+    //The space in which the light should be transposed before sending to the shader.
+    private LightSpace lightSpace;
 
     /**
      * Creates a new technique definition.
@@ -202,6 +223,14 @@ public class TechniqueDef implements Savable {
      */
     public void setLightMode(LightMode lightMode) {
         this.lightMode = lightMode;
+        //if light space is not specified we set it to Legacy
+        if(lightSpace == null){
+            if(lightMode== LightMode.MultiPass){
+                lightSpace = LightSpace.Legacy;
+            }else{
+                lightSpace = LightSpace.World;
+            }
+        }
     }
     
     public void setLogic(TechniqueDefLogic logic) {
@@ -714,4 +743,20 @@ public class TechniqueDef implements Savable {
                 + ", renderState=" + renderState
                 + ", forcedRenderState=" + forcedRenderState + "]";
     }
+
+    /**
+     * Returns the space in which the light data should be passed to the shader.
+     * @return the light space
+     */
+    public LightSpace getLightSpace() {
+        return lightSpace;
+    }
+
+    /**
+     * Sets the space in which the light data should be passed to the shader.
+     * @param lightSpace the light space
+     */
+    public void setLightSpace(LightSpace lightSpace) {
+        this.lightSpace = lightSpace;
+    }
 }

+ 4 - 4
jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java

@@ -32,9 +32,7 @@
 package com.jme3.material.logic;
 
 import com.jme3.asset.AssetManager;
-import com.jme3.light.AmbientLight;
-import com.jme3.light.Light;
-import com.jme3.light.LightList;
+import com.jme3.light.*;
 import com.jme3.material.TechniqueDef;
 import com.jme3.math.ColorRGBA;
 import com.jme3.renderer.Caps;
@@ -88,8 +86,10 @@ public class DefaultTechniqueDefLogic implements TechniqueDefLogic {
         return ambientLightColor;
     }
 
+
+
     @Override
-    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
         Renderer renderer = renderManager.getRenderer();
         renderer.setShader(shader);
         renderMeshFromGeometry(renderer, geometry);

+ 3 - 1
jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java

@@ -73,7 +73,7 @@ public final class MultiPassLightingLogic extends DefaultTechniqueDefLogic {
     }
 
     @Override
-    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
         Renderer r = renderManager.getRenderer();
         Uniform lightDir = shader.getUniform("g_LightDirection");
         Uniform lightColor = shader.getUniform("g_LightColor");
@@ -156,6 +156,8 @@ public final class MultiPassLightingLogic extends DefaultTechniqueDefLogic {
 
                     lightDir.setValue(VarType.Vector4, tmpLightDirection);
 
+                    break;
+                case Probe:
                     break;
                 default:
                     throw new UnsupportedOperationException("Unknown type of light: " + l.getType());

+ 251 - 0
jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java

@@ -0,0 +1,251 @@
+/*
+ * 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.material.logic;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.light.*;
+import com.jme3.material.*;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.math.*;
+import com.jme3.renderer.*;
+import com.jme3.scene.Geometry;
+import com.jme3.shader.*;
+import com.jme3.util.TempVars;
+
+import java.util.EnumSet;
+
+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 RenderState ADDITIVE_LIGHT = new RenderState();
+
+    private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
+
+    static {
+        ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive);
+        ADDITIVE_LIGHT.setDepthWrite(false);
+    }
+
+    private final int singlePassLightingDefineId;
+    private final int nbLightsDefineId;
+
+    public SinglePassAndImageBasedLightingLogic(TechniqueDef techniqueDef) {
+        super(techniqueDef);
+        singlePassLightingDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_SINGLE_PASS_LIGHTING, VarType.Boolean);
+        nbLightsDefineId = techniqueDef.addShaderUnmappedDefine(DEFINE_NB_LIGHTS, VarType.Int);
+    }
+
+    @Override
+    public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager,
+            EnumSet<Caps> rendererCaps, LightList lights, DefineList defines) {
+        defines.set(nbLightsDefineId, renderManager.getSinglePassLightBatchSize() * 3);
+        defines.set(singlePassLightingDefineId, true);
+        return super.makeCurrent(assetManager, renderManager, rendererCaps, lights, defines);
+    }
+
+    /**
+     * Uploads the lights in the light list as two uniform arrays.<br/><br/> *
+     * <p>
+     * <code>uniform vec4 g_LightColor[numLights];</code><br/> //
+     * g_LightColor.rgb is the diffuse/specular color of the light.<br/> //
+     * g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point, <br/> //
+     * 2 = Spot. <br/> <br/>
+     * <code>uniform vec4 g_LightPosition[numLights];</code><br/> //
+     * g_LightPosition.xyz is the position of the light (for point lights)<br/>
+     * // or the direction of the light (for directional lights).<br/> //
+     * g_LightPosition.w is the inverse radius (1/r) of the light (for
+     * attenuation) <br/> </p>
+     */
+    protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex, int lastTexUnit) {
+        if (numLights == 0) { // this shader does not do lighting, ignore.
+            return 0;
+        }
+
+        Uniform lightData = shader.getUniform("g_LightData");
+        lightData.setVector4Length(numLights * 3);//8 lights * max 3
+        Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
+        Uniform lightProbeData = shader.getUniform("g_LightProbeData");
+        lightProbeData.setVector4Length(1);
+        Uniform lightProbeIrrMap = shader.getUniform("g_IrradianceMap");
+        Uniform lightProbePemMap = shader.getUniform("g_PrefEnvMap");
+
+        LightProbe lightProbe = null;
+        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);
+            ambientColor.setValue(VarType.Vector4, ambientLightColor);
+        }
+
+        //If there is a lightProbe in the list we force it's 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(), 0);
+            //assigning new texture indexes
+            int irrUnit = lastTexUnit++;
+            int pemUnit = lastTexUnit++;
+
+            rm.getRenderer().setTexture(irrUnit, lightProbe.getIrradianceMap());
+            lightProbeIrrMap.setValue(VarType.Int, irrUnit);
+            rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap());
+            lightProbePemMap.setValue(VarType.Int, pemUnit);
+        } else {
+            //Disable IBL for this pass
+            lightProbeData.setVector4InArray(0,0,0,-1, 0);
+        }
+
+        int lightDataIndex = 0;
+        TempVars vars = TempVars.get();
+        Vector4f tmpVec = vars.vect4f1;
+        int curIndex;
+        int endIndex = numLights + startIndex;
+        boolean useIBL = false;
+        for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) {
+
+
+            Light l = lightList.get(curIndex);
+            if(l.getType() == Light.Type.Ambient){
+                endIndex++;
+                continue;
+            }
+            ColorRGBA color = l.getColor();
+            //Color
+
+            if(l.getType() != Light.Type.Probe){
+                lightData.setVector4InArray(color.getRed(),
+                        color.getGreen(),
+                        color.getBlue(),
+                        l.getType().getId(),
+                        lightDataIndex);
+                lightDataIndex++;
+            }
+
+            switch (l.getType()) {
+                case Directional:
+                    DirectionalLight dl = (DirectionalLight) l;
+                    Vector3f dir = dl.getDirection();
+                    //Data directly sent in view space to avoid a matrix mult for each pixel
+                    tmpVec.set(dir.getX(), dir.getY(), dir.getZ(), 0.0f);
+                    lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), -1, lightDataIndex);
+                    lightDataIndex++;
+                    //PADDING
+                    lightData.setVector4InArray(0,0,0,0, lightDataIndex);
+                    lightDataIndex++;
+                    break;
+                case Point:
+                    PointLight pl = (PointLight) l;
+                    Vector3f pos = pl.getPosition();
+                    float invRadius = pl.getInvRadius();
+                    tmpVec.set(pos.getX(), pos.getY(), pos.getZ(), 1.0f);
+
+                    lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRadius, lightDataIndex);
+                    lightDataIndex++;
+                    //PADDING
+                    lightData.setVector4InArray(0,0,0,0, lightDataIndex);
+                    lightDataIndex++;
+                    break;
+                case Spot:
+                    SpotLight sl = (SpotLight) l;
+                    Vector3f pos2 = sl.getPosition();
+                    Vector3f dir2 = sl.getDirection();
+                    float invRange = sl.getInvSpotRange();
+                    float spotAngleCos = sl.getPackedAngleCos();
+                    tmpVec.set(pos2.getX(), pos2.getY(), pos2.getZ(),  1.0f);
+
+                    lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), invRange, lightDataIndex);
+                    lightDataIndex++;
+
+                    tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(),  0.0f);
+                    lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex);
+                    lightDataIndex++;
+                    break;
+                default:
+                    throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
+            }
+        }
+        vars.release();
+
+        //Padding of unsued buffer space
+        while(lightDataIndex < numLights * 3) {
+            lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex);
+            lightDataIndex++;
+        }
+        return curIndex;
+    }
+
+    @Override
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
+        int nbRenderedLights = 0;
+        Renderer renderer = renderManager.getRenderer();
+        int batchSize = renderManager.getSinglePassLightBatchSize();
+        if (lights.size() == 0) {
+            updateLightListUniforms(shader, geometry, lights,batchSize, renderManager, 0, lastTexUnit);
+            renderer.setShader(shader);
+            renderMeshFromGeometry(renderer, geometry);
+        } else {
+            while (nbRenderedLights < lights.size()) {
+                nbRenderedLights = updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, nbRenderedLights, lastTexUnit);
+                renderer.setShader(shader);
+                renderMeshFromGeometry(renderer, geometry);
+            }
+        }
+        return;
+    }
+
+    protected LightProbe 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) {
+                ambientLightColor.addLocal(l.getColor());
+                if(removeLights){
+                    lightList.remove(l);
+                    j--;
+                }
+            }
+            if (l instanceof LightProbe) {
+                probe = (LightProbe)l;
+                if(removeLights){
+                    lightList.remove(l);
+                    j--;
+                }
+            }
+        }
+        ambientLightColor.a = 1.0f;
+        return probe;
+    }
+}

+ 3 - 1
jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java

@@ -185,6 +185,8 @@ public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic {
                     lightData.setVector4InArray(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos, lightDataIndex);
                     lightDataIndex++;
                     break;
+                case Probe:
+                    break;
                 default:
                     throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
             }
@@ -199,7 +201,7 @@ public final class SinglePassLightingLogic extends DefaultTechniqueDefLogic {
     }
 
     @Override
-    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
         int nbRenderedLights = 0;
         Renderer renderer = renderManager.getRenderer();
         int batchSize = renderManager.getSinglePassLightBatchSize();

+ 1 - 1
jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java

@@ -171,7 +171,7 @@ public final class StaticPassLightingLogic extends DefaultTechniqueDefLogic {
     }
 
     @Override
-    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights) {
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) {
         Renderer renderer = renderManager.getRenderer();
         Matrix4f viewMatrix = renderManager.getCurrentCamera().getViewMatrix();
         updateLightListUniforms(viewMatrix, shader, lights);

+ 1 - 1
jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java

@@ -93,5 +93,5 @@ public interface TechniqueDefLogic {
      * @param geometry The geometry to render
      * @param lights Lights which influence the geometry.
      */
-    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights);
+    public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit);
 }

+ 9 - 0
jme3-core/src/main/java/com/jme3/renderer/RenderManager.java

@@ -804,6 +804,15 @@ public class RenderManager {
     public void setLightFilter(LightFilter lightFilter) {
         this.lightFilter = lightFilter;
     }
+    
+    /**
+     * Returns the current LightFilter.
+     * 
+     * @return the current light filter 
+     */
+    public LightFilter getLightFilter() {
+        return this.lightFilter;
+    }
 
     /**
      * Defines what light mode will be selected when a technique offers several light modes.

+ 7 - 0
jme3-core/src/main/java/com/jme3/scene/Geometry.java

@@ -319,6 +319,13 @@ public class Geometry extends Spatial {
         worldLights.sort(true);
     }
 
+    @Override
+    protected void updateWorldLightList() {
+        super.updateWorldLightList();
+        // geometry requires lights to be sorted
+        worldLights.sort(true);
+    }
+
     /**
      * Associate this <code>Geometry</code> with a {@link GeometryGroupNode}.
      *

+ 1 - 1
jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md

@@ -119,7 +119,7 @@ MaterialDef Phong Lighting {
 
     Technique {
         LightMode SinglePass
-        
+
         VertexShader GLSL100:   Common/MatDefs/Light/SPLighting.vert
         FragmentShader GLSL100: Common/MatDefs/Light/SPLighting.frag
 

+ 238 - 0
jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag

@@ -0,0 +1,238 @@
+#import "Common/ShaderLib/Parallax.glsllib"
+#import "Common/ShaderLib/PBR.glsllib"
+#import "Common/ShaderLib/Lighting.glsllib"
+
+varying vec2 texCoord;
+#ifdef SEPARATE_TEXCOORD
+  varying vec2 texCoord2;
+#endif
+
+varying vec4 Color;
+
+uniform vec4 g_LightData[NB_LIGHTS];
+
+uniform vec3 g_CameraPosition;
+
+uniform float m_Roughness;
+uniform float m_Metallic;
+
+varying vec3 wPosition;    
+
+
+//#ifdef INDIRECT_LIGHTING
+//  uniform sampler2D m_IntegrateBRDF;
+  uniform samplerCube g_PrefEnvMap;
+  uniform samplerCube g_IrradianceMap;
+  uniform vec4 g_LightProbeData;
+//#endif
+
+#ifdef BASECOLORMAP
+  uniform sampler2D m_BaseColorMap;
+#endif
+#ifdef METALLICMAP
+  uniform sampler2D m_MetallicMap;
+#endif
+#ifdef ROUGHNESSMAP
+  uniform sampler2D m_RoughnessMap;
+#endif
+
+#ifdef EMISSIVE
+    uniform vec4 m_Emissive;
+#endif
+#ifdef EMISSIVEMAP
+    uniform sampler2D m_EmissiveMap;
+#endif
+#if defined(EMISSIVE) || defined(EMISSIVEMAP)
+    uniform float m_EmissivePower;
+    uniform float m_EmissiveIntensity;
+#endif 
+
+#ifdef SPECGLOSSPIPELINE
+  uniform sampler2D m_SpecularMap;
+  uniform sampler2D m_GlossMap;
+#endif
+
+#ifdef PARALLAXMAP
+  uniform sampler2D m_ParallaxMap;  
+#endif
+#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP)))
+    uniform float m_ParallaxHeight;
+#endif
+
+#ifdef LIGHTMAP
+  uniform sampler2D m_LightMap;
+#endif
+  
+#if defined(NORMALMAP) || defined(PARALLAXMAP)
+  uniform sampler2D m_NormalMap;   
+  varying vec4 wTangent;
+#endif
+varying vec3 wNormal;
+
+#ifdef DISCARD_ALPHA
+uniform float m_AlphaDiscardThreshold;
+#endif
+
+void main(){
+    vec2 newTexCoord;
+    vec3 viewDir = normalize(g_CameraPosition - wPosition);
+
+    #if defined(NORMALMAP) || defined(PARALLAXMAP)
+        mat3 tbnMat = mat3(wTangent.xyz, wTangent.w * cross( (wNormal), (wTangent.xyz)), wNormal.xyz);
+    #endif
+
+    #if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP)))
+       vec3 vViewDir =  viewDir * tbnMat;  
+       #ifdef STEEP_PARALLAX
+           #ifdef NORMALMAP_PARALLAX
+               //parallax map is stored in the alpha channel of the normal map         
+               newTexCoord = steepParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight);
+           #else
+               //parallax map is a texture
+               newTexCoord = steepParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight);         
+           #endif
+       #else
+           #ifdef NORMALMAP_PARALLAX
+               //parallax map is stored in the alpha channel of the normal map         
+               newTexCoord = classicParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight);
+           #else
+               //parallax map is a texture
+               newTexCoord = classicParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight);
+           #endif
+       #endif
+    #else
+       newTexCoord = texCoord;    
+    #endif
+    
+    #ifdef BASECOLORMAP
+        vec4 albedo = texture2D(m_BaseColorMap, newTexCoord);
+    #else
+        vec4 albedo = Color;
+    #endif
+    #ifdef ROUGHNESSMAP
+        float Roughness = texture2D(m_RoughnessMap, newTexCoord).r * max(m_Roughness, 1e-8);
+    #else
+        float Roughness =  max(m_Roughness, 1e-8);
+    #endif
+    #ifdef METALLICMAP   
+        float Metallic = texture2D(m_MetallicMap, newTexCoord).r;
+    #else
+        float Metallic =  max(m_Metallic, 0.0);
+    #endif
+ 
+    float alpha = Color.a * albedo.a;
+
+    #ifdef DISCARD_ALPHA
+        if(alpha < m_AlphaDiscardThreshold){
+            discard;
+        }
+    #endif
+ 
+    // ***********************
+    // Read from textures
+    // ***********************
+    #if defined(NORMALMAP)
+      vec4 normalHeight = texture2D(m_NormalMap, newTexCoord);
+      //Note the -2.0 and -1.0. We invert the green channel of the normal map, 
+      //as it's complient with normal maps generated with blender.
+      //see http://hub.jmonkeyengine.org/forum/topic/parallax-mapping-fundamental-bug/#post-256898
+      //for more explanation.
+      vec3 normal = normalize((normalHeight.xyz * vec3(2.0,-2.0,2.0) - vec3(1.0,-1.0,1.0)));
+      normal = normalize(tbnMat * normal);
+      //normal = normalize(normal * inverse(tbnMat));
+    #else
+      vec3 normal = normalize(wNormal);            
+    #endif
+
+   
+    #ifdef LIGHTMAP
+       vec3 lightMapColor;
+       #ifdef SEPARATE_TEXCOORD
+          lightMapColor = texture2D(m_LightMap, texCoord2).rgb;
+       #else
+          lightMapColor = texture2D(m_LightMap, texCoord).rgb;
+       #endif
+       specularColor.rgb *= lightMapColor;
+       albedo.rgb  *= lightMapColor;
+    #endif
+
+    float specular = 0.5;
+    #ifdef SPECGLOSSPIPELINE
+          vec4 specularColor = texture2D(m_SpecularMap, newTexCoord);
+          vec4 diffuseColor = albedo;
+          Roughness = 1.0 - texture2D(m_GlossMap, newTexCoord).r;          
+    #else      
+        float nonMetalSpec = 0.08 * specular;
+        vec4 specularColor = (nonMetalSpec - nonMetalSpec * Metallic) + albedo * Metallic;
+        vec4 diffuseColor = albedo - albedo * Metallic;
+    #endif
+
+    gl_FragColor.rgb = vec3(0.0);
+    float ndotv = max( dot( normal, viewDir ),0.0);
+    for( int i = 0;i < NB_LIGHTS; i+=3){
+        vec4 lightColor = g_LightData[i];
+        vec4 lightData1 = g_LightData[i+1];                
+        vec4 lightDir;
+        vec3 lightVec;            
+        lightComputeDir(wPosition, lightColor.w, lightData1, lightDir, lightVec);
+
+        float fallOff = 1.0;
+        #if __VERSION__ >= 110
+            // allow use of control flow
+        if(lightColor.w > 1.0){
+        #endif
+            fallOff =  computeSpotFalloff(g_LightData[i+2], lightVec);
+        #if __VERSION__ >= 110
+        }
+        #endif
+        //point light attenuation
+        fallOff *= lightDir.w;
+
+        lightDir.xyz = normalize(lightDir.xyz);            
+        vec3 directDiffuse;
+        vec3 directSpecular;
+        
+        PBR_ComputeDirectLight(normal, lightDir.xyz, viewDir,
+                            lightColor.rgb,specular, Roughness, ndotv,
+                            directDiffuse,  directSpecular);
+
+        vec3 directLighting = diffuseColor.rgb *directDiffuse + directSpecular * specularColor.rgb;
+        
+        gl_FragColor.rgb += directLighting * fallOff;
+    }
+
+    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, g_LightProbeData.xyz is the position of the lightProbe.
+    rv = g_LightProbeData.w * (wPosition - g_LightProbeData.xyz) +rv;
+
+     //horizon fade from http://marmosetco.tumblr.com/post/81245981087
+    float horiz = dot(rv, wNormal.xyz);
+    float horizFadePower= 1.0 - Roughness;
+    horiz = clamp( 1.0 + horizFadePower * horiz, 0.0, 1.0 );
+    horiz *= horiz;
+
+    vec3 indirectDiffuse = vec3(0.0);
+    vec3 indirectSpecular = vec3(0.0);    
+    indirectDiffuse = textureCube(g_IrradianceMap, normal.xyz).rgb * diffuseColor.rgb;
+
+    indirectSpecular = ApproximateSpecularIBLPolynomial(g_PrefEnvMap, specularColor.rgb, Roughness, ndotv, rv.xyz);
+    indirectSpecular *= vec3(horiz);
+
+    vec3 indirectLighting =  indirectDiffuse + indirectSpecular;
+
+    gl_FragColor.rgb = gl_FragColor.rgb + indirectLighting * step( 0.0, g_LightProbeData.w);        
+ 
+    #if defined(EMISSIVE) || defined (EMISSIVEMAP)
+        #ifdef EMISSIVEMAP
+            vec4 emissive = texture2D(m_EmissiveMap, newTexCoord);
+        #else
+            vec4 emissive = m_Emissive;
+        #endif
+        gl_FragColor += emissive * pow(emissive.a, m_EmissivePower) * m_EmissiveIntensity;
+    #endif
+           
+    gl_FragColor.a = alpha;
+    
+   
+}

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

@@ -0,0 +1,307 @@
+MaterialDef PBR Lighting {
+
+    MaterialParameters {
+
+        // Alpha threshold for fragment discarding
+        Float AlphaDiscardThreshold (AlphaTestFallOff)
+
+        //metalness of the material
+        Float Metallic : 0.0
+        //Roughness of the material
+        Float Roughness : 1.0        
+        // Base material color
+        Color BaseColor
+        // The emissive color of the object
+        Color Emissive        
+        // the emissive power
+        Float EmissivePower : 3.0        
+        // the emissive intensity
+        Float EmissiveIntensity : 1.0
+
+        // BaseColor map
+        Texture2D BaseColorMap
+
+        // Specular/gloss map
+        Texture2D MetallicMap -LINEAR
+        
+        // Roughness Map
+        Texture2D RoughnessMap -LINEAR
+        
+        // Texture of the emissive parts of the material
+        Texture2D EmissiveMap
+
+        // Normal map
+        Texture2D NormalMap -LINEAR
+
+        // For Spec gloss pipeline
+        Texture2D SpecularMap
+        Texture2D GlossMap
+
+        Vector4 ProbeData
+
+        // Prefiltered Env Map for indirect specular lighting
+        TextureCubeMap PrefEnvMap -LINEAR
+        
+        // Irradiance map for indirect diffuse lighting
+        TextureCubeMap IrradianceMap -LINEAR
+
+        //integrate BRDF map for indirect Lighting
+        Texture2D IntegrateBRDF -LINEAR
+
+        // Parallax/height map
+        Texture2D ParallaxMap -LINEAR
+
+        //Set to true is parallax map is stored in the alpha channel of the normal map
+        Boolean PackedNormalParallax   
+
+        //Sets the relief height for parallax mapping
+        Float ParallaxHeight : 0.05       
+
+        //Set to true to activate Steep Parallax mapping
+        Boolean SteepParallax
+
+        // Set to Use Lightmap
+        Texture2D LightMap
+
+        // Set to use TexCoord2 for the lightmap sampling
+        Boolean SeparateTexCoord
+
+        //shadows
+        Int FilterMode
+        Boolean HardwareShadows
+
+        Texture2D ShadowMap0
+        Texture2D ShadowMap1
+        Texture2D ShadowMap2
+        Texture2D ShadowMap3
+        //pointLights
+        Texture2D ShadowMap4
+        Texture2D ShadowMap5
+        
+        Float ShadowIntensity
+        Vector4 Splits
+        Vector2 FadeInfo
+
+        Matrix4 LightViewProjectionMatrix0
+        Matrix4 LightViewProjectionMatrix1
+        Matrix4 LightViewProjectionMatrix2
+        Matrix4 LightViewProjectionMatrix3
+        //pointLight
+        Matrix4 LightViewProjectionMatrix4
+        Matrix4 LightViewProjectionMatrix5   
+        Vector3 LightPos
+        Vector3 LightDir
+
+        Float PCFEdge
+        Float ShadowMapSize
+
+        // For hardware skinning
+        Int NumberOfBones
+        Matrix4Array BoneMatrices
+                
+        //For instancing
+        Boolean UseInstancing
+
+        //For Vertex Color
+        Boolean UseVertexColor
+    }
+
+ Technique {
+        LightMode SinglePassAndImageBased
+        
+        VertexShader GLSL100:   Common/MatDefs/Light/PBRLighting.vert
+        FragmentShader GLSL100: Common/MatDefs/Light/PBRLighting.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+            CameraPosition
+            WorldMatrix
+        }
+
+        Defines {         
+            BASECOLORMAP : BaseColorMap            
+            NORMALMAP : NormalMap
+            METALLICMAP : MetallicMap
+            ROUGHNESSMAP : RoughnessMap
+            EMISSIVEMAP : EmissiveMap
+            EMISSIVE : Emissive
+            SPECGLOSSPIPELINE : SpecularMap
+            PARALLAXMAP : ParallaxMap
+            NORMALMAP_PARALLAX : PackedNormalParallax
+            STEEP_PARALLAX : SteepParallax
+            LIGHTMAP : LightMap
+            SEPARATE_TEXCOORD : SeparateTexCoord
+            DISCARD_ALPHA : AlphaDiscardThreshold                        
+            NUM_BONES : NumberOfBones                        
+            INSTANCING : UseInstancing
+            //INDIRECT_LIGHTING : IntegrateBRDF
+            VERTEX_COLOR : UseVertexColor
+        }
+    }
+
+   
+
+    Technique PreShadow {
+
+        VertexShader GLSL100 :   Common/MatDefs/Shadow/PreShadow.vert
+        FragmentShader GLSL100 : Common/MatDefs/Shadow/PreShadow.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+            WorldViewMatrix
+            ViewProjectionMatrix
+            ViewMatrix
+        }
+
+        Defines {
+            COLOR_MAP : ColorMap
+            DISCARD_ALPHA : AlphaDiscardThreshold
+            NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
+        }
+
+        ForcedRenderState {
+            FaceCull Off
+            DepthTest On
+            DepthWrite On
+            PolyOffset 5 3
+            ColorWrite Off
+        }
+
+    }
+
+
+    Technique PostShadow15{
+        VertexShader GLSL150:   Common/MatDefs/Shadow/PostShadow.vert
+        FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+            WorldMatrix
+            ViewProjectionMatrix
+            ViewMatrix
+        }
+
+        Defines {
+            HARDWARE_SHADOWS : HardwareShadows
+            FILTER_MODE : FilterMode
+            PCFEDGE : PCFEdge
+            DISCARD_ALPHA : AlphaDiscardThreshold           
+            COLOR_MAP : ColorMap
+            SHADOWMAP_SIZE : ShadowMapSize
+            FADE : FadeInfo
+            PSSM : Splits
+            POINTLIGHT : LightViewProjectionMatrix5
+            NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
+        }
+
+        ForcedRenderState {
+            Blend Modulate
+            DepthWrite Off                 
+            PolyOffset -0.1 0
+        }
+    }
+
+    Technique PostShadow{
+        VertexShader GLSL100:   Common/MatDefs/Shadow/PostShadow.vert
+        FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadow.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+            WorldMatrix
+            ViewProjectionMatrix
+            ViewMatrix
+        }
+
+        Defines {
+            HARDWARE_SHADOWS : HardwareShadows
+            FILTER_MODE : FilterMode
+            PCFEDGE : PCFEdge
+            DISCARD_ALPHA : AlphaDiscardThreshold           
+            COLOR_MAP : ColorMap
+            SHADOWMAP_SIZE : ShadowMapSize
+            FADE : FadeInfo
+            PSSM : Splits
+            POINTLIGHT : LightViewProjectionMatrix5
+            NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
+        }
+
+        ForcedRenderState {
+            Blend Modulate
+            DepthWrite Off   
+            PolyOffset -0.1 0  
+        }
+    }
+
+  Technique PreNormalPass {
+
+        VertexShader GLSL100 :   Common/MatDefs/SSAO/normal.vert
+        FragmentShader GLSL100 : Common/MatDefs/SSAO/normal.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+            WorldViewMatrix
+            NormalMatrix
+            ViewProjectionMatrix
+            ViewMatrix
+        }
+
+        Defines {
+            DIFFUSEMAP_ALPHA : DiffuseMap
+            NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
+        }
+
+    }
+
+
+    Technique PreNormalPassDerivative {
+
+        VertexShader GLSL100 :   Common/MatDefs/MSSAO/normal.vert
+        FragmentShader GLSL100 : Common/MatDefs/MSSAO/normal.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+            WorldViewMatrix
+            NormalMatrix                        
+            ViewProjectionMatrix
+            ViewMatrix
+        }
+
+        Defines {
+            DIFFUSEMAP_ALPHA : DiffuseMap
+            NUM_BONES : NumberOfBones
+            INSTANCING : UseInstancing
+        }
+
+    }
+
+    Technique GBuf {
+
+        VertexShader GLSL100:   Common/MatDefs/Light/GBuf.vert
+        FragmentShader GLSL100: Common/MatDefs/Light/GBuf.frag
+
+        WorldParameters {
+            WorldViewProjectionMatrix
+            NormalMatrix
+            WorldViewMatrix
+            WorldMatrix
+        }
+
+        Defines {
+            VERTEX_COLOR : UseVertexColor
+            MATERIAL_COLORS : UseMaterialColors
+            V_TANGENT : VTangent
+            MINNAERT  : Minnaert
+            WARDISO   : WardIso
+
+            DIFFUSEMAP : DiffuseMap
+            NORMALMAP : NormalMap
+            SPECULARMAP : SpecularMap
+            PARALLAXMAP : ParallaxMap
+        }
+    }
+
+}

+ 65 - 0
jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert

@@ -0,0 +1,65 @@
+#import "Common/ShaderLib/Instancing.glsllib"
+#import "Common/ShaderLib/Skinning.glsllib"
+
+uniform vec4 m_BaseColor;
+
+uniform vec4 g_AmbientLightColor;
+varying vec2 texCoord;
+
+#ifdef SEPARATE_TEXCOORD
+  varying vec2 texCoord2;
+  attribute vec2 inTexCoord2;
+#endif
+
+varying vec4 Color;
+
+attribute vec3 inPosition;
+attribute vec2 inTexCoord;
+attribute vec3 inNormal;
+
+#ifdef VERTEX_COLOR
+  attribute vec4 inColor;
+#endif
+
+varying vec3 wNormal;
+varying vec3 wPosition;
+#if defined(NORMALMAP) || defined(PARALLAXMAP)
+    attribute vec4 inTangent;
+    varying vec4 wTangent;
+#endif
+
+void main(){
+    vec4 modelSpacePos = vec4(inPosition, 1.0);
+    vec3 modelSpaceNorm = inNormal;
+
+    #if  ( defined(NORMALMAP) || defined(PARALLAXMAP)) && !defined(VERTEX_LIGHTING)
+         vec3 modelSpaceTan  = inTangent.xyz;
+    #endif
+
+    #ifdef NUM_BONES
+         #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING)
+         Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan);
+         #else
+         Skinning_Compute(modelSpacePos, modelSpaceNorm);
+         #endif
+    #endif
+
+    gl_Position = TransformWorldViewProjection(modelSpacePos);
+    texCoord = inTexCoord;
+    #ifdef SEPARATE_TEXCOORD
+       texCoord2 = inTexCoord2;
+    #endif
+
+    wPosition = TransformWorld(modelSpacePos).xyz;
+    wNormal  = TransformWorldNormal(modelSpaceNorm);
+
+    #if defined(NORMALMAP) || defined(PARALLAXMAP)
+      wTangent = vec4(TransformWorldNormal(modelSpaceTan),inTangent.w);
+    #endif
+
+    Color = m_BaseColor;
+    
+    #ifdef VERTEX_COLOR                    
+        Color *= inColor;
+    #endif
+}

+ 45 - 0
jme3-core/src/main/resources/Common/MatDefs/Misc/reflect.j3md

@@ -0,0 +1,45 @@
+MaterialDef Simple {
+    MaterialParameters {
+        TextureCubeMap CubeMap
+    }
+    Technique {
+        WorldParameters {
+            WorldViewProjectionMatrix
+            WorldMatrix
+            CameraPosition
+        }
+        VertexShaderNodes {
+            ShaderNode Reflect {
+                Definition : Reflect : Common/MatDefs/ShaderNodes/Environment/reflect.j3sn
+                InputMappings {
+                    normal = Attr.inNormal
+                    position = Global.position.xyz
+                    worldMatrix = WorldParam.WorldMatrix
+                    camPosition = WorldParam.CameraPosition
+                }
+            }
+            ShaderNode CommonVert {
+                Definition : CommonVert : Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn
+                InputMappings {
+                    worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix
+                    modelPosition = Global.position.xyz
+                }
+                OutputMappings {
+                    Global.position = projPosition
+                }
+            }
+        }
+        FragmentShaderNodes {
+            ShaderNode EnvMapping {
+                Definition : EnvMapping : Common/MatDefs/ShaderNodes/Environment/envMapping.j3sn
+                InputMappings {
+                    refVec = Reflect.refVec
+                    cubeMap = MatParam.CubeMap
+                }
+                OutputMappings {
+                    Global.color = color
+                }
+            }
+        }
+    }
+}

+ 22 - 0
jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping.j3sn

@@ -0,0 +1,22 @@
+ShaderNodeDefinitions{ 
+    ShaderNodeDefinition EnvMapping {      
+        Type: Fragment
+
+        Shader GLSL100: Common/MatDefs/ShaderNodes/Environment/envMapping100.frag
+        Shader GLSL130: Common/MatDefs/ShaderNodes/Environment/envMapping130.frag
+        
+        Documentation{
+            fetches a texel in a cube map            
+            @input vec3 refVec the reflection vector
+            @input samplerCube cubeMap the cube map
+            @output vec4 color the output color
+        }
+        Input {
+            vec3 refVec
+            samplerCube cubeMap
+        }
+        Output {
+             vec4 color
+        }
+    }
+}

+ 8 - 0
jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping100.frag

@@ -0,0 +1,8 @@
+
+void main(){
+        //@input vec3 refVec the reflection vector
+    //@input samplerCube cubeMap the cube map
+    //@output vec4 color the output color
+
+    color = textureCubeLod(cubeMap, refVec, 0.0);
+}

+ 8 - 0
jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/envMapping130.frag

@@ -0,0 +1,8 @@
+
+void main(){
+        //@input vec3 refVec the reflection vector
+    //@input samplerCube cubeMap the cube map
+    //@output vec4 color the output color
+
+        color = textureLod(cubeMap, refVec, 0.0);
+}

+ 25 - 0
jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect.j3sn

@@ -0,0 +1,25 @@
+ShaderNodeDefinitions{ 
+    ShaderNodeDefinition Reflect {      
+        Type: Vertex
+
+        Shader GLSL100: Common/MatDefs/ShaderNodes/Environment/reflect100.vert
+        
+        Documentation{
+            Computes the relfection vector necessary to do some environment mapping            
+            @input vec3 position position in model space
+            @input vec3 normal the normal of the vertex
+            @input vec3 camPosition camera position in world space
+            @input mat4 worldMatrix the world matrix
+            @output vec3 refVec the reflection vector
+        }
+        Input {
+            vec3 position
+            vec3 normal
+            vec3 camPosition
+            mat4 worldMatrix
+        }
+        Output {
+             vec3 refVec
+        }
+    }
+}

+ 14 - 0
jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect100.vert

@@ -0,0 +1,14 @@
+
+void main(){
+        //@input vec3 position position in model space
+    //@input vec3 normal the normal of the vertex
+    //@input vec3 camPosition camera position in world space
+    //@input mat4 worldMatrix the world view matrix
+    //@output vec3 refVec the reflection vector
+
+    vec3 worldPos = (worldMatrix * vec4(position, 1.0)).xyz;
+    vec3 N = normalize((worldMatrix * vec4(normal, 0.0)).xyz);
+    vec3 I = normalize( camPosition - worldPos  ).xyz;
+    refVec.xyz = reflect(-I, N);
+
+}

+ 12 - 5
jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib

@@ -60,14 +60,16 @@ vec4 TransformWorldViewProjection(vec4 position)
     return g_ViewProjectionMatrix * TransformWorld(position);
 }
 
-vec3 TransformNormal(vec3 vec)
-{
+vec3 TransformWorldNormal(vec3 vec) {
     vec4 quat = vec4(inInstanceData[0].w, inInstanceData[1].w,
                      inInstanceData[2].w, inInstanceData[3].w);
 
-    vec3 worldNormal = vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz);
-    
-    return (g_ViewMatrix * vec4(worldNormal, 0.0)).xyz;
+    return vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz);
+}
+
+vec3 TransformNormal(vec3 vec)
+{
+    return (g_ViewMatrix * vec4(TransformWorldNormal(vec), 0.0)).xyz;
 }
 
 // Prevent user from using g_** matrices which will have invalid data in this case.
@@ -97,4 +99,9 @@ vec3 TransformNormal(vec3 normal) {
 	return g_NormalMatrix * normal;
 }
 
+vec3 TransformWorldNormal(vec3 normal) {
+    return normalize((g_WorldMatrix * vec4(normal,0.0)).xyz);
+}
+
+ 
 #endif

+ 154 - 0
jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib

@@ -0,0 +1,154 @@
+
+#ifndef PI
+    #define PI 3.14159265358979323846264
+#endif
+
+//Specular fresnel computation
+vec3 F_Shlick(float vh,	vec3 F0){
+	float fresnelFact = pow(2.0, (-5.55473*vh - 6.98316) * vh);
+	return mix(F0, vec3(1.0, 1.0, 1.0), fresnelFact);
+}
+
+void PBR_ComputeDirectLightSpecWF(vec3 normal, vec3 lightDir, vec3 viewDir,
+                            vec3 lightColor, vec3 specColor, float roughness, float ndotv,
+                            out vec3 outDiffuse, out vec3 outSpecular){
+    // Compute halfway vector.
+    vec3 halfVec = normalize(lightDir + viewDir);
+
+    // Compute ndotl, ndoth,  vdoth terms which are needed later.
+    float ndotl = max( dot(normal,   lightDir), 0.0);
+    float ndoth = max( dot(normal,   halfVec),  0.0);       
+    float hdotv = max( dot(viewDir,  halfVec),  0.0);
+
+    // Compute diffuse using energy-conserving Lambert.
+    // Alternatively, use Oren-Nayar for really rough 
+    // materials or if you have lots of processing power ...
+    outDiffuse = vec3(ndotl) * lightColor;
+
+    //cook-torrence, microfacet BRDF : http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
+   
+    float alpha = roughness * roughness;
+
+    //D, GGX normaal Distribution function     
+    float alpha2 = alpha * alpha;
+    float sum  = ((ndoth * ndoth) * (alpha2 - 1.0) + 1.0);
+    float denom = PI * sum * sum;
+    float D = alpha2 / denom;  
+
+    // Compute Fresnel function via Schlick's approximation.
+    vec3 fresnel =  F_Shlick(hdotv,  specColor);
+    
+    //G Shchlick GGX Gometry shadowing term,  k = alpha/2
+    float k = alpha * 0.5;
+
+    // UE4 way to optimise shlick GGX Gometry shadowing term
+    //http://graphicrants.blogspot.co.uk/2013/08/specular-brdf-reference.html
+    float G_V = ndotv + sqrt( (ndotv - ndotv * k) * ndotv + k );
+    float G_L = ndotl + sqrt( (ndotl - ndotl * k) * ndotl + k );    
+    // the max here is to avoid division by 0 that may cause some small glitches.
+    float G = 1.0/max( G_V * G_L ,0.01); 
+
+    float specular = D * G * ndotl;  
+ 
+    outSpecular = fresnel * vec3(specular) * lightColor;
+}
+
+void PBR_ComputeDirectLight(vec3 normal, vec3 lightDir, vec3 viewDir,
+                            vec3 lightColor, float fZero, float roughness, float ndotv,
+                            out vec3 outDiffuse, out vec3 outSpecular){
+    // Compute halfway vector.
+    vec3 halfVec = normalize(lightDir + viewDir);
+
+    // Compute ndotl, ndoth,  vdoth terms which are needed later.
+    float ndotl = max( dot(normal,   lightDir), 0.0);
+    float ndoth = max( dot(normal,   halfVec),  0.0);       
+    float hdotv = max( dot(viewDir,  halfVec),  0.0);
+
+    // Compute diffuse using energy-conserving Lambert.
+    // Alternatively, use Oren-Nayar for really rough 
+    // materials or if you have lots of processing power ...
+    outDiffuse = vec3(ndotl) * lightColor;
+
+    //cook-torrence, microfacet BRDF : http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
+   
+    float alpha = roughness * roughness;
+
+    //D, GGX normaal Distribution function     
+    float alpha2 = alpha * alpha;
+    float sum  = ((ndoth * ndoth) * (alpha2 - 1.0) + 1.0);
+    float denom = PI * sum * sum;
+    float D = alpha2 / denom;  
+
+    // Compute Fresnel function via Schlick's approximation.
+    float fresnel = fZero + ( 1.0 - fZero ) * pow( 2.0, (-5.55473 * hdotv - 6.98316) * hdotv);
+    
+    //G Shchlick GGX Gometry shadowing term,  k = alpha/2
+    float k = alpha * 0.5;
+
+ /*   
+    //classic Schlick ggx
+    float G_V = ndotv / (ndotv * (1.0 - k) + k);
+    float G_L = ndotl / (ndotl * (1.0 - k) + k);
+    float G = ( G_V * G_L );
+    
+    float specular =(D* fresnel * G) /(4 * ndotv);
+   */
+ 
+    // UE4 way to optimise shlick GGX Gometry shadowing term
+    //http://graphicrants.blogspot.co.uk/2013/08/specular-brdf-reference.html
+    float G_V = ndotv + sqrt( (ndotv - ndotv * k) * ndotv + k );
+    float G_L = ndotl + sqrt( (ndotl - ndotl * k) * ndotl + k );    
+    // the max here is to avoid division by 0 that may cause some small glitches.
+    float G = 1.0/max( G_V * G_L ,0.01); 
+
+    float specular = D * fresnel * G * ndotl;  
+ 
+    outSpecular = vec3(specular) * lightColor;
+}
+
+//https://knarkowicz.wordpress.com/2014/12/27/analytical-dfg-term-for-ibl/
+vec3 EnvDFGPolynomial( vec3 specularColor, float roughness, float ndotv ){
+    float x = 1.0 - roughness;
+    float y = ndotv;
+ 
+    float b1 = -0.1688;
+    float b2 = 1.895;
+    float b3 = 0.9903;
+    float b4 = -4.853;
+    float b5 = 8.404;
+    float b6 = -5.069;
+    float bias = clamp( min( b1 * x + b2 * x * x, b3 + b4 * y + b5 * y * y + b6 * y * y * y ), 0.0, 1.0 );
+ 
+    float d0 = 0.6045;
+    float d1 = 1.699;
+    float d2 = -0.5228;
+    float d3 = -3.603;
+    float d4 = 1.404;
+    float d5 = 0.1939;
+    float d6 = 2.661;
+    float delta = clamp(( d0 + d1 * x + d2 * y + d3 * x * x + d4 * x * y + d5 * y * y + d6 * x * x * x ), 0.0, 1.0);
+    float scale = delta - bias;
+ 
+    bias *= clamp( 2.5 / (roughness) * specularColor.y, 0.0, 1.0 );
+    return specularColor * scale + bias;
+}
+
+vec3 ApproximateSpecularIBL(samplerCube envMap,sampler2D integrateBRDF, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec){
+    //TODO magic values should be replaced by defines.
+    float Lod = log2(Roughness) * 1.1 + 6.0 - 2.0;
+    vec3 PrefilteredColor =  textureCubeLod(envMap, refVec.xyz,Lod).rgb;
+    vec2 EnvBRDF = texture2D(integrateBRDF,vec2(Roughness, ndotv)).rg;
+    return PrefilteredColor * ( SpecularColor * EnvBRDF.x+ EnvBRDF.y );    
+}
+
+vec3 ApproximateSpecularIBLPolynomial(samplerCube envMap, vec3 SpecularColor , float Roughness, float ndotv, vec3 refVec){
+    //TODO magic values should be replaced by defines.
+    float Lod = log2(Roughness) * 1.5 + 6.0 - 1.0;
+    vec3 PrefilteredColor =  textureCubeLod(envMap, refVec.xyz, Lod).rgb;    
+    return PrefilteredColor * EnvDFGPolynomial(SpecularColor, Roughness, ndotv);
+}
+
+
+
+
+

BIN
jme3-core/src/main/resources/Common/Textures/integrateBRDF.ktx


+ 18 - 3
jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java

@@ -31,9 +31,7 @@
  */
 package com.jme3.material.plugins;
 
-import com.jme3.material.logic.MultiPassLightingLogic;
-import com.jme3.material.logic.SinglePassLightingLogic;
-import com.jme3.material.logic.DefaultTechniqueDefLogic;
+import com.jme3.material.logic.*;
 import com.jme3.asset.*;
 import com.jme3.material.*;
 import com.jme3.material.RenderState.BlendEquation;
@@ -121,9 +119,21 @@ public class J3MLoader implements AssetLoader {
         if (split.length != 2){
             throw new IOException("LightMode statement syntax incorrect");
         }
+
         LightMode lm = LightMode.valueOf(split[1]);
         technique.setLightMode(lm);
     }
+    
+    
+    // LightMode <SPACE>
+    private void readLightSpace(String statement) throws IOException{
+        String[] split = statement.split(whitespacePattern);
+        if (split.length != 2){
+            throw new IOException("LightSpace statement syntax incorrect");
+        }
+        TechniqueDef.LightSpace ls = TechniqueDef.LightSpace.valueOf(split[1]);        
+        technique.setLightSpace(ls);
+    }
 
     // ShadowMode <MODE>
     private void readShadowMode(String statement) throws IOException{
@@ -543,6 +553,8 @@ public class J3MLoader implements AssetLoader {
             readShaderStatement(statement.getLine());
         }else if (split[0].equals("LightMode")){
             readLightMode(statement.getLine());
+        }else if (split[0].equals("LightSpace")){
+            readLightSpace(statement.getLine());
         }else if (split[0].equals("ShadowMode")){
             readShadowMode(statement.getLine());
         }else if (split[0].equals("WorldParameters")){
@@ -650,6 +662,9 @@ public class J3MLoader implements AssetLoader {
             case StaticPass:
                 technique.setLogic(new StaticPassLightingLogic(technique));
                 break;
+            case SinglePassAndImageBased:
+                technique.setLogic(new SinglePassAndImageBasedLightingLogic(technique));
+                break;
             default:
                 throw new UnsupportedOperationException();
         }

+ 6 - 0
jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java

@@ -296,6 +296,12 @@ public class DDSLoader implements AssetLoader {
                     // exit here, the rest of the structure is not valid
                     // the real format will be available in the DX10 header
                     return;
+                    
+                case 113:
+                    compressed = false;
+                    bpp = 64;
+                    pixelFormat = Image.Format.RGBA16F;
+                    break;
                 default:
                     throw new IOException("Unknown fourcc: " + string(fourcc) + ", " + Integer.toHexString(fourcc));
             }

+ 354 - 0
jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXLoader.java

@@ -0,0 +1,354 @@
+/*
+ * 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.texture.plugins.ktx;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetLoader;
+import com.jme3.asset.TextureKey;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.opengl.GLImageFormat;
+import com.jme3.renderer.opengl.GLImageFormats;
+import com.jme3.texture.Image;
+import com.jme3.texture.image.ColorSpace;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.LittleEndien;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * A KTX file loader
+ * KTX file format is an image container defined by the Kronos group
+ * See specs here https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
+ * 
+ * This loader doesn't support compressed files yet.
+ * 
+ * @author Nehon
+ */
+public class KTXLoader implements AssetLoader {
+    
+    private final static Logger log = Logger.getLogger(KTXLoader.class.getName());
+ 
+    private final static byte[] fileIdentifier = {
+        (byte) 0xAB, (byte) 0x4B, (byte) 0x54, (byte) 0x58, (byte) 0x20, (byte) 0x31, (byte) 0x31, (byte) 0xBB, (byte) 0x0D, (byte) 0x0A, (byte) 0x1A, (byte) 0x0A
+    };
+    private boolean slicesInside = false;
+
+    @Override
+    public Object load(AssetInfo info) throws IOException {
+        if (!(info.getKey() instanceof TextureKey)) {
+            throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey");
+        }
+
+        InputStream in = null;
+        try {
+            in = info.openStream();
+            Image img = load(in);
+            return img;
+        } finally {
+            if (in != null) {
+                in.close();
+            }
+        }
+    }
+
+    private Image load(InputStream stream) {
+
+        byte[] fileId = new byte[12];
+
+        DataInput in = new DataInputStream(stream);
+        try {
+            stream.read(fileId, 0, 12);
+            if (!checkFileIdentifier(fileId)) {
+                throw new IllegalArgumentException("Unrecognized ktx file identifier : " + new String(fileId) + " should be " + new String(fileIdentifier));
+            }
+
+            int endianness = in.readInt();
+            //opposite endianness
+            if (endianness == 0x01020304) {
+                in = new LittleEndien(stream);
+            }
+            int glType = in.readInt();
+            int glTypeSize = in.readInt();
+            int glFormat = in.readInt();
+            int glInternalFormat = in.readInt();
+            int glBaseInternalFormat = in.readInt();
+            int pixelWidth = in.readInt();
+            int pixelHeight = in.readInt();
+            int pixelDepth = in.readInt();
+            int numberOfArrayElements = in.readInt();
+            int numberOfFaces = in.readInt();
+            int numberOfMipmapLevels = in.readInt();
+            int bytesOfKeyValueData = in.readInt();
+
+            log.log(Level.FINE, "glType = {0}", glType);
+            log.log(Level.FINE, "glTypeSize = {0}", glTypeSize);
+            log.log(Level.FINE, "glFormat = {0}", glFormat);
+            log.log(Level.FINE, "glInternalFormat = {0}", glInternalFormat);
+            log.log(Level.FINE, "glBaseInternalFormat = {0}", glBaseInternalFormat);
+            log.log(Level.FINE, "pixelWidth = {0}", pixelWidth);
+            log.log(Level.FINE, "pixelHeight = {0}", pixelHeight);
+            log.log(Level.FINE, "pixelDepth = {0}", pixelDepth);
+            log.log(Level.FINE, "numberOfArrayElements = {0}", numberOfArrayElements);
+            log.log(Level.FINE, "numberOfFaces = {0}", numberOfFaces);
+            log.log(Level.FINE, "numberOfMipmapLevels = {0}", numberOfMipmapLevels);
+            log.log(Level.FINE, "bytesOfKeyValueData = {0}", bytesOfKeyValueData);
+            
+            if((numberOfFaces >1 && pixelDepth >1) || (numberOfFaces >1 && numberOfArrayElements >1) ||  (pixelDepth >1 && numberOfArrayElements >1)){
+                throw new UnsupportedOperationException("jME doesn't support cube maps of 3D textures or arrays of 3D texture or arrays of cube map of 3d textures");
+            }
+            
+
+            PixelReader pixelReader = parseMetaData(bytesOfKeyValueData, in);
+            if (pixelReader == null){
+                pixelReader = new SrTuRoPixelReader(); 
+            }
+            
+            //some of the values may be 0 we need them at least to be 1
+            pixelDepth = Math.max(1, pixelDepth);
+            numberOfArrayElements = Math.max(1, numberOfArrayElements);
+            numberOfFaces = Math.max(1, numberOfFaces);
+            numberOfMipmapLevels = Math.max(1, numberOfMipmapLevels);
+            
+            int nbSlices = Math.max(numberOfFaces,numberOfArrayElements);
+
+            Image.Format imgFormat = getImageFormat(glFormat, glInternalFormat, glType);
+            log.log(Level.FINE, "img format {0}", imgFormat.toString());
+            
+           
+            int bytePerPixel = imgFormat.getBitsPerPixel() / 8;            
+            int byteBuffersSize = computeBuffersSize(numberOfMipmapLevels, pixelWidth, pixelHeight, bytePerPixel, pixelDepth);
+            log.log(Level.FINE, "data size {0}", byteBuffersSize);
+            
+            int[] mipMapSizes = new int[numberOfMipmapLevels];
+            
+            Image image = createImage(nbSlices, byteBuffersSize, imgFormat, pixelWidth, pixelHeight, pixelDepth);
+            
+            byte[] pixelData = new byte[bytePerPixel];            
+            
+            int offset = 0;
+            //iterate over data
+            for (int mipLevel = 0; mipLevel < numberOfMipmapLevels; mipLevel++) {
+                //size of the image in byte.
+                //this value is bogus in many example, when using mipmaps.
+                //instead we compute the theorical size and display a warning when it does not match.
+                int fileImageSize = in.readInt();
+                
+                int width = Math.max(1, pixelWidth >> mipLevel);
+                int height = Math.max(1, pixelHeight >> mipLevel);
+               
+                int imageSize = width * height * bytePerPixel;
+                mipMapSizes[mipLevel] = imageSize;
+                log.log(Level.FINE, "current mip size {0}", imageSize);
+                if(fileImageSize != imageSize){
+                    log.log(Level.WARNING, "Mip map size is wrong in the file for mip level {0} size is {1} should be {2}", new Object[]{mipLevel, fileImageSize, imageSize});
+                }
+                
+                for (int arrayElem = 0; arrayElem < numberOfArrayElements; arrayElem++) {
+                    for (int face = 0; face < numberOfFaces; face++) {
+                        int nbPixelRead = 0;
+                        for (int depth = 0; depth < pixelDepth; depth++) {
+                            ByteBuffer byteBuffer = image.getData(getSlice(face, arrayElem));    
+
+                            log.log(Level.FINE, "position {0}", byteBuffer.position());
+                            byteBuffer.position(offset);                                                        
+                            nbPixelRead = pixelReader.readPixels(width, height, pixelData, byteBuffer, in);
+                        }
+                        //cube padding
+                        if (numberOfFaces == 6 && numberOfArrayElements == 0) {
+                            in.skipBytes(3 - ((nbPixelRead + 3) % 4));
+                        }
+                    }
+                }
+                //mip padding
+                log.log(Level.FINE, "skipping {0}", (3 - ((imageSize + 3) % 4)));
+                in.skipBytes(3 - ((imageSize + 3) % 4));
+                offset+=imageSize;
+            }
+            //there are loaded mip maps we set the sizes
+            if(numberOfMipmapLevels >1){
+                image.setMipMapSizes(mipMapSizes);
+            }
+            //if 3D texture and slices' orientation is inside, we reverse the data array.
+            if(pixelDepth > 1 && slicesInside){
+                Collections.reverse(image.getData());
+            }
+            return image;
+
+        } catch (IOException ex) {
+            Logger.getLogger(KTXLoader.class.getName()).log(Level.SEVERE, null, ex);
+        }
+        return null;
+    }
+
+    /**
+     * returns the slice from the face and the array index
+     * @param face the face
+     * @param arrayElem the array index
+     * @return 
+     */
+    private static int getSlice(int face, int arrayElem) {
+        return Math.max(face, arrayElem);
+    }
+
+    /**
+     * Computes a buffer size from given parameters
+     * @param numberOfMipmapLevels 
+     * @param pixelWidth
+     * @param pixelHeight
+     * @param bytePerPixel
+     * @param pixelDepth
+     * @return 
+     */
+    private int computeBuffersSize(int numberOfMipmapLevels, int pixelWidth, int pixelHeight, int bytePerPixel, int pixelDepth) {
+        int byteBuffersSize = 0;
+        for (int mipLevel = 0; mipLevel < numberOfMipmapLevels; mipLevel++) {
+            int width = Math.max(1, pixelWidth >> mipLevel);
+            int height = Math.max(1, pixelHeight >> mipLevel);
+            byteBuffersSize += width * height * bytePerPixel;
+            log.log(Level.FINE, "mip level size : {0} : {1}", new Object[]{mipLevel, width * height * bytePerPixel});
+        }
+        return byteBuffersSize * pixelDepth;
+    }
+
+    /**
+     * Create an image with given parameters
+     * @param nbSlices
+     * @param byteBuffersSize
+     * @param imgFormat
+     * @param pixelWidth
+     * @param pixelHeight
+     * @param depth
+     * @return 
+     */
+    private Image createImage(int nbSlices, int byteBuffersSize, Image.Format imgFormat, int pixelWidth, int pixelHeight, int depth) {
+        ArrayList<ByteBuffer> imageData = new ArrayList<ByteBuffer>(nbSlices);
+        for (int i = 0; i < nbSlices; i++) {
+            imageData.add(BufferUtils.createByteBuffer(byteBuffersSize));
+        }
+        Image image = new Image(imgFormat, pixelWidth, pixelHeight, depth, imageData, ColorSpace.sRGB);
+        return image;
+    }
+
+    /**
+     * Parse the file metaData to select the PixelReader that suits the file 
+     * coordinates orientation
+     * @param bytesOfKeyValueData
+     * @param in
+     * @return
+     * @throws IOException 
+     */
+    private PixelReader parseMetaData(int bytesOfKeyValueData, DataInput in) throws IOException {
+        PixelReader pixelReader = null;
+        for (int i = 0; i < bytesOfKeyValueData;) {
+            //reading key values
+            int keyAndValueByteSize = in.readInt();            
+            byte[] keyValue = new byte[keyAndValueByteSize];
+            in.readFully(keyValue);
+            
+            
+            //parsing key values
+            String[] kv = new String(keyValue).split("\0");            
+            for (int j = 0; j < kv.length; j += 2) {
+                System.err.println("key : " + kv[j]);
+                System.err.println("value : " + kv[j + 1]);
+                if(kv[j].equalsIgnoreCase("KTXorientation")){
+                    if(kv[j + 1].startsWith("S=r,T=d") ){
+                        pixelReader = new SrTdRiPixelReader();
+                    }else{
+                        pixelReader = new SrTuRoPixelReader();
+                    }
+                    if(kv[j + 1].contains("R=i")){
+                        slicesInside = true;
+                    }
+                }
+            }
+            
+            //padding
+            int padding = 3 - ((keyAndValueByteSize + 3) % 4);            
+            if (padding > 0) {
+                in.skipBytes(padding);
+            }
+            i += 4 + keyAndValueByteSize + padding;
+        }
+        return pixelReader;
+    }
+
+    /**
+     * Chacks the file id
+     * @param b
+     * @return 
+     */
+    private boolean checkFileIdentifier(byte[] b) {
+        boolean check = true;
+        for (int i = 0; i < 12; i++) {
+            if (b[i] != fileIdentifier[i]) {
+                check = false;
+            }
+        }
+        return check;
+    }
+
+    /**
+     * returns the JME image format from gl formats and types.
+     * @param glFormat
+     * @param glInternalFormat
+     * @param glType
+     * @return 
+     */
+    private Image.Format getImageFormat(int glFormat, int glInternalFormat, int glType) {
+        EnumSet<Caps> caps = EnumSet.allOf(Caps.class);
+        GLImageFormat[][] formats = GLImageFormats.getFormatsForCaps(caps);
+        for (GLImageFormat[] format : formats) {
+            for (int j = 0; j < format.length; j++) {
+                GLImageFormat glImgFormat = format[j];
+                if (glImgFormat != null) {
+                    if (glImgFormat.format == glFormat && glImgFormat.dataType == glType) {
+                        if (glFormat == glInternalFormat || glImgFormat.internalFormat == glInternalFormat) {
+                            return Image.Format.values()[j];
+                        }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+}

+ 279 - 0
jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXWriter.java

@@ -0,0 +1,279 @@
+/*
+ * 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.texture.plugins.ktx;
+
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.opengl.GLImageFormat;
+import com.jme3.renderer.opengl.GLImageFormats;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.Texture3D;
+import com.jme3.texture.TextureArray;
+import com.jme3.texture.TextureCubeMap;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * This class allows one to write a KTX file.
+ * It doesn't support compressed data yet.
+ * 
+ * @author Nehon
+ */
+public class KTXWriter {
+
+    private final static Logger log = Logger.getLogger(KTXWriter.class.getName());
+    
+    private final String filePath;
+
+    private final static byte[] fileIdentifier = {
+        (byte) 0xAB, (byte) 0x4B, (byte) 0x54, (byte) 0x58, (byte) 0x20, (byte) 0x31, (byte) 0x31, (byte) 0xBB, (byte) 0x0D, (byte) 0x0A, (byte) 0x1A, (byte) 0x0A
+    };
+
+    /**
+     * Creates a KTXWriter that will write files in the given path
+     * @param path 
+     */
+    public KTXWriter(String path) {
+        filePath = path;
+    }
+
+    /**
+     * Writes a 2D image from the given image in a KTX file named from the fileName param
+     * Note that the fileName should contain the extension (.ktx sounds like a wise choice)
+     * @param image the image to write
+     * @param fileName the name of the file to write
+     */
+    public void write(Image image, String fileName) {
+        write(image, Texture2D.class, fileName);
+    }
+
+    /**
+     * Writes an image with the given params
+     * 
+     * textureType, allows one to write textureArrays, Texture3D, and TextureCubeMaps.
+     * Texture2D will write a 2D image.
+     * Note that the fileName should contain the extension (.ktx sounds like a wise choice)
+     * @param image the image to write
+     * @param textureType the texture type
+     * @param fileName the name of the file to write
+     */
+    public void write(Image image, Class<? extends Texture> textureType, String fileName) {
+
+        FileOutputStream outs = null;
+        try {
+            File file = new File(filePath + "/" + fileName);            
+            outs = new FileOutputStream(file);
+
+            DataOutput out = new DataOutputStream(outs);
+
+            //fileID
+            out.write(fileIdentifier);
+            //endianness
+            out.writeInt(0x04030201);
+            GLImageFormat format = getGlFormat(image.getFormat());
+            //glType
+            out.writeInt(format.dataType);
+            //glTypeSize
+            out.writeInt(1);
+            //glFormat
+            out.writeInt(format.format);
+            //glInernalFormat
+            out.writeInt(format.internalFormat);
+            //glBaseInternalFormat
+            out.writeInt(format.format);
+            //pixelWidth
+            out.writeInt(image.getWidth());
+            //pixelHeight
+            out.writeInt(image.getHeight());
+
+            int pixelDepth = 1;
+            int numberOfArrayElements = 1;
+            int numberOfFaces = 1;
+            if (image.getDepth() > 1) {
+                //pixelDepth
+                if (textureType == Texture3D.class) {
+                    pixelDepth = image.getDepth();
+                }
+            }
+            if(image.getData().size()>1){
+                //numberOfArrayElements
+                if (textureType == TextureArray.class) {
+                    numberOfArrayElements = image.getData().size();
+                }
+                //numberOfFaces                
+                if (textureType == TextureCubeMap.class) {
+                    numberOfFaces = image.getData().size();
+                }
+            }
+            out.writeInt(pixelDepth);
+            out.writeInt(numberOfArrayElements);
+            out.writeInt(numberOfFaces);
+
+            int numberOfMipmapLevels = 1;
+            //numberOfMipmapLevels
+            if (image.hasMipmaps()) {
+                numberOfMipmapLevels = image.getMipMapSizes().length;
+            }
+            out.writeInt(numberOfMipmapLevels);
+
+            //bytesOfKeyValueData
+            String keyValues = "KTXorientation\0S=r,T=u\0";
+            int bytesOfKeyValueData = keyValues.length() + 4;
+            int padding = 3 - ((bytesOfKeyValueData + 3) % 4);
+            bytesOfKeyValueData += padding;
+            out.writeInt(bytesOfKeyValueData);
+
+            //keyAndValueByteSize
+            out.writeInt(bytesOfKeyValueData - 4 - padding);
+            //values
+            out.writeBytes(keyValues);
+            pad(padding, out);
+
+            int offset = 0;
+            //iterate over data
+            for (int mipLevel = 0; mipLevel < numberOfMipmapLevels; mipLevel++) {
+
+                int width = Math.max(1, image.getWidth() >> mipLevel);
+                int height = Math.max(1, image.getHeight() >> mipLevel);
+                
+                int imageSize;
+
+                if (image.hasMipmaps()) {
+                    imageSize = image.getMipMapSizes()[mipLevel];
+                } else {
+                    imageSize = width * height * image.getFormat().getBitsPerPixel() / 8;
+                }
+                out.writeInt(imageSize);
+
+                for (int arrayElem = 0; arrayElem < numberOfArrayElements; arrayElem++) {
+                    for (int face = 0; face < numberOfFaces; face++) {
+                        int nbPixelWritten = 0;
+                        for (int depth = 0; depth < pixelDepth; depth++) {
+                            ByteBuffer byteBuffer = image.getData(getSlice(face, arrayElem));
+                            // BufferUtils.ensureLargeEnough(byteBuffer, imageSize);
+                            log.log(Level.FINE, "position {0}", byteBuffer.position());
+                            byteBuffer.position(offset);
+                            byte[] b = getByteBufferArray(byteBuffer, imageSize);
+                            out.write(b);
+
+                            nbPixelWritten = b.length;
+                        }
+                        //cube padding
+                        if (numberOfFaces == 6 && numberOfArrayElements == 0) {
+                            padding = 3 - ((nbPixelWritten + 3) % 4);
+                            pad(padding, out);
+                        }
+                    }
+                }
+                //mip padding
+                log.log(Level.FINE, "skipping {0}", (3 - ((imageSize + 3) % 4)));
+                padding = 3 - ((imageSize + 3) % 4);
+                pad(padding, out);
+                offset += imageSize;
+            }
+
+        } catch (FileNotFoundException ex) {
+            Logger.getLogger(KTXWriter.class.getName()).log(Level.SEVERE, null, ex);
+        } catch (IOException ex) {
+            Logger.getLogger(KTXWriter.class.getName()).log(Level.SEVERE, null, ex);
+        } finally {
+            try {
+                if(outs != null){
+                    outs.close();
+                }
+            } catch (IOException ex) {
+                Logger.getLogger(KTXWriter.class.getName()).log(Level.SEVERE, null, ex);
+            }
+        }
+    }
+
+    /**
+     * writes padding data to the output padding times.
+     * @param padding
+     * @param out
+     * @throws IOException 
+     */
+    private void pad(int padding, DataOutput out) throws IOException {
+        //padding
+        for (int i = 0; i < padding; i++) {
+            out.write('\0');
+        }
+    }
+
+    /**
+     * Get a byte array from a byte buffer.
+     * @param byteBuffer the  byte buffer
+     * @param size the size of the resulting array
+     * @return 
+     */
+    private byte[] getByteBufferArray(ByteBuffer byteBuffer, int size) {
+        byte[] b;
+        if (byteBuffer.hasArray()) {
+            b = byteBuffer.array();
+        } else {
+            b = new byte[size];
+            byteBuffer.get(b, 0, size);
+        }
+        return b;
+    }
+
+    /**
+     * get the glformat from JME image Format
+     * @param format
+     * @return 
+     */
+    private GLImageFormat getGlFormat(Image.Format format) {
+        EnumSet<Caps> caps = EnumSet.allOf(Caps.class);
+        GLImageFormat[][] formats = GLImageFormats.getFormatsForCaps(caps);
+        return formats[0][format.ordinal()];
+    }
+
+    /**
+     * get a slice from the face and the array index
+     * @param face
+     * @param arrayElem
+     * @return 
+     */
+    private static int getSlice(int face,int arrayElem) {
+        return Math.max(face,  arrayElem);
+    }
+}

+ 46 - 0
jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/PixelReader.java

@@ -0,0 +1,46 @@
+/*
+ * 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.texture.plugins.ktx;
+
+import java.io.DataInput;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ *
+ * Interface used to read a set of pixels in a KTX file
+ * @author Nehon
+ */
+public interface PixelReader {
+
+    public int readPixels(int pixelWidth, int pixelHeight, byte[] pixelData, ByteBuffer buffer, DataInput in) throws IOException;
+}

+ 60 - 0
jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTdRiPixelReader.java

@@ -0,0 +1,60 @@
+/*
+ * 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.texture.plugins.ktx;
+
+import java.io.DataInput;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * reads the pixels of an image whose origin is the top left corner
+ *
+ * @author Nehon
+ */
+public class SrTdRiPixelReader implements PixelReader {
+
+    @Override
+    public int readPixels(int pixelWidth, int pixelHeight, byte[] pixelData, ByteBuffer buffer, DataInput in) throws IOException {
+        int pixelRead = 0;
+        for (int row = pixelHeight - 1; row >= 0; row--) {
+            for (int pixel = 0; pixel < pixelWidth; pixel++) {
+                in.readFully(pixelData);
+                for (int i = 0; i < pixelData.length; i++) {
+                    buffer.put((row * pixelWidth + pixel) * pixelData.length + i, pixelData[i]);
+                }
+                pixelRead += pixelData.length;
+            }
+        }
+        return pixelRead;
+    }
+
+}

+ 58 - 0
jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/SrTuRoPixelReader.java

@@ -0,0 +1,58 @@
+/*
+ * 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.texture.plugins.ktx;
+
+import java.io.DataInput;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * reads the pixels of an image whose origin is the bottom left corner
+ *
+ * @author Nehon
+ */
+public class SrTuRoPixelReader implements PixelReader {
+
+    @Override
+    public int readPixels(int pixelWidth, int pixelHeight, byte[] pixelData, ByteBuffer buffer, DataInput in) throws IOException {
+        int pixelRead = 0;
+        for (int row = 0; row < pixelHeight; row++) {
+            for (int pixel = 0; pixel < pixelWidth; pixel++) {
+                in.readFully(pixelData);
+                buffer.put(pixelData);
+                pixelRead += pixelData.length;
+            }
+        }
+        return pixelRead;
+    }
+
+}

+ 1 - 1
jme3-examples/src/main/java/jme3test/batching/TestBatchNodeCluster.java

@@ -60,7 +60,7 @@ public class TestBatchNodeCluster extends SimpleApplication {
         settingst.setVSync(false);
         settingst.setFullscreen(false);
         app.setSettings(settingst);
-        app.setShowSettings(false);
+        app.setShowSettings(false); 
         app.start();
     }
     private ActionListener al = new ActionListener() {

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

@@ -0,0 +1,90 @@
+package jme3test.light;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.ChaseCamera;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.light.DirectionalLight;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.shadow.DirectionalLightShadowFilter;
+import com.jme3.shadow.DirectionalLightShadowRenderer;
+ 
+public class TestColorApp extends SimpleApplication {
+    public static void main(String[] args) {
+        TestColorApp app = new TestColorApp();
+        app.start();
+    }
+ 
+    @Override
+    public void simpleInitApp() {
+        // Lights
+        DirectionalLight sun = new DirectionalLight();
+        Vector3f sunPosition = new Vector3f(1, -1, 1);
+        sun.setDirection(sunPosition);
+        sun.setColor(new ColorRGBA(1f,1f,1f,1f));
+        rootNode.addLight(sun);
+ 
+        //DirectionalLightShadowFilter sun_renderer = new DirectionalLightShadowFilter(assetManager, 2048, 4);
+        DirectionalLightShadowRenderer sun_renderer = new DirectionalLightShadowRenderer(assetManager, 2048, 1);
+        sun_renderer.setLight(sun);
+        viewPort.addProcessor(sun_renderer);
+        
+//        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
+//        fpp.addFilter(sun_renderer);
+//        viewPort.addProcessor(fpp);
+        
+        rootNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
+ 
+        // Camera
+        viewPort.setBackgroundColor(new ColorRGBA(.6f, .6f, .6f, 1f));
+        ChaseCamera chaseCam = new ChaseCamera(cam, inputManager);
+ 
+ 
+        // Objects
+        // Ground Object
+        final Geometry groundBoxWhite = new Geometry("Box", new Box(7.5f, 7.5f, .25f));
+        float[] f = {-FastMath.PI / 2, 3 * FastMath.PI / 2, 0f};
+        groundBoxWhite.setLocalRotation(new Quaternion(f));
+        groundBoxWhite.move(7.5f, -.75f, 7.5f);
+        final Material groundMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        groundMaterial.setColor("Diffuse", new ColorRGBA(.9f, .9f, .9f, .9f));
+        groundBoxWhite.setMaterial(groundMaterial);
+        groundBoxWhite.addControl(chaseCam);
+        rootNode.attachChild(groundBoxWhite);
+ 
+        // Planter
+        Geometry planterBox = new Geometry("Box", new Box(.5f, .5f, .5f));
+        final Material planterMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        planterMaterial.setTexture("DiffuseMap", assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
+        planterBox.setMaterial(groundMaterial);
+        planterBox.setLocalTranslation(10, 0, 9);
+        rootNode.attachChild(planterBox);
+ 
+        // Action!
+        inputManager.addMapping("on", new KeyTrigger(KeyInput.KEY_Z));
+        inputManager.addMapping("off", new KeyTrigger(KeyInput.KEY_X));
+ 
+        inputManager.addListener(new AnalogListener() {
+            @Override
+            public void onAnalog(String s, float v, float v1) {
+                if (s.equals("on")) {
+                    groundBoxWhite.setMaterial(planterMaterial);
+                }
+                if (s.equals("off")) {
+                    groundBoxWhite.setMaterial(groundMaterial);
+                }
+            }
+        }, "on", "off");
+ 
+        inputEnabled = true;
+    }
+}

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

@@ -0,0 +1,131 @@
+/*
+ * 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 jme3test.light;
+
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.PointLight;
+import com.jme3.light.SpotLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+import com.jme3.shadow.EdgeFilteringMode;
+import com.jme3.shadow.PointLightShadowRenderer;
+import com.jme3.shadow.SpotLightShadowRenderer;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+
+
+public class TestShadowBug extends SimpleApplication {
+  public static void main(String[] args) {
+    TestShadowBug app = new TestShadowBug();
+    app.start();
+  }
+
+  @Override
+  public void simpleInitApp() {
+    flyCam.setMoveSpeed(100f);
+    rootNode.attachChild(makeFloor());
+
+    Node characters = new Node("Characters");
+    characters.setShadowMode(ShadowMode.Cast);
+    rootNode.attachChild(characters);
+
+    Spatial golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml");
+    golem.scale(0.5f);
+    golem.setLocalTranslation(200.0f, -6f, 200f);
+    golem.setShadowMode(ShadowMode.CastAndReceive);
+    characters.attachChild(golem);
+
+    DirectionalLight sun = new DirectionalLight();
+    sun.setDirection(new Vector3f(-1f, -1f, 1f));
+    sun.setColor(ColorRGBA.White.mult(1.3f));
+    rootNode.addLight(sun);
+    characters.addLight(sun);
+
+    SpotLight spot = new SpotLight();
+    spot.setSpotRange(13f);                           // distance
+    spot.setSpotInnerAngle(15f * FastMath.DEG_TO_RAD); // inner light cone (central beam)
+    spot.setSpotOuterAngle(20f * FastMath.DEG_TO_RAD); // outer light cone (edge of the light)
+    spot.setColor(ColorRGBA.White.mult(1.3f));         // light color
+    spot.setPosition(new Vector3f(192.0f, -1f, 192f));
+    spot.setDirection(new Vector3f(1, -0.5f, 1));
+    rootNode.addLight(spot);
+
+    PointLight lamp_light = new PointLight();
+    lamp_light.setColor(ColorRGBA.Yellow);
+    lamp_light.setRadius(20f);
+    lamp_light.setPosition(new Vector3f(210.0f, 0f, 210f));
+    rootNode.addLight(lamp_light);
+
+    SpotLightShadowRenderer slsr = new SpotLightShadowRenderer(assetManager, 512);
+    slsr.setLight(spot);
+    slsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
+    slsr.setShadowIntensity(0.6f);
+    slsr.setFlushQueues(false);
+    viewPort.addProcessor(slsr);
+
+    PointLightShadowRenderer plsr = new PointLightShadowRenderer(assetManager, 512);
+    plsr.setLight(lamp_light);
+    plsr.setShadowIntensity(0.6f);
+    plsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest);
+    plsr.setFlushQueues(false);
+    viewPort.addProcessor(plsr);
+
+    viewPort.getCamera().setLocation(new Vector3f(192.0f, 10f, 192f));
+    float[] angles = new float[]{3.14f/2, 3.14f/2, 0};
+    viewPort.getCamera().setRotation(new Quaternion(angles));
+  }
+
+  protected Geometry makeFloor() {
+    Box box = new Box(220, .2f, 220);
+    box.scaleTextureCoordinates(new Vector2f(10, 10));
+    Geometry floor = new Geometry("the Floor", box);
+    floor.setLocalTranslation(200, -9, 200);
+    Material matGroundL = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+    Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+    grass.setWrap(WrapMode.Repeat);
+    matGroundL.setTexture("DiffuseMap", grass);
+    floor.setMaterial(matGroundL);
+    floor.setShadowMode(ShadowMode.CastAndReceive);
+    return floor;
+  }
+}

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

@@ -0,0 +1,96 @@
+/*
+ * 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 jme3test.light;
+
+import com.jme3.app.ChaseCameraAppState;
+import com.jme3.app.SimpleApplication;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Box;
+import com.jme3.util.TangentBinormalGenerator;
+
+/**
+ *
+ * @author Nehon
+ */
+public class TestTangentCube extends SimpleApplication {
+
+    public static void main(String... args) {
+        TestTangentCube app = new TestTangentCube();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        Box aBox = new Box(1, 1, 1);
+        Geometry aGeometry = new Geometry("Box", aBox);
+        TangentBinormalGenerator.generate(aBox);
+
+        Material aMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+        aMaterial.setTexture("DiffuseMap",
+                assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
+        aMaterial.setTexture("NormalMap",
+                assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall_normal.jpg"));
+        aMaterial.setBoolean("UseMaterialColors", false);
+        aMaterial.setColor("Diffuse", ColorRGBA.White);
+        aMaterial.setColor("Specular", ColorRGBA.White);
+        aMaterial.setFloat("Shininess", 64f);
+        aGeometry.setMaterial(aMaterial);
+
+        // Rotate 45 degrees to see multiple faces
+        aGeometry.rotate(FastMath.QUARTER_PI, FastMath.QUARTER_PI, 0.0f);
+        rootNode.attachChild(aGeometry);
+
+        /**
+         * Must add a light to make the lit object visible!
+         */
+        PointLight aLight = new PointLight();
+        aLight.setPosition(new Vector3f(0, 3, 3));
+        aLight.setColor(ColorRGBA.Red);
+        rootNode.addLight(aLight);
+//
+//        AmbientLight bLight = new AmbientLight();
+//        bLight.setColor(ColorRGBA.Gray);
+//        rootNode.addLight(bLight);
+
+        
+        ChaseCameraAppState chaser = new ChaseCameraAppState();
+        chaser.setTarget(aGeometry);
+        getStateManager().attach(chaser);
+    }
+
+}

+ 71 - 0
jme3-examples/src/main/java/jme3test/light/pbr/ConsoleProgressReporter.java

@@ -0,0 +1,71 @@
+/*
+ * 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 jme3test.light.pbr;
+
+import com.jme3.environment.generation.JobProgressAdapter;
+import com.jme3.light.LightProbe;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A basic logger for environment map rendering progress.
+ * @author nehon
+ */
+public class ConsoleProgressReporter extends JobProgressAdapter<LightProbe>{
+
+    private static final Logger logger = Logger.getLogger(ConsoleProgressReporter.class.getName());
+    
+    long time;
+
+    @Override
+    public void start() {
+        time = System.currentTimeMillis();
+        logger.log(Level.INFO,"Starting generation");
+    }
+
+    @Override
+    public void progress(double value) {       
+        logger.log(Level.INFO, "Progress : {0}%", (value * 100));
+    }
+
+    @Override
+    public void step(String message) {       
+        logger.info(message);
+    }
+    
+    @Override
+    public void done(LightProbe result) {
+        long end = System.currentTimeMillis();
+        logger.log(Level.INFO, "Generation done in {0}", ((float)(end - time) / 1000f));
+    }
+    
+}

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

@@ -0,0 +1,141 @@
+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;
+import com.jme3.environment.util.EnvMapUtils;
+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.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.ui.Picture;
+import com.jme3.util.MaterialDebugAppState;
+
+/**
+ * test
+ *
+ * @author nehon
+ */
+public class RefEnv extends SimpleApplication {
+
+    private Node tex;
+    private Node ref;
+    private Picture refDE;
+    private Picture refM;
+
+    public static void main(String[] args) {
+        RefEnv app = new RefEnv();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+
+        cam.setLocation(new Vector3f(-2.3324413f, 2.9567573f, 4.6054406f));
+        cam.setRotation(new Quaternion(0.06310794f, 0.9321281f, -0.29613864f, 0.1986369f));
+        Spatial sc = assetManager.loadModel("Scenes/PBR/spheres.j3o");
+        rootNode.attachChild(sc);
+        rootNode.getChild("Scene").setCullHint(Spatial.CullHint.Always);
+
+        ref = new Node("reference pictures");
+        refDE = new Picture("refDE");
+        refDE.setHeight(cam.getHeight());
+        refDE.setWidth(cam.getWidth());
+        refDE.setImage(assetManager,"jme3test/light/pbr/spheresRefDE.png", false);
+        refM = new Picture("refM");
+        refM.setImage(assetManager,"jme3test/light/pbr/spheresRefM.png", false);
+        refM.setHeight(cam.getHeight());
+        refM.setWidth(cam.getWidth());
+
+        ref.attachChild(refDE);
+
+        stateManager.attach(new EnvironmentCamera());
+
+        inputManager.addMapping("tex", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addMapping("switch", new KeyTrigger(KeyInput.KEY_RETURN));
+        inputManager.addMapping("ref", new KeyTrigger(KeyInput.KEY_R));
+        inputManager.addListener(new ActionListener() {
+
+            @Override
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if (name.equals("tex") && isPressed) {
+                    if (tex == null) {
+                        return;
+                    }
+                    if (tex.getParent() == null) {
+                        guiNode.attachChild(tex);
+                    } else {
+                        tex.removeFromParent();
+                    }
+                }
+
+                if (name.equals("switch") && isPressed) {
+                    switchMat(rootNode.getChild("Scene"));
+                }
+                if (name.equals("ref") && isPressed) {
+                    if (ref.getParent() == null) {
+                        guiNode.attachChild(ref);
+                    } else {
+                        ref.removeFromParent();
+                    }
+                }
+            }
+        }, "tex", "switch", "ref");
+
+    }
+
+    private void switchMat(Spatial s) {
+        if (s instanceof Node) {
+            Node n = (Node) s;
+            for (Spatial children : n.getChildren()) {
+                switchMat(children);
+            }
+        } else if (s instanceof Geometry) {
+            Geometry g = (Geometry) s;
+            Material mat = g.getMaterial();
+            if (((Float) mat.getParam("Metallic").getValue()) == 1f) {
+                mat.setFloat("Metallic", 0);
+                mat.setColor("BaseColor", ColorRGBA.Black);
+                ref.attachChild(refDE);
+                refM.removeFromParent();
+            } else {
+                mat.setFloat("Metallic", 1);
+                mat.setColor("BaseColor", ColorRGBA.White);
+                ref.attachChild(refM);
+                refDE.removeFromParent();
+            }
+        }
+    }
+
+    private int frame = 0;
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        frame++;
+
+        if (frame == 2) {
+            final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter<LightProbe>() {
+
+                @Override
+                public void done(LightProbe result) {
+                    System.err.println("Done rendering env maps");
+                    tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager);
+                  //  guiNode.attachChild(tex);
+                    rootNode.getChild("Scene").setCullHint(Spatial.CullHint.Dynamic);
+                }
+            });
+            ((BoundingSphere) probe.getBounds()).setRadius(100);
+            rootNode.addLight(probe);
+
+        }
+    }
+}

+ 199 - 0
jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java

@@ -0,0 +1,199 @@
+/*
+ * 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 jme3test.light.pbr;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.light.LightProbe;
+import com.jme3.environment.LightProbeFactory;
+import com.jme3.environment.EnvironmentCamera;
+import com.jme3.environment.generation.JobProgressAdapter;
+import com.jme3.environment.util.LightsDebugState;
+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.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.FXAAFilter;
+import com.jme3.post.filters.ToneMapFilter;
+import com.jme3.post.ssao.SSAOFilter;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.plugins.ktx.KTXLoader;
+import com.jme3.util.MaterialDebugAppState;
+import com.jme3.util.SkyFactory;
+import com.jme3.util.mikktspace.MikktspaceTangentGenerator;
+
+/**
+ * A test case for PBR lighting.
+ * Still experimental.
+ *
+ * @author nehon
+ */
+public class TestPBRLighting extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestPBRLighting app = new TestPBRLighting();
+        app.start();
+    }
+    private Geometry model;
+    private DirectionalLight dl;
+    private Node modelNode;
+    private int frame = 0;   
+    private Material pbrMat;    
+
+    @Override
+    public void simpleInitApp() {
+        assetManager.registerLoader(KTXLoader.class, "ktx");
+
+        viewPort.setBackgroundColor(ColorRGBA.White);
+        modelNode = (Node) new Node("modelNode");
+        model = (Geometry) assetManager.loadModel("Models/Tank/tank.j3o");
+        MikktspaceTangentGenerator.generate(model);
+        modelNode.attachChild(model);
+
+        dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+        rootNode.addLight(dl);
+        dl.setColor(ColorRGBA.White);
+        rootNode.attachChild(modelNode);
+
+      
+        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
+//        fpp.addFilter(new FXAAFilter());
+        fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(4.0f)));
+//        fpp.addFilter(new SSAOFilter(0.5f, 3, 0.2f, 0.2f));
+        viewPort.addProcessor(fpp);
+
+        //Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Sky_Cloudy.hdr", SkyFactory.EnvMapType.EquirectMap);
+        Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap);
+        //Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", SkyFactory.EnvMapType.CubeMap);
+        //Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/road.hdr", SkyFactory.EnvMapType.EquirectMap);
+        rootNode.attachChild(sky);
+
+        pbrMat = assetManager.loadMaterial("Models/Tank/tank.j3m");
+        model.setMaterial(pbrMat);
+
+
+        final EnvironmentCamera envCam = new EnvironmentCamera(128, new Vector3f(0, 3f, 0));
+        stateManager.attach(envCam);
+        
+//        EnvironmentManager envManager = new EnvironmentManager();
+//        stateManager.attach(envManager);
+        
+ //       envManager.setScene(rootNode);
+        
+        LightsDebugState debugState = new LightsDebugState();
+        stateManager.attach(debugState);
+        
+        ChaseCamera chaser = new ChaseCamera(cam, modelNode, inputManager);
+        chaser.setDragToRotate(true);
+        chaser.setMinVerticalRotation(-FastMath.HALF_PI);
+        chaser.setMaxDistance(1000);
+        chaser.setSmoothMotion(true);
+        chaser.setRotationSensitivity(10);
+        chaser.setZoomSensitivity(5);
+        flyCam.setEnabled(false);
+        //flyCam.setMoveSpeed(100);
+
+        inputManager.addListener(new ActionListener() {
+            @Override
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if (name.equals("debug") && isPressed) {
+                    //envCam.toggleDebug();
+                }
+
+                if (name.equals("up") && isPressed) {
+                    model.move(0, tpf * 100f, 0);
+                }
+
+                if (name.equals("down") && isPressed) {
+                    model.move(0, -tpf * 100f, 0);
+                }
+                if (name.equals("left") && isPressed) {
+                    model.move(0, 0, tpf * 100f);
+                }
+                if (name.equals("right") && isPressed) {
+                    model.move(0, 0, -tpf * 100f);
+                }
+                if (name.equals("light") && isPressed) {
+                    dl.setDirection(cam.getDirection().normalize());
+                }
+            }
+        }, "toggle", "light", "up", "down", "left", "right", "debug");
+
+        inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_RETURN));
+        inputManager.addMapping("light", new KeyTrigger(KeyInput.KEY_F));
+        inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_UP));
+        inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_DOWN));
+        inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_LEFT));
+        inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_RIGHT));
+        inputManager.addMapping("debug", new KeyTrigger(KeyInput.KEY_D));
+        
+        
+        MaterialDebugAppState debug = new MaterialDebugAppState();
+        debug.registerBinding("Common/MatDefs/Light/PBRLighting.frag", rootNode);
+        getStateManager().attach(debug);
+
+    }
+
+    @Override
+    public void simpleUpdate(float tpf) {
+        frame++;
+
+        if (frame == 2) {
+            modelNode.removeFromParent();
+            final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter<LightProbe>() {
+
+                @Override
+                public void done(LightProbe result) {
+                    System.err.println("Done rendering env maps");
+                }
+            });
+            ((BoundingSphere)probe.getBounds()).setRadius(100);
+            rootNode.addLight(probe);
+            //getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe);
+            
+        }
+        if (frame > 10 && modelNode.getParent() == null) {
+            rootNode.attachChild(modelNode);
+        }
+    }
+
+}
+

+ 387 - 0
jme3-examples/src/main/java/jme3test/light/pbr/TestPbrEnv.java

@@ -0,0 +1,387 @@
+/*
+ * 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 jme3test.light.pbr;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.input.CameraInput;
+import com.jme3.input.KeyInput;
+import com.jme3.input.MouseInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.input.controls.MouseAxisTrigger;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.shadow.DirectionalLightShadowRenderer;
+import com.jme3.shadow.EdgeFilteringMode;
+
+import com.jme3.environment.LightProbeFactory;
+import com.jme3.environment.EnvironmentCamera;
+import com.jme3.environment.util.LightsDebugState;
+import com.jme3.light.LightProbe;
+import com.jme3.material.TechniqueDef;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.BloomFilter;
+import com.jme3.post.filters.FXAAFilter;
+import com.jme3.post.filters.ToneMapFilter;
+import com.jme3.post.ssao.SSAOFilter;
+import com.jme3.scene.Node;
+import com.jme3.texture.plugins.ktx.KTXLoader;
+import com.jme3.util.SkyFactory;
+import com.jme3.util.TangentBinormalGenerator;
+
+public class TestPbrEnv extends SimpleApplication implements ActionListener {
+
+    public static final int SHADOWMAP_SIZE = 1024;
+    private Spatial[] obj;
+    private Material[] mat;
+    private DirectionalLightShadowRenderer dlsr;
+    private LightsDebugState debugState;
+
+    private EnvironmentCamera envCam;
+
+    private Geometry ground;
+    private Material matGroundU;
+    private Material matGroundL;
+
+    private Geometry camGeom;
+
+    public static void main(String[] args) {
+        TestPbrEnv app = new TestPbrEnv();
+        app.start();
+    } 
+
+    
+    public void loadScene() {
+        
+        renderManager.setPreferredLightMode(TechniqueDef.LightMode.SinglePass);
+        renderManager.setSinglePassLightBatchSize(3);
+        obj = new Spatial[2];
+        // Setup first view
+
+        mat = new Material[2];
+        mat[0] = assetManager.loadMaterial("jme3test/light/pbr/pbrMat.j3m");
+        //mat[1] = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m");
+        mat[1] = assetManager.loadMaterial("jme3test/light/pbr/pbrMat2.j3m");
+//        mat[1].setBoolean("UseMaterialColors", true);
+//        mat[1].setColor("Ambient", ColorRGBA.White.mult(0.5f));
+//        mat[1].setColor("Diffuse", ColorRGBA.White.clone());
+
+        obj[0] = new Geometry("sphere", new Sphere(30, 30, 2));
+        obj[0].setShadowMode(ShadowMode.CastAndReceive);
+        obj[1] = new Geometry("cube", new Box(1.0f, 1.0f, 1.0f));
+        obj[1].setShadowMode(ShadowMode.CastAndReceive);
+        TangentBinormalGenerator.generate(obj[1]);
+        TangentBinormalGenerator.generate(obj[0]);
+
+//        for (int i = 0; i < 60; i++) {
+//            Spatial t = obj[FastMath.nextRandomInt(0, obj.length - 1)].clone(false);
+//            t.setName("Cube" + i);
+//            t.setLocalScale(FastMath.nextRandomFloat() * 10f);
+//            t.setMaterial(mat[FastMath.nextRandomInt(0, mat.length - 1)]);
+//            rootNode.attachChild(t);
+//            t.setLocalTranslation(FastMath.nextRandomFloat() * 200f, FastMath.nextRandomFloat() * 30f + 20, 30f * (i + 2f));
+//        }
+
+        for (int i = 0; i < 2; i++) {
+            Spatial t = obj[0].clone(false);
+            t.setName("Cube" + i);
+            t.setLocalScale( 10f);
+            t.setMaterial(mat[1].clone());
+            rootNode.attachChild(t);
+            t.setLocalTranslation(i * 200f+ 100f, 50, 800f * (i));
+        }
+        
+        Box b = new Box(1000, 2, 1000);
+        b.scaleTextureCoordinates(new Vector2f(20, 20));
+        ground = new Geometry("soil", b);
+        TangentBinormalGenerator.generate(ground);
+        ground.setLocalTranslation(0, 10, 550);
+        matGroundU = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        matGroundU.setColor("Color", ColorRGBA.Green);
+
+//        matGroundL = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+//        Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+//        grass.setWrap(WrapMode.Repeat);
+//        matGroundL.setTexture("DiffuseMap", grass);
+
+        matGroundL = assetManager.loadMaterial("jme3test/light/pbr/pbrMat4.j3m");
+        
+        ground.setMaterial(matGroundL);
+
+        //ground.setShadowMode(ShadowMode.CastAndReceive);
+        rootNode.attachChild(ground);
+
+        l = new DirectionalLight();
+        l.setColor(ColorRGBA.White);
+        //l.setDirection(new Vector3f(0.5973172f, -0.16583486f, 0.7846725f).normalizeLocal());
+        l.setDirection(new Vector3f(-0.2823181f, -0.41889593f, 0.863031f).normalizeLocal());
+        
+        rootNode.addLight(l);
+
+        AmbientLight al = new AmbientLight();
+        al.setColor(ColorRGBA.White.mult(0.5f));
+      //  rootNode.addLight(al);
+
+        //Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", SkyFactory.EnvMapType.CubeMap);
+        Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap);
+        sky.setLocalScale(350);
+
+        rootNode.attachChild(sky);
+    }
+    DirectionalLight l;
+
+    @Override
+    public void simpleInitApp() {
+        assetManager.registerLoader(KTXLoader.class, "ktx");
+        
+        
+        // put the camera in a bad position
+        cam.setLocation(new Vector3f(-52.433647f, 68.69636f, -118.60924f));
+        cam.setRotation(new Quaternion(0.10294232f, 0.25269797f, -0.027049713f, 0.96167296f));      
+
+        flyCam.setMoveSpeed(100);
+
+        loadScene();
+
+        dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 4);
+        dlsr.setLight(l);
+        //dlsr.setLambda(0.55f);
+        dlsr.setShadowIntensity(0.5f);
+        dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON);
+        //dlsr.displayDebug();
+ //       viewPort.addProcessor(dlsr);
+        
+        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
+        
+        fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(6.0f)));
+        SSAOFilter ssao = new SSAOFilter();
+        ssao.setIntensity(5);
+                
+        fpp.addFilter(ssao);
+        
+        BloomFilter bloomFilter = new BloomFilter();
+        fpp.addFilter(bloomFilter);
+        fpp.addFilter(new FXAAFilter());
+        //viewPort.addProcessor(fpp);
+
+        initInputs();
+
+//        envManager = new EnvironmentManager();
+//        getStateManager().attach(envManager);
+//        
+        envCam = new EnvironmentCamera();
+        getStateManager().attach(envCam);
+
+        debugState = new LightsDebugState();
+        debugState.setProbeScale(5);        
+        getStateManager().attach(debugState);
+
+        camGeom = new Geometry("camGeom", new Sphere(16, 16, 2));
+//        Material m = new Material(assetManager, "Common/MatDefs/Misc/UnshadedNodes.j3md");
+//        m.setColor("Color", ColorRGBA.Green);
+        Material m = assetManager.loadMaterial("jme3test/light/pbr/pbrMat3.j3m");
+        camGeom.setMaterial(m);
+        camGeom.setLocalTranslation(0, 20, 0);
+        camGeom.setLocalScale(5);
+        rootNode.attachChild(camGeom);
+        
+   //     envManager.setScene(rootNode);
+        
+//        MaterialDebugAppState debug = new MaterialDebugAppState();
+//        debug.registerBinding("MatDefs/PBRLighting.frag", rootNode);
+//        getStateManager().attach(debug);
+        
+        flyCam.setDragToRotate(true);
+        setPauseOnLostFocus(false);
+        
+       // cam.lookAt(camGeom.getWorldTranslation(), Vector3f.UNIT_Y);
+
+    }
+
+    private void fixFLyCamInputs() {
+        inputManager.deleteMapping(CameraInput.FLYCAM_LEFT);
+        inputManager.deleteMapping(CameraInput.FLYCAM_RIGHT);
+        inputManager.deleteMapping(CameraInput.FLYCAM_UP);
+        inputManager.deleteMapping(CameraInput.FLYCAM_DOWN);
+
+        inputManager.addMapping(CameraInput.FLYCAM_LEFT, new MouseAxisTrigger(MouseInput.AXIS_X, true));
+
+        inputManager.addMapping(CameraInput.FLYCAM_RIGHT, new MouseAxisTrigger(MouseInput.AXIS_X, false));
+
+        inputManager.addMapping(CameraInput.FLYCAM_UP, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
+
+        inputManager.addMapping(CameraInput.FLYCAM_DOWN, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
+
+        inputManager.addListener(flyCam, CameraInput.FLYCAM_LEFT, CameraInput.FLYCAM_RIGHT, CameraInput.FLYCAM_UP, CameraInput.FLYCAM_DOWN);
+    }
+
+    private void initInputs() {
+        inputManager.addMapping("switchGroundMat", new KeyTrigger(KeyInput.KEY_M));
+        inputManager.addMapping("snapshot", new KeyTrigger(KeyInput.KEY_SPACE));
+        inputManager.addMapping("fc", new KeyTrigger(KeyInput.KEY_F));
+        inputManager.addMapping("debugProbe", new KeyTrigger(KeyInput.KEY_RETURN));
+        inputManager.addMapping("debugTex", new KeyTrigger(KeyInput.KEY_T));
+        inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_UP));
+        inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_DOWN));
+        inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_RIGHT));
+        inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_LEFT));
+        inputManager.addMapping("delete", new KeyTrigger(KeyInput.KEY_DELETE));
+
+        inputManager.addListener(this, "delete","switchGroundMat", "snapshot", "debugTex", "debugProbe", "fc", "up", "down", "left", "right");
+    }
+    
+    private LightProbe lastProbe;
+    private Node debugGui ;
+
+    @Override
+    public void onAction(String name, boolean keyPressed, float tpf) {
+
+        if (name.equals("switchGroundMat") && keyPressed) {
+            if (ground.getMaterial() == matGroundL) {
+                ground.setMaterial(matGroundU);
+            } else {
+                
+                ground.setMaterial(matGroundL);
+            }
+        }
+
+        if (name.equals("snapshot") && keyPressed) {
+            envCam.setPosition(camGeom.getWorldTranslation());
+            lastProbe = LightProbeFactory.makeProbe(envCam, rootNode, new ConsoleProgressReporter());            
+            ((BoundingSphere)lastProbe.getBounds()).setRadius(200);
+            rootNode.addLight(lastProbe);
+
+        }
+        
+        if (name.equals("delete") && keyPressed) {           
+            System.err.println(rootNode.getWorldLightList().size());
+            rootNode.removeLight(lastProbe);           
+            System.err.println("deleted");
+            System.err.println(rootNode.getWorldLightList().size());
+        }
+
+        if (name.equals("fc") && keyPressed) {
+
+            flyCam.setEnabled(true);
+        }
+
+        if (name.equals("debugProbe") && keyPressed) {
+            //getStateManager().getState(EnvironmentCamera.class).toggleDebug();
+            if (!debugState.isEnabled()) {
+                debugState.setEnabled(true);
+                debugState.setDebugMode(LightsDebugState.DebugMode.IrradianceMap);
+            } else if (debugState.getDebugMode() == LightsDebugState.DebugMode.IrradianceMap) {
+                debugState.setDebugMode(LightsDebugState.DebugMode.PrefilteredEnvMap);
+            } else if (debugState.getDebugMode() == LightsDebugState.DebugMode.PrefilteredEnvMap) {
+                debugState.setEnabled(false);
+            }
+
+        }
+        
+        if (name.equals("debugTex") && keyPressed) {
+            if(debugGui == null || debugGui.getParent() == null){
+                debugGui = lastProbe.getDebugGui(assetManager);
+                debugGui.setLocalTranslation(10, 200, 0);
+                guiNode.attachChild(debugGui);
+            } else if(debugGui != null){
+                debugGui.removeFromParent();
+            }
+        }
+
+        if (name.equals("up")) {
+            up = keyPressed;
+        }
+        if (name.equals("down")) {
+            down = keyPressed;
+        }
+        if (name.equals("right")) {
+            right = keyPressed;
+        }
+        if (name.equals("left")) {
+            left = keyPressed;
+        }
+        if (name.equals("fwd")) {
+            fwd = keyPressed;
+        }
+        if (name.equals("back")) {
+            back = keyPressed;
+        }
+
+    }
+    boolean up = false;
+    boolean down = false;
+    boolean left = false;
+    boolean right = false;
+    boolean fwd = false;
+    boolean back = false;
+    float time = 0;
+    float s = 50f;
+    boolean initialized = false;
+
+    @Override
+    public void simpleUpdate(float tpf) {
+
+        if (!initialized) {
+            fixFLyCamInputs();
+            initialized = true;
+        }
+        float val = tpf * s;
+        if (up) {
+            camGeom.move(0, 0, val);
+        }
+        if (down) {
+            camGeom.move(0, 0, -val);
+
+        }
+        if (right) {
+            camGeom.move(-val, 0, 0);
+
+        }
+        if (left) {
+            camGeom.move(val, 0, 0);
+
+        }
+
+    }
+
+}

+ 157 - 0
jme3-examples/src/main/java/jme3test/material/TestParallaxPBR.java

@@ -0,0 +1,157 @@
+/*
+ * 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.material;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.AnalogListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Quad;
+import com.jme3.util.SkyFactory;
+import com.jme3.util.TangentBinormalGenerator;
+
+public class TestParallaxPBR extends SimpleApplication {
+
+    private Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal();
+
+    public static void main(String[] args) {
+        TestParallaxPBR app = new TestParallaxPBR();
+        app.start();
+    }
+
+    public void setupSkyBox() {
+        rootNode.attachChild(SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false));
+    }
+    DirectionalLight dl;
+
+    public void setupLighting() {
+
+        dl = new DirectionalLight();
+        dl.setDirection(lightDir);
+        dl.setColor(new ColorRGBA(.9f, .9f, .9f, 1));
+        rootNode.addLight(dl);
+    }
+    Material mat;
+
+    public void setupFloor() {
+        mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWallPBR.j3m");
+        //mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWallPBR2.j3m");
+                
+        Node floorGeom = new Node("floorGeom");
+        Quad q = new Quad(100, 100);
+        q.scaleTextureCoordinates(new Vector2f(10, 10));
+        Geometry g = new Geometry("geom", q);
+        g.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
+        floorGeom.attachChild(g);
+        
+        
+        TangentBinormalGenerator.generate(floorGeom);
+        floorGeom.setLocalTranslation(-50, 22, 60);
+        //floorGeom.setLocalScale(100);
+
+        floorGeom.setMaterial(mat);        
+        rootNode.attachChild(floorGeom);
+    }
+
+    public void setupSignpost() {
+        Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml");
+        Material mat = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m");
+        TangentBinormalGenerator.generate(signpost);
+        signpost.setMaterial(mat);
+        signpost.rotate(0, FastMath.HALF_PI, 0);
+        signpost.setLocalTranslation(12, 23.5f, 30);
+        signpost.setLocalScale(4);
+        signpost.setShadowMode(ShadowMode.CastAndReceive);
+        rootNode.attachChild(signpost);
+    }
+
+    @Override
+    public void simpleInitApp() {
+        cam.setLocation(new Vector3f(-15.445636f, 30.162927f, 60.252777f));
+        cam.setRotation(new Quaternion(0.05173137f, 0.92363626f, -0.13454558f, 0.35513034f));
+        flyCam.setMoveSpeed(30);
+
+
+        setupLighting();
+        setupSkyBox();
+        setupFloor();
+        setupSignpost();
+
+        inputManager.addListener(new AnalogListener() {
+
+            public void onAnalog(String name, float value, float tpf) {
+                if ("heightUP".equals(name)) {
+                    parallaxHeigh += 0.01;
+                    mat.setFloat("ParallaxHeight", parallaxHeigh);
+                }
+                if ("heightDown".equals(name)) {
+                    parallaxHeigh -= 0.01;
+                    parallaxHeigh = Math.max(parallaxHeigh, 0);
+                    mat.setFloat("ParallaxHeight", parallaxHeigh);
+                }
+
+            }
+        }, "heightUP", "heightDown");
+        inputManager.addMapping("heightUP", new KeyTrigger(KeyInput.KEY_I));
+        inputManager.addMapping("heightDown", new KeyTrigger(KeyInput.KEY_K));
+
+        inputManager.addListener(new ActionListener() {
+
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if (isPressed && "toggleSteep".equals(name)) {
+                    steep = !steep;
+                    mat.setBoolean("SteepParallax", steep);
+                }
+            }
+        }, "toggleSteep");
+        inputManager.addMapping("toggleSteep", new KeyTrigger(KeyInput.KEY_SPACE));
+    }
+    float parallaxHeigh = 0.05f;
+    float time = 0;
+    boolean steep = false;
+
+    @Override
+    public void simpleUpdate(float tpf) {
+//        time+=tpf;
+//        lightDir.set(FastMath.sin(time), -1, FastMath.cos(time));
+//        bsr.setDirection(lightDir);
+//        dl.setDirection(lightDir);
+    }
+}

+ 180 - 0
jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java

@@ -0,0 +1,180 @@
+/*
+ * 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 jme3test.post;
+
+
+import com.jme3.app.SimpleApplication;
+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.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.post.FilterPostProcessor;
+import com.jme3.post.filters.BloomFilter;
+import com.jme3.post.filters.BloomFilter.GlowMode;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.WireFrustum;
+import com.jme3.scene.shape.Box;
+import com.jme3.util.SkyFactory;
+
+public class TestBloomAlphaThreshold extends SimpleApplication
+{
+
+	float angle;
+	Spatial lightMdl;
+	Spatial teapot;
+	Geometry frustumMdl;
+	WireFrustum frustum;
+	boolean active = true;
+	FilterPostProcessor fpp;
+
+	public static void main(String[] args)
+	{
+		TestBloomAlphaThreshold app = new TestBloomAlphaThreshold();
+		app.start();
+	}
+
+	@Override
+	public void simpleInitApp()
+	{
+		// put the camera in a bad position
+		cam.setLocation(new Vector3f(-2.336393f, 11.91392f, -10));
+		cam.setRotation(new Quaternion(0.23602544f, 0.11321983f, -0.027698677f, 0.96473104f));
+		// cam.setFrustumFar(1000);
+
+		Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+
+		mat.setFloat("Shininess", 15f);
+		mat.setBoolean("UseMaterialColors", true);
+		mat.setColor("Ambient", ColorRGBA.Yellow.mult(0.2f));
+		mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f));
+		mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f));
+		mat.setColor("GlowColor", ColorRGBA.Green);
+
+		Material matSoil = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
+		matSoil.setFloat("Shininess", 15f);
+		matSoil.setBoolean("UseMaterialColors", true);
+		matSoil.setColor("Ambient", ColorRGBA.Gray);
+		matSoil.setColor("Diffuse", ColorRGBA.Black);
+		matSoil.setColor("Specular", ColorRGBA.Gray);
+
+		teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
+		teapot.setLocalTranslation(0, 0, 10);
+
+		teapot.setMaterial(mat);
+		teapot.setShadowMode(ShadowMode.CastAndReceive);
+		teapot.setLocalScale(10.0f);
+		rootNode.attachChild(teapot);
+
+		Geometry soil = new Geometry("soil", new Box(new Vector3f(0, -13, 550), 800, 10, 700));
+		soil.setMaterial(matSoil);
+		soil.setShadowMode(ShadowMode.CastAndReceive);
+		rootNode.attachChild(soil);
+
+		Material matBox = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+		matBox.setTexture("ColorMap", assetManager.loadTexture("Textures/ColoredTex/Monkey.png"));
+		matBox.setFloat("AlphaDiscardThreshold", 0.5f);
+    
+		Geometry box = new Geometry("box", new Box(new Vector3f(-3.5f, 10, -2), 2, 2, 2));
+		box.setMaterial(matBox);
+                box.setQueueBucket(RenderQueue.Bucket.Translucent);
+		// box.setShadowMode(ShadowMode.CastAndReceive);
+		rootNode.attachChild(box);
+
+		DirectionalLight light = new DirectionalLight();
+		light.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
+		light.setColor(ColorRGBA.White.mult(1.5f));
+		rootNode.addLight(light);
+
+		// load sky
+		Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/FullskiesBlueClear03.dds", false);
+		sky.setCullHint(Spatial.CullHint.Never);
+		rootNode.attachChild(sky);
+
+		fpp = new FilterPostProcessor(assetManager);
+		int numSamples = getContext().getSettings().getSamples();
+		if (numSamples > 0)
+		{
+			fpp.setNumSamples(numSamples);
+		}
+
+		BloomFilter bloom = new BloomFilter(GlowMode.Objects);
+		bloom.setDownSamplingFactor(2);
+		bloom.setBlurScale(1.37f);
+		bloom.setExposurePower(3.30f);
+		bloom.setExposureCutOff(0.2f);
+		bloom.setBloomIntensity(2.45f);
+		BloomUI ui = new BloomUI(inputManager, bloom);
+
+		viewPort.addProcessor(fpp);
+		fpp.addFilter(bloom);
+		initInputs();
+
+	}
+
+	private void initInputs()
+	{
+		inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE));
+
+		ActionListener acl = new ActionListener()
+		{
+
+			@Override
+			public void onAction(String name, boolean keyPressed, float tpf)
+			{
+				if (name.equals("toggle") && keyPressed)
+				{
+					if (active)
+					{
+						active = false;
+						viewPort.removeProcessor(fpp);
+					}
+					else
+					{
+						active = true;
+						viewPort.addProcessor(fpp);
+					}
+				}
+			}
+		};
+
+		inputManager.addListener(acl, "toggle");
+
+	}
+
+}

+ 112 - 0
jme3-examples/src/main/java/jme3test/texture/ktx/TestLoadKtx.java

@@ -0,0 +1,112 @@
+/*
+ * 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 jme3test.texture.ktx;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.asset.TextureKey;
+import com.jme3.math.ColorRGBA;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Node;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture2D;
+import com.jme3.texture.TextureCubeMap;
+import com.jme3.texture.plugins.ktx.KTXLoader;
+import com.jme3.ui.Picture;
+
+/**
+ * test
+ * @author nehon
+ */
+public class TestLoadKtx extends SimpleApplication {
+
+    public static void main(String[] args) {
+        TestLoadKtx app = new TestLoadKtx();
+        //app.setShowSettings(false);
+        app.start();
+    }
+   
+    @Override
+    public void simpleInitApp() {
+        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+        assetManager.registerLoader(KTXLoader.class, "ktx");
+             
+        
+        Texture2D t = (Texture2D)assetManager.loadTexture("Textures/ktx/down-reference.ktx");
+        Picture p = new Picture("bla", false);
+        p.setTexture(assetManager, t, false);
+        p.setLocalTranslation(200, 200, 0);
+        p.setWidth(t.getImage().getWidth());
+        p.setHeight(t.getImage().getHeight());
+        guiNode.attachChild(p);
+        
+        
+        Texture2D t2 = (Texture2D)assetManager.loadTexture("Textures/ktx/up-reference.ktx");
+        Picture p2 = new Picture("bla", false);
+        p2.setTexture(assetManager, t2, false);
+        p2.setLocalTranslation(400, 200, 0);
+        p2.setWidth(t2.getImage().getWidth());
+        p2.setHeight(t2.getImage().getHeight());
+        guiNode.attachChild(p2);
+        
+        Texture2D t3 = (Texture2D)assetManager.loadTexture("Textures/ktx/rgba-reference.ktx");
+        Picture p3 = new Picture("bla", false);
+        p3.setTexture(assetManager, t3, false);
+        p3.setLocalTranslation(200, 400, 0);
+        p3.setWidth(t3.getImage().getWidth());
+        p3.setHeight(t3.getImage().getHeight());
+        guiNode.attachChild(p3);
+
+        
+        Texture2D t4 = (Texture2D)assetManager.loadTexture("Textures/ktx/rgb-amg-reference.ktx");
+        Picture p4 = new Picture("bla", false);
+        p4.setTexture(assetManager, t4, false);
+        p4.setLocalTranslation(400, 400, 0);
+        p4.setWidth(t4.getImage().getWidth());
+        p4.setHeight(t4.getImage().getHeight());
+        guiNode.attachChild(p4);
+        
+        
+        flyCam.setDragToRotate(true);
+               
+    }
+    
+    
+    @Override
+    public void simpleUpdate(float tpf) {
+        //TODO: add update code
+    }
+
+    @Override
+    public void simpleRender(RenderManager rm) {
+        //TODO: add render code
+    }
+}

+ 13 - 0
jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat.j3m

@@ -0,0 +1,13 @@
+Material My Material : Common/MatDefs/Light/PBRLighting.j3md {
+     MaterialParameters {
+        Roughness : 0.1
+        BaseColor : 1.0 0.2 0.2 1.0
+        Metallic : 0.0
+        EmissivePower : 0.0
+        EmissiveIntensity : 0.0
+        Emissive : 1.0 0.8 0.8 1.0
+        ParallaxHeight : 0.0
+     }
+    AdditionalRenderState {
+    }
+}

+ 12 - 0
jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat2.j3m

@@ -0,0 +1,12 @@
+Material My Material : Common/MatDefs/Light/PBRLighting.j3md {
+     MaterialParameters {
+        Metallic : 0.0
+        Roughness : 0.0
+        BaseColor : 0.8 0.81960785 0.39607844 1.0
+        EmissiveIntensity : 5.0
+        EmissivePower : 2.0
+        Emissive : 1.0 0.0 0.0 1.0
+     }
+    AdditionalRenderState {
+    }
+}

+ 9 - 0
jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat3.j3m

@@ -0,0 +1,9 @@
+Material My Material : Common/MatDefs/Light/PBRLighting.j3md {
+     MaterialParameters {
+        BaseColor : 0.6 0.6 0.6 1.0
+        Metallic : 1.0
+        Roughness : 0.05
+     }
+    AdditionalRenderState {
+    }
+}

+ 11 - 0
jme3-examples/src/main/resources/jme3test/light/pbr/pbrMat4.j3m

@@ -0,0 +1,11 @@
+Material My Material : Common/MatDefs/Light/PBRLighting.j3md {
+     MaterialParameters {
+        BaseColorMap : Repeat Textures/Terrain/BrickWall/BrickWall.jpg
+        Roughness : 0.01
+        NormalMap : Repeat Textures/Terrain/BrickWall/BrickWall_normal.jpg
+        Metallic : 1.0
+        BaseColor : 1.0 1.0 1.0 1.0
+     }
+    AdditionalRenderState {
+    }
+}

BIN
jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefDE.png


BIN
jme3-examples/src/main/resources/jme3test/light/pbr/spheresRefM.png


BIN
jme3-testdata/src/main/resources/Models/Tank/Tank_Base_Color.png


BIN
jme3-testdata/src/main/resources/Models/Tank/Tank_Emissive.png


BIN
jme3-testdata/src/main/resources/Models/Tank/Tank_Metallic.png


BIN
jme3-testdata/src/main/resources/Models/Tank/Tank_Normal.png


BIN
jme3-testdata/src/main/resources/Models/Tank/Tank_Roughness.png


+ 14 - 0
jme3-testdata/src/main/resources/Models/Tank/tank.j3m

@@ -0,0 +1,14 @@
+Material Tank : Common/MatDefs/Light/PBRLighting.j3md {
+     MaterialParameters {
+        
+        MetallicMap : Flip Models/Tank/Tank_Metallic.png 
+        RoughnessMap : Flip Models/Tank/Tank_Roughness.png
+        NormalMap : Flip Models/Tank/Tank_Normal.png
+        BaseColorMap : Flip Models/Tank/Tank_Base_Color.png
+        EmissiveMap : Flip Models/Tank/Tank_Emissive.png
+        EmissiveIntensity : 2.0
+     
+     }
+    AdditionalRenderState {
+    }
+}

BIN
jme3-testdata/src/main/resources/Models/Tank/tank.j3o


+ 3 - 0
jme3-testdata/src/main/resources/Models/Tank/tank.j3odata

@@ -0,0 +1,3 @@
+#
+#Sat Apr 11 15:27:27 CEST 2015
+ORIGINAL_PATH=Models/Tank/tank.obj

BIN
jme3-testdata/src/main/resources/Scenes/PBR/spheres.j3o


BIN
jme3-testdata/src/main/resources/Textures/Sky/Path.hdr


+ 9 - 0
jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWallPBR.j3m

@@ -0,0 +1,9 @@
+Material Pong Rock PBR : Common/MatDefs/Light/PBRLighting.j3md {
+     MaterialParameters {
+         Roughness : 1.0
+         Metallic : 0.0
+         BaseColorMap : Repeat Textures/Terrain/BrickWall/BrickWall.jpg
+         NormalMap : Repeat Textures/Terrain/BrickWall/BrickWall_normal_parallax.dds
+         PackedNormalParallax: true
+     }
+}

+ 8 - 0
jme3-testdata/src/main/resources/Textures/Terrain/BrickWall/BrickWallPBR2.j3m

@@ -0,0 +1,8 @@
+Material Pong Rock PBR : Common/MatDefs/Light/PBRLighting.j3md {
+     MaterialParameters {
+         Roughness : 1.0
+         Metallic : 0.0
+         BaseColorMap : Repeat Textures/Terrain/BrickWall/BrickWall.jpg
+         ParallaxMap : Repeat Textures/Terrain/BrickWall/BrickWall_height.jpg         
+     }
+}

BIN
jme3-testdata/src/main/resources/Textures/ktx/down-reference.ktx


BIN
jme3-testdata/src/main/resources/Textures/ktx/irrMap.ktx


BIN
jme3-testdata/src/main/resources/Textures/ktx/prefilteredMap.ktx


BIN
jme3-testdata/src/main/resources/Textures/ktx/rgb-amg-reference.ktx


BIN
jme3-testdata/src/main/resources/Textures/ktx/rgb-mipmap-reference.ktx


BIN
jme3-testdata/src/main/resources/Textures/ktx/rgba-reference.ktx


BIN
jme3-testdata/src/main/resources/Textures/ktx/up-reference.ktx


+ 131 - 0
sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbe.java

@@ -0,0 +1,131 @@
+/*
+ *  Copyright (c) 2009-2010 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.gde.core.sceneexplorer.nodes;
+
+import com.jme3.light.LightProbe;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import java.beans.PropertyEditor;
+import java.lang.reflect.InvocationTargetException;
+import org.openide.nodes.PropertySupport;
+import org.openide.nodes.Sheet;
+
+/**
+ *
+ * @author normenhansen
+ */
[email protected](service=SceneExplorerNode.class)
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class JmeLightProbe extends JmeLight{
+    protected LightProbe lightProbe;
+
+    public JmeLightProbe() {
+    }
+
+    public JmeLightProbe(Spatial spatial, LightProbe lightProbe) {
+        super(spatial, lightProbe);
+        this.lightProbe = lightProbe;
+        lookupContents.add(lightProbe);
+        setName("LightProbe");
+    }
+
+    @Override
+    protected Sheet createSheet() {
+        //TODO: multithreading..
+        Sheet sheet = super.createSheet();
+        Sheet.Set set = Sheet.createPropertiesSet();
+        set.setDisplayName("LightProbe");
+        set.setName(LightProbe.class.getName());
+        LightProbe obj = lightProbe;
+        if (obj == null) {
+            return sheet;
+        }
+
+        set.put(makeProperty(obj, Vector3f.class, "getPosition", "setPosition", "Position"));
+        set.put(makeEmbedProperty(obj.getBounds(), obj.getBounds().getClass(), float.class, "getRadius", "setRadius", "Radius"));
+        set.put(createButtonProperty());
+        sheet.put(set);
+        return sheet;
+
+    }
+
+    public LightProbe getLightProbe() {
+        return lightProbe;
+    }
+
+    @Override
+    public Class getExplorerObjectClass() {
+        return LightProbe.class;
+    }
+
+    @Override
+    public Class getExplorerNodeClass() {
+        return JmeLightProbe.class;
+    }
+    
+    protected void setModified(){
+        java.awt.EventQueue.invokeLater(new Runnable() {
+
+            @Override
+            public void run() {
+                fireSave(true);
+            }
+        });
+        
+    }
+
+     private Property createButtonProperty() {
+        return new PropertySupport.ReadWrite<Object>("update", Object.class, "Refresh maps", "Click here to refresh environment maps ") {
+            JmeLightProbeButtonProperty pe;
+
+            @Override
+            public Object getValue() throws IllegalAccessException, InvocationTargetException {
+                return "";
+            }
+
+            @Override
+            public PropertyEditor getPropertyEditor() {
+                if (pe == null) {
+                    pe = new JmeLightProbeButtonProperty(JmeLightProbe.this, (Node)getSpatial());
+                    pe.attachEnv(pe.env);
+                }
+                return pe;
+            }
+
+            @Override
+            public void setValue(Object t) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+            }
+        };
+    }
+}

+ 99 - 0
sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbeButtonProperty.java

@@ -0,0 +1,99 @@
+package com.jme3.gde.core.sceneexplorer.nodes;
+
+import com.jme3.environment.EnvironmentCamera;
+import com.jme3.environment.LightProbeFactory;
+import com.jme3.environment.generation.JobProgressAdapter;
+import com.jme3.gde.core.scene.SceneApplication;
+import com.jme3.gde.core.scene.controller.SceneToolController;
+import com.jme3.gde.core.util.ButtonInplaceEditor;
+import com.jme3.light.LightProbe;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyEditorSupport;
+import java.util.concurrent.Callable;
+import org.netbeans.api.progress.ProgressHandle;
+import org.netbeans.api.progress.ProgressHandleFactory;
+import org.openide.explorer.propertysheet.ExPropertyEditor;
+import org.openide.explorer.propertysheet.InplaceEditor;
+import org.openide.explorer.propertysheet.PropertyEnv;
+
+/**
+ *
+ * @author Nehon
+ */
+public class JmeLightProbeButtonProperty extends PropertyEditorSupport implements ExPropertyEditor, InplaceEditor.Factory {
+
+    JmeLightProbe probe;
+    Node node;
+
+    public JmeLightProbeButtonProperty(JmeLightProbe pe, Node node) {
+        super();
+        this.node = node;
+        this.probe = pe;
+    }
+    PropertyEnv env;
+
+    @Override
+    public void attachEnv(PropertyEnv env) {
+        this.env = env;
+        env.registerInplaceEditorFactory(this);
+    }
+    private ButtonInplaceEditor ed = null;
+
+    @Override
+    public InplaceEditor getInplaceEditor() {
+        if (ed == null) {
+            ed = new ButtonInplaceEditor("Refresh");
+            ed.addActionListener(new ActionListener() {
+
+                @Override
+                public void actionPerformed(ActionEvent e) {
+
+                    SceneApplication.getApplication().enqueue(new Callable<Object>() {
+
+                        @Override
+                        public Object call() throws Exception {
+
+                            EnvironmentCamera envCam = SceneApplication.getApplication().getStateManager().getState(EnvironmentCamera.class);
+                            SceneToolController toolController = SceneApplication.getApplication().getStateManager().getState(SceneToolController.class);
+                            if (toolController != null) {
+                                envCam.setPosition(toolController.getCursorLocation());
+                            } else {
+                                envCam.setPosition(new Vector3f(0, 0, 0));
+                            }
+                            LightProbeFactory.updateProbe(probe.getLightProbe(), envCam, node, new JmeLightProbeProgressHandler());
+
+                            probe.setModified();
+
+                            return null;
+                        }
+                    });
+                }
+            });
+        }
+        return ed;
+    }
+
+    @Override
+    public boolean isPaintable() {
+        return true;
+    }
+
+    @Override
+    public void paintValue(Graphics gfx, Rectangle box) {
+        if (ed == null) {
+            getInplaceEditor();
+        }
+        ed.setSize(box.width, box.height);
+        ed.doLayout();
+        Graphics g = gfx.create(box.x, box.y, box.width, box.height);
+        ed.setOpaque(false);
+        ed.paint(g);
+        g.dispose();
+        probe.refresh(false);
+    }
+}

+ 42 - 0
sdk/jme3-core/src/com/jme3/gde/core/sceneexplorer/nodes/JmeLightProbeProgressHandler.java

@@ -0,0 +1,42 @@
+ 
+package com.jme3.gde.core.sceneexplorer.nodes;
+
+import com.jme3.environment.generation.JobProgressAdapter;
+import com.jme3.light.LightProbe;
+import org.netbeans.api.progress.ProgressHandle;
+import org.netbeans.api.progress.ProgressHandleFactory;
+
+/**
+ *
+ * @author Nehon
+ */
+
+
+public class JmeLightProbeProgressHandler extends JobProgressAdapter<LightProbe> {
+    
+    int lastProgress;
+    
+    ProgressHandle handle = ProgressHandleFactory.createHandle("Generating environment maps");
+    
+    @Override
+    public void start() {
+        handle.start(100);
+    }
+    
+    @Override
+    public void progress(double value) {
+        lastProgress = (int) (value * 100);
+        handle.progress(lastProgress);
+    }
+    
+    @Override
+    public void step(String message) {
+        handle.progress(message, lastProgress);
+    }
+    
+    @Override
+    public void done(LightProbe t) {
+        handle.finish();
+    }
+    
+}

+ 57 - 0
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightGizmoControl.java

@@ -0,0 +1,57 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.gde.scenecomposer.gizmo.light;
+
+import com.jme3.light.Light;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.control.BillboardControl;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import org.openide.util.Exceptions;
+
+/**
+ * Updates the marker's position whenever the light has moved. It is also a
+ * BillboardControl, so this marker always faces the camera
+ */
+public class LightGizmoControl extends BillboardControl {
+
+    private final Vector3f lastPos = new Vector3f();
+    private Vector3f lightPos;
+
+    LightGizmoControl(Light light) {
+        super();
+
+        try {
+            Method getPosition = light.getClass().getMethod("getPosition");
+            lightPos = (Vector3f) getPosition.invoke(light);
+        } catch (NoSuchMethodException ex) {
+            //light type doesn't have a get position method, silancing the exception
+        } catch (SecurityException ex) {
+            Exceptions.printStackTrace(ex);
+        } catch (IllegalAccessException ex) {
+            Exceptions.printStackTrace(ex);
+        } catch (IllegalArgumentException ex) {
+            Exceptions.printStackTrace(ex);
+        } catch (InvocationTargetException ex) {
+            Exceptions.printStackTrace(ex);
+        }
+
+    }
+
+    @Override
+    protected void controlUpdate(float f) {
+        super.controlUpdate(f);
+
+        if (!lightPos.equals(lastPos)) {
+            if (getSpatial() != null) {
+                lastPos.set(lightPos);
+                getSpatial().setLocalTranslation(lastPos);
+            }
+        }
+
+    }
+
+}

+ 68 - 0
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightGizmoFactory.java

@@ -0,0 +1,68 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.gde.scenecomposer.gizmo.light;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.environment.util.BoundingSphereDebug;
+import com.jme3.gde.scenecomposer.gizmo.light.shape.ProbeRadiusShape;
+import com.jme3.light.Light;
+import com.jme3.light.LightProbe;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.BillboardControl;
+import com.jme3.scene.shape.Quad;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.texture.Texture;
+
+/**
+ * Handles the creation of the appropriate light gizmo according to the light type.
+ * @author Nehon
+ */
+public class LightGizmoFactory {
+     
+    public static Spatial createGizmo(AssetManager assetManager, Light light){
+        switch (light.getType()){
+            case Probe:
+                return createLightProbeGizmo(assetManager, light);
+            default:
+                return createDefaultGizmo(assetManager, light);                
+        }
+       
+    }
+    
+    private static Spatial createDefaultGizmo(AssetManager assetManager, Light light){
+        Quad q = new Quad(0.5f, 0.5f);
+        Geometry g =  new Geometry(light.getName(), q);   
+        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+        Texture tex = assetManager.loadTexture("com/jme3/gde/scenecomposer/lightbulb32.png");
+        mat.setTexture("ColorMap", tex);
+        mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
+        g.setMaterial(mat);
+        g.addControl(new LightGizmoControl(light));
+        g.setQueueBucket(RenderQueue.Bucket.Transparent);
+        return g;
+    }
+    
+    private static Spatial createLightProbeGizmo(AssetManager assetManager, Light light){
+        Node debugNode = new Node("Environment debug Node");
+        Sphere s = new Sphere(16, 16, 0.5f);
+        Geometry debugGeom = new Geometry(light.getName(), s);
+        Material debugMaterial = new Material(assetManager, "Common/MatDefs/Misc/reflect.j3md");
+        debugGeom.setMaterial(debugMaterial);
+        Spatial debugBounds = ProbeRadiusShape.createShape(assetManager);
+        
+        debugNode.attachChild(debugGeom);
+        debugNode.attachChild(debugBounds);
+        debugNode.addControl(new LightProbeGizmoControl((LightProbe)light));
+        
+        return debugNode;        
+    }
+         
+}

+ 59 - 0
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/LightProbeGizmoControl.java

@@ -0,0 +1,59 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.gde.scenecomposer.gizmo.light;
+
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.environment.util.LightsDebugState;
+import com.jme3.light.LightProbe;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.control.AbstractControl;
+
+/**
+ * Updates the marker's position whenever the light probe has moved. 
+ * Also update the gizmo radius according to the probe radius.
+ */
+public class LightProbeGizmoControl extends AbstractControl{
+
+    private final Vector3f lastPos = new Vector3f();
+    private final LightProbe lightProbe;
+
+    LightProbeGizmoControl(LightProbe light) {
+        lightProbe = light;
+
+    }
+
+    @Override
+    protected void controlUpdate(float f) {       
+
+        if (!lightProbe.getPosition().equals(lastPos)) {
+            if (getSpatial() != null) {
+                lastPos.set(lightProbe.getPosition());
+                getSpatial().setLocalTranslation(lastPos);
+            }
+        }
+          
+        Geometry probeGeom = (Geometry) ((Node) getSpatial()).getChild(0);
+        Material m = probeGeom.getMaterial();        
+        if (lightProbe.isReady()) {            
+            m.setTexture("CubeMap", lightProbe.getPrefilteredEnvMap());            
+        } 
+        Geometry probeRadius = (Geometry) ((Node) getSpatial()).getChild(1);
+        probeRadius.setLocalScale(((BoundingSphere) lightProbe.getBounds()).getRadius());
+        
+
+    }
+
+    @Override
+    protected void controlRender(RenderManager rm, ViewPort vp) {
+        
+    }
+
+}

+ 23 - 0
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed.j3sn

@@ -0,0 +1,23 @@
+ShaderNodeDefinitions{ 
+    ShaderNodeDefinition Dashed {      
+        Type: Fragment
+
+        Shader GLSL100: com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed100.frag
+        
+        Documentation{
+            Renders dashed lines            
+            @input vec2 texCoord The texture coordinates
+            @input float size the size of the dashes
+            @input vec4 inColor the color of the fragment so far
+            @outColor vec4 color the output color
+        }
+        Input {
+            vec2 texCoord
+            vec4 inColor
+            float size
+        }
+        Output {
+            vec4 outColor
+        }
+    }
+}

+ 11 - 0
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed100.frag

@@ -0,0 +1,11 @@
+
+void main(){
+        //@input vec2 texCoord The texture coordinates
+    //@input float size the size of the dashes
+    //@output vec4 color the output color
+
+    //insert glsl code here
+    outColor = inColor;
+    outColor.a = step(1.0 - size, texCoord.x);
+    
+}

+ 37 - 0
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/dashed.j3md

@@ -0,0 +1,37 @@
+MaterialDef Simple {
+    MaterialParameters {
+        Float DashSize
+    }
+    Technique {
+        WorldParameters {
+            WorldViewProjectionMatrix
+        }
+        VertexShaderNodes {
+            ShaderNode CommonVert {
+                Definition : CommonVert : Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn
+                InputMappings {
+                    worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix
+                    modelPosition = Global.position.xyz
+                    texCoord1 = Attr.inTexCoord
+                    vertColor = Attr.inColor
+                }
+                OutputMappings {
+                    Global.position = projPosition
+                }
+            }
+        }
+        FragmentShaderNodes {
+            ShaderNode Dashed {
+                Definition : Dashed : com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/Dashed.j3sn
+                InputMappings {
+                    texCoord = CommonVert.texCoord1
+                    inColor = CommonVert.vertColor
+                    size = MatParam.DashSize
+                }
+                OutputMappings {
+                    Global.color = outColor
+                }
+            }
+        }
+    }
+}

+ 168 - 0
sdk/jme3-scenecomposer/src/com/jme3/gde/scenecomposer/gizmo/light/shape/ProbeRadiusShape.java

@@ -0,0 +1,168 @@
+ /*
+ * 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.gde.scenecomposer.gizmo.light.shape;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.control.BillboardControl;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * 
+ * A debuging shape for a BoundingSphere 
+ * Consists of 3 axis aligned circles.
+ * 
+ * @author nehon
+ */
+public class ProbeRadiusShape extends Mesh {
+
+    protected int vertCount;
+    protected int triCount;
+    protected int radialSamples = 256;
+    protected boolean useEvenSlices;
+    protected boolean interior;
+    /**
+     * the distance from the center point each point falls on
+     */
+    public float radius;
+
+    public float getRadius() {
+        return radius;
+    }
+
+    public ProbeRadiusShape() {
+        setGeometryData();
+        setIndexData();
+    }
+
+    /**
+     * builds the vertices based on the radius
+     */
+    private void setGeometryData() {
+        setMode(Mode.Lines);
+
+        FloatBuffer posBuf = BufferUtils.createVector3Buffer((radialSamples + 1));
+        FloatBuffer colBuf = BufferUtils.createFloatBuffer((radialSamples + 1) * 4);
+        FloatBuffer texBuf = BufferUtils.createVector2Buffer(radialSamples + 1);
+        
+
+        setBuffer(Type.Position, 3, posBuf);
+        setBuffer(Type.Color, 4, colBuf);
+        setBuffer(Type.TexCoord, 2, texBuf);
+
+        // generate geometry
+        float fInvRS = 1.0f / radialSamples;
+
+        // Generate points on the unit circle to be used in computing the mesh
+        // points on a sphere slice.
+        float[] afSin = new float[(radialSamples + 1)];
+        float[] afCos = new float[(radialSamples + 1)];
+        for (int iR = 0; iR < radialSamples; iR++) {
+            float fAngle = FastMath.TWO_PI * fInvRS * iR;
+            afCos[iR] = FastMath.cos(fAngle);
+            afSin[iR] = FastMath.sin(fAngle);
+        }
+        afSin[radialSamples] = afSin[0];
+        afCos[radialSamples] = afCos[0];
+
+        for (int iR = 0; iR <= radialSamples; iR++) {
+            posBuf.put(afCos[iR])
+                    .put(afSin[iR])
+                    .put(0);
+            colBuf.put(ColorRGBA.Orange.r)
+                    .put(ColorRGBA.Orange.g)
+                    .put(ColorRGBA.Orange.b)
+                    .put(ColorRGBA.Orange.a);
+            texBuf.put(iR % 2f)
+                    .put(iR % 2f);
+
+        }
+
+        updateBound();
+        setStatic();
+    }
+
+    /**
+     * sets the indices for rendering the sphere.
+     */
+    private void setIndexData() {
+        // allocate connectivity
+        int nbSegments = (radialSamples);// * 3;
+
+        ShortBuffer idxBuf = BufferUtils.createShortBuffer(2 * nbSegments);
+        setBuffer(Type.Index, 2, idxBuf);
+
+        int idx = 0;
+        int segDone = 0;
+        while (segDone < nbSegments) {
+            idxBuf.put((short) idx);
+            idxBuf.put((short) (idx + 1));
+            idx++;
+            segDone++;
+        }
+
+    }
+    
+
+    /**
+     * Convenience factory method that creates a debuging bounding sphere geometry
+     * @param assetManager the assetManager
+     * @return the bounding sphere debug geometry.
+     */
+    public static Geometry createShape(AssetManager assetManager) {
+        ProbeRadiusShape b = new ProbeRadiusShape();
+        Geometry geom = new Geometry("BoundingDebug", b);
+
+        Material mat = new Material(assetManager, "com/jme3/gde/scenecomposer/gizmo/light/mat/dashed/dashed.j3md");        
+        mat.getAdditionalRenderState().setWireframe(true);
+        mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
+        mat.getAdditionalRenderState().setDepthWrite(false);
+        mat.getAdditionalRenderState().setDepthTest(false);  
+        mat.setFloat("DashSize", 0.5f);
+        geom.setQueueBucket(RenderQueue.Bucket.Transparent);
+        geom.addControl(new BillboardControl());
+        
+        
+        geom.setMaterial(mat);
+        return geom;
+
+    }
+}