]> git.cworth.org Git - akamaru/blobdiff - akamaru.c
Indent to 8 columns rather than 2
[akamaru] / akamaru.c
index 48a226e93fbdb940a95d2fd9e9ac225c7063b958..fd3c468a82d34f5467f1cfbb17ff6b9ecbe57c38 100644 (file)
--- a/akamaru.c
+++ b/akamaru.c
@@ -1,9 +1,4 @@
-/*                                           -*- mode: c; c-basic-offset: 2 -*-
- * To compile:
- *
- *     gcc -Wall -g $(pkg-config --cflags --libs gtk+-2.0 cairo) \
- *             akamaru.c -o akamaru
- *
+/*                                           -*- mode: c; c-basic-offset: 8 -*-
  * See:
  *
  *     http://en.wikipedia.org/wiki/Verlet_integration
  *
  * TODO:
  *
- *     - Add code to add boxes
- *     - Add circle object
+ * - Add code to add boxes
+ * - Add circle object
+ * - Try out this idea: make constraint solver take mean of all
+ *   corrections at the end instead of meaning as it goes.
  */
 
-#include <gtk/gtk.h>
-#include <cairo.h>
-#include <cairo-xlib.h>
-#include <gdk/gdkx.h>
+#include <glib.h>
 #include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
 #include <math.h>
 
-const double ground_friction = 0.1, ground_level = 400;
-const double box_left = 200, box_top = 200, box_bottom = 210;
+#include "akamaru.h"
+
 const double elasticity = 0.7;
-const double edge_fuzz = 1;
-
-typedef struct _xy_pair Point;
-typedef struct _xy_pair Vector;
-struct _xy_pair {
-  double x, y;
-};
-
-typedef struct _Object Object;
-typedef struct _Stick Stick;
-typedef struct _Offset Offset;
-typedef struct _Model Model;
-
-struct _Object {
-  Vector force;
-
-  Point position;
-  Point previous_position;
-  Vector velocity;
-
-  double mass;
-  double theta;
-};
-
-struct _Stick {
-  Object *a, *b;
-  int length;
-};
-
-struct _Offset {
-  Object *a, *b;
-  int dx, dy;
-};
-
-struct _Model {
-  int num_objects;
-  Object *objects;
-  int num_sticks;
-  Stick *sticks;
-  int num_offsets;
-  Offset *offsets;
-  double k;
-  double friction;
-
-  Object *anchor_object;
-  Vector anchor_position;
-
-  double theta;
-};
+const double friction = 8;
+const double gravity = 50;
 
-static void
-model_init_snake (Model *model)
+void
+object_init (Object *object, double x, double y, double mass)
 {
-  const int num_objects = 20;
-  const int num_sticks = num_objects * 2 - 3;
-  int i;
-
-  model->objects = g_new (Object, num_objects);
-  model->num_objects = num_objects;
-  model->sticks = g_new (Stick, num_sticks);
-  model->num_sticks = num_sticks;
-  model->num_offsets = 0;
-
-  for (i = 0; i < num_objects; i++) {
-    model->objects[i].position.x = random() % 200 + 20;
-    model->objects[i].position.y = random() % 200 + 20;
-    model->objects[i].previous_position.x = random() % 200 + 20;
-    model->objects[i].previous_position.y = random() % 200 + 20;
-
-    if (i + 1 < num_objects) {
-      model->sticks[i * 2].a = &model->objects[i];
-      model->sticks[i * 2].b = &model->objects[i + 1];
-      model->sticks[i * 2].length = random() % 20 + 20;
-    }
-    if (i + 2 < num_objects) {
-      model->sticks[i * 2 + 1].a = &model->objects[i];
-      model->sticks[i * 2 + 1].b = &model->objects[i + 2];
-      model->sticks[i * 2 + 1].length = random() % 20 + 20;
-    }
-  }
-
-  model->anchor_object = NULL;
+       object->position.x = x;
+       object->position.y = y;
+       object->previous_position.x = x;
+       object->previous_position.y = y;
+       object->mass = mass;
 }
 
