1 /* -*- mode: c; c-basic-offset: 2 -*-
4 * gcc -Wall -g $(pkg-config --cflags --libs gtk+-2.0 cairo) \
5 * akamaru.c main.c -o akamaru
10 #include <cairo-xlib.h>
20 model_init_polygons (Model *model)
22 const int num_polygons = 5;
23 const double ground_level = 500;
25 model->polygons = g_new (Polygon, num_polygons);
26 polygon_init_diamond (&model->polygons[0], 250, 300);
27 polygon_init_diamond (&model->polygons[1], 400, 150);
28 polygon_init_rectangle (&model->polygons[2], -100, 200, 200, 250);
29 polygon_init_rectangle (&model->polygons[3], -200, ground_level,
30 1200, ground_level + 400);
32 polygon_init_rectangle (&model->polygons[4], 300, 320, 400, 350);
34 model->num_polygons = num_polygons;
38 model_init_snake (Model *model)
40 const int num_objects = 20;
41 const int num_sticks = num_objects * 2 - 3;
44 memset (model, 0, sizeof *model);
45 model->objects = g_new (Object, num_objects);
46 model->num_objects = num_objects;
47 model->sticks = g_new (Stick, num_sticks);
48 model->num_sticks = num_sticks;
49 model_init_polygons (model);
51 for (i = 0; i < num_objects; i++) {
52 model->objects[i].position.x = random() % 200 + 20;
53 model->objects[i].position.y = random() % 200 + 20;
54 model->objects[i].previous_position.x = random() % 200 + 20;
55 model->objects[i].previous_position.y = random() % 200 + 20;
56 model->objects[i].mass = 1;
58 if (i + 1 < num_objects) {
59 model->sticks[i * 2].a = &model->objects[i];
60 model->sticks[i * 2].b = &model->objects[i + 1];
61 model->sticks[i * 2].length = random() % 20 + 20;
63 if (i + 2 < num_objects) {
64 model->sticks[i * 2 + 1].a = &model->objects[i];
65 model->sticks[i * 2 + 1].b = &model->objects[i + 2];
66 model->sticks[i * 2 + 1].length = random() % 20 + 20;
70 model->anchor_object = NULL;
74 model_init_rope (Model *model)
76 const int num_objects = 20;
77 const int num_sticks = num_objects - 1;
78 const int stick_length = 10;
81 memset (model, 0, sizeof *model);
82 model->objects = g_new (Object, num_objects);
83 model->num_objects = num_objects;
84 model->sticks = g_new (Stick, num_sticks);
85 model->num_sticks = num_sticks;
86 model_init_polygons (model);
88 for (i = 0; i < num_objects; i++) {
89 model->objects[i].position.x = 200;
90 model->objects[i].position.y = 40 + i * stick_length;
91 model->objects[i].previous_position.x = 200;
92 model->objects[i].previous_position.y = 40 + i * stick_length;
93 model->objects[i].mass = 1;
95 if (i + 1 < num_objects) {
96 model->sticks[i].a = &model->objects[i];
97 model->sticks[i].b = &model->objects[i + 1];
98 model->sticks[i].length = stick_length;
102 model->anchor_object = NULL;
106 model_init_curtain (Model *model)
108 const int num_ropes = 5;
109 const int num_rope_objects = 15;
110 const int num_objects = num_ropes * num_rope_objects;
111 const int num_sticks = num_ropes * (num_rope_objects - 1);
112 const int stick_length = 10;
113 const int rope_offset = 30;
115 int i, j, index, stick_index;
117 memset (model, 0, sizeof *model);
118 model->objects = g_new (Object, num_objects);
119 model->num_objects = num_objects;
120 model->sticks = g_new (Stick, num_sticks);
121 model->num_sticks = num_sticks;
122 model->offsets = g_new (Offset, 1);
123 model->num_offsets = 1;
124 model_init_polygons (model);
126 model->offsets[0].num_objects = num_ropes;
127 model->offsets[0].objects = g_new (Object *, num_ropes);
128 model->offsets[0].dx = rope_offset;
129 model->offsets[0].dy = 0;
131 for (i = 0; i < num_ropes; i++) {
132 for (j = 0; j < num_rope_objects; j++) {
133 x = 200 + i * rope_offset;
134 y = 40 + j * stick_length;
135 index = i * num_rope_objects + j;
136 model->objects[index].position.x = x;
137 model->objects[index].position.y = y;
138 model->objects[index].previous_position.x = x;
139 model->objects[index].previous_position.y = y;
140 model->objects[index].mass = 1;
142 if (j + 1 < num_rope_objects) {
143 stick_index = i * (num_rope_objects - 1) + j;
144 model->sticks[stick_index].a = &model->objects[index];
145 model->sticks[stick_index].b = &model->objects[index + 1];
146 model->sticks[stick_index].length = stick_length;
150 model->offsets[0].objects[i] = &model->objects[i * num_rope_objects];
153 model->anchor_object = NULL;
157 model_init_grid (Model *model)
159 const int num_ropes = 4;
160 const int num_rope_objects = 4;
161 const int num_objects = num_ropes * num_rope_objects;
162 const int num_strings = num_ropes * (num_rope_objects - 1) +
163 (num_ropes - 1) * num_rope_objects;
164 const int string_length = 20;
165 const int rope_offset = 20;
167 int i, j, index, string_index;
169 memset (model, 0, sizeof *model);
170 model->objects = g_new (Object, num_objects);
171 model->num_objects = num_objects;
172 model->strings = g_new (String, num_strings);
173 model->num_strings = num_strings;
174 model->offsets = g_new (Offset, 1);
175 model->num_offsets = 1;
176 model_init_polygons (model);
178 model->offsets[0].num_objects = num_ropes;
179 model->offsets[0].objects = g_new (Object *, num_ropes);
180 model->offsets[0].dx = rope_offset;
181 model->offsets[0].dy = 0;
183 for (i = 0; i < num_ropes; i++) {
184 for (j = 0; j < num_rope_objects; j++) {
185 x = 200 + i * rope_offset;
186 y = 40 + j * string_length;
187 index = i * num_rope_objects + j;
188 model->objects[index].position.x = x;
189 model->objects[index].position.y = y;
190 model->objects[index].previous_position.x = x;
191 model->objects[index].previous_position.y = y;
192 model->objects[index].mass = 1;
194 if (i + 1 < num_ropes) {
195 string_index = i * num_rope_objects + j;
196 model->strings[string_index].a = &model->objects[index];
197 model->strings[string_index].b = &model->objects[index + num_rope_objects];
198 model->strings[string_index].length = string_length;
201 if (j + 1 < num_rope_objects) {
203 (num_ropes - 1) * num_rope_objects + i * (num_rope_objects - 1) + j;
204 model->strings[string_index].a = &model->objects[index];
205 model->strings[string_index].b = &model->objects[index + 1];
206 model->strings[string_index].length = string_length;
210 model->offsets[0].objects[i] = &model->objects[i * num_rope_objects];
213 model->anchor_object = NULL;
217 model_init_molecule (Model *model)
219 const int num_objects = 8;
220 const int num_springs = num_objects * 2;
221 const int spring_length = 50;
224 memset (model, 0, sizeof *model);
225 model->objects = g_new (Object, num_objects);
226 model->num_objects = num_objects;
227 model->springs = g_new (Spring, num_springs);
228 model->num_springs = num_springs;
231 for (i = 0; i < num_objects; i++) {
232 model->objects[i].position.x = 200 + i * 20;
233 model->objects[i].position.y = 200;
234 model->objects[i].previous_position.x = 200 + i * 20;
235 model->objects[i].previous_position.y = 200;
236 model->objects[i].mass = 0;
239 for (i = 0; i < num_objects; i++) {
240 model->springs[i * 2].a = &model->objects[i];
241 model->springs[i * 2].b = &model->objects[(i + 1) % num_objects];
242 model->springs[i * 2].length = spring_length;
243 model->springs[i * 2 + 1].a = &model->objects[i];
244 model->springs[i * 2 + 1].b = &model->objects[(i + 2) % num_objects];
245 model->springs[i * 2 + 1].length = spring_length;
251 model_init_wobbly (Model *model)
253 const int width = 8, height = 8;
254 const int num_objects = width * height;
255 const int num_offset_springs = (width - 1) * height + width * (height - 1);
256 const int distance = 30;
258 int i, j, object_index, spring_index;
260 memset (model, 0, sizeof *model);
261 model->objects = g_new (Object, num_objects);
262 model->num_objects = num_objects;
263 model->offset_springs = g_new (OffsetSpring, num_offset_springs);
264 model->num_offset_springs = num_offset_springs;
267 model_init_polygons (model);
271 for (i = 0; i < width; i++) {
272 for (j = 0; j < height; j++) {
273 x = 200 + i * distance;
274 y = 40 + j * distance;
275 model->objects[object_index].position.x = x;
276 model->objects[object_index].position.y = y;
277 model->objects[object_index].previous_position.x = x;
278 model->objects[object_index].previous_position.y = y;
279 model->objects[object_index].mass = 0;
282 model->offset_springs[spring_index].a = &model->objects[object_index];
283 model->offset_springs[spring_index].b = &model->objects[object_index + height];
284 model->offset_springs[spring_index].dx = distance;
285 model->offset_springs[spring_index].dy = 0;
289 if (j + 1 < height) {
290 model->offset_springs[spring_index].a = &model->objects[object_index];
291 model->offset_springs[spring_index].b = &model->objects[object_index + 1];
292 model->offset_springs[spring_index].dx = 0;
293 model->offset_springs[spring_index].dy = distance;
302 typedef struct _Color Color;
304 double red, green, blue;
308 draw_sticks (cairo_t *cr,
314 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
316 cairo_set_line_width (cr, 2);
317 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
318 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
320 for (i = 0; i < model->num_sticks; i++) {
322 model->sticks[i].a->position.x,
323 model->sticks[i].a->position.y);
325 model->sticks[i].b->position.x,
326 model->sticks[i].b->position.y);
333 draw_strings (cairo_t *cr,
339 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
341 cairo_set_line_width (cr, 1);
342 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
343 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
345 for (i = 0; i < model->num_strings; i++) {
347 model->strings[i].a->position.x,
348 model->strings[i].a->position.y);
350 model->strings[i].b->position.x,
351 model->strings[i].b->position.y);
358 draw_offsets (cairo_t *cr,
364 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
366 cairo_set_line_width (cr, 4);
367 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
368 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
370 for (i = 0; i < model->num_offsets; i++) {
371 for (j = 0; j < model->offsets[i].num_objects; j++) {
373 model->offsets[i].objects[j]->position.x,
374 model->offsets[i].objects[j]->position.y);
382 draw_springs (cairo_t *cr,
388 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.4);
390 cairo_set_line_width (cr, 2);
391 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
392 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
394 for (i = 0; i < model->num_springs; i++) {
396 model->springs[i].a->position.x,
397 model->springs[i].a->position.y);
399 model->springs[i].b->position.x,
400 model->springs[i].b->position.y);
407 draw_offset_springs (cairo_t *cr,
413 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.4);
415 cairo_set_line_width (cr, 2);
416 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
417 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
419 for (i = 0; i < model->num_offset_springs; i++) {
421 model->offset_springs[i].a->position.x,
422 model->offset_springs[i].a->position.y);
424 model->offset_springs[i].b->position.x,
425 model->offset_springs[i].b->position.y);
432 draw_polygons (cairo_t *cr, Model *model, Color *color)
437 for (i = 0; i < model->num_polygons; i++) {
438 p = &model->polygons[i];
439 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.4);
442 for (j = 0; j < p->num_points; j++)
443 cairo_line_to (cr, p->points[j].x, p->points[j].y);
444 cairo_close_path (cr);
451 draw_objects (cairo_t *cr, Model *model, Color *color)
455 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.4);
456 for (i = 0; i < model->num_objects; i++) {
457 cairo_arc (cr, model->objects[i].position.x,
458 model->objects[i].position.y,
464 static Color blue = { 0, 0, 1 };
465 static Color green = { 0, 1, 0 };
466 static Color red = { 1, 0, 0 };
467 static Color black = { 0, 0, 0 };
469 typedef struct _Closure Closure;
471 GtkWidget *drawing_area;
472 GtkWidget *fps_label;
476 struct timeval start;
480 draw_model (GtkWidget *widget, Model *model)
484 cr = gdk_cairo_create (widget->window);
486 cairo_set_source_rgb (cr, 1, 1, 1);
489 draw_polygons (cr, model, &blue);
490 draw_sticks (cr, model, &black);
491 draw_strings (cr, model, &green);
492 draw_springs (cr, model, &black);
493 draw_offsets (cr, model, &blue);
494 draw_offset_springs (cr, model, &blue);
495 draw_objects (cr, model, &red);
501 expose_event (GtkWidget *widget,
502 GdkEventExpose *event,
505 Closure *closure = data;
507 draw_model (widget, closure->model);
513 button_press_event (GtkWidget *widget,
514 GdkEventButton *event,
517 Closure *closure = data;
519 if (event->button != 1)
522 closure->model->anchor_position.x = event->x;
523 closure->model->anchor_position.y = event->y;
524 closure->model->anchor_object = model_find_nearest (closure->model,
531 button_release_event (GtkWidget *widget,
532 GdkEventButton *event,
535 Closure *closure = data;
537 if ((event->state & GDK_BUTTON1_MASK) == 0)
540 closure->model->anchor_object = NULL;
546 motion_notify_event (GtkWidget *widget,
547 GdkEventMotion *event,
550 Closure *closure = data;
552 GdkModifierType state;
554 gdk_window_get_pointer (event->window, &x, &y, &state);
556 closure->model->anchor_position.x = x + 0.5;
557 closure->model->anchor_position.y = y + 0.5;
562 typedef void (*ModelInitFunc) (Model *model);
565 model_changed (GtkComboBox *combo, gpointer user_data)
567 Closure *closure = user_data;
569 GtkTreeModel *tree_model;
573 tree_model = gtk_combo_box_get_model (combo);
574 if (!gtk_combo_box_get_active_iter (combo, &iter))
577 gtk_tree_model_get (tree_model, &iter, 0, &name, 1, &init, -1);
579 model_fini (closure->model);
580 (*init) (closure->model);
583 static GtkTreeModel *
584 create_model_store (void)
590 { "Rope", model_init_rope },
591 { "Snake", model_init_snake },
592 { "Curtain", model_init_curtain },
593 { "Grid", model_init_grid },
594 { "Molecule", model_init_molecule },
595 { "Wobbly", model_init_wobbly }
602 store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
604 for (i = 0; i < G_N_ELEMENTS(models); i++) {
605 gtk_tree_store_append (store, &iter, NULL);
606 gtk_tree_store_set (store, &iter,
607 0, models[i].name, 1, models[i].init, -1);
610 return GTK_TREE_MODEL (store);
615 create_model_combo (Closure *closure)
618 GtkWidget *combo, *label;
620 GtkCellRenderer *renderer;
622 hbox = gtk_hbox_new (FALSE, 8);
624 label = gtk_label_new_with_mnemonic ("_Model:");
625 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
627 store = create_model_store ();
628 combo = gtk_combo_box_new_with_model (store);
629 gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
630 g_object_unref (store);
632 renderer = gtk_cell_renderer_text_new ();
633 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE);
634 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer,
638 gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
639 gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0);
640 g_signal_connect (combo, "changed",
641 G_CALLBACK (model_changed), closure);
643 label = gtk_label_new ("Frames per second: 0");
644 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
646 closure->fps_label = label;
652 create_window (Closure *closure)
658 GtkWidget *model_combo;
660 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
661 gtk_window_set_title (GTK_WINDOW (window), "Akamaru");
663 g_signal_connect (window, "destroy",
664 G_CALLBACK (gtk_main_quit), &window);
666 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
668 vbox = gtk_vbox_new (FALSE, 8);
669 gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
670 gtk_container_add (GTK_CONTAINER (window), vbox);
673 * Create the drawing area
676 frame = gtk_frame_new (NULL);
677 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
678 gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
680 da = gtk_drawing_area_new ();
681 /* set a minimum size */
682 gtk_widget_set_size_request (da, 200, 200);
684 gtk_container_add (GTK_CONTAINER (frame), da);
686 /* Signals used to handle backing pixmap */
688 g_signal_connect (da, "expose_event",
689 G_CALLBACK (expose_event), closure);
693 g_signal_connect (da, "motion_notify_event",
694 G_CALLBACK (motion_notify_event), closure);
695 g_signal_connect (da, "button_press_event",
696 G_CALLBACK (button_press_event), closure);
697 g_signal_connect (da, "button_release_event",
698 G_CALLBACK (button_release_event), closure);
700 /* Ask to receive events the drawing area doesn't normally
703 gtk_widget_set_events (da, gtk_widget_get_events (da)
704 | GDK_LEAVE_NOTIFY_MASK
705 | GDK_BUTTON_PRESS_MASK
706 | GDK_BUTTON_RELEASE_MASK
707 | GDK_POINTER_MOTION_MASK
708 | GDK_POINTER_MOTION_HINT_MASK);
710 model_combo = create_model_combo (closure);
711 gtk_box_pack_start (GTK_BOX (vbox), model_combo, FALSE, FALSE, 0);
713 closure->drawing_area = da;
717 timeout_callback (gpointer data)
719 Closure *closure = data;
721 model_step (closure->model, 0.2);
724 if (closure->i == 1) {
725 gtk_widget_queue_draw (closure->drawing_area);
727 closure->frame_count++;
730 if (closure->frame_count == 200) {
731 struct timeval end, elapsed;
735 closure->frame_count = 0;
736 gettimeofday (&end, NULL);
737 if (closure->start.tv_usec > end.tv_usec) {
738 end.tv_usec += 1000000;
742 elapsed.tv_usec = end.tv_usec - closure->start.tv_usec;
743 elapsed.tv_sec = end.tv_sec - closure->start.tv_sec;
745 total = elapsed.tv_sec + ((double) elapsed.tv_usec / 1e6);
749 closure->start = end;
750 snprintf (text, sizeof text, "Frames per second: %.2f", 200 / total);
751 gtk_label_set_text (GTK_LABEL (closure->fps_label), text);
758 main (int argc, char *argv[])
763 gtk_init (&argc, &argv);
764 model_init_rope (&model);
765 create_window (&closure);
767 gtk_widget_show_all (gtk_widget_get_toplevel (closure.drawing_area));
768 closure.model = &model;
769 closure.frame_count = 0;
770 gettimeofday (&closure.start, NULL);
771 g_timeout_add (40, timeout_callback, &closure);