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>
26 const double ground_friction = 0.1, ground_level = 400;
27 const double box_left = 200, box_top = 200, box_bottom = 210;
28 const double elasticity = 0.7;
29 const double edge_fuzz = 1;
31 typedef struct _xy_pair Point;
32 typedef struct _xy_pair Vector;
37 typedef struct _Object Object;
38 typedef struct _Stick Stick;
39 typedef struct _String String;
40 typedef struct _Offset Offset;
41 typedef struct _Model Model;
47 Point previous_position;
81 Object *anchor_object;
82 Vector anchor_position;
88 model_init_snake (Model *model)
90 const int num_objects = 20;
91 const int num_sticks = num_objects * 2 - 3;
94 model->objects = g_new (Object, num_objects);
95 model->num_objects = num_objects;
96 model->sticks = g_new (Stick, num_sticks);
97 model->num_sticks = num_sticks;
98 model->strings = NULL;
99 model->num_strings = 0;
100 model->offsets = NULL;
101 model->num_offsets = 0;
103 for (i = 0; i < num_objects; i++) {
104 model->objects[i].position.x = random() % 200 + 20;
105 model->objects[i].position.y = random() % 200 + 20;
106 model->objects[i].previous_position.x = random() % 200 + 20;
107 model->objects[i].previous_position.y = random() % 200 + 20;
109 if (i + 1 < num_objects) {
110 model->sticks[i * 2].a = &model->objects[i];
111 model->sticks[i * 2].b = &model->objects[i + 1];
112 model->sticks[i * 2].length = random() % 20 + 20;
114 if (i + 2 < num_objects) {
115 model->sticks[i * 2 + 1].a = &model->objects[i];
116 model->sticks[i * 2 + 1].b = &model->objects[i + 2];
117 model->sticks[i * 2 + 1].length = random() % 20 + 20;
121 model->anchor_object = NULL;
125 model_init_rope (Model *model)
127 const int num_objects = 20;
128 const int num_sticks = num_objects - 1;
129 const int stick_length = 5;
132 model->objects = g_new (Object, num_objects);
133 model->num_objects = num_objects;
134 model->sticks = g_new (Stick, num_sticks);
135 model->num_sticks = num_sticks;
136 model->strings = NULL;
137 model->num_strings = 0;
138 model->offsets = NULL;
139 model->num_offsets = 0;
141 for (i = 0; i < num_objects; i++) {
142 model->objects[i].position.x = 200;
143 model->objects[i].position.y = 40 + i * stick_length;
144 model->objects[i].previous_position.x = 200;
145 model->objects[i].previous_position.y = 40 + i * stick_length;
147 if (i + 1 < num_objects) {
148 model->sticks[i].a = &model->objects[i];
149 model->sticks[i].b = &model->objects[i + 1];
150 model->sticks[i].length = stick_length;
154 model->anchor_object = NULL;
158 model_init_curtain (Model *model)
160 const int num_ropes = 5;
161 const int num_rope_objects = 15;
162 const int num_objects = num_ropes * num_rope_objects;
163 const int num_sticks = num_ropes * (num_rope_objects - 1);
164 const int stick_length = 10;
165 const int rope_offset = 30;
167 int i, j, index, stick_index;
169 model->objects = g_new (Object, num_objects);
170 model->num_objects = num_objects;
171 model->sticks = g_new (Stick, num_sticks);
172 model->num_sticks = num_sticks;
173 model->strings = NULL;
174 model->num_strings = 0;
175 model->offsets = g_new (Offset, num_ropes - 1);
176 model->num_offsets = num_ropes - 1;
178 for (i = 0; i < num_ropes; i++) {
179 for (j = 0; j < num_rope_objects; j++) {
180 x = 200 + i * rope_offset;
181 y = 40 + j * stick_length;
182 index = i * num_rope_objects + j;
183 model->objects[index].position.x = x;
184 model->objects[index].position.y = y;
185 model->objects[index].previous_position.x = x;
186 model->objects[index].previous_position.y = y;
188 if (j + 1 < num_rope_objects) {
189 stick_index = i * (num_rope_objects - 1) + j;
190 model->sticks[stick_index].a = &model->objects[index];
191 model->sticks[stick_index].b = &model->objects[index + 1];
192 model->sticks[stick_index].length = stick_length;
196 if (i + 1 < num_ropes) {
197 model->offsets[i].a = &model->objects[i * num_rope_objects];
198 model->offsets[i].b = &model->objects[(i + 1) * num_rope_objects];
199 model->offsets[i].dx = rope_offset;
200 model->offsets[i].dy = 0;
204 model->anchor_object = NULL;
208 model_init_grid (Model *model)
210 const int num_ropes = 10;
211 const int num_rope_objects = 10;
212 const int num_objects = num_ropes * num_rope_objects;
213 const int num_strings = num_ropes * (num_rope_objects - 1) +
214 (num_ropes - 1) * num_rope_objects;
215 const int string_length = 10;
216 const int rope_offset = 10;
218 int i, j, index, string_index;
220 model->objects = g_new (Object, num_objects);
221 model->num_objects = num_objects;
222 model->sticks = NULL;
223 model->num_sticks = 0;
224 model->strings = g_new (String, num_strings);
225 model->num_strings = num_strings;
226 model->offsets = g_new (Offset, num_ropes - 1);
227 model->num_offsets = num_ropes - 1;
229 for (i = 0; i < num_ropes; i++) {
230 for (j = 0; j < num_rope_objects; j++) {
231 x = 200 + i * rope_offset;
232 y = 40 + j * string_length;
233 index = i * num_rope_objects + j;
234 model->objects[index].position.x = x;
235 model->objects[index].position.y = y;
236 model->objects[index].previous_position.x = x;
237 model->objects[index].previous_position.y = y;
239 if (i + 1 < num_ropes) {
240 string_index = i * num_rope_objects + j;
241 model->strings[string_index].a = &model->objects[index];
242 model->strings[string_index].b = &model->objects[index + num_rope_objects];
243 model->strings[string_index].length = string_length;
246 if (j + 1 < num_rope_objects) {
248 (num_ropes - 1) * num_rope_objects + i * (num_rope_objects - 1) + j;
249 model->strings[string_index].a = &model->objects[index];
250 model->strings[string_index].b = &model->objects[index + 1];
251 model->strings[string_index].length = string_length;
255 if (i + 1 < num_ropes) {
256 model->offsets[i].a = &model->objects[i * num_rope_objects];
257 model->offsets[i].b = &model->objects[(i + 1) * num_rope_objects];
258 model->offsets[i].dx = rope_offset;
259 model->offsets[i].dy = 0;
263 model->anchor_object = NULL;
267 model_fini (Model *model)
269 g_free (model->objects);
270 model->objects = NULL;
271 model->num_objects = 0;
272 g_free (model->sticks);
273 model->sticks = NULL;
275 g_free (model->strings);
276 model->strings = NULL;
278 g_free (model->offsets);
279 model->offsets = NULL;
280 model->num_offsets = 0;
284 model_accumulate_forces (Model *model)
288 for (i = 0; i < model->num_objects; i++) {
289 model->objects[i].force.x = 0;
290 model->objects[i].force.y = 3;
295 model_integrate (Model *model, double step)
301 for (i = 0; i < model->num_objects; i++) {
302 o = &model->objects[i];
307 x + (x - o->previous_position.x) + o->force.x * step * step;
309 y + (y - o->previous_position.y) + o->force.y * step * step;
311 o->previous_position.x = x;
312 o->previous_position.y = y;
316 /* The square root in the distance computation for the string and
317 * stick constraints can be aproximated using Newton:
320 * (model->sticks[i].length +
321 * (dx * dx + dy * dy) / model->sticks[i].length) / 2;
323 * This works really well, since the constraints aren't typically
324 * violated much. Thus, the distance is really close to the stick
325 * length, which then makes a good initial guess. However, the
326 * approximation seems to be slower that just calling sqrt()...
330 estimate_distance (double dx, double dy, double r)
332 #ifdef APPROXIMATE_SQUARE_ROOTS
333 return (r + (dx * dx + dy * dy) / r) / 2;
335 return sqrt (dx * dx + dy * dy);
340 model_constrain (Model *model, double step)
342 double dx, dy, x, y, distance, fraction;
345 /* Anchor object constraint. */
346 if (model->anchor_object != NULL) {
347 model->anchor_object->position.x = model->anchor_position.x;
348 model->anchor_object->position.y = model->anchor_position.y;
349 model->anchor_object->previous_position.x = model->anchor_position.x;
350 model->anchor_object->previous_position.y = model->anchor_position.y;
353 /* FIXME: this should be "is point inside box" test instead. Figure
354 * out from previous_position which edge the point has passed
355 * through and reflect in that. */
356 for (i = 0; i < model->num_objects; i++) {
357 x = model->objects[i].position.x;
358 y = model->objects[i].position.y;
359 if (box_top - edge_fuzz <= y &&
360 model->objects[i].previous_position.y <= box_top + edge_fuzz &&
362 model->objects[i].position.y = box_top - (y - box_top) * elasticity;
363 model->objects[i].previous_position.y =
364 box_top - (model->objects[i].previous_position.y - box_top) * elasticity;
368 /* Ground collision detection constraints. This puts a ground level
369 * in to make sure the points don't fall off the screen. */
370 for (i = 0; i < model->num_objects; i++) {
371 x = model->objects[i].position.x;
372 y = model->objects[i].position.y;
374 if (model->objects[i].position.y > ground_level) {
375 model->objects[i].position.y =
376 ground_level - (model->objects[i].position.y - ground_level) * elasticity;
377 model->objects[i].previous_position.y =
378 ground_level - (model->objects[i].previous_position.y - ground_level) * elasticity;
380 /* Friction on impact */
381 model->objects[i].position.x =
382 model->objects[i].position.x * (1 - ground_friction) +
383 model->objects[i].previous_position.x * ground_friction;
387 /* Offset constraints. */
388 for (i = 0; i < model->num_offsets; i++) {
389 x = (model->offsets[i].a->position.x + model->offsets[i].b->position.x) / 2;
390 y = (model->offsets[i].a->position.y + model->offsets[i].b->position.y) / 2;
391 model->offsets[i].a->position.x = x - model->offsets[i].dx / 2;
392 model->offsets[i].a->position.y = y - model->offsets[i].dy / 2;
393 model->offsets[i].b->position.x = x + model->offsets[i].dx / 2;
394 model->offsets[i].b->position.y = y + model->offsets[i].dy / 2;
397 /* String constraints. */
398 for (i = 0; i < model->num_strings; i++) {
399 x = model->strings[i].a->position.x;
400 y = model->strings[i].a->position.y;
401 dx = model->strings[i].b->position.x - x;
402 dy = model->strings[i].b->position.y - y;
403 distance = estimate_distance (dx, dy, model->strings[i].length);
404 if (distance < model->strings[i].length)
406 fraction = (distance - model->strings[i].length) / distance / 2;
407 model->strings[i].a->position.x = x + dx * fraction;
408 model->strings[i].a->position.y = y + dy * fraction;
409 model->strings[i].b->position.x = x + dx * (1 - fraction);
410 model->strings[i].b->position.y = y + dy * (1 - fraction);
413 /* Stick constraints. */
414 for (i = 0; i < model->num_sticks; i++) {
415 x = model->sticks[i].a->position.x;
416 y = model->sticks[i].a->position.y;
417 dx = model->sticks[i].b->position.x - x;
418 dy = model->sticks[i].b->position.y - y;
419 distance = estimate_distance (dx, dy, model->sticks[i].length);
420 fraction = (distance - model->sticks[i].length) / distance / 2;
421 model->sticks[i].a->position.x = x + dx * fraction;
422 model->sticks[i].a->position.y = y + dy * fraction;
423 model->sticks[i].b->position.x = x + dx * (1 - fraction);
424 model->sticks[i].b->position.y = y + dy * (1 - fraction);
429 model_step (Model *model, double delta_t)
433 model_accumulate_forces (model);
434 model_integrate (model, delta_t);
436 for (i = 0; i < 100; i++)
437 model_constrain (model, delta_t);
439 model->theta += delta_t;
443 object_distance (Object *object, double x, double y)
447 dx = object->position.x - x;
448 dy = object->position.y - y;
450 return sqrt (dx*dx + dy*dy);
454 model_find_nearest (Model *model, double x, double y)
457 double distance, min_distance;
460 for (i = 0; i < model->num_objects; i++) {
461 distance = object_distance (&model->objects[i], x, y);
462 if (i == 0 || distance < min_distance) {
463 min_distance = distance;
464 object = &model->objects[i];
471 typedef struct _Color Color;
473 double red, green, blue;
477 draw_sticks (cairo_t *cr,
483 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
485 cairo_set_line_width (cr, 2);
486 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
487 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
489 for (i = 0; i < model->num_sticks; i++) {
491 model->sticks[i].a->position.x,
492 model->sticks[i].a->position.y);
494 model->sticks[i].b->position.x,
495 model->sticks[i].b->position.y);
502 draw_strings (cairo_t *cr,
508 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
510 cairo_set_line_width (cr, 1);
511 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
512 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
514 for (i = 0; i < model->num_strings; i++) {
516 model->strings[i].a->position.x,
517 model->strings[i].a->position.y);
519 model->strings[i].b->position.x,
520 model->strings[i].b->position.y);
527 draw_offsets (cairo_t *cr,
533 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
535 cairo_set_line_width (cr, 4);
536 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
537 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
539 for (i = 0; i < model->num_offsets; i++) {
541 model->offsets[i].a->position.x,
542 model->offsets[i].a->position.y);
544 model->offsets[i].b->position.x,
545 model->offsets[i].b->position.y);
552 draw_constraints (cairo_t *cr,
556 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
558 cairo_move_to (cr, 0, ground_level);
559 cairo_line_to (cr, 1500, ground_level);
560 cairo_line_to (cr, 1500, ground_level + 10);
561 cairo_line_to (cr, 0, ground_level + 10);
562 cairo_close_path (cr);
564 cairo_move_to (cr, 0, box_top);
565 cairo_line_to (cr, box_left, box_top);
566 cairo_line_to (cr, box_left, box_bottom);
567 cairo_line_to (cr, 0, box_bottom);
568 cairo_close_path (cr);
574 draw_objects (cairo_t *cr, Model *model, Color *color)
578 for (i = 0; i < model->num_objects; i++) {
582 static Color blue = { 0, 0, 1 };
583 static Color green = { 0, 1, 0 };
584 static Color red = { 1, 0, 0 };
585 static Color black = { 0, 0, 0 };
586 static Color white = { 1, 1, 1 };
588 typedef struct _Closure Closure;
590 GtkWidget *drawing_area;
591 GtkWidget *fps_label;
595 struct timeval start;
599 expose_event (GtkWidget *widget,
600 GdkEventExpose *event,
603 Closure *closure = data;
606 cr = gdk_cairo_create (widget->window);
608 cairo_set_source_rgb (cr, 1, 1, 1);
611 draw_constraints (cr, closure->model, &red);
612 draw_sticks (cr, closure->model, &black);
613 draw_strings (cr, closure->model, &green);
614 draw_offsets (cr, closure->model, &blue);
615 draw_objects (cr, closure->model, &white);
623 button_press_event (GtkWidget *widget,
624 GdkEventButton *event,
627 Closure *closure = data;
629 if (event->button != 1)
632 closure->model->anchor_position.x = event->x;
633 closure->model->anchor_position.y = event->y;
634 closure->model->anchor_object = model_find_nearest (closure->model,
641 button_release_event (GtkWidget *widget,
642 GdkEventButton *event,
645 Closure *closure = data;
647 if ((event->state & GDK_BUTTON1_MASK) == 0)
650 closure->model->anchor_object = NULL;
656 motion_notify_event (GtkWidget *widget,
657 GdkEventMotion *event,
660 Closure *closure = data;
662 GdkModifierType state;
664 gdk_window_get_pointer (event->window, &x, &y, &state);
666 closure->model->anchor_position.x = x + 0.5;
667 closure->model->anchor_position.y = y + 0.5;
672 typedef void (*ModelInitFunc) (Model *model);
675 model_changed (GtkComboBox *combo, gpointer user_data)
677 Closure *closure = user_data;
679 GtkTreeModel *tree_model;
683 tree_model = gtk_combo_box_get_model (combo);
684 if (!gtk_combo_box_get_active_iter (combo, &iter))
687 gtk_tree_model_get (tree_model, &iter, 0, &name, 1, &init, -1);
689 model_fini (closure->model);
690 (*init) (closure->model);
693 static GtkTreeModel *
694 create_model_store (void)
700 { "Rope", model_init_rope },
701 { "Snake", model_init_snake },
702 { "Curtain", model_init_curtain },
703 { "Grid", model_init_grid }
710 store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
712 for (i = 0; i < G_N_ELEMENTS(models); i++) {
713 gtk_tree_store_append (store, &iter, NULL);
714 gtk_tree_store_set (store, &iter,
715 0, models[i].name, 1, models[i].init, -1);
718 return GTK_TREE_MODEL (store);
723 create_model_combo (Closure *closure)
726 GtkWidget *combo, *label;
728 GtkCellRenderer *renderer;
730 hbox = gtk_hbox_new (FALSE, 8);
732 label = gtk_label_new_with_mnemonic ("_Model:");
733 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
735 store = create_model_store ();
736 combo = gtk_combo_box_new_with_model (store);
737 gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
738 g_object_unref (store);
740 renderer = gtk_cell_renderer_text_new ();
741 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE);
742 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer,
746 gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
747 gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0);
748 g_signal_connect (combo, "changed",
749 G_CALLBACK (model_changed), closure);
751 label = gtk_label_new ("Frames per second: 0");
752 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
754 closure->fps_label = label;
760 create_window (Closure *closure)
766 GtkWidget *model_combo;
768 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
769 gtk_window_set_title (GTK_WINDOW (window), "Akamaru");
771 g_signal_connect (window, "destroy",
772 G_CALLBACK (gtk_main_quit), &window);
774 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
776 vbox = gtk_vbox_new (FALSE, 8);
777 gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
778 gtk_container_add (GTK_CONTAINER (window), vbox);
781 * Create the drawing area
784 frame = gtk_frame_new (NULL);
785 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
786 gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
788 da = gtk_drawing_area_new ();
789 /* set a minimum size */
790 gtk_widget_set_size_request (da, 600, 500);
792 gtk_container_add (GTK_CONTAINER (frame), da);
794 /* Signals used to handle backing pixmap */
796 g_signal_connect (da, "expose_event",
797 G_CALLBACK (expose_event), closure);
801 g_signal_connect (da, "motion_notify_event",
802 G_CALLBACK (motion_notify_event), closure);
803 g_signal_connect (da, "button_press_event",
804 G_CALLBACK (button_press_event), closure);
805 g_signal_connect (da, "button_release_event",
806 G_CALLBACK (button_release_event), closure);
808 /* Ask to receive events the drawing area doesn't normally
811 gtk_widget_set_events (da, gtk_widget_get_events (da)
812 | GDK_LEAVE_NOTIFY_MASK
813 | GDK_BUTTON_PRESS_MASK
814 | GDK_BUTTON_RELEASE_MASK
815 | GDK_POINTER_MOTION_MASK
816 | GDK_POINTER_MOTION_HINT_MASK);
818 model_combo = create_model_combo (closure);
819 gtk_box_pack_start (GTK_BOX (vbox), model_combo, FALSE, FALSE, 0);
821 closure->drawing_area = da;
825 timeout_callback (gpointer data)
827 Closure *closure = data;
830 model_step (closure->model, 1);
833 if (closure->i == 1) {
834 gtk_widget_queue_draw (closure->drawing_area);
836 closure->frame_count++;
839 if (closure->frame_count == 200) {
840 struct timeval end, elapsed;
844 closure->frame_count = 0;
845 gettimeofday (&end, NULL);
846 if (closure->start.tv_usec > end.tv_usec) {
847 end.tv_usec += 1000000;
851 elapsed.tv_usec = end.tv_usec - closure->start.tv_usec;
852 elapsed.tv_sec = end.tv_sec - closure->start.tv_sec;
854 total = elapsed.tv_sec + ((double) elapsed.tv_usec / 1e6);
858 closure->start = end;
859 snprintf (text, sizeof text, "Frames per second: %.2f", 200 / total);
860 gtk_label_set_text (GTK_LABEL (closure->fps_label), text);
867 main (int argc, char *argv[])
872 gtk_init (&argc, &argv);
873 model_init_rope (&model);
874 create_window (&closure);
876 gtk_widget_show_all (gtk_widget_get_toplevel (closure.drawing_area));
877 closure.model = &model;
878 closure.frame_count = 0;
879 gettimeofday (&closure.start, NULL);
880 g_timeout_add (40, timeout_callback, &closure);