-static void
-model_init_rope (Model *model)
+void
+spring_init (Spring *spring, Object *a, Object *b, double length)
 {
-  const int num_objects = 20;
-  const int num_sticks = num_objects - 1;
-  const int stick_length = 20;
-  int i;
-
-  model->objects = g_new (Object, num_objects);
-  model->num_objects = num_objects;
-  model->sticks = g_new (Stick, num_sticks);
-  model->num_sticks = num_sticks;
-
-  for (i = 0; i < num_objects; i++) {
-    model->objects[i].position.x = 200;
-    model->objects[i].position.y = 40 + i * stick_length;
-    model->objects[i].previous_position.x = 200;
-    model->objects[i].previous_position.y = 40 + i * stick_length;
-
-    if (i + 1 < num_objects) {
-      model->sticks[i].a = &model->objects[i];
-      model->sticks[i].b = &model->objects[i + 1];
-      model->sticks[i].length = stick_length;
-    }
-  }
-
-  model->anchor_object = NULL;
+       spring->a = a;
+       spring->b = b;
+       spring->length = length;
 }
 
-static void
-model_init_curtain (Model *model)
+void
+stick_init (Stick *stick, Object *a, Object *b, double length)
 {
-  const int num_ropes = 5;
-  const int num_rope_objects = 15;
-  const int num_objects = num_ropes * num_rope_objects;
-  const int num_sticks = num_ropes * (num_rope_objects - 1);
-  const int stick_length = 10;
-  const int rope_offset = 30;
-  double x, y;
-  int i, j, index, stick_index;
-
-  model->objects = g_new (Object, num_objects);
-  model->num_objects = num_objects;
-  model->sticks = g_new (Stick, num_sticks);
-  model->num_sticks = num_sticks;
-  model->offsets = g_new (Offset, num_ropes - 1);
-  model->num_offsets = num_ropes - 1;
-
-  for (i = 0; i < num_ropes; i++) {
-    for (j = 0; j < num_rope_objects; j++) {
-      x = 200 + i * rope_offset;
-      y = 40 + j * stick_length;
-      index = i * num_rope_objects + j;
-      model->objects[index].position.x = x;
-      model->objects[index].position.y = y;
-      model->objects[index].previous_position.x = x;
-      model->objects[index].previous_position.y = y;
-
-      if (j + 1 < num_rope_objects) {
-       stick_index = i * (num_rope_objects - 1) + j;
-       model->sticks[stick_index].a = &model->objects[index];
-       model->sticks[stick_index].b = &model->objects[index + 1];
-       model->sticks[stick_index].length = stick_length;
-      }
-    }
-
-    if (i + 1 < num_ropes) {
-      model->offsets[i].a = &model->objects[i * num_rope_objects];
-      model->offsets[i].b = &model->objects[(i + 1) * num_rope_objects];
-      model->offsets[i].dx = rope_offset;
-      model->offsets[i].dy = 0;
-    }
-  }
-
-  model->anchor_object = NULL;
+       stick->a = a;
+       stick->b = b;
+       stick->length = length;
 }
 
-static void
-model_fini (Model *model)
+void
+string_init (String *string, Object *a, Object *b, double length)
 {
-  g_free (model->objects);
-  model->objects = NULL;
-  model->num_objects = 0;
-  g_free (model->sticks);
-  model->sticks = NULL;
-  model->sticks = 0;
-  g_free (model->offsets);
-  model->offsets = NULL;
-  model->num_offsets = 0;
+       string->a = a;
+       string->b = b;
+       string->length = length;
 }
 
-static void
-model_accumulate_forces (Model *model)
+void
+offset_spring_init (OffsetSpring *spring, Object *a, Object *b,
+                   double dx, double dy)
 {
-  int i;
-
-  for (i = 0; i < model->num_objects; i++) {
-    model->objects[i].force.x = 0;
-    model->objects[i].force.y = 3;
-  }
+       spring->a = a;
+       spring->b = b;
+       spring->dx = dx;
+       spring->dy = dy;
 }
 
-static void
-model_integrate (Model *model, double step)
+void
+spacer_init (Spacer *spacer, Object *a, Object *b, double length)
 {
-  double x, y;
-  Object *o;
-  int i;
-
-  for (i = 0; i < model->num_objects; i++) {
-    o = &model->objects[i];
-    x = o->position.x;
-    y = o->position.y;
-    
-    o->position.x =
-      x + (x - o->previous_position.x) + o->force.x * step * step;
-    o->position.y =
-      y + (y - o->previous_position.y) + o->force.y * step * step;
-
-    o->previous_position.x = x;
-    o->previous_position.y = y;
-  }
+       spacer->a = a;
+       spacer->b = b;
+       spacer->length = length;
 }
 
