# Wave Mechanics

CS 482 Lecture, Dr. Lawlor

I claim simple wave dynamics requires only three variables:
• X velocity
• Y velocity
• Height
X variation in height drives X velocity: steep cliff facing right will resolve itself by fluid flowing to the right.

Y variation in height drives Y velocity the same way.

Velocity "divergence" drives height: if all velocities are converging on one point, that point gets higher.

These three things should make waves.   Let's try this shader:
`	float XvelChange=L.z-R.z;	float HtChange=L.x-R.x;		vec4 N=C;	N.x+=vel*XvelChange;	N.z+=ht*HtChange;`
This is exactly the discrete form of the partial differential equations at Wikipedia for the shallow water wave equations.

## Stability

Sadly, this is unstable--it explodes into weird zebra patterns.

There's a simple fix: blur things out, by averaging the neighbor values.
`	vec4 blur=0.25*(L+R+T+B); /* neighborhood average */	new=(1.0-blurFrac)*new+blurFrac*blur;`
Instead, Wikipedia recommends pushing back against velocities: big velocities will tend to damp out this way, but you need to add so much viscosity, nothing interesting happens.  Piles of tar are not known for their cool ripples.

(Curiously, the unstable zebra patterns are almost prettier than the waves we're trying to simulate!)

## 2D ripples

Here's the 2D version, with both X and Y change:
`	float vel=0.2, ht=0.2; // constants		vec4 N=C;	N.x+=vel*(L.z-R.z); // height gradient	N.y+=vel*(B.z-T.z);	N.z+=ht*(L.x-R.x+B.y-T.y); // velocity divergence`
Initial conditions make a big difference: if you have nonzero X and Y initial velocity, then your waves will move at that velocity.

The "ht" and "vel" constants are related: the ratio is the scale factor between initial height and velocity.

# Shallow Water Wave Equation demo

Er, JavaScript or WebGL doesn't seem to be running, so basically all you're going to see is the bare code...

 // Make a texture: var size=512; // Set up texture filtering properly var tex_filter=function() { /* // Blocky nearest sampling: gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); */ // Linear smooth sampling: gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); } // Allocate a texture of these dimensions: var babgloo_tex=function(size) { igloo.check("before texture create"); // Make igloo texture var tex=igloo.texture(null, gl.RGBA, gl.CLAMP_TO_EDGE, gl.NEAREST, gl.FLOAT) .blank(size,size); tex.bind(); tex_filter(); // Make a framebuffer, to render into the texture: tex.fbo=igloo.framebuffer(tex); // Clear bound texture gl.bindTexture(gl.TEXTURE_2D, null); // Link igloo texture over to babylon for render: tex.babytex = BABYLON_tex(tex.texture); igloo.check("after texture create"); return tex; } var tex=babgloo_tex(size); var tex2=babgloo_tex(size); // Build a program to render into it: var prog=igloo.program( igloo_vertexshader, PixAnvil.loadTab("Frag") ); var rendercount=0; var render=function (prog,desttex,srctex) { igloo.check("before pingpong render"); // Render into destination texture: desttex.fbo.attach(desttex); var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); if (status!=gl.FRAMEBUFFER_COMPLETE) throw("Framebuffer status: "+status); // Read from source texture var srcunit=3; // texture unit number if (srctex) srctex.bind(srcunit); tex_filter(); // Turn random rendering stuff off: gl.disable(gl.CULL_FACE); gl.disable(gl.STENCIL_TEST); gl.disable(gl.BLEND); gl.disable(gl.DEPTH_TEST); gl.disable(gl.SCISSOR_TEST); gl.viewport(0, 0, size,size); prog.use() .uniformi('texsrc',srcunit) .uniform('time',lib.time) .uniform('dt',lib.dt) .uniform('texdel',[1.0/size,1.0/size]) .attrib('quad', igloo_quad, 2) .draw(gl.TRIANGLE_STRIP, 4); // Turn stuff on again: gl.enable(gl.BLEND); gl.enable(gl.DEPTH_TEST); desttex.fbo.unbind(); // back to drawing to screen // Fix up babylon state BABYLON_flush(); console.log("Rendered offscreen image"); igloo.check("after pingpong render"); } // Make a plane to show the texture function planeSim() { var o=this; o.mesh = BABYLON.Mesh.CreatePlane("texshow", 6.0, scene); var m = new BABYLON.StandardMaterial("texture", scene); m.backFaceCulling = false; // double sided o.mesh.material = m; // Draw this object using Babylon texture: o.mesh.material.emissiveTexture=tex.babytex; // Turn off diffuse and specular, so you can see emissive: o.mesh.material.diffuseColor=new BABYLON.Color3(0,0,0); o.mesh.material.specularColor=new BABYLON.Color3(0,0,0); o.mass=10000.0; // mass in kg o.V=new vec3(0,0,1); // moving up o.P=new vec3(0,0,0.002); // in the air (m) o.mesh.position=o.P; // hook display to sim o.mesh.rotation.x=Math.PI/2; // Make plane stand upright } sim.obj=new planeSim(); lib.createGround(-4); /* GLSL Fragment Shader */ #ifdef GL_ES precision highp float; #endif varying vec2 texcoords; // our texture coords, [0-1] uniform vec2 texdel; // texcoord change per pixel uniform sampler2D texsrc; // source texture // Sample our texture at this pixel offset vec4 texat(vec2 cen,float dx,float dy) { return texture2D(texsrc,cen+vec2(dx*texdel.x,dy*texdel.y)); } uniform float time; // lib.time uniform float dt; // lib.dt void main() { if (time<0.1) { // startup gl_FragColor = vec4(0.5,0.5,0.5,1.0); } else if (length(texcoords-vec2(0.1,0.3))<0.02*sin(5.0*time)) { // blinking blue sphere gl_FragColor = vec4(0.5,0.5,5.0,1); } else if (length(texcoords-vec2(0.7,0.7))<0.2) { // constant gray sphere gl_FragColor = vec4(0.5,0.5,0.5,1); } else { // ordinary pixel vec2 cen=texcoords; vec4 T = texat(cen, 0.0,-1.0); vec4 L = texat(cen,-1.0, 0.0); vec4 C = texat(cen, 0.0, 0.0); vec4 R = texat(cen,+1.0, 0.0); vec4 B = texat(cen, 0.0,+1.0); // constants include: timestep, PDE coeffs, grid size float vel=0.25, ht=0.25, blurFrac=0.27; vec4 N=C; N.x+=vel*(L.z-R.z); // height gradient N.y+=vel*(B.z-T.z); N.z+=ht*(L.x-R.x+B.y-T.y); // velocity divergence vec4 blur=0.25*(L+R+T+B); /* neighborhood average */ N=(1.0-blurFrac)*N+blurFrac*blur; gl_FragColor = N; } gl_FragColor.a=1.0; // opaque } // Run the shader into this texture: render(prog,tex,tex2); // Pingpong tex and tex2 var tmp=tex; tex=tex2; tex2=tmp; /* Simple Newtonian Physics Object "o": */ var o=sim.obj; o.P.pe(o.V.t(lib.dt)); if (o.P.z<0.0) { o.P.z=0.0; o.V.z=Math.abs(o.V.z); } // Make the camera follow the rocket up camera.setTarget(o.P); // User Interface (UI) code: lib.wasd_camera();