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 = 20;
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;
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;
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;
138 model->anchor_object = NULL;
142 model_init_curtain (Model *model)
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;
151 int i, j, index, stick_index;
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;
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;
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;
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;
186 model->anchor_object = NULL;
190 model_fini (Model *model)
192 g_free (model->objects);
193 model->objects = NULL;
194 model->num_objects = 0;
195 g_free (model->sticks);
196 model->sticks = NULL;
198 g_free (model->offsets);
199 model->offsets = NULL;
200 model->num_offsets = 0;
204 model_accumulate_forces (Model *model)
208 for (i = 0; i < model->num_objects; i++) {
209 model->objects[i].force.x = 0;
210 model->objects[i].force.y = 3;
215 model_integrate (Model *model, double step)
221 for (i = 0; i < model->num_objects; i++) {
222 o = &model->objects[i];
227 x + (x - o->previous_position.x) + o->force.x * step * step;
229 y + (y - o->previous_position.y) + o->force.y * step * step;
231 o->previous_position.x = x;
232 o->previous_position.y = y;
237 model_constrain (Model *model, double step)
239 double dx, dy, x, y, distance, fraction;
242 /* Anchor object constraint. */
243 if (model->anchor_object != NULL) {
244 model->anchor_object->position.x = model->anchor_position.x;
245 model->anchor_object->position.y = model->anchor_position.y;
246 model->anchor_object->previous_position.x = model->anchor_position.x;
247 model->anchor_object->previous_position.y = model->anchor_position.y;
250 /* FIXME: this should be "is point inside box" test instead. Figure
251 * out from previous_position which edge the point has passed
252 * through and reflect in that. */
253 for (i = 0; i < model->num_objects; i++) {
254 x = model->objects[i].position.x;
255 y = model->objects[i].position.y;
256 if (box_top - edge_fuzz <= y &&
257 model->objects[i].previous_position.y <= box_top + edge_fuzz &&
259 model->objects[i].position.y = box_top - (y - box_top) * elasticity;
260 model->objects[i].previous_position.y =
261 box_top - (model->objects[i].previous_position.y - box_top) * elasticity;
265 /* Ground collision detection constraints. This puts a ground level
266 * in to make sure the points don't fall off the screen. */
267 for (i = 0; i < model->num_objects; i++) {
268 x = model->objects[i].position.x;
269 y = model->objects[i].position.y;
271 if (model->objects[i].position.y > ground_level) {
272 model->objects[i].position.y =
273 ground_level - (model->objects[i].position.y - ground_level) * elasticity;
274 model->objects[i].previous_position.y =
275 ground_level - (model->objects[i].previous_position.y - ground_level) * elasticity;
277 /* Friction on impact */
278 model->objects[i].position.x =
279 model->objects[i].position.x * (1 - ground_friction) +
280 model->objects[i].previous_position.x * ground_friction;
284 /* Offset constraints. */
285 for (i = 0; i < model->num_offsets; i++) {
286 x = (model->offsets[i].a->position.x + model->offsets[i].b->position.x) / 2;
287 y = (model->offsets[i].a->position.y + model->offsets[i].b->position.y) / 2;
288 model->offsets[i].a->position.x = x - model->offsets[i].dx / 2;
289 model->offsets[i].a->position.y = y - model->offsets[i].dy / 2;
290 model->offsets[i].b->position.x = x + model->offsets[i].dx / 2;
291 model->offsets[i].b->position.y = y + model->offsets[i].dy / 2;
295 /* Stick constraints. */
296 for (i = 0; i < model->num_sticks; i++) {
297 x = model->sticks[i].a->position.x;
298 y = model->sticks[i].a->position.y;
299 dx = model->sticks[i].b->position.x - x;
300 dy = model->sticks[i].b->position.y - y;
301 distance = sqrt (dx * dx + dy * dy);
302 fraction = (distance - model->sticks[i].length) / distance / 2;
303 model->sticks[i].a->position.x = x + dx * fraction;
304 model->sticks[i].a->position.y = y + dy * fraction;
305 model->sticks[i].b->position.x = x + dx * (1 - fraction);
306 model->sticks[i].b->position.y = y + dy * (1 - fraction);
309 /* Stick constraints, without square roots. */
310 squared = stick_length * stick_length;
311 for (i = 0; i < model->num_objects - 1; i++) {
313 x = model->objects[i].position.x;
314 y = model->objects[i].position.y;
315 dx = model->objects[j].position.x - x;
316 dy = model->objects[j].position.y - y;
317 fraction = squared / (dx * dx + dy * dy + squared) - 0.5;
318 model->objects[i].position.x = x + dx * fraction;
319 model->objects[i].position.y = y + dy * fraction;
320 model->objects[j].position.x = x + dx * (1 - fraction);
321 model->objects[j].position.y = y + dy * (1 - fraction);
327 model_step (Model *model, double delta_t)
331 model_accumulate_forces (model);
332 model_integrate (model, delta_t);
334 for (i = 0; i < 5; i++)
335 model_constrain (model, delta_t);
337 model->theta += delta_t;
341 object_distance (Object *object, double x, double y)
345 dx = object->position.x - x;
346 dy = object->position.y - y;
348 return sqrt (dx*dx + dy*dy);
352 model_find_nearest (Model *model, double x, double y)
355 double distance, min_distance;
358 for (i = 0; i < model->num_objects; i++) {
359 distance = object_distance (&model->objects[i], x, y);
360 if (i == 0 || distance < min_distance) {
361 min_distance = distance;
362 object = &model->objects[i];
369 typedef struct _Color Color;
371 double red, green, blue;
375 draw_sticks (cairo_t *cr,
381 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
383 cairo_set_line_width (cr, 2);
384 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
385 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
387 for (i = 0; i < model->num_sticks; i++) {
389 model->sticks[i].a->position.x,
390 model->sticks[i].a->position.y);
392 model->sticks[i].b->position.x,
393 model->sticks[i].b->position.y);
400 draw_offsets (cairo_t *cr,
406 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
408 cairo_set_line_width (cr, 4);
409 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
410 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
412 for (i = 0; i < model->num_offsets; i++) {
414 model->offsets[i].a->position.x,
415 model->offsets[i].a->position.y);
417 model->offsets[i].b->position.x,
418 model->offsets[i].b->position.y);
425 draw_constraints (cairo_t *cr,
429 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
431 cairo_move_to (cr, 0, ground_level);
432 cairo_line_to (cr, 1500, ground_level);
433 cairo_line_to (cr, 1500, ground_level + 10);
434 cairo_line_to (cr, 0, ground_level + 10);
435 cairo_close_path (cr);
437 cairo_move_to (cr, 0, box_top);
438 cairo_line_to (cr, box_left, box_top);
439 cairo_line_to (cr, box_left, box_bottom);
440 cairo_line_to (cr, 0, box_bottom);
441 cairo_close_path (cr);
447 draw_objects (cairo_t *cr, Model *model, Color *color)
451 for (i = 0; i < model->num_objects; i++) {
455 static Color blue = { 0, 0, 1 };
456 static Color red = { 1, 0, 0 };
457 static Color black = { 0, 0, 0 };
458 static Color white = { 1, 1, 1 };
461 expose_event (GtkWidget *widget,
462 GdkEventExpose *event,
468 cr = gdk_cairo_create (widget->window);
470 cairo_set_source_rgb (cr, 1, 1, 1);
473 draw_constraints (cr, model, &red);
474 draw_sticks (cr, model, &black);
475 draw_offsets (cr, model, &blue);
476 draw_objects (cr, model, &white);
484 button_press_event (GtkWidget *widget,
485 GdkEventButton *event,
490 if (event->button != 1)
493 model->anchor_position.x = event->x;
494 model->anchor_position.y = event->y;
495 model->anchor_object = model_find_nearest (model, event->x, event->y);
501 button_release_event (GtkWidget *widget,
502 GdkEventButton *event,
507 if ((event->state & GDK_BUTTON1_MASK) == 0)
510 model->anchor_object = NULL;
516 motion_notify_event (GtkWidget *widget,
517 GdkEventMotion *event,
522 GdkModifierType state;
524 gdk_window_get_pointer (event->window, &x, &y, &state);
526 model->anchor_position.x = x + 0.5;
527 model->anchor_position.y = y + 0.5;
532 typedef void (*ModelInitFunc) (Model *model);
535 model_changed (GtkComboBox *combo, gpointer user_data)
537 Model *model = user_data;
539 GtkTreeModel *tree_model;
543 tree_model = gtk_combo_box_get_model (combo);
544 if (!gtk_combo_box_get_active_iter (combo, &iter))
547 gtk_tree_model_get (tree_model, &iter, 0, &name, 1, &init, -1);
553 static GtkTreeModel *
554 create_model_store (void)
560 { "Rope", model_init_rope },
561 { "Snake", model_init_snake },
562 { "Curtain", model_init_curtain }
569 store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
571 for (i = 0; i < G_N_ELEMENTS(models); i++) {
572 gtk_tree_store_append (store, &iter, NULL);
573 gtk_tree_store_set (store, &iter,
574 0, models[i].name, 1, models[i].init, -1);
577 return GTK_TREE_MODEL (store);
582 create_model_combo (Model *model)
585 GtkWidget *combo, *label;
587 GtkCellRenderer *renderer;
589 hbox = gtk_hbox_new (FALSE, 8);
591 label = gtk_label_new_with_mnemonic ("_Model:");
592 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
594 store = create_model_store ();
595 combo = gtk_combo_box_new_with_model (store);
596 gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
597 g_object_unref (store);
599 renderer = gtk_cell_renderer_text_new ();
600 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE);
601 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer,
605 gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
606 gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0);
607 g_signal_connect (combo, "changed",
608 G_CALLBACK (model_changed), model);
614 create_window (Model *model)
620 GtkWidget *model_combo;
622 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
623 gtk_window_set_title (GTK_WINDOW (window), "Drawing Area");
625 g_signal_connect (window, "destroy",
626 G_CALLBACK (gtk_main_quit), &window);
628 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
630 vbox = gtk_vbox_new (FALSE, 8);
631 gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
632 gtk_container_add (GTK_CONTAINER (window), vbox);
635 * Create the drawing area
638 frame = gtk_frame_new (NULL);
639 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
640 gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
642 da = gtk_drawing_area_new ();
643 /* set a minimum size */
644 gtk_widget_set_size_request (da, 600, 500);
646 gtk_container_add (GTK_CONTAINER (frame), da);
648 /* Signals used to handle backing pixmap */
650 g_signal_connect (da, "expose_event",
651 G_CALLBACK (expose_event), model);
655 g_signal_connect (da, "motion_notify_event",
656 G_CALLBACK (motion_notify_event), model);
657 g_signal_connect (da, "button_press_event",
658 G_CALLBACK (button_press_event), model);
659 g_signal_connect (da, "button_release_event",
660 G_CALLBACK (button_release_event), model);
662 /* Ask to receive events the drawing area doesn't normally
665 gtk_widget_set_events (da, gtk_widget_get_events (da)
666 | GDK_LEAVE_NOTIFY_MASK
667 | GDK_BUTTON_PRESS_MASK
668 | GDK_BUTTON_RELEASE_MASK
669 | GDK_POINTER_MOTION_MASK
670 | GDK_POINTER_MOTION_HINT_MASK);
672 model_combo = create_model_combo (model);
673 gtk_box_pack_start (GTK_BOX (vbox), model_combo, FALSE, FALSE, 0);
678 typedef struct _Closure Closure;
680 GtkWidget *drawing_area;
686 timeout_callback (gpointer data)
688 Closure *closure = data;
691 for (i = 0; i < 3; i++)
692 model_step (closure->model, 0.5);
695 if (closure->i == 1) {
696 gtk_widget_queue_draw (closure->drawing_area);
704 main (int argc, char *argv[])
709 gtk_init (&argc, &argv);
710 model_init_rope (&model);
711 closure.drawing_area = create_window (&model);
713 gtk_widget_show_all (gtk_widget_get_toplevel (closure.drawing_area));
714 closure.model = &model;
715 g_timeout_add (100, timeout_callback, &closure);