-static void
-model_constrain (Model *model, double step)
+void
+anchor_init (Anchor *anchor, Object *object, double x, double y)
 {
-  double dx, dy, x, y, distance, fraction;
-  int i;
-
-  /* Anchor object constraint. */
-  if (model->anchor_object != NULL) {
-    model->anchor_object->position.x = model->anchor_position.x;
-    model->anchor_object->position.y = model->anchor_position.y;
-    model->anchor_object->previous_position.x = model->anchor_position.x;
-    model->anchor_object->previous_position.y = model->anchor_position.y;
-  }
-
-  /* FIXME: this should be "is point inside box" test instead. Figure
-   * out from previous_position which edge the point has passed
-   * through and reflect in that. */
-  for (i = 0; i < model->num_objects; i++) {
-    x = model->objects[i].position.x;
-    y = model->objects[i].position.y;
-    if (box_top - edge_fuzz <= y &&
-       model->objects[i].previous_position.y <= box_top + edge_fuzz &&
-       x < box_left) {
-      model->objects[i].position.y = box_top - (y - box_top) * elasticity;
-      model->objects[i].previous_position.y =
-       box_top - (model->objects[i].previous_position.y - box_top) * elasticity;
-    }
-  }
-
-  /* Ground collision detection constraints.  This puts a ground level
-   * in to make sure the points don't fall off the screen. */
-  for (i = 0; i < model->num_objects; i++) {
-    x = model->objects[i].position.x;
-    y = model->objects[i].position.y;
-
-    if (model->objects[i].position.y > ground_level) {
-      model->objects[i].position.y =
-       ground_level - (model->objects[i].position.y - ground_level) * elasticity;
-      model->objects[i].previous_position.y =
-       ground_level - (model->objects[i].previous_position.y - ground_level) * elasticity;
-
-      /* Friction on impact */
-      model->objects[i].position.x =
-       model->objects[i].position.x * (1 - ground_friction) +
-       model->objects[i].previous_position.x * ground_friction;
-    }
-  }
-
-  /* Offset constraints. */
-  for (i = 0; i < model->num_offsets; i++) {
-    x = (model->offsets[i].a->position.x + model->offsets[i].b->position.x) / 2;
-    y = (model->offsets[i].a->position.y + model->offsets[i].b->position.y) / 2;
-    model->offsets[i].a->position.x = x - model->offsets[i].dx / 2;
-    model->offsets[i].a->position.y = y - model->offsets[i].dy / 2;
-    model->offsets[i].b->position.x = x + model->offsets[i].dx / 2;
-    model->offsets[i].b->position.y = y + model->offsets[i].dy / 2;
-  }
-
-#if 1
-  /* Stick constraints. */
-  for (i = 0; i < model->num_sticks; i++) {
-    x = model->sticks[i].a->position.x;
-    y = model->sticks[i].a->position.y;
-    dx = model->sticks[i].b->position.x - x;
-    dy = model->sticks[i].b->position.y - y;
-    distance = sqrt (dx * dx + dy * dy);
-    fraction = (distance - model->sticks[i].length) / distance / 2;
-    model->sticks[i].a->position.x = x + dx * fraction;
-    model->sticks[i].a->position.y = y + dy * fraction;
-    model->sticks[i].b->position.x = x + dx * (1 - fraction);
-    model->sticks[i].b->position.y = y + dy * (1 - fraction);
-  }
-#else
-  /* Stick constraints, without square roots. */
-  squared = stick_length * stick_length;
-  for (i = 0; i < model->num_objects - 1; i++) {
-    j = i + 1;
-    x = model->objects[i].position.x;
-    y = model->objects[i].position.y;
-    dx = model->objects[j].position.x - x;
-    dy = model->objects[j].position.y - y;
-    fraction = squared / (dx * dx + dy * dy + squared) - 0.5;
-    model->objects[i].position.x = x + dx * fraction;
-    model->objects[i].position.y = y + dy * fraction;
-    model->objects[j].position.x = x + dx * (1 - fraction);
-    model->objects[j].position.y = y + dy * (1 - fraction);
-  }
-#endif
+       anchor->object = object;
+       anchor->x = x;
+       anchor->y = y;
 }
 
