Some OpenGL optimization and state-change reduction.
[neverball] / ball / game.c
index 615202e..e9917a2 100644 (file)
@@ -1,4 +1,4 @@
-/*   
+/*
  * Copyright (C) 2003 Robert Kooima
  *
  * NEVERBALL is  free software; you can redistribute  it and/or modify
@@ -23,7 +23,7 @@
 #include "part.h"
 #include "image.h"
 #include "audio.h"
-#include "solid.h"
+#include "solid_gl.h"
 #include "config.h"
 #include "binary.h"
 #include "level.h"
@@ -68,6 +68,111 @@ static float fade_d = 0.0;              /* Fade in/out direction             */
 
 /*---------------------------------------------------------------------------*/
 
+static int   grow = 0;                  /* Should the ball be changing size? */
+static float grow_orig = 0;             /* the original ball size            */
+static float grow_goal = 0;             /* how big or small to get!          */
+static float grow_t = 0.0;              /* timer for the ball to grow...     */
+static float grow_strt = 0;             /* starting value for growth         */
+static int   got_orig = 0;              /* Do we know original ball size?    */
+
+#define GROW_TIME  0.5f                 /* sec for the ball to get to size.  */
+#define GROW_BIG   1.5f                 /* large factor                      */
+#define GROW_SMALL 0.5f                 /* small factor                      */
+
+static int   grow_state = 0;            /* Current state (values -1, 0, +1)  */
+
+static void grow_init(const struct s_file *fp, int type)
+{
+    if (!got_orig)
+    {
+        grow_orig  = fp->uv->r;
+        grow_goal  = grow_orig;
+        grow_strt  = grow_orig;
+
+        grow_state = 0;
+
+        got_orig   = 1;
+    }
+
+    if (type == ITEM_SHRINK)
+    {
+        audio_play(AUD_SHRINK, 1.f);
+
+        switch (grow_state)
+        {
+        case -1:
+            break;
+
+        case  0:
+            grow_goal = grow_orig * GROW_SMALL;
+            grow_state = -1;
+            grow = 1;
+            break;
+
+        case +1:
+            grow_goal = grow_orig;
+            grow_state = 0;
+            grow = 1;
+            break;
+        }
+    }
+    else if (type == ITEM_GROW)
+    {
+        audio_play(AUD_GROW, 1.f);
+
+        switch (grow_state)
+        {
+        case -1:
+            grow_goal = grow_orig;
+            grow_state = 0;
+            grow = 1;
+            break;
+
+        case  0:
+            grow_goal = grow_orig * GROW_BIG;
+            grow_state = +1;
+            grow = 1;
+            break;
+
+        case +1:
+            break;
+        }
+    }
+
+    if (grow)
+    {
+        grow_t = 0.0;
+        grow_strt = fp->uv->r;
+    }
+}
+
+static void grow_step(const struct s_file *fp, float dt)
+{
+    float dr;
+
+    if (!grow)
+        return;
+
+    /* Calculate new size based on how long since you touched the coin... */
+
+    grow_t += dt;
+
+    if (grow_t >= GROW_TIME)
+    {
+        grow = 0;
+        grow_t = GROW_TIME;
+    }
+
+    dr = grow_strt + ((grow_goal-grow_strt) * (1.0f / (GROW_TIME / grow_t)));
+
+    /* No sinking through the floor! Keeps ball's bottom constant. */
+
+    fp->uv->p[1] += (dr - fp->uv->r);
+    fp->uv->r     =  dr;
+}
+
+/*---------------------------------------------------------------------------*/
+
 static void view_init(void)
 {
     view_a  = 0.f;
@@ -98,7 +203,7 @@ static void view_init(void)
     view_e[2][2] = 1.f;
 }
 
