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