| /* |
| * 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 : |
| * <code> |
| * 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); |
| * </code> |
| * @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() { |
| } |
| } |
| } |