-int game_init(const struct level * level, int t, int g)
+int game_init(const struct level *level, int t, int g)
 {
     clock      = (float) t / 100.f;
     clock_down = (t > 0);
@@ -107,6 +212,13 @@ int game_init(const struct level * level, int t, int g)
     if (game_state)
         game_free();
 
+    if (!sol_load_gl(&file, config_data(level->file),
+                     config_get_d(CONFIG_TEXTURES),
+                     config_get_d(CONFIG_SHADOW)))
+        return (game_state = 0);
+
+    game_state = 1;
+
     game_ix = 0.f;
     game_iz = 0.f;
     game_rx = 0.f;
@@ -129,22 +241,23 @@ int game_init(const struct level * level, int t, int g)
     view_init();
     back_init(level->grad, config_get_d(CONFIG_GEOMETRY));
 
-    sol_load(&back, config_data(level->back),
-                   config_get_d(CONFIG_TEXTURES), 0);
+    sol_load_gl(&back, config_data(level->back),
+                config_get_d(CONFIG_TEXTURES), 0);
 
-    if (sol_load(&file, level->file,
-                 config_get_d(CONFIG_TEXTURES), config_get_d(CONFIG_SHADOW)))
-        return (game_state = 1);
-    else
-        return (game_state = 0);
+    /* Initialize ball size tracking... */
+
+    got_orig = 0;
+    grow = 0;
+
+    return game_state;
 }
 
 void game_free(void)
 {
     if (game_state)
     {
-        sol_free(&file);
-        sol_free(&back);
+        sol_free_gl(&file);
+        sol_free_gl(&back);
         back_free();
     }
     game_state = 0;
@@ -187,41 +300,101 @@ static void game_draw_balls(const struct s_file *fp)
                  fp->uv[0].r);
 
         glColor4fv(c);
-
         ball_draw();
     }
     glPopMatrix();
 }
 
-static void game_draw_coins(const struct s_file *fp)
+static void game_draw_items(const struct s_file *fp)
 {
     float r = 360.f * SDL_GetTicks() / 1000.f;
-    int ci;
+    int hi;
+
+    item_push(ITEM_COIN);
+    {
+        for (hi = 0; hi < fp->hc; hi++)
+
+            if (fp->hv[hi].t == ITEM_COIN && fp->hv[hi].n > 0)
+            {
+                glPushMatrix();
+                {
+                    glTranslatef(fp->hv[hi].p[0],
+                                 fp->hv[hi].p[1],
+                                 fp->hv[hi].p[2]);
+                    glRotatef(r, 0.0f, 1.0f, 0.0f);
+                    item_draw(&fp->hv[hi], r);
+                }
+                glPopMatrix();
+            }
+    }
+    item_pull();
+
+    item_push(ITEM_SHRINK);
+    {
+        for (hi = 0; hi < fp->hc; hi++)
+
+            if (fp->hv[hi].t == ITEM_SHRINK)
+            {
+                glPushMatrix();
+                {
+                    glTranslatef(fp->hv[hi].p[0],
+                                 fp->hv[hi].p[1],
+                                 fp->hv[hi].p[2]);
+                    glRotatef(r, 0.0f, 1.0f, 0.0f);
+                    item_draw(&fp->hv[hi], r);
+                }
+                glPopMatrix();
+            }
+    }
+    item_pull();
 
-    coin_push();
+    item_push(ITEM_GROW);
     {
-        for (ci = 0; ci < fp->cc; ci++)
-            if (fp->cv[ci].n > 0)
+        for (hi = 0; hi < fp->hc; hi++)
+
+            if (fp->hv[hi].t == ITEM_GROW)
             {
                 glPushMatrix();
                 {
-                    glTranslatef(fp->cv[ci].p[0],
-                                 fp->cv[ci].p[1],
-                                 fp->cv[ci].p[2]);
+                    glTranslatef(fp->hv[hi].p[0],
+                                 fp->hv[hi].p[1],
+                                 fp->hv[hi].p[2]);
                     glRotatef(r, 0.0f, 1.0f, 0.0f);
-                    coin_draw(fp->cv[ci].n, r);
+                    item_draw(&fp->hv[hi], r);
                 }
                 glPopMatrix();
             }
     }
-    coin_pull();
+    item_pull();
 }
 
 static void game_draw_goals(const struct s_file *fp, float rx, float ry)
 {
-    int zi;
-
     if (goal_c == 0)
+    {
+        int zi;
+
+        /* Draw the goal particles. */
+
+        glEnable(GL_TEXTURE_2D);
+        {
+            for (zi = 0; zi < fp->zc; zi++)
+            {
+                glPushMatrix();
+                {
+                    glTranslatef(fp->zv[zi].p[0],
+                                 fp->zv[zi].p[1],
+                                 fp->zv[zi].p[2]);
+                    
+                    part_draw_goal(rx, ry, fp->zv[zi].r, goal_k);
+                }
+                glPopMatrix();
+            }
+        }
+        glDisable(GL_TEXTURE_2D);
+
+        /* Draw the goal column. */
+
         for (zi = 0; zi < fp->zc; zi++)
         {
             glPushMatrix();
@@ -230,13 +403,15 @@ static void game_draw_goals(const struct s_file *fp, float rx, float ry)
                              fp->zv[zi].p[1],
                              fp->zv[zi].p[2]);
 
-                part_draw_goal(rx, ry, fp->zv[zi].r, goal_k, fp->zv[zi].c);
+                glScalef(fp->zv[zi].r,
+                         goal_k,
+                         fp->zv[zi].r);
 
-                glScalef(fp->zv[zi].r, goal_k, fp->zv[zi].r);
                 goal_draw();
             }
             glPopMatrix();
         }
