We closed this forum 18 June 2010. It has served us well since 2005 as the ALPHA forum did before it from 2002 to 2005. New discussions are ongoing at the new URL http://forum.processing.org. You'll need to sign up and get a new user account. We're sorry about that inconvenience, but we think it's better in the long run. The content on this forum will remain online.
IndexProgramming Questions & HelpPrograms › 2d water simulation with refraction
Pages: 1 2 
2d water simulation with refraction (Read 4788 times)
2d water simulation with refraction
Mar 8th, 2009, 1:10am
 
Hi there, i've made the classic 2d water sim, but instead of rendering the heightmap directly, i've attempted a (very basic) ray-tracing system to try and re-create the way light is refracted by water. It looks so-so, but the effect is ruined some-what by the pixelly nature of the ray-tracing. I suspect that there is a far better way of calculating the amount of light hitting each pixel which allows a smoother output. Any ideas?
Heres the code, draw walls with the mouse and press 'd' to create a ripple.

float[] temp;
int tankX = 401;
int tankY = 401;
int tankSize = tankX*tankY;
float tankScale = 1;
float damping = 0.99;
int dropSize=10;
float lightIntensity=0.2;

PImage water;

float[] waveBuffer1 = new float[tankSize];
float[] waveBuffer2 = new float[tankSize];
int[] wallMap = new int[tankSize];

void setup()
{
 frameRate(100);
 size(round(tankScale*tankX),round(tankScale*tankY));
 background(1);
 noStroke();
 smooth();
 colorMode(HSB,1,1,1,1);

 water=createImage(width,height,ARGB);
 
 PFont font1 = createFont("arial",12);
 textFont(font1, 12);
}

void draw(){
 background(0);
 processWater();
 image(water,0,0);
 fill(1);
 text("fps: "+frameRate,10,20);
}

void processWater(){
 float[] dispMap = new float[tankSize];
 for(int y=1; y<tankY-1; y++)
 for(int x=1; x<tankX-1; x++){
   int i=y*tankX+x;
   float sumX=waveBuffer2[i-1]+waveBuffer2[i+1];
   float sumY=waveBuffer2[i-tankX]+waveBuffer2[i+tankX];
   waveBuffer1[i] = ((sumX+sumY)/2) - waveBuffer1[i]*1;
   waveBuffer1[i] *= damping*(1-wallMap[i]);
   
   //calc displacement map
   sumX=-waveBuffer2[i-1]+waveBuffer2[i+1];
   sumY=-waveBuffer2[i-tankX]+waveBuffer2[i+tankX];
   int dispX=x-round(sumX*1);
   int dispY=y-round(sumY*1);
   if(dispX>1 && dispX<tankX-1 && dispY>1 && dispY<tankY-1){
     dispMap[dispY*tankX+dispX]+=lightIntensity;
   }
 }
 
 //draw refractions
 water.loadPixels();
 for(int y=1; y<tankY-1; y++)
 for(int x=1; x<tankX-1; x++){
   water.pixels[y*tankX+x]=color(dispMap[y*tankX+x]/10);
   if(wallMap[y*tankX+x]==1)water.pixels[y*tankX+x]=color(0.2);
 }
 
  //swap buffers
 temp=waveBuffer1;
 waveBuffer1=waveBuffer2;
 waveBuffer2=temp;
 
 water.updatePixels();
}

void mouseDragged(){
 if(mouseX>dropSize/2 && mouseX<tankX-dropSize/2 && mouseY>dropSize/2 && mouseY<tankY-dropSize/2)
 for(int y=-dropSize/2;y<=dropSize/2;y++)
 for(int x=-dropSize/2;x<=dropSize/2;x++)
 wallMap[(mouseY+y)*width+(mouseX+x)]=1;
}

void keyPressed(){
 if(key==32){
   if(mouseX>dropSize/2 && mouseX<tankX-dropSize/2 && mouseY>dropSize/2 && mouseY<tankY-dropSize/2)
   for(int y=-dropSize/2;y<=dropSize/2;y++)
   for(int x=-dropSize/2;x<=dropSize/2;x++)
   waveBuffer1[(mouseY+y)*width+(mouseX+x)]=100;
 }
}
Re: 2d water simulation with refraction
Reply #1 - Mar 8th, 2009, 4:29am
 
Nice looking affect - just to mention its not d to ripple, its spacebar!

As for calculating light per pixel Im unsure on how the best way to go about doing this - perhaps only doing it on sections of the pixels is the best way - as not all the water is necessary at a single point?
Re: 2d water simulation with refraction
Reply #2 - Mar 8th, 2009, 4:34am
 
Yes, ray-tracing is notoriously susceptible to speckly image artifacts produced by areas where very small changes in incident ray direction can produce wildly different results in the final color.  I can totally see this happening with modeling refraction through ripply water.