-static void
-model_step (Model *model, double delta_t)
+void
+polygon_init (Polygon *p, int enclosing, int num_points, ...)
 {
-  int i;
-
-  model_accumulate_forces (model);
-  model_integrate (model, delta_t);
-
-  for (i = 0; i < 5; i++)
-    model_constrain (model, delta_t);
-
-  model->theta += delta_t;
+       double dx, dy, length;
+       int i, j;
+       va_list ap;
+
+       /* Polygons are defined counter-clock-wise in a coordinate system
+        * with the y-axis pointing down. */
+
+       va_start (ap, num_points);
+       p->num_points = num_points;
+       p->points = g_new (Point, num_points);
+       p->enclosing = enclosing;
+
+       for (i = 0; i < num_points; i++) {
+               p->points[i].x = va_arg (ap, double);
+               p->points[i].y = va_arg (ap, double);
+       }
+       va_end (ap);
+  
+       p->normals = g_new (Vector, p->num_points);
+       /* Compute outward pointing normals.  p->normals[i] is the normal
+        * for the edged between p->points[i] and p->points[i + 1]. */
+       for (i = 0; i < p->num_points; i++) {
+               j = (i + 1) % p->num_points;
+               dx = p->points[j].x - p->points[i].x;
+               dy = p->points[j].y - p->points[i].y;
+               length = sqrt (dx * dx + dy * dy);
+               p->normals[i].x = -dy / length;
+               p->normals[i].y = dx / length;
+       }
 }
 
-static double
-object_distance (Object *object, double x, double y)
+void
+polygon_init_diamond (Polygon *polygon, double x, double y)
 {
-  double dx, dy;
-
-  dx = object->position.x - x;
-  dy = object->position.y - y;
-
-  return sqrt (dx*dx + dy*dy);
+       return polygon_init (polygon, FALSE, 5, 
+                            x, y, 
+                            x + 10, y + 40,
+                            x + 90, y + 40,
+                            x + 100, y,
+                            x + 50, y - 20);
 }
 
-static Object *
-model_find_nearest (Model *model, double x, double y)
+void
+polygon_init_rectangle (Polygon *polygon, double x0, double y0,
+                       double x1, double y1)
 {
-  Object *object;
-  double distance, min_distance;
-  int i;
-
-  for (i = 0; i < model->num_objects; i++) {
-    distance = object_distance (&model->objects[i], x, y);
-    if (i == 0 || distance < min_distance) {
-      min_distance = distance;
-      object = &model->objects[i];
-    }
-  }
-
-  return object;
+       return polygon_init (polygon, FALSE, 4, x0, y0, x0, y1, x1, y1, x1, y0);
 }
 
-typedef struct _Color Color;
-struct _Color {
-  double red, green, blue;
-};
-
-static void
-draw_sticks (cairo_t *cr,
-            Model   *model,
-            Color   *color)
+void
+polygon_init_enclosing_rectangle (Polygon *polygon, double x0, double y0,
+                                 double x1, double y1)
 {
-  int i;
-
-  cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
-  cairo_new_path (cr);
-  cairo_set_line_width (cr, 2);
-  cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
-  cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
-
-  for (i = 0; i < model->num_sticks; i++) {
-    cairo_move_to (cr,
-                  model->sticks[i].a->position.x,
-                  model->sticks[i].a->position.y);
-    cairo_line_to (cr,
-                  model->sticks[i].b->position.x,
-                  model->sticks[i].b->position.y);
-  }
-
-  cairo_stroke (cr);
+       return polygon_init (polygon, TRUE, 4, x0, y0, x0, y1, x1, y1, x1, y0);
 }
 
-static void
-draw_offsets (cairo_t *cr,
-             Model   *model,
-             Color   *color)
+void
+model_fini (Model *model)
 {
-  int i;
-
-  cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
-  cairo_new_path (cr);
-  cairo_set_line_width (cr, 4);
-  cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
-  cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
-
-  for (i = 0; i < model->num_offsets; i++) {
-    cairo_move_to (cr,
-                  model->offsets[i].a->position.x,
-                  model->offsets[i].a->position.y);
-    cairo_line_to (cr,
-                  model->offsets[i].b->position.x,
-                  model->offsets[i].b->position.y);
-  }
-
-  cairo_stroke (cr);
+       int i;
+
+       g_free (model->objects);
+       g_free (model->sticks);
+       g_free (model->strings);
+       for (i = 0; i < model->num_offsets; i++)
+               g_free (model->offsets[i].objects);
+       g_free (model->springs);
+       g_free (model->offset_springs);
+       g_free (model->spacers);
+       for (i = 0; i < model->num_polygons; i++)
+               g_free (model->polygons[i].points);
+       g_free (model->polygons);
+
+       memset (model, 0, sizeof *model);
 }
 
 static void