+    }
 }
 
 static void game_draw_jumps(const struct s_file *fp)
@@ -250,8 +425,10 @@ static void game_draw_jumps(const struct s_file *fp)
             glTranslatef(fp->jv[ji].p[0],
                          fp->jv[ji].p[1],
                          fp->jv[ji].p[2]);
+            glScalef(fp->jv[ji].r,
+                     1.0f,
+                     fp->jv[ji].r);
 
-            glScalef(fp->jv[ji].r, 1.f, fp->jv[ji].r);
             jump_draw(!jump_e);
         }
         glPopMatrix();
@@ -264,15 +441,18 @@ static void game_draw_swchs(const struct s_file *fp)
 
     for (xi = 0; xi < fp->xc; xi++)
     {
-       if (fp->xv[xi].i)
-           continue;
+        if (fp->xv[xi].i)
+            continue;
+
         glPushMatrix();
         {
             glTranslatef(fp->xv[xi].p[0],
                          fp->xv[xi].p[1],
                          fp->xv[xi].p[2]);
+            glScalef(fp->xv[xi].r,
+                     1.0f,
+                     fp->xv[xi].r);
 
-            glScalef(fp->xv[xi].r, 1.f, fp->xv[xi].r);
             swch_draw(fp->xv[xi].f, fp->xv[xi].e);
         }
         glPopMatrix();
@@ -281,7 +461,7 @@ static void game_draw_swchs(const struct s_file *fp)
 
 /*---------------------------------------------------------------------------*/
 
-static void game_refl_all(int s)
+static void game_refl_all()
 {
     const float *ball_p = file.uv->p;
 
@@ -306,8 +486,8 @@ static void game_refl_all(int s)
 static void game_draw_light(void)
 {
     const float light_p[2][4] = {
-        { -8.0f, +32.0f, -8.0f, 1.0f },
-        { +8.0f, +32.0f, +8.0f, 1.0f },
+        { -8.0f, +32.0f, -8.0f, 0.0f },
+        { +8.0f, +32.0f, +8.0f, 0.0f },
     };
     const float light_c[2][4] = {
         { 1.0f, 0.8f, 0.8f, 1.0f },
@@ -347,79 +527,104 @@ static void game_draw_back(int pose, int d, const float p[3])
         {
             /* Draw all background layers back to front. */
 
-            sol_back(&back, BACK_DIST, FAR_DIST, t);
+            sol_back(&back, BACK_DIST, FAR_DIST,  t);
             back_draw(0);
-            sol_back(&back, 0, BACK_DIST, t);
+            sol_back(&back,         0, BACK_DIST, t);
 
             /* Draw all foreground geometry in the background file. */
 
+            /* HACK: This is never used.
             sol_draw(&back);
+            */
         }
         else back_draw(0);
     }
     glPopMatrix();
 }
 
-static void game_draw_fore(int pose, float rx, float ry, int d, const float p[3])
+static void game_draw_fore(int pose, float rx,
+                                     float ry, int d, const float p[3])
 {
+    static const float a[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
+    static const float s[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
+    static const float e[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
+    static const float h[1] = { 0.0f };
+    
     const float *ball_p = file.uv->p;
     const float  ball_r = file.uv->r;
-    
-    glPushAttrib(GL_LIGHTING_BIT | GL_COLOR_BUFFER_BIT);
+
+    glPushMatrix();
     {
-        glPushMatrix();
-        {
-            /* Rotate the environment about the position of the ball. */
+        /* Rotate the environment about the position of the ball. */
 
-            glTranslatef(+ball_p[0], +ball_p[1] * d, +ball_p[2]);
-            glRotatef(-game_rz * d, view_e[2][0], view_e[2][1], view_e[2][2]);
-            glRotatef(-game_rx * d, view_e[0][0], view_e[0][1], view_e[0][2]);
-            glTranslatef(-ball_p[0], -ball_p[1] * d, -ball_p[2]);
+        glTranslatef(+ball_p[0], +ball_p[1] * d, +ball_p[2]);
+        glRotatef(-game_rz * d, view_e[2][0], view_e[2][1], view_e[2][2]);
+        glRotatef(-game_rx * d, view_e[0][0], view_e[0][1], view_e[0][2]);
+        glTranslatef(-ball_p[0], -ball_p[1] * d, -ball_p[2]);
 
-            if (d < 0)
-            {
-                GLdouble e[4];
+        if (d < 0)
+        {
+            GLdouble e[4];
 
-                e[0] = +0;
-                e[1] = +1;
-                e[2] = +0;
-                e[3] = -0.00001;
+            e[0] = +0;
+            e[1] = +1;
+            e[2] = +0;
+            e[3] = -0.00001;
 
-                glEnable(GL_CLIP_PLANE0);
-                glClipPlane(GL_CLIP_PLANE0, e);
-            }
+            glEnable(GL_CLIP_PLANE0);
+            glClipPlane(GL_CLIP_PLANE0, e);
+        }
+
+        /* Draw the floor. */
 
-            /* Draw the floor. */
+        sol_draw(&file);
 
-            sol_draw(&file);
+        if (pose == 0)
+        {
+            /* Draw the ball shadow. */
 
-            if (config_get_d(CONFIG_SHADOW))
+            if (d > 0 && config_get_d(CONFIG_SHADOW))
             {
                 shad_draw_set(ball_p, ball_r);
                 sol_shad(&file);
                 shad_draw_clr();
             }
 
-            /* Draw the game elements. */
+            /* Draw the ball and coins. */
+
+            game_draw_items(&file);
+            game_draw_balls(&file);
+        }
 
-            glEnable(GL_BLEND);
-            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+        /* Draw the particles and light columns. */
 
-            if (pose == 0)
+        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT,   a);
+        glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR,  s);
+        glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION,  e);
+        glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, h);
+
+        glEnable(GL_COLOR_MATERIAL);
+        glDisable(GL_LIGHTING);
+        glDepthMask(GL_FALSE);
+        {
+            part_draw_coin(-rx * d, -ry);
+
+            glDisable(GL_TEXTURE_2D);
             {
-                part_draw_coin(-rx * d, -ry);
-                game_draw_coins(&file);
-                game_draw_balls(&file);
+                game_draw_goals(&file, -rx * d, -ry);
+                game_draw_jumps(&file);
+                game_draw_swchs(&file);
             }
-            game_draw_goals(&file, -rx * d, -ry);
-            game_draw_jumps(&file);
-            game_draw_swchs(&file);
+            glEnable(GL_TEXTURE_2D);
+        }
+        glDepthMask(GL_TRUE);
+        glEnable(GL_LIGHTING);
+        glDisable(GL_COLOR_MATERIAL);
 
+        if (d < 0)
             glDisable(GL_CLIP_PLANE0);
-        }
-        glPopMatrix();
     }
-    glPopAttrib();
+    glPopMatrix();
 }
 
 void game_draw(int pose, float st)
@@ -455,42 +660,43 @@ void game_draw(int pose, float st)
 
             if (config_get_d(CONFIG_REFLECTION))
             {
-                /* Draw the mirror only into the stencil buffer. */
-
-                glDisable(GL_DEPTH_TEST);
                 glEnable(GL_STENCIL_TEST);
-                glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF);
-                glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
-                glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+                {
+                    /* Draw the mirrors only into the stencil buffer. */
 
-                game_refl_all(0);
+                    glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF);
+                    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
+                    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+                    glDepthMask(GL_FALSE);
 
-                /* Draw the scene reflected into color and depth buffers. */
+                    game_refl_all();
 
-                glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
-                glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
-                glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);
-                glEnable(GL_DEPTH_TEST);
+                    glDepthMask(GL_TRUE);
+                    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+                    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+                    glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);
 
