]> git.cworth.org Git - akamaru/blob - akamaru.c
629d01f87b108e42ec18698aef41722129259751
[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_accumulate_forces (Model *model)
191 {
192   int i;
193
194   for (i = 0; i < model->num_objects; i++) {
195     model->objects[i].force.x = 0;
196     model->objects[i].force.y = 3;
197   }
198 }
199
200 static void
201 model_integrate (Model *model, double step)
202 {
203   double x, y;
204   Object *o;
205   int i;
206
207   for (i = 0; i < model->num_objects; i++) {
208     o = &model->objects[i];
209     x = o->position.x;
210     y = o->position.y;
211     
212     o->position.x =
213       x + (x - o->previous_position.x) + o->force.x * step * step;
214     o->position.y =
215       y + (y - o->previous_position.y) + o->force.y * step * step;
216
217     o->previous_position.x = x;
218     o->previous_position.y = y;
219   }
220 }
221
222 static void
223 model_constrain (Model *model, double step)
224 {
225   double dx, dy, x, y, distance, fraction;
226   int i;
227
228   /* Anchor object constraint. */
229   if (model->anchor_object != NULL) {
230     model->anchor_object->position.x = model->anchor_position.x;
231     model->anchor_object->position.y = model->anchor_position.y;
232     model->anchor_object->previous_position.x = model->anchor_position.x;
233     model->anchor_object->previous_position.y = model->anchor_position.y;
234   }
235
236   /* FIXME: this should be "is point inside box" test instead. Figure
237    * out from previous_position which edge the point has passed
238    * through and reflect in that. */
239   for (i = 0; i < model->num_objects; i++) {
240     x = model->objects[i].position.x;
241     y = model->objects[i].position.y;
242     if (box_top - edge_fuzz <= y &&
243         model->objects[i].previous_position.y <= box_top + edge_fuzz &&
244         x < box_left) {
245       model->objects[i].position.y = box_top - (y - box_top) * elasticity;
246       model->objects[i].previous_position.y =
247         box_top - (model->objects[i].previous_position.y - box_top) * elasticity;
248     }
249   }
250
251   /* Ground collision detection constraints.  This puts a ground level
252    * in to make sure the points don't fall off the screen. */
253   for (i = 0; i < model->num_objects; i++) {
254     x = model->objects[i].position.x;
255     y = model->objects[i].position.y;
256
257     if (model->objects[i].position.y > ground_level) {
258       model->objects[i].position.y =
259         ground_level - (model->objects[i].position.y - ground_level) * elasticity;
260       model->objects[i].previous_position.y =
261         ground_level - (model->objects[i].previous_position.y - ground_level) * elasticity;
262
263       /* Friction on impact */
264       model->objects[i].position.x =
265         model->objects[i].position.x * (1 - ground_friction) +
266         model->objects[i].previous_position.x * ground_friction;
267     }
268   }
269
270   /* Offset constraints. */
271   for (i = 0; i < model->num_offsets; i++) {
272     x = (model->offsets[i].a->position.x + model->offsets[i].b->position.x) / 2;
273     y = (model->offsets[i].a->position.y + model->offsets[i].b->position.y) / 2;
274     model->offsets[i].a->position.x = x - model->offsets[i].dx / 2;
275     model->offsets[i].a->position.y = y - model->offsets[i].dy / 2;
276     model->offsets[i].b->position.x = x + model->offsets[i].dx / 2;
277     model->offsets[i].b->position.y = y + model->offsets[i].dy / 2;
278   }
279
280 #if 1
281   /* Stick constraints. */
282   for (i = 0; i < model->num_sticks; i++) {
283     x = model->sticks[i].a->position.x;
284     y = model->sticks[i].a->position.y;
285     dx = model->sticks[i].b->position.x - x;
286     dy = model->sticks[i].b->position.y - y;
287     distance = sqrt (dx * dx + dy * dy);
288     fraction = (distance - model->sticks[i].length) / distance / 2;
289     model->sticks[i].a->position.x = x + dx * fraction;
290     model->sticks[i].a->position.y = y + dy * fraction;
291     model->sticks[i].b->position.x = x + dx * (1 - fraction);
292     model->sticks[i].b->position.y = y + dy * (1 - fraction);
293   }
294 #else
295   /* Stick constraints, without square roots. */
296   squared = stick_length * stick_length;
297   for (i = 0; i < model->num_objects - 1; i++) {
298     j = i + 1;
299     x = model->objects[i].position.x;
300     y = model->objects[i].position.y;
301     dx = model->objects[j].position.x - x;
302     dy = model->objects[j].position.y - y;
303     fraction = squared / (dx * dx + dy * dy + squared) - 0.5;
304     model->objects[i].position.x = x + dx * fraction;
305     model->objects[i].position.y = y + dy * fraction;
306     model->objects[j].position.x = x + dx * (1 - fraction);
307     model->objects[j].position.y = y + dy * (1 - fraction);
308   }
309 #endif
310 }
311
312 static void
313 model_step (Model *model, double delta_t)
314 {
315   int i;
316
317   model_accumulate_forces (model);
318   model_integrate (model, delta_t);
319
320   for (i = 0; i < 5; i++)
321     model_constrain (model, delta_t);
322
323   model->theta += delta_t;
324 }
325
326 static double
327 object_distance (Object *object, double x, double y)
328 {
329   double dx, dy;
330
331   dx = object->position.x - x;
332   dy = object->position.y - y;
333
334   return sqrt (dx*dx + dy*dy);
335 }
336
337 static Object *
338 model_find_nearest (Model *model, double x, double y)
339 {
340   Object *object;
341   double distance, min_distance;
342   int i;
343
344   for (i = 0; i < model->num_objects; i++) {
345     distance = object_distance (&model->objects[i], x, y);
346     if (i == 0 || distance < min_distance) {
347       min_distance = distance;
348       object = &model->objects[i];
349     }
350   }
351
352   return object;
353 }
354
355 typedef struct _Color Color;
356 struct _Color {
357   double red, green, blue;
358 };
359
360 static void
361 draw_sticks (cairo_t *cr,
362              Model   *model,
363              Color   *color)
364 {
365   int i;
366
367   cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
368   cairo_new_path (cr);
369   cairo_set_line_width (cr, 2);
370   cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
371   cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
372
373   for (i = 0; i < model->num_sticks; i++) {
374     cairo_move_to (cr,
375                    model->sticks[i].a->position.x,
376                    model->sticks[i].a->position.y);
377     cairo_line_to (cr,
378                    model->sticks[i].b->position.x,
379                    model->sticks[i].b->position.y);
380   }
381
382   cairo_stroke (cr);
383 }
384
385 static void
386 draw_offsets (cairo_t *cr,
387               Model   *model,
388               Color   *color)
389 {
390   int i;
391
392   cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
393   cairo_new_path (cr);
394   cairo_set_line_width (cr, 4);
395   cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
396   cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
397
398   for (i = 0; i < model->num_offsets; i++) {
399     cairo_move_to (cr,
400                    model->offsets[i].a->position.x,
401                    model->offsets[i].a->position.y);
402     cairo_line_to (cr,
403                    model->offsets[i].b->position.x,
404                    model->offsets[i].b->position.y);
405   }
406
407   cairo_stroke (cr);
408 }
409
410 static void
411 draw_constraints (cairo_t *cr,
412                   Model   *model,
413                   Color   *color)
414 {
415   cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
416
417   cairo_move_to (cr, 0, ground_level);
418   cairo_line_to (cr, 1500, ground_level);
419   cairo_line_to (cr, 1500, ground_level + 10);
420   cairo_line_to (cr, 0, ground_level + 10);
421   cairo_close_path (cr);
422
423   cairo_move_to (cr, 0, box_top);
424   cairo_line_to (cr, box_left, box_top);
425   cairo_line_to (cr, box_left, box_bottom);
426   cairo_line_to (cr, 0, box_bottom);
427   cairo_close_path (cr);
428
429   cairo_fill (cr);
430 }
431
432 static void
433 draw_objects (cairo_t *cr, Model *model, Color *color)
434 {
435   int i;
436
437   for (i = 0; i < model->num_objects; i++) {
438   }
439 }
440
441 static Color blue = { 0, 0, 1 };
442 static Color red = { 1, 0, 0 };
443 static Color black = { 0, 0, 0 };
444 static Color white = { 1, 1, 1 };
445
446 static gboolean
447 sproing_expose_event (GtkWidget      *widget,
448                       GdkEventExpose *event,
449                       gpointer        data)
450 {
451   Model *model = data;
452   cairo_t *cr;
453
454   cr = gdk_cairo_create (widget->window);
455
456   cairo_set_source_rgb (cr, 1, 1, 1);
457   cairo_paint (cr);
458
459   draw_constraints (cr, model, &red);
460   draw_sticks (cr, model, &black);
461   draw_offsets (cr, model, &blue);
462   draw_objects (cr, model, &white);
463
464   cairo_destroy (cr);
465
466   return TRUE;
467 }
468
469 static gboolean
470 sproing_button_press_event (GtkWidget      *widget,
471                             GdkEventButton *event,
472                             gpointer        data)
473 {
474   Model *model = data;
475
476   if (event->button != 1)
477     return TRUE;
478
479   model->anchor_position.x = event->x;
480   model->anchor_position.y = event->y;
481   model->anchor_object = model_find_nearest (model, event->x, event->y);
482
483   return TRUE;
484 }
485
486 static gboolean
487 sproing_button_release_event (GtkWidget      *widget,
488                               GdkEventButton *event,
489                               gpointer        data)
490 {
491   Model *model = data;
492
493   if ((event->state & GDK_BUTTON1_MASK) == 0)
494     return TRUE;
495
496   model->anchor_object = NULL;
497
498   return TRUE;
499 }
500
501 static gboolean
502 sproing_motion_notify_event (GtkWidget      *widget,
503                              GdkEventMotion *event,
504                              gpointer        data)
505 {
506   Model *model = data;
507   int x, y;
508   GdkModifierType state;
509
510   gdk_window_get_pointer (event->window, &x, &y, &state);
511
512   model->anchor_position.x = x + 0.5;
513   model->anchor_position.y = y + 0.5;
514
515   return TRUE;
516 }
517
518 static void
519 spring_constant_changed (GtkSpinButton *spinbutton, gpointer user_data)
520 {
521   Model *model = user_data;
522
523   model->k = gtk_spin_button_get_value (spinbutton);
524 }
525
526 static void
527 friction_changed (GtkSpinButton *spinbutton, gpointer user_data)
528 {
529   Model *model = user_data;
530
531   model->friction = gtk_spin_button_get_value (spinbutton);
532 }
533
534 static GtkWidget *
535 create_spinners (Model *model)
536 {
537   GtkWidget *hbox;
538   GtkWidget *spinner, *label;
539
540   hbox = gtk_hbox_new (FALSE, 8);
541
542   label = gtk_label_new_with_mnemonic ("_Spring constant:");
543   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
544   spinner = gtk_spin_button_new_with_range  (0.05, 30.00, 0.05);
545   gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinner);
546   gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
547   gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), model->k);
548   g_signal_connect (spinner, "value-changed",
549                     G_CALLBACK (spring_constant_changed), model);
550
551   label = gtk_label_new_with_mnemonic ("_Friction:");
552   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
553   spinner = gtk_spin_button_new_with_range  (0.05, 15.00, 0.05);
554   gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinner);
555   gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
556   gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), model->friction);
557   g_signal_connect (spinner, "value-changed",
558                     G_CALLBACK (friction_changed), model);
559
560   return hbox;
561 }
562
563 static GtkWidget *
564 create_window (Model *model)
565 {
566   GtkWidget *window;
567   GtkWidget *frame;
568   GtkWidget *vbox;
569   GtkWidget *da;
570   GtkWidget *spinners;
571
572   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
573   gtk_window_set_title (GTK_WINDOW (window), "Drawing Area");
574
575   g_signal_connect (window, "destroy",
576                     G_CALLBACK (gtk_main_quit), &window);
577
578   gtk_container_set_border_width (GTK_CONTAINER (window), 8);
579
580   vbox = gtk_vbox_new (FALSE, 8);
581   gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
582   gtk_container_add (GTK_CONTAINER (window), vbox);
583
584   /*
585    * Create the drawing area
586    */
587       
588   frame = gtk_frame_new (NULL);
589   gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
590   gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
591       
592   da = gtk_drawing_area_new ();
593   /* set a minimum size */
594   gtk_widget_set_size_request (da, 600, 500);
595
596   gtk_container_add (GTK_CONTAINER (frame), da);
597
598   /* Signals used to handle backing pixmap */
599       
600   g_signal_connect (da, "expose_event",
601                     G_CALLBACK (sproing_expose_event), model);
602       
603   /* Event signals */
604       
605   g_signal_connect (da, "motion_notify_event",
606                     G_CALLBACK (sproing_motion_notify_event), model);
607   g_signal_connect (da, "button_press_event",
608                     G_CALLBACK (sproing_button_press_event), model);
609   g_signal_connect (da, "button_release_event",
610                     G_CALLBACK (sproing_button_release_event), model);
611
612   /* Ask to receive events the drawing area doesn't normally
613    * subscribe to
614    */
615   gtk_widget_set_events (da, gtk_widget_get_events (da)
616                          | GDK_LEAVE_NOTIFY_MASK
617                          | GDK_BUTTON_PRESS_MASK
618                          | GDK_BUTTON_RELEASE_MASK
619                          | GDK_POINTER_MOTION_MASK
620                          | GDK_POINTER_MOTION_HINT_MASK);
621
622   spinners = create_spinners (model);
623   gtk_box_pack_start (GTK_BOX (vbox), spinners, FALSE, FALSE, 0);
624
625   return da;
626 }
627
628 typedef struct _Closure Closure;
629 struct _Closure {
630   GtkWidget *drawing_area;
631   Model *model;
632   int i;
633 };
634
635 static gint
636 timeout_callback (gpointer data)
637 {
638   Closure *closure = data;
639   int i;
640
641   for (i = 0; i < 3; i++)
642     model_step (closure->model, 0.5);
643
644   closure->i++;
645   if (closure->i == 1) {
646     gtk_widget_queue_draw (closure->drawing_area);
647     closure->i = 0;
648   }
649
650   return TRUE;
651 }
652
653 int
654 main (int argc, char *argv[])
655 {
656   Closure closure;
657   Model model;
658
659   gtk_init (&argc, &argv);
660   model_init_curtain (&model);
661   closure.drawing_area = create_window (&model);
662   closure.i = 0;
663   gtk_widget_show_all (gtk_widget_get_toplevel (closure.drawing_area));
664   closure.model = &model;
665   g_timeout_add (100, timeout_callback, &closure);
666   gtk_main ();
667
668   return 0;
669 }