-draw_constraints (cairo_t *cr,
-                 Model   *model,
-                 Color   *color)
+model_accumulate_forces (Model *model)
 {
-  cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
-
-  cairo_move_to (cr, 0, ground_level);
-  cairo_line_to (cr, 1500, ground_level);
-  cairo_line_to (cr, 1500, ground_level + 10);
-  cairo_line_to (cr, 0, ground_level + 10);
-  cairo_close_path (cr);
-
-  cairo_move_to (cr, 0, box_top);
-  cairo_line_to (cr, box_left, box_top);
-  cairo_line_to (cr, box_left, box_bottom);
-  cairo_line_to (cr, 0, box_bottom);
-  cairo_close_path (cr);
-
-  cairo_fill (cr);
+       int i;
+       double x, y, dx, dy, distance, displacement;
+       Point middle;
+       Vector u, v;
+
+       for (i = 0; i < model->num_objects; i++) {
+               /* Gravity */
+               model->objects[i].force.x = 0;
+               model->objects[i].force.y = gravity * model->objects[i].mass;
+
+               /* Friction */
+               v.x = model->objects[i].position.x - model->objects[i].previous_position.x;
+               v.y = model->objects[i].position.y - model->objects[i].previous_position.y;
+               model->objects[i].force.x -= v.x * friction;
+               model->objects[i].force.y -= v.y * friction;
+       }
+
+       for (i = 0; i < model->num_springs; i++) {
+               x = model->springs[i].a->position.x;
+               y = model->springs[i].a->position.y;
+               dx = model->springs[i].b->position.x - x;
+               dy = model->springs[i].b->position.y - y;
+               distance = sqrt (dx * dx + dy * dy);
+               u.x = dx / distance;
+               u.y = dy / distance;
+               displacement = distance - model->springs[i].length;
+               model->springs[i].a->force.x += u.x * model->k * displacement;
+               model->springs[i].a->force.y += u.y * model->k * displacement;
+               model->springs[i].b->force.x -= u.x * model->k * displacement;
+               model->springs[i].b->force.y -= u.y * model->k * displacement;
+       }
+
+       for (i = 0; i < model->num_offset_springs; i++) {
+               middle.x = 
+                       (model->offset_springs[i].a->position.x + 
+                        model->offset_springs[i].b->position.x) / 2;
+               middle.y = 
+                       (model->offset_springs[i].a->position.y + 
+                        model->offset_springs[i].b->position.y) / 2;
+
+               x = middle.x - model->offset_springs[i].dx / 2;
+               y = middle.y - model->offset_springs[i].dy / 2;
+
+               dx = x - model->offset_springs[i].a->position.x;
+               dy = y - model->offset_springs[i].a->position.y;
+
+               model->offset_springs[i].a->force.x += dx * model->k;
+               model->offset_springs[i].a->force.y += dy * model->k;
+               model->offset_springs[i].b->force.x -= dx * model->k;
+               model->offset_springs[i].b->force.y -= dy * model->k;
+       }
+
+       for (i = 0; i < model->num_objects; i++) {
+               double f = 
+                       model->objects[i].force.x * model->objects[i].force.x +
+                       model->objects[i].force.y * model->objects[i].force.y;
+
+               if (f > 100000000)
+                       abort();
+       }
 }
 
 static void
-draw_objects (cairo_t *cr, Model *model, Color *color)
+model_integrate (Model *model, double step)
 {
-  int i;
-
-  for (i = 0; i < model->num_objects; i++) {
-  }
+       double x, y;
+       Object *o;
+       int i;
+
+       for (i = 0; i < model->num_objects; i++) {
+               o = &model->objects[i];
+               x = o->position.x;
+               y = o->position.y;
+    
+               o->position.x =
+                       x + (x - o->previous_position.x) + o->force.x * step * step;
+               o->position.y =
+                       y + (y - o->previous_position.y) + o->force.y * step * step;
+
+               o->previous_position.x = x;
+               o->previous_position.y = y;
+       }
 }
 
-static Color blue = { 0, 0, 1 };
-static Color red = { 1, 0, 0 };
-static Color black = { 0, 0, 0 };
-static Color white = { 1, 1, 1 };
+/* The square root in the distance computation for the string and
+ * stick constraints can be aproximated using Newton:
+ *
+ *    distance = 
+ *      (model->sticks[i].length +
+ *       (dx * dx + dy * dy) / model->sticks[i].length) / 2;
+ *
+ * This works really well, since the constraints aren't typically
+ * violated much.  Thus, the distance is really close to the stick
+ * length, which then makes a good initial guess.  However, the
+ * approximation seems to be slower that just calling sqrt()...
+ */
 
