⛏️ index : haiku.git

/*

	ChartWindow.cpp

	by Pierre Raynaud-Richard.

	Copyright 1998 Be Incorporated, All Rights Reserved.

*/

#include "ChartWindow.h"

#include <AppFileInfo.h>
#include <Application.h>
#include <Bitmap.h>
#include <Box.h>
#include <Button.h>
#include <ByteOrder.h>
#include <Catalog.h>
#include <CheckBox.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <FindDirectory.h>
#include <Menu.h>
#include <MenuBar.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <Path.h>
#include <PlaySound.h>
#include <PopUpMenu.h>
#include <RadioButton.h>
#include <Screen.h>
#include <Slider.h>
#include <TextControl.h>

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

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "ChartWindow"

/* pseudo-random generator parameters (not very good ones,
   but good enough for what we do here). */
enum {
	CRC_START		= 0x29dec231,
	CRC_KEY			= 0x1789feb3
};

#define	MAX_FONT_SIZE	12.0f

/* various offset, width, height and position used to align and
   set the various UI elements. */
enum {
	TOP_LEFT_LIMIT	= 26,
	H_BORDER		= 5,
	V_BORDER		= 2,
	ANIM_LABEL		= 52,
	ANIM_POPUP		= 125,
	DISP_LABEL		= 40,
	DISP_POPUP		= 122,
	BUTTON_WIDTH	= 50,
	BUTTON_OFFSET	= -100,
	SPACE_LABEL		= 40,
	SPACE_POPUP		= 53,
	INSTANT_LOAD	= 205,
	LEFT_OFFSET		= 2,
	STATUS_BOX		= 98,
	STATUS_LABEL	= 13,
	STATUS_EDIT		= 25,
	STATUS_OFFSET	= 2,
	BOX_H_OFFSET	= 4,
	BOX_V_OFFSET	= 14,
	FULL_SCREEN		= 16,
	AUTO_DEMO		= 22,
	SECOND_THREAD	= 16,
	COLORS_BOX		= 146,
	COLORS_LABEL	= 16,
	COLORS_OFFSET	= 2,
	COLOR_CELL		= 8,
	SPECIAL_BOX		= 92,
	SPECIAL_LABEL	= 16,
	SPECIAL_OFFSET	= 2,
	STAR_DENSITY_H	= 160,
	STAR_DENSITY_V	= 34,
	REFRESH_RATE_H	= 320,
	REFRESH_RATE_V	= 34
};


// Now calculated based off font size
int32 LEFT_WIDTH = 96;


/* min, max and standard setting of the star count, also
   called starfield density. */
enum {
	STAR_DENSITY_MIN		= 400,
	STAR_DENSITY_MAX		= 20000,
	STAR_DENSITY_DEFAULT	= 2000
};

/* min, max and standard setting of the refresh rate. */
#define	REFRESH_RATE_MIN		0.6
#define	REFRESH_RATE_MAX		600.0
#define	REFRESH_RATE_DEFAULT	60.0

/* private enums used to identify the 3 types of
  programmable picture buttons. */
enum {
	COLOR_BUTTON_PICT	= 0,
	DENSITY_BUTTON_PICT	= 1,
	REFRESH_BUTTON_PICT	= 2
};

/* min, max zoom (also default offscreen size), and max dimensions
   of the content area of the window. */
enum {
	WINDOW_H_MIN		= 220,
	WINDOW_V_MIN		= 146,
	WINDOW_H_STD		= 800,
	WINDOW_V_STD		= 600,
	WINDOW_H_MAX		= 1920,
	WINDOW_V_MAX		= 1440,

/* increment step used to dynamically resize the offscreen buffer */
	WINDOW_H_STEP		= 224,
	WINDOW_V_STEP		= 168
};

/* time delay between refresh of the stat counters */
enum {
	STAT_DELAY			= 1000000
};

/* ratio between the rear clipping depth and the front clipping
   depth. */
#define		Z_CUT_RATIO		20.0

/* prefered aspect ratio between horizontal and vertical
   dimensions of the animation frame. */
#define		DH_REF			0.8
#define		DV_REF			0.6

/* no comments (almost :-) */
#define		abs(x) 	(((x)>0)?(x):-(x))

/* the 7 colors for stars. */
static rgb_color	color_list[7] = {
	{ 255, 160, 160, 255 },	/* red    */
	{ 160, 255, 160, 255 },	/* green  */
	{ 160, 160, 255, 255 },	/* blue   */
	{ 255, 255, 160, 255 },	/* yellow */
	{ 255, 208, 160, 255 },	/* orange */
	{ 255, 160, 255, 255 },	/* pink   */
	{ 255, 255, 255, 255 }	/* white  */
};

/* the 8 levels of lighting, in 1/65536. */
static int32 light_gradient[8] = {
	0x2000,
	0x5000,
	0x7800,
	0x9800,
	0xb800,
	0xd000,
	0xe800,
	0x10000
};


//	#pragma mark helper classes


/* multiply a vector by a constant */
TPoint
TPoint::operator* (const float k) const
{
	TPoint v;

	v.x = x*k;
	v.y = y*k;
	v.z = z*k;
	return v;
}

/* substract 2 vectors */
TPoint
TPoint::operator- (const TPoint& v2) const
{
	TPoint v;

	v.x = x-v2.x;
	v.y = y-v2.y;
	v.z = z-v2.z;
	return v;
}

/* add 2 vectors */
TPoint TPoint::operator+ (const TPoint& v2) const {
	TPoint v;

	v.x = x+v2.x;
	v.y = y+v2.y;
	v.z = z+v2.z;
	return v;
}

/* vectorial product of 2 vectors */
TPoint
TPoint::operator^ (const TPoint& v2) const
{
	TPoint v;

	v.x = y*v2.z - z*v2.y;
	v.y = z*v2.x - x*v2.z;
	v.z = x*v2.y - y*v2.x;
	return v;
}

/* length of a vector */
float
TPoint::Length() const
{
	return sqrt(x*x + y*y + z*z);
}

/* product of a vector by a matrix */
TPoint
TMatrix::operator* (const TPoint& v) const
{
	TPoint res;

	res.x = m[0][0]*v.x + m[1][0]*v.y + m[2][0]*v.z;
	res.y = m[0][1]*v.x + m[1][1]*v.y + m[2][1]*v.z;
	res.z = m[0][2]*v.x + m[1][2]*v.y + m[2][2]*v.z;
	return res;
}

/* extract the Nth vector/column of a matrix. */
TPoint
TMatrix::Axis(int32 index)
{
	TPoint v;

	v.x = m[index][0];
	v.y = m[index][1];
	v.z = m[index][2];
	return v;
}

/* as we use rotation matrix, the invert of the matrix
   is equal to the transpose */
TMatrix
TMatrix::Transpose() const
{
	TMatrix inv;

	inv.m[0][0] = m[0][0];
	inv.m[0][1] = m[1][0];
	inv.m[0][2] = m[2][0];
	inv.m[1][0] = m[0][1];
	inv.m[1][1] = m[1][1];
	inv.m[1][2] = m[2][1];
	inv.m[2][0] = m[0][2];
	inv.m[2][1] = m[1][2];
	inv.m[2][2] = m[2][2];
	return inv;
}

/* set a spherical rotation matrix */
void
TMatrix::Set(const float alpha, const float theta, const float phi)
{
	float cD,sD,cI,sI,cA,sA;

	/* trigonometry */
	cD = cos(alpha);
	sD = sin(alpha);
	cI = cos(theta);
	sI = sin(theta);
	cA = cos(phi);
	sA = sin(phi);

	/* rotation matrix */
	m[0][0] = cD*cA+sD*sI*sA;
	m[1][0] = -sA*cI;
	m[2][0] = sD*cA-cD*sI*sA;
	m[0][1] = cD*sA-sD*sI*cA;
	m[1][1] = cI*cA;
	m[2][1] = sD*sA+cD*cA*sI;
	m[0][2] = -sD*cI;
	m[1][2] = -sI;
	m[2][2] = cD*cI;
}


//	#pragma mark -


/* this function will play a wav sound file, with the specified
   following name, in the application folder. This is activated
   when you press the button "Auto demo". */
void
LaunchSound()
{
/*
	BEntry			soundFile;
	app_info 		info;
	status_t		err;
	entry_ref		snd_ref;
	BDirectory		appFolder;
	sound_handle	sndhandle;

	err = be_app->GetAppInfo(&info);
	BEntry appEntry(&info.ref);
	if (err != B_NO_ERROR)
		return;
	err = appEntry.GetParent(&appFolder);
	if (err != B_NO_ERROR)
		return;
	appFolder.FindEntry("demo.wav", &soundFile);
	err = soundFile.GetRef(&snd_ref);
	sndhandle = play_sound(&snd_ref, true, true, true);
*/
}

/* return the version_info of a file, described by its name
   and its generic folder (in find_directory syntax). */
status_t
get_file_version_info(directory_which dir,
	char *filename, version_info *info)
{
	BPath 			path;
	BFile			file;
	status_t		res;
	BAppFileInfo	appinfo;

	/* find the directory */
	if ((res = find_directory(dir, &path)) != B_NO_ERROR)
		return res;

	/* find the file */
	path.Append(filename);
	file.SetTo(path.Path(), O_RDONLY);
	if ((res = file.InitCheck()) != B_NO_ERROR)
		return res;

	/* get the version_info */
	if ((res = appinfo.SetTo(&file)) != B_NO_ERROR)
		return res;
	return appinfo.GetVersionInfo(info, B_APP_VERSION_KIND);
}


//	#pragma mark -


