POV-Ray : Newsgroups : povray.off-topic : Liquid Physics Server Time
14 Oct 2026 20:31:13 EDT (-0400)
  Liquid Physics (Message 1 to 37 of 37)  
From: Chambers
Subject: Liquid Physics
Date: 30 Mar 2008 17:49:01
Message: <47f018dd@news.povray.org>
So, just for fun I'm writing a quick liquid simulation.  Because I like 
liquids, and I'm learning C#, that's why :)

Anyway, I'm running into some problems with the simulation.  Namely, a 
certain instability.  Here's what I'm doing:

For each particle, I loop through all the other particles (i=0..count-1, 
j=i+1..count), and check the distance between them (I use some 
additional bounding to speed things up, but that's not important to my 
problem here).  I compute the forces the two particles place on each 
other, and then add them to each particles' total "force" value.

Once I'm done computing interactions, I loop through all the particles, 
add the force to the velocity, and reset the force to zero.

The way I see it (and granted, I've never written a liquid sim before), 
there are two forces I need to deal with, pertaining to two stages that 
occur when liquid particles collide:

1) When the particles are farther apart, they attract each other.  This 
weak attraction would be the driving force behind surface tension.

2) When the particles are closer together, they push against each other. 
  This force must be much stronger than the first in order to maintain 
some volume.

Now, so far I'm only actually implementing the second force above. 
However, what I see is that the particles tend to settle pretty quickly 
to the floor, and then they clump together.  Apparently, the particles 
on the outside "force" the particles in the middle of the clump closer 
and closer, until they suddenly explode and things go flying everywhere.

I *think* the problem has to do with conservation of momentum.  I 
naively dealt with that by halving the force applied to each particle in 
a pair (one half of the force gets applied to each), but that's not 
quite right, is it?  The momentum of each particle shouldn't simply be 
split evenly with every individual particle it collides with... rather, 
I'll need to collect a list of interactions, and split this particle's 
momentum between *all* of those other particles...

If anyone else here has dealt with similar issues, I'd appreciate 
knowing if I'm on the right track...

-- 
...Ben Chambers
www.pacificwebguy.com


Post a reply to this message

From: scott
Subject: Re: Liquid Physics
Date: 31 Mar 2008 02:13:05
Message: <47f08f01$1@news.povray.org>
> Once I'm done computing interactions, I loop through all the particles, 
> add the force to the velocity, and reset the force to zero.

Firstly, I wouldn't use integration like this, it's called Euler integration 
and is basically rubbish and well known for being unstable.  A far better 
method, and the de-facto standard in real-time applications, is Runge-Kutta 
4, or RK4 for short.  It is far more stable and much more accurate per CPU 
time than the Euler method.

There are numerous tutorials and explanations online of how to use RK4 in 
exactly the sort of simulation you are doing, but one of my favourites is 
here, it's very clear.  It's got small code snippets in C++ but they are 
very simple and should be easy enough to convert to any language.

http://www.gaffer.org/game-physics/integration-basics

> The way I see it (and granted, I've never written a liquid sim before), 
> there are two forces I need to deal with, pertaining to two stages that 
> occur when liquid particles collide:
>
> 1) When the particles are farther apart, they attract each other.  This 
> weak attraction would be the driving force behind surface tension.
>
> 2) When the particles are closer together, they push against each other. 
> This force must be much stronger than the first in order to maintain some 
> volume.
>
> Now, so far I'm only actually implementing the second force above.

When I did this sort of thing before, I did both forces as one using a 
single function.  Something like:

Force = A * (1/d^4) - B * (1/d^2)

Where d is the distance between two particles, and A,B are constants.

> However, what I see is that the particles tend to settle pretty quickly to 
> the floor, and then they clump together.  Apparently, the particles on the 
> outside "force" the particles in the middle of the clump closer and 
> closer, until they suddenly explode and things go flying everywhere.

Your physics time-step is too big.  WHat is happening is that from one frame 
to the next two particles are moving from far apart to *very* close 
together.  IRL (and with a smaller time-step or better integrator) this 
wouldn't happen, because there would be inbetween steps that prevent the 
particles getting this close together.

There are several ways to fix this (I would do all of them if I were you):

1) Use RK4 instead of Euler (this will let you get away with much bigger 
time-steps without explosion)
2) Use smaller time-steps, eg update the physics 10 times for every graphics 
update
3) Limit the maximum force artificially in the code to avoid explosions

So long as your time step and integration method are accurate enough to 
avoid explosions, it doesn't matter what the formula for Force is, so long 
as you apply it equally and opposite to each pair of particles momentum will 
be conserved.

I would also add some damping force, proportional to speed or speed squared, 
this will also help calm things down.


Post a reply to this message

From: Invisible
Subject: Re: Liquid Physics
Date: 31 Mar 2008 08:16:53
Message: <47f0e445$1@news.povray.org>
scott wrote:

> Firstly, I wouldn't use integration like this, it's called Euler 
> integration and is basically rubbish and well known for being unstable.  
> A far better method, and the de-facto standard in real-time 
> applications, is Runge-Kutta 4, or RK4 for short.  It is far more stable 
> and much more accurate per CPU time than the Euler method.

True indeed. Now, if I could just figure out how the hell to implement 
RK4...

[Sure, I have a dictionary of mathematics, and there's Wikipedia and 
there's MathWorld, but all involve pretty abstract mathematics that is 
difficult to figure out how to apply to a really complicated 
differential euqation.]

> There are numerous tutorials and explanations online of how to use RK4 
> in exactly the sort of simulation you are doing, but one of my 
> favourites is here, it's very clear.  It's got small code snippets in 
> C++ but they are very simple and should be easy enough to convert to any 
> language.
> 
> http://www.gaffer.org/game-physics/integration-basics

Mmm, that's a nice article. I'm still having trouble following it 
though. Perhaps a Haskell example?

   integrate t dt a (v,p) = (v + dt*(a t v p), p + dt*v)

