/*
 *  acm : an aerial combat simulator for X
 *  Classic instruments module
 *  Copyright (C) 2007  Umberto Salsi
 *
 *  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 <assert.h>

#include "../util/memory.h"
#include "../util/units.h"
#include "../V/Vlibmath.h"
#include "aps.h"
#include "draw.h"
#include "pm.h"
#include "vpath.h"

#define instruments_IMPORT
#include "instruments.h"

/**
 * The viewer object associated to every aircraft contains a pointer
 * to an instance of this struct, giving the state of the classic
 * instruments.
 */
typedef struct instruments_Type {

	/**
	 * Disposed structs are linked in a recycle pool; set to NULL for
	 * currently used structs.
	 */
	struct instruments_Type *next;

	/**
	 * If classic instruments panel is enabled, then every instrument
	 * needs to be updated.
	 */
	_BOOL enabled;

	/** Anemometer: cosine of the stall angle. */
	double cos_alpha_stall;

	/** Attitude: last update time (s). */
	double attitude_erection_upd;
	/** Attitude: pilot's pitch offset; default zero (RAD). */
	double attitude_pitch_offset;
	/** Attitude: normalized gyro orientation (NED reference). */
	VPoint attitude_gyro;
	/** Attitude current bank. */
	double attitude_bank;
	/** Attitude current pitch. */
	double attitude_pitch;

	/** Altimeter: pressure offset in [2800,3100] inHg*100; default 2992. */
	int altimeter_p0;

	/** VSI: last update time (s). */
	double vsi_last_upd;
	/** VSI: last computed vertical speed (ft/min). */
	double vsi_last_vs;

	/** Timer: current operation mode (0=off, 1=run, 2=stop). */
	int timer_op;
	/** Timer: displayed value in 'stop' mode (s). */
	int timer_freezed;
	/** Timer: time offset relative to the ACM clock in mode 'run' and 'stop'
	 * (s).
	 */
	double timer_offset;

} instruments_Type;

/**
 * Linked list of recycled instruments_data structures.
 */
static instruments_Type *free_list = NULL;

/** Maps a viewer into instruments_data pointer. */
#define INST(u) ((instruments_Type *)(u->inst))

/** 'Blinking' display toggle. */
static _BOOL blink = FALSE;
static double blink_toggle_time = 0.0;

static Alib_Pixel
	black_color,
	white_color,
	attitude_bank_sky_color,
	attitude_bank_ground_color,
	attitude_pitch_sky_color,
	attitude_pitch_ground_color;

static vpath_Type * ball_ladder_vpath = NULL;

static vpath_Type *anemometer_vpath = NULL;


/** Bank scale ring outer radius (in 1/1000 of the available slot half width). */
#define ATTITUDE_BANK_OUTER_RADIUS 1000

/** Bank scale ring inner radius (in 1/1000 of the available slot half width). */
#define ATTITUDE_BANK_INNER_RADIUS 700

/** Bank scale ring, upper half (sky) on a circle of radius 1000. */
static Alib_Polygon *attitude_bank_half_upper_ring;

/** Bank scale ring, lower half (ground) on a circle of radius 1000. */
static Alib_Polygon *attitude_bank_half_lower_ring;

/** Bank scale ring, zero bank tick mark. */
static Alib_Polygon *attitude_bank_zero_tick_mark;

/** Bank needle. */
static Alib_Polygon *attitude_bank_needle;

/** Pitch needle. */
static Alib_Polygon *attitude_pitch_needle;


static int inited;


static void instruments_cleanup()
{
	instruments_Type *p;

	while( free_list != NULL ){
		p = free_list;
		free_list = free_list->next;
		memory_dispose(p);
	}

	memory_dispose(ball_ladder_vpath);
	ball_ladder_vpath = NULL;

	memory_dispose(anemometer_vpath);
	anemometer_vpath = NULL;
	
	memory_dispose(attitude_bank_half_upper_ring);
	memory_dispose(attitude_bank_half_lower_ring);
	memory_dispose(attitude_bank_zero_tick_mark);
	memory_dispose(attitude_bank_needle);
	memory_dispose(attitude_pitch_needle);
	
	inited = 0;
}

/**
 * Initializes this module.
 */
static void instruments_init()
{
	int i;
	
	if( inited )
		return;
	inited = 1;
	memory_registerCleanup(instruments_cleanup);
	
	
	black_color = gui_getColorIndexString(NULL, "#000000");
	white_color = gui_getColorIndexString(NULL, "#ffffff");
	attitude_bank_sky_color = gui_getColorIndexString(NULL, "#0083cb");
	attitude_bank_ground_color = gui_getColorIndexString(NULL, "#6c5735");
	attitude_pitch_sky_color = gui_getColorIndexString(NULL, "#0074b3");
	attitude_pitch_ground_color = gui_getColorIndexString(NULL, "#5f4c2f");
	
	/*
	 * Attitude's polygons are drawn on a circle of radius
	 * ATTITUDE_BANK_OUTER_RADIUS units.
	 */
	
	// Create attitude_bank_half_upper_ring:
	attitude_bank_half_upper_ring = Alib_Polygon_new();
	int angle_deg;
	for(angle_deg = 0; angle_deg <= 180; angle_deg += 10){
		double a = units_DEGtoRAD(angle_deg);
		Alib_Polygon_addPointXY(attitude_bank_half_upper_ring,
			ATTITUDE_BANK_OUTER_RADIUS * cos(a),
			- ATTITUDE_BANK_OUTER_RADIUS * sin(a));
	}
	Alib_Polygon_addPointXY(attitude_bank_half_upper_ring,
		-ATTITUDE_BANK_INNER_RADIUS, 0);
	for(angle_deg = 170; angle_deg >= 0; angle_deg -= 10){
		double a = units_DEGtoRAD(angle_deg);
		Alib_Polygon_addPointXY(attitude_bank_half_upper_ring,
			ATTITUDE_BANK_INNER_RADIUS * cos(a),
			- ATTITUDE_BANK_INNER_RADIUS * sin(a));
	}
	
	// Create attitude_bank_half_lower_ring:
	attitude_bank_half_lower_ring = Alib_Polygon_clone(attitude_bank_half_upper_ring);
	for(i = attitude_bank_half_lower_ring->npts - 1; i >= 0; i--)
		attitude_bank_half_lower_ring->pts[i].y *= -1;
	
	// Create zero bank tick mark:
	attitude_bank_zero_tick_mark = Alib_Polygon_new();
	Alib_Polygon_addPointXY(attitude_bank_zero_tick_mark,
		0,  -ATTITUDE_BANK_INNER_RADIUS);
	Alib_Polygon_addPointXY(attitude_bank_zero_tick_mark,
		-100, -ATTITUDE_BANK_OUTER_RADIUS);
	Alib_Polygon_addPointXY(attitude_bank_zero_tick_mark,
		100, -ATTITUDE_BANK_OUTER_RADIUS);
	
	// Create bank needle:
	attitude_bank_needle = Alib_Polygon_new();
	Alib_Polygon_addPointXY(attitude_bank_needle, -100, -ATTITUDE_BANK_INNER_RADIUS + 200);
	Alib_Polygon_addPointXY(attitude_bank_needle, 100, -ATTITUDE_BANK_INNER_RADIUS + 200);
	Alib_Polygon_addPointXY(attitude_bank_needle, 0, -ATTITUDE_BANK_INNER_RADIUS);
	
	// Create pitch needle:
	//Alib_Point poly[] = { {0, 200}, {170, 50}, {500, 50}, {500, 0},
	//	{140, 0}, {0, 130}, {-140, 0}, {-500, 0}, {-500, 50}, {-170, 50} };
	Alib_Point poly[] = { {0,80}, {500, 200}, {0, 0}, {-500, 200} };
	attitude_pitch_needle = Alib_Polygon_new();
	i = sizeof(poly) / sizeof(Alib_Point) - 1;
	for( ; i >= 0; i--)
		Alib_Polygon_addPoint(attitude_pitch_needle, &poly[i]);
}