ChartWindow::ChartWindow(BRect frame, const char *name)
	: BDirectWindow(frame, name, B_TITLED_WINDOW, 0)
{
	float h, v, h2, v2;
	int32 colors[3];
	BRect r;
	BMenu *menu;
	BButton *button;
	BCheckBox *check_box, *full_screen;
	BMenuItem *item;
	BMenuField *popup;
	BStringView *string;
	BRadioButton *radio;

	// we're not font-sensitive, so we make sure we don't look too ugly
	BFont font;
	if (font.Size() > MAX_FONT_SIZE)
		font.SetSize(MAX_FONT_SIZE);
	BFont boldFont(be_bold_font);
	if (boldFont.Size() > MAX_FONT_SIZE)
		boldFont.SetSize(MAX_FONT_SIZE);

	/* offset the content area frame in window relative coordinate */
	frame.OffsetTo(0.0, 0.0);

	/* init the pattern anti-aliasing tables. */
	InitPatterns();

	/* set window size limits */
	SetSizeLimits(WINDOW_H_MIN, WINDOW_H_MAX, WINDOW_V_MIN, WINDOW_V_MAX);
	SetZoomLimits(WINDOW_H_STD, WINDOW_V_STD);

	/* initial bitmap buffer */
	fOffscreen = NULL;
	fMaxWidth = WINDOW_H_STD - LEFT_WIDTH;
	fMaxHeight = WINDOW_V_STD - TOP_LEFT_LIMIT;

	/* initialise the default setting state */
	for (int32 i = 0; i < 7; i++)
		fCurrentSettings.colors[i] = false;
	fCurrentSettings.colors[1] = true;
	fCurrentSettings.colors[2] = true;
	fCurrentSettings.colors[3] = true;
	fCurrentSettings.fullscreen_mode = WINDOW_MODE;
	fCurrentSettings.special = SPECIAL_NONE;
	fCurrentSettings.display = DISPLAY_OFF;
	fCurrentSettings.animation = ANIMATION_OFF;
	fCurrentSettings.back_color.red = 0;
	fCurrentSettings.back_color.green = 0;
	fCurrentSettings.back_color.blue = 0;
	fCurrentSettings.back_color.alpha = 255;
	fCurrentSettings.star_density = STAR_DENSITY_DEFAULT;
	fCurrentSettings.refresh_rate = REFRESH_RATE_DEFAULT;
	BScreen	screen(this);
	fCurrentSettings.depth	= screen.ColorSpace();
	fCurrentSettings.width = (int32)frame.right+1-LEFT_WIDTH;
	fCurrentSettings.height = (int32)frame.bottom+1-TOP_LEFT_LIMIT;
	fPreviousFullscreenMode = WINDOW_MODE;
	fNextSettings.Set(&fCurrentSettings);

	/* initialise various global parameters */
	fInstantLoadLevel = 0;
	fSecondThreadThreshold = 0.5;
	fLastDynamicDelay = 0.0;
	fCrcAlea = CRC_START;

	/* initialise the starfield and the special structs */
	fStars.list = (star*)malloc(sizeof(star)*STAR_DENSITY_MAX);
	fSpecials.list = (star*)malloc(sizeof(star)*SPECIAL_COUNT_MAX);
	fSpecialList = (special*)malloc(sizeof(special)*SPECIAL_COUNT_MAX);
	InitStars(SPACE_CHAOS);
	fStars.count = fCurrentSettings.star_density;
	fStars.erase_count = 0;
	InitSpecials(SPECIAL_NONE);
	fSpecials.erase_count = 0;
	colors[0] = 1;
	colors[1] = 2;
	colors[2] = 3;
	SetStarColors(colors, 3);

	/* set camera default position and rotation */
	fCameraAlpha = 0.2;
	fCameraTheta = 0.0;
	fCameraPhi = 0.0;
	fCamera.Set(fCameraAlpha, fCameraTheta, fCameraPhi);
	fCameraInvert = fCamera.Transpose();
	fOrigin.x = 0.5;
	fOrigin.y = 0.5;
	fOrigin.z = 0.1;

	/* initialise camera animation */
	fTrackingTarget = -1;
	fSpeed = 0.0115;
	fTargetSpeed = fSpeed;

	/* initialise the view coordinate system */
	InitGeometry();
	SetGeometry(fCurrentSettings.width, fCurrentSettings.height);
	SetCubeOffset();

	/* init the direct buffer in a valid state */
	fDirectBuffer.buffer_width = fCurrentSettings.width;
	fDirectBuffer.buffer_height = fCurrentSettings.height;
	fDirectBuffer.clip_list_count = 1;
	fDirectBuffer.clip_bounds.top = 0;
	fDirectBuffer.clip_bounds.left = 0;
	fDirectBuffer.clip_bounds.right = -1;
	fDirectBuffer.clip_bounds.bottom = -1;
	fDirectBuffer.clip_list[0].top = 0;
	fDirectBuffer.clip_list[0].left = 0;
	fDirectBuffer.clip_list[0].right = -1;
	fDirectBuffer.clip_list[0].bottom = -1;
	fDirectConnected = false;

	/* build the UI content of the window */

	/* top line background */
	r.Set(0.0, 0.0, frame.right, TOP_LEFT_LIMIT - 1);
	fTopView = new BView(r, "top view", B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW);
	fTopView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
	AddChild(fTopView);

	LEFT_WIDTH = (int32)fTopView->StringWidth(B_TRANSLATE("Full screen"))
							+ 22 + H_BORDER;

	if (LEFT_WIDTH < 96)
		LEFT_WIDTH = 96;

	h = 2;
	v = V_BORDER;

		/* instant load vue-meter */
		r.Set(h, v, h+INSTANT_LOAD-1, v + (TOP_LEFT_LIMIT - 1 - 2*V_BORDER));
		fInstantLoad = new InstantView(r);
		fTopView->AddChild(fInstantLoad);
		fInstantLoad->SetViewColor(0, 0, 0);

	h += INSTANT_LOAD+H_BORDER;

		/* camera animation popup */
		menu = new BPopUpMenu(B_TRANSLATE("Off"));
		item = new BMenuItem(B_TRANSLATE("Off"), new BMessage(ANIM_OFF_MSG));
		item->SetTarget(this);
		menu->AddItem(item);
		item = new BMenuItem(B_TRANSLATE("Slow rotation"),
			new BMessage(ANIM_SLOW_ROT_MSG));
		item->SetTarget(this);
		menu->AddItem(item);
		item = new BMenuItem(B_TRANSLATE("Slow motion"),
			new BMessage(ANIM_SLOW_MOVE_MSG));
		item->SetTarget(this);
		menu->AddItem(item);
		item = new BMenuItem(B_TRANSLATE("Fast motion"),
			new BMessage(ANIM_FAST_MOVE_MSG));
		item->SetTarget(this);
		menu->AddItem(item);
		item = new BMenuItem(B_TRANSLATE("Free motion"),
			new BMessage(ANIM_FREE_MOVE_MSG));
		item->SetTarget(this);
		menu->AddItem(item);

		r.Set(h, v, h+ANIM_LABEL+ANIM_POPUP-1, v +
			(TOP_LEFT_LIMIT - 1 - 2*V_BORDER));
		popup = new BMenuField(r, "", B_TRANSLATE("Animation:"), menu);
		popup->SetFont(&font);
		popup->MenuBar()->SetFont(&font);
		popup->Menu()->SetFont(&font);
		popup->SetDivider(popup->StringWidth(popup->Label()) + 4.0f);
		fTopView->AddChild(popup);

	h += ANIM_LABEL + ANIM_POPUP + H_BORDER;

		/* display mode popup */
		menu = new BPopUpMenu(B_TRANSLATE("Off"));
		item = new BMenuItem(B_TRANSLATE("Off"), new BMessage(DISP_OFF_MSG));
		item->SetTarget(this);
		menu->AddItem(item);
		item = new BMenuItem(B_TRANSLATE("LineArray"),
			new BMessage(DISP_LINE_MSG));
		item->SetTarget(this);
		item->SetEnabled(false);
		menu->AddItem(item);
		item = new BMenuItem(B_TRANSLATE("DrawBitmap"),
			new BMessage(DISP_BITMAP_MSG));
		item->SetTarget(this);
		menu->AddItem(item);
		item = new BMenuItem(B_TRANSLATE("DirectWindow"),
			new BMessage(DISP_DIRECT_MSG));
		item->SetTarget(this);
		item->SetEnabled(BDirectWindow::SupportsWindowMode());
		menu->AddItem(item);

		r.Set(h, v, h+DISP_LABEL+DISP_POPUP-1, v +
			(TOP_LEFT_LIMIT - 1 - 2*V_BORDER));
		popup = new BMenuField(r, "", B_TRANSLATE("Display:"), menu);
		popup->SetFont(&font);
		popup->MenuBar()->SetFont(&font);
		popup->Menu()->SetFont(&font);
		popup->SetDivider(popup->StringWidth(popup->Label()) + 4.0f);
		fTopView->AddChild(popup);

	h += DISP_LABEL + DISP_POPUP + H_BORDER;

		/* create the offwindow (invisible) button on the left side.
		   this will be used to record the content of the Picture
		   button. */
		r.Set(0, 0, BUTTON_WIDTH-1, TOP_LEFT_LIMIT - 1 - 2*V_BORDER);
		fOffwindowButton = new BButton(r, "", "", NULL);
		fOffwindowButton->Hide();
		AddChild(fOffwindowButton);
		fOffwindowButton->ResizeTo(r.Width(), r.Height());

		/* refresh rate picture button */
		r.Set(h, v, h+BUTTON_WIDTH-1, v + (TOP_LEFT_LIMIT - 1 - 2*V_BORDER));
		fRefreshButton = new BPictureButton(r, "",
										  ButtonPicture(false, REFRESH_BUTTON_PICT),
										  ButtonPicture(true, REFRESH_BUTTON_PICT),
										  new BMessage(OPEN_REFRESH_MSG));
		fRefreshButton->SetViewColor(B_TRANSPARENT_32_BIT);
		fRefreshButton->ResizeToPreferred();
		fTopView->AddChild(fRefreshButton);

	h += BUTTON_WIDTH+2*H_BORDER;

		/* background color button */
		r.Set(h, v, h+BUTTON_WIDTH-1, v + (TOP_LEFT_LIMIT - 1 - 2*V_BORDER));
		fColorButton = new BPictureButton(r, "",
										  ButtonPicture(false, COLOR_BUTTON_PICT),
										  ButtonPicture(true, COLOR_BUTTON_PICT),
										  new BMessage(OPEN_COLOR_MSG));
		fColorButton->SetViewColor(B_TRANSPARENT_32_BIT);
		fColorButton->ResizeToPreferred();
		fTopView->AddChild(fColorButton);

	h += BUTTON_WIDTH+2*H_BORDER;

		/* star density button */
		r.Set(h, v, h+BUTTON_WIDTH-1, v + (TOP_LEFT_LIMIT - 1 - 2*V_BORDER));
		fDensityButton = new BPictureButton(r, "",
											ButtonPicture(false, DENSITY_BUTTON_PICT),
											ButtonPicture(true, DENSITY_BUTTON_PICT),
											new BMessage(OPEN_DENSITY_MSG));
		fDensityButton->SetViewColor(B_TRANSPARENT_32_BIT);
		fDensityButton->ResizeToPreferred();
		fTopView->AddChild(fDensityButton);

	h += BUTTON_WIDTH+H_BORDER;

		/* starfield type popup */
		menu = new BPopUpMenu(B_TRANSLATE("Chaos"));
		item = new BMenuItem(B_TRANSLATE("Chaos"),
			new BMessage(SPACE_CHAOS_MSG));
		item->SetTarget(this);
		menu->AddItem(item);
		item = new BMenuItem(B_TRANSLATE("Amas"), new BMessage(SPACE_AMAS_MSG));
		item->SetTarget(this);
		menu->AddItem(item);
		item = new BMenuItem(B_TRANSLATE("Spiral"),
			new BMessage(SPACE_SPIRAL_MSG));
		item->SetTarget(this);
		menu->AddItem(item);

		r.Set(h, v, h+SPACE_LABEL+SPACE_POPUP-1, v +
			(TOP_LEFT_LIMIT - 1 - 2*V_BORDER));
		popup = new BMenuField(r, "", B_TRANSLATE("Space:"), menu);
		popup->SetFont(&font);
		popup->MenuBar()->SetFont(&font);
		popup->Menu()->SetFont(&font);
		popup->ResizeToPreferred();
		popup->SetDivider(popup->StringWidth(popup->Label()) + 4.0f);
		fTopView->AddChild(popup);

	h += SPACE_LABEL+SPACE_POPUP+2*H_BORDER;

	/* left column gray background */
	r.Set(0.0, TOP_LEFT_LIMIT, LEFT_WIDTH - 1, frame.bottom);
	fLeftView = new BView(r, "top view", B_FOLLOW_LEFT | B_FOLLOW_TOP_BOTTOM, B_WILL_DRAW);
	fLeftView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
	AddChild(fLeftView);

	h2 = LEFT_OFFSET;
	v2 = LEFT_OFFSET;
	h = h2;
	v = v2;

		/* status box */
		r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2, v+STATUS_BOX-1);
		fStatusBox = new BBox(r);
		fStatusBox->SetFont(&boldFont);
		fStatusBox->SetLabel(B_TRANSLATE("Status"));
		fLeftView->AddChild(fStatusBox);
		float boxWidth, boxHeight;
		fStatusBox->GetPreferredSize(&boxWidth, &boxHeight);
		boxWidth += r.left;

		h = BOX_H_OFFSET;
		v = BOX_V_OFFSET;

			/* frames per second title string */
			r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2*BOX_H_OFFSET-1,
				v+STATUS_LABEL-1);
			string = new BStringView(r, "", B_TRANSLATE("Frames/s"));
			string->SetFont(&font);
			string->SetAlignment(B_ALIGN_CENTER);
			fStatusBox->AddChild(string);

		v += STATUS_LABEL+STATUS_OFFSET;

			/* frames per second display string */
			r.Set(h-1, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2*BOX_H_OFFSET,
				v+STATUS_EDIT-1);
			fFramesView = new BStringView(r, "", "0.0");
			fFramesView->SetAlignment(B_ALIGN_RIGHT);
			fFramesView->SetFont(be_bold_font);
			fFramesView->SetFontSize(24.0);
			fFramesView->SetViewColor(B_TRANSPARENT_32_BIT);
			fStatusBox->AddChild(fFramesView);

		v += STATUS_EDIT+STATUS_OFFSET;

			/* CPU load pourcentage title string */
			r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2*BOX_H_OFFSET-1,
				v+STATUS_LABEL-1);
			string = new BStringView(r, "", B_TRANSLATE("CPU load"));
			string->SetAlignment(B_ALIGN_CENTER);
			string->SetFont(&font);
			fStatusBox->AddChild(string);

		v += STATUS_LABEL+STATUS_OFFSET;

			/* CPU load pourcentage display string */
			r.Set(h-1, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2*BOX_H_OFFSET,
				v+STATUS_EDIT-1);
			fCpuLoadView = new BStringView(r, "", "0.0");
			fCpuLoadView->SetAlignment(B_ALIGN_RIGHT);
			fCpuLoadView->SetFont(be_bold_font);
			fCpuLoadView->SetFontSize(24.0);
			fCpuLoadView->SetViewColor(B_TRANSPARENT_32_BIT);
			fStatusBox->AddChild(fCpuLoadView);

	v2 += STATUS_BOX+LEFT_OFFSET*2;
	h = h2;
	v = v2;

		/* Fullscreen mode check box */
		r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-1, v+FULL_SCREEN-1);
		full_screen = new BCheckBox(r, "", B_TRANSLATE("Full screen"),
			new BMessage(FULL_SCREEN_MSG));
		full_screen->SetTarget(this);
		full_screen->SetFont(&font);
		full_screen->ResizeToPreferred();

		float width, height;
		full_screen->GetPreferredSize(&width, &height);
		boxWidth = max_c(width + r.left, boxWidth);
		fLeftView->AddChild(full_screen);

	v2 += FULL_SCREEN+LEFT_OFFSET*2;
	h = h2;
	v = v2;

		/* Automatic demonstration activation button */
		r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-1, v+AUTO_DEMO-1);
		button = new BButton(r, "", B_TRANSLATE("Auto demo"),
			new BMessage(AUTO_DEMO_MSG));
		button->SetTarget(this);
		button->ResizeToPreferred();
		button->GetPreferredSize(&width, &height);
		boxWidth = max_c(width + r.left, boxWidth);
		fLeftView->AddChild(button);

	v2 += button->Frame().Height()+LEFT_OFFSET*2;
	h = h2;
	v = v2;

		/* Enabling second thread check box */
		r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-1, v+SECOND_THREAD-1);
		check_box = new BCheckBox(r, "", B_TRANSLATE("2 threads"),
			new BMessage(SECOND_THREAD_MSG));
		check_box->SetTarget(this);
		check_box->SetFont(&font);
		check_box->ResizeToPreferred();
		fLeftView->AddChild(check_box);

	v2 += SECOND_THREAD+LEFT_OFFSET*2 + 2;
	h = h2;
	v = v2;

		/* Star color selection box */
		r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2, v+COLORS_BOX-1);
		fColorsBox = new BBox(r);
		fColorsBox->SetLabel(B_TRANSLATE("Colors"));
		fColorsBox->SetFont(&boldFont);
		fLeftView->AddChild(fColorsBox);

		h = BOX_H_OFFSET;
		v = BOX_V_OFFSET;

			/* star color red check box */
			r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2*BOX_H_OFFSET-1,
				v+COLORS_LABEL-1);
			check_box = new BCheckBox(r, "", B_TRANSLATE("Red"),
				new BMessage(COLORS_RED_MSG));
			check_box->SetFont(&font);
			check_box->ResizeToPreferred();
			fColorsBox->AddChild(check_box);

		v += COLORS_LABEL+COLORS_OFFSET;

			/* star color green check box */
			r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2*BOX_H_OFFSET-1,
				v+COLORS_LABEL-1);
			check_box = new BCheckBox(r, "", B_TRANSLATE("Green"),
				new BMessage(COLORS_GREEN_MSG));
			check_box->SetValue(1);
			check_box->SetFont(&font);
			check_box->ResizeToPreferred();
			fColorsBox->AddChild(check_box);

		v += COLORS_LABEL+COLORS_OFFSET;

			/* star color blue check box */
			r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2*BOX_H_OFFSET-1,
				v+COLORS_LABEL-1);
			check_box = new BCheckBox(r, "", B_TRANSLATE("Blue"),
				new BMessage(COLORS_BLUE_MSG));
			check_box->SetValue(1);
			check_box->SetFont(&font);
			check_box->ResizeToPreferred();
			fColorsBox->AddChild(check_box);

		v += COLORS_LABEL+COLORS_OFFSET;

			/* star color yellow check box */
			r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2*BOX_H_OFFSET-1,
				v+COLORS_LABEL-1);
			check_box = new BCheckBox(r, "", B_TRANSLATE("Yellow"),
				new BMessage(COLORS_YELLOW_MSG));
			check_box->SetValue(1);
			check_box->SetFont(&font);
			check_box->ResizeToPreferred();
			fColorsBox->AddChild(check_box);

		v += COLORS_LABEL+COLORS_OFFSET;

			/* star color orange check box */
			r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2*BOX_H_OFFSET-1,
				v+COLORS_LABEL-1);
			check_box = new BCheckBox(r, "", B_TRANSLATE("Orange"),
				new BMessage(COLORS_ORANGE_MSG));
			check_box->SetFont(&font);
			check_box->ResizeToPreferred();
			fColorsBox->AddChild(check_box);

		v += COLORS_LABEL+COLORS_OFFSET;

			/* star color pink check box */
			r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2*BOX_H_OFFSET-1,
				v+COLORS_LABEL-1);
			check_box = new BCheckBox(r, "", B_TRANSLATE("Pink"),
				new BMessage(COLORS_PINK_MSG));
			check_box->SetFont(&font);
			check_box->ResizeToPreferred();
			fColorsBox->AddChild(check_box);

		v += COLORS_LABEL+COLORS_OFFSET;

			/* star color white check box */
			r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2*BOX_H_OFFSET-1,
				v+COLORS_LABEL-1);
			check_box = new BCheckBox(r, "", B_TRANSLATE("White"),
				new BMessage(COLORS_WHITE_MSG));
			check_box->SetFont(&font);
			check_box->ResizeToPreferred();
			fColorsBox->AddChild(check_box);

	v2 += COLORS_BOX+LEFT_OFFSET*2;
	h = h2;
	v = v2;

		/* Special type selection box */
		r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2, v+SPECIAL_BOX-1);
		fSpecialBox = new BBox(r);
		fSpecialBox->SetFont(&boldFont);
		fSpecialBox->SetLabel(B_TRANSLATE("Special"));
		fLeftView->AddChild(fSpecialBox);

		h = BOX_H_OFFSET;
		v = BOX_V_OFFSET;

			/* no special radio button */
			r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2*BOX_H_OFFSET-1,
				v+COLORS_LABEL-1);
			radio = new BRadioButton(r, "", B_TRANSLATE("None"),
				new BMessage(SPECIAL_NONE_MSG));
			radio->SetValue(1);
			radio->SetFont(&font);
			radio->ResizeToPreferred();
			fSpecialBox->AddChild(radio);

		v += COLORS_LABEL+COLORS_OFFSET;

			/* comet special animation radio button */
			r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2*BOX_H_OFFSET-1,
				v+COLORS_LABEL-1);
			radio = new BRadioButton(r, "", B_TRANSLATE("Comet"),
				new BMessage(SPECIAL_COMET_MSG));
			radio->SetFont(&font);
			radio->ResizeToPreferred();
			fSpecialBox->AddChild(radio);

		v += COLORS_LABEL+COLORS_OFFSET;

			/* novas special animation radio button */
			r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2*BOX_H_OFFSET-1,
				v+COLORS_LABEL-1);
			radio = new BRadioButton(r, "", B_TRANSLATE("Novas"),
				new BMessage(SPECIAL_NOVAS_MSG));
			radio->SetFont(&font);
			radio->ResizeToPreferred();
			fSpecialBox->AddChild(radio);

		v += COLORS_LABEL+COLORS_OFFSET;

			/* space batle special animation radio button (not implemented) */
			r.Set(h, v, h+LEFT_WIDTH-2*LEFT_OFFSET-2*BOX_H_OFFSET-1,
				v+COLORS_LABEL-1);
			radio = new BRadioButton(r, "", B_TRANSLATE("Battle"),
				new BMessage(SPECIAL_BATTLE_MSG));
			radio->SetEnabled(false);
			radio->SetFont(&font);
			radio->ResizeToPreferred();
			fSpecialBox->AddChild(radio);

	// Note: direct window mode uses LEFT_WIDTH to calculate
	// the left border of the animation view, so we use it here too.
	//fLeftView->ResizeTo(max_c(boxWidth + 2, fLeftView->Bounds().Width()), fLeftView->Bounds().Height());
	/* animation area */
	r.Set(LEFT_WIDTH, TOP_LEFT_LIMIT, frame.right, frame.bottom);
	fChartView = new ChartView(r);
	fChartView->SetViewColor(0, 0, 0);
	AddChild(fChartView);

	/*Resize Window Height to fit contents, if needed*/
	r = Bounds();
	r.bottom = fSpecialBox->Frame().bottom + H_BORDER * 2 + 20;

	if (r.Height() > Bounds().Height())
		ResizeTo(r.Width(), r.Height());

	/* allocate the semaphores */
	fDrawingLock = create_sem(1, "chart locker");
	fSecondThreadLock = create_sem(0, "chart second locker");
	fSecondThreadRelease = create_sem(0, "chart second release");

	/* spawn the asynchronous animation threads */
	fKillThread = false;
	fAnimationThread = spawn_thread(ChartWindow::Animation, "chart animation",
								B_NORMAL_PRIORITY,
								(void*)this);

	fSecondAnimationThread = spawn_thread(ChartWindow::Animation2, "chart animation2",
								B_NORMAL_PRIORITY,
								(void*)this);
	resume_thread(fSecondAnimationThread);
	resume_thread(fAnimationThread);
}


