Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members

celestiacore.cpp

Go to the documentation of this file.
00001 // celestiacore.cpp
00002 // 
00003 // Platform-independent UI handling and initialization for Celestia.
00004 // winmain, gtkmain, and glutmain are thin, platform-specific modules
00005 // that sit directly on top of CelestiaCore and feed it mouse and
00006 // keyboard events.  CelestiaCore then turns those events into calls
00007 // to Renderer and Simulation.
00008 //
00009 // Copyright (C) 2001, Chris Laurel <claurel@shatters.net>
00010 //
00011 // This program is free software; you can redistribute it and/or
00012 // modify it under the terms of the GNU General Public License
00013 // as published by the Free Software Foundation; either version 2
00014 // of the License, or (at your option) any later version.
00015 
00016 #include <cstdio>
00017 #include <iostream>
00018 #include <fstream>
00019 #include <iomanip>
00020 #include <algorithm>
00021 #include <cstdlib>
00022 #include <cctype>
00023 #include <cstring>
00024 #include <cassert>
00025 #include <ctime>
00026 #include <celengine/gl.h>
00027 #include <celmath/vecmath.h>
00028 #include <celmath/quaternion.h>
00029 #include <celmath/mathlib.h>
00030 #include <celutil/util.h>
00031 #include <celutil/filetype.h>
00032 #include <celutil/directory.h>
00033 #include <celutil/formatnum.h>
00034 #include <celengine/astro.h>
00035 #include <celengine/overlay.h>
00036 #include <celengine/console.h>
00037 #include <celengine/execution.h>
00038 #include <celengine/cmdparser.h>
00039 // #include <celengine/solarsysxml.h>
00040 #include <celengine/multitexture.h>
00041 #include "favorites.h"
00042 #include "celestiacore.h"
00043 #include <celutil/debug.h>
00044 #include <celutil/utf8.h>
00045 #include "url.h"
00046 
00047 using namespace std;
00048 
00049 static const int DragThreshold = 3;
00050 
00051 // Perhaps you'll want to put this stuff in configuration file.
00052 static const double timeScaleFactor = 10.0f;
00053 static const double fMinSlewRate = 3.0;
00054 static const double fMaxKeyAccel = 20.0;
00055 static const float fAltitudeThreshold = 4.0f;
00056 static const float RotationBraking = 10.0f;
00057 static const float RotationDecay = 2.0f;
00058 static const double MaximumTimeRate = 1.0e15;
00059 static const float stdFOV = degToRad(45.0f);
00060 static const float MaximumFOV = degToRad(120.0f);
00061 static const float MinimumFOV = degToRad(0.001f);
00062 static float KeyRotationAccel = degToRad(120.0f);
00063 static float MouseRotationSensitivity = degToRad(1.0f);
00064 
00065 static const int ConsolePageRows = 10;
00066 static Console console(200, 120);
00067 
00068 
00069 static void warning(string s)
00070 {
00071     cout << s;
00072 }
00073 
00074 
00075 struct OverlayImage
00076 {
00077     Texture* texture;
00078     int xSize;
00079     int ySize;
00080     int left;
00081     int bottom;
00082 };
00083 
00084 vector<OverlayImage> overlayImages;
00085 
00086 
00087 // Extremely basic implementation of an ExecutionEnvironment for
00088 // running scripts.
00089 class CoreExecutionEnvironment : public ExecutionEnvironment
00090 {
00091 private:
00092     CelestiaCore& core;
00093 
00094 public:
00095     CoreExecutionEnvironment(CelestiaCore& _core) : core(_core)
00096     {
00097     }
00098 
00099     Simulation* getSimulation() const
00100     {
00101         return core.getSimulation();
00102     }
00103 
00104     Renderer* getRenderer() const
00105     {
00106         return core.getRenderer();
00107     }
00108 
00109     CelestiaCore* getCelestiaCore() const
00110     {
00111         return &core;         
00112     }
00113 
00114     void showText(string s, int horig, int vorig, int hoff, int voff,
00115                   double duration)
00116     {
00117         core.showText(s, horig, vorig, hoff, voff, duration);
00118     }
00119 };
00120 
00121 
00122 // If right dragging to rotate, adjust the rotation rate based on the
00123 // distance from the reference object.  This makes right drag rotation
00124 // useful even when the camera is very near the surface of an object.
00125 // Disable adjustments if the reference is a deep sky object, since they
00126 // have no true surface (and the observer is likely to be inside one.)
00127 float ComputeRotationCoarseness(Simulation& sim)
00128 {
00129     float coarseness = 1.5f;
00130 
00131     Selection selection = sim.getActiveObserver()->getFrame().refObject;
00132     if (selection.getType() == Selection::Type_Star ||
00133         selection.getType() == Selection::Type_Body)
00134     {
00135         double radius = selection.radius();
00136         double t = sim.getTime();
00137         UniversalCoord observerPosition = sim.getActiveObserver()->getPosition();
00138         UniversalCoord selectionPosition = selection.getPosition(t);
00139         double distance = astro::microLightYearsToKilometers(observerPosition.distanceTo(selectionPosition));
00140         double altitude = distance - radius;
00141         if (altitude > 0.0 && altitude < radius)                        
00142         {
00143             coarseness *= (float) max(0.01, altitude / radius);
00144         }
00145     }
00146 
00147     return coarseness;
00148 }
00149 
00150 
00151 View::View(View::Type _type,
00152            Observer* _observer,
00153            float _x, float _y,
00154            float _width, float _height) :
00155     type(_type),
00156     observer(_observer),
00157     parent(0),
00158     child1(0),
00159     child2(0),
00160     x(_x),
00161     y(_y),
00162     width(_width),
00163     height(_height),
00164     renderFlags(0),
00165     labelMode(0),
00166     zoom(1),
00167     alternateZoom(1)
00168 {
00169 }
00170 
00171 void View::mapWindowToView(float wx, float wy, float& vx, float& vy) const
00172 {
00173     vx = (wx - x) / width;
00174     vy = (wy + (y + height - 1)) / height;
00175     vx = (vx - 0.5f) * (width / height);
00176     vy = 0.5f - vy;
00177 }
00178 
00179 void View::walkTreeResize(View* sibling, int sign) {
00180    float ratio;
00181    switch (parent->type)
00182     {
00183     case View::HorizontalSplit:
00184         ratio = parent->height / (parent->height -  height);
00185         sibling->height *= ratio;
00186         if (sign == 1)
00187         {
00188             sibling->y = parent->y + (sibling->y - parent->y) * ratio;
00189         }
00190         else
00191         {
00192             sibling->y = parent->y + (sibling->y - (y + height)) * ratio;
00193         }
00194         break;
00195 
00196     case View::VerticalSplit:
00197         ratio = parent->width / (parent->width - width);
00198         sibling->width *= ratio;
00199         if (sign == 1)
00200         {
00201             sibling->x = parent->x + (sibling->x - parent->x) * ratio;
00202         }
00203         else
00204         {
00205             sibling->x = parent->x + (sibling->x - (x + width) ) * ratio;
00206         }
00207         break;
00208     case View::ViewWindow:
00209         break;
00210     }
00211     if (sibling->child1) walkTreeResize(sibling->child1, sign);
00212     if (sibling->child2) walkTreeResize(sibling->child2, sign);
00213 }
00214 
00215 bool View::walkTreeResizeDelta(View* v, float delta, bool check)
00216 {
00217    View *p=v;
00218    int sign = -1;
00219    float ratio;
00220    double newSize;
00221 
00222    if (v->child1)
00223    {
00224        if (!walkTreeResizeDelta(v->child1, delta, check))
00225            return false;
00226    }
00227 
00228    if (v->child2)
00229    {
00230        if (!walkTreeResizeDelta(v->child2, delta, check))
00231            return false;
00232    }
00233 
00234    while ( p != child1 && p != child2 && (p = p->parent) ) ;
00235    if (p == child1) sign = 1;
00236    switch (type)
00237     {
00238     case View::HorizontalSplit:
00239         delta = -delta;
00240         ratio = (p->height  + sign * delta) / p->height;
00241         newSize = v->height * ratio;
00242         if (newSize <= .1) return false;
00243         if (check) return true;
00244         v->height = (float) newSize;
00245         if (sign == 1)
00246         {
00247             v->y = p->y + (v->y - p->y) * ratio;
00248         }
00249         else
00250         {
00251             v->y = p->y + delta + (v->y - p->y) * ratio;
00252         }
00253         break;
00254 
00255     case View::VerticalSplit:
00256         ratio = (p->width + sign * delta) / p->width;
00257         newSize = v->width * ratio;
00258         if (newSize <= .1) return false;
00259         if (check) return true;
00260         v->width = (float) newSize;
00261         if (sign == 1)
00262         {
00263             v->x = p->x + (v->x - p->x) * ratio;
00264         }
00265         else
00266         {
00267             v->x = p->x + delta + (v->x - p->x) * ratio;
00268         }
00269         break;
00270     case View::ViewWindow:
00271         break;
00272     }
00273 
00274     return true;
00275 }
00276 
00277 
00278 CelestiaCore::CelestiaCore() :
00279     config(NULL),
00280     universe(NULL),
00281     favorites(NULL),
00282     destinations(NULL),
00283     sim(NULL),
00284     renderer(NULL),
00285     overlay(NULL),
00286     width(1),
00287     height(1),
00288     font(NULL),
00289     titleFont(NULL),
00290     messageText(""),
00291     messageHOrigin(0),
00292     messageVOrigin(0),
00293     messageHOffset(0),
00294     messageVOffset(0),
00295     messageStart(0.0),
00296     messageDuration(0.0),
00297     typedText(""),
00298     typedTextCompletionIdx(-1),
00299     textEnterMode(KbNormal),
00300     hudDetail(1),
00301     wireframe(false),
00302     editMode(false),
00303     altAzimuthMode(false),
00304     showConsole(false),
00305     lightTravelFlag(false),
00306     flashFrameStart(0.0),
00307     timer(NULL),
00308     runningScript(NULL),
00309     execEnv(NULL),
00310 #ifdef CELX
00311     celxScript(NULL),
00312 #endif // CELX
00313     timeZoneBias(0),
00314     showFPSCounter(false),
00315     nFrames(0),
00316     fps(0.0),
00317     fpsCounterStartTime(0.0),
00318     oldFOV(stdFOV),
00319     mouseMotion(0.0f),
00320     dollyMotion(0.0),
00321     dollyTime(0.0),
00322     zoomMotion(0.0),
00323     zoomTime(0.0),
00324     sysTime(0.0),
00325     currentTime(0.0),
00326     timeScale(1.0),
00327     paused(false),
00328     scriptPaused(false),
00329     joystickRotation(0.0f, 0.0f, 0.0f),
00330     KeyAccel(1.0),
00331     movieCapture(NULL),
00332     recording(false),
00333     contextMenuCallback(NULL),
00334     logoTexture(NULL),
00335     alerter(NULL),
00336     cursorHandler(NULL),
00337     defaultCursorShape(CelestiaCore::CrossCursor),
00338     historyCurrent(0),
00339     activeView(0),
00340     showActiveViewFrame(false),
00341     showViewFrames(true),
00342     resizeSplit(0),
00343     screenDpi(96),
00344     distanceToScreen(400)
00345 {
00346     /* Get a renderer here so it may be queried for capabilities of the
00347        underlying engine even before rendering is enabled. It's initRenderer()
00348        routine will be called much later. */
00349     renderer = new Renderer();
00350     timer = CreateTimer();
00351 
00352     execEnv = new CoreExecutionEnvironment(*this);
00353 
00354     int i;
00355     for (i = 0; i < KeyCount; i++)
00356     {
00357         keysPressed[i] = false;
00358         shiftKeysPressed[i] = false;
00359     }
00360     for (i = 0; i < JoyButtonCount; i++)
00361         joyButtonsPressed[i] = false;
00362 
00363     clog.rdbuf(console.rdbuf());
00364     cerr.rdbuf(console.rdbuf());
00365     console.setWindowHeight(ConsolePageRows);
00366 }
00367 
00368 CelestiaCore::~CelestiaCore()
00369 {
00370     if (movieCapture != NULL)
00371         recordEnd();
00372     delete execEnv;
00373 }
00374 
00375 void CelestiaCore::readFavoritesFile()
00376 {
00377     // Set up favorites list
00378     if (config->favoritesFile != "")
00379     {
00380         ifstream in(config->favoritesFile.c_str(), ios::in);
00381 
00382         if (in.good())
00383         {
00384             favorites = ReadFavoritesList(in);
00385             if (favorites == NULL)
00386             {
00387                 warning(_("Error reading favorites file."));
00388             }
00389         }
00390     }
00391 }
00392 
00393 void CelestiaCore::writeFavoritesFile()
00394 {
00395     if (config->favoritesFile != "")
00396     {
00397         ofstream out(config->favoritesFile.c_str(), ios::out);
00398         if (out.good())
00399         WriteFavoritesList(*favorites, out);
00400     }
00401 }
00402 
00403 void CelestiaCore::activateFavorite(FavoritesEntry& fav)
00404 {
00405     sim->cancelMotion();
00406     sim->setTime(fav.jd);
00407     sim->setObserverPosition(fav.position);
00408     sim->setObserverOrientation(fav.orientation);
00409     sim->setSelection(sim->findObjectFromPath(fav.selectionName));
00410     sim->setFrame(fav.coordSys, sim->getSelection());
00411 }
00412 
00413 void CelestiaCore::addFavorite(string name, string parentFolder, FavoritesList::iterator* iter)
00414 {
00415     FavoritesList::iterator pos;
00416     if(!iter)
00417         pos = favorites->end();
00418     else
00419         pos = *iter;
00420     FavoritesEntry* fav = new FavoritesEntry();
00421     fav->jd = sim->getTime();
00422     fav->position = sim->getObserver().getPosition();
00423     fav->orientation = sim->getObserver().getOrientation();
00424     fav->name = name;
00425     fav->isFolder = false;
00426     fav->parentFolder = parentFolder;
00427     fav->selectionName = sim->getSelection().getName();
00428     fav->coordSys = sim->getFrame().coordSys;
00429     
00430     favorites->insert(pos, fav);
00431 }
00432 
00433 void CelestiaCore::addFavoriteFolder(string name, FavoritesList::iterator* iter)
00434 {
00435     FavoritesList::iterator pos;
00436     if(!iter)
00437         pos = favorites->end();
00438     else
00439         pos = *iter;
00440     FavoritesEntry* fav = new FavoritesEntry();
00441     fav->name = name;
00442     fav->isFolder = true;
00443     
00444     favorites->insert(pos, fav);
00445 }
00446 
00447 FavoritesList* CelestiaCore::getFavorites()
00448 {
00449     return favorites;
00450 }
00451 
00452 
00453 const DestinationList* CelestiaCore::getDestinations()
00454 {
00455     return destinations;
00456 }
00457 
00458 
00459 // Used in the super-secret edit mode
00460 void showSelectionInfo(const Selection& sel)
00461 {
00462     Vec3f axis(0.0f, 1.0, 0.0f);
00463     float angle = 0.0f;
00464 
00465     if (sel.deepsky() != NULL)
00466         sel.deepsky()->getOrientation().getAxisAngle(axis, angle);
00467     else if (sel.body() != NULL)
00468         sel.body()->getOrientation().getAxisAngle(axis, angle);
00469 
00470     cout << sel.getName() << '\n';
00471     cout << _("Orientation: ") << '[' << axis.x << ',' << axis.y << ',' << axis.z << "], " << radToDeg(angle) << '\n';
00472 }
00473 
00474 
00475 void CelestiaCore::cancelScript()
00476 {
00477     if (runningScript != NULL)
00478     {
00479         delete runningScript;
00480         scriptPaused = false;
00481         runningScript = NULL;
00482     }
00483 #ifdef CELX
00484     if (celxScript != NULL)
00485     {
00486         celxScript->cleanup();
00487         if (textEnterMode & KbPassToScript)
00488             setTextEnterMode(textEnterMode & ~KbPassToScript);
00489         delete celxScript;
00490         celxScript = NULL;
00491         scriptPaused = false;
00492     }
00493 #endif
00494 }
00495 
00496 
00497 void CelestiaCore::runScript(CommandSequence* script)
00498 {
00499     cancelScript();
00500     if (runningScript == NULL && script != NULL)
00501         runningScript = new Execution(*script, *execEnv);
00502 }
00503 
00504 
00505 void CelestiaCore::runScript(const string& filename)
00506 {
00507     cancelScript();
00508     ContentType type = DetermineFileType(filename);
00509 
00510     if (type == Content_CelestiaLegacyScript)
00511     {
00512         ifstream scriptfile(filename.c_str());
00513         if (!scriptfile.good())
00514         {
00515             if (alerter != NULL)
00516                 alerter->fatalError(_("Error opening script file."));
00517             else
00518                 flash(_("Error opening script file."));
00519         }
00520         else
00521         {
00522             CommandParser parser(scriptfile);
00523             CommandSequence* script = parser.parse();
00524             if (script == NULL)
00525             {
00526                 const vector<string>* errors = parser.getErrors();
00527                 const char* errorMsg = "";
00528                 if (errors->size() > 0)
00529                     errorMsg = (*errors)[0].c_str();
00530                 if (alerter != NULL)
00531                     alerter->fatalError(errorMsg);
00532                 else
00533                     flash(errorMsg);
00534             }
00535             else
00536             {
00537                 runningScript = new Execution(*script, *execEnv);
00538             }
00539         }
00540     }
00541 #ifdef CELX
00542     else if (type == Content_CelestiaScript)
00543     {
00544         ifstream scriptfile(filename.c_str());
00545         if (!scriptfile.good())
00546         {
00547             char errMsg[1024];
00548             sprintf(errMsg, _("Error opening script '%s'"), filename.c_str());
00549             if (alerter != NULL)
00550                 alerter->fatalError(errMsg);
00551             else
00552                 flash(errMsg);
00553         }
00554 
00555         celxScript = new LuaState();
00556         celxScript->init(this);
00557         int status = celxScript->loadScript(scriptfile, filename);
00558         if (status != 0)
00559         {
00560             string errMsg = celxScript->getErrorMessage();
00561             if (errMsg.empty())
00562                 errMsg = _("Unknown error opening script");
00563             if (alerter != NULL)
00564                 alerter->fatalError(errMsg);
00565             else
00566                 flash(errMsg);
00567 
00568             delete celxScript;
00569             celxScript = NULL;
00570         }
00571         else
00572         {
00573             // Coroutine execution; control may be transferred between the
00574             // script and Celestia's event loop
00575             if (!celxScript->createThread())
00576             {
00577                 const char* errMsg = _("Script coroutine initialization failed");
00578                 if (alerter != NULL)
00579                     alerter->fatalError(errMsg);
00580                 else
00581                     flash(errMsg);
00582                 delete celxScript;
00583                 celxScript = NULL;
00584             }
00585         }
00586     }
00587 #endif
00588     else
00589     {
00590         if (alerter != NULL)
00591             alerter->fatalError(_("Invalid filetype"));
00592         else
00593             flash(_("Invalid filetype"));
00594     }
00595 }
00596 
00597 
00598 bool checkMask(int modifiers, int mask)
00599 {
00600     return (modifiers & mask) == mask;
00601 }
00602 
00603 void CelestiaCore::mouseButtonDown(float x, float y, int button)
00604 {
00605     mouseMotion = 0.0f;
00606 
00607     if (views.size() > 1) {
00608         // To select the clicked into view before a drag.
00609         pickView(x, y);
00610     }
00611     
00612     if (views.size() > 1 && button == LeftButton) // look if click is near a view border
00613     {
00614         View *v1 = 0, *v2 = 0;
00615         for (vector<View*>::iterator i = views.begin(); i != views.end(); i++)
00616         {
00617             View* v = *i;
00618             float vx, vy, vxp, vyp;
00619             vx = ( x / width - v->x ) / v->width;
00620             vy = ( (1 - y / height ) - v->y ) / v->height;
00621             vxp = vx * v->width * width;
00622             vyp = vy * v->height * height;
00623             if ( vx >=0 && vx <= 1 && ( abs(vyp) <= 2 || abs(vyp - v->height * height) <= 2)
00624               || vy >=0 && vy <= 1 && ( abs(vxp) <= 2 || abs(vxp - v->width * width) <= 2)   )
00625             {
00626                 if (v1 == 0)
00627                 {
00628                     v1 = v;
00629                 }
00630                 else
00631                 {
00632                     v2 = v;
00633                     break;
00634                 }
00635             }
00636         }
00637         if (v2 != 0) {
00638              // Look for common ancestor to v1 & v2 = split being draged.
00639              View *p1 = v1, *p2 = v2;
00640              while ( (p1 = p1->parent) )
00641              {
00642                  p2 = v2;
00643                  while ( (p2 = p2->parent) && p1 != p2) ;
00644                  if (p2 != 0) break;
00645              }
00646              if (p2 != 0)
00647              {
00648                  resizeSplit = p1;
00649              }
00650         }
00651     }
00652 
00653 }
00654 
00655 void CelestiaCore::mouseButtonUp(float x, float y, int button)
00656 {
00657 
00658     // Four pixel tolerance for picking
00659     float pickTolerance = sim->getActiveObserver()->getFOV() / height * 4.0f;
00660 
00661     if (resizeSplit)
00662     {
00663         resizeSplit = 0;
00664         return;
00665     }
00666 
00667     // If the mouse hasn't moved much since it was pressed, treat this
00668     // as a selection or context menu event.  Otherwise, assume that the
00669     // mouse was dragged and ignore the event.
00670     if (mouseMotion < DragThreshold)
00671     {
00672         if (button == LeftButton)
00673         {
00674             pickView(x, y);
00675             
00676             float pickX, pickY;
00677             float aspectRatio = ((float) width / (float) height);
00678             views[activeView]->mapWindowToView((float) x / (float) width,
00679                                                (float) y / (float) height,
00680                                                pickX, pickY);
00681             Vec3f pickRay =
00682                 sim->getActiveObserver()->getPickRay(pickX * aspectRatio, pickY);
00683 
00684             Selection oldSel = sim->getSelection();
00685             Selection newSel = sim->pickObject(pickRay, pickTolerance);
00686             addToHistory();
00687             sim->setSelection(newSel);
00688             if (!oldSel.empty() && oldSel == newSel)
00689                 sim->centerSelection();
00690         }
00691         else if (button == RightButton)
00692         {
00693             float pickX, pickY;
00694             float aspectRatio = ((float) width / (float) height);
00695             views[activeView]->mapWindowToView((float) x / (float) width,
00696                                                (float) y / (float) height,
00697                                                pickX, pickY);
00698             Vec3f pickRay =
00699                 sim->getActiveObserver()->getPickRay(pickX * aspectRatio, pickY);
00700 
00701             Selection sel = sim->pickObject(pickRay, pickTolerance);
00702             if (!sel.empty())
00703             {
00704                 if (contextMenuCallback != NULL)
00705                     contextMenuCallback(x, y, sel);
00706             }
00707         }
00708         else if (button == MiddleButton)
00709         {
00710             if (views[activeView]->zoom != 1)
00711             {
00712                 views[activeView]->alternateZoom = views[activeView]->zoom;
00713                 views[activeView]->zoom = 1;
00714             }
00715             else
00716             {
00717                 views[activeView]->zoom = views[activeView]->alternateZoom;
00718             }
00719             setFOVFromZoom();
00720 
00721             // If AutoMag, adapt the faintestMag to the new fov
00722             if((renderer->getRenderFlags() & Renderer::ShowAutoMag) != 0)
00723                 setFaintestAutoMag();
00724         }
00725     }
00726 }
00727 
00728 void CelestiaCore::mouseWheel(float motion, int modifiers)
00729 {
00730     if (motion != 0.0)
00731     {
00732         if ((modifiers & ShiftKey) != 0)
00733         {
00734             zoomTime = currentTime;
00735             zoomMotion = 0.25f * motion;
00736         }
00737         else
00738         {
00739             dollyTime = currentTime;
00740             dollyMotion = 0.25f * motion;
00741         }
00742     }
00743 }
00744 
00748 void CelestiaCore::mouseMove(float x, float y)
00749 {
00750     if (views.size() > 1 && cursorHandler != NULL)
00751     {
00752         View* v1 = 0;
00753         View* v2 = 0;
00754 
00755         for (vector<View*>::iterator i = views.begin(); i != views.end(); i++)
00756         {
00757             View* v = *i;
00758             float vx, vy, vxp, vyp;
00759             vx = (x / width - v->x) / v->width;
00760             vy = ((1 - y / height) - v->y ) / v->height;
00761             vxp = vx * v->width * width;
00762             vyp = vy * v->height * height;
00763 
00764             if (vx >=0 && vx <= 1 && (abs(vyp) <= 2 || abs(vyp - v->height * height) <= 2))
00765             {
00766                 cursorHandler->setCursorShape(CelestiaCore::SizeVerCursor);
00767                 return;
00768             } 
00769             else if (vy >=0 && vy <= 1 && (abs(vxp) <= 2 || abs(vxp - v->width * width) <= 2)) 
00770             {
00771                 cursorHandler->setCursorShape(CelestiaCore::SizeHorCursor);
00772                 return;
00773             } 
00774         }
00775         cursorHandler->setCursorShape(defaultCursorShape);
00776     }
00777     return;
00778 }
00779     
00780 void CelestiaCore::mouseMove(float dx, float dy, int modifiers)
00781 {
00782     if (resizeSplit != 0)
00783     {
00784         switch(resizeSplit->type) {
00785         case View::HorizontalSplit:
00786             if (   resizeSplit->walkTreeResizeDelta(resizeSplit->child1, dy / height, true)
00787                 && resizeSplit->walkTreeResizeDelta(resizeSplit->child2, dy / height, true))
00788             {
00789                 resizeSplit->walkTreeResizeDelta(resizeSplit->child1, dy / height, false);
00790                 resizeSplit->walkTreeResizeDelta(resizeSplit->child2, dy / height, false);
00791             }
00792             break;
00793         case View::VerticalSplit:
00794             if (   resizeSplit->walkTreeResizeDelta(resizeSplit->child1, dx / width, true)
00795                 && resizeSplit->walkTreeResizeDelta(resizeSplit->child2, dx / width, true)
00796             )
00797             {
00798                 resizeSplit->walkTreeResizeDelta(resizeSplit->child1, dx / width, false);
00799                 resizeSplit->walkTreeResizeDelta(resizeSplit->child2, dx / width, false);
00800             }
00801             break;
00802         case View::ViewWindow:
00803             break;
00804         }
00805         setFOVFromZoom();
00806         return;
00807     }
00808     if ((modifiers & (LeftButton | RightButton)) != 0)
00809     {
00810         if (editMode && checkMask(modifiers, LeftButton | ShiftKey | ControlKey))
00811         {
00812             // Rotate the selected object
00813             Selection sel = sim->getSelection();
00814             Quatf q(1);
00815             if (sel.getType() == Selection::Type_DeepSky)
00816                 q = sel.deepsky()->getOrientation();
00817             else if (sel.getType() == Selection::Type_Body)
00818                 q = sel.body()->getOrientation();
00819 
00820             q.yrotate(dx / width);
00821             q.xrotate(dy / height);
00822 
00823             if (sel.getType() == Selection::Type_DeepSky)
00824                 sel.deepsky()->setOrientation(q);
00825             else if (sel.getType() == Selection::Type_Body)
00826                 sel.body()->setOrientation(q);
00827         }
00828         else if (editMode && checkMask(modifiers, RightButton | ShiftKey | ControlKey))
00829         {
00830             // Rotate the selected object about an axis from its center to the
00831             // viewer.
00832             Selection sel = sim->getSelection();
00833             if (sel.deepsky() != NULL)
00834             {
00835                 double t = sim->getTime();
00836                 Vec3d v = sel.getPosition(t) - sim->getObserver().getPosition();
00837                 Vec3f axis((float) v.x, (float) v.y, (float) v.z);
00838                 axis.normalize();
00839 
00840                 Quatf r;
00841                 r.setAxisAngle(axis, dx / width);
00842 
00843                 Quatf q = sel.deepsky()->getOrientation();
00844                 sel.deepsky()->setOrientation(r * q);
00845             }
00846         }
00847         else if (checkMask(modifiers, LeftButton | RightButton) ||
00848                  checkMask(modifiers, LeftButton | ControlKey))
00849         {
00850             // Y-axis controls distance (exponentially), and x-axis motion
00851             // rotates the camera about the view normal.
00852             float amount = dy / height;
00853             sim->changeOrbitDistance(amount * 5);
00854             if (dx * dx > dy * dy)
00855             {
00856                 Observer& observer = sim->getObserver();
00857                 Vec3d v = Vec3d(0, 0, dx * -MouseRotationSensitivity);
00858                 RigidTransform rt = observer.getSituation();
00859                 Quatd dr = 0.5 * (v * rt.rotation);
00860                 rt.rotation += dr;
00861                 rt.rotation.normalize();
00862                 observer.setSituation(rt);
00863             }
00864         }
00865         else if (checkMask(modifiers, LeftButton | ShiftKey))
00866         {
00867             // Mouse zoom control
00868             float amount = dy / height;
00869             float minFOV = MinimumFOV;
00870             float maxFOV = MaximumFOV;
00871             float fov = sim->getActiveObserver()->getFOV();
00872 
00873             if (fov < minFOV)
00874                 fov = minFOV;
00875 
00876             // In order for the zoom to have the right feel, it should be
00877             // exponential.
00878             float newFOV = minFOV + (float) exp(log(fov - minFOV) + amount * 4);
00879             if (newFOV < minFOV)
00880                 newFOV = minFOV;
00881             else if (newFOV > maxFOV)
00882                 newFOV = maxFOV;
00883             sim->getActiveObserver()->setFOV(newFOV);
00884             setZoomFromFOV();
00885 
00886             if ((renderer->getRenderFlags() & Renderer::ShowAutoMag))
00887             {
00888                 setFaintestAutoMag();
00889                 char buf[128];
00890                 sprintf(buf, _("Magnitude limit: %.2f"), sim->getFaintestVisible());
00891                 flash(buf);
00892             }
00893         }
00894         else
00895         {
00896             Quatf q(1);
00897             // For a small field of view, rotate the camera more finely
00898             float coarseness = 1.5f;
00899             if ((modifiers & RightButton) == 0)
00900             {
00901                 coarseness = radToDeg(sim->getActiveObserver()->getFOV()) / 30.0f;
00902             }
00903             else 
00904             {
00905                 // If right dragging to rotate, adjust the rotation rate
00906                 // based on the distance from the reference object.
00907                 coarseness = ComputeRotationCoarseness(*sim);
00908             }
00909             q.yrotate(dx / width * coarseness);
00910             q.xrotate(dy / height * coarseness);
00911             if ((modifiers & RightButton) != 0)
00912                 sim->orbit(q);
00913             else
00914                 sim->rotate(~q);
00915         }
00916 
00917         mouseMotion += abs(dy) + abs(dx);
00918     }
00919 }
00920 
00922 void CelestiaCore::pickView(float x, float y)
00923 {
00924     if (x+2 < views[activeView]->x * width || x-2 > (views[activeView]->x + views[activeView]->width) * width
00925         || (height - y)+2 < views[activeView]->y * height ||  (height - y)-2 > (views[activeView]->y + views[activeView]->height) * height)
00926     {
00927         vector<View*>::iterator i = views.begin();
00928         int n = 0;
00929         while (i < views.end() && (x+2 < (*i)->x * width || x-2 > ((*i)->x + (*i)->width) * width
00930                                     || (height - y)+2 < (*i)->y * height ||  (height - y)-2 > ((*i)->y + (*i)->height) * height))
00931         {
00932                 i++; n++;
00933         }
00934         activeView = n;
00935         sim->setActiveObserver(views[activeView]->observer);
00936         if (!showActiveViewFrame)
00937             flashFrameStart = currentTime;
00938         return;
00939     }
00940 }
00941 
00942 void CelestiaCore::joystickAxis(int axis, float amount)
00943 {
00944     float deadZone = 0.25f;
00945 
00946     if (abs(amount) < deadZone)
00947         amount = 0.0f;
00948     else
00949         amount = (amount - deadZone) * (1.0f / (1.0f - deadZone));
00950 
00951     amount = sign(amount) * square(amount);
00952 
00953     if (axis == Joy_XAxis)
00954         joystickRotation.y = amount;
00955     else if (axis == Joy_YAxis)
00956         joystickRotation.x = -amount;
00957 }
00958 
00959 
00960 void CelestiaCore::joystickButton(int button, bool down)
00961 {
00962     if (button >= 0 && button < JoyButtonCount)
00963         joyButtonsPressed[button] = down;
00964 }
00965 
00966 
00967 static void scrollConsole(Console& con, int lines)
00968 {
00969     int topRow = con.getWindowRow();
00970     int height = con.getHeight();
00971 
00972     if (lines < 0)
00973     {
00974         if (topRow + lines > -height)
00975             console.setWindowRow(topRow + lines);
00976         else
00977             console.setWindowRow(-(height - 1));
00978     }
00979     else
00980     {
00981         if (topRow + lines <= -ConsolePageRows)
00982             console.setWindowRow(topRow + lines);
00983         else
00984             console.setWindowRow(-ConsolePageRows);
00985     }
00986 }
00987 
00988 
00989 void CelestiaCore::keyDown(int key, int modifiers)
00990 {
00991     switch (key)
00992     {
00993     case Key_F1:
00994         sim->setTargetSpeed(0);
00995         break;
00996     case Key_F2:
00997         sim->setTargetSpeed(astro::kilometersToMicroLightYears(1.0f));
00998         break;
00999     case Key_F3:
01000         sim->setTargetSpeed(astro::kilometersToMicroLightYears(1000.0f));
01001         break;
01002     case Key_F4:
01003         sim->setTargetSpeed((float) astro::kilometersToMicroLightYears(astro::speedOfLight));
01004         break;
01005     case Key_F5:
01006         sim->setTargetSpeed((float) astro::kilometersToMicroLightYears(astro::speedOfLight * 10.0));
01007         break;
01008     case Key_F6:
01009         sim->setTargetSpeed(astro::AUtoMicroLightYears(1.0f));
01010         break;
01011     case Key_F7:
01012         sim->setTargetSpeed(1e6);
01013         break;
01014     case Key_F11:
01015         if (movieCapture != NULL)
01016         {
01017             if (isRecording())
01018                 recordPause();
01019             else
01020                 recordBegin();
01021         }
01022         break;
01023     case Key_F12:
01024         if (movieCapture != NULL)
01025             recordEnd();
01026         break;
01027     case Key_NumPad2:
01028     case Key_NumPad4:
01029     case Key_NumPad6:
01030     case Key_NumPad7:
01031     case Key_NumPad8:
01032     case Key_NumPad9:
01033         sim->setTargetSpeed(sim->getTargetSpeed());
01034         break;
01035 
01036     case Key_Down:
01037         if (showConsole)
01038             scrollConsole(console, 1);
01039         break;
01040 
01041     case Key_Up:
01042         if (showConsole)
01043             scrollConsole(console, -1);
01044         break;
01045 
01046     case Key_PageDown:
01047         if (showConsole)
01048             scrollConsole(console, ConsolePageRows);
01049         break;
01050 
01051     case Key_PageUp:
01052         if (showConsole)
01053             scrollConsole(console, -ConsolePageRows);
01054         break;
01055     }
01056 
01057     if (KeyAccel < fMaxKeyAccel)
01058         KeyAccel *= 1.1;
01059 
01060     // Only process alphanumeric keys if we're not in text enter mode
01061     if (islower(key))
01062         key = toupper(key);
01063     if (!(key >= 'A' && key <= 'Z' && (textEnterMode != KbNormal) ))
01064     {
01065         if (modifiers & ShiftKey)
01066             shiftKeysPressed[key] = true;
01067         else
01068             keysPressed[key] = true;
01069     }
01070 }
01071 
01072 void CelestiaCore::keyUp(int key, int)
01073 {
01074     KeyAccel = 1.0;
01075     if (islower(key))
01076         key = toupper(key);
01077     keysPressed[key] = false;
01078     shiftKeysPressed[key] = false;
01079 }
01080 
01081 void CelestiaCore::charEntered(char c, int modifiers)
01082 {
01083     char C[2];
01084     C[0] = c;
01085     C[1] = '\0';
01086     charEntered(C, modifiers);
01087 }
01088 
01089 void CelestiaCore::charEntered(const char *c_p, int modifiers)
01090 {
01091     Observer* observer = sim->getActiveObserver();
01092 
01093     char c = *c_p;
01094 
01095 
01096 #ifdef CELX
01097     if (celxScript != NULL && (textEnterMode & KbPassToScript))
01098     {
01099         if (c != '\033' && celxScript->charEntered(c_p))
01100         {
01101             return;
01102         }
01103     }
01104 #endif
01105 
01106     if (textEnterMode & KbAutoComplete)
01107     {
01108         wchar_t wc = NULL;
01109         UTF8Decode(c_p, 0, strlen(c_p), wc);
01110 #ifdef MACOSX
01111         if ( wc && (isalpha(wc) || isdigit(wc) || ispunct(c) || c == ' ') )
01112 #else
01113         if ( wc && (iswalpha(wc) || iswdigit(wc) || iswpunct(c) || c == ' ') )
01114 #endif
01115         {
01116             typedText += string(c_p);
01117             typedTextCompletion = sim->getObjectCompletion(typedText, (renderer->getLabelMode() & Renderer::LocationLabels) != 0);
01118             typedTextCompletionIdx = -1;
01119 #ifdef AUTO_COMPLETION
01120             if (typedTextCompletion.size() == 1)
01121             {
01122                 string::size_type pos = typedText.rfind('/', typedText.length());
01123                 if (pos != string::npos)
01124                     typedText = typedText.substr(0, pos + 1) + typedTextCompletion[0];
01125                 else
01126                     typedText = typedTextCompletion[0];
01127             }
01128 #endif
01129         }
01130         else if (c == '\b')
01131         {
01132             typedTextCompletionIdx = -1;
01133             if (typedText.size() > 0)
01134             {
01135 #ifdef AUTO_COMPLETION
01136                 do
01137                 {
01138 #endif
01139                     // We remove bytes like b10xxx xxxx at the end of typeText
01140                     // these are guarantied to not be the first byte of a UTF-8 char
01141                     while (typedText.size() && ((typedText[typedText.size() - 1] & 0xC0) == 0x80)) {
01142                         typedText = string(typedText, 0, typedText.size() - 1);                                            
01143                     }
01144                     // We then remove the first byte of the last UTF-8 char of typedText.
01145                     typedText = string(typedText, 0, typedText.size() - 1);
01146                     if (typedText.size() > 0)
01147                     {
01148                         typedTextCompletion = sim->getObjectCompletion(typedText, (renderer->getLabelMode() & Renderer::LocationLabels) != 0);
01149                     } else {
01150                         typedTextCompletion.clear();
01151                     }
01152 #ifdef AUTO_COMPLETION
01153                 } while (typedText.size() > 0 && typedTextCompletion.size() == 1);
01154 #endif
01155             }
01156         }
01157         else if (c == '\011') // TAB
01158         {
01159             if (typedTextCompletionIdx + 1 < (int) typedTextCompletion.size())
01160                 typedTextCompletionIdx++;
01161             else if ((int) typedTextCompletion.size() > 0 && typedTextCompletionIdx + 1 == (int) typedTextCompletion.size())
01162                 typedTextCompletionIdx = 0;
01163             if (typedTextCompletionIdx >= 0) {
01164                 string::size_type pos = typedText.rfind('/', typedText.length());
01165                 if (pos != string::npos)
01166                     typedText = typedText.substr(0, pos + 1) + typedTextCompletion[typedTextCompletionIdx];
01167                 else
01168                     typedText = typedTextCompletion[typedTextCompletionIdx];
01169             }
01170         }
01171         else if (c == Key_BackTab)
01172         {
01173             if (typedTextCompletionIdx > 0)
01174                 typedTextCompletionIdx--;
01175             else if (typedTextCompletionIdx == 0)
01176                 typedTextCompletionIdx = typedTextCompletion.size() - 1;
01177             else if (typedTextCompletion.size() > 0)
01178                 typedTextCompletionIdx = typedTextCompletion.size() - 1;
01179             if (typedTextCompletionIdx >= 0) {
01180                 string::size_type pos = typedText.rfind('/', typedText.length());
01181                 if (pos != string::npos)
01182                     typedText = typedText.substr(0, pos + 1) + typedTextCompletion[typedTextCompletionIdx];
01183                 else
01184                     typedText = typedTextCompletion[typedTextCompletionIdx];
01185             }
01186         }
01187         else if (c == '\033') // ESC
01188         {
01189             setTextEnterMode(textEnterMode & ~KbAutoComplete);
01190         }
01191         else if (c == '\n' || c == '\r')
01192         {
01193             if (typedText != "")
01194             {
01195                 Selection sel = sim->findObjectFromPath(typedText, true);
01196                 if (!sel.empty())
01197                 {
01198                     addToHistory();
01199                     sim->setSelection(sel);
01200                 }
01201                 typedText = "";
01202             }
01203             setTextEnterMode(textEnterMode & ~KbAutoComplete);
01204         }
01205         return;
01206     }
01207 
01208     char C = toupper(c);
01209     switch (C)
01210     {
01211     case '\001': // Ctrl+A
01212         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowAtmospheres);
01213         notifyWatchers(RenderFlagsChanged);
01214         break;
01215 
01216     case '\002': // Ctrl+B
01217         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowBoundaries);
01218         notifyWatchers(RenderFlagsChanged);
01219         break;
01220 
01221     case '\n':
01222     case '\r':
01223         setTextEnterMode(textEnterMode | KbAutoComplete);
01224         break;
01225 
01226     case '\b':
01227         sim->setSelection(sim->getSelection().parent());
01228         break;
01229 
01230     case '\014': // Ctrl+L
01231         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowNightMaps);
01232         notifyWatchers(RenderFlagsChanged);
01233         break;
01234 
01235     case '\013': // Ctrl+K
01236         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowMarkers);
01237         if (renderer->getRenderFlags() & Renderer::ShowMarkers)
01238         {
01239             flash(_("Markers enabled"));
01240         }
01241         else
01242             flash(_("Markers disabled"));
01243         notifyWatchers(RenderFlagsChanged);
01244         break;
01245 
01246     case '\005':  // Ctrl+E
01247         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowEclipseShadows);
01248         notifyWatchers(RenderFlagsChanged);
01249         break;
01250 
01251     case '\007':  // Ctrl+G
01252         flash(_("Goto surface"));
01253         addToHistory();
01254         //if (sim->getFrame().coordSys == astro::Universal)
01255             sim->geosynchronousFollow();
01256         sim->gotoSurface(5.0);
01257         // sim->gotoSelection(0.0001, Vec3f(0, 1, 0), astro::ObserverLocal);
01258         break;
01259 
01260     case '\006': // Ctrl+F
01261         flash(_("Alt-azimuth mode"));
01262         addToHistory();
01263         altAzimuthMode = !altAzimuthMode;
01264         break;
01265 
01266     case 127: // Delete
01267         deleteView();
01268         break;
01269 
01270     case '\011': // TAB
01271         activeView++;
01272         if (activeView >= (int) views.size())
01273             activeView = 0;
01274         sim->setActiveObserver(views[activeView]->observer);
01275         if (!showActiveViewFrame)
01276             flashFrameStart = currentTime;
01277         break;
01278 
01279     case '\020':  // Ctrl+P
01280         if (!sim->getSelection().empty())
01281         {
01282             Selection sel = sim->getSelection();
01283             if (sim->getUniverse()->isMarked(sel, 1))
01284             {
01285                 sim->getUniverse()->unmarkObject(sel, 1);
01286             }
01287             else
01288             {
01289                 sim->getUniverse()->markObject(sel,
01290                                                10.0f,
01291                                                Color(0.0f, 1.0f, 0.0f, 0.9f),
01292                                                Marker::Diamond,
01293                                                1);
01294             }
01295         }
01296         break;
01297 
01298     case '\025': // Ctrl+U
01299         splitView(View::VerticalSplit);
01300         break;
01301 
01302     case '\022': // Ctrl+R
01303         splitView(View::HorizontalSplit);
01304         break;
01305 
01306     case '\004': // Ctrl+D
01307         singleView();
01308         break;
01309 
01310     case '\023':  // Ctrl+S
01311         renderer->setStarStyle((Renderer::StarStyle) (((int) renderer->getStarStyle() + 1) %
01312                                                       (int) Renderer::StarStyleCount));
01313         switch (renderer->getStarStyle())
01314         {
01315         case Renderer::FuzzyPointStars:
01316             flash(_("Star style: fuzzy points"));
01317             break;
01318         case Renderer::PointStars:
01319             flash(_("Star style: points"));
01320             break;
01321         case Renderer::ScaledDiscStars:
01322             flash(_("Star style: scaled discs"));
01323             break;
01324         default:
01325             break;
01326         }
01327 
01328         notifyWatchers(RenderFlagsChanged);
01329         break;
01330 
01331     case '\024':  // Ctrl+T
01332         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowCometTails);
01333         if (renderer->getRenderFlags() & Renderer::ShowCometTails)
01334         {
01335             flash(_("Comet tails enabled"));
01336         }
01337         else
01338             flash(_("Comet tails disabled"));
01339         notifyWatchers(RenderFlagsChanged);
01340         break;
01341 
01342     case '\026':  // Ctrl+V
01343         {
01344             GLContext* context = renderer->getGLContext();
01345             GLContext::GLRenderPath path = context->getRenderPath();
01346             GLContext::GLRenderPath newPath = context->nextRenderPath();
01347 
01348             if (newPath != path)
01349             {
01350                 switch (newPath)
01351                 {
01352                 case GLContext::GLPath_Basic:
01353                     flash(_("Render path: Basic"));
01354                     break;
01355                 case GLContext::GLPath_Multitexture:
01356                     flash(_("Render path: Multitexture"));
01357                     break;
01358                 case GLContext::GLPath_NvCombiner:
01359                     flash(_("Render path: NVIDIA combiners"));
01360                     break;
01361                 case GLContext::GLPath_DOT3_ARBVP:
01362                     flash(_("Render path: OpenGL vertex program"));
01363                     break;
01364                 case GLContext::GLPath_NvCombiner_NvVP:
01365                     flash(_("Render path: NVIDIA vertex program and combiners"));
01366                     break;
01367                 case GLContext::GLPath_NvCombiner_ARBVP:
01368                     flash(_("Render path: OpenGL vertex program/NVIDIA combiners"));
01369                     break;
01370                 case GLContext::GLPath_ARBFP_ARBVP:
01371                     flash(_("Render path: OpenGL 1.5 vertex/fragment program"));
01372                     break;
01373                 case GLContext::GLPath_NV30:
01374                     flash(_("Render path: NVIDIA GeForce FX"));
01375                     break;
01376                 case GLContext::GLPath_GLSL:
01377                     flash(_("Render path: OpenGL 2.0"));
01378                     break;
01379                 }
01380                 context->setRenderPath(newPath);
01381                 notifyWatchers(RenderFlagsChanged);
01382             }
01383         }
01384         break;
01385 
01386     case '\027':  // Ctrl+W
01387         wireframe = !wireframe;
01388         renderer->setRenderMode(wireframe ? GL_LINE : GL_FILL);
01389         break;
01390 
01391     case '\030':  // Ctrl+X
01392         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowSmoothLines);
01393         notifyWatchers(RenderFlagsChanged);
01394         break;
01395 
01396     case '\031':  // Ctrl+Y
01397         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowAutoMag);
01398         if (renderer->getRenderFlags() & Renderer::ShowAutoMag)
01399         {
01400             flash(_("Auto-magnitude enabled"));
01401             setFaintestAutoMag();
01402         }
01403         else
01404             flash(_("Auto-magnitude disabled"));
01405         notifyWatchers(RenderFlagsChanged);
01406         break;
01407 
01408 
01409     case '\033': // Escape
01410         cancelScript();
01411         addToHistory();
01412         if (textEnterMode != KbNormal)
01413         {
01414             setTextEnterMode(KbNormal);
01415         }
01416         else
01417         {
01418             if (sim->getObserverMode() == Observer::Travelling)
01419                 sim->setObserverMode(Observer::Free);
01420             else
01421                 sim->setFrame(astro::Universal, Selection());
01422             if (!sim->getTrackedObject().empty())
01423                 sim->setTrackedObject(Selection());
01424         }
01425         flash(_("Cancel"));
01426         break;
01427 
01428     case ' ':
01429         if (paused)
01430         {
01431             sim->setTimeScale(timeScale);
01432             scriptPaused = false;
01433             paused = false;
01434         }
01435         else
01436         {
01437             sim->setTimeScale(0.0);
01438             paused = true;
01439 
01440             // If there's a script running then pause it.  This has the
01441             // potentially confusing side effect of rendering nonfunctional
01442             // goto, center, and other movement commands.
01443 #ifdef CELX
01444             if (runningScript != NULL || celxScript != NULL)
01445 #else
01446             if (runningScript != NULL)
01447 #endif
01448                 scriptPaused = true;
01449             else
01450                 scriptPaused = false;
01451         }
01452 
01453         if (paused)
01454         {
01455             if (scriptPaused)
01456                 flash(_("Time and script are paused"));
01457             else
01458                 flash(_("Time is paused"));
01459         }
01460         else
01461         {
01462             flash(_("Resume"));
01463         }
01464         break;
01465 
01466     case '!':
01467         if (editMode)
01468             showSelectionInfo(sim->getSelection());
01469         else
01470         {
01471             double t = (double) time(NULL) / 86400.0 +
01472                 (double) astro::Date(1970, 1, 1);
01473             sim->setTime(t);
01474         }
01475         break;
01476 
01477     case '%':
01478         {
01479             const ColorTemperatureTable* current =
01480                 renderer->getStarColorTable();
01481             if (current == GetStarColorTable(ColorTable_Enhanced))
01482             {
01483                 renderer->setStarColorTable(GetStarColorTable(ColorTable_Blackbody_D65));
01484             }
01485             else if (current == GetStarColorTable(ColorTable_Blackbody_D65))
01486             {
01487                 renderer->setStarColorTable(GetStarColorTable(ColorTable_Enhanced));
01488             }
01489             else
01490             {
01491                 // Unknown color table
01492             }
01493         }
01494         break;
01495 
01496     case '^':
01497         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowNebulae);
01498         notifyWatchers(RenderFlagsChanged);
01499         break;
01500 
01501 
01502     case '&':
01503         renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::LocationLabels);
01504         notifyWatchers(LabelFlagsChanged);
01505         break;
01506 
01507     case '*':
01508         addToHistory();
01509         sim->reverseObserverOrientation();
01510         break;
01511 
01512     case '?':
01513         addToHistory();
01514         if (!sim->getSelection().empty())
01515         {
01516             Vec3d v = sim->getSelection().getPosition(sim->getTime()) -
01517             sim->getObserver().getPosition();
01518             int hours, mins;
01519             float secs;
01520             char buf[128];
01521             if (astro::microLightYearsToKilometers(v.length()) >= 
01522                 86400.0 * astro::speedOfLight) 
01523             {
01524                 // Light travel time in years, if >= 1day
01525                 sprintf(buf, _("Light travel time:  %.4f yr "), 
01526                         v.length() * 1.0e-6);
01527                 flash(buf, 2.0);
01528             } 
01529             else
01530             {
01531                 // If Light travel delay < 1 day, display in [ hr : min : sec ]
01532                 getLightTravelDelay(v.length(), hours, mins, secs);
01533                 if (hours == 0)
01534                     sprintf(buf, _("Light travel time:  %d min  %.1f s"), 
01535                             mins, secs);
01536                 else
01537                     sprintf(buf, _("Light travel time:  %d h  %d min  %.1f s")
01538                             ,hours, mins, secs);
01539                 flash(buf, 2.0);
01540             }
01541         }
01542         break;
01543 
01544     case '-': 
01545         addToHistory();
01546 
01547         if (sim->getSelection().body() &&
01548             (sim->getTargetSpeed() < 0.99 *
01549             astro::kilometersToMicroLightYears(astro::speedOfLight)))
01550         {
01551             Vec3d v = sim->getSelection().getPosition(sim->getTime()) -
01552                       sim->getObserver().getPosition();
01553             lightTravelFlag = !lightTravelFlag;
01554             if (lightTravelFlag)
01555             {
01556                 flash(_("Light travel delay included"),2.0);
01557                 setLightTravelDelay(v.length());
01558             }
01559             else
01560             {
01561                 flash(_("Light travel delay switched off"),2.0);
01562                 setLightTravelDelay(-v.length());
01563             }
01564         }
01565         else
01566         {
01567             flash(_("Light travel delay ignored"));
01568         }
01569         break;
01570 
01571     case ',':
01572         addToHistory();
01573         if (observer->getFOV() > MinimumFOV)
01574         {
01575             observer->setFOV(observer->getFOV() / 1.05f);
01576             setZoomFromFOV();
01577             if((renderer->getRenderFlags() & Renderer::ShowAutoMag))
01578             {
01579                 setFaintestAutoMag();
01580                 char buf[128];
01581                 sprintf(buf, _("Magnitude limit: %.2f"), sim->getFaintestVisible());
01582                 flash(buf);
01583             }
01584         }
01585         break;
01586 
01587     case '.':
01588         addToHistory();
01589         if (observer->getFOV() < MaximumFOV)
01590         {
01591             observer->setFOV(observer->getFOV() * 1.05f);
01592             setZoomFromFOV();
01593             if((renderer->getRenderFlags() & Renderer::ShowAutoMag) != 0)
01594             {
01595                 setFaintestAutoMag();
01596                 char buf[128];
01597                 sprintf(buf, _("Magnitude limit: %.2f"), sim->getFaintestVisible());
01598                 flash(buf);
01599             }
01600         }
01601         break;
01602 
01603     case '+':
01604         addToHistory();
01605         if (observer->getDisplayedSurface() != "")
01606         {
01607             observer->setDisplayedSurface("");
01608             flash(_("Using normal surface textures."));
01609         }
01610         else
01611         {
01612             observer->setDisplayedSurface(_("limit of knowledge"));
01613             flash(_("Using limit of knowledge surface textures."));
01614         }
01615         break;
01616 
01617     case '/':
01618         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowDiagrams);
01619         notifyWatchers(RenderFlagsChanged);
01620         break;
01621 
01622     case '0':
01623         addToHistory();
01624         sim->selectPlanet(-1);
01625         break;
01626 
01627     case '1':
01628     case '2':
01629     case '3':
01630     case '4':
01631     case '5':
01632     case '6':
01633     case '7':
01634     case '8':
01635     case '9':
01636         addToHistory();
01637         sim->selectPlanet(c - '1');
01638         break;
01639 
01640     case ';':
01641         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowCelestialSphere);
01642         notifyWatchers(RenderFlagsChanged);
01643         break;
01644 
01645     case '=':
01646         renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::ConstellationLabels);
01647         notifyWatchers(LabelFlagsChanged);
01648         break;
01649 
01650     case 'B':
01651         renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::StarLabels);
01652         notifyWatchers(LabelFlagsChanged);
01653         break;
01654 
01655     case 'C':
01656         addToHistory();
01657         if (c == 'c')
01658             sim->centerSelection();
01659         else
01660             sim->centerSelectionCO();
01661         break;
01662 
01663     case 'D':
01664        addToHistory();
01665        if (config->demoScriptFile != "")
01666            runScript(config->demoScriptFile);
01667         break;
01668 
01669     case 'E':
01670         renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::GalaxyLabels);
01671         notifyWatchers(LabelFlagsChanged);
01672         break;
01673 
01674     case 'F':
01675         addToHistory();
01676         flash(_("Follow"));
01677         sim->follow();
01678         break;
01679 
01680     case 'G':
01681         addToHistory();
01682         if (sim->getFrame().coordSys == astro::Universal)
01683             sim->follow();
01684         sim->gotoSelection(5.0, Vec3f(0, 1, 0), astro::ObserverLocal);
01685         break;
01686 
01687     case 'H':
01688         addToHistory();
01689         sim->setSelection(sim->getUniverse()->getStarCatalog()->find(0));
01690         break;
01691 
01692     case 'I':
01693         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowCloudMaps);
01694         notifyWatchers(RenderFlagsChanged);
01695         break;
01696 
01697     case 'J':
01698         addToHistory();
01699         timeScale = -timeScale;
01700         if (!paused)
01701             sim->setTimeScale(timeScale);
01702         if (timeScale >= 0)
01703             flash(_("Time: Forward"));
01704         else
01705             flash(_("Time: Backward"));
01706         break;
01707 
01708     case 'K':
01709         addToHistory();
01710         {
01711             timeScale /= timeScaleFactor;
01712             if (!paused)
01713                 sim->setTimeScale(timeScale);
01714             char buf[128];
01715             sprintf(buf, _("Time rate: %.1f"), timeScale);
01716             flash(buf);
01717         }
01718         break;
01719 
01720     case 'L':
01721         addToHistory();
01722         if (timeScale < MaximumTimeRate)
01723         {
01724             timeScale *= timeScaleFactor;
01725             if (!paused)
01726                 sim->setTimeScale(timeScale);
01727             char buf[128];
01728             sprintf(buf, _("Time rate: %.1f"), timeScale);
01729             flash(buf);
01730         }
01731         break;
01732 
01733     case 'M':
01734         renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::MoonLabels);
01735         notifyWatchers(LabelFlagsChanged);
01736         break;
01737 
01738     case 'N':
01739         renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::SpacecraftLabels);
01740         notifyWatchers(LabelFlagsChanged);
01741         break;
01742 
01743     case 'O':
01744         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowOrbits);
01745         notifyWatchers(RenderFlagsChanged);
01746         break;
01747 
01748     case 'P':
01749         renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::PlanetLabels);
01750         notifyWatchers(LabelFlagsChanged);
01751         break;
01752 
01753     case 'Q':
01754         sim->setTargetSpeed(-sim->getTargetSpeed());
01755         break;
01756 
01757     case 'R':
01758         if (c == 'r') // Skip rangechecking as setResolution does it already
01759             renderer->setResolution(renderer->getResolution() - 1);
01760         else
01761             renderer->setResolution(renderer->getResolution() + 1);
01762         switch (renderer->getResolution())
01763         {
01764         case 0:
01765             flash(_("Low res textures"));
01766             break;
01767         case 1:
01768             flash(_("Medium res textures"));
01769             break;
01770         case 2:
01771             flash(_("High res textures"));
01772             break;
01773         }
01774         break;
01775 
01776     case 'S':
01777         sim->setTargetSpeed(0);
01778         break;
01779 
01780     case 'T':
01781         addToHistory();
01782         if (sim->getTrackedObject().empty())
01783             sim->setTrackedObject(sim->getSelection());
01784         else
01785             sim->setTrackedObject(Selection());
01786         break;
01787 
01788     case 'U':
01789         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowGalaxies);
01790         notifyWatchers(RenderFlagsChanged);
01791         break;
01792 
01793     case 'V':
01794         setHudDetail((getHudDetail() + 1) % 3);
01795         break;
01796 
01797     case 'W':
01798         if (c == 'w')
01799             renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::AsteroidLabels);
01800         else
01801             renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::CometLabels);
01802         notifyWatchers(LabelFlagsChanged);
01803         break;
01804 
01805     case 'X':
01806         sim->setTargetSpeed(sim->getTargetSpeed());
01807         break;
01808 
01809     case 'Y':
01810         flash(_("Sync Orbit"));
01811         addToHistory();
01812         sim->geosynchronousFollow();
01813         break;
01814 
01815     case ':':
01816         flash(_("Lock"));
01817         addToHistory();
01818         sim->phaseLock();
01819         break;
01820 
01821     case '"':
01822         flash(_("Chase"));
01823         addToHistory();
01824         sim->chase();
01825         break;
01826 
01827 
01828 
01829     case '[':
01830         if ((renderer->getRenderFlags() & Renderer::ShowAutoMag) == 0)
01831         {
01832             if (sim->getFaintestVisible() > 1.0f)
01833             {
01834                 setFaintest(sim->getFaintestVisible() - 0.2f);
01835                 notifyWatchers(FaintestChanged);
01836                 char buf[128];
01837                 sprintf(buf, _("Magnitude limit: %.2f"),sim->getFaintestVisible());
01838                 flash(buf);
01839             }
01840         }
01841         else if (renderer->getFaintestAM45deg() > 6.0f)
01842         {
01843             renderer->setFaintestAM45deg(renderer->getFaintestAM45deg() - 0.1f);
01844             setFaintestAutoMag();
01845             char buf[128];
01846             sprintf(buf, _("Auto magnitude limit at 45 degrees:  %.2f"),renderer->getFaintestAM45deg());
01847             flash(buf);
01848         }
01849         break;
01850 
01851     case '\\':
01852         addToHistory();
01853         timeScale = 1.0f;
01854         if (!paused)
01855             sim->setTimeScale(timeScale);
01856         break;
01857 
01858     case ']':
01859         if((renderer->getRenderFlags() & Renderer::ShowAutoMag) == 0)
01860         {
01861             if (sim->getFaintestVisible() < 15.0f)
01862             {
01863                 setFaintest(sim->getFaintestVisible() + 0.2f);
01864                 notifyWatchers(FaintestChanged);
01865                 char buf[128];
01866                 sprintf(buf, _("Magnitude limit: %.2f"),sim->getFaintestVisible());
01867                 flash(buf);
01868             }
01869         }
01870         else if (renderer->getFaintestAM45deg() < 12.0f)
01871         {
01872             renderer->setFaintestAM45deg(renderer->getFaintestAM45deg() + 0.1f);
01873             setFaintestAutoMag();
01874             char buf[128];
01875             sprintf(buf, _("Auto magnitude limit at 45 degrees:  %.2f"),renderer->getFaintestAM45deg());
01876             flash(buf);
01877         }
01878         break;
01879 
01880     case '`':
01881         showFPSCounter = !showFPSCounter;
01882         break;
01883 
01884     case '{':
01885         if (renderer->getAmbientLightLevel() > 0.05f)
01886             renderer->setAmbientLightLevel(renderer->getAmbientLightLevel() - 0.05f);
01887         else
01888             renderer->setAmbientLightLevel(0.0f);
01889         notifyWatchers(AmbientLightChanged);
01890         break;
01891 
01892     case '}':
01893         if (renderer->getAmbientLightLevel() < 0.95f)
01894             renderer->setAmbientLightLevel(renderer->getAmbientLightLevel() + 0.05f);
01895         else
01896             renderer->setAmbientLightLevel(1.0f);
01897         notifyWatchers(AmbientLightChanged);
01898         break;
01899 
01900     case '(':
01901         {
01902             char buf[128];
01903             Galaxy::decreaseLightGain();
01904             sprintf(buf, "%s:  %3.2f %%", _("Light gain"), Galaxy::getLightGain() * 100.0f);
01905             flash(buf);
01906             notifyWatchers(GalaxyLightGainChanged);
01907         }
01908         break;
01909 
01910     case ')':
01911         {
01912             char buf[128];
01913             Galaxy::increaseLightGain();
01914             sprintf(buf, "%s:  %3.2f %%", _("Light gain"), Galaxy::getLightGain() * 100.0f);
01915             flash(buf);
01916             notifyWatchers(GalaxyLightGainChanged);
01917         }
01918         break;
01919 
01920     case '~':
01921         showConsole = !showConsole;
01922         break;
01923 
01924     case '@':
01925         editMode = !editMode;
01926         break;
01927     }
01928 }
01929 
01930 
01931 void CelestiaCore::getLightTravelDelay(double distance, int& hours, int& mins, 
01932                                     float& secs)
01933 {
01934     // light travel time in hours
01935     double lt = astro::microLightYearsToKilometers(distance)/
01936                 (3600.0 * astro::speedOfLight);
01937     hours = (int) lt;
01938     double mm    = (lt - hours) * 60;  
01939     mins = (int) mm;
01940     secs = (float) ((mm  - mins) * 60);
01941 } 
01942 
01943 void CelestiaCore::setLightTravelDelay(double distance)
01944 {
01945     // light travel time in days
01946     double lt = astro::microLightYearsToKilometers(distance)/
01947                 (86400.0 * astro::speedOfLight);
01948     sim->setTime(sim->getTime() - lt);    
01949 }
01950 
01951 void CelestiaCore::start(double t)
01952 {
01953     if (config->initScriptFile != "")
01954     {
01955         // using the KdeAlerter in runScript would create an infinite loop, 
01956         // break it here by resetting config->initScriptFile:
01957         string filename = config->initScriptFile;
01958         config->initScriptFile = "";
01959         runScript(filename);
01960     }
01961 
01962     // Set the simulation starting time to the current system time
01963     sim->setTime(t);
01964     sim->update(0.0);
01965 
01966     sysTime = timer->getTime();
01967 
01968     if (startURL != "")
01969         goToUrl(startURL);
01970 }
01971 
01972 void CelestiaCore::setStartURL(string url) 
01973 {
01974     if (!url.substr(0,4).compare("cel:")) 
01975     {
01976         startURL = url;
01977         config->initScriptFile = "";
01978     } 
01979     else 
01980     {
01981         config->initScriptFile = url;
01982     }
01983 }
01984 
01985 
01986 void CelestiaCore::tick()
01987 {
01988     double lastTime = sysTime;
01989     sysTime = timer->getTime();
01990 
01991     // The time step is normally driven by the system clock; however, when
01992     // recording a movie, we fix the time step the frame rate of the movie.
01993     double dt = 0.0;
01994     if (movieCapture != NULL && recording)
01995     {
01996         dt = 1.0 / movieCapture->getFrameRate();
01997     }
01998     else
01999     {
02000         dt = sysTime - lastTime;
02001     }
02002 
02003     // Pause script execution
02004     if (scriptPaused)
02005         dt = 0.0;
02006 
02007     currentTime += dt;
02008 
02009     // Mouse wheel zoom
02010     if (zoomMotion != 0.0f)
02011     {
02012         double span = 0.1;
02013         double fraction;
02014                 
02015         if (currentTime - zoomTime >= span)
02016             fraction = (zoomTime + span) - (currentTime - dt);
02017         else
02018             fraction = dt / span;
02019 
02020         // sim->changeOrbitDistance(zoomMotion * (float) fraction);
02021         if (currentTime - zoomTime >= span)
02022             zoomMotion = 0.0f;
02023     }
02024 
02025     // Mouse wheel dolly
02026     if (dollyMotion != 0.0)
02027     {
02028         double span = 0.1;
02029         double fraction;
02030                 
02031         if (currentTime - dollyTime >= span)
02032             fraction = (dollyTime + span) - (currentTime - dt);
02033         else
02034             fraction = dt / span;
02035 
02036         sim->changeOrbitDistance((float) (dollyMotion * fraction));
02037         if (currentTime - dollyTime >= span)
02038             dollyMotion = 0.0f;
02039     }
02040 
02041     // Keyboard dolly
02042     if (keysPressed[Key_Home])
02043         sim->changeOrbitDistance((float) (-dt * 2));
02044     if (keysPressed[Key_End])
02045         sim->changeOrbitDistance((float) (dt * 2));
02046 
02047     // Keyboard rotate
02048     Vec3f av = sim->getObserver().getAngularVelocity();
02049 
02050     av = av * (float) exp(-dt * RotationDecay);
02051 
02052     float fov = sim->getActiveObserver()->getFOV() / stdFOV;
02053     FrameOfReference frame = sim->getFrame();
02054 
02055     // Handle arrow keys; disable them when the log console is displayed,
02056     // because then they're used to scroll up and down.
02057     if (!showConsole)
02058     {
02059         if (!altAzimuthMode)
02060         {
02061             if (keysPressed[Key_Left])
02062                 av += Vec3f(0.0f, 0.0f, (float) (dt * -KeyRotationAccel));
02063             if (keysPressed[Key_Right])
02064                 av += Vec3f(0.0f, 0.0f, (float) (dt * KeyRotationAccel));
02065             if (keysPressed[Key_Down])
02066                 av += Vec3f((float) (dt * fov * -KeyRotationAccel), 0.0f, 0.0f);
02067             if (keysPressed[Key_Up])
02068                 av += Vec3f((float) (dt * fov * KeyRotationAccel), 0.0f, 0.0f);
02069         }
02070         else
02071         {
02072             if (!frame.refObject.empty())
02073             {
02074                 Quatf orientation = sim->getObserver().getOrientation();
02075                 Vec3d upd = sim->getObserver().getPosition() -
02076                     frame.refObject.getPosition(sim->getTime());
02077                 upd.normalize();
02078                 
02079                 Vec3f up((float) upd.x, (float) upd.y, (float) upd.z);
02080                 
02081                 Vec3f v = up * (float) (KeyRotationAccel * dt);
02082                 v = v * (~orientation).toMatrix3();
02083 
02084                 if (keysPressed[Key_Left])
02085                     av -= v;
02086                 if (keysPressed[Key_Right])
02087                     av += v;
02088                 if (keysPressed[Key_Down])
02089                     av += Vec3f((float) (dt * fov * -KeyRotationAccel), 0.0f, 0.0f);
02090                 if (keysPressed[Key_Up])
02091                     av += Vec3f((float) (dt * fov * KeyRotationAccel), 0.0f, 0.0f);
02092             }
02093         }
02094     }
02095 
02096     if (keysPressed[Key_NumPad4])
02097         av += Vec3f(0.0f, (float) (dt * fov * -KeyRotationAccel), 0.0f);
02098     if (keysPressed[Key_NumPad6])
02099         av += Vec3f(0.0f, (float) (dt * fov * KeyRotationAccel), 0.0f);
02100     if (keysPressed[Key_NumPad2])
02101         av += Vec3f((float) (dt * fov * -KeyRotationAccel), 0.0f, 0.0f);
02102     if (keysPressed[Key_NumPad8])
02103         av += Vec3f((float) (dt * fov * KeyRotationAccel), 0.0f, 0.0f);
02104     if (keysPressed[Key_NumPad7] || joyButtonsPressed[JoyButton7])
02105         av += Vec3f(0.0f, 0.0f, (float) (dt * -KeyRotationAccel));
02106     if (keysPressed[Key_NumPad9] || joyButtonsPressed[JoyButton8])
02107         av += Vec3f(0.0f, 0.0f, (float) (dt * KeyRotationAccel));
02108 
02109     //Use Boolean to indicate if sim->setTargetSpeed() is called
02110     bool bSetTargetSpeed = false;
02111     if (joystickRotation != Vec3f(0.0f, 0.0f, 0.0f))
02112     {
02113         bSetTargetSpeed = true;
02114 
02115         av += (float) (dt * KeyRotationAccel) * joystickRotation;
02116         sim->setTargetSpeed(sim->getTargetSpeed());
02117     }
02118 
02119     if (keysPressed[Key_NumPad5])
02120         av = av * (float) exp(-dt * RotationBraking);
02121 
02122     sim->getObserver().setAngularVelocity(av);
02123 
02124     if (keysPressed[(int)'A'] || joyButtonsPressed[JoyButton2])
02125     {
02126         bSetTargetSpeed = true;
02127 
02128         if (sim->getTargetSpeed() == 0.0f)
02129             sim->setTargetSpeed(astro::kilometersToMicroLightYears(0.1f));
02130         else
02131             sim->setTargetSpeed(sim->getTargetSpeed() * (float) exp(dt * 3));
02132     }
02133     if (keysPressed[(int)'Z'] || joyButtonsPressed[JoyButton1])
02134     {
02135         bSetTargetSpeed = true;
02136 
02137         sim->setTargetSpeed(sim->getTargetSpeed() / (float) exp(dt * 3));
02138     }
02139     if (!bSetTargetSpeed && av.length() > 0.0f)
02140     {
02141         //Force observer velocity vector to align with observer direction if an observer
02142         //angular velocity still exists.
02143         sim->setTargetSpeed(sim->getTargetSpeed());
02144     }
02145 
02146     if (!frame.refObject.empty())
02147     {
02148         Quatf q(1.0f);
02149         float coarseness = ComputeRotationCoarseness(*sim);
02150 
02151         if (shiftKeysPressed[Key_Left])
02152             q = q * Quatf::yrotation((float) (dt * -KeyRotationAccel * coarseness));
02153         if (shiftKeysPressed[Key_Right])
02154             q = q * Quatf::yrotation((float) (dt *  KeyRotationAccel * coarseness));
02155         if (shiftKeysPressed[Key_Up])
02156             q = q * Quatf::xrotation((float) (dt * -KeyRotationAccel * coarseness));
02157         if (shiftKeysPressed[Key_Down])
02158             q = q * Quatf::xrotation((float) (dt *  KeyRotationAccel * coarseness));
02159         sim->orbit(q);
02160     }
02161 
02162     // If there's a script running, tick it
02163     if (runningScript != NULL)
02164     {
02165         bool finished = runningScript->tick(dt);
02166         if (finished)
02167             cancelScript();
02168     }
02169 
02170 #ifdef CELX
02171     if (celxScript != NULL)
02172     {
02173         bool finished = celxScript->tick(dt);
02174         if (finished)
02175             cancelScript();
02176     }
02177 #endif // CELX
02178 
02179     sim->update(dt);
02180 }
02181 
02182 
02183 void CelestiaCore::draw()
02184 {
02185     if (views.size() == 1)
02186     {
02187         // I'm not certain that a special case for one view is required; but,
02188         // it's possible that there exists some broken hardware out there
02189         // that has to fall back to software rendering if the scissor test
02190         // is enable.  To keep performance on this hypothetical hardware
02191         // reasonable in the typical single view case, we'll use this
02192         // scissorless special case.  I'm only paranoid because I've been
02193         // burned by crap hardware so many times. cjl
02194         glViewport(0, 0, width, height);
02195         renderer->resize(width, height);
02196         sim->render(*renderer);
02197     }
02198     else
02199     {
02200         glEnable(GL_SCISSOR_TEST);
02201         for (vector<View*>::iterator iter = views.begin();
02202              iter != views.end(); iter++)
02203         {
02204             View* view = *iter;
02205 
02206             glScissor((GLint) (view->x * width),
02207                       (GLint) (view->y * height),
02208                       (GLsizei) (view->width * width),
02209                       (GLsizei) (view->height * height));
02210             glViewport((GLint) (view->x * width),
02211                        (GLint) (view->y * height),
02212                        (GLsizei) (view->width * width),
02213                        (GLsizei) (view->height * height));
02214             renderer->resize((int) (view->width * width),
02215                              (int) (view->height * height));
02216             sim->render(*renderer, *view->observer);
02217         }
02218         glDisable(GL_SCISSOR_TEST);
02219         glViewport(0, 0, width, height);
02220     }
02221 
02222     renderOverlay();
02223     if (showConsole)
02224     {
02225         console.setFont(font);
02226         glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
02227         console.begin();
02228         glTranslatef(0.0f, 200.0f, 0.0f);
02229         console.render(ConsolePageRows);
02230         console.end();
02231     }
02232         
02233 
02234     if (movieCapture != NULL && recording)
02235         movieCapture->captureFrame();
02236 
02237     // Frame rate counter
02238     nFrames++;
02239     if (nFrames == 100 || sysTime - fpsCounterStartTime > 10.0)
02240     {
02241         fps = (double) nFrames / (sysTime - fpsCounterStartTime);
02242         nFrames = 0;
02243         fpsCounterStartTime = sysTime;
02244     }
02245 
02246 #if 0
02247     GLenum err = glGetError();
02248     if (err != GL_NO_ERROR)
02249     {
02250         cout << _("GL error: ") << gluErrorString(err) << '\n';
02251     }
02252 #endif
02253 }
02254 
02255 
02256 void CelestiaCore::resize(GLsizei w, GLsizei h)
02257 {
02258     if (h == 0)
02259         h = 1;
02260 
02261     glViewport(0, 0, w, h);
02262     if (renderer != NULL)
02263         renderer->resize(w, h);
02264     if (overlay != NULL)
02265         overlay->setWindowSize(w, h);
02266     console.setScale(w, h);
02267     width = w;
02268     height = h;
02269 
02270     setFOVFromZoom();
02271 }
02272 
02273 
02274 void CelestiaCore::splitView(View::Type type, View* av, float splitPos)
02275 {
02276     if (av == NULL)
02277         av = views[activeView];
02278     bool vertical = ( type == View::VerticalSplit );
02279     Observer* o = sim->addObserver();
02280     bool tooSmall = false;
02281 
02282     switch (type) // If active view is too small, don't split it.
02283     {
02284     case View::HorizontalSplit:
02285         if (av->height < 0.2f) tooSmall = true;
02286         break;
02287     case View::VerticalSplit:
02288         if (av->width < 0.2f) tooSmall = true;
02289         break;
02290     case View::ViewWindow:
02291         return;
02292         break;
02293     }
02294 
02295     if (tooSmall)
02296     {
02297         flash(_("View too small to be split"));
02298         return;
02299     }
02300     flash(_("Added view"));
02301 
02302     // Make the new observer a copy of the old one
02303     // TODO: This works, but an assignment operator for Observer
02304     // should be defined.
02305     *o = *(sim->getActiveObserver());
02306 
02307     float w1, h1, w2, h2;
02308     if (vertical)
02309     {
02310         w1 = av->width * splitPos;
02311         w2 = av->width - w1;
02312         h1 = av->height;
02313         h2 = av->height;
02314     }
02315     else
02316     {
02317         w1 = av->width;
02318         w2 = av->width;
02319         h1 = av->height * splitPos;
02320         h2 = av->height - h1;
02321     }        
02322 
02323     View* split = new View(type,
02324                            0,
02325                            av->x,
02326                            av->y,
02327                            av->width,
02328                            av->height);
02329     split->parent = av->parent;
02330     if (av->parent != 0)
02331     {
02332         if (av->parent->child1 == av)
02333             av->parent->child1 = split;
02334         else
02335             av->parent->child2 = split;
02336     }
02337     split->child1 = av;
02338 
02339     av->width = w1;
02340     av->height = h1;
02341     av->parent = split;
02342 
02343     View* view = new View(View::ViewWindow,
02344                           o,
02345                           av->x + (vertical ? w1 : 0),
02346                           av->y + (vertical ? 0  : h1),
02347                           w2, h2);
02348     split->child2 = view;
02349     view->parent = split;
02350     view->zoom = av->zoom;
02351 
02352     views.insert(views.end(), view);
02353 
02354     setFOVFromZoom();
02355 }
02356 
02357 void CelestiaCore::setFOVFromZoom()
02358 {
02359     for (vector<View*>::iterator i = views.begin(); i < views.end(); i++)
02360         if ((*i)->type == View::ViewWindow)
02361         {
02362             double fov = 2 * atan(height * (*i)->height / (screenDpi / 25.4) / 2. / distanceToScreen) / (*i)->zoom;
02363             (*i)->observer->setFOV((float) fov);
02364         }
02365 }
02366 
02367 void CelestiaCore::setZoomFromFOV()
02368 {
02369     for (vector<View*>::iterator i = views.begin(); i < views.end(); i++)
02370         if ((*i)->type == View::ViewWindow)
02371         {
02372             (*i)->zoom = (float) (2 * atan(height * (*i)->height / (screenDpi / 25.4) / 2. / distanceToScreen) /  (*i)->observer->getFOV());
02373         }
02374 }
02375 
02376 void CelestiaCore::singleView(View* av)
02377 {
02378     if (av == NULL)
02379         av = views[activeView];
02380 
02381     for (unsigned int i = 0; i < views.size(); i++)
02382     {
02383         if (views[i] != av)
02384         {
02385             sim->removeObserver(views[i]->observer);
02386             delete views[i]->observer;
02387             delete views[i];
02388         }
02389     }
02390 
02391     av->x = 0.0f;
02392     av->y = 0.0f; 
02393     av->width = 1.0f;
02394     av->height = 1.0f;
02395     av->parent = 0;
02396 
02397     views.clear();
02398     views.insert(views.end(), av);
02399     activeView = 0;
02400     sim->setActiveObserver(views[activeView]->observer);
02401     setFOVFromZoom();
02402 }
02403 
02404 void CelestiaCore::deleteView(View* v)
02405 {
02406     if (v == NULL)
02407         v = views[activeView];
02408 
02409     if (v->parent == 0) return;
02410 
02411     for(vector<View*>::iterator i = views.begin(); i < views.end() ; i++)
02412     {
02413         if (*i == v)
02414         {
02415             views.erase(i);
02416             break;
02417         }
02418     }
02419 
02420     int sign;
02421     View *sibling;
02422     if (v->parent->child1 == v)
02423     {
02424         sibling = v->parent->child2;
02425         sign = -1;
02426     }
02427     else
02428     {
02429         sibling = v->parent->child1;
02430         sign = 1;
02431     }
02432     sibling->parent = v->parent->parent;
02433     if (v->parent->parent != 0) {
02434         if (v->parent->parent->child1 == v->parent)
02435             v->parent->parent->child1 = sibling;
02436         else
02437             v->parent->parent->child2 = sibling;
02438     }
02439 
02440     v->walkTreeResize(sibling, sign);
02441 
02442     sim->removeObserver(v->observer);
02443     delete(v->observer);
02444     activeView = 0;
02445     sim->setActiveObserver(views[activeView]->observer);
02446 
02447     delete(v->parent);
02448     delete(v);
02449 
02450     if (!showActiveViewFrame)
02451         flashFrameStart = currentTime;
02452     setFOVFromZoom();
02453 }
02454 
02455 bool CelestiaCore::getFramesVisible() const
02456 {
02457     return showViewFrames;
02458 }
02459 
02460 void CelestiaCore::setFramesVisible(bool visible)
02461 {
02462     showViewFrames = visible;
02463 }
02464 
02465 bool CelestiaCore::getActiveFrameVisible() const
02466 {
02467     return showActiveViewFrame;
02468 }
02469 
02470 void CelestiaCore::setActiveFrameVisible(bool visible)
02471 {
02472     showActiveViewFrame = visible;
02473 }
02474 
02475 
02476 void CelestiaCore::setContextMenuCallback(ContextMenuFunc callback)
02477 {
02478     contextMenuCallback = callback;
02479 }
02480 
02481 
02482 Renderer* CelestiaCore::getRenderer() const
02483 {
02484     return renderer;
02485 }
02486 
02487 Simulation* CelestiaCore::getSimulation() const
02488 {
02489     return sim;
02490 }
02491 
02492 void CelestiaCore::showText(string s,
02493                             int horig, int vorig,
02494                             int hoff, int voff,
02495                             double duration)
02496 {
02497     messageText = s;
02498     messageHOrigin = horig;
02499     messageVOrigin = vorig;
02500     messageHOffset = hoff;
02501     messageVOffset = voff;
02502     messageStart = currentTime;
02503     messageDuration = duration;
02504 }
02505 
02506 int CelestiaCore::getTextWidth(string s) const
02507 {
02508     return titleFont->getWidth(s);
02509 }
02510 
02511 static FormattedNumber SigDigitNum(double v, int digits)
02512 {
02513     return FormattedNumber(v, digits,
02514                            FormattedNumber::GroupThousands |
02515                            FormattedNumber::SignificantDigits);
02516 }
02517 
02518 
02519 static void displayDistance(Overlay& overlay, double distance)
02520 {
02521     const char* units = "";
02522 
02523     if (abs(distance) >= astro::parsecsToLightYears(1e+6))
02524     {
02525         units = "Mpc";
02526         distance = astro::lightYearsToParsecs(distance) / 1e+6;
02527     }
02528     else if (abs(distance) >= 0.5 * astro::parsecsToLightYears(1e+3))
02529     {
02530         units = "Kpc";
02531         distance = astro::lightYearsToParsecs(distance) / 1e+3;
02532     }
02533     else if (abs(distance) >= astro::AUtoLightYears(1000.0f))
02534     {
02535         units = "ly";
02536     }
02537     else if (abs(distance) >= astro::kilometersToLightYears(10000000.0))
02538     {
02539         units = "au";
02540         distance = astro::lightYearsToAU(distance);
02541     }
02542     else if (abs(distance) > astro::kilometersToLightYears(1.0f))
02543     {
02544         units = "km";
02545         distance = astro::lightYearsToKilometers(distance);
02546     }
02547     else
02548     {
02549         units = "m";
02550         distance = astro::lightYearsToKilometers(distance) * 1000.0f;
02551     }
02552 
02553     overlay << SigDigitNum(distance, 5) << ' ' << units;
02554 }
02555 
02556 
02557 static void displayDuration(Overlay& overlay, double days)
02558 {
02559     if (days > 1.0)
02560         overlay << days << _(" days");
02561     else if (days > 1.0 / 24.0)
02562         overlay << days * 24.0 << _(" hours");
02563     else if (days > 1.0 / (24.0 * 60.0))
02564         overlay << days * 24.0 * 60.0 << _(" minutes");
02565     else
02566         overlay << days * 24.0 * 60.0 * 60.0 << " seconds";
02567 }
02568 
02569 
02570 static void displayAngle(Overlay& overlay, double angle)
02571 {
02572     int degrees, minutes;
02573     double seconds;
02574     astro::decimalToDegMinSec(angle, degrees, minutes, seconds);
02575 
02576     if (degrees > 0)
02577     {
02578         overlay.printf("%d%s %02d' %.1f\"",
02579                         degrees, UTF8_DEGREE_SIGN, minutes, seconds);
02580     }
02581     else if (minutes > 0)
02582     {
02583         overlay.printf("%02d' %.1f\"", minutes, seconds);
02584     }
02585     else
02586     {
02587         overlay.printf("%.1f\"", seconds);
02588     }
02589 }
02590 
02591 
02592 static void displayApparentDiameter(Overlay& overlay,
02593                                     double radius,
02594                                     double distance)
02595 {
02596     if (distance > radius)
02597     {
02598         double arcSize = radToDeg(asin(radius / distance) * 2.0);
02599 
02600         // Only display the arc size if it's less than 160 degrees and greater
02601         // than one second--otherwise, it's probably not interesting data.
02602         if (arcSize < 160.0 && arcSize > 1.0 / 3600.0)
02603         {
02604             overlay << _("Apparent diameter: ");
02605             displayAngle(overlay, arcSize);
02606             overlay << '\n';
02607         }
02608     }
02609 }
02610 
02611 static void displayApparentMagnitude(Overlay& overlay,
02612                                      float absMag,
02613                                      double distance)
02614 {
02615     float appMag = absMag;
02616     if (distance > 32.6167)
02617     {
02618         appMag = astro::absToAppMag(absMag, (float) distance);
02619         overlay << _("Apparent magnitude: ");
02620     }
02621     else
02622     {
02623         overlay << _("Absolute magnitude: ");
02624     }
02625 
02626     overlay.printf("%.1f\n", appMag);
02627 }
02628 
02629 static void displayAcronym(Overlay& overlay, char* s)
02630 {
02631     if (strchr(s, ' ') != NULL)
02632     {
02633         int length = strlen(s);
02634         char lastChar = ' ';
02635 
02636         for (int i = 0; i < length; i++)
02637         {
02638             if (lastChar == ' ' && s[i] != ' ')
02639                 overlay << s[i];
02640             lastChar = s[i];
02641         }
02642     }
02643     else
02644     {
02645         overlay << s;
02646     }
02647 }
02648 
02649 
02650 static void displayStarInfo(Overlay& overlay,
02651                             int detail,
02652                             Star& star,
02653                             const Universe& universe,
02654                             double distance)
02655 {
02656     overlay << _("Distance: ");
02657     displayDistance(overlay, distance);
02658     overlay << '\n';
02659 
02660     if (!star.getVisibility())
02661     {
02662         overlay << _("Star system barycenter\n");
02663         return;
02664     }
02665 
02666     overlay.printf(_("Abs (app) mag: %.2f (%.2f)\n"),
02667                    star.getAbsoluteMagnitude(),
02668                    astro::absToAppMag(star.getAbsoluteMagnitude(),
02669                                       (float) distance));
02670 
02671     if (star.getLuminosity() > 1.0e-10f)
02672         overlay << _("Luminosity: ") << SigDigitNum(star.getLuminosity(), 3) << "x Sun\n";
02673     overlay << _("Class: ");
02674     if (star.getSpectralType()[0] == 'Q')
02675         overlay << _("Neutron star");
02676     else if (star.getSpectralType()[0] == 'X')
02677         overlay << _("Black hole");
02678     else
02679         overlay << star.getSpectralType();
02680     overlay << '\n';
02681 
02682     displayApparentDiameter(overlay, star.getRadius(),
02683                             astro::lightYearsToKilometers(distance));
02684 
02685     if (detail > 1)
02686     {
02687         overlay << _("Surface temp: ") << SigDigitNum(star.getTemperature(), 3) << " K\n";
02688         overlay.printf(_("Radius: %.2f Rsun\n"), star.getRadius() / 696000.0f);
02689 
02690         overlay << _("Rotation period: ");
02691         float period = star.getRotationElements().period;
02692         displayDuration(overlay, period);
02693         overlay << '\n';
02694 
02695         SolarSystem* sys = universe.getSolarSystem(&star);
02696         if (sys != NULL && sys->getPlanets()->getSystemSize() != 0)
02697             overlay << _("Planetary companions present\n");
02698     }
02699 }
02700 
02701 
02702 static void displayDSOinfo(Overlay& overlay, const DeepSkyObject& dso, double distance)
02703 {
02704     char descBuf[128];
02705 
02706     dso.getDescription(descBuf, sizeof(descBuf));
02707     overlay << descBuf << '\n';
02708     if (distance >= 0)
02709     {
02710         overlay << _("Distance: ");
02711         displayDistance(overlay, distance);
02712     }
02713     else
02714     {
02715         overlay << _("Distance from center: ");
02716         displayDistance(overlay, distance + dso.getRadius());
02717      }          
02718     overlay << '\n';
02719     overlay << _("Radius: ");
02720     displayDistance(overlay, dso.getRadius());
02721     overlay << '\n';
02722 
02723     displayApparentDiameter(overlay, dso.getRadius(), distance);
02724     if (dso.getAbsoluteMagnitude() > DSO_DEFAULT_ABS_MAGNITUDE)
02725     {
02726         displayApparentMagnitude(overlay,
02727                                  dso.getAbsoluteMagnitude(),
02728                                  distance);
02729     }
02730 }
02731 
02732 
02733 static void displayPlanetInfo(Overlay& overlay,
02734                               int detail,
02735                               Body& body,
02736                               double t,
02737                               double distance,
02738                               Vec3d viewVec)
02739 {
02740     double kmDistance = astro::lightYearsToKilometers(distance);
02741 
02742     overlay << _("Distance: ");
02743     distance = astro::kilometersToLightYears(kmDistance - body.getRadius());
02744     displayDistance(overlay, distance);
02745     overlay << '\n';
02746 
02747     overlay << _("Radius: ");
02748     distance = astro::kilometersToLightYears(body.getRadius());
02749     displayDistance(overlay, distance);
02750     overlay << '\n';
02751 
02752     displayApparentDiameter(overlay, body.getRadius(), kmDistance);
02753 
02754     if (detail > 1)
02755     {
02756         overlay << _("Day length: ");
02757         displayDuration(overlay, body.getRotationElements().period);
02758         overlay << '\n';
02759         
02760         PlanetarySystem* system = body.getSystem();
02761         if (system != NULL)
02762         {
02763             const Star* sun = system->getStar();
02764             if (sun != NULL)
02765             {
02766                 double distFromSun = body.getHeliocentricPosition(t).distanceFromOrigin();
02767                 float planetTemp = sun->getTemperature() * 
02768                     (float) (::pow(1.0 - body.getAlbedo(), 0.25) *
02769                              sqrt(sun->getRadius() / (2.0 * distFromSun)));
02770                 overlay << setprecision(0);
02771                 overlay << _("Temperature: ") << planetTemp << " K\n";
02772                 overlay << setprecision(3);
02773             }
02774             
02775 #if 0
02776             // Code to display apparent magnitude.  Disabled because it's not very
02777             // accurate.  Too many simplifications are used when computing the amount
02778             // of light reflected from a body.
02779             Point3d bodyPos = body.getHeliocentricPosition(t);
02780             float appMag = body.getApparentMagnitude(*sun,
02781                                                      bodyPos - Point3d(0, 0, 0),
02782                                                      viewVec);
02783             overlay.printf(_("Apparent mag: %.2f\n"), appMag);
02784 #endif
02785         }
02786     }
02787 }
02788 
02789 
02790 static void displayLocationInfo(Overlay& overlay,
02791                                 Location& location,
02792                                 double distance)
02793 {
02794     overlay << _("Distance: ");
02795     displayDistance(overlay, distance);
02796     overlay << '\n';
02797 
02798     Body* body = location.getParentBody();
02799     if (body != NULL)
02800     {
02801         Vec3f lonLatAlt = body->cartesianToPlanetocentric(location.getPosition());
02802         char ewHemi = ' ';
02803         char nsHemi = ' ';
02804         float lon = 0.0f;
02805         float lat = 0.0f;
02806 
02807         // Terrible hack for Earth and Moon longitude conventions.  Fix by
02808         // adding a field to specify the longitude convention in .ssc files.
02809         if (body->getName() == "Earth" || body->getName() == "Moon")
02810         {
02811             if (lonLatAlt.y < 0.0f)
02812                 nsHemi = 'S';
02813             else if (lonLatAlt.y > 0.0f)
02814                 nsHemi = 'N';
02815 
02816             if (lonLatAlt.x < 0.0f)
02817                 ewHemi = 'W';
02818             else if (lonLatAlt.x > 0.0f)
02819                 ewHemi = 'E';
02820 
02821             lon = abs(radToDeg(lonLatAlt.x));
02822             lat = abs(radToDeg(lonLatAlt.y));
02823         }
02824         else
02825         {
02826             // Swap hemispheres if the object is a retrograde rotator
02827             Quatd q = ~body->getEclipticalToEquatorial(astro::J2000);
02828             bool retrograde = (Vec3d(0.0, 1.0, 0.0) * q.toMatrix3()).y < 0.0;
02829 
02830             if ((lonLatAlt.y < 0.0f) ^ retrograde)
02831                 nsHemi = 'S';
02832             else if ((lonLatAlt.y > 0.0f) ^ retrograde)
02833                 nsHemi = 'N';
02834 
02835             if (retrograde)
02836                 ewHemi = 'E';
02837             else
02838                 ewHemi = 'W';
02839 
02840             lon = -radToDeg(lonLatAlt.x);
02841             if (lon < 0.0f)
02842                 lon += 360.0f;
02843             lat = abs(radToDeg(lonLatAlt.y));
02844         }
02845 
02846         overlay << body->getName(true).c_str() << " ";
02847         overlay.unsetf(ios::fixed);
02848         overlay << setprecision(6);
02849         overlay << lat << nsHemi << ' ' << lon << ewHemi << '\n';
02850     }
02851 }
02852 
02853 
02854 static void displaySelectionName(Overlay& overlay,
02855                                  const Selection& sel,
02856                                  const Universe& univ)
02857 {
02858     switch (sel.getType())
02859     {
02860     case Selection::Type_Body:
02861         overlay << sel.body()->getName(true).c_str();
02862         break;
02863     case Selection::Type_DeepSky:
02864         overlay << univ.getDSOCatalog()->getDSOName(sel.deepsky());
02865         break;
02866     case Selection::Type_Star:
02867         //displayStarName(overlay, *(sel.star()), *univ.getStarCatalog());
02868         overlay << univ.getStarCatalog()->getStarName(*sel.star());
02869         break;
02870     case Selection::Type_Location:
02871         overlay << sel.location()->getName(true).c_str();
02872         break;
02873     default:
02874         break;
02875     }
02876 }
02877 
02878 
02879 static void showViewFrame(const View* v, int width, int height)
02880 {
02881     glBegin(GL_LINE_LOOP);
02882     glVertex3f(v->x * width, v->y * height, 0.0f);
02883     glVertex3f(v->x * width, (v->y + v->height) * height - 1, 0.0f);
02884     glVertex3f((v->x + v->width) * width - 1, (v->y + v->height) * height - 1, 0.0f);
02885     glVertex3f((v->x + v->width) * width - 1, v->y * height, 0.0f);
02886     glEnd();
02887 }
02888 
02889 
02890 void CelestiaCore::renderOverlay()
02891 {
02892     static locale currentLocale;
02893     static locale cLocale;
02894     static bool localeSet = false;
02895 
02896     if (!localeSet) {
02897         cLocale = overlay->getloc();
02898         currentLocale = locale(cLocale, "", std::locale::numeric);
02899         localeSet = true;
02900     }
02901 
02902     if (font == NULL)
02903         return;
02904 
02905     overlay->setFont(font);
02906 
02907     int fontHeight = font->getHeight();
02908     int emWidth = font->getWidth("M");
02909 
02910     overlay->begin();
02911 
02912 
02913     if (views.size() > 1)
02914     {
02915         // Render a thin border arround all views
02916         if (showViewFrames || resizeSplit)
02917         {
02918             glLineWidth(1.0f);
02919             glDisable(GL_TEXTURE_2D);
02920             glColor4f(0.5f, 0.5f, 0.5f, 1.0f);
02921             for(vector<View*>::iterator i = views.begin(); i != views.end(); i++)
02922             {
02923                 showViewFrame(*i, width, height);
02924             }
02925         }
02926         glLineWidth(1.0f);
02927 
02928         // Render a very simple border around the active view
02929         View* av = views[activeView];
02930 
02931         if (showActiveViewFrame)
02932         {
02933             glLineWidth(2.0f);
02934             glDisable(GL_TEXTURE_2D);
02935             glColor4f(0.5f, 0.5f, 1.0f, 1.0f);
02936             showViewFrame(av, width, height);
02937             glLineWidth(1.0f);
02938         }
02939 
02940         if (currentTime < flashFrameStart + 0.5)
02941         {
02942             glLineWidth(8.0f);
02943             glColor4f(0.5f, 0.5f, 1.0f,
02944                       (float) (1.0 - (currentTime - flashFrameStart) / 0.5));
02945             showViewFrame(av, width, height);
02946             glLineWidth(1.0f);
02947         }
02948     }
02949 
02950     if (hudDetail > 0)
02951     {
02952         // Time and date
02953         glPushMatrix();
02954         glColor4f(0.7f, 0.7f, 1.0f, 1.0f);
02955         glTranslatef((float) (width - (11 + timeZoneName.length() + 3) * emWidth),
02956                      (float) (height - fontHeight),
02957                      0.0f);
02958         overlay->beginText();
02959 
02960         bool time_displayed = false;
02961         double lt = 0.0;
02962 
02963         if (sim->getSelection().getType() == Selection::Type_Body &&
02964             (sim->getTargetSpeed() < 0.99 *
02965              astro::kilometersToMicroLightYears(astro::speedOfLight)))
02966         {                   
02967             if (lightTravelFlag) 
02968             {
02969                 Vec3d v = sim->getSelection().getPosition(sim->getTime()) -
02970                               sim->getObserver().getPosition();
02971                 // light travel time in days
02972                 lt = astro::microLightYearsToKilometers(v.length()) / (86400.0 * astro::speedOfLight);
02973             }            
02974         }
02975         else
02976         {
02977             lt = 0.0;
02978         }
02979 
02980         overlay->imbue(locale::classic());
02981 
02982         if (timeZoneBias != 0 &&
02983             sim->getTime() < 2465442 &&
02984             sim->getTime() > 2415733) 
02985         {
02986             time_t time = (int) astro::julianDateToSeconds(sim->getTime() - 2440587.5 + lt);
02987             struct tm *localt = localtime(&time);
02988             if (localt != NULL)
02989             {
02990                 astro::Date d;
02991                 d.year = localt->tm_year + 1900;
02992                 d.month = localt->tm_mon + 1;
02993                 d.day = localt->tm_mday;
02994                 d.hour = localt->tm_hour;
02995                 d.minute = localt->tm_min;
02996                 d.seconds = (int) localt->tm_sec;
02997                 *overlay << d << " ";
02998                 displayAcronym(*overlay, tzname[localt->tm_isdst > 0 ? 1 : 0]);
02999                 time_displayed = true;
03000                 if (lightTravelFlag && lt > 0.0)
03001                 {
03002                     glColor4f(0.42f, 1.0f, 1.0f, 1.0f);
03003                     *overlay << _("  LT");
03004                     glColor4f(0.7f, 0.7f, 1.0f, 1.0f);
03005                 }
03006                 *overlay << '\n';
03007             }
03008         } 
03009         
03010         if (!time_displayed)
03011         {
03012             *overlay << astro::Date(sim->getTime() + lt);
03013             *overlay << _(" UTC");
03014             if (lightTravelFlag && lt > 0.0)
03015             {
03016                 glColor4f(0.42f, 1.0f, 1.0f, 1.0f);
03017                 *overlay << _("  LT");
03018                 glColor4f(0.7f, 0.7f, 1.0f, 1.0f);
03019             }
03020             *overlay << '\n';
03021         }
03022 
03023         setlocale(LC_NUMERIC, "");
03024         overlay->imbue(currentLocale);
03025 
03026         {
03027             *overlay << setprecision(0);
03028             if (abs(abs(timeScale) - 1) < 1e-6)
03029             {
03030                 if (sign(timeScale) == 1)
03031                     *overlay << _("Real time");
03032                 else
03033                     *overlay << _("-Real time");
03034             }
03035             else if (abs(timeScale) == 0.0f)
03036             {
03037                 *overlay << _("Time stopped");
03038             }
03039             else if (abs(timeScale) > 1.0)
03040             {
03041                 *overlay << timeScale << UTF8_MULTIPLICATION_SIGN << _(" faster");
03042             }
03043             else
03044             {
03045                 *overlay << 1.0 / timeScale << UTF8_MULTIPLICATION_SIGN << _(" slower");
03046             }
03047             *overlay << setprecision(3);
03048 
03049             if (paused)
03050             {
03051                 glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
03052                 *overlay << _(" (Paused)");
03053             }
03054         }
03055     
03056         overlay->endText();
03057         glPopMatrix();
03058 
03059         // Speed
03060         glPushMatrix();
03061         glTranslatef(0.0f, (float) (fontHeight * 2 + 5), 0.0f);
03062         glColor4f(0.7f, 0.7f, 1.0f, 1.0f);
03063 
03064         overlay->beginText();
03065         *overlay << '\n';
03066         if (showFPSCounter)
03067             *overlay << _("FPS: ") << fps;
03068         overlay->setf(ios::fixed);
03069         *overlay << _("\nSpeed: ") << setprecision(3);
03070 
03071         double speed = sim->getObserver().getVelocity().length();
03072         if (speed < astro::kilometersToMicroLightYears(1.0f))
03073             *overlay << astro::microLightYearsToKilometers(speed) * 1000.0f << " m/s";
03074         else if (speed < astro::kilometersToMicroLightYears(10000.0f))
03075             *overlay << astro::microLightYearsToKilometers(speed) << " km/s";
03076         else if (speed < astro::kilometersToMicroLightYears((float) astro::speedOfLight * 100.0f))
03077             *overlay << astro::microLightYearsToKilometers(speed) / astro::speedOfLight << 'c';
03078         else if (speed < astro::AUtoMicroLightYears(1000.0f))
03079             *overlay << astro::microLightYearsToAU(speed) << " AU/s";
03080         else
03081             *overlay << speed * 1e-6 << " ly/s";
03082         *overlay << setprecision(3);
03083 
03084         overlay->endText();
03085         glPopMatrix();
03086 
03087         // Field of view and camera mode in lower right corner
03088         glPushMatrix();
03089         glTranslatef((float) (width - emWidth * 15), 
03090                      (float) (fontHeight * 3 + 5), 0.0f);
03091         overlay->beginText();
03092         glColor4f(0.6f, 0.6f, 1.0f, 1);
03093 
03094         if (sim->getObserverMode() == Observer::Travelling)
03095         {
03096             *overlay << _("Travelling ");
03097             double timeLeft = sim->getArrivalTime() - sim->getRealTime();
03098             if (timeLeft >= 1)
03099                 *overlay << '(' << (int) timeLeft << ')';
03100             *overlay << '\n';
03101         }
03102         else
03103         {
03104             *overlay << '\n';
03105         }
03106 
03107         if (!sim->getTrackedObject().empty())
03108         {
03109             *overlay << _("Track ");
03110             displaySelectionName(*overlay, sim->getTrackedObject(),
03111                                  *sim->getUniverse());
03112         }
03113         *overlay << '\n';
03114         
03115         {
03116             FrameOfReference frame = sim->getFrame();
03117 
03118             switch (frame.coordSys)
03119             {
03120             case astro::Ecliptical:
03121                 *overlay << _("Follow ");
03122                 displaySelectionName(*overlay, frame.refObject,
03123                                      *sim->getUniverse());
03124                 break;
03125             case astro::Geographic:
03126                 *overlay << _("Sync Orbit ");
03127                 displaySelectionName(*overlay, frame.refObject,
03128                                      *sim->getUniverse());
03129                 break;
03130             case astro::PhaseLock:
03131                 *overlay << _("Lock ");
03132                 displaySelectionName(*overlay, frame.refObject,
03133                                      *sim->getUniverse());
03134                 *overlay << " -> ";
03135                 displaySelectionName(*overlay, frame.targetObject,
03136                                      *sim->getUniverse());
03137                 break;
03138 
03139             case astro::Chase:
03140                 *overlay << _("Chase ");
03141                 displaySelectionName(*overlay, frame.refObject,
03142                                      *sim->getUniverse());
03143                 break;
03144 
03145             default:
03146                 break;
03147             }
03148 
03149             *overlay << '\n';
03150         }
03151 
03152         glColor4f(0.7f, 0.7f, 1.0f, 1.0f);
03153 
03154         // Field of view
03155         float fov = radToDeg(sim->getActiveObserver()->getFOV());
03156         overlay->printf(_("FOV: "));
03157         displayAngle(*overlay, fov);
03158         overlay->printf(" (%.2f%s)\n", views[activeView]->zoom,
03159                         UTF8_MULTIPLICATION_SIGN);
03160         overlay->endText();
03161         glPopMatrix();
03162     }
03163 
03164     // Selection info
03165     Selection sel = sim->getSelection();
03166     if (!sel.empty() && hudDetail > 0)
03167     {
03168         glPushMatrix();
03169         glColor4f(0.7f, 0.7f, 1.0f, 1.0f);
03170         glTranslatef(0.0f, (float) (height - titleFont->getHeight()), 0.0f);
03171 
03172         overlay->beginText();
03173         Vec3d v = sel.getPosition(sim->getTime()) -
03174             sim->getObserver().getPosition();
03175         switch (sel.getType())
03176         {
03177         case Selection::Type_Star:
03178             {
03179                 if (sel != lastSelection)
03180                 {
03181                     lastSelection = sel;
03182                     selectionNames = sim->getUniverse()->getStarCatalog()->getStarNameList(*sel.star());
03183                 }
03184 
03185                 overlay->setFont(titleFont);
03186                 *overlay << selectionNames;
03187                 overlay->setFont(font);
03188                 *overlay << '\n';
03189                 displayStarInfo(*overlay,
03190                                 hudDetail,
03191                                 *(sel.star()),
03192                                 *(sim->getUniverse()),
03193                                 v.length() * 1e-6);
03194             }
03195             break;
03196 
03197         case Selection::Type_DeepSky:
03198             {
03199                 if (sel != lastSelection)
03200                 {
03201                     lastSelection = sel;
03202                     selectionNames = sim->getUniverse()->getDSOCatalog()->getDSONameList(sel.deepsky());
03203                 }
03204 
03205                 overlay->setFont(titleFont);
03206                 *overlay << selectionNames;
03207                 overlay->setFont(font);
03208                 *overlay << '\n';
03209                 displayDSOinfo(*overlay, *sel.deepsky(),
03210                                v.length() * 1e-6 - sel.deepsky()->getRadius());
03211             }
03212             break;
03213 
03214         case Selection::Type_Body:
03215             {
03216                 overlay->setFont(titleFont);
03217                 *overlay << sel.body()->getName(true).c_str();
03218                 overlay->setFont(font);
03219                 *overlay << '\n';
03220                 displayPlanetInfo(*overlay,
03221                                   hudDetail,
03222                                   *(sel.body()),
03223                                   sim->getTime(),
03224                                   v.length() * 1e-6,
03225                                   v * astro::microLightYearsToKilometers(1.0));
03226             }
03227             break;
03228 
03229         case Selection::Type_Location:
03230             overlay->setFont(titleFont);
03231             *overlay << sel.location()->getName(true).c_str();
03232             overlay->setFont(font);
03233             *overlay << '\n';
03234             displayLocationInfo(*overlay,
03235                                 *(sel.location()),
03236                                 v.length() * 1e-6);
03237             break;
03238 
03239         default:
03240             break;
03241         }
03242 
03243         overlay->endText();
03244 
03245         glPopMatrix();
03246     }
03247 
03248     // Text input
03249     if (textEnterMode & KbAutoComplete)
03250     {
03251         overlay->setFont(titleFont);
03252         glPushMatrix();
03253         glColor4f(0.7f, 0.7f, 1.0f, 0.2f);
03254         overlay->rect(0.0f, 0.0f, (float) width, 100.0f);
03255         glTranslatef(0.0f, fontHeight * 3.0f + 35.0f, 0.0f);
03256         glColor4f(0.6f, 0.6f, 1.0f, 1.0f);
03257         overlay->beginText();
03258         *overlay << _("Target name: ") << ReplaceGreekLetterAbbr(typedText);
03259         overlay->endText();
03260         overlay->setFont(font);
03261         if (typedTextCompletion.size() >= 1)
03262         {
03263             int nb_cols = 4;
03264             int nb_lines = 3;
03265             int start = 0;
03266             glTranslatef(3.0f, -font->getHeight() - 3.0f, 0.0f);
03267             vector<std::string>::const_iterator iter = typedTextCompletion.begin();
03268             if (typedTextCompletionIdx >= nb_cols * nb_lines)
03269             {
03270                start = (typedTextCompletionIdx / nb_lines + 1 - nb_cols) * nb_lines;
03271                iter += start;
03272             }
03273             for (int i=0; iter < typedTextCompletion.end() && i < nb_cols; i++)
03274             {
03275                 glPushMatrix();
03276                 overlay->beginText();
03277                 for (int j = 0; iter < typedTextCompletion.end() && j < nb_lines; iter++, j++)
03278                 {
03279                     if (i * nb_lines + j == typedTextCompletionIdx - start)
03280                         glColor4f(1.0f, 0.6f, 0.6f, 1);
03281                     else
03282                         glColor4f(0.6f, 0.6f, 1.0f, 1);
03283                     *overlay << ReplaceGreekLetterAbbr(*iter) << "\n";
03284                 }
03285                 overlay->endText();
03286                 glPopMatrix();
03287                 glTranslatef((float) (width/nb_cols), 0.0f, 0.0f);
03288            }
03289         }
03290         glPopMatrix();
03291         overlay->setFont(font);
03292     }
03293 
03294     // Text messages
03295     if (messageText != "" && currentTime < messageStart + messageDuration)
03296     {
03297         int emWidth = titleFont->getWidth("M");
03298         int fontHeight = titleFont->getHeight();
03299         int x = messageHOffset * emWidth;
03300         int y = messageVOffset * fontHeight;
03301 
03302         if (messageHOrigin == 0)
03303             x += width / 2;
03304         else if (messageHOrigin > 0)
03305             x += width;
03306         if (messageVOrigin == 0)
03307             y += height / 2;
03308         else if (messageVOrigin > 0)
03309             y += height;
03310         else if (messageVOrigin < 0)
03311             y -= fontHeight;
03312 
03313         overlay->setFont(titleFont);
03314         glPushMatrix();
03315 
03316         float alpha = 1.0f;
03317         if (currentTime > messageStart + messageDuration - 0.5)
03318             alpha = (float) ((messageStart + messageDuration - currentTime) / 0.5);
03319         glColor4f(1.0f, 1.0f, 1.0f, alpha);
03320         glTranslatef((float) x, (float) y, 0.0f);
03321         overlay->beginText();
03322         *overlay << _(messageText.c_str());
03323         overlay->endText();
03324         glPopMatrix();
03325         overlay->setFont(font);
03326     }
03327 
03328     if (movieCapture != NULL)
03329     {
03330         int movieWidth = movieCapture->getWidth();
03331         int movieHeight = movieCapture->getHeight();
03332         glPushMatrix();
03333         glColor4f(1, 0, 0, 1);
03334         overlay->rect((float) ((width - movieWidth) / 2 - 1),
03335                       (float) ((height - movieHeight) / 2 - 1),
03336                       (float) (movieWidth + 1), 
03337                       (float) (movieHeight + 1), false);
03338         glTranslatef((float) ((width - movieWidth) / 2),
03339                      (float) ((height + movieHeight) / 2 + 2), 0.0f);
03340         *overlay << movieWidth << 'x' << movieHeight << _(" at ") <<
03341             movieCapture->getFrameRate() << _(" fps");
03342         if (recording)
03343             *overlay << _("  Recording");
03344         else
03345             *overlay << _("  Paused");
03346 
03347         glPopMatrix();
03348 
03349         glPushMatrix();
03350         glTranslatef((float) ((width + movieWidth) / 2 - emWidth * 5),
03351                      (float) ((height + movieHeight) / 2 + 2),
03352                      0.0f);
03353         float sec = movieCapture->getFrameCount() /
03354             movieCapture->getFrameRate();
03355         int min = (int) (sec / 60);
03356         sec -= min * 60.0f;
03357         overlay->printf("%3d:%05.2f", min, sec);
03358         glPopMatrix();
03359 
03360         glPushMatrix();
03361         glTranslatef((float) ((width - movieWidth) / 2),
03362                      (float) ((height - movieHeight) / 2 - fontHeight - 2),
03363                      0.0f);
03364         *overlay << _("F11 Start/Pause    F12 Stop");
03365         glPopMatrix();
03366 
03367         glPopMatrix();
03368     }
03369 
03370     if (editMode)
03371     {
03372         glPushMatrix();
03373         glTranslatef((float) ((width - font->getWidth(_("Edit Mode"))) / 2),
03374                      (float) (height - fontHeight), 0.0f);
03375         glColor4f(1, 0, 1, 1);
03376         *overlay << _("Edit Mode");
03377         glPopMatrix();
03378     }
03379 
03380 #if 0
03381     // Experimental code for overlaying images
03382     {
03383         glPushMatrix();
03384         glLoadIdentity();
03385         glTranslatef(0.375f, 0.375f, 0);
03386 
03387         glEnable(GL_TEXTURE_2D);
03388         for (vector<OverlayImage>::iterator iter = overlayImages.begin();
03389              iter != overlayImages.end(); iter++)
03390         {
03391             glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
03392             if (iter->texture != NULL)
03393             {
03394                 iter->texture->bind();
03395                 glBegin(GL_QUADS);
03396                 glTexCoord2f(0, 1);
03397                 glVertex2f(iter->left, iter->bottom);
03398                 glTexCoord2f(1, 1);
03399                 glVertex2f(iter->left + iter->xSize, iter->bottom);
03400                 glTexCoord2f(1, 0);
03401                 glVertex2f(iter->left + iter->xSize, iter->bottom + iter->ySize);
03402                 glTexCoord2f(0, 0);
03403                 glVertex2f(iter->left, iter->bottom + iter->ySize);
03404                 glEnd();
03405             }
03406         }
03407 
03408         glPopMatrix();
03409     }
03410 #endif
03411 
03412     // Show logo at start
03413     if (logoTexture != NULL)
03414     {
03415         glEnable(GL_TEXTURE_2D);
03416         if (currentTime < 5.0)
03417         {
03418             int xSize = (int) (logoTexture->getWidth() * 0.8f);
03419             int ySize = (int) (logoTexture->getHeight() * 0.8f);
03420             int left = (width - xSize) / 2;
03421             int bottom = height / 2;
03422 
03423             float topAlpha, botAlpha;
03424             if (currentTime < 4.0)
03425             {
03426                 botAlpha = (float) clamp(currentTime / 1.0);
03427                 topAlpha = (float) clamp(currentTime / 4.0);
03428             }
03429             else
03430             {
03431                 botAlpha = topAlpha = (float) (5.0 - currentTime);
03432             }
03433 
03434             logoTexture->bind();
03435             glBegin(GL_QUADS);
03436             glColor4f(0.8f, 0.8f, 1.0f, botAlpha);
03437             //glColor4f(1.0f, 1.0f, 1.0f, botAlpha);
03438             glTexCoord2f(0.0f, 1.0f);
03439             glVertex2i(left, bottom);
03440             glTexCoord2f(1.0f, 1.0f);
03441             glVertex2i(left + xSize, bottom);
03442             glColor4f(0.6f, 0.6f, 1.0f, topAlpha);
03443             //glColor4f(1.0f, 1.0f, 1.0f, topAlpha);
03444             glTexCoord2f(1.0f, 0.0f);
03445             glVertex2i(left + xSize, bottom + ySize);
03446             glTexCoord2f(0.0f, 0.0f);
03447             glVertex2i(left, bottom + ySize);
03448             glEnd();
03449         }
03450         else
03451         {
03452             delete logoTexture;
03453             logoTexture = NULL;
03454         }
03455     }
03456 
03457     overlay->end();
03458     setlocale(LC_NUMERIC, "C");
03459     overlay->imbue(currentLocale);
03460 
03461 }
03462 
03463 
03464 class SolarSystemLoader : public EnumFilesHandler
03465 {
03466  public:
03467     Universe* universe;
03468     ProgressNotifier* notifier;
03469     SolarSystemLoader(Universe* u, ProgressNotifier* pn) : universe(u), notifier(pn) {};
03470 
03471     bool process(const string& filename)
03472     {
03473         if (DetermineFileType(filename) == Content_CelestiaCatalog)
03474         {
03475             string fullname = getPath() + '/' + filename;
03476             clog << _("Loading solar system catalog: ") << fullname << '\n';
03477             if (notifier)
03478                 notifier->update(filename);
03479                 
03480             ifstream solarSysFile(fullname.c_str(), ios::in);
03481             if (solarSysFile.good())
03482             {
03483                 LoadSolarSystemObjects(solarSysFile,
03484                                        *universe,
03485                                        getPath());
03486             }
03487         }
03488 
03489         return true;
03490     };
03491 };
03492 
03493 template <class OBJDB> class CatalogLoader : public EnumFilesHandler
03494 {
03495 public:
03496     OBJDB*      objDB;
03497     string      typeDesc;
03498     ContentType contentType;
03499     ProgressNotifier* notifier;
03500 
03501     CatalogLoader(OBJDB* db,
03502                   const std::string& typeDesc,
03503                   const ContentType& contentType,
03504                   ProgressNotifier* pn) :
03505         objDB      (db),
03506         typeDesc   (typeDesc),
03507         contentType(contentType),
03508         notifier(pn)
03509     {
03510     }
03511 
03512     bool process(const string& filename)
03513     {
03514         if (DetermineFileType(filename) == contentType)
03515         {
03516             string fullname = getPath() + '/' + filename;
03517             clog << _("Loading ") << typeDesc << " catalog: " << fullname << '\n';
03518             if (notifier)
03519                 notifier->update(filename);
03520                 
03521             ifstream catalogFile(fullname.c_str(), ios::in);
03522             if (catalogFile.good())
03523             {
03524                 bool success = objDB->load(catalogFile, getPath());
03525                 if (!success)
03526                 {
03527                     DPRINTF(0, _("Error reading star file: %s\n"),
03528                             fullname.c_str());
03529                 }
03530                     DPRINTF(0, "Error reading %s catalog file: %s\n", typeDesc.c_str(), fullname.c_str());
03531             }
03532         }
03533         return true;
03534     }
03535 };
03536 
03537 typedef CatalogLoader<StarDatabase> StarLoader;
03538 typedef CatalogLoader<DSODatabase>  DeepSkyLoader;
03539 
03540 
03541 bool CelestiaCore::initSimulation(const string* configFileName,
03542                                   const vector<string>* extrasDirs,
03543                                   ProgressNotifier* progressNotifier)
03544 {
03545     // Say we're not ready to render yet.
03546     // bReady = false;
03547 #ifdef REQUIRE_LICENSE_FILE
03548     // Check for the presence of the license file--don't run unless it's there.
03549     {
03550         ifstream license("License.txt");
03551         if (!license.good())
03552         {
03553             fatalError(_("License file 'License.txt' is missing!"));
03554             return false;
03555         }
03556     }
03557 #endif
03558 
03559     if (configFileName != NULL)
03560     {
03561         config = ReadCelestiaConfig(*configFileName);
03562     }
03563     else
03564     {
03565         config = ReadCelestiaConfig("celestia.cfg");
03566 
03567         string localConfigFile = WordExp("~/.celestia.cfg");
03568         if (localConfigFile != "")
03569             ReadCelestiaConfig(localConfigFile.c_str(), config);
03570     }
03571 
03572     if (config == NULL)
03573     {
03574         fatalError(_("Error reading configuration file."));
03575         return false;
03576     }
03577 
03578     // Insert additional extras directories into the configuration. These
03579     // additional directories typically come from the command line. It may
03580     // be useful to permit other command line overrides of config file fields.
03581     if (extrasDirs != NULL)
03582     {
03583         // Only insert the additional extras directories that aren't also
03584         // listed in the configuration file. The additional directories are added
03585         // after the ones from the config file and the order in which they were
03586         // specified is preserved. This process in O(N*M), but the number of
03587         // additional extras directories should be small.
03588         for (vector<string>::const_iterator iter = extrasDirs->begin();
03589              iter != extrasDirs->end(); iter++)
03590         {
03591             if (find(config->extrasDirs.begin(), config->extrasDirs.end(), *iter) == 
03592                 config->extrasDirs.end())
03593             {
03594                 config->extrasDirs.push_back(*iter);
03595             }
03596         }
03597     }
03598 
03599     KeyRotationAccel = degToRad(config->rotateAcceleration);
03600     MouseRotationSensitivity = degToRad(config->mouseRotationSensitivity);
03601 
03602     readFavoritesFile();
03603 
03604     // If we couldn't read the favorites list from a file, allocate
03605     // an empty list.
03606     if (favorites == NULL)
03607         favorites = new FavoritesList();
03608 
03609     universe = new Universe();
03610 
03611     if (!readStars(*config, progressNotifier))
03612     {
03613         fatalError(_("Cannot read star database."));
03614         return false;
03615     }
03616 
03617     // First read the solar system files listed individually in the
03618     // config file.
03619     {
03620         SolarSystemCatalog* solarSystemCatalog = new SolarSystemCatalog();
03621         universe->setSolarSystemCatalog(solarSystemCatalog);
03622         for (vector<string>::const_iterator iter = config->solarSystemFiles.begin();
03623              iter != config->solarSystemFiles.end();
03624              iter++)
03625         {
03626             if (progressNotifier)
03627                 progressNotifier->update(*iter);
03628             
03629             ifstream solarSysFile(iter->c_str(), ios::in);
03630             if (!solarSysFile.good())
03631             {
03632                 warning(_("Error opening solar system catalog.\n"));
03633             }
03634             else
03635             {
03636                 LoadSolarSystemObjects(solarSysFile, *universe, "");
03637             }
03638         }
03639     }
03640 
03641 #if 0
03642     // XML support not ready yet
03643     {
03644         bool success = LoadSolarSystemObjectsXML("data/test.xml",
03645                                                  *universe);
03646         if (!success)
03647             warning(_("Error opening test.xml\n"));
03648     }
03649 #endif
03650 
03651     // Next, read all the solar system files in the extras directories
03652     {
03653         for (vector<string>::const_iterator iter = config->extrasDirs.begin();
03654              iter != config->extrasDirs.end(); iter++)
03655         {
03656             if (*iter != "")
03657             {
03658                 Directory* dir = OpenDirectory(*iter);
03659 
03660                 SolarSystemLoader loader(universe, progressNotifier);
03661                 loader.pushDir(*iter);
03662                 dir->enumFiles(loader, true);
03663 
03664                 delete dir;
03665             }
03666         }
03667     }
03668 
03669     DSONameDatabase* dsoNameDB  = new DSONameDatabase;
03670     DSODatabase*     dsoDB      = new DSODatabase;
03671     dsoDB->setNameDatabase(dsoNameDB);
03672 
03673     // We'll first read a very large DSO catalog from a bundled file:
03674     if (config->deepSkyCatalog != "")
03675     {
03676         ifstream dsoFile(config->deepSkyCatalog.c_str(), ios::in);
03677         
03678         if (progressNotifier)
03679             progressNotifier->update(config->deepSkyCatalog);
03680             
03681 #if 0 //TODO: define a binary file format for DSOs !!
03682         if (!dsoDB->loadBinary(deepSkyFile))
03683         {
03684             cerr << _("Error reading deep sky file\n");
03685             delete dsoDB;
03686             return false;
03687         }
03688 #endif
03689         if (!dsoFile.good())
03690         {
03691             cerr << _("Error opening ") << config->deepSkyCatalog << '\n';
03692             delete dsoDB;
03693             return false;
03694         }
03695         else if (!dsoDB->load(dsoFile, ""))
03696         {
03697             cerr << "Cannot read Deep Sky Objects database." << config->deepSkyCatalog << '\n';
03698             delete dsoDB;
03699             return false;
03700         }
03701     }
03702     
03703     // TODO:add support for additional catalogs declared in the config file.
03704 
03705     // Next, read all the deep sky files in the extras directories
03706     {
03707         for (vector<string>::const_iterator iter = config->extrasDirs.begin();
03708              iter != config->extrasDirs.end(); iter++)
03709         {
03710             if (*iter != "")
03711             {
03712                 Directory* dir = OpenDirectory(*iter);
03713      
03714                 DeepSkyLoader loader(dsoDB,
03715                                      "deep sky object",
03716                                      Content_CelestiaDeepSkyCatalog,
03717                                      progressNotifier);
03718                 loader.pushDir(*iter);
03719                 dir->enumFiles(loader, true);
03720 
03721                 delete dir;
03722             }
03723         }
03724     }
03725 
03726     dsoDB->finish();
03727     universe->setDSOCatalog(dsoDB);
03728 
03729     // Load asterisms:
03730     if (config->asterismsFile != "")
03731     {
03732         ifstream asterismsFile(config->asterismsFile.c_str(), ios::in);
03733         if (!asterismsFile.good())
03734         {
03735             warning(_("Error opening asterisms file."));
03736         }
03737         else
03738         {
03739             AsterismList* asterisms = ReadAsterismList(asterismsFile,
03740                                                        *universe->getStarCatalog());
03741             universe->setAsterisms(asterisms);
03742         }
03743     }
03744     
03745     if (config->boundariesFile != "")
03746     {
03747         ifstream boundariesFile(config->boundariesFile.c_str(), ios::in);
03748         if (!boundariesFile.good())
03749         {
03750             warning(_("Error opening constellation boundaries files."));
03751         }
03752         else
03753         {
03754             ConstellationBoundaries* boundaries = ReadBoundaries(boundariesFile);
03755             universe->setBoundaries(boundaries);
03756         }
03757     }
03758 
03759     // Load destinations list
03760     if (config->destinationsFile != "")
03761     {
03762         ifstream destfile(config->destinationsFile.c_str());
03763         if (destfile.good())
03764         {
03765             destinations = ReadDestinationList(destfile);
03766         }
03767     }
03768 
03769     sim = new Simulation(universe);
03770     if((renderer->getRenderFlags() & Renderer::ShowAutoMag) == 0)
03771     sim->setFaintestVisible(config->faintestVisible);
03772 
03773     View* view = new View(View::ViewWindow, sim->getActiveObserver(), 0.0f, 0.0f, 1.0f, 1.0f);
03774     views.insert(views.end(), view);
03775 
03776     if (!compareIgnoringCase(getConfig()->cursor, "inverting crosshair")) 
03777     {
03778         defaultCursorShape = CelestiaCore::InvertedCrossCursor;
03779     }
03780     
03781     if (!compareIgnoringCase(getConfig()->cursor, "arrow")) 
03782     {
03783         defaultCursorShape = CelestiaCore::ArrowCursor;
03784     }
03785     
03786     if (cursorHandler != NULL)
03787     {
03788         cursorHandler->setCursorShape(defaultCursorShape);
03789     }
03790 
03791     return true;
03792 }
03793 
03794 
03795 bool CelestiaCore::initRenderer()
03796 {
03797     renderer->setRenderFlags(Renderer::ShowStars |
03798                              Renderer::ShowPlanets |
03799                              Renderer::ShowAtmospheres |
03800                              Renderer::ShowAutoMag);
03801 
03802     GLContext* context = new GLContext();
03803     assert(context != NULL);
03804     if (context == NULL)
03805         return false;
03806 
03807     context->init(config->ignoreGLExtensions);
03808     // Choose the render path, starting with the least desirable
03809     context->setRenderPath(GLContext::GLPath_Basic);
03810     context->setRenderPath(GLContext::GLPath_Multitexture);
03811     context->setRenderPath(GLContext::GLPath_DOT3_ARBVP);
03812     context->setRenderPath(GLContext::GLPath_NvCombiner_NvVP);
03813     context->setRenderPath(GLContext::GLPath_NvCombiner_ARBVP);
03814     cout << _("render path: ") << context->getRenderPath() << '\n';
03815 
03816     Renderer::DetailOptions detailOptions;
03817     detailOptions.ringSystemSections = config->ringSystemSections;
03818     detailOptions.orbitPathSamplePoints = config->orbitPathSamplePoints;
03819     detailOptions.shadowTextureSize = config->shadowTextureSize;
03820     detailOptions.eclipseTextureSize = config->eclipseTextureSize;
03821     
03822     // Prepare the scene for rendering.
03823     if (!renderer->init(context, (int) width, (int) height, detailOptions))
03824     {
03825         fatalError(_("Failed to initialize renderer"));
03826         return false;
03827     }
03828 
03829     // Set up the star labels
03830     for (vector<string>::const_iterator iter = config->labelledStars.begin();
03831          iter != config->labelledStars.end();
03832          iter++)
03833     {
03834         Star* star = universe->getStarCatalog()->find(*iter);
03835         if (star != NULL)
03836             renderer->addLabelledStar(star, *iter);
03837     }
03838 
03839     if((renderer->getRenderFlags() & Renderer::ShowAutoMag) != 0)
03840     {
03841         renderer->setFaintestAM45deg(renderer->getFaintestAM45deg());
03842         setFaintestAutoMag();
03843     }
03844     else
03845     {
03846     renderer->setBrightnessBias(0.1f);
03847     renderer->setSaturationMagnitude(1.0f);
03848     }
03849 
03850     if (config->mainFont == "")
03851         font = LoadTextureFont("fonts/default.txf");
03852     else
03853         font = LoadTextureFont(string("fonts/") + config->mainFont);
03854     if (font == NULL)
03855     {
03856         cout << _("Error loading font; text will not be visible.\n");
03857     }
03858     font->buildTexture();
03859 
03860     if (config->titleFont != "")
03861         titleFont = LoadTextureFont(string("fonts") + "/" + config->titleFont);
03862     if (titleFont != NULL)
03863         titleFont->buildTexture();
03864     else
03865         titleFont = font;
03866 
03867     // Set up the overlay
03868     overlay = new Overlay();
03869     overlay->setWindowSize(width, height);
03870 
03871     if (config->labelFont == "")
03872     {
03873         renderer->setFont(font);
03874     }
03875     else
03876     {
03877         TextureFont* labelFont = LoadTextureFont(string("fonts") + "/" + config->labelFont);
03878         if (labelFont == NULL)
03879         {
03880             renderer->setFont(font);
03881         }
03882         else
03883         {
03884             labelFont->buildTexture();
03885             renderer->setFont(labelFont);
03886         }
03887     }
03888 
03889     if (config->logoTextureFile != "")
03890     {
03891         logoTexture = LoadTextureFromFile(string("textures") + "/" + config->logoTextureFile);
03892     }
03893 
03894     return true;
03895 }
03896 
03897 
03898 static void loadCrossIndex(StarDatabase* starDB,
03899                            StarDatabase::Catalog catalog,
03900                            const string& filename)
03901 {
03902     if (!filename.empty())
03903     {
03904         ifstream xrefFile(filename.c_str(), ios::in | ios::binary);
03905         if (xrefFile.good())
03906         {
03907             if (!starDB->loadCrossIndex(catalog, xrefFile))
03908                 cerr << _("Error reading cross index ") << filename << '\n';
03909             else
03910                 clog << _("Loaded cross index ") << filename << '\n';
03911         }
03912     }
03913 }
03914 
03915 
03916 bool CelestiaCore::readStars(const CelestiaConfig& cfg,
03917                              ProgressNotifier* progressNotifier)
03918 {
03919     StarDetails::InitializeStarTextures();
03920 
03921         
03922     ifstream starNamesFile(cfg.starNamesFile.c_str(), ios::in);
03923     if (!starNamesFile.good())
03924     {
03925         cerr << _("Error opening ") << cfg.starNamesFile << '\n';
03926         return false;
03927     }
03928 
03929     StarNameDatabase* starNameDB = StarNameDatabase::readNames(starNamesFile);
03930     if (starNameDB == NULL)
03931     {
03932         cerr << _("Error reading star names file\n");
03933         return false;
03934     }
03935 
03936     // First load the binary star database file.  The majority of stars
03937     // will be defined here.
03938     StarDatabase* starDB = new StarDatabase();
03939     if (!cfg.starDatabaseFile.empty())
03940     {
03941         if (progressNotifier)
03942             progressNotifier->update(cfg.starDatabaseFile);
03943             
03944         ifstream starFile(cfg.starDatabaseFile.c_str(), ios::in | ios::binary);
03945         if (!starFile.good())
03946         {
03947             cerr << _("Error opening ") << cfg.starDatabaseFile << '\n';
03948             delete starDB;
03949             return false;
03950         }
03951 
03952         if (!starDB->loadBinary(starFile))
03953         {
03954             delete starDB;
03955             cerr << _("Error reading stars file\n");
03956             return false;
03957         }
03958     }
03959 
03960     starDB->setNameDatabase(starNameDB);
03961 
03962     loadCrossIndex(starDB, StarDatabase::HenryDraper, cfg.HDCrossIndexFile);
03963     loadCrossIndex(starDB, StarDatabase::SAO,         cfg.SAOCrossIndexFile);
03964     loadCrossIndex(starDB, StarDatabase::Gliese,      cfg.GlieseCrossIndexFile);
03965 
03966     // Next, read any ASCII star catalog files specified in the StarCatalogs
03967     // list.
03968     if (!cfg.starCatalogFiles.empty())
03969     {
03970         for (vector<string>::const_iterator iter = config->starCatalogFiles.begin();
03971              iter != config->starCatalogFiles.end(); iter++)
03972         {
03973             if (*iter != "")
03974             {
03975                 ifstream starFile(iter->c_str(), ios::in);
03976                 if (starFile.good())
03977                 {
03978                     starDB->load(starFile, "");
03979                 }
03980                 else
03981                 {
03982                     cerr << _("Error opening star catalog ") << *iter << '\n';
03983                 }
03984             }
03985         }
03986     }
03987 
03988     // Now, read supplemental star files from the extras directories
03989     for (vector<string>::const_iterator iter = config->extrasDirs.begin();
03990          iter != config->extrasDirs.end(); iter++)
03991     {
03992         if (*iter != "")
03993         {
03994             Directory* dir = OpenDirectory(*iter);
03995 
03996             StarLoader loader(starDB, "star", Content_CelestiaStarCatalog, progressNotifier);
03997             loader.pushDir(*iter);
03998             dir->enumFiles(loader, true);
03999 
04000             delete dir;
04001         }
04002     }
04003 
04004     starDB->finish();
04005 
04006     universe->setStarCatalog(starDB);
04007 
04008     return true;
04009 }
04010 
04011 
04014 void CelestiaCore::setFaintest(float magnitude)
04015 {
04016     renderer->setBrightnessBias(0.1f);
04017     renderer->setSaturationMagnitude(1.0f);
04018     sim->setFaintestVisible(magnitude);
04019 }
04020 
04024 void CelestiaCore::setFaintestAutoMag()
04025 {
04026     float faintestMag;
04027     renderer->setBrightnessBias(0.1f);
04028     renderer->autoMag(faintestMag);
04029     sim->setFaintestVisible(faintestMag);
04030 }
04031 
04032 void CelestiaCore::fatalError(const string& msg)
04033 {
04034     if (alerter == NULL)
04035         cout << msg;
04036     else
04037         alerter->fatalError(msg);
04038 }
04039 
04040 
04041 void CelestiaCore::setAlerter(Alerter* a)
04042 {
04043     alerter = a;
04044 }
04045 
04046 CelestiaCore::Alerter* CelestiaCore::getAlerter() const
04047 {
04048     return alerter;
04049 }
04050 
04054 void CelestiaCore::setCursorHandler(CursorHandler* handler)
04055 {
04056     cursorHandler = handler;
04057 }
04058 
04059 CelestiaCore::CursorHandler* CelestiaCore::getCursorHandler() const
04060 {
04061     return cursorHandler;
04062 }
04063 
04064 int CelestiaCore::getTimeZoneBias() const
04065 {
04066     return timeZoneBias;
04067 }
04068 
04069 bool CelestiaCore::getLightDelayActive() const
04070 {
04071     return lightTravelFlag;
04072 }
04073 
04074 void CelestiaCore::setLightDelayActive(bool lightDelayActive)
04075 {
04076     lightTravelFlag = lightDelayActive;
04077 }
04078 
04079 void CelestiaCore::setTextEnterMode(int mode)
04080 {
04081     if (mode != textEnterMode)
04082     {
04083         if ((mode & KbAutoComplete) != (textEnterMode & KbAutoComplete))
04084         {
04085             typedText = "";
04086             typedTextCompletion.clear();
04087             typedTextCompletionIdx = -1;
04088         }
04089         textEnterMode = mode;
04090         notifyWatchers(TextEnterModeChanged);
04091     }
04092 }
04093 
04094 int CelestiaCore::getTextEnterMode() const
04095 {
04096     return textEnterMode;
04097 }
04098 
04099 void CelestiaCore::setScreenDpi(int dpi)
04100 {
04101     screenDpi = dpi;
04102     setFOVFromZoom();
04103     renderer->setScreenDpi(dpi);
04104 }
04105 
04106 int CelestiaCore::getScreenDpi() const
04107 {
04108     return screenDpi;
04109 }
04110 
04111 void CelestiaCore::setDistanceToScreen(int dts)
04112 {
04113     distanceToScreen = dts;
04114     setFOVFromZoom();
04115 }
04116 
04117 int CelestiaCore::getDistanceToScreen() const
04118 {
04119     return distanceToScreen;
04120 }
04121 
04122 void CelestiaCore::setTimeZoneBias(int bias)
04123 {
04124     timeZoneBias = bias;
04125     notifyWatchers(TimeZoneChanged);
04126 }
04127 
04128 
04129 string CelestiaCore::getTimeZoneName() const
04130 {
04131     return timeZoneName;
04132 }
04133 
04134 
04135 void CelestiaCore::setTimeZoneName(const string& zone)
04136 {
04137     timeZoneName = zone;
04138 }
04139 
04140 
04141 int CelestiaCore::getHudDetail()
04142 {
04143     return hudDetail;
04144 }
04145 
04146 void CelestiaCore::setHudDetail(int newHudDetail)
04147 {
04148     hudDetail = newHudDetail%3;
04149     notifyWatchers(VerbosityLevelChanged);
04150 }
04151 
04152 
04153 void CelestiaCore::initMovieCapture(MovieCapture* mc)
04154 {
04155     if (movieCapture == NULL)
04156         movieCapture = mc;
04157 }
04158 
04159 void CelestiaCore::recordBegin()
04160 {
04161     if (movieCapture != NULL)
04162         recording = true;
04163 }
04164 
04165 void CelestiaCore::recordPause()
04166 {
04167     recording = false;
04168 }
04169 
04170 void CelestiaCore::recordEnd()
04171 {
04172     if (movieCapture != NULL)
04173     {
04174         recordPause();
04175         movieCapture->end();
04176         delete movieCapture;
04177         movieCapture = NULL;
04178     }
04179 }
04180 
04181 bool CelestiaCore::isCaptureActive()
04182 {
04183     return movieCapture != NULL;
04184 }
04185 
04186 bool CelestiaCore::isRecording()
04187 {
04188     return recording;
04189 }
04190 
04191 void CelestiaCore::flash(const string& s, double duration)
04192 {
04193     if (hudDetail > 0)
04194         showText(s, -1, -1, 0, 5, duration);
04195 }
04196 
04197 
04198 CelestiaConfig* CelestiaCore::getConfig() const
04199 {
04200     return config;
04201 }
04202 
04203 
04204 void CelestiaCore::addWatcher(CelestiaWatcher* watcher)
04205 {
04206     assert(watcher != NULL);
04207     watchers.insert(watchers.end(), watcher);
04208 }
04209 
04210 void CelestiaCore::removeWatcher(CelestiaWatcher* watcher)
04211 {
04212     vector<CelestiaWatcher*>::iterator iter =
04213         find(watchers.begin(), watchers.end(), watcher);
04214     if (iter != watchers.end())
04215         watchers.erase(iter);
04216 }
04217 
04218 void CelestiaCore::notifyWatchers(int property)
04219 {
04220     for (vector<CelestiaWatcher*>::iterator iter = watchers.begin();
04221          iter != watchers.end(); iter++)
04222     {
04223         (*iter)->notifyChange(this, property);
04224     }
04225 }
04226 
04227 
04228 void CelestiaCore::goToUrl(const string& urlStr)
04229 {
04230     Url url(urlStr, this);
04231     url.goTo();
04232     timeScale = sim->getTimeScale();
04233     notifyWatchers(RenderFlagsChanged|LabelFlagsChanged);
04234 }
04235 
04236 
04237 void CelestiaCore::addToHistory()
04238 {
04239     Url url(this);
04240     if (!history.empty() && historyCurrent < history.size() - 1)
04241     {
04242         // truncating history to current position
04243         while (historyCurrent != history.size() - 1)
04244         {
04245             history.pop_back();
04246         }
04247     }
04248     history.push_back(url);
04249     historyCurrent = history.size() - 1;
04250     notifyWatchers(HistoryChanged);
04251 }
04252 
04253 
04254 void CelestiaCore::back()
04255 {
04256     if (historyCurrent == 0) return;
04257     if (historyCurrent == history.size() - 1)
04258     {
04259         addToHistory();
04260         historyCurrent = history.size()-1;
04261     }
04262     historyCurrent--;
04263     history[historyCurrent].goTo();
04264     timeScale = sim->getTimeScale();
04265     notifyWatchers(HistoryChanged|RenderFlagsChanged|LabelFlagsChanged);
04266 }
04267 
04268 
04269 void CelestiaCore::forward()
04270 {
04271     if (historyCurrent == history.size()-1) return;
04272     historyCurrent++;
04273     history[historyCurrent].goTo();
04274     timeScale = sim->getTimeScale();
04275     notifyWatchers(HistoryChanged|RenderFlagsChanged|LabelFlagsChanged);
04276 }
04277 
04278 
04279 const vector<Url>& CelestiaCore::getHistory() const
04280 {
04281     return history;
04282 }
04283 
04284 vector<Url>::size_type CelestiaCore::getHistoryCurrent() const
04285 {
04286     return historyCurrent;
04287 }
04288 
04289 void CelestiaCore::setHistoryCurrent(vector<Url>::size_type curr)
04290 {
04291     if (curr >= history.size()) return;
04292     if (historyCurrent == history.size()) {
04293         addToHistory();
04294     }
04295     historyCurrent = curr;
04296     history[curr].goTo();
04297     notifyWatchers(HistoryChanged|RenderFlagsChanged|LabelFlagsChanged);
04298 }

Generated on Sat Jan 14 22:30:30 2006 for Celestia by  doxygen 1.4.1