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