ChartWindow::~ChartWindow()
{
	status_t result;

	/* setting this flag force both animation threads to quit */
	fKillThread = true;

	/* wait for the two animation threads to quit */
	wait_for_thread(fAnimationThread, &result);
	wait_for_thread(fSecondAnimationThread, &result);

	/* free the offscreen bitmap if any */
	delete fOffscreen;

	/* release the semaphores used for synchronisation */
	delete_sem(fDrawingLock);
	delete_sem(fSecondThreadLock);
	delete_sem(fSecondThreadRelease);

	/* free the buffers used to store the starlists */
	free(fStars.list);
	free(fSpecials.list);
	free(fSpecialList);
}


//	#pragma mark Standard window members


bool
ChartWindow::QuitRequested()
{
	be_app->PostMessage(B_QUIT_REQUESTED);
	return BWindow::QuitRequested();
}


void
ChartWindow::MessageReceived(BMessage *message)
{
	int32			index, color;
	BHandler		*handler;
	BCheckBox		*check_box;
	BSlider			*slider;

	message->FindPointer("source", (void**)&handler);
	switch(message->what) {
		/* This is a key part of the architecture. MessageReceived is
		   called whenever the user interact with a UI element to change
		   a setting. The window is locked at this point, so changing
		   the setting of the engine at that point would be dangerous.
		   We could easily goofed and create a bad dependencies between
		   the Window locking mechanism and DirectConnected, that
		   would generate a deadlock and force the app_server to kill
		   the application. Bad business. To avoid that, we keep two
		   different engine setting. One that is currently used by the
		   animation engine, the other one that will retain all the
		   changes generated by the user until the engine is ready to
		   use them. So message received will write into that setting
		   state and the engine will read it from time to time. Both
		   access can be done asynchronously as all intermediate state
		   generated by the MessageReceived write are valid (we don't
		   need to make those transactions atomic). */
		case ANIM_OFF_MSG :
		case ANIM_SLOW_ROT_MSG :
		case ANIM_SLOW_MOVE_MSG :
		case ANIM_FAST_MOVE_MSG :
		case ANIM_FREE_MOVE_MSG :
			fNextSettings.animation = ANIMATION_OFF + (message->what - ANIM_OFF_MSG);
			break;
		case DISP_OFF_MSG :
		case DISP_BITMAP_MSG :
		case DISP_DIRECT_MSG :
			fNextSettings.display = DISPLAY_OFF + (message->what - DISP_OFF_MSG);
			break;
		case SPACE_CHAOS_MSG :
		case SPACE_AMAS_MSG :
		case SPACE_SPIRAL_MSG :
			fNextSettings.space_model = SPACE_CHAOS + (message->what - SPACE_CHAOS_MSG);
			break;
		case FULL_SCREEN_MSG :
			check_box = dynamic_cast<BCheckBox*>(handler);
			if (check_box == NULL)
				break;

			if (check_box->Value())
				fNextSettings.fullscreen_mode = FULLSCREEN_MODE;
			else
				fNextSettings.fullscreen_mode = WINDOW_MODE;
			break;
		case AUTO_DEMO_MSG :
			fNextSettings.fullscreen_mode = FULLDEMO_MODE;
			fNextSettings.animation = ANIMATION_FREE_MOVE;
			fNextSettings.special = SPECIAL_COMET;
			LaunchSound();
			break;
		case BACK_DEMO_MSG :
			fNextSettings.fullscreen_mode = fPreviousFullscreenMode;
			break;
		case SECOND_THREAD_MSG :
			check_box = dynamic_cast<BCheckBox*>(handler);
			if (check_box == NULL)
				break;

			fNextSettings.second_thread =  (check_box->Value()?true:false);
			break;
		case COLORS_RED_MSG :
		case COLORS_GREEN_MSG :
		case COLORS_BLUE_MSG :
		case COLORS_YELLOW_MSG :
		case COLORS_ORANGE_MSG :
		case COLORS_PINK_MSG :
		case COLORS_WHITE_MSG :
			index = message->what - COLORS_RED_MSG;
			check_box = dynamic_cast<BCheckBox*>(handler);
			if (check_box == NULL)
				break;

			fNextSettings.colors[index] = (check_box->Value()?true:false);
			break;
		case SPECIAL_NONE_MSG :
		case SPECIAL_COMET_MSG :
		case SPECIAL_NOVAS_MSG :
		case SPECIAL_BATTLE_MSG :
			fNextSettings.special = SPECIAL_NONE + (message->what - SPECIAL_NONE_MSG);
			break;
		case COLOR_PALETTE_MSG :
			message->FindInt32("be:value", &color);
			fNextSettings.back_color.red = (color >> 24);
			fNextSettings.back_color.green = (color >> 16);
			fNextSettings.back_color.blue = (color >> 8);
			fNextSettings.back_color.alpha = color;
			break;
		case STAR_DENSITY_MSG :
			slider = dynamic_cast<BSlider*>(handler);
			if (slider == NULL)
				break;

			fNextSettings.star_density = slider->Value();
			break;
		case REFRESH_RATE_MSG :
			slider = dynamic_cast<BSlider*>(handler);
			if (slider == NULL)
				break;

			fNextSettings.refresh_rate = exp(slider->Value()*0.001*(log(REFRESH_RATE_MAX/REFRESH_RATE_MIN)))*
									REFRESH_RATE_MIN;
			break;
		/* open the three floating window used to do live setting of
		   some advanced parameters. Those windows will return live
		   feedback that will be executed by some of the previous
		   messages. */
		case OPEN_COLOR_MSG :
			OpenColorPalette(BPoint(200.0, 200.0));
			break;
		case OPEN_DENSITY_MSG :
			OpenStarDensity(BPoint(280.0, 280.0));
			break;
		case OPEN_REFRESH_MSG :
			OpenRefresh(BPoint(240.0, 340.0));
			break;
		/* let other messages pass through... */
		default :
			BDirectWindow::MessageReceived(message);
			break;
	}
}


void
ChartWindow::ScreenChanged(BRect screen_size, color_space depth)
{
	/* this is the same principle than the one described for
	   MessageReceived, to inform the engine that the depth of
	   the screen changed (needed only for offscreen bitmap.
	   In DirectWindow, you get a direct notification). */
	fNextSettings.depth = BScreen(this).ColorSpace();
}


void
ChartWindow::FrameResized(float new_width, float new_height)
{
	/* this is the same principle than the one described for
	   MessageReceived, to inform the engine that the window
	   size changed (needed only for offscreen bitmap. In
	   DirectWindow, you get a direct notification). */
	fNextSettings.width = (int32)Frame().Width()+1-LEFT_WIDTH;
	fNextSettings.height = (int32)Frame().Height()+1-TOP_LEFT_LIMIT;
}


//	#pragma mark User Interface related stuff...


/* loop through the window list of the application, looking for
   a window with a specified name. */
BWindow	*
ChartWindow::GetAppWindow(const char *name)
{
	int32		index;
	BWindow		*window;

	for (index = 0;; index++) {
		window = be_app->WindowAt(index);
		if (window == NULL)
			break;
		if (window->LockWithTimeout(200000) == B_OK) {
			// skip the w> prefix in window's name.
			if (strcmp(window->Name() + 2, name) == 0) {
				window->Unlock();
				break;
			}
			window->Unlock();
		}
	}
	return window;
}

/* this function return a picture (in active or inactive state) of
   a standard BButton with some specific content draw in the middle.
   button_type indicate what special content should be used. */
BPicture *
ChartWindow::ButtonPicture(bool active, int32 button_type)
{
	char		word[6];
	int32		value;
	BRect		r;
	BPoint		delta;
	BPicture	*pict;


	/* create and open the picture */
	pict = new BPicture();
	r = fOffwindowButton->Bounds();
	fOffwindowButton->SetValue(active);
	fOffwindowButton->BeginPicture(pict);
	/* draw the standard BButton in whatever state is required. */
	fOffwindowButton->Draw(r);
	if (button_type == COLOR_BUTTON_PICT) {
		/* this button just contains a rectangle of the current background
		   color, with a one pixel black border. */
		r.InsetBy(6.0, 4.0);
		fOffwindowButton->SetHighColor(0, 0, 0);
		fOffwindowButton->StrokeRect(r);
		r.InsetBy(1.0, 1.0);
		fOffwindowButton->SetHighColor(fCurrentSettings.back_color);
		fOffwindowButton->FillRect(r);
	}
	else if (button_type == DENSITY_BUTTON_PICT) {
		/* this button just contains a big string (using a bigger font size
		   than what a standard BButton would allow) with the current value
		   of the star density pourcentage. */
		value = (fCurrentSettings.star_density*100 + STAR_DENSITY_MAX/2) / STAR_DENSITY_MAX;
		sprintf(word, "%3" B_PRId32, value);
	draw_string:
		fOffwindowButton->SetFont(be_bold_font);
		fOffwindowButton->SetFontSize(14.0);
		delta.x = BUTTON_WIDTH/2-(fOffwindowButton->StringWidth(word) * 0.5);
		delta.y = (TOP_LEFT_LIMIT-2*V_BORDER)/2 + 6.0;
		fOffwindowButton->DrawString(word, delta);
	}
	else {
		/* this button just contains a big string (using a bigger font size
		   than what a standard BButton would allow) with the current value
		   of the target refresh rate in frames per second. */
		sprintf(word, "%3.1f", fCurrentSettings.refresh_rate + 0.05);
		goto draw_string;
	}
	/* close and return the picture */
	return fOffwindowButton->EndPicture();
}

/* Create a floating window including a slightly modified version of
   BColorControl, ChartColorControl, that will return live feedback
   as the same time the user will change the color setting of the
   background. */
