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