OK, well that seems trivial enough. So how about RK4?

   evaluate (v,p) t dt (dv,dp) =
     let
       v' = v + dt*dv
       p' = p + dt*dp
     in  (v', a (t+dt) v' p')

   integrate t dt a (v,p) =
     let
       a = evaluate (v,p) t dt -- er... where's the rest?
       b = evaluate (v,p) t (dt/2) a
       c = evaluate (v,p) t (dt/2) b
       d = evaluate (v,p) t dt     c
       dv = (fst a + 2 * (fst b + fst c) + fst d) / 6
       dp = (snd a + 2 * (snd b + snd c) + snd d) / 6
     in (v + dt*dv, p + dt*dp)

Er... is that actually correct? And what's with the formula for A? I'm 
not really understanding how this is supposed to work...

> Your physics time-step is too big.

Yep, I've seen this happen lots before.

> There are several ways to fix this (I would do all of them if I were you):
> 
> 1) Use RK4 instead of Euler (this will let you get away with much bigger 
> time-steps without explosion)
> 2) Use smaller time-steps, eg update the physics 10 times for every 
> graphics update
> 3) Limit the maximum force artificially in the code to avoid explosions

Indeed.

For my chaos pendulum simulations, I added a constant term to the 
denominator in the "inverse square law" force calculations to prevent 
division by zero and absurdly large forces.

I intended (but never got round to) implementing a system where the 
computer integrates a time step, then integrates it again with twice the 
resolution, and if the results differ too much, it subdivides further...

-- 
http://blog.orphi.me.uk/
http://www.zazzle.com/MathematicalOrchid*


Post a reply to this message

From: scott
Subject: Re: Liquid Physics
Date: 31 Mar 2008 09:22:09
Message: <47f0f391$1@news.povray.org>
>> http://www.gaffer.org/game-physics/integration-basics
>
> Mmm, that's a nice article. I'm still having trouble following it though. 
> Perhaps a Haskell example?
>
>   integrate t dt a (v,p) = (v + dt*(a t v p), p + dt*v)
>
> OK, well that seems trivial enough. So how about RK4?
>
>   evaluate (v,p) t dt (dv,dp) =
>     let
>       v' = v + dt*dv
>       p' = p + dt*dp
>     in  (v', a (t+dt) v' p')
>
>   integrate t dt a (v,p) =
>     let
>       a = evaluate (v,p) t dt -- er... where's the rest?

The only difference is that in the evaluate function without the supplied 
derivative, you don't need to update the state, simply return the derivative 
at time t using the initial state.

>       b = evaluate (v,p) t (dt/2) a
>       c = evaluate (v,p) t (dt/2) b
>       d = evaluate (v,p) t dt     c
>       dv = (fst a + 2 * (fst b + fst c) + fst d) / 6
>       dp = (snd a + 2 * (snd b + snd c) + snd d) / 6
>     in (v + dt*dv, p + dt*dp)
>
> Er... is that actually correct? And what's with the formula for A? I'm not 
> really understanding how this is supposed to work...

What it's doing is this:

1) Work out the derivative at time t.  This is simply a matter of 
calculating the new acceleration at time t (as the derivative of velocity) 
based on the current state, and returning the current velocity (as the 
derivative of position).

2) Using the derivative you just calculated, a, calculate a new state at 
time t+dt/2 using normal Euler integration.  Return the derivative of the 
new state.

3) Same as step 2, but use b instead of a, and store the result as 
derivative c.

4) Same as 2, but this time use the derivative c to do the whole step dt 
from the first point.

5) Take a weighted average of all the derivatives you just caluclated, a-d, 
this gives the most accurate derivative.

6) Use this weighted average to actually advance your state by dt using 
normal Euler integration.

If you want to actually understand the maths behind it, well that's another 
story (and another Google search, or two :-).

> I intended (but never got round to) implementing a system where the 
> computer integrates a time step, then integrates it again with twice the 
> resolution, and if the results differ too much, it subdivides further...

A good idea, it's called "adaptive time step".


Post a reply to this message

From: Invisible
Subject: Re: Liquid Physics
Date: 31 Mar 2008 09:38:41
Message: <47f0f771$1@news.povray.org>
>>   integrate t dt a (v,p) =
>>     let
>>       a = evaluate (v,p) t dt -- er... where's the rest?
> 
> The only difference is that in the evaluate function without the 
> supplied derivative, you don't need to update the state, simply return 
> the derivative at time t using the initial state.

Er... right... I think...

So just return v and the instantenous acceleration at time t?

It's news to me that C++ supports optional function arguments.

[And while we're on the subject, this is the first example of C++ code 
I've ever seen which appears to be comprehensible. Usually it just looks 
like gibberish...]

So anyway, it computes several derivatives and takes their weighted 
average. That seems simple enough in principle. I rather suspect that in 
practice it's spine-tinglingly easy to screw up some tiny detail that 
makes the entire algorithm not work properly. There's *a lot* of 
individual steps there to get right...

>> I intended (but never got round to) implementing a system where the 
>> computer integrates a time step, then integrates it again with twice 
>> the resolution, and if the results differ too much, it subdivides 
>> further...
> 
> A good idea, it's called "adaptive time step".

Mmm, as with all adaptive algorithms, the key is to get the maximum step 
size "small enought", and to avoid infinite recursion. (I also wondered 
if maybe it would be better to just look at the magnitude of force 
changes or something to decide how "stable" the system is at this point...)

-- 
http://blog.orphi.me.uk/
http://www.zazzle.com/MathematicalOrchid*


Post a reply to this message

From: scott
Subject: Re: Liquid Physics
Date: 31 Mar 2008 10:10:46
Message: <47f0fef6$1@news.povray.org>
> So just return v and the instantenous acceleration at time t?

Yes, exactly.

> It's news to me that C++ supports optional function arguments.

Of course it does (see printf) but you don't use that here.  You just define 
two functions with the same name but with different arguments (this is where 
the article text is a bit misleading IMO).  The compiler knows which one to 
actually call because it matches up the arguments.

> [And while we're on the subject, this is the first example of C++ code 
> I've ever seen which appears to be comprehensible. Usually it just looks 
> like gibberish...]