-                glFrontFace(GL_CW);
-                glPushMatrix();
-                {
-                    glScalef(+1.f, -1.f, +1.f);
+                    /* Draw the scene reflected into color and depth buffers. */
 
-                    game_draw_light();
-                    game_draw_back(pose,         -1, pdn);
-                    game_draw_fore(pose, rx, ry, -1, pdn);
-                }
-                glPopMatrix();
-                glFrontFace(GL_CCW);
+                    glFrontFace(GL_CW);
+                    glPushMatrix();
+                    {
+                        glScalef(+1.f, -1.f, +1.f);
 
+                        game_draw_light();
+                        game_draw_back(pose,         -1, pdn);
+                        game_draw_fore(pose, rx, ry, -1, pdn);
+                    }
+                    glPopMatrix();
+                    glFrontFace(GL_CCW);
+                }
                 glDisable(GL_STENCIL_TEST);
             }
 
             /* Draw the scene normally. */
 
             game_draw_light();
-            game_refl_all(pose ? 0 : config_get_d(CONFIG_SHADOW));
+            game_refl_all();
             game_draw_back(pose,         +1, pup);
             game_draw_fore(pose, rx, ry, +1, pup);
         }
@@ -620,36 +826,44 @@ static void game_update_time(float dt, int b)
     }
 }
 
