Paint background first, then mirrors
[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 = NULL, *grad_name = NULL;
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,
426                      config_get_d(CONFIG_TEXTURES),
427                      config_get_d(CONFIG_SHADOW)))
428         return (client_state = 0);
429
430     reflective = sol_reflective(&file);
431
432     client_state = 1;
433
434     game_tilt_init(&tilt);
435
436     /* Initialize jump and goal states. */
437
438     jump_e = 1;
439     jump_b = 0;
440
441     goal_e = 0;
442     goal_k = 0.0f;
443
444     /* Initialise the level, background, particles, fade, and view. */
445
446     fade_k =  1.0f;
447     fade_d = -2.0f;
448
449
450     version.x = 0;
451     version.y = 0;
452
453     for (i = 0; i < file.dc; i++)
454     {
455         char *k = file.av + file.dv[i].ai;
456         char *v = file.av + file.dv[i].aj;
457
458         if (strcmp(k, "back") == 0) back_name = v;
459         if (strcmp(k, "grad") == 0) grad_name = v;
460
461         if (strcmp(k, "version") == 0)
462             sscanf(v, "%d.%d", &version.x, &version.y);
463     }
464
465     /*
466      * If the client map's version is 1, assume the map is compatible
467      * with the server.  This ensures that 1.5.0 replays don't trigger
468      * bogus map compatibility warnings.  (Post-1.5.0 replays will
469      * have CMD_MAP override this.)
470      */
471
472     game_compat_map = version.x == 1;
473
474     part_reset(GOAL_HEIGHT, JUMP_HEIGHT);
475
476     ups          = 0;
477     first_update = 1;
478
479     back_init(grad_name, config_get_d(CONFIG_GEOMETRY));
480     sol_load_gl(&back, back_name,
481                 config_get_d(CONFIG_TEXTURES), 0);
482
483     return client_state;
484 }
485
486 void game_client_free(void)
487 {
488     if (client_state)
489     {
490         game_proxy_clr();
491         sol_free_gl(&file);
492         sol_free_gl(&back);
493         back_free();
494     }
495     client_state = 0;
496 }
497
498 /*---------------------------------------------------------------------------*/
499
500 int curr_clock(void)
501 {
502     return (int) (timer * 100.f);
503 }
504
505 int curr_coins(void)
506 {
507     return coins;
508 }
509
510 int curr_status(void)
511 {
512     return status;
513 }
514
515 /*---------------------------------------------------------------------------*/
516
517 static void game_draw_balls(const struct s_file *fp,
518                             const float *bill_M, float t)
519 {
520     float c[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
521
522     float ball_M[16];
523     float pend_M[16];
524
525     m_basis(ball_M, fp->uv[0].e[0], fp->uv[0].e[1], fp->uv[0].e[2]);
526     m_basis(pend_M, fp->uv[0].E[0], fp->uv[0].E[1], fp->uv[0].E[2]);
527
528     glPushAttrib(GL_LIGHTING_BIT);
529     glPushMatrix();
530     {
531         glTranslatef(fp->uv[0].p[0],
532                      fp->uv[0].p[1] + BALL_FUDGE,
533                      fp->uv[0].p[2]);
534         glScalef(fp->uv[0].r,
535                  fp->uv[0].r,
536                  fp->uv[0].r);
537
538         glColor4fv(c);
539         ball_draw(ball_M, pend_M, bill_M, t);
540     }
541     glPopMatrix();
542     glPopAttrib();
543 }
544
545 static void game_draw_items(const struct s_file *fp, float t)
546 {
547     float r = 360.f * t;
548     int hi;
549
550     glPushAttrib(GL_LIGHTING_BIT);
551     {
552         item_push(ITEM_COIN);
553         {
554             for (hi = 0; hi < fp->hc; hi++)
555
556                 if (fp->hv[hi].t == ITEM_COIN && fp->hv[hi].n > 0)
557                 {
558                     glPushMatrix();
559                     {
560                         glTranslatef(fp->hv[hi].p[0],
561                                      fp->hv[hi].p[1],
562                                      fp->hv[hi].p[2]);
563                         glRotatef(r, 0.0f, 1.0f, 0.0f);
564                         item_draw(&fp->hv[hi], r);
565                     }
566                     glPopMatrix();
567                 }
568         }
569         item_pull();
570
571         item_push(ITEM_SHRINK);
572         {
573             for (hi = 0; hi < fp->hc; hi++)
574
575                 if (fp->hv[hi].t == ITEM_SHRINK)
576                 {
577                     glPushMatrix();
578                     {
579                         glTranslatef(fp->hv[hi].p[0],
580                                      fp->hv[hi].p[1],
581                                      fp->hv[hi].p[2]);
582                         glRotatef(r, 0.0f, 1.0f, 0.0f);
583                         item_draw(&fp->hv[hi], r);
584                     }
585                     glPopMatrix();
586                 }
587         }
588         item_pull();
589
590         item_push(ITEM_GROW);
591         {
592             for (hi = 0; hi < fp->hc; hi++)
593
594                 if (fp->hv[hi].t == ITEM_GROW)
595                 {
596                     glPushMatrix();
597                     {
598                         glTranslatef(fp->hv[hi].p[0],
599                                      fp->hv[hi].p[1],
600                                      fp->hv[hi].p[2]);
601                         glRotatef(r, 0.0f, 1.0f, 0.0f);
602                         item_draw(&fp->hv[hi], r);
603                     }
604                     glPopMatrix();
605                 }
606         }
607         item_pull();
608     }
609     glPopAttrib();
610 }
611
612 static void game_draw_goals(const struct s_file *fp, const float *M, float t)
613 {
614     if (goal_e)
615     {
616         int zi;
617
618         /* Draw the goal particles. */
619
620         glEnable(GL_TEXTURE_2D);
621         {
622             for (zi = 0; zi < fp->zc; zi++)
623             {
624                 glPushMatrix();
625                 {
626                     glTranslatef(fp->zv[zi].p[0],
627                                  fp->zv[zi].p[1],
628                                  fp->zv[zi].p[2]);
629
630                     part_draw_goal(M, fp->zv[zi].r, goal_k, t);
631                 }
632                 glPopMatrix();
633             }
634         }
635         glDisable(GL_TEXTURE_2D);
636
637         /* Draw the goal column. */
638
639         for (zi = 0; zi < fp->zc; zi++)
640         {
641             glPushMatrix();
642             {
643                 glTranslatef(fp->zv[zi].p[0],
644                              fp->zv[zi].p[1],
645                              fp->zv[zi].p[2]);
646
647                 glScalef(fp->zv[zi].r,
648                          goal_k,
649                          fp->zv[zi].r);
650
651                 goal_draw();
652             }
653             glPopMatrix();
654         }
655     }
656 }
657
658 static void game_draw_jumps(const struct s_file *fp, const float *M, float t)
659 {
660     int ji;
661
662     glEnable(GL_TEXTURE_2D);
663     {
664         for (ji = 0; ji < fp->jc; ji++)
665         {
666             glPushMatrix();
667             {
668                 glTranslatef(fp->jv[ji].p[0],
669                              fp->jv[ji].p[1],
670                              fp->jv[ji].p[2]);
671
672                 part_draw_jump(M, fp->jv[ji].r, 1.0f, t);
673             }
674             glPopMatrix();
675         }
676     }
677     glDisable(GL_TEXTURE_2D);
678
679     for (ji = 0; ji < fp->jc; ji++)
680     {
681         glPushMatrix();
682         {
683             glTranslatef(fp->jv[ji].p[0],
684                          fp->jv[ji].p[1],
685                          fp->jv[ji].p[2]);
686             glScalef(fp->jv[ji].r,
687                      1.0f,
688                      fp->jv[ji].r);
689
690             jump_draw(!jump_e);
691         }
692         glPopMatrix();
693     }
694 }
695
696 static void game_draw_swchs(const struct s_file *fp)
697 {
698     int xi;
699
700     for (xi = 0; xi < fp->xc; xi++)
701     {
702         if (fp->xv[xi].i)
703             continue;
704
705         glPushMatrix();
706         {
707             glTranslatef(fp->xv[xi].p[0],
708                          fp->xv[xi].p[1],
709                          fp->xv[xi].p[2]);
710             glScalef(fp->xv[xi].r,
711                      1.0f,
712                      fp->xv[xi].r);
713
714             swch_draw(fp->xv[xi].f, fp->xv[xi].e);
715         }
716         glPopMatrix();
717     }
718 }
719
720 /*---------------------------------------------------------------------------*/
721
722 static void game_draw_tilt(int d)
723 {
724     const float *ball_p = file.uv->p;
725
726     /* Rotate the environment about the position of the ball. */
727
728     glTranslatef(+ball_p[0], +ball_p[1] * d, +ball_p[2]);
729     glRotatef(-tilt.rz * d, tilt.z[0], tilt.z[1], tilt.z[2]);
730     glRotatef(-tilt.rx * d, tilt.x[0], tilt.x[1], tilt.x[2]);
731     glTranslatef(-ball_p[0], -ball_p[1] * d, -ball_p[2]);
732 }
733
734 static void game_refl_all(void)
735 {
736     glPushMatrix();
737     {
738         game_draw_tilt(1);
739
740         /* Draw the floor. */
741
742         sol_refl(&file);
743     }
744     glPopMatrix();
745 }
746
747 /*---------------------------------------------------------------------------*/
748
749 static void game_draw_light(void)
750 {
751     const float light_p[2][4] = {
752         { -8.0f, +32.0f, -8.0f, 0.0f },
753         { +8.0f, +32.0f, +8.0f, 0.0f },
754     };
755     const float light_c[2][4] = {
756         { 1.0f, 0.8f, 0.8f, 1.0f },
757         { 0.8f, 1.0f, 0.8f, 1.0f },
758     };
759
760     /* Configure the lighting. */
761
762     glEnable(GL_LIGHT0);
763     glLightfv(GL_LIGHT0, GL_POSITION, light_p[0]);
764     glLightfv(GL_LIGHT0, GL_DIFFUSE,  light_c[0]);
765     glLightfv(GL_LIGHT0, GL_SPECULAR, light_c[0]);
766
767     glEnable(GL_LIGHT1);
768     glLightfv(GL_LIGHT1, GL_POSITION, light_p[1]);
769     glLightfv(GL_LIGHT1, GL_DIFFUSE,  light_c[1]);
770     glLightfv(GL_LIGHT1, GL_SPECULAR, light_c[1]);
771 }
772
773 static void game_draw_back(int pose, int d, float t)
774 {
775     if (pose == 2)
776         return;
777
778     glPushMatrix();
779     {
780         if (d < 0)
781         {
782             glRotatef(tilt.rz * 2, tilt.z[0], tilt.z[1], tilt.z[2]);
783             glRotatef(tilt.rx * 2, tilt.x[0], tilt.x[1], tilt.x[2]);
784         }
785
786         glTranslatef(view_p[0], view_p[1] * d, view_p[2]);
787
788         if (config_get_d(CONFIG_BACKGROUND))
789         {
790             /* Draw all background layers back to front. */
791
792             sol_back(&back, BACK_DIST, FAR_DIST,  t);
793             back_draw(0);
794             sol_back(&back,         0, BACK_DIST, t);
795         }
796         else back_draw(0);
797     }
798     glPopMatrix();
799 }
800
801 static void game_clip_refl(int d)
802 {
803     /* Fudge to eliminate the floor from reflection. */
804
805     GLdouble e[4], k = -0.00001;
806
807     e[0] = 0;
808     e[1] = 1;
809     e[2] = 0;
810     e[3] = k;
811
812     glClipPlane(GL_CLIP_PLANE0, e);
813 }
814
815 static void game_clip_ball(int d, const float *p)
816 {
817     GLdouble r, c[3], pz[4], nz[4];
818
819     /* Compute the plane giving the front of the ball, as seen from view_p. */
820
821     c[0] = p[0];
822     c[1] = p[1] * d;
823     c[2] = p[2];
824
825     pz[0] = view_p[0] - c[0];
826     pz[1] = view_p[1] - c[1];
827     pz[2] = view_p[2] - c[2];
828
829     r = sqrt(pz[0] * pz[0] + pz[1] * pz[1] + pz[2] * pz[2]);
830
831     pz[0] /= r;
832     pz[1] /= r;
833     pz[2] /= r;
834     pz[3] = -(pz[0] * c[0] +
835               pz[1] * c[1] +
836               pz[2] * c[2]);
837
838     /* Find the plane giving the back of the ball, as seen from view_p. */
839
840     nz[0] = -pz[0];
841     nz[1] = -pz[1];
842     nz[2] = -pz[2];
843     nz[3] = -pz[3];
844
845     /* Reflect these planes as necessary, and store them in the GL state. */
846
847     pz[1] *= d;
848     nz[1] *= d;
849
850     glClipPlane(GL_CLIP_PLANE1, nz);
851     glClipPlane(GL_CLIP_PLANE2, pz);
852 }
853
854 static void game_draw_fore(int pose, const float *M, int d, float t)
855 {
856     const float *ball_p = file.uv->p;
857     const float  ball_r = file.uv->r;
858
859     glPushMatrix();
860     {
861         /* Rotate the environment about the position of the ball. */
862
863         game_draw_tilt(d);
864
865         /* Compute clipping planes for reflection and ball facing. */
866
867         game_clip_refl(d);
868         game_clip_ball(d, ball_p);
869
870         if (d < 0)
871             glEnable(GL_CLIP_PLANE0);
872
873         switch (pose)
874         {
875         case 1:
876             sol_draw(&file, 0, 1);
877             break;
878
879         case 0:
880             /* Draw the coins. */
881
882             game_draw_items(&file, t);
883
884             /* Draw the floor. */
885
886             sol_draw(&file, 0, 1);
887
888             /* Fall through. */
889
890         case 2:
891
892             /* Draw the ball shadow. */
893
894             if (d > 0 && config_get_d(CONFIG_SHADOW))
895             {
896                 shad_draw_set(ball_p, ball_r);
897                 sol_shad(&file);
898                 shad_draw_clr();
899             }
900
901             /* Draw the ball. */
902
903             game_draw_balls(&file, M, t);
904
905             break;
906         }
907
908         /* Draw the particles and light columns. */
909
910         glEnable(GL_COLOR_MATERIAL);
911         glDisable(GL_LIGHTING);
912         glDepthMask(GL_FALSE);
913         {
914             glColor3f(1.0f, 1.0f, 1.0f);
915
916             sol_bill(&file, M, t);
917             part_draw_coin(M, t);
918
919             glDisable(GL_TEXTURE_2D);
920             {
921                 game_draw_goals(&file, M, t);
922                 game_draw_jumps(&file, M, t);
923                 game_draw_swchs(&file);
924             }
925             glEnable(GL_TEXTURE_2D);
926
927             glColor3f(1.0f, 1.0f, 1.0f);
928         }
929         glDepthMask(GL_TRUE);
930         glEnable(GL_LIGHTING);
931         glDisable(GL_COLOR_MATERIAL);
932
933         if (d < 0)
934             glDisable(GL_CLIP_PLANE0);
935     }
936     glPopMatrix();
937 }
938
939 void game_draw(int pose, float t)
940 {
941     float fov = (float) config_get_d(CONFIG_VIEW_FOV);
942
943     if (jump_b) fov *= 2.f * fabsf(jump_dt - 0.5);
944
945     if (client_state)
946     {
947         video_push_persp(fov, 0.1f, FAR_DIST);
948         glPushMatrix();
949         {
950             float T[16], U[16], M[16], v[3];
951
952             /* Compute direct and reflected view bases. */
953
954             v[0] = +view_p[0];
955             v[1] = -view_p[1];
956             v[2] = +view_p[2];
957
958             m_view(T, view_c, view_p, view_e[1]);
959             m_view(U, view_c, v,      view_e[1]);
960
961             m_xps(M, T);
962
963             /* Apply current the view. */
964
965             v_sub(v, view_c, view_p);
966
967             glTranslatef(0.f, 0.f, -v_len(v));
968             glMultMatrixf(M);
969             glTranslatef(-view_c[0], -view_c[1], -view_c[2]);
970
971             if (reflective && config_get_d(CONFIG_REFLECTION))
972             {
973                 glEnable(GL_STENCIL_TEST);
974                 {
975                     /* Draw the mirrors only into the stencil buffer. */
976
977                     glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF);
978                     glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
979                     glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
980                     glDepthMask(GL_FALSE);
981
982                     game_refl_all();
983
984                     glDepthMask(GL_TRUE);
985                     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
986                     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
987                     glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);
988
989                     /* Draw the scene reflected into color and depth buffers. */
990
991                     glFrontFace(GL_CW);
992                     glPushMatrix();
993                     {
994                         glScalef(+1.0f, -1.0f, +1.0f);
995
996                         game_draw_light();
997                         game_draw_back(pose,    -1, t);
998                         game_draw_fore(pose, U, -1, t);
999                     }
1000                     glPopMatrix();
1001                     glFrontFace(GL_CCW);
1002                 }
1003                 glDisable(GL_STENCIL_TEST);
1004             }
1005
1006             /* Draw the scene normally. */
1007
1008             game_draw_light();
1009
1010             if (reflective)
1011             {
1012                 if (config_get_d(CONFIG_REFLECTION))
1013                 {
1014                     /* Draw background while preserving reflections. */
1015
1016                     glEnable(GL_STENCIL_TEST);
1017                     {
1018                         glStencilFunc(GL_NOTEQUAL, 1, 0xFFFFFFFF);
1019                         game_draw_back(pose, +1, t);
1020                     }
1021                     glDisable(GL_STENCIL_TEST);
1022
1023                     /* Draw mirrors. */
1024
1025                     game_refl_all();
1026                 }
1027                 else
1028                 {
1029                     /* Draw background. */
1030
1031                     game_draw_back(pose, +1, t);
1032
1033                     /*
1034                      * Draw mirrors, first fully opaque with a custom
1035                      * material color, then blending normally with the
1036                      * opaque surfaces using their original material
1037                      * properties.  (Keeps background from showing
1038                      * through.)
1039                      */
1040
1041                     glEnable(GL_COLOR_MATERIAL);
1042                     {
1043                         glColor4f(0.0, 0.0, 0.05, 1.0);
1044                         game_refl_all();
1045                         glColor4f(1.0,  1.0,  1.0,  1.0);
1046                     }
1047                     glDisable(GL_COLOR_MATERIAL);
1048
1049                     game_refl_all();
1050                 }
1051             }
1052             else
1053             {
1054                 game_draw_back(pose, +1, t);
1055                 game_refl_all();
1056             }
1057
1058             game_draw_fore(pose, T, +1, t);
1059         }
1060         glPopMatrix();
1061         video_pop_matrix();
1062
1063         /* Draw the fade overlay. */
1064
1065         fade_draw(fade_k);
1066     }
1067 }
1068
1069 /*---------------------------------------------------------------------------*/
1070
1071 void game_look(float phi, float theta)
1072 {
1073     view_c[0] = view_p[0] + fsinf(V_RAD(theta)) * fcosf(V_RAD(phi));
1074     view_c[1] = view_p[1] +                       fsinf(V_RAD(phi));
1075     view_c[2] = view_p[2] - fcosf(V_RAD(theta)) * fcosf(V_RAD(phi));
1076 }
1077
1078 /*---------------------------------------------------------------------------*/
1079
1080 void game_kill_fade(void)
1081 {
1082     fade_k = 0.0f;
1083     fade_d = 0.0f;
1084 }
1085
1086 void game_step_fade(float dt)
1087 {
1088     if ((fade_k < 1.0f && fade_d > 0.0f) ||
1089         (fade_k > 0.0f && fade_d < 0.0f))
1090         fade_k += fade_d * dt;
1091
1092     if (fade_k < 0.0f)
1093     {
1094         fade_k = 0.0f;
1095         fade_d = 0.0f;
1096     }
1097     if (fade_k > 1.0f)
1098     {
1099         fade_k = 1.0f;
1100         fade_d = 0.0f;
1101     }
1102 }
1103
1104 void game_fade(float d)
1105 {
1106     fade_d = d;
1107 }
1108
1109 /*---------------------------------------------------------------------------*/
1110
1111 const struct s_file *game_client_file(void)
1112 {
1113     return &file;
1114 }
1115
1116 /*---------------------------------------------------------------------------*/