Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Animating Water Ripples in ASCII

87 views
Skip to first unread message

Dustin Carlino / Da-Breegster

unread,
Jul 3, 2010, 12:01:32 AM7/3/10
to
For the impatient, see http://mnemonicrl.blogspot.com/2010/07/animating-ascii-water-ripples.html
for screenshots.

I desired a simple ripple effect, similar to http://www.dualheights.se/caustics/,
for use in pools and other small bodies of water. I'm still looking
into a means of showing flow in rivers and streams, but for more
static scenes, this'll be sufficient.

Concept:
A ripple starts at a point and expands (outwards only) to adjacent
tiles. By expanding orthogonally, you get a growing diamond shape. Add
diagonals to produce a boring box. So if you randomly include
diagonals, you'll get a vague blobby shape. This is desirable. I
haven't managed a way to get good circles yet.
When two different ripples (well, a 'particle' from each) collide,
destroy one of the particles. After the ripples pass through each
other, the broken one will expand to fill the gap, but it'll change
the shape in such a way that vaguely parodies actual wave
interference.

a 'ripple' contains a list of current particles (initialized to
wherever the ripple starts) and a lookup table of coordinates where
the ripple has passed through, to prevent expanding inwards
Pseudo-code:
Each animation tick, for each ripple:
Change the map back to the normal, still water tile for each
particle in the current list
For each particle in the current list:
Expand to adjacent tiles. Randomly include diagonals for fun
shapes.
For each tile,
Skip it if the tile's in the old lookup table list or if the map
is blocked
Skip it randomly, about 10% of the time, to further deform the
blob shape
If there's a different ripple's particle in the spot, then skip
to the next adjacent tile. This provides the interference effect. (I
found that deactivating the particle in the ripple we hit doesn't look
that great, both ripples fill the gap too slowly)
Add this tile to the old lookup table list, and mod the map to
show moving water here
Add this particle to the list for next round
If there aren't any new particles or if a certain number of
iterations has passed, then stop and kill the ripple. Otherwise, make
the current list equal to the list for next round
Re-draw the screen.

Aargh, the formatting is probably ghastly. If any part of this is
unclear, I can elaborate or even provide my code. I may post a video
in a few days. The effect can also be seen in MnemonicRL; after
updating and quitting the game, manually run: ./single.pl Hotel.game
and then go north to the atrium, and east to the pool.

Thanks, and I hope somebody gets some use out of this method for cheap
water animation. If anybody can provide a good explanation of how to
use Perlin noise for something similar, I'm all ears.

Walter Landry

unread,
Jul 3, 2010, 11:39:07 AM7/3/10
to
"Dustin Carlino / Da-Breegster" <dabre...@gmail.com> writes:

> For the impatient, see http://mnemonicrl.blogspot.com/2010/07/animating-ascii-water-ripples.html
> for screenshots.
>
> I desired a simple ripple effect, similar to http://www.dualheights.se/caustics/,
> for use in pools and other small bodies of water. I'm still looking
> into a means of showing flow in rivers and streams, but for more
> static scenes, this'll be sufficient.

The physics of this is exactly the same as modeling sound. I actually
wrote up a proper physics-based sound simulator. It handled reflections
and interference well. It ended up not being fast enough for me, but it
should be fine for you. I propagate the sound throughout the maze for
each tick, but you only need to propagate a few steps. I ended up using
Djikstra maps, but that will not give you nice reflections.

As you noticed, you have to add some dissipation to get the simulation
working well. There is also a sound level threshold. If all of the
sounds go below that level, it stops simulating. For your case, you
would probably mark the ripples wherever the pressure is above a certain
level.

In case you or someone else is interested, I am attaching some code. I
extracted it from my code, so I have not tested it in isolation. It
includes Map and Sound classes. You add sounds sources with
"add_sound_source" and then simulate it with "compute_sounds". You
would probably want to render at every step in the "do" loop. For the
terrain, the points with '#' reflect the sound.

If you have any questions, feel free to ask.

Cheers,
Walter Landry
wla...@caltech.edu

