Rename game_comp_grav to game_tilt_grav
[neverball] / ball / game_client.c
1 /*
2  * Copyright (C) 2003 Robert Kooima
3  *
4  * NEVERBALL is  free software; you can redistribute  it and/or modify
5  * it under the  terms of the GNU General  Public License as published
6  * by the Free  Software Foundation; either version 2  of the License,
7  * or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT  ANY  WARRANTY;  without   even  the  implied  warranty  of
11  * MERCHANTABILITY or  FITNESS FOR A PARTICULAR PURPOSE.   See the GNU
12  * General Public License for more details.
13  */
14
15 #include <SDL.h>
16 #include <math.h>
17 #include <assert.h>
18
19 #include "glext.h"
20 #include "vec3.h"
21 #include "geom.h"
22 #include "item.h"
23 #include "back.h"
24 #include "part.h"
25 #include "ball.h"
26 #include "image.h"
27 #include "audio.h"
28 #include "solid_gl.h"
29 #include "config.h"
30 #include "video.h"
31
32 #include "game_client.h"
33 #include "game_common.h"
34 #include "game_proxy.h"
35
36 #include "cmd.h"
37
38 /*---------------------------------------------------------------------------*/
39
40 int game_compat_map;                    /* Client/server map compat flag     */
41
42 /*---------------------------------------------------------------------------*/
43
44 static int client_state = 0;
45
46 static struct s_file file;
47 static struct s_file back;
48
49 static int   reflective;                /* Reflective geometry used?         */
50
51 static float timer      = 0.f;          /* Clock time                        */
52
53 static int status = GAME_NONE;          /* Outcome of the game               */
54
55 static struct game_tilt tilt;           /* Floor rotation                    */
56
57 static float view_a;                    /* Ideal view rotation about Y axis  */
58 static float view_fov;                  /* Field of view                     */
59
60 static float view_c[3];                 /* Current view center               */
61 static float view_p[3];                 /* Current view position             */
62 static float view_e[3][3];              /* Current view reference frame      */
63
64 static int   coins  = 0;                /* Collected coins                   */
65 static int   goal_e = 0;                /* Goal enabled flag                 */
66 static float goal_k = 0;                /* Goal animation                    */
67
68 static int   jump_e = 1;                /* Jumping enabled flag              */
69 static int   jump_b = 0;                /* Jump-in-progress flag             */
70 static float jump_dt;                   /* Jump duration                     */
71
72 static float fade_k = 0.0;              /* Fade in/out level                 */
73 static float fade_d = 0.0;              /* Fade in/out direction             */
74
75 static int ups;                         /* Updates per second                */
76 static int first_update;                /* First update flag                 */
77 static int curr_ball;                   /* Current ball index                */
78
79 struct
80 {
81     int x, y;
82 } version;                              /* Current map version               */
83
84 /*---------------------------------------------------------------------------*/
85
86 static void game_run_cmd(const union cmd *cmd)
87 {
88     static const float gup[] = { 0.0f, +9.8f, 0.0f };
89     static const float gdn[] = { 0.0f, -9.8f, 0.0f };
90
91     /*
92      * Neverball <= 1.5.1 does not send explicit tilt axes, rotation
93      * happens directly around view vectors.  So for compatibility if
94      * at the time of receiving tilt angles we have not yet received
95      * the tilt axes, we use the view vectors.
96      */
97     static int got_tilt_axes;
98
99     float f[3];
100
101     if (client_state)
102     {
103         struct s_item *hp;
104         struct s_ball *up;
105
106         float dt;
107         int i;
108
109         switch (cmd->type)
110         {
111         case CMD_END_OF_UPDATE:
112
113             got_tilt_axes = 0;
114
115             if (first_update)
116             {
117                 first_update = 0;
118                 break;
119             }
120
121             /* Compute gravity for particle effects. */
122
123             if (status == GAME_GOAL)
124                 game_tilt_grav(f, gup, &tilt);
125             else
126                 game_tilt_grav(f, gdn, &tilt);
127
128             /* Step particle, goal and jump effects. */
129
130             if (ups > 0)
131             {
132                 dt = 1.0f / (float) ups;
133
134                 if (goal_e && goal_k < 1.0f)
135                     goal_k += dt;
136
137                 if (jump_b)
138                 {
139                     jump_dt += dt;
140
141                     if (1.0f < jump_dt)
142                         jump_b = 0;
143                 }
144
145                 part_step(f, dt);
146             }
147
148             break;
149
150         case CMD_MAKE_BALL:
151             /* Allocate a new ball and mark it as the current ball. */
152
153             if ((up = realloc(file.uv, sizeof (*up) * (file.uc + 1))))
154             {
155                 file.uv = up;
156                 curr_ball = file.uc;
157                 file.uc++;
158             }
159             break;
160
161         case CMD_MAKE_ITEM:
162             /* Allocate and initialise a new item. */
163
164             if ((hp = realloc(file.hv, sizeof (*hp) * (file.hc + 1))))
165             {
166                 struct s_item h;
167
168                 v_cpy(h.p, cmd->mkitem.p);
169
170                 h.t = cmd->mkitem.t;
171                 h.n = cmd->mkitem.n;
172
173                 file.hv          = hp;
174                 file.hv[file.hc] = h;
175                 file.hc++;
176             }
177
178             break;
179
180         case CMD_PICK_ITEM:
181             /* Set up particle effects and discard the item. */
182
183             assert(cmd->pkitem.hi < file.hc);
184
185             hp = &file.hv[cmd->pkitem.hi];
186
187             item_color(hp, f);
188             part_burst(hp->p, f);
189
190             hp->t = ITEM_NONE;
191
192             break;
193
194         case CMD_TILT_ANGLES:
195             if (!got_tilt_axes)
196                 game_tilt_axes(&tilt, view_e);
197
198             tilt.rx = cmd->tiltangles.x;
199             tilt.rz = cmd->tiltangles.z;
200             break;
201
202         case CMD_SOUND:
203             /* Play the sound, then free its file name. */
204
205             if (cmd->sound.n)
206             {
207                 audio_play(cmd->sound.n, cmd->sound.a);
208
209                 /*
210                  * FIXME Command memory management should be done
211                  * elsewhere and done properly.
212                  */
213
214                 free(cmd->sound.n);
215             }
216             break;
217
218         case CMD_TIMER:
219             timer = cmd->timer.t;
220             break;
221
222         case CMD_STATUS:
223             status = cmd->status.t;
224             break;
225
226         case CMD_COINS:
227             coins = cmd->coins.n;
228             break;
229
230         case CMD_JUMP_ENTER:
231             jump_b  = 1;
232             jump_e  = 0;
233             jump_dt = 0.0f;
234             break;
235
236         case CMD_JUMP_EXIT:
237             jump_e = 1;
238             break;
239
240         case CMD_BODY_PATH:
241             file.bv[cmd->bodypath.bi].pi = cmd->bodypath.pi;
242             break;
243
244         case CMD_BODY_TIME:
245             file.bv[cmd->bodytime.bi].t = cmd->bodytime.t;
246             break;
247
248         case CMD_GOAL_OPEN:
249             /*
250              * Enable the goal and make sure it's fully visible if
251              * this is the first update.
252              */
253
254             if (!goal_e)
255             {
256                 goal_e = 1;
257                 goal_k = first_update ? 1.0f : 0.0f;
258             }
259             break;
260
261         case CMD_SWCH_ENTER:
262             file.xv[cmd->swchenter.xi].e = 1;
263             break;
264
265         case CMD_SWCH_TOGGLE:
266             file.xv[cmd->swchtoggle.xi].f = !file.xv[cmd->swchtoggle.xi].f;
267             break;
268
269         case CMD_SWCH_EXIT:
270             file.xv[cmd->swchexit.xi].e = 0;
271             break;
272
273         case CMD_UPDATES_PER_SECOND:
274             ups = cmd->ups.n;
275             break;
276
277         case CMD_BALL_RADIUS:
278             file.uv[curr_ball].r = cmd->ballradius.r;
279             break;
280
281         case CMD_CLEAR_ITEMS:
282             if (file.hv)
283             {
284                 free(file.hv);
285                 file.hv = NULL;
286             }
287             file.hc = 0;
288             break;
289
290         case CMD_CLEAR_BALLS:
291             if (file.uv)
292             {
293                 free(file.uv);
294                 file.uv = NULL;
295             }
296             file.uc = 0;
297             break;
298
299         case CMD_BALL_POSITION:
300             v_cpy(file.uv[curr_ball].p, cmd->ballpos.p);
301             break;
302
303         case CMD_BALL_BASIS:
304             v_cpy(file.uv[curr_ball].e[0], cmd->ballbasis.e[0]);
305             v_cpy(file.uv[curr_ball].e[1], cmd->ballbasis.e[1]);
306
307             v_crs(file.uv[curr_ball].e[2],
308                   file.uv[curr_ball].e[0],
309                   file.uv[curr_ball].e[1]);
310             break;
311
312         case CMD_BALL_PEND_BASIS:
313             v_cpy(file.uv[curr_ball].E[0], cmd->ballpendbasis.E[0]);
314             v_cpy(file.uv[curr_ball].E[1], cmd->ballpendbasis.E[1]);
315
316             v_crs(file.uv[curr_ball].E[2],
317                   file.uv[curr_ball].E[0],
318                   file.uv[curr_ball].E[1]);
319             break;
320
321         case CMD_VIEW_POSITION:
322             v_cpy(view_p, cmd->viewpos.p);
323             break;
324
325         case CMD_VIEW_CENTER:
326             v_cpy(view_c, cmd->viewcenter.c);
327             break;
328
329         case CMD_VIEW_BASIS:
330             v_cpy(view_e[0], cmd->viewbasis.e[0]);
331             v_cpy(view_e[1], cmd->viewbasis.e[1]);
332
333             v_crs(view_e[2], view_e[0], view_e[1]);
334
335             view_a = V_DEG(fatan2f(view_e[2][0], view_e[2][2]));
336
337             break;
338
339         case CMD_CURRENT_BALL:
340             curr_ball = cmd->currball.ui;
341             break;
342
343         case CMD_PATH_FLAG:
344             file.pv[cmd->pathflag.pi].f = cmd->pathflag.f;
345             break;
346
347         case CMD_STEP_SIMULATION:
348             /*
349              * Simulate body motion.
350              *
351              * This is done on the client side due to replay file size
352              * concerns and isn't done as part of CMD_END_OF_UPDATE to
353              * match the server state as closely as possible.  Body
354              * time is still synchronised with the server on a
355              * semi-regular basis and path indices are handled through
356              * CMD_BODY_PATH, thus this code doesn't need to be as
357              * sophisticated as sol_body_step.
358              */
359
360             dt = cmd->stepsim.dt;
361
362             for (i = 0; i < file.bc; i++)
363             {
364                 struct s_body *bp = file.bv + i;
365                 struct s_path *pp = file.pv + bp->pi;
366
367                 if (bp->pi >= 0 && pp->f)
368                     bp->t += dt;
369             }
370             break;
371
372         case CMD_MAP:
373
374             /*
375              * Note if the loaded map matches the server's
376              * expectations. (No, this doesn't actually load a map,
377              * yet.  Something else somewhere else does.)
378              */
379
380             free(cmd->map.name);
381             game_compat_map = version.x == cmd->map.version.x;
382             break;
383
384         case CMD_TILT_AXES:
385             got_tilt_axes = 1;
386             v_cpy(tilt.x, cmd->tiltaxes.x);
387             v_cpy(tilt.z, cmd->tiltaxes.z);
388             break;
389
390         case CMD_NONE:
391         case CMD_MAX:
392             break;
393         }
394     }
395 }
396
397 void game_client_step(fs_file demo_fp)
398 {
399     union cmd *cmdp;
400
401     while ((cmdp = game_proxy_deq()))
402     {
403         /*
404          * Note: cmd_put is called first here because game_run_cmd
405          * frees some command struct members.
406          */
407
408         if (demo_fp)
409             cmd_put(demo_fp, cmdp);
410
411         game_run_cmd(cmdp);
412
413         free(cmdp);
414     }
415 }
416
417 /*---------------------------------------------------------------------------*/
418
419 int  game_client_init(const char *file_name)
420 {
421     char *back_name = NULL, *grad_name = NULL;
422     int i;
423
424     coins  = 0;
425     status = GAME_NONE;
426
427     if (client_state)
428         game_client_free();
429
430     if (!sol_load_gl(&file, file_name,
431                      config_get_d(CONFIG_TEXTURES),
432                      config_get_d(CONFIG_SHADOW)))
433         return (client_state = 0);
434
435     reflective = sol_reflective(&file);
436
437     client_state = 1;
438
439     game_tilt_init(&tilt);
440
441     /* Initialize jump and goal states. */
442
443     jump_e = 1;
444     jump_b = 0;
445
446     goal_e = 0;
447     goal_k = 0.0f;
448
449     /* Initialise the level, background, particles, fade, and view. */
450
451     fade_k =  1.0f;
452     fade_d = -2.0f;
453
454
455     version.x = 0;
456     version.y = 0;
457
458     for (i = 0; i < file.dc; i++)
459     {
460         char *k = file.av + file.dv[i].ai;
461         char *v = file.av + file.dv[i].aj;
462
463         if (strcmp(k, "back") == 0) back_name = v;
464         if (strcmp(k, "grad") == 0) grad_name = v;
465
466         if (strcmp(k, "version") == 0)
467             sscanf(v, "%d.%d", &version.x, &version.y);
468     }
469
470     /*
471      * If the client map's version is 1, assume the map is compatible
472      * with the server.  This ensures that 1.5.0 replays don't trigger
473      * bogus map compatibility warnings.  (Post-1.5.0 replays will
474      * have CMD_MAP override this.)
475      */
476
477     game_compat_map = version.x == 1;
478
479     part_reset(GOAL_HEIGHT, JUMP_HEIGHT);
480
481     view_fov = (float) config_get_d(CONFIG_VIEW_FOV);
482
483     ups          = 0;
484     first_update = 1;
485
486     back_init(grad_name, config_get_d(CONFIG_GEOMETRY));
487     sol_load_gl(&back, back_name,
488                 config_get_d(CONFIG_TEXTURES), 0);
489
490     return client_state;
491 }
492
493 void game_client_free(void)
494 {
495     if (client_state)
496     {
497         game_proxy_clr();
498         sol_free_gl(&file);
499         sol_free_gl(&back);
500         back_free();
501     }
502     client_state = 0;
503 }
504
505 /*---------------------------------------------------------------------------*/
506
507 int curr_clock(void)
508 {
509     return (int) (timer * 100.f);
510 }
511
512 int curr_coins(void)
513 {
514     return coins;
515 }
516
517 int curr_status(void)
518 {
519     return status;
520 }
521
522 /*---------------------------------------------------------------------------*/
523
524 static void game_draw_balls(const struct s_file *fp,
525                             const float *bill_M, float t)
526 {
527     float c[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
528
529     float ball_M[16];
530     float pend_M[16];
531
532     m_basis(ball_M, fp->uv[0].e[0], fp->uv[0].e[1], fp->uv[0].e[2]);
533     m_basis(pend_M, fp->uv[0].E[0], fp->uv[0].E[1], fp->uv[0].E[2]);
534
535     glPushAttrib(GL_LIGHTING_BIT);
536     glPushMatrix();
537     {
538         glTranslatef(fp->uv[0].p[0],
539                      fp->uv[0].p[1] + BALL_FUDGE,
540                      fp->uv[0].p[2]);
541         glScalef(fp->uv[0].r,
542                  fp->uv[0].r,
543                  fp->uv[0].r);
544
545         glColor4fv(c);
546         ball_draw(ball_M, pend_M, bill_M, t);
547     }
548     glPopMatrix();
549     glPopAttrib();
550 }
551
552 static void game_draw_items(const struct s_file *fp, float t)
553 {
554     float r = 360.f * t;
555     int hi;
556
557     glPushAttrib(GL_LIGHTING_BIT);
558     {
559         item_push(ITEM_COIN);
560         {
561             for (hi = 0; hi < fp->hc; hi++)
562
563                 if (fp->hv[hi].t == ITEM_COIN && fp->hv[hi].n > 0)
564                 {
565                     glPushMatrix();
566                     {
567                         glTranslatef(fp->hv[hi].p[0],
568                                      fp->hv[hi].p[1],
569                                      fp->hv[hi].p[2]);
570                         glRotatef(r, 0.0f, 1.0f, 0.0f);
571                         item_draw(&fp->hv[hi], r);
572                     }
573                     glPopMatrix();
574                 }
575         }
576         item_pull();
577
578         item_push(ITEM_SHRINK);
579         {
580             for (hi = 0; hi < fp->hc; hi++)
581
582                 if (fp->hv[hi].t == ITEM_SHRINK)
583                 {
584                     glPushMatrix();
585                     {
586                         glTranslatef(fp->hv[hi].p[0],
587                                      fp->hv[hi].p[1],
588                                      fp->hv[hi].p[2]);
589                         glRotatef(r, 0.0f, 1.0f, 0.0f);
590                         item_draw(&fp->hv[hi], r);
591                     }
592                     glPopMatrix();
593                 }
594         }
595         item_pull();
596
597         item_push(ITEM_GROW);
598         {
599             for (hi = 0; hi < fp->hc; hi++)
600
601                 if (fp->hv[hi].t == ITEM_GROW)
602                 {
603                     glPushMatrix();
604                     {
605                         glTranslatef(fp->hv[hi].p[0],
606                                      fp->hv[hi].p[1],
607                                      fp->hv[hi].p[2]);
608                         glRotatef(r, 0.0f, 1.0f, 0.0f);
609                         item_draw(&fp->hv[hi], r);
610                     }
611                     glPopMatrix();
612                 }
613         }
614         item_pull();
615     }
616     glPopAttrib();
617 }
618
619 static void game_draw_goals(const struct s_file *fp, const float *M, float t)
620 {
621     if (goal_e)
622     {
623         int zi;
624
625         /* Draw the goal particles. */
626
627         glEnable(GL_TEXTURE_2D);
628         {
629             for (zi = 0; zi < fp->zc; zi++)
630             {
631                 glPushMatrix();
632                 {
633                     glTranslatef(fp->zv[zi].p[0],
634                                  fp->zv[zi].p[1],
635                                  fp->zv[zi].p[2]);
636
637                     part_draw_goal(M, fp->zv[zi].r, goal_k, t);
638                 }
639                 glPopMatrix();
640             }
641         }
642         glDisable(GL_TEXTURE_2D);
643
644         /* Draw the goal column. */
645
646         for (zi = 0; zi < fp->zc; zi++)
647         {
648             glPushMatrix();
649             {
650                 glTranslatef(fp->zv[zi].p[0],
651                              fp->zv[zi].p[1],
652                              fp->zv[zi].p[2]);
653
654                 glScalef(fp->zv[zi].r,
655                          goal_k,
656                          fp->zv[zi].r);
657
658                 goal_draw();
659             }
660             glPopMatrix();
661         }
662     }
663 }
664
665 static void game_draw_jumps(const struct s_file *fp, const float *M, float t)
666 {
667     int ji;
668
669     glEnable(GL_TEXTURE_2D);
670     {
671         for (ji = 0; ji < fp->jc; ji++)
672         {
673             glPushMatrix();
674             {
675                 glTranslatef(fp->jv[ji].p[0],
676                              fp->jv[ji].p[1],
677                              fp->jv[ji].p[2]);
678
679                 part_draw_jump(M, fp->jv[ji].r, 1.0f, t);
680             }
681             glPopMatrix();
682         }
683     }
684     glDisable(GL_TEXTURE_2D);
685
686     for (ji = 0; ji < fp->jc; ji++)
687     {
688         glPushMatrix();
689         {
690             glTranslatef(fp->jv[ji].p[0],
691                          fp->jv[ji].p[1],
692                          fp->jv[ji].p[2]);
693             glScalef(fp->jv[ji].r,
694                      1.0f,
695                      fp->jv[ji].r);
696
697             jump_draw(!jump_e);
698         }
699         glPopMatrix();
700     }
701 }
702
703 static void game_draw_swchs(const struct s_file *fp)
704 {
705     int xi;
706
707     for (xi = 0; xi < fp->xc; xi++)
708     {
709         if (fp->xv[xi].i)
710             continue;
711
712         glPushMatrix();
713         {
714             glTranslatef(fp->xv[xi].p[0],
715                          fp->xv[xi].p[1],
716                          fp->xv[xi].p[2]);
717             glScalef(fp->xv[xi].r,
718                      1.0f,
719                      fp->xv[xi].r);
720
721             swch_draw(fp->xv[xi].f, fp->xv[xi].e);
722         }
723         glPopMatrix();
724     }
725 }
726
727 /*---------------------------------------------------------------------------*/
728
729 static void game_draw_tilt(int d)
730 {
731     const float *ball_p = file.uv->p;
732
733     /* Rotate the environment about the position of the ball. */
734
735     glTranslatef(+ball_p[0], +ball_p[1] * d, +ball_p[2]);
736     glRotatef(-tilt.rz * d, tilt.z[0], tilt.z[1], tilt.z[2]);
737     glRotatef(-tilt.rx * d, tilt.x[0], tilt.x[1], tilt.x[2]);
738     glTranslatef(-ball_p[0], -ball_p[1] * d, -ball_p[2]);
739 }
740
741 static void game_refl_all(void)
742 {
743     glPushMatrix();
744     {
745         game_draw_tilt(1);
746
747         /* Draw the floor. */
748
749         sol_refl(&file);
750     }
751     glPopMatrix();
752 }
753
754 /*---------------------------------------------------------------------------*/
755
756 static void game_draw_light(void)
757 {
758     const float light_p[2][4] = {
759         { -8.0f, +32.0f, -8.0f, 0.0f },
760         { +8.0f, +32.0f, +8.0f, 0.0f },
761     };
762     const float light_c[2][4] = {
763         { 1.0f, 0.8f, 0.8f, 1.0f },
764         { 0.8f, 1.0f, 0.8f, 1.0f },
765     };
766
767     /* Configure the lighting. */
768
769     glEnable(GL_LIGHT0);
770     glLightfv(GL_LIGHT0, GL_POSITION, light_p[0]);
771     glLightfv(GL_LIGHT0, GL_DIFFUSE,  light_c[0]);
772     glLightfv(GL_LIGHT0, GL_SPECULAR, light_c[0]);
773
774     glEnable(GL_LIGHT1);
775     glLightfv(GL_LIGHT1, GL_POSITION, light_p[1]);
776     glLightfv(GL_LIGHT1, GL_DIFFUSE,  light_c[1]);
777     glLightfv(GL_LIGHT1, GL_SPECULAR, light_c[1]);
778 }
779
780 static void game_draw_back(int pose, int d, float t)
781 {
782     if (pose == 2)
783         return;
784
785     glPushMatrix();
786     {
787         if (d < 0)
788         {
789             glRotatef(tilt.rz * 2, tilt.z[0], tilt.z[1], tilt.z[2]);
790             glRotatef(tilt.rx * 2, tilt.x[0], tilt.x[1], tilt.x[2]);
791         }
792
793         glTranslatef(view_p[0], view_p[1] * d, view_p[2]);
794
795         if (config_get_d(CONFIG_BACKGROUND))
796         {
797             /* Draw all background layers back to front. */
798
799             sol_back(&back, BACK_DIST, FAR_DIST,  t);
800             back_draw(0);
801             sol_back(&back,         0, BACK_DIST, t);
802         }
803         else back_draw(0);
804     }
805     glPopMatrix();
806 }
807
808 static void game_clip_refl(int d)
809 {
810     /* Fudge to eliminate the floor from reflection. */
811
812     GLdouble e[4], k = -0.00001;
813
814     e[0] = 0;
815     e[1] = 1;
816     e[2] = 0;
817     e[3] = k;
818
819     glClipPlane(GL_CLIP_PLANE0, e);
820 }
821
822 static void game_clip_ball(int d, const float *p)
823 {
824     GLdouble r, c[3], pz[4], nz[4];
825
826     /* Compute the plane giving the front of the ball, as seen from view_p. */
827
828     c[0] = p[0];
829     c[1] = p[1] * d;
830     c[2] = p[2];
831
832     pz[0] = view_p[0] - c[0];
833     pz[1] = view_p[1] - c[1];
834     pz[2] = view_p[2] - c[2];
835
836     r = sqrt(pz[0] * pz[0] + pz[1] * pz[1] + pz[2] * pz[2]);
837
838     pz[0] /= r;
839     pz[1] /= r;
840     pz[2] /= r;
841     pz[3] = -(pz[0] * c[0] +
842               pz[1] * c[1] +
843               pz[2] * c[2]);
844
845     /* Find the plane giving the back of the ball, as seen from view_p. */
846
847     nz[0] = -pz[0];
848     nz[1] = -pz[1];
849     nz[2] = -pz[2];
850     nz[3] = -pz[3];
851
852     /* Reflect these planes as necessary, and store them in the GL state. */
853
854     pz[1] *= d;
855     nz[1] *= d;
856
857     glClipPlane(GL_CLIP_PLANE1, nz);
858     glClipPlane(GL_CLIP_PLANE2, pz);
859 }
860
861 static void game_draw_fore(int pose, const float *M, int d, float t)
862 {
863     const float *ball_p = file.uv->p;
864     const float  ball_r = file.uv->r;
865
866     glPushMatrix();
867     {
868         /* Rotate the environment about the position of the ball. */
869
870         game_draw_tilt(d);
871
872         /* Compute clipping planes for reflection and ball facing. */
873
874         game_clip_refl(d);
875         game_clip_ball(d, ball_p);
876
877         if (d < 0)
878             glEnable(GL_CLIP_PLANE0);
879
880         switch (pose)
881         {
882         case 1:
883             sol_draw(&file, 0, 1);
884             break;
885
886         case 0:
887             /* Draw the coins. */
888
889             game_draw_items(&file, t);
890
891             /* Draw the floor. */
892
893             sol_draw(&file, 0, 1);
894
895             /* Fall through. */
896
897         case 2:
898
899             /* Draw the ball shadow. */
900
901             if (d > 0 && config_get_d(CONFIG_SHADOW))
902             {
903                 shad_draw_set(ball_p, ball_r);
904                 sol_shad(&file);
905                 shad_draw_clr();
906             }
907
908             /* Draw the ball. */
909
910             game_draw_balls(&file, M, t);
911
912             break;
913         }
914
915         /* Draw the particles and light columns. */
916
917         glEnable(GL_COLOR_MATERIAL);
918         glDisable(GL_LIGHTING);
919         glDepthMask(GL_FALSE);
920         {
921             glColor3f(1.0f, 1.0f, 1.0f);
922
923             sol_bill(&file, M, t);
924             part_draw_coin(M, t);
925
926             glDisable(GL_TEXTURE_2D);
927             {
928                 game_draw_goals(&file, M, t);
929                 game_draw_jumps(&file, M, t);
930                 game_draw_swchs(&file);
931             }
932             glEnable(GL_TEXTURE_2D);
933
934             glColor3f(1.0f, 1.0f, 1.0f);
935         }
936         glDepthMask(GL_TRUE);
937         glEnable(GL_LIGHTING);
938         glDisable(GL_COLOR_MATERIAL);
939
940         if (d < 0)
941             glDisable(GL_CLIP_PLANE0);
942     }
943     glPopMatrix();
944 }
945
946 void game_draw(int pose, float t)
947 {
948     float fov = view_fov;
949
950     if (jump_b) fov *= 2.f * fabsf(jump_dt - 0.5);
951
952     if (client_state)
953     {
954         video_push_persp(fov, 0.1f, FAR_DIST);
955         glPushMatrix();
956         {
957             float T[16], U[16], M[16], v[3];
958
959             /* Compute direct and reflected view bases. */
960
961             v[0] = +view_p[0];
962             v[1] = -view_p[1];
963             v[2] = +view_p[2];
964
965             m_view(T, view_c, view_p, view_e[1]);
966             m_view(U, view_c, v,      view_e[1]);
967
968             m_xps(M, T);
969
970             /* Apply current the view. */
971
972             v_sub(v, view_c, view_p);
973
974             glTranslatef(0.f, 0.f, -v_len(v));
975             glMultMatrixf(M);
976             glTranslatef(-view_c[0], -view_c[1], -view_c[2]);
977
978             if (reflective && config_get_d(CONFIG_REFLECTION))
979             {
980                 glEnable(GL_STENCIL_TEST);
981                 {
982                     /* Draw the mirrors only into the stencil buffer. */
983
984                     glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF);
985                     glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
986                     glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
987                     glDepthMask(GL_FALSE);
988
989                     game_refl_all();
990
991                     glDepthMask(GL_TRUE);
992                     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
993                     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
994                     glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);
995
996                     /* Draw the scene reflected into color and depth buffers. */
997
998                     glFrontFace(GL_CW);
999                     glPushMatrix();
1000                     {
1001                         glScalef(+1.0f, -1.0f, +1.0f);
1002
1003                         game_draw_light();
1004                         game_draw_back(pose,    -1, t);
1005                         game_draw_fore(pose, U, -1, t);
1006                     }
1007                     glPopMatrix();
1008                     glFrontFace(GL_CCW);
1009                 }
1010                 glDisable(GL_STENCIL_TEST);
1011             }
1012
1013             /* Draw the scene normally. */
1014
1015             game_draw_light();
1016             game_refl_all();
1017             game_draw_back(pose,    +1, t);
1018             game_draw_fore(pose, T, +1, t);
1019         }
1020         glPopMatrix();
1021         video_pop_matrix();
1022
1023         /* Draw the fade overlay. */
1024
1025         fade_draw(fade_k);
1026     }
1027 }
1028
1029 /*---------------------------------------------------------------------------*/
1030
1031 void game_look(float phi, float theta)
1032 {
1033     view_c[0] = view_p[0] + fsinf(V_RAD(theta)) * fcosf(V_RAD(phi));
1034     view_c[1] = view_p[1] +                       fsinf(V_RAD(phi));
1035     view_c[2] = view_p[2] - fcosf(V_RAD(theta)) * fcosf(V_RAD(phi));
1036 }
1037
1038 /*---------------------------------------------------------------------------*/
1039
1040 void game_kill_fade(void)
1041 {
1042     fade_k = 0.0f;
1043     fade_d = 0.0f;
1044 }
1045
1046 void game_step_fade(float dt)
1047 {
1048     if ((fade_k < 1.0f && fade_d > 0.0f) ||
1049         (fade_k > 0.0f && fade_d < 0.0f))
1050         fade_k += fade_d * dt;
1051
1052     if (fade_k < 0.0f)
1053     {
1054         fade_k = 0.0f;
1055         fade_d = 0.0f;
1056     }
1057     if (fade_k > 1.0f)
1058     {
1059         fade_k = 1.0f;
1060         fade_d = 0.0f;
1061     }
1062 }
1063
1064 void game_fade(float d)
1065 {
1066     fade_d = d;
1067 }
1068
1069 /*---------------------------------------------------------------------------*/
1070
1071 const struct s_file *game_client_file(void)
1072 {
1073     return &file;
1074 }
1075
1076 /*---------------------------------------------------------------------------*/