/*
 *  acm : an aerial combat simulator for X
 *  Copyright (C) 1991-1998  Riley Rainey
 *
 *  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; version 2 dated June, 1991.
 *
 *  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., 675 Mass Ave., Cambridge, MA 02139, USA.
 */

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

#include "../util/memory.h"
#include "../util/units.h"
#include "astro.h"
#include "browse.h"
#include "damage.h"
#include "dis_if.h"
#include "hud.h"
#include "instruments.h"
#include "magnetic_compass.h"
#include "panel.h"
#include "place.h"
#include "pm.h"
#include "prompt.h"
#include "sounds.h"
#include "terminal.h"
#include "terrain.h"

#define render_IMPORT
#include "render.h"
#include "gear.h"

#define render_DEFAULT_GROUND_COLOR "#406030"

/**
 * Entry in the array of items to draw using the painter algorithm.
 */
typedef struct {
	/** Distance of the item. */
	double distance;
	/** Item to be drawn. */
	craft *item;
} ItemToDraw;

static VPoint    hex[6] =
{
	{1.00000, 0.00000, 0.0},
	{0.50000, 0.86603, 0.0},
	{-0.50000, 0.86603, 0.0},
	{-1.00000, 0.00000, 0.0},
	{-0.50000, -0.86603, 0.0},
	{0.50000, -0.86603, 0.0}
};

static double visibility = units_NMtoMETERS(10);

/** If ground flat or tiled. */
static render_Ground ground_mode = render_GROUND_FLAT;
VColor_Type *sunColor;

/**
 * Ground color name as retrieved from the zone the craft if currently flying
 * over.
 */
static char *groundColorName;

static VColor_Type *groundColor; // may depend on the scenery
static ItemToDraw *toDraw;
static int toDrawCapacity;
static int toDrawLen;

static int inited;


static void render_cleanup()
{
	memory_dispose(toDraw);
	toDraw = NULL;
	toDrawCapacity = 0;
	toDrawLen = 0;
	
	inited = 0;
}


static void render_init()
{
	if( inited )
		return;
	inited = 1;
	
	groundColorName = render_DEFAULT_GROUND_COLOR;
	groundColor = VColor_getByName(groundColorName, 0);
	sunColor = VColor_getByRGB(255, 255, 255, 0);
	
	toDraw = NULL;
	toDrawCapacity = 0;
	toDrawLen = 0;
	
	memory_registerCleanup(render_cleanup);
}


void render_setVisibility(double range)
{
	if (range < units_NMtoMETERS(0.01) ) {
		range = 0.01;
	}
	else if (range > units_NMtoMETERS(50.0) ) {
		range = units_NMtoMETERS(50.0);
	}
	visibility = range;
}


void render_setGroundDepth(render_Ground mode)
{
	ground_mode = mode;
}


void render_setClouds(double base, double top)
{
	clouds_base = base;
	clouds_top = top;
}


/**
 * Draw the Sun.
 * @param c Point of view.
 * @param ps Add Sun polygon here.
 * @return Sun elevation over the NED horizon: positive values means above (RAD).
 */
static double render_addSun(craft *c, VPolySet *ps)
{
	VPoint sun;
	astro_getSun(curTime, &sun);
	VTransform(&sun, &c->XYZtoNED, &sun);
	double elevation = atan2(-sun.z, sqrt(sun.x*sun.x + sun.y*sun.y));
	
	/*
	 * Draw the Sun disc as a circle perpendicular to the sun vector.
	 * We need a point p of the polygon: first we take any perpendicular
	 * point to sun, then normalize to Sun disc radius, then add the
	 * sun vector, finally rotate that vector around sun to generate the
	 * vertices:
	 */
	VPoint p = (VPoint){sun.y, -sun.x, 0};
	double sun_radius = 6.5e8; // radius of the Sun (m)
	double p_mag = sun_radius / VMagnitude(&p);
	p.x *= p_mag;
	p.y *= p_mag;
	p.z *= p_mag;
	VAdd(&sun, &p, &p);
	
	/*
	 * Rotate p around craft-to-sun vector step by step to get vertices:
	 */
	VMatrix r;
	VIdentMatrix(&r);
#define SUN_STEPS 8
	VRotateAroundAxis(&r, &sun, 2*M_PI/SUN_STEPS);
	VPoint disc[SUN_STEPS];
	int i;
	for(i = 0; i < SUN_STEPS; i++){
		VTransform_(&p, &r, &p);
		disc[i] = p;
		VReverseTransform(&disc[i], &c->XYZtoNED, &disc[i]);
	}
	VPolygon *sun_poly = VCreatePolygon(SUN_STEPS, disc, sunColor);
	VPolySet_Add(ps, sun_poly);
	return elevation;
}


