1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
#include "scene.h"
#include "test_util.h"
#include <assert.h>
#include <math.h>
#include <stdlib.h>
void scene_get_first(void *scene) {
scene_get_body(scene, 0);
}
void scene_remove_first(void *scene) {
scene_remove_body(scene, 0);
}
void test_empty_scene() {
scene_t *scene = scene_init();
assert(scene_bodies(scene) == 0);
for (int i = 0; i < 10; i++) scene_tick(scene, 1);
assert(test_assert_fail(scene_get_first, scene));
assert(test_assert_fail(scene_remove_first, scene));
scene_free(scene);
}
list_t *make_shape() {
list_t *shape = list_init(4, free);
vector_t *v = malloc(sizeof(*v));
*v = (vector_t) {-1, -1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {+1, -1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {+1, +1};
list_add(shape, v);
v = malloc(sizeof(*v));
*v = (vector_t) {-1, +1};
list_add(shape, v);
return shape;
}
void test_scene() {
// Build a scene with 3 bodies
scene_t *scene = scene_init();
assert(scene_bodies(scene) == 0);
body_t *body1 = body_init(make_shape(), 1, (rgb_color_t) {1, 1, 1});
scene_add_body(scene, body1);
assert(scene_bodies(scene) == 1);
assert(scene_get_body(scene, 0) == body1);
body_t *body2 = body_init(make_shape(), 2, (rgb_color_t) {1, 1, 1});
scene_add_body(scene, body2);
assert(scene_bodies(scene) == 2);
assert(scene_get_body(scene, 0) == body1);
assert(scene_get_body(scene, 1) == body2);
body_t *body3 = body_init(make_shape(), 3, (rgb_color_t) {1, 1, 1});
scene_add_body(scene, body3);
assert(scene_bodies(scene) == 3);
assert(scene_get_body(scene, 0) == body1);
assert(scene_get_body(scene, 1) == body2);
assert(scene_get_body(scene, 2) == body3);
// Set the bodies' positions with no velocity and ensure they match
body_set_centroid(body1, (vector_t) {1, 1});
body_set_centroid(body2, (vector_t) {2, 2});
body_set_centroid(body3, (vector_t) {3, 3});
scene_tick(scene, 1);
assert(vec_isclose(body_get_centroid(body1), (vector_t) {1, 1}));
assert(vec_isclose(body_get_centroid(body2), (vector_t) {2, 2}));
assert(vec_isclose(body_get_centroid(body3), (vector_t) {3, 3}));
body_set_velocity(body1, (vector_t) {+1, 0});
body_set_velocity(body2, (vector_t) {-1, 0});
body_set_velocity(body3, (vector_t) {0, +1});
scene_tick(scene, 1);
assert(vec_isclose(body_get_centroid(body1), (vector_t) {2, 1}));
assert(vec_isclose(body_get_centroid(body2), (vector_t) {1, 2}));
assert(vec_isclose(body_get_centroid(body3), (vector_t) {3, 4}));
// Try removing the second body
scene_remove_body(scene, 1);
assert(scene_bodies(scene) == 3); // removal is deferred until the next tick
scene_tick(scene, 0);
assert(scene_bodies(scene) == 2);
assert(scene_get_body(scene, 0) == body1);
assert(scene_get_body(scene, 1) == body3);
// Tick the remaining bodies
scene_tick(scene, 1);
assert(vec_isclose(body_get_centroid(body1), (vector_t) {3, 1}));
assert(vec_isclose(body_get_centroid(body3), (vector_t) {3, 5}));
scene_free(scene);
}
// A force creator that moves a body in uniform circular motion about the origin
void centripetal_force(void *aux) {
body_t *body = aux;
vector_t v = body_get_velocity(body);
vector_t r = body_get_centroid(body);
assert(isclose(vec_dot(v, r), 0));
vector_t force =
vec_multiply(-body_get_mass(body) * vec_dot(v, v) / vec_dot(r, r), r);
body_add_force(body, force);
}
void test_force_creator() {
const double OMEGA = 3;
const double R = 2;
const double DT = 1e-6;
const int STEPS = 1000000;
scene_t *scene = scene_init();
body_t *body = body_init(make_shape(), 123, (rgb_color_t) {0, 0, 0});
vector_t radius = {R, 0};
body_set_centroid(body, radius);
body_set_velocity(body, (vector_t) {0, OMEGA * R});
scene_add_body(scene, body);
scene_add_force_creator(scene, centripetal_force, body, NULL);
for (int i = 0; i < STEPS; i++) {
vector_t expected_x = vec_rotate(radius, OMEGA * i * DT);
assert(vec_within(1e-4, body_get_centroid(body), expected_x));
scene_tick(scene, DT);
}
scene_free(scene);
}
typedef struct {
scene_t *scene;
double coefficient;
} force_aux_t;
// A force creator that applies constant downwards gravity to all bodies in a scene
void constant_gravity(void *aux) {
force_aux_t *gravity_aux = aux;
size_t body_count = scene_bodies(gravity_aux->scene);
for (size_t i = 0; i < body_count; i++) {
body_t *body = scene_get_body(gravity_aux->scene, i);
vector_t force = {0, -gravity_aux->coefficient * body_get_mass(body)};
body_add_force(body, force);
}
}
// A force creator that applies drag proportional to v ** 2 to all bodies in a scene
void air_drag(void *aux) {
force_aux_t *drag_aux = aux;
size_t body_count = scene_bodies(drag_aux->scene);
for (size_t i = 0; i < body_count; i++) {
body_t *body = scene_get_body(drag_aux->scene, i);
vector_t v = body_get_velocity(body);
vector_t force =
vec_multiply(-drag_aux->coefficient * sqrt(vec_dot(v, v)), v);
body_add_force(body, force);
}
}
void test_force_creator_aux() {
const double LIGHT_MASS = 10, HEAVY_MASS = 20;
const double GRAVITY = 9.8, DRAG = 3;
const double DT = 1e-3;
const int STEPS = 100000;
scene_t *scene = scene_init();
body_t *light = body_init(make_shape(), LIGHT_MASS, (rgb_color_t) {0, 0, 0});
scene_add_body(scene, light);
body_t *heavy = body_init(make_shape(), HEAVY_MASS, (rgb_color_t) {0, 0, 0});
scene_add_body(scene, heavy);
force_aux_t *gravity_aux = malloc(sizeof(*gravity_aux));
gravity_aux->scene = scene;
gravity_aux->coefficient = GRAVITY;
scene_add_force_creator(scene, constant_gravity, gravity_aux, free);
force_aux_t *drag_aux = malloc(sizeof(*drag_aux));
drag_aux->scene = scene;
drag_aux->coefficient = DRAG;
scene_add_force_creator(scene, air_drag, drag_aux, free);
for (int i = 0; i < STEPS; i++) scene_tick(scene, DT);
assert(vec_isclose(
body_get_velocity(light),
(vector_t) {0, -sqrt(GRAVITY * LIGHT_MASS / DRAG)
}));
assert(vec_isclose(
body_get_velocity(heavy),
(vector_t) {0, -sqrt(GRAVITY * HEAVY_MASS / DRAG)
}));
scene_free(scene);
}
/*
This test checks that a force creator is no longer called after
one of its bodies has been removed.
There are initially 3 bodies in the scene.
remove_body() removes the last remaining body each tick.
count_calls() depends on the first and second bodies,
so it should only be called during the first two ticks.
*/
void remove_body(void *aux) {
scene_t *scene = aux;
size_t body_count = scene_bodies(scene);
if (body_count > 0) {
body_remove(scene_get_body(scene, body_count - 1));
}
}
typedef struct {
int count;
scene_t *scene;
} count_aux_t;
void count_calls(void *aux) {
count_aux_t *count_aux = aux;
// Every time count_calls() is called, the body count should decrease by 1
assert(scene_bodies(count_aux->scene) == 3 - count_aux->count);
// Record that count_calls() was called an additional time
count_aux->count++;
}
void test_reaping() {
scene_t *scene = scene_init();
for (int i = 0; i < 3; i++) {
scene_add_body(scene, body_init(make_shape(), 1, (rgb_color_t) {0, 0, 0}));
}
scene_add_bodies_force_creator(scene, remove_body, scene, list_init(0, NULL), NULL);
count_aux_t *count_aux = malloc(sizeof(*count_aux));
count_aux->count = 0;
count_aux->scene = scene;
list_t *required_bodies = list_init(2, NULL);
list_add(required_bodies, scene_get_body(scene, 0));
list_add(required_bodies, scene_get_body(scene, 1));
scene_add_bodies_force_creator(scene, count_calls, count_aux, required_bodies, NULL);
while (scene_bodies(scene) > 0) {
scene_tick(scene, 1);
}
assert(count_aux->count == 2);
free(count_aux);
scene_free(scene);
}
int main(int argc, char *argv[]) {
// Run all tests if there are no command-line arguments
bool all_tests = argc == 1;
// Read test name from file
char testname[100];
if (!all_tests) {
read_testname(argv[1], testname, sizeof(testname));
}
DO_TEST(test_empty_scene)
DO_TEST(test_scene)
DO_TEST(test_force_creator)
DO_TEST(test_force_creator_aux)
DO_TEST(test_reaping)
puts("scene_test PASS");
}