Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pygame water ripple effect

I have Googled for it but there are no ready scripts - as opposed to the same effect on Flash. I have checked the algorithm on The Water Effect Explained and also tested an implementation of the Perlin Noise, which provides a good simulation of the end of waves on a flat surface. I am looking for the same implementation found on several Flash Effects, based on mouseover/hover actions. This is targetting an interactive floor library, and I would enjoy moving away from Flash for this matter, particularly to avoid such easy reverse-engineering of the code - and yes, I know it could just use some ready-made flash code, but I would only use that as a last resort.

Has anyone seen a suitable implementation of this effect for Pygame (using OpenGL or not)?

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

The culprit here is the (code) interface to pass a list of values that will be sent from an external interpreter (tracker - not TUIO though) via Python. I have tried for some straight days but Pygame is not able to generate anything as fast as sheer C/C++ code (as used for the shaders in OpenGL), and my knowledge of C/C++ is null. So the target is to at least have that coming from Python code.

A good example, different from the Flash effect but that is still good is Water Simulation using Java applet.

(bounty is showing answers do not have enough detail since this was the closest to 'the OP is incapable of creating the code he wants to as he lacks fundamental skills and this answer will probably be of use to several people').

like image 374
planestepper Avatar asked Oct 04 '11 12:10

planestepper


2 Answers

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.

like image 141
planestepper Avatar answered Oct 11 '22 01:10

planestepper


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 numpy  def 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, w1  def 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) #  
like image 44
spam_eggs Avatar answered Oct 11 '22 02:10

spam_eggs