Most ray tracers attempt to over-come this by treating pixels not as single point samples from the scene, but rather, as tiny little windows through which the scene is seen.  To get a good reading on what you see through each tiny one-pixel window, you sample the pixel by sending rays through several points within the pixel.  You shoot rays at its corners, at its center, and average them.  Or you can can sub-sample a grid of points within the pixel, shoot some fixed-number of rays at random locations within that pixel, or even do some sort of clever adaptive thing where you increase the over-sampling within a pixel dynamically based on whether the pixel is "too different" from its neighboring pixels.

Lots of strategies, but they all amount to sending several, very slightly different, rays and averaging their results to determine the final color of that pixel.

You can read more to your heart's content on Wikipedia'a page about Ray Tracing, or in the documentation for POVRay.
Re: 2d water simulation with refraction
Reply #3 - Mar 8th, 2009, 2:45pm
 
Thanks for the replies. After reading the wiki page i've realised that i'm actually doing photon-mapping (if you can even call it that at all!) because the rays are sent from the light source. It seems to say that caustics can only be done with photon mapping, and that the level of detail is directly related to the number of photons sent out, which in my case is one for each pixel. Is there some way of interpolating between the values maybe?
Re: 2d water simulation with refraction
Reply #4 - Mar 9th, 2009, 5:34pm
 
Ah, yes.  That is true; standard ray tracing cannot reproduce caustics.  Which sucks, because caustics are way cool.  Smiley

But sending out rays from the light source can indeed do it.  I have never implemented a photon mapping algorithm myself, but I am given to understand that it is usually used in a two-stage process: photon mapping generates a map of surface illumination for every point in the scene, and then standard ray tracing maps the illuminated scene through a particular viewpoint to generate the final image.

In your case, the "scene" would appear to be a flat plane representing the surface underneath the water, and the viewpoint is just "looking straight down", so there is really no need to implement a second stage ray-tracing algorithm.  You can just map points on that flat plane directly to pixels on the screen.

But yes, as for how to get better results, I think that shooting more rays is the way to go.  It is, at least, a place to start.  If it were me, instead of sending one ray per pixel, I would try subdividing each pixel into either a regular grid of sub-pixel rays, or sending several rays at randomly selected locations through the pixel.  Then augment your code to properly handle adding up the results as they strike the flat plane.

Another thing you could try is, instead of treating the rays as point-like "single photons", to treat them like small spotlights.  That is, if you find that the ray strikes the flat plane at point (12, 92) or whatever, you could add some light to that spot, but also add a lesser amount of light to the neighboring pixels.  That will inherently give you smoother results for a given number of rays, although it is kind of cheating a little bit.
Re: 2d water simulation with refraction
Reply #5 - Mar 10th, 2009, 5:24pm
 
This effect is really superbly done.

Kudos to you man!.
Re: 2d water simulation with refraction
Reply #6 - Dec 13th, 2009, 7:35pm
 
This is an awesome piece of code.

I applied an algorithmic background.

float[] temp;
int tankX = 401;
int tankY = 401;
int tankSize = tankX*tankY;
float tankScale = 1;
float damping = 0.99;
int dropSize=20;//10
float lightIntensity=0.6;//0.2

PImage water;
public boolean rise = false;
float[] waveBuffer1 = new float[tankSize];
float[] waveBuffer2 = new float[tankSize];
int[] wallMap = new int[tankSize];
PImage bbg;
import processing.opengl.*;
void setup()
{
frameRate(100);
size(round(tankScale*tankX),round(tankScale*tankY),OPENGL);
 makebg(false,false);
background(1);
noStroke();
smooth();
colorMode(HSB,1,1,1,1);

water=createImage(width,height,ARGB);

PFont font1 = createFont("arial",12);
textFont(font1, 12);

}

void draw(){
background(0);
processWater();
image(water,0,0);
fill(1);
text("fps: "+frameRate,10,20);
}