/**
 * Add a horizontal hexagonal layer to the polygons set.
 * @param c Craft, for NED to XYZ transformation.
 * @param color Color of the hexagon.
 * @param z "Zeta" position of the hexagon in aircraft's NED frame (m), then
 * positive if below.
 * @param r Radius of the hexagon (m).
 * @param ps Polygons set to add to.
 */
static void
render_addHorizontalLayer(craft * c, VColor_Type * color, double z, double r, VPolySet *ps)
{
	VPoint    hex1[6];
	int i;
	for (i = 0; i < 6; ++i) {
		hex1[i].x = hex[i].x * r;
		hex1[i].y = hex[i].y * r;
		hex1[i].z = z;
		VReverseTransform(&hex1[i], &c->XYZtoNED, &hex1[i]);
	}
	VPolySet_Add(ps, VCreatePolygon(6, hex1, color));
}


/**
 * Add hexagonal polygon for the terrain.
 * @param c Craft, for NED to XYZ transformation.
 * @param local_z Local altitude of the terrain below the aircraft.
 * @param groundColor
 * @param ps Polygons set to add to.
 */
static void render_drawFlatTerrain(craft * c, double local_z, VColor_Type *groundColor, VPolySet *ps)
{
	render_addHorizontalLayer(c, groundColor, c->w.z - local_z, visibility, ps);
	
/*
	double r = 0.95 * visibility;
	int i;
	for(i = 4; i >= 0; i--){
		double h = 0.5 * r*r / 6e6;
		addHorizontalLayer(c, ground_color, c->w.z - local_z + h, r, ps);
		r *= 0.5;
	}
*/
}


static double myfmod(double x, double m)
{
	if( x >= 0 ){
		return x - m * floor(x / m);
	} else {
		return m + x + m * floor(-x / m);
	}
}


// Terrain with n*n grid for a total of (n-1)^2 tiles.
#define n 21


/**
 * Draws grid of tiles on a square 2*visibility meters wide below the aircraft.
 * Each tile will be blended to the fog color according to the distance.
 * Due to the limited number of colors, each tile remains perfectly visible.
 * We take advantage of this artifact to generate an interesting texture effect
 * that helps feeling the speed and orientation in VFR. To this aim the square
 * grid must be still with the terrain rather than being centered below the
 * aircraft. We use the current latitude and longitude and some modulo
 * arithmetic trickery to generate the anchor point A in NED where that
 * square grid has to be centered.
 * Here the Earth is a perfect sphere of radius earth_MAJOR and the local
 * terrain altitude is ignored in order to calculate A, but the result is
 * good enough.
 * @param c Craft, for NED to XYZ transformation.
 * @param local_z Local altitude of the terrain below the aircraft.
 * @param groundColor
 * @param ps Polygons set to add to.
 */
