Show exspline.c syntax highlighted
/*
* Example program for the Allegro library, by Shawn Hargreaves.
*
* This program demonstrates the use of al_draw_spline curves to create smooth
* paths connecting a number of node points. This can be useful for
* constructing realistic motion and animations.
*
* The technique is to connect the series of guide points p1..p(n) with
* al_draw_spline curves from p1-p2, p2-p3, etc. Each al_draw_spline must pass though
* both of its guide points, so they must be used as the first and fourth
* of the al_draw_spline control points. The fun bit is coming up with sensible
* values for the second and third al_draw_spline control points, such that the
* al_draw_spline segments will have equal gradients where they meet. I came
* up with the following solution:
*
* For each guide point p(n), calculate the desired tangent to the curve
* at that point. I took this to be the vector p(n-1) -> p(n+1), which
* can easily be calculated with the inverse tangent function, and gives
* decent looking results. One implication of this is that two dummy
* guide points are needed at each end of the curve, which are used in
* the tangent calculations but not connected to the set of splines.
*
* Having got these tangents, it becomes fairly easy to calculate the
* al_draw_spline control points. For a al_draw_spline between guide points p(a) and
* p(b), the second control point should lie along the positive tangent
* from p(a), and the third control point should lie along the negative
* tangent from p(b). How far they are placed along these tangents
* controls the shape of the curve: I found that applying a 'curviness'
* scaling factor to the distance between p(a) and p(b) works well.
*
* One thing to note about splines is that the generated points are
* not all equidistant. Instead they tend to bunch up nearer to the
* ends of the al_draw_spline, which means you will need to apply some fudges
* to get an object to move at a constant speed. On the other hand,
* in situations where the curve has a noticable change of direction
* at each guide point, the effect can be quite nice because it makes
* the object slow down for the curve.
*/
#include <stdio.h>
#include "allegro.h"
typedef struct NODE
{
int x, y;
fixed tangent;
} NODE;
#define MAX_NODES 1024
NODE nodes[MAX_NODES];
int node_count;
fixed curviness;
int show_tangents;
int show_control_points;
/* calculates the distance between two nodes */
fixed node_dist(NODE n1, NODE n2)
{
#define SCALE 64
fixed dx = al_int_to_fix(n1.x - n2.x) / SCALE;
fixed dy = al_int_to_fix(n1.y - n2.y) / SCALE;
return al_fix_sqrt(al_fix_mul(dx, dx) + al_fix_mul(dy, dy)) * SCALE;
}
/* constructs nodes to go at the ends of the list, for tangent calculations */
NODE dummy_node(NODE node, NODE prev)
{
NODE n;
n.x = node.x - (prev.x - node.x) / 8;
n.y = node.y - (prev.y - node.y) / 8;
return n;
}
/* calculates a set of node tangents */
void calc_tangents(void)
{
int i;
nodes[0] = dummy_node(nodes[1], nodes[2]);
nodes[node_count] = dummy_node(nodes[node_count-1], nodes[node_count-2]);
node_count++;
for (i=1; i<node_count-1; i++)
nodes[i].tangent = al_fix_atan2(al_int_to_fix(nodes[i+1].y - nodes[i-1].y),
al_int_to_fix(nodes[i+1].x - nodes[i-1].x));
}
/* draws one of the path nodes */
void draw_node(int n)
{
char b[8];
al_draw_circle_fill(al_screen, nodes[n].x, nodes[n].y, 2, al_palette_color[1]);
sprintf(b, "%d", n);
al_text_mode(-1);
al_put_text(al_screen, al_font_8x8, b, nodes[n].x-7, nodes[n].y-7, al_palette_color[255]);
}
/* calculates the control points for a al_draw_spline segment */
void get_control_points(NODE n1, NODE n2, int points[8])
{
fixed dist = al_fix_mul(node_dist(n1, n2), curviness);
points[0] = n1.x;
points[1] = n1.y;
points[2] = n1.x + al_fix_to_int(al_fix_mul(al_fix_cos(n1.tangent), dist));
points[3] = n1.y + al_fix_to_int(al_fix_mul(al_fix_sin(n1.tangent), dist));
points[4] = n2.x - al_fix_to_int(al_fix_mul(al_fix_cos(n2.tangent), dist));
points[5] = n2.y - al_fix_to_int(al_fix_mul(al_fix_sin(n2.tangent), dist));
points[6] = n2.x;
points[7] = n2.y;
}
/* draws a al_draw_spline curve connecting two nodes */
void draw_spline(NODE n1, NODE n2)
{
int points[8];
int i;
get_control_points(n1, n2, points);
al_draw_spline(al_screen, points, al_palette_color[255]);
if (show_control_points)
for (i=1; i<=2; i++)
al_draw_circle_fill(al_screen, points[i*2], points[i*2+1], 2, al_palette_color[2]);
}
/* draws the al_draw_spline paths */
void draw_splines(void)
{
char b[80];
int i;
al_acquire_screen();
al_clear_to_color(al_screen, al_make_color(255, 255, 255));
al_text_mode(al_palette_color[0]);
al_put_text_centre(al_screen, al_font_8x8, "Spline curve path", AL_SCREEN_W/2, 8, al_palette_color[255]);
sprintf(b, "Curviness = %.2f", al_fix_to_(curviness));
al_put_text_centre(al_screen, al_font_8x8, b, AL_SCREEN_W/2, 32, al_palette_color[255]);
al_put_text_centre(al_screen, al_font_8x8, "Up/down keys to alter", AL_SCREEN_W/2, 44, al_palette_color[255]);
al_put_text_centre(al_screen, al_font_8x8, "Space to walk", AL_SCREEN_W/2, 68, al_palette_color[255]);
al_put_text_centre(al_screen, al_font_8x8, "C to display control points", AL_SCREEN_W/2, 92, al_palette_color[255]);
al_put_text_centre(al_screen, al_font_8x8, "T to display tangents", AL_SCREEN_W/2, 104, al_palette_color[255]);
for (i=1; i<node_count-2; i++)
draw_spline(nodes[i], nodes[i+1]);
for (i=1; i<node_count-1; i++) {
draw_node(i);
if (show_tangents) {
al_draw_line(al_screen, nodes[i].x - al_fix_to_int(al_fix_cos(nodes[i].tangent) * 24),
nodes[i].y - al_fix_to_int(al_fix_sin(nodes[i].tangent) * 24),
nodes[i].x + al_fix_to_int(al_fix_cos(nodes[i].tangent) * 24),
nodes[i].y + al_fix_to_int(al_fix_sin(nodes[i].tangent) * 24),
al_palette_color[1]);
}
}
al_release_screen();
}
/* let the user input a list of path nodes */
void input_nodes(void)
{
al_clear_to_color(al_screen, al_make_color(255, 255, 255));
al_text_mode(al_palette_color[0]);
al_put_text_centre(al_screen, al_font_8x8, "Click the left mouse button to add path nodes",
AL_SCREEN_W/2, 8, al_palette_color[255]);
al_put_text_centre(al_screen, al_font_8x8, "Right mouse button or any al_key to finish",
AL_SCREEN_W/2, 24, al_palette_color[255]);
node_count = 1;
al_show_mouse(al_screen);
do {
al_poll_mouse();
} while (al_mouse_b);
al_clear_keybuf();
for (;;) {
al_poll_mouse();
if (al_mouse_b & 1) {
if (node_count < MAX_NODES-1) {
nodes[node_count].x = al_mouse_x;
nodes[node_count].y = al_mouse_y;
al_show_mouse(NULL);
draw_node(node_count);
al_show_mouse(al_screen);
node_count++;
}
do {
al_poll_mouse();
} while (al_mouse_b & 1);
}
if ((al_mouse_b & 2) || (al_key_pressed())) {
if (node_count < 3)
al_show_alert("You must enter at least two nodes", NULL, NULL, "OK", NULL, 13, 0);
else
break;
}
}
al_show_mouse(NULL);
do {
al_poll_mouse();
} while (al_mouse_b);
al_clear_keybuf();
}
/* moves a sprite along the al_draw_spline path */
void walk(void)
{
#define MAX_POINTS 256
int points[8];
int x[MAX_POINTS], y[MAX_POINTS];
int n, i;
int npoints;
int ox, oy;
al_acquire_screen();
al_clear_to_color(al_screen, al_make_color(255, 255, 255));
for (i=1; i<node_count-1; i++)
draw_node(i);
al_release_screen();
do {
al_poll_mouse();
} while (al_mouse_b);
al_clear_keybuf();
ox = -16;
oy = -16;
al_drawing_mode(DRAW_MODE_XOR, NULL, 0, 0);
for (n=1; n < node_count-2; n++) {
npoints = (al_fix_to_int(node_dist(nodes[n], nodes[n+1]))+3) / 4;
if (npoints < 1)
npoints = 1;
else if (npoints > MAX_POINTS)
npoints = MAX_POINTS;
get_control_points(nodes[n], nodes[n+1], points);
al_calc_spline(points, npoints, x, y);
for (i=1; i<npoints; i++) {
al_vsync();
al_acquire_screen();
al_draw_circle_fill(al_screen, ox, oy, 6, al_palette_color[2]);
al_draw_circle_fill(al_screen, x[i], y[i], 6, al_palette_color[2]);
al_release_screen();
ox = x[i];
oy = y[i];
al_poll_mouse();
if ((al_key_pressed()) || (al_mouse_b))
goto getout;
}
}
getout:
al_drawing_mode(DRAW_MODE_SOLID, NULL, 0, 0);
do {
al_poll_mouse();
} while (al_mouse_b);
al_clear_keybuf();
}
/* main program */
int main(void)
{
int c;
allegro_init();
al_install_keyboard();
al_install_mouse();
al_install_timer();
if (al_set_gfx_mode(AL_GFX_SAFE, 640, 480, 0, 0) != 0) {
al_set_gfx_mode(AL_GFX_NONE, 0, 0, 0, 0);
al_show_message("Unable to set any graphic mode\n%s\n", al_error);
return 1;
}
al_set_palette(al_desktop_palette);
input_nodes();
calc_tangents();
curviness = al_float_to_fix(0.25);
show_tangents = FALSE;
show_control_points = FALSE;
draw_splines();
for (;;) {
if (al_key_pressed()) {
c = al_read_key() >> 8;
if (c == AL_KEY_ESC)
break;
else if (c == AL_KEY_UP) {
curviness += al_float_to_fix(0.05);
draw_splines();
}
else if (c == AL_KEY_DOWN) {
curviness -= al_float_to_fix(0.05);
draw_splines();
}
else if (c == AL_KEY_SPACE) {
walk();
draw_splines();
}
else if (c == AL_KEY_T) {
show_tangents = !show_tangents;
draw_splines();
}
else if (c == AL_KEY_C) {
show_control_points = !show_control_points;
draw_splines();
}
}
}
return 0;
}
AL_END_OF_MAIN();
See more files for this project here