mutter/src/spring-model.c

428 lines
9.1 KiB
C
Raw Normal View History

#include "spring-model.h"
#include <math.h>
struct XYPair
{
double x, y;
};
#define GRID_WIDTH 4
#define GRID_HEIGHT 4
#define MODEL_MAX_OBJECTS (GRID_WIDTH * GRID_HEIGHT)
#define MODEL_MAX_SPRINGS (MODEL_MAX_OBJECTS * 2)
#define DEFAULT_SPRING_K 5.0
#define DEFAULT_FRICTION 1.4
struct Spring {
Object *a;
Object *b;
/* Spring position at rest, from a to b:
offset = b.position - a.position
*/
Vector offset;
};
struct Object {
Vector force;
Point position;
Vector velocity;
double mass;
double theta;
int immobile;
};
struct Model {
int num_objects;
Object objects[MODEL_MAX_OBJECTS];
int num_springs;
Spring springs[MODEL_MAX_SPRINGS];
Object *anchor_object;
Vector anchor_offset;
double friction;/* Friction constant */
double k;/* Spring constant */
double last_time;
double steps;
};
static void
object_init (Object *object,
double position_x, double position_y,
double velocity_x, double velocity_y, double mass)
{
object->position.x = position_x;
object->position.y = position_y;
object->velocity.x = velocity_x;
object->velocity.y = velocity_y;
object->mass = mass;
object->force.x = 0;
object->force.y = 0;
object->immobile = 0;
}
static void
spring_init (Spring *spring,
Object *object_a, Object *object_b,
double offset_x, double offset_y)
{
spring->a = object_a;
spring->b = object_b;
spring->offset.x = offset_x;
spring->offset.y = offset_y;
}
static void
model_add_spring (Model *model,
Object *object_a, Object *object_b,
double offset_x, double offset_y)
{
Spring *spring;
g_assert (model->num_springs < MODEL_MAX_SPRINGS);
spring = &model->springs[model->num_springs];
model->num_springs++;
spring_init (spring, object_a, object_b, offset_x, offset_y);
}
static void
object_apply_force (Object *object, double fx, double fy)
{
object->force.x += fx;
object->force.y += fy;
}
/* The model here can be understood as a rigid body of the spring's
* rest shape, centered on the vector between the two object
* positions. This rigid body is then connected by linear-force
* springs to each object. This model does degnerate into a simple
* spring for linear displacements, and does something reasonable for
* rotation.
*
* There are other possibilities for handling the rotation of the
* spring, and it might be interesting to explore something which has
* better length-preserving properties. For example, with the current
* model, an initial 180 degree rotation of the spring results in the
* spring collapsing down to 0 size before expanding back to it's
* natural size again.
*/
static void
spring_exert_forces (Spring *spring, double k)
{
Vector da, db;
Vector a, b;
a = spring->a->position;
b = spring->b->position;
/* A nice vector diagram would likely help here, but my ASCII-art
* skills aren't up to the task. Here's how to make your own
* diagram:
*
* Draw a and b, and the vector AB from a to b
* Find the center of AB
* Draw spring->offset so that its center point is on the center of AB
* Draw da from a to the initial point of spring->offset
* Draw db from b to the final point of spring->offset
*
* The math below should be easy to verify from the diagram.
*/
da.x = 0.5 * (b.x - a.x - spring->offset.x);
da.y = 0.5 * (b.y - a.y - spring->offset.y);
db.x = 0.5 * (a.x - b.x + spring->offset.x);
db.y = 0.5 * (a.y - b.y + spring->offset.y);
object_apply_force (spring->a, k * da.x, k * da.y);
object_apply_force (spring->b, k * db.x, k * db.y);
}
static void
model_step_object (Model *model, Object *object)
{
Vector acceleration;
object->theta += 0.05;
/* Slow down due to friction. */
object->force.x -= model->friction * object->velocity.x;
object->force.y -= model->friction * object->velocity.y;
acceleration.x = object->force.x / object->mass;
acceleration.y = object->force.y / object->mass;
if (object->immobile)
{
object->velocity.x = 0;
object->velocity.y = 0;
}
else
{
object->velocity.x += acceleration.x;
object->velocity.y += acceleration.y;
object->position.x += object->velocity.x;
object->position.y += object->velocity.y;
}
object->force.x = 0.0;
object->force.y = 0.0;
}
static void
model_init_grid (Model *model, MetaRectangle *rect, gboolean expand)
{
int x, y, i, v_x, v_y;
int hpad, vpad;
model->num_objects = MODEL_MAX_OBJECTS;
model->num_springs = 0;
i = 0;
if (expand)
{
hpad = rect->width / 3;
vpad = rect->height / 3;
}
else
{
hpad = rect->width / 6;
vpad = rect->height / 6;
}
2006-05-25 10:40:50 -04:00
#define EXPAND_DELTA 4
for (y = 0; y < GRID_HEIGHT; y++)
for (x = 0; x < GRID_WIDTH; x++)
{
2006-05-25 10:40:50 -04:00
if (expand)
{
if (y == 0)
v_y = - EXPAND_DELTA * g_random_double();
else if (y == GRID_HEIGHT - 1)
v_y = EXPAND_DELTA * g_random_double();
else
v_y = 2 * EXPAND_DELTA * g_random_double() - EXPAND_DELTA;
if (x == 0)
v_x = - EXPAND_DELTA * g_random_double();
else if (x == GRID_WIDTH - 1)
v_x = EXPAND_DELTA * g_random_double();
else
v_x = 2 * EXPAND_DELTA * g_random_double() - EXPAND_DELTA;
}
else
{
v_x = v_y = 0;
}
#if 0
if (expand)
object_init (&model->objects[i],
rect->x + x * rect->width / 6 + rect->width / 4,
rect->y + y * rect->height / 6 + rect->height / 4,
v_x, v_y, 20);
else
#endif
{
#if 0
g_print ("obj: %d %d\n", rect->x + x * rect->width / 3,
rect->y + y * rect->height / 3);
#endif
object_init (&model->objects[i],
rect->x + x * rect->width / 3,
rect->y + y * rect->height / 3,
v_x, v_y, 15);
}
if (x > 0)
model_add_spring (model,
&model->objects[i - 1],
&model->objects[i],
hpad, 0);
if (y > 0)
model_add_spring (model,
&model->objects[i - GRID_WIDTH],
&model->objects[i],
0, vpad);
i++;
}
}
static void
model_init (Model *model, MetaRectangle *rect, gboolean expand)
{
model->anchor_object = NULL;
model->k = DEFAULT_SPRING_K;
model->friction = DEFAULT_FRICTION;
model_init_grid (model, rect, expand);
model->steps = 0;
model->last_time = 0;
}
Model *
model_new (MetaRectangle *rect, gboolean expand)
{
Model *model = g_new0 (Model, 1);
model_init (model, rect, expand);
return model;
}
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);
}
static Object *
model_find_nearest (Model *model, double x, double y)
{
Object *object = &model->objects[0];
double distance, min_distance = 0.0;
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;
}
void
model_begin_move (Model *model, int x, int y)
{
if (model->anchor_object)
model->anchor_object->immobile = 0;
model->anchor_object = model_find_nearest (model, x, y);
model->anchor_offset.x = x - model->anchor_object->position.x;
model->anchor_offset.y = y - model->anchor_object->position.y;
g_print ("ypos: %f %f\n", model->anchor_object->position.y,
model->anchor_object->position.x);
g_print ("anchor offset: %f %f\n",
model->anchor_offset.x,
model->anchor_offset.y);
model->anchor_object->immobile = 1;
}
void
model_set_anchor (Model *model,
int x,
int y)
{
if (model->anchor_object)
model->anchor_object->immobile = 0;
model->anchor_object = model_find_nearest (model, x, y);
model->anchor_offset.x = model->anchor_object->position.x - x;
model->anchor_offset.y = model->anchor_object->position.y - y;
model->anchor_object->immobile = 1;
}
void
model_update_move (Model *model, int x, int y)
{
model->anchor_object->position.x = x - model->anchor_offset.x;
model->anchor_object->position.y = y - model->anchor_offset.y;
}
static void
on_end_move (Model *model)
{
if (model->anchor_object)
{
model->anchor_object->immobile = 0;
model->anchor_object = NULL;
}
}
2006-05-25 10:40:50 -04:00
#define EPSILON 0.02
gboolean
model_is_calm (Model *model)
{
int i, j;
gboolean calm = TRUE;
for (i = 0; i < model->num_objects; i++)
{
if (model->objects[i].velocity.x > EPSILON ||
model->objects[i].velocity.y > EPSILON ||
model->objects[i].velocity.x < - EPSILON ||
model->objects[i].velocity.y < - EPSILON)
{
return FALSE;
}
}
return TRUE;
}
void
model_step (Model *model)
{
int i;
for (i = 0; i < model->num_springs; i++)
spring_exert_forces (&model->springs[i], model->k);
for (i = 0; i < model->num_objects; i++)
model_step_object (model, &model->objects[i]);
}
void
model_destroy (Model *model)
{
g_free (model);
}
void
model_get_position (Model *model,
int i,
int j,
double *x,
double *y)
{
if (x)
*x = model->objects[j * 4 + i].position.x;
if (y)
*y = model->objects[j * 4 + i].position.y;
}