static void render_drawTiledTerrain(craft * c, double local_z, VColor_Type *groundColor, VPolySet *ps)
{
	// Evaluate each tile size as angles:
	double cos_lat = cos(c->w.latitude);
	double sin_lat = sin(c->w.latitude);
	double cos_lon = cos(c->w.longitude);
	double sin_lon = sin(c->w.longitude);
	// Each tile north-to-south size (RAD):
	double lat_step = 2 * visibility / (n - 1) / earth_MAJOR;
	if( lat_step < units_DEGtoRAD(1/3600.0) ) // not less than 1 arc second
		lat_step = units_DEGtoRAD(1/3600.0);
	else if( lat_step > units_DEGtoRAD(1) ) // no more than 1 degree
		lat_step = units_DEGtoRAD(1);
	// Each tile west-to-east size (RAD):
	double lon_step = lat_step;
	while( cos_lat * lon_step < lat_step / 2 ) // tile height at least half width
		lon_step *= 2;
	
	// Compute anchor point of the grid in NED:
	VPoint A;
	A.x = - earth_MAJOR * myfmod(c->w.latitude, lat_step);
	A.y = - earth_MAJOR * cos_lat * myfmod(c->w.longitude, lon_step);
	A.z = c->w.z - local_z;
	// ...then transform in world coordinates:
	VReverseTransform(&A, &c->XYZtoNED, &A);
	
	int h, v;
	VPoint grid[n][n];
	for(h = 0; h < n; h++){
		for(v = 0; v < n; v++){
			double delta_lat = (h - n/2) * lat_step;
			double delta_lon = (v - n/2) * lon_step;
			/*
			 * Exact formulas for the points of the grid are:
			 * 
			 * grid[h][v].x = earth_MAJOR * cos(lat+delta_lat) * sin(lon+delta_lon)
			 * grid[h][v].y = earth_MAJOR * cos(lat+delta_lat) * sin(lon+delta_lon)
			 * grid[h][v].z = earth_MAJOR * sin(lat+delta_lat)
			 * 
			 * where lat,lon are the coords of A. Here the first order approximation
			 * on delta_lat,delta_lon is used to save some sin() and cos() calculations:
			 */
			VSetPoint(&grid[h][v],
				A.x - earth_MAJOR * ( cos_lat * sin_lon * delta_lon
					+ cos_lon * sin_lat * delta_lat),
				A.y + earth_MAJOR * ( cos_lat * cos_lon * delta_lon
					- sin_lon * sin_lat * delta_lat),
				A.z + earth_MAJOR * cos_lat * delta_lat);
		}
	}
	for(h = 0; h < n-1; h++){
		for(v = 0; v < n-1; v++){
			VPoint tile[4];
			tile[0] = grid[h+1][v];
			tile[1] = grid[h+1][v+1];
			tile[2] = grid[h][v+1];
			tile[3] = grid[h][v];
			VPolySet_Add(ps, VCreatePolygon(4, tile, groundColor));
		}
	}
}


/**
 * Add terrain to the polygons set.
 * @param c Craft, for NED to XYZ transformation.
 * @param local_z Local altitude of the terrain below the aircraft.
 * @param ps Polygons set to add to.
 */
static void
render_addTerrain(craft * c, double local_z, VColor_Type *groundColor, VPolySet *ps)
{
	if( ground_mode == render_GROUND_FLAT )
		render_drawFlatTerrain(c, local_z, groundColor, ps);
	else
		render_drawTiledTerrain(c, local_z, groundColor, ps);
}


/**
 * Add item 'p' to the list of items to draw for the view from craft 'c'.
 * @param c
 * @param p
 * @param zmin Draw only if above this altitude (m).
 * @param zmax Draw only if below this altitude.
 */
