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