]> git.cworth.org Git - akamaru/blob - akamaru.c
fe14d2009e4e7e9c3da3f9630a067ac10ae71250
[akamaru] / akamaru.c
1 /*                                           -*- mode: c; c-basic-offset: 2 -*-
2  * To compile:
3  *
4  *     gcc -Wall -g $(pkg-config --cflags --libs gtk+-2.0 cairo) \
5  *             akamaru.c -o akamaru
6  *
7  * See:
8  *
9  *     http://en.wikipedia.org/wiki/Verlet_integration
10  *     http://www.teknikus.dk/tj/gdc2001.htm
11  *
12  * TODO:
13  *
14  *     - Add code to add boxes
15  *     - Add circle object
16  */
17
18 #include <gtk/gtk.h>
19 #include <cairo.h>
20 #include <cairo-xlib.h>
21 #include <gdk/gdkx.h>
22 #include <stdlib.h>
23 #include <math.h>
24
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;
29
30 typedef struct _xy_pair Point;
31 typedef struct _xy_pair Vector;
32 struct _xy_pair {
33   double x, y;
34 };
35
36 typedef struct _Object Object;
37 typedef struct _Stick Stick;
38 typedef struct _String String;
39 typedef struct _Offset Offset;
40 typedef struct _Model Model;
41
42 struct _Object {
43   Vector force;
44
45   Point position;
46   Point previous_position;
47   Vector velocity;
48
49   double mass;
50   double theta;
51 };
52
53 struct _Stick {
54   Object *a, *b;
55   int length;
56 };
57
58 struct _String {
59   Object *a, *b;
60   int length;
61 };
62
63 struct _Offset {
64   Object *a, *b;
65   int dx, dy;
66 };
67
68 struct _Model {
69   int num_objects;
70   Object *objects;
71   int num_sticks;
72   Stick *sticks;
73   int num_strings;
74   String *strings;
75   int num_offsets;
76   Offset *offsets;
77   double k;
78   double friction;
79
80   Object *anchor_object;
81   Vector anchor_position;
82
83   double theta;
84 };
85
86 static void
87 model_init_snake (Model *model)
88 {
89   const int num_objects = 20;
90   const int num_sticks = num_objects * 2 - 3;
91   int i;
92
93   model->objects = g_new (Object, num_objects);
94   model->num_objects = num_objects;
95   model->sticks = g_new (Stick, num_sticks);
96   model->num_sticks = num_sticks;
97   model->strings = NULL;
98   model->num_strings = 0;
99   model->offsets = NULL;
100   model->num_offsets = 0;
101
102   for (i = 0; i < num_objects; i++) {
103     model->objects[i].position.x = random() % 200 + 20;
104     model->objects[i].position.y = random() % 200 + 20;
105     model->objects[i].previous_position.x = random() % 200 + 20;
106     model->objects[i].previous_position.y = random() % 200 + 20;
107
108     if (i + 1 < num_objects) {
109       model->sticks[i * 2].a = &model->objects[i];
110       model->sticks[i * 2].b = &model->objects[i + 1];
111       model->sticks[i * 2].length = random() % 20 + 20;
112     }
113     if (i + 2 < num_objects) {
114       model->sticks[i * 2 + 1].a = &model->objects[i];
115       model->sticks[i * 2 + 1].b = &model->objects[i + 2];
116       model->sticks[i * 2 + 1].length = random() % 20 + 20;
117     }
118   }
119
120   model->anchor_object = NULL;
121 }
122
123 static void
124 model_init_rope (Model *model)
125 {
126   const int num_objects = 20;
127   const int num_sticks = num_objects - 1;
128   const int stick_length = 5;
129   int i;
130
131   model->objects = g_new (Object, num_objects);
132   model->num_objects = num_objects;
133   model->sticks = g_new (Stick, num_sticks);
134   model->num_sticks = num_sticks;
135   model->strings = NULL;
136   model->num_strings = 0;
137   model->offsets = NULL;
138   model->num_offsets = 0;
139
140   for (i = 0; i < num_objects; i++) {
141     model->objects[i].position.x = 200;
142     model->objects[i].position.y = 40 + i * stick_length;
143     model->objects[i].previous_position.x = 200;
144     model->objects[i].previous_position.y = 40 + i * stick_length;
145
146     if (i + 1 < num_objects) {
147       model->sticks[i].a = &model->objects[i];
148       model->sticks[i].b = &model->objects[i + 1];
149       model->sticks[i].length = stick_length;
150     }
151   }
152
153   model->anchor_object = NULL;
154 }
155
156 static void
157 model_init_curtain (Model *model)
158 {
159   const int num_ropes = 5;
160   const int num_rope_objects = 15;
161   const int num_objects = num_ropes * num_rope_objects;
162   const int num_sticks = num_ropes * (num_rope_objects - 1);
163   const int stick_length = 10;
164   const int rope_offset = 30;
165   double x, y;
166   int i, j, index, stick_index;
167
168   model->objects = g_new (Object, num_objects);
169   model->num_objects = num_objects;
170   model->sticks = g_new (Stick, num_sticks);
171   model->num_sticks = num_sticks;
172   model->strings = NULL;
173   model->num_strings = 0;
174   model->offsets = g_new (Offset, num_ropes - 1);
175   model->num_offsets = num_ropes - 1;
176
177   for (i = 0; i < num_ropes; i++) {
178     for (j = 0; j < num_rope_objects; j++) {
179       x = 200 + i * rope_offset;
180       y = 40 + j * stick_length;
181       index = i * num_rope_objects + j;
182       model->objects[index].position.x = x;
183       model->objects[index].position.y = y;
184       model->objects[index].previous_position.x = x;
185       model->objects[index].previous_position.y = y;
186
187       if (j + 1 < num_rope_objects) {
188         stick_index = i * (num_rope_objects - 1) + j;
189         model->sticks[stick_index].a = &model->objects[index];
190         model->sticks[stick_index].b = &model->objects[index + 1];
191         model->sticks[stick_index].length = stick_length;
192       }
193     }
194
195     if (i + 1 < num_ropes) {
196       model->offsets[i].a = &model->objects[i * num_rope_objects];
197       model->offsets[i].b = &model->objects[(i + 1) * num_rope_objects];
198       model->offsets[i].dx = rope_offset;
199       model->offsets[i].dy = 0;
200     }
201   }
202
203   model->anchor_object = NULL;
204 }
205
206 static void
207 model_init_grid (Model *model)
208 {
209   const int num_ropes = 4;
210   const int num_rope_objects = 4;
211   const int num_objects = num_ropes * num_rope_objects;
212   const int num_strings = num_ropes * (num_rope_objects - 1) +
213     (num_ropes - 1) * num_rope_objects;
214   const int string_length = 30;
215   const int rope_offset = 30;
216   double x, y;
217   int i, j, index, string_index;
218
219   model->objects = g_new (Object, num_objects);
220   model->num_objects = num_objects;
221   model->sticks = NULL;
222   model->num_sticks = 0;
223   model->strings = g_new (String, num_strings);
224   model->num_strings = num_strings;
225   model->offsets = g_new (Offset, num_ropes - 1);
226   model->num_offsets = num_ropes - 1;
227
228   for (i = 0; i < num_ropes; i++) {
229     for (j = 0; j < num_rope_objects; j++) {
230       x = 200 + i * rope_offset;
231       y = 40 + j * string_length;
232       index = i * num_rope_objects + j;
233       model->objects[index].position.x = x;
234       model->objects[index].position.y = y;
235       model->objects[index].previous_position.x = x;
236       model->objects[index].previous_position.y = y;
237
238       if (i + 1 < num_ropes) {
239         string_index = i * num_rope_objects + j;
240         model->strings[string_index].a = &model->objects[index];
241         model->strings[string_index].b = &model->objects[index + num_rope_objects];
242         model->strings[string_index].length = string_length;
243       }
244
245       if (j + 1 < num_rope_objects) {
246         string_index =
247           (num_ropes - 1) * num_rope_objects + i * (num_rope_objects - 1) + j;
248         model->strings[string_index].a = &model->objects[index];
249         model->strings[string_index].b = &model->objects[index + 1];
250         model->strings[string_index].length = string_length;
251       }
252     }
253
254     if (i + 1 < num_ropes) {
255       model->offsets[i].a = &model->objects[i * num_rope_objects];
256       model->offsets[i].b = &model->objects[(i + 1) * num_rope_objects];
257       model->offsets[i].dx = rope_offset;
258       model->offsets[i].dy = 0;
259     }
260   }
261
262   model->anchor_object = NULL;
263 }
264
265 static void
266 model_fini (Model *model)
267 {
268   g_free (model->objects);
269   model->objects = NULL;
270   model->num_objects = 0;
271   g_free (model->sticks);
272   model->sticks = NULL;
273   model->sticks = 0;
274   g_free (model->strings);
275   model->strings = NULL;
276   model->strings = 0;
277   g_free (model->offsets);
278   model->offsets = NULL;
279   model->num_offsets = 0;
280 }
281
282 static void
283 model_accumulate_forces (Model *model)
284 {
285   int i;
286
287   for (i = 0; i < model->num_objects; i++) {
288     model->objects[i].force.x = 0;
289     model->objects[i].force.y = 3;
290   }
291 }
292
293 static void
294 model_integrate (Model *model, double step)
295 {
296   double x, y;
297   Object *o;
298   int i;
299
300   for (i = 0; i < model->num_objects; i++) {
301     o = &model->objects[i];
302     x = o->position.x;
303     y = o->position.y;
304     
305     o->position.x =
306       x + (x - o->previous_position.x) + o->force.x * step * step;
307     o->position.y =
308       y + (y - o->previous_position.y) + o->force.y * step * step;
309
310     o->previous_position.x = x;
311     o->previous_position.y = y;
312   }
313 }
314
315 static void
316 model_constrain (Model *model, double step)
317 {
318   double dx, dy, x, y, distance, fraction;
319   int i;
320
321   /* Anchor object constraint. */
322   if (model->anchor_object != NULL) {
323     model->anchor_object->position.x = model->anchor_position.x;
324     model->anchor_object->position.y = model->anchor_position.y;
325     model->anchor_object->previous_position.x = model->anchor_position.x;
326     model->anchor_object->previous_position.y = model->anchor_position.y;
327   }
328
329   /* FIXME: this should be "is point inside box" test instead. Figure
330    * out from previous_position which edge the point has passed
331    * through and reflect in that. */
332   for (i = 0; i < model->num_objects; i++) {
333     x = model->objects[i].position.x;
334     y = model->objects[i].position.y;
335     if (box_top - edge_fuzz <= y &&
336         model->objects[i].previous_position.y <= box_top + edge_fuzz &&
337         x < box_left) {
338       model->objects[i].position.y = box_top - (y - box_top) * elasticity;
339       model->objects[i].previous_position.y =
340         box_top - (model->objects[i].previous_position.y - box_top) * elasticity;
341     }
342   }
343
344   /* Ground collision detection constraints.  This puts a ground level
345    * in to make sure the points don't fall off the screen. */
346   for (i = 0; i < model->num_objects; i++) {
347     x = model->objects[i].position.x;
348     y = model->objects[i].position.y;
349
350     if (model->objects[i].position.y > ground_level) {
351       model->objects[i].position.y =
352         ground_level - (model->objects[i].position.y - ground_level) * elasticity;
353       model->objects[i].previous_position.y =
354         ground_level - (model->objects[i].previous_position.y - ground_level) * elasticity;
355
356       /* Friction on impact */
357       model->objects[i].position.x =
358         model->objects[i].position.x * (1 - ground_friction) +
359         model->objects[i].previous_position.x * ground_friction;
360     }
361   }
362
363   /* Offset constraints. */
364   for (i = 0; i < model->num_offsets; i++) {
365     x = (model->offsets[i].a->position.x + model->offsets[i].b->position.x) / 2;
366     y = (model->offsets[i].a->position.y + model->offsets[i].b->position.y) / 2;
367     model->offsets[i].a->position.x = x - model->offsets[i].dx / 2;
368     model->offsets[i].a->position.y = y - model->offsets[i].dy / 2;
369     model->offsets[i].b->position.x = x + model->offsets[i].dx / 2;
370     model->offsets[i].b->position.y = y + model->offsets[i].dy / 2;
371   }
372
373   /* String constraints. */
374   for (i = 0; i < model->num_strings; i++) {
375     x = model->strings[i].a->position.x;
376     y = model->strings[i].a->position.y;
377     dx = model->strings[i].b->position.x - x;
378     dy = model->strings[i].b->position.y - y;
379     distance = sqrt (dx * dx + dy * dy);
380     if (distance < model->strings[i].length)
381       continue;
382     fraction = (distance - model->strings[i].length) / distance / 2;
383     model->strings[i].a->position.x = x + dx * fraction;
384     model->strings[i].a->position.y = y + dy * fraction;
385     model->strings[i].b->position.x = x + dx * (1 - fraction);
386     model->strings[i].b->position.y = y + dy * (1 - fraction);
387   }
388
389 #if 1
390   /* Stick constraints. */
391   for (i = 0; i < model->num_sticks; i++) {
392     x = model->sticks[i].a->position.x;
393     y = model->sticks[i].a->position.y;
394     dx = model->sticks[i].b->position.x - x;
395     dy = model->sticks[i].b->position.y - y;
396     distance = sqrt (dx * dx + dy * dy);
397     fraction = (distance - model->sticks[i].length) / distance / 2;
398     model->sticks[i].a->position.x = x + dx * fraction;
399     model->sticks[i].a->position.y = y + dy * fraction;
400     model->sticks[i].b->position.x = x + dx * (1 - fraction);
401     model->sticks[i].b->position.y = y + dy * (1 - fraction);
402   }
403 #else
404   /* Stick constraints, without square roots. */
405   squared = stick_length * stick_length;
406   for (i = 0; i < model->num_objects - 1; i++) {
407     j = i + 1;
408     x = model->objects[i].position.x;
409     y = model->objects[i].position.y;
410     dx = model->objects[j].position.x - x;
411     dy = model->objects[j].position.y - y;
412     fraction = squared / (dx * dx + dy * dy + squared) - 0.5;
413     model->objects[i].position.x = x + dx * fraction;
414     model->objects[i].position.y = y + dy * fraction;
415     model->objects[j].position.x = x + dx * (1 - fraction);
416     model->objects[j].position.y = y + dy * (1 - fraction);
417   }
418 #endif
419 }
420
421 static void
422 model_step (Model *model, double delta_t)
423 {
424   int i;
425
426   model_accumulate_forces (model);
427   model_integrate (model, delta_t);
428
429   for (i = 0; i < 35; i++)
430     model_constrain (model, delta_t);
431
432   model->theta += delta_t;
433 }
434
435 static double
436 object_distance (Object *object, double x, double y)
437 {
438   double dx, dy;
439
440   dx = object->position.x - x;
441   dy = object->position.y - y;
442
443   return sqrt (dx*dx + dy*dy);
444 }
445
446 static Object *
447 model_find_nearest (Model *model, double x, double y)
448 {
449   Object *object;
450   double distance, min_distance;
451   int i;
452
453   for (i = 0; i < model->num_objects; i++) {
454     distance = object_distance (&model->objects[i], x, y);
455     if (i == 0 || distance < min_distance) {
456       min_distance = distance;
457       object = &model->objects[i];
458     }
459   }
460
461   return object;
462 }
463
464 typedef struct _Color Color;
465 struct _Color {
466   double red, green, blue;
467 };
468
469 static void
470 draw_sticks (cairo_t *cr,
471              Model   *model,
472              Color   *color)
473 {
474   int i;
475
476   cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
477   cairo_new_path (cr);
478   cairo_set_line_width (cr, 2);
479   cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
480   cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
481
482   for (i = 0; i < model->num_sticks; i++) {
483     cairo_move_to (cr,
484                    model->sticks[i].a->position.x,
485                    model->sticks[i].a->position.y);
486     cairo_line_to (cr,
487                    model->sticks[i].b->position.x,
488                    model->sticks[i].b->position.y);
489   }
490
491   cairo_stroke (cr);
492 }
493
494 static void
495 draw_strings (cairo_t *cr,
496               Model   *model,
497               Color   *color)
498 {
499   int i;
500
501   cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
502   cairo_new_path (cr);
503   cairo_set_line_width (cr, 1);
504   cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
505   cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
506
507   for (i = 0; i < model->num_strings; i++) {
508     cairo_move_to (cr,
509                    model->strings[i].a->position.x,
510                    model->strings[i].a->position.y);
511     cairo_line_to (cr,
512                    model->strings[i].b->position.x,
513                    model->strings[i].b->position.y);
514   }
515
516   cairo_stroke (cr);
517 }
518
519 static void
520 draw_offsets (cairo_t *cr,
521               Model   *model,
522               Color   *color)
523 {
524   int i;
525
526   cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
527   cairo_new_path (cr);
528   cairo_set_line_width (cr, 4);
529   cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
530   cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
531
532   for (i = 0; i < model->num_offsets; i++) {
533     cairo_move_to (cr,
534                    model->offsets[i].a->position.x,
535                    model->offsets[i].a->position.y);
536     cairo_line_to (cr,
537                    model->offsets[i].b->position.x,
538                    model->offsets[i].b->position.y);
539   }
540
541   cairo_stroke (cr);
542 }
543
544 static void
545 draw_constraints (cairo_t *cr,
546                   Model   *model,
547                   Color   *color)
548 {
549   cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
550
551   cairo_move_to (cr, 0, ground_level);
552   cairo_line_to (cr, 1500, ground_level);
553   cairo_line_to (cr, 1500, ground_level + 10);
554   cairo_line_to (cr, 0, ground_level + 10);
555   cairo_close_path (cr);
556
557   cairo_move_to (cr, 0, box_top);
558   cairo_line_to (cr, box_left, box_top);
559   cairo_line_to (cr, box_left, box_bottom);
560   cairo_line_to (cr, 0, box_bottom);
561   cairo_close_path (cr);
562
563   cairo_fill (cr);
564 }
565
566 static void
567 draw_objects (cairo_t *cr, Model *model, Color *color)
568 {
569   int i;
570
571   for (i = 0; i < model->num_objects; i++) {
572   }
573 }
574
575 static Color blue = { 0, 0, 1 };
576 static Color green = { 0, 1, 0 };
577 static Color red = { 1, 0, 0 };
578 static Color black = { 0, 0, 0 };
579 static Color white = { 1, 1, 1 };
580
581 static gboolean
582 expose_event (GtkWidget      *widget,
583               GdkEventExpose *event,
584               gpointer        data)
585 {
586   Model *model = data;
587   cairo_t *cr;
588
589   cr = gdk_cairo_create (widget->window);
590
591   cairo_set_source_rgb (cr, 1, 1, 1);
592   cairo_paint (cr);
593
594   draw_constraints (cr, model, &red);
595   draw_sticks (cr, model, &black);
596   draw_strings (cr, model, &green);
597   draw_offsets (cr, model, &blue);
598   draw_objects (cr, model, &white);
599
600   cairo_destroy (cr);
601
602   return TRUE;
603 }
604
605 static gboolean
606 button_press_event (GtkWidget      *widget,
607                     GdkEventButton *event,
608                     gpointer        data)
609 {
610   Model *model = data;
611
612   if (event->button != 1)
613     return TRUE;
614
615   model->anchor_position.x = event->x;
616   model->anchor_position.y = event->y;
617   model->anchor_object = model_find_nearest (model, event->x, event->y);
618
619   return TRUE;
620 }
621
622 static gboolean
623 button_release_event (GtkWidget      *widget,
624                       GdkEventButton *event,
625                       gpointer        data)
626 {
627   Model *model = data;
628
629   if ((event->state & GDK_BUTTON1_MASK) == 0)
630     return TRUE;
631
632   model->anchor_object = NULL;
633
634   return TRUE;
635 }
636
637 static gboolean
638 motion_notify_event (GtkWidget      *widget,
639                      GdkEventMotion *event,
640                      gpointer        data)
641 {
642   Model *model = data;
643   int x, y;
644   GdkModifierType state;
645
646   gdk_window_get_pointer (event->window, &x, &y, &state);
647
648   model->anchor_position.x = x + 0.5;
649   model->anchor_position.y = y + 0.5;
650
651   return TRUE;
652 }
653
654 typedef void (*ModelInitFunc) (Model *model);
655
656 static void
657 model_changed (GtkComboBox *combo, gpointer user_data)
658 {
659   Model *model = user_data;
660   GtkTreeIter iter;
661   GtkTreeModel *tree_model;
662   ModelInitFunc init;
663   char *name;
664
665   tree_model = gtk_combo_box_get_model (combo);
666   if (!gtk_combo_box_get_active_iter (combo, &iter))
667     return;
668
669   gtk_tree_model_get (tree_model, &iter, 0, &name, 1, &init, -1);
670
671   model_fini (model);
672   (*init) (model);
673 }
674
675 static GtkTreeModel *
676 create_model_store (void)
677 {
678   static struct {
679     const char *name;
680     ModelInitFunc init;
681   } models[] = {
682     { "Rope", model_init_rope },
683     { "Snake", model_init_snake },
684     { "Curtain", model_init_curtain },
685     { "Grid", model_init_grid }
686   };
687
688   GtkTreeIter iter;
689   GtkTreeStore *store;
690   gint i;
691
692   store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
693
694   for (i = 0; i < G_N_ELEMENTS(models); i++) {
695     gtk_tree_store_append (store, &iter, NULL);
696     gtk_tree_store_set (store, &iter,
697                         0, models[i].name, 1, models[i].init, -1);
698  }
699   
700   return GTK_TREE_MODEL (store);
701
702 }
703
704 static GtkWidget *
705 create_model_combo (Model *model)
706 {
707   GtkWidget *hbox;
708   GtkWidget *combo, *label;
709   GtkTreeModel *store;
710   GtkCellRenderer *renderer;
711
712   hbox = gtk_hbox_new (FALSE, 8);
713
714   label = gtk_label_new_with_mnemonic ("_Model:");
715   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
716
717   store = create_model_store ();
718   combo = gtk_combo_box_new_with_model (store);
719   gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
720   g_object_unref (store);
721
722   renderer = gtk_cell_renderer_text_new ();
723   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE);
724   gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer,
725                                   "text", 0,
726                                   NULL);
727
728   gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
729   gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0);
730   g_signal_connect (combo, "changed",
731                     G_CALLBACK (model_changed), model);
732
733   return hbox;
734 }
735
736 static GtkWidget *
737 create_window (Model *model)
738 {
739   GtkWidget *window;
740   GtkWidget *frame;
741   GtkWidget *vbox;
742   GtkWidget *da;
743   GtkWidget *model_combo;
744
745   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
746   gtk_window_set_title (GTK_WINDOW (window), "Drawing Area");
747
748   g_signal_connect (window, "destroy",
749                     G_CALLBACK (gtk_main_quit), &window);
750
751   gtk_container_set_border_width (GTK_CONTAINER (window), 8);
752
753   vbox = gtk_vbox_new (FALSE, 8);
754   gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
755   gtk_container_add (GTK_CONTAINER (window), vbox);
756
757   /*
758    * Create the drawing area
759    */
760       
761   frame = gtk_frame_new (NULL);
762   gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
763   gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
764       
765   da = gtk_drawing_area_new ();
766   /* set a minimum size */
767   gtk_widget_set_size_request (da, 600, 500);
768
769   gtk_container_add (GTK_CONTAINER (frame), da);
770
771   /* Signals used to handle backing pixmap */
772       
773   g_signal_connect (da, "expose_event",
774                     G_CALLBACK (expose_event), model);
775       
776   /* Event signals */
777       
778   g_signal_connect (da, "motion_notify_event",
779                     G_CALLBACK (motion_notify_event), model);
780   g_signal_connect (da, "button_press_event",
781                     G_CALLBACK (button_press_event), model);
782   g_signal_connect (da, "button_release_event",
783                     G_CALLBACK (button_release_event), model);
784
785   /* Ask to receive events the drawing area doesn't normally
786    * subscribe to
787    */
788   gtk_widget_set_events (da, gtk_widget_get_events (da)
789                          | GDK_LEAVE_NOTIFY_MASK
790                          | GDK_BUTTON_PRESS_MASK
791                          | GDK_BUTTON_RELEASE_MASK
792                          | GDK_POINTER_MOTION_MASK
793                          | GDK_POINTER_MOTION_HINT_MASK);
794
795   model_combo = create_model_combo (model);
796   gtk_box_pack_start (GTK_BOX (vbox), model_combo, FALSE, FALSE, 0);
797
798   return da;
799 }
800
801 typedef struct _Closure Closure;
802 struct _Closure {
803   GtkWidget *drawing_area;
804   Model *model;
805   int i;
806 };
807
808 static gint
809 timeout_callback (gpointer data)
810 {
811   Closure *closure = data;
812   int i;
813
814   for (i = 0; i < 3; i++)
815     model_step (closure->model, 0.5);
816
817   closure->i++;
818   if (closure->i == 1) {
819     gtk_widget_queue_draw (closure->drawing_area);
820     closure->i = 0;
821   }
822
823   return TRUE;
824 }
825
826 int
827 main (int argc, char *argv[])
828 {
829   Closure closure;
830   Model model;
831
832   gtk_init (&argc, &argv);
833   model_init_rope (&model);
834   closure.drawing_area = create_window (&model);
835   closure.i = 0;
836   gtk_widget_show_all (gtk_widget_get_toplevel (closure.drawing_area));
837   closure.model = &model;
838   g_timeout_add (100, timeout_callback, &closure);
839   gtk_main ();
840
841   return 0;
842 }