void
ChartWindow::OpenColorPalette(BPoint here)
{
	BRect frame;
	BPoint point;

	BWindow *window = GetAppWindow(B_TRANSLATE("Space color"));
	if (window == NULL) {
		frame.Set(here.x, here.y, here.x + 199.0, here.y + 99.0);
		window = new BWindow(frame, B_TRANSLATE("Space color"),
							 B_FLOATING_WINDOW_LOOK,
							 B_FLOATING_APP_WINDOW_FEEL,
							 B_NOT_ZOOMABLE | B_WILL_ACCEPT_FIRST_CLICK | B_NOT_RESIZABLE);
		point.Set(0, 0);
		BColorControl *colorControl = new ChartColorControl(point,
			new BMessage(COLOR_PALETTE_MSG));
		colorControl->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
		colorControl->SetTarget(NULL, this);
		colorControl->SetValue(fCurrentSettings.back_color);
		colorControl->ResizeToPreferred();
		window->ResizeTo(colorControl->Bounds().Width(), colorControl->Bounds().Height());
		window->AddChild(colorControl);
		window->Show();
	}
	window->Activate();
}

/* Create a floating window including a BSlider, that will return
   live feedback when the user will change the star density of the
   starfield */
void
ChartWindow::OpenStarDensity(BPoint here)
{
	BWindow	*window = GetAppWindow(B_TRANSLATE("Star density"));
	if (window == NULL) {
		BRect frame(here.x, here.y, here.x + STAR_DENSITY_H-1,
			here.y + STAR_DENSITY_V-1);
		window = new BWindow(frame, B_TRANSLATE("Star density"),
							 B_FLOATING_WINDOW_LOOK,
							 B_FLOATING_APP_WINDOW_FEEL,
							 B_NOT_RESIZABLE | B_NOT_ZOOMABLE | B_WILL_ACCEPT_FIRST_CLICK);
		frame.OffsetTo(0.0, 0.0);
		BSlider	*slider = new BSlider(frame, "", NULL, new BMessage(STAR_DENSITY_MSG),
							 STAR_DENSITY_MIN, STAR_DENSITY_MAX);
		slider->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
		slider->SetTarget(NULL, this);
		slider->SetValue(fCurrentSettings.star_density);
		slider->SetModificationMessage(new BMessage(STAR_DENSITY_MSG));
		slider->SetLimitLabels(B_TRANSLATE(" 5% (low)"),
			B_TRANSLATE("(high) 100% "));
		slider->ResizeToPreferred();
		window->ResizeTo(slider->Bounds().Width(), slider->Bounds().Height());
		window->AddChild(slider);
		window->Show();
	}
	window->Activate();
}

/* Create a floating window including a BSlider, that will return
   live feedback when the user will change the target refresh rate
   of the animation */
void
ChartWindow::OpenRefresh(BPoint here)
{
	BWindow *window = GetAppWindow(B_TRANSLATE("Refresh rate"));
	if (window == NULL) {
		BRect frame(here.x, here.y, here.x + REFRESH_RATE_H-1,
			here.y + REFRESH_RATE_V-1);
		window = new BWindow(frame, B_TRANSLATE("Refresh rate"),
							 B_FLOATING_WINDOW_LOOK,
							 B_FLOATING_APP_WINDOW_FEEL,
							 B_NOT_RESIZABLE | B_NOT_ZOOMABLE | B_WILL_ACCEPT_FIRST_CLICK);
		frame.OffsetTo(0.0, 0.0);
		BSlider *slider = new BSlider(frame, "", NULL, new BMessage(REFRESH_RATE_MSG), 0, 1000);
		slider->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
		slider->SetTarget(NULL, this);
		slider->SetValue((int32)(1000 * log(fCurrentSettings.refresh_rate / REFRESH_RATE_MIN) /
						log(REFRESH_RATE_MAX/REFRESH_RATE_MIN)));
		slider->SetModificationMessage(new BMessage(REFRESH_RATE_MSG));
		slider->SetLimitLabels(B_TRANSLATE(" 0.6 f/s  (logarithmic scale)"),
			B_TRANSLATE("600.0 f/s"));
		slider->ResizeToPreferred();
		window->ResizeTo(slider->Bounds().Width(), slider->Bounds().Height());
		window->AddChild(slider);
		window->Show();
	}
	window->Activate();
}

/* This update the state of the frames per second vue-meter in a lazy way. */
void
ChartWindow::DrawInstantLoad(float frame_per_second)
{
	int32 i;
	bigtime_t	timeout;

	int32 level = (int32)((frame_per_second + 6.0) * (1.0/12.0));
	if (level > 50)
		level = 50;

	/* if the load level is inchanged, nothing more to do... */
	if (level == fInstantLoadLevel)
		return;

	/* We need to lock the window to be able to draw that. But as some
	   BControl are still synchronous, if the user is still tracking them,
	   the window can stay block for a long time. It's not such a big deal
	   when using the offscreen buffer as we won't be able to draw it in
	   any case. But in DirectWindow mode, we're not limited by that so
	   it would be stupid to block the engine loop here. That's why in
	   that case, we will try to lock the window with a timeout of 0us. */
	if (fCurrentSettings.display == DISPLAY_BITMAP)
		timeout = 100000;
	else
		timeout = 0;
	if (LockWithTimeout(timeout) != B_OK)
		return;

	/* the new level is higher than the previous. We need to draw more
	   colored bars. */
	if (level > fInstantLoadLevel) {
		for (i = fInstantLoadLevel; i < level; i++) {
			if (i < fInstantLoad->step)
				fInstantLoad->SetHighColor(255, 90, 90);
			else if ((i / fInstantLoad->step) & 1)
				fInstantLoad->SetHighColor(90, 255, 90);
			else
				fInstantLoad->SetHighColor(40, 200, 40);
			fInstantLoad->FillRect(BRect(3 + i * 4, 2, 5 + i * 4, 19));
		}
	}
	/* the level is lower than before, we need to erase some bars. */
	else {
		fInstantLoad->SetHighColor(0, 0, 0);
		for (i = level; i < fInstantLoadLevel; i++)
			fInstantLoad->FillRect(BRect(3 + i * 4, 2, 5 +i * 4, 19));
	}
	/* we want that drawing to be completed as soon as possible */
	Flush();

	fInstantLoadLevel = level;
	Unlock();
}


void
ChartWindow::PrintStatNumbers(float fps)
{
	char		text_frames[6];
	char		text_cpu_load[6];
	float		frame_rate, load;
	bigtime_t	timeout;

	/* rules use to determine the stat numbers : if the target framerate
	   is greater than the simulate one, then we consider that 100.0 cpu
	   was used, and we only got the simulate framerate. */
	if (fps <= fCurrentSettings.refresh_rate) {
		load = 100.0;
		frame_rate = fps + 0.05;
	}
	/* if the target framerate is less than the simulate one, then we
	   calculate what fraction of the cpu would have been required to
	   deliver the target framerate, and we said that the target framerate
	   was delivered. */
	else {
		load = (100.0*fCurrentSettings.refresh_rate)/fps + 0.05;
		frame_rate = fCurrentSettings.refresh_rate + 0.05;
	}

	/* convert numbers in strings */
	sprintf(text_cpu_load, "%3.1f", load);
	sprintf(text_frames, "%3.1f", frame_rate);

	/* same remark than for DrawInstantLoad. We want to avoid to
	   block if using DirectWindow mode. */
	if (fCurrentSettings.display == DISPLAY_BITMAP)
		timeout = 100000;
	else
		timeout = 0;

	if (LockWithTimeout(timeout) == B_OK) {
		fFramesView->SetText(text_frames);
		fCpuLoadView->SetText(text_cpu_load);
		Unlock();
	}
}


//	#pragma mark Engine setting related functions.


void
ChartWindow::InitGeometry()
{
	/* calculate some parameters required for the 3d processing */
	float dz = sqrt(1.0 - (DH_REF*DH_REF + DV_REF*DV_REF) * (0.5 + 0.5/Z_CUT_RATIO) * (0.5 + 0.5/Z_CUT_RATIO));
	fDepthRef = dz / (1.0 - 1.0/Z_CUT_RATIO);

	/* set the position of the pyramid of vision, so that it was always
	   possible to include it into a 1x1x1 cube parallel to the 3 main
	   axis. */
	fGeometry.z_max = fDepthRef;
	fGeometry.z_min = fDepthRef/Z_CUT_RATIO;

	/* used for lighting processing */
	fGeometry.z_max_square = fGeometry.z_max * fGeometry.z_max;

	/* preprocess that for the fast clipping based on the pyramid of vision */
	fGeometry.xz_max = (0.5*DH_REF)/fGeometry.z_max;
	fGeometry.xz_min = -fGeometry.xz_max;
	fGeometry.yz_max = (0.5*DV_REF)/fGeometry.z_max;
	fGeometry.yz_min = -fGeometry.yz_max;
}

/* second part of the asynchronous setting mechanism. This will be
   called once during every loop of the animation engine, at a time
   when the engine is not using the setting for realtime processing.
   Each setting will be checked for potential change, and action
   will be taken if needed. The window can be locked at that time
   because the structure of the animation engine loop guarantees
   that DirectConnected can not stay blocked at the same time that
   this method is executed. */
void
ChartWindow::ChangeSetting(setting new_set)
{
	int32			i, color_count, old_step;
	int32			color_index[7];

	/* check for change of window/fullscreen/fullscreen demo mode */
	if (fCurrentSettings.fullscreen_mode != new_set.fullscreen_mode) {
		switch (new_set.fullscreen_mode) {
		case WINDOW_MODE :
			fPreviousFullscreenMode = WINDOW_MODE;
			ResizeTo(fPreviousFrame.Width(), fPreviousFrame.Height());
			MoveTo(fPreviousFrame.left, fPreviousFrame.top);
			break;
		case FULLSCREEN_MODE :
			{
				fPreviousFullscreenMode = FULLSCREEN_MODE;
				if (fCurrentSettings.fullscreen_mode == WINDOW_MODE)
					fPreviousFrame = Frame();
				BScreen	a_screen(this);
				MoveTo(a_screen.Frame().left, a_screen.Frame().top);
				ResizeTo(a_screen.Frame().Width(), a_screen.Frame().Height());
			}
			break;
		case FULLDEMO_MODE :
			{
				fPreviousFullscreenMode = fCurrentSettings.fullscreen_mode;
				if (fCurrentSettings.fullscreen_mode == WINDOW_MODE)
					fPreviousFrame = Frame();
				BScreen	b_screen(this);
				ResizeTo(b_screen.Frame().Width() + LEFT_WIDTH, b_screen.Frame().Height() + TOP_LEFT_LIMIT);
				MoveTo(b_screen.Frame().left - LEFT_WIDTH, b_screen.Frame().top - TOP_LEFT_LIMIT);
			}
			break;
		}
	}

	/* check for change in the target refresh rate */
	if (fCurrentSettings.refresh_rate != new_set.refresh_rate) {
		fCurrentSettings.refresh_rate = new_set.refresh_rate;
		old_step = fInstantLoad->step;
		fInstantLoad->step = (int32)((fCurrentSettings.refresh_rate+6.0)/12.0);
		if (fInstantLoad->step < 1)
			fInstantLoad->step = 1;
		if (LockWithTimeout(200000) == B_OK) {
			if (old_step != fInstantLoad->step)
				fInstantLoad->Invalidate();
			fRefreshButton->SetEnabledOff(ButtonPicture(false, REFRESH_BUTTON_PICT));
			fRefreshButton->SetEnabledOn(ButtonPicture(true, REFRESH_BUTTON_PICT));
			fRefreshButton->Invalidate();
			Unlock();
		}
		if (fCurrentSettings.animation != ANIMATION_OFF)
			fFrameDelay = (bigtime_t)(1000000.0/new_set.refresh_rate);
	}

	/* check for change in the star colors list */
	for (i=0; i<7; i++)
		if (fCurrentSettings.colors[i] != new_set.colors[i]) {
			/* if any, get the list of usable color index... */
			color_count = 0;
			for (i=0; i<7; i++)
				if (new_set.colors[i])
					color_index[color_count++] = i;
			/* check that at least one color is enabled */
			if (color_count == 0)
				color_index[color_count++] = 6;
			/* set a new color distribution in the starfield */
			SetStarColors(color_index, color_count);
			break;
		}

	/* check for change of the special effect setting */
	if (new_set.special != fCurrentSettings.special)
		InitSpecials(new_set.special);

	/* check for change of the display method */
	if (new_set.display != fCurrentSettings.display) {
		if (new_set.display == DISPLAY_BITMAP) {
			/* check the settings of the offscreen bitmap */
			CheckBitmap(new_set.depth, new_set.width, new_set.height);
			/* synchronise the camera geometry and the offscreen buffer geometry */
			SetGeometry(fBitmapBuffer.buffer_width, fBitmapBuffer.buffer_height);
			/* reset the offscreen background and cancel the erasing */
			SetBitmapBackGround();
			fStars.erase_count = 0;
			fSpecials.erase_count = 0;
		}
		if (new_set.display == DISPLAY_DIRECT) {
			/* this need to be atomic in regard of DirectConnected */
			while (acquire_sem(fDrawingLock) == B_INTERRUPTED)
				;

			// Clear the non-direct view, which may still be drawn
			fChartView->LockLooper();
			fChartView->SetHighColor(fCurrentSettings.back_color);
			fChartView->FillRect(fChartView->Bounds());
			fChartView->UnlockLooper();

			/* synchronise the camera geometry and the direct buffer geometry */
			SetGeometry(fDirectBuffer.buffer_width, fDirectBuffer.buffer_height);
			/* cancel erasing of stars not in visible part of the direct window */
			RefreshClipping(&fDirectBuffer, &fStars);
			RefreshClipping(&fDirectBuffer, &fSpecials);
			release_sem(fDrawingLock);
		}
	}

	/* check for change of the animation mode. */
	if (new_set.animation != fCurrentSettings.animation) {
		/* when there is no camera animation, we loop only
		   10 times per second. */
		if (new_set.animation == ANIMATION_OFF)
			fFrameDelay = 100000;
		else
			fFrameDelay = (bigtime_t)(1000000.0/new_set.refresh_rate);
		/* reset the free camera animation context for a fresh start */
		if (new_set.animation == ANIMATION_FREE_MOVE) {
			fDynamicAlpha = 0.0;
			fDynamicTheta = 0.0;
			fDynamicPhi = 0.0;
			fCountAlpha = 0;
			fCountTheta = 0;
			fCountPhi = 0;
		}
	}

	/* check for change of starfield model */
	if (new_set.space_model != fCurrentSettings.space_model) {
		/* Generate a new starfield. Also reset the special animation */
		InitStars(new_set.space_model);
		InitSpecials(new_set.special);
	}

	/* check for change of the background color */
	if ((new_set.back_color.red != fCurrentSettings.back_color.red) ||
		(new_set.back_color.green != fCurrentSettings.back_color.green) ||
		(new_set.back_color.blue != fCurrentSettings.back_color.blue)) {
		if (LockWithTimeout(200000) == B_OK) {
			BScreen		screen(this);
			/* set the background color and it's 8 bits index equivalent */
			fCurrentSettings.back_color = new_set.back_color;
			fBackColorIndex = screen.IndexForColor(new_set.back_color);
			/* set the nackground color of the view (directwindow mode) */
			fChartView->SetViewColor(new_set.back_color);
			/* change the color of the picture button used in the UI */
			fColorButton->SetEnabledOff(ButtonPicture(false, COLOR_BUTTON_PICT));
			fColorButton->SetEnabledOn(ButtonPicture(true, COLOR_BUTTON_PICT));
			fColorButton->Invalidate();
			/* update all dependencies in the offscreen buffer descriptor */
			SetColorSpace(&fBitmapBuffer, fBitmapBuffer.depth);
			/* update all dependencies in the directwindow buffer descriptor */
			while (acquire_sem(fDrawingLock) == B_INTERRUPTED)
				;
			SetColorSpace(&fDirectBuffer, fDirectBuffer.depth);
			release_sem(fDrawingLock);
			/* in offscreen mode, erase the background and cancel star erasing */
			if (new_set.display == DISPLAY_BITMAP) {
				SetBitmapBackGround();
				fStars.erase_count = 0;
				fSpecials.erase_count = 0;
			}
			/* in directwindow mode, just force an update */
			else
				fChartView->Invalidate();
			Unlock();
		}
	}

	/* check for change of the star animation density */
	if (new_set.star_density != fCurrentSettings.star_density) {
		if (LockWithTimeout(200000) == B_OK) {
			fCurrentSettings.star_density = new_set.star_density;
			/* change the picture button used in the UI */
			fDensityButton->SetEnabledOff(ButtonPicture(false, DENSITY_BUTTON_PICT));
			fDensityButton->SetEnabledOn(ButtonPicture(true, DENSITY_BUTTON_PICT));
			fDensityButton->Invalidate();
			Unlock();
		}
		fStars.count = new_set.star_density;
	}

	/* check for change in the buffer format for the offscreen bitmap.
	   DirectWindow depth change are always handle in realtime */
	if (new_set.depth != fCurrentSettings.depth) {
		CheckBitmap(new_set.depth, new_set.width, new_set.height);
		/* need to reset the buffer if it's currently used for display */
		if (new_set.display == DISPLAY_BITMAP) {
			SetBitmapBackGround();
			fStars.erase_count = 0;
			fSpecials.erase_count = 0;
		}
	}

	/* check for change in the drawing area of the offscreen bitmap */
	if ((new_set.width != fCurrentSettings.width) || (new_set.height != fCurrentSettings.height)) {
		CheckBitmap(new_set.depth, new_set.width, new_set.height);
		fBitmapBuffer.buffer_width = new_set.width;
		fBitmapBuffer.buffer_height = new_set.height;
		if (new_set.display == DISPLAY_BITMAP)
			SetGeometry(fBitmapBuffer.buffer_width, fBitmapBuffer.buffer_height);
		SetBitmapClipping(new_set.width, new_set.height);
	}

	/* copy the new state as the new current state */
	fCurrentSettings.Set(&new_set);
}

