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