/**
 * Draws a needle rotating counter-clock wise around (xo,yo), len pixels long.
 */
static void drawNeedle(Alib_Window *w, int xo, int yo, double angle, double len, Alib_Pixel color)
{
	static Alib_Point pts[] = { {200, -70}, {1000, -5}, {1000, 5}, {200, 70} };
	Alib_Matrix m;
	Alib_MatrixIdentity(&m);
	Alib_MatrixRotate(&m, angle);
	Alib_MatrixScale(&m, 0.001 * len);
	Alib_MatrixTranslate(&m, xo, yo);
	Alib_fillPolygonWithMatrix(w, pts, 4, &m, color);
}


/**
 * Draws the turn and slip indicator.
 * @param u Viewer instance of the aircraft.
 * @param x0 Horizontal position of the center of the dial.
 * @param y0 Vertical position of the center of the dial.
 * @param width Width and height of the dial.
 */
static void instruments_turnslip_draw(viewer *u, double xo, double yo, double width)
{
	double radius, x1, y1, r, co, si, a, r1, r2, l, m;
	draw_Type *dd;
	int j;

	radius = 0.48 * width;

	dd = draw_new();

	draw_circle(dd, xo, yo, radius);

	/*
		Yaw rate:
	*/
	x1 = xo;
	y1 = yo + 0.25 * radius;

	r = 0.90 * radius;
	for( j=-30; j<= 30; j+=15 ){
		co = cos( units_DEGtoRAD(j) );
		si = sin( units_DEGtoRAD(j) );
		draw_segment(dd, x1 + r*si, y1 - r*co,  x1 + radius*si, y1 - radius*co);
	}

	/*
		This instrument measures the rotational speed around the craft
		z axis:
	*/

	a = u->c->r;
	a = fmin(a, units_DEGtoRAD(5.0));
	a = fmax(a, -units_DEGtoRAD(5.0));
	//draw_pointer(dd, x1, y1, -M_PI/2.0 + a*30.0/3.0, 0.87*radius);
	drawNeedle(u->w, x1, y1, M_PI/2.0 - a*30.0/3.0, 0.87*radius, white_color);

	/*
		Slip indicator:
	*/

	x1 = xo;
	y1 = yo - 1.5*radius;

	/* curved glass: */
	r1 = 2.0*radius;
	r2 = 2.25*radius;
	l = units_DEGtoRAD(15.0);
	draw_arc(dd, x1, y1, r1, units_DEGtoRAD(90.0) - l, units_DEGtoRAD(90.0) + l);
	draw_arc(dd, x1, y1, r2, units_DEGtoRAD(90.0) - l, units_DEGtoRAD(90.0) + l);
	co = cos(l);
	si = sin(l);
	draw_segment(dd, x1 - r1*si, y1 + r1*co,  x1 - r2*si, y1 + r2*co);
	draw_segment(dd, x1 + r1*si, y1 + r1*co,  x1 + r2*si, y1 + r2*co);

	/* center marks of the curved glass: */
	co = cos(units_DEGtoRAD(4.0));
	si = sin(units_DEGtoRAD(4.0));
	draw_segment(dd, x1 - r1*si, y1 + r1*co,  x1 - r2*si, y1 + r2*co);
	draw_segment(dd, x1 + r1*si, y1 + r1*co,  x1 + r2*si, y1 + r2*co);

	/* ball: */
	a = atan2(-u->c->G.y, -u->c->G.z);
	/* max angular displacement of the ball (approximated for little angles): */
	m = l - (r2-r1)/(r1+r2);
	a = fmin(a, m);
	a = fmax(a, -m);
	co = cos(a);
	si = sin(a);
	draw_circle(dd, x1 + (r1+r2)/2.0*si, y1 + (r1+r2)/2.0*co, 0.95*(r2-r1)/2.0);

	draw_stroke(dd, u->v, white_color);
	draw_free(dd);
}


/*
	ANEMOMETER
	==========
*/


#define ANEMOMETER_MAX_VEL 450.0
#define ANEMOMETER_MAX_ANGLE units_DEGtoRAD(320.0)
#define ANEMOMETER_LINEARITY 0.8
#define ANEMOMETER_OFFSET 30.0


static void instruments_anemometer_init(viewer * u)
{
	instruments_Type *d = INST(u);
	craftType *p = u->c->cinfo;

	d->cos_alpha_stall = cos(p->alpha_stall);
}


static double map_vel_to_angle(double vel)
{
	double a;
	static double k = 0.0;

	if( k == 0.0 )
		k = 1.0 / (sqrt(ANEMOMETER_MAX_VEL - ANEMOMETER_OFFSET + ANEMOMETER_LINEARITY*ANEMOMETER_LINEARITY) - ANEMOMETER_LINEARITY);
	vel = fmax(vel - ANEMOMETER_OFFSET, 0.0);
	a = k*(sqrt(vel + ANEMOMETER_LINEARITY*ANEMOMETER_LINEARITY) - ANEMOMETER_LINEARITY);
	a = fmin(a, 1.0);
	return a * ANEMOMETER_MAX_ANGLE;
}


static void instruments_draw_bold_arc(draw_Type *dd, double xo, double yo, double r,
	double a1, double a2)
{
	draw_arc(dd, xo, yo, r, a1, a2);
	draw_arc(dd, xo, yo, r - 1.0, a1, a2);
	draw_arc(dd, xo, yo, r + 1.0, a1, a2);
}