-static gboolean
-expose_event (GtkWidget      *widget,
-             GdkEventExpose *event,
-             gpointer        data)
+static inline double
+estimate_distance (double dx, double dy, double r)
 {
-  Model *model = data;
-  cairo_t *cr;
-
-  cr = gdk_cairo_create (widget->window);
-
-  cairo_set_source_rgb (cr, 1, 1, 1);
-  cairo_paint (cr);
-
-  draw_constraints (cr, model, &red);
-  draw_sticks (cr, model, &black);
-  draw_offsets (cr, model, &blue);
-  draw_objects (cr, model, &white);
-
-  cairo_destroy (cr);
-
-  return TRUE;
+#ifdef APPROXIMATE_SQUARE_ROOTS
+       return (r + (dx * dx + dy * dy) / r) / 2;
+#else
+       return sqrt (dx * dx + dy * dy);
+#endif
 }
 
-static gboolean
-button_press_event (GtkWidget     *widget,
-                   GdkEventButton *event,
-                   gpointer        data)
+static int
+polygon_contains_point (Polygon *polygon, Point *point)
 {
-  Model *model = data;
+       int i;
+       double dx, dy;
 
-  if (event->button != 1)
-    return TRUE;
+       for (i = 0; i < polygon->num_points; i++) {
+               dx = point->x - polygon->points[i].x;
+               dy = point->y - polygon->points[i].y;
 
-  model->anchor_position.x = event->x;
-  model->anchor_position.y = event->y;
-  model->anchor_object = model_find_nearest (model, event->x, event->y);
+               if (polygon->normals[i].x * dx + polygon->normals[i].y * dy >= 0)
+                       return polygon->enclosing;
+       }
 
-  return TRUE;
+       return !polygon->enclosing;
 }
 
-static gboolean
-button_release_event (GtkWidget             *widget,
-                     GdkEventButton *event,
-                     gpointer        data)
+static void
+polygon_reflect_object (Polygon *polygon, Object *object)
 {
-  Model *model = data;
-
-  if ((event->state & GDK_BUTTON1_MASK) == 0)
-    return TRUE;
-
-  model->anchor_object = NULL;
-
-  return TRUE;
+       int i, edge;
+       double d, distance;
+       Vector *n;
+
+       distance = -1000;
+       for (i = 0; i < polygon->num_points; i++) {
+               d = polygon->normals[i].x * (object->position.x - polygon->points[i].x) +
+                       polygon->normals[i].y * (object->position.y - polygon->points[i].y);
+
+               if (d > distance) {
+                       distance = d;
+                       edge = i;
+                       n = &polygon->normals[i];
+               }
+       }
+
+       object->position.x -= (1 + elasticity) * distance * n->x;
+       object->position.y -= (1 + elasticity) * distance * n->y;
+
+       distance =
+               n->x * (object->previous_position.x - polygon->points[edge].x) +
+               n->y * (object->previous_position.y - polygon->points[edge].y);
+
+       object->previous_position.x -= (1 + elasticity) * distance * n->x;
+       object->previous_position.y -= (1 + elasticity) * distance * n->y;
 }
 
-static gboolean
-motion_notify_event (GtkWidget     *widget,
-                    GdkEventMotion *event,
-                    gpointer        data)
+static void
+model_constrain_polygon (Model *model, Polygon *polygon)
 {
-  Model *model = data;
-  int x, y;
-  GdkModifierType state;
-
-  gdk_window_get_pointer (event->window, &x, &y, &state);
+       int i;
 
-  model->anchor_position.x = x + 0.5;
-  model->anchor_position.y = y + 0.5;
-
-  return TRUE;
+       for (i = 0; i < model->num_objects; i++) {
+               if (polygon_contains_point (polygon, &model->objects[i].position))
+                       polygon_reflect_object (polygon, &model->objects[i]);
+       }
 }
 
-typedef void (*ModelInitFunc) (Model *model);
-
 static void
-model_changed (GtkComboBox *combo, gpointer user_data)
+model_constrain_anchor (Model *model, Anchor *anchor)
 {
-  Model *model = user_data;
-  GtkTreeIter iter;
-  GtkTreeModel *tree_model;
-  ModelInitFunc init;
-  char *name;
-
-  tree_model = gtk_combo_box_get_model (combo);
-  if (!gtk_combo_box_get_active_iter (combo, &iter))
-    return;
-
-  gtk_tree_model_get (tree_model, &iter, 0, &name, 1, &init, -1);
-
-  model_fini (model);
-  (*init) (model);
+       anchor->object->position.x = anchor->x;
+       anchor->object->position.y = anchor->y;
+       anchor->object->previous_position.x = anchor->x;
+       anchor->object->previous_position.y = anchor->y;
 }
 
