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