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