Hi all,
I've been reading some in the Baraff papers on differential equations.
First, when he's talking about Euler integration and it being derived
from the infinite Taylor series, I noticed that as we're calculating
forces & torques, we're actually calculating the 2nd derivative.
So, instead of the often-seen Euler integration:
F=calcForces();
acc=F/m;
vel+=acc*dt;
pos+=vel*dt;
(and get an O(h^2) error term)
You would instead use one more term of the Taylor series:
F=calcForces();
acc=F/m;
vel+=acc*dt;
pos+=vel*dt+(dt^2)*acc
(compare the last line with Y_new=h*f(Y0)+h^2*f'(Y0)+O(h^3))
I always thought if I did a thing like that, I'd have to just use
dt*acc or 1/2*dt*acc, something like that. I'll try the new approach,
since that seems to get you from error O(h^2) to O(h^3) without any
cost. Haven't read about that anywhere.
Also, another chapter of Baraff's papers explains implicit
differential solving, and talks about solving a differential equation
backwards in time. So instead of taking some steps into the future, it
tries to be in the future and find a value which goes back to the
value you're currently at:
Y_new=Y0+h*f(Y_new)
Where Y_new is the value in the future, and Y0 is the current value.
To estimate Y_new, he finally comes up with:
delta(Y) = f(Y0) / (1/h*I - f'(Y0))
Where delta(Y) is ofcourse the change in value, f(Y0) is your normal
function which you already use, I is an identity matrix, h is the
timestep, and f' is ofcourse the 2nd derivative.
However, as I stated at the top, it seems with the forces -> position
pipeline we're already calculating the 2nd derivative (the
acceleration), which is a problem for most other differential
equations, it seems. So in my not too mathy vision, that would mean
that instead of calculating f'(Y0) and coming up with a matrix, as is
done in the Baraff paper using explicit formulae, it is possible to
state that the 'acc' variable IS f'(Y0), and you get:
delta(Y) = f(Y0) / (1/h - f'(Y0))
<=>
delta(Y) = vel / (1/h - acc)
Not that I find this formula intuitive in its meaning.
In the Baraff paper, this is used for solving a stiff ODE, with the
example being a stiff spring (F=-k*y). We all know the situation when
applying a lot of braking torque and the first time the wheel starts
jittering around velocity 0. This time reversal approach supposedly
solves this problem and comes back with the neutral point (y=0) with
whatever step you take. Seems like something to investigate further.
:)
Does anybody have experience with this approach, and can anyone see
where my thinking may go wrong, as I don't really think the last
formula seems to intuitive.
Thanks for any thoughts,
Ruud van Gaal
Free car sim : http://www.marketgraph.nl/gallery/racer/
Pencil art : http://www.marketgraph.nl/gallery/
Baraff's integration methods for stiff car physics
Ruud van Gaal discussed a time-reversed integration technique from David Baraff's papers that may stabilise stiff springs in his Racer sim. Gregor Veble compared it to predictor-corrector methods, Sebastien Tixier recommended fourth-order Runge-Kutta, and Matthew Jessick of Motorsims warned that implicit methods gain stability at the cost of the dynamics being modelled. Side discussion covered code structure and graphics performance.
With Ruud van Gaal (Racer)
physics/realism debateRacer
Automated summary.
Well that's it. First your sim wouldn't run on my funky old Voodoo 3, and now you've gone and made my head explode.
Thanks! ;-)
SB
"Ruud van Gaal" <[email hidden]> wrote in message news:[email hidden]...
Quoted text (75 lines)
> Hi all, > > I've been reading some in the Baraff papers on differential equations. > First, when he's talking about Euler integration and it being derived > from the infinite Taylor series, I noticed that as we're calculating > forces & torques, we're actually calculating the 2nd derivative. > So, instead of the often-seen Euler integration: > > F=calcForces(); > acc=F/m; > vel+=acc*dt; > pos+=vel*dt; > > (and get an O(h^2) error term) > You would instead use one more term of the Taylor series: > > F=calcForces(); > acc=F/m; > vel+=acc*dt; > pos+=vel*dt+(dt^2)*acc > > (compare the last line with Y_new=h*f(Y0)+h^2*f'(Y0)+O(h^3)) > > I always thought if I did a thing like that, I'd have to just use > dt*acc or 1/2*dt*acc, something like that. I'll try the new approach, > since that seems to get you from error O(h^2) to O(h^3) without any > cost. Haven't read about that anywhere. > > Also, another chapter of Baraff's papers explains implicit > differential solving, and talks about solving a differential equation > backwards in time. So instead of taking some steps into the future, it > tries to be in the future and find a value which goes back to the > value you're currently at: > > Y_new=Y0+h*f(Y_new) > > Where Y_new is the value in the future, and Y0 is the current value. > To estimate Y_new, he finally comes up with: > > delta(Y) = f(Y0) / (1/h*I - f'(Y0)) > > Where delta(Y) is ofcourse the change in value, f(Y0) is your normal > function which you already use, I is an identity matrix, h is the > timestep, and f' is ofcourse the 2nd derivative. > > However, as I stated at the top, it seems with the forces -> position > pipeline we're already calculating the 2nd derivative (the > acceleration), which is a problem for most other differential > equations, it seems. So in my not too mathy vision, that would mean > that instead of calculating f'(Y0) and coming up with a matrix, as is > done in the Baraff paper using explicit formulae, it is possible to > state that the 'acc' variable IS f'(Y0), and you get: > > delta(Y) = f(Y0) / (1/h - f'(Y0)) > <=> > delta(Y) = vel / (1/h - acc) > > Not that I find this formula intuitive in its meaning. > In the Baraff paper, this is used for solving a stiff ODE, with the > example being a stiff spring (F=-k*y). We all know the situation when > applying a lot of braking torque and the first time the wheel starts > jittering around velocity 0. This time reversal approach supposedly > solves this problem and comes back with the neutral point (y=0) with > whatever step you take. Seems like something to investigate further. > :) > > Does anybody have experience with this approach, and can anyone see > where my thinking may go wrong, as I don't really think the last > formula seems to intuitive. > > Thanks for any thoughts, > > Ruud van Gaal > Free car sim : http://www.marketgraph.nl/gallery/racer/ > Pencil art : http://www.marketgraph.nl/gallery/Hi Ruud,
sounds a bit like predictor/corrector method, but with a twist. It
definitely seems worth investigating further, especially if the claim of
stabilizing large stiffnesses is true (which is the major headache in
most calculations). Why not implement it and report on it so we can all
see what's to it ;)?
-Gregor
Ruud van Gaal wrote:
Quoted text (10 lines)
> > Does anybody have experience with this approach, and can anyone see > where my thinking may go wrong, as I don't really think the last > formula seems to intuitive. > > Thanks for any thoughts, > > Ruud van Gaal > Free car sim : http://www.marketgraph.nl/gallery/racer/ > Pencil art : http://www.marketgraph.nl/gallery/On Tue, 16 Oct 2001 09:51:20 +0200, Gregor Veble
<[email hidden]> wrote:
>Hi Ruud,
>
>sounds a bit like predictor/corrector method, but with a twist. It
>definitely seems worth investigating further, especially if the claim of
>stabilizing large stiffnesses is true (which is the major headache in
>most calculations).
In the report an example stiff spring is used, which varies in y using
force=-k*y. In other words, 'y' wants to go to 0 (like a suspension or
tire being braked). With the normal Euler approach, you quickly get an
explosion if your timestep isn't small enough.
With this reversed time thing, Baraff proves that ANY timestep would
have it end up at 0. :) So you could take 1s timesteps even, hehe.
> Why not implement it and report on it so we can all
>see what's to it ;)?
I'll print my own rattle and have a go. :)
It was just such a new thing for me and it definitely seems
interesting... if it works. ;-)
Ruud van Gaal
Free car sim : http://www.marketgraph.nl/gallery/racer/
Pencil art : http://www.marketgraph.nl/gallery/
On Tue, 16 Oct 2001 03:30:17 GMT, "Steve Blankenship"
<[email hidden]> wrote:
>Well that's it. First your sim wouldn't run on my funky old Voodoo 3, and now you've gone and made my head explode.
>
>Thanks! ;-)
You're welcome. :)
It should run on a V3, but you may have to resize the textures to fit
256x256. I still have been too lazy to program this in. ;-)
Voodoo OpenGL drivers are a nuisance anyway, but it is even said to
work on V2's. (you may have to copy 3dfxvgl.dll to the Racer dir and
rename it 'opengl32.dll').
Hope you can get your head together again.
Ruud van Gaal
Free car sim : http://www.marketgraph.nl/gallery/racer/
Pencil art : http://www.marketgraph.nl/gallery/
Why don't you implement the best integrater ever => Runge-Kutta of order 4 ?
Its not SO complicated ;o)
Differential Equation Basics : p B6 : Andrew Kitkin and David Baraff, 1997.
--
Sebastien TIXIER - Game Developer
Dynamics and Car Physics
http://www.eden-studios.fr
GPLRank Normal:-44.24 Monster:-124.44
On Tue, 16 Oct 2001 14:00:21 +0200, Sebastien Tixier
<[email hidden]> wrote:
>Why don't you implement the best integrater ever => Runge-Kutta of order 4 ?
>
>Its not SO complicated ;o)
Well it requires quite a bit of restructuring here; I must get ODE
classes in so that I can vary the integrator more or less
independently of the physics classes. That's holding me back a little,
apart from the fact that the ODE must be able to restore the state,
which can mean copying quite a lot of parameters ('vectorizing' the
state may cause a lot of copying back & forth for an ODE-friendly
chunk of memory).
On the other hand, my Geforce3 just came in and now I'm driving
Carrera with over 200 fps. :)) (so I have some copying time to spare,
hehe)
>Differential Equation Basics : p B6 : Andrew Kitkin and David Baraff, 1997.
The next chapter, 'Implicitly solving Equations' (not too sure about
the title) talks about methods when even RK4 gives you explosive
differential eq's. It just seems like it can be used with little, if
any, extra cost because a force is already the 2nd derivative of a
position.
Ruud van Gaal
Free car sim : http://www.marketgraph.nl/gallery/racer/
Pencil art : http://www.marketgraph.nl/gallery/
Quoted text (7 lines)
> Well it requires quite a bit of restructuring here; I must get ODE > classes in so that I can vary the integrator more or less > independently of the physics classes. That's holding me back a little, > apart from the fact that the ODE must be able to restore the state, > which can mean copying quite a lot of parameters ('vectorizing' the > state may cause a lot of copying back & forth for an ODE-friendly > chunk of memory).Quoted text (7 lines)
> >Differential Equation Basics : p B6 : Andrew Kitkin and David Baraff, 1997. > > The next chapter, 'Implicitly solving Equations' (not too sure about > the title) talks about methods when even RK4 gives you explosive > differential eq's. It just seems like it can be used with little, if > any, extra cost because a force is already the 2nd derivative of a > position.With an implicit integration scheme you get
stability, but at the cost of losing the dynamics
you are trying to model if the timestep is too large.
Sometimes this is OK, sometimes you should take
smaller time steps. It depends on your requirements.
Ruud van Gaal wrote:
As an example,
for this model, zero is of course just as wrong as growing
without bound. Without damping, an oscillation should continue.
For the explicit integration questions you were discussing,
there are many methods described in numerical methods books
that provide good accuracy and also good frequency response
for real time use near the edge of stability. Inventing your
own would seem to be a waste of time. (Similar to inventing
one's own cryptographic algorithm ;)
If you are going to go to the trouble of modelling
suspensions with specific frequencies, it is probably
a good idea to make sure that the integration scheme
has the capability to faithfully portray those dynamics -
whether the scheme is explicit OR implicit.
--
Matthew V. Jessick Motorsims
[email hidden] http://www.moto1.net
Vehicle Dynamics Engineer (972)910-8866 Ext.125, Fax: (972)910-8216
Quoted text (11 lines)
> On Tue, 16 Oct 2001 09:51:20 +0200, Gregor Veble > <[email hidden]> wrote: > In the report an example stiff spring is used, which varies in y using > force=-k*y. In other words, 'y' wants to go to 0 (like a suspension or > tire being braked). With the normal Euler approach, you quickly get an > explosion if your timestep isn't small enough. > With this reversed time thing, Baraff proves that ANY timestep would > have it end up at 0. :) So you could take 1s timesteps even, hehe. > force=-k*yOn Tue, 16 Oct 2001 15:46:47 +0200, Sebastien Tixier
<[email hidden]> wrote:
>Okey, well, i knew that integrator are big problem before starting coding ,so we
>have structured our physics so we could change the integrator as we want ;o)
Ah, an advantage yes. Never knew about that when I started. :)
And compilers even tend to move members around, so a fixed memory
location in my class (and size) may not be enough.
I assume you have a sort of Store() and Restore() method so that the
ODE can get parameters of the physics objects back?
Ouch, probably asking for too many features there if it sticks at
5fps. I'll have to try the new smoke, skidmark and sun effects
tomorrow. Cool stuff.
Hope you can see the idea that I was babbling about. :)
Ruud van Gaal
Free car sim : http://www.marketgraph.nl/gallery/racer/
Pencil art : http://www.marketgraph.nl/gallery/
Quoted text (7 lines)
>> On the other hand, my Geforce3 just came in and now I'm driving >> Carrera with over 200 fps. :)) (so I have some copying time to spare, >> hehe) > >Lucky bastard ;o) I'm stick with my GeForce 1 DDR , i just tried D3D8, i >have some initialisatio problems, can't get more than 5fps with 10K triangles >when i have more than 200fps on OpenGL ... :o(Quoted text (9 lines)
>> >Differential Equation Basics : p B6 : Andrew Kitkin and David Baraff, 1997. >> >> The next chapter, 'Implicitly solving Equations' (not too sure about >> the title) talks about methods when even RK4 gives you explosive >> differential eq's. It just seems like it can be used with little, if >> any, extra cost because a force is already the 2nd derivative of a >> position. > >Well, i'm currently reading it ;o)On Tue, 16 Oct 2001 14:27:41 GMT, mjessick-Motorsims
<[email hidden]> wrote:
>With an implicit integration scheme you get
>stability, but at the cost of losing the dynamics
>you are trying to model if the timestep is too large.
>Sometimes this is OK, sometimes you should take
>smaller time steps. It depends on your requirements.
Yes, I know; I now have a Geforce3 which runs my sim now at ~200fps,
so drawing all these frames makes me NEED to run at at least 200Hz. :)
Ah yes, ofcourse. I'll reread the section to see if it wasn't some
kind of damper then.
>For the explicit integration questions you were discussing,
>there are many methods described in numerical methods books
>that provide good accuracy and also good frequency response
>for real time use near the edge of stability. Inventing your
>own would seem to be a waste of time. (Similar to inventing
>one's own cryptographic algorithm ;)
I'll visit the bookstore next time and see if there are any good books
I could use. Only saw a predictor-corrector method once for example,
in one of the SAE papers.
Thanks,
Ruud van Gaal
Free car sim : http://www.marketgraph.nl/gallery/racer/
Pencil art : http://www.marketgraph.nl/gallery/
Quoted text (17 lines)
>Ruud van Gaal wrote: > >> On Tue, 16 Oct 2001 09:51:20 +0200, Gregor Veble >> <[email hidden]> wrote: >> In the report an example stiff spring is used, which varies in y using >> force=-k*y. In other words, 'y' wants to go to 0 (like a suspension or >> tire being braked). With the normal Euler approach, you quickly get an >> explosion if your timestep isn't small enough. >> With this reversed time thing, Baraff proves that ANY timestep would >> have it end up at 0. :) So you could take 1s timesteps even, hehe. > > > > force=-k*y > >As an example, >for this model, zero is of course just as wrong as growing >without bound. Without damping, an oscillation should continue.
Ruud van Gaal wrote:
> Ah, an advantage yes. Never knew about that when I started. :)
> And compilers even tend to move members around, so a fixed memory
> location in my class (and size) may not be enough.
A class what's this ? i Code in C... don't like C++, it's too much compiler
dependant, and it create too much hidden local variables, especially when
surchaging operator functions.
> I assume you have a sort of Store() and Restore() method so that the
> ODE can get parameters of the physics objects back?
nop, too slow ;o) ODE integrate directly parameters in the objects structure.
I have a sort of RegisterValueToIntegrate function, you give the offset in the
object source structure to integrate and the target offset in the structure.
exemple
typedef struct _dyn_object_
{
VECTOR4 position;
VECTOR4 velocity;
} dynOBJECT;
#define OffsetMember( type,member ) ((int)(&((*type)(0)->member)))
RegisterValueToIntegrate( OffsetMember( dybOBJECT, velocity.vx), OffsetMember(
dybOBJECT, velocity.vx) );
So i don't copy value, i just have a big table in the ODE, for each variable to
integrate i know where to find the value in memory.
No, it's worst than that... i've just made a simple program that just initialised
dx8, create a dx8 vertex buffer,
make a triangle srtip of 10K vertices, and it stucks at 5fps , when every GeForce
could do this at 200fps...
i have really something initialised wrong :o(
> Hope you can see the idea that I was babbling about. :)
Well, i had too much job at work , so i couldn't read it entierly. I'll read it
tomorow.
--
S�bastien TIXIER - Game Developer
Dynamics and Car Physics
Overall GPLRank -44.24
Monster GPLRank -114.44
Quoted text (7 lines)
> >Lucky bastard ;o) I'm stick with my GeForce 1 DDR , i just tried D3D8, i > >have some initialisatio problems, can't get more than 5fps with 10K triangles > >when i have more than 200fps on OpenGL ... :o( > > Ouch, probably asking for too many features there if it sticks at > 5fps. I'll have to try the new smoke, skidmark and sun effects > tomorrow. Cool stuff.and don't forget the heisenberg compensators!
Sudesh
"Ruud van Gaal" <[email hidden]> schreef in bericht
news:[email hidden]...
Quoted text (49 lines)
> On Tue, 16 Oct 2001 14:27:41 GMT, mjessick-Motorsims > <[email hidden]> wrote: > > >With an implicit integration scheme you get > >stability, but at the cost of losing the dynamics > >you are trying to model if the timestep is too large. > >Sometimes this is OK, sometimes you should take > >smaller time steps. It depends on your requirements. > > Yes, I know; I now have a Geforce3 which runs my sim now at ~200fps, > so drawing all these frames makes me NEED to run at at least 200Hz. :) > > >Ruud van Gaal wrote: > > > >> On Tue, 16 Oct 2001 09:51:20 +0200, Gregor Veble > >> <[email hidden]> wrote: > >> In the report an example stiff spring is used, which varies in y using > >> force=-k*y. In other words, 'y' wants to go to 0 (like a suspension or > >> tire being braked). With the normal Euler approach, you quickly get an > >> explosion if your timestep isn't small enough. > >> With this reversed time thing, Baraff proves that ANY timestep would > >> have it end up at 0. :) So you could take 1s timesteps even, hehe. > > > > > > > force=-k*y > > > >As an example, > >for this model, zero is of course just as wrong as growing > >without bound. Without damping, an oscillation should continue. > > Ah yes, ofcourse. I'll reread the section to see if it wasn't some > kind of damper then. > > >For the explicit integration questions you were discussing, > >there are many methods described in numerical methods books > >that provide good accuracy and also good frequency response > >for real time use near the edge of stability. Inventing your > >own would seem to be a waste of time. (Similar to inventing > >one's own cryptographic algorithm ;) > > I'll visit the bookstore next time and see if there are any good books > I could use. Only saw a predictor-corrector method once for example, > in one of the SAE papers. > > Thanks, > > Ruud van Gaal > Free car sim : http://www.marketgraph.nl/gallery/racer/ > Pencil art : http://www.marketgraph.nl/gallery/>
> No, it's worst than that... i've just made a simple program that just initialised
> dx8, create a dx8 vertex buffer,
> make a triangle srtip of 10K vertices, and it stucks at 5fps , when every GeForce
> could do this at 200fps...
> i have really something initialised wrong :o(
I've found the problem. I made every thing correctly, the problem was the exemple
i've made : random vertices between -1 and 1 . And a triangle strip of these vertices
is VERY GPU expensive ! Even a GeForce 3 can't handle it, because there is BIG
polygones , LOT of write in ZBuffers !
I've mad a more SMART exemple, random normal size triangles and i can draw 2Mo
triangles per seconds with no problems now ;o)
--
Sebastien TIXIER - Game Developer
Dynamics and Car Physics
http://www.eden-studios.fr
GPLRank Normal:-44.24 Monster:-124.44
On Wed, 17 Oct 2001 07:23:32 +0200, Sebastien Tixier
<[email hidden]> wrote:
Hehe, sounds MUCH better. :)
Sometimes I pity the poor track creators who have to make more & more
content, because well, getting 100fps means you have too little to do.
;-)
Ruud van Gaal
Free car sim : http://www.marketgraph.nl/gallery/racer/
Pencil art : http://www.marketgraph.nl/gallery/
Quoted text (15 lines)
>> >> No, it's worst than that... i've just made a simple program that just initialised >> dx8, create a dx8 vertex buffer, >> make a triangle srtip of 10K vertices, and it stucks at 5fps , when every GeForce >> could do this at 200fps... >> i have really something initialised wrong :o( > >I've found the problem. I made every thing correctly, the problem was the exemple >i've made : random vertices between -1 and 1 . And a triangle strip of these vertices > >is VERY GPU expensive ! Even a GeForce 3 can't handle it, because there is BIG >polygones , LOT of write in ZBuffers ! > >I've mad a more SMART exemple, random normal size triangles and i can draw 2Mo >triangles per seconds with no problems now ;o)On Tue, 16 Oct 2001 20:48:40 +0200, "sudesh"
<[email hidden]> wrote:
>and don't forget the heisenberg compensators!
It's in there and I drink it every day. That's how I get more
productive. ;-)
Ruud van Gaal
Free car sim : http://www.marketgraph.nl/gallery/racer/
Pencil art : http://www.marketgraph.nl/gallery/
>
> Hehe, sounds MUCH better. :)
>
> Sometimes I pity the poor track creators who have to make more & more
> content, because well, getting 100fps means you have too little to do.
> ;-)
At work now with have aprox. 4 graphist for 1 developer ! But the more complicated for the
graphist/modelers is to understand the "weird" developer graphics constraint ;o) Usually
they
used too much stufff... ;o) especially very big texture the little logo decal on the car
;o)))))
--
Sebastien TIXIER - Game Developer
Dynamics and Car Physics
http://www.eden-studios.fr
GPLRank Normal:-44.24 Monster:-124.44
On Wed, 17 Oct 2001 14:22:02 +0200, Sebastien Tixier
<[email hidden]> wrote:
Lol, I know the thing. You ask for a little button that can light up
and you get a 723x853 file with the buttons just randomly placed
somewhere in the picture and 95% empty pixels. :)
Ruud van Gaal
Free car sim : http://www.marketgraph.nl/gallery/racer/
Pencil art : http://www.marketgraph.nl/gallery/
Quoted text (10 lines)
>> Sometimes I pity the poor track creators who have to make more & more >> content, because well, getting 100fps means you have too little to do. >> ;-) > >At work now with have aprox. 4 graphist for 1 developer ! But the more complicated for the > >graphist/modelers is to understand the "weird" developer graphics constraint ;o) Usually >they >used too much stufff... ;o) especially very big texture the little logo decal on the car >;o)))))On Tue, 16 Oct 2001 19:42:47 +0200, S�bastien Tixier
<[email hidden]> wrote:
>A class what's this ? i Code in C... don't like C++, it's too much compiler
>dependant, and it create too much hidden local variables, especially when
>surchaging operator functions.
Yes, for performance you mustn't take over too much code from others.
:) I know the matrix and vector classes where every a+b turns into 1
big slow stack fest.
Still, C++ has much merits and I would recommend it even, just don't
use templates too much, never use multiple inheritance (if you ever
made a compiler, you'd know what pain this gives you when mixing 2
class hierarchies), and avoid most operator overloading. And oh yeah,
never malloc() in time-critical code.
And then, it's nice to be able to code like you do in C, only in
reverse:
CarMove(car,x,y) -> car->Move(x,y)
Ah ok, I see, I'll have to see if my ODEs (have never done a survey of
which there are in Racer) map out so nicely to needing just pos & vel
(if I don't need to restore other variables as well).
>So i don't copy value, i just have a big table in the ODE, for each variable to
>integrate i know where to find the value in memory.
I see. Something to see if it will fit here.
...
>No, it's worst than that... i've just made a simple program that just initialised
>dx8, create a dx8 vertex buffer,
>make a triangle srtip of 10K vertices, and it stucks at 5fps , when every GeForce
>could do this at 200fps...
Glad to see you got it working.
>> Hope you can see the idea that I was babbling about. :)
>
>Well, i had too much job at work , so i couldn't read it entierly. I'll read it
>tomorow.
Let me know if you see anything in it. :)
Ruud van Gaal
Free car sim : http://www.marketgraph.nl/gallery/racer/
Pencil art : http://www.marketgraph.nl/gallery/


































