/* XRACER (C) 1999-2000 Richard W.M. Jones <rich@annexia.org> and other AUTHORS
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: player.c,v 1.19 2000/03/19 23:48:47 rich Exp $
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>

#include "xracer.h"
#include "xracer-log.h"
#include "xracer-player.h"
#include "xracer-math.h"
#include "xracer-track.h"
#include "xracer-craft.h"
#include "xracer-powerup.h"
#include "xracer-game.h"

struct xrPlayer xrPilot;

struct xrPlayer *xrDrones;
int xrNrDrones;

struct xrPlayer *xrNetworkPlayers;
int xrNrNetworkPlayers;

static int mode;
static int nr_laps;

/* This matrix gives ideal distance between each point in the
 * craft, maintained by springs.
 */
static GLfloat spring_dist[3][3] = {
  { 0,    0.25, 0.25 },
  { 0.25, 0,    0.1  },
  { 0.25, 0.1,  0    }
};

/* Some constants. Since these can be modified in debug mode, they
 * are actually stored as variables. The physics of the game is
 * very sensitive to the actual values of these constants.
 */
static GLfloat gravity_force = 0.18; /* In 0.96.5, this was 0.09 */
static GLfloat spring_damping = 0.5; /* In 0.96.5, this was 0.1 */
static GLfloat momentum_damping = 0.9;
static GLfloat levitate_dist = 0.2; /* In 0.94, this was 4.0 */
static GLfloat levitate_damping = 0.5; /* In 0.96.5, this was 0.5 */
/* static GLfloat suction_factor = 1.0; - Note: unused */

#define OOBE_UP_FACTOR 0.1
#define OOBE_BACK_FACTOR 1.5

static void init_player_struct (struct xrPlayer *player, int nr_laps);
static void calculate_view (struct xrPlayer *player);

/* Program-level initializations. */
void
xrPlayerInit ()
{
}

/* At the start of a game, this function is called. */
void
xrPlayerStartGame (int _mode, int _nr_laps)
{
  GLfloat p[3];
  int seg;

  mode = _mode;
  nr_laps = _nr_laps;

  /* This is the only mode implemented so far. */
  xrLogAssert (mode == XR_GAME_MODE_TIME_TRIAL);

  /* In time trial mode, there are no drones or network players, only
   * the pilot.
   */
  xrNrDrones = 0;
  xrDrones = 0;
  xrNrNetworkPlayers = 0;
  xrNetworkPlayers = 0;

  init_player_struct (&xrPilot, nr_laps);

  /* Get the pilot's starting position from the track. */
  if (xrTrack->get_pilot_start_position)
    xrTrack->get_pilot_start_position (p);

  xrLog (LOG_DEBUG, "pilot_start_position: (%g, %g, %g)", p[0], p[1], p[2]);

  /* XXX Pilot always points towards +Y at the moment.
   * Need to use get_pilot_start_rotation instead.
   */
  xrPilot.posn[0][0] = p[0];
  xrPilot.posn[0][1] = p[1] + 0.25;
  xrPilot.posn[0][2] = p[2];
  xrPilot.posn[1][0] = p[0] - 0.05;
  xrPilot.posn[1][1] = p[1];
  xrPilot.posn[1][2] = p[2];
  xrPilot.posn[2][0] = p[0] + 0.05;
  xrPilot.posn[2][1] = p[1];
  xrPilot.posn[2][2] = p[2];

  memcpy (xrPilot.old_posn, xrPilot.posn, sizeof xrPilot.posn);

  /* Get the pilot's starting segment. */
  if (xrTrack->get_pilot_start_segment)
    {
      seg = xrTrack->get_pilot_start_segment ();

      xrPilot.seg[0] = xrPilot.seg[1] = xrPilot.seg[2] = seg;

      memcpy (xrPilot.old_seg, xrPilot.seg, sizeof xrPilot.seg);

      /* Remember start segment for the lap calculation code. */
      xrPilot.start_segment = seg;
      xrPilot.start_segment_minus_one = xrPilot.start_segment - 1;
      if (xrPilot.start_segment_minus_one < 0)
	xrPilot.start_segment_minus_one += xrTrack->get_nr_enterplanes ();
    }

  /* Give the pilot the default craft. */
  xrPilot.craft = xrCraftGetDefault ();

  /* Initialize viewpoint stuff. */
  calculate_view (&xrPilot);
}