static vpath_Type * build_anemometer_vpath()
{
	int i, j, n, l;
	double radius, r, r1, r2, r3, fh, fw, si, co, vel, a;
	vpath_Type *p;
	double v[200];
	char buf[10];
	VMatrix m;

	p = vpath_new();

	radius = 1.0;

	/*
		Build array v[] of the velocity to display
	*/
	n = 0;
	/* add speeds from 40 up to 150 kt step 10: */
	for( i = 40; i <= 250; i+= 10 )
		v[n++] = (double) i;
	/* add speeds from 200 up to ANEMOMETER_MAX_VEL kt step 50: */
	for( i = 200; i <= ANEMOMETER_MAX_VEL; i += 10 )
		v[n++] = (double) i;
	v[n++] = -1.0;

	i = 0;
	j = 0;
	r = 0.83 * radius;  /* labels center radius */
	r1 = 0.60 * radius; /* nock - inner radius */
	r2 = 0.65 * radius; /* short nock - outer radius */
	r3 = 0.72 * radius; /* long nock - outer radius */
	while( v[j] >= 0.0 ){
		vel = v[j];
		a = map_vel_to_angle(vel);
		co = cos(a);
		si = sin(a);
		
		if(
			(vel <= 120.0 && (int) vel % 20 == 0)
		||  ((int) vel % 50 == 0)
		){
			sprintf(buf, "%d", (int) vel);
			l = strlen(buf);
			VIdentMatrix(&m);
			fh = 0.10*radius;
			fw = 2.0 * fh / l;  /* same width (fw*l) for any length (l) */
			VScaleMatrix(&m, fw, fh, 1.0);
			VTranslate(&m, r*si - 0.5*fw*l, - r*co + 0.5*fh, 0.0);
			vpath_draw_string(p, buf, l, &m);

			vpath_moveTo(p, &(VPoint){r1*si, - r1*co, 0.0});
			vpath_lineTo(p, &(VPoint){r3*si, - r3*co, 0.0});
		} else {
			vpath_moveTo(p, &(VPoint){r1*si, - r1*co, 0.0});
			vpath_lineTo(p, &(VPoint){r2*si, - r2*co, 0.0});
		}

		j++;
	}


	/*
		"KT":
	*/

	strcpy(buf, "KT");
	l = strlen(buf);
	fh = 0.15 * radius;
	fw = fh;
	VIdentMatrix(&m);
	VScaleMatrix(&m, fw, fh, 1.0);
	VTranslate(&m, -0.5*fw*l, 0.3*radius + 0.5*fh, 0.0);
	vpath_draw_string(p, buf, l, &m);

	return p;
}


static void instruments_anemometer_draw(viewer *u, double xo, double yo, double width)
{
	double radius, a, b, r1, r2, ias;
	draw_Type *dd;
	VMatrix m;

	radius = 0.48 * width;

	dd = draw_new();
	draw_circle(dd, xo, yo, radius);
	double cos_alpha_stall = INST(u)->cos_alpha_stall;

	/*
		Speed limit arcs
	*/

	craftType *p = u->c->cinfo;

	r1 = 0.55 * radius;
	r2 = 0.50 * radius;

	// White arc:
	if( p->Vs0 > 0.0 && p->Vfe > 0.0 ){
		instruments_draw_bold_arc(dd, xo, yo, r1,
			map_vel_to_angle(p->Vs0 * cos_alpha_stall) - 0.5*M_PI,
			map_vel_to_angle(p->Vfe) - 0.5*M_PI);
	}
	if( p->Vs0 > 0.0 && p->Vs1 > 0.0 ){
		instruments_draw_bold_arc(dd, xo, yo, r2,
			map_vel_to_angle(p->Vs0 * cos_alpha_stall) - 0.5*M_PI,
			map_vel_to_angle(p->Vs1 * cos_alpha_stall) - 0.5*M_PI);
	}
	
	// Green arc:
	if( p->Vs1 > 0.0 && p->Vfe > 0.0 && p->Vno > 0.0 ){
		draw_Type *green = draw_new();
		b = map_vel_to_angle(p->Vno) - 0.5*M_PI;
		instruments_draw_bold_arc(green, xo, yo, r1,
			map_vel_to_angle(p->Vfe) - 0.5*M_PI, b);
		instruments_draw_bold_arc(green, xo, yo, r2,
			map_vel_to_angle(p->Vs1 * cos_alpha_stall) - 0.5*M_PI, b);
		draw_stroke(green, u->v, radarColor);
		draw_free(green);
	}

	// Yellow arc:
	if( p->Vno > 0.0 && p->Vne > 0.0 ){
		draw_Type *yellow = draw_new();
		a = map_vel_to_angle(p->Vno) - 0.5*M_PI;
		b = map_vel_to_angle(p->Vne) - 0.5*M_PI;
		instruments_draw_bold_arc(yellow, xo, yo, r1, a, b);
		instruments_draw_bold_arc(yellow, xo, yo, r2, a, b);
		draw_stroke(yellow, u->v, yellowColor);
		draw_free(yellow);
	}

	// Red line (actually here we draw a short arc):
	if( p->Vne > 0.0 ){
		a = map_vel_to_angle(p->Vne);
		draw_Type *red = draw_new();
		instruments_draw_bold_arc(red, xo, yo, r1,
			a - 0.5*M_PI,
			a + units_DEGtoRAD(3) - 0.5*M_PI);
		instruments_draw_bold_arc(red, xo, yo, r2,
			a - 0.5*M_PI,
			a + units_DEGtoRAD(3) - 0.5*M_PI);
		draw_stroke(red, u->v, redColor);
		draw_free(red);
	}

	if( anemometer_vpath == NULL )
		anemometer_vpath = build_anemometer_vpath();
	
	VIdentMatrix(&m);
	VScaleMatrix(&m, radius, radius, radius);
	VTranslate(&m, xo, yo, -1.0);
	vpath_stroke(anemometer_vpath, &m, u->w, white_color);
	
	/* Mach number: */
	char s[5];
	snprintf(s, sizeof(s), "%.2f", u->c->mach);
	double fh = 0.12*radius;
	double fw = VFontWidthPixels(u->v, (int) (fh+0.5));
	VDrawStrokeString(u->v,
		(int) (xo - 0.5 * fw * strlen(s) + 0.5),
		(int) (yo - 0.35*radius + 0.5),
		s, strlen(s), (int)(fh+0.5), white_color);

	/* Draw pointer: */
	ias = units_FPStoKT( u->c->IAS );
	if( ias < ANEMOMETER_OFFSET )
		ias = ANEMOMETER_OFFSET;
	a = map_vel_to_angle( ias );
	//draw_pointer(dd, xo, yo, a - M_PI/2.0, 0.60 * radius);
	draw_stroke(dd, u->v, white_color);

	draw_free(dd);
	
	drawNeedle(u->w, xo, yo, M_PI/2.0 - a, 0.60 * radius, white_color);
}


