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
16 * - Try out this idea: make constraint solver take mean of all
17 * corrections at the end instead of meaning as it goes.
22 #include <cairo-xlib.h>
29 const double ground_friction = 0.1, ground_level = 400;
30 const double box_left = 200, box_top = 200, box_bottom = 210;
31 const double elasticity = 0.7;
32 const double edge_fuzz = 1;
34 typedef struct _xy_pair Point;
35 typedef struct _xy_pair Vector;
40 typedef struct _Object Object;
41 typedef struct _Stick Stick;
42 typedef struct _String String;
43 typedef struct _Polygon Polygon;
44 typedef struct _Offset Offset;
45 typedef struct _Model Model;
51 Point previous_position;
94 Object *anchor_object;
95 Vector anchor_position;
101 polygon_init (Polygon *p, int num_points, ...)
103 double dx, dy, length;
107 /* Polygons are defined counter-clock-wise in a coordinate system
108 * with the y-axis pointing down. */
110 va_start (ap, num_points);
111 p->num_points = num_points;
112 p->points = g_new (Point, num_points);
114 for (i = 0; i < num_points; i++) {
115 p->points[i].x = va_arg (ap, double);
116 p->points[i].y = va_arg (ap, double);
120 p->normals = g_new (Vector, p->num_points);
121 /* Compute outward pointing normals. p->normals[i] is the normal
122 * for the edged between p->points[i] and p->points[i + 1]. */
123 for (i = 0; i < p->num_points; i++) {
124 j = (i + 1) % p->num_points;
125 dx = p->points[j].x - p->points[i].x;
126 dy = p->points[j].y - p->points[i].y;
127 length = sqrt (dx * dx + dy * dy);
128 p->normals[i].x = -dy / length;
129 p->normals[i].y = dx / length;
134 polygon_init_diamond (Polygon *polygon, double x, double y)
136 return polygon_init (polygon, 5,
145 polygon_init_rectangle (Polygon *polygon, double x0, double y0,
146 double x1, double y1)
148 return polygon_init (polygon, 4, x0, y0, x0, y1, x1, y1, x1, y0);
152 model_init_polygons (Model *model)
154 const int num_polygons = 5;
156 model->polygons = g_new (Polygon, num_polygons);
157 polygon_init_diamond (&model->polygons[0], 250, 300);
158 polygon_init_diamond (&model->polygons[1], 400, 150);
159 polygon_init_rectangle (&model->polygons[2], -100, 200, 200, 250);
160 polygon_init_rectangle (&model->polygons[3], -200, ground_level,
161 1200, ground_level + 400);
163 polygon_init_rectangle (&model->polygons[4], 300, 320, 400, 350);
166 model->num_polygons = num_polygons;
170 model_init_snake (Model *model)
172 const int num_objects = 20;
173 const int num_sticks = num_objects * 2 - 3;
176 memset (model, 0, sizeof *model);
177 model->objects = g_new (Object, num_objects);
178 model->num_objects = num_objects;
179 model->sticks = g_new (Stick, num_sticks);
180 model->num_sticks = num_sticks;
181 model_init_polygons (model);
183 for (i = 0; i < num_objects; i++) {
184 model->objects[i].position.x = random() % 200 + 20;
185 model->objects[i].position.y = random() % 200 + 20;
186 model->objects[i].previous_position.x = random() % 200 + 20;
187 model->objects[i].previous_position.y = random() % 200 + 20;
189 if (i + 1 < num_objects) {
190 model->sticks[i * 2].a = &model->objects[i];
191 model->sticks[i * 2].b = &model->objects[i + 1];
192 model->sticks[i * 2].length = random() % 20 + 20;
194 if (i + 2 < num_objects) {
195 model->sticks[i * 2 + 1].a = &model->objects[i];
196 model->sticks[i * 2 + 1].b = &model->objects[i + 2];
197 model->sticks[i * 2 + 1].length = random() % 20 + 20;
201 model->anchor_object = NULL;
205 model_init_rope (Model *model)
207 const int num_objects = 20;
208 const int num_sticks = num_objects - 1;
209 const int stick_length = 10;
212 memset (model, 0, sizeof *model);
213 model->objects = g_new (Object, num_objects);
214 model->num_objects = num_objects;
215 model->sticks = g_new (Stick, num_sticks);
216 model->num_sticks = num_sticks;
217 model_init_polygons (model);
219 for (i = 0; i < num_objects; i++) {
220 model->objects[i].position.x = 200;
221 model->objects[i].position.y = 40 + i * stick_length;
222 model->objects[i].previous_position.x = 200;
223 model->objects[i].previous_position.y = 40 + i * stick_length;
225 if (i + 1 < num_objects) {
226 model->sticks[i].a = &model->objects[i];
227 model->sticks[i].b = &model->objects[i + 1];
228 model->sticks[i].length = stick_length;
232 model->anchor_object = NULL;
236 model_init_curtain (Model *model)
238 const int num_ropes = 5;
239 const int num_rope_objects = 15;
240 const int num_objects = num_ropes * num_rope_objects;
241 const int num_sticks = num_ropes * (num_rope_objects - 1);
242 const int stick_length = 10;
243 const int rope_offset = 30;
245 int i, j, index, stick_index;
247 memset (model, 0, sizeof *model);
248 model->objects = g_new (Object, num_objects);
249 model->num_objects = num_objects;
250 model->sticks = g_new (Stick, num_sticks);
251 model->num_sticks = num_sticks;
252 model->offsets = g_new (Offset, num_ropes - 1);
253 model->num_offsets = num_ropes - 1;
254 model_init_polygons (model);
256 for (i = 0; i < num_ropes; i++) {
257 for (j = 0; j < num_rope_objects; j++) {
258 x = 200 + i * rope_offset;
259 y = 40 + j * stick_length;
260 index = i * num_rope_objects + j;
261 model->objects[index].position.x = x;
262 model->objects[index].position.y = y;
263 model->objects[index].previous_position.x = x;
264 model->objects[index].previous_position.y = y;
266 if (j + 1 < num_rope_objects) {
267 stick_index = i * (num_rope_objects - 1) + j;
268 model->sticks[stick_index].a = &model->objects[index];
269 model->sticks[stick_index].b = &model->objects[index + 1];
270 model->sticks[stick_index].length = stick_length;
274 if (i + 1 < num_ropes) {
275 model->offsets[i].a = &model->objects[i * num_rope_objects];
276 model->offsets[i].b = &model->objects[(i + 1) * num_rope_objects];
277 model->offsets[i].dx = rope_offset;
278 model->offsets[i].dy = 0;
282 model->anchor_object = NULL;
286 model_init_grid (Model *model)
288 const int num_ropes = 10;
289 const int num_rope_objects = 10;
290 const int num_objects = num_ropes * num_rope_objects;
291 const int num_strings = num_ropes * (num_rope_objects - 1) +
292 (num_ropes - 1) * num_rope_objects;
293 const int string_length = 10;
294 const int rope_offset = 10;
296 int i, j, index, string_index;
298 memset (model, 0, sizeof *model);
299 model->objects = g_new (Object, num_objects);
300 model->num_objects = num_objects;
301 model->strings = g_new (String, num_strings);
302 model->num_strings = num_strings;
303 model->offsets = g_new (Offset, num_ropes - 1);
304 model->num_offsets = num_ropes - 1;
305 model_init_polygons (model);
307 for (i = 0; i < num_ropes; i++) {
308 for (j = 0; j < num_rope_objects; j++) {
309 x = 200 + i * rope_offset;
310 y = 40 + j * string_length;
311 index = i * num_rope_objects + j;
312 model->objects[index].position.x = x;
313 model->objects[index].position.y = y;
314 model->objects[index].previous_position.x = x;
315 model->objects[index].previous_position.y = y;
317 if (i + 1 < num_ropes) {
318 string_index = i * num_rope_objects + j;
319 model->strings[string_index].a = &model->objects[index];
320 model->strings[string_index].b = &model->objects[index + num_rope_objects];
321 model->strings[string_index].length = string_length;
324 if (j + 1 < num_rope_objects) {
326 (num_ropes - 1) * num_rope_objects + i * (num_rope_objects - 1) + j;
327 model->strings[string_index].a = &model->objects[index];
328 model->strings[string_index].b = &model->objects[index + 1];
329 model->strings[string_index].length = string_length;
333 if (i + 1 < num_ropes) {
334 model->offsets[i].a = &model->objects[i * num_rope_objects];
335 model->offsets[i].b = &model->objects[(i + 1) * num_rope_objects];
336 model->offsets[i].dx = rope_offset;
337 model->offsets[i].dy = 0;
341 model->anchor_object = NULL;
345 model_fini (Model *model)
347 g_free (model->objects);
348 g_free (model->sticks);
349 g_free (model->strings);
350 g_free (model->offsets);
351 memset (model, 0, sizeof *model);
355 model_accumulate_forces (Model *model)
359 for (i = 0; i < model->num_objects; i++) {
360 model->objects[i].force.x = 0;
361 model->objects[i].force.y = 3;
366 model_integrate (Model *model, double step)
372 for (i = 0; i < model->num_objects; i++) {
373 o = &model->objects[i];
378 x + 0.9 * (x - o->previous_position.x) + o->force.x * step * step;
380 y + 0.9 * (y - o->previous_position.y) + o->force.y * step * step;
382 o->previous_position.x = x;
383 o->previous_position.y = y;
387 /* The square root in the distance computation for the string and
388 * stick constraints can be aproximated using Newton:
391 * (model->sticks[i].length +
392 * (dx * dx + dy * dy) / model->sticks[i].length) / 2;
394 * This works really well, since the constraints aren't typically
395 * violated much. Thus, the distance is really close to the stick
396 * length, which then makes a good initial guess. However, the
397 * approximation seems to be slower that just calling sqrt()...
401 estimate_distance (double dx, double dy, double r)
403 #ifdef APPROXIMATE_SQUARE_ROOTS
404 return (r + (dx * dx + dy * dy) / r) / 2;
406 return sqrt (dx * dx + dy * dy);
411 polygon_contains_point (Polygon *polygon, Point *point)
416 for (i = 0; i < polygon->num_points; i++) {
417 dx = point->x - polygon->points[i].x;
418 dy = point->y - polygon->points[i].y;
420 if (polygon->normals[i].x * dx + polygon->normals[i].y * dy >= 0)
428 polygon_reflect_object (Polygon *polygon, Object *object)
435 for (i = 0; i < polygon->num_points; i++) {
436 d = polygon->normals[i].x * (object->position.x - polygon->points[i].x) +
437 polygon->normals[i].y * (object->position.y - polygon->points[i].y);
443 n = &polygon->normals[i];
447 object->position.x -= (1 + elasticity) * distance * n->x;
448 object->position.y -= (1 + elasticity) * distance * n->y;
451 n->x * (object->previous_position.x - polygon->points[edge].x) +
452 n->y * (object->previous_position.y - polygon->points[edge].y);
454 object->previous_position.x -= (1 + elasticity) * distance * n->x;
455 object->previous_position.y -= (1 + elasticity) * distance * n->y;
459 model_constrain_polygon (Model *model, Polygon *polygon)
463 for (i = 0; i < model->num_objects; i++) {
464 if (polygon_contains_point (polygon, &model->objects[i].position))
465 polygon_reflect_object (polygon, &model->objects[i]);
470 model_constrain (Model *model)
472 double dx, dy, x, y, distance, fraction;
475 /* Anchor object constraint. */
476 if (model->anchor_object != NULL) {
477 model->anchor_object->position.x = model->anchor_position.x;
478 model->anchor_object->position.y = model->anchor_position.y;
479 model->anchor_object->previous_position.x = model->anchor_position.x;
480 model->anchor_object->previous_position.y = model->anchor_position.y;
483 /* Offset constraints. */
484 for (i = 0; i < model->num_offsets; i++) {
485 x = (model->offsets[i].a->position.x + model->offsets[i].b->position.x) / 2;
486 y = (model->offsets[i].a->position.y + model->offsets[i].b->position.y) / 2;
487 model->offsets[i].a->position.x = x - model->offsets[i].dx / 2;
488 model->offsets[i].a->position.y = y - model->offsets[i].dy / 2;
489 model->offsets[i].b->position.x = x + model->offsets[i].dx / 2;
490 model->offsets[i].b->position.y = y + model->offsets[i].dy / 2;
493 /* String constraints. */
494 for (i = 0; i < model->num_strings; i++) {
495 x = model->strings[i].a->position.x;
496 y = model->strings[i].a->position.y;
497 dx = model->strings[i].b->position.x - x;
498 dy = model->strings[i].b->position.y - y;
499 distance = estimate_distance (dx, dy, model->strings[i].length);
500 if (distance < model->strings[i].length)
502 fraction = (distance - model->strings[i].length) / distance / 2;
503 model->strings[i].a->position.x = x + dx * fraction;
504 model->strings[i].a->position.y = y + dy * fraction;
505 model->strings[i].b->position.x = x + dx * (1 - fraction);
506 model->strings[i].b->position.y = y + dy * (1 - fraction);
509 /* Stick constraints. */
510 for (i = 0; i < model->num_sticks; i++) {
511 x = model->sticks[i].a->position.x;
512 y = model->sticks[i].a->position.y;
513 dx = model->sticks[i].b->position.x - x;
514 dy = model->sticks[i].b->position.y - y;
515 distance = estimate_distance (dx, dy, model->sticks[i].length);
516 fraction = (distance - model->sticks[i].length) / distance / 2;
517 model->sticks[i].a->position.x = x + dx * fraction;
518 model->sticks[i].a->position.y = y + dy * fraction;
519 model->sticks[i].b->position.x = x + dx * (1 - fraction);
520 model->sticks[i].b->position.y = y + dy * (1 - fraction);
523 /* Polygon constraints. */
524 for (i = 0; i < model->num_polygons; i++)
525 model_constrain_polygon (model, &model->polygons[i]);
529 model_step (Model *model, double delta_t)
533 model_accumulate_forces (model);
534 model_integrate (model, delta_t);
536 for (i = 0; i < 100; i++)
537 model_constrain (model);
539 model->theta += delta_t;
543 object_distance (Object *object, double x, double y)
547 dx = object->position.x - x;
548 dy = object->position.y - y;
550 return sqrt (dx*dx + dy*dy);
554 model_find_nearest (Model *model, double x, double y)
557 double distance, min_distance;
560 for (i = 0; i < model->num_objects; i++) {
561 distance = object_distance (&model->objects[i], x, y);
562 if (i == 0 || distance < min_distance) {
563 min_distance = distance;
564 object = &model->objects[i];
571 typedef struct _Color Color;
573 double red, green, blue;
577 draw_sticks (cairo_t *cr,
583 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
585 cairo_set_line_width (cr, 2);
586 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
587 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
589 for (i = 0; i < model->num_sticks; i++) {
591 model->sticks[i].a->position.x,
592 model->sticks[i].a->position.y);
594 model->sticks[i].b->position.x,
595 model->sticks[i].b->position.y);
602 draw_strings (cairo_t *cr,
608 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
610 cairo_set_line_width (cr, 1);
611 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
612 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
614 for (i = 0; i < model->num_strings; i++) {
616 model->strings[i].a->position.x,
617 model->strings[i].a->position.y);
619 model->strings[i].b->position.x,
620 model->strings[i].b->position.y);
627 draw_offsets (cairo_t *cr,
633 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
635 cairo_set_line_width (cr, 4);
636 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
637 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
639 for (i = 0; i < model->num_offsets; i++) {
641 model->offsets[i].a->position.x,
642 model->offsets[i].a->position.y);
644 model->offsets[i].b->position.x,
645 model->offsets[i].b->position.y);
652 draw_polygons (cairo_t *cr, Model *model, Color *color)
657 for (i = 0; i < model->num_polygons; i++) {
658 p = &model->polygons[i];
659 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.4);
662 for (j = 0; j < p->num_points; j++)
663 cairo_line_to (cr, p->points[j].x, p->points[j].y);
664 cairo_close_path (cr);
671 draw_objects (cairo_t *cr, Model *model, Color *color)
675 for (i = 0; i < model->num_objects; i++) {
679 static Color blue = { 0, 0, 1 };
680 static Color green = { 0, 1, 0 };
681 static Color red = { 1, 0, 0 };
682 static Color black = { 0, 0, 0 };
683 static Color white = { 1, 1, 1 };
685 typedef struct _Closure Closure;
687 GtkWidget *drawing_area;
688 GtkWidget *fps_label;
692 struct timeval start;
696 expose_event (GtkWidget *widget,
697 GdkEventExpose *event,
700 Closure *closure = data;
703 cr = gdk_cairo_create (widget->window);
705 cairo_set_source_rgb (cr, 1, 1, 1);
708 draw_polygons (cr, closure->model, &blue);
709 draw_sticks (cr, closure->model, &black);
710 draw_strings (cr, closure->model, &green);
711 draw_offsets (cr, closure->model, &blue);
712 draw_objects (cr, closure->model, &white);
720 button_press_event (GtkWidget *widget,
721 GdkEventButton *event,
724 Closure *closure = data;
726 if (event->button != 1)
729 closure->model->anchor_position.x = event->x;
730 closure->model->anchor_position.y = event->y;
731 closure->model->anchor_object = model_find_nearest (closure->model,
738 button_release_event (GtkWidget *widget,
739 GdkEventButton *event,
742 Closure *closure = data;
744 if ((event->state & GDK_BUTTON1_MASK) == 0)
747 closure->model->anchor_object = NULL;
753 motion_notify_event (GtkWidget *widget,
754 GdkEventMotion *event,
757 Closure *closure = data;
759 GdkModifierType state;
761 gdk_window_get_pointer (event->window, &x, &y, &state);
763 closure->model->anchor_position.x = x + 0.5;
764 closure->model->anchor_position.y = y + 0.5;
769 typedef void (*ModelInitFunc) (Model *model);
772 model_changed (GtkComboBox *combo, gpointer user_data)
774 Closure *closure = user_data;
776 GtkTreeModel *tree_model;
780 tree_model = gtk_combo_box_get_model (combo);
781 if (!gtk_combo_box_get_active_iter (combo, &iter))
784 gtk_tree_model_get (tree_model, &iter, 0, &name, 1, &init, -1);
786 model_fini (closure->model);
787 (*init) (closure->model);
790 static GtkTreeModel *
791 create_model_store (void)
797 { "Rope", model_init_rope },
798 { "Snake", model_init_snake },
799 { "Curtain", model_init_curtain },
800 { "Grid", model_init_grid }
807 store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
809 for (i = 0; i < G_N_ELEMENTS(models); i++) {
810 gtk_tree_store_append (store, &iter, NULL);
811 gtk_tree_store_set (store, &iter,
812 0, models[i].name, 1, models[i].init, -1);
815 return GTK_TREE_MODEL (store);
820 create_model_combo (Closure *closure)
823 GtkWidget *combo, *label;
825 GtkCellRenderer *renderer;
827 hbox = gtk_hbox_new (FALSE, 8);
829 label = gtk_label_new_with_mnemonic ("_Model:");
830 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
832 store = create_model_store ();
833 combo = gtk_combo_box_new_with_model (store);
834 gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
835 g_object_unref (store);
837 renderer = gtk_cell_renderer_text_new ();
838 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE);
839 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer,
843 gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
844 gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0);
845 g_signal_connect (combo, "changed",
846 G_CALLBACK (model_changed), closure);
848 label = gtk_label_new ("Frames per second: 0");
849 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
851 closure->fps_label = label;
857 create_window (Closure *closure)
863 GtkWidget *model_combo;
865 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
866 gtk_window_set_title (GTK_WINDOW (window), "Akamaru");
868 g_signal_connect (window, "destroy",
869 G_CALLBACK (gtk_main_quit), &window);
871 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
873 vbox = gtk_vbox_new (FALSE, 8);
874 gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
875 gtk_container_add (GTK_CONTAINER (window), vbox);
878 * Create the drawing area
881 frame = gtk_frame_new (NULL);
882 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
883 gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
885 da = gtk_drawing_area_new ();
886 /* set a minimum size */
887 gtk_widget_set_size_request (da, 600, 500);
889 gtk_container_add (GTK_CONTAINER (frame), da);
891 /* Signals used to handle backing pixmap */
893 g_signal_connect (da, "expose_event",
894 G_CALLBACK (expose_event), closure);
898 g_signal_connect (da, "motion_notify_event",
899 G_CALLBACK (motion_notify_event), closure);
900 g_signal_connect (da, "button_press_event",
901 G_CALLBACK (button_press_event), closure);
902 g_signal_connect (da, "button_release_event",
903 G_CALLBACK (button_release_event), closure);
905 /* Ask to receive events the drawing area doesn't normally
908 gtk_widget_set_events (da, gtk_widget_get_events (da)
909 | GDK_LEAVE_NOTIFY_MASK
910 | GDK_BUTTON_PRESS_MASK
911 | GDK_BUTTON_RELEASE_MASK
912 | GDK_POINTER_MOTION_MASK
913 | GDK_POINTER_MOTION_HINT_MASK);
915 model_combo = create_model_combo (closure);
916 gtk_box_pack_start (GTK_BOX (vbox), model_combo, FALSE, FALSE, 0);
918 closure->drawing_area = da;
922 timeout_callback (gpointer data)
924 Closure *closure = data;
926 model_step (closure->model, 1);
929 if (closure->i == 1) {
930 gtk_widget_queue_draw (closure->drawing_area);
932 closure->frame_count++;
935 if (closure->frame_count == 200) {
936 struct timeval end, elapsed;
940 closure->frame_count = 0;
941 gettimeofday (&end, NULL);
942 if (closure->start.tv_usec > end.tv_usec) {
943 end.tv_usec += 1000000;
947 elapsed.tv_usec = end.tv_usec - closure->start.tv_usec;
948 elapsed.tv_sec = end.tv_sec - closure->start.tv_sec;
950 total = elapsed.tv_sec + ((double) elapsed.tv_usec / 1e6);
954 closure->start = end;
955 snprintf (text, sizeof text, "Frames per second: %.2f", 200 / total);
956 gtk_label_set_text (GTK_LABEL (closure->fps_label), text);
963 main (int argc, char *argv[])
968 gtk_init (&argc, &argv);
969 model_init_rope (&model);
970 create_window (&closure);
972 gtk_widget_show_all (gtk_widget_get_toplevel (closure.drawing_area));
973 closure.model = &model;
974 closure.frame_count = 0;
975 gettimeofday (&closure.start, NULL);
976 g_timeout_add (50, timeout_callback, &closure);