/* This function is called to clean up at the end of a game. */
void
xrPlayerEndGame ()
{
  /* This is the only mode implemented so far. */
  xrLogAssert (mode == XR_GAME_MODE_TIME_TRIAL);

  /* Clean up lap times array. */
  free (xrPilot.lap_time);
}

/* Calculate the parameters used for gluLookAt and other things. */
static void
calculate_view (struct xrPlayer *player)
{
  GLfloat back_line[3], left_side[3], offset[3];

  /* Midpoint of back line == eye position. */
  xrMidpoint (player->posn[1], player->posn[2], player->backline_midpoint);

  /* Up is a vector at right angles to the back line and the
   * left and right sides. Use a cross product to calculate this.
   */
  back_line[0] = player->posn[2][0] - player->posn[1][0];
  back_line[1] = player->posn[2][1] - player->posn[1][1];
  back_line[2] = player->posn[2][2] - player->posn[1][2];
  left_side[0] = player->posn[0][0] - player->posn[1][0];
  left_side[1] = player->posn[0][1] - player->posn[1][1];
  left_side[2] = player->posn[0][2] - player->posn[1][2];
  xrCrossProduct (back_line, left_side, player->up);
  xrNormalize (player->up, player->up);

  /* Move back and upwards to form pilot view from OOBE. */
  offset[0] = (player->backline_midpoint[0] - player->posn[0][0])
    * OOBE_BACK_FACTOR
    + player->up[0]*OOBE_UP_FACTOR;
  offset[1] = (player->backline_midpoint[1] - player->posn[0][1])
    * OOBE_BACK_FACTOR
    + player->up[1]*OOBE_UP_FACTOR;
  offset[2] = (player->backline_midpoint[2] - player->posn[0][2])
    * OOBE_BACK_FACTOR
    + player->up[2]*OOBE_UP_FACTOR;

  player->eye_oobe[0] = player->backline_midpoint[0] + offset[0];
  player->eye_oobe[1] = player->backline_midpoint[1] + offset[1];
  player->eye_oobe[2] = player->backline_midpoint[2] + offset[2];

  player->centre_oobe[0] = player->posn[0][0] + offset[0];
  player->centre_oobe[1] = player->posn[0][1] + offset[1];
  player->centre_oobe[2] = player->posn[0][2] + offset[2];
}

/* The player has finished the game. */
static inline void
finished_game (struct xrPlayer *player)
{
  xrLogNotImpl ();
}

/* The player has begun a new lap. */
static inline void
start_new_lap (struct xrPlayer *player)
{
  /* THIS_LAP is the current lap. LAST_LAP is the lap that the player
   * just finished. Both numbers are integers, counting from 0. And
   * normally 0 <= N <= NR_LAPS.
   */
  int this_lap = player->current_lap;
  int last_lap = player->current_lap - 1;

  /* Record the time for the last lap. */
  player->lap_time[last_lap] = xrCurrentTime - player->start_of_lap_time;

  /* Reset clock for the current lap. */
  player->start_of_lap_time = xrCurrentTime;

  /* Completed the game? */
  if (this_lap == nr_laps)
    {
      finished_game (player);
    }
}

/* The player has just gone through the enter plane into a new
 * lap. This is *not necessarily the same* as them starting a
 * new lap, since they may have turned round and gone backwards.
 * To do things strictly per-new-lap, use start_new_lap function
 * instead.
 */
static inline void
increment_lap (struct xrPlayer *player)
{
  player->current_lap ++;
  if (player->current_lap > player->displayed_current_lap)
    {
      /* This is where the player *really* enters a new lap. */
      player->displayed_current_lap = player->current_lap;

      start_new_lap (player);
    }

  xrLog (LOG_DEBUG, "current_lap = %d, displayed_current_lap = %d",
	 player->current_lap, player->displayed_current_lap);
}

static inline void
decrement_lap (struct xrPlayer *player)
{
  player->current_lap --;

  xrLog (LOG_DEBUG, "current_lap = %d", player->current_lap);
}

/* This function is called when we actually hit or go through FACE.
 * We kill all acceleration, drop all go-faster points, reduce
 * the thrust. We then push the whole craft back onto the track.
 */

#define DUNK_MOMENTUM_PENALTY 0.33
#define DUNK_THRUST_PENALTY 0.33
#define DUNK_SHIELD_PENALTY 0.05

