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 & Rémy 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      */
SimpleWaterProcessor(AssetManager manager)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 
initialize(RenderManager rm, ViewPort vp)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 
reshape(ViewPort vp, int w, int h)168     public void reshape(ViewPort vp, int w, int h) {
169     }
170 
isInitialized()171     public boolean isInitialized() {
172         return rm != null;
173     }
174     float time = 0;
175     float savedTpf = 0;
176 
preFrame(float tpf)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 
postQueue(RenderQueue rq)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 
postFrame(FrameBuffer out)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 
cleanup()245     public void cleanup() {
246     }
247 
248     //debug only : displays maps
displayMap(Renderer r, Picture pic, int left)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 
loadTextures(AssetManager manager)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 
createTextures()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 
applyTextures(Material mat)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 
createPreViews()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 
destroyViews()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      */
getMaterial()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      */
setReflectionScene(Spatial spat)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      */
getRenderWidth()347     public int getRenderWidth() {
348         return renderWidth;
349     }
350 
351     /**
352      * returns the height of the reflection and refraction textures
353      * @return
354      */
getRenderHeight()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      */
setRenderSize(int width, int height)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      */
getPlane()374     public Plane getPlane() {
375         return plane;
376     }
377 
378     /**
379      * Set the water plane for this processor.
380      * @param plane
381      */
setPlane(Plane plane)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      */
setPlane(Vector3f origin, Vector3f normal)393     public void setPlane(Vector3f origin, Vector3f normal) {
394         this.plane.setOriginNormal(origin, normal);
395         updateClipPlanes();
396     }
397 
updateClipPlanes()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
setLightPosition(Vector3f position)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      */
setWaterColor(ColorRGBA color)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      */
setWaterDepth(float depth)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      */
getWaterDepth()437     public float getWaterDepth() {
438         return waterDepth;
439     }
440 
441     /**
442      * returns water transparency
443      * @return
444      */
getWaterTransparency()445     public float getWaterTransparency() {
446         return waterTransparency;
447     }
448 
449     /**
450      * sets the water transparency default os 0.1f
451      * @param waterTransparency
452      */
setWaterTransparency(float waterTransparency)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      */
setWaveSpeed(float speed)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      */
setDistortionScale(float value)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      */
setDistortionMix(float value)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      */
setTexScale(float value)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      */
isDebug()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      */
setDebug(boolean debug)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      */
createWaterGeometry(float width, float height)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      */
getReflectionClippingOffset()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      */
setReflectionClippingOffset(float reflectionClippingOffset)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      */
getRefractionClippingOffset()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      */
setRefractionClippingOffset(float refractionClippingOffset)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 
initialize(RenderManager rm, ViewPort vp)563         public void initialize(RenderManager rm, ViewPort vp) {
564             this.rm = rm;
565             this.vp = vp;
566         }
567 
reshape(ViewPort vp, int w, int h)568         public void reshape(ViewPort vp, int w, int h) {
569         }
570 
isInitialized()571         public boolean isInitialized() {
572             return rm != null;
573         }
574 
preFrame(float tpf)575         public void preFrame(float tpf) {
576             refractionCam.setClipPlane(refractionClipPlane, Plane.Side.Negative);//,-1
577 
578         }
579 
postQueue(RenderQueue rq)580         public void postQueue(RenderQueue rq) {
581         }
582 
postFrame(FrameBuffer out)583         public void postFrame(FrameBuffer out) {
584         }
585 
cleanup()586         public void cleanup() {
587         }
588     }
589 }
590