/*
	ATTITUDE INDICATOR
	==================
*/

#define ATTITUDE_MAX_PITCH_OFFSET units_DEGtoRAD(10.0)
#define ATTITUDE_MAX_PITCH units_DEGtoRAD(70.0)
#define ATTITUDE_MAX_BANK  units_DEGtoRAD(70.0)
#define ATTITUDE_UPDATE_PERIOD 0.2


static void instruments_attitude_update(viewer *u)
{
	VPoint *gyro, g;
	double m, k, dt;

	/*
		Erection system: the gyro axis (smoothly) follows the local
		vertical:
	*/
	dt = curTime - INST(u)->attitude_erection_upd;
	if( dt > ATTITUDE_UPDATE_PERIOD ){
		INST(u)->attitude_erection_upd = curTime;
		gyro = &INST(u)->attitude_gyro;
		VTransform_(&u->c->G, &u->c->trihedral, &g);
		m = VMagnitude(&g);
		if( m > 0.98  &&  m < 1.02 /* erection cut-out */){
			k = 0.005 * dt / m;
			g.x = gyro->x + k * g.x;
			g.y = gyro->y + k * g.y;
			g.z = gyro->z + k * g.z;
			m = VMagnitude(&g);
			if( m > 1e-6 ){
				k = 1.0 / m;
				gyro->x = k * g.x;
				gyro->y = k * g.y;
				gyro->z = k * g.z;
			}
		}
	}

	/*
		Compute gyro angles of bank and pitch:
	*/

	VReverseTransform_(&INST(u)->attitude_gyro, &u->c->trihedral, &g);
	double bank = atan2(-g.y, -g.z); /* positive turning right */
	double pitch = atan2(g.x, sqrt(g.y*g.y + g.z*g.z)); /* positive pulling up */

	int tilt = FALSE;

	if( bank > ATTITUDE_MAX_BANK ){
		tilt = TRUE;
		bank = ATTITUDE_MAX_BANK;
	} else if( bank < -ATTITUDE_MAX_BANK ){
		tilt = TRUE;
		bank = -ATTITUDE_MAX_BANK;
	}

	if( pitch > ATTITUDE_MAX_PITCH ){
		tilt = TRUE;
		pitch = ATTITUDE_MAX_PITCH;
	} else if( pitch < -ATTITUDE_MAX_PITCH ){
		tilt = TRUE;
		pitch = -ATTITUDE_MAX_PITCH;
	}

	if( tilt ){
		g.x = sin(pitch) * cos(bank);
		g.y = -sin(bank);
		g.z = -cos(pitch) * cos(bank);
		VTransform_(&g, &u->c->trihedral, &INST(u)->attitude_gyro);
	}
	
	INST(u)->attitude_bank = bank;
	INST(u)->attitude_pitch = pitch;
}


static void instruments_attitude_init(viewer *u)
{
	INST(u)->attitude_pitch_offset = 0.0;

	/* Init gyro randomly oriented: */
	INST(u)->attitude_gyro.x = 0.2;
	INST(u)->attitude_gyro.y = 0.2;
	INST(u)->attitude_gyro.z = -0.959;
	INST(u)->attitude_erection_upd = curTime;
	// Initialize bank and pitch angles:
	instruments_attitude_update(u);
}


void instruments_attitude_adjust_pitch(viewer * u, double delta)
{
	if( u->inst == NULL )
		return;

	INST(u)->attitude_pitch_offset += delta;
	INST(u)->attitude_pitch_offset = fmin(INST(u)->attitude_pitch_offset,
		ATTITUDE_MAX_PITCH_OFFSET);
	INST(u)->attitude_pitch_offset = fmax(INST(u)->attitude_pitch_offset,
		-ATTITUDE_MAX_PITCH_OFFSET);
}


void instruments_attitude_reset(viewer * u)
{
	VPoint g;

	if( u->inst == NULL )
		return;

	g.x = 0.0;  g.y = 0.0;  g.z = -1.0;
	VTransform_(&g, &u->c->trihedral, &INST(u)->attitude_gyro);
	// Initialize bank and pitch angles:
	instruments_attitude_update(u);
}


/**
	Draw arc on attitude ball. The arc starts from (pitch1,yaw1) and ends on
	(pitch2,yaw2). Longer arcs are split in two or more segments.
*/
static void build_ball_draw_arc(
	double pitch1, double yaw1,
	double pitch2, double yaw2)
{
	int n, i;
	VPoint a, b, c;
	double m;

	pitch1 = units_DEGtoRAD(pitch1);
	yaw1  = units_DEGtoRAD(yaw1);
	pitch2 = units_DEGtoRAD(pitch2);
	yaw2  = units_DEGtoRAD(yaw2);

	n = (int) (fmax( fabs(pitch2-pitch1), fabs(yaw2-yaw1) )
		/ units_DEGtoRAD(5) + 0.5);

	if( n < 1 )
		n = 1;
	
	VSetPoint(&a, cos(pitch1)*sin(yaw1), -sin(pitch1), -cos(pitch1)*cos(yaw1));
	VSetPoint(&b, cos(pitch2)*sin(yaw2), -sin(pitch2), -cos(pitch2)*cos(yaw2));

	vpath_moveTo(ball_ladder_vpath, &a);
	
	for( i=1; i<=n; i++ ){

		VSetPoint(&c, a.x + (double)i/n*(b.x-a.x),
			a.y - (double)i/n*(b.y-a.y),
			a.z + (double)i/n*(b.z-a.z));

		m = VMagnitude(&c);

		VSetPoint(&c, c.x/m, c.y/m, c.z/m);

		vpath_lineTo(ball_ladder_vpath, &c);
	}
}


static void build_ball_draw_str(int pitch, int yaw)
{
	VMatrix m;
	double fh, fw;
	char s[10];
	int s_len;

	sprintf(s, "%d", abs(pitch));
	s_len = strlen(s);

	fh = 0.05;
	fw = 0.05;

	VIdentMatrix(&m);
	VScaleMatrix(&m, fw, fh, 1.0);
	VTranslate(&m, -s_len*fw/2, fh/2, -1.0);
	VRotate(&m, YRotation, units_DEGtoRAD(yaw));
	VRotate(&m, XRotation, units_DEGtoRAD(-pitch));
	vpath_draw_string(ball_ladder_vpath, s, s_len, &m);

	VIdentMatrix(&m);
	VScaleMatrix(&m, fw, fh, 1.0);
	VTranslate(&m, -s_len*fw/2, fh/2, -1.0);
	VRotate(&m, YRotation, units_DEGtoRAD(-yaw));
	VRotate(&m, XRotation, units_DEGtoRAD(-pitch));
	vpath_draw_string(ball_ladder_vpath, s, s_len, &m);
}