static inline void
dunk_face (struct xrPlayer *player)
{
  int p;

  xrLog (LOG_DEBUG, "dunk_face called");

  /* Drop all go-faster points. */
  player->faster = 1;

  /* Reduce speed. */
  if (player->thrust > player->craft->max_thrust * DUNK_THRUST_PENALTY)
    player->thrust = player->craft->max_thrust * DUNK_THRUST_PENALTY;

  /* Reduce shield. */
  if (!player->has_external_shield)
    {
      player->shield -= DUNK_SHIELD_PENALTY;
      if (player->shield < 0) player->shield = 0;
    }

  /* Reduce momentum. */
  for (p = 0; p < 3; ++p)
    {
      player->momentum[p][0] *= DUNK_MOMENTUM_PENALTY;
      player->momentum[p][1] *= DUNK_MOMENTUM_PENALTY;
      player->momentum[p][2] *= DUNK_MOMENTUM_PENALTY;
    }
}

static inline void
dunk_tube (struct xrPlayer *player)
{
  int p;

  xrLog (LOG_DEBUG, "dunk_tube called");

  /* Drop all go-faster points. */
  player->faster = 1;

  /* Reduce speed. */
  if (player->thrust > player->craft->max_thrust * DUNK_THRUST_PENALTY)
    player->thrust = player->craft->max_thrust * DUNK_THRUST_PENALTY;

  /* Reduce shield. */
  if (!player->has_external_shield)
    {
      player->shield -= DUNK_SHIELD_PENALTY;
      if (player->shield < 0) player->shield = 0;
    }

  /* Reduce momentum. */
  for (p = 0; p < 3; ++p)
    {
      player->momentum[p][0] *= DUNK_MOMENTUM_PENALTY;
      player->momentum[p][1] *= DUNK_MOMENTUM_PENALTY;
      player->momentum[p][2] *= DUNK_MOMENTUM_PENALTY;
    }
}

static inline void
gravity (GLfloat *accel, int p)
{
  accel[2] -= gravity_force * xrTimeInterval;
}

static inline void
faces (GLfloat *accel, const struct xrPlayer *player, int p,
       const struct xrTrackFace *nearest_face, GLfloat dist)
{
  if (nearest_face && dist < levitate_dist)
    {
      GLfloat t, force, forcev[3];

      t = (levitate_dist - dist) / levitate_dist;
      if (t < 0) t = 0; else if (t > 1) t = 1;
      force = levitate_damping * t * t;

#if 0
      xrLog (LOG_DEBUG, "contribution of face %p: %g",
	     nearest_face, force);
#endif

      forcev[0] = nearest_face->face_plane_coefficient[0];
      forcev[1] = nearest_face->face_plane_coefficient[1];
      forcev[2] = nearest_face->face_plane_coefficient[2];
      xrNormalize (forcev, forcev);

      force *= xrTimeInterval;
      accel[0] += forcev[0] * force;
      accel[1] += forcev[1] * force;
      accel[2] += forcev[2] * force;
    }
}

static inline void
springs (GLfloat *accel, const struct xrPlayer *player, int p)
{
  GLfloat ideal, dx, dy, dz, actual, n[3], force;
  int i;

  for (i = 0; i <= 2; ++i)
    if (i != p)
      {
	/* Ideal distance. */
	ideal = spring_dist[p][i];

	/* Actual distance. */
	dx = player->old_posn[p][0] - player->old_posn[i][0];
	dy = player->old_posn[p][1] - player->old_posn[i][1];
	dz = player->old_posn[p][2] - player->old_posn[i][2];
	actual = sqrt (dx*dx + dy*dy + dz*dz);

	/* Compute normal vector of force. */
	n[0] = player->old_posn[p][0] - player->old_posn[i][0];
	n[1] = player->old_posn[p][1] - player->old_posn[i][1];
	n[2] = player->old_posn[p][2] - player->old_posn[i][2];
	xrNormalize (n, n);

	/* Calculate force. */
	force = spring_damping * (ideal - actual);

	/* Apply force. */
	accel[0] += n[0] * force;
	accel[1] += n[1] * force;
	accel[2] += n[2] * force;
      }
}

static inline void
thrust (GLfloat *accel, const struct xrPlayer *player, int p,
	const GLfloat *thrust_vec)
{
  GLfloat factor = player->thrust * player->faster * xrTimeInterval;
  accel[0] += thrust_vec[0] * factor;
  accel[1] += thrust_vec[1] * factor;
  accel[2] += thrust_vec[2] * factor;
}

