2b1dcfc20dffe481abe8d1f953e8a313d086a6cf
[neverball] / putt / game.c
1 /*
2  * Copyright (C) 2003 Robert Kooima
3  *
4  * NEVERPUTT 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
18 #include "glext.h"
19 #include "game.h"
20 #include "vec3.h"
21 #include "geom.h"
22 #include "ball.h"
23 #include "back.h"
24 #include "hole.h"
25 #include "hud.h"
26 #include "image.h"
27 #include "audio.h"
28 #include "solid_gl.h"
29 #include "solid_phys.h"
30 #include "config.h"
31 #include "video.h"
32
33 /*---------------------------------------------------------------------------*/
34
35 static struct s_file file;
36 static int           ball;
37
38 static float view_a;                    /* Ideal view rotation about Y axis  */
39 static float view_m;
40 static float view_ry;                   /* Angular velocity about Y axis     */
41 static float view_dy;                   /* Ideal view distance above ball    */
42 static float view_dz;                   /* Ideal view distance behind ball   */
43
44 static float view_c[3];                 /* Current view center               */
45 static float view_v[3];                 /* Current view vector               */
46 static float view_p[3];                 /* Current view position             */
47 static float view_e[3][3];              /* Current view orientation          */
48
49 static float jump_e = 1;                /* Jumping enabled flag              */
50 static float jump_b = 0;                /* Jump-in-progress flag             */
51 static float jump_dt;                   /* Jump duration                     */
52 static float jump_p[3];                 /* Jump destination                  */
53
54 /*---------------------------------------------------------------------------*/
55
56 static void view_init(void)
57 {
58     view_a  = 0.f;
59     view_m  = 0.f;
60     view_ry = 0.f;
61     view_dy = 3.f;
62     view_dz = 5.f;
63
64     view_c[0] = 0.f;
65     view_c[1] = 0.f;
66     view_c[2] = 0.f;
67
68     view_p[0] =     0.f;
69     view_p[1] = view_dy;
70     view_p[2] = view_dz;
71
72     view_e[0][0] = 1.f;
73     view_e[0][1] = 0.f;
74     view_e[0][2] = 0.f;
75     view_e[1][0] = 0.f;
76     view_e[1][1] = 1.f;
77     view_e[1][2] = 0.f;
78     view_e[2][0] = 0.f;
79     view_e[2][1] = 0.f;
80     view_e[2][2] = 1.f;
81 }
82
83 void game_init(const char *s)
84 {
85     jump_e = 1;
86     jump_b = 0;
87
88     view_init();
89     sol_load_gl(&file, config_data(s), config_get_d(CONFIG_TEXTURES),
90                                     config_get_d(CONFIG_SHADOW));
91 }
92
93 void game_free(void)
94 {
95     sol_free_gl(&file);
96 }
97
98 /*---------------------------------------------------------------------------*/
99
100 static void game_draw_vect_prim(const struct s_file *fp, GLenum mode)
101 {
102     float p[3];
103     float x[3];
104     float z[3];
105     float r;
106
107     v_cpy(p, fp->uv[ball].p);
108     v_cpy(x, view_e[0]);
109     v_cpy(z, view_e[2]);
110
111     r = fp->uv[ball].r;
112
113     glBegin(mode);
114     {
115         glColor4f(1.0f, 1.0f, 0.5f, 0.5f);
116         glVertex3f(p[0] - x[0] * r,
117                    p[1] - x[1] * r,
118                    p[2] - x[2] * r);
119
120         glColor4f(1.0f, 0.0f, 0.0f, 0.5f);
121         glVertex3f(p[0] + z[0] * view_m,
122                    p[1] + z[1] * view_m,
123                    p[2] + z[2] * view_m);
124
125         glColor4f(1.0f, 1.0f, 0.0f, 0.5f);
126         glVertex3f(p[0] + x[0] * r,
127                    p[1] + x[1] * r,
128                    p[2] + x[2] * r);
129     }
130     glEnd();
131 }
132
133 static void game_draw_vect(const struct s_file *fp)
134 {
135     if (view_m > 0.f)
136     {
137         glPushAttrib(GL_TEXTURE_BIT);
138         glPushAttrib(GL_POLYGON_BIT);
139         glPushAttrib(GL_LIGHTING_BIT);
140         glPushAttrib(GL_DEPTH_BUFFER_BIT);
141         {
142             glEnable(GL_COLOR_MATERIAL);
143             glDisable(GL_LIGHTING);
144             glDisable(GL_TEXTURE_2D);
145             glDepthMask(GL_FALSE);
146
147             glEnable(GL_DEPTH_TEST);
148             game_draw_vect_prim(fp, GL_TRIANGLES);
149
150             glDisable(GL_DEPTH_TEST);
151             game_draw_vect_prim(fp, GL_LINE_STRIP);
152         }
153         glPopAttrib();
154         glPopAttrib();
155         glPopAttrib();
156         glPopAttrib();
157     }
158 }
159
160 static void game_draw_balls(const struct s_file *fp,
161                             const float *bill_M, float t)
162 {
163     static const GLfloat color[5][4] = {
164         { 1.0f, 1.0f, 1.0f, 0.7f },
165         { 1.0f, 0.0f, 0.0f, 1.0f },
166         { 0.0f, 1.0f, 0.0f, 1.0f },
167         { 0.0f, 0.0f, 1.0f, 1.0f },
168         { 1.0f, 1.0f, 0.0f, 1.0f },
169     };
170
171     int ui;
172
173     for (ui = curr_party(); ui > 0; ui--)
174     {
175         if (ui == ball)
176         {
177             float ball_M[16];
178             float pend_M[16];
179
180             m_basis(ball_M, fp->uv[ui].e[0], fp->uv[ui].e[1], fp->uv[ui].e[2]);
181             m_basis(pend_M, fp->uv[ui].E[0], fp->uv[ui].E[1], fp->uv[ui].E[2]);
182
183             glPushMatrix();
184             {
185                 glTranslatef(fp->uv[ui].p[0],
186                              fp->uv[ui].p[1] + BALL_FUDGE,
187                              fp->uv[ui].p[2]);
188                 glScalef(fp->uv[ui].r,
189                          fp->uv[ui].r,
190                          fp->uv[ui].r);
191
192                 glEnable(GL_COLOR_MATERIAL);
193                 glColor4fv(color[ui]);
194                 ball_draw(ball_M, pend_M, bill_M, t);
195                 glDisable(GL_COLOR_MATERIAL);
196             }
197             glPopMatrix();
198         }
199         else
200         {
201             glPushMatrix();
202             {
203                 glTranslatef(fp->uv[ui].p[0],
204                              fp->uv[ui].p[1] - fp->uv[ui].r + BALL_FUDGE,
205                              fp->uv[ui].p[2]);
206                 glScalef(fp->uv[ui].r,
207                          fp->uv[ui].r,
208                          fp->uv[ui].r);
209
210                 glColor4f(color[ui][0],
211                           color[ui][1],
212                           color[ui][2], 0.5f);
213
214                 mark_draw();
215             }
216             glPopMatrix();
217         }
218     }
219     glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
220 }
221
222 static void game_draw_goals(const struct s_file *fp)
223 {
224     int zi;
225
226     for (zi = 0; zi < fp->zc; zi++)
227     {
228         glPushMatrix();
229         {
230             glTranslatef(fp->zv[zi].p[0],
231                          fp->zv[zi].p[1],
232                          fp->zv[zi].p[2]);
233             flag_draw();
234         }
235         glPopMatrix();
236     }
237 }
238
239 static void game_draw_jumps(const struct s_file *fp)
240 {
241     int ji;
242
243     for (ji = 0; ji < fp->jc; ji++)
244     {
245         glPushMatrix();
246         {
247             glTranslatef(fp->jv[ji].p[0],
248                          fp->jv[ji].p[1],
249                          fp->jv[ji].p[2]);
250
251             glScalef(fp->jv[ji].r, 1.f, fp->jv[ji].r);
252             jump_draw(!jump_e);
253         }
254         glPopMatrix();
255     }
256 }
257
258 static void game_draw_swchs(const struct s_file *fp)
259 {
260     int xi;
261
262     for (xi = 0; xi < fp->xc; xi++)
263     {
264         if (fp->xv[xi].i)
265             continue;
266
267         glPushMatrix();
268         {
269             glTranslatef(fp->xv[xi].p[0],
270                          fp->xv[xi].p[1],
271                          fp->xv[xi].p[2]);
272
273             glScalef(fp->xv[xi].r, 1.f, fp->xv[xi].r);
274             swch_draw(fp->xv[xi].f, fp->xv[xi].e);
275         }
276         glPopMatrix();
277     }
278 }
279
280 /*---------------------------------------------------------------------------*/
281
282 void game_draw(int pose, float t)
283 {
284     static const float a[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
285     static const float s[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
286     static const float e[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
287     static const float h[1] = { 0.0f };
288
289     const float light_p[4] = { 8.f, 32.f, 8.f, 1.f };
290
291     const struct s_file *fp = &file;
292
293     float fov = FOV;
294
295     if (jump_b) fov *= 2.0f * fabsf(jump_dt - 0.5f);
296
297     config_push_persp(fov, 0.1f, FAR_DIST);
298     glPushAttrib(GL_LIGHTING_BIT);
299     glPushMatrix();
300     {
301         float T[16], M[16], v[3], rx, ry;
302
303         m_view(T, view_c, view_p, view_e[1]);
304         m_xps(M, T);
305
306         v_sub(v, view_c, view_p);
307
308         rx = V_DEG(fatan2f(-v[1], fsqrtf(v[0] * v[0] + v[2] * v[2])));
309         ry = V_DEG(fatan2f(+v[0], -v[2]));
310
311         glTranslatef(0.f, 0.f, -v_len(v));
312         glMultMatrixf(M);
313         glTranslatef(-view_c[0], -view_c[1], -view_c[2]);
314
315         /* Center the skybox about the position of the camera. */
316
317         glPushMatrix();
318         {
319             glTranslatef(view_p[0], view_p[1], view_p[2]);
320             back_draw(0);
321         }
322         glPopMatrix();
323
324         glEnable(GL_LIGHT0);
325         glLightfv(GL_LIGHT0, GL_POSITION, light_p);
326
327         /* Draw the floor. */
328
329         sol_draw(fp, 0, 1);
330
331         if (config_get_d(CONFIG_SHADOW) && !pose)
332         {
333             shad_draw_set(fp->uv[ball].p, fp->uv[ball].r);
334             sol_shad(fp);
335             shad_draw_clr();
336         }
337
338         /* Draw the game elements. */
339
340         glEnable(GL_BLEND);
341         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
342
343         if (pose == 0)
344         {
345             game_draw_balls(fp, T, t);
346             game_draw_vect(fp);
347         }
348
349         glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT,   a);
350         glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,  s);
351         glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION,  e);
352         glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, h);
353
354         game_draw_goals(fp);
355
356         glEnable(GL_COLOR_MATERIAL);
357         glDisable(GL_LIGHTING);
358         glDisable(GL_TEXTURE_2D);
359         glDepthMask(GL_FALSE);
360         {
361             game_draw_jumps(fp);
362             game_draw_swchs(fp);
363         }
364         glDepthMask(GL_TRUE);
365         glEnable(GL_TEXTURE_2D);
366         glEnable(GL_LIGHTING);
367         glDisable(GL_COLOR_MATERIAL);
368     }
369     glPopMatrix();
370     glPopAttrib();
371     config_pop_matrix();
372 }
373
374 /*---------------------------------------------------------------------------*/
375
376 void game_update_view(float dt)
377 {
378     const float y[3] = { 0.f, 1.f, 0.f };
379
380     float dy;
381     float dz;
382     float k;
383     float e[3];
384     float d[3];
385     float s = 2.f * dt;
386
387     /* Center the view about the ball. */
388
389     v_cpy(view_c, file.uv[ball].p);
390     v_inv(view_v, file.uv[ball].v);
391
392     switch (config_get_d(CONFIG_CAMERA))
393     {
394     case 2:
395         /* Camera 2: View vector is given by view angle. */
396
397         view_e[2][0] = fsinf(V_RAD(view_a));
398         view_e[2][1] = 0.f;
399         view_e[2][2] = fcosf(V_RAD(view_a));
400
401         s = 1.f;
402         break;
403
404     default:
405         /* View vector approaches the ball velocity vector. */
406
407         v_mad(e, view_v, y, v_dot(view_v, y));
408         v_inv(e, e);
409
410         k = v_dot(view_v, view_v);
411
412         v_sub(view_e[2], view_p, view_c);
413         v_mad(view_e[2], view_e[2], view_v, k * dt * 0.1f);
414     }
415
416     /* Orthonormalize the basis of the view in its new position. */
417
418     v_crs(view_e[0], view_e[1], view_e[2]);
419     v_crs(view_e[2], view_e[0], view_e[1]);
420     v_nrm(view_e[0], view_e[0]);
421     v_nrm(view_e[2], view_e[2]);
422
423     /* The current view (dy, dz) approaches the ideal (view_dy, view_dz). */
424
425     v_sub(d, view_p, view_c);
426
427     dy = v_dot(view_e[1], d);
428     dz = v_dot(view_e[2], d);
429
430     dy += (view_dy - dy) * s;
431     dz += (view_dz - dz) * s;
432
433     /* Compute the new view position. */
434
435     view_p[0] = view_p[1] = view_p[2] = 0.f;
436
437     v_mad(view_p, view_c, view_e[1], dy);
438     v_mad(view_p, view_p, view_e[2], dz);
439
440     view_a = V_DEG(fatan2f(view_e[2][0], view_e[2][2]));
441 }
442
443 static int game_update_state(float dt)
444 {
445     static float t = 0.f;
446
447     struct s_file *fp = &file;
448     float p[3];
449
450     if (dt > 0.f)
451         t += dt;
452     else
453         t = 0.f;
454
455     /* Test for a switch. */
456
457     if (sol_swch_test(fp, ball))
458         audio_play(AUD_SWITCH, 1.f);
459
460     /* Test for a jump. */
461
462     if (jump_e == 1 && jump_b == 0 && sol_jump_test(fp, jump_p, ball) == 1)
463     {
464         jump_b  = 1;
465         jump_e  = 0;
466         jump_dt = 0.f;
467
468         audio_play(AUD_JUMP, 1.f);
469     }
470     if (jump_e == 0 && jump_b == 0 &&  sol_jump_test(fp, jump_p, ball) == 0)
471         jump_e = 1;
472
473     /* Test for fall-out. */
474
475     if (fp->uv[ball].p[1] < -10.f)
476         return GAME_FALL;
477
478     /* Test for a goal or stop. */
479
480     if (t > 1.f)
481     {
482         t = 0.f;
483
484         if (sol_goal_test(fp, p, ball))
485             return GAME_GOAL;
486         else
487             return GAME_STOP;
488     }
489
490     return GAME_NONE;
491 }
492
493 /*
494  * On  most  hardware, rendering  requires  much  more  computing power  than
495  * physics.  Since  physics takes less time  than graphics, it  make sense to
496  * detach  the physics update  time step  from the  graphics frame  rate.  By
497  * performing multiple physics updates for  each graphics update, we get away
498  * with higher quality physics with little impact on overall performance.
499  *
500  * Toward this  end, we establish a  baseline maximum physics  time step.  If
501  * the measured  frame time  exceeds this  maximum, we cut  the time  step in
502  * half, and  do two updates.  If THIS  time step exceeds the  maximum, we do
503  * four updates.  And  so on.  In this way, the physics  system is allowed to
504  * seek an optimal update rate independent of, yet in integral sync with, the
505  * graphics frame rate.
506  */
507
508 int game_step(const float g[3], float dt)
509 {
510     struct s_file *fp = &file;
511
512     static float s = 0.f;
513     static float t = 0.f;
514
515     float d = 0.f;
516     float b = 0.f;
517     float st = 0.f;
518     int i, n = 1, m = 0;
519
520     s = (7.f * s + dt) / 8.f;
521     t = s;
522
523     if (jump_b)
524     {
525         jump_dt += dt;
526
527         /* Handle a jump. */
528
529         if (0.5 < jump_dt)
530         {
531             fp->uv[ball].p[0] = jump_p[0];
532             fp->uv[ball].p[1] = jump_p[1];
533             fp->uv[ball].p[2] = jump_p[2];
534         }
535         if (1.f < jump_dt)
536             jump_b = 0;
537     }
538     else
539     {
540         /* Run the sim. */
541
542         while (t > MAX_DT && n < MAX_DN)
543         {
544             t /= 2;
545             n *= 2;
546         }
547
548         for (i = 0; i < n; i++)
549         {
550             d = sol_step(fp, g, t, ball, &m);
551
552             if (b < d)
553                 b = d;
554             if (m)
555                 st += t;
556         }
557
558         /* Mix the sound of a ball bounce. */
559
560         if (b > 0.5)
561             audio_play(AUD_BUMP, (float) (b - 0.5) * 2.0f);
562     }
563
564     game_update_view(dt);
565     return game_update_state(st);
566 }
567
568 void game_putt(void)
569 {
570     /*
571      * HACK: The BALL_FUDGE here  guarantees that a putt doesn't drive
572      * the ball  too directly down  toward a lump,  triggering rolling
573      * friction too early and stopping the ball prematurely.
574      */
575
576     file.uv[ball].v[0] = -4.f * view_e[2][0] * view_m;
577     file.uv[ball].v[1] = -4.f * view_e[2][1] * view_m + BALL_FUDGE;
578     file.uv[ball].v[2] = -4.f * view_e[2][2] * view_m;
579
580     view_m = 0.f;
581 }
582
583 /*---------------------------------------------------------------------------*/
584
585 void game_set_rot(int d)
586 {
587     view_a += (float) (30.f * d) / config_get_d(CONFIG_MOUSE_SENSE);
588 }
589
590 void game_clr_mag(void)
591 {
592     view_m = 1.f;
593 }
594
595 void game_set_mag(int d)
596 {
597     view_m -= (float) (1.f * d) / config_get_d(CONFIG_MOUSE_SENSE);
598
599     if (view_m < 0.25)
600         view_m = 0.25;
601 }
602
603 void game_set_fly(float k)
604 {
605     struct s_file *fp = &file;
606
607     float  x[3] = { 1.f, 0.f, 0.f };
608     float  y[3] = { 0.f, 1.f, 0.f };
609     float  z[3] = { 0.f, 0.f, 1.f };
610     float c0[3] = { 0.f, 0.f, 0.f };
611     float p0[3] = { 0.f, 0.f, 0.f };
612     float c1[3] = { 0.f, 0.f, 0.f };
613     float p1[3] = { 0.f, 0.f, 0.f };
614     float  v[3];
615
616     v_cpy(view_e[0], x);
617     v_cpy(view_e[1], y);
618     v_sub(view_e[2], fp->uv[ball].p, fp->zv[0].p);
619
620     if (fabs(v_dot(view_e[1], view_e[2])) > 0.999)
621         v_cpy(view_e[2], z);
622
623     v_crs(view_e[0], view_e[1], view_e[2]);
624     v_crs(view_e[2], view_e[0], view_e[1]);
625
626     v_nrm(view_e[0], view_e[0]);
627     v_nrm(view_e[2], view_e[2]);
628
629     /* k = 0.0 view is at the ball. */
630
631     if (fp->uc > 0)
632     {
633         v_cpy(c0, fp->uv[ball].p);
634         v_cpy(p0, fp->uv[ball].p);
635     }
636
637     v_mad(p0, p0, view_e[1], view_dy);
638     v_mad(p0, p0, view_e[2], view_dz);
639
640     /* k = +1.0 view is s_view 0 */
641
642     if (k >= 0 && fp->wc > 0)
643     {
644         v_cpy(p1, fp->wv[0].p);
645         v_cpy(c1, fp->wv[0].q);
646     }
647
648     /* k = -1.0 view is s_view 1 */
649
650     if (k <= 0 && fp->wc > 1)
651     {
652         v_cpy(p1, fp->wv[1].p);
653         v_cpy(c1, fp->wv[1].q);
654     }
655
656     /* Interpolate the views. */
657
658     v_sub(v, p1, p0);
659     v_mad(view_p, p0, v, k * k);
660
661     v_sub(v, c1, c0);
662     v_mad(view_c, c0, v, k * k);
663
664     /* Orthonormalize the view basis. */
665
666     v_sub(view_e[2], view_p, view_c);
667     v_crs(view_e[0], view_e[1], view_e[2]);
668     v_crs(view_e[2], view_e[0], view_e[1]);
669     v_nrm(view_e[0], view_e[0]);
670     v_nrm(view_e[2], view_e[2]);
671
672     view_a = V_DEG(fatan2f(view_e[2][0], view_e[2][2]));
673 }
674
675 void game_ball(int i)
676 {
677     int ui;
678
679     ball = i;
680
681     jump_e = 1;
682     jump_b = 0;
683
684     for (ui = 0; ui < file.uc; ui++)
685     {
686         file.uv[ui].v[0] = 0.f;
687         file.uv[ui].v[1] = 0.f;
688         file.uv[ui].v[2] = 0.f;
689
690         file.uv[ui].w[0] = 0.f;
691         file.uv[ui].w[1] = 0.f;
692         file.uv[ui].w[2] = 0.f;
693     }
694 }
695
696 void game_get_pos(float p[3], float e[3][3])
697 {
698     v_cpy(p,    file.uv[ball].p);
699     v_cpy(e[0], file.uv[ball].e[0]);
700     v_cpy(e[1], file.uv[ball].e[1]);
701     v_cpy(e[2], file.uv[ball].e[2]);
702 }
703
704 void game_set_pos(float p[3], float e[3][3])
705 {
706     v_cpy(file.uv[ball].p,    p);
707     v_cpy(file.uv[ball].e[0], e[0]);
708     v_cpy(file.uv[ball].e[1], e[1]);
709     v_cpy(file.uv[ball].e[2], e[2]);
710 }
711
712 /*---------------------------------------------------------------------------*/
713