1 /* -*- mode: c; c-basic-offset: 2 -*-
4 * gcc -Wall -g $(pkg-config --cflags --libs gtk+-2.0 cairo) \
9 * http://en.wikipedia.org/wiki/Verlet_integration
10 * http://www.teknikus.dk/tj/gdc2001.htm
14 * - Add code to add boxes
20 #include <cairo-xlib.h>
25 const double ground_friction = 0.1, ground_level = 400;
26 const double box_left = 200, box_top = 200, box_bottom = 210;
27 const double elasticity = 0.7;
28 const double edge_fuzz = 1;
30 typedef struct _xy_pair Point;
31 typedef struct _xy_pair Vector;
36 typedef struct _Object Object;
37 typedef struct _Stick Stick;
38 typedef struct _Model Model;
44 Point previous_position;
64 Object *anchor_object;
65 Vector anchor_position;
71 model_init_snake (Model *model)
73 const int num_objects = 20;
74 const int num_sticks = num_objects * 2 - 3;
77 model->objects = g_new (Object, num_objects);
78 model->num_objects = num_objects;
79 model->sticks = g_new (Stick, num_sticks);
80 model->num_sticks = num_sticks;
82 for (i = 0; i < num_objects; i++) {
83 model->objects[i].position.x = random() % 200 + 20;
84 model->objects[i].position.y = random() % 200 + 20;
85 model->objects[i].previous_position.x = random() % 200 + 20;
86 model->objects[i].previous_position.y = random() % 200 + 20;
88 if (i + 1 < num_objects) {
89 model->sticks[i * 2].a = &model->objects[i];
90 model->sticks[i * 2].b = &model->objects[i + 1];
91 model->sticks[i * 2].length = random() % 20 + 20;
93 if (i + 2 < num_objects) {
94 model->sticks[i * 2 + 1].a = &model->objects[i];
95 model->sticks[i * 2 + 1].b = &model->objects[i + 2];
96 model->sticks[i * 2 + 1].length = random() % 20 + 20;
100 model->anchor_object = NULL;
104 model_init_rope (Model *model)
106 const int num_objects = 20;
107 const int num_sticks = num_objects - 1;
108 const int stick_length = 20;
111 model->objects = g_new (Object, num_objects);
112 model->num_objects = num_objects;
113 model->sticks = g_new (Stick, num_sticks);
114 model->num_sticks = num_sticks;
116 for (i = 0; i < num_objects; i++) {
117 model->objects[i].position.x = 200;
118 model->objects[i].position.y = 40 + i * stick_length;
119 model->objects[i].previous_position.x = 200;
120 model->objects[i].previous_position.y = 40 + i * stick_length;
122 if (i + 1 < num_objects) {
123 model->sticks[i].a = &model->objects[i];
124 model->sticks[i].b = &model->objects[i + 1];
125 model->sticks[i].length = stick_length;
129 model->anchor_object = NULL;
133 model_accumulate_forces (Model *model)
137 for (i = 0; i < model->num_objects; i++) {
138 model->objects[i].force.x = 0;
139 model->objects[i].force.y = 3;
144 model_integrate (Model *model, double step)
150 for (i = 0; i < model->num_objects; i++) {
151 o = &model->objects[i];
156 x + (x - o->previous_position.x) + o->force.x * step * step;
158 y + (y - o->previous_position.y) + o->force.y * step * step;
160 o->previous_position.x = x;
161 o->previous_position.y = y;
166 model_constrain (Model *model, double step)
168 double dx, dy, x, y, distance, fraction, squared;
171 /* Anchor object constraint. */
172 if (model->anchor_object != NULL) {
173 model->anchor_object->position.x = model->anchor_position.x;
174 model->anchor_object->position.y = model->anchor_position.y;
175 model->anchor_object->previous_position.x = model->anchor_position.x;
176 model->anchor_object->previous_position.y = model->anchor_position.y;
179 /* FIXME: this should be "is point inside box" test instead. Figure
180 * out from previous_position which edge the point has passed
181 * through and reflect in that. */
182 for (i = 0; i < model->num_objects; i++) {
183 x = model->objects[i].position.x;
184 y = model->objects[i].position.y;
185 if (box_top - edge_fuzz <= y &&
186 model->objects[i].previous_position.y <= box_top + edge_fuzz &&
188 model->objects[i].position.y = box_top - (y - box_top) * elasticity;
189 model->objects[i].previous_position.y =
190 box_top - (model->objects[i].previous_position.y - box_top) * elasticity;
194 /* Ground collision detection constraints. This puts a ground level
195 * in to make sure the points don't fall off the screen. */
196 for (i = 0; i < model->num_objects; i++) {
197 x = model->objects[i].position.x;
198 y = model->objects[i].position.y;
200 if (model->objects[i].position.y > ground_level) {
201 model->objects[i].position.y =
202 ground_level - (model->objects[i].position.y - ground_level) * elasticity;
203 model->objects[i].previous_position.y =
204 ground_level - (model->objects[i].previous_position.y - ground_level) * elasticity;
206 /* Friction on impact */
207 model->objects[i].position.x =
208 model->objects[i].position.x * (1 - ground_friction) +
209 model->objects[i].previous_position.x * ground_friction;
214 /* Stick constraints. */
215 for (i = 0; i < model->num_sticks; i++) {
216 x = model->sticks[i].a->position.x;
217 y = model->sticks[i].a->position.y;
218 dx = model->sticks[i].b->position.x - x;
219 dy = model->sticks[i].b->position.y - y;
220 distance = sqrt (dx * dx + dy * dy);
221 fraction = (distance - model->sticks[i].length) / distance / 2;
222 model->sticks[i].a->position.x = x + dx * fraction;
223 model->sticks[i].a->position.y = y + dy * fraction;
224 model->sticks[i].b->position.x = x + dx * (1 - fraction);
225 model->sticks[i].b->position.y = y + dy * (1 - fraction);
228 /* Stick constraints, without square roots. */
229 squared = stick_length * stick_length;
230 for (i = 0; i < model->num_objects - 1; i++) {
232 x = model->objects[i].position.x;
233 y = model->objects[i].position.y;
234 dx = model->objects[j].position.x - x;
235 dy = model->objects[j].position.y - y;
236 fraction = squared / (dx * dx + dy * dy + squared) - 0.5;
237 model->objects[i].position.x = x + dx * fraction;
238 model->objects[i].position.y = y + dy * fraction;
239 model->objects[j].position.x = x + dx * (1 - fraction);
240 model->objects[j].position.y = y + dy * (1 - fraction);
246 model_step (Model *model, double delta_t)
250 model_accumulate_forces (model);
251 model_integrate (model, delta_t);
253 for (i = 0; i < 50; i++)
254 model_constrain (model, delta_t);
256 model->theta += delta_t;
260 object_distance (Object *object, double x, double y)
264 dx = object->position.x - x;
265 dy = object->position.y - y;
267 return sqrt (dx*dx + dy*dy);
271 model_find_nearest (Model *model, double x, double y)
274 double distance, min_distance;
277 for (i = 0; i < model->num_objects; i++) {
278 distance = object_distance (&model->objects[i], x, y);
279 if (i == 0 || distance < min_distance) {
280 min_distance = distance;
281 object = &model->objects[i];
288 typedef struct _Color Color;
290 double red, green, blue;
294 draw_star (cairo_t *cr,
300 const int spike_count = 5;
301 const int inner_radius = 2;
302 const int outer_radius = 4;
306 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
308 for (i = 0; i < spike_count; i++) {
309 x = cx + cos ((i * 2) * M_PI / spike_count + theta) * inner_radius;
310 y = cy + sin ((i * 2) * M_PI / spike_count + theta) * inner_radius;
313 cairo_move_to (cr, x, y);
315 cairo_line_to (cr, x, y);
317 x = cx + cos ((i * 2 + 1) * M_PI / spike_count + theta) * outer_radius;
318 y = cy + sin ((i * 2 + 1) * M_PI / spike_count + theta) * outer_radius;
320 cairo_line_to (cr, x, y);
326 draw_lines (cairo_t *cr,
332 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
334 cairo_set_line_width (cr, 4);
335 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
336 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
338 for (i = 0; i < model->num_sticks; i++) {
340 model->sticks[i].a->position.x,
341 model->sticks[i].a->position.y);
343 model->sticks[i].b->position.x,
344 model->sticks[i].b->position.y);
351 draw_constraints (cairo_t *cr,
355 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
357 cairo_move_to (cr, 0, ground_level);
358 cairo_line_to (cr, 1500, ground_level);
359 cairo_line_to (cr, 1500, ground_level + 10);
360 cairo_line_to (cr, 0, ground_level + 10);
361 cairo_close_path (cr);
363 cairo_move_to (cr, 0, box_top);
364 cairo_line_to (cr, box_left, box_top);
365 cairo_line_to (cr, box_left, box_bottom);
366 cairo_line_to (cr, 0, box_bottom);
367 cairo_close_path (cr);
372 static Color blue = { 0, 0, 1 };
373 static Color red = { 1, 0, 0 };
376 sproing_expose_event (GtkWidget *widget,
377 GdkEventExpose *event,
383 cr = gdk_cairo_create (widget->window);
385 cairo_set_source_rgb (cr, 1, 1, 1);
388 draw_constraints (cr, model, &red);
389 draw_lines (cr, model, &blue);
392 for (i = 0; i < model->num_objects; i++) {
393 draw_star (widget, model->objects[i].position.x,
394 model->objects[i].position.y, model->objects[i].theta, &blue);
404 sproing_button_press_event (GtkWidget *widget,
405 GdkEventButton *event,
410 if (event->button != 1)
413 model->anchor_position.x = event->x;
414 model->anchor_position.y = event->y;
415 model->anchor_object = model_find_nearest (model, event->x, event->y);
421 sproing_button_release_event (GtkWidget *widget,
422 GdkEventButton *event,
427 if ((event->state & GDK_BUTTON1_MASK) == 0)
430 model->anchor_object = NULL;
436 sproing_motion_notify_event (GtkWidget *widget,
437 GdkEventMotion *event,
442 GdkModifierType state;
444 gdk_window_get_pointer (event->window, &x, &y, &state);
446 model->anchor_position.x = x + 0.5;
447 model->anchor_position.y = y + 0.5;
453 spring_constant_changed (GtkSpinButton *spinbutton, gpointer user_data)
455 Model *model = user_data;
457 model->k = gtk_spin_button_get_value (spinbutton);
461 friction_changed (GtkSpinButton *spinbutton, gpointer user_data)
463 Model *model = user_data;
465 model->friction = gtk_spin_button_get_value (spinbutton);
469 create_spinners (Model *model)
472 GtkWidget *spinner, *label;
474 hbox = gtk_hbox_new (FALSE, 8);
476 label = gtk_label_new_with_mnemonic ("_Spring constant:");
477 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
478 spinner = gtk_spin_button_new_with_range (0.05, 30.00, 0.05);
479 gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinner);
480 gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
481 gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), model->k);
482 g_signal_connect (spinner, "value-changed",
483 G_CALLBACK (spring_constant_changed), model);
485 label = gtk_label_new_with_mnemonic ("_Friction:");
486 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
487 spinner = gtk_spin_button_new_with_range (0.05, 15.00, 0.05);
488 gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinner);
489 gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
490 gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), model->friction);
491 g_signal_connect (spinner, "value-changed",
492 G_CALLBACK (friction_changed), model);
498 create_window (Model *model)
506 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
507 gtk_window_set_title (GTK_WINDOW (window), "Drawing Area");
509 g_signal_connect (window, "destroy",
510 G_CALLBACK (gtk_main_quit), &window);
512 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
514 vbox = gtk_vbox_new (FALSE, 8);
515 gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
516 gtk_container_add (GTK_CONTAINER (window), vbox);
519 * Create the drawing area
522 frame = gtk_frame_new (NULL);
523 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
524 gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
526 da = gtk_drawing_area_new ();
527 /* set a minimum size */
528 gtk_widget_set_size_request (da, 600, 500);
530 gtk_container_add (GTK_CONTAINER (frame), da);
532 /* Signals used to handle backing pixmap */
534 g_signal_connect (da, "expose_event",
535 G_CALLBACK (sproing_expose_event), model);
539 g_signal_connect (da, "motion_notify_event",
540 G_CALLBACK (sproing_motion_notify_event), model);
541 g_signal_connect (da, "button_press_event",
542 G_CALLBACK (sproing_button_press_event), model);
543 g_signal_connect (da, "button_release_event",
544 G_CALLBACK (sproing_button_release_event), model);
546 /* Ask to receive events the drawing area doesn't normally
549 gtk_widget_set_events (da, gtk_widget_get_events (da)
550 | GDK_LEAVE_NOTIFY_MASK
551 | GDK_BUTTON_PRESS_MASK
552 | GDK_BUTTON_RELEASE_MASK
553 | GDK_POINTER_MOTION_MASK
554 | GDK_POINTER_MOTION_HINT_MASK);
556 spinners = create_spinners (model);
557 gtk_box_pack_start (GTK_BOX (vbox), spinners, FALSE, FALSE, 0);
562 typedef struct _Closure Closure;
564 GtkWidget *drawing_area;
570 timeout_callback (gpointer data)
572 Closure *closure = data;
575 for (i = 0; i < 3; i++)
576 model_step (closure->model, 0.5);
579 if (closure->i == 1) {
580 gtk_widget_queue_draw (closure->drawing_area);
588 main (int argc, char *argv[])
593 gtk_init (&argc, &argv);
594 model_init_snake (&model);
595 closure.drawing_area = create_window (&model);
597 gtk_widget_show_all (gtk_widget_get_toplevel (closure.drawing_area));
598 closure.model = &model;
599 g_timeout_add (100, timeout_callback, &closure);