175e1a538c4d6a14c2d891395b3352386e0baf8f
[xscreensaver] / hacks / speedmine.c
1 /* -*- Mode: C; c-basic-offset: 4; tab-width: 4 -*-
2  * speedmine, Copyright (C) 2001 Conrad Parker <conrad@deephackmode.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 /*
14  * Written mostly over the Easter holiday, 2001. Psychedelic option due to
15  * a night at Home nightclub, Sydney. Three all-nighters of solid partying
16  * were involved in the week this hack was written.
17  *
18  * Happy Birthday to WierdArms (17 April) and Pat (18 April)
19  */
20
21 /*
22  * Hacking notes
23  *
24  * This program generates a rectangular terrain grid and maps this onto
25  * a semi-circular tunnel. The terrain has length TERRAIN_LENGTH, which
26  * corresponds to length along the tunnel, and breadth TERRAIN_BREADTH,
27  * which corresponds to circumference around the tunnel. For each frame,
28  * the tunnel is perspective mapped onto a set of X and Y screen values.
29  *
30  * Throughout this code the following temporary variable names are used:
31  *
32  *                      i       iterates along the tunnel in the direction of travel
33  *                      j       iterates around the tunnel clockwise
34  *                      t       iterates along the length of the perspective mapped values
35  *                              from the furthest to the nearest
36  *
37  * Thus, the buffers are used with these iterators:
38  *
39  *                      terrain[i][j]                                   terrain map
40  *                      worldx[i][j], worldy[i][j]              world coordinates (after wrapping)
41  *                      {x,y,z}curvature[i]                             tunnel curvature
42  *                      wideness[i]                                             tunnel wideness
43  *                      bonuses[i]                                              bonus values
44  *
45  *                      xvals[t][j], yvals[t][j]                screen coordinates
46  *                      {min,max}{x,y}[t]                               bounding boxes of screen coords
47  */
48
49 /* Define or undefine NDEBUG to turn assert and abort debugging off or on */
50 #define NDEBUG
51 #include <assert.h>
52
53 #include <math.h>
54
55 #include "screenhack.h"
56 #include "erase.h"
57
58 #define MIN(a,b) ((a)<(b)?(a):(b))
59 #define MAX(a,b) ((a)>(b)?(a):(b))
60
61 #define RAND(r) (int)(((r)>0)?(random() % (long)(r)): -(random() % (long)(-r)))
62
63 #define SIGN3(a) ((a)>0?1:((a)<0?-1:0))
64
65 #define MODULO(a,b) while ((a)<0) (a)+=(b); (a) %= (b);
66
67 static Display * display;
68 static Pixmap dbuf, stars_mask;
69 static Colormap cmap;
70 static unsigned int default_fg_pixel;
71 static GC draw_gc, erase_gc, tunnelend_gc, stars_gc, stars_erase_gc;
72
73 /* No. of shades of each color (ground, walls, bonuses) */
74 #define MAX_COLORS 32
75 static int ncolors, nr_ground_colors, nr_wall_colors, nr_bonus_colors;
76 static XColor ground_colors[MAX_COLORS], wall_colors[MAX_COLORS];
77 static XColor bonus_colors[MAX_COLORS];
78 static GC ground_gcs[MAX_COLORS], wall_gcs[MAX_COLORS], bonus_gcs[MAX_COLORS];
79
80 static int be_wormy;
81
82 static int width, height;
83 static int delay;
84
85 static int smoothness;
86 static int verbose_flag;
87 static int wire_flag;
88 static int terrain_flag;
89 static int widening_flag;
90 static int bumps_flag;
91 static int bonuses_flag;
92 static int crosshair_flag;
93 static int psychedelic_flag;
94
95 #ifdef NDEBUG
96 #define DEBUG_FLAG 0
97 #else
98 #define DEBUG_FLAG 1
99 #endif
100
101 static double maxspeed;
102
103 static double thrust, gravity;
104
105 static double vertigo;
106 static double curviness;
107 static double twistiness;
108
109 static double pos=0.0;
110 static double speed=-1.1;
111 static double accel=0.00000001;
112 static double step=0.0;
113
114
115 #define FORWARDS 1
116 #define BACKWARDS -1
117 static int direction = FORWARDS;
118
119 /* Apparently AIX's math.h bogusly defines `nearest' as a function,
120    in violation of the ANSI C spec. */
121 #undef nearest
122 #define nearest n3arest
123
124 static int pindex=0, nearest=0;
125 static int flipped_at=0;
126 static int xoffset=0, yoffset=0;
127
128 static int bonus_bright = 0;
129 static int wire_bonus=0;
130 #define wireframe (wire_flag||wire_bonus>8||wire_bonus%2==1)
131
132 static double speed_bonus=0.0;
133 #define effective_speed (direction*(speed+speed_bonus))
134
135 static int spin_bonus = 0;
136 static int backwards_bonus = 0;
137
138 /* No. of levels of interpolation, for perspective */
139 #define INTERP 32
140
141 /* These must be powers of 2 */
142 #define TERRAIN_LENGTH 256
143 #define TERRAIN_BREADTH 32
144
145 /* total "perspective distance" of terrain */
146 #define TERRAIN_PDIST (INTERP*TERRAIN_LENGTH)
147
148 #define ROTS 1024
149 #define TB_MUL (ROTS/TERRAIN_BREADTH)
150 static double sintab[ROTS], costab[ROTS];
151
152 static int orientation = (17*ROTS)/22;
153
154 static int terrain[TERRAIN_LENGTH][TERRAIN_BREADTH];
155 static double xcurvature[TERRAIN_LENGTH];
156 static double ycurvature[TERRAIN_LENGTH];
157 static double zcurvature[TERRAIN_LENGTH];
158 static int wideness[TERRAIN_LENGTH];
159 static int bonuses[TERRAIN_LENGTH];
160 static int xvals[TERRAIN_LENGTH][TERRAIN_BREADTH];
161 static int yvals[TERRAIN_LENGTH][TERRAIN_BREADTH];
162 static double worldx[TERRAIN_LENGTH][TERRAIN_BREADTH];
163 static double worldy[TERRAIN_LENGTH][TERRAIN_BREADTH];
164 static int minx[TERRAIN_LENGTH], maxx[TERRAIN_LENGTH];
165 static int miny[TERRAIN_LENGTH], maxy[TERRAIN_LENGTH];
166
167 #define random_elevation() (terrain_flag?(random() % 200):0)
168 #define random_curvature() (curviness>0.0?((double)(random() % 40)-20)*curviness:0.0)
169 #define random_twist() (twistiness>0.0?((double)(random() % 40)-20)*twistiness:0.0)
170 #define random_wideness() (widening_flag?(int)(random() % 1200):0)
171
172 #define STEEL_ELEVATION 300
173
174 /* a forward declaration ... */
175 static void change_colors(void);
176
177 #if HAVE_GETTIMEOFDAY
178 static int total_nframes = 0;
179 static int nframes = 0;
180 static double fps = 0.0;
181 static double fps_start, fps_end;
182 static struct timeval start_time;
183
184 /*
185  * get_time ()
186  *
187  * returns the total time elapsed since the beginning of the demo
188  */
189 static double get_time(void) {
190   struct timeval t;
191   float f;
192 #if GETTIMEOFDAY_TWO_ARGS
193   gettimeofday(&t, NULL);
194 #else
195   gettimeofday(&t);
196 #endif
197   t.tv_sec -= start_time.tv_sec;
198   f = ((double)t.tv_sec) + t.tv_usec*1e-6;
199   return f;
200 }
201
202 /*
203  * init_time ()
204  *
205  * initialises the timing structures
206  */
207 static void init_time(void) {
208 #if GETTIMEOFDAY_TWO_ARGS
209   gettimeofday(&start_time, NULL);
210 #else
211   gettimeofday(&start_time);
212 #endif
213   fps_start = get_time();
214 }
215 #endif
216
217 /*
218  * perspective()
219  *
220  * perspective map the world coordinates worldx[i][j], worldy[i][j] onto
221  * screen coordinates xvals[t][j], yvals[t][j]
222  */
223 static void
224 perspective (void)
225 {
226         static int rotation_offset=0;
227
228         int i, j, jj, t=0, depth, view_pos;
229         int rotation_bias, r;
230         double xc=0.0, yc=0.0, zc=0.0;
231         double xcc=0.0, ycc=0.0, zcc=0.0;
232         double xx, yy;
233         double zfactor, zf;
234
235         zf = 8.0*28.0 / (double)(width*TERRAIN_LENGTH);
236         if (be_wormy) zf *= 3.0;
237
238         depth = TERRAIN_PDIST - INTERP + pindex;
239
240         view_pos = (nearest+3*TERRAIN_LENGTH/4)%TERRAIN_LENGTH;
241         
242         xoffset += - xcurvature[view_pos]*curviness/8;
243         xoffset /= 2;
244
245         yoffset += - ycurvature[view_pos]*curviness/4;
246         yoffset /= 2;
247
248         rotation_offset += (int)((zcurvature[view_pos]-zcurvature[nearest])*ROTS/8);
249         rotation_offset /= 2;
250         rotation_bias = orientation + spin_bonus - rotation_offset;
251
252         if (bumps_flag) {
253                 if (be_wormy) {
254                         yoffset -= ((terrain[view_pos][TERRAIN_BREADTH/4] * width /(8*1600)));
255                         rotation_bias += (terrain[view_pos][TERRAIN_BREADTH/4+2] -
256                                                          terrain[view_pos][TERRAIN_BREADTH/4-2])/8;
257                 } else {
258                         yoffset -= ((terrain[view_pos][TERRAIN_BREADTH/4] * width /(2*1600)));
259                         rotation_bias += (terrain[view_pos][TERRAIN_BREADTH/4+2] -
260                                                          terrain[view_pos][TERRAIN_BREADTH/4-2])/16;
261                 }
262         }
263
264         MODULO(rotation_bias, ROTS);
265
266         for (t=0; t < TERRAIN_LENGTH; t++) {
267                 i = nearest + t; MODULO(i, TERRAIN_LENGTH);
268                 xc += xcurvature[i]; yc += ycurvature[i]; zc += zcurvature[i];
269                 xcc += xc; ycc += yc; zcc += zc;
270                 maxx[i] = maxy[i] = 0;
271                 minx[i] = width; miny[i] = height;
272         }
273
274         for (t=0; t < TERRAIN_LENGTH; t++) {
275                 i = nearest - 1 - t; MODULO(i, TERRAIN_LENGTH);
276
277                 zfactor = (double)depth* (12.0 - TERRAIN_LENGTH/8.0) * zf;
278                 for (j=0; j < TERRAIN_BREADTH; j++) {
279                         jj = direction * j; MODULO(jj, TERRAIN_BREADTH);
280             /* jwz: not totally sure if this is right, but it avoids div0 */
281             if (zfactor != 0) {
282                 xx = (worldx[i][jj]-(vertigo*xcc))/zfactor;
283                 yy = (worldy[i][j]-(vertigo*ycc))/zfactor;
284             } else {
285                 xx = 0;
286                 yy = 0;
287             }
288                         r = rotation_bias + (int)(vertigo*zcc); MODULO(r, ROTS);
289
290                         xvals[t][j] = xoffset + width/2 +
291                                           (int)(xx * costab[r] - yy * sintab[r]);
292                         maxx[t] = MAX(maxx[t], xvals[t][j]);
293                         minx[t] = MIN(minx[t], xvals[t][j]);
294
295                         yvals[t][j] = yoffset + height/2 +
296                                                   (int)(xx * sintab[r] + yy * costab[r]);
297                         maxy[t] = MAX(maxy[t], yvals[t][j]);
298                         miny[t] = MIN(miny[t], yvals[t][j]);
299                 }
300                 xcc -= xc; ycc -= yc; zcc -= zc;
301                 xc -= xcurvature[i]; yc -= ycurvature[i]; zc -= zcurvature[i];
302                 depth -= INTERP;
303         }
304 }
305
306 /*
307  * wrap_tunnel (start, end)
308  *
309  * wrap the terrain terrain[i][j] around the semi-circular tunnel function
310  * 
311  *                      x' = x/2 * cos(theta) - (y-k) * x * sin(theta)
312  *                      y' = x/4 * sin(theta) + y * cos(theta)
313  *
314  * between i=start and i=end inclusive, producing world coordinates
315  * worldx[i][j], worldy[i][j]
316  */
317 static void
318 wrap_tunnel (int start, int end)
319 {
320         int i, j, v;
321         double x, y;
322
323         assert (start < end);
324
325         for (i=start; i <= end; i++) {
326                 for (j=0; j < TERRAIN_BREADTH; j++) {
327                         x = j * (1.0/TERRAIN_BREADTH);
328                         v = terrain[i][j];
329                         y = (double)(v==STEEL_ELEVATION?200:v) - wideness[i] - 1200;
330
331                         /* lower road */
332                         if (j > TERRAIN_BREADTH/8 && j < 3*TERRAIN_BREADTH/8) y -= 300;
333
334                     worldx[i][j] = x/2 * costab[j*TB_MUL] -
335                                                         (y-height/4.0)*x*sintab[j*TB_MUL];
336                         worldy[i][j] = x/4 * sintab[j*TB_MUL] +
337                                                         y * costab[j*TB_MUL];
338                 }
339         }
340 }
341
342 /*
343  * flip_direction()
344  *
345  * perform the state transitions and terrain transformation for the
346  * "look backwards/look forwards" bonus
347  */
348 static void
349 flip_direction (void)
350 {
351         int i, ip, in, j, t;
352
353         direction = -direction;
354
355         bonus_bright = 20;
356
357         for (i=0; i < TERRAIN_LENGTH; i++) {
358                 in = nearest + i; MODULO(in, TERRAIN_BREADTH);
359                 ip = nearest - i; MODULO(ip, TERRAIN_BREADTH);
360                 for (j=0; j < TERRAIN_BREADTH; j++) {
361                         t = terrain[ip][j];
362                         terrain[ip][j] = terrain[in][j];
363                         terrain[in][j] = t;
364                 }
365         }
366 }
367
368 /*
369  * generate_smooth (start, end)
370  *
371  * generate smooth terrain between i=start and i=end inclusive
372  */
373 static void
374 generate_smooth (int start, int end)
375 {
376         int i,j, ii;
377
378         assert (start < end);
379
380         for (i=start; i <= end; i++) {
381                 ii = i; MODULO(ii, TERRAIN_LENGTH);
382                 for (j=0; j < TERRAIN_BREADTH; j++) {
383                         terrain[i][j] = STEEL_ELEVATION;
384                 }
385         }
386 }
387
388 /*
389  * generate_straight (start, end)
390  *
391  * zero the curvature and wideness between i=start and i=end inclusive
392  */
393 static void
394 generate_straight (int start, int end)
395 {
396         int i,j, ii;
397
398         assert (start < end);
399
400         for (i=start; i <= end; i++) {
401                 ii = i; MODULO(ii, TERRAIN_LENGTH);
402                 for (j=0; j < TERRAIN_BREADTH; j++) {
403                         xcurvature[ii] = 0;
404                         ycurvature[ii] = 0;
405                         zcurvature[ii] = 0;
406                         wideness[ii] = 0;
407                 }
408         }
409 }
410
411 /*
412  * int generate_terrain_value (v1, v2, v3, v4, w)
413  *
414  * generate terrain value near the average of v1, v2, v3, v4, with
415  * perturbation proportional to w
416  */
417 static int
418 generate_terrain_value (int v1, int v2, int v3, int v4, int w)
419 {
420         int sum, ret;
421         int rval;
422
423         if (!terrain_flag) return 0;
424
425         sum = v1 + v2 + v3 + v4;
426
427         rval = w*sum/smoothness;
428         if (rval == 0) rval = 2;
429
430         ret = (sum/4 -(rval/2) + RAND(rval));
431
432         if (ret < -400 || ret > 400) {
433                 ret = sum/4;
434         }
435
436         return ret;
437 }
438
439 /*
440  * generate_terrain (start, end, final)
441  *
442  * generate terrain[i][j] between i=start and i=end inclusive
443  *
444  * This is performed by successive subdivision of the terrain into
445  * rectangles of decreasing size. Subdivision continues until the
446  * the minimum width or height of these rectangles is 'final'; ie.
447  * final=1 indicates to subdivide as far as possible, final=2 indicates
448  * to stop one subdivision before that (leaving a checkerboard pattern
449  * uncalculated) etc.
450  */
451 static void
452 generate_terrain (int start, int end, int final)
453 {
454         int i,j,w,l;
455         int ip, jp, in, jn; /* prev, next values */
456         int diff;
457
458         assert (start < end);
459         assert (start >= 0 && start < TERRAIN_LENGTH);
460         assert (end >= 0 && end < TERRAIN_LENGTH);
461
462         diff = end - start + 1;
463
464         terrain[end][0] = random_elevation();
465         terrain[end][TERRAIN_BREADTH/2] = random_elevation();
466
467         for (w= diff/2, l=TERRAIN_BREADTH/4;
468              w >= final || l >= final; w /= 2, l /= 2) {
469
470                 if (w<1) w=1; if (l<1) l=1;
471
472                 for (i=start+w-1; i < end; i += (w*2)) {
473                         ip = i-w; MODULO(ip, TERRAIN_LENGTH);
474                         in = i+w; MODULO(in, TERRAIN_LENGTH);
475                         for (j=l-1; j < TERRAIN_BREADTH; j += (l*2)) {
476                                 jp = j-1; MODULO(jp, TERRAIN_BREADTH);
477                                 jn = j+1; MODULO(jn, TERRAIN_BREADTH);
478                                 terrain[i][j] =
479                                         generate_terrain_value (terrain[ip][jp], terrain[in][jp], 
480                                             terrain[ip][jn], terrain[in][jn], w);
481                         }
482                 }
483
484                 for (i=start+(w*2)-1; i < end; i += (w*2)) {
485                         ip = i-w; MODULO(ip, TERRAIN_LENGTH);
486                         in = i+w; MODULO(in, TERRAIN_LENGTH);
487                         for (j=l-1; j < TERRAIN_BREADTH; j += (l*2)) {
488                                 jp = j-1; MODULO(jp, TERRAIN_BREADTH);
489                                 jn = j+1; MODULO(jn, TERRAIN_BREADTH);
490                                 terrain[i][j] =
491                                         generate_terrain_value (terrain[ip][j], terrain[in][j],
492                                             terrain[i][jp], terrain[i][jn], w);
493                         }
494                 }
495
496                 for (i=start+w-1; i < end; i += (w*2)) {
497                         ip = i-w; MODULO(ip, TERRAIN_LENGTH);
498                         in = i+w; MODULO(in, TERRAIN_LENGTH);
499                         for (j=2*l-1; j < TERRAIN_BREADTH; j += (l*2)) {
500                                 jp = j-1; MODULO(jp, TERRAIN_BREADTH);
501                                 jn = j+1; MODULO(jn, TERRAIN_BREADTH);
502                                 terrain[i][j] =
503                                         generate_terrain_value (terrain[ip][j], terrain[in][j],
504                                             terrain[i][jp], terrain[i][jn], w);
505                         }
506                 }
507         }
508 }
509
510 /*
511  * double generate_curvature_value (v1, v2, w)
512  *
513  * generate curvature value near the average of v1 and v2, with perturbation
514  * proportional to w
515  */
516 static double
517 generate_curvature_value (double v1, double v2, int w)
518 {
519         double sum, avg, diff, ret;
520         int rval;
521
522         assert (!isnan(v1) && !isnan(v2));
523
524         sum = v1+v2;
525         avg = sum/2.0;
526
527         diff = MIN(v1 - avg, v2 - avg);
528
529         rval = (int)diff * w;
530         if (rval == 0.0) return avg;
531
532         ret = (avg -((double)rval)/500.0 + ((double)RAND(rval))/1000.0);
533
534         assert (!isnan(ret));
535
536         return ret;
537 }
538
539 /*
540  * generate_curves (start, end)
541  *
542  * generate xcurvature[i], ycurvature[i], zcurvature[i] and wideness[i]
543  * between start and end inclusive
544  */
545 static void
546 generate_curves (int start, int end)
547 {
548         int i, diff, ii, in, ip, w;
549
550         assert (start < end);
551
552         diff = end - start + 1; MODULO (diff, TERRAIN_LENGTH);
553
554         if (random() % 100 == 0)
555           xcurvature[end] = 30 * random_curvature();
556         else if (random() % 10 == 0)
557           xcurvature[end] = 20 * random_curvature();
558         else
559           xcurvature[end] = 10 * random_curvature();
560
561         if (random() % 50 == 0)
562           ycurvature[end] = 20 * random_curvature();
563         else if (random() % 25 == 0)
564           ycurvature[end] = 30 * random_curvature();
565         else
566           ycurvature[end] = 10 * random_curvature();
567
568         if (random() % 3 == 0)
569           zcurvature[end] = random_twist();
570         else
571           zcurvature[end] =
572                           generate_curvature_value (zcurvature[end], random_twist(), 1);
573
574         if (be_wormy)
575                         wideness[end] = random_wideness();
576         else
577                 wideness[end] =
578                         generate_curvature_value (wideness[end], random_wideness(), 1);
579
580     for (w=diff/2; w >= 1; w /= 2) {
581       for (i=start+w-1; i < end; i+=(w*2)) {
582         ii = i; MODULO (ii, TERRAIN_LENGTH);
583                 ip = i-w; MODULO (ip, TERRAIN_LENGTH);
584             in = i+w; MODULO (in, TERRAIN_LENGTH);
585             xcurvature[ii] =
586                                 generate_curvature_value (xcurvature[ip], xcurvature[in], w);
587             ycurvature[ii] =
588                                 generate_curvature_value (ycurvature[ip], ycurvature[in], w);
589             zcurvature[ii] =
590                                 generate_curvature_value (zcurvature[ip], zcurvature[in], w);
591             wideness[ii] =
592                                 generate_curvature_value (wideness[ip], wideness[in], w);
593       }
594     }
595 }
596
597 /*
598  * do_bonus ()
599  *
600  * choose a random bonus and perform its state transition
601  */
602 static void
603 do_bonus (void)
604 {
605         static int jamming=0;
606
607         bonus_bright = 20;
608
609         if (jamming > 0) {
610                 jamming--;
611                 nearest -= 2; MODULO(nearest, TERRAIN_LENGTH);
612                 return;
613         }
614
615         if (psychedelic_flag) change_colors();
616
617         switch (random() % 7) {
618         case 0: /* switch to or from wireframe */
619                 wire_bonus = (wire_bonus?0:300);
620                 break;
621         case 1: /* speedup */
622                 speed_bonus = 40.0;
623                 break;
624         case 2:
625                 spin_bonus += ROTS;
626                 break;
627         case 3:
628                 spin_bonus -= ROTS;
629                 break;
630         case 4: /* look backwards / look forwards */
631                 flipped_at = nearest;
632                 flip_direction ();
633                 backwards_bonus = (backwards_bonus?0:10);
634                 break;
635         case 5:
636                 change_colors();
637                 break;
638         case 6: /* jam against the bonus a few times; deja vu! */
639                 nearest -= 2; MODULO(nearest, TERRAIN_LENGTH);
640                 jamming = 3;
641                 break;
642         default:
643                 assert(0);
644                 break;
645         }
646 }
647
648 /*
649  * check_bonus ()
650  *
651  * check if a bonus has been passed in the last frame, and handle it
652  */
653 static void
654 check_bonuses (void)
655 {
656         int i, ii, start, end;
657
658         if (!bonuses_flag) return;
659
660         if (step >= 0.0) {
661                 start = nearest; end = nearest + (int)floor(step);
662         } else {
663                 end = nearest; start = nearest + (int)floor(step);
664         }
665
666         if (be_wormy) {
667                 start += TERRAIN_LENGTH/4;
668                 end += TERRAIN_LENGTH/4;
669         }
670
671         for (i=start; i < end; i++) {
672                 ii = i; MODULO(ii, TERRAIN_LENGTH);
673                 if (bonuses[ii] == 1) do_bonus ();
674         }
675 }
676
677 /*
678  * decrement_bonuses (double time_per_frame)
679  *
680  * decrement timers associated with bonuses
681  */
682 static void
683 decrement_bonuses (double time_per_frame)
684 {
685         if (!bonuses_flag) return;
686
687         if (bonus_bright > 0) bonus_bright-=4;
688         if (wire_bonus > 0) wire_bonus--;
689         if (speed_bonus > 0) speed_bonus -= 2.0;
690
691         if (spin_bonus > 10) spin_bonus -= (int)(step*13.7);
692         else if (spin_bonus < -10) spin_bonus += (int)(step*11.3);
693
694         if (backwards_bonus > 1) backwards_bonus--;
695         else if (backwards_bonus == 1) {
696                 nearest += 2*(MAX(flipped_at, nearest) - MIN(flipped_at,nearest));
697                 MODULO(nearest, TERRAIN_LENGTH);
698                 flip_direction ();
699                 backwards_bonus = 0;
700         }
701 }
702
703 /*
704  * set_bonuses (start, end)
705  *
706  * choose if to and where to set a bonus between i=start and i=end inclusive
707  */
708 static void
709 set_bonuses (int start, int end)
710 {
711         int i, diff, ii;
712
713         if (!bonuses_flag) return;
714
715         assert (start < end);
716
717         diff = end - start;
718
719         for (i=start; i <= end; i++) {
720                 ii = i; if (ii>=TERRAIN_LENGTH) ii -= TERRAIN_LENGTH;
721                 bonuses[ii] = 0;
722         }
723         if (random() % 4 == 0) {
724                 i = start + RAND(diff-3);
725                 ii = i; if (ii>=TERRAIN_LENGTH) ii -= TERRAIN_LENGTH;
726                 bonuses[ii] = 2; /* marker */
727                 ii = i+3; if (ii>=TERRAIN_LENGTH) ii -= TERRAIN_LENGTH;
728                 bonuses[ii] = 1; /* real thing */
729         }
730 }
731
732 /*
733  * regenerate_terrain ()
734  *
735  * regenerate a portion of the terrain map of length TERRAIN_LENGTH/4 iff
736  * we just passed between two quarters of the terrain.
737  *
738  * choose the kind of terrain to produce, produce it and wrap the tunnel
739  */
740 static void
741 regenerate_terrain (void)
742 {
743         int start, end;
744         int passed;
745
746         passed = nearest % (TERRAIN_LENGTH/4);
747
748         if (speed == 0.0 ||
749                 (speed > 0.0 && passed > (int)step) ||
750                 (speed < 0.0 && (TERRAIN_LENGTH/4)-passed > (int)fabs(step))) {
751
752                 return;
753         }
754
755         end = nearest - passed - 1; MODULO(end, TERRAIN_LENGTH);
756         start = end - TERRAIN_LENGTH/4 + 1; MODULO(start, TERRAIN_LENGTH);
757
758         if (DEBUG_FLAG) printf ("Regenerating [%d - %d]\n", start, end);
759
760         set_bonuses (start, end);
761
762         switch (random() % 64) {
763         case 0:
764         case 1:
765                 generate_terrain (start, end, 1);
766                 generate_smooth (start,
767                         start + TERRAIN_LENGTH/8 + (random() % TERRAIN_LENGTH/8));
768                 break;
769         case 2:
770                 generate_smooth (start, end);
771                 generate_terrain (start, end, 4); break;
772         case 3:
773                 generate_smooth (start, end);
774                 generate_terrain (start, end, 2); break;
775         default:
776                 generate_terrain (start, end, 1);
777         }
778
779         if (random() % 16 == 0) {
780                 generate_straight (start, end);
781         } else {
782                 generate_curves (start, end);
783         }
784
785         wrap_tunnel (start, end);
786 }
787
788 /*
789  * init_terrain ()
790  *
791  * initialise the terrain map for the beginning of the demo
792  */
793 static void
794 init_terrain (void)
795 {
796         int i, j;
797
798         for (i=0; i < TERRAIN_LENGTH; i++) {
799                 for (j=0; j < TERRAIN_BREADTH; j++) {
800                         terrain[i][j] = 0;
801                 }
802         }
803
804         terrain[TERRAIN_LENGTH-1][0] =  - (random() % 300);
805         terrain[TERRAIN_LENGTH-1][TERRAIN_BREADTH/2] =  - (random() % 300);
806
807         generate_smooth (0, TERRAIN_LENGTH-1);
808         generate_terrain (0, TERRAIN_LENGTH/4 -1, 4);
809         generate_terrain (TERRAIN_LENGTH/4, TERRAIN_LENGTH/2 -1, 2);
810         generate_terrain (TERRAIN_LENGTH/2, 3*TERRAIN_LENGTH/4 -1, 1);
811         generate_smooth (3*TERRAIN_LENGTH/4, TERRAIN_LENGTH-1);
812 }
813
814 /*
815  * init_curves ()
816  *
817  * initialise the curvatures and wideness for the beginning of the demo.
818  */
819 static void
820 init_curves (void)
821 {
822         int i;
823
824         for (i=0; i < TERRAIN_LENGTH-1; i++) {
825         xcurvature[i] = 0.0;
826             ycurvature[i] = 0.0;
827                 zcurvature[i] = 0.0;
828         }
829
830     xcurvature[TERRAIN_LENGTH-1] = random_curvature();
831     ycurvature[TERRAIN_LENGTH-1] = random_curvature();
832     zcurvature[TERRAIN_LENGTH-1] = random_twist();
833
834         generate_straight (0, TERRAIN_LENGTH/4-1);
835         generate_curves (TERRAIN_LENGTH/4, TERRAIN_LENGTH/2-1);
836         generate_curves (TERRAIN_LENGTH/2, 3*TERRAIN_LENGTH/4-1);
837         generate_straight (3*TERRAIN_LENGTH/4, TERRAIN_LENGTH-1);
838
839 }
840
841 /*
842  * render_quads (dpy, d, t, dt, i)
843  *
844  * renders the quadrilaterals from perspective depth t to t+dt.
845  * i is passed as a hint, where i corresponds to t as asserted.
846  */
847 static void
848 render_quads (Display * dpy, Drawable d, int t, int dt, int i)
849 {
850         int j, t2, j2, in;
851         int index;
852         XPoint points[4];
853         GC gc;
854
855         assert (i == (nearest - (t + dt) + TERRAIN_LENGTH) % TERRAIN_LENGTH);
856
857         in = i + 1; MODULO(in, TERRAIN_LENGTH);
858
859         for (j=0; j < TERRAIN_BREADTH; j+=dt) {
860                 t2 = t+dt; if (t2 >= TERRAIN_LENGTH) t2 -= TERRAIN_LENGTH;
861                 j2 = j+dt; if (j2 >= TERRAIN_BREADTH) j2 -= TERRAIN_BREADTH;
862                 points[0].x = xvals[t][j]; points[0].y = yvals[t][j];
863                 points[1].x = xvals[t2][j]; points[1].y = yvals[t2][j];
864                 points[2].x = xvals[t2][j2]; points[2].y = yvals[t2][j2];
865                 points[3].x = xvals[t][j2]; points[3].y = yvals[t][j2];
866
867             index = bonus_bright + ncolors/3 +
868                                 t*(t*INTERP + pindex) * ncolors /
869                             (3*TERRAIN_LENGTH*TERRAIN_PDIST);
870                 if (!wireframe) {
871                         index += (int)((points[0].y - points[3].y) / 8);
872                         index += (int)((worldx[i][j] - worldx[in][j]) / 40);
873                         index += (int)((terrain[in][j] - terrain[i][j]) / 100);
874                 }
875                 if (be_wormy && psychedelic_flag) index += ncolors/4;
876
877                 index = MIN (index, ncolors-1);
878                 index = MAX (index, 0);
879
880                 if (bonuses[i]) {
881                         XSetClipMask (dpy, bonus_gcs[index], None);
882                 }
883
884                 if (wireframe) {
885                         if (bonuses[i]) gc = bonus_gcs[index];
886                         else gc = ground_gcs[index];
887                         XDrawLines (dpy, d, gc, points, 4, CoordModeOrigin);
888                 } else {
889                         if (bonuses[i])
890                                 gc = bonus_gcs[index];
891                         else if ((direction>0 && j < TERRAIN_BREADTH/8) ||
892                                 (j > TERRAIN_BREADTH/8 && j < 3*TERRAIN_BREADTH/8-1) ||
893                                 (direction < 0 && j > 3*TERRAIN_BREADTH/8-1 &&
894                                         j < TERRAIN_BREADTH/2) ||
895                                 terrain[i][j] == STEEL_ELEVATION ||
896                                 wideness[in] - wideness[i] > 200) 
897                                 gc = ground_gcs[index];
898                         else
899                                 gc = wall_gcs[index];
900
901                         XFillPolygon (dpy, d, gc, points, 4, Nonconvex, CoordModeOrigin);
902                 }
903         }
904 }
905
906 /*
907  * render_pentagons (dpy, d, t, dt, i)
908  *
909  * renders the pentagons from perspective depth t to t+dt.
910  * i is passed as a hint, where i corresponds to t as asserted.
911  */
912 static void
913 render_pentagons (Display *dpy, Drawable d, int t, int dt, int i)
914 {
915         int j, t2, j2, j3, in;
916         int index;
917         XPoint points[5];
918         GC gc;
919
920         assert (i == (nearest -t + TERRAIN_LENGTH) % TERRAIN_LENGTH);
921
922         in = i + 1; MODULO(in, TERRAIN_LENGTH);
923
924         for (j=0; j < TERRAIN_BREADTH; j+=dt*2) {
925                 t2 = t+(dt*2); if (t2 >= TERRAIN_LENGTH) t2 -= TERRAIN_LENGTH;
926                 j2 = j+dt; if (j2 >= TERRAIN_BREADTH) j2 -= TERRAIN_BREADTH;
927                 j3 = j+dt+dt; if (j3 >= TERRAIN_BREADTH) j3 -= TERRAIN_BREADTH;
928                 points[0].x = xvals[t][j]; points[0].y = yvals[t][j];
929                 points[1].x = xvals[t2][j]; points[1].y = yvals[t2][j];
930                 points[2].x = xvals[t2][j2]; points[2].y = yvals[t2][j2];
931                 points[3].x = xvals[t2][j3]; points[3].y = yvals[t2][j3];
932                 points[4].x = xvals[t][j3]; points[4].y = yvals[t][j3];
933
934             index = bonus_bright + ncolors/3 +
935                                 t*(t*INTERP + pindex) * ncolors /
936                             (3*TERRAIN_LENGTH*TERRAIN_PDIST);
937                 if (!wireframe) {
938                         index += (int)((points[0].y - points[3].y) / 8);
939                         index += (int)((worldx[i][j] - worldx[in][j]) / 40);
940                         index += (int)((terrain[in][j] - terrain[i][j]) / 100);
941                 }
942                 if (be_wormy && psychedelic_flag) index += ncolors/4;
943
944                 index = MIN (index, ncolors-1);
945                 index = MAX (index, 0);
946
947                 if (bonuses[i]) {
948                         XSetClipMask (dpy, bonus_gcs[index], None);
949                 }
950
951                 if (wireframe) {
952                         if (bonuses[i]) gc = bonus_gcs[index];
953                         else gc = ground_gcs[index];
954                         XDrawLines (dpy, d, gc, points, 5, CoordModeOrigin);
955                 } else {
956                         if (bonuses[i])
957                                 gc = bonus_gcs[index];
958                         else if (j < TERRAIN_BREADTH/8 ||
959                                 (j > TERRAIN_BREADTH/8 && j < 3*TERRAIN_BREADTH/8-1) ||
960                                 terrain[i][j] == STEEL_ELEVATION ||
961                                 wideness[in] - wideness[i] > 200) 
962                                 gc = ground_gcs[index];
963                         else
964                                 gc = wall_gcs[index];
965
966                         XFillPolygon (dpy, d, gc, points, 5, Complex, CoordModeOrigin);
967                 }
968         }
969 }
970
971 /*
972  * render_block (dpy, d, gc, t)
973  *
974  * render a filled polygon at perspective depth t using the given GC
975  */
976 static void
977 render_block (Display * dpy, Drawable d, GC gc, int t)
978 {
979         int i;
980
981         XPoint erase_points[TERRAIN_BREADTH/2];
982
983         for (i=0; i < TERRAIN_BREADTH/2; i++) {
984                 erase_points[i].x = xvals[t][i*2];
985                 erase_points[i].y = yvals[t][i*2];
986         }
987
988         XFillPolygon (dpy, d, gc, erase_points,
989                                   TERRAIN_BREADTH/2, Complex, CoordModeOrigin);
990 }
991
992 /*
993  * regenerate_stars_mask (dpy, t)
994  *
995  * regenerate the clip mask 'stars_mask' for drawing the bonus stars at
996  * random positions within the bounding box at depth t
997  */
998 static void
999 regenerate_stars_mask (Display * dpy, int t)
1000 {
1001         int i, w, h, a, b, l1, l2;
1002         const int lim = width*TERRAIN_LENGTH/(300*(TERRAIN_LENGTH-t));
1003
1004         w = maxx[t] - minx[t];
1005         h = maxy[t] - miny[t];
1006
1007         if (w<6||h<6) return;
1008
1009         XFillRectangle (dpy, stars_mask, stars_erase_gc,
1010                                         0, 0, width, height);
1011
1012         l1 = (t>3*TERRAIN_LENGTH/4?2:1);
1013         l2 = (t>7*TERRAIN_LENGTH/8?2:1);
1014
1015         for (i=0; i < lim; i++) {
1016                 a = RAND(w); b = RAND(h);
1017                 XDrawLine (dpy, stars_mask, stars_gc,
1018                                         minx[t]+a-l1, miny[t]+b, minx[t]+a+l1, miny[t]+b);
1019                 XDrawLine (dpy, stars_mask, stars_gc,
1020                                         minx[t]+a, miny[t]+b-l1, minx[t]+a, miny[t]+b+l1);
1021         }
1022         for (i=0; i < lim; i++) {
1023                 a = RAND(w); b = RAND(h);
1024                 XDrawLine (dpy, stars_mask, stars_gc,
1025                                         minx[t]+a-l2, miny[t]+b, minx[t]+a+l2, miny[t]+b);
1026                 XDrawLine (dpy, stars_mask, stars_gc,
1027                                         minx[t]+a, miny[t]+b-l2, minx[t]+a, miny[t]+b+l2);
1028         }
1029 }
1030
1031 /*
1032  * render_bonus_block (dpy, d, t, i)
1033  *
1034  * draw the bonus stars at depth t.
1035  * i is passed as a hint, where i corresponds to t as asserted.
1036  */
1037 static void
1038 render_bonus_block (Display * dpy, Drawable d, int t, int i)
1039 {
1040         int bt;
1041
1042         assert (i == (nearest -t + TERRAIN_LENGTH) % TERRAIN_LENGTH);
1043
1044         if (!bonuses[i] || wireframe) return;
1045
1046         regenerate_stars_mask (dpy, t);
1047
1048         bt = t * nr_bonus_colors / (2*TERRAIN_LENGTH);
1049
1050         XSetClipMask (dpy, bonus_gcs[bt], stars_mask);
1051
1052         render_block (dpy, d, bonus_gcs[bt], t);
1053 }
1054
1055 static int
1056 begin_at (void)
1057 {
1058         int t;
1059         int max_minx=0, min_maxx=width, max_miny=0, min_maxy=height;
1060
1061         for (t=TERRAIN_LENGTH-1; t > 0; t--) {
1062                 max_minx = MAX (max_minx, minx[t]);
1063                 min_maxx = MIN (min_maxx, maxx[t]);
1064                 max_miny = MAX (max_miny, miny[t]);
1065                 min_maxy = MIN (min_maxy, maxy[t]);
1066
1067                 if (max_miny >= min_maxy || max_minx >= min_maxx) break;
1068         }
1069
1070         return t;
1071 }
1072
1073 /*
1074  * render_speedmine (dpy, d)
1075  *
1076  * render the current frame.
1077  */
1078 static void
1079 render_speedmine (Display * dpy, Drawable d)
1080 {
1081         int t, i=nearest, dt=1;
1082         GC gc;
1083
1084         assert (nearest >= 0 && nearest < TERRAIN_LENGTH);
1085
1086         if (be_wormy || wireframe) {
1087                 XFillRectangle (dpy, d, erase_gc, 0, 0, width, height);
1088
1089                 dt=4;
1090                 for (t=0; t < TERRAIN_LENGTH/4; t+=dt) {
1091                         render_bonus_block (dpy, d, t, i);
1092                         i -= dt; MODULO(i, TERRAIN_LENGTH);
1093                         render_quads (dpy, d, t, dt, i);
1094                 }
1095
1096                 assert (t == TERRAIN_LENGTH/4);
1097         } else {
1098                 t = MAX(begin_at(), TERRAIN_LENGTH/4);
1099                 /*t = TERRAIN_LENGTH/4; dt = 2; */
1100                 dt = (t >= 3*TERRAIN_LENGTH/4 ? 1 : 2);
1101                 i = (nearest -t + TERRAIN_LENGTH) % TERRAIN_LENGTH;
1102                 render_block (dpy, d, tunnelend_gc, t);
1103         }
1104
1105         dt=2;
1106
1107         if (t == TERRAIN_LENGTH/4)
1108                 render_pentagons (dpy, d, t, dt, i);
1109
1110         for (; t < 3*TERRAIN_LENGTH/4; t+=dt) {
1111                 render_bonus_block (dpy, d, t, i);
1112                 i -= dt; MODULO(i, TERRAIN_LENGTH);
1113                 render_quads (dpy, d, t, dt, i);
1114         }
1115
1116         dt=1;
1117         if (be_wormy) {
1118                 for (; t < TERRAIN_LENGTH-(1+(pindex<INTERP/2)); t+=dt) {
1119                         render_bonus_block (dpy, d, t, i);
1120                         i -= dt; MODULO(i, TERRAIN_LENGTH);
1121                 }
1122         } else {
1123                 if (wireframe) {assert (t == 3*TERRAIN_LENGTH/4);}
1124
1125                 if (t == 3*TERRAIN_LENGTH/4)
1126                         render_pentagons (dpy, d, t, dt, i);
1127
1128                 for (; t < TERRAIN_LENGTH-(1+(pindex<INTERP/2)); t+=dt) {
1129                         render_bonus_block (dpy, d, t, i);
1130                         i -= dt; MODULO(i, TERRAIN_LENGTH);
1131                         render_quads (dpy, d, t, dt, i);
1132                 }
1133         }
1134
1135         /* Draw crosshair */
1136         if (crosshair_flag) {
1137                 gc = (wireframe ? bonus_gcs[nr_bonus_colors/2] : erase_gc);
1138                 XFillRectangle (dpy, d, gc,
1139                                                 width/2+(xoffset)-8, height/2+(yoffset*2)-1, 16, 3);
1140                 XFillRectangle (dpy, d, gc,
1141                                                 width/2+(xoffset)-1, height/2+(yoffset*2)-8, 3, 16);
1142         }
1143
1144 }
1145
1146 /*
1147  * move (step)
1148  *
1149  * move to the position for the next frame, and modify the state variables
1150  * nearest, pindex, pos, speed
1151  */
1152 static void
1153 move (double step)
1154 {
1155         double dpos;
1156
1157         pos += step;
1158         dpos = SIGN3(pos) * floor(fabs(pos));
1159
1160         pindex += SIGN3(effective_speed) + INTERP;
1161         while (pindex >= INTERP) {
1162                 nearest --;
1163                 pindex -= INTERP;
1164         }
1165         while (pindex < 0) {
1166                 nearest ++;
1167                 pindex += INTERP;
1168         }
1169
1170     nearest += dpos; MODULO(nearest, TERRAIN_LENGTH);
1171
1172         pos -= dpos;
1173
1174         accel = thrust + ycurvature[nearest] * gravity;
1175         speed += accel;
1176         if (speed > maxspeed) speed = maxspeed;
1177         if (speed < -maxspeed) speed = -maxspeed;
1178 }
1179
1180 /*
1181  * speedmine (dpy, window)
1182  *
1183  * do everything required for one frame of the demo
1184  */
1185 static void
1186 speedmine (Display *dpy, Window window)
1187 {
1188         double elapsed, time_per_frame = 0.04;
1189
1190         regenerate_terrain ();
1191
1192         perspective ();
1193
1194         render_speedmine (dpy, dbuf);
1195         XCopyArea (dpy, dbuf, window, draw_gc, 0, 0, width, height, 0, 0);
1196
1197 #if HAVE_GETTIMEOFDAY
1198         fps_end = get_time();
1199         nframes++;
1200         total_nframes++;
1201
1202         if (fps_end > fps_start + 0.5) {
1203                 elapsed = fps_end - fps_start;
1204                 fps_start = get_time();
1205
1206                 time_per_frame = elapsed / nframes - delay*1e-6;
1207                 fps = nframes / elapsed;
1208                 if (DEBUG_FLAG) {
1209                         printf ("%f s elapsed\t%3f s/frame\t%.1f FPS\n", elapsed,
1210                                         time_per_frame, fps);
1211                 }
1212                 step = effective_speed * elapsed;
1213
1214                 nframes = 0;
1215         }
1216 #else
1217         time_per_frame = 0.04;
1218         step = effective_speed;
1219 #endif
1220
1221         move (step);
1222
1223         decrement_bonuses (time_per_frame);
1224
1225         check_bonuses ();
1226 }
1227
1228 /*
1229  * speedmine_color_ramp (dpy, cmap, gcs, colors, ncolors, s1, s2, v1, v2)
1230  *
1231  * generate a color ramp of up to *ncolors between randomly chosen hues,
1232  * varying from saturation s1 to s2 and value v1 to v2, placing the colors
1233  * in 'colors' and creating corresponding GCs in 'gcs'.
1234  *
1235  * The number of colors actually allocated is returned in ncolors.
1236  */
1237 static void
1238 speedmine_color_ramp (Display * dpy, Colormap cmap, GC *gcs, XColor * colors,
1239                                          int *ncolors, double s1, double s2, double v1, double v2)
1240 {
1241         XGCValues gcv;
1242         int h1, h2;
1243         unsigned long flags;
1244         int i;
1245
1246         assert (*ncolors >= 0);
1247         assert (s1 >= 0.0 && s1 <= 1.0 && v1 >= 0.0 && v2 <= 1.0);
1248
1249         if (psychedelic_flag) {
1250                 h1 = RAND(360); h2 = (h1 + 180) % 360;
1251         } else {
1252                 h1 = h2 = RAND(360);
1253         }
1254
1255         make_color_ramp (dpy, cmap, h1, s1, v1, h2, s2, v2,
1256                                      colors, ncolors, False, True, False);
1257
1258         flags = GCForeground;
1259         for (i=0; i < *ncolors; i++) {
1260                 gcv.foreground = colors[i].pixel;
1261                 gcs[i] = XCreateGC (dpy, dbuf, flags, &gcv);
1262         }
1263
1264 }
1265
1266 /*
1267  * change_colors ()
1268  *
1269  * perform the color changing bonus. New colors are allocated for the
1270  * walls and bonuses, and if the 'psychedelic' option is set then new
1271  * colors are also chosen for the ground.
1272  */
1273 static void
1274 change_colors (void)
1275 {
1276         double s1, s2;
1277
1278         if (psychedelic_flag) {
1279                 free_colors (display, cmap, bonus_colors, nr_bonus_colors);
1280                 free_colors (display, cmap, wall_colors, nr_wall_colors);
1281                 free_colors (display, cmap, ground_colors, nr_ground_colors);
1282                 ncolors = MAX_COLORS;
1283
1284                 s1 = 0.4; s2 = 0.9;
1285
1286                 speedmine_color_ramp (display, cmap, ground_gcs, ground_colors,
1287                                                           &ncolors, 0.0, 0.8, 0.0, 0.9);
1288                 nr_ground_colors = ncolors;
1289         } else {
1290                 free_colors (display, cmap, bonus_colors, nr_bonus_colors);
1291                 free_colors (display, cmap, wall_colors, nr_wall_colors);
1292                 ncolors = nr_ground_colors;
1293
1294                 s1 = 0.0; s2 = 0.6;
1295         }
1296
1297     speedmine_color_ramp (display, cmap, wall_gcs, wall_colors, &ncolors,
1298                                                   s1, s2, 0.0, 0.9);
1299     nr_wall_colors = ncolors;
1300
1301     speedmine_color_ramp (display, cmap, bonus_gcs, bonus_colors, &ncolors,
1302                                                   0.6, 0.9, 0.4, 1.0);
1303         nr_bonus_colors = ncolors;
1304 }
1305
1306 /*
1307  * init_psychedelic_colors (dpy, window, cmap)
1308  *
1309  * initialise a psychedelic colormap
1310  */
1311 static void
1312 init_psychedelic_colors (Display * dpy, Window window, Colormap cmap)
1313 {
1314   XGCValues gcv;
1315
1316   gcv.foreground = get_pixel_resource ("tunnelend", "TunnelEnd", dpy, cmap);
1317   tunnelend_gc = XCreateGC (dpy, window, GCForeground, &gcv);
1318
1319   ncolors = MAX_COLORS;
1320
1321   speedmine_color_ramp (dpy, cmap, ground_gcs, ground_colors, &ncolors,
1322                                                 0.0, 0.8, 0.0, 0.9);
1323   nr_ground_colors = ncolors;
1324
1325   speedmine_color_ramp (dpy, cmap, wall_gcs, wall_colors, &ncolors,
1326                                                 0.0, 0.6, 0.0, 0.9);
1327   nr_wall_colors = ncolors;
1328
1329   speedmine_color_ramp (dpy, cmap, bonus_gcs, bonus_colors, &ncolors,
1330                                                 0.6, 0.9, 0.4, 1.0);
1331   nr_bonus_colors = ncolors;
1332 }
1333
1334 /*
1335  * init_colors (dpy, window, cmap)
1336  *
1337  * initialise a normal colormap
1338  */
1339 static void
1340 init_colors (Display * dpy, Window window, Colormap cmap)
1341 {
1342   XGCValues gcv;
1343   XColor dark, light;
1344   int h1, h2;
1345   double s1, s2, v1, v2;
1346   unsigned long flags;
1347   int i;
1348
1349   gcv.foreground = get_pixel_resource ("tunnelend", "TunnelEnd", dpy, cmap);
1350   tunnelend_gc = XCreateGC (dpy, window, GCForeground, &gcv);
1351
1352   ncolors = MAX_COLORS;
1353
1354   dark.pixel = get_pixel_resource ("darkground", "DarkGround", dpy, cmap);
1355   XQueryColor (dpy, cmap, &dark);
1356
1357   light.pixel = get_pixel_resource ("lightground", "LightGround", dpy, cmap);
1358   XQueryColor (dpy, cmap, &light);
1359
1360   rgb_to_hsv (dark.red, dark.green, dark.blue, &h1, &s1, &v1);
1361   rgb_to_hsv (light.red, light.green, light.blue, &h2, &s2, &v2);
1362   make_color_ramp (dpy, cmap, h1, s1, v1, h2, s2, v2,
1363                                   ground_colors, &ncolors, False, True, False);
1364   nr_ground_colors = ncolors;
1365
1366   flags = GCForeground;
1367   for (i=0; i < ncolors; i++) {
1368         gcv.foreground = ground_colors[i].pixel;
1369         ground_gcs[i] = XCreateGC (dpy, dbuf, flags, &gcv);
1370   }
1371
1372   speedmine_color_ramp (dpy, cmap, wall_gcs, wall_colors, &ncolors,
1373                                                 0.0, 0.6, 0.0, 0.9);
1374   nr_wall_colors = ncolors;
1375
1376   speedmine_color_ramp (dpy, cmap, bonus_gcs, bonus_colors, &ncolors,
1377                                                 0.6, 0.9, 0.4, 1.0);
1378   nr_bonus_colors = ncolors;
1379 }
1380
1381 /*
1382  * print_stats ()
1383  *
1384  * print out average FPS stats for the demo
1385  */
1386 static void
1387 print_stats (void)
1388 {
1389         if (total_nframes >= 1)
1390                 printf ("Rendered %d frames averaging %f FPS\n", total_nframes,
1391                                 total_nframes / get_time());
1392 }
1393
1394 /*
1395  * init_speedmine (dpy, window)
1396  *
1397  * initialise the demo
1398  */
1399 static void
1400 init_speedmine (Display *dpy, Window window)
1401 {
1402   XGCValues gcv;
1403   XWindowAttributes xgwa;
1404   int i;
1405   double th;
1406   int wide;
1407
1408   display = dpy;
1409
1410   XGetWindowAttributes (dpy, window, &xgwa);
1411   cmap = xgwa.colormap;
1412   width = xgwa.width;
1413   height = xgwa.height;
1414
1415   verbose_flag = get_boolean_resource ("verbose", "Boolean");
1416
1417   dbuf = XCreatePixmap (dpy, window, width, height, xgwa.depth);
1418   stars_mask = XCreatePixmap (dpy, window, width, height, 1);
1419
1420   gcv.foreground = default_fg_pixel =
1421     get_pixel_resource ("foreground", "Foreground", dpy, cmap);
1422   draw_gc = XCreateGC (dpy, window, GCForeground, &gcv);
1423   stars_gc = XCreateGC (dpy, stars_mask, GCForeground, &gcv);
1424
1425   gcv.foreground = get_pixel_resource ("background", "Background", dpy, cmap);
1426   erase_gc = XCreateGC (dpy, dbuf, GCForeground, &gcv);
1427   stars_erase_gc = XCreateGC (dpy, stars_mask, GCForeground, &gcv);
1428
1429   wire_flag = get_boolean_resource ("wire", "Boolean");
1430
1431   psychedelic_flag = get_boolean_resource ("psychedelic", "Boolean");
1432
1433   delay = get_integer_resource("delay", "Integer");
1434
1435   smoothness = get_integer_resource("smoothness", "Integer");
1436   if (smoothness < 1) smoothness = 1;
1437
1438   maxspeed = get_float_resource("maxspeed", "Float");
1439   maxspeed *= 0.01;
1440   maxspeed = fabs(maxspeed);
1441
1442   thrust = get_float_resource("thrust", "Float");
1443   thrust *= 0.2;
1444
1445   gravity = get_float_resource("gravity", "Float");
1446   gravity *= 0.002/9.8;
1447
1448   vertigo = get_float_resource("vertigo", "Float");
1449   vertigo *= 0.2;
1450
1451   curviness = get_float_resource("curviness", "Float");
1452   curviness *= 0.25;
1453
1454   twistiness = get_float_resource("twistiness", "Float");
1455   twistiness *= 0.125;
1456
1457   terrain_flag = get_boolean_resource ("terrain", "Boolean");
1458   widening_flag = get_boolean_resource ("widening", "Boolean");
1459   bumps_flag = get_boolean_resource ("bumps", "Boolean");
1460   bonuses_flag = get_boolean_resource ("bonuses", "Boolean");
1461   crosshair_flag = get_boolean_resource ("crosshair", "Boolean");
1462
1463   be_wormy = get_boolean_resource ("worm", "Boolean");
1464   if (be_wormy) {
1465       maxspeed   *= 1.43;
1466       thrust     *= 10;
1467       gravity    *= 3;
1468       vertigo    *= 0.5;
1469       smoothness *= 2;
1470       curviness  *= 2;
1471       twistiness *= 2;
1472       psychedelic_flag = True;
1473       crosshair_flag = False;
1474   }
1475
1476   if (psychedelic_flag) init_psychedelic_colors (dpy, window, cmap);
1477   else init_colors (dpy, window, cmap);
1478
1479   for (i=0; i<ROTS; i++) {
1480         th = M_PI * 2.0 * i / ROTS;
1481         costab[i] = cos(th);
1482         sintab[i] = sin(th);
1483   }
1484
1485   wide = random_wideness();
1486
1487   for (i=0; i < TERRAIN_LENGTH; i++) {
1488         wideness[i] = wide;
1489         bonuses[i] = 0;
1490   }
1491
1492   init_terrain ();
1493   init_curves ();
1494   wrap_tunnel (0, TERRAIN_LENGTH-1);
1495
1496   if (DEBUG_FLAG || verbose_flag) atexit(print_stats);
1497
1498   step = effective_speed;
1499
1500 #ifdef HAVE_GETTIMEOFDAY
1501   init_time ();
1502 #endif
1503
1504 }
1505
1506 \f
1507 /*
1508  * Down the speedmine, you'll find speed
1509  * to satisfy your moving needs;
1510  * So if you're looking for a blast
1511  * then hit the speedmine, really fast.
1512  */
1513
1514 /*
1515  * Speedworm likes to choke and spit
1516  * and chase his tail, and dance a bit
1517  * he really is a funky friend;
1518  * he's made of speed from end to end.
1519  */
1520
1521 char *progclass = "Speedmine";
1522
1523 char *defaults [] = {
1524   ".verbose: False",
1525   "*worm: False",
1526   "*wire: False",
1527   ".background: black",
1528   ".foreground: white",
1529   "*darkground: #101010",
1530   "*lightground: #a0a0a0",
1531   "*tunnelend: #000000",
1532   "*delay:      30000",
1533   "*maxspeed: 700",
1534   "*thrust: 1.0",
1535   "*gravity: 9.8",
1536   "*vertigo: 1.0",
1537   "*terrain: True",
1538   "*smoothness: 6",
1539   "*curviness: 1.0",
1540   "*twistiness: 1.0",
1541   "*widening: True",
1542   "*bumps: True",
1543   "*bonuses: True",
1544   "*crosshair: True",
1545   "*psychedelic: False",
1546   0
1547 };
1548
1549 XrmOptionDescRec options [] = {
1550   { "-verbose",                 ".verbose",                             XrmoptionNoArg, "True"},
1551   { "-worm",                    ".worm",                                XrmoptionNoArg, "True"},
1552   { "-wire",                    ".wire",                                XrmoptionNoArg, "True"},
1553   { "-nowire",                  ".wire",                                XrmoptionNoArg, "False"},
1554   { "-darkground",              ".darkground",                  XrmoptionSepArg, 0 },
1555   { "-lightground",             ".lightground",                 XrmoptionSepArg, 0 },
1556   { "-tunnelend",               ".tunnelend",                   XrmoptionSepArg, 0 },
1557   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
1558   { "-maxspeed",                ".maxspeed",                    XrmoptionSepArg, 0 },
1559   { "-thrust",                  ".thrust",                              XrmoptionSepArg, 0 },
1560   { "-gravity",                 ".gravity",                             XrmoptionSepArg, 0 },
1561   { "-vertigo",                 ".vertigo",                             XrmoptionSepArg, 0 },
1562   { "-terrain",                 ".terrain",                             XrmoptionNoArg, "True"},
1563   { "-noterrain",               ".terrain",                             XrmoptionNoArg, "False"},
1564   { "-smoothness",      ".smoothness",                  XrmoptionSepArg, 0 },
1565   { "-curviness",               ".curviness",                   XrmoptionSepArg, 0 },
1566   { "-twistiness",              ".twistiness",                  XrmoptionSepArg, 0 },
1567   { "-widening",                ".widening",                    XrmoptionNoArg, "True"},
1568   { "-nowidening",              ".widening",                    XrmoptionNoArg, "False"},
1569   { "-bumps",                   ".bumps",                               XrmoptionNoArg, "True"},
1570   { "-nobumps",                 ".bumps",                               XrmoptionNoArg, "False"},
1571   { "-bonuses",                 ".bonuses",                             XrmoptionNoArg, "True"},
1572   { "-nobonuses",               ".bonuses",                             XrmoptionNoArg, "False"},
1573   { "-crosshair",               ".crosshair",                   XrmoptionNoArg, "True"},
1574   { "-nocrosshair",             ".crosshair",                   XrmoptionNoArg, "False"},
1575   { "-psychedelic",             ".psychedelic",                 XrmoptionNoArg, "True"},
1576   { "-nopsychedelic",   ".psychedelic",                 XrmoptionNoArg, "False"},
1577   { 0, 0, 0, 0 }
1578 };
1579
1580
1581 void
1582 screenhack (Display *dpy, Window window)
1583 {
1584 #ifndef NDEBUG
1585         atexit (abort);
1586 #endif
1587
1588         init_speedmine (dpy, window);
1589
1590         while (1) {
1591                 speedmine (dpy, window);
1592                 XSync (dpy, False);
1593                 screenhack_handle_events (dpy);
1594                 if (delay) usleep(delay);
1595         }
1596 }
1597
1598 /* vim: ts=4
1599  */