#if 0
/* If the craft has veered off the track, then this function
 * applies a strong force back towards the centre of the track.
 */
static inline void
apply_suction (GLfloat *accel, const struct xrPlayer *player, int p)
{
  GLfloat track_midpoint[3];
  GLfloat v[3], factor;

#if 0
  xrLog (LOG_DEBUG, "apply_suction called: p = %d", p);
#endif

  xrTrack->get_midpoint (player->old_seg[p], track_midpoint);

  /* Compute vector in direction of midpoint of track. */
  v[0] = track_midpoint[0] - player->old_posn[p][0];
  v[1] = track_midpoint[1] - player->old_posn[p][1];
  v[2] = track_midpoint[2] - player->old_posn[p][2];

  factor = suction_factor * xrTimeInterval;
  accel[0] += v[0] * factor;
  accel[1] += v[1] * factor;
  accel[2] += v[2] * factor;
}
#endif

#if 0
static inline int
gone_through_tube (const struct xrPlayer *player, int p, int nr_segments)
{
  int this_seg, next_seg;
  GLfloat this_midpoint[3], next_midpoint[3];
  GLfloat v[3];
  GLfloat radius, d;

  this_seg = player->old_seg[p];
  next_seg = (this_seg + 1) % (nr_segments - 1);

  xrTrack->get_midpoint (this_seg, this_midpoint);
  xrTrack->get_midpoint (next_seg, next_midpoint);

  v[0] = next_midpoint[0] - this_midpoint[0];
  v[1] = next_midpoint[1] - this_midpoint[1];
  v[2] = next_midpoint[2] - this_midpoint[2];

  radius = xrTrack->get_radius (player->old_seg[p]);

  d = xrDistancePointToLine (player->old_posn[p], this_midpoint, v);

#if 0
  xrLog (LOG_DEBUG, "gone_through_tube: d = %g, radius = %g", d, radius);
#endif

  return d > radius;
}
#endif