class Sound {
public:
double p[200][200], vx[200][200], vy[200][200], p_new[200][200],
vx_new[200][200], vy_new[200][200], p_max[200][200],
vx_max[200][200], vy_max[200][200];
Sound(int width, int height) {}
};


class Map {
public:

int width;
int height;

char terrain[200][200];
Sound sound;
std::list<std::pair<Pos,double>> sound_sources;

void add_sound_source(const Pos &q, const double &s)
{
sound_sources.push_back(std::make_pair(q,s));
}


double flux(double q[200][200],int im, int jm, int ip, int jp, bool sign)
{
/* Walls reflect sound, so we handle them like internal boundary
conditions: sound_wall=sound_neighbor,
v_normal_wall=-v_normal_neighbor,
v_perpendicular_wall=v_perpendicular_neighbor */

bool plus_factor=true;
bool minus_factor=true;
if(im<0 || jm<0)
{
im=ip;
jm=jp;
minus_factor=sign;
}
if(ip>=width || jp>=height)
{
ip=im;
jp=jm;
plus_factor=sign;
}

if(terrain[im][jm]=='#')
{
im=ip;
jm=jp;
minus_factor=sign;
}
if(terrain[ip][jp]=='#')
{
ip=im;
jp=jm;
plus_factor=sign;
}
double minus=(minus_factor ? q[im][jm] : -q[im][jm]);
double plus=(plus_factor ? q[ip][jp] : -q[ip][jp]);

return plus-minus;
}

void compute_sounds()
{
const double threshold=1;
const double decay_factor=1.0;
double global_max_sound;
for(int i=0;i<width;++i)
for(int j=0;j<height;++j)
sound.p_max[i][j]=sound.vx_max[i][j]=sound.vy_max[i][j]=0;

int min_x=width;
int min_y=height;
int max_x=0;
int max_y=0;
for(auto s=sound_sources.begin(); s!=sound_sources.end(); ++s)
{
sound.p[s->first.x][s->first.y]+=s->second;

min_x=std::min(min_x,s->first.x);
min_y=std::min(min_y,s->first.y);
max_x=std::max(max_x,s->first.x);
max_y=std::max(max_y,s->first.y);
}

do {
/* First, evolve the wave equation. We compute the derivative
two ways, along x,y and along the diagonals. This allows
diagonal transmission of sound through corners */

global_max_sound=0;
min_x=std::max(min_x-1,0);
min_y=std::max(min_y-1,0);
max_x=std::min(max_x+1,width-1);
max_y=std::min(max_y+1,height-1);

for(int i=min_x;i<=max_x;++i)
for(int j=min_y;j<=max_y;++j)
{
if(terrain[i][j]!='#')
{
/* Set the new values */
sound.p_new[i][j]=
(sound.p[i][j]
- (flux(sound.vx,i,j,i+1,j,false)
+ flux(sound.vx,i-1,j,i,j,false)
- flux(sound.p,i,j,i+1,j,true)
+ flux(sound.p,i-1,j,i,j,true)
+ flux(sound.vy,i,j,i,j+1,false)
+ flux(sound.vy,i,j-1,i,j,false)
- flux(sound.p,i,j,i,j+1,true)
+ flux(sound.p,i,j-1,i,j,true))/4)*decay_factor;
sound.vx_new[i][j]=(sound.vx[i][j]
- (flux(sound.p,i,j,i+1,j,true)
+ flux(sound.p,i-1,j,i,j,true)
- flux(sound.vx,i,j,i+1,j,false)
+ flux(sound.vx,i-1,j,i,j,false))/4);
sound.vy_new[i][j]=(sound.vy[i][j]
- (flux(sound.p,i,j,i,j+1,true)
+ flux(sound.p,i,j-1,i,j,true)
- flux(sound.vy,i,j,i,j+1,false)
+ flux(sound.vy,i,j-1,i,j,false))/4);
/* Get local and global maximums */
global_max_sound=std::max(global_max_sound,
std::fabs(sound.p_new[i][j]));
if(std::fabs(sound.p_new[i][j])>std::fabs(sound.p_max[i][j]))
{
sound.p_max[i][j]=std::fabs(sound.p_new[i][j]);
sound.vx_max[i][j]=sound.vx_new[i][j];
sound.vy_max[i][j]=sound.vy_new[i][j];
}
}
}
/* Swap the old and the new */
std::swap(sound.p,sound.p_new);
std::swap(sound.vx,sound.vx_new);
std::swap(sound.vy,sound.vy_new);

} while(global_max_sound>threshold);

for(int i=0;i<width;++i)
for(int j=0;j<height;++j)
sound.p[i][j]=sound.vx[i][j]=sound.vy[i][j]=
sound.p_new[i][j]=sound.vx_new[i][j]=sound.vy_new[i][j]=0;

sound_sources.clear();
}
};

