Split SOL data structures into base, varying and rendering parts
[neverball] / ball / game_draw.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 "vec3.h"
16 #include "glext.h"
17 #include "ball.h"
18 #include "item.h"
19 #include "part.h"
20 #include "geom.h"
21 #include "config.h"
22 #include "back.h"
23 #include "video.h"
24
25 #include "solid_draw.h"
26
27 #include "game_draw.h"
28
29 /*---------------------------------------------------------------------------*/
30
31 static void game_draw_balls(const struct s_vary *vary,
32                             const float *bill_M, float t)
33 {
34     float c[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
35
36     float ball_M[16];
37     float pend_M[16];
38
39     m_basis(ball_M, vary->uv[0].e[0], vary->uv[0].e[1], vary->uv[0].e[2]);
40     m_basis(pend_M, vary->uv[0].E[0], vary->uv[0].E[1], vary->uv[0].E[2]);
41
42     glPushAttrib(GL_LIGHTING_BIT);
43     glPushMatrix();
44     {
45         glTranslatef(vary->uv[0].p[0],
46                      vary->uv[0].p[1] + BALL_FUDGE,
47                      vary->uv[0].p[2]);
48         glScalef(vary->uv[0].r,
49                  vary->uv[0].r,
50                  vary->uv[0].r);
51
52         glColor4fv(c);
53         ball_draw(ball_M, pend_M, bill_M, t);
54     }
55     glPopMatrix();
56     glPopAttrib();
57 }
58
59 static void game_draw_items(const struct s_vary *vary, float t)
60 {
61     float r = 360.f * t;
62     int hi;
63
64     glPushAttrib(GL_LIGHTING_BIT);
65     {
66         item_push(ITEM_COIN);
67         {
68             for (hi = 0; hi < vary->hc; hi++)
69
70                 if (vary->hv[hi].t == ITEM_COIN && vary->hv[hi].n > 0)
71                 {
72                     glPushMatrix();
73                     {
74                         glTranslatef(vary->hv[hi].p[0],
75                                      vary->hv[hi].p[1],
76                                      vary->hv[hi].p[2]);
77                         glRotatef(r, 0.0f, 1.0f, 0.0f);
78                         item_draw(&vary->hv[hi], r);
79                     }
80                     glPopMatrix();
81                 }
82         }
83         item_pull();
84
85         item_push(ITEM_SHRINK);
86         {
87             for (hi = 0; hi < vary->hc; hi++)
88
89                 if (vary->hv[hi].t == ITEM_SHRINK)
90                 {
91                     glPushMatrix();
92                     {
93                         glTranslatef(vary->hv[hi].p[0],
94                                      vary->hv[hi].p[1],
95                                      vary->hv[hi].p[2]);
96                         glRotatef(r, 0.0f, 1.0f, 0.0f);
97                         item_draw(&vary->hv[hi], r);
98                     }
99                     glPopMatrix();
100                 }
101         }
102         item_pull();
103
104         item_push(ITEM_GROW);
105         {
106             for (hi = 0; hi < vary->hc; hi++)
107
108                 if (vary->hv[hi].t == ITEM_GROW)
109                 {
110                     glPushMatrix();
111                     {
112                         glTranslatef(vary->hv[hi].p[0],
113                                      vary->hv[hi].p[1],
114                                      vary->hv[hi].p[2]);
115                         glRotatef(r, 0.0f, 1.0f, 0.0f);
116                         item_draw(&vary->hv[hi], r);
117                     }
118                     glPopMatrix();
119                 }
120         }
121         item_pull();
122     }
123     glPopAttrib();
124 }
125
126 static void game_draw_goals(const struct game_draw *gd,
127                             const float *M, float t)
128 {
129     const struct s_base *base = &gd->file.base;
130
131     if (gd->goal_e)
132     {
133         int zi;
134
135         /* Draw the goal particles. */
136
137         glEnable(GL_TEXTURE_2D);
138         {
139             for (zi = 0; zi < base->zc; zi++)
140             {
141                 glPushMatrix();
142                 {
143                     glTranslatef(base->zv[zi].p[0],
144                                  base->zv[zi].p[1],
145                                  base->zv[zi].p[2]);
146
147                     part_draw_goal(M, base->zv[zi].r, gd->goal_k, t);
148                 }
149                 glPopMatrix();
150             }
151         }
152         glDisable(GL_TEXTURE_2D);
153
154         /* Draw the goal column. */
155
156         for (zi = 0; zi < base->zc; zi++)
157         {
158             glPushMatrix();
159             {
160                 glTranslatef(base->zv[zi].p[0],
161                              base->zv[zi].p[1],
162                              base->zv[zi].p[2]);
163
164                 glScalef(base->zv[zi].r,
165                          gd->goal_k,
166                          base->zv[zi].r);
167
168                 goal_draw();
169             }
170             glPopMatrix();
171         }
172     }
173 }
174
175 static void game_draw_jumps(const struct game_draw *gd,
176                             const float *M, float t)
177 {
178     const struct s_base *base = &gd->file.base;
179
180     int ji;
181
182     glEnable(GL_TEXTURE_2D);
183     {
184         for (ji = 0; ji < base->jc; ji++)
185         {
186             glPushMatrix();
187             {
188                 glTranslatef(base->jv[ji].p[0],
189                              base->jv[ji].p[1],
190                              base->jv[ji].p[2]);
191
192                 part_draw_jump(M, base->jv[ji].r, 1.0f, t);
193             }
194             glPopMatrix();
195         }
196     }
197     glDisable(GL_TEXTURE_2D);
198
199     for (ji = 0; ji < base->jc; ji++)
200     {
201         glPushMatrix();
202         {
203             glTranslatef(base->jv[ji].p[0],
204                          base->jv[ji].p[1],
205                          base->jv[ji].p[2]);
206             glScalef(base->jv[ji].r,
207                      1.0f,
208                      base->jv[ji].r);
209
210             jump_draw(!gd->jump_e);
211         }
212         glPopMatrix();
213     }
214 }
215
216 static void game_draw_swchs(const struct s_vary *vary)
217 {
218     int xi;
219
220     for (xi = 0; xi < vary->xc; xi++)
221     {
222         struct v_swch *xp = vary->xv + xi;
223
224         if (xp->base->i)
225             continue;
226
227         glPushMatrix();
228         {
229             glTranslatef(xp->base->p[0],
230                          xp->base->p[1],
231                          xp->base->p[2]);
232             glScalef(xp->base->r,
233                      1.0f,
234                      xp->base->r);
235
236             swch_draw(xp->f, xp->e);
237         }
238         glPopMatrix();
239     }
240 }
241
242 /*---------------------------------------------------------------------------*/
243
244 static void game_draw_tilt(const struct game_draw *gd, int d)
245 {
246     const struct game_tilt *tilt = &gd->tilt;
247     const float *ball_p = gd->file.vary.uv[0].p;
248
249     /* Rotate the environment about the position of the ball. */
250
251     glTranslatef(+ball_p[0], +ball_p[1] * d, +ball_p[2]);
252     glRotatef(-tilt->rz * d, tilt->z[0], tilt->z[1], tilt->z[2]);
253     glRotatef(-tilt->rx * d, tilt->x[0], tilt->x[1], tilt->x[2]);
254     glTranslatef(-ball_p[0], -ball_p[1] * d, -ball_p[2]);
255 }
256
257 static void game_refl_all(const struct game_draw *gd)
258 {
259     glPushMatrix();
260     {
261         game_draw_tilt(gd, 1);
262
263         /* Draw the floor. */
264
265         sol_refl(&gd->file.draw);
266     }
267     glPopMatrix();
268 }
269
270 /*---------------------------------------------------------------------------*/
271
272 static void game_draw_light(void)
273 {
274     const float light_p[2][4] = {
275         { -8.0f, +32.0f, -8.0f, 0.0f },
276         { +8.0f, +32.0f, +8.0f, 0.0f },
277     };
278     const float light_c[2][4] = {
279         { 1.0f, 0.8f, 0.8f, 1.0f },
280         { 0.8f, 1.0f, 0.8f, 1.0f },
281     };
282
283     /* Configure the lighting. */
284
285     glEnable(GL_LIGHT0);
286     glLightfv(GL_LIGHT0, GL_POSITION, light_p[0]);
287     glLightfv(GL_LIGHT0, GL_DIFFUSE,  light_c[0]);
288     glLightfv(GL_LIGHT0, GL_SPECULAR, light_c[0]);
289
290     glEnable(GL_LIGHT1);
291     glLightfv(GL_LIGHT1, GL_POSITION, light_p[1]);
292     glLightfv(GL_LIGHT1, GL_DIFFUSE,  light_c[1]);
293     glLightfv(GL_LIGHT1, GL_SPECULAR, light_c[1]);
294 }
295
296 static void game_draw_back(const struct game_draw *gd, int pose, int d, float t)
297 {
298     if (pose == POSE_BALL)
299         return;
300
301     glPushMatrix();
302     {
303         const struct game_view *view = &gd->view;
304
305         if (d < 0)
306         {
307             const struct game_tilt *tilt = &gd->tilt;
308
309             glRotatef(tilt->rz * 2, tilt->z[0], tilt->z[1], tilt->z[2]);
310             glRotatef(tilt->rx * 2, tilt->x[0], tilt->x[1], tilt->x[2]);
311         }
312
313         glTranslatef(view->p[0], view->p[1] * d, view->p[2]);
314
315         if (config_get_d(CONFIG_BACKGROUND))
316         {
317             /* Draw all background layers back to front. */
318
319             sol_back(&gd->back.draw, BACK_DIST, FAR_DIST,  t);
320             back_draw(0);
321             sol_back(&gd->back.draw,         0, BACK_DIST, t);
322         }
323         else back_draw(0);
324     }
325     glPopMatrix();
326 }
327
328 static void game_clip_refl(int d)
329 {
330     /* Fudge to eliminate the floor from reflection. */
331
332     GLdouble e[4], k = -0.00001;
333
334     e[0] = 0;
335     e[1] = 1;
336     e[2] = 0;
337     e[3] = k;
338
339     glClipPlane(GL_CLIP_PLANE0, e);
340 }
341
342 static void game_clip_ball(const struct game_draw *gd, int d, const float *p)
343 {
344     GLdouble r, c[3], pz[4], nz[4];
345
346     /* Compute the plane giving the front of the ball, as seen from view.p. */
347
348     c[0] = p[0];
349     c[1] = p[1] * d;
350     c[2] = p[2];
351
352     pz[0] = gd->view.p[0] - c[0];
353     pz[1] = gd->view.p[1] - c[1];
354     pz[2] = gd->view.p[2] - c[2];
355
356     r = sqrt(pz[0] * pz[0] + pz[1] * pz[1] + pz[2] * pz[2]);
357
358     pz[0] /= r;
359     pz[1] /= r;
360     pz[2] /= r;
361     pz[3] = -(pz[0] * c[0] +
362               pz[1] * c[1] +
363               pz[2] * c[2]);
364
365     /* Find the plane giving the back of the ball, as seen from view.p. */
366
367     nz[0] = -pz[0];
368     nz[1] = -pz[1];
369     nz[2] = -pz[2];
370     nz[3] = -pz[3];
371
372     /* Reflect these planes as necessary, and store them in the GL state. */
373
374     pz[1] *= d;
375     nz[1] *= d;
376
377     glClipPlane(GL_CLIP_PLANE1, nz);
378     glClipPlane(GL_CLIP_PLANE2, pz);
379 }
380
381 static void game_draw_fore(const struct game_draw *gd,
382                            int pose, const float *M,
383                            int d, float t)
384 {
385     const float *ball_p = gd->file.vary.uv[0].p;
386     const float  ball_r = gd->file.vary.uv[0].r;
387
388     const struct s_draw *draw = &gd->file.draw;
389
390     glPushMatrix();
391     {
392         /* Rotate the environment about the position of the ball. */
393
394         game_draw_tilt(gd, d);
395
396         /* Compute clipping planes for reflection and ball facing. */
397
398         game_clip_refl(d);
399         game_clip_ball(gd, d, ball_p);
400
401         if (d < 0)
402             glEnable(GL_CLIP_PLANE0);
403
404         switch (pose)
405         {
406         case POSE_LEVEL:
407             sol_draw(draw, 0, 1);
408             break;
409
410         case POSE_NONE:
411             /* Draw the coins. */
412
413             game_draw_items(draw->vary, t);
414
415             /* Draw the floor. */
416
417             sol_draw(draw, 0, 1);
418
419             /* Fall through. */
420
421         case POSE_BALL:
422
423             /* Draw the ball shadow. */
424
425             if (d > 0 && config_get_d(CONFIG_SHADOW))
426             {
427                 shad_draw_set(ball_p, ball_r);
428                 sol_shad(draw);
429                 shad_draw_clr();
430             }
431
432             /* Draw the ball. */
433
434             game_draw_balls(draw->vary, M, t);
435
436             break;
437         }
438
439         /* Draw the particles and light columns. */
440
441         glEnable(GL_COLOR_MATERIAL);
442         glDisable(GL_LIGHTING);
443         glDepthMask(GL_FALSE);
444         {
445             glColor3f(1.0f, 1.0f, 1.0f);
446
447             sol_bill(draw, M, t);
448             part_draw_coin(M, t);
449
450             glDisable(GL_TEXTURE_2D);
451             {
452                 game_draw_goals(gd, M, t);
453                 game_draw_jumps(gd, M, t);
454                 game_draw_swchs(draw->vary);
455             }
456             glEnable(GL_TEXTURE_2D);
457
458             glColor3f(1.0f, 1.0f, 1.0f);
459         }
460         glDepthMask(GL_TRUE);
461         glEnable(GL_LIGHTING);
462         glDisable(GL_COLOR_MATERIAL);
463
464         if (d < 0)
465             glDisable(GL_CLIP_PLANE0);
466     }
467     glPopMatrix();
468 }
469
470 /*---------------------------------------------------------------------------*/
471
472 void game_draw(const struct game_draw *gd, int pose, float t)
473 {
474     float fov = (float) config_get_d(CONFIG_VIEW_FOV);
475
476     if (gd->jump_b) fov *= 2.f * fabsf(gd->jump_dt - 0.5);
477
478     if (gd->state)
479     {
480         const struct game_view *view = &gd->view;
481
482         video_push_persp(fov, 0.1f, FAR_DIST);
483         glPushMatrix();
484         {
485             float T[16], U[16], M[16], v[3];
486
487
488             /* Compute direct and reflected view bases. */
489
490             v[0] = +view->p[0];
491             v[1] = -view->p[1];
492             v[2] = +view->p[2];
493
494             m_view(T, view->c, view->p, view->e[1]);
495             m_view(U, view->c, v,       view->e[1]);
496
497             m_xps(M, T);
498
499             /* Apply the current view. */
500
501             v_sub(v, view->c, view->p);
502
503             glTranslatef(0.f, 0.f, -v_len(v));
504             glMultMatrixf(M);
505             glTranslatef(-view->c[0], -view->c[1], -view->c[2]);
506
507             if (gd->reflective && config_get_d(CONFIG_REFLECTION))
508             {
509                 glEnable(GL_STENCIL_TEST);
510                 {
511                     /* Draw the mirrors only into the stencil buffer. */
512
513                     glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF);
514                     glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
515                     glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
516                     glDepthMask(GL_FALSE);
517
518                     game_refl_all(gd);
519
520                     glDepthMask(GL_TRUE);
521                     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
522                     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
523                     glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);
524
525                     /* Draw the scene reflected into color and depth buffers. */
526
527                     glFrontFace(GL_CW);
528                     glPushMatrix();
529                     {
530                         glScalef(+1.0f, -1.0f, +1.0f);
531
532                         game_draw_light();
533                         game_draw_back(gd, pose,    -1, t);
534                         game_draw_fore(gd, pose, U, -1, t);
535                     }
536                     glPopMatrix();
537                     glFrontFace(GL_CCW);
538                 }
539                 glDisable(GL_STENCIL_TEST);
540             }
541
542             /* Draw the scene normally. */
543
544             game_draw_light();
545
546             if (gd->reflective)
547             {
548                 if (config_get_d(CONFIG_REFLECTION))
549                 {
550                     /* Draw background while preserving reflections. */
551
552                     glEnable(GL_STENCIL_TEST);
553                     {
554                         glStencilFunc(GL_NOTEQUAL, 1, 0xFFFFFFFF);
555                         game_draw_back(gd, pose, +1, t);
556                     }
557                     glDisable(GL_STENCIL_TEST);
558
559                     /* Draw mirrors. */
560
561                     game_refl_all(gd);
562                 }
563                 else
564                 {
565                     /* Draw background. */
566
567                     game_draw_back(gd, pose, +1, t);
568
569                     /*
570                      * Draw mirrors, first fully opaque with a custom
571                      * material color, then blending normally with the
572                      * opaque surfaces using their original material
573                      * properties.  (Keeps background from showing
574                      * through.)
575                      */
576
577                     glEnable(GL_COLOR_MATERIAL);
578                     {
579                         glColor4f(0.0, 0.0, 0.05, 1.0);
580                         game_refl_all(gd);
581                         glColor4f(1.0,  1.0,  1.0,  1.0);
582                     }
583                     glDisable(GL_COLOR_MATERIAL);
584
585                     game_refl_all(gd);
586                 }
587             }
588             else
589             {
590                 game_draw_back(gd, pose, +1, t);
591                 game_refl_all(gd);
592             }
593
594             game_draw_fore(gd, pose, T, +1, t);
595         }
596         glPopMatrix();
597         video_pop_matrix();
598
599         /* Draw the fade overlay. */
600
601         fade_draw(gd->fade_k);
602     }
603 }
604
605 /*---------------------------------------------------------------------------*/