File: exspline.c

package info (click to toggle)
allegro4 2%3A4.0.1-1
  • links: PTS
  • area: main
  • in suites: woody
  • size: 17,052 kB
  • ctags: 12,972
  • sloc: ansic: 109,525; asm: 16,672; cpp: 3,221; sh: 1,761; makefile: 556; pascal: 105; perl: 73
file content (384 lines) | stat: -rw-r--r-- 9,032 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
/*
 *    Example program for the Allegro library, by Shawn Hargreaves.
 *
 *    This program demonstrates the use of 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
 *    spline curves from p1-p2, p2-p3, etc. Each spline must pass though
 *    both of its guide points, so they must be used as the first and fourth
 *    of the spline control points. The fun bit is coming up with sensible
 *    values for the second and third spline control points, such that the
 *    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
 *    spline control points. For a 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 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 = itofix(n1.x - n2.x) / SCALE;
   fixed dy = itofix(n1.y - n2.y) / SCALE;

   return fixsqrt(fixmul(dx, dx) + fixmul(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 = fixatan2(itofix(nodes[i+1].y - nodes[i-1].y),
				  itofix(nodes[i+1].x - nodes[i-1].x));
}



/* draws one of the path nodes */
void draw_node(int n)
{
   char b[8];

   circlefill(screen, nodes[n].x, nodes[n].y, 2, palette_color[1]);

   sprintf(b, "%d", n);
   text_mode(-1);
   textout(screen, font, b, nodes[n].x-7, nodes[n].y-7, palette_color[255]);
}



/* calculates the control points for a spline segment */
void get_control_points(NODE n1, NODE n2, int points[8])
{
   fixed dist = fixmul(node_dist(n1, n2), curviness);

   points[0] = n1.x;
   points[1] = n1.y;

   points[2] = n1.x + fixtoi(fixmul(fixcos(n1.tangent), dist));
   points[3] = n1.y + fixtoi(fixmul(fixsin(n1.tangent), dist));

   points[4] = n2.x - fixtoi(fixmul(fixcos(n2.tangent), dist));
   points[5] = n2.y - fixtoi(fixmul(fixsin(n2.tangent), dist));

   points[6] = n2.x;
   points[7] = n2.y;
}



/* draws a spline curve connecting two nodes */
void draw_spline(NODE n1, NODE n2)
{
   int points[8];
   int i;

   get_control_points(n1, n2, points);
   spline(screen, points, palette_color[255]);

   if (show_control_points)
      for (i=1; i<=2; i++)
	 circlefill(screen, points[i*2], points[i*2+1], 2, palette_color[2]);
}



/* draws the spline paths */
void draw_splines(void)
{
   char b[80];
   int i;

   acquire_screen();

   clear_to_color(screen, makecol(255, 255, 255));

   text_mode(palette_color[0]);
   textout_centre(screen, font, "Spline curve path", SCREEN_W/2, 8, palette_color[255]);
   sprintf(b, "Curviness = %.2f", fixtof(curviness));
   textout_centre(screen, font, b, SCREEN_W/2, 32, palette_color[255]);
   textout_centre(screen, font, "Up/down keys to alter", SCREEN_W/2, 44, palette_color[255]);
   textout_centre(screen, font, "Space to walk", SCREEN_W/2, 68, palette_color[255]);
   textout_centre(screen, font, "C to display control points", SCREEN_W/2, 92, palette_color[255]);
   textout_centre(screen, font, "T to display tangents", SCREEN_W/2, 104, 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) {
	 line(screen, nodes[i].x - fixtoi(fixcos(nodes[i].tangent) * 24),
		      nodes[i].y - fixtoi(fixsin(nodes[i].tangent) * 24),
		      nodes[i].x + fixtoi(fixcos(nodes[i].tangent) * 24),
		      nodes[i].y + fixtoi(fixsin(nodes[i].tangent) * 24),
		      palette_color[1]);
      }
   }

   release_screen();
}



/* let the user input a list of path nodes */
void input_nodes(void)
{
   clear_to_color(screen, makecol(255, 255, 255));

   text_mode(palette_color[0]);
   textout_centre(screen, font, "Click the left mouse button to add path nodes",
		  SCREEN_W/2, 8, palette_color[255]);
   textout_centre(screen, font, "Right mouse button or any key to finish",
		  SCREEN_W/2, 24, palette_color[255]);

   node_count = 1;

   show_mouse(screen);

   do {
      poll_mouse();
   } while (mouse_b);

   clear_keybuf();

   for (;;) {
      poll_mouse();

      if (mouse_b & 1) {
	 if (node_count < MAX_NODES-1) {
	    nodes[node_count].x = mouse_x;
	    nodes[node_count].y = mouse_y;

	    show_mouse(NULL);
	    draw_node(node_count);
	    show_mouse(screen);

	    node_count++;
	 }

	 do {
	    poll_mouse();
	 } while (mouse_b & 1);
      }

      if ((mouse_b & 2) || (keypressed())) {
	 if (node_count < 3)
	    alert("You must enter at least two nodes", NULL, NULL, "OK", NULL, 13, 0);
	 else
	    break;
      }
   }

   show_mouse(NULL);

   do {
      poll_mouse();
   } while (mouse_b);

   clear_keybuf();
}



/* moves a sprite along the 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;

   acquire_screen();

   clear_to_color(screen, makecol(255, 255, 255));

   for (i=1; i<node_count-1; i++)
      draw_node(i);

   release_screen();

   do {
      poll_mouse();
   } while (mouse_b);

   clear_keybuf();

   ox = -16;
   oy = -16;

   xor_mode(TRUE);

   for (n=1; n < node_count-2; n++) {
      npoints = (fixtoi(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);
      calc_spline(points, npoints, x, y);

      for (i=1; i<npoints; i++) {
	 vsync();
	 acquire_screen();
	 circlefill(screen, ox, oy, 6, palette_color[2]);
	 circlefill(screen, x[i], y[i], 6, palette_color[2]);
	 release_screen();
	 ox = x[i];
	 oy = y[i];

	 poll_mouse();

	 if ((keypressed()) || (mouse_b))
	    goto getout;
      }
   }

   getout:

   xor_mode(FALSE);

   do {
      poll_mouse();
   } while (mouse_b);

   clear_keybuf();
}



/* main program */
int main(void)
{
   int c;

   allegro_init();
   install_keyboard();
   install_mouse();
   install_timer();
   if (set_gfx_mode(GFX_SAFE, 640, 480, 0, 0) != 0) {
      set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
      allegro_message("Unable to set any graphic mode\n%s\n", allegro_error);
      return 1;
   }
   set_palette(desktop_palette);

   input_nodes();
   calc_tangents();

   curviness = ftofix(0.25);
   show_tangents = FALSE;
   show_control_points = FALSE;

   draw_splines();

   for (;;) {
      if (keypressed()) {
	 c = readkey() >> 8;
	 if (c == KEY_ESC)
	    break;
	 else if (c == KEY_UP) {
	    curviness += ftofix(0.05);
	    draw_splines();
	 }
	 else if (c == KEY_DOWN) {
	    curviness -= ftofix(0.05);
	    draw_splines();
	 }
	 else if (c == KEY_SPACE) {
	    walk();
	    draw_splines();
	 }
	 else if (c == KEY_T) {
	    show_tangents = !show_tangents;
	    draw_splines();
	 }
	 else if (c == KEY_C) {
	    show_control_points = !show_control_points;
	    draw_splines();
	 }
      }
   }

   return 0;
}

END_OF_MAIN();