/* -*- mode: c; c-basic-offset: 2 -*- * See: * * http://en.wikipedia.org/wiki/Verlet_integration * http://www.teknikus.dk/tj/gdc2001.htm * * TODO: * * - Add circle object * - Try out this idea: make constraint solver take mean of all * corrections at the end instead of meaning as it goes. * - Consider a size, or radius attribute for objects. * - Make a gravitation object - initialized with a vector or just a point. */ #include #include #include #include #include #include "akamaru.h" static void link_init (Link *link) { link->prev = link; link->next = link; } static void link_insert_before (Link *link, Link *anchor) { link->prev = anchor->prev; link->next = anchor; link->prev->next = link; link->next->prev = link; } static void link_remove (Link *link) { link->prev->next = link->next; link->next->prev = link->prev; } static void list_init (List *list, size_t offset) { link_init (&list->head); list->offset = offset; } static void list_for_each (List *list, ListFunc func, void *data) { Link *l, *next; for (l = list->head.next; l != &list->head; l = next) { next = l->next; func ((char *) l - list->offset, data); } } static void list_append (List *list, void *element) { Link *link; link = (Link *) ((char *) element + list->offset); link_insert_before (link, &list->head); } void object_init (Object *object, double x, double y, double mass) { object->position.x = x; object->position.y = y; object->previous_position.x = x; object->previous_position.y = y; object->mass = mass; } void spring_init (Spring *spring, Object *a, Object *b, double length) { spring->a = a; spring->b = b; spring->length = length; } Spring * model_add_spring (Model *model, Object *a, Object *b, double length) { Spring *spring; spring = g_new (Spring, 1); spring->a = a; spring->b = b; spring->length = length; list_append (&model->spring_list, spring); return spring; } void model_for_each_spring (Model *model, SpringFunc func, void *data) { list_for_each (&model->spring_list, (ListFunc) func, data); } void stick_init (Stick *stick, Object *a, Object *b, double length) { stick->a = a; stick->b = b; stick->length = length; } Stick * model_add_stick (Model *model, Object *a, Object *b, double length) { Stick *stick; stick = g_new (Stick, 1); stick->a = a; stick->b = b; stick->length = length; list_append (&model->stick_list, stick); return stick; } void model_for_each_stick (Model *model, StickFunc func, void *data) { list_for_each (&model->stick_list, (ListFunc) func, data); } void string_init (String *string, Object *a, Object *b, double length) { string->a = a; string->b = b; string->length = length; } String * model_add_string (Model *model, Object *a, Object *b, double length) { String *string; string = g_new (String, 1); string->a = a; string->b = b; string->length = length; list_append (&model->string_list, string); return string; } void model_for_each_string (Model *model, StringFunc func, void *data) { list_for_each (&model->string_list, (ListFunc) func, data); } OffsetSpring * model_add_offset_spring (Model *model, Object *a, Object *b, double dx, double dy) { OffsetSpring *spring; spring = g_new (OffsetSpring, 1); spring->a = a; spring->b = b; spring->dx = dx; spring->dy = dy; list_append (&model->offset_spring_list, spring); return spring; } void model_for_each_offset_spring (Model *model, OffsetSpringFunc func, void *data) { list_for_each (&model->offset_spring_list, (ListFunc) func, data); } void spacer_init (Spacer *spacer, Object *a, Object *b, double length) { spacer->a = a; spacer->b = b; spacer->length = length; } Spacer * model_add_spacer (Model *model, Object *a, Object *b, double length) { Spacer *spacer; spacer = g_new (Spacer, 1); spacer->a = a; spacer->b = b; spacer->length = length; list_append (&model->spacer_list, spacer); return spacer; } void model_for_each_spacer (Model *model, SpacerFunc func, void *data) { list_for_each (&model->spacer_list, (ListFunc) func, data); } void anchor_init (Anchor *anchor, Object *object, double x, double y) { anchor->object = object; anchor->x = x; anchor->y = y; } Anchor * model_add_anchor (Model *model, Object *object, double x, double y) { Anchor *anchor; anchor = g_new (Anchor, 1); anchor->object = object; anchor->x = x; anchor->y = y; list_append (&model->anchor_list, anchor); return anchor; } void model_delete_anchor (Model *model, Anchor *anchor) { link_remove (&anchor->link); g_free (anchor); } void model_for_each_anchor (Model *model, AnchorFunc func, void *data) { list_for_each (&model->anchor_list, (ListFunc) func, data); } void polygon_init_from_va_list (Polygon *p, int enclosing, int num_points, va_list ap) { double dx, dy, length; int i, j; /* Polygons are defined counter-clock-wise in a coordinate system * with the y-axis pointing down. */ 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); } 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; } } void polygon_init (Polygon *polygon, int enclosing, int num_points, ...) { va_list ap; va_start (ap, num_points); polygon_init_from_va_list (polygon, enclosing, num_points, ap); va_end (ap); } Polygon * model_add_polygon (Model *model, int enclosing, int num_points, ...) { Polygon *polygon; va_list ap; polygon = g_new0 (Polygon, 1); va_start (ap, num_points); polygon_init_from_va_list (polygon, enclosing, num_points, ap); va_end (ap); list_append (&model->polygon_list, polygon); return polygon; } void model_for_each_polygon (Model *model, PolygonFunc func, void *data) { list_for_each (&model->polygon_list, (ListFunc) func, data); } Polygon * model_add_diamond (Model *model, double x, double y) { return model_add_polygon (model, FALSE, 5, x, y, x + 10, y + 40, x + 90, y + 40, x + 100, y, x + 50, y - 20); } Polygon * model_add_rectangle (Model *model, double x0, double y0, double x1, double y1) { return model_add_polygon (model, FALSE, 4, x0, y0, x0, y1, x1, y1, x1, y0); } Polygon * model_add_enclosing_rectangle (Model *model, double x0, double y0, double x1, double y1) { return model_add_polygon (model, TRUE, 4, x0, y0, x0, y1, x1, y1, x1, y0); } Offset * model_add_offset (Model *model, int num_objects, double dx, double dy) { Offset *offset; offset = g_new (Offset, 1); offset->num_objects = num_objects; offset->objects = g_new (Object *, num_objects); offset->dx = dx; offset->dy = dy; list_append (&model->offset_list, offset); return offset; } void model_for_each_offset (Model *model, OffsetFunc func, void *data) { list_for_each (&model->offset_list, (ListFunc) func, data); } void model_init (Model *model) { memset (model, 0, sizeof *model); list_init (&model->object_list, G_STRUCT_OFFSET (Object, link)); list_init (&model->spacer_list, G_STRUCT_OFFSET (Spacer, link)); list_init (&model->string_list, G_STRUCT_OFFSET (String, link)); list_init (&model->stick_list, G_STRUCT_OFFSET (Stick, link)); list_init (&model->spring_list, G_STRUCT_OFFSET (Spring, link)); list_init (&model->anchor_list, G_STRUCT_OFFSET (Anchor, link)); list_init (&model->polygon_list, G_STRUCT_OFFSET (Polygon, link)); list_init (&model->offset_list, G_STRUCT_OFFSET (Offset, link)); list_init (&model->offset_spring_list, G_STRUCT_OFFSET (OffsetSpring, link)); } Object * model_add_object (Model *model, double x, double y, double mass, void *data) { Object *object; object = g_new (Object, 1); object->position.x = x; object->position.y = y; object->previous_position.x = x; object->previous_position.y = y; object->mass = mass; list_append (&model->object_list, object); object->data = data; return object; } void model_for_each_object (Model *model, ObjectFunc func, void *data) { list_for_each (&model->object_list, (ListFunc) func, data); } static void free_element (void *element, void *data) { g_free (element); } static void free_polygon (Polygon *polygon, void *data) { g_free (polygon->points); g_free (polygon->normals); g_free (polygon); } static void free_offset (Offset *offset, void *data) { g_free (offset->objects); g_free (offset); } void model_fini (Model *model) { list_for_each (&model->object_list, free_element, NULL); list_for_each (&model->spacer_list, free_element, NULL); list_for_each (&model->string_list, free_element, NULL); list_for_each (&model->stick_list, free_element, NULL); list_for_each (&model->spring_list, free_element, NULL); list_for_each (&model->anchor_list, free_element, NULL); model_for_each_polygon (model, free_polygon, NULL); model_for_each_offset (model, free_offset, NULL); list_for_each (&model->offset_spring_list, free_element, NULL); } static void object_accumulate_forces (Object *object, void *data) { Model *model = data; Vector v; /* Gravity */ object->force.x = model->gravity.x * object->mass; object->force.y = model->gravity.y * object->mass; /* Friction */ v.x = object->position.x - object->previous_position.x; v.y = object->position.y - object->previous_position.y; object->force.x -= v.x * model->friction; object->force.y -= v.y * model->friction; } static void spring_accumulate_forces (Spring *spring, void *data) { Model *model = data; double x, y, dx, dy, distance, displacement; Vector u; x = spring->a->position.x; y = spring->a->position.y; dx = spring->b->position.x - x; dy = spring->b->position.y - y; distance = sqrt (dx * dx + dy * dy); u.x = dx / distance; u.y = dy / distance; displacement = distance - spring->length; spring->a->force.x += u.x * model->k * displacement; spring->a->force.y += u.y * model->k * displacement; spring->b->force.x -= u.x * model->k * displacement; spring->b->force.y -= u.y * model->k * displacement; } void offset_spring_accumulate_forces (OffsetSpring *spring, void *data) { Model *model = data; double dx, dy; Point middle; middle.x = (spring->a->position.x + spring->b->position.x) / 2; middle.y = (spring->a->position.y + spring->b->position.y) / 2; dx = (middle.x - spring->dx / 2) - spring->a->position.x; dy = (middle.y - spring->dy / 2) - spring->a->position.y; spring->a->force.x += dx * model->k; spring->a->force.y += dy * model->k; spring->b->force.x -= dx * model->k; spring->b->force.y -= dy * model->k; } static void model_accumulate_forces (Model *model) { model_for_each_object (model, object_accumulate_forces, model); model_for_each_spring (model, spring_accumulate_forces, model); model_for_each_offset_spring (model, offset_spring_accumulate_forces, model); } static void object_integrate (Object *object, void *data) { double x, y, step; step = * (double *) data; x = object->position.x; y = object->position.y; object->position.x = x + (x - object->previous_position.x) + object->force.x * step * step; object->position.y = y + (y - object->previous_position.y) + object->force.y * step * step; object->previous_position.x = x; object->previous_position.y = y; } /* 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 inline double estimate_distance (double dx, double dy, double r) { #ifdef APPROXIMATE_SQUARE_ROOTS return (r + (dx * dx + dy * dy) / r) / 2; #else return sqrt (dx * dx + dy * dy); #endif } static int polygon_contains_point (Polygon *polygon, Point *point) { int i; double dx, dy; for (i = 0; i < polygon->num_points; i++) { dx = point->x - polygon->points[i].x; dy = point->y - polygon->points[i].y; if (polygon->normals[i].x * dx + polygon->normals[i].y * dy >= 0) return polygon->enclosing; } return !polygon->enclosing; } static void polygon_reflect_object (Polygon *polygon, Object *object, double elasticity) { 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; } typedef struct _ObjectConstrainPolygonClosure { Model *model; Polygon *polygon; } ObjectConstrainPolygonClosure; static void object_constrain_polygon (Object *object, void *data) { ObjectConstrainPolygonClosure *closure = data; if (polygon_contains_point (closure->polygon, &object->position)) polygon_reflect_object (closure->polygon, object, closure->model->elasticity); } static void model_constrain_polygon (Polygon *polygon, void *data) { ObjectConstrainPolygonClosure closure; closure.polygon = polygon; closure.model = data; model_for_each_object (closure.model, object_constrain_polygon, &closure); } static void model_constrain_anchor (Anchor *anchor, void *data) { if (anchor->object == NULL) return; anchor->object->position.x = anchor->x; anchor->object->position.y = anchor->y; } static void model_constrain_offset (Offset *offset, void *data) { 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 void model_constrain_string (String *string, void *data) { double x, y, dx, dy, distance, fraction; x = string->a->position.x; y = string->a->position.y; dx = string->b->position.x - x; dy = string->b->position.y - y; distance = estimate_distance (dx, dy, string->length); if (distance < string->length) return; fraction = (distance - string->length) / distance / 2; string->a->position.x = x + dx * fraction; string->a->position.y = y + dy * fraction; string->b->position.x = x + dx * (1 - fraction); string->b->position.y = y + dy * (1 - fraction); } static void model_constrain_spacer (Spacer *spacer, void *data) { double x, y, dx, dy, distance, fraction; x = spacer->a->position.x; y = spacer->a->position.y; dx = spacer->b->position.x - x; dy = spacer->b->position.y - y; distance = estimate_distance (dx, dy, spacer->length); if (distance > spacer->length) return; fraction = (distance - spacer->length) / distance / 2; spacer->a->position.x = x + dx * fraction; spacer->a->position.y = y + dy * fraction; spacer->b->position.x = x + dx * (1 - fraction); spacer->b->position.y = y + dy * (1 - fraction); } static void model_constrain_stick (Stick *stick, void *data) { double x, y, dx, dy, distance, fraction; x = stick->a->position.x; y = stick->a->position.y; dx = stick->b->position.x - x; dy = stick->b->position.y - y; distance = estimate_distance (dx, dy, stick->length); fraction = (distance - stick->length) / distance / 2; stick->a->position.x = x + dx * fraction; stick->a->position.y = y + dy * fraction; stick->b->position.x = x + dx * (1 - fraction); stick->b->position.y = y + dy * (1 - fraction); } static void model_constrain (Model *model) { model_for_each_anchor (model, model_constrain_anchor, model); model_for_each_string (model, model_constrain_string, model); model_for_each_spacer (model, model_constrain_spacer, model); model_for_each_stick (model, model_constrain_stick, model); model_for_each_offset (model, model_constrain_offset, model); model_for_each_polygon (model, model_constrain_polygon, model); } void model_step (Model *model, double delta_t) { int i; model_accumulate_forces (model); model_for_each_object (model, object_integrate, &delta_t); for (i = 0; i < model->constrain_iterations; i++) model_constrain (model); model->theta += delta_t; } static double object_distance (Object *object, double x, double y) { double dx, dy; dx = object->position.x - x; dy = object->position.y - y; return sqrt (dx*dx + dy*dy); } typedef struct { double x; double y; double distance; Object *object; } FindNearestClosure; static void model_update_closest (Object *object, void *data) { FindNearestClosure *closure = data; double distance; distance = object_distance (object, closure->x, closure->y); if (closure->object == NULL || distance < closure->distance) { closure->distance = distance; closure->object = object; } } Object * model_find_nearest (Model *model, double x, double y) { FindNearestClosure closure; closure.x = x; closure.y = y; closure.object = NULL; model_for_each_object (model, model_update_closest, &closure); return closure.object; }