Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » making physics more realistic

Credits go to Billybob, Maverick, orz, and Plucky for helping out!
This thread is locked; no one can reply to it. rss feed Print
making physics more realistic
Steve Terry
Member #1,989
March 2002
avatar

If you remember from the old thread I started with vectors I'm still working on it in my spare time and I've gotten the balls to bounce off of each other realistically but when they come to rest I would like them to settle out realistically. At the moment some balls that are slightly off center and on top of each other fall off and settle ok while others tend to "stick" like with the old problem I had so what you get is 4 or 5 balls all on top of each other but leaning in a way they should topple over, but I don't know how to make them more fluidic so that occurs. I saw a demo by Marcello IIRC that was a physics water type demo and that's pretty much how these particles should settle out, going to the state of least resistance or whatever.

Here is my current code:

1#include <allegro.h>
2#include <math.h>
3 
4#define error(a, b...) do{ \
5 char __tmp__[255]; \
6 snprintf(__tmp__, 255, a, ##b); \
7 fprintf(stderr, "%s:%s:%i ERROR: %s\n", __FILE__, __FUNCTION__, __LINE__, __tmp__); \
8 fflush(stderr); \
9 exit(-1); \
10}while(0)
11 
12typedef struct vector_s {
13 double x, y;
14} vector_t;
15 
16double vector_magnitude(vector_t vect){
17 return sqrt(vect.x*vect.x + vect.y*vect.y);
18}
19 
20vector_t vector_normalize(vector_t v){
21 double dist = vector_magnitude(v);
22 vector_t vect = { v.x / dist, v.y / dist };
23 return vect;
24}
25 
26double vector_dot(vector_t a, vector_t b){
27 return a.x*b.x + a.y*b.y;
28}
29 
30vector_t vector(double x, double y){
31 vector_t vect = { x, y };
32 return vect;
33}
34 
35vector_t vector_mul(vector_t a, vector_t b){
36 vector_t vect = { a.x * b.x, a.y * b.y };
37 return vect;
38}
39 
40vector_t vector_div(vector_t a, vector_t b){
41 vector_t vect = { a.x / b.x, a.y / b.y };
42 return vect;
43}
44 
45vector_t vector_add(vector_t a, vector_t b){
46 vector_t vect = { a.x + b.x, a.y + b.y };
47 return vect;
48}
49 
50vector_t vector_sub(vector_t a, vector_t b){
51 vector_t vect = { a.x - b.x, a.y - b.y };
52 return vect;
53}
54 
55vector_t vector_mulf(vector_t a, double b){
56 vector_t vect = { a.x * b, a.y * b };
57 return vect;
58}
59 
60vector_t vector_divf(vector_t a, double b){
61 vector_t vect = { a.x / b, a.y / b };
62 return vect;
63}
64 
65vector_t vector_addf(vector_t a, double b){
66 vector_t vect = { a.x + b, a.y + b };
67 return vect;
68}
69 
70vector_t vector_subf(vector_t a, double b){
71 vector_t vect = { a.x - b, a.y - b };
72 return vect;
73}
74 
75vector_t gravity = {0.0, .01};
76vector_t bounce = {0.9, 0.9};
77 
78#define MAX_PARTICLES 256
79 
80typedef struct particle{
81 vector_t vel;
82 vector_t pos;
83}PARTICLE;
84 
85PARTICLE particles[MAX_PARTICLES];
86 
87int circle_collision(int x1, int y1, int r1, int x2, int y2, int r2){
88 if((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) <= (r1 + r2)*(r1 + r2))
89 return 1;
90 return 0;
91}
92 
93int main(void){
94 BITMAP *buff = NULL;
95 int i, j;
96 int num_particles = 0;
97 int release_particle = 0;
98 int collision = 0;
99 vector_t oldpos;
100 vector_t start;
101 if(allegro_init() != 0)
102 error("allegro_init failed: %s", allegro_error);
103 if(install_keyboard() != 0)
104 error("install_keyboard failed: %s", allegro_error);
105 install_mouse();
106 set_color_depth(16);
107 if(set_gfx_mode(GFX_AUTODETECT_FULLSCREEN, 640, 480, 0, 0) != 0)
108 error("set_gfx_mode failed: %s", allegro_error);
109 buff = create_bitmap(SCREEN_W, SCREEN_H);
110 while(!key[KEY_ESC]){
111 clear_bitmap(buff);
112 show_mouse(buff);
113 if(!(mouse_b & 1) && release_particle){
114 particles[num_particles].vel = vector(-(mouse_x - start.x)/20, -(mouse_y - start.y)/20);
115 particles[num_particles].pos = vector(mouse_x, mouse_y);
116 num_particles = (num_particles + 1) % MAX_PARTICLES;
117 release_particle = 0;
118 }
119 else if((mouse_b & 1) && !release_particle){
120 release_particle = 1;
121 start = vector(mouse_x, mouse_y);
122 }
123 for(i = 0; i < num_particles; i++){
124 collision = 0;
125 oldpos = particles<i>.pos;
126 particles<i>.vel = vector_add(particles<i>.vel, gravity);
127 particles<i>.pos = vector_add(particles<i>.pos, particles<i>.vel);
128 if(particles<i>.pos.y + 6 > SCREEN_H){
129 particles<i>.vel = vector(particles<i>.vel.x, -particles<i>.vel.y);
130 particles<i>.vel = vector_mul(particles<i>.vel, bounce);
131 particles<i>.pos.y = SCREEN_H - 6;
132 }
133 else if(particles<i>.pos.x + 6 > SCREEN_W){
134 particles<i>.vel = vector(-particles<i>.vel.x, particles<i>.vel.y);
135 particles<i>.pos.x = SCREEN_W - 6;
136 }
137 else if(particles<i>.pos.y < 6){
138 particles<i>.vel = vector(particles<i>.vel.x, -particles<i>.vel.y);
139 particles<i>.pos.y = 6;
140 }
141 else if(particles<i>.pos.x < 6){
142 particles<i>.vel = vector(-particles<i>.vel.x, particles<i>.vel.y);
143 particles<i>.pos.x = 6;
144 }
145 for(j = 0; j < num_particles; j++){
146 if (i != j){
147 if(circle_collision(particles<i>.pos.x, particles<i>.pos.y, 6, particles[j].pos.x,
148 particles[j].pos.y, 6)){
149 vector_t v1 = particles<i>.vel;
150 vector_t v2 = particles[j].vel;
151 vector_t n = vector_normalize(vector(particles<i>.pos.x - particles[j].pos.x, particles<i>.pos.y - particles[j].pos.y));
152 particles<i>.vel = vector_add(v1, vector_mulf(n, vector_dot(vector_sub(v2, v1), n)));
153 particles[j].vel = vector_add(v2, vector_mulf(n, vector_dot(vector_sub(v1, v2), n)));
154 collision = 1;
155 }
156 }
157 if(collision){
158 particles<i>.pos = oldpos;
159 particles<i>.vel = vector_mul(particles<i>.vel, bounce);
160 collision = 0;
161 }
162 }
163 scare_mouse();
164 circlefill(buff, particles<i>.pos.x, particles<i>.pos.y, 6, makecol(255,255,255));
165 unscare_mouse();
166 }
167 if(release_particle)
168 line(buff, start.x, start.y, mouse_x, mouse_y, makecol(255, 0, 0));
169 blit(buff, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
170 }
171 destroy_bitmap(buff);
172 return EXIT_SUCCESS;
173}END_OF_MAIN();

Thanks for all the help I recieved on the old thread.

___________________________________
[ Facebook ]
Microsoft is not the Borg collective. The Borg collective has got proper networking. - planetspace.de
Bill Gates is in fact Shawn Hargreaves' ßî+çh. - Gideon Weems

orz
Member #565
August 2000

Well...
1. I think you're processing each collision twice. You have a for (i = 0; i < num_particles; i++) { ... for (j = 0; j < num_particles; j++) {...}...}
A classic optimization is to change the j loop to start at i like this: for (j = i j < num_particles; j++) {...}
That can also fix bugs, depending upon how you're processing your collisions (since processing a collision twice may in fact be incorrect, since it would then have an impossible velocity for its position, etc). It may help in your case.

2. Have you considered using C++ for your vector syntax? For instance, many people prefer
(particles<i>.pos - particles[j].pos)
over
vector(
particles<i>.pos.x - particles[j].pos.x,
particles<i>.pos.y - particles[j].pos.y
)
edit: some of your code also does it in the form of vector_sub(particles<i>.pos, particles[j].pos), which I suppose might be better, but I still suggest the C++ form as the most intuitive and concise.

3. You multiply the entire velocity vector of the first colliding particle by bounce... I would recommend that you instead multiply only the added component by bounce, and that you do so for both particles in each collision.

Steve Terry
Member #1,989
March 2002
avatar

The first suggestion isn't going to work because you need to check the particle you are checking for collsions against, against all particles in the list, not just ones on or after it. Making it j = i causes particles earlier in the list fall through particles later in the list so it's an incorrect assumption. I think I'm leaving it as C syntax at the moment because it's just source and it has nothing to do with my problem, thanks for the suggestion though. The third suggestion is probably closest to something that may be what I need to do but I don't think it worked unless I did something wrong :-/

___________________________________
[ Facebook ]
Microsoft is not the Borg collective. The Borg collective has got proper networking. - planetspace.de
Bill Gates is in fact Shawn Hargreaves' ßî+çh. - Gideon Weems

Maverick
Member #2,337
May 2002

Steve Terry said:

The first suggestion isn't going to work because you need to check the particle you are checking for collsions against, against all particles in the list, not just ones on or after it. Making it j = i causes particles earlier in the list fall through particles later in the list so it's an incorrect assumption.

Not quite true. Although it may seem counterintuitive, this is a fairly traditional math combination problem. After you pair the first element in the list with every other element, you can't count the first element again.

For example, you have 3 elements: 1 2 3. On the first loop, you get the pairs {1,2} and {1,3}. On the second loop, you have to start at 2 to get the {2,3} pair, or you'll just add the pair {1,2} again.

Notice that you only do 2 loops for 3 elements (since there's nothing left in the last outer loop to pair with), and you have to start at i+1 in the inner loop (since you don't want to compare elements to themselves). So, what you really want is:

for (i = 0; i < num_particles - 1; i++) {
  for (j = i + 1; j < num_particles; j++) {
    // Collision response code goes here.
  }
}

Instead of num_particles^2 comparisons, you'd only be doing (num_particles - 1)/2 * num_particles comparisons.

-Maverick

-----
"the polls here don't change as much because I believe so much in free speakin' that I want everyone a chance to vote at least once, and possibly a few dozen times, that way they are really heard." -Matthew Leverton

Steve Terry
Member #1,989
March 2002
avatar

Either way it still makes the particles now act like ice where they melt into one big conglomeration and not the way they are supposed to. Mabe it's just the shape of the particles or something that's causing them to act as such.. I could try changing the size and see what happens... I dunno.

___________________________________
[ Facebook ]
Microsoft is not the Borg collective. The Borg collective has got proper networking. - planetspace.de
Bill Gates is in fact Shawn Hargreaves' ßî+çh. - Gideon Weems

orz
Member #565
August 2000

Ignore impossible collison, add these lines:

vector_t dv = vector_sub(v1, v2);
if (vector_dot(n, dv) > 0) continue;

Those two lines combined cause any collision with a negative impact velocity to be ignored.

Billybob
Member #3,136
January 2003

I didn't check your code or anything, but I think the problem is that when you collide one particle with another you are doing point math...not sphere math. When a ball falls on another ball(ignoring the bounce of the 2nd ball) the ball will "roll" off because it's a ball.

Here, think this may have the math required to make the particles roll off each other: [url http://www.gamasutra.com/features/20000516/lander_pfv.htm]

EDIT: and if that doesn't help make particles roll off each other in your demo...at least you can try pool table physics! ;D

EDIT: well if that didn't help...here's a theory that is probably wrong but may help a bit: When a particle/ball collides with another ball(not a flat wall) then have the reflection vector be perpendicular to the angle between the centers of the two balls/particles. That way the ball will be forced(roll) away from the other ball. Although in reality this is wrong...really what happens is the ball rolls off the other ball...causing it to continue to roll away. This algo wont make the ball continue to roll away. Maybe use the rolling algo stuff from the document I posted to hack in some roll...*shrugs*

orz
Member #565
August 2000

His math is correct for circles, asside from the lack of spin and friction and the slightly buggy way inelasticity was implemented. It works okay, I've tested it with a few changes.

edit: added mention of elasticity, removed the word static.

Plucky
Member #1,346
May 2001
avatar

If you haven't already done so, after determining that a collision occured, you should reset the ball locations so that they're no longer overlapping and just touching. This could solve problems where even after moving the balls after a collision, the balls still overlap, causing an unwanted collision with opposite vectors as a result.

Second, you need to implement a static physics model. In other words, forces on objects in addition to just momentum. If after a collision, if a static (2nd) ball cannot move in the "resulting" direction, then (a) the first moving ball new velocity vector must be recalculated such that it the second ball is like an immovable surface, [edit] well not quite, but this would be a good start [/edit] (b) you must transmit forces [edit] (look up momentum, forces, impulse) [/edit] from the 2nd ball to any other static ball it touches, and (c) apply gravitational force [edit] (not just "acceleration", since the ball could be held up by other balls) [/edit] to every static ball as well so they would eventually push balls out of the way if they're free to do so.

Steve Terry
Member #1,989
March 2002
avatar

Sorry for taking so long to reply I just hadn't gotten to it yet... but orz seemed to solve it for the most part so kudos to him. The only thing I don't like is how slow it goes on this PIII machine, after about 50 or so balls it dramatically slows down to where when it gets to around 200 it looks like everything is in slow motion :-/.

___________________________________
[ Facebook ]
Microsoft is not the Borg collective. The Borg collective has got proper networking. - planetspace.de
Bill Gates is in fact Shawn Hargreaves' ßî+çh. - Gideon Weems

orz
Member #565
August 2000

I discovered one more bug... it's possible (by clicking many times at exactly the same X coordinate) to cause two balls to have exactly the same positions. If that happens, then the vector_normal call in the collision handling will produce weird results, causing velocities to become Not a Number, and then then balls go to the lower left hand corner and start eating any other balls that touch them (you'll see what I mean). This can be fixed by adding a check in the collision code, if ((particles<i>.pos.x == particles[j].pos.x) && (particles<i>.pos.y == particles[j].pos.y)) continue;

Anyway, speed: you don't have any code timing the thing to the clock, so what happens is that with few balls the speed is limited by the rendering, and with many balls the speed is limitted by the physics. Here's some performance numbers off of my computer:

circumstance     rendering physics total
with 0 balls:    8 ms      0 ms    8 ms
with 235 balls:  9 ms      9 ms    18 ms

If you want it to go faster with 235 balls, you can do a few things:
1. Optimize your physics to go faster. Generally this would involve sorting or categorizing your balls somehow based upon their positions. There's lots of algorithms available, some are pretty easy, some are rediculously complex. Some libraries (including my own PMASK 4 & 5) include helpers for this.
2. Do your physics more times per rendering - if you did physics twice per frame, then with 235 balls you'd get about 37 fps on my computer (compared to the current ~55), but the balls moving 33% faster than they currently do, with the same precision of physics.
3. Increase the average velocity of your balls : ) (though the quality of collision calculations would suffer).

Steve Terry
Member #1,989
March 2002
avatar

hmm right I could qsort the list based on the balls x position and that would give a good increase in speed I think. Heh balls canablizing other balls... fun ;D

___________________________________
[ Facebook ]
Microsoft is not the Borg collective. The Borg collective has got proper networking. - planetspace.de
Bill Gates is in fact Shawn Hargreaves' ßî+çh. - Gideon Weems

Go to: