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