/* Update the pilot's position. */
static void
pilot_update (struct xrPlayer *player, const struct xrGameControls *controls)
{
  GLfloat craft_mp[3], mp[3], thrust_vec[3], back_line[3];
  GLfloat yaw_m[16], pitch_m[16], roll_m[16];
  float yoke_x = 0, yoke_y = 0;
  int p;
#if 0
  int suction = 0;
#endif
  int nr_segments = xrTrack->get_nr_enterplanes ();
  const struct xrTrackFace *nearest_face_to_point[3];
  GLfloat distance_to_nearest_face[3];

#if 0
  xrLog (LOG_DEBUG,
	 "pilot: (%g, %g, %g), (%g, %g, %g), (%g, %g, %g)",
	 player->posn[0][0], player->posn[0][1], player->posn[0][2],
	 player->posn[1][0], player->posn[1][1], player->posn[1][2],
	 player->posn[2][0], player->posn[2][1], player->posn[2][2]);
#endif

  /* Examine pilot accelerate and brake controls. */
  if (controls->keyboard_accelerate ||
      controls->mouse_accelerate ||
      (controls->joystick_flag &&
       controls->joystick_status.button[0]))
    {
      /* Accelerate. */
      player->thrust += player->craft->accel;
      if (player->thrust > player->craft->max_thrust)
	player->thrust = player->craft->max_thrust;
    }

  if (controls->keyboard_brake ||
      controls->mouse_brake ||
      (controls->joystick_flag &&
       controls->joystick_status.button[1]))
    {
      /* Brake. */
      player->thrust -= player->craft->brake;
      if (player->thrust < 0)
	player->thrust = 0;
    }

  /* Examine the yoke position. */
  if (!controls->keyboard_left && !controls->keyboard_right) {
    if (player->keyboard_x < 0) {
      player->keyboard_x += xrTimeInterval;
      if (player->keyboard_x > 0) player->keyboard_x = 0;
    }
    if (player->keyboard_x > 0) {
      player->keyboard_x -= xrTimeInterval;
      if (player->keyboard_x < 0) player->keyboard_x = 0;
    }
  }
  if (controls->keyboard_left && !controls->keyboard_right)
    {
      if (player->keyboard_x > 0) player->keyboard_x = 0;
      player->keyboard_x -= xrTimeInterval;
      if (player->keyboard_x < -1) player->keyboard_x = -1;

      yoke_x = player->keyboard_x;
    }
  else if (!controls->keyboard_left && controls->keyboard_right)
    {
      if (player->keyboard_x < 0) player->keyboard_x = 0;
      player->keyboard_x += xrTimeInterval;
      if (player->keyboard_x > 1) player->keyboard_x = 1;

      yoke_x = player->keyboard_x;
    }
  else if (controls->mouse_enabled)
    {
      /* Scale mouse to range -1 .. 1. */
      float t = 2. * controls->mouse_x / xrWidth - 1.;

      yoke_x = controls->mouse_sensitivity * t;
    }
  else if (controls->joystick_flag)
    {
      yoke_x = controls->joystick_status.x;
    }

  if (!controls->keyboard_up && !controls->keyboard_down) {
    if (player->keyboard_y < 0) {
      player->keyboard_y += xrTimeInterval;
      if (player->keyboard_y > 0) player->keyboard_y = 0;
    }
    if (player->keyboard_y > 0) {
      player->keyboard_y -= xrTimeInterval;
      if (player->keyboard_y < 0) player->keyboard_y = 0;
    }
  }
  if (controls->keyboard_up && !controls->keyboard_down)
    {
      if (player->keyboard_y > 0) player->keyboard_y = 0;
      player->keyboard_y -= xrTimeInterval;
      if (player->keyboard_y < -1) player->keyboard_y = -1;

      yoke_y = player->keyboard_y;
    }
  else if (!controls->keyboard_up && controls->keyboard_down)
    {
      if (player->keyboard_y < 0) player->keyboard_y = 0;
      player->keyboard_y += xrTimeInterval;
      if (player->keyboard_y > 1) player->keyboard_y = 1;

      yoke_y = player->keyboard_y;
    }
  else if (controls->mouse_enabled)
    {
      /* Scale mouse to range -1 .. 1. */
      float t = 2. * controls->mouse_y / xrHeight - 1.;

      yoke_y = controls->mouse_sensitivity * t;
    }
  else if (controls->joystick_flag)
    {
      yoke_y = - controls->joystick_status.y;
    }

#if 0
  if (yoke_x != 0 || yoke_y != 0)
    xrLog (LOG_DEBUG, "yoke = %g, %g", yoke_x, yoke_y);
#endif

  /* Banking left and right.
   * NB. x < 0 => bank left,
   *     x > 0 => bank right.
   */
  player->roll = -yoke_x * player->craft->roll_mult * xrTimeInterval;
  player->yaw = yoke_x * player->craft->yaw_mult * xrTimeInterval;

  /* Nose up and down.
   * NB. y < 0 => nose down,
   *     y > 0 => nose up.
   */
  player->pitch = yoke_y * player->craft->pitch_mult * xrTimeInterval;

  /* Copy the current position to the old position. */
  memcpy (player->old_posn, player->posn, sizeof player->posn);
  memcpy (player->old_seg,  player->seg,  sizeof player->seg);

  /* Compute the *thrust vector* and the *craft midpoint*. The
   * thrust vector runs from the midpoint of the back line
   * to the nose of the craft. This is used in thrust calculations.
   * The craft midpoint is the midpoint of the back line and
   * the nose. This is used when calculating the yaw of the craft.
   */
  xrMidpoint (player->old_posn[1], player->old_posn[2], mp);
  thrust_vec[0] = player->old_posn[0][0] - mp[0];
  thrust_vec[1] = player->old_posn[0][1] - mp[1];
  thrust_vec[2] = player->old_posn[0][2] - mp[2];
  xrNormalize (thrust_vec, thrust_vec);

  xrMidpoint (mp, player->old_posn[0], craft_mp);

  /* Compute rotation matrices for roll, pitch, yaw. These are
   * applied to each point in the craft, so it makes sense to
   * precompute them here.
   */
  xrMakeRotationMatrix (player->yaw,
			player->up[0], player->up[1], player->up[2], yaw_m);

  back_line[0] = player->posn[2][0] - player->posn[1][0];
  back_line[1] = player->posn[2][1] - player->posn[1][1];
  back_line[2] = player->posn[2][2] - player->posn[1][2];

  xrMakeRotationMatrix (player->pitch,
			back_line[0], back_line[1], back_line[2], pitch_m);

  xrMakeRotationMatrix (player->roll,
			thrust_vec[0], thrust_vec[1], thrust_vec[2], roll_m);

  /* XXX Apply a correction here if craft turns upside down */

  /* XXX Triggers here. */

  /* Has any point hit one of the sides of the tube or one of the faces? */
  for (p = 0; p < 3; ++p)
    {
      int *fp;
      const struct xrTrackFace *nearest_face = 0;
      GLfloat min_dist;

      /* Compute the list of faces of influence. */
      fp = xrTrack->map_point_to_faces (player->old_posn[p],
					player->old_seg[p]);

      /* Compute closest face of influence. */
      min_dist = 1000;
      nearest_face = 0;
      while (*fp >= 0)
	{
	  GLfloat dist;
	  const struct xrTrackFace *face = xrTrack->get_face (*fp++);

	  dist = xrDistancePointToPlane (face->face_plane_coefficient,
					 player->old_posn[p]);

	  if (dist < min_dist)
	    {
	      min_dist = dist;
	      nearest_face = face;
	    }
	}

#if 0
      if (nearest_face)
	xrLog (LOG_DEBUG, "nearest_face = %p, min_dist = %g",
	       nearest_face, min_dist);
#endif

      /* Have we actually gone through the nearest face? */
      if (nearest_face && min_dist < 0)
	{
	  dunk_face (player);
#if 0
	  suction = 1;
#endif
	}

      /* Save these for later. */
      nearest_face_to_point[p] = nearest_face;
      distance_to_nearest_face[p] = min_dist;

#if 0
      /* Have we actually gone through the surrounding tube? */
      if (gone_through_tube (player, p, nr_segments))
	{
	  dunk_tube (player);
	  suction = 1;
	}
#endif
    }

  /* Update each point in turn. */
  for (p = 0; p < 3; ++p)
    {
      GLfloat accel[3] = {0, 0, 0};
      GLfloat v[4], w[4];
      int next_seg;
      const struct xrTrackEnterPlane *enterplane;
      GLfloat d;

      /* The forces acting on each point are:
       *
       * - gravity, straight down
       * - thrust, along a line from midpoint of back to nose
       * - 2 springs, pulling and pushing towards other 2 points
       * - any faces which may influence the point
       * - contributions from roll, pitch and yaw matrices
       * - collisions with other players, if any
       *
       * We add up all these forces to produce a
       * new acceleration. The acceleration is
       * then added to the momentum, and the momentum
       * is added to the current position.
       */
      gravity (accel, p);

      faces (accel, player, p,
	     nearest_face_to_point[p], distance_to_nearest_face[p]);

      springs (accel, player, p);

      thrust (accel, player, p, thrust_vec);

#if 0
      if (suction)
	apply_suction (accel, player, p);
#endif

      /* XXXXXXXXXXXXXXX */



      /* Compute new momentum. */
      player->momentum[p][0] += accel[0];
      player->momentum[p][1] += accel[1];
      player->momentum[p][2] += accel[2];

      /* Compute new position. */
      player->posn[p][0] += player->momentum[p][0];
      player->posn[p][1] += player->momentum[p][1];
      player->posn[p][2] += player->momentum[p][2];

      /* Damp momentum. */
      player->momentum[p][0] *= momentum_damping;
      player->momentum[p][1] *= momentum_damping;
      player->momentum[p][2] *= momentum_damping;

      /* Factor in roll, pitch and yaw. */
      v[0] = player->posn[p][0] - craft_mp[0];
      v[1] = player->posn[p][1] - craft_mp[1];
      v[2] = player->posn[p][2] - craft_mp[2];
      v[3] = 0;
      xrMatrixVectorMultiply (roll_m, v, w);
      xrMatrixVectorMultiply (yaw_m, w, v);
      xrMatrixVectorMultiply (pitch_m, v, w);
      player->posn[p][0] = w[0] + craft_mp[0];
      player->posn[p][1] = w[1] + craft_mp[1];
      player->posn[p][2] = w[2] + craft_mp[2];

      /* Gone through enter plane of next segment? */
      next_seg = (player->seg[p]+1) % nr_segments;
      enterplane = xrTrack->get_enterplane (next_seg);
      d = enterplane->plane_coefficient[0] * player->posn[p][0] +
	  enterplane->plane_coefficient[1] * player->posn[p][1] +
	  enterplane->plane_coefficient[2] * player->posn[p][2] +
	  enterplane->plane_coefficient[3];
      if (d >= 0)
	{
	  player->seg[p] = next_seg;
#if 0
	  xrLog (LOG_DEBUG, "point %d entered next segment %d",
		 p, player->seg[p]);
#endif

	  /* Has the nose gone through the enter plane *into* the
	   * start segment? If so, then we have just completed
	   * a lap!
	   */
	  if (p == 0 && player->seg[0] == player->start_segment)
	    {
	      increment_lap (player);
	    }
	}
      else
	{
	  /* Gone through enter plane back into previous segment? */
	  enterplane = xrTrack->get_enterplane (player->seg[p]);
	  d = enterplane->plane_coefficient[0] * player->posn[p][0] +
	      enterplane->plane_coefficient[1] * player->posn[p][1] +
	      enterplane->plane_coefficient[2] * player->posn[p][2] +
	      enterplane->plane_coefficient[3];
	  if (d < 0)
	    {
	      player->seg[p]--;
	      if (player->seg[p] < 0)
		player->seg[p] += nr_segments;

#if 0
	      xrLog (LOG_DEBUG, "point %d entered last segment %d",
		     p, player->seg[p]);
#endif

	      /* Has the nose gone through the enter plane into
	       * the segment *before* the start segment? If so,
	       * then we have just gone back a lap.
	       */
	      if (p == 0 && player->seg[0] == player->start_segment_minus_one)
		{
		  decrement_lap (player);
		}
	    }
	}
    }

  /* Recalculate viewpoint stuff. */
  calculate_view (player);
}