void processWater(){
float[] dispMap = new float[tankSize];
for(int y=1; y<tankY-1; y++)
for(int x=1; x<tankX-1; x++){
  int i=y*tankX+x;
  float sumX=waveBuffer2[i-1]+waveBuffer2[i+1];
  float sumY=waveBuffer2[i-tankX]+waveBuffer2[i+tankX];
  waveBuffer1[i] = ((sumX+sumY)/2) - waveBuffer1[i]*1;
  waveBuffer1[i] *= damping*(1-wallMap[i]);
 
  //calc displacement map
  sumX=-waveBuffer2[i-1]+waveBuffer2[i+1];
  sumY=-waveBuffer2[i-tankX]+waveBuffer2[i+tankX];
  int dispX=x-round(sumX*1);
  int dispY=y-round(sumY*1);
  if(dispX>1 && dispX<tankX-1 && dispY>1 && dispY<tankY-1){
    dispMap[dispY*tankX+dispX]+=lightIntensity;
  }
}

//draw refractions
water.loadPixels();
for(int y=1; y<tankY-1; y++)
for(int x=1; x<tankX-1; x++){
  //water.pixels[y*tankX+x]=color(dispMap[y*tankX+x]/10);
  water.pixels[y*tankX+x]=blendColor(color(dispMap[y*tankX+x]/10),bbg.pixels[x+width*y],DIFFERENCE);
 
  color ff= blendColor(color(dispMap[y*tankX+x]/10),bbg.pixels[x+width*y],DIFFERENCE);
  //water.pixels[y*tankX+x]=bbg.pixels[x+width*y];
  if(wallMap[y*tankX+x] ==1  || wallMap[y*tankX+x] == bbg.pixels[(mouseY)*width+(mouseX)])
  {
   
 //   water.pixels[y*tankX+x]=color(0.2);
 
//     water.pixels[y*tankX+x]=color(bbg.pixels[x+width*y+1]);
    color cp = color(bbg.pixels[x+width*y]);
    float rr = red(ff);
    float gg = green (ff);
    float bb = blue (ff);
    color po = color (rr,gg,bb);
         water.pixels[y*tankX+x]=blendColor(color (rr,gg,bb),cp,DIFFERENCE);
   
  }
}

 //swap buffers
temp=waveBuffer1;
waveBuffer1=waveBuffer2;
waveBuffer2=temp;

water.updatePixels();
}

void mouseDragged(){
 if (mouseButton==37){ //LEFT
if(mouseX>dropSize/2 && mouseX<tankX-dropSize/2 && mouseY>dropSize/2 && mouseY<tankY-dropSize/2)
for(int y=-dropSize/2;y<=dropSize/2;y++)
for(int x=-dropSize/2;x<=dropSize/2;x++){
wallMap[(mouseY+y)*width+(mouseX+x)]= 1;
}

 }
 if (rise){
  if (mouseButton==39){ //RIGHT
if(mouseX>dropSize/2 && mouseX<tankX-dropSize/2 && mouseY>dropSize/2 && mouseY<tankY-dropSize/2)
for(int y=-dropSize/2;y<=dropSize/2;y++)
for(int x=-dropSize/2;x<=dropSize/2;x++)
wallMap[(mouseY+y)*width+(mouseX+x)]=bbg.pixels[(mouseY+y)*width+(mouseX+x)];    
   
  }
 }
// println(mouseButton);
}

void keyPressed(){
if(key==' '){ //32 == SPACE
  if(mouseX>dropSize/2 && mouseX<tankX-dropSize/2 && mouseY>dropSize/2 && mouseY<tankY-dropSize/2)
  for(int y=-dropSize/2;y<=dropSize/2;y++)
  for(int x=-dropSize/2;x<=dropSize/2;x++)
  waveBuffer1[(mouseY+y)*width+(mouseX+x)]=color(100);//100
}

if (key =='r'){
 rise =!rise;
}
}

int cpass=0;


void makebg(Boolean animate, Boolean invert){
bbg = new PImage(width,height);
 loadPixels();
 color colx = 0;
for(int y=0;y<height;y++){
   for(int x=0;x<width;x++){
       int pos=y*width+x;
       color col = pixels[pos] ;//
        colx = color(height-y,x,y-x);
       if (animate){
        //colx = color(height-y,X,cpass+y);
        colx = color(height-y,x,cpass-X);
       }

       
       float rr= red(colx);
       float bb= blue(colx);
       float gg= green(colx);
       
         rr = colx >> 16 & 0xff;
       gg = colx >> 8 & 0xff;
       bb = colx  & 0xff;

 if (!invert)      bbg.pixels[pos]=color(rr,gg,bb);
   if (invert)      bbg.pixels[pos]=-color(rr,gg,bb);
   }
   if (!online){
    arraycopy(bbg.pixels,pixels);
   }
 }
 if (animate){
   cpass++;
 }
if (cpass>4000){
cpass=0;  
}

}
Re: 2d water simulation with refraction
Reply #7 - Mar 20th, 2010, 7:50am
 
This is by far the best water refraction code on the net that I have seen.

Here is the code remixed with the permission of brik

...
http://www.openprocessing.org/visuals/?visualID=8359

This algorithm has a lot of potential. A friend and I are making a game out of this algorithm.

Processing rocks!!!

Hint. Reduce the frame-rate to 15fps and press 'r' with rhythm at first and then press it repeatedly to see some awesome refraction.
Re: 2d water simulation with refraction
Reply #8 - Mar 21st, 2010, 8:07am
 
