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