/* Update the position of all players. This is typically called once
 * per frame or during the idle function.
 */
void
xrPlayerUpdate (const struct xrGameControls *controls)
{
  xrLogAssert (mode == XR_GAME_MODE_TIME_TRIAL);

  pilot_update (&xrPilot, controls);
}

/* Perform the basic initialization on a player structure. */
static void
init_player_struct (struct xrPlayer *player, int nr_laps)
{
  memset (player, 0, sizeof *player);
  player->faster = 1;
  player->shield = 1;
  player->lap_time = xmalloc (sizeof (GLfloat) * nr_laps);
  player->start_of_lap_time = xrCurrentTime;
}

/* Change internal constants when debugging. */
void xrPlayerDebugSnapshotSettings ()
{
  xrLog (LOG_DEBUG,
	 "settings: gravity = %g  spring_damping = %g"
	 "  momentum_damping = %g  levitate_dist = %g"
	 "  levitate_damping = %g",
	 gravity_force, spring_damping, momentum_damping,
	 levitate_dist, levitate_damping);
}

void
xrPlayerDebugGravityUp ()
{
  gravity_force += 0.01;
  xrLog (LOG_DEBUG, "gravity = %g", gravity_force);
}

void
xrPlayerDebugGravityDown ()
{
  gravity_force -= 0.01;
  xrLog (LOG_DEBUG, "gravity = %g", gravity_force);
}