static void
insertToDraw(craft * c, craft * p, double zmin, double zmax)
{
	double distance;
	
	if( !(zmin <= p->w.z && p->w.z < zmax) )
		return;
	
	/*
	 *  FIXME: original comment below might need update.
	 * 
	 *  Here's a kludge for you:  to avoid polygon clipping, I'm going to 
	 *  cheat and hack a way to get ground objects to display properly.
	 *  if the Z coordinate of an object is zero (i.e. on ground objects),
	 *  I'll add a huge offset to their distance values to force them to be
	 *  plotted first -- and in roughly their correct drawing order.
	 *
	 *  To support automated world outline maps, stbl[0] is considered to be
	 *  the world map, and will always be plotted in the background.
	 */

	if( p->cinfo->object == NULL ){
		printf("%s:%d: p->cinfo->object is NULL for %s\n", __FILE__, __LINE__, p->cinfo->name);
		return;
	}
	if( p->cinfo->object->name[0] == '#' ){
		
		/*
		 * Object name beginning with '#' is handled in special way, as its
		 * distance is set beyond the center of the Earth. This ensures it will
		 * be painted first on the background. Typically it is the FEATURE
		 * representing the terrain surface.
		 */
		
		distance = (7000.0 * units_NmToFeetFactor) * (7000.0 * units_NmToFeetFactor);
		
	} else {
		
		/*
		 * "Normal" item: compute its distance from us -- well, actually the
		 * square of that distance, but it does not matter here.
		 */
		
		VPoint ds;
		ds.x = p->Sg.x - c->Sg.x;
		ds.y = p->Sg.y - c->Sg.y;
		ds.z = p->Sg.z - c->Sg.z;
		distance = VMagnitude2(&ds);
	}
	
	/* Add item to draw to the array, allocating more space if necessary: */
	if( toDrawLen >= toDrawCapacity ){
		// Increase capacity about +50%:
		toDrawCapacity = toDrawCapacity + toDrawCapacity/2 + 10;
		toDraw = memory_realloc(toDraw, toDrawCapacity * sizeof(ItemToDraw));
	}
	toDraw[toDrawLen++] = (ItemToDraw) {distance, p};
}


/**
 * Compares order of items to draw for qsort(); nearest comes first.
 * @param a
 * @param b
 * @return 
 */
static int toDrawCmp(const void *a, const void *b)
{
	double d = ((ItemToDraw *)a)->distance - ((ItemToDraw *)b)->distance;
	if( d < 0 )
		return -1;
	else if( d > 0 )
		return +1;
	else
		return 0;
}


