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