/* Initialise the starfield in the different modes */
void
ChartWindow::InitStars(int32 space_model)
{
	star		*s;
	int32		step;
	int32		amas_select[32];
	float		dx, dy, dz, dist, fact, alpha, r;
	float		factor[8];
	uint32		i, index, i_step;
	TPoint		amas[8];

	switch (space_model) {
	/* Create a random starfield */
	case SPACE_CHAOS :
		FillStarList(fStars.list, STAR_DENSITY_MAX);
		fKeyPointCount = 0;
		break;

	/* Create a starfield with big concentration of stars (amas) */
	case SPACE_AMAS :
	case SPACE_SPIRAL :
		/* pick 8 random position for the amas */
		FillStarList(fStars.list, 8);
		for (i=0; i<8; i++) {
			amas[i].x = fStars.list[i].x;
			amas[i].y = fStars.list[i].y;
			amas[i].z = fStars.list[i].z;
			amas_select[i] = i;
			factor[i] = ((float)(fCrcAlea&2047) + 0.5)*(1.0/128.0) + 16.0/3.0;
			CrcStep();
			CrcStep();
		}
		/* make each amas ramdomly smaller or bigger */
		for (i=8; i<32; i++) {
			amas_select[i] = (fCrcAlea & 7);
			CrcStep();
		}

		/* create a random starfield */
		FillStarList(fStars.list, STAR_DENSITY_MAX);

		/* In spiral mode, only half the star will be put into the amas.
		   the other half will be put into the spiral galaxy. */
		if (space_model == SPACE_AMAS)
			i_step = 1;
		else
			i_step = 2;
		s = fStars.list;

		for (i=0; i<STAR_DENSITY_MAX; i+=i_step) {
			/* for every star, calculate its position relative to the
			   center of the corresponding amas. */
			index = amas_select[i&31];
			dx = s->x-amas[index].x;
			if (dx < -0.5) dx += 1.0;
			if (dx > 0.5)  dx -= 1.0;
			dy = s->y-amas[index].y;
			if (dy < -0.5) dy += 1.0;
			if (dy > 0.5)  dy -= 1.0;
			dz = s->z-amas[index].z;
			if (dz < -0.5) dz += 1.0;
			if (dz > 0.5)  dz -= 1.0;

			/* make the star randomly closer from its center, but keep
			   it on the same orientation. */
			step = 0;
			dist = (abs(dx) + abs(dy) + abs(dz))*factor[index];
			while (dist > 1.0) {
				dist *= 0.5;
				step++;
			}

			step -= (fCrcAlea&3);
			CrcStep();
			fact = 1.0;
			for (;step>=0; step--)
				fact *= 0.55;
			dx *= fact;
			dy *= fact;
			dz *= fact;

			/* put the star back in the [0-1]x[0-1]x[0-1] iteration of
			   the cubic torus. */
			s->x = amas[index].x + dx;
			if (s->x >= 1.0) s->x -= 1.0;
			if (s->x <= 0.0) s->x += 1.0;
			s->y = amas[index].y + dy;
			if (s->y >= 1.0) s->y -= 1.0;
			if (s->y <= 0.0) s->y += 1.0;
			s->z = amas[index].z + dz;
			if (s->z >= 1.0) s->z -= 1.0;
			if (s->z <= 0.0) s->z += 1.0;

			s += i_step;
		}

		/* record the center of the amas as key points for the free
		   camera animation mode. */
		for (i=0; i<8; i++)
			fKeyPoints[i] = amas[i];
		fKeyPointCount = 8;

		/* no further processing needed in amas only mode. */
		if (space_model == SPACE_AMAS)
			break;

		/* in spiral mode, the second half of the star will be distributed
		   on random spiral like galaxy. */
		s = fStars.list+1;
		for (i=1; i<STAR_DENSITY_MAX; i+=2) {
			/* some random point (probability 50 %) will be move into a
			   big amas at the center of the spiral galaxy. */
			if (fCrcAlea & 2048) {
				/* for every star, calculate its position relative to the
				   center of the galaxy. */
				dx = s->x - 0.5;
				dy = s->y - 0.5;
				dz = s->z - 0.5;

				/* make the star randomly closer from its center, but keep
				   it on the same orientation. */
				step = 0;
				dist = (dx*dx + dy*dy + dz*dz) * (32.0/0.75);
				while (dist > 1.0) {
					dist *= 0.5;
					step++;
				}

				step -= (fCrcAlea&3);
				CrcStep();
				fact = 0.5;
				for (;step>=0; step--)
					fact *= 0.55;
				dx *= fact;
				dy *= fact;
				dz *= fact;
			}
			else {
				/* other star are put at a random place somewhere on one of
				   teh two spiral arms... */
				alpha = 3.4 * s->x * (s->x*0.5 + 1.0);
				if (fCrcAlea & 64)
					alpha += 3.14159;
				r = s->x * 0.34 + 0.08;
				r += (s->y-0.725 + 0.03 * (float)(fCrcAlea & 15))*0.04*(1.2+r);
				r *= 0.5;
				dx = (s->z-0.8 + 0.04 * (float)(fCrcAlea & 15)) * (2.0 - abs(s->y - 0.5)) * (0.025*0.5);
				dy = cos(alpha) * r;
				dz = sin(alpha) * r;
			}
			CrcStep();

			/* put the star back in the [0-1]x[0-1]x[0-1] iteration of
			   the cubic torus. */
			s->x = 0.5 + dx;
			s->y = 0.5 + dy;
			s->z = 0.5 + dz;
			s += 2;
		}

		/* add the center of the galaxy to the key point list for free camera
		   animation mode */
		fKeyPoints[8].x = 0.5;
		fKeyPoints[8].y = 0.5;
		fKeyPoints[8].z = 0.5;
		/* add seven other galaxy star to the key point list */
		for (i=9; i<16; i++) {
			fKeyPoints[i].x = fStars.list[i*(STAR_DENSITY_MAX/18)].x;
			fKeyPoints[i].y = fStars.list[i*(STAR_DENSITY_MAX/18)].y;
			fKeyPoints[i].z = fStars.list[i*(STAR_DENSITY_MAX/18)].z;
		}
		fKeyPointCount = 16;
		break;
	}

	/* In all starfield modes, for all stars, peek a random brightness level */
	for (i=0; i<STAR_DENSITY_MAX; i++) {
		fStars.list[i].size = (float)((fCrcAlea&15)+17)*(1.0/56.0);
		if ((fCrcAlea & 0xc0) == 0)
			fStars.list[i].size *= 2.0;
		if ((fCrcAlea & 0x3f00) == 0)
			fStars.list[i].size *= 3.0;
		CrcStep();
	}
}

/* Fill a list of star with random position in the [0-1]x[0-1]x[0-1] cube */
void
ChartWindow::FillStarList(star *list, int32 count)
{
	int32		i;

	for (i=0; i<count; i++) {
		list[i].x = ((float)(fCrcAlea&2047) + 0.5)*(1.0/2048.0);
		CrcStep();
	}
	for (i=0; i<count; i++) {
		list[i].y = ((float)(fCrcAlea&2047) + 0.5)*(1.0/2048.0);
		CrcStep();
	}
	for (i=0; i<count; i++) {
		list[i].z = ((float)(fCrcAlea&2047) + 0.5)*(1.0/2048.0);
		CrcStep();
	}
}

/* initialise anything needed to enable a specific special animation */
void
ChartWindow::InitSpecials(int32 code)
{
	int			i, j;
	float		alpha, ksin, kcos, coeff;
	TPoint		dx, dy;
	TMatrix		matrix;

	switch (code) {
	/* turn special animation off */
	case SPECIAL_NONE :
		fSpecials.count = 0;
		break;

	/* Initialise the pixel-comet animation */
	case SPECIAL_COMET :
		/* Get a bunchof random values by getting some radom stars */
		fSpecials.count = 512;
		FillStarList(fSpecials.list, 4);
		/* for both comets... */
		for (j=0; j<2; j++) {
			/* select the initial position of the comet head */
			fComet[j].x = fSpecials.list[j].x;
			fComet[j].y = fSpecials.list[j].y;
			fComet[j].z = fSpecials.list[j].z;
			fSpecials.list[0].size = 1.4;

			/* select the speed vector of the comet */
			matrix.Set(fSpecials.list[j+2].x * 6.28319, fSpecials.list[j+2].y * 3.14159 - 1.5708, 0.0);
			fDeltaComet[j] = matrix.Axis(0) * 0.0015;
			dx = matrix.Axis(1);
			dy = matrix.Axis(2);

			for (i=j+2; i<fSpecials.count; i+=2) {
				/* make the pixel invisible at first */
				fSpecials.list[i].x = -10.0;
				fSpecials.list[i].y = 0.0;
				fSpecials.list[i].z = 0.0;
				/* spread the initial count on a linear scale (to make pixel
				   appear progressively */
				fSpecialList[i].comet.count = i/2;
				/* spread the pixel trace count randomly on a [93-124] range */
				fSpecialList[i].comet.count0 = (fCrcAlea & 31) + 93;
				CrcStep();
				/* pick a random ejection angle */
				alpha = ((fCrcAlea>>8) & 1023) * (6.283159/1024.0);
				CrcStep();

				/* pick a random ejection speed */
				coeff = 0.000114 + 0.0000016 * (float)((fCrcAlea>>17) & 31);
				if ((fCrcAlea & 7) > 4) coeff *= 0.75;
				if ((fCrcAlea & 7) == 7) coeff *= 0.65;
				CrcStep();

				/* calculate the ejection speed vector */
				ksin = sin(alpha) * coeff;
				kcos = cos(alpha) * coeff;
				fSpecialList[i].comet.dx = dx.x * kcos + dy.x * ksin;
				fSpecialList[i].comet.dy = dx.y * kcos + dy.y * ksin;
				fSpecialList[i].comet.dz = dx.z * kcos + dy.z * ksin;
			}
		}
		break;

	/* Add a list of random star (used for nova effect by modifying their
	   brightness level in real time) close from the first stars of the
	   starfield. */
	case SPECIAL_NOVAS :
		fSpecials.count = 96;
		for (i=0; i<fSpecials.count; i++) {
			fSpecialList[i].nova.count = i + 40;
			fSpecialList[i].nova.count0 = (fCrcAlea & 63) + 28;
			CrcStep();
			fSpecials.list[i].x = fStars.list[i].x + (fCrcAlea & 1)*0.02 - 0.01;
			CrcStep();
			fSpecials.list[i].y = fStars.list[i].y + (fCrcAlea & 1)*0.02 - 0.01;
			CrcStep();
			fSpecials.list[i].z = fStars.list[i].z + (fCrcAlea & 1)*0.02 - 0.01;
			CrcStep();
			fSpecials.list[i].size = 0.0;
		}
		break;

	/* not implemented */
	case SPECIAL_BATTLE :
		fSpecials.count = 0;
		break;
	}
}

/* select a color for each star (and special animation point) by
   looping through the color index list. */