void
xrPlayerDebugSpringDampingUp ()
{
  spring_damping += 0.1;
  xrLog (LOG_DEBUG, "spring_damping = %g", spring_damping);
}

void
xrPlayerDebugSpringDampingDown ()
{
  spring_damping -= 0.1;
  xrLog (LOG_DEBUG, "spring_damping = %g", spring_damping);
}

void
xrPlayerDebugMomentumDampingUp ()
{
  momentum_damping += 0.1;
  xrLog (LOG_DEBUG, "momentum_damping = %g", momentum_damping);
}

void
xrPlayerDebugMomentumDampingDown ()
{
  momentum_damping -= 0.1;
  xrLog (LOG_DEBUG, "momentum_damping = %g", momentum_damping);
}

void
xrPlayerDebugLevitateDistUp ()
{
  levitate_dist += 0.1;
  xrLog (LOG_DEBUG, "levitate_dist = %g", levitate_dist);
}

void
xrPlayerDebugLevitateDistDown ()
{
  levitate_dist -= 0.1;
  xrLog (LOG_DEBUG, "levitate_dist = %g", levitate_dist);
}

void
xrPlayerDebugLevitateDampingUp ()
{
  levitate_damping += 0.1;
  xrLog (LOG_DEBUG, "levitate_damping = %g", levitate_damping);
}

void
xrPlayerDebugLevitateDampingDown ()
{
  levitate_damping -= 0.1;
  xrLog (LOG_DEBUG, "levitate_damping = %g", levitate_damping);
}
