]> git.cworth.org Git - akamaru/blob - akamaru.c
Different stick lengths, add back rope model.
[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 _Model Model;
39
40 struct _Object {
41   Vector force;
42
43   Point position;
44   Point previous_position;
45   Vector velocity;
46
47   double mass;
48   double theta;
49 };
50
51 struct _Stick {
52   Object *a, *b;
53   int length;
54 };
55
56 struct _Model {
57   int num_objects;
58   Object *objects;
59   int num_sticks;
60   Stick *sticks;
61   double k;
62   double friction;
63
64   Object *anchor_object;
65   Vector anchor_position;
66
67   double theta;
68 };
69
70 static void
71 model_init_snake (Model *model)
72 {
73   const int num_objects = 20;
74   const int num_sticks = num_objects * 2 - 3;
75   int i;
76
77   model->objects = g_new (Object, num_objects);
78   model->num_objects = num_objects;
79   model->sticks = g_new (Stick, num_sticks);
80   model->num_sticks = num_sticks;
81
82   for (i = 0; i < num_objects; i++) {
83     model->objects[i].position.x = random() % 200 + 20;
84     model->objects[i].position.y = random() % 200 + 20;
85     model->objects[i].previous_position.x = random() % 200 + 20;
86     model->objects[i].previous_position.y = random() % 200 + 20;
87
88     if (i + 1 < num_objects) {
89       model->sticks[i * 2].a = &model->objects[i];
90       model->sticks[i * 2].b = &model->objects[i + 1];
91       model->sticks[i * 2].length = random() % 20 + 20;
92     }
93     if (i + 2 < num_objects) {
94       model->sticks[i * 2 + 1].a = &model->objects[i];
95       model->sticks[i * 2 + 1].b = &model->objects[i + 2];
96       model->sticks[i * 2 + 1].length = random() % 20 + 20;
97     }
98   }
99
100   model->anchor_object = NULL;
101 }
102
103 static void
104 model_init_rope (Model *model)
105 {
106   const int num_objects = 20;
107   const int num_sticks = num_objects - 1;
108   const int stick_length = 20;
109   int i;
110
111   model->objects = g_new (Object, num_objects);
112   model->num_objects = num_objects;
113   model->sticks = g_new (Stick, num_sticks);
114   model->num_sticks = num_sticks;
115
116   for (i = 0; i < num_objects; i++) {
117     model->objects[i].position.x = 200;
118     model->objects[i].position.y = 40 + i * stick_length;
119     model->objects[i].previous_position.x = 200;
120     model->objects[i].previous_position.y = 40 + i * stick_length;
121
122     if (i + 1 < num_objects) {
123       model->sticks[i].a = &model->objects[i];
124       model->sticks[i].b = &model->objects[i + 1];
125       model->sticks[i].length = stick_length;
126     }
127   }
128
129   model->anchor_object = NULL;
130 }
131
132 static void
133 model_accumulate_forces (Model *model)
134 {
135   int i;
136
137   for (i = 0; i < model->num_objects; i++) {
138     model->objects[i].force.x = 0;
139     model->objects[i].force.y = 3;
140   }
141 }
142
143 static void
144 model_integrate (Model *model, double step)
145 {
146   double x, y;
147   Object *o;
148   int i;
149
150   for (i = 0; i < model->num_objects; i++) {
151     o = &model->objects[i];
152     x = o->position.x;
153     y = o->position.y;
154     
155     o->position.x =
156       x + (x - o->previous_position.x) + o->force.x * step * step;
157     o->position.y =
158       y + (y - o->previous_position.y) + o->force.y * step * step;
159
160     o->previous_position.x = x;
161     o->previous_position.y = y;
162   }
163 }
164
165 static void
166 model_constrain (Model *model, double step)
167 {
168   double dx, dy, x, y, distance, fraction, squared;
169   int i;
170
171   /* Anchor object constraint. */
172   if (model->anchor_object != NULL) {
173     model->anchor_object->position.x = model->anchor_position.x;
174     model->anchor_object->position.y = model->anchor_position.y;
175     model->anchor_object->previous_position.x = model->anchor_position.x;
176     model->anchor_object->previous_position.y = model->anchor_position.y;
177   }
178
179   /* FIXME: this should be "is point inside box" test instead. Figure
180    * out from previous_position which edge the point has passed
181    * through and reflect in that. */
182   for (i = 0; i < model->num_objects; i++) {
183     x = model->objects[i].position.x;
184     y = model->objects[i].position.y;
185     if (box_top - edge_fuzz <= y &&
186         model->objects[i].previous_position.y <= box_top + edge_fuzz &&
187         x < box_left) {
188       model->objects[i].position.y = box_top - (y - box_top) * elasticity;
189       model->objects[i].previous_position.y =
190         box_top - (model->objects[i].previous_position.y - box_top) * elasticity;
191     }
192   }
193
194   /* Ground collision detection constraints.  This puts a ground level
195    * in to make sure the points don't fall off the screen. */
196   for (i = 0; i < model->num_objects; i++) {
197     x = model->objects[i].position.x;
198     y = model->objects[i].position.y;
199
200     if (model->objects[i].position.y > ground_level) {
201       model->objects[i].position.y =
202         ground_level - (model->objects[i].position.y - ground_level) * elasticity;
203       model->objects[i].previous_position.y =
204         ground_level - (model->objects[i].previous_position.y - ground_level) * elasticity;
205
206       /* Friction on impact */
207       model->objects[i].position.x =
208         model->objects[i].position.x * (1 - ground_friction) +
209         model->objects[i].previous_position.x * ground_friction;
210     }
211   }
212
213 #if 1
214   /* Stick constraints. */
215   for (i = 0; i < model->num_sticks; i++) {
216     x = model->sticks[i].a->position.x;
217     y = model->sticks[i].a->position.y;
218     dx = model->sticks[i].b->position.x - x;
219     dy = model->sticks[i].b->position.y - y;
220     distance = sqrt (dx * dx + dy * dy);
221     fraction = (distance - model->sticks[i].length) / distance / 2;
222     model->sticks[i].a->position.x = x + dx * fraction;
223     model->sticks[i].a->position.y = y + dy * fraction;
224     model->sticks[i].b->position.x = x + dx * (1 - fraction);
225     model->sticks[i].b->position.y = y + dy * (1 - fraction);
226   }
227 #else
228   /* Stick constraints, without square roots. */
229   squared = stick_length * stick_length;
230   for (i = 0; i < model->num_objects - 1; i++) {
231     j = i + 1;
232     x = model->objects[i].position.x;
233     y = model->objects[i].position.y;
234     dx = model->objects[j].position.x - x;
235     dy = model->objects[j].position.y - y;
236     fraction = squared / (dx * dx + dy * dy + squared) - 0.5;
237     model->objects[i].position.x = x + dx * fraction;
238     model->objects[i].position.y = y + dy * fraction;
239     model->objects[j].position.x = x + dx * (1 - fraction);
240     model->objects[j].position.y = y + dy * (1 - fraction);
241   }
242 #endif
243 }
244
245 static void
246 model_step (Model *model, double delta_t)
247 {
248   int i;
249
250   model_accumulate_forces (model);
251   model_integrate (model, delta_t);
252
253   for (i = 0; i < 50; i++)
254     model_constrain (model, delta_t);
255
256   model->theta += delta_t;
257 }
258
259 static double
260 object_distance (Object *object, double x, double y)
261 {
262   double dx, dy;
263
264   dx = object->position.x - x;
265   dy = object->position.y - y;
266
267   return sqrt (dx*dx + dy*dy);
268 }
269
270 static Object *
271 model_find_nearest (Model *model, double x, double y)
272 {
273   Object *object;
274   double distance, min_distance;
275   int i;
276
277   for (i = 0; i < model->num_objects; i++) {
278     distance = object_distance (&model->objects[i], x, y);
279     if (i == 0 || distance < min_distance) {
280       min_distance = distance;
281       object = &model->objects[i];
282     }
283   }
284
285   return object;
286 }
287
288 typedef struct _Color Color;
289 struct _Color {
290   double red, green, blue;
291 };
292
293 static void
294 draw_star (cairo_t      *cr,
295            gdouble      cx,
296            gdouble      cy,
297            double       theta,
298            Color        *color)
299 {
300   const int spike_count = 5;
301   const int inner_radius = 2;
302   const int outer_radius = 4;
303   double x, y;
304   int i;
305
306   cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
307   cairo_new_path (cr);
308   for (i = 0; i < spike_count; i++) {
309     x = cx + cos ((i * 2) * M_PI / spike_count + theta) * inner_radius;
310     y = cy + sin ((i * 2) * M_PI / spike_count + theta) * inner_radius;
311
312     if (i == 0)
313       cairo_move_to (cr, x, y);
314     else
315       cairo_line_to (cr, x, y);
316                  
317     x = cx + cos ((i * 2 + 1) * M_PI / spike_count + theta) * outer_radius;
318     y = cy + sin ((i * 2 + 1) * M_PI / spike_count + theta) * outer_radius;
319
320     cairo_line_to (cr, x, y);
321   }
322   cairo_fill (cr);
323 }
324
325 static void
326 draw_lines (cairo_t *cr,
327             Model   *model,
328             Color   *color)
329 {
330   int i;
331
332   cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
333   cairo_new_path (cr);
334   cairo_set_line_width (cr, 4);
335   cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
336   cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
337
338   for (i = 0; i < model->num_sticks; i++) {
339     cairo_move_to (cr,
340                    model->sticks[i].a->position.x,
341                    model->sticks[i].a->position.y);
342     cairo_line_to (cr,
343                    model->sticks[i].b->position.x,
344                    model->sticks[i].b->position.y);
345   }
346
347   cairo_stroke (cr);
348 }
349
350 static void
351 draw_constraints (cairo_t *cr,
352                   Model   *model,
353                   Color   *color)
354 {
355   cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
356
357   cairo_move_to (cr, 0, ground_level);
358   cairo_line_to (cr, 1500, ground_level);
359   cairo_line_to (cr, 1500, ground_level + 10);
360   cairo_line_to (cr, 0, ground_level + 10);
361   cairo_close_path (cr);
362
363   cairo_move_to (cr, 0, box_top);
364   cairo_line_to (cr, box_left, box_top);
365   cairo_line_to (cr, box_left, box_bottom);
366   cairo_line_to (cr, 0, box_bottom);
367   cairo_close_path (cr);
368
369   cairo_fill (cr);
370 }
371
372 static Color blue = { 0, 0, 1 };
373 static Color red = { 1, 0, 0 };
374
375 static gboolean
376 sproing_expose_event (GtkWidget      *widget,
377                       GdkEventExpose *event,
378                       gpointer        data)
379 {
380   Model *model = data;
381   cairo_t *cr;
382
383   cr = gdk_cairo_create (widget->window);
384
385   cairo_set_source_rgb (cr, 1, 1, 1);
386   cairo_paint (cr);
387
388   draw_constraints (cr, model, &red);
389   draw_lines (cr, model, &blue);
390
391 #if 0
392   for (i = 0; i < model->num_objects; i++) {
393     draw_star (widget, model->objects[i].position.x,
394                model->objects[i].position.y, model->objects[i].theta, &blue);
395   }
396 #endif
397
398   cairo_destroy (cr);
399
400   return TRUE;
401 }
402
403 static gboolean
404 sproing_button_press_event (GtkWidget      *widget,
405                             GdkEventButton *event,
406                             gpointer        data)
407 {
408   Model *model = data;
409
410   if (event->button != 1)
411     return TRUE;
412
413   model->anchor_position.x = event->x;
414   model->anchor_position.y = event->y;
415   model->anchor_object = model_find_nearest (model, event->x, event->y);
416
417   return TRUE;
418 }
419
420 static gboolean
421 sproing_button_release_event (GtkWidget      *widget,
422                               GdkEventButton *event,
423                               gpointer        data)
424 {
425   Model *model = data;
426
427   if ((event->state & GDK_BUTTON1_MASK) == 0)
428     return TRUE;
429
430   model->anchor_object = NULL;
431
432   return TRUE;
433 }
434
435 static gboolean
436 sproing_motion_notify_event (GtkWidget      *widget,
437                              GdkEventMotion *event,
438                              gpointer        data)
439 {
440   Model *model = data;
441   int x, y;
442   GdkModifierType state;
443
444   gdk_window_get_pointer (event->window, &x, &y, &state);
445
446   model->anchor_position.x = x + 0.5;
447   model->anchor_position.y = y + 0.5;
448
449   return TRUE;
450 }
451
452 static void
453 spring_constant_changed (GtkSpinButton *spinbutton, gpointer user_data)
454 {
455   Model *model = user_data;
456
457   model->k = gtk_spin_button_get_value (spinbutton);
458 }
459
460 static void
461 friction_changed (GtkSpinButton *spinbutton, gpointer user_data)
462 {
463   Model *model = user_data;
464
465   model->friction = gtk_spin_button_get_value (spinbutton);
466 }
467
468 static GtkWidget *
469 create_spinners (Model *model)
470 {
471   GtkWidget *hbox;
472   GtkWidget *spinner, *label;
473
474   hbox = gtk_hbox_new (FALSE, 8);
475
476   label = gtk_label_new_with_mnemonic ("_Spring constant:");
477   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
478   spinner = gtk_spin_button_new_with_range  (0.05, 30.00, 0.05);
479   gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinner);
480   gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
481   gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), model->k);
482   g_signal_connect (spinner, "value-changed",
483                     G_CALLBACK (spring_constant_changed), model);
484
485   label = gtk_label_new_with_mnemonic ("_Friction:");
486   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
487   spinner = gtk_spin_button_new_with_range  (0.05, 15.00, 0.05);
488   gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinner);
489   gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
490   gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), model->friction);
491   g_signal_connect (spinner, "value-changed",
492                     G_CALLBACK (friction_changed), model);
493
494   return hbox;
495 }
496
497 static GtkWidget *
498 create_window (Model *model)
499 {
500   GtkWidget *window;
501   GtkWidget *frame;
502   GtkWidget *vbox;
503   GtkWidget *da;
504   GtkWidget *spinners;
505
506   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
507   gtk_window_set_title (GTK_WINDOW (window), "Drawing Area");
508
509   g_signal_connect (window, "destroy",
510                     G_CALLBACK (gtk_main_quit), &window);
511
512   gtk_container_set_border_width (GTK_CONTAINER (window), 8);
513
514   vbox = gtk_vbox_new (FALSE, 8);
515   gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
516   gtk_container_add (GTK_CONTAINER (window), vbox);
517
518   /*
519    * Create the drawing area
520    */
521       
522   frame = gtk_frame_new (NULL);
523   gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
524   gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
525       
526   da = gtk_drawing_area_new ();
527   /* set a minimum size */
528   gtk_widget_set_size_request (da, 600, 500);
529
530   gtk_container_add (GTK_CONTAINER (frame), da);
531
532   /* Signals used to handle backing pixmap */
533       
534   g_signal_connect (da, "expose_event",
535                     G_CALLBACK (sproing_expose_event), model);
536       
537   /* Event signals */
538       
539   g_signal_connect (da, "motion_notify_event",
540                     G_CALLBACK (sproing_motion_notify_event), model);
541   g_signal_connect (da, "button_press_event",
542                     G_CALLBACK (sproing_button_press_event), model);
543   g_signal_connect (da, "button_release_event",
544                     G_CALLBACK (sproing_button_release_event), model);
545
546   /* Ask to receive events the drawing area doesn't normally
547    * subscribe to
548    */
549   gtk_widget_set_events (da, gtk_widget_get_events (da)
550                          | GDK_LEAVE_NOTIFY_MASK
551                          | GDK_BUTTON_PRESS_MASK
552                          | GDK_BUTTON_RELEASE_MASK
553                          | GDK_POINTER_MOTION_MASK
554                          | GDK_POINTER_MOTION_HINT_MASK);
555
556   spinners = create_spinners (model);
557   gtk_box_pack_start (GTK_BOX (vbox), spinners, FALSE, FALSE, 0);
558
559   return da;
560 }
561
562 typedef struct _Closure Closure;
563 struct _Closure {
564   GtkWidget *drawing_area;
565   Model *model;
566   int i;
567 };
568
569 static gint
570 timeout_callback (gpointer data)
571 {
572   Closure *closure = data;
573   int i;
574
575   for (i = 0; i < 3; i++)
576     model_step (closure->model, 0.5);
577
578   closure->i++;
579   if (closure->i == 1) {
580     gtk_widget_queue_draw (closure->drawing_area);
581     closure->i = 0;
582   }
583
584   return TRUE;
585 }
586
587 int
588 main (int argc, char *argv[])
589 {
590   Closure closure;
591   Model model;
592
593   gtk_init (&argc, &argv);
594   model_init_snake (&model);
595   closure.drawing_area = create_window (&model);
596   closure.i = 0;
597   gtk_widget_show_all (gtk_widget_get_toplevel (closure.drawing_area));
598   closure.model = &model;
599   g_timeout_add (100, timeout_callback, &closure);
600   gtk_main ();
601
602   return 0;
603 }