Pender

unread,
Jul 3, 2010, 2:53:27 PM7/3/10
to
On Jul 3, 12:01 am, "Dustin Carlino / Da-Breegster"
<dabreegs...@gmail.com> wrote:
> For the impatient, seehttp://mnemonicrl.blogspot.com/2010/07/animating-ascii-water-ripples....
> for screenshots.
>
> I desired a simple ripple effect, similar tohttp://www.dualheights.se/caustics/,

You could do something similar with a basic diffusion algorithm.
Imagine a gas simulator: each tile has a gas quantity, and the gas
quantity of a cell in generation N+1 equals the average of the gas
quantities of itself and its non-obstructed neighbors in generation N.
That causes gas to expand in a circular cloud, and you can display
either a range of colors to reflect gas quantity or impose a cut-off
to determine whether to display a different color.

The same thing would work to illustrate water ripples. In that case,
you are tracking water agitation instead of gas, and things moving
through the water generate a bunch of agitation on that cell each
turn. The agitation of a tile should also decay by a constant amount
each turn. It wouldn't handle reflection of water waves off of walls,
and it wouldn't handle destructive interference between waves, but I
bet it would feel pretty decent.

Ido Yehieli

unread,
Jul 4, 2010, 6:05:37 AM7/4/10
to
On 07/03/2010 06:01 , Dustin Carlino / Da-Breegster wrote:
> For the impatient, see http://mnemonicrl.blogspot.com/2010/07/animating-ascii-water-ripples.html
> for screenshots.

A video would be much preferable to screenshots in this case.

-Ido.

Hmmm

unread,
Jul 4, 2010, 10:26:56 PM7/4/10
to
"Dustin Carlino / Da-Breegster" <dabre...@gmail.com> wrote in
news:d4892a3d-a03e-4430...@i31g2000yqm.googlegroups.com:

> A ripple starts at a point and expands (outwards only) to adjacent
> tiles. By expanding orthogonally, you get a growing diamond shape. Add
> diagonals to produce a boring box. So if you randomly include
> diagonals, you'll get a vague blobby shape. This is desirable. I
> haven't managed a way to get good circles yet.

http://freespace.virgin.net/hugo.elias/graphics/x_water.htm

Walter Landry

unread,
Jul 4, 2010, 11:14:00 PM7/4/10
to
Hmmm <Hm...@hmm.hmm> writes:

This person is also simulating the physics directly. The "mysterious
filter" is just a traditional way to discretize the wave equation using
leap frog time stepping. See for example Eqn 19.1.31 in

http://www.fizyka.umk.pl/nrbook/c19-1.pdf

Ripples do not travel along diagonals. So if you have something like


B..#..
...#..
...#..
###...
......
.....A


Ripples that start at A will not reach B, and vice versa. Otherwise, it
should work as well as the code I posted (and be simpler to understand).

Cheers,
Walter Landry
wla...@caltech.edu

Dustin Carlino / Da-Breegster

unread,
Jul 5, 2010, 1:56:29 AM7/5/10
to
On Jul 4, 5:05 am, Ido Yehieli <ido.yehi...@gmail.com> wrote:
> On 07/03/2010 06:01 , Dustin Carlino / Da-Breegster wrote:
>
> > For the impatient, seehttp://mnemonicrl.blogspot.com/2010/07/animating-ascii-water-ripples....

