mutter/src/spring-model.c
Elijah Newren 0201fcfc6c Stick an emacs comment directive at the beginning of all the code files so
2006-10-01  Elijah Newren  <newren gmail com>

	* src/*.[ch]: Stick an emacs comment directive at the beginning of
	all the code files so that people using emacs will be more likely
	to get coding style correct in their patches.  We still need a
	similar vi directive.  #358866
2006-10-01 22:30:10 +00:00

419 lines
9.0 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
#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;
}
#define EXPAND_DELTA 4
for (y = 0; y < GRID_HEIGHT; y++)
for (x = 0; x < GRID_WIDTH; x++)
{
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;
}
#define EPSILON 0.02
gboolean
model_is_calm (Model *model)
{
int i;
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;
}