void
ChartWindow::SetStarColors(int32 *color_list, int32 color_count)
{
	int32		i, index;

	index = 0;
	for (i=0; i<STAR_DENSITY_MAX; i++) {
		fStars.list[i].color_type = color_list[index];
		index++;
		if (index >= color_count)
			index = 0;
	}
	for (i=0; i<SPECIAL_COUNT_MAX; i++) {
		fSpecials.list[i].color_type = color_list[index];
		index++;
		if (index >= color_count)
			index = 0;
	}
}


void
ChartWindow::SetGeometry(int32 dh, int32 dv)
{
	float zoom;

	/* calculate the zoom factor for the 3d projection */
	fGeometry.zoom_factor = (float)dh*(fDepthRef/DH_REF);
	zoom = (float)dv*(fDepthRef/DV_REF);
	if (zoom > fGeometry.zoom_factor)
		fGeometry.zoom_factor = zoom;

	/* offset of the origin in the view area */
	fGeometry.offset_h = (float)dh * 0.5;
	fGeometry.offset_v = (float)dv * 0.5;

	/* sub-pixel precision double-sampling */
	fGeometry.zoom_factor *= 2.0;
	fGeometry.offset_h = fGeometry.offset_h * 2.0 - 1.0;
	fGeometry.offset_v = fGeometry.offset_v * 2.0 - 1.0;
}


void
ChartWindow::SetColorSpace(buffer *buf, color_space depth)
{
	bool swap_needed;
	int32 red_shift = 0, green_shift = 0;
	int32 blue_shift = 0, alpha_shift = 0;
	int32 step_doubling = 0;

	int32 red_divide_shift = 0, green_divide_shift = 0;
	int32 blue_divide_shift = 0, alpha_divide_shift = 0;
	int32 i;
	uint32 color;
	uint32 *col;
	BScreen screen(this);
	rgb_color ref_color;

	/* depending the colorspace of the target buffer, set parameters used
	   to encode the RGBA information for various color information in
	   the right format. */
	buf->depth = depth;
	switch (depth) {
		case B_RGBA32_BIG :
		case B_RGB32_BIG :
		case B_RGBA32 :
		case B_RGB32 :
			buf->depth_mode = PIXEL_4_BYTES;
			buf->bytes_per_pixel = 4;
			red_shift = 16;
			green_shift = 8;
			blue_shift = 0;
			alpha_shift = 24;
			red_divide_shift = 0;
			green_divide_shift = 0;
			blue_divide_shift = 0;
			alpha_divide_shift = 0;
			step_doubling = 32;
			break;
		case B_RGB16_BIG :
		case B_RGB16 :
			buf->depth_mode = PIXEL_2_BYTES;
			buf->bytes_per_pixel = 2;
			red_shift = 11;
			red_divide_shift = 3;
			green_shift = 5;
			green_divide_shift = 2;
			blue_shift = 0;
			blue_divide_shift = 3;
			alpha_shift = 32;
			alpha_divide_shift = 8;
			step_doubling = 16;
			break;
		case B_RGB15 :
		case B_RGBA15 :
		case B_RGB15_BIG :
		case B_RGBA15_BIG :
			buf->depth_mode = PIXEL_2_BYTES;
			buf->bytes_per_pixel = 2;
			red_shift = 10;
			red_divide_shift = 3;
			green_shift = 5;
			green_divide_shift = 3;
			blue_shift = 0;
			blue_divide_shift = 3;
			alpha_shift = 15;
			alpha_divide_shift = 7;
			step_doubling = 16;
			break;
		case B_CMAP8 :
		default:
			buf->depth_mode = PIXEL_1_BYTE;
			buf->bytes_per_pixel = 1;
			break;
	}

	/* Check if the endianess of the buffer is different from the
	   endianess use by the processor to encode the color information */
	switch (depth) {
		case B_RGBA32_BIG :
		case B_RGB32_BIG :
		case B_RGB16_BIG :
		case B_RGB15_BIG :
		case B_RGBA15_BIG :
			swap_needed = true;
			break;
		case B_RGBA32 :
		case B_RGB32 :
		case B_RGB16 :
		case B_RGB15 :
		case B_RGBA15 :
		case B_CMAP8 :
		default:
			swap_needed = false;
			break;
	}

#if B_HOST_IS_BENDIAN
	swap_needed = ~swap_needed;
#endif
	/* fill the color tables (8 light level for 7 colors, and also encode
	   the background color */
	col = buf->colors[0];
	switch (buf->depth_mode) {
		case PIXEL_1_BYTE :
			/* 8 bits, indexed mode */
			for (i=0; i<7*8; i++) {
				ref_color = color_list[i>>3];
				ref_color.red   = (ref_color.red*light_gradient[i&7])>>16;
				ref_color.green = (ref_color.green*light_gradient[i&7])>>16;
				ref_color.blue  = (ref_color.blue*light_gradient[i&7])>>16;
				color = screen.IndexForColor(ref_color);
				col[i] = (color<<24) | (color<<16) | (color<<8) | color;
			}
			color = screen.IndexForColor(fCurrentSettings.back_color);
			buf->back_color = (color<<24) | (color<<16) | (color<<8) | color;
			break;
		case PIXEL_2_BYTES :
		case PIXEL_4_BYTES :
			/* 15, 16 or 32 bytes, RGB modes. Those modes just directly encode
			   part of the bits of the initial rgba_color, at the right bit
			   position */
			for (i=0; i<7*8; i++) {
				ref_color = color_list[i>>3];
				ref_color.red   = (ref_color.red*light_gradient[i&7])>>16;
				ref_color.green = (ref_color.green*light_gradient[i&7])>>16;
				ref_color.blue  = (ref_color.blue*light_gradient[i&7])>>16;
				color = ((uint8)ref_color.red >> red_divide_shift) << red_shift;
				color |= ((uint8)ref_color.green >> green_divide_shift) << green_shift;
				color |= ((uint8)ref_color.blue >> blue_divide_shift) << blue_shift;
				color |= ((uint8)ref_color.alpha >> alpha_divide_shift) << alpha_shift;
				col[i] = (color<<step_doubling) | color;
			}
			color = ((uint8)fCurrentSettings.back_color.red >> red_divide_shift) << red_shift;
			color |= ((uint8)fCurrentSettings.back_color.green >> green_divide_shift) << green_shift;
			color |= ((uint8)fCurrentSettings.back_color.blue >> blue_divide_shift) << blue_shift;
			color |= ((uint8)fCurrentSettings.back_color.alpha >> alpha_divide_shift) << alpha_shift;
			buf->back_color = (color<<step_doubling) | color;
			break;
	}

	/* do the endianess swap if needed */
	if (swap_needed) {
		col = buf->colors[0];
		for (i = 0; i < 7*8; i++) {
			col[i] = B_SWAP_INT32(col[i]);
		}
		buf->back_color = B_SWAP_INT32(buf->back_color);
	}
}


/*!
	For each different offset used to access a pixel of the star matrix,
	create a buffer pointer based on the main buffer pointer offset by
	the pixel matrix offset. That way, any pixel of the matrix can be
	address later by just picking the right pointer and indexing it by
	the global star offset
*/
void
ChartWindow::SetPatternBits(buffer *buf)
{
	for (int32 i=0; i<32; i++) {
		buf->pattern_bits[i] = (void*)((char*)buf->bits +
			buf->bytes_per_row * pattern_dv[i] +
			buf->bytes_per_pixel * pattern_dh[i]);
	}
}


//	#pragma mark Engine processing related functions.


/*!
	That's the main thread controling the animation and synchronising
	the engine state with the changes coming from the UI.
*/
int32
ChartWindow::Animation(void *data)
{
	int32			i, cur_4_frames_index, cur_last_fps, count_fps;
	float			time_factor = 0, total_fps;
	float			last_fps[4];
	bigtime_t		next_stat;
	bigtime_t		timer, time_left, current;
	bigtime_t		before_frame, after_frame, fps;
	bigtime_t		last_4_frames[4];
	ChartWindow		*w;

	w = (ChartWindow*)data;

	/* init refresh rate control */
	timer = system_time();
	w->fFrameDelay = 100000;

	/* init performance timing control variables */
	next_stat = timer + STAT_DELAY;
	cur_4_frames_index = 0;
	cur_last_fps = 0;
	for (i=0; i<4; i++)
		last_fps[i] = 0.0;
	total_fps = 0.0;
	count_fps = 0;

	/* here start the loop doing all the good stuff */
	while (!w->fKillThread) {

		/* start the performance mesurement here */
		before_frame = system_time();

		/* credit the timer by the current delay between frame */
		timer += w->fFrameDelay;

		/* change the settings, if needed */
		w->ChangeSetting(w->fNextSettings);

		/* draw the next frame */
		if (w->fCurrentSettings.display == DISPLAY_BITMAP) {
			w->RefreshStars(&w->fBitmapBuffer, time_factor * 2.4);
			if (w->LockWithTimeout(200000) == B_OK) {
				w->fChartView->DrawBitmap(w->fOffscreen);
				w->Unlock();
			}
		}
		else if (w->fCurrentSettings.display == DISPLAY_DIRECT) {
			/* This part get the drawing-lock to guarantee that the
			   directbuffer context won't change during the drawing
			   operations. During that period, no Window should be
			   done to avoid any potential deadlock. */
			while (acquire_sem(w->fDrawingLock) == B_INTERRUPTED)
				;

			if (w->fDirectConnected)
				w->RefreshStars(&w->fDirectBuffer, time_factor * 2.4);

			release_sem(w->fDrawingLock);
		}

		/* do the camera animation */
		w->CameraAnimation(time_factor);

		/* end the performance mesurement here */
		after_frame = system_time();

		/* performance timing calculation here (if display enabled). */
		if (w->fCurrentSettings.display != DISPLAY_OFF) {
			/* record frame duration into a 2 levels 4 entries ring buffer */
			last_4_frames[cur_4_frames_index] = after_frame - before_frame;
			cur_4_frames_index++;
			if (cur_4_frames_index == 4) {
				cur_4_frames_index = 0;
				last_fps[cur_last_fps++ & 3] =
					last_4_frames[0]+last_4_frames[1]+last_4_frames[2]+last_4_frames[3];
				/* the instant load is calculated based on the average duration
				   of the last 16 frames. */
				fps = (bigtime_t) (16e6 / (last_fps[0]+last_fps[1]+last_fps[2]+last_fps[3]));
				w->DrawInstantLoad(fps);

				total_fps += fps;
				count_fps += 1;

				/* The statistic numbers are based on the ratio between the real
				   duration and the frame count during a period of approximately
				   STAT_DELAY microseconds. */
				if (after_frame > next_stat) {
					w->PrintStatNumbers(total_fps/(float)count_fps);
					next_stat = after_frame+STAT_DELAY;
					total_fps = 0.0;
					count_fps = 0;
				}
			}
		}

		/* do a pause if necessary */
		current = system_time();
		time_left = timer-current;
		if (time_left > 2000) {
			snooze(time_left);
			time_left = 0;
		}
		else if (time_left < -5000)
			timer = current;

		/* this factor controls the dynamic timing configuration, that
		   slow down or speed up the whole animation step to compensate
		   for varaiation of the framerate. */
		time_factor = (float)(system_time() - before_frame) * (1.0/4e4);
	}
	return 0;
}

/* This is the second thread doing star animation. It's just a poor
   slave of the Animation thread. It's directly synchronised with its
   master, and will only do some star animation processing whenever
   its master allows him to do so. */
int32
ChartWindow::Animation2(void *data)
{
	ChartWindow *w = (ChartWindow*)data;
	while (!w->fKillThread) {
		/* This thread need to both wait for its master to unblock
		   him to do some real work, or for the main control to
		   set the fKillThread flag, asking it to quit. */
		status_t status;
		do {
			status = acquire_sem_etc(w->fSecondThreadLock, 1, B_TIMEOUT, 500000);
			if (w->fKillThread)
				return 0;
		} while (status == B_TIMED_OUT || status == B_INTERRUPTED);

		/* the duration of the processing is needed to control the
		   dynamic load split (see RefreshStar) */
		bigtime_t before = system_time();
		RefreshStarPacket(w->fSecondThreadBuffer, &w->fStars2, &w->fGeometry);
		RefreshStarPacket(w->fSecondThreadBuffer, &w->fSpecials2, &w->fGeometry);
		bigtime_t after = system_time();

		w->fSecondThreadDelay = max_c(after-before, 1);

		release_sem(w->fSecondThreadRelease);
	}
	return 0;
}


void
ChartWindow::SetCubeOffset()
{
	int32		i;
	TPoint		min, max, dx, dy, dz, p1;

	/* calculate the shortest aligned cube encapsulating the pyramid
	   of vision, by calculating the min and max on the 3 main axis
	   of the coordinates of the 8 extremities of the pyramid of
	   vision (as limited by its 4 sides and the rear and front
	   cut plan) */
	min.x = min.y = min.z = 10.0;
	max.x = max.y = max.z = -10.0;

	dx = fCamera.Axis(0)*(DH_REF*0.5);
	dy = fCamera.Axis(1)*(DV_REF*0.5);
	dz = fCamera.Axis(2)*fDepthRef;

	for (i=0; i<8; i++) {
		/* left side / right side */
		if (i&1) p1 = dz + dx;
		else	 p1 = dz - dx;
		/* top side / bottom side */
		if (i&2) p1 = p1 + dy;
		else	 p1 = p1 - dy;
		/* rear cut plan / front cut plan */
		if (i&4) p1 = p1 * (1.0 / Z_CUT_RATIO);
		/* relative to the position of the camera */
		p1 = p1 + fOrigin;

		if (min.x > p1.x) min.x = p1.x;
		if (min.y > p1.y) min.y = p1.y;
		if (min.z > p1.z) min.z = p1.z;
		if (max.x < p1.x) max.x = p1.x;
		if (max.y < p1.y) max.y = p1.y;
		if (max.z < p1.z) max.z = p1.z;
	}

	/* offset the camera origin by +1 or -1 on any axis (which
	   doesn't change its relative position in the cubic torus
	   as the cubic torus repeat itself identicaly for any move
	   of +1 or -1 on any axis), to get the bounding cube into
	   [0-2[ x [0-2[ x [0-2[. As the pyramid of vision is just
	   small enough to gurantee that its bounding box will never
	   be larger than 1 on any axis, it's always possible. */
	while (min.x < 0.0) {
		min.x += 1.0;
		max.x += 1.0;
		fOrigin.x += 1.0;
	}
	while (min.y < 0.0) {
		min.y += 1.0;
		max.y += 1.0;
		fOrigin.y += 1.0;
	}
	while (min.z < 0.0) {
		min.z += 1.0;
		max.z += 1.0;
		fOrigin.z += 1.0;
	}
	while (max.x >= 2.0) {
		min.x -= 1.0;
		max.x -= 1.0;
		fOrigin.x -= 1.0;
	}
	while (max.y >= 2.0) {
		min.y -= 1.0;
		max.y -= 1.0;
		fOrigin.y -= 1.0;
	}
	while (max.z >= 2.0) {
		min.z -= 1.0;
		max.z -= 1.0;
		fOrigin.z -= 1.0;
	}

	/* set the cutting plans. For example, if the bouding box of
	   the pyramid of vision of the camera imcludes only X in
	   [0.43 ; 1.37], we know that points with X in [0 ; 0.4] are
	   not visible from the camera. So we will offset them by +1
	   in [1.0 ; 1.4] where they will be visible. Same process
	   on other axis. That way, we have to test every star of the
	   starfield in one position and only one. */
	fCut.x = (min.x + max.x - 1.0) * 0.5;
	fCut.y = (min.y + max.y - 1.0) * 0.5;
	fCut.z = (min.z + max.z - 1.0) * 0.5;

	/* Make sure those new settings are copied into the struct
	   used by the embedded C-engine. */
	SyncGeo();
}

