/* ChartWindow.cpp by Pierre Raynaud-Richard. Copyright 1998 Be Incorporated, All Rights Reserved. */ #include "ChartWindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(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(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(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(handler); if (slider == NULL) break; fNextSettings.star_density = slider->Value(); break; case REFRESH_RATE_MSG : slider = dynamic_cast(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; ix-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; ix - 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>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= color_count) index = 0; } for (i=0; i= 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<> 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<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 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; icomet.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; inova.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; ibuffer_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; iclip_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[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; }