Maybe because it's written for the purpose of clarity rather than trying to 
make it look "clever" by being totally unreadable.

> So anyway, it computes several derivatives and takes their weighted 
> average. That seems simple enough in principle. I rather suspect that in 
> practice it's spine-tinglingly easy to screw up some tiny detail that 
> makes the entire algorithm not work properly. There's *a lot* of 
> individual steps there to get right...

Yeh, try it first with a simple model like a mass on a spring, or a 
pendulum.  You can then check the results with the actual theory of those 
models, and even compare to standard Euler at the same time.

When I first wrote my RK4 code for the thing I'm currently working on, I 
accidentally updated the initial state for each of the 4 derivative 
calculations.  It was only when I noticed that the velocities being reported 
didn't match up with the actual distances being covered (like covering 1km 
in 10 seconds at a speed of 20 m/s!) that I found the bug.

> Mmm, as with all adaptive algorithms, the key is to get the maximum step 
> size "small enought", and to avoid infinite recursion. (I also wondered if 
> maybe it would be better to just look at the magnitude of force changes or 
> something to decide how "stable" the system is at this point...)

A common method used in car simulations (where the tyres are very stiff and 
hence need a very small time step or the thing explodes easily) is to check 
that energy is (almost) conserved.  Going back to the simple mass/spring 
model, if you keep track of the kinetic energy in the mass and the spring 
energy stored, it should be fairly trivial to include a check if the total 
energy has suddenly spiked.  If so you could then do some smaller time 
steps.  Of course if you are getting input from outside the system you need 
to account for this too.


Post a reply to this message

From: Invisible
Subject: Re: Liquid Physics
Date: 31 Mar 2008 10:17:24
Message: <47f10084$1@news.povray.org>
>> It's news to me that C++ supports optional function arguments.
> 
> Of course it does (see printf) but you don't use that here.  You just 
> define two functions with the same name but with different arguments 
> (this is where the article text is a bit misleading IMO).  The compiler 
> knows which one to actually call because it matches up the arguments.

Ah, right, so it's overloaded but that's not shown in the article?

(BTW, I thought printf() was only for C, not C++?)

>> [And while we're on the subject, this is the first example of C++ code 
>> I've ever seen which appears to be comprehensible. Usually it just 
>> looks like gibberish...]
> 
> Maybe because it's written for the purpose of clarity rather than trying 
> to make it look "clever" by being totally unreadable.

Did the Haskell version look "readable"?

> When I first wrote my RK4 code for the thing I'm currently working on, I 
> accidentally updated the initial state for each of the 4 derivative 
> calculations.  It was only when I noticed that the velocities being 
> reported didn't match up with the actual distances being covered (like 
> covering 1km in 10 seconds at a speed of 20 m/s!) that I found the bug.

One of the nice things about Haskell is that you can't make that 
mistake. Since you never update anything in-place, you can't 
accidentally destroy a result you need to use again later, and by left 
wondering where this "wrong" data is coming from.

...unfortunately you *can* still accidentally return the wrong damn 
varible from your function. :-S

> A common method used in car simulations (where the tyres are very stiff 
> and hence need a very small time step or the thing explodes easily) is 
> to check that energy is (almost) conserved.  Going back to the simple 
> mass/spring model, if you keep track of the kinetic energy in the mass 
> and the spring energy stored, it should be fairly trivial to include a 
> check if the total energy has suddenly spiked.  If so you could then do 
> some smaller time steps.  Of course if you are getting input from 
> outside the system you need to account for this too.

Mmm, it has a flavour...

-- 
http://blog.orphi.me.uk/
http://www.zazzle.com/MathematicalOrchid*


Post a reply to this message

From: scott
Subject: Re: Liquid Physics
Date: 31 Mar 2008 10:38:17
Message: <47f10569$1@news.povray.org>
> Ah, right, so it's overloaded but that's not shown in the article?

Yup, it is in the source code however.

> (BTW, I thought printf() was only for C, not C++?)

Well it is for C, but anything you can do in C you can do in C++.  Not that 
it's a good idea though...

> Did the Haskell version look "readable"?

Not really, I tried to add some to your code to show what I meant with the 
other evaluate function, but I couldn't work out what was going on!

> One of the nice things about Haskell is that you can't make that mistake. 
> Since you never update anything in-place, you can't accidentally destroy a 
> result you need to use again later, and by left wondering where this 
> "wrong" data is coming from.

Seems logical, but without thinking about it too much, how would you keep 
track of the state of the system while it's running?  Are you basically 
saying that variables are write-once in Haskell?  SO I couldn't do something 
like:

currentState = Integrate(currentState);

I would need to instead do:

frame++
state[frame] = Integrate( state[frame-1] )


Post a reply to this message

From: Invisible
Subject: Re: Liquid Physics
Date: 31 Mar 2008 10:56:14
Message: <47f1099e@news.povray.org>
>> Ah, right, so it's overloaded but that's not shown in the article?
> 
> Yup, it is in the source code however.

Ah, OK. I thought it was just a typo or something...

>> (BTW, I thought printf() was only for C, not C++?)
> 
> Well it is for C, but anything you can do in C you can do in C++.  Not 
> that it's a good idea though...

Ah yes, C++ is a superset of C. But I was thinking (hoping?) that in C++ 
you have something less hellish than prinf() to play with...

>> Did the Haskell version look "readable"?
> 
> Not really, I tried to add some to your code to show what I meant with 
> the other evaluate function, but I couldn't work out what was going on!

No, Haskell doesn't support overloaded functions, so you'd have to make 
some moderately radical (oxymoron?!) alterations to make it work.

> Seems logical, but without thinking about it too much, how would you 
> keep track of the state of the system while it's running?

Ah, grasshopper, you are still thinking in imperative terms. ;-)

A true Haskeller would define a notionally infinite list which 
notionally contains the entire state of the system at every future point 
in time. Examining the elements of this list causes the integration to 
actually be performed, and assuming you let go of the start of the list, 
the GC will delete each state after it has been calculated.

