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;
124 model->offsets = NULL;
125 model->num_objects = 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_fini (Model *model)
194 g_free (model->objects);
195 model->objects = NULL;
196 model->num_objects = 0;
197 g_free (model->sticks);
198 model->sticks = NULL;
200 g_free (model->offsets);
201 model->offsets = NULL;
202 model->num_offsets = 0;
206 model_accumulate_forces (Model *model)
210 for (i = 0; i < model->num_objects; i++) {
211 model->objects[i].force.x = 0;
212 model->objects[i].force.y = 3;
217 model_integrate (Model *model, double step)
223 for (i = 0; i < model->num_objects; i++) {
224 o = &model->objects[i];
229 x + (x - o->previous_position.x) + o->force.x * step * step;
231 y + (y - o->previous_position.y) + o->force.y * step * step;
233 o->previous_position.x = x;
234 o->previous_position.y = y;
239 model_constrain (Model *model, double step)
241 double dx, dy, x, y, distance, fraction;
244 /* Anchor object constraint. */
245 if (model->anchor_object != NULL) {
246 model->anchor_object->position.x = model->anchor_position.x;
247 model->anchor_object->position.y = model->anchor_position.y;
248 model->anchor_object->previous_position.x = model->anchor_position.x;
249 model->anchor_object->previous_position.y = model->anchor_position.y;
252 /* FIXME: this should be "is point inside box" test instead. Figure
253 * out from previous_position which edge the point has passed
254 * through and reflect in that. */
255 for (i = 0; i < model->num_objects; i++) {
256 x = model->objects[i].position.x;
257 y = model->objects[i].position.y;
258 if (box_top - edge_fuzz <= y &&
259 model->objects[i].previous_position.y <= box_top + edge_fuzz &&
261 model->objects[i].position.y = box_top - (y - box_top) * elasticity;
262 model->objects[i].previous_position.y =
263 box_top - (model->objects[i].previous_position.y - box_top) * elasticity;
267 /* Ground collision detection constraints. This puts a ground level
268 * in to make sure the points don't fall off the screen. */
269 for (i = 0; i < model->num_objects; i++) {
270 x = model->objects[i].position.x;
271 y = model->objects[i].position.y;
273 if (model->objects[i].position.y > ground_level) {
274 model->objects[i].position.y =
275 ground_level - (model->objects[i].position.y - ground_level) * elasticity;
276 model->objects[i].previous_position.y =
277 ground_level - (model->objects[i].previous_position.y - ground_level) * elasticity;
279 /* Friction on impact */
280 model->objects[i].position.x =
281 model->objects[i].position.x * (1 - ground_friction) +
282 model->objects[i].previous_position.x * ground_friction;
286 /* Offset constraints. */
287 for (i = 0; i < model->num_offsets; i++) {
288 x = (model->offsets[i].a->position.x + model->offsets[i].b->position.x) / 2;
289 y = (model->offsets[i].a->position.y + model->offsets[i].b->position.y) / 2;
290 model->offsets[i].a->position.x = x - model->offsets[i].dx / 2;
291 model->offsets[i].a->position.y = y - model->offsets[i].dy / 2;
292 model->offsets[i].b->position.x = x + model->offsets[i].dx / 2;
293 model->offsets[i].b->position.y = y + model->offsets[i].dy / 2;
297 /* Stick constraints. */
298 for (i = 0; i < model->num_sticks; i++) {
299 x = model->sticks[i].a->position.x;
300 y = model->sticks[i].a->position.y;
301 dx = model->sticks[i].b->position.x - x;
302 dy = model->sticks[i].b->position.y - y;
303 distance = sqrt (dx * dx + dy * dy);
304 fraction = (distance - model->sticks[i].length) / distance / 2;
305 model->sticks[i].a->position.x = x + dx * fraction;
306 model->sticks[i].a->position.y = y + dy * fraction;
307 model->sticks[i].b->position.x = x + dx * (1 - fraction);
308 model->sticks[i].b->position.y = y + dy * (1 - fraction);
311 /* Stick constraints, without square roots. */
312 squared = stick_length * stick_length;
313 for (i = 0; i < model->num_objects - 1; i++) {
315 x = model->objects[i].position.x;
316 y = model->objects[i].position.y;
317 dx = model->objects[j].position.x - x;
318 dy = model->objects[j].position.y - y;
319 fraction = squared / (dx * dx + dy * dy + squared) - 0.5;
320 model->objects[i].position.x = x + dx * fraction;
321 model->objects[i].position.y = y + dy * fraction;
322 model->objects[j].position.x = x + dx * (1 - fraction);
323 model->objects[j].position.y = y + dy * (1 - fraction);
329 model_step (Model *model, double delta_t)
333 model_accumulate_forces (model);
334 model_integrate (model, delta_t);
336 for (i = 0; i < 5; i++)
337 model_constrain (model, delta_t);
339 model->theta += delta_t;
343 object_distance (Object *object, double x, double y)
347 dx = object->position.x - x;
348 dy = object->position.y - y;
350 return sqrt (dx*dx + dy*dy);
354 model_find_nearest (Model *model, double x, double y)
357 double distance, min_distance;
360 for (i = 0; i < model->num_objects; i++) {
361 distance = object_distance (&model->objects[i], x, y);
362 if (i == 0 || distance < min_distance) {
363 min_distance = distance;
364 object = &model->objects[i];
371 typedef struct _Color Color;
373 double red, green, blue;
377 draw_sticks (cairo_t *cr,
383 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
385 cairo_set_line_width (cr, 2);
386 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
387 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
389 for (i = 0; i < model->num_sticks; i++) {
391 model->sticks[i].a->position.x,
392 model->sticks[i].a->position.y);
394 model->sticks[i].b->position.x,
395 model->sticks[i].b->position.y);
402 draw_offsets (cairo_t *cr,
408 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
410 cairo_set_line_width (cr, 4);
411 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
412 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
414 for (i = 0; i < model->num_offsets; i++) {
416 model->offsets[i].a->position.x,
417 model->offsets[i].a->position.y);
419 model->offsets[i].b->position.x,
420 model->offsets[i].b->position.y);
427 draw_constraints (cairo_t *cr,
431 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
433 cairo_move_to (cr, 0, ground_level);
434 cairo_line_to (cr, 1500, ground_level);
435 cairo_line_to (cr, 1500, ground_level + 10);
436 cairo_line_to (cr, 0, ground_level + 10);
437 cairo_close_path (cr);
439 cairo_move_to (cr, 0, box_top);
440 cairo_line_to (cr, box_left, box_top);
441 cairo_line_to (cr, box_left, box_bottom);
442 cairo_line_to (cr, 0, box_bottom);
443 cairo_close_path (cr);
449 draw_objects (cairo_t *cr, Model *model, Color *color)
453 for (i = 0; i < model->num_objects; i++) {
457 static Color blue = { 0, 0, 1 };
458 static Color red = { 1, 0, 0 };
459 static Color black = { 0, 0, 0 };
460 static Color white = { 1, 1, 1 };
463 expose_event (GtkWidget *widget,
464 GdkEventExpose *event,
470 cr = gdk_cairo_create (widget->window);
472 cairo_set_source_rgb (cr, 1, 1, 1);
475 draw_constraints (cr, model, &red);
476 draw_sticks (cr, model, &black);
477 draw_offsets (cr, model, &blue);
478 draw_objects (cr, model, &white);
486 button_press_event (GtkWidget *widget,
487 GdkEventButton *event,
492 if (event->button != 1)
495 model->anchor_position.x = event->x;
496 model->anchor_position.y = event->y;
497 model->anchor_object = model_find_nearest (model, event->x, event->y);
503 button_release_event (GtkWidget *widget,
504 GdkEventButton *event,
509 if ((event->state & GDK_BUTTON1_MASK) == 0)
512 model->anchor_object = NULL;
518 motion_notify_event (GtkWidget *widget,
519 GdkEventMotion *event,
524 GdkModifierType state;
526 gdk_window_get_pointer (event->window, &x, &y, &state);
528 model->anchor_position.x = x + 0.5;
529 model->anchor_position.y = y + 0.5;
534 typedef void (*ModelInitFunc) (Model *model);
537 model_changed (GtkComboBox *combo, gpointer user_data)
539 Model *model = user_data;
541 GtkTreeModel *tree_model;
545 tree_model = gtk_combo_box_get_model (combo);
546 if (!gtk_combo_box_get_active_iter (combo, &iter))
549 gtk_tree_model_get (tree_model, &iter, 0, &name, 1, &init, -1);
555 static GtkTreeModel *
556 create_model_store (void)
562 { "Rope", model_init_rope },
563 { "Snake", model_init_snake },
564 { "Curtain", model_init_curtain }
571 store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
573 for (i = 0; i < G_N_ELEMENTS(models); i++) {
574 gtk_tree_store_append (store, &iter, NULL);
575 gtk_tree_store_set (store, &iter,
576 0, models[i].name, 1, models[i].init, -1);
579 return GTK_TREE_MODEL (store);
584 create_model_combo (Model *model)
587 GtkWidget *combo, *label;
589 GtkCellRenderer *renderer;
591 hbox = gtk_hbox_new (FALSE, 8);
593 label = gtk_label_new_with_mnemonic ("_Model:");
594 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
596 store = create_model_store ();
597 combo = gtk_combo_box_new_with_model (store);
598 gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
599 g_object_unref (store);
601 renderer = gtk_cell_renderer_text_new ();
602 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE);
603 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer,
607 gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
608 gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0);
609 g_signal_connect (combo, "changed",
610 G_CALLBACK (model_changed), model);
616 create_window (Model *model)
622 GtkWidget *model_combo;
624 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
625 gtk_window_set_title (GTK_WINDOW (window), "Drawing Area");
627 g_signal_connect (window, "destroy",
628 G_CALLBACK (gtk_main_quit), &window);
630 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
632 vbox = gtk_vbox_new (FALSE, 8);
633 gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
634 gtk_container_add (GTK_CONTAINER (window), vbox);
637 * Create the drawing area
640 frame = gtk_frame_new (NULL);
641 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
642 gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
644 da = gtk_drawing_area_new ();
645 /* set a minimum size */
646 gtk_widget_set_size_request (da, 600, 500);
648 gtk_container_add (GTK_CONTAINER (frame), da);
650 /* Signals used to handle backing pixmap */
652 g_signal_connect (da, "expose_event",
653 G_CALLBACK (expose_event), model);
657 g_signal_connect (da, "motion_notify_event",
658 G_CALLBACK (motion_notify_event), model);
659 g_signal_connect (da, "button_press_event",
660 G_CALLBACK (button_press_event), model);
661 g_signal_connect (da, "button_release_event",
662 G_CALLBACK (button_release_event), model);
664 /* Ask to receive events the drawing area doesn't normally
667 gtk_widget_set_events (da, gtk_widget_get_events (da)
668 | GDK_LEAVE_NOTIFY_MASK
669 | GDK_BUTTON_PRESS_MASK
670 | GDK_BUTTON_RELEASE_MASK
671 | GDK_POINTER_MOTION_MASK
672 | GDK_POINTER_MOTION_HINT_MASK);
674 model_combo = create_model_combo (model);
675 gtk_box_pack_start (GTK_BOX (vbox), model_combo, FALSE, FALSE, 0);
680 typedef struct _Closure Closure;
682 GtkWidget *drawing_area;
688 timeout_callback (gpointer data)
690 Closure *closure = data;
693 for (i = 0; i < 3; i++)
694 model_step (closure->model, 0.5);
697 if (closure->i == 1) {
698 gtk_widget_queue_draw (closure->drawing_area);
706 main (int argc, char *argv[])
711 gtk_init (&argc, &argv);
712 model_init_rope (&model);
713 closure.drawing_area = create_window (&model);
715 gtk_widget_show_all (gtk_widget_get_toplevel (closure.drawing_area));
716 closure.model = &model;
717 g_timeout_add (100, timeout_callback, &closure);