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_accumulate_forces (Model *model)
194 for (i = 0; i < model->num_objects; i++) {
195 model->objects[i].force.x = 0;
196 model->objects[i].force.y = 3;
201 model_integrate (Model *model, double step)
207 for (i = 0; i < model->num_objects; i++) {
208 o = &model->objects[i];
213 x + (x - o->previous_position.x) + o->force.x * step * step;
215 y + (y - o->previous_position.y) + o->force.y * step * step;
217 o->previous_position.x = x;
218 o->previous_position.y = y;
223 model_constrain (Model *model, double step)
225 double dx, dy, x, y, distance, fraction;
228 /* Anchor object constraint. */
229 if (model->anchor_object != NULL) {
230 model->anchor_object->position.x = model->anchor_position.x;
231 model->anchor_object->position.y = model->anchor_position.y;
232 model->anchor_object->previous_position.x = model->anchor_position.x;
233 model->anchor_object->previous_position.y = model->anchor_position.y;
236 /* FIXME: this should be "is point inside box" test instead. Figure
237 * out from previous_position which edge the point has passed
238 * through and reflect in that. */
239 for (i = 0; i < model->num_objects; i++) {
240 x = model->objects[i].position.x;
241 y = model->objects[i].position.y;
242 if (box_top - edge_fuzz <= y &&
243 model->objects[i].previous_position.y <= box_top + edge_fuzz &&
245 model->objects[i].position.y = box_top - (y - box_top) * elasticity;
246 model->objects[i].previous_position.y =
247 box_top - (model->objects[i].previous_position.y - box_top) * elasticity;
251 /* Ground collision detection constraints. This puts a ground level
252 * in to make sure the points don't fall off the screen. */
253 for (i = 0; i < model->num_objects; i++) {
254 x = model->objects[i].position.x;
255 y = model->objects[i].position.y;
257 if (model->objects[i].position.y > ground_level) {
258 model->objects[i].position.y =
259 ground_level - (model->objects[i].position.y - ground_level) * elasticity;
260 model->objects[i].previous_position.y =
261 ground_level - (model->objects[i].previous_position.y - ground_level) * elasticity;
263 /* Friction on impact */
264 model->objects[i].position.x =
265 model->objects[i].position.x * (1 - ground_friction) +
266 model->objects[i].previous_position.x * ground_friction;
270 /* Offset constraints. */
271 for (i = 0; i < model->num_offsets; i++) {
272 x = (model->offsets[i].a->position.x + model->offsets[i].b->position.x) / 2;
273 y = (model->offsets[i].a->position.y + model->offsets[i].b->position.y) / 2;
274 model->offsets[i].a->position.x = x - model->offsets[i].dx / 2;
275 model->offsets[i].a->position.y = y - model->offsets[i].dy / 2;
276 model->offsets[i].b->position.x = x + model->offsets[i].dx / 2;
277 model->offsets[i].b->position.y = y + model->offsets[i].dy / 2;
281 /* Stick constraints. */
282 for (i = 0; i < model->num_sticks; i++) {
283 x = model->sticks[i].a->position.x;
284 y = model->sticks[i].a->position.y;
285 dx = model->sticks[i].b->position.x - x;
286 dy = model->sticks[i].b->position.y - y;
287 distance = sqrt (dx * dx + dy * dy);
288 fraction = (distance - model->sticks[i].length) / distance / 2;
289 model->sticks[i].a->position.x = x + dx * fraction;
290 model->sticks[i].a->position.y = y + dy * fraction;
291 model->sticks[i].b->position.x = x + dx * (1 - fraction);
292 model->sticks[i].b->position.y = y + dy * (1 - fraction);
295 /* Stick constraints, without square roots. */
296 squared = stick_length * stick_length;
297 for (i = 0; i < model->num_objects - 1; i++) {
299 x = model->objects[i].position.x;
300 y = model->objects[i].position.y;
301 dx = model->objects[j].position.x - x;
302 dy = model->objects[j].position.y - y;
303 fraction = squared / (dx * dx + dy * dy + squared) - 0.5;
304 model->objects[i].position.x = x + dx * fraction;
305 model->objects[i].position.y = y + dy * fraction;
306 model->objects[j].position.x = x + dx * (1 - fraction);
307 model->objects[j].position.y = y + dy * (1 - fraction);
313 model_step (Model *model, double delta_t)
317 model_accumulate_forces (model);
318 model_integrate (model, delta_t);
320 for (i = 0; i < 5; i++)
321 model_constrain (model, delta_t);
323 model->theta += delta_t;
327 object_distance (Object *object, double x, double y)
331 dx = object->position.x - x;
332 dy = object->position.y - y;
334 return sqrt (dx*dx + dy*dy);
338 model_find_nearest (Model *model, double x, double y)
341 double distance, min_distance;
344 for (i = 0; i < model->num_objects; i++) {
345 distance = object_distance (&model->objects[i], x, y);
346 if (i == 0 || distance < min_distance) {
347 min_distance = distance;
348 object = &model->objects[i];
355 typedef struct _Color Color;
357 double red, green, blue;
361 draw_sticks (cairo_t *cr,
367 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
369 cairo_set_line_width (cr, 2);
370 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
371 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
373 for (i = 0; i < model->num_sticks; i++) {
375 model->sticks[i].a->position.x,
376 model->sticks[i].a->position.y);
378 model->sticks[i].b->position.x,
379 model->sticks[i].b->position.y);
386 draw_offsets (cairo_t *cr,
392 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
394 cairo_set_line_width (cr, 4);
395 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
396 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
398 for (i = 0; i < model->num_offsets; i++) {
400 model->offsets[i].a->position.x,
401 model->offsets[i].a->position.y);
403 model->offsets[i].b->position.x,
404 model->offsets[i].b->position.y);
411 draw_constraints (cairo_t *cr,
415 cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
417 cairo_move_to (cr, 0, ground_level);
418 cairo_line_to (cr, 1500, ground_level);
419 cairo_line_to (cr, 1500, ground_level + 10);
420 cairo_line_to (cr, 0, ground_level + 10);
421 cairo_close_path (cr);
423 cairo_move_to (cr, 0, box_top);
424 cairo_line_to (cr, box_left, box_top);
425 cairo_line_to (cr, box_left, box_bottom);
426 cairo_line_to (cr, 0, box_bottom);
427 cairo_close_path (cr);
433 draw_objects (cairo_t *cr, Model *model, Color *color)
437 for (i = 0; i < model->num_objects; i++) {
441 static Color blue = { 0, 0, 1 };
442 static Color red = { 1, 0, 0 };
443 static Color black = { 0, 0, 0 };
444 static Color white = { 1, 1, 1 };
447 sproing_expose_event (GtkWidget *widget,
448 GdkEventExpose *event,
454 cr = gdk_cairo_create (widget->window);
456 cairo_set_source_rgb (cr, 1, 1, 1);
459 draw_constraints (cr, model, &red);
460 draw_sticks (cr, model, &black);
461 draw_offsets (cr, model, &blue);
462 draw_objects (cr, model, &white);
470 sproing_button_press_event (GtkWidget *widget,
471 GdkEventButton *event,
476 if (event->button != 1)
479 model->anchor_position.x = event->x;
480 model->anchor_position.y = event->y;
481 model->anchor_object = model_find_nearest (model, event->x, event->y);
487 sproing_button_release_event (GtkWidget *widget,
488 GdkEventButton *event,
493 if ((event->state & GDK_BUTTON1_MASK) == 0)
496 model->anchor_object = NULL;
502 sproing_motion_notify_event (GtkWidget *widget,
503 GdkEventMotion *event,
508 GdkModifierType state;
510 gdk_window_get_pointer (event->window, &x, &y, &state);
512 model->anchor_position.x = x + 0.5;
513 model->anchor_position.y = y + 0.5;
519 spring_constant_changed (GtkSpinButton *spinbutton, gpointer user_data)
521 Model *model = user_data;
523 model->k = gtk_spin_button_get_value (spinbutton);
527 friction_changed (GtkSpinButton *spinbutton, gpointer user_data)
529 Model *model = user_data;
531 model->friction = gtk_spin_button_get_value (spinbutton);
535 create_spinners (Model *model)
538 GtkWidget *spinner, *label;
540 hbox = gtk_hbox_new (FALSE, 8);
542 label = gtk_label_new_with_mnemonic ("_Spring constant:");
543 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
544 spinner = gtk_spin_button_new_with_range (0.05, 30.00, 0.05);
545 gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinner);
546 gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
547 gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), model->k);
548 g_signal_connect (spinner, "value-changed",
549 G_CALLBACK (spring_constant_changed), model);
551 label = gtk_label_new_with_mnemonic ("_Friction:");
552 gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
553 spinner = gtk_spin_button_new_with_range (0.05, 15.00, 0.05);
554 gtk_label_set_mnemonic_widget (GTK_LABEL (label), spinner);
555 gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
556 gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), model->friction);
557 g_signal_connect (spinner, "value-changed",
558 G_CALLBACK (friction_changed), model);
564 create_window (Model *model)
572 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
573 gtk_window_set_title (GTK_WINDOW (window), "Drawing Area");
575 g_signal_connect (window, "destroy",
576 G_CALLBACK (gtk_main_quit), &window);
578 gtk_container_set_border_width (GTK_CONTAINER (window), 8);
580 vbox = gtk_vbox_new (FALSE, 8);
581 gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
582 gtk_container_add (GTK_CONTAINER (window), vbox);
585 * Create the drawing area
588 frame = gtk_frame_new (NULL);
589 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
590 gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
592 da = gtk_drawing_area_new ();
593 /* set a minimum size */
594 gtk_widget_set_size_request (da, 600, 500);
596 gtk_container_add (GTK_CONTAINER (frame), da);
598 /* Signals used to handle backing pixmap */
600 g_signal_connect (da, "expose_event",
601 G_CALLBACK (sproing_expose_event), model);
605 g_signal_connect (da, "motion_notify_event",
606 G_CALLBACK (sproing_motion_notify_event), model);
607 g_signal_connect (da, "button_press_event",
608 G_CALLBACK (sproing_button_press_event), model);
609 g_signal_connect (da, "button_release_event",
610 G_CALLBACK (sproing_button_release_event), model);
612 /* Ask to receive events the drawing area doesn't normally
615 gtk_widget_set_events (da, gtk_widget_get_events (da)
616 | GDK_LEAVE_NOTIFY_MASK
617 | GDK_BUTTON_PRESS_MASK
618 | GDK_BUTTON_RELEASE_MASK
619 | GDK_POINTER_MOTION_MASK
620 | GDK_POINTER_MOTION_HINT_MASK);
622 spinners = create_spinners (model);
623 gtk_box_pack_start (GTK_BOX (vbox), spinners, FALSE, FALSE, 0);
628 typedef struct _Closure Closure;
630 GtkWidget *drawing_area;
636 timeout_callback (gpointer data)
638 Closure *closure = data;
641 for (i = 0; i < 3; i++)
642 model_step (closure->model, 0.5);
645 if (closure->i == 1) {
646 gtk_widget_queue_draw (closure->drawing_area);
654 main (int argc, char *argv[])
659 gtk_init (&argc, &argv);
660 model_init_curtain (&model);
661 closure.drawing_area = create_window (&model);
663 gtk_widget_show_all (gtk_widget_get_toplevel (closure.drawing_area));
664 closure.model = &model;
665 g_timeout_add (100, timeout_callback, &closure);