Pygame water ripple effect Pygame water ripple effect python python

Pygame water ripple effect


After doing homework (a.k.a. research) and trying to directly convert the Java code reference posted on the question into Python, and having a very, very sad experience while trying to have Python/Numpy update a humongous array of pixel colors based on their positions for the rippling of the ripple effect (sorry, my first language isn't English), thus parsing several (x,y) positions for each pass of the effect calculations and blitting that onto the displayed surface on the screen (surfarray ensues), I've come to the conclusion - that is backed up by other commenters - that Pygame simply won't be powerful enough to actually traverse that entire array of pixels and apply results of calculations onto every pixel on the screen at a minimum rate of 24 fps (for a less-than-average experience).

Quoting the very developer behind Last Light Productions and the former Project Geometrian, Ian Mallet:

PyGame is not so good for pixel pushing. Nothing is, other than the GPU.

The search then turned out to be a search for Alkahest - something that would turn out never to be truly found - and based on the same idea of rippling images, but this time by using transparency to see through several layers of Pygame surfaces, I posted the question Pygame circular cropping/masks on Gamedev. The chosen answer actually corroborates the fact I already feared that Pygame would never be macho enough for the job.

One day later I went back to my previous ideas on development and came across Ogre3D. It turns out that (1) Ogre3D and samples are open-source and (2) one of the examples is a 3-D water model that interacts with a moving object, exactly the same thing I tried to achieve in 2-D, but in a much more professional manner.

Since my knowledge in C/C++ is nil, I decided to ask about how to customize the Ogre3D water demo for a glimpse of where to start looking, and one of the answers pointed me to software from Touchscape where an SDK is provided (see this answer).

Ogre3D pretty much wrapped it up. Water ripple effect, OpenGL (which it may optionally use based on hardware), Game Engine and Python wrappers via Python-Ogre - so my answer to my own question,

Can anyone provide a suitable implementation of this effect using OpenCV/OpenGL and Pygame?

is basically

Yes. Check Ogre3D's water demo, provided with the SDK - and plug it into Python via Python-Ogre.


The following using numpy might get you started. It should be fast enough as it is though you could get much faster even in python (have a look here to see how http://www.scipy.org/PerformancePython).

By the way there are several drawbacks in the method described :

  1. you cannot control the speed of the ripples - to do that you would have to modify the equations used in the ripple function (if you figure out how it relates to the wave equation http://en.wikipedia.org/wiki/Wave_equation then you are done)
  2. the "depth" of the "pool" is fixed (and probably too shallow). I added a depth parameter to magnify the effect
  3. the article reads integer pixel offsets - you would get a much nicer result with interpolated values (i guess you can do that with opengl, but my knowledge in that area is nil)

code:

import numpydef ripple(w1, w2, damp, n = 1):    for _ in xrange(n):        w2 *= -2        w2[1:-1,1:-1] += w1[0:-2, 1: -1]        w2[1:-1,1:-1] += w1[2:  , 1: -1]        w2[1:-1,1:-1] += w1[1:-1, 0: -2]        w2[1:-1,1:-1] += w1[1:-1, 2:   ]        w2 *= .5 * (1. - 1./damp)        w1, w2 = w2, w1def refract(x, y, w, rindex, depth = 10):    sx = x[0,1] - x[0,0]    sy = y[1,0] - y[0,0]    dw_dx = (w[2: ,1:-1] - w[:-2,1:-1]) / sx * .5    dw_dy = (w[1:-1,2: ] - w[1:-1,:-2]) / sy * .5    xang = numpy.arctan(dw_dx)    xrefract = numpy.arcsin(sin(xang) / rindex)    dx = numpy.tan(xrefract) * dw_dx * depth    yang = numpy.arctan(dw_dy)    yrefract = numpy.arcsin(sin(yang) / rindex)    dy = numpy.tan(yrefract) * dw_dy * depth    dx *= numpy.sign(dw_dx)    dy *= numpy.sign(dw_dy)    xmin = x[0,0]    xmax = x[0,-1]    x[1:-1,1:-1] += dx    x[:,:] = numpy.where(x < xmin, xmin, x)    x[:,:] = numpy.where(x > xmax, xmax, x)    ymin = y[0,0]    ymax = y[-1,0]    y[1:-1,1:-1] += dy    y[:,:] = numpy.where(y < ymin, ymin, y)    y[:,:] = numpy.where(y > ymax, ymax, y)

x and y should be grids from a numpy.meshgrid call : here's a sample usage:

    x,y = meshgrid(x,y)    w = 10 * exp(- (x*x + y*y))    w1 = w.copy()    x1,y1 = meshgrid(r_[0:len(x):1.0], r_[0:len(y):1.0])    ripple(w, w1, 16) # w1 will be modified    refract(x1, y1, w1, rindex=2, depth=10) # x1 and y1 will be modified    numpy.around(x1, out=x1) # but you will get better results with interpolate    numpy.around(y1, out=y1) #