static void build_ball_ladder()
{
	ball_ladder_vpath = vpath_new();

	/* Pitch ladder: */
	build_ball_draw_arc( 30,-15,  30,+15);  build_ball_draw_str( 30, 20 );
	build_ball_draw_arc( 25, -3,  25, +3);
	build_ball_draw_arc( 20,-10,  20,+10);  build_ball_draw_str( 20, 15 );
	build_ball_draw_arc( 15, -3,  15, +3);
	build_ball_draw_arc( 10, -5,  10, +5);  build_ball_draw_str( 10, 10 );
	build_ball_draw_arc(  5, -3,   5, +3);
	build_ball_draw_arc(  0,-45,   0,+45);
	build_ball_draw_arc( -5, -3,  -5, +3);
	build_ball_draw_arc(-10, -5, -10, +5);  build_ball_draw_str(-10, 10 );
	build_ball_draw_arc(-15, -3, -15, +3);
	build_ball_draw_arc(-20,-10, -20,+10);  build_ball_draw_str(-20, 15 );
	build_ball_draw_arc(-25, -3, -25, +3);
	build_ball_draw_arc(-30,-15, -30,+15);  build_ball_draw_str(-30, 20 );
}


#define ATTITUDE_EDGES_MAX_NUMBER 200

static int attitude_pitch_dome_edges_number;
static VPoint attitude_pitch_dome_sky_edges[200];
static VPoint attitude_pitch_dome_ground_edges[200];


static void build_attitude_pitch_dome_background()
{
	int i, lon_deg, lat_deg;
	VPoint p;
	double t, lon_rad, lat_rad;
	
	int lon_step = 10;
	int lon_max = 40;
	int lat_step = 10;
	int lat_max = 40;

	i = 0;

	/*
	 * We are drawing the edges of the "north" half doom.
	 * "x" goes right, "y" goes down, "z" goes forward.
	 * This doom is then parallel projected to the xy plane of the screen, giving
	 * a nice 3D effect while it rotates.
	 * 
	 * Add points of the horizon line:
	 */
	lat_rad = 0;
	for(lon_deg = -lon_max; lon_deg <= lon_max; lon_deg += lon_step){
		lon_rad = units_DEGtoRAD(lon_deg);
		t = cos(lat_rad);
		p = (VPoint) {t * sin(lon_rad), -sin(lat_rad), -t * cos(lon_rad)};
		attitude_pitch_dome_sky_edges[i] = p;
		p.y = -p.y;
		attitude_pitch_dome_ground_edges[i] = p;
		i++;
	}
	
	// Add points to the right ("meridian" at lon 30E):
	lon_deg = lon_max;
	lon_rad = units_DEGtoRAD(lon_deg);
	for(lat_deg = lat_step; lat_deg <= lat_max; lat_deg += lat_step){
		lat_rad = units_DEGtoRAD(lat_deg);
		t = cos(lat_rad);
		p = (VPoint) {t * sin(lon_rad), -sin(lat_rad), -t * cos(lon_rad)};
		attitude_pitch_dome_sky_edges[i] = p;
		p.y = -p.y;
		attitude_pitch_dome_ground_edges[i] = p;
		i++;
	}
	
	// Add points to the upper edge ("parallel" at lat 30N):
	lat_deg = lat_max;
	lat_rad = units_DEGtoRAD(lat_deg);
	for(lon_deg = lon_max - lon_step; lon_deg >= -lon_max; lon_deg -= lon_step){
		lon_rad = units_DEGtoRAD(lon_deg);
		t = cos(lat_rad);
		p = (VPoint) {t * sin(lon_rad), -sin(lat_rad), -t * cos(lon_rad)};
		attitude_pitch_dome_sky_edges[i] = p;
		p.y = -p.y;
		attitude_pitch_dome_ground_edges[i] = p;
		i++;
	}
	
	// Add points to the left edge ("meridian" at lon 30W):
	lon_deg = -lon_max;
	lon_rad = units_DEGtoRAD(lon_deg);
	for(lat_deg = lat_max - lat_step; lat_deg >= lat_step; lat_deg -= lat_step){
		lat_rad = units_DEGtoRAD(lat_deg);
		t = cos(lat_rad);
		p = (VPoint) {t * sin(lon_rad), -sin(lat_rad), -t * cos(lon_rad)};
		attitude_pitch_dome_sky_edges[i] = p;
		p.y = -p.y;
		attitude_pitch_dome_ground_edges[i] = p;
		i++;
	}
	
	assert( i < ATTITUDE_EDGES_MAX_NUMBER );
	attitude_pitch_dome_edges_number = i;
}


/**
 * Draw the pitch scale dome background.
 * @param u
 * @param R Maps the points of a unit radius dome to the screen.
 */
static void instruments_attitude_draw_pitch_dome_background(viewer * u, VMatrix *R)
{
	if( attitude_pitch_dome_edges_number == 0 )
		build_attitude_pitch_dome_background();
	
	Alib_Point sky[ATTITUDE_EDGES_MAX_NUMBER], ground[ATTITUDE_EDGES_MAX_NUMBER];
	int i;
	for( i = attitude_pitch_dome_edges_number - 1; i >= 0; i-- ){
		VPoint p;
		VTransform(&attitude_pitch_dome_sky_edges[i], R, &p);
		sky[i].x = p.x + 0.5;  sky[i].y = p.y + 0.5;
		VTransform(&attitude_pitch_dome_ground_edges[i], R, &p);
		ground[i].x = p.x + 0.5;  ground[i].y = p.y + 0.5;
	}

	Alib_fillPolygon(u->w, sky, attitude_pitch_dome_edges_number,
		attitude_pitch_sky_color);
	Alib_fillPolygon(u->w, ground, attitude_pitch_dome_edges_number,
		attitude_pitch_ground_color);
}


