diff --git a/engine/build.xml b/engine/build.xml
index adb5a50d1..1671dc776 100644
--- a/engine/build.xml
+++ b/engine/build.xml
@@ -67,12 +67,11 @@
Compile Desktop
-
Compile Blender Loader
@@ -330,7 +329,6 @@
-
@@ -388,7 +386,6 @@
-
diff --git a/engine/nbproject/build-impl.xml b/engine/nbproject/build-impl.xml
index 7e454c9af..58b86470a 100644
--- a/engine/nbproject/build-impl.xml
+++ b/engine/nbproject/build-impl.xml
@@ -170,7 +170,6 @@ is divided into following sections:
-
@@ -253,7 +252,6 @@ is divided into following sections:
Must set src.core-data.dir
Must set src.core-plugins.dir
Must set src.desktop.dir
- Must set src.desktop-fx.dir
Must set src.terrain.dir
Must set src.jbullet.dir
Must set src.bullet.dir
@@ -289,7 +287,7 @@ is divided into following sections:
-
+
@@ -329,7 +327,7 @@ is divided into following sections:
-
+
@@ -361,7 +359,7 @@ is divided into following sections:
-
+
@@ -659,7 +657,7 @@ is divided into following sections:
-
+
@@ -668,7 +666,6 @@ is divided into following sections:
-
@@ -703,7 +700,7 @@ is divided into following sections:
Must select some files in the IDE or set javac.includes
-
+
@@ -935,9 +932,6 @@ is divided into following sections:
-
-
-
@@ -998,9 +992,6 @@ is divided into following sections:
-
-
-
diff --git a/engine/nbproject/genfiles.properties b/engine/nbproject/genfiles.properties
index 91ac5c4a0..ecf98b35a 100644
--- a/engine/nbproject/genfiles.properties
+++ b/engine/nbproject/genfiles.properties
@@ -3,8 +3,8 @@ build.xml.script.CRC32=34d4c2f2
build.xml.stylesheet.CRC32=958a1d3e
# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
-nbproject/build-impl.xml.data.CRC32=3272ce3a
-nbproject/build-impl.xml.script.CRC32=d1b33dd7
+nbproject/build-impl.xml.data.CRC32=11158891
+nbproject/build-impl.xml.script.CRC32=283800d6
nbproject/build-impl.xml.stylesheet.CRC32=0ae3a408@1.44.1.45
nbproject/profiler-build-impl.xml.data.CRC32=aff514c1
nbproject/profiler-build-impl.xml.script.CRC32=abda56ed
diff --git a/engine/nbproject/project.properties b/engine/nbproject/project.properties
index 7c75523d7..4f00f4f3e 100644
--- a/engine/nbproject/project.properties
+++ b/engine/nbproject/project.properties
@@ -109,7 +109,6 @@ src.bullet.dir=src/bullet
src.core-data.dir=src/core-data
src.core-plugins.dir=src/core-plugins
src.core.dir=src/core
-src.desktop-fx.dir=src/desktop-fx
src.desktop.dir=src/desktop
src.jbullet.dir=src/jbullet
src.jogg.dir=src/jogg
diff --git a/engine/nbproject/project.xml b/engine/nbproject/project.xml
index 71794ec5a..4ed8a1d61 100644
--- a/engine/nbproject/project.xml
+++ b/engine/nbproject/project.xml
@@ -15,7 +15,6 @@
-
diff --git a/engine/src/core/com/jme3/post/HDRRenderer.java b/engine/src/core/com/jme3/post/HDRRenderer.java
new file mode 100644
index 000000000..bac8334ab
--- /dev/null
+++ b/engine/src/core/com/jme3/post/HDRRenderer.java
@@ -0,0 +1,419 @@
+/*
+ * 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.post;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.Vector2f;
+import com.jme3.renderer.*;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.MagFilter;
+import com.jme3.texture.Texture.MinFilter;
+import com.jme3.texture.Texture2D;
+import com.jme3.ui.Picture;
+import java.util.Collection;
+import java.util.logging.Logger;
+
+public class HDRRenderer implements SceneProcessor {
+
+ private static final int LUMMODE_NONE = 0x1,
+ LUMMODE_ENCODE_LUM = 0x2,
+ LUMMODE_DECODE_LUM = 0x3;
+
+ private Renderer renderer;
+ private RenderManager renderManager;
+ private ViewPort viewPort;
+ private static final Logger logger = Logger.getLogger(HDRRenderer.class.getName());
+
+ private Camera fbCam = new Camera(1, 1);
+
+ private FrameBuffer msFB;
+
+ private FrameBuffer mainSceneFB;
+ private Texture2D mainScene;
+ private FrameBuffer scene64FB;
+ private Texture2D scene64;
+ private FrameBuffer scene8FB;
+ private Texture2D scene8;
+ private FrameBuffer scene1FB[] = new FrameBuffer[2];
+ private Texture2D scene1[] = new Texture2D[2];
+
+ private Material hdr64;
+ private Material hdr8;
+ private Material hdr1;
+ private Material tone;
+
+ private Picture fsQuad;
+ private float time = 0;
+ private int curSrc = -1;
+ private int oppSrc = -1;
+ private float blendFactor = 0;
+
+ private int numSamples = 0;
+ private float exposure = 0.18f;
+ private float whiteLevel = 100f;
+ private float throttle = -1;
+ private int maxIterations = -1;
+ private Image.Format bufFormat = Format.RGB16F;
+
+ private MinFilter fbMinFilter = MinFilter.BilinearNoMipMaps;
+ private MagFilter fbMagFilter = MagFilter.Bilinear;
+ private AssetManager manager;
+
+ private boolean enabled = true;
+
+ public HDRRenderer(AssetManager manager, Renderer renderer){
+ this.manager = manager;
+ this.renderer = renderer;
+
+ Collection caps = renderer.getCaps();
+ if (caps.contains(Caps.PackedFloatColorBuffer))
+ bufFormat = Format.RGB111110F;
+ else if (caps.contains(Caps.FloatColorBuffer))
+ bufFormat = Format.RGB16F;
+ else{
+ enabled = false;
+ return;
+ }
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setSamples(int samples){
+ this.numSamples = samples;
+ }
+
+ public void setExposure(float exp){
+ this.exposure = exp;
+ }
+
+ public void setWhiteLevel(float whiteLevel){
+ this.whiteLevel = whiteLevel;
+ }
+
+ public void setMaxIterations(int maxIterations){
+ this.maxIterations = maxIterations;
+
+ // regenerate shaders if needed
+ if (hdr64 != null)
+ createLumShaders();
+ }
+
+ public void setThrottle(float throttle){
+ this.throttle = throttle;
+ }
+
+ public void setUseFastFilter(boolean fastFilter){
+ if (fastFilter){
+ fbMagFilter = MagFilter.Nearest;
+ fbMinFilter = MinFilter.NearestNoMipMaps;
+ }else{
+ fbMagFilter = MagFilter.Bilinear;
+ fbMinFilter = MinFilter.BilinearNoMipMaps;
+ }
+ }
+
+ public Picture createDisplayQuad(/*int mode, Texture tex*/){
+ if (scene64 == null)
+ return null;
+
+ Material mat = new Material(manager, "Common/MatDefs/Hdr/LogLum.j3md");
+// if (mode == LUMMODE_ENCODE_LUM)
+// mat.setBoolean("EncodeLum", true);
+// else if (mode == LUMMODE_DECODE_LUM)
+ mat.setBoolean("DecodeLum", true);
+ mat.setTexture("Texture", scene64);
+// mat.setTexture("Texture", tex);
+
+ Picture dispQuad = new Picture("Luminance Display");
+ dispQuad.setMaterial(mat);
+ return dispQuad;
+ }
+
+ private Material createLumShader(int srcW, int srcH, int bufW, int bufH, int mode,
+ int iters, Texture tex){
+ Material mat = new Material(manager, "Common/MatDefs/Hdr/LogLum.j3md");
+
+ Vector2f blockSize = new Vector2f(1f / bufW, 1f / bufH);
+ Vector2f pixelSize = new Vector2f(1f / srcW, 1f / srcH);
+ Vector2f blocks = new Vector2f();
+ float numPixels = Float.POSITIVE_INFINITY;
+ if (iters != -1){
+ do {
+ pixelSize.multLocal(2);
+ blocks.set(blockSize.x / pixelSize.x,
+ blockSize.y / pixelSize.y);
+ numPixels = blocks.x * blocks.y;
+ } while (numPixels > iters);
+ }else{
+ blocks.set(blockSize.x / pixelSize.x,
+ blockSize.y / pixelSize.y);
+ numPixels = blocks.x * blocks.y;
+ }
+ System.out.println(numPixels);
+
+ mat.setBoolean("Blocks", true);
+ if (mode == LUMMODE_ENCODE_LUM)
+ mat.setBoolean("EncodeLum", true);
+ else if (mode == LUMMODE_DECODE_LUM)
+ mat.setBoolean("DecodeLum", true);
+
+ mat.setTexture("Texture", tex);
+ mat.setVector2("BlockSize", blockSize);
+ mat.setVector2("PixelSize", pixelSize);
+ mat.setFloat("NumPixels", numPixels);
+
+ return mat;
+ }
+
+ private void createLumShaders(){
+ int w = mainSceneFB.getWidth();
+ int h = mainSceneFB.getHeight();
+ hdr64 = createLumShader(w, h, 64, 64, LUMMODE_ENCODE_LUM, maxIterations, mainScene);
+ hdr8 = createLumShader(64, 64, 8, 8, LUMMODE_NONE, maxIterations, scene64);
+ hdr1 = createLumShader(8, 8, 1, 1, LUMMODE_NONE, maxIterations, scene8);
+ }
+
+ private int opposite(int i){
+ return i == 1 ? 0 : 1;
+ }
+
+ private void renderProcessing(Renderer r, FrameBuffer dst, Material mat){
+ if (dst == null){
+ fsQuad.setWidth(mainSceneFB.getWidth());
+ fsQuad.setHeight(mainSceneFB.getHeight());
+ fbCam.resize(mainSceneFB.getWidth(), mainSceneFB.getHeight(), true);
+ }else{
+ fsQuad.setWidth(dst.getWidth());
+ fsQuad.setHeight(dst.getHeight());
+ fbCam.resize(dst.getWidth(), dst.getHeight(), true);
+ }
+ fsQuad.setMaterial(mat);
+ fsQuad.updateGeometricState();
+ renderManager.setCamera(fbCam, true);
+
+ r.setFrameBuffer(dst);
+ r.clearBuffers(true, true, true);
+ renderManager.renderGeometry(fsQuad);
+ }
+
+ private void renderToneMap(Renderer r, FrameBuffer out){
+ tone.setFloat("A", exposure);
+ tone.setFloat("White", whiteLevel);
+ tone.setTexture("Lum", scene1[oppSrc]);
+ tone.setTexture("Lum2", scene1[curSrc]);
+ tone.setFloat("BlendFactor", blendFactor);
+ renderProcessing(r, out, tone);
+ }
+
+ private void updateAverageLuminance(Renderer r){
+ renderProcessing(r, scene64FB, hdr64);
+ renderProcessing(r, scene8FB, hdr8);
+ renderProcessing(r, scene1FB[curSrc], hdr1);
+ }
+
+ public boolean isInitialized(){
+ return viewPort != null;
+ }
+
+ public void reshape(ViewPort vp, int w, int h){
+ if (mainSceneFB != null){
+ renderer.deleteFrameBuffer(mainSceneFB);
+ }
+
+ mainSceneFB = new FrameBuffer(w, h, 1);
+ mainScene = new Texture2D(w, h, bufFormat);
+ mainSceneFB.setDepthBuffer(Format.Depth);
+ mainSceneFB.setColorTexture(mainScene);
+ mainScene.setMagFilter(fbMagFilter);
+ mainScene.setMinFilter(fbMinFilter);
+
+ if (msFB != null){
+ renderer.deleteFrameBuffer(msFB);
+ }
+
+ tone.setTexture("Texture", mainScene);
+
+ Collection caps = renderer.getCaps();
+ if (numSamples > 1 && caps.contains(Caps.FrameBufferMultisample)){
+ msFB = new FrameBuffer(w, h, numSamples);
+ msFB.setDepthBuffer(Format.Depth);
+ msFB.setColorBuffer(bufFormat);
+ vp.setOutputFrameBuffer(msFB);
+ }else{
+ if (numSamples > 1)
+ logger.warning("FBO multisampling not supported on this GPU, request ignored.");
+
+ vp.setOutputFrameBuffer(mainSceneFB);
+ }
+
+ createLumShaders();
+ }
+
+ public void initialize(RenderManager rm, ViewPort vp){
+ if (!enabled)
+ return;
+
+ renderer = rm.getRenderer();
+ renderManager = rm;
+ viewPort = vp;
+
+ // loadInitial()
+ fsQuad = new Picture("HDR Fullscreen Quad");
+
+ Format lumFmt = Format.RGB8;
+ scene64FB = new FrameBuffer(64, 64, 1);
+ scene64 = new Texture2D(64, 64, lumFmt);
+ scene64FB.setColorTexture(scene64);
+ scene64.setMagFilter(fbMagFilter);
+ scene64.setMinFilter(fbMinFilter);
+
+ scene8FB = new FrameBuffer(8, 8, 1);
+ scene8 = new Texture2D(8, 8, lumFmt);
+ scene8FB.setColorTexture(scene8);
+ scene8.setMagFilter(fbMagFilter);
+ scene8.setMinFilter(fbMinFilter);
+
+ scene1FB[0] = new FrameBuffer(1, 1, 1);
+ scene1[0] = new Texture2D(1, 1, lumFmt);
+ scene1FB[0].setColorTexture(scene1[0]);
+
+ scene1FB[1] = new FrameBuffer(1, 1, 1);
+ scene1[1] = new Texture2D(1, 1, lumFmt);
+ scene1FB[1].setColorTexture(scene1[1]);
+
+ // prepare tonemap shader
+ tone = new Material(manager, "Common/MatDefs/Hdr/ToneMap.j3md");
+ tone.setFloat("A", 0.18f);
+ tone.setFloat("White", 100);
+
+ // load();
+ int w = vp.getCamera().getWidth();
+ int h = vp.getCamera().getHeight();
+ reshape(vp, w, h);
+
+
+ }
+
+ public void preFrame(float tpf) {
+ if (!enabled)
+ return;
+
+ time += tpf;
+ blendFactor = (time / throttle);
+ }
+
+ public void postQueue(RenderQueue rq) {
+ }
+
+ public void postFrame(FrameBuffer out) {
+ if (!enabled)
+ return;
+
+ if (msFB != null){
+ // first render to multisampled FB
+// renderer.setFrameBuffer(msFB);
+// renderer.clearBuffers(true,true,true);
+//
+// renderManager.renderViewPortRaw(viewPort);
+
+ // render back to non-multisampled FB
+ renderer.copyFrameBuffer(msFB, mainSceneFB);
+ }else{
+// renderer.setFrameBuffer(mainSceneFB);
+// renderer.clearBuffers(true,true,false);
+//
+// renderManager.renderViewPortRaw(viewPort);
+ }
+
+ // should we update avg lum?
+ if (throttle == -1){
+ // update every frame
+ curSrc = 0;
+ oppSrc = 0;
+ blendFactor = 0;
+ time = 0;
+ updateAverageLuminance(renderer);
+ }else{
+ if (curSrc == -1){
+ curSrc = 0;
+ oppSrc = 0;
+
+ // initial update
+ updateAverageLuminance(renderer);
+
+ blendFactor = 0;
+ time = 0;
+ }else if (time > throttle){
+
+ // time to switch
+ oppSrc = curSrc;
+ curSrc = opposite(curSrc);
+
+ updateAverageLuminance(renderer);
+
+ blendFactor = 0;
+ time = 0;
+ }
+ }
+
+ // since out == mainSceneFB, tonemap into the main screen instead
+ //renderToneMap(renderer, out);
+ renderToneMap(renderer, null);
+
+ renderManager.setCamera(viewPort.getCamera(), false);
+ }
+
+ public void cleanup() {
+ if (!enabled)
+ return;
+
+ if (msFB != null)
+ renderer.deleteFrameBuffer(msFB);
+ if (mainSceneFB != null)
+ renderer.deleteFrameBuffer(mainSceneFB);
+ if (scene64FB != null){
+ renderer.deleteFrameBuffer(scene64FB);
+ renderer.deleteFrameBuffer(scene8FB);
+ renderer.deleteFrameBuffer(scene1FB[0]);
+ renderer.deleteFrameBuffer(scene1FB[1]);
+ }
+ }
+
+}
diff --git a/engine/src/core/com/jme3/post/filters/BloomFilter.java b/engine/src/core/com/jme3/post/filters/BloomFilter.java
new file mode 100644
index 000000000..770e7f448
--- /dev/null
+++ b/engine/src/core/com/jme3/post/filters/BloomFilter.java
@@ -0,0 +1,309 @@
+/*
+ * 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.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.texture.Image.Format;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * BloomFilter is used to make objects in the scene have a glow effect.
+ * There are 2 mode : Scene and Objects.
+ * Scene mode extracts the bright parts of the scene to make them glow
+ * Object mode make objects glow according to their material's glowMap or their GlowColor
+ * @see advanced:bloom_and_glow for more details
+ *
+ * @author Rémy Bouquet aka Nehon
+ */
+public class BloomFilter extends Filter {
+
+ /**
+ * GlowMode specifies if the glow will be applied to the whole scene,or to objects that have aglow color or a glow map
+ */
+ public enum GlowMode {
+
+ /**
+ * Apply bloom filter to bright areas in the scene.
+ */
+ Scene,
+ /**
+ * Apply bloom only to objects that have a glow map or a glow color.
+ */
+ Objects,
+ /**
+ * Apply bloom to both bright parts of the scene and objects with glow map.
+ */
+ SceneAndObjects;
+ }
+
+ private GlowMode glowMode = GlowMode.Scene;
+ //Bloom parameters
+ private float blurScale = 1.5f;
+ private float exposurePower = 5.0f;
+ private float exposureCutOff = 0.0f;
+ private float bloomIntensity = 2.0f;
+ private float downSamplingFactor = 1;
+ private Pass preGlowPass;
+ private Pass extractPass;
+ private Pass horizontalBlur = new Pass();
+ private Pass verticalalBlur = new Pass();
+ private Material extractMat;
+ private Material vBlurMat;
+ private Material hBlurMat;
+ private int screenWidth;
+ private int screenHeight;
+
+ /**
+ * Creates a Bloom filter
+ */
+ public BloomFilter() {
+ super("BloomFilter");
+ }
+
+ /**
+ * Creates the bloom filter with the specific glow mode
+ * @param glowMode
+ */
+ public BloomFilter(GlowMode glowMode) {
+ this();
+ this.glowMode = glowMode;
+ }
+
+ @Override
+ protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+ screenWidth = (int) Math.max(1, (w / downSamplingFactor));
+ screenHeight = (int) Math.max(1, (h / downSamplingFactor));
+ // System.out.println(screenWidth + " " + screenHeight);
+ if (glowMode != GlowMode.Scene) {
+ preGlowPass = new Pass();
+ preGlowPass.init(renderManager.getRenderer(), screenWidth, screenHeight, Format.RGBA8, Format.Depth);
+ }
+
+ postRenderPasses = new ArrayList();
+ //configuring extractPass
+ extractMat = new Material(manager, "Common/MatDefs/Post/BloomExtract.j3md");
+ extractPass = new Pass() {
+
+ @Override
+ public boolean requiresSceneAsTexture() {
+ return true;
+ }
+
+ @Override
+ public void beforeRender() {
+ extractMat.setFloat("ExposurePow", exposurePower);
+ extractMat.setFloat("ExposureCutoff", exposureCutOff);
+ if (glowMode != GlowMode.Scene) {
+ extractMat.setTexture("GlowMap", preGlowPass.getRenderedTexture());
+ }
+ extractMat.setBoolean("Extract", glowMode != GlowMode.Objects);
+ }
+ };
+
+ extractPass.init(renderManager.getRenderer(), screenWidth, screenHeight, Format.RGBA8, Format.Depth, 1, extractMat);
+ postRenderPasses.add(extractPass);
+
+ //configuring horizontal blur pass
+ hBlurMat = new Material(manager, "Common/MatDefs/Blur/HGaussianBlur.j3md");
+ horizontalBlur = new Pass() {
+
+ @Override
+ public void beforeRender() {
+ hBlurMat.setTexture("Texture", extractPass.getRenderedTexture());
+ hBlurMat.setFloat("Size", screenWidth);
+ hBlurMat.setFloat("Scale", blurScale);
+ }
+ };
+
+ horizontalBlur.init(renderManager.getRenderer(), screenWidth, screenHeight, Format.RGBA8, Format.Depth, 1, hBlurMat);
+ postRenderPasses.add(horizontalBlur);
+
+ //configuring vertical blur pass
+ vBlurMat = new Material(manager, "Common/MatDefs/Blur/VGaussianBlur.j3md");
+ verticalalBlur = new Pass() {
+
+ @Override
+ public void beforeRender() {
+ vBlurMat.setTexture("Texture", horizontalBlur.getRenderedTexture());
+ vBlurMat.setFloat("Size", screenHeight);
+ vBlurMat.setFloat("Scale", blurScale);
+ }
+ };
+
+ verticalalBlur.init(renderManager.getRenderer(), screenWidth, screenHeight, Format.RGBA8, Format.Depth, 1, vBlurMat);
+ postRenderPasses.add(verticalalBlur);
+
+
+ //final material
+ material = new Material(manager, "Common/MatDefs/Post/BloomFinal.j3md");
+ material.setTexture("BloomTex", verticalalBlur.getRenderedTexture());
+ }
+
+
+ @Override
+ protected Material getMaterial() {
+ material.setFloat("BloomIntensity", bloomIntensity);
+ return material;
+ }
+
+ @Override
+ protected void postQueue(RenderManager renderManager, ViewPort viewPort) {
+ if (glowMode != GlowMode.Scene) {
+ renderManager.getRenderer().setBackgroundColor(ColorRGBA.BlackNoAlpha);
+ renderManager.getRenderer().setFrameBuffer(preGlowPass.getRenderFrameBuffer());
+ renderManager.getRenderer().clearBuffers(true, true, true);
+ renderManager.setForcedTechnique("Glow");
+ renderManager.renderViewPortQueues(viewPort, false);
+ renderManager.setForcedTechnique(null);
+ renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer());
+ }
+ }
+
+ /**
+ * returns the bloom intensity
+ * @return
+ */
+ public float getBloomIntensity() {
+ return bloomIntensity;
+ }
+
+ /**
+ * intensity of the bloom effect default is 2.0
+ * @param bloomIntensity
+ */
+ public void setBloomIntensity(float bloomIntensity) {
+ this.bloomIntensity = bloomIntensity;
+ }
+
+ /**
+ * returns the blur scale
+ * @return
+ */
+ public float getBlurScale() {
+ return blurScale;
+ }
+
+ /**
+ * sets The spread of the bloom default is 1.5f
+ * @param blurScale
+ */
+ public void setBlurScale(float blurScale) {
+ this.blurScale = blurScale;
+ }
+
+ /**
+ * returns the exposure cutoff
+ * for more details see {@link setExposureCutOff(float exposureCutOff)}
+ * @return
+ */
+ public float getExposureCutOff() {
+ return exposureCutOff;
+ }
+
+ /**
+ * Define the color threshold on which the bloom will be applied (0.0 to 1.0)
+ * @param exposureCutOff
+ */
+ public void setExposureCutOff(float exposureCutOff) {
+ this.exposureCutOff = exposureCutOff;
+ }
+
+ /**
+ * returns the exposure power
+ * form more details see {@link setExposurePower(float exposurePower)}
+ * @return
+ */
+ public float getExposurePower() {
+ return exposurePower;
+ }
+
+ /**
+ * defines how many time the bloom extracted color will be multiplied by itself. default id 5.0
+ * a high value will reduce rough edges in the bloom and somhow the range of the bloom area *
+ * @param exposurePower
+ */
+ public void setExposurePower(float exposurePower) {
+ this.exposurePower = exposurePower;
+ }
+
+ /**
+ * returns the downSampling factor
+ * form more details see {@link setDownSamplingFactor(float downSamplingFactor)}
+ * @return
+ */
+ public float getDownSamplingFactor() {
+ return downSamplingFactor;
+ }
+
+ /**
+ * Sets the downSampling factor : the size of the computed texture will be divided by this factor. default is 1 for no downsampling
+ * A 2 value is a good way of widening the blur
+ * @param downSamplingFactor
+ */
+ public void setDownSamplingFactor(float downSamplingFactor) {
+ this.downSamplingFactor = downSamplingFactor;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(glowMode, "glowMode", GlowMode.Scene);
+ oc.write(blurScale, "blurScale", 1.5f);
+ oc.write(exposurePower, "exposurePower", 5.0f);
+ oc.write(exposureCutOff, "exposureCutOff", 0.0f);
+ oc.write(bloomIntensity, "bloomIntensity", 2.0f);
+ oc.write(downSamplingFactor, "downSamplingFactor", 1);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ glowMode = ic.readEnum("glowMode", GlowMode.class, GlowMode.Scene);
+ blurScale = ic.readFloat("blurScale", 1.5f);
+ exposurePower = ic.readFloat("exposurePower", 5.0f);
+ exposureCutOff = ic.readFloat("exposureCutOff", 0.0f);
+ bloomIntensity = ic.readFloat("bloomIntensity", 2.0f);
+ downSamplingFactor = ic.readFloat("downSamplingFactor", 1);
+ }
+}
diff --git a/engine/src/core/com/jme3/post/filters/CartoonEdgeFilter.java b/engine/src/core/com/jme3/post/filters/CartoonEdgeFilter.java
new file mode 100644
index 000000000..3e9998ba7
--- /dev/null
+++ b/engine/src/core/com/jme3/post/filters/CartoonEdgeFilter.java
@@ -0,0 +1,245 @@
+/*
+ * 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.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.post.Filter;
+import com.jme3.post.Filter.Pass;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.texture.Image.Format;
+
+/**
+ * Applies a cartoon-style edge detection filter to all objects in the scene.
+ *
+ * @author Kirill Vainer
+ */
+public class CartoonEdgeFilter extends Filter {
+
+ private Pass normalPass;
+ private float edgeWidth = 1.0f;
+ private float edgeIntensity = 1.0f;
+ private float normalThreshold = 0.5f;
+ private float depthThreshold = 0.1f;
+ private float normalSensitivity = 1.0f;
+ private float depthSensitivity = 10.0f;
+ private ColorRGBA edgeColor = new ColorRGBA(0, 0, 0, 1);
+
+ /**
+ * Creates a CartoonEdgeFilter
+ */
+ public CartoonEdgeFilter() {
+ super("CartoonEdgeFilter");
+ }
+
+ @Override
+ protected boolean isRequiresDepthTexture() {
+ return true;
+ }
+
+ @Override
+ protected void postQueue(RenderManager renderManager, ViewPort viewPort) {
+ Renderer r = renderManager.getRenderer();
+ r.setFrameBuffer(normalPass.getRenderFrameBuffer());
+ renderManager.getRenderer().clearBuffers(true, true, true);
+ renderManager.setForcedTechnique("PreNormalPass");
+ renderManager.renderViewPortQueues(viewPort, false);
+ renderManager.setForcedTechnique(null);
+ renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer());
+ }
+
+ @Override
+ protected Material getMaterial() {
+ material.setTexture("NormalsTexture", normalPass.getRenderedTexture());
+ return material;
+ }
+
+ @Override
+ protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+ normalPass = new Pass();
+ normalPass.init(renderManager.getRenderer(), w, h, Format.RGBA8, Format.Depth);
+ material = new Material(manager, "Common/MatDefs/Post/CartoonEdge.j3md");
+ material.setFloat("EdgeWidth", edgeWidth);
+ material.setFloat("EdgeIntensity", edgeIntensity);
+ material.setFloat("NormalThreshold", normalThreshold);
+ material.setFloat("DepthThreshold", depthThreshold);
+ material.setFloat("NormalSensitivity", normalSensitivity);
+ material.setFloat("DepthSensitivity", depthSensitivity);
+ material.setColor("EdgeColor", edgeColor);
+ }
+
+ /**
+ * Return the depth sensitivity
+ * for more details see {@link setDepthSensitivity(float depthSensitivity)}
+ * @return
+ */
+ public float getDepthSensitivity() {
+ return depthSensitivity;
+ }
+
+ /**
+ * sets the depth sensitivity
+ * defines how much depth will influence edges, default is 10
+ * @param depthSensitivity
+ */
+ public void setDepthSensitivity(float depthSensitivity) {
+ this.depthSensitivity = depthSensitivity;
+ if (material != null) {
+ material.setFloat("DepthSensitivity", depthSensitivity);
+ }
+ }
+
+ /**
+ * returns the depth threshold
+ * for more details see {@link setDepthThreshold(float depthThreshold)}
+ * @return
+ */
+ public float getDepthThreshold() {
+ return depthThreshold;
+ }
+
+ /**
+ * sets the depth threshold
+ * Defines at what threshold of difference of depth an edge is outlined default is 0.1f
+ * @param depthThreshold
+ */
+ public void setDepthThreshold(float depthThreshold) {
+ this.depthThreshold = depthThreshold;
+ if (material != null) {
+ material.setFloat("DepthThreshold", depthThreshold);
+ }
+ }
+
+ /**
+ * returns the edge intensity
+ * for more details see {@link setEdgeIntensity(float edgeIntensity) }
+ * @return
+ */
+ public float getEdgeIntensity() {
+ return edgeIntensity;
+ }
+
+ /**
+ * sets the edge intensity
+ * Defineshow visilble will be the outlined edges
+ * @param edgeIntensity
+ */
+ public void setEdgeIntensity(float edgeIntensity) {
+ this.edgeIntensity = edgeIntensity;
+ if (material != null) {
+ material.setFloat("EdgeIntensity", edgeIntensity);
+ }
+ }
+
+ /**
+ * returns the width of the edges
+ * @return
+ */
+ public float getEdgeWidth() {
+ return edgeWidth;
+ }
+
+ /**
+ * sets the witdh of the edge in pixels default is 1
+ * @param edgeWidth
+ */
+ public void setEdgeWidth(float edgeWidth) {
+ this.edgeWidth = edgeWidth;
+ if (material != null) {
+ material.setFloat("EdgeWidth", edgeWidth);
+ }
+
+ }
+
+ /**
+ * returns the normals sensitivity
+ * form more details see {@link setNormalSensitivity(float normalSensitivity)}
+ * @return
+ */
+ public float getNormalSensitivity() {
+ return normalSensitivity;
+ }
+
+ /**
+ * sets the normals sensitivity default is 1
+ * @param normalSensitivity
+ */
+ public void setNormalSensitivity(float normalSensitivity) {
+ this.normalSensitivity = normalSensitivity;
+ if (material != null) {
+ material.setFloat("NormalSensitivity", normalSensitivity);
+ }
+ }
+
+ /**
+ * returns the normal threshold
+ * for more details see {@link setNormalThreshold(float normalThreshold)}
+ *
+ * @return
+ */
+ public float getNormalThreshold() {
+ return normalThreshold;
+ }
+
+ /**
+ * sets the normal threshold default is 0.5
+ * @param normalThreshold
+ */
+ public void setNormalThreshold(float normalThreshold) {
+ this.normalThreshold = normalThreshold;
+ if (material != null) {
+ material.setFloat("NormalThreshold", normalThreshold);
+ }
+ }
+
+ /**
+ * returns the edge color
+ * @return
+ */
+ public ColorRGBA getEdgeColor() {
+ return edgeColor;
+ }
+
+ /**
+ * Sets the edge color, default is black
+ * @param edgeColor
+ */
+ public void setEdgeColor(ColorRGBA edgeColor) {
+ this.edgeColor = edgeColor;
+ if (material != null) {
+ material.setColor("EdgeColor", edgeColor);
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/post/filters/ColorOverlayFilter.java b/engine/src/core/com/jme3/post/filters/ColorOverlayFilter.java
new file mode 100644
index 000000000..a7f30f884
--- /dev/null
+++ b/engine/src/core/com/jme3/post/filters/ColorOverlayFilter.java
@@ -0,0 +1,111 @@
+/*
+ * 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.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import java.io.IOException;
+
+/**
+ * This filter simply multiply the whole scene by a color
+ * @author Rémy Bouquet aka Nehon
+ */
+public class ColorOverlayFilter extends Filter {
+
+ private ColorRGBA color = ColorRGBA.White;
+
+ /**
+ * creates a colorOverlayFilter with a white coor (transparent)
+ */
+ public ColorOverlayFilter() {
+ super("Color Overlay");
+ }
+
+ /**
+ * creates a colorOverlayFilter with the given color
+ * @param color
+ */
+ public ColorOverlayFilter(ColorRGBA color) {
+ this();
+ this.color = color;
+ }
+
+ @Override
+ protected Material getMaterial() {
+
+ material.setColor("Color", color);
+ return material;
+ }
+
+ /**
+ * returns the color
+ * @return color
+ */
+ public ColorRGBA getColor() {
+ return color;
+ }
+
+ /**
+ * sets the color
+ * @param color
+ */
+ public void setColor(ColorRGBA color) {
+ this.color = color;
+ }
+
+ @Override
+ protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+ material = new Material(manager, "Common/MatDefs/Post/Overlay.j3md");
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(color, "color", ColorRGBA.White);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ color = (ColorRGBA) ic.readSavable("color", ColorRGBA.White);
+ }
+}
diff --git a/engine/src/core/com/jme3/post/filters/CrossHatchFilter.java b/engine/src/core/com/jme3/post/filters/CrossHatchFilter.java
new file mode 100644
index 000000000..52c204896
--- /dev/null
+++ b/engine/src/core/com/jme3/post/filters/CrossHatchFilter.java
@@ -0,0 +1,305 @@
+/*
+ * 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.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+
+/**
+ * A Post Processing filter that makes the screen look like it was drawn as
+ * diagonal lines with a pen.
+ * Try combining this with a cartoon edge filter to obtain manga style visuals.
+ *
+ * Based on an article from Geeks3D:
+ * http://www.geeks3d.com/20110219/shader-library-crosshatching-glsl-filter/
+ *
+ * @author: Roy Straver a.k.a. Baal Garnaal
+ */
+public class CrossHatchFilter extends Filter {
+
+ private ColorRGBA lineColor = ColorRGBA.Black.clone();
+ private ColorRGBA paperColor = ColorRGBA.White.clone();
+ private float colorInfluenceLine = 0.8f;
+ private float colorInfluencePaper = 0.1f;
+ private float fillValue = 0.9f;
+ private float luminance1 = 0.9f;
+ private float luminance2 = 0.7f;
+ private float luminance3 = 0.5f;
+ private float luminance4 = 0.3f;
+ private float luminance5 = 0.0f;
+ private float lineThickness = 1.0f;
+ private float lineDistance = 4.0f;
+
+ /**
+ * Creates a crossHatch filter
+ */
+ public CrossHatchFilter() {
+ super("CrossHatchFilter");
+ }
+
+ /**
+ * Creates a crossHatch filter
+ * @param lineColor the colors of the lines
+ * @param paperColor the paper color
+ */
+ public CrossHatchFilter(ColorRGBA lineColor, ColorRGBA paperColor) {
+ this();
+ this.lineColor = lineColor;
+ this.paperColor = paperColor;
+ }
+
+ @Override
+ protected boolean isRequiresDepthTexture() {
+ return false;
+ }
+
+ @Override
+ protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+ material = new Material(manager, "Common/MatDefs/Post/CrossHatch.j3md");
+ material.setColor("LineColor", lineColor);
+ material.setColor("PaperColor", paperColor);
+
+ material.setFloat("ColorInfluenceLine", colorInfluenceLine);
+ material.setFloat("ColorInfluencePaper", colorInfluencePaper);
+
+ material.setFloat("FillValue", fillValue);
+
+ material.setFloat("Luminance1", luminance1);
+ material.setFloat("Luminance2", luminance2);
+ material.setFloat("Luminance3", luminance3);
+ material.setFloat("Luminance4", luminance4);
+ material.setFloat("Luminance5", luminance5);
+
+ material.setFloat("LineThickness", lineThickness);
+ material.setFloat("LineDistance", lineDistance);
+ }
+
+ @Override
+ protected Material getMaterial() {
+ return material;
+ }
+
+ /**
+ * Sets color used to draw lines
+ * @param lineColor
+ */
+ public void setLineColor(ColorRGBA lineColor) {
+ this.lineColor = lineColor;
+ if (material != null) {
+ material.setColor("LineColor", lineColor);
+ }
+ }
+
+ /**
+ * Sets color used as background
+ * @param paperColor
+ */
+ public void setPaperColor(ColorRGBA paperColor) {
+ this.paperColor = paperColor;
+ if (material != null) {
+ material.setColor("PaperColor", paperColor);
+ }
+ }
+
+ /**
+ * Sets color influence of original image on lines drawn
+ * @param colorInfluenceLine
+ */
+ public void setColorInfluenceLine(float colorInfluenceLine) {
+ this.colorInfluenceLine = colorInfluenceLine;
+ if (material != null) {
+ material.setFloat("ColorInfluenceLine", colorInfluenceLine);
+ }
+ }
+
+ /**
+ * Sets color influence of original image on non-line areas
+ * @param colorInfluencePaper
+ */
+ public void setColorInfluencePaper(float colorInfluencePaper) {
+ this.colorInfluencePaper = colorInfluencePaper;
+ if (material != null) {
+ material.setFloat("ColorInfluencePaper", colorInfluencePaper);
+ }
+ }
+
+ /**
+ * Sets line/paper color ratio for areas with values < luminance5,
+ * really dark areas get no lines but a filled blob instead
+ * @param fillValue
+ */
+ public void setFillValue(float fillValue) {
+ this.fillValue = fillValue;
+ if (material != null) {
+ material.setFloat("FillValue", fillValue);
+ }
+ }
+
+ /**
+ *
+ * Sets minimum luminance levels for lines drawn
+ * @param luminance1 Top-left to down right 1
+ * @param luminance2 Top-right to bottom left 1
+ * @param luminance3 Top-left to down right 2
+ * @param luminance4 Top-right to bottom left 2
+ * @param luminance5 Blobs
+ */
+ public void setLuminanceLevels(float luminance1, float luminance2, float luminance3, float luminance4, float luminance5) {
+ this.luminance1 = luminance1;
+ this.luminance2 = luminance2;
+ this.luminance3 = luminance3;
+ this.luminance4 = luminance4;
+ this.luminance5 = luminance5;
+
+ if (material != null) {
+ material.setFloat("Luminance1", luminance1);
+ material.setFloat("Luminance2", luminance2);
+ material.setFloat("Luminance3", luminance3);
+ material.setFloat("Luminance4", luminance4);
+ material.setFloat("Luminance5", luminance5);
+ }
+ }
+
+ /**
+ * Sets the thickness of lines drawn
+ * @param lineThickness
+ */
+ public void setLineThickness(float lineThickness) {
+ this.lineThickness = lineThickness;
+ if (material != null) {
+ material.setFloat("LineThickness", lineThickness);
+ }
+ }
+
+ /**
+ * Sets minimum distance between lines drawn
+ * Primary lines are drawn at 2*lineDistance
+ * Secondary lines are drawn at lineDistance
+ * @param lineDistance
+ */
+ public void setLineDistance(float lineDistance) {
+ this.lineDistance = lineDistance;
+ if (material != null) {
+ material.setFloat("LineDistance", lineDistance);
+ }
+ }
+
+ /**
+ * Returns line color
+ * @return
+ */
+ public ColorRGBA getLineColor() {
+ return lineColor;
+ }
+
+ /**
+ * Returns paper background color
+ * @return
+ */
+ public ColorRGBA getPaperColor() {
+ return paperColor;
+ }
+
+ /**
+ * Returns current influence of image colors on lines
+ */
+ public float getColorInfluenceLine() {
+ return colorInfluenceLine;
+ }
+
+ /**
+ * Returns current influence of image colors on paper background
+ */
+ public float getColorInfluencePaper() {
+ return colorInfluencePaper;
+ }
+
+ /**
+ * Returns line/paper color ratio for blobs
+ */
+ public float getFillValue() {
+ return fillValue;
+ }
+
+ /**
+ * Returns the thickness of the lines drawn
+ */
+ public float getLineThickness() {
+ return lineThickness;
+ }
+
+ /**
+ * Returns minimum distance between lines
+ */
+ public float getLineDistance() {
+ return lineDistance;
+ }
+
+ /**
+ * Returns treshold for lines 1
+ */
+ public float getLuminance1() {
+ return luminance1;
+ }
+
+ /**
+ * Returns treshold for lines 2
+ */
+ public float getLuminance2() {
+ return luminance2;
+ }
+
+ /**
+ * Returns treshold for lines 3
+ */
+ public float getLuminance3() {
+ return luminance3;
+ }
+
+ /**
+ * Returns treshold for lines 4
+ */
+ public float getLuminance4() {
+ return luminance4;
+ }
+
+ /**
+ * Returns treshold for blobs
+ */
+ public float getLuminance5() {
+ return luminance5;
+ }
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/post/filters/DepthOfFieldFilter.java b/engine/src/core/com/jme3/post/filters/DepthOfFieldFilter.java
new file mode 100644
index 000000000..55591c988
--- /dev/null
+++ b/engine/src/core/com/jme3/post/filters/DepthOfFieldFilter.java
@@ -0,0 +1,158 @@
+/*
+ * 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.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+
+/**
+ * A post-processing filter that performs a depth range
+ * blur using a scaled convolution filter.
+ *
+ * @version $Revision: 779 $
+ * @author Paul Speed
+ */
+public class DepthOfFieldFilter extends Filter {
+
+ private float focusDistance = 50f;
+ private float focusRange = 10f;
+ private float blurScale = 1f;
+ // These values are set internally based on the
+ // viewport size.
+ private float xScale;
+ private float yScale;
+
+ /**
+ * Creates a DepthOfField filter
+ */
+ public DepthOfFieldFilter() {
+ super("Depth Of Field");
+ }
+
+ @Override
+ protected boolean isRequiresDepthTexture() {
+ return true;
+ }
+
+ @Override
+ protected Material getMaterial() {
+
+ return material;
+ }
+
+ @Override
+ protected void initFilter(AssetManager assets, RenderManager renderManager,
+ ViewPort vp, int w, int h) {
+ material = new Material(assets, "Common/MatDefs/Post/DepthOfField.j3md");
+ material.setFloat("FocusDistance", focusDistance);
+ material.setFloat("FocusRange", focusRange);
+
+
+ xScale = 1.0f / w;
+ yScale = 1.0f / h;
+
+ material.setFloat("XScale", blurScale * xScale);
+ material.setFloat("YScale", blurScale * yScale);
+ }
+
+ /**
+ * Sets the distance at which objects are purely in focus.
+ */
+ public void setFocusDistance(float f) {
+
+ this.focusDistance = f;
+ if (material != null) {
+ material.setFloat("FocusDistance", focusDistance);
+ }
+
+ }
+
+ /**
+ * returns the focus distance
+ * @return
+ */
+ public float getFocusDistance() {
+ return focusDistance;
+ }
+
+ /**
+ * Sets the range to either side of focusDistance where the
+ * objects go gradually out of focus. Less than focusDistance - focusRange
+ * and greater than focusDistance + focusRange, objects are maximally "blurred".
+ */
+ public void setFocusRange(float f) {
+ this.focusRange = f;
+ if (material != null) {
+ material.setFloat("FocusRange", focusRange);
+ }
+
+ }
+
+ /**
+ * returns the focus range
+ * @return
+ */
+ public float getFocusRange() {
+ return focusRange;
+ }
+
+ /**
+ * Sets the blur amount by scaling the convolution filter up or
+ * down. A value of 1 (the default) performs a sparse 5x5 evenly
+ * distribubted convolution at pixel level accuracy. Higher values skip
+ * more pixels, and so on until you are no longer blurring the image
+ * but simply hashing it.
+ *
+ * The sparse convolution is as follows:
+ *%MINIFYHTMLc3d0cd9fab65de6875a381fd3f83e1b338%*
+ * Where 'x' is the texel being modified. Setting blur scale higher
+ * than 1 spaces the samples out.
+ */
+ public void setBlurScale(float f) {
+ this.blurScale = f;
+ if (material != null) {
+ material.setFloat("XScale", blurScale * xScale);
+ material.setFloat("YScale", blurScale * yScale);
+ }
+ }
+
+ /**
+ * returns the blur scale
+ * @return
+ */
+ public float getBlurScale() {
+ return blurScale;
+ }
+}
diff --git a/engine/src/core/com/jme3/post/filters/FXAAFilter.java b/engine/src/core/com/jme3/post/filters/FXAAFilter.java
new file mode 100644
index 000000000..8ba3c16bb
--- /dev/null
+++ b/engine/src/core/com/jme3/post/filters/FXAAFilter.java
@@ -0,0 +1,95 @@
+package com.jme3.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+
+/**
+ * http://www.geeks3d.com/20110405/fxaa-fast-approximate-anti-aliasing-demo-glsl-opengl-test-radeon-geforce/3/
+ * http://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf
+ *
+ * @author Phate666 (adapted to jme3)
+ *
+ */
+public class FXAAFilter extends Filter {
+
+ private float subPixelShift = 1.0f / 4.0f;
+ private float vxOffset = 0.0f;
+ private float spanMax = 8.0f;
+ private float reduceMul = 1.0f / 8.0f;
+
+ public FXAAFilter() {
+ super("FXAAFilter");
+ }
+
+ @Override
+ protected void initFilter(AssetManager manager,
+ RenderManager renderManager, ViewPort vp, int w, int h) {
+ material = new Material(manager, "Common/MatDefs/Post/FXAA.j3md");
+ material.setFloat("SubPixelShift", subPixelShift);
+ material.setFloat("VxOffset", vxOffset);
+ material.setFloat("SpanMax", spanMax);
+ material.setFloat("ReduceMul", reduceMul);
+ }
+
+ @Override
+ protected Material getMaterial() {
+ return material;
+ }
+
+ public void setSpanMax(float spanMax) {
+ this.spanMax = spanMax;
+ if (material != null) {
+ material.setFloat("SpanMax", this.spanMax);
+ }
+ }
+
+ /**
+ * set to 0.0f for higher quality
+ *
+ * @param subPixelShift
+ */
+ public void setSubPixelShift(float subPixelShift) {
+ this.subPixelShift = subPixelShift;
+ if (material != null) {
+ material.setFloat("SubPixelShif", this.subPixelShift);
+ }
+ }
+
+ /**
+ * set to 0.0f for higher quality
+ *
+ * @param reduceMul
+ */
+ public void setReduceMul(float reduceMul) {
+ this.reduceMul = reduceMul;
+ if (material != null) {
+ material.setFloat("ReduceMul", this.reduceMul);
+ }
+ }
+
+ public void setVxOffset(float vxOffset) {
+ this.vxOffset = vxOffset;
+ if (material != null) {
+ material.setFloat("VxOffset", this.vxOffset);
+ }
+ }
+
+ public float getReduceMul() {
+ return reduceMul;
+ }
+
+ public float getSpanMax() {
+ return spanMax;
+ }
+
+ public float getSubPixelShift() {
+ return subPixelShift;
+ }
+
+ public float getVxOffset() {
+ return vxOffset;
+ }
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/post/filters/FadeFilter.java b/engine/src/core/com/jme3/post/filters/FadeFilter.java
new file mode 100644
index 000000000..902bc8901
--- /dev/null
+++ b/engine/src/core/com/jme3/post/filters/FadeFilter.java
@@ -0,0 +1,170 @@
+/*
+ * 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.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import java.io.IOException;
+
+/**
+ *
+ * Fade Filter allows you to make an animated fade effect on a scene.
+ * @author Rémy Bouquet aka Nehon
+ * implemented from boxjar implementation
+ * @see http://jmonkeyengine.org/groups/graphics/forum/topic/newbie-question-general-fade-inout-effect/#post-105559
+ */
+public class FadeFilter extends Filter {
+
+ private float value = 1;
+ private boolean playing = false;
+ private float direction = 1;
+ private float duration = 1;
+
+ /**
+ * Creates a FadeFilter
+ */
+ public FadeFilter() {
+ super("Fade In/Out");
+ }
+
+ /**
+ * Creates a FadeFilter with the given duration
+ * @param duration
+ */
+ public FadeFilter(float duration) {
+ this();
+ this.duration = duration;
+ }
+
+ @Override
+ protected Material getMaterial() {
+ material.setFloat("Value", value);
+ return material;
+ }
+
+ @Override
+ protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+ material = new Material(manager, "Common/MatDefs/Post/Fade.j3md");
+ }
+
+ @Override
+ protected void preFrame(float tpf) {
+ if (playing) {
+ value += tpf * direction / duration;
+
+ if (direction > 0 && value > 1) {
+ value = 1;
+ playing = false;
+ setEnabled(false);
+ }
+ if (direction < 0 && value < 0) {
+ value = 0;
+ playing = false;
+ setEnabled(false);
+ }
+ }
+ }
+
+ /**
+ * returns the duration of the effect
+ * @return
+ */
+ public float getDuration() {
+ return duration;
+ }
+
+ /**
+ * Sets the duration of the filter default is 1 second
+ * @param duration
+ */
+ public void setDuration(float duration) {
+ this.duration = duration;
+ }
+
+ /**
+ * fades the scene in (black to scene)
+ */
+ public void fadeIn() {
+ setEnabled(true);
+ direction = 1;
+ playing = true;
+ }
+
+ /**
+ * fades the scene out (scene to black)
+ */
+ public void fadeOut() {
+ setEnabled(true);
+ direction = -1;
+ playing = true;
+
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(duration, "duration", 1);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ duration = ic.readFloat("duration", 1);
+ }
+
+ /**
+ * return the current value of the fading
+ * can be used to chack if fade is complete (eg value=1)
+ * @return
+ */
+ public float getValue() {
+ return value;
+ }
+
+ /**
+ * sets the fade value
+ * can be used to force complete black or compete scene
+ * @param value
+ */
+ public void setValue(float value) {
+ this.value = value;
+ }
+}
diff --git a/engine/src/core/com/jme3/post/filters/FogFilter.java b/engine/src/core/com/jme3/post/filters/FogFilter.java
new file mode 100644
index 000000000..ff617970a
--- /dev/null
+++ b/engine/src/core/com/jme3/post/filters/FogFilter.java
@@ -0,0 +1,172 @@
+/*
+ * 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.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import java.io.IOException;
+
+/**
+ * A filter to render a fog effect
+ * @author Rémy Bouquet aka Nehon
+ */
+public class FogFilter extends Filter {
+
+ private ColorRGBA fogColor = ColorRGBA.White.clone();
+ private float fogDensity = 0.7f;
+ private float fogDistance = 1000;
+
+ /**
+ * Creates a FogFilter
+ */
+ public FogFilter() {
+ super("FogFilter");
+ }
+
+ /**
+ * Create a fog filter
+ * @param fogColor the color of the fog (default is white)
+ * @param fogDensity the density of the fog (default is 0.7)
+ * @param fogDistance the distance of the fog (default is 1000)
+ */
+ public FogFilter(ColorRGBA fogColor, float fogDensity, float fogDistance) {
+ this();
+ this.fogColor = fogColor;
+ this.fogDensity = fogDensity;
+ this.fogDistance = fogDistance;
+ }
+
+ @Override
+ protected boolean isRequiresDepthTexture() {
+ return true;
+ }
+
+ @Override
+ protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+ material = new Material(manager, "Common/MatDefs/Post/Fog.j3md");
+ material.setColor("FogColor", fogColor);
+ material.setFloat("FogDensity", fogDensity);
+ material.setFloat("FogDistance", fogDistance);
+ }
+
+ @Override
+ protected Material getMaterial() {
+
+ return material;
+ }
+
+
+ /**
+ * returns the fog color
+ * @return
+ */
+ public ColorRGBA getFogColor() {
+ return fogColor;
+ }
+
+ /**
+ * Sets the color of the fog
+ * @param fogColor
+ */
+ public void setFogColor(ColorRGBA fogColor) {
+ if (material != null) {
+ material.setColor("FogColor", fogColor);
+ }
+ this.fogColor = fogColor;
+ }
+
+ /**
+ * returns the fog density
+ * @return
+ */
+ public float getFogDensity() {
+ return fogDensity;
+ }
+
+ /**
+ * Sets the density of the fog, a high value gives a thick fog
+ * @param fogColor
+ */
+ public void setFogDensity(float fogDensity) {
+ if (material != null) {
+ material.setFloat("FogDensity", fogDensity);
+ }
+ this.fogDensity = fogDensity;
+ }
+
+ /**
+ * returns the fog distance
+ * @return
+ */
+ public float getFogDistance() {
+ return fogDistance;
+ }
+
+ /**
+ * the distance of the fog. the higer the value the distant the fog looks
+ * @param fogDistance
+ */
+ public void setFogDistance(float fogDistance) {
+ if (material != null) {
+ material.setFloat("FogDistance", fogDistance);
+ }
+ this.fogDistance = fogDistance;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(fogColor, "fogColor", ColorRGBA.White.clone());
+ oc.write(fogDensity, "fogDensity", 0.7f);
+ oc.write(fogDistance, "fogDistance", 1000);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ fogColor = (ColorRGBA) ic.readSavable("fogColor", ColorRGBA.White.clone());
+ fogDensity = ic.readFloat("fogDensity", 0.7f);
+ fogDistance = ic.readFloat("fogDistance", 1000);
+ }
+
+
+}
diff --git a/engine/src/core/com/jme3/post/filters/GammaCorrectionFilter.java b/engine/src/core/com/jme3/post/filters/GammaCorrectionFilter.java
new file mode 100644
index 000000000..9e283ca13
--- /dev/null
+++ b/engine/src/core/com/jme3/post/filters/GammaCorrectionFilter.java
@@ -0,0 +1,78 @@
+package com.jme3.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+
+/**
+ *
+ * @author Phate666
+ * @version 1.0 initial version
+ * @version 1.1 added luma
+ */
+public class GammaCorrectionFilter extends Filter
+{
+ private float gamma = 2.0f;
+ private boolean computeLuma = false;
+
+ public GammaCorrectionFilter()
+ {
+ super("GammaCorrectionFilter");
+ }
+
+ public GammaCorrectionFilter(float gamma)
+ {
+ this();
+ this.setGamma(gamma);
+ }
+
+ @Override
+ protected Material getMaterial()
+ {
+ return material;
+ }
+
+ @Override
+ protected void initFilter(AssetManager manager,
+ RenderManager renderManager, ViewPort vp, int w, int h)
+ {
+ material = new Material(manager,
+ "Common/MatDefs/Post/GammaCorrection.j3md");
+ material.setFloat("gamma", gamma);
+ material.setBoolean("computeLuma", computeLuma);
+ }
+
+ public float getGamma()
+ {
+ return gamma;
+ }
+
+ /**
+ * set to 0.0 to disable gamma correction
+ * @param gamma
+ */
+ public void setGamma(float gamma)
+ {
+ if (material != null)
+ {
+ material.setFloat("gamma", gamma);
+ }
+ this.gamma = gamma;
+ }
+
+ public boolean isComputeLuma()
+ {
+ return computeLuma;
+ }
+
+ public void setComputeLuma(boolean computeLuma)
+ {
+ if (material != null)
+ {
+ material.setBoolean("computeLuma", computeLuma);
+ }
+ this.computeLuma = computeLuma;
+ }
+}
diff --git a/engine/src/core/com/jme3/post/filters/LightScatteringFilter.java b/engine/src/core/com/jme3/post/filters/LightScatteringFilter.java
new file mode 100644
index 000000000..953f10a66
--- /dev/null
+++ b/engine/src/core/com/jme3/post/filters/LightScatteringFilter.java
@@ -0,0 +1,243 @@
+/*
+ * 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.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.post.Filter;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import java.io.IOException;
+
+/**
+ * LightScattering filters creates rays comming from a light sources
+ * This is often reffered as god rays.
+ *
+ * @author Rémy Bouquet aka Nehon
+ */
+public class LightScatteringFilter extends Filter {
+
+ private Vector3f lightPosition;
+ private Vector3f screenLightPos = new Vector3f();
+ private int nbSamples = 50;
+ private float blurStart = 0.02f;
+ private float blurWidth = 0.9f;
+ private float lightDensity = 1.4f;
+ private boolean adaptative = true;
+ Vector3f viewLightPos = new Vector3f();
+ private boolean display = true;
+ private float innerLightDensity;
+
+ /**
+ * creates a lightScaterring filter
+ */
+ public LightScatteringFilter() {
+ super("Light Scattering");
+ }
+
+ /**
+ * Creates a lightScatteringFilter
+ * @param lightPosition
+ */
+ public LightScatteringFilter(Vector3f lightPosition) {
+ this();
+ this.lightPosition = lightPosition;
+ }
+
+ @Override
+ protected boolean isRequiresDepthTexture() {
+ return true;
+ }
+
+ @Override
+ protected Material getMaterial() {
+ material.setVector3("LightPosition", screenLightPos);
+ material.setInt("NbSamples", nbSamples);
+ material.setFloat("BlurStart", blurStart);
+ material.setFloat("BlurWidth", blurWidth);
+ material.setFloat("LightDensity", innerLightDensity);
+ material.setBoolean("Display", display);
+ return material;
+ }
+
+ @Override
+ protected void postQueue(RenderManager renderManager, ViewPort viewPort) {
+ getClipCoordinates(lightPosition, screenLightPos, viewPort.getCamera());
+ // screenLightPos.x = screenLightPos.x / viewPort.getCamera().getWidth();
+ // screenLightPos.y = screenLightPos.y / viewPort.getCamera().getHeight();
+
+ viewPort.getCamera().getViewMatrix().mult(lightPosition, viewLightPos);
+ //System.err.println("viewLightPos "+viewLightPos);
+ display = screenLightPos.x < 1.6f && screenLightPos.x > -0.6f && screenLightPos.y < 1.6f && screenLightPos.y > -0.6f && viewLightPos.z < 0;
+//System.err.println("camdir "+viewPort.getCamera().getDirection());
+//System.err.println("lightPos "+lightPosition);
+//System.err.println("screenLightPos "+screenLightPos);
+ if (adaptative) {
+ innerLightDensity = Math.max(lightDensity - Math.max(screenLightPos.x, screenLightPos.y), 0.0f);
+ } else {
+ innerLightDensity = lightDensity;
+ }
+ }
+
+ private Vector3f getClipCoordinates(Vector3f worldPosition, Vector3f store, Camera cam) {
+
+ float w = cam.getViewProjectionMatrix().multProj(worldPosition, store);
+ store.divideLocal(w);
+
+ store.x = ((store.x + 1f) * (cam.getViewPortRight() - cam.getViewPortLeft()) / 2f + cam.getViewPortLeft());
+ store.y = ((store.y + 1f) * (cam.getViewPortTop() - cam.getViewPortBottom()) / 2f + cam.getViewPortBottom());
+ store.z = (store.z + 1f) / 2f;
+
+ return store;
+ }
+
+ @Override
+ protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+ material = new Material(manager, "Common/MatDefs/Post/LightScattering.j3md");
+ }
+
+ /**
+ * returns the blur start of the scattering
+ * see {@link setBlurStart(float blurStart)}
+ * @return
+ */
+ public float getBlurStart() {
+ return blurStart;
+ }
+
+ /**
+ * sets the blur start
+ * at which distance from the light source the effect starts default is 0.02
+ * @param blurStart
+ */
+ public void setBlurStart(float blurStart) {
+ this.blurStart = blurStart;
+ }
+
+ /**
+ * returns the blur width
+ * see {@link setBlurWidth(float blurWidth)}
+ * @return
+ */
+ public float getBlurWidth() {
+ return blurWidth;
+ }
+
+ /**
+ * sets the blur width default is 0.9
+ * @param blurWidth
+ */
+ public void setBlurWidth(float blurWidth) {
+ this.blurWidth = blurWidth;
+ }
+
+ /**
+ * retiurns the light density
+ * see {@link setLightDensity(float lightDensity)}
+ *
+ * @return
+ */
+ public float getLightDensity() {
+ return lightDensity;
+ }
+
+ /**
+ * sets how much the effect is visible over the rendered scene default is 1.4
+ * @param lightDensity
+ */
+ public void setLightDensity(float lightDensity) {
+ this.lightDensity = lightDensity;
+ }
+
+ /**
+ * returns the light position
+ * @return
+ */
+ public Vector3f getLightPosition() {
+ return lightPosition;
+ }
+
+ /**
+ * sets the light position
+ * @param lightPosition
+ */
+ public void setLightPosition(Vector3f lightPosition) {
+ this.lightPosition = lightPosition;
+ }
+
+ /**
+ * returns the nmber of samples for the radial blur
+ * @return
+ */
+ public int getNbSamples() {
+ return nbSamples;
+ }
+
+ /**
+ * sets the number of samples for the radial blur default is 50
+ * the higher the value the higher the quality, but the slower the performances.
+ * @param nbSamples
+ */
+ public void setNbSamples(int nbSamples) {
+ this.nbSamples = nbSamples;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(lightPosition, "lightPosition", Vector3f.ZERO);
+ oc.write(nbSamples, "nbSamples", 50);
+ oc.write(blurStart, "blurStart", 0.02f);
+ oc.write(blurWidth, "blurWidth", 0.9f);
+ oc.write(lightDensity, "lightDensity", 1.4f);
+ oc.write(adaptative, "adaptative", true);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ lightPosition = (Vector3f) ic.readSavable("lightPosition", Vector3f.ZERO);
+ nbSamples = ic.readInt("nbSamples", 50);
+ blurStart = ic.readFloat("blurStart", 0.02f);
+ blurWidth = ic.readFloat("blurWidth", 0.9f);
+ lightDensity = ic.readFloat("lightDensity", 1.4f);
+ adaptative = ic.readBoolean("adaptative", true);
+ }
+}
diff --git a/engine/src/core/com/jme3/post/filters/PosterizationFilter.java b/engine/src/core/com/jme3/post/filters/PosterizationFilter.java
new file mode 100644
index 000000000..c980edaee
--- /dev/null
+++ b/engine/src/core/com/jme3/post/filters/PosterizationFilter.java
@@ -0,0 +1,147 @@
+/*
+ * 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.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+
+/**
+ * A Post Processing filter to change colors appear with sharp edges as if the
+ * available amount of colors available was not enough to draw the true image.
+ * Possibly useful in cartoon styled games. Use the strength variable to lessen
+ * influence of this filter on the total result. Values from 0.2 to 0.7 appear
+ * to give nice results.
+ *
+ * Based on an article from Geeks3D:
+ * http://www.geeks3d.com/20091027/shader-library-posterization-post-processing-effect-glsl/
+ *
+ * @author: Roy Straver a.k.a. Baal Garnaal
+ */
+public class PosterizationFilter extends Filter {
+
+ private int numColors = 8;
+ private float gamma = 0.6f;
+ private float strength = 1.0f;
+
+ /**
+ * Creates a posterization Filter
+ */
+ public PosterizationFilter() {
+ super("PosterizationFilter");
+ }
+
+ /**
+ * Creates a posterization Filter with the given number of colors
+ * @param numColors
+ */
+ public PosterizationFilter(int numColors) {
+ this();
+ this.numColors = numColors;
+ }
+
+ /**
+ * Creates a posterization Filter with the given number of colors and gamma
+ * @param numColors
+ * @param gamma
+ */
+ public PosterizationFilter(int numColors, float gamma) {
+ this(numColors);
+ this.gamma = gamma;
+ }
+
+ @Override
+ protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+ material = new Material(manager, "Common/MatDefs/Post/Posterization.j3md");
+ material.setInt("NumColors", numColors);
+ material.setFloat("Gamma", gamma);
+ material.setFloat("Strength", strength);
+ }
+
+ @Override
+ protected Material getMaterial() {
+ return material;
+ }
+
+ /**
+ * Sets number of color levels used to draw the screen
+ */
+ public void setNumColors(int numColors) {
+ this.numColors = numColors;
+ if (material != null) {
+ material.setInt("NumColors", numColors);
+ }
+ }
+
+ /**
+ * Sets gamma level used to enhange visual quality
+ */
+ public void setGamma(float gamma) {
+ this.gamma = gamma;
+ if (material != null) {
+ material.setFloat("Gamma", gamma);
+ }
+ }
+
+ /**
+ * Sets urrent strength value, i.e. influence on final image
+ */
+ public void setStrength(float strength) {
+ this.strength = strength;
+ if (material != null) {
+ material.setFloat("Strength", strength);
+ }
+ }
+
+ /**
+ * Returns number of color levels used
+ */
+ public int getNumColors() {
+ return numColors;
+ }
+
+ /**
+ * Returns current gamma value
+ */
+ public float getGamma() {
+ return gamma;
+ }
+
+ /**
+ * Returns current strength value, i.e. influence on final image
+ */
+ public float getStrength() {
+ return strength;
+ }
+}
\ No newline at end of file
diff --git a/engine/src/core/com/jme3/post/filters/RadialBlurFilter.java b/engine/src/core/com/jme3/post/filters/RadialBlurFilter.java
new file mode 100644
index 000000000..db0b4ed5f
--- /dev/null
+++ b/engine/src/core/com/jme3/post/filters/RadialBlurFilter.java
@@ -0,0 +1,156 @@
+/*
+ * 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.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.shader.VarType;
+import java.io.IOException;
+
+/**
+ * Radially blurs the scene from the center of it
+ * @author Rémy Bouquet aka Nehon
+ */
+public class RadialBlurFilter extends Filter {
+
+ private float sampleDist = 1.0f;
+ private float sampleStrength = 2.2f;
+ private float[] samples = {-0.08f, -0.05f, -0.03f, -0.02f, -0.01f, 0.01f, 0.02f, 0.03f, 0.05f, 0.08f};
+
+ /**
+ * Creates a RadialBlurFilter
+ */
+ public RadialBlurFilter() {
+ super("Radial blur");
+ }
+
+ /**
+ * Creates a RadialBlurFilter
+ * @param sampleDist the distance between samples
+ * @param sampleStrength the strenght of each sample
+ */
+ public RadialBlurFilter(float sampleDist, float sampleStrength) {
+ this();
+ this.sampleDist = sampleDist;
+ this.sampleStrength = sampleStrength;
+ }
+
+ @Override
+ protected Material getMaterial() {
+
+ material.setFloat("SampleDist", sampleDist);
+ material.setFloat("SampleStrength", sampleStrength);
+ material.setParam("Samples", VarType.FloatArray, samples);
+
+ return material;
+ }
+
+ /**
+ * return the sample distance
+ * @return
+ */
+ public float getSampleDistance() {
+ return sampleDist;
+ }
+
+ /**
+ * sets the samples distances default is 1
+ * @param sampleDist
+ */
+ public void setSampleDistance(float sampleDist) {
+ this.sampleDist = sampleDist;
+ }
+
+ /**
+ *
+ * @return
+ * @deprecated use {@link getSampleDistance()}
+ */
+ @Deprecated
+ public float getSampleDist() {
+ return sampleDist;
+ }
+
+ /**
+ *
+ * @param sampleDist
+ * @deprecated use {@link setSampleDistance(float sampleDist)}
+ */
+ @Deprecated
+ public void setSampleDist(float sampleDist) {
+ this.sampleDist = sampleDist;
+ }
+
+ /**
+ * Returns the sample Strength
+ * @return
+ */
+ public float getSampleStrength() {
+ return sampleStrength;
+ }
+
+ /**
+ * sets the sample streanght default is 2.2
+ * @param sampleStrength
+ */
+ public void setSampleStrength(float sampleStrength) {
+ this.sampleStrength = sampleStrength;
+ }
+
+ @Override
+ protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+ material = new Material(manager, "Common/MatDefs/Blur/RadialBlur.j3md");
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(sampleDist, "sampleDist", 1.0f);
+ oc.write(sampleStrength, "sampleStrength", 2.2f);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ sampleDist = ic.readFloat("sampleDist", 1.0f);
+ sampleStrength = ic.readFloat("sampleStrength", 2.2f);
+ }
+}
diff --git a/engine/src/core/com/jme3/post/filters/TranslucentBucketFilter.java b/engine/src/core/com/jme3/post/filters/TranslucentBucketFilter.java
new file mode 100644
index 000000000..47be41382
--- /dev/null
+++ b/engine/src/core/com/jme3/post/filters/TranslucentBucketFilter.java
@@ -0,0 +1,80 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.post.filters;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.post.Filter;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Texture2D;
+
+/**
+ * A filter to handle translucent objects when rendering a scene with filters that uses depth like WaterFilter and SSAOFilter
+ * just create a TranslucentBucketFilter and add it to the Filter list of a FilterPostPorcessor
+ * @author Nehon
+ */
+public final class TranslucentBucketFilter extends Filter {
+
+ private RenderManager renderManager;
+
+ @Override
+ protected void initFilter(AssetManager manager, RenderManager rm, ViewPort vp, int w, int h) {
+ this.renderManager = rm;
+ material = new Material(manager, "Common/MatDefs/Post/Overlay.j3md");
+ material.setColor("Color", ColorRGBA.White);
+ Texture2D tex = processor.getFilterTexture();
+ material.setTexture("Texture", tex);
+ if (tex.getImage().getMultiSamples() > 1) {
+ material.setInt("NumSamples", tex.getImage().getMultiSamples());
+ } else {
+ material.clearParam("NumSamples");
+ }
+ renderManager.setHandleTranslucentBucket(false);
+ }
+
+ /**
+ * Override this method and return false if your Filter does not need the scene texture
+ * @return
+ */
+ @Override
+ protected boolean isRequiresSceneTexture() {
+ return false;
+ }
+
+ @Override
+ protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
+ renderManager.setCamera(viewPort.getCamera(), false);
+ if (prevFilterBuffer != sceneBuffer) {
+ renderManager.getRenderer().copyFrameBuffer(prevFilterBuffer, sceneBuffer, false);
+ }
+ renderManager.getRenderer().setFrameBuffer(sceneBuffer);
+ viewPort.getQueue().renderQueue(RenderQueue.Bucket.Translucent, renderManager, viewPort.getCamera());
+ }
+
+ @Override
+ protected void cleanUpFilter(Renderer r) {
+ if (renderManager != null) {
+ renderManager.setHandleTranslucentBucket(true);
+ }
+ }
+
+ @Override
+ protected Material getMaterial() {
+ return material;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ if (renderManager != null) {
+ renderManager.setHandleTranslucentBucket(!enabled);
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/post/ssao/SSAOFilter.java b/engine/src/core/com/jme3/post/ssao/SSAOFilter.java
new file mode 100644
index 000000000..4336ede4a
--- /dev/null
+++ b/engine/src/core/com/jme3/post/ssao/SSAOFilter.java
@@ -0,0 +1,326 @@
+/*
+ * 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.post.ssao;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.post.Filter;
+import com.jme3.post.Filter.Pass;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.shader.VarType;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * SSAO stands for screen space ambient occlusion
+ * It's a technique that fake ambient lighting by computing shadows that near by objects would casts on each others
+ * under the effect of an ambient light
+ * more info on this in this blog post http://jmonkeyengine.org/2010/08/16/screen-space-ambient-occlusion-for-jmonkeyengine-3-0/
+ *
+ * @author Rémy Bouquet aka Nehon
+ */
+public class SSAOFilter extends Filter {
+
+ private Pass normalPass;
+ private Vector3f frustumCorner;
+ private Vector2f frustumNearFar;
+ private Vector2f[] samples = {new Vector2f(1.0f, 0.0f), new Vector2f(-1.0f, 0.0f), new Vector2f(0.0f, 1.0f), new Vector2f(0.0f, -1.0f)};
+ private float sampleRadius = 5.1f;
+ private float intensity = 1.5f;
+ private float scale = 0.2f;
+ private float bias = 0.1f;
+ private boolean useOnlyAo = false;
+ private boolean useAo = true;
+ private Material ssaoMat;
+ private Pass ssaoPass;
+// private Material downSampleMat;
+// private Pass downSamplePass;
+ private float downSampleFactor = 1f;
+
+ /**
+ * Create a Screen Space Ambient Occlusion Filter
+ */
+ public SSAOFilter() {
+ super("SSAOFilter");
+ }
+
+ /**
+ * Create a Screen Space Ambient Occlusion Filter
+ * @param sampleRadius The radius of the area where random samples will be picked. default 5.1f
+ * @param intensity intensity of the resulting AO. default 1.2f
+ * @param scale distance between occluders and occludee. default 0.2f
+ * @param bias the width of the occlusion cone considered by the occludee. default 0.1f
+ */
+ public SSAOFilter(float sampleRadius, float intensity, float scale, float bias) {
+ this();
+ this.sampleRadius = sampleRadius;
+ this.intensity = intensity;
+ this.scale = scale;
+ this.bias = bias;
+ }
+
+ @Override
+ protected boolean isRequiresDepthTexture() {
+ return true;
+ }
+
+ @Override
+ protected void postQueue(RenderManager renderManager, ViewPort viewPort) {
+ Renderer r = renderManager.getRenderer();
+ r.setFrameBuffer(normalPass.getRenderFrameBuffer());
+ renderManager.getRenderer().clearBuffers(true, true, true);
+ renderManager.setForcedTechnique("PreNormalPass");
+ renderManager.renderViewPortQueues(viewPort, false);
+ renderManager.setForcedTechnique(null);
+ renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer());
+ }
+
+ @Override
+ protected Material getMaterial() {
+ return material;
+ }
+
+ @Override
+ protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+ int screenWidth = w;
+ int screenHeight = h;
+ postRenderPasses = new ArrayList();
+
+ normalPass = new Pass();
+ normalPass.init(renderManager.getRenderer(), (int) (screenWidth / downSampleFactor), (int) (screenHeight / downSampleFactor), Format.RGBA8, Format.Depth);
+
+
+ frustumNearFar = new Vector2f();
+
+ float farY = (vp.getCamera().getFrustumTop() / vp.getCamera().getFrustumNear()) * vp.getCamera().getFrustumFar();
+ float farX = farY * ((float) screenWidth / (float) screenHeight);
+ frustumCorner = new Vector3f(farX, farY, vp.getCamera().getFrustumFar());
+ frustumNearFar.x = vp.getCamera().getFrustumNear();
+ frustumNearFar.y = vp.getCamera().getFrustumFar();
+
+
+
+
+
+ //ssao Pass
+ ssaoMat = new Material(manager, "Common/MatDefs/SSAO/ssao.j3md");
+ ssaoMat.setTexture("Normals", normalPass.getRenderedTexture());
+ Texture random = manager.loadTexture("Common/MatDefs/SSAO/Textures/random.png");
+ random.setWrap(Texture.WrapMode.Repeat);
+ ssaoMat.setTexture("RandomMap", random);
+
+ ssaoPass = new Pass() {
+
+ @Override
+ public boolean requiresDepthAsTexture() {
+ return true;
+ }
+ };
+
+ ssaoPass.init(renderManager.getRenderer(), (int) (screenWidth / downSampleFactor), (int) (screenHeight / downSampleFactor), Format.RGBA8, Format.Depth, 1, ssaoMat);
+ ssaoPass.getRenderedTexture().setMinFilter(Texture.MinFilter.Trilinear);
+ ssaoPass.getRenderedTexture().setMagFilter(Texture.MagFilter.Bilinear);
+ postRenderPasses.add(ssaoPass);
+ material = new Material(manager, "Common/MatDefs/SSAO/ssaoBlur.j3md");
+ material.setTexture("SSAOMap", ssaoPass.getRenderedTexture());
+
+ ssaoMat.setVector3("FrustumCorner", frustumCorner);
+ ssaoMat.setFloat("SampleRadius", sampleRadius);
+ ssaoMat.setFloat("Intensity", intensity);
+ ssaoMat.setFloat("Scale", scale);
+ ssaoMat.setFloat("Bias", bias);
+ material.setBoolean("UseAo", useAo);
+ material.setBoolean("UseOnlyAo", useOnlyAo);
+ ssaoMat.setVector2("FrustumNearFar", frustumNearFar);
+ material.setVector2("FrustumNearFar", frustumNearFar);
+ ssaoMat.setParam("Samples", VarType.Vector2Array, samples);
+
+ float xScale = 1.0f / w;
+ float yScale = 1.0f / h;
+
+ float blurScale = 2f;
+ material.setFloat("XScale", blurScale * xScale);
+ material.setFloat("YScale", blurScale * yScale);
+
+ }
+
+ /**
+ * Return the bias
+ * see {@link setBias(float bias)}
+ * @return
+ */
+ public float getBias() {
+ return bias;
+ }
+
+ /**
+ * Sets the the width of the occlusion cone considered by the occludee default is 0.1f
+ * @param bias
+ */
+ public void setBias(float bias) {
+ this.bias = bias;
+ if (ssaoMat != null) {
+ ssaoMat.setFloat("Bias", bias);
+ }
+ }
+
+ /**
+ * returns the ambient occlusion intensity
+ * @return
+ */
+ public float getIntensity() {
+ return intensity;
+ }
+
+ /**
+ * Sets the Ambient occlusion intensity default is 1.2f
+ * @param intensity
+ */
+ public void setIntensity(float intensity) {
+ this.intensity = intensity;
+ if (ssaoMat != null) {
+ ssaoMat.setFloat("Intensity", intensity);
+ }
+
+ }
+
+ /**
+ * returns the sample radius
+ * see {link setSampleRadius(float sampleRadius)}
+ * @return
+ */
+ public float getSampleRadius() {
+ return sampleRadius;
+ }
+
+ /**
+ * Sets the radius of the area where random samples will be picked dafault 5.1f
+ * @param sampleRadius
+ */
+ public void setSampleRadius(float sampleRadius) {
+ this.sampleRadius = sampleRadius;
+ if (ssaoMat != null) {
+ ssaoMat.setFloat("SampleRadius", sampleRadius);
+ }
+
+ }
+
+ /**
+ * returns the scale
+ * see {@link setScale(float scale)}
+ * @return
+ */
+ public float getScale() {
+ return scale;
+ }
+
+ /**
+ *
+ * Returns the distance between occluders and occludee. default 0.2f
+ * @param scale
+ */
+ public void setScale(float scale) {
+ this.scale = scale;
+ if (ssaoMat != null) {
+ ssaoMat.setFloat("Scale", scale);
+ }
+ }
+
+ /**
+ * debugging only , will be removed
+ * @return
+ */
+ public boolean isUseAo() {
+ return useAo;
+ }
+
+ /**
+ * debugging only , will be removed
+ * @return
+ */
+ public void setUseAo(boolean useAo) {
+ this.useAo = useAo;
+ if (material != null) {
+ material.setBoolean("UseAo", useAo);
+ }
+
+ }
+
+ /**
+ * debugging only , will be removed
+ * @return
+ */
+ public boolean isUseOnlyAo() {
+ return useOnlyAo;
+ }
+
+ /**
+ * debugging only , will be removed
+ * @return
+ */
+ public void setUseOnlyAo(boolean useOnlyAo) {
+ this.useOnlyAo = useOnlyAo;
+ if (material != null) {
+ material.setBoolean("UseOnlyAo", useOnlyAo);
+ }
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(sampleRadius, "sampleRadius", 5.1f);
+ oc.write(intensity, "intensity", 1.5f);
+ oc.write(scale, "scale", 0.2f);
+ oc.write(bias, "bias", 0.1f);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ sampleRadius = ic.readFloat("sampleRadius", 5.1f);
+ intensity = ic.readFloat("intensity", 1.5f);
+ scale = ic.readFloat("scale", 0.2f);
+ bias = ic.readFloat("bias", 0.1f);
+ }
+}
diff --git a/engine/src/core/com/jme3/shadow/BasicShadowRenderer.java b/engine/src/core/com/jme3/shadow/BasicShadowRenderer.java
new file mode 100644
index 000000000..3fd01c93f
--- /dev/null
+++ b/engine/src/core/com/jme3/shadow/BasicShadowRenderer.java
@@ -0,0 +1,216 @@
+/*
+ * 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.shadow;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.GeometryList;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture2D;
+import com.jme3.ui.Picture;
+
+/**
+ * BasicShadowRenderer uses standard shadow mapping with one map
+ * it's useful to render shadows in a small scene, but edges might look a bit jagged.
+ *
+ * @author Kirill Vainer
+ */
+public class BasicShadowRenderer implements SceneProcessor {
+
+ private RenderManager renderManager;
+ private ViewPort viewPort;
+ private FrameBuffer shadowFB;
+ private Texture2D shadowMap;
+ private Camera shadowCam;
+ private Material preshadowMat;
+ private Material postshadowMat;
+ private Picture dispPic = new Picture("Picture");
+ private boolean noOccluders = false;
+ private Vector3f[] points = new Vector3f[8];
+ private Vector3f direction = new Vector3f();
+
+ /**
+ * Creates a BasicShadowRenderer
+ * @param manager the asset manager
+ * @param size the size of the shadow map (the map is square)
+ */
+ public BasicShadowRenderer(AssetManager manager, int size) {
+ shadowFB = new FrameBuffer(size, size, 1);
+ shadowMap = new Texture2D(size, size, Format.Depth);
+ shadowFB.setDepthTexture(shadowMap);
+ shadowCam = new Camera(size, size);
+
+ preshadowMat = new Material(manager, "Common/MatDefs/Shadow/PreShadow.j3md");
+ postshadowMat = new Material(manager, "Common/MatDefs/Shadow/PostShadow.j3md");
+ postshadowMat.setTexture("ShadowMap", shadowMap);
+
+ dispPic.setTexture(manager, shadowMap, false);
+
+ for (int i = 0; i < points.length; i++) {
+ points[i] = new Vector3f();
+ }
+ }
+
+ public void initialize(RenderManager rm, ViewPort vp) {
+ renderManager = rm;
+ viewPort = vp;
+
+ reshape(vp, vp.getCamera().getWidth(), vp.getCamera().getHeight());
+ }
+
+ public boolean isInitialized() {
+ return viewPort != null;
+ }
+
+ /**
+ * returns the light direction used for this processor
+ * @return
+ */
+ public Vector3f getDirection() {
+ return direction;
+ }
+
+ /**
+ * sets the light direction to use to computs shadows
+ * @param direction
+ */
+ public void setDirection(Vector3f direction) {
+ this.direction.set(direction).normalizeLocal();
+ }
+
+ /**
+ * debug only
+ * @return
+ */
+ public Vector3f[] getPoints() {
+ return points;
+ }
+
+ /**
+ * debug only
+ * returns the shadow camera
+ * @return
+ */
+ public Camera getShadowCamera() {
+ return shadowCam;
+ }
+
+ public void postQueue(RenderQueue rq) {
+ GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast);
+ if (occluders.size() == 0) {
+ noOccluders = true;
+ return;
+ } else {
+ noOccluders = false;
+ }
+
+ GeometryList receivers = rq.getShadowQueueContent(ShadowMode.Receive);
+
+ // update frustum points based on current camera
+ Camera viewCam = viewPort.getCamera();
+ ShadowUtil.updateFrustumPoints(viewCam,
+ viewCam.getFrustumNear(),
+ viewCam.getFrustumFar(),
+ 1.0f,
+ points);
+
+ Vector3f frustaCenter = new Vector3f();
+ for (Vector3f point : points) {
+ frustaCenter.addLocal(point);
+ }
+ frustaCenter.multLocal(1f / 8f);
+
+ // update light direction
+ shadowCam.setProjectionMatrix(null);
+ shadowCam.setParallelProjection(true);
+// shadowCam.setFrustumPerspective(45, 1, 1, 20);
+
+ shadowCam.lookAtDirection(direction, Vector3f.UNIT_Y);
+ shadowCam.update();
+ shadowCam.setLocation(frustaCenter);
+ shadowCam.update();
+ shadowCam.updateViewProjection();
+
+ // render shadow casters to shadow map
+ ShadowUtil.updateShadowCamera(occluders, receivers, shadowCam, points);
+
+ Renderer r = renderManager.getRenderer();
+ renderManager.setCamera(shadowCam, false);
+ renderManager.setForcedMaterial(preshadowMat);
+
+ r.setFrameBuffer(shadowFB);
+ r.clearBuffers(false, true, false);
+ viewPort.getQueue().renderShadowQueue(ShadowMode.Cast, renderManager, shadowCam, true);
+ r.setFrameBuffer(viewPort.getOutputFrameBuffer());
+
+ renderManager.setForcedMaterial(null);
+ renderManager.setCamera(viewCam, false);
+ }
+
+ /**
+ * debug only
+ * @return
+ */
+ public Picture getDisplayPicture() {
+ return dispPic;
+ }
+
+ public void postFrame(FrameBuffer out) {
+ if (!noOccluders) {
+ postshadowMat.setMatrix4("LightViewProjectionMatrix", shadowCam.getViewProjectionMatrix());
+ renderManager.setForcedMaterial(postshadowMat);
+ viewPort.getQueue().renderShadowQueue(ShadowMode.Receive, renderManager, viewPort.getCamera(), true);
+ renderManager.setForcedMaterial(null);
+ }
+ }
+
+ public void preFrame(float tpf) {
+ }
+
+ public void cleanup() {
+ }
+
+ public void reshape(ViewPort vp, int w, int h) {
+ dispPic.setPosition(w / 20f, h / 20f);
+ dispPic.setWidth(w / 5f);
+ dispPic.setHeight(h / 5f);
+ }
+}
diff --git a/engine/src/core/com/jme3/shadow/PssmShadowRenderer.java b/engine/src/core/com/jme3/shadow/PssmShadowRenderer.java
new file mode 100644
index 000000000..fb9924033
--- /dev/null
+++ b/engine/src/core/com/jme3/shadow/PssmShadowRenderer.java
@@ -0,0 +1,539 @@
+/*
+ * 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.shadow;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Vector3f;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.GeometryList;
+import com.jme3.renderer.queue.OpaqueComparator;
+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.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture.MagFilter;
+import com.jme3.texture.Texture.MinFilter;
+import com.jme3.texture.Texture.ShadowCompareMode;
+import com.jme3.texture.Texture2D;
+import com.jme3.ui.Picture;
+
+/**
+ * PssmShadow renderer use Parrallel Split Shadow Mapping technique (pssm)
+ * It splits the view frustum in several parts and compute a shadow map for each
+ * one.
splits are distributed so that the closer they are from the camera,
+ * the smaller they are to maximize the resolution used of the shadow map.
+ * This result in a better quality shadow than standard shadow mapping.
for
+ * more informations on this read this
+ * http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html
+ *
+ * @author Rémy Bouquet aka Nehon
+ */
+public class PssmShadowRenderer implements SceneProcessor {
+
+ /**
+ * FilterMode
specifies how shadows are filtered
+ */
+ public enum FilterMode {
+
+ /**
+ * Shadows are not filtered. Nearest sample is used, causing in blocky
+ * shadows.
+ */
+ Nearest,
+ /**
+ * Bilinear filtering is used. Has the potential of being hardware
+ * accelerated on some GPUs
+ */
+ Bilinear,
+ /**
+ * Dither-based sampling is used, very cheap but can look bad
+ * at low resolutions.
+ */
+ Dither,
+ /**
+ * 4x4 percentage-closer filtering is used. Shadows will be smoother
+ * at the cost of performance
+ */
+ PCF4,
+ /**
+ * 8x8 percentage-closer filtering is used. Shadows will be smoother
+ * at the cost of performance
+ */
+ PCF8
+ }
+
+ /**
+ * Specifies the shadow comparison mode
+ */
+ public enum CompareMode {
+
+ /**
+ * Shadow depth comparisons are done by using shader code
+ */
+ Software,
+ /**
+ * Shadow depth comparisons are done by using the GPU's dedicated
+ * shadowing pipeline.
+ */
+ Hardware;
+ }
+ private int nbSplits = 3;
+ private float lambda = 0.65f;
+ private float shadowIntensity = 0.7f;
+ private float zFarOverride = 0;
+ private RenderManager renderManager;
+ private ViewPort viewPort;
+ private FrameBuffer[] shadowFB;
+ private Texture2D[] shadowMaps;
+ private Texture2D dummyTex;
+ private Camera shadowCam;
+ private Material preshadowMat;
+ private Material postshadowMat;
+ private GeometryList splitOccluders = new GeometryList(new OpaqueComparator());
+ private Matrix4f[] lightViewProjectionsMatrices;
+ private ColorRGBA splits;
+ private float[] splitsArray;
+ private boolean noOccluders = false;
+ private Vector3f direction = new Vector3f();
+ private AssetManager assetManager;
+ private boolean debug = false;
+ private float edgesThickness = 1.0f;
+ private FilterMode filterMode;
+ private CompareMode compareMode;
+ private Picture[] dispPic;
+ private Vector3f[] points = new Vector3f[8];
+ private boolean flushQueues = true;
+
+ /**
+ * Create a PSSM Shadow Renderer
+ * More info on the technique at http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html
+ * @param manager the application asset manager
+ * @param size the size of the rendered shadowmaps (512,1024,2048, etc...)
+ * @param nbSplits the number of shadow maps rendered (the more shadow maps the more quality, the less fps).
+ */
+ public PssmShadowRenderer(AssetManager manager, int size, int nbSplits) {
+ assetManager = manager;
+ nbSplits = Math.max(Math.min(nbSplits, 4), 1);
+ this.nbSplits = nbSplits;
+
+ shadowFB = new FrameBuffer[nbSplits];
+ shadowMaps = new Texture2D[nbSplits];
+ dispPic = new Picture[nbSplits];
+ lightViewProjectionsMatrices = new Matrix4f[nbSplits];
+ splits = new ColorRGBA();
+ splitsArray = new float[nbSplits + 1];
+
+ //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash)
+ dummyTex = new Texture2D(size, size, Format.RGBA8);
+
+ preshadowMat = new Material(manager, "Common/MatDefs/Shadow/PreShadow.j3md");
+ postshadowMat = new Material(manager, "Common/MatDefs/Shadow/PostShadowPSSM.j3md");
+
+ for (int i = 0; i < nbSplits; i++) {
+ lightViewProjectionsMatrices[i] = new Matrix4f();
+ shadowFB[i] = new FrameBuffer(size, size, 1);
+ shadowMaps[i] = new Texture2D(size, size, Format.Depth);
+
+ shadowFB[i].setDepthTexture(shadowMaps[i]);
+
+ //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash)
+ shadowFB[i].setColorTexture(dummyTex);
+
+ postshadowMat.setTexture("ShadowMap" + i, shadowMaps[i]);
+
+ //quads for debuging purpose
+ dispPic[i] = new Picture("Picture" + i);
+ dispPic[i].setTexture(manager, shadowMaps[i], false);
+ }
+
+ setCompareMode(CompareMode.Hardware);
+ setFilterMode(FilterMode.Bilinear);
+ setShadowIntensity(0.7f);
+
+ shadowCam = new Camera(size, size);
+ shadowCam.setParallelProjection(true);
+
+ for (int i = 0; i < points.length; i++) {
+ points[i] = new Vector3f();
+ }
+ }
+
+ /**
+ * Sets the filtering mode for shadow edges see {@link FilterMode} for more info
+ * @param filterMode
+ */
+ public void setFilterMode(FilterMode filterMode) {
+ if (filterMode == null) {
+ throw new NullPointerException();
+ }
+
+ if (this.filterMode == filterMode) {
+ return;
+ }
+
+ this.filterMode = filterMode;
+ postshadowMat.setInt("FilterMode", filterMode.ordinal());
+ postshadowMat.setFloat("PCFEdge", edgesThickness);
+ if (compareMode == CompareMode.Hardware) {
+ for (Texture2D shadowMap : shadowMaps) {
+ if (filterMode == FilterMode.Bilinear) {
+ shadowMap.setMagFilter(MagFilter.Bilinear);
+ shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps);
+ } else {
+ shadowMap.setMagFilter(MagFilter.Nearest);
+ shadowMap.setMinFilter(MinFilter.NearestNoMipMaps);
+ }
+ }
+ }
+ }
+
+ /**
+ * sets the shadow compare mode see {@link CompareMode} for more info
+ * @param compareMode
+ */
+ public void setCompareMode(CompareMode compareMode) {
+ if (compareMode == null) {
+ throw new NullPointerException();
+ }
+
+ if (this.compareMode == compareMode) {
+ return;
+ }
+
+ this.compareMode = compareMode;
+ for (Texture2D shadowMap : shadowMaps) {
+ if (compareMode == CompareMode.Hardware) {
+ shadowMap.setShadowCompareMode(ShadowCompareMode.LessOrEqual);
+ if (filterMode == FilterMode.Bilinear) {
+ shadowMap.setMagFilter(MagFilter.Bilinear);
+ shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps);
+ } else {
+ shadowMap.setMagFilter(MagFilter.Nearest);
+ shadowMap.setMinFilter(MinFilter.NearestNoMipMaps);
+ }
+ } else {
+ shadowMap.setShadowCompareMode(ShadowCompareMode.Off);
+ shadowMap.setMagFilter(MagFilter.Nearest);
+ shadowMap.setMinFilter(MinFilter.NearestNoMipMaps);
+ }
+ }
+ postshadowMat.setBoolean("HardwareShadows", compareMode == CompareMode.Hardware);
+ }
+
+ //debug function that create a displayable frustrum
+ private Geometry createFrustum(Vector3f[] pts, int i) {
+ WireFrustum frustum = new WireFrustum(pts);
+ Geometry frustumMdl = new Geometry("f", frustum);
+ frustumMdl.setCullHint(Spatial.CullHint.Never);
+ frustumMdl.setShadowMode(ShadowMode.Off);
+ Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ mat.getAdditionalRenderState().setWireframe(true);
+ frustumMdl.setMaterial(mat);
+ switch (i) {
+ case 0:
+ frustumMdl.getMaterial().setColor("Color", ColorRGBA.Pink);
+ break;
+ case 1:
+ frustumMdl.getMaterial().setColor("Color", ColorRGBA.Red);
+ break;
+ case 2:
+ frustumMdl.getMaterial().setColor("Color", ColorRGBA.Green);
+ break;
+ case 3:
+ frustumMdl.getMaterial().setColor("Color", ColorRGBA.Blue);
+ break;
+ default:
+ frustumMdl.getMaterial().setColor("Color", ColorRGBA.White);
+ break;
+ }
+
+ frustumMdl.updateGeometricState();
+ return frustumMdl;
+ }
+
+ public void initialize(RenderManager rm, ViewPort vp) {
+ renderManager = rm;
+ viewPort = vp;
+ }
+
+ public boolean isInitialized() {
+ return viewPort != null;
+ }
+
+ /**
+ * returns the light direction used by the processor
+ * @return
+ */
+ public Vector3f getDirection() {
+ return direction;
+ }
+
+ /**
+ * Sets the light direction to use to compute shadows
+ * @param direction
+ */
+ public void setDirection(Vector3f direction) {
+ this.direction.set(direction).normalizeLocal();
+ }
+
+ @SuppressWarnings("fallthrough")
+ public void postQueue(RenderQueue rq) {
+ GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast);
+ if (occluders.size() == 0) {
+ return;
+ }
+
+ GeometryList receivers = rq.getShadowQueueContent(ShadowMode.Receive);
+ if (receivers.size() == 0) {
+ return;
+ }
+
+ Camera viewCam = viewPort.getCamera();
+
+ float zFar = zFarOverride;
+ if (zFar == 0) {
+ zFar = viewCam.getFrustumFar();
+ }
+
+ //We prevent computing the frustum points and splits with zeroed or negative near clip value
+ float frustumNear = Math.max(viewCam.getFrustumNear(), 0.001f);
+ ShadowUtil.updateFrustumPoints(viewCam, frustumNear, zFar, 1.0f, points);
+
+ //shadowCam.setDirection(direction);
+ shadowCam.getRotation().lookAt(direction, shadowCam.getUp());
+ shadowCam.update();
+ shadowCam.updateViewProjection();
+
+ PssmShadowUtil.updateFrustumSplits(splitsArray, frustumNear, zFar, lambda);
+
+
+ switch (splitsArray.length) {
+ case 5:
+ splits.a = splitsArray[4];
+ case 4:
+ splits.b = splitsArray[3];
+ case 3:
+ splits.g = splitsArray[2];
+ case 2:
+ case 1:
+ splits.r = splitsArray[1];
+ break;
+ }
+
+ Renderer r = renderManager.getRenderer();
+ renderManager.setForcedMaterial(preshadowMat);
+ renderManager.setForcedTechnique("PreShadow");
+
+ for (int i = 0; i < nbSplits; i++) {
+
+ // update frustum points based on current camera and split
+ ShadowUtil.updateFrustumPoints(viewCam, splitsArray[i], splitsArray[i + 1], 1.0f, points);
+
+ //Updating shadow cam with curent split frustra
+ ShadowUtil.updateShadowCamera(occluders, receivers, shadowCam, points, splitOccluders);
+
+ //saving light view projection matrix for this split
+ lightViewProjectionsMatrices[i] = shadowCam.getViewProjectionMatrix().clone();
+ renderManager.setCamera(shadowCam, false);
+
+ r.setFrameBuffer(shadowFB[i]);
+ r.clearBuffers(false, true, false);
+
+ // render shadow casters to shadow map
+ viewPort.getQueue().renderShadowQueue(splitOccluders, renderManager, shadowCam, true);
+ }
+ if (flushQueues) {
+ occluders.clear();
+ }
+ //restore setting for future rendering
+ r.setFrameBuffer(viewPort.getOutputFrameBuffer());
+ renderManager.setForcedMaterial(null);
+ renderManager.setForcedTechnique(null);
+ renderManager.setCamera(viewCam, false);
+
+ }
+
+ //debug only : displays depth shadow maps
+ private void displayShadowMap(Renderer r) {
+ Camera cam = viewPort.getCamera();
+ renderManager.setCamera(cam, true);
+ int h = cam.getHeight();
+ for (int i = 0; i < dispPic.length; i++) {
+ dispPic[i].setPosition(64 * (i + 1) + 128 * i, h / 20f);
+ dispPic[i].setWidth(128);
+ dispPic[i].setHeight(128);
+ dispPic[i].updateGeometricState();
+ renderManager.renderGeometry(dispPic[i]);
+ }
+ renderManager.setCamera(cam, false);
+ }
+
+ /**For dubuging purpose
+ * Allow to "snapshot" the current frustrum to the scene
+ */
+ public void displayDebug() {
+ debug = true;
+ }
+
+ public void postFrame(FrameBuffer out) {
+ Camera cam = viewPort.getCamera();
+ if (!noOccluders) {
+ postshadowMat.setColor("Splits", splits);
+ for (int i = 0; i < nbSplits; i++) {
+ postshadowMat.setMatrix4("LightViewProjectionMatrix" + i, lightViewProjectionsMatrices[i]);
+ }
+ renderManager.setForcedMaterial(postshadowMat);
+
+ viewPort.getQueue().renderShadowQueue(ShadowMode.Receive, renderManager, cam, flushQueues);
+
+ renderManager.setForcedMaterial(null);
+ renderManager.setCamera(cam, false);
+
+ }
+ if (debug) {
+ displayShadowMap(renderManager.getRenderer());
+ }
+ }
+
+ public void preFrame(float tpf) {
+ }
+
+ public void cleanup() {
+ }
+
+ public void reshape(ViewPort vp, int w, int h) {
+ }
+
+ /**
+ * returns the labda parameter
+ * see {@link setLambda(float lambda)}
+ * @return lambda
+ */
+ public float getLambda() {
+ return lambda;
+ }
+
+ /*
+ * Adjust the repartition of the different shadow maps in the shadow extend
+ * usualy goes from 0.0 to 1.0
+ * a low value give a more linear repartition resulting in a constant quality in the shadow over the extends, but near shadows could look very jagged
+ * a high value give a more logarithmic repartition resulting in a high quality for near shadows, but the quality quickly decrease over the extend.
+ * the default value is set to 0.65f (theoric optimal value).
+ * @param lambda the lambda value.
+ */
+ public void setLambda(float lambda) {
+ this.lambda = lambda;
+ }
+
+ /**
+ * How far the shadows are rendered in the view
+ * see {@link setShadowZExtend(float zFar)}
+ * @return shadowZExtend
+ */
+ public float getShadowZExtend() {
+ return zFarOverride;
+ }
+
+ /**
+ * Set the distance from the eye where the shadows will be rendered
+ * default value is dynamicaly computed to the shadow casters/receivers union bound zFar, capped to view frustum far value.
+ * @param zFar the zFar values that override the computed one
+ */
+ public void setShadowZExtend(float zFar) {
+ this.zFarOverride = zFar;
+ }
+
+ /**
+ * returns the shdaow intensity
+ * see {@link setShadowIntensity(float shadowIntensity)}
+ * @return shadowIntensity
+ */
+ public float getShadowIntensity() {
+ return shadowIntensity;
+ }
+
+ /**
+ * Set the shadowIntensity, the value should be between 0 and 1,
+ * a 0 value gives a bright and invisilble shadow,
+ * a 1 value gives a pitch black shadow,
+ * default is 0.7
+ * @param shadowIntensity the darkness of the shadow
+ */
+ public void setShadowIntensity(float shadowIntensity) {
+ this.shadowIntensity = shadowIntensity;
+ postshadowMat.setFloat("ShadowIntensity", shadowIntensity);
+ }
+
+ /**
+ * returns the edges thickness
+ * see {@link setEdgesThickness(int edgesThickness)}
+ * @return edgesThickness
+ */
+ public int getEdgesThickness() {
+ return (int) (edgesThickness * 10);
+ }
+
+ /**
+ * Sets the shadow edges thickness. default is 1, setting it to lower values can help to reduce the jagged effect of the shadow edges
+ * @param edgesThickness
+ */
+ public void setEdgesThickness(int edgesThickness) {
+ this.edgesThickness = Math.max(1, Math.min(edgesThickness, 10));
+ this.edgesThickness *= 0.1f;
+ postshadowMat.setFloat("PCFEdge", edgesThickness);
+ }
+
+ /**
+ * returns true if the PssmRenderer flushed the shadow queues
+ * @return flushQueues
+ */
+ public boolean isFlushQueues() {
+ return flushQueues;
+ }
+
+ /**
+ * Set this to false if you want to use several PssmRederers to have multiple shadows cast by multiple light sources.
+ * Make sure the last PssmRenderer in the stack DO flush the queues, but not the others
+ * @param flushQueues
+ */
+ public void setFlushQueues(boolean flushQueues) {
+ this.flushQueues = flushQueues;
+ }
+}
diff --git a/engine/src/core/com/jme3/shadow/PssmShadowUtil.java b/engine/src/core/com/jme3/shadow/PssmShadowUtil.java
new file mode 100644
index 000000000..263362565
--- /dev/null
+++ b/engine/src/core/com/jme3/shadow/PssmShadowUtil.java
@@ -0,0 +1,81 @@
+/*
+ * 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.shadow;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix4f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.queue.GeometryList;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+/**
+ * Includes various useful shadow mapping functions.
+ *
+ * @see
+ *
+ * for more info.
+ */
+public final class PssmShadowUtil {
+
+ /**
+ * Updates the frustum splits stores in splits
using PSSM.
+ */
+ public static void updateFrustumSplits(float[] splits, float near, float far, float lambda) {
+ for (int i = 0; i < splits.length; i++) {
+ float IDM = i / (float) splits.length;
+ float log = near * FastMath.pow((far / near), IDM);
+ float uniform = near + (far - near) * IDM;
+ splits[i] = log * lambda + uniform * (1.0f - lambda);
+ }
+
+ // This is used to improve the correctness of the calculations. Our main near- and farplane
+ // of the camera always stay the same, no matter what happens.
+ splits[0] = near;
+ splits[splits.length - 1] = far;
+ }
+
+ /**
+ * Compute the Zfar in the model vieuw to adjust the Zfar distance for the splits calculation
+ */
+ public static float computeZFar(GeometryList occ, GeometryList recv, Camera cam) {
+ Matrix4f mat = cam.getViewMatrix();
+ BoundingBox bbOcc = ShadowUtil.computeUnionBound(occ, mat);
+ BoundingBox bbRecv = ShadowUtil.computeUnionBound(recv, mat);
+
+ return min(max(bbOcc.getZExtent() - bbOcc.getCenter().z, bbRecv.getZExtent() - bbRecv.getCenter().z), cam.getFrustumFar());
+ }
+}
diff --git a/engine/src/core/com/jme3/shadow/ShadowCamera.java b/engine/src/core/com/jme3/shadow/ShadowCamera.java
new file mode 100644
index 000000000..920e4564d
--- /dev/null
+++ b/engine/src/core/com/jme3/shadow/ShadowCamera.java
@@ -0,0 +1,75 @@
+/*
+ * 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.shadow;
+
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.Light;
+import com.jme3.light.PointLight;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+
+/**
+ * Creates a camera according to a light
+ * Handy to compute projection matrix of a light
+ * @author Kirill Vainer
+ */
+public class ShadowCamera {
+
+ private Vector3f[] points = new Vector3f[8];
+ private Light target;
+
+ public ShadowCamera(Light target) {
+ this.target = target;
+ for (int i = 0; i < points.length; i++) {
+ points[i] = new Vector3f();
+ }
+ }
+
+ /**
+ * Updates the camera view direction and position based on the light
+ */
+ public void updateLightCamera(Camera lightCam) {
+ if (target.getType() == Light.Type.Directional) {
+ DirectionalLight dl = (DirectionalLight) target;
+ lightCam.setParallelProjection(true);
+ lightCam.setLocation(Vector3f.ZERO);
+ lightCam.lookAtDirection(dl.getDirection(), Vector3f.UNIT_Y);
+ lightCam.setFrustum(-1, 1, -1, 1, 1, -1);
+ } else {
+ PointLight pl = (PointLight) target;
+ lightCam.setParallelProjection(false);
+ lightCam.setLocation(pl.getPosition());
+ // direction will have to be calculated automatically
+ lightCam.setFrustumPerspective(45, 1, 1, 300);
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/shadow/ShadowUtil.java b/engine/src/core/com/jme3/shadow/ShadowUtil.java
new file mode 100644
index 000000000..c488f5e89
--- /dev/null
+++ b/engine/src/core/com/jme3/shadow/ShadowUtil.java
@@ -0,0 +1,486 @@
+/*
+ * 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.shadow;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.queue.GeometryList;
+import com.jme3.scene.Geometry;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Includes various useful shadow mapping functions.
+ *
+ * @see
+ *
+ * for more info.
+ */
+public class ShadowUtil {
+
+ /**
+ * Updates a points arrays with the frustum corners of the provided camera.
+ * @param viewCam
+ * @param points
+ */
+ public static void updateFrustumPoints2(Camera viewCam, Vector3f[] points) {
+ int w = viewCam.getWidth();
+ int h = viewCam.getHeight();
+ float n = viewCam.getFrustumNear();
+ float f = viewCam.getFrustumFar();
+
+ points[0].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), n));
+ points[1].set(viewCam.getWorldCoordinates(new Vector2f(0, h), n));
+ points[2].set(viewCam.getWorldCoordinates(new Vector2f(w, h), n));
+ points[3].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), n));
+
+ points[4].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), f));
+ points[5].set(viewCam.getWorldCoordinates(new Vector2f(0, h), f));
+ points[6].set(viewCam.getWorldCoordinates(new Vector2f(w, h), f));
+ points[7].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), f));
+ }
+
+ /**
+ * Updates the points array to contain the frustum corners of the given
+ * camera. The nearOverride and farOverride variables can be used
+ * to override the camera's near/far values with own values.
+ *
+ * TODO: Reduce creation of new vectors
+ *
+ * @param viewCam
+ * @param nearOverride
+ * @param farOverride
+ */
+ public static void updateFrustumPoints(Camera viewCam,
+ float nearOverride,
+ float farOverride,
+ float scale,
+ Vector3f[] points) {
+
+ Vector3f pos = viewCam.getLocation();
+ Vector3f dir = viewCam.getDirection();
+ Vector3f up = viewCam.getUp();
+
+ float depthHeightRatio = viewCam.getFrustumTop() / viewCam.getFrustumNear();
+ float near = nearOverride;
+ float far = farOverride;
+ float ftop = viewCam.getFrustumTop();
+ float fright = viewCam.getFrustumRight();
+ float ratio = fright / ftop;
+
+ float near_height;
+ float near_width;
+ float far_height;
+ float far_width;
+
+ if (viewCam.isParallelProjection()) {
+ near_height = ftop;
+ near_width = near_height * ratio;
+ far_height = ftop;
+ far_width = far_height * ratio;
+ } else {
+ near_height = depthHeightRatio * near;
+ near_width = near_height * ratio;
+ far_height = depthHeightRatio * far;
+ far_width = far_height * ratio;
+ }
+
+ Vector3f right = dir.cross(up).normalizeLocal();
+
+ Vector3f temp = new Vector3f();
+ temp.set(dir).multLocal(far).addLocal(pos);
+ Vector3f farCenter = temp.clone();
+ temp.set(dir).multLocal(near).addLocal(pos);
+ Vector3f nearCenter = temp.clone();
+
+ Vector3f nearUp = temp.set(up).multLocal(near_height).clone();
+ Vector3f farUp = temp.set(up).multLocal(far_height).clone();
+ Vector3f nearRight = temp.set(right).multLocal(near_width).clone();
+ Vector3f farRight = temp.set(right).multLocal(far_width).clone();
+
+ points[0].set(nearCenter).subtractLocal(nearUp).subtractLocal(nearRight);
+ points[1].set(nearCenter).addLocal(nearUp).subtractLocal(nearRight);
+ points[2].set(nearCenter).addLocal(nearUp).addLocal(nearRight);
+ points[3].set(nearCenter).subtractLocal(nearUp).addLocal(nearRight);
+
+ points[4].set(farCenter).subtractLocal(farUp).subtractLocal(farRight);
+ points[5].set(farCenter).addLocal(farUp).subtractLocal(farRight);
+ points[6].set(farCenter).addLocal(farUp).addLocal(farRight);
+ points[7].set(farCenter).subtractLocal(farUp).addLocal(farRight);
+
+ if (scale != 1.0f) {
+ // find center of frustum
+ Vector3f center = new Vector3f();
+ for (int i = 0; i < 8; i++) {
+ center.addLocal(points[i]);
+ }
+ center.divideLocal(8f);
+
+ Vector3f cDir = new Vector3f();
+ for (int i = 0; i < 8; i++) {
+ cDir.set(points[i]).subtractLocal(center);
+ cDir.multLocal(scale - 1.0f);
+ points[i].addLocal(cDir);
+ }
+ }
+ }
+
+ /**
+ * Compute bounds of a geomList
+ * @param list
+ * @param transform
+ * @return
+ */
+ public static BoundingBox computeUnionBound(GeometryList list, Transform transform) {
+ BoundingBox bbox = new BoundingBox();
+ for (int i = 0; i < list.size(); i++) {
+ BoundingVolume vol = list.get(i).getWorldBound();
+ BoundingVolume newVol = vol.transform(transform);
+ //Nehon : prevent NaN and infinity values to screw the final bounding box
+ if (!Float.isNaN(newVol.getCenter().x) && !Float.isInfinite(newVol.getCenter().x)) {
+ bbox.mergeLocal(newVol);
+ }
+ }
+ return bbox;
+ }
+
+ /**
+ * Compute bounds of a geomList
+ * @param list
+ * @param mat
+ * @return
+ */
+ public static BoundingBox computeUnionBound(GeometryList list, Matrix4f mat) {
+ BoundingBox bbox = new BoundingBox();
+ BoundingVolume store = null;
+ for (int i = 0; i < list.size(); i++) {
+ BoundingVolume vol = list.get(i).getWorldBound();
+ store = vol.clone().transform(mat, null);
+ //Nehon : prevent NaN and infinity values to screw the final bounding box
+ if (!Float.isNaN(store.getCenter().x) && !Float.isInfinite(store.getCenter().x)) {
+ bbox.mergeLocal(store);
+ }
+ }
+ return bbox;
+ }
+
+ /**
+ * Computes the bounds of multiple bounding volumes
+ * @param bv
+ * @return
+ */
+ public static BoundingBox computeUnionBound(List bv) {
+ BoundingBox bbox = new BoundingBox();
+ for (int i = 0; i < bv.size(); i++) {
+ BoundingVolume vol = bv.get(i);
+ bbox.mergeLocal(vol);
+ }
+ return bbox;
+ }
+
+ /**
+ * Compute bounds from an array of points
+ * @param pts
+ * @param transform
+ * @return
+ */
+ public static BoundingBox computeBoundForPoints(Vector3f[] pts, Transform transform) {
+ Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY);
+ Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY);
+ Vector3f temp = new Vector3f();
+ for (int i = 0; i < pts.length; i++) {
+ transform.transformVector(pts[i], temp);
+
+ min.minLocal(temp);
+ max.maxLocal(temp);
+ }
+ Vector3f center = min.add(max).multLocal(0.5f);
+ Vector3f extent = max.subtract(min).multLocal(0.5f);
+ return new BoundingBox(center, extent.x, extent.y, extent.z);
+ }
+
+ /**
+ * Compute bounds from an array of points
+ * @param pts
+ * @param mat
+ * @return
+ */
+ public static BoundingBox computeBoundForPoints(Vector3f[] pts, Matrix4f mat) {
+ Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY);
+ Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY);
+ Vector3f temp = new Vector3f();
+
+ for (int i = 0; i < pts.length; i++) {
+ float w = mat.multProj(pts[i], temp);
+
+ temp.x /= w;
+ temp.y /= w;
+ // Why was this commented out?
+ temp.z /= w;
+
+ min.minLocal(temp);
+ max.maxLocal(temp);
+ }
+
+ Vector3f center = min.add(max).multLocal(0.5f);
+ Vector3f extent = max.subtract(min).multLocal(0.5f);
+ //Nehon 08/18/2010 : Added an offset to the extend to avoid banding artifacts when the frustum are aligned
+ return new BoundingBox(center, extent.x + 2.0f, extent.y + 2.0f, extent.z + 2.5f);
+ }
+
+ /**
+ * Updates the shadow camera to properly contain the given
+ * points (which contain the eye camera frustum corners)
+ *
+ * @param occluders
+ * @param lightCam
+ * @param points
+ */
+ public static void updateShadowCamera(Camera shadowCam, Vector3f[] points) {
+ boolean ortho = shadowCam.isParallelProjection();
+ shadowCam.setProjectionMatrix(null);
+
+ if (ortho) {
+ shadowCam.setFrustum(-1, 1, -1, 1, 1, -1);
+ } else {
+ shadowCam.setFrustumPerspective(45, 1, 1, 150);
+ }
+
+ Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix();
+ Matrix4f projMatrix = shadowCam.getProjectionMatrix();
+
+ BoundingBox splitBB = computeBoundForPoints(points, viewProjMatrix);
+
+ Vector3f splitMin = splitBB.getMin(null);
+ Vector3f splitMax = splitBB.getMax(null);
+
+// splitMin.z = 0;
+
+ // Create the crop matrix.
+ float scaleX, scaleY, scaleZ;
+ float offsetX, offsetY, offsetZ;
+
+ scaleX = 2.0f / (splitMax.x - splitMin.x);
+ scaleY = 2.0f / (splitMax.y - splitMin.y);
+ offsetX = -0.5f * (splitMax.x + splitMin.x) * scaleX;
+ offsetY = -0.5f * (splitMax.y + splitMin.y) * scaleY;
+ scaleZ = 1.0f / (splitMax.z - splitMin.z);
+ offsetZ = -splitMin.z * scaleZ;
+
+ Matrix4f cropMatrix = new Matrix4f(scaleX, 0f, 0f, offsetX,
+ 0f, scaleY, 0f, offsetY,
+ 0f, 0f, scaleZ, offsetZ,
+ 0f, 0f, 0f, 1f);
+
+
+ Matrix4f result = new Matrix4f();
+ result.set(cropMatrix);
+ result.multLocal(projMatrix);
+
+ shadowCam.setProjectionMatrix(result);
+ }
+
+ /**
+ * Updates the shadow camera to properly contain the given
+ * points (which contain the eye camera frustum corners) and the
+ * shadow occluder objects.
+ *
+ * @param occluders
+ * @param lightCam
+ * @param points
+ */
+ public static void updateShadowCamera(GeometryList occluders,
+ GeometryList receivers,
+ Camera shadowCam,
+ Vector3f[] points) {
+ updateShadowCamera(occluders, receivers, shadowCam, points, null);
+ }
+
+ /**
+ * Updates the shadow camera to properly contain the given
+ * points (which contain the eye camera frustum corners) and the
+ * shadow occluder objects.
+ *
+ * @param occluders
+ * @param lightCam
+ * @param points
+ */
+ public static void updateShadowCamera(GeometryList occluders,
+ GeometryList receivers,
+ Camera shadowCam,
+ Vector3f[] points,
+ GeometryList splitOccluders) {
+
+ boolean ortho = shadowCam.isParallelProjection();
+
+ shadowCam.setProjectionMatrix(null);
+
+ if (ortho) {
+ shadowCam.setFrustum(-1, 1, -1, 1, 1, -1);
+ } else {
+ shadowCam.setFrustumPerspective(45, 1, 1, 150);
+ }
+
+ // create transform to rotate points to viewspace
+ Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix();
+
+ BoundingBox splitBB = computeBoundForPoints(points, viewProjMatrix);
+
+ ArrayList visRecvList = new ArrayList();
+ for (int i = 0; i < receivers.size(); i++) {
+ // convert bounding box to light's viewproj space
+ Geometry receiver = receivers.get(i);
+ BoundingVolume bv = receiver.getWorldBound();
+ BoundingVolume recvBox = bv.transform(viewProjMatrix, null);
+
+ if (splitBB.intersects(recvBox)) {
+ visRecvList.add(recvBox);
+ }
+ }
+
+ ArrayList visOccList = new ArrayList();
+ for (int i = 0; i < occluders.size(); i++) {
+ // convert bounding box to light's viewproj space
+ Geometry occluder = occluders.get(i);
+ BoundingVolume bv = occluder.getWorldBound();
+ BoundingVolume occBox = bv.transform(viewProjMatrix, null);
+
+ boolean intersects = splitBB.intersects(occBox);
+ if (!intersects && occBox instanceof BoundingBox) {
+ BoundingBox occBB = (BoundingBox) occBox;
+ //Kirill 01/10/2011
+ // Extend the occluder further into the frustum
+ // This fixes shadow dissapearing issues when
+ // the caster itself is not in the view camera
+ // but its shadow is in the camera
+ // The number is in world units
+ occBB.setZExtent(occBB.getZExtent() + 50);
+ occBB.setCenter(occBB.getCenter().addLocal(0, 0, 25));
+ if (splitBB.intersects(occBB)) {
+ // To prevent extending the depth range too much
+ // We return the bound to its former shape
+ // Before adding it
+ occBB.setZExtent(occBB.getZExtent() - 50);
+ occBB.setCenter(occBB.getCenter().subtractLocal(0, 0, 25));
+ visOccList.add(occBox);
+ if (splitOccluders != null) {
+ splitOccluders.add(occluder);
+ }
+ }
+ } else if (intersects) {
+ visOccList.add(occBox);
+ if (splitOccluders != null) {
+ splitOccluders.add(occluder);
+ }
+ }
+ }
+
+ BoundingBox casterBB = computeUnionBound(visOccList);
+ BoundingBox receiverBB = computeUnionBound(visRecvList);
+
+ //Nehon 08/18/2010 this is to avoid shadow bleeding when the ground is set to only receive shadows
+ if (visOccList.size() != visRecvList.size()) {
+ casterBB.setXExtent(casterBB.getXExtent() + 2.0f);
+ casterBB.setYExtent(casterBB.getYExtent() + 2.0f);
+ casterBB.setZExtent(casterBB.getZExtent() + 2.0f);
+ }
+
+ Vector3f casterMin = casterBB.getMin(null);
+ Vector3f casterMax = casterBB.getMax(null);
+
+ Vector3f receiverMin = receiverBB.getMin(null);
+ Vector3f receiverMax = receiverBB.getMax(null);
+
+ Vector3f splitMin = splitBB.getMin(null);
+ Vector3f splitMax = splitBB.getMax(null);
+
+ splitMin.z = 0;
+
+ if (!ortho) {
+ shadowCam.setFrustumPerspective(45, 1, 1, splitMax.z);
+ }
+
+ Matrix4f projMatrix = shadowCam.getProjectionMatrix();
+
+ Vector3f cropMin = new Vector3f();
+ Vector3f cropMax = new Vector3f();
+
+ // IMPORTANT: Special handling for Z values
+ cropMin.x = max(max(casterMin.x, receiverMin.x), splitMin.x);
+ cropMax.x = min(min(casterMax.x, receiverMax.x), splitMax.x);
+
+ cropMin.y = max(max(casterMin.y, receiverMin.y), splitMin.y);
+ cropMax.y = min(min(casterMax.y, receiverMax.y), splitMax.y);
+
+ cropMin.z = min(casterMin.z, splitMin.z);
+ cropMax.z = min(receiverMax.z, splitMax.z);
+
+
+ // Create the crop matrix.
+ float scaleX, scaleY, scaleZ;
+ float offsetX, offsetY, offsetZ;
+
+ scaleX = (2.0f) / (cropMax.x - cropMin.x);
+ scaleY = (2.0f) / (cropMax.y - cropMin.y);
+
+ offsetX = -0.5f * (cropMax.x + cropMin.x) * scaleX;
+ offsetY = -0.5f * (cropMax.y + cropMin.y) * scaleY;
+
+ scaleZ = 1.0f / (cropMax.z - cropMin.z);
+ offsetZ = -cropMin.z * scaleZ;
+
+
+
+ Matrix4f cropMatrix = new Matrix4f(scaleX, 0f, 0f, offsetX,
+ 0f, scaleY, 0f, offsetY,
+ 0f, 0f, scaleZ, offsetZ,
+ 0f, 0f, 0f, 1f);
+
+
+ Matrix4f result = new Matrix4f();
+ result.set(cropMatrix);
+ result.multLocal(projMatrix);
+
+ shadowCam.setProjectionMatrix(result);
+
+ }
+}
diff --git a/engine/src/core/com/jme3/water/ReflectionProcessor.java b/engine/src/core/com/jme3/water/ReflectionProcessor.java
new file mode 100644
index 000000000..9a14df82f
--- /dev/null
+++ b/engine/src/core/com/jme3/water/ReflectionProcessor.java
@@ -0,0 +1,125 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.water;
+
+import com.jme3.math.Plane;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.texture.FrameBuffer;
+
+/**
+ * Reflection Processor
+ * Used to render the reflected scene in an off view port
+ */
+public class ReflectionProcessor implements SceneProcessor {
+
+ private RenderManager rm;
+ private ViewPort vp;
+ private Camera reflectionCam;
+ private FrameBuffer reflectionBuffer;
+ private Plane reflectionClipPlane;
+
+ /**
+ * Creates a ReflectionProcessor
+ * @param reflectionCam the cam to use for reflection
+ * @param reflectionBuffer the FrameBuffer to render to
+ * @param reflectionClipPlane the clipping plane
+ */
+ public ReflectionProcessor(Camera reflectionCam, FrameBuffer reflectionBuffer, Plane reflectionClipPlane) {
+ this.reflectionCam = reflectionCam;
+ this.reflectionBuffer = reflectionBuffer;
+ this.reflectionClipPlane = reflectionClipPlane;
+ }
+
+ public void initialize(RenderManager rm, ViewPort vp) {
+ this.rm = rm;
+ this.vp = vp;
+ }
+
+ public void reshape(ViewPort vp, int w, int h) {
+ }
+
+ public boolean isInitialized() {
+ return rm != null;
+ }
+
+ public void preFrame(float tpf) {
+ }
+
+ public void postQueue(RenderQueue rq) {
+ //we need special treatement for the sky because it must not be clipped
+ rm.getRenderer().setFrameBuffer(reflectionBuffer);
+ reflectionCam.setProjectionMatrix(null);
+ rm.setCamera(reflectionCam, false);
+ rm.getRenderer().clearBuffers(true, true, true);
+ //Rendering the sky whithout clipping
+ rm.getRenderer().setDepthRange(1, 1);
+ vp.getQueue().renderQueue(RenderQueue.Bucket.Sky, rm, reflectionCam, true);
+ rm.getRenderer().setDepthRange(0, 1);
+ //setting the clip plane to the cam
+ reflectionCam.setClipPlane(reflectionClipPlane, Plane.Side.Positive);//,1
+ rm.setCamera(reflectionCam, false);
+
+ }
+
+ public void postFrame(FrameBuffer out) {
+ }
+
+ public void cleanup() {
+ }
+
+ /**
+ * Internal use only
+ * returns the frame buffer
+ * @return
+ */
+ public FrameBuffer getReflectionBuffer() {
+ return reflectionBuffer;
+ }
+
+ /**
+ * Internal use only
+ * sets the frame buffer
+ * @param reflectionBuffer
+ */
+ public void setReflectionBuffer(FrameBuffer reflectionBuffer) {
+ this.reflectionBuffer = reflectionBuffer;
+ }
+
+ /**
+ * returns the reflection cam
+ * @return
+ */
+ public Camera getReflectionCam() {
+ return reflectionCam;
+ }
+
+ /**
+ * sets the reflection cam
+ * @param reflectionCam
+ */
+ public void setReflectionCam(Camera reflectionCam) {
+ this.reflectionCam = reflectionCam;
+ }
+
+ /**
+ * returns the reflection clip plane
+ * @return
+ */
+ public Plane getReflectionClipPlane() {
+ return reflectionClipPlane;
+ }
+
+ /**
+ * Sets the reflection clip plane
+ * @param reflectionClipPlane
+ */
+ public void setReflectionClipPlane(Plane reflectionClipPlane) {
+ this.reflectionClipPlane = reflectionClipPlane;
+ }
+}
diff --git a/engine/src/core/com/jme3/water/SimpleWaterProcessor.java b/engine/src/core/com/jme3/water/SimpleWaterProcessor.java
new file mode 100644
index 000000000..70ccd1130
--- /dev/null
+++ b/engine/src/core/com/jme3/water/SimpleWaterProcessor.java
@@ -0,0 +1,589 @@
+/*
+ * 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.water;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Quad;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.texture.Texture2D;
+import com.jme3.ui.Picture;
+
+/**
+ *
+ * Simple Water renders a simple plane that use reflection and refraction to look like water.
+ * It's pretty basic, but much faster than the WaterFilter
+ * It's useful if you aim low specs hardware and still want a good looking water.
+ * Usage is :
+ *
+ * SimpleWaterProcessor waterProcessor = new SimpleWaterProcessor(assetManager);
+ * //setting the scene to use for reflection
+ * waterProcessor.setReflectionScene(mainScene);
+ * //setting the light position
+ * waterProcessor.setLightPosition(lightPos);
+ *
+ * //setting the water plane
+ * Vector3f waterLocation=new Vector3f(0,-20,0);
+ * waterProcessor.setPlane(new Plane(Vector3f.UNIT_Y, waterLocation.dot(Vector3f.UNIT_Y)));
+ * //setting the water color
+ * waterProcessor.setWaterColor(ColorRGBA.Brown);
+ *
+ * //creating a quad to render water to
+ * Quad quad = new Quad(400,400);
+ *
+ * //the texture coordinates define the general size of the waves
+ * quad.scaleTextureCoordinates(new Vector2f(6f,6f));
+ *
+ * //creating a geom to attach the water material
+ * Geometry water=new Geometry("water", quad);
+ * water.setLocalTranslation(-200, -20, 250);
+ * water.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
+ * //finally setting the material
+ * water.setMaterial(waterProcessor.getMaterial());
+ *
+ * //attaching the water to the root node
+ * rootNode.attachChild(water);
+ *
+ * @author Normen Hansen & Rémy Bouquet
+ */
+public class SimpleWaterProcessor implements SceneProcessor {
+
+ protected RenderManager rm;
+ protected ViewPort vp;
+ protected Spatial reflectionScene;
+ protected ViewPort reflectionView;
+ protected ViewPort refractionView;
+ protected FrameBuffer reflectionBuffer;
+ protected FrameBuffer refractionBuffer;
+ protected Camera reflectionCam;
+ protected Camera refractionCam;
+ protected Texture2D reflectionTexture;
+ protected Texture2D refractionTexture;
+ protected Texture2D depthTexture;
+ protected Texture2D normalTexture;
+ protected Texture2D dudvTexture;
+ protected int renderWidth = 512;
+ protected int renderHeight = 512;
+ protected Plane plane = new Plane(Vector3f.UNIT_Y, Vector3f.ZERO.dot(Vector3f.UNIT_Y));
+ protected float speed = 0.05f;
+ protected Ray ray = new Ray();
+ protected Vector3f targetLocation = new Vector3f();
+ protected AssetManager manager;
+ protected Material material;
+ protected float waterDepth = 1;
+ protected float waterTransparency = 0.4f;
+ protected boolean debug = false;
+ private Picture dispRefraction;
+ private Picture dispReflection;
+ private Picture dispDepth;
+ private Plane reflectionClipPlane;
+ private Plane refractionClipPlane;
+ private float refractionClippingOffset = 0.3f;
+ private float reflectionClippingOffset = -5f;
+ private Vector3f vect1 = new Vector3f();
+ private Vector3f vect2 = new Vector3f();
+ private Vector3f vect3 = new Vector3f();
+
+ /**
+ * Creates a SimpleWaterProcessor
+ * @param manager the asset manager
+ */
+ public SimpleWaterProcessor(AssetManager manager) {
+ this.manager = manager;
+ material = new Material(manager, "Common/MatDefs/Water/SimpleWater.j3md");
+ material.setFloat("waterDepth", waterDepth);
+ material.setFloat("waterTransparency", waterTransparency / 10);
+ material.setColor("waterColor", ColorRGBA.White);
+ material.setVector3("lightPos", new Vector3f(1, -1, 1));
+
+ material.setColor("distortionScale", new ColorRGBA(0.2f, 0.2f, 0.2f, 0.2f));
+ material.setColor("distortionMix", new ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f));
+ material.setColor("texScale", new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
+ updateClipPlanes();
+
+ }
+
+ public void initialize(RenderManager rm, ViewPort vp) {
+ this.rm = rm;
+ this.vp = vp;
+
+ loadTextures(manager);
+ createTextures();
+ applyTextures(material);
+
+ createPreViews();
+
+ material.setVector2("FrustumNearFar", new Vector2f(vp.getCamera().getFrustumNear(), vp.getCamera().getFrustumFar()));
+
+ if (debug) {
+ dispRefraction = new Picture("dispRefraction");
+ dispRefraction.setTexture(manager, refractionTexture, false);
+ dispReflection = new Picture("dispRefraction");
+ dispReflection.setTexture(manager, reflectionTexture, false);
+ dispDepth = new Picture("depthTexture");
+ dispDepth.setTexture(manager, depthTexture, false);
+ }
+ }
+
+ public void reshape(ViewPort vp, int w, int h) {
+ }
+
+ public boolean isInitialized() {
+ return rm != null;
+ }
+ float time = 0;
+ float savedTpf = 0;
+
+ public void preFrame(float tpf) {
+ time = time + (tpf * speed);
+ if (time > 1f) {
+ time = 0;
+ }
+ material.setFloat("time", time);
+ savedTpf = tpf;
+ }
+
+ public void postQueue(RenderQueue rq) {
+ Camera sceneCam = rm.getCurrentCamera();
+
+ //update ray
+ ray.setOrigin(sceneCam.getLocation());
+ ray.setDirection(sceneCam.getDirection());
+
+ //update refraction cam
+ refractionCam.setLocation(sceneCam.getLocation());
+ refractionCam.setRotation(sceneCam.getRotation());
+ refractionCam.setFrustum(sceneCam.getFrustumNear(),
+ sceneCam.getFrustumFar(),
+ sceneCam.getFrustumLeft(),
+ sceneCam.getFrustumRight(),
+ sceneCam.getFrustumTop(),
+ sceneCam.getFrustumBottom());
+
+ //update reflection cam
+ boolean inv = false;
+ if (!ray.intersectsWherePlane(plane, targetLocation)) {
+ ray.setDirection(ray.getDirection().negateLocal());
+ ray.intersectsWherePlane(plane, targetLocation);
+ inv = true;
+ }
+ Vector3f loc = plane.reflect(sceneCam.getLocation(), new Vector3f());
+ reflectionCam.setLocation(loc);
+ reflectionCam.setFrustum(sceneCam.getFrustumNear(),
+ sceneCam.getFrustumFar(),
+ sceneCam.getFrustumLeft(),
+ sceneCam.getFrustumRight(),
+ sceneCam.getFrustumTop(),
+ sceneCam.getFrustumBottom());
+ // tempVec and calcVect are just temporary vector3f objects
+ vect1.set(sceneCam.getLocation()).addLocal(sceneCam.getUp());
+ float planeDistance = plane.pseudoDistance(vect1);
+ vect2.set(plane.getNormal()).multLocal(planeDistance * 2.0f);
+ vect3.set(vect1.subtractLocal(vect2)).subtractLocal(loc).normalizeLocal().negateLocal();
+ // now set the up vector
+ reflectionCam.lookAt(targetLocation, vect3);
+ if (inv) {
+ reflectionCam.setAxes(reflectionCam.getLeft().negateLocal(), reflectionCam.getUp(), reflectionCam.getDirection().negateLocal());
+ }
+
+ //Rendering reflection and refraction
+ rm.renderViewPort(reflectionView, savedTpf);
+ rm.renderViewPort(refractionView, savedTpf);
+ rm.getRenderer().setFrameBuffer(vp.getOutputFrameBuffer());
+ rm.setCamera(sceneCam, false);
+
+ }
+
+ public void postFrame(FrameBuffer out) {
+ if (debug) {
+ displayMap(rm.getRenderer(), dispRefraction, 64);
+ displayMap(rm.getRenderer(), dispReflection, 256);
+ displayMap(rm.getRenderer(), dispDepth, 448);
+ }
+ }
+
+ public void cleanup() {
+ }
+
+ //debug only : displays maps
+ protected void displayMap(Renderer r, Picture pic, int left) {
+ Camera cam = vp.getCamera();
+ rm.setCamera(cam, true);
+ int h = cam.getHeight();
+
+ pic.setPosition(left, h / 20f);
+
+ pic.setWidth(128);
+ pic.setHeight(128);
+ pic.updateGeometricState();
+ rm.renderGeometry(pic);
+ rm.setCamera(cam, false);
+ }
+
+ protected void loadTextures(AssetManager manager) {
+ normalTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/water_normalmap.dds");
+ dudvTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/dudv_map.jpg");
+ normalTexture.setWrap(WrapMode.Repeat);
+ dudvTexture.setWrap(WrapMode.Repeat);
+ }
+
+ protected void createTextures() {
+ reflectionTexture = new Texture2D(renderWidth, renderHeight, Format.RGBA8);
+ refractionTexture = new Texture2D(renderWidth, renderHeight, Format.RGBA8);
+ depthTexture = new Texture2D(renderWidth, renderHeight, Format.Depth);
+ }
+
+ protected void applyTextures(Material mat) {
+ mat.setTexture("water_reflection", reflectionTexture);
+ mat.setTexture("water_refraction", refractionTexture);
+ mat.setTexture("water_depthmap", depthTexture);
+ mat.setTexture("water_normalmap", normalTexture);
+ mat.setTexture("water_dudvmap", dudvTexture);
+ }
+
+ protected void createPreViews() {
+ reflectionCam = new Camera(renderWidth, renderHeight);
+ refractionCam = new Camera(renderWidth, renderHeight);
+
+ // create a pre-view. a view that is rendered before the main view
+ reflectionView = new ViewPort("Reflection View", reflectionCam);
+ reflectionView.setClearFlags(true, true, true);
+ reflectionView.setBackgroundColor(ColorRGBA.Black);
+ // create offscreen framebuffer
+ reflectionBuffer = new FrameBuffer(renderWidth, renderHeight, 1);
+ //setup framebuffer to use texture
+ reflectionBuffer.setDepthBuffer(Format.Depth);
+ reflectionBuffer.setColorTexture(reflectionTexture);
+
+ //set viewport to render to offscreen framebuffer
+ reflectionView.setOutputFrameBuffer(reflectionBuffer);
+ reflectionView.addProcessor(new ReflectionProcessor(reflectionCam, reflectionBuffer, reflectionClipPlane));
+ // attach the scene to the viewport to be rendered
+ reflectionView.attachScene(reflectionScene);
+
+ // create a pre-view. a view that is rendered before the main view
+ refractionView = new ViewPort("Refraction View", refractionCam);
+ refractionView.setClearFlags(true, true, true);
+ refractionView.setBackgroundColor(ColorRGBA.Black);
+ // create offscreen framebuffer
+ refractionBuffer = new FrameBuffer(renderWidth, renderHeight, 1);
+ //setup framebuffer to use texture
+ refractionBuffer.setDepthBuffer(Format.Depth);
+ refractionBuffer.setColorTexture(refractionTexture);
+ refractionBuffer.setDepthTexture(depthTexture);
+ //set viewport to render to offscreen framebuffer
+ refractionView.setOutputFrameBuffer(refractionBuffer);
+ refractionView.addProcessor(new RefractionProcessor());
+ // attach the scene to the viewport to be rendered
+ refractionView.attachScene(reflectionScene);
+ }
+
+ protected void destroyViews() {
+ // rm.removePreView(reflectionView);
+ rm.removePreView(refractionView);
+ }
+
+ /**
+ * Get the water material from this processor, apply this to your water quad.
+ * @return
+ */
+ public Material getMaterial() {
+ return material;
+ }
+
+ /**
+ * Sets the reflected scene, should not include the water quad!
+ * Set before adding processor.
+ * @param spat
+ */
+ public void setReflectionScene(Spatial spat) {
+ reflectionScene = spat;
+ }
+
+ /**
+ * returns the width of the reflection and refraction textures
+ * @return
+ */
+ public int getRenderWidth() {
+ return renderWidth;
+ }
+
+ /**
+ * returns the height of the reflection and refraction textures
+ * @return
+ */
+ public int getRenderHeight() {
+ return renderHeight;
+ }
+
+ /**
+ * Set the reflection Texture render size,
+ * set before adding the processor!
+ * @param with
+ * @param height
+ */
+ public void setRenderSize(int width, int height) {
+ renderWidth = width;
+ renderHeight = height;
+ }
+
+ /**
+ * returns the water plane
+ * @return
+ */
+ public Plane getPlane() {
+ return plane;
+ }
+
+ /**
+ * Set the water plane for this processor.
+ * @param plane
+ */
+ public void setPlane(Plane plane) {
+ this.plane.setConstant(plane.getConstant());
+ this.plane.setNormal(plane.getNormal());
+ updateClipPlanes();
+ }
+
+ /**
+ * Set the water plane using an origin (location) and a normal (reflection direction).
+ * @param origin Set to 0,-6,0 if your water quad is at that location for correct reflection
+ * @param normal Set to 0,1,0 (Vector3f.UNIT_Y) for normal planar water
+ */
+ public void setPlane(Vector3f origin, Vector3f normal) {
+ this.plane.setOriginNormal(origin, normal);
+ updateClipPlanes();
+ }
+
+ private void updateClipPlanes() {
+ reflectionClipPlane = plane.clone();
+ reflectionClipPlane.setConstant(reflectionClipPlane.getConstant() + reflectionClippingOffset);
+ refractionClipPlane = plane.clone();
+ refractionClipPlane.setConstant(refractionClipPlane.getConstant() + refractionClippingOffset);
+
+ }
+
+ /**
+ * Set the light Position for the processor
+ * @param position
+ */
+ //TODO maybe we should provide a convenient method to compute position from direction
+ public void setLightPosition(Vector3f position) {
+ material.setVector3("lightPos", position);
+ }
+
+ /**
+ * Set the color that will be added to the refraction texture.
+ * @param color
+ */
+ public void setWaterColor(ColorRGBA color) {
+ material.setColor("waterColor", color);
+ }
+
+ /**
+ * Higher values make the refraction texture shine through earlier.
+ * Default is 4
+ * @param depth
+ */
+ public void setWaterDepth(float depth) {
+ waterDepth = depth;
+ material.setFloat("waterDepth", depth);
+ }
+
+ /**
+ * return the water depth
+ * @return
+ */
+ public float getWaterDepth() {
+ return waterDepth;
+ }
+
+ /**
+ * returns water transparency
+ * @return
+ */
+ public float getWaterTransparency() {
+ return waterTransparency;
+ }
+
+ /**
+ * sets the water transparency default os 0.1f
+ * @param waterTransparency
+ */
+ public void setWaterTransparency(float waterTransparency) {
+ this.waterTransparency = Math.max(0, waterTransparency);
+ material.setFloat("waterTransparency", waterTransparency / 10);
+ }
+
+ /**
+ * Sets the speed of the wave animation, default = 0.05f.
+ * @param speed
+ */
+ public void setWaveSpeed(float speed) {
+ this.speed = speed;
+ }
+
+ /**
+ * Sets the scale of distortion by the normal map, default = 0.2
+ */
+ public void setDistortionScale(float value) {
+ material.setColor("distortionScale", new ColorRGBA(value, value, value, value));
+ }
+
+ /**
+ * Sets how the normal and dudv map are mixed to create the wave effect, default = 0.5
+ */
+ public void setDistortionMix(float value) {
+ material.setColor("distortionMix", new ColorRGBA(value, value, value, value));
+ }
+
+ /**
+ * Sets the scale of the normal/dudv texture, default = 1.
+ * Note that the waves should be scaled by the texture coordinates of the quad to avoid animation artifacts,
+ * use mesh.scaleTextureCoordinates(Vector2f) for that.
+ */
+ public void setTexScale(float value) {
+ material.setColor("texScale", new ColorRGBA(value, value, value, value));
+ }
+
+ /**
+ * retruns true if the waterprocessor is in debug mode
+ * @return
+ */
+ public boolean isDebug() {
+ return debug;
+ }
+
+ /**
+ * set to true to display reflection and refraction textures in the GUI for debug purpose
+ * @param debug
+ */
+ public void setDebug(boolean debug) {
+ this.debug = debug;
+ }
+
+ /**
+ * Creates a quad with the water material applied to it.
+ * @param width
+ * @param height
+ * @return
+ */
+ public Geometry createWaterGeometry(float width, float height) {
+ Quad quad = new Quad(width, height);
+ Geometry geom = new Geometry("WaterGeometry", quad);
+ geom.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
+ geom.setMaterial(material);
+ return geom;
+ }
+
+ /**
+ * returns the reflection clipping plane offset
+ * @return
+ */
+ public float getReflectionClippingOffset() {
+ return reflectionClippingOffset;
+ }
+
+ /**
+ * sets the reflection clipping plane offset
+ * set a nagetive value to lower the clipping plane for relection texture rendering.
+ * @param reflectionClippingOffset
+ */
+ public void setReflectionClippingOffset(float reflectionClippingOffset) {
+ this.reflectionClippingOffset = reflectionClippingOffset;
+ updateClipPlanes();
+ }
+
+ /**
+ * returns the refraction clipping plane offset
+ * @return
+ */
+ public float getRefractionClippingOffset() {
+ return refractionClippingOffset;
+ }
+
+ /**
+ * Sets the refraction clipping plane offset
+ * set a positive value to raise the clipping plane for refraction texture rendering
+ * @param refractionClippingOffset
+ */
+ public void setRefractionClippingOffset(float refractionClippingOffset) {
+ this.refractionClippingOffset = refractionClippingOffset;
+ updateClipPlanes();
+ }
+
+ /**
+ * Refraction Processor
+ */
+ public class RefractionProcessor implements SceneProcessor {
+
+ RenderManager rm;
+ ViewPort vp;
+
+ public void initialize(RenderManager rm, ViewPort vp) {
+ this.rm = rm;
+ this.vp = vp;
+ }
+
+ public void reshape(ViewPort vp, int w, int h) {
+ }
+
+ public boolean isInitialized() {
+ return rm != null;
+ }
+
+ public void preFrame(float tpf) {
+ refractionCam.setClipPlane(refractionClipPlane, Plane.Side.Negative);//,-1
+
+ }
+
+ public void postQueue(RenderQueue rq) {
+ }
+
+ public void postFrame(FrameBuffer out) {
+ }
+
+ public void cleanup() {
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/water/WaterFilter.java b/engine/src/core/com/jme3/water/WaterFilter.java
new file mode 100644
index 000000000..d54a192f2
--- /dev/null
+++ b/engine/src/core/com/jme3/water/WaterFilter.java
@@ -0,0 +1,1050 @@
+/*
+ * 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.water;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.Light;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.post.Filter;
+import com.jme3.post.Filter.Pass;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture.WrapMode;
+import com.jme3.texture.Texture2D;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * The WaterFilter is a 2D post process that simulate water.
+ * It renders water above and under water.
+ * See this blog post for more info http://jmonkeyengine.org/2011/01/15/new-advanced-water-effect-for-jmonkeyengine-3/
+ *
+ *
+ * @author Rémy Bouquet aka Nehon
+ */
+public class WaterFilter extends Filter {
+
+ private Pass reflectionPass;
+ protected Spatial reflectionScene;
+ protected ViewPort reflectionView;
+ private Texture2D normalTexture;
+ private Texture2D foamTexture;
+ private Texture2D causticsTexture;
+ private Texture2D heightTexture;
+ private Plane plane;
+ private Camera reflectionCam;
+ protected Ray ray = new Ray();
+ private Vector3f targetLocation = new Vector3f();
+ private ReflectionProcessor reflectionProcessor;
+ private Matrix4f biasMatrix = new Matrix4f(0.5f, 0.0f, 0.0f, 0.5f,
+ 0.0f, 0.5f, 0.0f, 0.5f,
+ 0.0f, 0.0f, 0.0f, 0.5f,
+ 0.0f, 0.0f, 0.0f, 1.0f);
+ private Matrix4f textureProjMatrix = new Matrix4f();
+ private boolean underWater;
+ private RenderManager renderManager;
+ private ViewPort viewPort;
+ private float time = 0;
+ //properties
+ private float speed = 1;
+ private Vector3f lightDirection = new Vector3f(0, -1, 0);
+ private ColorRGBA lightColor = ColorRGBA.White;
+ private float waterHeight = 0.0f;
+ private ColorRGBA waterColor = new ColorRGBA(0.0078f, 0.3176f, 0.5f, 1.0f);
+ private ColorRGBA deepWaterColor = new ColorRGBA(0.0039f, 0.00196f, 0.145f, 1.0f);
+ private Vector3f colorExtinction = new Vector3f(5.0f, 20.0f, 30.0f);
+ private float waterTransparency = 0.1f;
+ private float maxAmplitude = 1.5f;
+ private float shoreHardness = 0.1f;
+ private boolean useFoam = true;
+ private float foamIntensity = 0.5f;
+ private float foamHardness = 1.0f;
+ private Vector3f foamExistence = new Vector3f(0.45f, 4.35f, 1.5f);
+ private float waveScale = 0.005f;
+ private float sunScale = 3.0f;
+ private float shininess = 0.7f;
+ private Vector2f windDirection = new Vector2f(0.0f, -1.0f);
+ private int reflectionMapSize = 512;
+ private boolean useRipples = true;
+ private float normalScale = 3.0f;
+ private boolean useHQShoreline = true;
+ private boolean useSpecular = true;
+ private boolean useRefraction = true;
+ private float refractionStrength = 0.0f;
+ private float refractionConstant = 0.5f;
+ private float reflectionDisplace = 30;
+ private float underWaterFogDistance = 120;
+ private boolean useCaustics = true;
+ private float causticsIntensity = 0.5f;
+
+ /**
+ * Create a Water Filter
+ */
+ public WaterFilter() {
+ super("WaterFilter");
+ }
+
+ public WaterFilter(Node reflectionScene, Vector3f lightDirection) {
+ super("WaterFilter");
+ this.reflectionScene = reflectionScene;
+ this.lightDirection = lightDirection;
+ }
+
+ @Override
+ protected boolean isRequiresDepthTexture() {
+ return true;
+ }
+
+ @Override
+ protected void preFrame(float tpf) {
+ time = time + (tpf * speed);
+ material.setFloat("Time", time);
+ Camera sceneCam = viewPort.getCamera();
+ biasMatrix.mult(sceneCam.getViewProjectionMatrix(), textureProjMatrix);
+ material.setMatrix4("TextureProjMatrix", textureProjMatrix);
+ material.setVector3("CameraPosition", sceneCam.getLocation());
+ material.setMatrix4("ViewProjectionMatrixInverse", sceneCam.getViewProjectionMatrix().invert());
+
+ material.setFloat("WaterHeight", waterHeight);
+
+ //update reflection cam
+ ray.setOrigin(sceneCam.getLocation());
+ ray.setDirection(sceneCam.getDirection());
+ plane = new Plane(Vector3f.UNIT_Y, new Vector3f(0, waterHeight, 0).dot(Vector3f.UNIT_Y));
+ reflectionProcessor.setReflectionClipPlane(plane);
+ boolean inv = false;
+ if (!ray.intersectsWherePlane(plane, targetLocation)) {
+ ray.setDirection(ray.getDirection().negateLocal());
+ ray.intersectsWherePlane(plane, targetLocation);
+ inv = true;
+ }
+ Vector3f loc = plane.reflect(sceneCam.getLocation(), new Vector3f());
+ reflectionCam.setLocation(loc);
+ reflectionCam.setFrustum(sceneCam.getFrustumNear(),
+ sceneCam.getFrustumFar(),
+ sceneCam.getFrustumLeft(),
+ sceneCam.getFrustumRight(),
+ sceneCam.getFrustumTop(),
+ sceneCam.getFrustumBottom());
+ TempVars vars = TempVars.get();
+
+
+ vars.vect1.set(sceneCam.getLocation()).addLocal(sceneCam.getUp());
+ float planeDistance = plane.pseudoDistance(vars.vect1);
+ vars.vect2.set(plane.getNormal()).multLocal(planeDistance * 2.0f);
+ vars.vect3.set(vars.vect1.subtractLocal(vars.vect2)).subtractLocal(loc).normalizeLocal().negateLocal();
+
+ reflectionCam.lookAt(targetLocation, vars.vect3);
+ vars.release();
+
+ if (inv) {
+ reflectionCam.setAxes(reflectionCam.getLeft().negateLocal(), reflectionCam.getUp(), reflectionCam.getDirection().negateLocal());
+ }
+
+ //if we're under water no need to compute reflection
+ if (sceneCam.getLocation().y >= waterHeight) {
+ boolean rtb = true;
+ if (!renderManager.isHandleTranslucentBucket()) {
+ renderManager.setHandleTranslucentBucket(true);
+ rtb = false;
+ }
+ renderManager.renderViewPort(reflectionView, tpf);
+ if (!rtb) {
+ renderManager.setHandleTranslucentBucket(false);
+ }
+ renderManager.setCamera(sceneCam, false);
+ renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer());
+
+
+ underWater = false;
+ } else {
+ underWater = true;
+ }
+ }
+
+ @Override
+ protected Material getMaterial() {
+ return material;
+ }
+
+ private DirectionalLight findLight(Node node) {
+ for (Light light : node.getWorldLightList()) {
+ if (light instanceof DirectionalLight) {
+ return (DirectionalLight) light;
+ }
+ }
+ for (Spatial child : node.getChildren()) {
+ if (child instanceof Node) {
+ return findLight((Node) child);
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+
+ if (reflectionScene == null) {
+ reflectionScene = vp.getScenes().get(0);
+ DirectionalLight l = findLight((Node) reflectionScene);
+ if (l != null) {
+ lightDirection = l.getDirection();
+ }
+
+ }
+
+ this.renderManager = renderManager;
+ this.viewPort = vp;
+ reflectionPass = new Pass();
+ reflectionPass.init(renderManager.getRenderer(), reflectionMapSize, reflectionMapSize, Format.RGBA8, Format.Depth);
+ reflectionCam = new Camera(reflectionMapSize, reflectionMapSize);
+ reflectionView = new ViewPort("reflectionView", reflectionCam);
+ reflectionView.setClearFlags(true, true, true);
+ reflectionView.attachScene(reflectionScene);
+ reflectionView.setOutputFrameBuffer(reflectionPass.getRenderFrameBuffer());
+ plane = new Plane(Vector3f.UNIT_Y, new Vector3f(0, waterHeight, 0).dot(Vector3f.UNIT_Y));
+ reflectionProcessor = new ReflectionProcessor(reflectionCam, reflectionPass.getRenderFrameBuffer(), plane);
+ reflectionView.addProcessor(reflectionProcessor);
+
+ normalTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/water_normalmap.dds");
+ if (foamTexture == null) {
+ foamTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/foam.jpg");
+ }
+ if (causticsTexture == null) {
+ causticsTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/caustics.jpg");
+ }
+ heightTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/heightmap.jpg");
+
+ normalTexture.setWrap(WrapMode.Repeat);
+ foamTexture.setWrap(WrapMode.Repeat);
+ causticsTexture.setWrap(WrapMode.Repeat);
+ heightTexture.setWrap(WrapMode.Repeat);
+
+ material = new Material(manager, "Common/MatDefs/Water/Water.j3md");
+ material.setTexture("HeightMap", heightTexture);
+ material.setTexture("CausticsMap", causticsTexture);
+ material.setTexture("FoamMap", foamTexture);
+ material.setTexture("NormalMap", normalTexture);
+ material.setTexture("ReflectionMap", reflectionPass.getRenderedTexture());
+
+ material.setFloat("WaterTransparency", waterTransparency);
+ material.setFloat("NormalScale", normalScale);
+ material.setFloat("R0", refractionConstant);
+ material.setFloat("MaxAmplitude", maxAmplitude);
+ material.setVector3("LightDir", lightDirection);
+ material.setColor("LightColor", lightColor);
+ material.setFloat("ShoreHardness", shoreHardness);
+ material.setFloat("RefractionStrength", refractionStrength);
+ material.setFloat("WaveScale", waveScale);
+ material.setVector3("FoamExistence", foamExistence);
+ material.setFloat("SunScale", sunScale);
+ material.setVector3("ColorExtinction", colorExtinction);
+ material.setFloat("Shininess", shininess);
+ material.setColor("WaterColor", waterColor);
+ material.setColor("DeepWaterColor", deepWaterColor);
+ material.setVector2("WindDirection", windDirection);
+ material.setFloat("FoamHardness", foamHardness);
+ material.setBoolean("UseRipples", useRipples);
+ material.setBoolean("UseHQShoreline", useHQShoreline);
+ material.setBoolean("UseSpecular", useSpecular);
+ material.setBoolean("UseFoam", useFoam);
+ material.setBoolean("UseCaustics", useCaustics);
+ material.setBoolean("UseRefraction", useRefraction);
+ material.setFloat("ReflectionDisplace", reflectionDisplace);
+ material.setFloat("FoamIntensity", foamIntensity);
+ material.setFloat("UnderWaterFogDistance", underWaterFogDistance);
+ material.setFloat("CausticsIntensity", causticsIntensity);
+
+
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+
+ oc.write(speed, "speed", 1f);
+ oc.write(lightDirection, "lightDirection", new Vector3f(0, -1, 0));
+ oc.write(lightColor, "lightColor", ColorRGBA.White);
+ oc.write(waterHeight, "waterHeight", 0.0f);
+ oc.write(waterColor, "waterColor", new ColorRGBA(0.0078f, 0.3176f, 0.5f, 1.0f));
+ oc.write(deepWaterColor, "deepWaterColor", new ColorRGBA(0.0039f, 0.00196f, 0.145f, 1.0f));
+
+ oc.write(colorExtinction, "colorExtinction", new Vector3f(5.0f, 20.0f, 30.0f));
+ oc.write(waterTransparency, "waterTransparency", 0.1f);
+ oc.write(maxAmplitude, "maxAmplitude", 1.5f);
+ oc.write(shoreHardness, "shoreHardness", 0.1f);
+ oc.write(useFoam, "useFoam", true);
+
+ oc.write(foamIntensity, "foamIntensity", 0.5f);
+ oc.write(foamHardness, "foamHardness", 1.0f);
+
+ oc.write(foamExistence, "foamExistence", new Vector3f(0.45f, 4.35f, 1.5f));
+ oc.write(waveScale, "waveScale", 0.005f);
+
+ oc.write(sunScale, "sunScale", 3.0f);
+ oc.write(shininess, "shininess", 0.7f);
+ oc.write(windDirection, "windDirection", new Vector2f(0.0f, -1.0f));
+ oc.write(reflectionMapSize, "reflectionMapSize", 512);
+ oc.write(useRipples, "useRipples", true);
+
+ oc.write(normalScale, "normalScale", 3.0f);
+ oc.write(useHQShoreline, "useHQShoreline", true);
+
+ oc.write(useSpecular, "useSpecular", true);
+
+ oc.write(useRefraction, "useRefraction", true);
+ oc.write(refractionStrength, "refractionStrength", 0.0f);
+ oc.write(refractionConstant, "refractionConstant", 0.5f);
+ oc.write(reflectionDisplace, "reflectionDisplace", 30f);
+ oc.write(underWaterFogDistance, "underWaterFogDistance", 120f);
+ oc.write(causticsIntensity, "causticsIntensity", 0.5f);
+
+ oc.write(useCaustics, "useCaustics", true);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ speed = ic.readFloat("speed", 1f);
+ lightDirection = (Vector3f) ic.readSavable("lightDirection", new Vector3f(0, -1, 0));
+ lightColor = (ColorRGBA) ic.readSavable("lightColor", ColorRGBA.White);
+ waterHeight = ic.readFloat("waterHeight", 0.0f);
+ waterColor = (ColorRGBA) ic.readSavable("waterColor", new ColorRGBA(0.0078f, 0.3176f, 0.5f, 1.0f));
+ deepWaterColor = (ColorRGBA) ic.readSavable("deepWaterColor", new ColorRGBA(0.0039f, 0.00196f, 0.145f, 1.0f));
+
+ colorExtinction = (Vector3f) ic.readSavable("colorExtinction", new Vector3f(5.0f, 20.0f, 30.0f));
+ waterTransparency = ic.readFloat("waterTransparency", 0.1f);
+ maxAmplitude = ic.readFloat("maxAmplitude", 1.5f);
+ shoreHardness = ic.readFloat("shoreHardness", 0.1f);
+ useFoam = ic.readBoolean("useFoam", true);
+
+ foamIntensity = ic.readFloat("foamIntensity", 0.5f);
+ foamHardness = ic.readFloat("foamHardness", 1.0f);
+
+ foamExistence = (Vector3f) ic.readSavable("foamExistence", new Vector3f(0.45f, 4.35f, 1.5f));
+ waveScale = ic.readFloat("waveScale", 0.005f);
+
+ sunScale = ic.readFloat("sunScale", 3.0f);
+ shininess = ic.readFloat("shininess", 0.7f);
+ windDirection = (Vector2f) ic.readSavable("windDirection", new Vector2f(0.0f, -1.0f));
+ reflectionMapSize = ic.readInt("reflectionMapSize", 512);
+ useRipples = ic.readBoolean("useRipples", true);
+
+ normalScale = ic.readFloat("normalScale", 3.0f);
+ useHQShoreline = ic.readBoolean("useHQShoreline", true);
+
+ useSpecular = ic.readBoolean("useSpecular", true);
+
+ useRefraction = ic.readBoolean("useRefraction", true);
+ refractionStrength = ic.readFloat("refractionStrength", 0.0f);
+ refractionConstant = ic.readFloat("refractionConstant", 0.5f);
+ reflectionDisplace = ic.readFloat("reflectionDisplace", 30f);
+ underWaterFogDistance = ic.readFloat("underWaterFogDistance", 120f);
+ causticsIntensity = ic.readFloat("causticsIntensity", 0.5f);
+
+ useCaustics = ic.readBoolean("useCaustics", true);
+
+ }
+
+ /**
+ * gets the height of the water plane
+ * @return
+ */
+ public float getWaterHeight() {
+ return waterHeight;
+ }
+
+ /**
+ * Sets the height of the water plane
+ * default is 0.0
+ * @param waterHeight
+ */
+ public void setWaterHeight(float waterHeight) {
+ this.waterHeight = waterHeight;
+ }
+
+ /**
+ * sets the scene to render in the reflection map
+ * @param reflectionScene
+ */
+ public void setReflectionScene(Spatial reflectionScene) {
+ this.reflectionScene = reflectionScene;
+ }
+
+ /**
+ * returns the waterTransparency value
+ * @return
+ */
+ public float getWaterTransparency() {
+ return waterTransparency;
+ }
+
+ /**
+ * Sets how fast will colours fade out. You can also think about this
+ * values as how clear water is. Therefore use smaller values (eg. 0.05)
+ * to have crystal clear water and bigger to achieve "muddy" water.
+ * default is 0.1f
+ * @param waterTransparency
+ */
+ public void setWaterTransparency(float waterTransparency) {
+ this.waterTransparency = waterTransparency;
+ if (material != null) {
+ material.setFloat("WaterTransparency", waterTransparency);
+ }
+ }
+
+ /**
+ * Returns the normal scales applied to the normal map
+ * @return
+ */
+ public float getNormalScale() {
+ return normalScale;
+ }
+
+ /**
+ * Sets the normal scaling factors to apply to the normal map.
+ * the higher the value the more small ripples will be visible on the waves.
+ * default is 1.0
+ * @param normalScale
+ */
+ public void setNormalScale(float normalScale) {
+ this.normalScale = normalScale;
+ if (material != null) {
+ material.setFloat("NormalScale", normalScale);
+ }
+ }
+
+ /**
+ * returns the refractoin constant
+ * @return
+ */
+ public float getRefractionConstant() {
+ return refractionConstant;
+ }
+
+ /**
+ * This is a constant related to the index of refraction (IOR) used to compute the fresnel term.
+ * F = R0 + (1-R0)( 1 - N.V)^5
+ * where F is the fresnel term, R0 the constant, N the normal vector and V tne view vector.
+ * It usually depend on the material you are lookinh through (here water).
+ * Default value is 0.3f
+ * In practice, the lowest the value and the less the reflection can be seen on water
+ * @param refractionConstant
+ */
+ public void setRefractionConstant(float refractionConstant) {
+ this.refractionConstant = refractionConstant;
+ if (material != null) {
+ material.setFloat("R0", refractionConstant);
+ }
+ }
+
+ /**
+ * return the maximum wave amplitude
+ * @return
+ */
+ public float getMaxAmplitude() {
+ return maxAmplitude;
+ }
+
+ /**
+ * Sets the maximum waves amplitude
+ * default is 1.0
+ * @param maxAmplitude
+ */
+ public void setMaxAmplitude(float maxAmplitude) {
+ this.maxAmplitude = maxAmplitude;
+ if (material != null) {
+ material.setFloat("MaxAmplitude", maxAmplitude);
+ }
+ }
+
+ /**
+ * gets the light direction
+ * @return
+ */
+ public Vector3f getLightDirection() {
+ return lightDirection;
+ }
+
+ /**
+ * Sets the light direction
+ * @param lightDirection
+ */
+ public void setLightDirection(Vector3f lightDirection) {
+ this.lightDirection = lightDirection;
+ if (material != null) {
+ material.setVector3("LightDir", lightDirection);
+ }
+ }
+
+ /**
+ * returns the light color
+ * @return
+ */
+ public ColorRGBA getLightColor() {
+ return lightColor;
+ }
+
+ /**
+ * Sets the light color to use
+ * default is white
+ * @param lightColor
+ */
+ public void setLightColor(ColorRGBA lightColor) {
+ this.lightColor = lightColor;
+ if (material != null) {
+ material.setColor("LightColor", lightColor);
+ }
+ }
+
+ /**
+ * Return the shoreHardeness
+ * @return
+ */
+ public float getShoreHardness() {
+ return shoreHardness;
+ }
+
+ /**
+ * The smaller this value is, the softer the transition between
+ * shore and water. If you want hard edges use very big value.
+ * Default is 0.1f.
+ * @param shoreHardness
+ */
+ public void setShoreHardness(float shoreHardness) {
+ this.shoreHardness = shoreHardness;
+ if (material != null) {
+ material.setFloat("ShoreHardness", shoreHardness);
+ }
+ }
+
+ /**
+ * returns the foam hardness
+ * @return
+ */
+ public float getFoamHardness() {
+ return foamHardness;
+ }
+
+ /**
+ * Sets the foam hardness : How much the foam will blend with the shore to avoid hard edged water plane.
+ * Default is 1.0
+ * @param foamHardness
+ */
+ public void setFoamHardness(float foamHardness) {
+ this.foamHardness = foamHardness;
+ if (material != null) {
+ material.setFloat("FoamHardness", foamHardness);
+ }
+ }
+
+ /**
+ * returns the refractionStrenght
+ * @return
+ */
+ public float getRefractionStrength() {
+ return refractionStrength;
+ }
+
+ /**
+ * This value modifies current fresnel term. If you want to weaken
+ * reflections use bigger value. If you want to empasize them use
+ * value smaller then 0. Default is 0.0f.
+ * @param refractionStrength
+ */
+ public void setRefractionStrength(float refractionStrength) {
+ this.refractionStrength = refractionStrength;
+ if (material != null) {
+ material.setFloat("RefractionStrength", refractionStrength);
+ }
+ }
+
+ /**
+ * returns the scale factor of the waves height map
+ * @return
+ */
+ public float getWaveScale() {
+ return waveScale;
+ }
+
+ /**
+ * Sets the scale factor of the waves height map
+ * the smaller the value the bigger the waves
+ * default is 0.005f
+ * @param waveScale
+ */
+ public void setWaveScale(float waveScale) {
+ this.waveScale = waveScale;
+ if (material != null) {
+ material.setFloat("WaveScale", waveScale);
+ }
+ }
+
+ /**
+ * returns the foam existance vector
+ * @return
+ */
+ public Vector3f getFoamExistence() {
+ return foamExistence;
+ }
+
+ /**
+ * Describes at what depth foam starts to fade out and
+ * at what it is completely invisible. The third value is at
+ * what height foam for waves appear (+ waterHeight).
+ * default is (0.45, 4.35, 1.0);
+ * @param foamExistence
+ */
+ public void setFoamExistence(Vector3f foamExistence) {
+ this.foamExistence = foamExistence;
+ if (material != null) {
+ material.setVector3("FoamExistence", foamExistence);
+ }
+ }
+
+ /**
+ * gets the scale of the sun
+ * @return
+ */
+ public float getSunScale() {
+ return sunScale;
+ }
+
+ /**
+ * Sets the scale of the sun for specular effect
+ * @param sunScale
+ */
+ public void setSunScale(float sunScale) {
+ this.sunScale = sunScale;
+ if (material != null) {
+ material.setFloat("SunScale", sunScale);
+ }
+ }
+
+ /**
+ * Returns the color exctinction vector of the water
+ * @return
+ */
+ public Vector3f getColorExtinction() {
+ return colorExtinction;
+ }
+
+ /**
+ * Return at what depth the refraction color extinct
+ * the first value is for red
+ * the second is for green
+ * the third is for blue
+ * Play with thos parameters to "trouble" the water
+ * default is (5.0, 20.0, 30.0f);
+ * @param colorExtinction
+ */
+ public void setColorExtinction(Vector3f colorExtinction) {
+ this.colorExtinction = colorExtinction;
+ if (material != null) {
+ material.setVector3("ColorExtinction", colorExtinction);
+ }
+ }
+
+ /**
+ * Sets the foam texture
+ * @param foamTexture
+ */
+ public void setFoamTexture(Texture2D foamTexture) {
+ this.foamTexture = foamTexture;
+ foamTexture.setWrap(WrapMode.Repeat);
+ if (material != null) {
+ material.setTexture("FoamMap", foamTexture);
+ }
+ }
+
+ /**
+ * Sets the height texture
+ * @param heightTexture
+ */
+ public void setHeightTexture(Texture2D heightTexture) {
+ this.heightTexture = heightTexture;
+ heightTexture.setWrap(WrapMode.Repeat);
+ }
+
+ /**
+ * Sets the normal Texture
+ * @param normalTexture
+ */
+ public void setNormalTexture(Texture2D normalTexture) {
+ this.normalTexture = normalTexture;
+ normalTexture.setWrap(WrapMode.Repeat);
+ }
+
+ /**
+ * return the shininess factor of the water
+ * @return
+ */
+ public float getShininess() {
+ return shininess;
+ }
+
+ /**
+ * Sets the shinines factor of the water
+ * default is 0.7f
+ * @param shininess
+ */
+ public void setShininess(float shininess) {
+ this.shininess = shininess;
+ if (material != null) {
+ material.setFloat("Shininess", shininess);
+ }
+ }
+
+ /**
+ * retruns the speed of the waves
+ * @return
+ */
+ public float getSpeed() {
+ return speed;
+ }
+
+ /**
+ * Set the speed of the waves (0.0 is still) default is 1.0
+ * @param speed
+ */
+ public void setSpeed(float speed) {
+ this.speed = speed;
+ }
+
+ /**
+ * returns the color of the water
+ *
+ * @return
+ */
+ public ColorRGBA getWaterColor() {
+ return waterColor;
+ }
+
+ /**
+ * Sets the color of the water
+ * see setDeepWaterColor for deep water color
+ * default is (0.0078f, 0.5176f, 0.5f,1.0f) (greenish blue)
+ * @param waterColour
+ */
+ public void setWaterColor(ColorRGBA waterColor) {
+ this.waterColor = waterColor;
+ if (material != null) {
+ material.setColor("WaterColor", waterColor);
+ }
+ }
+
+ /**
+ * returns the deep water color
+ * @return
+ */
+ public ColorRGBA getDeepWaterColor() {
+ return deepWaterColor;
+ }
+
+ /**
+ * sets the deep water color
+ * see setWaterColor for general color
+ * default is (0.0039f, 0.00196f, 0.145f,1.0f) (very dark blue)
+ * @param deepWaterColor
+ */
+ public void setDeepWaterColor(ColorRGBA deepWaterColor) {
+ this.deepWaterColor = deepWaterColor;
+ if (material != null) {
+ material.setColor("DeepWaterColor", deepWaterColor);
+ }
+ }
+
+ /**
+ * returns the wind direction
+ * @return
+ */
+ public Vector2f getWindDirection() {
+ return windDirection;
+ }
+
+ /**
+ * sets the wind direction
+ * the direction where the waves move
+ * default is (0.0f, -1.0f)
+ * @param windDirection
+ */
+ public void setWindDirection(Vector2f windDirection) {
+ this.windDirection = windDirection;
+ if (material != null) {
+ material.setVector2("WindDirection", windDirection);
+ }
+ }
+
+ /**
+ * returns the size of the reflection map
+ * @return
+ */
+ public int getReflectionMapSize() {
+ return reflectionMapSize;
+ }
+
+ /**
+ * Sets the size of the reflection map
+ * default is 512, the higher, the better quality, but the slower the effect.
+ * @param reflectionMapSize
+ */
+ public void setReflectionMapSize(int reflectionMapSize) {
+ this.reflectionMapSize = reflectionMapSize;
+ }
+
+ /**
+ * returns true if the water uses foam
+ * @return
+ */
+ public boolean isUseFoam() {
+ return useFoam;
+ }
+
+ /**
+ * set to true to use foam with water
+ * default true
+ * @param useFoam
+ */
+ public void setUseFoam(boolean useFoam) {
+ this.useFoam = useFoam;
+ if (material != null) {
+ material.setBoolean("UseFoam", useFoam);
+ }
+
+ }
+
+ /**
+ * sets the texture to use to render caustics on the ground underwater
+ * @param causticsTexture
+ */
+ public void setCausticsTexture(Texture2D causticsTexture) {
+ this.causticsTexture = causticsTexture;
+ if (material != null) {
+ material.setTexture("causticsMap", causticsTexture);
+ }
+ }
+
+ /**
+ * returns true if caustics are rendered
+ * @return
+ */
+ public boolean isUseCaustics() {
+ return useCaustics;
+ }
+
+ /**
+ * set to true if you want caustics to be rendered on the ground underwater, false otherwise
+ * @param useCaustics
+ */
+ public void setUseCaustics(boolean useCaustics) {
+ this.useCaustics = useCaustics;
+ if (material != null) {
+ material.setBoolean("UseCaustics", useCaustics);
+ }
+ }
+
+ /**
+ * return true
+ * @return
+ */
+ public boolean isUseHQShoreline() {
+ return useHQShoreline;
+ }
+
+ public void setUseHQShoreline(boolean useHQShoreline) {
+ this.useHQShoreline = useHQShoreline;
+ if (material != null) {
+ material.setBoolean("UseHQShoreline", useHQShoreline);
+ }
+
+ }
+
+ /**
+ * returns true if the water use the refraction
+ * @return
+ */
+ public boolean isUseRefraction() {
+ return useRefraction;
+ }
+
+ /**
+ * set to true to use refraction (default is true)
+ * @param useRefraction
+ */
+ public void setUseRefraction(boolean useRefraction) {
+ this.useRefraction = useRefraction;
+ if (material != null) {
+ material.setBoolean("UseRefraction", useRefraction);
+ }
+
+ }
+
+ /**
+ * returns true if the ater use ripples
+ * @return
+ */
+ public boolean isUseRipples() {
+ return useRipples;
+ }
+
+ /**
+ *
+ * Set to true tu use ripples
+ * @param useRipples
+ */
+ public void setUseRipples(boolean useRipples) {
+ this.useRipples = useRipples;
+ if (material != null) {
+ material.setBoolean("UseRipples", useRipples);
+ }
+
+ }
+
+ /**
+ * returns true if the water use specular
+ * @return
+ */
+ public boolean isUseSpecular() {
+ return useSpecular;
+ }
+
+ /**
+ * Set to true to use specular lightings on the water
+ * @param useSpecular
+ */
+ public void setUseSpecular(boolean useSpecular) {
+ this.useSpecular = useSpecular;
+ if (material != null) {
+ material.setBoolean("UseSpecular", useSpecular);
+ }
+ }
+
+ /**
+ * returns the foam intensity
+ * @return
+ */
+ public float getFoamIntensity() {
+ return foamIntensity;
+ }
+
+ /**
+ * sets the foam intensity default is 0.5f
+ * @param foamIntensity
+ */
+ public void setFoamIntensity(float foamIntensity) {
+ this.foamIntensity = foamIntensity;
+ if (material != null) {
+ material.setFloat("FoamIntensity", foamIntensity);
+
+ }
+ }
+
+ /**
+ * returns the reflection displace
+ * see {@link setReflectionDisplace(float reflectionDisplace)}
+ * @return
+ */
+ public float getReflectionDisplace() {
+ return reflectionDisplace;
+ }
+
+ /**
+ * Sets the reflection displace. define how troubled will look the reflection in the water. default is 30
+ * @param reflectionDisplace
+ */
+ public void setReflectionDisplace(float reflectionDisplace) {
+ this.reflectionDisplace = reflectionDisplace;
+ if (material != null) {
+ material.setFloat("m_ReflectionDisplace", reflectionDisplace);
+ }
+ }
+
+ /**
+ * returns true if the camera is under the water level
+ * @return
+ */
+ public boolean isUnderWater() {
+ return underWater;
+ }
+
+ /**
+ * returns the distance of the fog when under water
+ * @return
+ */
+ public float getUnderWaterFogDistance() {
+ return underWaterFogDistance;
+ }
+
+ /**
+ * sets the distance of the fog when under water.
+ * default is 120 (120 world units) use a high value to raise the view range under water
+ * @param underWaterFogDistance
+ */
+ public void setUnderWaterFogDistance(float underWaterFogDistance) {
+ this.underWaterFogDistance = underWaterFogDistance;
+ if (material != null) {
+ material.setFloat("UnderWaterFogDistance", underWaterFogDistance);
+ }
+ }
+
+ /**
+ * get the intensity of caustics under water
+ * @return
+ */
+ public float getCausticsIntensity() {
+ return causticsIntensity;
+ }
+
+ /**
+ * sets the intensity of caustics under water. goes from 0 to 1, default is 0.5f
+ * @param causticsIntensity
+ */
+ public void setCausticsIntensity(float causticsIntensity) {
+ this.causticsIntensity = causticsIntensity;
+ if (material != null) {
+ material.setFloat("CausticsIntensity", causticsIntensity);
+ }
+ }
+}