Water effect using height fields

Filled with confidence after playing with Corona SDK I found myself writing a simple physics game. With the early success of getting a frog sprite to jump off the ground I set myself the task of programming some water for him to swim in. 

 

 

It turns out that programming water simulations can be tricky, googling pointed me to some complex methods which use openGL. Luckily I found this presentation from Matthias Mueller which describes a simple algorithm suitable for Corona

Height Fields Algorithm

The algorithm divides the water into an array of equal length bars called height fields. The height (u) and vertical velocity (v) of each bar is stored in separate arrays. The new height (uNew) of each bar is calculated during each time step based on the change in vertical force (f) of each bar. f is  based on a constant wave speed (c) and the height of the neighbouring bars.

The gritty details of this algorithm are in Matthias’s presentation. Admittedly, I didn’t fully understand the derivation of the algorithm from the wave equation, so I like to think of the force as how much water flows out of  the neighbouring water bars and into the current bar during each timestep.

 

As you can see from the Lua implementation, I followed the algorithm in the presentation closely.

The function clampU()  fixes the maximum velocity of each bar. Without clampU the waves can continue to grow and the water surface becomes unstable.

To test this algorithm I used an onTouch event to create a bell shaped curve in the water surface. By repeatedly ‘touching’ the screen at different locations I could get a realistic-ish choppy water surface.

I also ran a filter - average() - over the height array during each render step. This removed the high frequency noise (i.e smoothed spikey changes in height) and also had the nice side effect of damping the waves over time.

Object Interaction

I wanted to combine the water surface simulation with Corona's easy to use physics engine. The slides describe how to do this by calculating the upward force of the water based on the displacement of the object.

force up=-object immersion * water density * gravity

 

force down = mass*gravity.

I calculated the object’s immersion based on how far below the water surface level  it was. After a few trial and error attempts to get an object to float via the water density and force variables, I found it difficult to balance the buoyancy force of the water with the downward force of the object. The object would either jump out the water or bobble unrealistically on the surface.

I decided to cheat here and created a ripple from the momentum of the object then allow my objects to sink slowly.

To float an object I set the object.y to the water surface height. It worked surprisingly well but unfortunately has the side efffect of removing the object from the physics engine so any other physics objects hitting it do not push it down into the water

Here is a video of the final effect with 20 boxes falling into the water.

Notes

  1. I haven’t performed any optimisations. One big inefficienc is drawing the water. I fill each ‘water bar’ with an individual line and remove the line for every render.

 

  1. The speed of the wave is not dependent on the render time step. This means on faster devices you will get faster waves. I used the render loop for convenience, a fixed timer may be more suitable.

 

  1. Additional special effects including anti-aliaising, refraction and reflection of light are discussed in the slides. I’m not sure how to achieve these as pixel level access and deformable textures are not in the Corona SDK.

 

  1. Manually calculating physics variables in the render loop does not work. For example, I tried calculating the speed of a physics body with the following code, then compared it to the vy property returned by getLinearVelocity(). The calculated speed variance was large compared to vy. I guess this is because the physics loop runs much faster than the render loop.

 

            speed = 0

speed = (prevY - body.y) / (timePassed/1000)

prevY = body.y

 

  1. If two boxes collide when dropped, there is no water interaction.
 

 

Show comments 0