static void instruments_attitude_draw(viewer * u, double xo, double yo, double width)
{
	Alib_Window *w;
	int j;
	double bank, pitch, radius, a, r, co, si;
	VMatrix R;
	Alib_Matrix bankMatrix;

	w = u->v->w;
	radius = 0.48 * width;
	bank = INST(u)->attitude_bank;
	pitch = INST(u)->attitude_pitch;
	
	Alib_setClipRect(w, &u->attitude);

	/*
		Draw pitch scale. First, build a rotational matrix
		acting on vectors of the screen frame: x axis pointing right,
		y axis down, z forward:
	*/

	r = 0.60 * width;  /* radius of the rotating pitch scale dome */

	VIdentMatrix(&R);
	VRotate(&R, XRotation, pitch);
	VRotate(&R, ZRotation, -bank);
	VScaleMatrix(&R, r, r, r);
	VTranslate(&R, xo, yo, 0.0);

	Alib_setClipRect(u->w, &u->attitude);
	Alib_Rect rect = u->attitude;
	Alib_expandRect(&rect, -0.35*radius, -0.35*radius);
	Alib_setClipRect(u->w, &rect);
	instruments_attitude_draw_pitch_dome_background(u, &R);
	if( ball_ladder_vpath == NULL )
		build_ball_ladder();
	vpath_stroke(ball_ladder_vpath, &R, w, white_color);
	Alib_setClipRect(u->w, &u->attitude);
	

	/*
	 * Draw bank scale background. Upper half is blue, lower half is brown.
	 */
	r = radius;
	Alib_MatrixIdentity(&bankMatrix);
	Alib_MatrixRotate(&bankMatrix, bank);
	Alib_MatrixScale(&bankMatrix, 0.001 * r);
	Alib_MatrixTranslate(&bankMatrix, xo, yo);
	Alib_fillPolygonWithMatrix(u->w,
		attitude_bank_half_upper_ring->pts,
		attitude_bank_half_upper_ring->npts,
		&bankMatrix, attitude_bank_sky_color);
	Alib_fillPolygonWithMatrix(u->w,
		attitude_bank_half_lower_ring->pts,
		attitude_bank_half_lower_ring->npts,
		&bankMatrix, attitude_bank_ground_color);
	
	/*
		Bank scale tick marks:
	*/
	
	// Zero bank tick mark:
	Alib_fillPolygonWithMatrix(u->w, attitude_bank_zero_tick_mark->pts,
		attitude_bank_zero_tick_mark->npts, &bankMatrix, white_color);

	// 10, 20, 30, 60 and 90 DEG bank tick marks:
	for( j = 10; j <= 90; j+=10 ){
		if( !( j <= 30 || (j % 30) == 0 ) )
			continue;
		a = units_DEGtoRAD(j);
		co = cos(a);
		si = sin(a);
		int outer;
		if( j % 30 == 0 )
			outer = ATTITUDE_BANK_OUTER_RADIUS; // major tick outer radius
		else
			outer = (ATTITUDE_BANK_OUTER_RADIUS + ATTITUDE_BANK_INNER_RADIUS) / 2; // minor tick outer radius

		// bank left tick:
		Alib_Point f = (Alib_Point) {ATTITUDE_BANK_INNER_RADIUS * si, -ATTITUDE_BANK_INNER_RADIUS * co};
		Alib_Point g = (Alib_Point) {outer * si, -outer * co};
		Alib_MatrixTransformPoint(&f, &bankMatrix, &f);
		Alib_MatrixTransformPoint(&g, &bankMatrix, &g);
		Alib_drawLine(u->w, f.x, f.y, g.x, g.y, white_color);

		// bank right tick:
		f = (Alib_Point) {-ATTITUDE_BANK_INNER_RADIUS * si, -ATTITUDE_BANK_INNER_RADIUS * co};
		g = (Alib_Point) {-outer * si, -outer * co};
		Alib_MatrixTransformPoint(&f, &bankMatrix, &f);
		Alib_MatrixTransformPoint(&g, &bankMatrix, &g);
		Alib_drawLine(u->w, f.x, f.y, g.x, g.y, white_color);
	}

	/*
		Bank needle:
	*/
	
	Alib_Matrix m;
	Alib_MatrixIdentity(&m);
	Alib_MatrixScale(&m, 0.001 * r);
	Alib_MatrixTranslate(&m, xo, yo);
	Alib_fillPolygonWithMatrix(u->w,
		attitude_bank_needle->pts,
		attitude_bank_needle->npts,
		&m, magentaColor);

	/*
		Pitch needle:
	*/
	
	Alib_MatrixTranslate(&m, 0, radius * INST(u)->attitude_pitch_offset);
	Alib_fillPolygonWithMatrix(u->w,
		attitude_pitch_needle->pts,
		attitude_pitch_needle->npts,
		&m, magentaColor);
}


/*
	ALTIMETER
	=========
*/


static void instruments_altimeter_init(viewer *u)
{
	INST(u)->altimeter_p0 = 2992;
}


void instruments_altimeter_correction(viewer *u, int delta)
{
	int p0;

	if( u->inst == NULL )
		return;

	p0 = INST(u)->altimeter_p0;

	p0 += delta;
	if( p0 < 2800 )
		p0 = 2800;
	else if( p0 > 3100 )
		p0 = 3100;
	
	INST(u)->altimeter_p0 = p0;
}


