66800028b9743b729962cac033f14ac56873cc1c
[neverball] / ball / game_server.c
1 /*
2  * Copyright (C) 2003 Robert Kooima
3  *
4  * NEVERBALL 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 #include <assert.h>
18
19 #include "vec3.h"
20 #include "item.h"
21 #include "solid_phys.h"
22 #include "config.h"
23 #include "binary.h"
24 #include "common.h"
25
26 #include "game_common.h"
27 #include "game_server.h"
28 #include "game_proxy.h"
29
30 #include "cmd.h"
31
32 /*---------------------------------------------------------------------------*/
33
34 static int server_state = 0;
35
36 static struct s_file file;
37
38 static float timer      = 0.f;          /* Clock time                        */
39 static int   timer_down = 1;            /* Timer go up or down?              */
40
41 static int status = GAME_NONE;          /* Outcome of the game               */
42
43 static float game_rx;                   /* Floor rotation about X axis       */
44 static float game_rz;                   /* Floor rotation about Z axis       */
45
46 static float view_a;                    /* Ideal view rotation about Y axis  */
47 static float view_dc;                   /* Ideal view distance above ball    */
48 static float view_dp;                   /* Ideal view distance above ball    */
49 static float view_dz;                   /* Ideal view distance behind ball   */
50
51 static float view_c[3];                 /* Current view center               */
52 static float view_v[3];                 /* Current view vector               */
53 static float view_p[3];                 /* Current view position             */
54 static float view_e[3][3];              /* Current view reference frame      */
55 static float view_k;
56
57 static int   coins  = 0;                /* Collected coins                   */
58 static int   goal_e = 0;                /* Goal enabled flag                 */
59 static float goal_k = 0;                /* Goal animation                    */
60 static int   jump_e = 1;                /* Jumping enabled flag              */
61 static int   jump_b = 0;                /* Jump-in-progress flag             */
62 static float jump_dt;                   /* Jump duration                     */
63 static float jump_p[3];                 /* Jump destination                  */
64
65 /*---------------------------------------------------------------------------*/
66
67 /*
68  * This is an abstraction of the game's input state.  All input is
69  * encapsulated here, and all references to the input by the game are
70  * made here.  TODO: This used to have the effect of homogenizing
71  * input for use in replay recording and playback, but it's not clear
72  * how relevant this approach is with the introduction of the command
73  * pipeline.
74  *
75  * x and z:
76  *     -32767 = -ANGLE_BOUND
77  *     +32767 = +ANGLE_BOUND
78  *
79  * r:
80  *     -32767 = -VIEWR_BOUND
81  *     +32767 = +VIEWR_BOUND
82  *
83  */
84
85 struct input
86 {
87     short x;
88     short z;
89     short r;
90     short c;
91 };
92
93 static struct input input_current;
94
95 static void input_init(void)
96 {
97     input_current.x = 0;
98     input_current.z = 0;
99     input_current.r = 0;
100     input_current.c = 0;
101 }
102
103 static void input_set_x(float x)
104 {
105     if (x < -ANGLE_BOUND) x = -ANGLE_BOUND;
106     if (x >  ANGLE_BOUND) x =  ANGLE_BOUND;
107
108     input_current.x = (short) (32767.0f * x / ANGLE_BOUND);
109 }
110
111 static void input_set_z(float z)
112 {
113     if (z < -ANGLE_BOUND) z = -ANGLE_BOUND;
114     if (z >  ANGLE_BOUND) z =  ANGLE_BOUND;
115
116     input_current.z = (short) (32767.0f * z / ANGLE_BOUND);
117 }
118
119 static void input_set_r(float r)
120 {
121     if (r < -VIEWR_BOUND) r = -VIEWR_BOUND;
122     if (r >  VIEWR_BOUND) r =  VIEWR_BOUND;
123
124     input_current.r = (short) (32767.0f * r / VIEWR_BOUND);
125 }
126
127 static void input_set_c(int c)
128 {
129     input_current.c = (short) c;
130 }
131
132 static float input_get_x(void)
133 {
134     return ANGLE_BOUND * (float) input_current.x / 32767.0f;
135 }
136
137 static float input_get_z(void)
138 {
139     return ANGLE_BOUND * (float) input_current.z / 32767.0f;
140 }
141
142 static float input_get_r(void)
143 {
144     return VIEWR_BOUND * (float) input_current.r / 32767.0f;
145 }
146
147 static int input_get_c(void)
148 {
149     return (int) input_current.c;
150 }
151
152 int input_put(FILE *fout)
153 {
154     if (server_state)
155     {
156         put_short(fout, &input_current.x);
157         put_short(fout, &input_current.z);
158         put_short(fout, &input_current.r);
159         put_short(fout, &input_current.c);
160
161         return 1;
162     }
163     return 0;
164 }
165
166 int input_get(FILE *fin)
167 {
168     if (server_state)
169     {
170         get_short(fin, &input_current.x);
171         get_short(fin, &input_current.z);
172         get_short(fin, &input_current.r);
173         get_short(fin, &input_current.c);
174
175         return (feof(fin) ? 0 : 1);
176     }
177     return 0;
178 }
179
180 /*---------------------------------------------------------------------------*/
181
182 /*
183  * Utility functions for preparing the "server" state and events for
184  * consumption by the "client".
185  */
186
187 static union cmd cmd;
188
189 static void game_cmd_eou(void)
190 {
191     cmd.type = CMD_END_OF_UPDATE;
192     game_proxy_enq(&cmd);
193 }
194
195 static void game_cmd_ups(void)
196 {
197     cmd.type  = CMD_UPDATES_PER_SECOND;
198     cmd.ups.n = UPS;
199     game_proxy_enq(&cmd);
200 }
201
202 static void game_cmd_sound(const char *filename, float a)
203 {
204     cmd.type = CMD_SOUND;
205
206     cmd.sound.n = strdup(filename);
207     cmd.sound.a = a;
208
209     game_proxy_enq(&cmd);
210 }
211
212 #define audio_play(s, f) game_cmd_sound((s), (f))
213
214 static void game_cmd_goalopen(void)
215 {
216     cmd.type = CMD_GOAL_OPEN;
217     game_proxy_enq(&cmd);
218 }
219
220 static void game_cmd_updball(void)
221 {
222     cmd.type = CMD_BALL_POSITION;
223     memcpy(cmd.ballpos.p, file.uv[0].p, sizeof (float) * 3);
224     game_proxy_enq(&cmd);
225
226     cmd.type = CMD_BALL_BASIS;
227     v_cpy(cmd.ballbasis.e[0], file.uv[0].e[0]);
228     v_cpy(cmd.ballbasis.e[1], file.uv[0].e[1]);
229     game_proxy_enq(&cmd);
230
231     cmd.type = CMD_BALL_PEND_BASIS;
232     v_cpy(cmd.ballpendbasis.E[0], file.uv[0].E[0]);
233     v_cpy(cmd.ballpendbasis.E[1], file.uv[0].E[1]);
234     game_proxy_enq(&cmd);
235 }
236
237 static void game_cmd_updview(void)
238 {
239     cmd.type = CMD_VIEW_POSITION;
240     memcpy(cmd.viewpos.p, view_p, sizeof (float) * 3);
241     game_proxy_enq(&cmd);
242
243     cmd.type = CMD_VIEW_CENTER;
244     memcpy(cmd.viewcenter.c, view_c, sizeof (float) * 3);
245     game_proxy_enq(&cmd);
246
247     cmd.type = CMD_VIEW_BASIS;
248     v_cpy(cmd.viewbasis.e[0], view_e[0]);
249     v_cpy(cmd.viewbasis.e[1], view_e[1]);
250     game_proxy_enq(&cmd);
251 }
252
253 static void game_cmd_ballradius(void)
254 {
255     cmd.type         = CMD_BALL_RADIUS;
256     cmd.ballradius.r = file.uv[0].r;
257     game_proxy_enq(&cmd);
258 }
259
260 static void game_cmd_init_balls(void)
261 {
262     cmd.type = CMD_CLEAR_BALLS;
263     game_proxy_enq(&cmd);
264
265     cmd.type = CMD_MAKE_BALL;
266     game_proxy_enq(&cmd);
267
268     game_cmd_updball();
269     game_cmd_ballradius();
270 }
271
272 static void game_cmd_init_items(void)
273 {
274     int i;
275
276     cmd.type = CMD_CLEAR_ITEMS;
277     game_proxy_enq(&cmd);
278
279     for (i = 0; i < file.hc; i++)
280     {
281         cmd.type = CMD_MAKE_ITEM;
282
283         v_cpy(cmd.mkitem.p, file.hv[i].p);
284
285         cmd.mkitem.t = file.hv[i].t;
286         cmd.mkitem.n = file.hv[i].n;
287
288         game_proxy_enq(&cmd);
289     }
290 }
291
292 static void game_cmd_pkitem(int hi)
293 {
294     cmd.type      = CMD_PICK_ITEM;
295     cmd.pkitem.hi = hi;
296     game_proxy_enq(&cmd);
297 }
298
299 static void game_cmd_jump(int e)
300 {
301     cmd.type = e ? CMD_JUMP_ENTER : CMD_JUMP_EXIT;
302     game_proxy_enq(&cmd);
303 }
304
305 static void game_cmd_rotate(void)
306 {
307     cmd.type = CMD_ROTATE;
308
309     cmd.rotate.x = game_rx;
310     cmd.rotate.z = game_rz;
311
312     game_proxy_enq(&cmd);
313 }
314
315 static void game_cmd_timer(void)
316 {
317     cmd.type    = CMD_TIMER;
318     cmd.timer.t = timer;
319     game_proxy_enq(&cmd);
320 }
321
322 static void game_cmd_coins(void)
323 {
324     cmd.type    = CMD_COINS;
325     cmd.coins.n = coins;
326     game_proxy_enq(&cmd);
327 }
328
329 static void game_cmd_status(void)
330 {
331     cmd.type     = CMD_STATUS;
332     cmd.status.t = status;
333     game_proxy_enq(&cmd);
334 }
335
336 /*---------------------------------------------------------------------------*/
337
338 static int   grow = 0;                  /* Should the ball be changing size? */
339 static float grow_orig = 0;             /* the original ball size            */
340 static float grow_goal = 0;             /* how big or small to get!          */
341 static float grow_t = 0.0;              /* timer for the ball to grow...     */
342 static float grow_strt = 0;             /* starting value for growth         */
343 static int   got_orig = 0;              /* Do we know original ball size?    */
344
345 #define GROW_TIME  0.5f                 /* sec for the ball to get to size.  */
346 #define GROW_BIG   1.5f                 /* large factor                      */
347 #define GROW_SMALL 0.5f                 /* small factor                      */
348
349 static int   grow_state = 0;            /* Current state (values -1, 0, +1)  */
350
351 static void grow_init(const struct s_file *fp, int type)
352 {
353     if (!got_orig)
354     {
355         grow_orig  = fp->uv->r;
356         grow_goal  = grow_orig;
357         grow_strt  = grow_orig;
358
359         grow_state = 0;
360
361         got_orig   = 1;
362     }
363
364     if (type == ITEM_SHRINK)
365     {
366         switch (grow_state)
367         {
368         case -1:
369             break;
370
371         case  0:
372             audio_play(AUD_SHRINK, 1.f);
373             grow_goal = grow_orig * GROW_SMALL;
374             grow_state = -1;
375             grow = 1;
376             break;
377
378         case +1:
379             audio_play(AUD_SHRINK, 1.f);
380             grow_goal = grow_orig;
381             grow_state = 0;
382             grow = 1;
383             break;
384         }
385     }
386     else if (type == ITEM_GROW)
387     {
388         switch (grow_state)
389         {
390         case -1:
391             audio_play(AUD_GROW, 1.f);
392             grow_goal = grow_orig;
393             grow_state = 0;
394             grow = 1;
395             break;
396
397         case  0:
398             audio_play(AUD_GROW, 1.f);
399             grow_goal = grow_orig * GROW_BIG;
400             grow_state = +1;
401             grow = 1;
402             break;
403
404         case +1:
405             break;
406         }
407     }
408
409     if (grow)
410     {
411         grow_t = 0.0;
412         grow_strt = fp->uv->r;
413     }
414 }
415
416 static void grow_step(const struct s_file *fp, float dt)
417 {
418     float dr;
419
420     if (!grow)
421         return;
422
423     /* Calculate new size based on how long since you touched the coin... */
424
425     grow_t += dt;
426
427     if (grow_t >= GROW_TIME)
428     {
429         grow = 0;
430         grow_t = GROW_TIME;
431     }
432
433     dr = grow_strt + ((grow_goal-grow_strt) * (1.0f / (GROW_TIME / grow_t)));
434
435     /* No sinking through the floor! Keeps ball's bottom constant. */
436
437     fp->uv->p[1] += (dr - fp->uv->r);
438     fp->uv->r     =  dr;
439
440     game_cmd_ballradius();
441 }
442
443 /*---------------------------------------------------------------------------*/
444
445 static void view_init(void)
446 {
447     view_dp  = (float) config_get_d(CONFIG_VIEW_DP) / 100.0f;
448     view_dc  = (float) config_get_d(CONFIG_VIEW_DC) / 100.0f;
449     view_dz  = (float) config_get_d(CONFIG_VIEW_DZ) / 100.0f;
450     view_k   = 1.0f;
451     view_a   = 0.0f;
452
453     view_c[0] = 0.f;
454     view_c[1] = view_dc;
455     view_c[2] = 0.f;
456
457     view_p[0] =     0.f;
458     view_p[1] = view_dp;
459     view_p[2] = view_dz;
460
461     view_e[0][0] = 1.f;
462     view_e[0][1] = 0.f;
463     view_e[0][2] = 0.f;
464     view_e[1][0] = 0.f;
465     view_e[1][1] = 1.f;
466     view_e[1][2] = 0.f;
467     view_e[2][0] = 0.f;
468     view_e[2][1] = 0.f;
469     view_e[2][2] = 1.f;
470 }
471
472 int game_server_init(const char *file_name, int t, int e)
473 {
474     timer      = (float) t / 100.f;
475     timer_down = (t > 0);
476     coins      = 0;
477     status     = GAME_NONE;
478
479     if (server_state)
480         game_server_free();
481
482     if (!sol_load_only_file(&file, config_data(file_name)))
483         return (server_state = 0);
484
485     server_state = 1;
486
487     input_init();
488
489     game_rx = 0.0f;
490     game_rz = 0.0f;
491
492     /* Initialize jump and goal states. */
493
494     jump_e = 1;
495     jump_b = 0;
496
497     goal_e = e ? 1    : 0;
498     goal_k = e ? 1.0f : 0.0f;
499
500     /* Initialize the view. */
501
502     view_init();
503
504     /* Initialize ball size tracking... */
505
506     got_orig = 0;
507     grow = 0;
508
509     sol_cmd_enq_func(game_proxy_enq);
510
511     /* Queue client commands. */
512
513     game_cmd_ups();
514     game_cmd_timer();
515
516     if (goal_e) game_cmd_goalopen();
517
518     game_cmd_init_balls();
519     game_cmd_init_items();
520     game_cmd_eou();
521
522     return server_state;
523 }
524
525 void game_server_free(void)
526 {
527     if (server_state)
528     {
529         sol_free(&file);
530         server_state = 0;
531     }
532 }
533
534 /*---------------------------------------------------------------------------*/
535
536 static void game_update_view(float dt)
537 {
538     float dc = view_dc * (jump_b ? 2.0f * fabsf(jump_dt - 0.5f) : 1.0f);
539     float da = input_get_r() * dt * 90.0f;
540     float k;
541
542     float M[16], v[3], Y[3] = { 0.0f, 1.0f, 0.0f };
543
544     /* Center the view about the ball. */
545
546     v_cpy(view_c, file.uv->p);
547     v_inv(view_v, file.uv->v);
548
549     switch (input_get_c())
550     {
551     case 1: /* Camera 1: Viewpoint chases the ball position. */
552
553         v_sub(view_e[2], view_p, view_c);
554
555         break;
556
557     case 2: /* Camera 2: View vector is given by view angle. */
558
559         view_e[2][0] = fsinf(V_RAD(view_a));
560         view_e[2][1] = 0.0;
561         view_e[2][2] = fcosf(V_RAD(view_a));
562
563         break;
564
565     default: /* Default: View vector approaches the ball velocity vector. */
566
567         v_sub(view_e[2], view_p, view_c);
568         v_nrm(view_e[2], view_e[2]);
569         v_mad(view_e[2], view_e[2], view_v, v_dot(view_v, view_v) * dt / 4);
570
571         break;
572     }
573
574     /* Apply manual rotation. */
575
576     m_rot(M, Y, V_RAD(da));
577     m_vxfm(view_e[2], M, view_e[2]);
578
579     /* Orthonormalize the new view reference frame. */
580
581     v_crs(view_e[0], view_e[1], view_e[2]);
582     v_crs(view_e[2], view_e[0], view_e[1]);
583     v_nrm(view_e[0], view_e[0]);
584     v_nrm(view_e[2], view_e[2]);
585
586     /* Compute the new view position. */
587
588     k = 1.0f + v_dot(view_e[2], view_v) / 10.0f;
589
590     view_k = view_k + (k - view_k) * dt;
591
592     if (view_k < 0.5) view_k = 0.5;
593
594     v_scl(v,    view_e[1], view_dp * view_k);
595     v_mad(v, v, view_e[2], view_dz * view_k);
596     v_add(view_p, v, file.uv->p);
597
598     /* Compute the new view center. */
599
600     v_cpy(view_c, file.uv->p);
601     v_mad(view_c, view_c, view_e[1], dc);
602
603     /* Note the current view angle. */
604
605     view_a = V_DEG(fatan2f(view_e[2][0], view_e[2][2]));
606
607     game_cmd_updview();
608 }
609
610 static void game_update_time(float dt, int b)
611 {
612     if (goal_e && goal_k < 1.0f)
613         goal_k += dt;
614
615    /* The ticking clock. */
616
617     if (b && timer_down)
618     {
619         if (timer < 600.f)
620             timer -= dt;
621         if (timer < 0.f)
622             timer = 0.f;
623     }
624     else if (b)
625     {
626         timer += dt;
627     }
628
629     if (b) game_cmd_timer();
630 }
631
632 static int game_update_state(int bt)
633 {
634     struct s_file *fp = &file;
635     struct s_goal *zp;
636     int hi;
637
638     float p[3];
639
640     /* Test for an item. */
641
642     if (bt && (hi = sol_item_test(fp, p, ITEM_RADIUS)) != -1)
643     {
644         struct s_item *hp = &file.hv[hi];
645
646         game_cmd_pkitem(hi);
647
648         grow_init(fp, hp->t);
649
650         if (hp->t == ITEM_COIN)
651         {
652             coins += hp->n;
653             game_cmd_coins();
654         }
655
656         audio_play(AUD_COIN, 1.f);
657
658         /* Discard item. */
659
660         hp->t = ITEM_NONE;
661     }
662
663     /* Test for a switch. */
664
665     if (sol_swch_test(fp, 0))
666         audio_play(AUD_SWITCH, 1.f);
667
668     /* Test for a jump. */
669
670     if (jump_e == 1 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 1)
671     {
672         jump_b  = 1;
673         jump_e  = 0;
674         jump_dt = 0.f;
675
676         audio_play(AUD_JUMP, 1.f);
677
678         game_cmd_jump(1);
679     }
680     if (jump_e == 0 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 0)
681     {
682         jump_e = 1;
683         game_cmd_jump(0);
684     }
685
686     /* Test for a goal. */
687
688     if (bt && goal_e && (zp = sol_goal_test(fp, p, 0)))
689     {
690         audio_play(AUD_GOAL, 1.0f);
691         return GAME_GOAL;
692     }
693
694     /* Test for time-out. */
695
696     if (bt && timer_down && timer <= 0.f)
697     {
698         audio_play(AUD_TIME, 1.0f);
699         return GAME_TIME;
700     }
701
702     /* Test for fall-out. */
703
704     if (bt && fp->uv[0].p[1] < fp->vv[0].p[1])
705     {
706         audio_play(AUD_FALL, 1.0f);
707         return GAME_FALL;
708     }
709
710     return GAME_NONE;
711 }
712
713 static int game_step(const float g[3], float dt, int bt)
714 {
715     if (server_state)
716     {
717         struct s_file *fp = &file;
718
719         float h[3];
720
721         /* Smooth jittery or discontinuous input. */
722
723         game_rx += (input_get_x() - game_rx) * dt / RESPONSE;
724         game_rz += (input_get_z() - game_rz) * dt / RESPONSE;
725
726         game_cmd_rotate();
727
728         grow_step(fp, dt);
729
730         game_comp_grav(h, g, view_a, game_rx, game_rz);
731
732         if (jump_b)
733         {
734             jump_dt += dt;
735
736             /* Handle a jump. */
737
738             if (0.5f < jump_dt)
739             {
740                 fp->uv[0].p[0] = jump_p[0];
741                 fp->uv[0].p[1] = jump_p[1];
742                 fp->uv[0].p[2] = jump_p[2];
743             }
744             if (1.0f < jump_dt)
745                 jump_b = 0;
746         }
747         else
748         {
749             /* Run the sim. */
750
751             float b = sol_step(fp, h, dt, 0, NULL);
752
753             /* Mix the sound of a ball bounce. */
754
755             if (b > 0.5f)
756             {
757                 float k = (b - 0.5f) * 2.0f;
758
759                 if (got_orig)
760                 {
761                     if      (fp->uv->r > grow_orig) audio_play(AUD_BUMPL, k);
762                     else if (fp->uv->r < grow_orig) audio_play(AUD_BUMPS, k);
763                     else                            audio_play(AUD_BUMPM, k);
764                 }
765                 else audio_play(AUD_BUMPM, k);
766             }
767         }
768
769         game_cmd_updball();
770
771         game_update_view(dt);
772         game_update_time(dt, bt);
773
774         return game_update_state(bt);
775     }
776     return GAME_NONE;
777 }
778
779 void game_server_step(float dt)
780 {
781     static const float gup[] = { 0.0f, +9.8f, 0.0f };
782     static const float gdn[] = { 0.0f, -9.8f, 0.0f };
783
784     switch (status)
785     {
786     case GAME_GOAL: game_step(gup, dt, 0); break;
787     case GAME_FALL: game_step(gdn, dt, 0); break;
788
789     case GAME_NONE:
790         if ((status = game_step(gdn, dt, 1)) != GAME_NONE)
791             game_cmd_status();
792         break;
793     }
794
795     game_cmd_eou();
796 }
797
798 /*---------------------------------------------------------------------------*/
799
800 void game_set_goal(void)
801 {
802     audio_play(AUD_SWITCH, 1.0f);
803     goal_e = 1;
804
805     game_cmd_goalopen();
806 }
807
808 void game_clr_goal(void)
809 {
810     goal_e = 0;
811 }
812
813 /*---------------------------------------------------------------------------*/
814
815 void game_set_x(int k)
816 {
817     input_set_x(-ANGLE_BOUND * k / JOY_MAX);
818 }
819
820 void game_set_z(int k)
821 {
822     input_set_z(+ANGLE_BOUND * k / JOY_MAX);
823 }
824
825 void game_set_ang(int x, int z)
826 {
827     input_set_x(x);
828     input_set_z(z);
829 }
830
831 void game_set_pos(int x, int y)
832 {
833     input_set_x(input_get_x() + 40.0f * y / config_get_d(CONFIG_MOUSE_SENSE));
834     input_set_z(input_get_z() + 40.0f * x / config_get_d(CONFIG_MOUSE_SENSE));
835 }
836
837 void game_set_cam(int c)
838 {
839     input_set_c(c);
840 }
841
842 void game_set_rot(float r)
843 {
844     input_set_r(r);
845 }
846
847 void game_set_fly(float k, const struct s_file *fp)
848 {
849     float  x[3] = { 1.f, 0.f, 0.f };
850     float  y[3] = { 0.f, 1.f, 0.f };
851     float  z[3] = { 0.f, 0.f, 1.f };
852     float c0[3] = { 0.f, 0.f, 0.f };
853     float p0[3] = { 0.f, 0.f, 0.f };
854     float c1[3] = { 0.f, 0.f, 0.f };
855     float p1[3] = { 0.f, 0.f, 0.f };
856     float  v[3];
857
858     if (!fp) fp = &file;
859
860     view_init();
861
862     z[0] = fsinf(V_RAD(view_a));
863     z[2] = fcosf(V_RAD(view_a));
864
865     v_cpy(view_e[0], x);
866     v_cpy(view_e[1], y);
867     v_cpy(view_e[2], z);
868
869     /* k = 0.0 view is at the ball. */
870
871     if (fp->uc > 0)
872     {
873         v_cpy(c0, fp->uv[0].p);
874         v_cpy(p0, fp->uv[0].p);
875     }
876
877     v_mad(p0, p0, y, view_dp);
878     v_mad(p0, p0, z, view_dz);
879     v_mad(c0, c0, y, view_dc);
880
881     /* k = +1.0 view is s_view 0 */
882
883     if (k >= 0 && fp->wc > 0)
884     {
885         v_cpy(p1, fp->wv[0].p);
886         v_cpy(c1, fp->wv[0].q);
887     }
888
889     /* k = -1.0 view is s_view 1 */
890
891     if (k <= 0 && fp->wc > 1)
892     {
893         v_cpy(p1, fp->wv[1].p);
894         v_cpy(c1, fp->wv[1].q);
895     }
896
897     /* Interpolate the views. */
898
899     v_sub(v, p1, p0);
900     v_mad(view_p, p0, v, k * k);
901
902     v_sub(v, c1, c0);
903     v_mad(view_c, c0, v, k * k);
904
905     /* Orthonormalize the view basis. */
906
907     v_sub(view_e[2], view_p, view_c);
908     v_crs(view_e[0], view_e[1], view_e[2]);
909     v_crs(view_e[2], view_e[0], view_e[1]);
910     v_nrm(view_e[0], view_e[0]);
911     v_nrm(view_e[2], view_e[2]);
912
913     game_cmd_updview();
914 }
915
916 /*---------------------------------------------------------------------------*/