In other words, define an infinite list, and then map some monadic 
function over it which displays the state of the screen or dumps it to a 
file or otherwise "does" something with it. And the result will be more 
or less like this C++ example.

Now, if you want to use *user input* to alter the integration as it 
proceeds, then you must take an somewhat different approach. (See below.)

> Are you 
> basically saying that variables are write-once in Haskell?

You *could* put it that way. It would be more correct to say that a 
variable is *defined* as having a specific value in Haskell. Consider this:

   let node1 = Node {next = node2, prev = node4}
       node2 = Node {next = node3, prev = node1}
       node3 = Node {next = node4, prev = node2}
       node4 = Node {next = node1, prev = node3}
   in ...

As you can see, it's not so much that you can only assign once to a 
variable, it's that you are *defining* what a variable's value *is*. The 
compiler will chase dependencies - even circular ones - and resolve them 
for you, figuring out what order to initialise everything and so forth.

(You can't really speak of assigning several values to the same variable 
"one after the other" because [pure] Haskell function don't have any 
notion of "time".)

> So I couldn't do something like:
> 
> currentState = Integrate(currentState);

No, you couldn't.

You could, however, write

   animate state = do
     dispaly state
     let new_state = integrate state
     animate new_state

which will display the initial state, call your integrate function to 
compute the next state, display that state, and so forth. (This method 
*does* allow you to accept user input and do something different based 
on what the user input is.)

A third way would be to use explicitly mutable state - something like this:

   state <- takeMVar my_state
   writeMVar (integrate state)

But there's no particular advantage in doing that here, and it just 
takes more typing to write out.

-- 
http://blog.orphi.me.uk/
http://www.zazzle.com/MathematicalOrchid*


Post a reply to this message

From: Nicolas Alvarez
Subject: Re: Liquid Physics
Date: 31 Mar 2008 11:13:14
Message: <47f10d9a$1@news.povray.org>
Invisible escribió:
>>> (BTW, I thought printf() was only for C, not C++?)
>>
>> Well it is for C, but anything you can do in C you can do in C++.  Not 
>> that it's a good idea though...
> 
> Ah yes, C++ is a superset of C. But I was thinking (hoping?) that in C++ 
> you have something less hellish than prinf() to play with...

You do.

C:
printf("Number is: %d\n", somenumber);

...and be careful types match between format string and arguments 
passed, and particularly that you passed *enough* arguments

C++:
cout << "Number is: " << somenumber << "\n";

...and there is no duplicated information that could get out-of-sync 
(format and arguments).

It's just you can still use the C way if you want to.


Post a reply to this message

From: Orchid XP v8
Subject: Re: Liquid Physics
Date: 31 Mar 2008 14:41:30
Message: <47f13e6a@news.povray.org>
>> Ah yes, C++ is a superset of C. But I was thinking (hoping?) that in 
>> C++ you have something less hellish than prinf() to play with...
> 
> You do.
> 
> C:
> printf("Number is: %d\n", somenumber);
> 
> ...and be careful types match between format string and arguments 
> passed, and particularly that you passed *enough* arguments
> 
> C++:
> cout << "Number is: " << somenumber << "\n";
> 
> ...and there is no duplicated information that could get out-of-sync 
> (format and arguments).
> 
> It's just you can still use the C way if you want to.

That does indeed seem like at least *some* improvement...

(In C, almost every single time anybody touched printf(), the compiler 
would utter something about "suspicious pointer conversion". To this day 
nobody knows what that actually means... Even the tutors didn't seem to 
know.)


Post a reply to this message

From: Kevin Wampler
Subject: Re: Liquid Physics
Date: 31 Mar 2008 15:46:34
Message: <47f14daa$1@news.povray.org>
Assuming that you're writing this simulation for graphics purposes 
rather than physical simulation where accuracy is a major concern 
there's some other methods that you might consider.

First, a bit of terminology.  There's two main paradigms I'm aware of 
which are used to simulate fluids: Eulerian and Lagrangian.  In the 
Eulerian approach you consider what's happening in a small fixed piece 
of space and look at how that changes based on the pieces of space 
around it.  In the Lagrangian approach, on the other hand, you consider 
what happens to pieces of the fluid as they move around.  The approach 
you describe is then a Lagrangian approach.