static void instruments_altimeter_draw(viewer * u, double xo, double yo, double width)
{
	Alib_Window *w;
	double alt, radius, r, r1, r2, a, fw, fh, co, si, x;
	int j, hi, lo, p0;
	draw_Type *dd;
	char buf[20];

	w = u->v->w;
	Alib_setClipRect(w, &u->altimeter);

	radius = 0.48 * width;

	dd = draw_new();

	draw_circle(dd, xo, yo, radius);

	j = 0;
	r = 0.85 * radius;  /* tick mark with number */
	r1 = 0.92 * radius; /* tick mark without number */
	r2 = 0.70 * radius; /* center circonf. for numbers */
	fh = 0.15 * radius;
	for( a = 0.0; a < units_DEGtoRAD(359.9); a += units_DEGtoRAD(360.0/20.0) ){
		co = cos(a);
		si = sin(a);
		if( j % 10 == 0 ){
			sprintf(buf, "%d", j/10);
			draw_string_centered(u->v, xo + r2*si, yo - r2*co, fh, buf, white_color);
			draw_segment(dd, xo + r*si, yo - r*co,   xo + radius*si, yo - radius*co);
		} else {
			draw_segment(dd, xo + r1*si, yo - r1*co,   xo + radius*si, yo - radius*co);
		}
		j = j+5;
	}

	/* FIXME: what if alt<0? */

	alt = units_METERStoFEET(u->c->w.z);
	/* apply altitude correction: */
	p0 = INST(u)->altimeter_p0;
	alt = alt + 949.9 * (p0*0.01 - 29.92);
	if( alt < -0.5 )
		alt = 1e5+alt;

	j = (int) (alt + 0.5);
	j = (j + 5) / 10 * 10;
	lo = j % 100;
	hi = j / 100;

	/* Thousand pointer: */
	a = fmod(alt, 10000.0) * M_PI / 5000.0;
	//draw_pointer(dd, xo, yo, a - units_DEGtoRAD(90.0), 0.5*radius);
	drawNeedle(u->w, xo, yo, units_DEGtoRAD(90.0) - a, 0.5*radius, white_color);

	/* Hundreds pointer: */
	a = fmod(alt, 1000.0) * M_PI / 500.0;
	//draw_pointer(dd, xo, yo, a - units_DEGtoRAD(90.0), 0.8*radius);
	drawNeedle(u->w, xo, yo, units_DEGtoRAD(90.0) - a, 0.8*radius, white_color);

	/* Digital value, flight level part: */
	fh = 0.18*radius;
	fw = VFontWidthPixels(u->v, (int) (fh+0.5));
	sprintf(buf, "%03d", hi);
	x = fw * 1.0;
	VDrawStrokeString(u->v,
		(int) (xo - fw * (strlen(buf)-1) + 0.5),
		(int) (yo - 0.2*radius + 0.5),
		buf, strlen(buf), (int)(fh+0.5), white_color);

	/* Digital value, last two digits: */
	fh = 0.12*radius;
	fw = VFontWidthPixels(u->v, (int)(fh+0.5));
	sprintf(buf, "%02d", lo);
	VDrawStrokeString(u->v,
		(int) (xo + x + 0.5),
		(int) (yo - 0.2*radius + 0.5),
		buf, strlen(buf), (int)(fh+0.5), white_color);

	/*
		Draw altitude correction:
	*/
	draw_string_centered(u->v, xo, yo + 0.1*radius, 0.10*radius, "inHg   hPa", white_color);
	sprintf(buf, "%2.2f %04d", 0.01*p0, (int)(1013.25/29.92 * 0.01 * p0 + 0.5));
	draw_string_centered(u->v, xo, yo+0.30*radius, 0.10*radius, buf, white_color);

	draw_string_centered(u->v, xo, yo-0.5*radius, 0.10*radius, "FEET", white_color);

	draw_stroke(dd, u->v, white_color);
	draw_free(dd);
}


/*
	Vertical speed indicator (VSI)
	==============================
*/

static double vario[] = {0.0, 100.0, 200.0, 300.0, 400.0, 500.0, 1000.0,
	2000.0, 3000.0, 4000.0, -1.0};


static double map_vario_to_angle(double v)
{
	static double k = 0.0;
	double a;

	if( k == 0.0 ){
		/* limit of the scale at 4000 ft/min: */
		k = M_PI / (sqrt(4500.0 + 15.0*15.0) - 15.0);
	}

	v = fmin(v, 4000.0);
	v = fmax(v, -4000.0);

	a = k * (sqrt(fabs(v) + 15.0*15.0) - 15.0);

	if( v < 0.0 )
		a = -a;
	
	return a;
}


static void instruments_vsi_init(viewer *u)
{
	INST(u)->vsi_last_upd = curTime;
	INST(u)->vsi_last_vs = - u->c->Cg.z * 60.0;   /* vertical speed, ft/min */
}


#define VSI_K (1.0/4.0)
/*
	If the vertical speed changes abruptly from vs(0) to some vs_actual,
	then in our model of the instrument lag the indicated vertical speed
	will be:

		vs(t) = (vs(0) - vs_actual) * exp(-VSI_K*t) + vs_actual
	
	where 1/VSI_K is the "time constant" (i.e. an extimation of the lag time).
*/


static void instruments_vsi_update(viewer *u)
{
	double vs, dt;

	vs = - u->c->Cg.z * 60.0;    /* vertical speed, ft/min */
	dt = curTime - INST(u)->vsi_last_upd;
	INST(u)->vsi_last_vs += VSI_K * (vs - INST(u)->vsi_last_vs) * dt;
	INST(u)->vsi_last_upd = curTime;
}


static void instruments_vsi_draw(viewer * u, double xo, double yo, double width)
{
	Alib_Window *w;
	double climb, radius, r, r1, r2, r3, a, fh, co, si;
	int j;
	draw_Type *dd;
	char buf[20];

	w = u->v->w;
	Alib_setClipRect(w, &u->vsi);

	radius = 0.48 * width;

	dd = draw_new();

	draw_circle(dd, xo, yo, radius);

	r1 = 0.87 * radius; /* tick mark with number */
	r2 = 0.92 * radius; /* tick mark without number */
	r3 = 0.70 * radius; /* center of the number */
	fh = 0.12 * radius;
	j = 0;
	while( vario[j] >= 0 ){
		climb = vario[j];
		a = map_vario_to_angle(climb);
		co = cos(a);
		si = sin(a);
		sprintf(buf, "%.0f", climb/100);
		if( (int) (climb + 0.5) % 500 == 0 ){
			draw_string_centered(u->v, xo - r3*co, yo - r3*si, fh, buf, white_color);
			draw_string_centered(u->v, xo - r3*co, yo + r3*si, fh, buf, white_color);
			r = r1;
		} else {
			r = r2;
		}
		draw_segment(dd, xo - r*co, yo - r*si,   xo - radius*co, yo - radius*si);
		/* FIXME: because of a bug in Vlib, drawing a segment over another
		segment results in no segment drawn at all! This if() fixes that: */
		if( j != 0 )
			draw_segment(dd, xo - r*co, yo + r*si,   xo - radius*co, yo + radius*si);
		j++;
	}

	a = map_vario_to_angle(INST(u)->vsi_last_vs);
	//draw_pointer(dd, xo, yo, a - M_PI, 0.80*radius);
	drawNeedle(u->w, xo, yo, M_PI - a, 0.80*radius, white_color);

#ifdef DEBUG
	/* actual v.s.: */
	climb = - u->c->Cg.z * 60.0;    /* climb ratio, ft/min */
	a = map_vario_to_angle(climb);
	draw_pointer(dd, xo, yo, a - M_PI, 0.40*radius);
#endif

	draw_string_centered(u->v, xo, yo-0.3*radius, 0.15*radius, "FPM", white_color);
	draw_string_centered(u->v, xo, yo+0.3*radius, 0.12*radius, "x100", white_color);

	draw_stroke(dd, u->v, white_color);
	draw_free(dd);
}


/*
	AutoPilot System lights panel
	=============================
*/