-static int game_update_state(int *state_value)
+static int game_update_state(int bt)
 {
     struct s_file *fp = &file;
+    struct s_goal *zp;
+    struct s_item *hp;
+
     float p[3];
     float c[3];
-    int bt = state_value != NULL;
-    int n;
 
-    /* Test for a coin grab. */
-    
-    if (bt && (n = sol_coin_test(fp, p, COIN_RADIUS)) > 0)
+    /* Test for an item. */
+    if (bt && (hp = sol_item_test(fp, p, COIN_RADIUS)))
     {
-        coin_color(c, n);
+        const char *sound = AUD_COIN;
+
+        item_color(hp, c);
         part_burst(p, c);
 
-       coins += n;
-       /* Check for goal open. */
-       if (goal_c > 0)
-       {
-           goal_c = goal_c - n;
-           if (goal_c <= 0)
-           {
-               audio_play(AUD_SWITCH, 1.f);
-               goal_c = 0;
-           }
-           else
-               audio_play(AUD_COIN, 1.f);
-       } 
-       else
-           audio_play(AUD_COIN, 1.f);
+        grow_init(fp, hp->t);
+
+        if (hp->t == ITEM_COIN)
+        {
+            coins += hp->n;
+
+            /* Check for goal open. */
+            if (goal_c > 0)
+            {
+                goal_c -= hp->n;
+                if (goal_c <= 0)
+                {
+                    sound = AUD_SWITCH;
+                    goal_c = 0;
+                }
+            }
+        }
+        audio_play(sound, 1.f);
+
+        /* Reset item type. */
+        hp->t = ITEM_NONE;
     }
 
     /* Test for a switch. */
@@ -663,7 +877,7 @@ static int game_update_state(int *state_value)
         jump_b  = 1;
         jump_e  = 0;
         jump_dt = 0.f;
-        
+
         audio_play(AUD_JUMP, 1.f);
     }
     if (jump_e == 0 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 0)