Hello,
i'm quite a noob,
i was asked to create a non-interactive (looping) water ripple effect, but the problem is that the shape of the ripples should be rectangular and not circular (surreal waves).
Any suggestion?
Thnx in advance!
Re: 2d water simulation with refraction
Reply #9 - Mar 21st, 2010, 9:21am
 
You can modify this code and make a picture with a square or even write a small procedure to add a rectangle/square when you click the mouse or hit a specific key.

This would create the rectangular waves you are looking for.

e.g.
Code:

void keyPressed(){
if (key=='e'){
for (int x = 0;x<20;x++){
for (int y = 0;y<20;y++){
wallMap[x+20*y]=1;

}
}
}
}

void keyReleased(){


for (int x = 0;x<20;x++){
for (int x = 0;x<20;x++){
for (int y = 0;y<20;y++){
wallMap[x+20*y]=0;

}
}
}

Re: 2d water simulation with refraction
Reply #10 - Mar 21st, 2010, 11:08am
 
Hi, Andrew,
thank U very much for the reply.
The code was not running so i added a new } at the end,
but the debug window says "can't find anything named "wallMap".

I'm now trying to start from zero to write somethin very simple since i don't understand the code of the several example that i found, but i m glad if You could explain Your code.

Thank and see You
sh
Re: 2d water simulation with refraction
Reply #11 - Mar 21st, 2010, 1:19pm
 
Well in the declaration section at the top of the code

you can make the int [] wallMap public
public  int[] wallMap = new int[tankSize];
e.g.

Code:

 public  int[] wallMap = new int[tankSize];


Once you do that you can manipulate the walls at leisure. That's how I got the processing logo as a pre-existing wall when the sketch starts.

What i will do is make the changes and post a working example at openprocessing.org and here as well

I will explain how you can use multiple shapes.

A neat idea would be to take the image from the cam or some black and white video and do a blob detection on your hand or some shape and generate ripples from that.

The possibilities are endless!!!  Cheesy
Re: 2d water simulation with refraction
Reply #12 - Mar 21st, 2010, 1:57pm
 
Thank You very much Andrew,
i would like to explain why i'm asking for such a surreal effect:
My friend is doing a research about an italian artist, Gino De Dominicis (1947-1998). In 1969 De Dominicis filmed a short movie about an "attempt to create rectangular - instead of circular shapes - around a stone that falls in the water".
So my friend asked me whether it was possible to create such an effect.
I said ... YES! But as You see i'm going crazy.
I'm very thankful for Your advices, i will check the board and openprocessing.
K.i.T [keep in touch]!
Re: 2d water simulation with refraction
Reply #13 - Mar 21st, 2010, 5:39pm
 
spritehad

I will post the procedure/code  here until i achieve exactly what you are attempting to achieve.

I modified the code to use a PGraphics image. The image draws a rectangle and an ellipse. Two basic shapes. I copy the image to a PImage object so as to get a black and white image and then you can then route that directly to the wallMap [] array.

The image follows the mouseX and mouseY co-ordinates off to the left a little so you can still create a ripple by pressing 'd'.

Just add this procedure to the end of the sketch and then call it at the top of the draw loop

Code:

void drawboat(){
 ball = createGraphics(width,height,P2D);
 ball.loadPixels();
 ball.beginDraw();
 // ball.background(-1);
 ball.stroke(-1);
 ball.fill(-1);
 ball.ellipse(mouseX-35,mouseY,30,30);

 ball.rect(mouseX-45,mouseY-10,30,30);
 ball.updatePixels();
 //  
 ld.pixels=ball.pixels;
 //arraycopy(ball.pixels,ld.pixels);
 //ld.filter(INVERT);
 //ld.updatePixels();
 for (int x = 0;x<width;x++){
   for (int y = 0;y<height;y++){
     //      //bbg.set(x,y,color(height-y,x,x-y)); //method 1
     //      //bbg2.set(x,y,color(height-y,x,x-y)); //method 1
     // bbg.set(px,py,v.get(x,y));
     //  bbg2.set(x,y,v.get(x,y));
     wallMap[x+width*y]=0;
     if (!mousePressed){
       if (brightness(ld.get(x,y))==1){
         wallMap[x+width*y]=1;
       }

     }



     //      
     //      
     //
     //
   }
 }
 //arraycopy(ball.pixels,wallMap);


}


Then add it to the beginning of the draw

Code:

void draw(){

drawboat();
//the rest of the code
}
Re: 2d water simulation with refraction
Reply #14 - Mar 22nd, 2010, 9:13am
 
Sorry for the delay, Andrew,
due to some hour diff (i m in europe).
I can't succeed to make the code properly work, but don't worry, i will study it a little more and post a result in case of success.
Thanks again for now!
See You soon
sh
Pages: 1 2