Home | History | Annotate | Download | only in water
      1 /*
      2  * Copyright (c) 2009-2010 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 package com.jme3.water;
     33 
     34 import com.jme3.asset.AssetManager;
     35 import com.jme3.material.Material;
     36 import com.jme3.math.*;
     37 import com.jme3.post.SceneProcessor;
     38 import com.jme3.renderer.Camera;
     39 import com.jme3.renderer.RenderManager;
     40 import com.jme3.renderer.Renderer;
     41 import com.jme3.renderer.ViewPort;
     42 import com.jme3.renderer.queue.RenderQueue;
     43 import com.jme3.scene.Geometry;
     44 import com.jme3.scene.Spatial;
     45 import com.jme3.scene.shape.Quad;
     46 import com.jme3.texture.FrameBuffer;
     47 import com.jme3.texture.Image.Format;
     48 import com.jme3.texture.Texture.WrapMode;
     49 import com.jme3.texture.Texture2D;
     50 import com.jme3.ui.Picture;
     51 
     52 /**
     53  *
     54  * Simple Water renders a simple plane that use reflection and refraction to look like water.
     55  * It's pretty basic, but much faster than the WaterFilter
     56  * It's useful if you aim low specs hardware and still want a good looking water.
     57  * Usage is :
     58  * <code>
     59  *      SimpleWaterProcessor waterProcessor = new SimpleWaterProcessor(assetManager);
     60  *      //setting the scene to use for reflection
     61  *      waterProcessor.setReflectionScene(mainScene);
     62  *      //setting the light position
     63  *      waterProcessor.setLightPosition(lightPos);
     64  *
     65  *      //setting the water plane
     66  *      Vector3f waterLocation=new Vector3f(0,-20,0);
     67  *      waterProcessor.setPlane(new Plane(Vector3f.UNIT_Y, waterLocation.dot(Vector3f.UNIT_Y)));
     68  *      //setting the water color
     69  *      waterProcessor.setWaterColor(ColorRGBA.Brown);
     70  *
     71  *      //creating a quad to render water to
     72  *      Quad quad = new Quad(400,400);
     73  *
     74  *      //the texture coordinates define the general size of the waves
     75  *      quad.scaleTextureCoordinates(new Vector2f(6f,6f));
     76  *
     77  *      //creating a geom to attach the water material
     78  *      Geometry water=new Geometry("water", quad);
     79  *      water.setLocalTranslation(-200, -20, 250);
     80  *      water.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
     81  *      //finally setting the material
     82  *      water.setMaterial(waterProcessor.getMaterial());
     83  *
     84  *      //attaching the water to the root node
     85  *      rootNode.attachChild(water);
     86  * </code>
     87  * @author Normen Hansen & Rmy Bouquet
     88  */
     89 public class SimpleWaterProcessor implements SceneProcessor {
     90 
     91     protected RenderManager rm;
     92     protected ViewPort vp;
     93     protected Spatial reflectionScene;
     94     protected ViewPort reflectionView;
     95     protected ViewPort refractionView;
     96     protected FrameBuffer reflectionBuffer;
     97     protected FrameBuffer refractionBuffer;
     98     protected Camera reflectionCam;
     99     protected Camera refractionCam;
    100     protected Texture2D reflectionTexture;
    101     protected Texture2D refractionTexture;
    102     protected Texture2D depthTexture;
    103     protected Texture2D normalTexture;
    104     protected Texture2D dudvTexture;
    105     protected int renderWidth = 512;
    106     protected int renderHeight = 512;
    107     protected Plane plane = new Plane(Vector3f.UNIT_Y, Vector3f.ZERO.dot(Vector3f.UNIT_Y));
    108     protected float speed = 0.05f;
    109     protected Ray ray = new Ray();
    110     protected Vector3f targetLocation = new Vector3f();
    111     protected AssetManager manager;
    112     protected Material material;
    113     protected float waterDepth = 1;
    114     protected float waterTransparency = 0.4f;
    115     protected boolean debug = false;
    116     private Picture dispRefraction;
    117     private Picture dispReflection;
    118     private Picture dispDepth;
    119     private Plane reflectionClipPlane;
    120     private Plane refractionClipPlane;
    121     private float refractionClippingOffset = 0.3f;
    122     private float reflectionClippingOffset = -5f;
    123     private Vector3f vect1 = new Vector3f();
    124     private Vector3f vect2 = new Vector3f();
    125     private Vector3f vect3 = new Vector3f();
    126 
    127     /**
    128      * Creates a SimpleWaterProcessor
    129      * @param manager the asset manager
    130      */
    131     public SimpleWaterProcessor(AssetManager manager) {
    132         this.manager = manager;
    133         material = new Material(manager, "Common/MatDefs/Water/SimpleWater.j3md");
    134         material.setFloat("waterDepth", waterDepth);
    135         material.setFloat("waterTransparency", waterTransparency / 10);
    136         material.setColor("waterColor", ColorRGBA.White);
    137         material.setVector3("lightPos", new Vector3f(1, -1, 1));
    138 
    139         material.setColor("distortionScale", new ColorRGBA(0.2f, 0.2f, 0.2f, 0.2f));
    140         material.setColor("distortionMix", new ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f));
    141         material.setColor("texScale", new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
    142         updateClipPlanes();
    143 
    144     }
    145 
    146     public void initialize(RenderManager rm, ViewPort vp) {
    147         this.rm = rm;
    148         this.vp = vp;
    149 
    150         loadTextures(manager);
    151         createTextures();
    152         applyTextures(material);
    153 
    154         createPreViews();
    155 
    156         material.setVector2("FrustumNearFar", new Vector2f(vp.getCamera().getFrustumNear(), vp.getCamera().getFrustumFar()));
    157 
    158         if (debug) {
    159             dispRefraction = new Picture("dispRefraction");
    160             dispRefraction.setTexture(manager, refractionTexture, false);
    161             dispReflection = new Picture("dispRefraction");
    162             dispReflection.setTexture(manager, reflectionTexture, false);
    163             dispDepth = new Picture("depthTexture");
    164             dispDepth.setTexture(manager, depthTexture, false);
    165         }
    166     }
    167 
    168     public void reshape(ViewPort vp, int w, int h) {
    169     }
    170 
    171     public boolean isInitialized() {
    172         return rm != null;
    173     }
    174     float time = 0;
    175     float savedTpf = 0;
    176 
    177     public void preFrame(float tpf) {
    178         time = time + (tpf * speed);
    179         if (time > 1f) {
    180             time = 0;
    181         }
    182         material.setFloat("time", time);
    183         savedTpf = tpf;
    184     }
    185 
    186     public void postQueue(RenderQueue rq) {
    187         Camera sceneCam = rm.getCurrentCamera();
    188 
    189         //update ray
    190         ray.setOrigin(sceneCam.getLocation());
    191         ray.setDirection(sceneCam.getDirection());
    192 
    193         //update refraction cam
    194         refractionCam.setLocation(sceneCam.getLocation());
    195         refractionCam.setRotation(sceneCam.getRotation());
    196         refractionCam.setFrustum(sceneCam.getFrustumNear(),
    197                 sceneCam.getFrustumFar(),
    198                 sceneCam.getFrustumLeft(),
    199                 sceneCam.getFrustumRight(),
    200                 sceneCam.getFrustumTop(),
    201                 sceneCam.getFrustumBottom());
    202 
    203         //update reflection cam
    204         boolean inv = false;
    205         if (!ray.intersectsWherePlane(plane, targetLocation)) {
    206             ray.setDirection(ray.getDirection().negateLocal());
    207             ray.intersectsWherePlane(plane, targetLocation);
    208             inv = true;
    209         }
    210         Vector3f loc = plane.reflect(sceneCam.getLocation(), new Vector3f());
    211         reflectionCam.setLocation(loc);
    212         reflectionCam.setFrustum(sceneCam.getFrustumNear(),
    213                 sceneCam.getFrustumFar(),
    214                 sceneCam.getFrustumLeft(),
    215                 sceneCam.getFrustumRight(),
    216                 sceneCam.getFrustumTop(),
    217                 sceneCam.getFrustumBottom());
    218         // tempVec and calcVect are just temporary vector3f objects
    219         vect1.set(sceneCam.getLocation()).addLocal(sceneCam.getUp());
    220         float planeDistance = plane.pseudoDistance(vect1);
    221         vect2.set(plane.getNormal()).multLocal(planeDistance * 2.0f);
    222         vect3.set(vect1.subtractLocal(vect2)).subtractLocal(loc).normalizeLocal().negateLocal();
    223         // now set the up vector
    224         reflectionCam.lookAt(targetLocation, vect3);
    225         if (inv) {
    226             reflectionCam.setAxes(reflectionCam.getLeft().negateLocal(), reflectionCam.getUp(), reflectionCam.getDirection().negateLocal());
    227         }
    228 
    229         //Rendering reflection and refraction
    230         rm.renderViewPort(reflectionView, savedTpf);
    231         rm.renderViewPort(refractionView, savedTpf);
    232         rm.getRenderer().setFrameBuffer(vp.getOutputFrameBuffer());
    233         rm.setCamera(sceneCam, false);
    234 
    235     }
    236 
    237     public void postFrame(FrameBuffer out) {
    238         if (debug) {
    239             displayMap(rm.getRenderer(), dispRefraction, 64);
    240             displayMap(rm.getRenderer(), dispReflection, 256);
    241             displayMap(rm.getRenderer(), dispDepth, 448);
    242         }
    243     }
    244 
    245     public void cleanup() {
    246     }
    247 
    248     //debug only : displays maps
    249     protected void displayMap(Renderer r, Picture pic, int left) {
    250         Camera cam = vp.getCamera();
    251         rm.setCamera(cam, true);
    252         int h = cam.getHeight();
    253 
    254         pic.setPosition(left, h / 20f);
    255 
    256         pic.setWidth(128);
    257         pic.setHeight(128);
    258         pic.updateGeometricState();
    259         rm.renderGeometry(pic);
    260         rm.setCamera(cam, false);
    261     }
    262 
    263     protected void loadTextures(AssetManager manager) {
    264         normalTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/water_normalmap.dds");
    265         dudvTexture = (Texture2D) manager.loadTexture("Common/MatDefs/Water/Textures/dudv_map.jpg");
    266         normalTexture.setWrap(WrapMode.Repeat);
    267         dudvTexture.setWrap(WrapMode.Repeat);
    268     }
    269 
    270     protected void createTextures() {
    271         reflectionTexture = new Texture2D(renderWidth, renderHeight, Format.RGBA8);
    272         refractionTexture = new Texture2D(renderWidth, renderHeight, Format.RGBA8);
    273         depthTexture = new Texture2D(renderWidth, renderHeight, Format.Depth);
    274     }
    275 
    276     protected void applyTextures(Material mat) {
    277         mat.setTexture("water_reflection", reflectionTexture);
    278         mat.setTexture("water_refraction", refractionTexture);
    279         mat.setTexture("water_depthmap", depthTexture);
    280         mat.setTexture("water_normalmap", normalTexture);
    281         mat.setTexture("water_dudvmap", dudvTexture);
    282     }
    283 
    284     protected void createPreViews() {
    285         reflectionCam = new Camera(renderWidth, renderHeight);
    286         refractionCam = new Camera(renderWidth, renderHeight);
    287 
    288         // create a pre-view. a view that is rendered before the main view
    289         reflectionView = new ViewPort("Reflection View", reflectionCam);
    290         reflectionView.setClearFlags(true, true, true);
    291         reflectionView.setBackgroundColor(ColorRGBA.Black);
    292         // create offscreen framebuffer
    293         reflectionBuffer = new FrameBuffer(renderWidth, renderHeight, 1);
    294         //setup framebuffer to use texture
    295         reflectionBuffer.setDepthBuffer(Format.Depth);
    296         reflectionBuffer.setColorTexture(reflectionTexture);
    297 
    298         //set viewport to render to offscreen framebuffer
    299         reflectionView.setOutputFrameBuffer(reflectionBuffer);
    300         reflectionView.addProcessor(new ReflectionProcessor(reflectionCam, reflectionBuffer, reflectionClipPlane));
    301         // attach the scene to the viewport to be rendered
    302         reflectionView.attachScene(reflectionScene);
    303 
    304         // create a pre-view. a view that is rendered before the main view
    305         refractionView = new ViewPort("Refraction View", refractionCam);
    306         refractionView.setClearFlags(true, true, true);
    307         refractionView.setBackgroundColor(ColorRGBA.Black);
    308         // create offscreen framebuffer
    309         refractionBuffer = new FrameBuffer(renderWidth, renderHeight, 1);
    310         //setup framebuffer to use texture
    311         refractionBuffer.setDepthBuffer(Format.Depth);
    312         refractionBuffer.setColorTexture(refractionTexture);
    313         refractionBuffer.setDepthTexture(depthTexture);
    314         //set viewport to render to offscreen framebuffer
    315         refractionView.setOutputFrameBuffer(refractionBuffer);
    316         refractionView.addProcessor(new RefractionProcessor());
    317         // attach the scene to the viewport to be rendered
    318         refractionView.attachScene(reflectionScene);
    319     }
    320 
    321     protected void destroyViews() {
    322         //  rm.removePreView(reflectionView);
    323         rm.removePreView(refractionView);
    324     }
    325 
    326     /**
    327      * Get the water material from this processor, apply this to your water quad.
    328      * @return
    329      */
    330     public Material getMaterial() {
    331         return material;
    332     }
    333 
    334     /**
    335      * Sets the reflected scene, should not include the water quad!
    336      * Set before adding processor.
    337      * @param spat
    338      */
    339     public void setReflectionScene(Spatial spat) {
    340         reflectionScene = spat;
    341     }
    342 
    343     /**
    344      * returns the width of the reflection and refraction textures
    345      * @return
    346      */
    347     public int getRenderWidth() {
    348         return renderWidth;
    349     }
    350 
    351     /**
    352      * returns the height of the reflection and refraction textures
    353      * @return
    354      */
    355     public int getRenderHeight() {
    356         return renderHeight;
    357     }
    358 
    359     /**
    360      * Set the reflection Texture render size,
    361      * set before adding the processor!
    362      * @param with
    363      * @param height
    364      */
    365     public void setRenderSize(int width, int height) {
    366         renderWidth = width;
    367         renderHeight = height;
    368     }
    369 
    370     /**
    371      * returns the water plane
    372      * @return
    373      */
    374     public Plane getPlane() {
    375         return plane;
    376     }
    377 
    378     /**
    379      * Set the water plane for this processor.
    380      * @param plane
    381      */
    382     public void setPlane(Plane plane) {
    383         this.plane.setConstant(plane.getConstant());
    384         this.plane.setNormal(plane.getNormal());
    385         updateClipPlanes();
    386     }
    387 
    388     /**
    389      * Set the water plane using an origin (location) and a normal (reflection direction).
    390      * @param origin Set to 0,-6,0 if your water quad is at that location for correct reflection
    391      * @param normal Set to 0,1,0 (Vector3f.UNIT_Y) for normal planar water
    392      */
    393     public void setPlane(Vector3f origin, Vector3f normal) {
    394         this.plane.setOriginNormal(origin, normal);
    395         updateClipPlanes();
    396     }
    397 
    398     private void updateClipPlanes() {
    399         reflectionClipPlane = plane.clone();
    400         reflectionClipPlane.setConstant(reflectionClipPlane.getConstant() + reflectionClippingOffset);
    401         refractionClipPlane = plane.clone();
    402         refractionClipPlane.setConstant(refractionClipPlane.getConstant() + refractionClippingOffset);
    403 
    404     }
    405 
    406     /**
    407      * Set the light Position for the processor
    408      * @param position
    409      */
    410     //TODO maybe we should provide a convenient method to compute position from direction
    411     public void setLightPosition(Vector3f position) {
    412         material.setVector3("lightPos", position);
    413     }
    414 
    415     /**
    416      * Set the color that will be added to the refraction texture.
    417      * @param color
    418      */
    419     public void setWaterColor(ColorRGBA color) {
    420         material.setColor("waterColor", color);
    421     }
    422 
    423     /**
    424      * Higher values make the refraction texture shine through earlier.
    425      * Default is 4
    426      * @param depth
    427      */
    428     public void setWaterDepth(float depth) {
    429         waterDepth = depth;
    430         material.setFloat("waterDepth", depth);
    431     }
    432 
    433     /**
    434      * return the water depth
    435      * @return
    436      */
    437     public float getWaterDepth() {
    438         return waterDepth;
    439     }
    440 
    441     /**
    442      * returns water transparency
    443      * @return
    444      */
    445     public float getWaterTransparency() {
    446         return waterTransparency;
    447     }
    448 
    449     /**
    450      * sets the water transparency default os 0.1f
    451      * @param waterTransparency
    452      */
    453     public void setWaterTransparency(float waterTransparency) {
    454         this.waterTransparency = Math.max(0, waterTransparency);
    455         material.setFloat("waterTransparency", waterTransparency / 10);
    456     }
    457 
    458     /**
    459      * Sets the speed of the wave animation, default = 0.05f.
    460      * @param speed
    461      */
    462     public void setWaveSpeed(float speed) {
    463         this.speed = speed;
    464     }
    465 
    466     /**
    467      * Sets the scale of distortion by the normal map, default = 0.2
    468      */
    469     public void setDistortionScale(float value) {
    470         material.setColor("distortionScale", new ColorRGBA(value, value, value, value));
    471     }
    472 
    473     /**
    474      * Sets how the normal and dudv map are mixed to create the wave effect, default = 0.5
    475      */
    476     public void setDistortionMix(float value) {
    477         material.setColor("distortionMix", new ColorRGBA(value, value, value, value));
    478     }
    479 
    480     /**
    481      * Sets the scale of the normal/dudv texture, default = 1.
    482      * Note that the waves should be scaled by the texture coordinates of the quad to avoid animation artifacts,
    483      * use mesh.scaleTextureCoordinates(Vector2f) for that.
    484      */
    485     public void setTexScale(float value) {
    486         material.setColor("texScale", new ColorRGBA(value, value, value, value));
    487     }
    488 
    489     /**
    490      * retruns true if the waterprocessor is in debug mode
    491      * @return
    492      */
    493     public boolean isDebug() {
    494         return debug;
    495     }
    496 
    497     /**
    498      * set to true to display reflection and refraction textures in the GUI for debug purpose
    499      * @param debug
    500      */
    501     public void setDebug(boolean debug) {
    502         this.debug = debug;
    503     }
    504 
    505     /**
    506      * Creates a quad with the water material applied to it.
    507      * @param width
    508      * @param height
    509      * @return
    510      */
    511     public Geometry createWaterGeometry(float width, float height) {
    512         Quad quad = new Quad(width, height);
    513         Geometry geom = new Geometry("WaterGeometry", quad);
    514         geom.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X));
    515         geom.setMaterial(material);
    516         return geom;
    517     }
    518 
    519     /**
    520      * returns the reflection clipping plane offset
    521      * @return
    522      */
    523     public float getReflectionClippingOffset() {
    524         return reflectionClippingOffset;
    525     }
    526 
    527     /**
    528      * sets the reflection clipping plane offset
    529      * set a nagetive value to lower the clipping plane for relection texture rendering.
    530      * @param reflectionClippingOffset
    531      */
    532     public void setReflectionClippingOffset(float reflectionClippingOffset) {
    533         this.reflectionClippingOffset = reflectionClippingOffset;
    534         updateClipPlanes();
    535     }
    536 
    537     /**
    538      * returns the refraction clipping plane offset
    539      * @return
    540      */
    541     public float getRefractionClippingOffset() {
    542         return refractionClippingOffset;
    543     }
    544 
    545     /**
    546      * Sets the refraction clipping plane offset
    547      * set a positive value to raise the clipping plane for refraction texture rendering
    548      * @param refractionClippingOffset
    549      */
    550     public void setRefractionClippingOffset(float refractionClippingOffset) {
    551         this.refractionClippingOffset = refractionClippingOffset;
    552         updateClipPlanes();
    553     }
    554 
    555     /**
    556      * Refraction Processor
    557      */
    558     public class RefractionProcessor implements SceneProcessor {
    559 
    560         RenderManager rm;
    561         ViewPort vp;
    562 
    563         public void initialize(RenderManager rm, ViewPort vp) {
    564             this.rm = rm;
    565             this.vp = vp;
    566         }
    567 
    568         public void reshape(ViewPort vp, int w, int h) {
    569         }
    570 
    571         public boolean isInitialized() {
    572             return rm != null;
    573         }
    574 
    575         public void preFrame(float tpf) {
    576             refractionCam.setClipPlane(refractionClipPlane, Plane.Side.Negative);//,-1
    577 
    578         }
    579 
    580         public void postQueue(RenderQueue rq) {
    581         }
    582 
    583         public void postFrame(FrameBuffer out) {
    584         }
    585 
    586         public void cleanup() {
    587         }
    588     }
    589 }
    590