@@ -671,22 +885,27 @@ static int game_update_state(int *state_value)
 
     /* Test for a goal. */
 
-    if (bt && goal_c == 0 && (n = sol_goal_test(fp, p, 0)))
+    if (bt && goal_c == 0 && (zp = sol_goal_test(fp, p, 0)))
     {
-       *state_value = n - 1;
-       audio_play(AUD_GOAL, 1.0f);
+        audio_play(AUD_GOAL, 1.0f);
         return GAME_GOAL;
     }
 
     /* Test for time-out. */
 
     if (bt && clock_down && clock <= 0.f)
+    {
+        audio_play(AUD_TIME, 1.0f);
         return GAME_TIME;
+    }
 
     /* Test for fall-out. */
 
     if (bt && fp->uv[0].p[1] < fp->vv[0].p[1])
+    {
+        audio_play(AUD_FALL, 1.0f);
         return GAME_FALL;
+    }
 
     return GAME_NONE;
 }
@@ -702,11 +921,11 @@ static int game_update_state(int *state_value)
  * the measured  frame time  exceeds this  maximum, we cut  the time  step in
  * half, and  do two updates.  If THIS  time step exceeds the  maximum, we do
  * four updates.  And  so on.  In this way, the physics  system is allowed to
- * seek an optimal update rate independant of, yet in integral sync with, the
+ * seek an optimal update rate independent of, yet in integral sync with, the
  * graphics frame rate.
  */
 
-int game_step(const float g[3], float dt, int *state_value)
+int game_step(const float g[3], float dt, int bt)
 {
     struct s_file *fp = &file;
 
@@ -733,6 +952,8 @@ int game_step(const float g[3], float dt, int *state_value)
             game_rz = game_iz;
         }
 
+        grow_step(fp, dt);
+
         game_update_grav(h, g);
         part_step(h, t);
 
@@ -768,14 +989,24 @@ int game_step(const float g[3], float dt, int *state_value)
             /* Mix the sound of a ball bounce. */
 
             if (b > 0.5)
-                audio_play(AUD_BUMP, (b - 0.5f) * 2.0f);
+            {
+                float k = (b - 0.5f) * 2.0f;
+
+                if (got_orig)
+                {
+                    if      (fp->uv->r > grow_orig) audio_play(AUD_BUMPL, k);
+                    else if (fp->uv->r < grow_orig) audio_play(AUD_BUMPS, k);
+                    else                            audio_play(AUD_BUMPM, k);
+                }
+                else audio_play(AUD_BUMPM, k);
+            }
         }
 
         game_step_fade(dt);
         game_update_view(dt);
-        game_update_time(dt, state_value != NULL);
+        game_update_time(dt, bt);
 
-        return game_update_state(state_value);
+        return game_update_state(bt);
     }
     return GAME_NONE;
 }
@@ -787,9 +1018,9 @@ void game_no_aa(void)
     float max = game_ix * game_ix + game_iz * game_iz;
     if (max > ANGLE_BOUND * ANGLE_BOUND)
     {
-       max = ANGLE_BOUND / sqrt(max);
-       game_ix *= max;
-       game_iz *= max;
+        max = ANGLE_BOUND / sqrt(max);
+        game_ix *= max;
+        game_iz *= max;
     }
 }
 
@@ -813,7 +1044,7 @@ void game_set_pos(int x, int y)
 {
     game_ix += 40.f * y / config_get_d(CONFIG_MOUSE_SENSE);
     game_iz += 40.f * x / config_get_d(CONFIG_MOUSE_SENSE);
-    
+
 #if NO_AA
     game_no_aa();
 #else
@@ -844,6 +1075,9 @@ void game_set_fly(float k)
     float p1[3] = { 0.f, 0.f, 0.f };
     float  v[3];
 
+    z[0] = fsinf(V_RAD(view_a));
+    z[2] = fcosf(V_RAD(view_a));
+
     v_cpy(view_e[0], x);
     v_cpy(view_e[1], y);
     v_cpy(view_e[2], z);