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