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