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 */
32package com.jme3.water;
33
34import com.jme3.asset.AssetManager;
35import com.jme3.material.Material;
36import com.jme3.math.*;
37import com.jme3.post.SceneProcessor;
38import com.jme3.renderer.Camera;
39import com.jme3.renderer.RenderManager;
40import com.jme3.renderer.Renderer;
41import com.jme3.renderer.ViewPort;
42import com.jme3.renderer.queue.RenderQueue;
43import com.jme3.scene.Geometry;
44import com.jme3.scene.Spatial;
45import com.jme3.scene.shape.Quad;
46import com.jme3.texture.FrameBuffer;
47import com.jme3.texture.Image.Format;
48import com.jme3.texture.Texture.WrapMode;
49import com.jme3.texture.Texture2D;
50import 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 */
89public 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