static void game_update_grav(float h[3], const float g[3]) { struct s_file *fp = &file; float x[3]; float y[3] = { 0.f, 1.f, 0.f }; float z[3]; float X[16]; float Z[16]; float M[16]; /* Compute the gravity vector from the given world rotations. */ v_sub(z, view_p, fp->uv->p); v_crs(x, y, z); v_crs(z, x, y); v_nrm(x, x); v_nrm(z, z); // m_rot (Z, z, V_RAD(game_rz)); m_rot (Z, z, V_RAD(game_rz*((20.0f/BOUND)))); // m_rot (X, x, V_RAD(game_rx)); m_rot (X, x, V_RAD(game_rx*((20.0f/BOUND)))); m_mult(M, Z, X); m_vxfm(h, M, g); }
/* * Integrate the rotation of the given basis E under angular velocity W * through time DT. */ void sol_rotate(float e[3][3], const float w[3], float dt) { if (v_len(w) > 0.0f) { float a[3], M[16], f[3][3]; /* Compute the rotation matrix. */ v_nrm(a, w); m_rot(M, a, v_len(w) * dt); /* Apply it to the basis. */ m_vxfm(f[0], M, e[0]); m_vxfm(f[1], M, e[1]); m_vxfm(f[2], M, e[2]); /* Re-orthonormalize the basis. */ v_crs(e[2], f[0], f[1]); v_crs(e[1], f[2], f[0]); v_crs(e[0], f[1], f[2]); v_nrm(e[0], e[0]); v_nrm(e[1], e[1]); v_nrm(e[2], e[2]); } }
void video_calc_view(float *M, const float *c, const float *p, const float *u) { float x[3]; float y[3]; float z[3]; v_sub(z, p, c); v_nrm(z, z); v_crs(x, u, z); v_nrm(x, x); v_crs(y, z, x); m_basis(M, x, y, z); }
/* * Compute the new linear and angular velocities of a bouncing ball. * Q gives the position of the point of impact and W gives the * velocity of the object being impacted. */ static float sol_bounce(struct v_ball *up, const float q[3], const float w[3], float dt) { float n[3], r[3], d[3], vn, wn; float *p = up->p; float *v = up->v; /* Find the normal of the impact. */ v_sub(r, p, q); v_sub(d, v, w); v_nrm(n, r); /* Find the new angular velocity. */ v_crs(up->w, d, r); v_scl(up->w, up->w, -1.0f / (up->r * up->r)); /* Find the new linear velocity. */ vn = v_dot(v, n); wn = v_dot(w, n); v_mad(v, v, n, 1.7 * (wn - vn)); v_mad(p, q, n, up->r); /* Return the "energy" of the impact, to determine the sound amplitude. */ return fabsf(v_dot(n, d)); }
/* * Compute the new angular velocity and orientation of a ball pendulum. * A gives the accelleration of the ball. G gives the gravity vector. */ void sol_pendulum(struct v_ball *up, const float a[3], const float g[3], float dt) { float v[3], A[3], F[3], r[3], Y[3], T[3] = { 0.0f, 0.0f, 0.0f }; const float m = 5.000f; const float ka = 0.500f; const float kd = 0.995f; /* Find the total force over DT. */ v_scl(A, a, ka); v_mad(A, A, g, -dt); /* Find the force. */ v_scl(F, A, m / dt); /* Find the position of the pendulum. */ v_scl(r, up->E[1], -up->r); /* Find the torque on the pendulum. */ if (fabsf(v_dot(r, F)) > 0.0f) v_crs(T, F, r); /* Apply the torque and dampen the angular velocity. */ v_mad(up->W, up->W, T, dt); v_scl(up->W, up->W, kd); /* Apply the angular velocity to the pendulum basis. */ sol_rotate(up->E, up->W, dt); /* Apply a torque turning the pendulum toward the ball velocity. */ v_mad(v, up->v, up->E[1], v_dot(up->v, up->E[1])); v_crs(Y, v, up->E[2]); v_scl(Y, up->E[1], 2 * v_dot(Y, up->E[1])); sol_rotate(up->E, Y, dt); }
void game_set_fly(float k) { struct s_vary *fp = &file.vary; float x[3] = { 1.f, 0.f, 0.f }; float y[3] = { 0.f, 1.f, 0.f }; float z[3] = { 0.f, 0.f, 1.f }; float c0[3] = { 0.f, 0.f, 0.f }; float p0[3] = { 0.f, 0.f, 0.f }; float c1[3] = { 0.f, 0.f, 0.f }; float p1[3] = { 0.f, 0.f, 0.f }; float v[3]; if (!state) return; v_cpy(view_e[0], x); v_cpy(view_e[1], y); if (fp->base->zc > 0) v_sub(view_e[2], fp->uv[ball].p, fp->base->zv[0].p); else v_cpy(view_e[2], z); if (fabs(v_dot(view_e[1], view_e[2])) > 0.999) v_cpy(view_e[2], z); v_crs(view_e[0], view_e[1], view_e[2]); v_crs(view_e[2], view_e[0], view_e[1]); v_nrm(view_e[0], view_e[0]); v_nrm(view_e[2], view_e[2]); /* k = 0.0 view is at the ball. */ if (fp->uc > 0) { v_cpy(c0, fp->uv[ball].p); v_cpy(p0, fp->uv[ball].p); } v_mad(p0, p0, view_e[1], view_dy); v_mad(p0, p0, view_e[2], view_dz); /* k = +1.0 view is s_view 0 */ if (k >= 0 && fp->base->wc > 0) { v_cpy(p1, fp->base->wv[0].p); v_cpy(c1, fp->base->wv[0].q); } /* k = -1.0 view is s_view 1 */ if (k <= 0 && fp->base->wc > 1) { v_cpy(p1, fp->base->wv[1].p); v_cpy(c1, fp->base->wv[1].q); } /* Interpolate the views. */ v_sub(v, p1, p0); v_mad(view_p, p0, v, k * k); v_sub(v, c1, c0); v_mad(view_c, c0, v, k * k); /* Orthonormalize the view basis. */ v_sub(view_e[2], view_p, view_c); v_crs(view_e[0], view_e[1], view_e[2]); v_crs(view_e[2], view_e[0], view_e[1]); v_nrm(view_e[0], view_e[0]); v_nrm(view_e[2], view_e[2]); view_a = V_DEG(fatan2f(view_e[2][0], view_e[2][2])); }
void game_update_view(float dt) { const float y[3] = { 0.f, 1.f, 0.f }; float dy; float dz; float k; float e[3]; float d[3]; float s = 2.f * dt; if (!state) return; /* Center the view about the ball. */ v_cpy(view_c, file.vary.uv[ball].p); v_inv(view_v, file.vary.uv[ball].v); switch (config_get_d(CONFIG_CAMERA)) { case 2: /* Camera 2: View vector is given by view angle. */ view_e[2][0] = fsinf(V_RAD(view_a)); view_e[2][1] = 0.f; view_e[2][2] = fcosf(V_RAD(view_a)); s = 1.f; break; default: /* View vector approaches the ball velocity vector. */ v_mad(e, view_v, y, v_dot(view_v, y)); v_inv(e, e); k = v_dot(view_v, view_v); v_sub(view_e[2], view_p, view_c); v_mad(view_e[2], view_e[2], view_v, k * dt * 0.1f); } /* Orthonormalize the basis of the view in its new position. */ v_crs(view_e[0], view_e[1], view_e[2]); v_crs(view_e[2], view_e[0], view_e[1]); v_nrm(view_e[0], view_e[0]); v_nrm(view_e[2], view_e[2]); /* The current view (dy, dz) approaches the ideal (view_dy, view_dz). */ v_sub(d, view_p, view_c); dy = v_dot(view_e[1], d); dz = v_dot(view_e[2], d); dy += (view_dy - dy) * s; dz += (view_dz - dz) * s; /* Compute the new view position. */ view_p[0] = view_p[1] = view_p[2] = 0.f; v_mad(view_p, view_c, view_e[1], dy); v_mad(view_p, view_p, view_e[2], dz); view_a = V_DEG(fatan2f(view_e[2][0], view_e[2][2])); }
static void game_run_cmd(const union cmd *cmd) { if (gd.state) { struct game_view *view = &gl.view[CURR]; struct game_tilt *tilt = &gl.tilt[CURR]; struct s_vary *vary = &gd.vary; struct v_item *hp; float v[4]; float dt; int idx; if (cs.next_update) { game_lerp_copy(&gl); cs.next_update = 0; } switch (cmd->type) { case CMD_END_OF_UPDATE: cs.got_tilt_axes = 0; cs.next_update = 1; if (cs.first_update) { game_lerp_copy(&gl); /* Hack to sync state before the next update. */ game_lerp_apply(&gl, &gd); cs.first_update = 0; break; } /* Compute gravity for particle effects. */ if (status == GAME_GOAL) game_tilt_grav(v, GRAVITY_UP, tilt); else game_tilt_grav(v, GRAVITY_DN, tilt); /* Step particle, goal and jump effects. */ if (cs.ups > 0) { dt = 1.0f / cs.ups; if (gd.goal_e && gl.goal_k[CURR] < 1.0f) gl.goal_k[CURR] += dt; if (gd.jump_b) { gl.jump_dt[CURR] += dt; if (gl.jump_dt[PREV] >= 1.0f) gd.jump_b = 0; } part_step(v, dt); } break; case CMD_MAKE_BALL: sol_lerp_cmd(&gl.lerp, &cs, cmd); break; case CMD_MAKE_ITEM: /* Allocate and initialize a new item. */ if ((hp = realloc(vary->hv, sizeof (*hp) * (vary->hc + 1)))) { vary->hv = hp; hp = &vary->hv[vary->hc]; vary->hc++; memset(hp, 0, sizeof (*hp)); v_cpy(hp->p, cmd->mkitem.p); hp->t = cmd->mkitem.t; hp->n = cmd->mkitem.n; } break; case CMD_PICK_ITEM: /* Set up particle effects and discard the item. */ if ((idx = cmd->pkitem.hi) >= 0 && idx < vary->hc) { hp = &vary->hv[idx]; item_color(hp, v); part_burst(hp->p, v); hp->t = ITEM_NONE; } break; case CMD_TILT_ANGLES: if (!cs.got_tilt_axes) { /* * Neverball <= 1.5.1 does not send explicit tilt * axes, rotation happens directly around view * vectors. So for compatibility if at the time of * receiving tilt angles we have not yet received the * tilt axes, we use the view vectors. */ game_tilt_axes(tilt, view->e); } tilt->rx = cmd->tiltangles.x; tilt->rz = cmd->tiltangles.z; break; case CMD_SOUND: /* Play the sound. */ if (cmd->sound.n) audio_play(cmd->sound.n, cmd->sound.a); break; case CMD_TIMER: timer = cmd->timer.t; break; case CMD_STATUS: status = cmd->status.t; break; case CMD_COINS: coins = cmd->coins.n; break; case CMD_JUMP_ENTER: gd.jump_b = 1; gd.jump_e = 0; gl.jump_dt[PREV] = 0.0f; gl.jump_dt[CURR] = 0.0f; break; case CMD_JUMP_EXIT: gd.jump_e = 1; break; case CMD_MOVE_PATH: case CMD_MOVE_TIME: case CMD_BODY_PATH: case CMD_BODY_TIME: sol_lerp_cmd(&gl.lerp, &cs, cmd); break; case CMD_GOAL_OPEN: /* * Enable the goal and make sure it's fully visible if * this is the first update. */ if (!gd.goal_e) { gd.goal_e = 1; gl.goal_k[CURR] = cs.first_update ? 1.0f : 0.0f; } break; case CMD_SWCH_ENTER: if ((idx = cmd->swchenter.xi) >= 0 && idx < vary->xc) vary->xv[idx].e = 1; break; case CMD_SWCH_TOGGLE: if ((idx = cmd->swchtoggle.xi) >= 0 && idx < vary->xc) vary->xv[idx].f = !vary->xv[idx].f; break; case CMD_SWCH_EXIT: if ((idx = cmd->swchexit.xi) >= 0 && idx < vary->xc) vary->xv[idx].e = 0; break; case CMD_UPDATES_PER_SECOND: cs.ups = cmd->ups.n; break; case CMD_BALL_RADIUS: sol_lerp_cmd(&gl.lerp, &cs, cmd); break; case CMD_CLEAR_ITEMS: free(vary->hv); vary->hv = NULL; vary->hc = 0; break; case CMD_CLEAR_BALLS: sol_lerp_cmd(&gl.lerp, &cs, cmd); break; case CMD_BALL_POSITION: sol_lerp_cmd(&gl.lerp, &cs, cmd); break; case CMD_BALL_BASIS: sol_lerp_cmd(&gl.lerp, &cs, cmd); break; case CMD_BALL_PEND_BASIS: sol_lerp_cmd(&gl.lerp, &cs, cmd); break; case CMD_VIEW_POSITION: v_cpy(view->p, cmd->viewpos.p); break; case CMD_VIEW_CENTER: v_cpy(view->c, cmd->viewcenter.c); break; case CMD_VIEW_BASIS: v_cpy(view->e[0], cmd->viewbasis.e[0]); v_cpy(view->e[1], cmd->viewbasis.e[1]); v_crs(view->e[2], view->e[0], view->e[1]); break; case CMD_CURRENT_BALL: if ((idx = cmd->currball.ui) >= 0 && idx < vary->uc) cs.curr_ball = idx; break; case CMD_PATH_FLAG: if ((idx = cmd->pathflag.pi) >= 0 && idx < vary->pc) vary->pv[idx].f = cmd->pathflag.f; break; case CMD_STEP_SIMULATION: sol_lerp_cmd(&gl.lerp, &cs, cmd); break; case CMD_MAP: /* * Note a version (mis-)match between the loaded map and what * the server has. (This doesn't actually load a map.) */ game_compat_map = (version.x == cmd->map.version.x); break; case CMD_TILT_AXES: cs.got_tilt_axes = 1; v_cpy(tilt->x, cmd->tiltaxes.x); v_cpy(tilt->z, cmd->tiltaxes.z); break; case CMD_NONE: case CMD_MAX: break; } } }
float sol_step(struct s_vary *vary, const float *g, float dt, int ui, int *m) { float P[3], V[3], v[3], r[3], a[3], d, e, nt, b = 0.0f, tt = dt, t; int c; union cmd cmd; if (ui < vary->uc) { struct v_ball *up = vary->uv + ui; /* If the ball is in contact with a surface, apply friction. */ v_cpy(a, up->v); v_cpy(v, up->v); v_cpy(up->v, g); if (m && sol_test_file(tt, P, V, up, vary) < 0.0005f) { v_cpy(up->v, v); v_sub(r, P, up->p); t = v_len(r) * v_len(g); if (t == 0.f) { t = SMALL; } if ((d = v_dot(r, g) / t) > 0.999f) { if ((e = (v_len(up->v) - dt)) > 0.0f) { /* Scale the linear velocity. */ v_nrm(up->v, up->v); v_scl(up->v, up->v, e); /* Scale the angular velocity. */ v_sub(v, V, up->v); v_crs(up->w, v, r); v_scl(up->w, up->w, -1.0f / (up->r * up->r)); } else { /* Friction has brought the ball to a stop. */ up->v[0] = 0.0f; up->v[1] = 0.0f; up->v[2] = 0.0f; (*m)++; } } else v_mad(up->v, v, g, tt); } else v_mad(up->v, v, g, tt); /* Test for collision. */ for (c = 16; c > 0 && tt > 0; c--) { float st; int mi, ms; /* HACK: avoid stepping across path changes. */ st = tt; for (mi = 0; mi < vary->mc; mi++) { struct v_move *mp = vary->mv + mi; struct v_path *pp = vary->pv + mp->pi; if (!pp->f) continue; if (mp->tm + ms_peek(st) > pp->base->tm) st = MS_TO_TIME(pp->base->tm - mp->tm); } /* Miss collisions if we reach the iteration limit. */ if (c > 1) nt = sol_test_file(st, P, V, up, vary); else nt = tt; cmd.type = CMD_STEP_SIMULATION; cmd.stepsim.dt = nt; sol_cmd_enq(&cmd); ms = ms_step(nt); sol_move_step(vary, nt, ms); sol_swch_step(vary, nt, ms); sol_ball_step(vary, nt); if (nt < st) if (b < (d = sol_bounce(up, P, V, nt))) b = d; tt -= nt; } v_sub(a, up->v, a); sol_pendulum(up, a, g, dt); } return b; }
int sol_lerp_cmd(struct s_lerp *fp, struct cmd_state *cs, const union cmd *cmd) { struct l_ball (*uv)[2]; struct l_ball *up; int idx, mi, i; int rc = 0; switch (cmd->type) { case CMD_MAKE_BALL: if ((uv = realloc(fp->uv, sizeof (*uv) * (fp->uc + 1)))) { fp->uv = uv; /* Update varying state. */ if (sol_vary_cmd(fp->vary, cs, cmd)) { fp->uc++; rc = 1; } } break; case CMD_MOVE_PATH: if ((mi = cmd->movepath.mi) >= 0 && mi < fp->mc) { /* Be extra paranoid. */ if ((idx = cmd->movepath.pi) >= 0 && idx < fp->vary->base->pc) fp->mv[mi][CURR].pi = idx; } break; case CMD_MOVE_TIME: if ((mi = cmd->movetime.mi) >= 0 && mi < fp->mc) { fp->mv[mi][CURR].t = cmd->movetime.t; } break; case CMD_BODY_PATH: /* Backward compatibility: update linear mover only. */ if ((idx = cmd->bodypath.bi) >= 0 && idx < fp->vary->bc && (mi = fp->vary->bv[idx].mi) >= 0) { /* Be EXTRA paranoid. */ if ((idx = cmd->bodypath.pi) >= 0 && idx < fp->vary->base->pc) fp->mv[mi][CURR].pi = idx; } break; case CMD_BODY_TIME: /* Same as CMD_BODY_PATH. */ if ((idx = cmd->bodytime.bi) >= 0 && idx < fp->vary->bc && (mi = fp->vary->bv[idx].mi) >= 0) { fp->mv[mi][CURR].t = cmd->bodytime.t; } break; case CMD_BALL_RADIUS: fp->uv[cs->curr_ball][CURR].r = cmd->ballradius.r; break; case CMD_CLEAR_BALLS: free(fp->uv); fp->uv = NULL; fp->uc = 0; sol_vary_cmd(fp->vary, cs, cmd); break; case CMD_BALL_POSITION: up = &fp->uv[cs->curr_ball][CURR]; v_cpy(up->p, cmd->ballpos.p); break; case CMD_BALL_BASIS: up = &fp->uv[cs->curr_ball][CURR]; v_cpy(up->e[0], cmd->ballbasis.e[0]); v_cpy(up->e[1], cmd->ballbasis.e[1]); v_crs(up->e[2], up->e[0], up->e[1]); break; case CMD_BALL_PEND_BASIS: up = &fp->uv[cs->curr_ball][CURR]; v_cpy(up->E[0], cmd->ballpendbasis.E[0]); v_cpy(up->E[1], cmd->ballpendbasis.E[1]); v_crs(up->E[2], up->E[0], up->E[1]); break; case CMD_STEP_SIMULATION: /* * Step each mover ahead. This way we cut down on replay size * significantly while still keeping things in sync with * occasional CMD_MOVE_PATH and CMD_MOVE_TIME. */ for (i = 0; i < fp->mc; i++) { struct l_move *mp = &fp->mv[i][CURR]; if (mp->pi >= 0 && fp->vary->pv[mp->pi].f) mp->t += cmd->stepsim.dt; } break; default: break; } return rc; }
void game_set_fly(float k) { struct s_file *fp = &file; float x[3] = { 1.f, 0.f, 0.f }; float y[3] = { 0.f, 1.f, 0.f }; float z[3] = { 0.f, 0.f, 1.f }; float c0[3] = { 0.f, 0.f, 0.f }; float p0[3] = { 0.f, 0.f, 0.f }; float c1[3] = { 0.f, 0.f, 0.f }; float p1[3] = { 0.f, 0.f, 0.f }; float v[3]; v_cpy(view_e[0], x); v_cpy(view_e[1], y); v_cpy(view_e[2], z); /* k = 0.0 view is at the ball. */ if (fp->uc > 0) { v_cpy(c0, fp->uv[0].p); v_cpy(p0, fp->uv[0].p); } v_mad(p0, p0, y, view_dp); v_mad(p0, p0, z, view_dz); v_mad(c0, c0, y, view_dc); /* k = +1.0 view is s_view 0 */ if (k >= 0 && fp->wc > 0) { v_cpy(p1, fp->wv[0].p); v_cpy(c1, fp->wv[0].q); } /* k = -1.0 view is s_view 1 */ if (k <= 0 && fp->wc > 1) { v_cpy(p1, fp->wv[1].p); v_cpy(c1, fp->wv[1].q); } /* Interpolate the views. */ v_sub(v, p1, p0); v_mad(view_p, p0, v, k * k); v_sub(v, c1, c0); v_mad(view_c, c0, v, k * k); /* Orthonormalize the view basis. */ v_sub(view_e[2], view_p, view_c); v_crs(view_e[0], view_e[1], view_e[2]); v_crs(view_e[2], view_e[0], view_e[1]); v_nrm(view_e[0], view_e[0]); v_nrm(view_e[2], view_e[2]); }
static void game_update_view(float dt) { float dc = view_dc * (jump_b ? 2.0f * fabsf(jump_dt - 0.5f) : 1.0f); float dx = view_ry * dt * 5.0f; float k; view_a += view_ry * dt * 90.f; /* Center the view about the ball. */ v_cpy(view_c, file.uv->p); v_inv(view_v, file.uv->v); switch (config_get_d(CONFIG_CAMERA)) { case 1: /* Camera 1: Viewpoint chases the ball position. */ v_sub(view_e[2], view_p, view_c); break; case 2: /* Camera 2: View vector is given by view angle. */ view_e[2][0] = fsinf(V_RAD(view_a)); view_e[2][1] = 0.f; view_e[2][2] = fcosf(V_RAD(view_a)); dx = 0.0f; break; default: /* Default: View vector approaches the ball velocity vector. */ k = v_dot(view_v, view_v); v_sub(view_e[2], view_p, view_c); v_mad(view_e[2], view_e[2], view_v, k * dt / 4); break; } /* Orthonormalize the basis of the view in its new position. */ v_crs(view_e[0], view_e[1], view_e[2]); v_crs(view_e[2], view_e[0], view_e[1]); v_nrm(view_e[0], view_e[0]); v_nrm(view_e[2], view_e[2]); /* Compute the new view position. */ k = 1.0f + v_dot(view_e[2], view_v) / 10.0f; view_k = view_k + (k - view_k) * dt; if (view_k < 0.5) view_k = 0.5; v_cpy(view_p, file.uv->p); v_mad(view_p, view_p, view_e[0], dx * view_k); v_mad(view_p, view_p, view_e[1], view_dp * view_k); v_mad(view_p, view_p, view_e[2], view_dz * view_k); /* Compute the new view center. */ v_cpy(view_c, file.uv->p); v_mad(view_c, view_c, view_e[1], dc); /* Note the current view angle. */ view_a = V_DEG(fatan2f(view_e[2][0], view_e[2][2])); }
static void game_update_view(float dt) { float dc = view.dc * (jump_b > 0 ? 2.0f * fabsf(jump_dt - 0.5f) : 1.0f); float da = input_get_r() * dt * 90.0f; float k; float M[16], v[3], Y[3] = { 0.0f, 1.0f, 0.0f }; float view_v[3]; float spd = (float) cam_speed(input_get_c()) / 1000.0f; /* Track manual rotation time. */ if (da == 0.0f) { if (view_time < 0.0f) { /* Transition time is influenced by activity time. */ view_fade = CLAMP(VIEW_FADE_MIN, -view_time, VIEW_FADE_MAX); view_time = 0.0f; } /* Inactivity. */ view_time += dt; } else { if (view_time > 0.0f) { view_fade = 0.0f; view_time = 0.0f; } /* Activity (yes, this is negative). */ view_time -= dt; } /* Center the view about the ball. */ v_cpy(view.c, vary.uv->p); view_v[0] = -vary.uv->v[0]; view_v[1] = 0.0f; view_v[2] = -vary.uv->v[2]; /* Compute view vector. */ if (spd >= 0.0f) { /* Viewpoint chases ball position. */ if (da == 0.0f) { float s; v_sub(view.e[2], view.p, view.c); v_nrm(view.e[2], view.e[2]); /* Gradually restore view vector convergence rate. */ s = fpowf(view_time, 3.0f) / fpowf(view_fade, 3.0f); s = CLAMP(0.0f, s, 1.0f); v_mad(view.e[2], view.e[2], view_v, v_len(view_v) * spd * s * dt); } } else { /* View vector is given by view angle. */ view.e[2][0] = fsinf(V_RAD(view.a)); view.e[2][1] = 0.0; view.e[2][2] = fcosf(V_RAD(view.a)); } /* Apply manual rotation. */ if (da != 0.0f) { m_rot(M, Y, V_RAD(da)); m_vxfm(v, M, view.e[2]); v_cpy(view.e[2], v); } /* Orthonormalize the new view reference frame. */ v_crs(view.e[0], view.e[1], view.e[2]); v_crs(view.e[2], view.e[0], view.e[1]); v_nrm(view.e[0], view.e[0]); v_nrm(view.e[2], view.e[2]); /* Compute the new view position. */ k = 1.0f + v_dot(view.e[2], view_v) / 10.0f; view_k = view_k + (k - view_k) * dt; if (view_k < 0.5) view_k = 0.5; v_scl(v, view.e[1], view.dp * view_k); v_mad(v, v, view.e[2], view.dz * view_k); v_add(view.p, v, vary.uv->p); /* Compute the new view center. */ v_cpy(view.c, vary.uv->p); v_mad(view.c, view.c, view.e[1], dc); /* Note the current view angle. */ view.a = V_DEG(fatan2f(view.e[2][0], view.e[2][2])); game_cmd_updview(); }