static void instruments_ap_panel(viewer *u, double x1, double y1, double x2, double y2)
{
	Alib_Window *w;
	double margin, width, h, fh;

	void draw_light(char *label, int order, _BOOL on)
	{
		double xa, ya, xb, yb;
		Alib_Rect r;

		xa = x1 + order*width + margin;
		ya = y1 + margin;
		xb = x1 + order*width + width - margin;
		yb = y1 + h - margin;
		if( on ){
			Alib_setRect(&r, xa, ya, xb, yb);
			Alib_fillRect(w, &r, magentaColor);
		}
		draw_string_centered(u->v, (xa+xb)/2.0, (ya+yb)/2.0,
			fh, label, black_color);
	}


	w = u->w;
	Alib_setClipRect(w, &u->stripe);

	/* size of every light: */
	width = (x2-x1)/7.0;
	h = y2-y1;

	/* margin around each light: */
	margin = 0.1*h;

	/* font height: */
	fh = 0.5 * (h - 2.0*margin);

	draw_light("Rate", 0, aps_rate_control_enabled(u->c));

	draw_light("VS/ALT", 1, aps_ap_enabled(u->c)
		&& ( ! aps_ap_warn(u->c) || ! blink ));

	draw_light("Nav", 2, aps_an_enabled(u->c)
		&& ( ! aps_an_warn(u->c) || ! blink ));

	draw_light("Land", 3, aps_al_enabled(u->c)
		&& ( ! aps_al_warn(u->c) || ! blink ));

	draw_light("Turn", 4, aps_aw_enabled(u->c)
		&& ( ! aps_aw_warn(u->c) || ! blink ));

	draw_light("Thr", 5, aps_at_enabled(u->c)
		&& ( ! aps_at_warn(u->c) || ! blink ));

	draw_light("Coord", 6, aps_ac_enabled(u->c)
	    && ( ! aps_ac_warn(u->c) || ! blink ));
}


/*
	TIMER
	=====
*/


static void instruments_timer_init(viewer *u)
{
	INST(u)->timer_op = 0; /* do not display */
}


void instruments_timer_toggle(viewer *u)
{
	if( u->inst == NULL )
		return;

	INST(u)->timer_op = (INST(u)->timer_op + 1) % 3;

	if ( INST(u)->timer_op == 1 ) {
		INST(u)->timer_offset = curTime;

	} else if ( INST(u)->timer_op == 2 ) {
		INST(u)->timer_freezed = (int) (curTime - INST(u)->timer_offset + 0.5);

	}
}


static void instruments_timer_draw(viewer *u, double x1, double y1, double x2, double y2)
{
	Alib_Window *w;
	int t, l;
	double h, fh, fw;
	char buffer[20];
	Alib_Rect rect;

	if ( INST(u)->timer_op == 0 ) {
		return;

	} else if ( INST(u)->timer_op == 1 ) {
		t = (int) (curTime - INST(u)->timer_offset);

	} else {
		t = INST(u)->timer_freezed;
	}

	if (t >= 3600) {
		int h, m, s;
		h = t / 3600;
		t = t % 3600;
		m = t / 60;
		s = t % 60;
		sprintf(buffer, "%d:%02d:%02d", h, m, s);
	} else if (t >= 60) {
		int m, s;
		m = t / 60;
		s = t % 60;
		sprintf(buffer, "%d:%02d", m, s);
	} else {
		sprintf(buffer, "%d", t);
	}

	/*
		Background:
	*/
	w = u->v->w;
	h = y2 - y1;
	Alib_setRect(&rect, x1, y1, x2, y2);
	Alib_setClipRect(w, &rect);
	
	fh = h/2.0;
	fw = VFontWidthPixels(u->v, (int)(fh+0.5));
	l = strlen(buffer);

	VDrawStrokeString(u->v,
		(int) (x2 - (l+2)*fw + 0.5),
		(int) (y1 + h/2.0 + fh/2.0 + 0.5),
		buffer, l, (int)(fh+0.5), white_color);
}


void instruments_update(viewer * u)
{
	if( u->inst == NULL )
		return;

	instruments_attitude_update(u);
	instruments_vsi_update(u);
}


void instruments_draw(viewer * u)
{
	Alib_Window *w;
	double width;
	Alib_Rect rect;
	
	/* Update instruments. */

	if( u->inst == NULL || ! INST(u)->enabled )
		return;
	
	w = u->v->w;

	/* We have only the flux gate, not an inertial platform: */
	u->c->showMag = TRUE;
	
	/* Update blink flag: */
	if( curTime >= blink_toggle_time ){
		blink = ! blink;
		if( blink )
			blink_toggle_time = curTime + 0.1;
		else
			blink_toggle_time = curTime + 0.3;
	}

	/* Clear area of the classic instruments: */
	Alib_setRect(&rect, 0, u->stripe.a.y, gui_getWidth(u->gui), u->engine.a.y);
	Alib_setClipRect(w, &rect);
	Alib_fillRect(w, &rect, panelBackgroundColor);

	width = RectWidth(u->vsi); /* FIXME: see FIXME above */

	/* Instruments that occupy a standard slot: */
	instruments_turnslip_draw  (u, u->turn.a.x+width/2.0, u->turn.a.y+width/2.0, width);
	instruments_anemometer_draw(u, u->anemometer.a.x+width/2.0, u->anemometer.a.y+width/2.0, width);
	instruments_attitude_draw  (u, u->attitude.a.x+width/2.0, u->attitude.a.y+width/2.0, width);
	instruments_altimeter_draw (u, u->altimeter.a.x+width/2.0, u->altimeter.a.y+width/2.0, width);
	instruments_vsi_draw       (u, u->vsi.a.x+width/2, u->vsi.a.y+width/2, width);

	/* Timer and auto-pilot system lights goes in the stripe: */
	instruments_timer_draw     (u, u->stripe.a.x, u->stripe.a.y, width, u->stripe.b.y);
	instruments_ap_panel       (u, width, u->stripe.a.y, u->stripe.b.x, u->stripe.b.y);
}


void instruments_enable(viewer * u)
{
	if( u->inst == NULL ){
		instruments_init();
		if( free_list == NULL ){
			u->inst = memory_allocate( sizeof(instruments_Type), NULL );
		} else {
			u->inst = free_list;
			free_list = free_list->next;
			INST(u)->next = NULL;
		}
		instruments_anemometer_init(u);
		instruments_attitude_init(u);
		instruments_altimeter_init(u);
		instruments_vsi_init(u);
		instruments_timer_init(u);
	}
	INST(u)->enabled = TRUE;
}


void instruments_disable(viewer * u)
{
	if( u->inst == NULL )
		return;
	INST(u)->enabled = FALSE;
}


_BOOL instruments_isEnabled(viewer * u)
{
	return (u->inst != NULL) && INST(u)->enabled;
}


void instruments_free(viewer *u)
{
	if( u->inst == NULL )
		return;
	
	INST(u)->next = free_list;
	free_list = u->inst;
	u->inst = NULL;
}