-static GtkTreeModel *
-create_model_store (void)
+static void
+model_constrain_offset (Model *model, Offset *offset)
 {
-  static struct {
-    const char *name;
-    ModelInitFunc init;
-  } models[] = {
-    { "Rope", model_init_rope },
-    { "Snake", model_init_snake },
-    { "Curtain", model_init_curtain }
-  };
-
-  GtkTreeIter iter;
-  GtkTreeStore *store;
-  gint i;
-
-  store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
-
-  for (i = 0; i < G_N_ELEMENTS(models); i++) {
-    gtk_tree_store_append (store, &iter, NULL);
-    gtk_tree_store_set (store, &iter,
-                       0, models[i].name, 1, models[i].init, -1);
- }
-  
-  return GTK_TREE_MODEL (store);
-
+       double x, y;
+       int i;
+
+       x = 0;
+       y = 0;
+       for (i = 0; i < offset->num_objects; i++) {
+               x += offset->objects[i]->position.x;
+               y += offset->objects[i]->position.y;
+       }
+
+       x = x / offset->num_objects - offset->dx * (offset->num_objects - 1) / 2;
+       y = y / offset->num_objects - offset->dy * (offset->num_objects - 1) / 2;
+    
+       for (i = 0; i < offset->num_objects; i++) {
+               offset->objects[i]->position.x = x + offset->dx * i;
+               offset->objects[i]->position.y = y + offset->dy * i;
+       }
 }
 
-static GtkWidget *
-create_model_combo (Model *model)
+static void
+model_constrain (Model *model)
 {
-  GtkWidget *hbox;
-  GtkWidget *combo, *label;
-  GtkTreeModel *store;
-  GtkCellRenderer *renderer;
-
-  hbox = gtk_hbox_new (FALSE, 8);
-
-  label = gtk_label_new_with_mnemonic ("_Model:");
-  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
-
-  store = create_model_store ();
-  combo = gtk_combo_box_new_with_model (store);
-  gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
-  g_object_unref (store);
-
-  renderer = gtk_cell_renderer_text_new ();
-  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE);
-  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer,
-                                 "text", 0,
-                                 NULL);
-
-  gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
-  gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0);
-  g_signal_connect (combo, "changed",
-                   G_CALLBACK (model_changed), model);
-
-  return hbox;
+       double dx, dy, x, y, distance, fraction;
+       int i;
+
+       if (model->mouse_anchor.object != NULL)
+               model_constrain_anchor (model, &model->mouse_anchor);
+       for (i = 0; i < model->num_anchors; i++)
+               model_constrain_anchor (model, &model->anchors[i]);
+
+       /* String constraints. */
+       for (i = 0; i < model->num_strings; i++) {
+               x = model->strings[i].a->position.x;
+               y = model->strings[i].a->position.y;
+               dx = model->strings[i].b->position.x - x;
+               dy = model->strings[i].b->position.y - y;
+               distance = estimate_distance (dx, dy, model->strings[i].length);
+               if (distance < model->strings[i].length)
+                       continue;
+               fraction = (distance - model->strings[i].length) / distance / 2;
+               model->strings[i].a->position.x = x + dx * fraction;
+               model->strings[i].a->position.y = y + dy * fraction;
+               model->strings[i].b->position.x = x + dx * (1 - fraction);
+               model->strings[i].b->position.y = y + dy * (1 - fraction);
+       }
+
+       /* Spacer constraints. */
+       for (i = 0; i < model->num_spacers; i++) {
+               x = model->spacers[i].a->position.x;
+               y = model->spacers[i].a->position.y;
+               dx = model->spacers[i].b->position.x - x;
+               dy = model->spacers[i].b->position.y - y;
+               distance = estimate_distance (dx, dy, model->spacers[i].length);
+               if (distance > model->spacers[i].length)
+                       continue;
+               fraction = (distance - model->spacers[i].length) / distance / 2;
+               model->spacers[i].a->position.x = x + dx * fraction;
+               model->spacers[i].a->position.y = y + dy * fraction;
+               model->spacers[i].b->position.x = x + dx * (1 - fraction);
+               model->spacers[i].b->position.y = y + dy * (1 - fraction);
+       }
+
+       /* Stick constraints. */
+       for (i = 0; i < model->num_sticks; i++) {
+               x = model->sticks[i].a->position.x;
+               y = model->sticks[i].a->position.y;
+               dx = model->sticks[i].b->position.x - x;
+               dy = model->sticks[i].b->position.y - y;
+               distance = estimate_distance (dx, dy, model->sticks[i].length);
+               fraction = (distance - model->sticks[i].length) / distance / 2;
+               model->sticks[i].a->position.x = x + dx * fraction;
+               model->sticks[i].a->position.y = y + dy * fraction;
+               model->sticks[i].b->position.x = x + dx * (1 - fraction);
+               model->sticks[i].b->position.y = y + dy * (1 - fraction);
+       }
+
+       /* Offset constraints. */
+       for (i = 0; i < model->num_offsets; i++)
+               model_constrain_offset (model, &model->offsets[i]);
+
+       /* Polygon constraints. */
+       for (i = 0; i < model->num_polygons; i++)
+               model_constrain_polygon (model, &model->polygons[i]);
 }
 
