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