1 /* -*- Mode: C; c-basic-offset: 4; tab-width: 4 -*-
2 * speedmine, Copyright (C) 2001 Conrad Parker <conrad@deephackmode.org>
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
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.
18 * Happy Birthday to WierdArms (17 April) and Pat (18 April)
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.
30 * Throughout this code the following temporary variable names are used:
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
37 * Thus, the buffers are used with these iterators:
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
45 * xvals[t][j], yvals[t][j] screen coordinates
46 * {min,max}{x,y}[t] bounding boxes of screen coords
49 /* Define or undefine NDEBUG to turn assert and abort debugging off or on */
55 #include "screenhack.h"
58 #define MIN(a,b) ((a)<(b)?(a):(b))
59 #define MAX(a,b) ((a)>(b)?(a):(b))
61 #define RAND(r) (int)(((r)>0)?(random() % (long)(r)): -(random() % (long)(-r)))
63 #define SIGN3(a) ((a)>0?1:((a)<0?-1:0))
65 #define MODULO(a,b) while ((a)<0) (a)+=(b); (a) %= (b);
67 static Display * display;
68 static Pixmap dbuf, stars_mask;
70 static unsigned int default_fg_pixel;
71 static GC draw_gc, erase_gc, tunnelend_gc, stars_gc, stars_erase_gc;
73 /* No. of shades of each color (ground, walls, bonuses) */
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];
82 static int width, height;
85 static int smoothness;
86 static int verbose_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;
101 static double maxspeed;
103 static double thrust, gravity;
105 static double vertigo;
106 static double curviness;
107 static double twistiness;
109 static double pos=0.0;
110 static double speed=-1.1;
111 static double accel=0.00000001;
112 static double step=0.0;
117 static int direction = FORWARDS;
119 /* Apparently AIX's math.h bogusly defines `nearest' as a function,
120 in violation of the ANSI C spec. */
122 #define nearest n3arest
124 static int pindex=0, nearest=0;
125 static int flipped_at=0;
126 static int xoffset=0, yoffset=0;
128 static int bonus_bright = 0;
129 static int wire_bonus=0;
130 #define wireframe (wire_flag||wire_bonus>8||wire_bonus%2==1)
132 static double speed_bonus=0.0;
133 #define effective_speed (direction*(speed+speed_bonus))
135 static int spin_bonus = 0;
136 static int backwards_bonus = 0;
138 /* No. of levels of interpolation, for perspective */
141 /* These must be powers of 2 */
142 #define TERRAIN_LENGTH 256
143 #define TERRAIN_BREADTH 32
145 /* total "perspective distance" of terrain */
146 #define TERRAIN_PDIST (INTERP*TERRAIN_LENGTH)
149 #define TB_MUL (ROTS/TERRAIN_BREADTH)
150 static double sintab[ROTS], costab[ROTS];
152 static int orientation = (17*ROTS)/22;
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];
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)
172 #define STEEL_ELEVATION 300
174 /* a forward declaration ... */
175 static void change_colors(void);
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;
187 * returns the total time elapsed since the beginning of the demo
189 static double get_time(void) {
192 #if GETTIMEOFDAY_TWO_ARGS
193 gettimeofday(&t, NULL);
197 t.tv_sec -= start_time.tv_sec;
198 f = ((double)t.tv_sec) + t.tv_usec*1e-6;
205 * initialises the timing structures
207 static void init_time(void) {
208 #if GETTIMEOFDAY_TWO_ARGS
209 gettimeofday(&start_time, NULL);
211 gettimeofday(&start_time);
213 fps_start = get_time();
220 * perspective map the world coordinates worldx[i][j], worldy[i][j] onto
221 * screen coordinates xvals[t][j], yvals[t][j]
226 static int rotation_offset=0;
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;
235 zf = 8.0*28.0 / (double)(width*TERRAIN_LENGTH);
236 if (be_wormy) zf *= 3.0;
238 depth = TERRAIN_PDIST - INTERP + pindex;
240 view_pos = (nearest+3*TERRAIN_LENGTH/4)%TERRAIN_LENGTH;
242 xoffset += - xcurvature[view_pos]*curviness/8;
245 yoffset += - ycurvature[view_pos]*curviness/4;
248 rotation_offset += (int)((zcurvature[view_pos]-zcurvature[nearest])*ROTS/8);
249 rotation_offset /= 2;
250 rotation_bias = orientation + spin_bonus - rotation_offset;
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;
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;
264 MODULO(rotation_bias, ROTS);
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;
274 for (t=0; t < TERRAIN_LENGTH; t++) {
275 i = nearest - 1 - t; MODULO(i, TERRAIN_LENGTH);
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 */
282 xx = (worldx[i][jj]-(vertigo*xcc))/zfactor;
283 yy = (worldy[i][j]-(vertigo*ycc))/zfactor;
288 r = rotation_bias + (int)(vertigo*zcc); MODULO(r, ROTS);
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]);
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]);
300 xcc -= xc; ycc -= yc; zcc -= zc;
301 xc -= xcurvature[i]; yc -= ycurvature[i]; zc -= zcurvature[i];
307 * wrap_tunnel (start, end)
309 * wrap the terrain terrain[i][j] around the semi-circular tunnel function
311 * x' = x/2 * cos(theta) - (y-k) * x * sin(theta)
312 * y' = x/4 * sin(theta) + y * cos(theta)
314 * between i=start and i=end inclusive, producing world coordinates
315 * worldx[i][j], worldy[i][j]
318 wrap_tunnel (int start, int end)
323 assert (start < end);
325 for (i=start; i <= end; i++) {
326 for (j=0; j < TERRAIN_BREADTH; j++) {
327 x = j * (1.0/TERRAIN_BREADTH);
329 y = (double)(v==STEEL_ELEVATION?200:v) - wideness[i] - 1200;
332 if (j > TERRAIN_BREADTH/8 && j < 3*TERRAIN_BREADTH/8) y -= 300;
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];
345 * perform the state transitions and terrain transformation for the
346 * "look backwards/look forwards" bonus
349 flip_direction (void)
353 direction = -direction;
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++) {
362 terrain[ip][j] = terrain[in][j];
369 * generate_smooth (start, end)
371 * generate smooth terrain between i=start and i=end inclusive
374 generate_smooth (int start, int end)
378 assert (start < end);
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;
389 * generate_straight (start, end)
391 * zero the curvature and wideness between i=start and i=end inclusive
394 generate_straight (int start, int end)
398 assert (start < end);
400 for (i=start; i <= end; i++) {
401 ii = i; MODULO(ii, TERRAIN_LENGTH);
402 for (j=0; j < TERRAIN_BREADTH; j++) {
412 * int generate_terrain_value (v1, v2, v3, v4, w)
414 * generate terrain value near the average of v1, v2, v3, v4, with
415 * perturbation proportional to w
418 generate_terrain_value (int v1, int v2, int v3, int v4, int w)
423 if (!terrain_flag) return 0;
425 sum = v1 + v2 + v3 + v4;
427 rval = w*sum/smoothness;
428 if (rval == 0) rval = 2;
430 ret = (sum/4 -(rval/2) + RAND(rval));
432 if (ret < -400 || ret > 400) {
440 * generate_terrain (start, end, final)
442 * generate terrain[i][j] between i=start and i=end inclusive
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
452 generate_terrain (int start, int end, int final)
455 int ip, jp, in, jn; /* prev, next values */
458 assert (start < end);
459 assert (start >= 0 && start < TERRAIN_LENGTH);
460 assert (end >= 0 && end < TERRAIN_LENGTH);
462 diff = end - start + 1;
464 terrain[end][0] = random_elevation();
465 terrain[end][TERRAIN_BREADTH/2] = random_elevation();
467 for (w= diff/2, l=TERRAIN_BREADTH/4;
468 w >= final || l >= final; w /= 2, l /= 2) {
470 if (w<1) w=1; if (l<1) l=1;
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);
479 generate_terrain_value (terrain[ip][jp], terrain[in][jp],
480 terrain[ip][jn], terrain[in][jn], w);
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);
491 generate_terrain_value (terrain[ip][j], terrain[in][j],
492 terrain[i][jp], terrain[i][jn], w);
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);
503 generate_terrain_value (terrain[ip][j], terrain[in][j],
504 terrain[i][jp], terrain[i][jn], w);
511 * double generate_curvature_value (v1, v2, w)
513 * generate curvature value near the average of v1 and v2, with perturbation
517 generate_curvature_value (double v1, double v2, int w)
519 double sum, avg, diff, ret;
522 assert (!isnan(v1) && !isnan(v2));
527 diff = MIN(v1 - avg, v2 - avg);
529 rval = (int)diff * w;
530 if (rval == 0.0) return avg;
532 ret = (avg -((double)rval)/500.0 + ((double)RAND(rval))/1000.0);
534 assert (!isnan(ret));
540 * generate_curves (start, end)
542 * generate xcurvature[i], ycurvature[i], zcurvature[i] and wideness[i]
543 * between start and end inclusive
546 generate_curves (int start, int end)
548 int i, diff, ii, in, ip, w;
550 assert (start < end);
552 diff = end - start + 1; MODULO (diff, TERRAIN_LENGTH);
554 if (random() % 100 == 0)
555 xcurvature[end] = 30 * random_curvature();
556 else if (random() % 10 == 0)
557 xcurvature[end] = 20 * random_curvature();
559 xcurvature[end] = 10 * random_curvature();
561 if (random() % 50 == 0)
562 ycurvature[end] = 20 * random_curvature();
563 else if (random() % 25 == 0)
564 ycurvature[end] = 30 * random_curvature();
566 ycurvature[end] = 10 * random_curvature();
568 if (random() % 3 == 0)
569 zcurvature[end] = random_twist();
572 generate_curvature_value (zcurvature[end], random_twist(), 1);
575 wideness[end] = random_wideness();
578 generate_curvature_value (wideness[end], random_wideness(), 1);
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);
586 generate_curvature_value (xcurvature[ip], xcurvature[in], w);
588 generate_curvature_value (ycurvature[ip], ycurvature[in], w);
590 generate_curvature_value (zcurvature[ip], zcurvature[in], w);
592 generate_curvature_value (wideness[ip], wideness[in], w);
600 * choose a random bonus and perform its state transition
605 static int jamming=0;
611 nearest -= 2; MODULO(nearest, TERRAIN_LENGTH);
615 if (psychedelic_flag) change_colors();
617 switch (random() % 7) {
618 case 0: /* switch to or from wireframe */
619 wire_bonus = (wire_bonus?0:300);
621 case 1: /* speedup */
630 case 4: /* look backwards / look forwards */
631 flipped_at = nearest;
633 backwards_bonus = (backwards_bonus?0:10);
638 case 6: /* jam against the bonus a few times; deja vu! */
639 nearest -= 2; MODULO(nearest, TERRAIN_LENGTH);
651 * check if a bonus has been passed in the last frame, and handle it
656 int i, ii, start, end;
658 if (!bonuses_flag) return;
661 start = nearest; end = nearest + (int)floor(step);
663 end = nearest; start = nearest + (int)floor(step);
667 start += TERRAIN_LENGTH/4;
668 end += TERRAIN_LENGTH/4;
671 for (i=start; i < end; i++) {
672 ii = i; MODULO(ii, TERRAIN_LENGTH);
673 if (bonuses[ii] == 1) do_bonus ();
678 * decrement_bonuses (double time_per_frame)
680 * decrement timers associated with bonuses
683 decrement_bonuses (double time_per_frame)
685 if (!bonuses_flag) return;
687 if (bonus_bright > 0) bonus_bright-=4;
688 if (wire_bonus > 0) wire_bonus--;
689 if (speed_bonus > 0) speed_bonus -= 2.0;
691 if (spin_bonus > 10) spin_bonus -= (int)(step*13.7);
692 else if (spin_bonus < -10) spin_bonus += (int)(step*11.3);
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);
704 * set_bonuses (start, end)
706 * choose if to and where to set a bonus between i=start and i=end inclusive
709 set_bonuses (int start, int end)
713 if (!bonuses_flag) return;
715 assert (start < end);
719 for (i=start; i <= end; i++) {
720 ii = i; if (ii>=TERRAIN_LENGTH) ii -= TERRAIN_LENGTH;
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 */
733 * regenerate_terrain ()
735 * regenerate a portion of the terrain map of length TERRAIN_LENGTH/4 iff
736 * we just passed between two quarters of the terrain.
738 * choose the kind of terrain to produce, produce it and wrap the tunnel
741 regenerate_terrain (void)
746 passed = nearest % (TERRAIN_LENGTH/4);
749 (speed > 0.0 && passed > (int)step) ||
750 (speed < 0.0 && (TERRAIN_LENGTH/4)-passed > (int)fabs(step))) {
755 end = nearest - passed - 1; MODULO(end, TERRAIN_LENGTH);
756 start = end - TERRAIN_LENGTH/4 + 1; MODULO(start, TERRAIN_LENGTH);
758 if (DEBUG_FLAG) printf ("Regenerating [%d - %d]\n", start, end);
760 set_bonuses (start, end);
762 switch (random() % 64) {
765 generate_terrain (start, end, 1);
766 generate_smooth (start,
767 start + TERRAIN_LENGTH/8 + (random() % TERRAIN_LENGTH/8));
770 generate_smooth (start, end);
771 generate_terrain (start, end, 4); break;
773 generate_smooth (start, end);
774 generate_terrain (start, end, 2); break;
776 generate_terrain (start, end, 1);
779 if (random() % 16 == 0) {
780 generate_straight (start, end);
782 generate_curves (start, end);
785 wrap_tunnel (start, end);
791 * initialise the terrain map for the beginning of the demo
798 for (i=0; i < TERRAIN_LENGTH; i++) {
799 for (j=0; j < TERRAIN_BREADTH; j++) {
804 terrain[TERRAIN_LENGTH-1][0] = - (random() % 300);
805 terrain[TERRAIN_LENGTH-1][TERRAIN_BREADTH/2] = - (random() % 300);
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);
817 * initialise the curvatures and wideness for the beginning of the demo.
824 for (i=0; i < TERRAIN_LENGTH-1; i++) {
830 xcurvature[TERRAIN_LENGTH-1] = random_curvature();
831 ycurvature[TERRAIN_LENGTH-1] = random_curvature();
832 zcurvature[TERRAIN_LENGTH-1] = random_twist();
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);
842 * render_quads (dpy, d, t, dt, i)
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.
848 render_quads (Display * dpy, Drawable d, int t, int dt, int i)
855 assert (i == (nearest - (t + dt) + TERRAIN_LENGTH) % TERRAIN_LENGTH);
857 in = i + 1; MODULO(in, TERRAIN_LENGTH);
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];
867 index = bonus_bright + ncolors/3 +
868 t*(t*INTERP + pindex) * ncolors /
869 (3*TERRAIN_LENGTH*TERRAIN_PDIST);
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);
875 if (be_wormy && psychedelic_flag) index += ncolors/4;
877 index = MIN (index, ncolors-1);
878 index = MAX (index, 0);
881 XSetClipMask (dpy, bonus_gcs[index], None);
885 if (bonuses[i]) gc = bonus_gcs[index];
886 else gc = ground_gcs[index];
887 XDrawLines (dpy, d, gc, points, 4, CoordModeOrigin);
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];
899 gc = wall_gcs[index];
901 XFillPolygon (dpy, d, gc, points, 4, Nonconvex, CoordModeOrigin);
907 * render_pentagons (dpy, d, t, dt, i)
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.
913 render_pentagons (Display *dpy, Drawable d, int t, int dt, int i)
915 int j, t2, j2, j3, in;
920 assert (i == (nearest -t + TERRAIN_LENGTH) % TERRAIN_LENGTH);
922 in = i + 1; MODULO(in, TERRAIN_LENGTH);
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];
934 index = bonus_bright + ncolors/3 +
935 t*(t*INTERP + pindex) * ncolors /
936 (3*TERRAIN_LENGTH*TERRAIN_PDIST);
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);
942 if (be_wormy && psychedelic_flag) index += ncolors/4;
944 index = MIN (index, ncolors-1);
945 index = MAX (index, 0);
948 XSetClipMask (dpy, bonus_gcs[index], None);
952 if (bonuses[i]) gc = bonus_gcs[index];
953 else gc = ground_gcs[index];
954 XDrawLines (dpy, d, gc, points, 5, CoordModeOrigin);
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];
964 gc = wall_gcs[index];
966 XFillPolygon (dpy, d, gc, points, 5, Complex, CoordModeOrigin);
972 * render_block (dpy, d, gc, t)
974 * render a filled polygon at perspective depth t using the given GC
977 render_block (Display * dpy, Drawable d, GC gc, int t)
981 XPoint erase_points[TERRAIN_BREADTH/2];
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];
988 XFillPolygon (dpy, d, gc, erase_points,
989 TERRAIN_BREADTH/2, Complex, CoordModeOrigin);
993 * regenerate_stars_mask (dpy, t)
995 * regenerate the clip mask 'stars_mask' for drawing the bonus stars at
996 * random positions within the bounding box at depth t
999 regenerate_stars_mask (Display * dpy, int t)
1001 int i, w, h, a, b, l1, l2;
1002 const int lim = width*TERRAIN_LENGTH/(300*(TERRAIN_LENGTH-t));
1004 w = maxx[t] - minx[t];
1005 h = maxy[t] - miny[t];
1007 if (w<6||h<6) return;
1009 XFillRectangle (dpy, stars_mask, stars_erase_gc,
1010 0, 0, width, height);
1012 l1 = (t>3*TERRAIN_LENGTH/4?2:1);
1013 l2 = (t>7*TERRAIN_LENGTH/8?2:1);
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);
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);
1032 * render_bonus_block (dpy, d, t, i)
1034 * draw the bonus stars at depth t.
1035 * i is passed as a hint, where i corresponds to t as asserted.
1038 render_bonus_block (Display * dpy, Drawable d, int t, int i)
1042 assert (i == (nearest -t + TERRAIN_LENGTH) % TERRAIN_LENGTH);
1044 if (!bonuses[i] || wireframe) return;
1046 regenerate_stars_mask (dpy, t);
1048 bt = t * nr_bonus_colors / (2*TERRAIN_LENGTH);
1050 XSetClipMask (dpy, bonus_gcs[bt], stars_mask);
1052 render_block (dpy, d, bonus_gcs[bt], t);
1059 int max_minx=0, min_maxx=width, max_miny=0, min_maxy=height;
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]);
1067 if (max_miny >= min_maxy || max_minx >= min_maxx) break;
1074 * render_speedmine (dpy, d)
1076 * render the current frame.
1079 render_speedmine (Display * dpy, Drawable d)
1081 int t, i=nearest, dt=1;
1084 assert (nearest >= 0 && nearest < TERRAIN_LENGTH);
1086 if (be_wormy || wireframe) {
1087 XFillRectangle (dpy, d, erase_gc, 0, 0, width, height);
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);
1096 assert (t == TERRAIN_LENGTH/4);
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);
1107 if (t == TERRAIN_LENGTH/4)
1108 render_pentagons (dpy, d, t, dt, i);
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);
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);
1123 if (wireframe) {assert (t == 3*TERRAIN_LENGTH/4);}
1125 if (t == 3*TERRAIN_LENGTH/4)
1126 render_pentagons (dpy, d, t, dt, i);
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);
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);
1149 * move to the position for the next frame, and modify the state variables
1150 * nearest, pindex, pos, speed
1158 dpos = SIGN3(pos) * floor(fabs(pos));
1160 pindex += SIGN3(effective_speed) + INTERP;
1161 while (pindex >= INTERP) {
1165 while (pindex < 0) {
1170 nearest += dpos; MODULO(nearest, TERRAIN_LENGTH);
1174 accel = thrust + ycurvature[nearest] * gravity;
1176 if (speed > maxspeed) speed = maxspeed;
1177 if (speed < -maxspeed) speed = -maxspeed;
1181 * speedmine (dpy, window)
1183 * do everything required for one frame of the demo
1186 speedmine (Display *dpy, Window window)
1188 double elapsed, time_per_frame = 0.04;
1190 regenerate_terrain ();
1194 render_speedmine (dpy, dbuf);
1195 XCopyArea (dpy, dbuf, window, draw_gc, 0, 0, width, height, 0, 0);
1197 #if HAVE_GETTIMEOFDAY
1198 fps_end = get_time();
1202 if (fps_end > fps_start + 0.5) {
1203 elapsed = fps_end - fps_start;
1204 fps_start = get_time();
1206 time_per_frame = elapsed / nframes - delay*1e-6;
1207 fps = nframes / elapsed;
1209 printf ("%f s elapsed\t%3f s/frame\t%.1f FPS\n", elapsed,
1210 time_per_frame, fps);
1212 step = effective_speed * elapsed;
1217 time_per_frame = 0.04;
1218 step = effective_speed;
1223 decrement_bonuses (time_per_frame);
1229 * speedmine_color_ramp (dpy, cmap, gcs, colors, ncolors, s1, s2, v1, v2)
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'.
1235 * The number of colors actually allocated is returned in ncolors.
1238 speedmine_color_ramp (Display * dpy, Colormap cmap, GC *gcs, XColor * colors,
1239 int *ncolors, double s1, double s2, double v1, double v2)
1243 unsigned long flags;
1246 assert (*ncolors >= 0);
1247 assert (s1 >= 0.0 && s1 <= 1.0 && v1 >= 0.0 && v2 <= 1.0);
1249 if (psychedelic_flag) {
1250 h1 = RAND(360); h2 = (h1 + 180) % 360;
1252 h1 = h2 = RAND(360);
1255 make_color_ramp (dpy, cmap, h1, s1, v1, h2, s2, v2,
1256 colors, ncolors, False, True, False);
1258 flags = GCForeground;
1259 for (i=0; i < *ncolors; i++) {
1260 gcv.foreground = colors[i].pixel;
1261 gcs[i] = XCreateGC (dpy, dbuf, flags, &gcv);
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.
1274 change_colors (void)
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;
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;
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;
1297 speedmine_color_ramp (display, cmap, wall_gcs, wall_colors, &ncolors,
1299 nr_wall_colors = ncolors;
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;
1307 * init_psychedelic_colors (dpy, window, cmap)
1309 * initialise a psychedelic colormap
1312 init_psychedelic_colors (Display * dpy, Window window, Colormap cmap)
1316 gcv.foreground = get_pixel_resource ("tunnelend", "TunnelEnd", dpy, cmap);
1317 tunnelend_gc = XCreateGC (dpy, window, GCForeground, &gcv);
1319 ncolors = MAX_COLORS;
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;
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;
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;
1335 * init_colors (dpy, window, cmap)
1337 * initialise a normal colormap
1340 init_colors (Display * dpy, Window window, Colormap cmap)
1345 double s1, s2, v1, v2;
1346 unsigned long flags;
1349 gcv.foreground = get_pixel_resource ("tunnelend", "TunnelEnd", dpy, cmap);
1350 tunnelend_gc = XCreateGC (dpy, window, GCForeground, &gcv);
1352 ncolors = MAX_COLORS;
1354 dark.pixel = get_pixel_resource ("darkground", "DarkGround", dpy, cmap);
1355 XQueryColor (dpy, cmap, &dark);
1357 light.pixel = get_pixel_resource ("lightground", "LightGround", dpy, cmap);
1358 XQueryColor (dpy, cmap, &light);
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;
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);
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;
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;
1384 * print out average FPS stats for the demo
1389 if (total_nframes >= 1)
1390 printf ("Rendered %d frames averaging %f FPS\n", total_nframes,
1391 total_nframes / get_time());
1395 * init_speedmine (dpy, window)
1397 * initialise the demo
1400 init_speedmine (Display *dpy, Window window)
1403 XWindowAttributes xgwa;
1410 XGetWindowAttributes (dpy, window, &xgwa);
1411 cmap = xgwa.colormap;
1413 height = xgwa.height;
1415 verbose_flag = get_boolean_resource ("verbose", "Boolean");
1417 dbuf = XCreatePixmap (dpy, window, width, height, xgwa.depth);
1418 stars_mask = XCreatePixmap (dpy, window, width, height, 1);
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);
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);
1429 wire_flag = get_boolean_resource ("wire", "Boolean");
1431 psychedelic_flag = get_boolean_resource ("psychedelic", "Boolean");
1433 delay = get_integer_resource("delay", "Integer");
1435 smoothness = get_integer_resource("smoothness", "Integer");
1436 if (smoothness < 1) smoothness = 1;
1438 maxspeed = get_float_resource("maxspeed", "Float");
1440 maxspeed = fabs(maxspeed);
1442 thrust = get_float_resource("thrust", "Float");
1445 gravity = get_float_resource("gravity", "Float");
1446 gravity *= 0.002/9.8;
1448 vertigo = get_float_resource("vertigo", "Float");
1451 curviness = get_float_resource("curviness", "Float");
1454 twistiness = get_float_resource("twistiness", "Float");
1455 twistiness *= 0.125;
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");
1463 be_wormy = get_boolean_resource ("worm", "Boolean");
1472 psychedelic_flag = True;
1473 crosshair_flag = False;
1476 if (psychedelic_flag) init_psychedelic_colors (dpy, window, cmap);
1477 else init_colors (dpy, window, cmap);
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);
1485 wide = random_wideness();
1487 for (i=0; i < TERRAIN_LENGTH; i++) {
1494 wrap_tunnel (0, TERRAIN_LENGTH-1);
1496 if (DEBUG_FLAG || verbose_flag) atexit(print_stats);
1498 step = effective_speed;
1500 #ifdef HAVE_GETTIMEOFDAY
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.
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.
1521 char *progclass = "Speedmine";
1523 char *defaults [] = {
1527 ".background: black",
1528 ".foreground: white",
1529 "*darkground: #101010",
1530 "*lightground: #a0a0a0",
1531 "*tunnelend: #000000",
1545 "*psychedelic: False",
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"},
1582 screenhack (Display *dpy, Window window)
1588 init_speedmine (dpy, window);
1591 speedmine (dpy, window);
1593 screenhack_handle_events (dpy);
1594 if (delay) usleep(delay);