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_level = 500;
30 const double elasticity = 0.7;
32 typedef struct _xy_pair Point;
33 typedef struct _xy_pair Vector;
38 typedef struct _Object Object;
39 typedef struct _Stick Stick;
40 typedef struct _String String;
41 typedef struct _Spring Spring;
42 typedef struct _Polygon Polygon;
43 typedef struct _Offset Offset;
44 typedef struct _Model Model;
50 Point previous_position;
101 Object *anchor_object;
102 Vector anchor_position;
108 polygon_init (Polygon *p, int num_points, ...)
110 double dx, dy, length;
114 /* Polygons are defined counter-clock-wise in a coordinate system
115 * with the y-axis pointing down. */
117 va_start (ap, num_points);
118 p->num_points = num_points;
119 p->points = g_new (Point, num_points);
121 for (i = 0; i < num_points; i++) {
122 p->points[i].x = va_arg (ap, double);
123 p->points[i].y = va_arg (ap, double);
127 p->normals = g_new (Vector, p->num_points);
128 /* Compute outward pointing normals. p->normals[i] is the normal
129 * for the edged between p->points[i] and p->points[i + 1]. */
130 for (i = 0; i < p->num_points; i++) {
131 j = (i + 1) % p->num_points;
132 dx = p->points[j].x - p->points[i].x;
133 dy = p->points[j].y - p->points[i].y;
134 length = sqrt (dx * dx + dy * dy);
135 p->normals[i].x = -dy / length;
136 p->normals[i].y = dx / length;
141 polygon_init_diamond (Polygon *polygon, double x, double y)
143 return polygon_init (polygon, 5,
152 polygon_init_rectangle (Polygon *polygon, double x0, double y0,
153 double x1, double y1)
155 return polygon_init (polygon, 4, x0, y0, x0, y1, x1, y1, x1, y0);
159 model_init_polygons (Model *model)
161 const int num_polygons = 5;
163 model->polygons = g_new (Polygon, num_polygons);
164 polygon_init_diamond (&model->polygons[0], 250, 300);
165 polygon_init_diamond (&model->polygons[1], 400, 150);
166 polygon_init_rectangle (&model->polygons[2], -100, 200, 200, 250);
167 polygon_init_rectangle (&model->polygons[3], -200, ground_level,
168 1200, ground_level + 400);
170 polygon_init_rectangle (&model->polygons[4], 300, 320, 400, 350);
173 model->num_polygons = num_polygons;
177 model_init_snake (Model *model)
179 const int num_objects = 20;
180 const int num_sticks = num_objects * 2 - 3;
183 memset (model, 0, sizeof *model);
184 model->objects = g_new (Object, num_objects);
185 model->num_objects = num_objects;
186 model->sticks = g_new (Stick, num_sticks);
187 model->num_sticks = num_sticks;
188 model_init_polygons (model);
190 for (i = 0; i < num_objects; i++) {
191 model->objects[i].position.x = random() % 200 + 20;
192 model->objects[i].position.y = random() % 200 + 20;
193 model->objects[i].previous_position.x = random() % 200 + 20;
194 model->objects[i].previous_position.y = random() % 200 + 20;
195 model->objects[i].mass = 1;
197 if (i + 1 < num_objects) {
198 model->sticks[i * 2].a = &model->objects[i];
199 model->sticks[i * 2].b = &model->objects[i + 1];
200 model->sticks[i * 2].length = random() % 20 + 20;
202 if (i + 2 < num_objects) {
203 model->sticks[i * 2 + 1].a = &model->objects[i];
204 model->sticks[i * 2 + 1].b = &model->objects[i + 2];
205 model->sticks[i * 2 + 1].length = random() % 20 + 20;
209 model->anchor_object = NULL;
213 model_init_rope (Model *model)
215 const int num_objects = 20;
216 const int num_sticks = num_objects - 1;
217 const int stick_length = 10;
220 memset (model, 0, sizeof *model);
221 model->objects = g_new (Object, num_objects);
222 model->num_objects = num_objects;
223 model->sticks = g_new (Stick, num_sticks);
224 model->num_sticks = num_sticks;
225 model_init_polygons (model);
227 for (i = 0; i < num_objects; i++) {
228 model->objects[i].position.x = 200;
229 model->objects[i].position.y = 40 + i * stick_length;
230 model->objects[i].previous_position.x = 200;
231 model->objects[i].previous_position.y = 40 + i * stick_length;
232 model->objects[i].mass = 1;
234 if (i + 1 < num_objects) {
235 model->sticks[i].a = &model->objects[i];
236 model->sticks[i].b = &model->objects[i + 1];
237 model->sticks[i].length = stick_length;
241 model->anchor_object = NULL;
245 model_init_curtain (Model *model)
247 const int num_ropes = 5;
248 const int num_rope_objects = 15;
249 const int num_objects = num_ropes * num_rope_objects;
250 const int num_sticks = num_ropes * (num_rope_objects - 1);
251 const int stick_length = 10;
252 const int rope_offset = 30;
254 int i, j, index, stick_index;
256 memset (model, 0, sizeof *model);
257 model->objects = g_new (Object, num_objects);
258 model->num_objects = num_objects;
259 model->sticks = g_new (Stick, num_sticks);
260 model->num_sticks = num_sticks;
261 model->offsets = g_new (Offset, 1);
262 model->num_offsets = 1;
263 model_init_polygons (model);
265 model->offsets[0].num_objects = num_ropes;
266 model->offsets[0].objects = g_new (Object *, num_ropes);
267 model->offsets[0].dx = rope_offset;
268 model->offsets[0].dy = 0;
270 for (i = 0; i < num_ropes; i++) {
271 for (j = 0; j < num_rope_objects; j++) {
272 x = 200 + i * rope_offset;
273 y = 40 + j * stick_length;
274 index = i * num_rope_objects + j;
275 model->objects[index].position.x = x;
276 model->objects[index].position.y = y;
277 model->objects[index].previous_position.x = x;
278 model->objects[index].previous_position.y = y;
279 model->objects[i].mass = 1;
281 if (j + 1 < num_rope_objects) {
282 stick_index = i * (num_rope_objects - 1) + j;
283 model->sticks[stick_index].a = &model->objects[index];
284 model->sticks[stick_index].b = &model->objects[index + 1];
285 model->sticks[stick_index].length = stick_length;
289 model->offsets[0].objects[i] = &model->objects[i * num_rope_objects];
292 model->anchor_object = NULL;
296 model_init_grid (Model *model)
298 const int num_ropes = 10;
299 const int num_rope_objects = 10;
300 const int num_objects = num_ropes * num_rope_objects;
301 const int num_strings = num_ropes * (num_rope_objects - 1) +
302 (num_ropes - 1) * num_rope_objects;
303 const int string_length = 10;
304 const int rope_offset = 10;
306 int i, j, index, string_index;
308 memset (model, 0, sizeof *model);
309 model->objects = g_new (Object, num_objects);
310 model->num_objects = num_objects;
311 model->strings = g_new (String, num_strings);
312 model->num_strings = num_strings;
313 model->offsets = g_new (Offset, 1);
314 model->num_offsets = 1;
315 model_init_polygons (model);
317 model->offsets[0].num_objects = num_ropes;
318 model->offsets[0].objects = g_new (Object *, num_ropes);
319 model->offsets[0].dx = rope_offset;
320 model->offsets[0].dy = 0;
322 for (i = 0; i < num_ropes; i++) {
323 for (j = 0; j < num_rope_objects; j++) {
324 x = 200 + i * rope_offset;
325 y = 40 + j * string_length;
326 index = i * num_rope_objects + j;
327 model->objects[index].position.x = x;
328 model->objects[index].position.y = y;
329 model->objects[index].previous_position.x = x;
330 model->objects[index].previous_position.y = y;
331 model->objects[index].mass = 1;
333 if (i + 1 < num_ropes) {
334 string_index = i * num_rope_objects + j;
335 model->strings[string_index].a = &model->objects[index];
336 model->strings[string_index].b = &model->objects[index + num_rope_objects];
337 model->strings[string_index].length = string_length;
340 if (j + 1 < num_rope_objects) {
342 (num_ropes - 1) * num_rope_objects + i * (num_rope_objects - 1) + j;
343 model->strings[string_index].a = &model->objects[index];
344 model->strings[string_index].b = &model->objects[index + 1];
345 model->strings[string_index].length = string_length;
349 model->offsets[0].objects[i] = &model->objects[i * num_rope_objects];
352 model->anchor_object = NULL;
356 model_init_molecule (Model *model)
358 const int num_objects = 8;
359 const int num_springs = num_objects * 2;
360 const int spring_length = 50;
363 memset (model, 0, sizeof *model);
364 model->objects = g_new (Object, num_objects);
365 model->num_objects = num_objects;
366 model->springs = g_new (Spring, num_springs);
367 model->num_springs = num_springs;
370 for (i = 0; i < num_objects; i++) {
371 model->objects[i].position.x = 200 + i * 20;
372 model->objects[i].position.y = 200;
373 model->objects[i].previous_position.x = 200 + i * 20;
374 model->objects[i].previous_position.y = 200;
375 model->objects[i].mass = 0;
378 for (i = 0; i < num_objects; i++) {
379 model->springs[i * 2].a = &model->objects[i];
380 model->springs[i * 2].b = &model->objects[(i + 1) % num_objects];
381 model->springs[i * 2].length = spring_length;
382 model->springs[i * 2 + 1].a = &model->objects[i];
383 model->springs[i * 2 + 1].b = &model->objects[(i + 2) % num_objects];
384 model->springs[i * 2 + 1].length = spring_length;
389 model_fini (Model *model)
391 g_free (model->objects);
392 g_free (model->sticks);
393 g_free (model->strings);
394 g_free (model->offsets);
395 memset (model, 0, sizeof *model);
399 model_accumulate_forces (Model *model)
402 double x, y, dx, dy, distance, displacement;
405 for (i = 0; i < model->num_objects; i++) {
406 model->objects[i].force.x = 0;
407 model->objects[i].force.y = 3 * model->objects[i].mass;
410 for (i = 0; i < model->num_springs; i++) {
411 x = model->springs[i].a->position.x;
412 y = model->springs[i].a->position.y;
413 dx = model->springs[i].b->position.x - x;
414 dy = model->springs[i].b->position.y - y;
415 distance = sqrt (dx * dx + dy * dy);
418 displacement = distance - model->springs[i].length;
419 model->springs[i].a->force.x += u.x * model->k * displacement;
420 model->springs[i].a->force.y += u.y * model->k * displacement;
421 model->springs[i].b->force.x -= u.x * model->k * displacement;
422 model->springs[i].b->force.y -= u.y * model->k * displacement;
427 model_integrate (Model *model, double step)
433 for (i = 0; i < model->num_objects; i++) {
434 o = &model->objects[i];
439 x + 0.8 * (x - o->previous_position.x) + o->force.x * step * step;
441 y + 0.8 * (y - o->previous_position.y) + o->force.y * step * step;
443 o->previous_position.x = x;
444 o->previous_position.y = y;
448 /* The square root in the distance computation for the string and
449 * stick constraints can be aproximated using Newton:
452 * (model->sticks[i].length +
453 * (dx * dx + dy * dy) / model->sticks[i].length) / 2;
455 * This works really well, since the constraints aren't typically
456 * violated much. Thus, the distance is really close to the stick
457 * length, which then makes a good initial guess. However, the
458 * approximation seems to be slower that just calling sqrt()...
462 estimate_distance (double dx, double dy, double r)
464 #ifdef APPROXIMATE_SQUARE_ROOTS
465 return (r + (dx * dx + dy * dy) / r) / 2;
467 return sqrt (dx * dx + dy * dy);
472 polygon_contains_point (Polygon *polygon, Point *point)
477 for (i = 0; i < polygon->num_points; i++) {
478 dx = point->x - polygon->points[i].x;
479 dy = point->y - polygon->points[i].y;
481 if (polygon->normals[i].x * dx + polygon->normals[i].y * dy >= 0)
489 polygon_reflect_object (Polygon *polygon, Object *object)
496 for (i = 0; i < polygon->num_points; i++) {
497 d = polygon->normals[i].x * (object->position.x - polygon->points[i].x) +
498 polygon->normals[i].y * (object->position.y - polygon->points[i].y);
504 n = &polygon->normals[i];
508 object->position.x -= (1 + elasticity) * distance * n->x;
509 object->position.y -= (1 + elasticity) * distance * n->y;
512 n->x * (object->previous_position.x - polygon->points[edge].x) +
513 n->y * (object->previous_position.y - polygon->points[edge].y);
515 object->previous_position.x -= (1 + elasticity) * distance * n->x;
516 object->previous_position.y -= (1 + elasticity) * distance * n->y;
520 model_constrain_polygon (Model *model, Polygon *polygon)
524 for (i = 0; i < model->num_objects; i++) {
525 if (polygon_contains_point (polygon, &model->objects[i].position))
526 polygon_reflect_object (polygon, &model->objects[i]);
531 model_constrain_offset (Model *model, Offset *offset)
538 for (i = 0; i < offset->num_objects; i++) {
539 x += offset->objects[i]->position.x;
540 y += offset->objects[i]->position.y;
543 x = x / offset->num_objects - offset->dx * (offset->num_objects - 1) / 2;
544 y = y / offset->num_objects - offset->dy * (offset->num_objects - 1) / 2;
546 for (i = 0; i < offset->num_objects; i++) {
547 offset->objects[i]->position.x = x + offset->dx * i;
548 offset->objects[i]->position.y = y + offset->dy * i;
553 model_constrain (Model *model)
555 double dx, dy, x, y, distance, fraction;
558 /* Anchor object constraint. */
559 if (model->anchor_object != NULL) {
560 model->anchor_object->position.x = model->anchor_position.x;
561 model->anchor_object->position.y = model->anchor_position.y;
562 model->anchor_object->previous_position.x = model->anchor_position.x;
563 model->anchor_object->previous_position.y = model->anchor_position.y;
566 /* String constraints. */
567 for (i = 0; i < model->num_strings; i++) {
568 x = model->strings[i].a->position.x;
569 y = model->strings[i].a->position.y;
570 dx = model->strings[i].b->position.x - x;
571 dy = model->strings[i].b->position.y - y;
572 distance = estimate_distance (dx, dy, model->strings[i].length);
573 if (distance < model->strings[i].length)
575 fraction = (distance - model->strings[i].length) / distance / 2;
576 model->strings[i].a->position.x = x + dx * fraction;
577 model->strings[i].a->position.y = y + dy * fraction;
578 model->strings[i].b->position.x = x + dx * (1 - fraction);
579 model->strings[i].b->position.y = y + dy * (1 - fraction);
582 /* Stick constraints. */
583 for (i = 0; i < model->num_sticks; i++) {
584 x = model->sticks[i].a->position.x;
585 y = model->sticks[i].a->position.y;
586 dx = model->sticks[i].b->position.x - x;
587 dy = model->sticks[i].b->position.y - y;
588 distance = estimate_distance (dx, dy, model->sticks[i].length);
589 fraction = (distance - model->sticks[i].length) / distance / 2;
590 model->sticks[i].a->position.x = x + dx * fraction;
591 model->sticks[i].a->position.y = y + dy * fraction;
592 model->sticks[i].b->position.x = x + dx * (1 - fraction);
593 model->sticks[i].b->position.y = y + dy * (1 - fraction);
596 /* Offset constraints. */
597 for (i = 0; i < model->num_offsets; i++)
598 model_constrain_offset (model, &model->offsets[i]);
600 /* Polygon constraints. */
601 for (i = 0; i < model->num_polygons; i++)
602 model_constrain_polygon (model, &model->polygons[i]);
606 model_step (Model *model, double delta_t)
610 model_accumulate_forces (model);
611 model_integrate (model, delta_t);
613 for (i = 0; i < 500; i++)
614 model_constrain (model);
616 model->theta += delta_t;
620 object_distance (Object *object, double x, double y)
624 dx = object->position.x - x;
625 dy = object->position.y - y;
627 return sqrt (dx*dx + dy*dy);
631 model_find_nearest (Model *model, double x, double y)
634 double distance, min_distance;
637 for (i = 0; i < model->num_objects; i++) {
638 distance = object_distance (&model->objects[i], x, y);
639 if (i == 0 || distance < min_distance) {
640 min_distance = distance;
641 object = &model->objects[i];
648 typedef struct _Color Color;
650 double red, green, blue;
654 draw_sticks (cairo_t *cr,
660 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
662 cairo_set_line_width (cr, 2);
663 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
664 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
666 for (i = 0; i < model->num_sticks; i++) {
668 model->sticks[i].a->position.x,
669 model->sticks[i].a->position.y);
671 model->sticks[i].b->position.x,
672 model->sticks[i].b->position.y);
679 draw_strings (cairo_t *cr,
685 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
687 cairo_set_line_width (cr, 1);
688 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
689 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
691 for (i = 0; i < model->num_strings; i++) {
693 model->strings[i].a->position.x,
694 model->strings[i].a->position.y);
696 model->strings[i].b->position.x,
697 model->strings[i].b->position.y);
704 draw_offsets (cairo_t *cr,
710 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
712 cairo_set_line_width (cr, 4);
713 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
714 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
716 for (i = 0; i < model->num_offsets; i++) {
717 for (j = 0; j < model->offsets[i].num_objects; j++) {
719 model->offsets[i].objects[j]->position.x,
720 model->offsets[i].objects[j]->position.y);
728 draw_springs (cairo_t *cr,
734 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.4);
736 cairo_set_line_width (cr, 2);
737 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
738 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
740 for (i = 0; i < model->num_springs; i++) {
742 model->springs[i].a->position.x,
743 model->springs[i].a->position.y);
745 model->springs[i].b->position.x,
746 model->springs[i].b->position.y);
753 draw_polygons (cairo_t *cr, Model *model, Color *color)
758 for (i = 0; i < model->num_polygons; i++) {
759 p = &model->polygons[i];
760 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.4);
763 for (j = 0; j < p->num_points; j++)
764 cairo_line_to (cr, p->points[j].x, p->points[j].y);
765 cairo_close_path (cr);
772 draw_objects (cairo_t *cr, Model *model, Color *color)
776 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.4);
777 for (i = 0; i < model->num_objects; i++) {
778 cairo_arc (cr, model->objects[i].position.x,
779 model->objects[i].position.y,
785 static Color blue = { 0, 0, 1 };
786 static Color green = { 0, 1, 0 };
787 static Color red = { 1, 0, 0 };
788 static Color black = { 0, 0, 0 };
790 typedef struct _Closure Closure;
792 GtkWidget *drawing_area;
793 GtkWidget *fps_label;
797 struct timeval start;
801 draw_model (GtkWidget *widget, Model *model)
805 cr = gdk_cairo_create (widget->window);
807 cairo_set_source_rgb (cr, 1, 1, 1);
810 draw_polygons (cr, model, &blue);
811 draw_sticks (cr, model, &black);
812 draw_strings (cr, model, &green);
813 draw_springs (cr, model, &black);
814 draw_offsets (cr, model, &blue);
815 draw_objects (cr, model, &red);
821 expose_event (GtkWidget *widget,
822 GdkEventExpose *event,
825 Closure *closure = data;
827 draw_model (widget, closure->model);
833 button_press_event (GtkWidget *widget,
834 GdkEventButton *event,
837 Closure *closure = data;
839 if (event->button != 1)
842 closure->model->anchor_position.x = event->x;
843 closure->model->anchor_position.y = event->y;
844 closure->model->anchor_object = model_find_nearest (closure->model,
851 button_release_event (GtkWidget *widget,
852 GdkEventButton *event,
855 Closure *closure = data;
857 if ((event->state & GDK_BUTTON1_MASK) == 0)
860 closure->model->anchor_object = NULL;
866 motion_notify_event (GtkWidget *widget,
867 GdkEventMotion *event,
870 Closure *closure = data;
872 GdkModifierType state;
874 gdk_window_get_pointer (event->window, &x, &y, &state);
876 closure->model->anchor_position.x = x + 0.5;
877 closure->model->anchor_position.y = y + 0.5;
882 typedef void (*ModelInitFunc) (Model *model);
885 model_changed (GtkComboBox *combo, gpointer user_data)
887 Closure *closure = user_data;
889 GtkTreeModel *tree_model;
893 tree_model = gtk_combo_box_get_model (combo);
894 if (!gtk_combo_box_get_active_iter (combo, &iter))
897 gtk_tree_model_get (tree_model, &iter, 0, &name, 1, &init, -1);
899 model_fini (closure->model);
900 (*init) (closure->model);
903 static GtkTreeModel *
904 create_model_store (void)
910 { "Rope", model_init_rope },
911 { "Snake", model_init_snake },
912 { "Curtain", model_init_curtain },
913 { "Grid", model_init_grid },
914 { "Molecule", model_init_molecule }
921 store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
923 for (i = 0; i < G_N_ELEMENTS(models); i++) {
924 gtk_tree_store_append (store, &iter, NULL);
925 gtk_tree_store_set (store, &iter,
926 0, models[i].name, 1, models[i].init, -1);
929 return GTK_TREE_MODEL (store);
934 create_model_combo (Closure *closure)
937 GtkWidget *combo, *label;
939 GtkCellRenderer *renderer;
941 hbox = gtk_hbox_new (FALSE, 8);
943 label = gtk_label_new_with_mnemonic ("_Model:");
944 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
946 store = create_model_store ();
947 combo = gtk_combo_box_new_with_model (store);
948 gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
949 g_object_unref (store);
951 renderer = gtk_cell_renderer_text_new ();
952 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE);
953 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer,
957 gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
958 gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0);
959 g_signal_connect (combo, "changed",
960 G_CALLBACK (model_changed), closure);
962 label = gtk_label_new ("Frames per second: 0");
963 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
965 closure->fps_label = label;
971 create_window (Closure *closure)
977 GtkWidget *model_combo;
979 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
980 gtk_window_set_title (GTK_WINDOW (window), "Akamaru");
982 g_signal_connect (window, "destroy",
983 G_CALLBACK (gtk_main_quit), &window);
985 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
987 vbox = gtk_vbox_new (FALSE, 8);
988 gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
989 gtk_container_add (GTK_CONTAINER (window), vbox);
992 * Create the drawing area
995 frame = gtk_frame_new (NULL);
996 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
997 gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
999 da = gtk_drawing_area_new ();
1000 /* set a minimum size */
1001 gtk_widget_set_size_request (da, 200, 200);
1003 gtk_container_add (GTK_CONTAINER (frame), da);
1005 /* Signals used to handle backing pixmap */
1007 g_signal_connect (da, "expose_event",
1008 G_CALLBACK (expose_event), closure);
1012 g_signal_connect (da, "motion_notify_event",
1013 G_CALLBACK (motion_notify_event), closure);
1014 g_signal_connect (da, "button_press_event",
1015 G_CALLBACK (button_press_event), closure);
1016 g_signal_connect (da, "button_release_event",
1017 G_CALLBACK (button_release_event), closure);
1019 /* Ask to receive events the drawing area doesn't normally
1022 gtk_widget_set_events (da, gtk_widget_get_events (da)
1023 | GDK_LEAVE_NOTIFY_MASK
1024 | GDK_BUTTON_PRESS_MASK
1025 | GDK_BUTTON_RELEASE_MASK
1026 | GDK_POINTER_MOTION_MASK
1027 | GDK_POINTER_MOTION_HINT_MASK);
1029 model_combo = create_model_combo (closure);
1030 gtk_box_pack_start (GTK_BOX (vbox), model_combo, FALSE, FALSE, 0);
1032 closure->drawing_area = da;
1036 timeout_callback (gpointer data)
1038 Closure *closure = data;
1040 model_step (closure->model, 1);
1043 if (closure->i == 1) {
1044 gtk_widget_queue_draw (closure->drawing_area);
1046 closure->frame_count++;
1049 if (closure->frame_count == 200) {
1050 struct timeval end, elapsed;
1054 closure->frame_count = 0;
1055 gettimeofday (&end, NULL);
1056 if (closure->start.tv_usec > end.tv_usec) {
1057 end.tv_usec += 1000000;
1061 elapsed.tv_usec = end.tv_usec - closure->start.tv_usec;
1062 elapsed.tv_sec = end.tv_sec - closure->start.tv_sec;
1064 total = elapsed.tv_sec + ((double) elapsed.tv_usec / 1e6);
1068 closure->start = end;
1069 snprintf (text, sizeof text, "Frames per second: %.2f", 200 / total);
1070 gtk_label_set_text (GTK_LABEL (closure->fps_label), text);
1077 main (int argc, char *argv[])
1082 gtk_init (&argc, &argv);
1083 model_init_rope (&model);
1084 create_window (&closure);
1086 gtk_widget_show_all (gtk_widget_get_toplevel (closure.drawing_area));
1087 closure.model = &model;
1088 closure.frame_count = 0;
1089 gettimeofday (&closure.start, NULL);
1090 g_timeout_add (100, timeout_callback, &closure);