-static GtkWidget *
-create_window (Model *model)
+void
+model_step (Model *model, double delta_t)
 {
-  GtkWidget *window;
-  GtkWidget *frame;
-  GtkWidget *vbox;
-  GtkWidget *da;
-  GtkWidget *model_combo;
-
-  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
-  gtk_window_set_title (GTK_WINDOW (window), "Drawing Area");
-
-  g_signal_connect (window, "destroy",
-                   G_CALLBACK (gtk_main_quit), &window);
-
-  gtk_container_set_border_width (GTK_CONTAINER (window), 8);
-
-  vbox = gtk_vbox_new (FALSE, 8);
-  gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
-  gtk_container_add (GTK_CONTAINER (window), vbox);
-
-  /*
-   * Create the drawing area
-   */
-      
-  frame = gtk_frame_new (NULL);
-  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
-  gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
-      
-  da = gtk_drawing_area_new ();
-  /* set a minimum size */
-  gtk_widget_set_size_request (da, 600, 500);
-
-  gtk_container_add (GTK_CONTAINER (frame), da);
-
-  /* Signals used to handle backing pixmap */
-      
-  g_signal_connect (da, "expose_event",
-                   G_CALLBACK (expose_event), model);
-      
-  /* Event signals */
-      
-  g_signal_connect (da, "motion_notify_event",
-                   G_CALLBACK (motion_notify_event), model);
-  g_signal_connect (da, "button_press_event",
-                   G_CALLBACK (button_press_event), model);
-  g_signal_connect (da, "button_release_event",
-                   G_CALLBACK (button_release_event), model);
-
-  /* Ask to receive events the drawing area doesn't normally
-   * subscribe to
-   */
-  gtk_widget_set_events (da, gtk_widget_get_events (da)
-                        | GDK_LEAVE_NOTIFY_MASK
-                        | GDK_BUTTON_PRESS_MASK
-                        | GDK_BUTTON_RELEASE_MASK
-                        | GDK_POINTER_MOTION_MASK
-                        | GDK_POINTER_MOTION_HINT_MASK);
-
-  model_combo = create_model_combo (model);
-  gtk_box_pack_start (GTK_BOX (vbox), model_combo, FALSE, FALSE, 0);
-
-  return da;
-}
+       int i;
 
-typedef struct _Closure Closure;
-struct _Closure {
-  GtkWidget *drawing_area;
-  Model *model;
-  int i;
-};
+       model_accumulate_forces (model);
+       model_integrate (model, delta_t);
+       for (i = 0; i < 20; i++)
+               model_constrain (model);
 
-static gint
-timeout_callback (gpointer data)
-{
-  Closure *closure = data;
-  int i;
+       model->theta += delta_t;
+}
 
-  for (i = 0; i < 3; i++)
-    model_step (closure->model, 0.5);
+static double
+object_distance (Object *object, double x, double y)
+{
+       double dx, dy;
 
-  closure->i++;
-  if (closure->i == 1) {
-    gtk_widget_queue_draw (closure->drawing_area);
-    closure->i = 0;
-  }
+       dx = object->position.x - x;
+       dy = object->position.y - y;
 
-  return TRUE;
+       return sqrt (dx*dx + dy*dy);
 }
 
-int
-main (int argc, char *argv[])
+Object *
+model_find_nearest (Model *model, double x, double y)
 {
-  Closure closure;
-  Model model;
-
-  gtk_init (&argc, &argv);
-  model_init_rope (&model);
-  closure.drawing_area = create_window (&model);
-  closure.i = 0;
-  gtk_widget_show_all (gtk_widget_get_toplevel (closure.drawing_area));
-  closure.model = &model;
-  g_timeout_add (100, timeout_callback, &closure);
-  gtk_main ();
-
-  return 0;
+       Object *object;
+       double distance, min_distance;
+       int i;
+
+       for (i = 0; i < model->num_objects; i++) {
+               distance = object_distance (&model->objects[i], x, y);
+               if (i == 0 || distance < min_distance) {
+                       min_distance = distance;
+                       object = &model->objects[i];
+               }
+       }
+
+       return object;
 }