/* move the camera around, as defined by the animation popup.
   This is adjusted by a time factor to compensate for change
   in the framerate. */
void
ChartWindow::CameraAnimation(float time_factor)
{
	TPoint			move;

	switch (fCurrentSettings.animation) {
	/* Slow rotation around the "center" of the visible area. */
	case ANIMATION_ROTATE :
		/* turn around a point at 0.45 in front of the camera */
		move = fCamera.Axis(2);
		move = move * 0.45;
		fOrigin = fOrigin + move;

		/* turn around the alpha angle of the spheric rotation
		   matrix */
		fCameraAlpha += 0.011*time_factor;
		if (fCameraAlpha > 2*3.14159)
			fCameraAlpha -= 2*3.14159;

		/* set the other two angles close from hardcoded values */
		if (fCameraTheta < 0.18)
			fCameraTheta += 0.003*time_factor;
		if (fCameraTheta > 0.22)
			fCameraTheta -= 0.003*time_factor;

		if (fCameraPhi < -0.02)
			fCameraPhi += 0.003*time_factor;
		if (fCameraPhi > 0.02)
			fCameraPhi -= 0.003*time_factor;

		fCamera.Set(fCameraAlpha, fCameraTheta, fCameraPhi);
		fCameraInvert = fCamera.Transpose();
		move = fCamera.Axis(2);
		move = move * -0.45;
		fOrigin = fOrigin + move;
		/* As we moved or rotated the camera, we need to process
		   again the parameters specific to the pyramid of vision. */
		SetCubeOffset();
		break;

	case ANIMATION_SLOW_MOVE :
		/* Just move forward, at slow speed */
		move = fCamera.Axis(2);
		move = move * 0.006*time_factor;
		fOrigin = fOrigin + move;
		SetCubeOffset();
		break;

	case ANIMATION_FAST_MOVE :
		/* Just move forward, at fast speed */
		move = fCamera.Axis(2);
		move = move * 0.018*time_factor;
		fOrigin = fOrigin + move;
		SetCubeOffset();
		break;

	case ANIMATION_FREE_MOVE :
		/* go into advanced selection process no more than once every
		   0.5 time unit (average time). */
		fLastDynamicDelay += time_factor;
		if (fLastDynamicDelay > 0.5) {
			fLastDynamicDelay -= 0.5;
			if (fLastDynamicDelay > 0.2)
				fLastDynamicDelay = 0.2;

			/* if we're not following any target, then just turn
			   randomly (modifying only the direction of the
			   acceleration) */
			if (fTrackingTarget < 0) {
				if ((fCrcAlea & 0x4200) == 0) {
					if (fCrcAlea & 0x8000)
						fCountAlpha += 1 - (fCountAlpha/4);
					else
						fCountAlpha += -1 - (fCountAlpha/4);
					CrcStep();
					if (fCrcAlea & 0x8000)
						fCountTheta += 1 - (fCountTheta/4);
					else
						fCountTheta += -1 - (fCountTheta/4);
					CrcStep();
					if (fCrcAlea & 0x8000)
						fCountPhi += 1 - (fCountPhi/4);
					else
						fCountPhi += -1 - (fCountPhi/4);
					CrcStep();
				}
				CrcStep();
			}
			/* if following a target, try to turn in its direction */
			else
				FollowTarget();

			/* Change target everyonce in a while... */
			if ((fCrcAlea & 0xf80) == 0)
				SelectNewTarget();

			/* depending the direction of acceleration, increase or
			   reduce the angular speed of the 3 spherical angles. */
			if (fCountAlpha < 0)
				fDynamicAlpha += -0.0005 - fDynamicAlpha * 0.025;
			else if (fCountAlpha > 0)
				fDynamicAlpha += 0.0005 - fDynamicAlpha * 0.025;

			if (fCountTheta < 0)
				fDynamicTheta += -0.0002 - fDynamicTheta * 0.025;
			else if (fCountTheta > 0)
				fDynamicTheta += 0.0002 - fDynamicTheta * 0.025;

			if (fCountPhi < 0)
				fDynamicPhi += -0.00025 - fDynamicPhi * 0.025;
			else if (fCountPhi >0)
				fDynamicPhi += 0.00025 - fDynamicPhi * 0.025;
		}

		/* turn the camera following the specified angular speed */
		fCameraAlpha += fDynamicAlpha*time_factor;
		if (fCameraAlpha < 0.0)
			fCameraAlpha += 2*3.14159;
		else if (fCameraAlpha > 2*3.14159)
			fCameraAlpha -= 2*3.14159;

		fCameraTheta += fDynamicTheta*time_factor;
		if (fCameraTheta < 0.0)
			fCameraTheta += 2*3.14159;
		else if (fCameraTheta > 2*3.14159)
			fCameraTheta -= 2*3.14159;

		fCameraPhi += fDynamicPhi*time_factor;
		if (fCameraPhi < 0.0)
			fCameraPhi += 2*3.14159;
		else if (fCameraPhi > 2*3.14159)
			fCameraPhi -= 2*3.14159;

		/* Set the new rotation matrix of the camera */
		fCamera.Set(fCameraAlpha, fCameraTheta, fCameraPhi);
		fCameraInvert = fCamera.Transpose();

		/* move the camera forward at medium speed */
		move = fCamera.Axis(2);
		move = move * 0.0115*time_factor;
		fOrigin = fOrigin + move;
		SetCubeOffset();
		break;
	}
}


void
ChartWindow::SelectNewTarget()
{
	float		ratio, ratio_min;
	float		dist, lateral, axial, ftmp;
	int32		i, index_min;
	TPoint		axis, pt, vect;

	axis = fCamera.Axis(2);
	ratio_min = 1e6;
	index_min = -3;

	for (i=-2; i<fKeyPointCount; i++) {
		/* if they're used, the comets are two good potential
		   targets. */
		if (i < 0) {
			if (fCurrentSettings.special == SPECIAL_COMET)
				pt = fComet[i+2];
			else
				continue;
		}
		/* other potential targets are the key_points defined
		   in the star field. */
		else
			pt = fKeyPoints[i];

		/* Qualify the interest of the potential target in
		   relationship with its distance and its proximity to
		   the axis of the camera. */
		if (pt.x < fCut.x)
			pt.x += 1.0;
		if (pt.y < fCut.y)
			pt.y += 1.0;
		if (pt.z < fCut.z)
			pt.z += 1.0;
		pt = pt - fOrigin;
		dist = pt.Length();
		ftmp = 1.0/dist;
		pt.x *= ftmp;
		pt.y *= ftmp;
		pt.z *= ftmp;
		vect = pt ^ axis;
		lateral = axis.Length();
		axial = pt.x*axis.x + pt.y*axis.y + pt.z*axis.z;
		ratio = (lateral/axial) * sqrt(dist);

		/* keep track of the best possible choice */
		if ((dist > 0.05) && (ratio < ratio_min)) {
			ratio_min = ratio;
			index_min = i;
		}
	}

	/* record what target has been chosen */
	fTrackingTarget = index_min+2;
}

/* Try to change the angular acceleration to aim in direction
   of the current target. */
void
ChartWindow::FollowTarget()
{
	float		x0, y0, x, y, z, cphi, sphi;
	TPoint		pt;

	/* get the target point */
	if (fTrackingTarget < 2)
		pt = fComet[fTrackingTarget];
	else
		pt = fKeyPoints[fTrackingTarget-2];
	/* move it in the right iteration of the cubic torus (the
	   one iteration that is the most likely to be close from
	   the pyramid of vision. */
	if (pt.x < fCut.x)
		pt.x += 1.0;
	if (pt.y < fCut.y)
		pt.y += 1.0;
	if (pt.z < fCut.z)
		pt.z += 1.0;
	/* convert the target coordinates in the camera referential */
	pt = pt - fOrigin;
	x = fCameraInvert.m[0][0]*pt.x + fCameraInvert.m[1][0]*pt.y + fCameraInvert.m[2][0]*pt.z;
	y = fCameraInvert.m[0][1]*pt.x + fCameraInvert.m[1][1]*pt.y + fCameraInvert.m[2][1]*pt.z;
	z = fCameraInvert.m[0][2]*pt.x + fCameraInvert.m[1][2]*pt.y + fCameraInvert.m[2][2]*pt.z;
	if (z <= 0.001) {
		/* need to do a U-turn (better to do it using theta). */
		fCountAlpha = 0;
		fCountTheta = -1;
		fCountPhi = 0;
	}
	else {
		/* need to do a direction adjustement (play with
		   alpha and theta) */
		cphi = cos(fCameraPhi);
		sphi = sin(fCameraPhi);
		x0 = x*cphi - y*sphi;
		y0 = x*sphi + y*cphi;

		/* need to move first on the left/right axis */
		if (abs(x0) > abs(y0)) {
			if (x0 > 0)
				fCountAlpha = -1;
			else
				fCountAlpha = 1;
			fCountTheta = 0;
			fCountPhi = 0;
		}
		/* need to move first on the top/bottom axis */
		else {
			if (y0 > 0)
				fCountTheta = -1;
			else
				fCountTheta = 1;
			fCountAlpha = 0;
			fCountPhi = 0;
		}
	}
}

/* Do whatever special processing is required to do special
   animation. This used a time_step (or time_factor) to
   compensate for change in the framerate of the animation. */
void
ChartWindow::AnimSpecials(float time_step)
{
	int			i, j;
	star		*s;
	float		delta;
	special		*sp;

	switch (fCurrentSettings.special) {
	case SPECIAL_COMET :
		/* for both comets... */
		for (j=0; j<2; j++) {
			/* move the comet forward, at its specific speed */
			fComet[j] = fComet[j] + fDeltaComet[j] * time_step;
			/* Insure that the comet stays in the [0-1]x[0-1]x[0-1]
			   iteration of the cubic torus. */
			if (fComet[j].x < 0.0) fComet[j].x += 1.0;
			else if (fComet[j].x > 1.0) fComet[j].x -= 1.0;
			if (fComet[j].y < 0.0) fComet[j].y += 1.0;
			else if (fComet[j].y > 1.0) fComet[j].y -= 1.0;
			if (fComet[j].z < 0.0) fComet[j].z += 1.0;
			else if (fComet[j].z > 1.0) fComet[j].z -= 1.0;
			/* set the position of the star used to represent the
			   head of the comet. */
			fSpecials.list[j].x = fComet[j].x;
			fSpecials.list[j].y = fComet[j].y;
			fSpecials.list[j].z = fComet[j].z;

			/* for other point, the ones that are ejected from the
			   comet, depending for allow long they have been ejected... */
			s = fSpecials.list+j+2;
			sp = fSpecialList+j+2;
			for (i=j+2; i<fSpecials.count; i+=2) {
				sp->comet.count -= (int32)time_step;
				/* they are reset and reejected again, just a little in
				   the back of the head of the comet */
				if (sp->comet.count <= 0.0) {
					delta = (0.6 + (float)(fCrcAlea & 31) * (1.0/32.0)) * time_step;
					s->x = fComet[j].x + 6.0 * sp->comet.dx - fDeltaComet[j].x * delta;
					s->y = fComet[j].y + 6.0 * sp->comet.dy - fDeltaComet[j].y * delta;
					s->z = fComet[j].z + 6.0 * sp->comet.dz - fDeltaComet[j].z * delta;
					s->size = 0.6;
					sp->comet.count = (int32)(sp->comet.count0 + (fCrcAlea & 63));
					CrcStep();
				}
				/* or they just move at their own (ejection) speed */
				else {
					s->x += sp->comet.dx * time_step;
					s->y += sp->comet.dy * time_step;
					s->z += sp->comet.dz * time_step;
					s->size *= (1.0 - 0.031 * time_step + 0.001 * time_step * time_step);
				}
				sp+=2;
				s+=2;
			}
		}
		break;

	case SPECIAL_NOVAS :
		/* Novas are just stars (usualy invisible) that periodically
		   become much brighter during a suddent flash, then disappear
		   again until their next cycle */
		sp = fSpecialList;
		for (i=0; i<fSpecials.count; i++) {
			sp->nova.count -= time_step;
			if (sp->nova.count <= 0.0) {
				fSpecials.list[i].x -= 10.0;
				sp->nova.count = sp->nova.count0 + (fCrcAlea & 31);
				CrcStep();
			}
			else if (sp->nova.count < 16.0) {
				if (fSpecials.list[i].x < 0.0)
					fSpecials.list[i].x += 10.0;
				fSpecials.list[i].size = sp->nova.count;
			}
			sp++;
		}
		break;

	case SPECIAL_BATTLE :
		/* not implemented */
		break;
	}
}


/* Sync the embedded camera state with the window class camera
   state (before calling the embedded C-engine in ChartRender.c */
void
ChartWindow::SyncGeo()
{
	fGeometry.x = fOrigin.x;
	fGeometry.y = fOrigin.y;
	fGeometry.z = fOrigin.z;
	fGeometry.cutx = fCut.x;
	fGeometry.cuty = fCut.y;
	fGeometry.cutz = fCut.z;
	memcpy(fGeometry.m, fCameraInvert.m, sizeof(float)*9);
}