As you've noticed, stability can be a problem when you integrate out 
these sorts of equations to determine how the fluid should move over 
time.  Changing the integration mechanism (such as to a Runge-Kutta 
integrator) or decreasing the time-step can help this to a degree, but 
there exist fluid simulation methods which are unconditionally stable. 
That is, the simulation will never `blow up' no matter what you do, even 
with very large time steps in the simulation.

Probably the simplest such technique is the "Stable Fluids" algorithm 
invented by Jos Stam.  It uses some concepts from both the Eulerian and 
Lagrangian viewpoints, and is this referred to as a "semi-Lagrangian" 
method.  There's a very easy to ready paper on the algorithm that even 
gives some code for how to implement it:

http://www.dgp.toronto.edu/people/stam/reality/Research/pdf/GDC03.pdf

The algorithm itself should be also pretty easy to implement, and is 
also useful in a educational sense as it uses some interesting 
techniques (such as, iirc, explicitly projecting the flow onto a 
divergence-free space to achieve an incompressible flow).

There are also some fully particle based fluid simulation methods which 
are more similar to yours in their apploach, but I'm not aware of any 
which are as simple to implement as the stable fluids algorithm.  You 
can check out some early examples:

http://graphics.ethz.ch/Downloads/Publications/Papers/2003/mue03b/p_Mue03b.pdf
http://www.cs.utah.edu/vissim/papers/ParticleFluid/ParticleFluidsLowRes.pdf

Or see a more recent paper on such a method at:

http://graphics.stanford.edu/~barta/research/adams-sig07.pdf

I suspect implementing one of these methods (or one of the newer 
Eulerian / semi-Lagrangian ones) would be more work than you care to 
spend, but I think stable fluids should be pretty reasonable to do 
without a crazy amount of work.

Note, however, that all the stuff I mentioned is based on simulating the 
Navier-Stokes equations.  If you're only looking for something that 
looks sortof-kinda like a fluid rather than behaving as a proper fluid 
then there's probably all sorts of shortcuts and hacks you can do, and 
in this case I agree with scott's suggestions so I'd start playing 
around with using a better integrator and/or adding some sort of 
artificial dampening.




Chambers wrote:
> So, just for fun I'm writing a quick liquid simulation.  Because I like 
> liquids, and I'm learning C#, that's why :)
> 
> Anyway, I'm running into some problems with the simulation.  Namely, a 
> certain instability.  Here's what I'm doing:
> 
> For each particle, I loop through all the other particles (i=0..count-1, 
> j=i+1..count), and check the distance between them (I use some 
> additional bounding to speed things up, but that's not important to my 
> problem here).  I compute the forces the two particles place on each 
> other, and then add them to each particles' total "force" value.
> 
> Once I'm done computing interactions, I loop through all the particles, 
> add the force to the velocity, and reset the force to zero.
> 
> The way I see it (and granted, I've never written a liquid sim before), 
> there are two forces I need to deal with, pertaining to two stages that 
> occur when liquid particles collide:
> 
> 1) When the particles are farther apart, they attract each other.  This 
> weak attraction would be the driving force behind surface tension.
> 
> 2) When the particles are closer together, they push against each other. 
>  This force must be much stronger than the first in order to maintain 
> some volume.
> 
> Now, so far I'm only actually implementing the second force above. 
> However, what I see is that the particles tend to settle pretty quickly 
> to the floor, and then they clump together.  Apparently, the particles 
> on the outside "force" the particles in the middle of the clump closer 
> and closer, until they suddenly explode and things go flying everywhere.
> 
> I *think* the problem has to do with conservation of momentum.  I 
> naively dealt with that by halving the force applied to each particle in 
> a pair (one half of the force gets applied to each), but that's not 
> quite right, is it?  The momentum of each particle shouldn't simply be 
> split evenly with every individual particle it collides with... rather, 
> I'll need to collect a list of interactions, and split this particle's 
> momentum between *all* of those other particles...
> 
> If anyone else here has dealt with similar issues, I'd appreciate 
> knowing if I'm on the right track...
>


Post a reply to this message

From: Chambers
Subject: Re: Liquid Physics
Date: 1 Apr 2008 00:27:05
Message: <47f1c7a9@news.povray.org>
scott wrote:
> The only difference is that in the evaluate function without the 
> supplied derivative, you don't need to update the state, simply return 
> the derivative at time t using the initial state.
> 
>>       b = evaluate (v,p) t (dt/2) a
>>       c = evaluate (v,p) t (dt/2) b
>>       d = evaluate (v,p) t dt     c
>>       dv = (fst a + 2 * (fst b + fst c) + fst d) / 6
>>       dp = (snd a + 2 * (snd b + snd c) + snd d) / 6
>>     in (v + dt*dv, p + dt*dp)

...

> 5) Take a weighted average of all the derivatives you just caluclated, 
> a-d, this gives the most accurate derivative.
> 
> 6) Use this weighted average to actually advance your state by dt using 
> normal Euler integration.

Here's a question: The derivative is based on the forces applied to the 
current particle.  The only way these forces change is when a particle 
collides with other particles (or ceases to collide).

So, for each of the above, am I to run a full collision detection?

-- 
...Ben Chambers
www.pacificwebguy.com


Post a reply to this message

From: Chambers
Subject: Re: Liquid Physics
Date: 1 Apr 2008 00:32:47
Message: <47f1c8ff@news.povray.org>
Invisible wrote:
> Ah, grasshopper, you are still thinking in imperative terms. ;-)

BTW, that grasshopper thing... does that come from the old TV show "Kung 
Fu"?  I could have sworn I saw it in a movie, though.  In fact, I asked 
at a couple local video stores what movie it was, and nobody knew... :(

-- 
...Ben Chambers
www.pacificwebguy.com


Post a reply to this message

From: Chambers
Subject: Re: Liquid Physics
Date: 1 Apr 2008 01:04:59
Message: <47f1d08b@news.povray.org>
scott wrote:
> Seems logical, but without thinking about it too much, how would you 
> keep track of the state of the system while it's running?  Are you 
> basically saying that variables are write-once in Haskell?

AFAIK, yes, since they're defined as immutable.

In other words, Haskell doesn't have variables, only constants.

-- 
...Ben Chambers
www.pacificwebguy.com


Post a reply to this message

From: scott
Subject: Re: Liquid Physics
Date: 1 Apr 2008 01:50:08
Message: <47f1db20$1@news.povray.org>
> A true Haskeller would define a notionally infinite list which notionally 
> contains the entire state of the system at every future point in time. 
> Examining the elements of this list causes the integration to actually be 
> performed, and assuming you let go of the start of the list, the GC will 
> delete each state after it has been calculated.

What's that like on performance though?  What would it be like if your state 
block consisted of hundreds of 4D vectors, and the system was being updated 
at 1000 Hz?  Would the GC be noticeable?

> (You can't really speak of assigning several values to the same variable 
> "one after the other" because [pure] Haskell function don't have any 
> notion of "time".)

Ah ok I see, so it's like you every variable is defined as "const" in the 
C++ sense, you can't modify it.

>   animate state = do
>     dispaly state
>     let new_state = integrate state
>     animate new_state

I assume that the compiler is clever enough to not keep on the stack any 
data that is not needed, ie old versions of state that are no longer needed. 
If you attempted that method in C++ you would fill up the RAM very quickly 
;-)


Post a reply to this message

From: scott
Subject: Re: Liquid Physics
Date: 1 Apr 2008 01:57:49
Message: <47f1dced$1@news.povray.org>
> Here's a question: The derivative is based on the forces applied to the 
> current particle.  The only way these forces change is when a particle 
> collides with other particles (or ceases to collide).
>
> So, for each of the above, am I to run a full collision detection?

Yes, inside the "acceleration" function, which is called from each 
"evaluate" you put your code for calculating the acceleration.  This, in 
your case, would presumably call the routine to check for collisions and 
work out the forces for all the particles.

Note that you need to put all the positions and velocities for every 
particle inside some state block and evaluate them in parallel using this 
algorithm - you can't do one at a time it doesn't work like that.

You may say that this is 4x as much work per integration step, but it will 
allow your time-step to be far more than 4x bigger and still be completely 
stable.


Post a reply to this message

From: Chambers
Subject: Re: Liquid Physics
Date: 1 Apr 2008 02:14:52
Message: <47f1e0ec@news.povray.org>
scott wrote:
> Note that you need to put all the positions and velocities for every 
> particle inside some state block and evaluate them in parallel using 
> this algorithm - you can't do one at a time it doesn't work like that.

That part, at least, seems obvious to me.

It certainly seems like I've got my work cut out for me :)  I almost 
feel that, if I'm going to implement something this complex, I may as 
well do something useful with it!

-- 
...Ben Chambers
www.pacificwebguy.com


Post a reply to this message

From: Invisible
Subject: Re: Liquid Physics
Date: 1 Apr 2008 04:38:57
Message: <47f202b1$1@news.povray.org>
scott wrote:
>> A true Haskeller would define a notionally infinite list which 
>> notionally contains the entire state of the system at every future 
>> point in time. Examining the elements of this list causes the 
>> integration to actually be performed, and assuming you let go of the 
>> start of the list, the GC will delete each state after it has been 
>> calculated.
> 
> What's that like on performance though?  What would it be like if your 
> state block consisted of hundreds of 4D vectors, and the system was 
> being updated at 1000 Hz?  Would the GC be noticeable?

Erm... without trying it, I couldn't say off the top of my head. Notice 
though that since this is "the normal way" to do stuff in Haskell, the 
compiler designers will be basing all their design decisions on the fact 
that people are going to be writing code this way.

I would say that for *vast* numbers of vectors, you might need to move 
over to using mutable arrays (uses less space and less GC time). But 
without benchmarking it I couldn't tell you exactly how much of a 
difference it makes. (I would also suspect that if you have "hundreds" 
of vectors you probably need random access to them too...)

>> (You can't really speak of assigning several values to the same 
>> variable "one after the other" because [pure] Haskell function don't 
>> have any notion of "time".)
> 
> Ah ok I see, so it's like you every variable is defined as "const" in 
> the C++ sense, you can't modify it.

Well, kinda, yeah.

It's like, a function can only have one body. Nobody would find that 
surprising. Like in C, you can't write a function, and then later change 
that function to something else. (While we're on the subject, in Haskell 
a "function name" is just a normal variable who's value happens to be a 
function...)

A basic property of Haskell code [and any "pure" functional language] is 
that if you take any variable and replace all occurrances of that 
variable with the expression that defines its value, the meaning of the 
program is unchanged. [Although possibly efficiency is reduced.] 
Notationally this is how you "execute" such a program.

>>   animate state = do
>>     dispaly state
>>     let new_state = integrate state
>>     animate new_state
> 
> I assume that the compiler is clever enough to not keep on the stack any 
> data that is not needed, ie old versions of state that are no longer 
> needed. If you attempted that method in C++ you would fill up the RAM 
> very quickly ;-)

Er, yes, in C++ (or most "normal" programming languages) the above loop 
would be an *absurdly* bad idea. In Haskell, this is the normal way to 
achieve looping, and hence the compiler goes to extreme lengths to 
optimise it well.

(If "state" is small enough, it may even get permanently allocated into 
CPU registers and updated in-place. It depends on exactly what it 
contains... If it's two floats like in the examples, then probably yes. 
If it's a set of 200 vectors, obviously no...)

-- 
http://blog.orphi.me.uk/
http://www.zazzle.com/MathematicalOrchid*


Post a reply to this message

From: Invisible
Subject: Re: Liquid Physics
Date: 1 Apr 2008 04:41:18
Message: <47f2033e$1@news.povray.org>
>> Are you 
>> basically saying that variables are write-once in Haskell?
> 
> AFAIK, yes, since they're defined as immutable.
> 
> In other words, Haskell doesn't have variables, only constants.

Well, depending on how you define "variable" and "constant".

Take this for example:   double x = 2*x

Every time you call this function, "x" can have a different value. It is 
a "variable". However, DURING EACH INVOCATION, "x" can only have one 
value. It is a "constant".

-- 
http://blog.orphi.me.uk/
http://www.zazzle.com/MathematicalOrchid*


Post a reply to this message

From: Invisible
Subject: Re: Liquid Physics
Date: 1 Apr 2008 04:43:16
Message: <47f203b4@news.povray.org>
scott wrote:

> Yes, inside the "acceleration" function, which is called from each 
> "evaluate" you put your code for calculating the acceleration.  This, in 
> your case, would presumably call the routine to check for collisions and 
> work out the forces for all the particles.
> 
> Note that you need to put all the positions and velocities for every 
> particle inside some state block and evaluate them in parallel using 
> this algorithm - you can't do one at a time it doesn't work like that.

Question: Would it be worth using some kind of space partitioning to 
enable you to process only "nearby" particles?

-- 
http://blog.orphi.me.uk/
http://www.zazzle.com/MathematicalOrchid*


Post a reply to this message

From: scott
Subject: Re: Liquid Physics
Date: 1 Apr 2008 05:13:04
Message: <47f20ab0@news.povray.org>
> Question: Would it be worth using some kind of space partitioning to 
> enable you to process only "nearby" particles?

I think he said he was using something like that already.


Post a reply to this message

From: Invisible
Subject: Re: Liquid Physics
Date: 1 Apr 2008 06:30:22
Message: <47f21cce@news.povray.org>
scott wrote:
>> Question: Would it be worth using some kind of space partitioning to 
>> enable you to process only "nearby" particles?
> 
> I think he said he was using something like that already.

Ah, OK.

-- 
http://blog.orphi.me.uk/
http://www.zazzle.com/MathematicalOrchid*


Post a reply to this message

From: Darren New
Subject: Re: Liquid Physics
Date: 1 Apr 2008 14:54:59
Message: <47f29313@news.povray.org>
Invisible wrote:
> It's like, a function can only have one body. Nobody would find that 
> surprising.

Unless you're using Erlang, I guess. :-)

"This is beyond dynamic binding. This is 
make-up-your-mind-after-it's-already-running binding."

-- 
   Darren New / San Diego, CA, USA (PST)
     "That's pretty. Where's that?"
          "It's the Age of Channelwood."
     "We should go there on vacation some time."


Post a reply to this message

From: Orchid XP v8
Subject: Re: Liquid Physics
Date: 1 Apr 2008 15:04:03
Message: <47f29533$1@news.povray.org>
Chambers wrote:

> BTW, that grasshopper thing... does that come from the old TV show "Kung 
> Fu"?  I could have sworn I saw it in a movie, though.  In fact, I asked 
> at a couple local video stores what movie it was, and nobody knew... :(

I have no idea. My boss used to call me grasshopper. No idea why...


Post a reply to this message

From: Stephen
Subject: Re: Liquid Physics
Date: 1 Apr 2008 15:17:55
Message: <u165v353jn1eekrhb3gdl47qfvu73dudat@4ax.com>
On Tue, 01 Apr 2008 21:04:23 +0100, Orchid XP v8 <voi### [at] devnull>
wrote:

>Chambers wrote:
>
>> BTW, that grasshopper thing... does that come from the old TV show "Kung 
>> Fu"?  I could have sworn I saw it in a movie, though.  In fact, I asked 
>> at a couple local video stores what movie it was, and nobody knew... :(
>
>I have no idea. My boss used to call me grasshopper. No idea why...

Walk the rice paper and leave no mark, Grasshopper.
-- 

Regards
     Stephen


Post a reply to this message

From: Invisible
Subject: Re: Wave Physics
Date: 2 Apr 2008 07:06:01
Message: <47f376a9$1@news.povray.org>
While we're on the subject... I've managed to get particle systems like 
this to work before. (E.g., my chaos pendulum example, and various other 
things.) What I haven't ever managed to do is figure out how to build 
myself my own wave tank.

I know I've asked this before, but what's the algorithm for this?

-- 
http://blog.orphi.me.uk/
http://www.zazzle.com/MathematicalOrchid*


Post a reply to this message

From: Invisible
Subject: Re: Liquid Physics
Date: 2 Apr 2008 07:18:18
Message: <47f3798a@news.povray.org>
Right. Well maybe it would be helpful to fix a few typos...

>   evaluate (v,p) t dt (dv,dp) =
>     let
>       v' = v + dt*dv
>       p' = p + dt*dp
>     in  (v', a (t+dt) v' p')

Err... that's incorrect. The acceleration function ("a") needs to be a 
parameter somewhere. Or it needs to be a top-level function. Let's make 
it top-level, like in the C++ version:

   acceleration t v p = ???

   evaluate (v,p) t dt Nothing = (v, acceleration t)
   evaluate (v,p) t dt (Just (dv,dp)) =
     let
       v' = v + dt*dv
       p' = p + dt*dp
     in  (v', acceleration (t+dt) v' p')

[I made the derivatives optional too. Did I get it right?]

>   integrate t dt a (v,p) =
>     let
>       a = evaluate (v,p) t dt -- er... where's the rest?
>       b = evaluate (v,p) t (dt/2) a
>       c = evaluate (v,p) t (dt/2) b
>       d = evaluate (v,p) t dt     c
>       dv = (fst a + 2 * (fst b + fst c) + fst d) / 6
>       dp = (snd a + 2 * (snd b + snd c) + snd d) / 6
>     in (v + dt*dv, p + dt*dp)

...and if we're using a top-level acceleration function, you don't need 
that "a" parameter there (which incidentally causes a name clash).

   integrate t dt (v,p) =
     let
       a = evaluate (v,p) t dt     Nothing
       b = evaluate (v,p) t (dt/2) (Just a)
       c = evaluate (v,p) t (dt/2) (Just b)
       d = evaluate (v,p) t dt     (Just c)
       dv = (fst a + 2 * (fst b + fst c) + fst d) / 6
       dp = (snd a + 2 * (snd b + snd c) + snd d) / 6
     in  (v + dt*dv, p + dt*dp)

I'll have to try this out later and see if it actually works...

-- 
http://blog.orphi.me.uk/
http://www.zazzle.com/MathematicalOrchid*


Post a reply to this message

From: Invisible
Subject: Re: Physics Code
Date: 2 Apr 2008 07:30:41
Message: <47f37c71$1@news.povray.org>
You know what? Screw it. Let's start again...

data State = State {velocity, position :: !Double}

data Derivative = Derivative {dvelocity, dposition :: !Double}

evaluate :: State -> Double -> Double -> Maybe Derivative -> Derivative
evaluate s t dt Nothing = Derivative {dvelocity = acceleration s t, 
dposition = velocity s}
evaluate s t dt (Just d) =
   let
     s' = State {velocity = velocity s + dvelocity d * dt, position = 
position s + dposition d * dt}
   in  Derivative {dvelocity = acceleration s' (t+dt), dposition = 
velocity s'}

integrate :: State -> Double -> Double -> State
integrate s t dt =
   let
     a = evaluate s t dt     Nothing
     b = evaluate s t (dt/2) (Just a)
     c = evaluate s t (dt/2) (Just b)
     d = evaluate s t dt     (Just c)
     dv = (dvelocity a + 2 * (dvelocity b + dvelocity c) + dvelocity d) / 6
     dp = (dposition a + 2 * (dposition b + dposition c) + dposition d) / 6
   in  State {velocity = velocity s + dv * dt, position = position s + 
dp * dt}

acceleration :: State -> Double -> Double
acceleration = error "not implemented yet"

Does that look correct?

-- 
http://blog.orphi.me.uk/
http://www.zazzle.com/MathematicalOrchid*


Post a reply to this message

From: Invisible
Subject: Re: Physics Code
Date: 2 Apr 2008 07:47:08
Message: <47f3804c$1@news.povray.org>
Invisible wrote:

> Does that look correct?

Well, I just ran it on a simple harmonic oscilator and got a sine wave, 
so that's always a good start...

-- 
http://blog.orphi.me.uk/
http://www.zazzle.com/MathematicalOrchid*


Post a reply to this message

From: scott
Subject: Re: Wave Physics
Date: 2 Apr 2008 07:54:19
Message: <47f381fb$1@news.povray.org>
> I know I've asked this before, but what's the algorithm for this?

The wave equation.

http://en.wikipedia.org/wiki/Wave_equation

2nd time differential of "wave value" is proportional to the 2nd spatial 
differential of "wave value"

2nd time differential is just acceleration (use it in an integrator like RK4 
to get the new positions).

2nd spatial differential in 2D is just d2u/d2x + d2u/d2y

See attached POV code.


Post a reply to this message


Attachments:
Download 'us-ascii' (3 KB)

From: Invisible
Subject: Re: Wave Physics
Date: 2 Apr 2008 08:10:52
Message: <47f385dc$1@news.povray.org>
scott wrote:
>> I know I've asked this before, but what's the algorithm for this?
> 
> The wave equation.
> 
> http://en.wikipedia.org/wiki/Wave_equation
> 
> 2nd time differential of "wave value" is proportional to the 2nd spatial 
> differential of "wave value"
> 
> 2nd time differential is just acceleration (use it in an integrator like 
> RK4 to get the new positions).
> 
> 2nd spatial differential in 2D is just d2u/d2x + d2u/d2y
> 
> See attached POV code.

Right. So for each pixel, measure the difference against the four 
neighboring pixels, total that up, multiply by the wave propogation 
constant, and that's the total acceleration for this pixel. Seems simple 
enough.

What happens if the wave propogation medium propogates different 
frequencies at different velocities?

-- 
http://blog.orphi.me.uk/
http://www.zazzle.com/MathematicalOrchid*


Post a reply to this message

From: Michael Zier
Subject: Re: Wave Physics
Date: 2 Apr 2008 08:14:24
Message: <47f386b0$1@news.povray.org>
> Right. So for each pixel, measure the difference against the four
> neighboring pixels, total that up, multiply by the wave propogation
> constant, and that's the total acceleration for this pixel. Seems simple
> enough.
> 
> What happens if the wave propogation medium propogates different
> frequencies at different velocities?

The wave propagation constant is not constant, but a function of 
frequency (dispersion relation).


Post a reply to this message

From: scott
Subject: Re: Wave Physics
Date: 2 Apr 2008 08:25:28
Message: <47f38948$1@news.povray.org>
> What happens if the wave propogation medium propogates different 
> frequencies at different velocities?

Well then your c is a function of frequency, it's also probably a function 
of "wave value" and "wave steepness" too in real life. (higher and steeper 
waves might propogate faster than lower waves).  Good luck calculating the 
frequency at a point though :-)  I'm thinking you might be able to do 
something clever with fourier though...


Post a reply to this message

From: Invisible
Subject: Re: Wave Physics
Date: 2 Apr 2008 08:28:25
Message: <47f389f9$1@news.povray.org>
scott wrote:
>> What happens if the wave propogation medium propogates different 
>> frequencies at different velocities?
> 
> Well then your c is a function of frequency, it's also probably a 
> function of "wave value" and "wave steepness" too in real life. (higher 
> and steeper waves might propogate faster than lower waves).  Good luck 
> calculating the frequency at a point though :-)  I'm thinking you might 
> be able to do something clever with fourier though...

Well, Reaktor's "Steam Pipe" instrument simplates dispersion using a 
simple all-pass filter in the feedback loop. OTOH, it's only trying to 
simulate a 1D wave. At exactly 1 point in space. Hmm, maybe you could do 
some sort of interesting convulation operation?

-- 
http://blog.orphi.me.uk/
http://www.zazzle.com/MathematicalOrchid*


Post a reply to this message

From: scott
Subject: Re: Wave Physics
Date: 3 Apr 2008 01:59:09
Message: <47f4803d$1@news.povray.org>
>> Well then your c is a function of frequency, it's also probably a 
>> function of "wave value" and "wave steepness" too in real life. (higher 
>> and steeper waves might propogate faster than lower waves).  Good luck 
>> calculating the frequency at a point though :-)  I'm thinking you might 
>> be able to do something clever with fourier though...
>
> Well, Reaktor's "Steam Pipe" instrument simplates dispersion using a 
> simple all-pass filter in the feedback loop. OTOH, it's only trying to 
> simulate a 1D wave. At exactly 1 point in space. Hmm, maybe you could do 
> some sort of interesting convulation operation?

I think if you are simulating a musical instrument, it would be better to 
get the resonant parts working first before you start trying to simulate 
different propagation speeds at different frequencies.  I would imagine that 
is a much smaller secondary effect.


Post a reply to this message

From: Invisible
Subject: Re: Wave Physics
Date: 3 Apr 2008 03:13:05
Message: <47f49191$1@news.povray.org>
scott wrote:
>>> Well then your c is a function of frequency, it's also probably a 
>>> function of "wave value" and "wave steepness" too in real life. 
>>> (higher and steeper waves might propogate faster than lower waves).  
>>> Good luck calculating the frequency at a point though :-)  I'm 
>>> thinking you might be able to do something clever with fourier though...
>>
>> Well, Reaktor's "Steam Pipe" instrument simplates dispersion using a 
>> simple all-pass filter in the feedback loop. OTOH, it's only trying to 
>> simulate a 1D wave. At exactly 1 point in space. Hmm, maybe you could 
>> do some sort of interesting convulation operation?
> 
> I think if you are simulating a musical instrument, it would be better 
> to get the resonant parts working first before you start trying to 
> simulate different propagation speeds at different frequencies.  I would 
> imagine that is a much smaller secondary effect.

Oh yeah, sure. I'm just going off at a tangent now. ;-)

-- 
http://blog.orphi.me.uk/
http://www.zazzle.com/MathematicalOrchid*


Post a reply to this message

Copyright 2003-2023 Persistence of Vision Raytracer Pty. Ltd.