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 _String String;
39 typedef struct _Offset Offset;
40 typedef struct _Model Model;
46 Point previous_position;
80 Object *anchor_object;
81 Vector anchor_position;
87 model_init_snake (Model *model)
89 const int num_objects = 20;
90 const int num_sticks = num_objects * 2 - 3;
93 model->objects = g_new (Object, num_objects);
94 model->num_objects = num_objects;
95 model->sticks = g_new (Stick, num_sticks);
96 model->num_sticks = num_sticks;
97 model->strings = NULL;
98 model->num_strings = 0;
99 model->offsets = NULL;
100 model->num_offsets = 0;
102 for (i = 0; i < num_objects; i++) {
103 model->objects[i].position.x = random() % 200 + 20;
104 model->objects[i].position.y = random() % 200 + 20;
105 model->objects[i].previous_position.x = random() % 200 + 20;
106 model->objects[i].previous_position.y = random() % 200 + 20;
108 if (i + 1 < num_objects) {
109 model->sticks[i * 2].a = &model->objects[i];
110 model->sticks[i * 2].b = &model->objects[i + 1];
111 model->sticks[i * 2].length = random() % 20 + 20;
113 if (i + 2 < num_objects) {
114 model->sticks[i * 2 + 1].a = &model->objects[i];
115 model->sticks[i * 2 + 1].b = &model->objects[i + 2];
116 model->sticks[i * 2 + 1].length = random() % 20 + 20;
120 model->anchor_object = NULL;
124 model_init_rope (Model *model)
126 const int num_objects = 20;
127 const int num_sticks = num_objects - 1;
128 const int stick_length = 5;
131 model->objects = g_new (Object, num_objects);
132 model->num_objects = num_objects;
133 model->sticks = g_new (Stick, num_sticks);
134 model->num_sticks = num_sticks;
135 model->strings = NULL;
136 model->num_strings = 0;
137 model->offsets = NULL;
138 model->num_offsets = 0;
140 for (i = 0; i < num_objects; i++) {
141 model->objects[i].position.x = 200;
142 model->objects[i].position.y = 40 + i * stick_length;
143 model->objects[i].previous_position.x = 200;
144 model->objects[i].previous_position.y = 40 + i * stick_length;
146 if (i + 1 < num_objects) {
147 model->sticks[i].a = &model->objects[i];
148 model->sticks[i].b = &model->objects[i + 1];
149 model->sticks[i].length = stick_length;
153 model->anchor_object = NULL;
157 model_init_curtain (Model *model)
159 const int num_ropes = 5;
160 const int num_rope_objects = 15;
161 const int num_objects = num_ropes * num_rope_objects;
162 const int num_sticks = num_ropes * (num_rope_objects - 1);
163 const int stick_length = 10;
164 const int rope_offset = 30;
166 int i, j, index, stick_index;
168 model->objects = g_new (Object, num_objects);
169 model->num_objects = num_objects;
170 model->sticks = g_new (Stick, num_sticks);
171 model->num_sticks = num_sticks;
172 model->strings = NULL;
173 model->num_strings = 0;
174 model->offsets = g_new (Offset, num_ropes - 1);
175 model->num_offsets = num_ropes - 1;
177 for (i = 0; i < num_ropes; i++) {
178 for (j = 0; j < num_rope_objects; j++) {
179 x = 200 + i * rope_offset;
180 y = 40 + j * stick_length;
181 index = i * num_rope_objects + j;
182 model->objects[index].position.x = x;
183 model->objects[index].position.y = y;
184 model->objects[index].previous_position.x = x;
185 model->objects[index].previous_position.y = y;
187 if (j + 1 < num_rope_objects) {
188 stick_index = i * (num_rope_objects - 1) + j;
189 model->sticks[stick_index].a = &model->objects[index];
190 model->sticks[stick_index].b = &model->objects[index + 1];
191 model->sticks[stick_index].length = stick_length;
195 if (i + 1 < num_ropes) {
196 model->offsets[i].a = &model->objects[i * num_rope_objects];
197 model->offsets[i].b = &model->objects[(i + 1) * num_rope_objects];
198 model->offsets[i].dx = rope_offset;
199 model->offsets[i].dy = 0;
203 model->anchor_object = NULL;
207 model_init_grid (Model *model)
209 const int num_ropes = 4;
210 const int num_rope_objects = 4;
211 const int num_objects = num_ropes * num_rope_objects;
212 const int num_strings = num_ropes * (num_rope_objects - 1) +
213 (num_ropes - 1) * num_rope_objects;
214 const int string_length = 30;
215 const int rope_offset = 30;
217 int i, j, index, string_index;
219 model->objects = g_new (Object, num_objects);
220 model->num_objects = num_objects;
221 model->sticks = NULL;
222 model->num_sticks = 0;
223 model->strings = g_new (String, num_strings);
224 model->num_strings = num_strings;
225 model->offsets = g_new (Offset, num_ropes - 1);
226 model->num_offsets = num_ropes - 1;
228 for (i = 0; i < num_ropes; i++) {
229 for (j = 0; j < num_rope_objects; j++) {
230 x = 200 + i * rope_offset;
231 y = 40 + j * string_length;
232 index = i * num_rope_objects + j;
233 model->objects[index].position.x = x;
234 model->objects[index].position.y = y;
235 model->objects[index].previous_position.x = x;
236 model->objects[index].previous_position.y = y;
238 if (i + 1 < num_ropes) {
239 string_index = i * num_rope_objects + j;
240 model->strings[string_index].a = &model->objects[index];
241 model->strings[string_index].b = &model->objects[index + num_rope_objects];
242 model->strings[string_index].length = string_length;
245 if (j + 1 < num_rope_objects) {
247 (num_ropes - 1) * num_rope_objects + i * (num_rope_objects - 1) + j;
248 model->strings[string_index].a = &model->objects[index];
249 model->strings[string_index].b = &model->objects[index + 1];
250 model->strings[string_index].length = string_length;
254 if (i + 1 < num_ropes) {
255 model->offsets[i].a = &model->objects[i * num_rope_objects];
256 model->offsets[i].b = &model->objects[(i + 1) * num_rope_objects];
257 model->offsets[i].dx = rope_offset;
258 model->offsets[i].dy = 0;
262 model->anchor_object = NULL;
266 model_fini (Model *model)
268 g_free (model->objects);
269 model->objects = NULL;
270 model->num_objects = 0;
271 g_free (model->sticks);
272 model->sticks = NULL;
274 g_free (model->strings);
275 model->strings = NULL;
277 g_free (model->offsets);
278 model->offsets = NULL;
279 model->num_offsets = 0;
283 model_accumulate_forces (Model *model)
287 for (i = 0; i < model->num_objects; i++) {
288 model->objects[i].force.x = 0;
289 model->objects[i].force.y = 3;
294 model_integrate (Model *model, double step)
300 for (i = 0; i < model->num_objects; i++) {
301 o = &model->objects[i];
306 x + (x - o->previous_position.x) + o->force.x * step * step;
308 y + (y - o->previous_position.y) + o->force.y * step * step;
310 o->previous_position.x = x;
311 o->previous_position.y = y;
316 model_constrain (Model *model, double step)
318 double dx, dy, x, y, distance, fraction;
321 /* Anchor object constraint. */
322 if (model->anchor_object != NULL) {
323 model->anchor_object->position.x = model->anchor_position.x;
324 model->anchor_object->position.y = model->anchor_position.y;
325 model->anchor_object->previous_position.x = model->anchor_position.x;
326 model->anchor_object->previous_position.y = model->anchor_position.y;
329 /* FIXME: this should be "is point inside box" test instead. Figure
330 * out from previous_position which edge the point has passed
331 * through and reflect in that. */
332 for (i = 0; i < model->num_objects; i++) {
333 x = model->objects[i].position.x;
334 y = model->objects[i].position.y;
335 if (box_top - edge_fuzz <= y &&
336 model->objects[i].previous_position.y <= box_top + edge_fuzz &&
338 model->objects[i].position.y = box_top - (y - box_top) * elasticity;
339 model->objects[i].previous_position.y =
340 box_top - (model->objects[i].previous_position.y - box_top) * elasticity;
344 /* Ground collision detection constraints. This puts a ground level
345 * in to make sure the points don't fall off the screen. */
346 for (i = 0; i < model->num_objects; i++) {
347 x = model->objects[i].position.x;
348 y = model->objects[i].position.y;
350 if (model->objects[i].position.y > ground_level) {
351 model->objects[i].position.y =
352 ground_level - (model->objects[i].position.y - ground_level) * elasticity;
353 model->objects[i].previous_position.y =
354 ground_level - (model->objects[i].previous_position.y - ground_level) * elasticity;
356 /* Friction on impact */
357 model->objects[i].position.x =
358 model->objects[i].position.x * (1 - ground_friction) +
359 model->objects[i].previous_position.x * ground_friction;
363 /* Offset constraints. */
364 for (i = 0; i < model->num_offsets; i++) {
365 x = (model->offsets[i].a->position.x + model->offsets[i].b->position.x) / 2;
366 y = (model->offsets[i].a->position.y + model->offsets[i].b->position.y) / 2;
367 model->offsets[i].a->position.x = x - model->offsets[i].dx / 2;
368 model->offsets[i].a->position.y = y - model->offsets[i].dy / 2;
369 model->offsets[i].b->position.x = x + model->offsets[i].dx / 2;
370 model->offsets[i].b->position.y = y + model->offsets[i].dy / 2;
373 /* String constraints. */
374 for (i = 0; i < model->num_strings; i++) {
375 x = model->strings[i].a->position.x;
376 y = model->strings[i].a->position.y;
377 dx = model->strings[i].b->position.x - x;
378 dy = model->strings[i].b->position.y - y;
379 distance = sqrt (dx * dx + dy * dy);
380 if (distance < model->strings[i].length)
382 fraction = (distance - model->strings[i].length) / distance / 2;
383 model->strings[i].a->position.x = x + dx * fraction;
384 model->strings[i].a->position.y = y + dy * fraction;
385 model->strings[i].b->position.x = x + dx * (1 - fraction);
386 model->strings[i].b->position.y = y + dy * (1 - fraction);
390 /* Stick constraints. */
391 for (i = 0; i < model->num_sticks; i++) {
392 x = model->sticks[i].a->position.x;
393 y = model->sticks[i].a->position.y;
394 dx = model->sticks[i].b->position.x - x;
395 dy = model->sticks[i].b->position.y - y;
396 distance = sqrt (dx * dx + dy * dy);
397 fraction = (distance - model->sticks[i].length) / distance / 2;
398 model->sticks[i].a->position.x = x + dx * fraction;
399 model->sticks[i].a->position.y = y + dy * fraction;
400 model->sticks[i].b->position.x = x + dx * (1 - fraction);
401 model->sticks[i].b->position.y = y + dy * (1 - fraction);
404 /* Stick constraints, without square roots. */
405 squared = stick_length * stick_length;
406 for (i = 0; i < model->num_objects - 1; i++) {
408 x = model->objects[i].position.x;
409 y = model->objects[i].position.y;
410 dx = model->objects[j].position.x - x;
411 dy = model->objects[j].position.y - y;
412 fraction = squared / (dx * dx + dy * dy + squared) - 0.5;
413 model->objects[i].position.x = x + dx * fraction;
414 model->objects[i].position.y = y + dy * fraction;
415 model->objects[j].position.x = x + dx * (1 - fraction);
416 model->objects[j].position.y = y + dy * (1 - fraction);
422 model_step (Model *model, double delta_t)
426 model_accumulate_forces (model);
427 model_integrate (model, delta_t);
429 for (i = 0; i < 35; i++)
430 model_constrain (model, delta_t);
432 model->theta += delta_t;
436 object_distance (Object *object, double x, double y)
440 dx = object->position.x - x;
441 dy = object->position.y - y;
443 return sqrt (dx*dx + dy*dy);
447 model_find_nearest (Model *model, double x, double y)
450 double distance, min_distance;
453 for (i = 0; i < model->num_objects; i++) {
454 distance = object_distance (&model->objects[i], x, y);
455 if (i == 0 || distance < min_distance) {
456 min_distance = distance;
457 object = &model->objects[i];
464 typedef struct _Color Color;
466 double red, green, blue;
470 draw_sticks (cairo_t *cr,
476 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
478 cairo_set_line_width (cr, 2);
479 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
480 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
482 for (i = 0; i < model->num_sticks; i++) {
484 model->sticks[i].a->position.x,
485 model->sticks[i].a->position.y);
487 model->sticks[i].b->position.x,
488 model->sticks[i].b->position.y);
495 draw_strings (cairo_t *cr,
501 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
503 cairo_set_line_width (cr, 1);
504 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
505 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
507 for (i = 0; i < model->num_strings; i++) {
509 model->strings[i].a->position.x,
510 model->strings[i].a->position.y);
512 model->strings[i].b->position.x,
513 model->strings[i].b->position.y);
520 draw_offsets (cairo_t *cr,
526 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
528 cairo_set_line_width (cr, 4);
529 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
530 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
532 for (i = 0; i < model->num_offsets; i++) {
534 model->offsets[i].a->position.x,
535 model->offsets[i].a->position.y);
537 model->offsets[i].b->position.x,
538 model->offsets[i].b->position.y);
545 draw_constraints (cairo_t *cr,
549 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
551 cairo_move_to (cr, 0, ground_level);
552 cairo_line_to (cr, 1500, ground_level);
553 cairo_line_to (cr, 1500, ground_level + 10);
554 cairo_line_to (cr, 0, ground_level + 10);
555 cairo_close_path (cr);
557 cairo_move_to (cr, 0, box_top);
558 cairo_line_to (cr, box_left, box_top);
559 cairo_line_to (cr, box_left, box_bottom);
560 cairo_line_to (cr, 0, box_bottom);
561 cairo_close_path (cr);
567 draw_objects (cairo_t *cr, Model *model, Color *color)
571 for (i = 0; i < model->num_objects; i++) {
575 static Color blue = { 0, 0, 1 };
576 static Color green = { 0, 1, 0 };
577 static Color red = { 1, 0, 0 };
578 static Color black = { 0, 0, 0 };
579 static Color white = { 1, 1, 1 };
582 expose_event (GtkWidget *widget,
583 GdkEventExpose *event,
589 cr = gdk_cairo_create (widget->window);
591 cairo_set_source_rgb (cr, 1, 1, 1);
594 draw_constraints (cr, model, &red);
595 draw_sticks (cr, model, &black);
596 draw_strings (cr, model, &green);
597 draw_offsets (cr, model, &blue);
598 draw_objects (cr, model, &white);
606 button_press_event (GtkWidget *widget,
607 GdkEventButton *event,
612 if (event->button != 1)
615 model->anchor_position.x = event->x;
616 model->anchor_position.y = event->y;
617 model->anchor_object = model_find_nearest (model, event->x, event->y);
623 button_release_event (GtkWidget *widget,
624 GdkEventButton *event,
629 if ((event->state & GDK_BUTTON1_MASK) == 0)
632 model->anchor_object = NULL;
638 motion_notify_event (GtkWidget *widget,
639 GdkEventMotion *event,
644 GdkModifierType state;
646 gdk_window_get_pointer (event->window, &x, &y, &state);
648 model->anchor_position.x = x + 0.5;
649 model->anchor_position.y = y + 0.5;
654 typedef void (*ModelInitFunc) (Model *model);
657 model_changed (GtkComboBox *combo, gpointer user_data)
659 Model *model = user_data;
661 GtkTreeModel *tree_model;
665 tree_model = gtk_combo_box_get_model (combo);
666 if (!gtk_combo_box_get_active_iter (combo, &iter))
669 gtk_tree_model_get (tree_model, &iter, 0, &name, 1, &init, -1);
675 static GtkTreeModel *
676 create_model_store (void)
682 { "Rope", model_init_rope },
683 { "Snake", model_init_snake },
684 { "Curtain", model_init_curtain },
685 { "Grid", model_init_grid }
692 store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
694 for (i = 0; i < G_N_ELEMENTS(models); i++) {
695 gtk_tree_store_append (store, &iter, NULL);
696 gtk_tree_store_set (store, &iter,
697 0, models[i].name, 1, models[i].init, -1);
700 return GTK_TREE_MODEL (store);
705 create_model_combo (Model *model)
708 GtkWidget *combo, *label;
710 GtkCellRenderer *renderer;
712 hbox = gtk_hbox_new (FALSE, 8);
714 label = gtk_label_new_with_mnemonic ("_Model:");
715 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
717 store = create_model_store ();
718 combo = gtk_combo_box_new_with_model (store);
719 gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
720 g_object_unref (store);
722 renderer = gtk_cell_renderer_text_new ();
723 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE);
724 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer,
728 gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
729 gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0);
730 g_signal_connect (combo, "changed",
731 G_CALLBACK (model_changed), model);
737 create_window (Model *model)
743 GtkWidget *model_combo;
745 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
746 gtk_window_set_title (GTK_WINDOW (window), "Drawing Area");
748 g_signal_connect (window, "destroy",
749 G_CALLBACK (gtk_main_quit), &window);
751 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
753 vbox = gtk_vbox_new (FALSE, 8);
754 gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
755 gtk_container_add (GTK_CONTAINER (window), vbox);
758 * Create the drawing area
761 frame = gtk_frame_new (NULL);
762 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
763 gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
765 da = gtk_drawing_area_new ();
766 /* set a minimum size */
767 gtk_widget_set_size_request (da, 600, 500);
769 gtk_container_add (GTK_CONTAINER (frame), da);
771 /* Signals used to handle backing pixmap */
773 g_signal_connect (da, "expose_event",
774 G_CALLBACK (expose_event), model);
778 g_signal_connect (da, "motion_notify_event",
779 G_CALLBACK (motion_notify_event), model);
780 g_signal_connect (da, "button_press_event",
781 G_CALLBACK (button_press_event), model);
782 g_signal_connect (da, "button_release_event",
783 G_CALLBACK (button_release_event), model);
785 /* Ask to receive events the drawing area doesn't normally
788 gtk_widget_set_events (da, gtk_widget_get_events (da)
789 | GDK_LEAVE_NOTIFY_MASK
790 | GDK_BUTTON_PRESS_MASK
791 | GDK_BUTTON_RELEASE_MASK
792 | GDK_POINTER_MOTION_MASK
793 | GDK_POINTER_MOTION_HINT_MASK);
795 model_combo = create_model_combo (model);
796 gtk_box_pack_start (GTK_BOX (vbox), model_combo, FALSE, FALSE, 0);
801 typedef struct _Closure Closure;
803 GtkWidget *drawing_area;
809 timeout_callback (gpointer data)
811 Closure *closure = data;
814 for (i = 0; i < 3; i++)
815 model_step (closure->model, 0.5);
818 if (closure->i == 1) {
819 gtk_widget_queue_draw (closure->drawing_area);
827 main (int argc, char *argv[])
832 gtk_init (&argc, &argv);
833 model_init_rope (&model);
834 closure.drawing_area = create_window (&model);
836 gtk_widget_show_all (gtk_widget_get_toplevel (closure.drawing_area));
837 closure.model = &model;
838 g_timeout_add (100, timeout_callback, &closure);