> > for screenshots.
>
> A video would be much preferable to screenshots in this case.
>
> -Ido.

Sorry for the delay, busy day.
http://www.mediafire.com/?2jniz4ihyyk
It's an ogv, that's Ogg Video. I uploaded to Youtube, but even after
processing it, it butchered the graphics completely. I'll play around
with different codecs later. Sorry for the inconvenience.

The page at virgin.net describes this diffusion method, in fact. I had
trouble stomaching a few details from it, but I don't remember which.
If I work on it again soon and get confused, I'll be more specific
about how this wasn't working for me.

Dustin Carlino / Da-Breegster

unread,
Jul 5, 2010, 2:09:33 AM7/5/10
to
On Jul 5, 12:56 am, "Dustin Carlino / Da-Breegster"

<dabreegs...@gmail.com> wrote:
> On Jul 4, 5:05 am, Ido Yehieli <ido.yehi...@gmail.com> wrote:
>
> > On 07/03/2010 06:01 , Dustin Carlino / Da-Breegster wrote:
>
> > > For the impatient, seehttp://mnemonicrl.blogspot.com/2010/07/animating-ascii-water-ripples....
> > > for screenshots.
>
> > A video would be much preferable to screenshots in this case.
>
> > -Ido.
>
> Sorry for the delay, busy day.http://www.mediafire.com/?2jniz4ihyyk

> It's an ogv, that's Ogg Video. I uploaded to Youtube, but even after
> processing it, it butchered the graphics completely. I'll play around
> with different codecs later. Sorry for the inconvenience.
>
> The page at virgin.net describes this diffusion method, in fact. I had
> trouble stomaching a few details from it, but I don't remember which.
> If I work on it again soon and get confused, I'll be more specific
> about how this wasn't working for me.

http://www.youtube.com/watch?v=SS8Wva8JRdk
Just kidding, here we go. Had to re-encode.

Krice

unread,
Jul 5, 2010, 3:09:49 AM7/5/10
to
On 5 heinä, 09:09, "Dustin Carlino / Da-Breegster"

> Just kidding, here we go. Had to re-encode.

Looks kind of psychedelic.

Ido Yehieli

unread,
Jul 5, 2010, 3:46:27 AM7/5/10
to

Indeed.

I don't know if I would have guessed it to be ripples in water, looks
more like some strange alien...something.

It's pretty cool either way!

-Ido.

Dustin Carlino / Da-Breegster

unread,
Jul 5, 2010, 1:45:16 PM7/5/10
to

Well I was shooting for ripples, but as long as people interpret it as
something going on in the water, then I guess I can't complain.

Gelatinous Mutant Coconut

unread,
Jul 6, 2010, 4:42:42 AM7/6/10
to
On Jul 5, 2:09 am, "Dustin Carlino / Da-Breegster"

<dabreegs...@gmail.com> wrote:
> http://www.youtube.com/watch?v=SS8Wva8JRdk
> Just kidding, here we go. Had to re-encode.

I think it looks pretty good when the player walks through the water
and leaves a wake behind. (Wait, looking at the video again, I'm not
sure if that actually happens?) I don't much like the ripples that
just happen for no apparent reason.

I'd explore these as ripple-starting events:

-When the player (or whatever) moves through water, start a ripple at
the tile the player moves into.
-When the player (or whatever) is just floating in the water,
sometimes generate a ripple at where the player is. (It's hard to be
completely still treading water.)
-If the water is outside and it starts raining, randomly place ripples
frequently.
-At a place where water enters a pool (from a pipe or whatever),
generate a ripple at that point at a constant rate.

Dustin Carlino / Da-Breegster

unread,
Jul 10, 2010, 8:46:23 AM7/10/10
to
On Jul 6, 3:42 am, Gelatinous Mutant Coconut

I saw http://doryen.eptalys.net/2010/05/improved-lakes/ this morning;
jice has done a brilliant job of using the ripple effect when the
player moves through the water. Your other sources make much more
sense as well. I'm working on a means of showing a fountain spout -- a
ripple emanating from that center would look great.

0 new messages