static void
renderCockpitView ( craft *c, viewer *u )
{
	instruments_update(u);
	
	if( ! gui_isVisible(u->gui) )
		return;

	/*
	 *  Build a vector of polygons for all objects in the scene.
	 *
	 *  This vector should be ordered from "most distant" to "closest" so that
	 *  the final display will end up correct.  Rather than generalizing this
	 *  to death, we'll use a few heuristics to get very close to what we need:
	 *
	 *  (0) Build a single polygon to represent the ground.
	 *  (1) Objects on the surface (stbl) are collected first.
	 *  (2) Planes and projectiles (ptbl and mtbl) are first sorted in 
	 *      descending order by their distance from the observer and then 
	 *      polygons are collected.
	 */

	VPolySet *ps, *ps_clipped;
	VPolygon *poly, *poly_clipped;
	craft *p;
	int j;
	VPoint    tmp, vp, fwd, up;
	double    v, view_dist, local_z, zmin, zmax;
	static _BOOL     blink = FALSE;
	static double    blink_toggle_time = 0.0;
	craft     *vc;

	VSetClipRect(u->v, &u->v->rect);

	/*
	 *  pay attention: vc will be the viewer craft information, c will be
	 *  the watched entity craft information
	 */

	vc = c;
	if ( c->type == CT_DIS_STEALTH ) {

		if ( c->vl->viewer_state == ViewerStatePiggyback ) {
			
			c = c->vl->watchedCraft;
		}

	}

	/*
	 *  Set up the eye space transformation for this viewpoint
	 */

	if (vc->flags & FL_CHASE_VIEW) {

		/*
		 * Set a convenient distance of view behind the aircraft based
		 * on the estimated length of the actual aircraft (m).
		 */

		view_dist = -10 * units_FEETtoMETERS(fabs(c->cinfo->rn.x - c->cinfo->rm.x));

		/*
		 * Set viewpoint vp in world frame XYZ (m).
		 * If the ground speed (projection of c->Cg over the horizontal plane)
		 * is greater than 2 m/s, use this as direction of view, otherwise
		 * use the current heading.
		 */

		tmp = c->Cg;
		tmp.z = 0.0;
		VReverseTransform_(&tmp, &c->XYZtoNED, &vp);
		v = VMagnitude(&vp);
		if (v > 2.0) {
			vp.x *= view_dist / v;
			vp.y *= view_dist / v;
			vp.z *= view_dist / v;
		}
		else {
			tmp.x = view_dist * cos(c->curHeading);
			tmp.y = view_dist * sin(c->curHeading);
			tmp.z = 0.0;
			VReverseTransform_(&tmp, &c->XYZtoNED, &vp);
		}
		vp.x += c->Sg.x;
		vp.y += c->Sg.y;
		vp.z += c->Sg.z;

		/*
		 * Look at the CM, world frame (m).
		 */

		fwd = c->Sg;
		
		/*
		 * Set "up" point position in world XYZ (m).
		 */

		tmp.x = tmp.y = 0.0;
		tmp.z = -1.0;
		VReverseTransform_(&tmp, &c->XYZtoNED, &up);
		up.x += vp.x;
		up.y += vp.y;
		up.z += vp.z;
	}
	else {
		if (c->cinfo) {
			VTransform_(&c->cinfo->viewPoint, &(c->trihedral), &tmp);
		}
		else {
			tmp.x = tmp.y = tmp.z = 0.0;
		}
		tmp.x = units_FEETtoMETERS(tmp.x);
		tmp.y = units_FEETtoMETERS(tmp.y);
		tmp.z = units_FEETtoMETERS(tmp.z);
		VReverseTransform(&tmp, &c->XYZtoNED, &vp);
		
		VTransform_(&u->viewDirection, &(c->trihedral), &tmp);
		VReverseTransform_(&tmp, &c->XYZtoNED, &fwd);
		fwd.x += vp.x;
		fwd.y += vp.y;
		fwd.z += vp.z;
		
		VTransform_(&u->viewUp, &(c->trihedral), &tmp);
		VReverseTransform_(&tmp, &c->XYZtoNED, &up);
		up.x += vp.x;
		up.y += vp.y;
		up.z += vp.z;
	}
	
	local_z = terrain_localAltitude(c);
	
	VGetEyeSpace(u->v, vp, fwd, up);
	
	ps = VPolySet_New();

	toDrawLen = 0;
	
	/*
	 * Add the Sun to the polygons we will draw later along with aircraft etc.
	 * As a side beneficial effect, we get the Sun elevation over the
	 * horizon which allows to compute a general brightness factor, which in
	 * turn allows to calculate the sky color and general scene brightness.
	 */
	double sun_elevation_over_terrain = render_addSun(c, ps);
	
	double terrain_brightness = (sun_elevation_over_terrain + units_DEGtoRAD(6)) / units_DEGtoRAD(10);
	if( terrain_brightness < 0 )
		terrain_brightness = 0.0;
	else if( terrain_brightness > 1.0 )
		terrain_brightness = 1.0;
	
	/*
	 * Compute Sun elevation over the far horizon of the Earth.
	 * Start assuming we are still very close to the ground:
	 */
	double sun_elevation_over_horizon = sun_elevation_over_terrain;
	double far_horizon_brightness = terrain_brightness;
	double half_globe_tan = INFINITY;
	// If pilot can see far horizon, do a precise calculation:
	if( c->w.z > 0 && c->w.z - local_z > 1.0 ){
		double r = c->w.z / earth_MAJOR;
		// Apparent half-angular size of the Earth as seen from the craft:
		half_globe_tan = 1.0 / sqrt(r*(r+2));
		sun_elevation_over_horizon += M_PI/2 - atan(half_globe_tan);
		far_horizon_brightness = (sun_elevation_over_horizon + units_DEGtoRAD(2)) / units_DEGtoRAD(4);
		if( far_horizon_brightness < 0 )
			far_horizon_brightness = 0;
		else if( far_horizon_brightness > 1 )
			far_horizon_brightness = 1;
	}

	/*
	 * Compute sky color. Note that each color component of the sky follows a
	 * different law for even nicer dusk effects. Sky becomes darker at high
	 * altitude.
	 */
	double altitudeFactor = 10000.0 / (fabs(c->w.z) + 10000.0);
	int skyColor = gui_getColorIndex(NULL,
		150*sqrt(far_horizon_brightness)*altitudeFactor, // red prevails at dusk
		150*far_horizon_brightness*altitudeFactor,
		220*far_horizon_brightness*far_horizon_brightness*altitudeFactor // blue prevails in plain light
	);
	
	VColor_Type *cloudsColor = VColor_getByRGB(
		200*terrain_brightness, 200*terrain_brightness, 200*terrain_brightness, 0);
	
	int hazeComponent = 176*terrain_brightness;
	VColor_Type *hazeColor = VColor_getByRGB(
		hazeComponent, hazeComponent, hazeComponent, 0);
	
	/*
	 * Draw far horizon, that is the round shape of the Earth. The "round shape"
	 * here is an... hexagon drawn at some safe distance between the center of
	 * the Earth and the terrain below to avoid overlapping with the terrain
	 * polygons.
	 */
	if( far_horizon_brightness > 0 ){
		double d = earth_MAJOR/2 + c->w.z; // safe distance below aircraft
		double r = d * half_globe_tan; // radius of the hexagon
		int farHorizonComponent = 176*far_horizon_brightness;
		VColor_Type *farHorizonColor = VColor_getByRGB(
			farHorizonComponent, farHorizonComponent, farHorizonComponent, 0);
		render_addHorizontalLayer(c, farHorizonColor, d, r, ps);
	}
	
	/*
	 * Configure Alib depth cueing color and culling distance:
	 */
	Alib_setVisibility(u->w, visibility, hazeColor);
	
	/*
	 * Ground color name. If the zone the craft is flying over defines a
	 * ground color, set that color, otherwise use the default.
	 */
	char *newGroundColorName = render_DEFAULT_GROUND_COLOR;
	if( c->zone != NULL && zone_isLoaded(c->zone)
	&& zone_getGroundColor(c->zone) != groundColorName ){
		newGroundColorName = zone_getGroundColor(c->zone);
		groundColor = VColor_getByName(newGroundColorName, ground_mode == render_GROUND_TILED);
	}
	
	/* Compute dimmed ground color. */
	VColor_Type *dimGroundColor = VColor_getByRGB(
		terrain_brightness * VColor_getRed(groundColor),
		terrain_brightness * VColor_getGreen(groundColor),
		terrain_brightness * VColor_getBlue(groundColor),
		ground_mode == render_GROUND_TILED);

	if( clouds_top <= clouds_base ){
		// No clouds.
		// Sky.
		VFillRectangle(u->v, &u->v->rect, skyColor);
		// Terrain below:
		render_addTerrain(c, local_z, dimGroundColor, ps);
		// Draw items from sea level up to the deep outer space:
		zmin = 0.0;
		zmax = INFINITY;

	} else if( c->w.z < clouds_base ){
		// Craft between terrain and clouds base.
		// Sky.
		VFillRectangle(u->v, &u->v->rect, skyColor);
		// Terrain below:
		render_addTerrain(c, local_z, dimGroundColor, ps);
		// Clouds above:
		render_addHorizontalLayer(c, cloudsColor, c->w.z - clouds_base, 1e5, ps);
		// Draw items from sea level up to the clouds base:
		zmin = 0.0;
		zmax = clouds_base;

	} else if( c->w.z < clouds_top ){
		// Craft inside clouds.
		// Pilot can't see nothing but a bright gray:
		VFillRectangle(u->v, &u->v->rect, VColor_getIndex(cloudsColor));
		// Nothing to draw - set impossible range:
		zmin = 0.0;
		zmax = -1.0;

	} else {
		// Craft above clouds.
		// Sky.
		VFillRectangle(u->v, &u->v->rect, skyColor);
		// Clouds below:
		render_addHorizontalLayer(c, cloudsColor, c->w.z - clouds_top, 1e5, ps);
		// Draw items from clouds top up to the deep outer space:
		zmin = clouds_top;
		zmax = INFINITY;
	}

	// Perspective on terrain and clouds polygons drawn so far:
	poly = VPolySet_First(ps);
	while( poly != NULL ){
		VTransformPolygon(poly, &u->v->eyeSpace);
		poly = VPolySet_Next(ps);
	}

	// Add surface elements:
	for (p = stbl; p != NULL; p = p->next) {
		insertToDraw(c, p, zmin, zmax);
	}

	for ((j = 0, p = ptbl); j < manifest_MAXPLAYERS; (++j, ++p)) {
		if (p->type != CT_FREE &&
			p->type != CT_DIS_STEALTH &&
			(p != c || c->flags & FL_CHASE_VIEW)) {
			insertToDraw(c, p, zmin, zmax);
		}
	}

	for ((j = 0, p = mtbl); j < manifest_MAXPROJECTILES; (++j, ++p)) {
		if (p->type != CT_FREE) {
			insertToDraw(c, p, zmin, zmax);
		}
	}
	
	/* Sort items to draw in increasing order of distance from 'c': */
	qsort(toDraw, toDrawLen, sizeof(ItemToDraw), toDrawCmp);
	
	/* Painter algorithm: draw far items first, near last: */
	for(j = toDrawLen-1; j >= 0; j--)
		place_craft(u->v, c, u, toDraw[j].item, ps);

	/*
	 *  Display this image for each viewer associated with this craft
	 */
	
	/* FIXME: to do */

	/*
	 *  Clip all polygons against the view frustum.
	 *
	 *  Attention: we can't release the ps set of polygons right
	 *  after the clipping because polygons completely visible
	 *  are shared with ps_clipped set.
	 */

	ps_clipped = VPolySet_New();
	poly = VPolySet_First(ps);
	while( poly != NULL ){

		VPolySet_Set(ps, NULL);

		poly = VClipSidedPolygon(poly, u->v->clipPoly);

		if( poly != NULL )
			VPolySet_Add(ps_clipped, poly);

		poly = VPolySet_Next(ps);
	}

	poly_clipped = VPolySet_First(ps_clipped);
	while( poly_clipped != NULL ){

		VFillPolygon(u->v, poly_clipped);

		poly_clipped = VPolySet_Next(ps_clipped);
	}

	/*
	 *  Release polygons
	 */

	VPolySet_Free(ps, TRUE);
	ps = NULL;

	VPolySet_Free(ps_clipped, TRUE);
	ps_clipped = NULL;
		
	hud_draw(u);
	
	terminal_draw(u);

	//browse_page( vc, u );

	if ( u->viewer_state == ViewerStateNormal ) {
		prompt_draw(u);
		magnetic_compass_draw(u);
		instruments_draw(u); // instruments panel upper row
		panel_updateAndDraw(c, u); // instruments panel bottom row
		doFlightStatusPage(c, u);
	}

	if( curTime > blink_toggle_time ){
		blink = ! blink;
		if( blink )
			blink_toggle_time = curTime + 0.05;
		else
			blink_toggle_time = curTime + 1.2;
	}
	
	if( ! blink ){
		
		char *s = NULL;
		
		if ( u->hasFocus && ! u->hasComm ) {
			s = "[PRESS `d' TO TAKE COMMANDS]";

		} else if( dis_if_isValidatingSiteId() ){
			s = "Validating site ID";

		} else if( u->hasFocus && c->type == CT_DRONE ){
			s = "[DRONE]";
		}
		
		if( s != NULL ){
			int len = strlen(s);
			int fw = (int) (0.6 * RectWidth(u->v->rect) / (double) len);
			int fh = fw;  /* FIXME */
			int x = RectMiddleX(u->v->rect) - (fw * len / 2);
			int y = RectMiddleY(u->v->rect) + (fh / 2);
			Alib_setClipRect(u->w, &u->v->rect);
			VDrawStrokeString(u->v, x+1, y, s, len, fh, blackColor);
			VDrawStrokeString(u->v, x, y, s, len, fh, whiteColor);
		}
		
	}

	/*  Expose the completed drawing  */
	VExposeBuffer(u->v);

}


