1 /* -*- mode: c; c-basic-offset: 2 -*-
4 * gcc -Wall -g $(pkg-config --cflags --libs gtk+-2.0 cairo) \
9 * http://en.wikipedia.org/wiki/Verlet_integration
10 * http://www.teknikus.dk/tj/gdc2001.htm
14 * - Add code to add boxes
20 #include <cairo-xlib.h>
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;
30 typedef struct _xy_pair Point;
31 typedef struct _xy_pair Vector;
36 typedef struct _Object Object;
37 typedef struct _Stick Stick;
38 typedef struct _Offset Offset;
39 typedef struct _Model Model;
45 Point previous_position;
72 Object *anchor_object;
73 Vector anchor_position;
79 model_init_snake (Model *model)
81 const int num_objects = 20;
82 const int num_sticks = num_objects * 2 - 3;
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;
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;
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;
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;
109 model->anchor_object = NULL;
113 model_init_rope (Model *model)
115 const int num_objects = 20;
116 const int num_sticks = num_objects - 1;
117 const int stick_length = 5;
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;
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;
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;
140 model->anchor_object = NULL;
144 model_init_curtain (Model *model)
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;
153 int i, j, index, stick_index;
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;
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;
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;
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;
188 model->anchor_object = NULL;
192 model_init_grid (Model *model)
194 const int num_ropes = 15;
195 const int num_rope_objects = 15;
196 const int num_objects = num_ropes * num_rope_objects;
197 const int num_sticks = num_ropes * (num_rope_objects - 1) +
198 (num_ropes - 1) * num_rope_objects;
199 const int stick_length = 10;
200 const int rope_offset = 10;
202 int i, j, index, stick_index;
204 model->objects = g_new (Object, num_objects);
205 model->num_objects = num_objects;
206 model->sticks = g_new (Stick, num_sticks);
207 model->num_sticks = num_sticks;
208 model->offsets = g_new (Offset, num_ropes - 1);
209 model->num_offsets = num_ropes - 1;
211 for (i = 0; i < num_ropes; i++) {
212 for (j = 0; j < num_rope_objects; j++) {
213 x = 200 + i * rope_offset;
214 y = 40 + j * stick_length;
215 index = i * num_rope_objects + j;
216 model->objects[index].position.x = x;
217 model->objects[index].position.y = y;
218 model->objects[index].previous_position.x = x;
219 model->objects[index].previous_position.y = y;
221 if (i + 1 < num_ropes) {
222 stick_index = i * num_rope_objects + j;
223 model->sticks[stick_index].a = &model->objects[index];
224 model->sticks[stick_index].b = &model->objects[index + num_rope_objects];
225 model->sticks[stick_index].length = stick_length;
228 if (j + 1 < num_rope_objects) {
230 (num_ropes - 1) * num_rope_objects + i * (num_rope_objects - 1) + j;
231 model->sticks[stick_index].a = &model->objects[index];
232 model->sticks[stick_index].b = &model->objects[index + 1];
233 model->sticks[stick_index].length = stick_length;
237 if (i + 1 < num_ropes) {
238 model->offsets[i].a = &model->objects[i * num_rope_objects];
239 model->offsets[i].b = &model->objects[(i + 1) * num_rope_objects];
240 model->offsets[i].dx = rope_offset;
241 model->offsets[i].dy = 0;
245 model->anchor_object = NULL;
249 model_fini (Model *model)
251 g_free (model->objects);
252 model->objects = NULL;
253 model->num_objects = 0;
254 g_free (model->sticks);
255 model->sticks = NULL;
257 g_free (model->offsets);
258 model->offsets = NULL;
259 model->num_offsets = 0;
263 model_accumulate_forces (Model *model)
267 for (i = 0; i < model->num_objects; i++) {
268 model->objects[i].force.x = 0;
269 model->objects[i].force.y = 2;
274 model_integrate (Model *model, double step)
280 for (i = 0; i < model->num_objects; i++) {
281 o = &model->objects[i];
286 x + (x - o->previous_position.x) + o->force.x * step * step;
288 y + (y - o->previous_position.y) + o->force.y * step * step;
290 o->previous_position.x = x;
291 o->previous_position.y = y;
296 model_constrain (Model *model, double step)
298 double dx, dy, x, y, distance, fraction;
301 /* Anchor object constraint. */
302 if (model->anchor_object != NULL) {
303 model->anchor_object->position.x = model->anchor_position.x;
304 model->anchor_object->position.y = model->anchor_position.y;
305 model->anchor_object->previous_position.x = model->anchor_position.x;
306 model->anchor_object->previous_position.y = model->anchor_position.y;
309 /* FIXME: this should be "is point inside box" test instead. Figure
310 * out from previous_position which edge the point has passed
311 * through and reflect in that. */
312 for (i = 0; i < model->num_objects; i++) {
313 x = model->objects[i].position.x;
314 y = model->objects[i].position.y;
315 if (box_top - edge_fuzz <= y &&
316 model->objects[i].previous_position.y <= box_top + edge_fuzz &&
318 model->objects[i].position.y = box_top - (y - box_top) * elasticity;
319 model->objects[i].previous_position.y =
320 box_top - (model->objects[i].previous_position.y - box_top) * elasticity;
324 /* Ground collision detection constraints. This puts a ground level
325 * in to make sure the points don't fall off the screen. */
326 for (i = 0; i < model->num_objects; i++) {
327 x = model->objects[i].position.x;
328 y = model->objects[i].position.y;
330 if (model->objects[i].position.y > ground_level) {
331 model->objects[i].position.y =
332 ground_level - (model->objects[i].position.y - ground_level) * elasticity;
333 model->objects[i].previous_position.y =
334 ground_level - (model->objects[i].previous_position.y - ground_level) * elasticity;
336 /* Friction on impact */
337 model->objects[i].position.x =
338 model->objects[i].position.x * (1 - ground_friction) +
339 model->objects[i].previous_position.x * ground_friction;
343 /* Offset constraints. */
344 for (i = 0; i < model->num_offsets; i++) {
345 x = (model->offsets[i].a->position.x + model->offsets[i].b->position.x) / 2;
346 y = (model->offsets[i].a->position.y + model->offsets[i].b->position.y) / 2;
347 model->offsets[i].a->position.x = x - model->offsets[i].dx / 2;
348 model->offsets[i].a->position.y = y - model->offsets[i].dy / 2;
349 model->offsets[i].b->position.x = x + model->offsets[i].dx / 2;
350 model->offsets[i].b->position.y = y + model->offsets[i].dy / 2;
354 /* Stick constraints. */
355 for (i = 0; i < model->num_sticks; i++) {
356 x = model->sticks[i].a->position.x;
357 y = model->sticks[i].a->position.y;
358 dx = model->sticks[i].b->position.x - x;
359 dy = model->sticks[i].b->position.y - y;
360 distance = sqrt (dx * dx + dy * dy);
361 fraction = (distance - model->sticks[i].length) / distance / 2;
362 model->sticks[i].a->position.x = x + dx * fraction;
363 model->sticks[i].a->position.y = y + dy * fraction;
364 model->sticks[i].b->position.x = x + dx * (1 - fraction);
365 model->sticks[i].b->position.y = y + dy * (1 - fraction);
368 /* Stick constraints, without square roots. */
369 squared = stick_length * stick_length;
370 for (i = 0; i < model->num_objects - 1; i++) {
372 x = model->objects[i].position.x;
373 y = model->objects[i].position.y;
374 dx = model->objects[j].position.x - x;
375 dy = model->objects[j].position.y - y;
376 fraction = squared / (dx * dx + dy * dy + squared) - 0.5;
377 model->objects[i].position.x = x + dx * fraction;
378 model->objects[i].position.y = y + dy * fraction;
379 model->objects[j].position.x = x + dx * (1 - fraction);
380 model->objects[j].position.y = y + dy * (1 - fraction);
386 model_step (Model *model, double delta_t)
390 model_accumulate_forces (model);
391 model_integrate (model, delta_t);
393 for (i = 0; i < 15; i++)
394 model_constrain (model, delta_t);
396 model->theta += delta_t;
400 object_distance (Object *object, double x, double y)
404 dx = object->position.x - x;
405 dy = object->position.y - y;
407 return sqrt (dx*dx + dy*dy);
411 model_find_nearest (Model *model, double x, double y)
414 double distance, min_distance;
417 for (i = 0; i < model->num_objects; i++) {
418 distance = object_distance (&model->objects[i], x, y);
419 if (i == 0 || distance < min_distance) {
420 min_distance = distance;
421 object = &model->objects[i];
428 typedef struct _Color Color;
430 double red, green, blue;
434 draw_sticks (cairo_t *cr,
440 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
442 cairo_set_line_width (cr, 2);
443 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
444 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
446 for (i = 0; i < model->num_sticks; i++) {
448 model->sticks[i].a->position.x,
449 model->sticks[i].a->position.y);
451 model->sticks[i].b->position.x,
452 model->sticks[i].b->position.y);
459 draw_offsets (cairo_t *cr,
465 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
467 cairo_set_line_width (cr, 4);
468 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
469 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
471 for (i = 0; i < model->num_offsets; i++) {
473 model->offsets[i].a->position.x,
474 model->offsets[i].a->position.y);
476 model->offsets[i].b->position.x,
477 model->offsets[i].b->position.y);
484 draw_constraints (cairo_t *cr,
488 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
490 cairo_move_to (cr, 0, ground_level);
491 cairo_line_to (cr, 1500, ground_level);
492 cairo_line_to (cr, 1500, ground_level + 10);
493 cairo_line_to (cr, 0, ground_level + 10);
494 cairo_close_path (cr);
496 cairo_move_to (cr, 0, box_top);
497 cairo_line_to (cr, box_left, box_top);
498 cairo_line_to (cr, box_left, box_bottom);
499 cairo_line_to (cr, 0, box_bottom);
500 cairo_close_path (cr);
506 draw_objects (cairo_t *cr, Model *model, Color *color)
510 for (i = 0; i < model->num_objects; i++) {
514 static Color blue = { 0, 0, 1 };
515 static Color red = { 1, 0, 0 };
516 static Color black = { 0, 0, 0 };
517 static Color white = { 1, 1, 1 };
520 expose_event (GtkWidget *widget,
521 GdkEventExpose *event,
527 cr = gdk_cairo_create (widget->window);
529 cairo_set_source_rgb (cr, 1, 1, 1);
532 draw_constraints (cr, model, &red);
533 draw_sticks (cr, model, &black);
534 draw_offsets (cr, model, &blue);
535 draw_objects (cr, model, &white);
543 button_press_event (GtkWidget *widget,
544 GdkEventButton *event,
549 if (event->button != 1)
552 model->anchor_position.x = event->x;
553 model->anchor_position.y = event->y;
554 model->anchor_object = model_find_nearest (model, event->x, event->y);
560 button_release_event (GtkWidget *widget,
561 GdkEventButton *event,
566 if ((event->state & GDK_BUTTON1_MASK) == 0)
569 model->anchor_object = NULL;
575 motion_notify_event (GtkWidget *widget,
576 GdkEventMotion *event,
581 GdkModifierType state;
583 gdk_window_get_pointer (event->window, &x, &y, &state);
585 model->anchor_position.x = x + 0.5;
586 model->anchor_position.y = y + 0.5;
591 typedef void (*ModelInitFunc) (Model *model);
594 model_changed (GtkComboBox *combo, gpointer user_data)
596 Model *model = user_data;
598 GtkTreeModel *tree_model;
602 tree_model = gtk_combo_box_get_model (combo);
603 if (!gtk_combo_box_get_active_iter (combo, &iter))
606 gtk_tree_model_get (tree_model, &iter, 0, &name, 1, &init, -1);
612 static GtkTreeModel *
613 create_model_store (void)
619 { "Rope", model_init_rope },
620 { "Snake", model_init_snake },
621 { "Curtain", model_init_curtain },
622 { "Grid", model_init_grid }
629 store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
631 for (i = 0; i < G_N_ELEMENTS(models); i++) {
632 gtk_tree_store_append (store, &iter, NULL);
633 gtk_tree_store_set (store, &iter,
634 0, models[i].name, 1, models[i].init, -1);
637 return GTK_TREE_MODEL (store);
642 create_model_combo (Model *model)
645 GtkWidget *combo, *label;
647 GtkCellRenderer *renderer;
649 hbox = gtk_hbox_new (FALSE, 8);
651 label = gtk_label_new_with_mnemonic ("_Model:");
652 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
654 store = create_model_store ();
655 combo = gtk_combo_box_new_with_model (store);
656 gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
657 g_object_unref (store);
659 renderer = gtk_cell_renderer_text_new ();
660 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE);
661 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer,
665 gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
666 gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0);
667 g_signal_connect (combo, "changed",
668 G_CALLBACK (model_changed), model);
674 create_window (Model *model)
680 GtkWidget *model_combo;
682 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
683 gtk_window_set_title (GTK_WINDOW (window), "Drawing Area");
685 g_signal_connect (window, "destroy",
686 G_CALLBACK (gtk_main_quit), &window);
688 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
690 vbox = gtk_vbox_new (FALSE, 8);
691 gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
692 gtk_container_add (GTK_CONTAINER (window), vbox);
695 * Create the drawing area
698 frame = gtk_frame_new (NULL);
699 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
700 gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
702 da = gtk_drawing_area_new ();
703 /* set a minimum size */
704 gtk_widget_set_size_request (da, 600, 500);
706 gtk_container_add (GTK_CONTAINER (frame), da);
708 /* Signals used to handle backing pixmap */
710 g_signal_connect (da, "expose_event",
711 G_CALLBACK (expose_event), model);
715 g_signal_connect (da, "motion_notify_event",
716 G_CALLBACK (motion_notify_event), model);
717 g_signal_connect (da, "button_press_event",
718 G_CALLBACK (button_press_event), model);
719 g_signal_connect (da, "button_release_event",
720 G_CALLBACK (button_release_event), model);
722 /* Ask to receive events the drawing area doesn't normally
725 gtk_widget_set_events (da, gtk_widget_get_events (da)
726 | GDK_LEAVE_NOTIFY_MASK
727 | GDK_BUTTON_PRESS_MASK
728 | GDK_BUTTON_RELEASE_MASK
729 | GDK_POINTER_MOTION_MASK
730 | GDK_POINTER_MOTION_HINT_MASK);
732 model_combo = create_model_combo (model);
733 gtk_box_pack_start (GTK_BOX (vbox), model_combo, FALSE, FALSE, 0);
738 typedef struct _Closure Closure;
740 GtkWidget *drawing_area;
746 timeout_callback (gpointer data)
748 Closure *closure = data;
751 for (i = 0; i < 3; i++)
752 model_step (closure->model, 0.5);
755 if (closure->i == 1) {
756 gtk_widget_queue_draw (closure->drawing_area);
764 main (int argc, char *argv[])
769 gtk_init (&argc, &argv);
770 model_init_rope (&model);
771 closure.drawing_area = create_window (&model);
773 gtk_widget_show_all (gtk_widget_get_toplevel (closure.drawing_area));
774 closure.model = &model;
775 g_timeout_add (100, timeout_callback, &closure);