void
ChartWindow::RefreshStars(buffer *buf, float time_step)
{
	/* do the specials animation (single-threaded) */
	AnimSpecials(time_step);

	/* do the projection, clipping, erase and redraw
	   of all stars. This operation is done by the
	   embedded C-engine. This code only control the
	   dynamic load split between the two threads, when
	   needed. */
	if (fCurrentSettings.second_thread) {

		int32 star_threshold = (int32)((float)fStars.count * fSecondThreadThreshold + 0.5);
		int32 special_threshold = (int32)((float)fSpecials.count * fSecondThreadThreshold + 0.5);

		/* split the work load (star and special animation)
		   between the two threads, proportionnaly to the
		   last split factor determined during the last
		   cycle. */
		star_packet stars1;
		stars1.list = fStars.list;
		stars1.count = star_threshold;
		stars1.erase_count = star_threshold;
		if (stars1.erase_count > fStars.erase_count)
			stars1.erase_count = fStars.erase_count;

		fStars2.list = fStars.list + star_threshold;
		fStars2.count = fStars.count - star_threshold;
		fStars2.erase_count = fStars.erase_count - star_threshold;
		if (fStars2.erase_count < 0)
			fStars2.erase_count = 0;

		star_packet specials1;
		specials1.list = fSpecials.list;
		specials1.count = special_threshold;
		specials1.erase_count = special_threshold;
		if (specials1.erase_count > fSpecials.erase_count)
			specials1.erase_count = fSpecials.erase_count;

		fSpecials2.list = fSpecials.list + special_threshold;
		fSpecials2.count = fSpecials.count - special_threshold;
		fSpecials2.erase_count = fSpecials.erase_count - special_threshold;
		if (fSpecials2.erase_count < 0)
			fSpecials2.erase_count = 0;

		fSecondThreadBuffer = buf;

		/* release the slave thread */
		release_sem(fSecondThreadLock);

		/* do its own part (time it) */
		bigtime_t before = system_time();
		RefreshStarPacket(buf, &stars1, &fGeometry);
		RefreshStarPacket(buf, &specials1, &fGeometry);
		bigtime_t after = system_time();

		/* wait for completion of the second thread */
		while (acquire_sem(fSecondThreadRelease) == B_INTERRUPTED)
			;

		/* calculate the new optimal split ratio depending
		   of the previous one and the time used by both
		   threads to do their work. */
		float ratio = ((float)fSecondThreadDelay /
				(float)max_c(after - before, 1))
			* (fSecondThreadThreshold / (1.0 - fSecondThreadThreshold));
		fSecondThreadThreshold = ratio / (1.0 + ratio);

	} else {
		 /* In single-threaded mode, nothing fancy to be done. */
		RefreshStarPacket(buf, &fStars, &fGeometry);
		RefreshStarPacket(buf, &fSpecials, &fGeometry);
	}

	/* All the stars that were drawn will have to be erased during
	   the next frame. */
	fStars.erase_count = fStars.count;
	fSpecials.erase_count = fSpecials.count;
}


//	#pragma mark Offscreen bitmap configuration related functions.


void
ChartWindow::CheckBitmap(color_space depth, int32 width, int32 height)
{
	color_space		cur_depth;

	if (LockWithTimeout(200000) != B_OK)
		return;
	/* If there was no offscreen before, or if it was too small
	   or in the wrong depth, then... */
	if (fOffscreen == NULL)
		cur_depth = B_NO_COLOR_SPACE;
	else
		cur_depth = fBitmapBuffer.depth;
	if ((cur_depth != depth) || (width > fMaxWidth) || (height > fMaxHeight)) {
		/* We free the old one if needed... */
		if (fOffscreen)
			delete fOffscreen;
		/* We chose a new size (resizing are done by big step to
		   avoid resizing to often)... */
		while ((width > fMaxWidth) || (height > fMaxHeight)) {
			fMaxWidth += WINDOW_H_STEP;
			fMaxHeight += WINDOW_V_STEP;
		}
		/* And we try to allocate a new BBitmap at the new size. */
		fOffscreen = new BBitmap(BRect(0, 0, fMaxWidth-1, fMaxHeight-1), depth);
		if (!fOffscreen->IsValid()) {
			/* If we failed, the offscreen is released and the buffer
			   clipping is set as empty. */
			delete fOffscreen;
			fOffscreen = NULL;
			fBitmapBuffer.depth = B_NO_COLOR_SPACE;
			fBitmapBuffer.clip_bounds.top = 0;
			fBitmapBuffer.clip_bounds.left = 0;
			fBitmapBuffer.clip_bounds.right = -1;
			fBitmapBuffer.clip_bounds.bottom = -1;
		}
		else {
			/* If we succeed, then initialise the generic buffer
			   descriptor, we set the clipping to the required size,
			   and we set the buffer background color. */
			fBitmapBuffer.bits = fOffscreen->Bits();
			fBitmapBuffer.bytes_per_row = fOffscreen->BytesPerRow();
			fBitmapBuffer.buffer_width = fCurrentSettings.width;
			fBitmapBuffer.buffer_height = fCurrentSettings.height;
			SetColorSpace(&fBitmapBuffer, fOffscreen->ColorSpace());
			SetPatternBits(&fBitmapBuffer);
			SetBitmapClipping(fCurrentSettings.width, fCurrentSettings.height);
			SetBitmapBackGround();
		}
	}
	Unlock();
}


void
ChartWindow::SetBitmapClipping(int32 width, int32 height)
{
	/* Set the bitmap buffer clipping to the required size of
	   the buffer (even if the allocated buffer is larger) */
	fBitmapBuffer.clip_list_count = 1;
	fBitmapBuffer.clip_bounds.top = 0;
	fBitmapBuffer.clip_bounds.left = 0;
	fBitmapBuffer.clip_bounds.right = width-1;
	fBitmapBuffer.clip_bounds.bottom = height-1;
	fBitmapBuffer.clip_list[0].top = fBitmapBuffer.clip_bounds.top;
	fBitmapBuffer.clip_list[0].left = fBitmapBuffer.clip_bounds.left;
	fBitmapBuffer.clip_list[0].right = fBitmapBuffer.clip_bounds.right;
	fBitmapBuffer.clip_list[0].bottom = fBitmapBuffer.clip_bounds.bottom;
}


void
ChartWindow::SetBitmapBackGround()
{
	int32		i, count;
	uint32		*bits;
	uint32		color;

	/* set the bitmap buffer to the right background color */
	bits = (uint32*)fOffscreen->Bits();
	count = fOffscreen->BitsLength()/4;
	color = fBitmapBuffer.back_color;

	for (i=0; i<count; i++)
		bits[i] = color;
}


//	#pragma mark DirectWindow related functions.


void
ChartWindow::DirectConnected(direct_buffer_info *info)
{
	/* block the animation thread. */
	while (acquire_sem(fDrawingLock) == B_INTERRUPTED)
		;
	/* update the direct screen infos. */
	SwitchContext(info);
	/* unblock the animation thread. */
	release_sem(fDrawingLock);
}

/* This function update the internal graphic context of the ChartWindow
   object to reflect the infos send through the DirectConnected API.
   It also update the state of stars (and erase some of them) to
   insure a clean transition during resize. As this function is called
   in DirectConnected, it's a bad idea to do any heavy drawing (long)
   operation. But as we only update the stars (the background will be
   update a little later by the view system), it's not a big deal. */
void
ChartWindow::SwitchContext(direct_buffer_info *info)
{
	uint32 i, j;

	/* you need to use that mask to read the buffer state. */
	switch (info->buffer_state & B_DIRECT_MODE_MASK) {
	/* start a direct screen connection. */
	case B_DIRECT_START :
		/* set the status as connected, and continue as a modify */
		fDirectConnected = true;

	/* change the state of a direct screen connection. */
	case B_DIRECT_MODIFY :
		/* update the description of the abstract buffer representing
		   the direct window connection. DirectConnected returns the
		   description of the full content area. As we want to use
		   only the animation view part of the window, we will need
		   to compensate for that when update the descriptor. */

		/* This calculate the base address of the animation view, taking into
		   account the base address of the screen buffer, the position of the
		   window and the position of the view in the window */
		fDirectBuffer.bits = (void*)((char*)info->bits +
			(info->window_bounds.top + TOP_LEFT_LIMIT) * info->bytes_per_row +
			(info->window_bounds.left + LEFT_WIDTH) * (info->bits_per_pixel>>3));
		/* Bytes per row and pixel-format are the same than the window values */
		fDirectBuffer.bytes_per_row = info->bytes_per_row;
		SetColorSpace(&fDirectBuffer, info->pixel_format);
		SetPatternBits(&fDirectBuffer);

		/* the width and height of the animation view are linked to the width
		   and height of the window itself, reduced by the size of the borders
		   reserved for the UI. */
		fDirectBuffer.buffer_width =
			info->window_bounds.right-info->window_bounds.left+1 - LEFT_WIDTH;
		fDirectBuffer.buffer_height =
			info->window_bounds.bottom-info->window_bounds.top+1 - TOP_LEFT_LIMIT;

		/* Now, we go through the clipping list and "clip" the clipping
		   rectangle to the animation view boundary. */
		j = 0;
		for (i=0; i<info->clip_list_count; i++) {
			fDirectBuffer.clip_list[j].top = info->clip_list[i].top - info->window_bounds.top;
			if (fDirectBuffer.clip_list[j].top < TOP_LEFT_LIMIT)
				fDirectBuffer.clip_list[j].top = TOP_LEFT_LIMIT;
			fDirectBuffer.clip_list[j].left = info->clip_list[i].left - info->window_bounds.left;
			if (fDirectBuffer.clip_list[j].left < LEFT_WIDTH)
				fDirectBuffer.clip_list[j].left = LEFT_WIDTH;
			fDirectBuffer.clip_list[j].right = info->clip_list[i].right - info->window_bounds.left;
			fDirectBuffer.clip_list[j].bottom = info->clip_list[i].bottom - info->window_bounds.top;

			/* All clipped rectangle that are not empty are recorded in
			   the buffer clipping list. We keep only the 64 first (as
			   a reasonnable approximation of most cases), but the rectangle
			   list could easily be made dynamic if needed. Those clipping
			   rectangle are offset to animation view coordinates */
			if ((fDirectBuffer.clip_list[j].top <= fDirectBuffer.clip_list[j].bottom) &&
				(fDirectBuffer.clip_list[j].left <= fDirectBuffer.clip_list[j].right)) {
				fDirectBuffer.clip_list[j].top -= TOP_LEFT_LIMIT;
				fDirectBuffer.clip_list[j].left -= LEFT_WIDTH;
				fDirectBuffer.clip_list[j].right -= LEFT_WIDTH;
				fDirectBuffer.clip_list[j].bottom -= TOP_LEFT_LIMIT;
				j++;
				if (j == 64)
					break;
			}
		}
		/* record the count of clipping rect in the new clipping list (less
		   or equal to the window clipping list count, as some rectangle can
		   be made invisible by the extra animation view clipping */
		fDirectBuffer.clip_list_count = j;

		/* the bounding box of the clipping list need to be calculated again
		   from scratch. Clipping the bounding box of the window clipping
		   region to the animation view can give us an incorrect (larger)
		   bounding box. Remember that the bounding box of a region is
		   required to be minimal */
		fDirectBuffer.clip_bounds.top = 20000;
		fDirectBuffer.clip_bounds.left = 20000;
		fDirectBuffer.clip_bounds.right = -20000;
		fDirectBuffer.clip_bounds.bottom = -20000;

		for (i=0; i<fDirectBuffer.clip_list_count; i++) {
			if (fDirectBuffer.clip_bounds.top > fDirectBuffer.clip_list[i].top)
				fDirectBuffer.clip_bounds.top = fDirectBuffer.clip_list[i].top;
			if (fDirectBuffer.clip_bounds.left > fDirectBuffer.clip_list[i].left)
				fDirectBuffer.clip_bounds.left = fDirectBuffer.clip_list[i].left;
			if (fDirectBuffer.clip_bounds.right < fDirectBuffer.clip_list[i].right)
				fDirectBuffer.clip_bounds.right = fDirectBuffer.clip_list[i].right;
			if (fDirectBuffer.clip_bounds.bottom < fDirectBuffer.clip_list[i].bottom)
				fDirectBuffer.clip_bounds.bottom = fDirectBuffer.clip_list[i].bottom;
		}

		/* If the bounding box is empty, nothing is visible and all erasing
		   should be canceled */
		if ((fDirectBuffer.clip_bounds.top > fDirectBuffer.clip_bounds.bottom) ||
			(fDirectBuffer.clip_bounds.left > fDirectBuffer.clip_bounds.right)) {
			fStars.erase_count = 0;
			goto nothing_visible;
		}

		if (fCurrentSettings.display == DISPLAY_DIRECT) {
			/* When the direct display mode is used, the geometry changes
			   need to be immediatly applied to the engine. */
			SetGeometry(fDirectBuffer.buffer_width, fDirectBuffer.buffer_height);
			/* if the buffer was reset then
			   we cancel the erasing of the stars for the next frame. */
			if (info->buffer_state & B_BUFFER_RESET) {
				fStars.erase_count = 0;
			}
			/* In the other case, we need to cancel the erasing of star that
			   were drawn at the previous frame, but are no longer visible */
			else if (info->buffer_state & B_CLIPPING_MODIFIED) {
				RefreshClipping(&fDirectBuffer, &fStars);
				RefreshClipping(&fDirectBuffer, &fSpecials);
			}
		}
		break;

	/* stop a direct screen connection */
	case B_DIRECT_STOP :
		/* set the status as not connected */
		fDirectConnected = false;
	nothing_visible:
		/* set an empty clipping */
		fDirectBuffer.clip_list_count = 1;
		fDirectBuffer.clip_bounds.top = 0;
		fDirectBuffer.clip_bounds.left = 0;
		fDirectBuffer.clip_bounds.right = -1;
		fDirectBuffer.clip_bounds.bottom = -1;
		fDirectBuffer.clip_list[0].top = 0;
		fDirectBuffer.clip_list[0].left = 0;
		fDirectBuffer.clip_list[0].right = -1;
		fDirectBuffer.clip_list[0].bottom = -1;
		break;
	}
}


/*! copy a setting into another */
void
ChartWindow::setting::Set(setting *master)
{
	memcpy(this, master, sizeof(setting));
}


/*! Pseudo-random generator increment function.	*/
void
ChartWindow::CrcStep()
{
	fCrcAlea <<= 1;
	if (fCrcAlea < 0)
		fCrcAlea ^= CRC_KEY;
}