static void
setAudio(craft * c)
{
	/* Set engine sound: */
	if( damage_isFunctioning(c, SYS_ENGINE1 ) )
		sounds_setBackgroundSound(c,
			c->rpm,
			(c->flags & FL_AFTERBURNER) ? 1 : 0,
			0.0 /* dummy, ignored */);
	
	/* Stall warning: */
	if( c->damageBits & FLAG_STALL_WARN )
		sounds_playSound(c, sounds_StallWarning, TRUE);
	else
		sounds_stopSound(c, sounds_StallWarning);
	
	/* Low altitude and gear warnings: */
	double radar_altitude = units_METERStoFEET(c->w.z - terrain_localAltitude(c));
	double vertical_speed = c->Cg.z; /* ft/s */
	if(
		radar_altitude < 2500 /* radar altitude available */
		&& vertical_speed > 0 /* diving */
	){
		int gear_down = gear_nosePosition(c) == 2
			&& gear_leftPosition(c) == 2
			&& gear_rightPosition(c) == 2;
		double time_to_impact = radar_altitude / vertical_speed;
		if( vertical_speed / time_to_impact > 0.2 * units_earth_g ){
			/* diving too fast to recover within 0.2 g limit before impact  */
			prompt_craft_print(c, "WARNING: pull-up!");
			sounds_playSound(c, sounds_GenericWarning, 0);
		} else if( ! gear_down && time_to_impact < 2 * M_PI_2 / c->cinfo->gearRate ){
			/* no time left to extend gear (2 factor for extra safety) */
			prompt_craft_print(c, "WARNING: pull-up!");
			sounds_playSound(c, sounds_GenericWarning, 0);
		} else if( ! gear_down && time_to_impact < 120 ){
			/* no gear, less than 2 min to impact */
			prompt_craft_print(c, "WARNING: gear down!");
			sounds_playSound(c, sounds_GenericWarning, 0);
		}
	}
	
	sounds_update(c);
}


void
render_drawCockpitViews(void)
{
	viewer *u;

	render_init();
	
	for (u = vl_head; u != NULL; u=u->vl_next ) {

		renderCockpitView( u->c, u );

		setAudio(u->c);
		
		u->c->zone = zones_load(zones, &u->c->w, u->c->zone, 0);

	}

}


void
render_setOutsideView(craft * c, viewer *u, render_ViewDirection v)
{
	if (v == render_VIEW_CHASE) {
		c->flags |= FL_CHASE_VIEW;
	}
	else {
		c->flags &= ~FL_CHASE_VIEW;
	}

	switch (v) {
	case render_VIEW_CHASE:
	case render_VIEW_FORWARD:
		u->viewDirection.x = 1.0;
		u->viewDirection.y = 0.0;
		u->viewDirection.z = 0.0;
		u->viewUp.x = 0.0;
		u->viewUp.y = 0.0;
		u->viewUp.z = -1.0;
		break;
	case render_VIEW_UP:
		u->viewDirection.x = 0.0;
		u->viewDirection.y = 0.0;
		u->viewDirection.z = -1.0;
		u->viewUp.x = -1.0;
		u->viewUp.y = 0.0;
		u->viewUp.z = 0.0;
		break;
	case render_VIEW_DOWN:
		u->viewDirection.x = 0.0;
		u->viewDirection.y = 0.0;
		u->viewDirection.z = 1.0;
		u->viewUp.x = 1.0;
		u->viewUp.y = 0.0;
		u->viewUp.z = 0.0;
		break;
	case render_VIEW_LEFT:
		u->viewDirection.x = 0.0;
		u->viewDirection.y = -1.0;
		u->viewDirection.z = 0.0;
		u->viewUp.x = 0.0;
		u->viewUp.y = 0.0;
		u->viewUp.z = -1.0;
		break;
	case render_VIEW_RIGHT:
		u->viewDirection.x = 0.0;
		u->viewDirection.y = 1.0;
		u->viewDirection.z = 0.0;
		u->viewUp.x = 0.0;
		u->viewUp.y = 0.0;
		u->viewUp.z = -1.0;
		break;
	case render_VIEW_AFT:
		u->viewDirection.x = -1.0;
		u->viewDirection.y = 0.0;
		u->viewDirection.z = 0.0;
		u->viewUp.x = 0.0;
		u->viewUp.y = 0.0;
		u->viewUp.z = -1.0;
		break;
	}
}
