1 // celestiacore.cpp
2 //
3 // Platform-independent UI handling and initialization for Celestia.
4 // winmain, gtkmain, and glutmain are thin, platform-specific modules
5 // that sit directly on top of CelestiaCore and feed it mouse and
6 // keyboard events.  CelestiaCore then turns those events into calls
7 // to Renderer and Simulation.
8 //
9 // Copyright (C) 2001-2009, the Celestia Development Team
10 //
11 // This program is free software; you can redistribute it and/or
12 // modify it under the terms of the GNU General Public License
13 // as published by the Free Software Foundation; either version 2
14 // of the License, or (at your option) any later version.
15 
16 #include <cstdio>
17 #include <iostream>
18 #include <fstream>
19 #include <iomanip>
20 #include <algorithm>
21 #include <cstdlib>
22 #include <cctype>
23 #include <cstring>
24 #include <cassert>
25 #include <ctime>
26 #include <celengine/gl.h>
27 #include <celmath/vecmath.h>
28 #include <celmath/quaternion.h>
29 #include <celmath/mathlib.h>
30 #include <celutil/util.h>
31 #include <celutil/filetype.h>
32 #include <celutil/directory.h>
33 #include <celutil/formatnum.h>
34 #include <celengine/astro.h>
35 #include <celengine/overlay.h>
36 #include <celengine/console.h>
37 #include <celengine/execution.h>
38 #include <celengine/cmdparser.h>
39 #include <celengine/multitexture.h>
40 #include <celengine/spiceinterface.h>
41 #include <celengine/axisarrow.h>
42 #include <celengine/planetgrid.h>
43 #include <celengine/visibleregion.h>
44 #include "favorites.h"
45 #include "celestiacore.h"
46 #include <celutil/debug.h>
47 #include <celutil/utf8.h>
48 #include "url.h"
49 
50 #ifdef CELX
51 #include <celengine/scriptobject.h>
52 #endif
53 
54 #ifdef _WIN32
55 #define TIMERATE_PRINTF_FORMAT "%.12g"
56 #else
57 #define TIMERATE_PRINTF_FORMAT "%'.12g"
58 #endif
59 
60 using namespace std;
61 
62 static const int DragThreshold = 3;
63 
64 // Perhaps you'll want to put this stuff in configuration file.
65 static const double CoarseTimeScaleFactor = 10.0;
66 static const double FineTimeScaleFactor = 2.0;
67 static const double fMinSlewRate = 3.0;
68 static const double fMaxKeyAccel = 20.0;
69 static const float fAltitudeThreshold = 4.0f;
70 static const float RotationBraking = 10.0f;
71 static const float RotationDecay = 2.0f;
72 static const double MaximumTimeRate = 1.0e15;
73 static const double MinimumTimeRate = 1.0e-15;
74 static const float stdFOV = degToRad(45.0f);
75 static const float MaximumFOV = degToRad(120.0f);
76 static const float MinimumFOV = degToRad(0.001f);
77 static float KeyRotationAccel = degToRad(120.0f);
78 static float MouseRotationSensitivity = degToRad(1.0f);
79 
80 static const int ConsolePageRows = 10;
81 static Console console(200, 120);
82 
83 
warning(string s)84 static void warning(string s)
85 {
86     cout << s;
87 }
88 
89 
90 struct OverlayImage
91 {
92     Texture* texture;
93     int xSize;
94     int ySize;
95     int left;
96     int bottom;
97 };
98 
99 vector<OverlayImage> overlayImages;
100 
101 
102 // Extremely basic implementation of an ExecutionEnvironment for
103 // running scripts.
104 class CoreExecutionEnvironment : public ExecutionEnvironment
105 {
106 private:
107     CelestiaCore& core;
108 
109 public:
CoreExecutionEnvironment(CelestiaCore & _core)110     CoreExecutionEnvironment(CelestiaCore& _core) : core(_core)
111     {
112     }
113 
getSimulation() const114     Simulation* getSimulation() const
115     {
116         return core.getSimulation();
117     }
118 
getRenderer() const119     Renderer* getRenderer() const
120     {
121         return core.getRenderer();
122     }
123 
getCelestiaCore() const124     CelestiaCore* getCelestiaCore() const
125     {
126         return &core;
127     }
128 
showText(string s,int horig,int vorig,int hoff,int voff,double duration)129     void showText(string s, int horig, int vorig, int hoff, int voff,
130                   double duration)
131     {
132         core.showText(s, horig, vorig, hoff, voff, duration);
133     }
134 };
135 
136 
137 // If right dragging to rotate, adjust the rotation rate based on the
138 // distance from the reference object.  This makes right drag rotation
139 // useful even when the camera is very near the surface of an object.
140 // Disable adjustments if the reference is a deep sky object, since they
141 // have no true surface (and the observer is likely to be inside one.)
ComputeRotationCoarseness(Simulation & sim)142 float ComputeRotationCoarseness(Simulation& sim)
143 {
144     float coarseness = 1.5f;
145 
146     Selection selection = sim.getActiveObserver()->getFrame()->getRefObject();
147     if (selection.getType() == Selection::Type_Star ||
148         selection.getType() == Selection::Type_Body)
149     {
150         double radius = selection.radius();
151         double t = sim.getTime();
152         UniversalCoord observerPosition = sim.getActiveObserver()->getPosition();
153         UniversalCoord selectionPosition = selection.getPosition(t);
154         double distance = astro::microLightYearsToKilometers(observerPosition.distanceTo(selectionPosition));
155         double altitude = distance - radius;
156         if (altitude > 0.0 && altitude < radius)
157         {
158             coarseness *= (float) max(0.01, altitude / radius);
159         }
160     }
161 
162     return coarseness;
163 }
164 
165 
View(View::Type _type,Observer * _observer,float _x,float _y,float _width,float _height)166 View::View(View::Type _type,
167            Observer* _observer,
168            float _x, float _y,
169            float _width, float _height) :
170     type(_type),
171     observer(_observer),
172     parent(0),
173     child1(0),
174     child2(0),
175     x(_x),
176     y(_y),
177     width(_width),
178     height(_height),
179     renderFlags(0),
180     labelMode(0),
181     zoom(1),
182     alternateZoom(1)
183 {
184 }
185 
mapWindowToView(float wx,float wy,float & vx,float & vy) const186 void View::mapWindowToView(float wx, float wy, float& vx, float& vy) const
187 {
188     vx = (wx - x) / width;
189     vy = (wy + (y + height - 1)) / height;
190     vx = (vx - 0.5f) * (width / height);
191     vy = 0.5f - vy;
192 }
193 
walkTreeResize(View * sibling,int sign)194 void View::walkTreeResize(View* sibling, int sign) {
195    float ratio;
196    switch (parent->type)
197     {
198     case View::HorizontalSplit:
199         ratio = parent->height / (parent->height -  height);
200         sibling->height *= ratio;
201         if (sign == 1)
202         {
203             sibling->y = parent->y + (sibling->y - parent->y) * ratio;
204         }
205         else
206         {
207             sibling->y = parent->y + (sibling->y - (y + height)) * ratio;
208         }
209         break;
210 
211     case View::VerticalSplit:
212         ratio = parent->width / (parent->width - width);
213         sibling->width *= ratio;
214         if (sign == 1)
215         {
216             sibling->x = parent->x + (sibling->x - parent->x) * ratio;
217         }
218         else
219         {
220             sibling->x = parent->x + (sibling->x - (x + width) ) * ratio;
221         }
222         break;
223     case View::ViewWindow:
224         break;
225     }
226     if (sibling->child1) walkTreeResize(sibling->child1, sign);
227     if (sibling->child2) walkTreeResize(sibling->child2, sign);
228 }
229 
walkTreeResizeDelta(View * v,float delta,bool check)230 bool View::walkTreeResizeDelta(View* v, float delta, bool check)
231 {
232    View *p=v;
233    int sign = -1;
234    float ratio;
235    double newSize;
236 
237    if (v->child1)
238    {
239        if (!walkTreeResizeDelta(v->child1, delta, check))
240            return false;
241    }
242 
243    if (v->child2)
244    {
245        if (!walkTreeResizeDelta(v->child2, delta, check))
246            return false;
247    }
248 
249    while ( p != child1 && p != child2 && (p = p->parent) ) ;
250    if (p == child1) sign = 1;
251    switch (type)
252     {
253     case View::HorizontalSplit:
254         delta = -delta;
255         ratio = (p->height  + sign * delta) / p->height;
256         newSize = v->height * ratio;
257         if (newSize <= .1) return false;
258         if (check) return true;
259         v->height = (float) newSize;
260         if (sign == 1)
261         {
262             v->y = p->y + (v->y - p->y) * ratio;
263         }
264         else
265         {
266             v->y = p->y + delta + (v->y - p->y) * ratio;
267         }
268         break;
269 
270     case View::VerticalSplit:
271         ratio = (p->width + sign * delta) / p->width;
272         newSize = v->width * ratio;
273         if (newSize <= .1) return false;
274         if (check) return true;
275         v->width = (float) newSize;
276         if (sign == 1)
277         {
278             v->x = p->x + (v->x - p->x) * ratio;
279         }
280         else
281         {
282             v->x = p->x + delta + (v->x - p->x) * ratio;
283         }
284         break;
285     case View::ViewWindow:
286         break;
287     }
288 
289     return true;
290 }
291 
292 
CelestiaCore()293 CelestiaCore::CelestiaCore() :
294     config(NULL),
295     universe(NULL),
296     favorites(NULL),
297     destinations(NULL),
298     sim(NULL),
299     renderer(NULL),
300     overlay(NULL),
301     width(1),
302     height(1),
303     font(NULL),
304     titleFont(NULL),
305     messageText(""),
306     messageHOrigin(0),
307     messageVOrigin(0),
308     messageHOffset(0),
309     messageVOffset(0),
310     messageStart(0.0),
311     messageDuration(0.0),
312     textColor(Color(1.0f, 1.0f, 1.0f)),
313     typedText(""),
314     typedTextCompletionIdx(-1),
315     textEnterMode(KbNormal),
316     hudDetail(1),
317     dateFormat(astro::Date::Locale),
318     dateStrWidth(0),
319     overlayElements(ShowTime | ShowVelocity | ShowSelection | ShowFrame),
320     wireframe(false),
321     editMode(false),
322     altAzimuthMode(false),
323     showConsole(false),
324     lightTravelFlag(false),
325     flashFrameStart(0.0),
326     timer(NULL),
327     runningScript(NULL),
328     execEnv(NULL),
329 #ifdef CELX
330     celxScript(NULL),
331     luaHook(NULL),
332     luaSandbox(NULL),
333 #endif // CELX
334     scriptState(ScriptCompleted),
335     timeZoneBias(0),
336     showFPSCounter(false),
337     nFrames(0),
338     fps(0.0),
339     fpsCounterStartTime(0.0),
340     oldFOV(stdFOV),
341     mouseMotion(0.0f),
342     dollyMotion(0.0),
343     dollyTime(0.0),
344     zoomMotion(0.0),
345     zoomTime(0.0),
346     sysTime(0.0),
347     currentTime(0.0),
348     viewChanged(true),
349     joystickRotation(0.0f, 0.0f, 0.0f),
350     KeyAccel(1.0),
351     movieCapture(NULL),
352     recording(false),
353     contextMenuCallback(NULL),
354     logoTexture(NULL),
355     alerter(NULL),
356     cursorHandler(NULL),
357     defaultCursorShape(CelestiaCore::CrossCursor),
358     historyCurrent(0),
359     activeView(views.begin()),
360     showActiveViewFrame(false),
361     showViewFrames(true),
362     resizeSplit(0),
363     screenDpi(96),
364     distanceToScreen(400)
365 {
366     /* Get a renderer here so it may be queried for capabilities of the
367        underlying engine even before rendering is enabled. It's initRenderer()
368        routine will be called much later. */
369     renderer = new Renderer();
370     timer = CreateTimer();
371 
372     execEnv = new CoreExecutionEnvironment(*this);
373 
374     int i;
375     for (i = 0; i < KeyCount; i++)
376     {
377         keysPressed[i] = false;
378         shiftKeysPressed[i] = false;
379     }
380     for (i = 0; i < JoyButtonCount; i++)
381         joyButtonsPressed[i] = false;
382 
383     clog.rdbuf(console.rdbuf());
384     cerr.rdbuf(console.rdbuf());
385     console.setWindowHeight(ConsolePageRows);
386 }
387 
~CelestiaCore()388 CelestiaCore::~CelestiaCore()
389 {
390     if (movieCapture != NULL)
391         recordEnd();
392 
393 #ifdef CELX
394     // Clean up all scripts
395     if (celxScript != NULL)
396         delete celxScript;
397     if (luaHook != NULL)
398         delete luaHook;
399     if (luaSandbox != NULL)
400         delete luaSandbox;
401 #endif
402 
403     delete execEnv;
404 
405 
406 }
407 
readFavoritesFile()408 void CelestiaCore::readFavoritesFile()
409 {
410     // Set up favorites list
411     if (config->favoritesFile != "")
412     {
413         ifstream in(config->favoritesFile.c_str(), ios::in);
414 
415         if (in.good())
416         {
417             favorites = ReadFavoritesList(in);
418             if (favorites == NULL)
419             {
420                 warning(_("Error reading favorites file."));
421             }
422         }
423     }
424 }
425 
writeFavoritesFile()426 void CelestiaCore::writeFavoritesFile()
427 {
428     if (config->favoritesFile != "")
429     {
430         ofstream out(config->favoritesFile.c_str(), ios::out);
431         if (out.good())
432         WriteFavoritesList(*favorites, out);
433     }
434 }
435 
activateFavorite(FavoritesEntry & fav)436 void CelestiaCore::activateFavorite(FavoritesEntry& fav)
437 {
438     sim->cancelMotion();
439     sim->setTime(fav.jd);
440     sim->setObserverPosition(fav.position);
441     sim->setObserverOrientation(fav.orientation);
442     if (fav.fov != 0.0)
443     {
444         sim->getActiveObserver()->setFOV(fav.fov);
445         setZoomFromFOV();
446     }
447     sim->setSelection(sim->findObjectFromPath(fav.selectionName));
448     sim->setFrame(fav.coordSys, sim->getSelection());
449 }
450 
addFavorite(string name,string parentFolder,FavoritesList::iterator * iter)451 void CelestiaCore::addFavorite(string name, string parentFolder, FavoritesList::iterator* iter)
452 {
453     FavoritesList::iterator pos;
454     if(!iter)
455         pos = favorites->end();
456     else
457         pos = *iter;
458     FavoritesEntry* fav = new FavoritesEntry();
459     fav->jd = sim->getTime();
460     fav->position = sim->getObserver().getPosition();
461     fav->orientation = sim->getObserver().getOrientationf();
462     fav->fov = sim->getObserver().getFOV();
463     fav->name = name;
464     fav->isFolder = false;
465     fav->parentFolder = parentFolder;
466 
467     Selection sel = sim->getSelection();
468     if (sel.deepsky() != NULL)
469         fav->selectionName = sim->getUniverse()->getDSOCatalog()->getDSOName(sel.deepsky());
470     else
471         fav->selectionName = sel.getName();
472 
473     fav->coordSys = sim->getFrame()->getCoordinateSystem();
474 
475     favorites->insert(pos, fav);
476 }
477 
addFavoriteFolder(string name,FavoritesList::iterator * iter)478 void CelestiaCore::addFavoriteFolder(string name, FavoritesList::iterator* iter)
479 {
480     FavoritesList::iterator pos;
481     if(!iter)
482         pos = favorites->end();
483     else
484         pos = *iter;
485     FavoritesEntry* fav = new FavoritesEntry();
486     fav->name = name;
487     fav->isFolder = true;
488 
489     favorites->insert(pos, fav);
490 }
491 
getFavorites()492 FavoritesList* CelestiaCore::getFavorites()
493 {
494     return favorites;
495 }
496 
497 
getDestinations()498 const DestinationList* CelestiaCore::getDestinations()
499 {
500     return destinations;
501 }
502 
503 
504 // Used in the super-secret edit mode
showSelectionInfo(const Selection & sel)505 void showSelectionInfo(const Selection& sel)
506 {
507     Vec3f axis(0.0f, 1.0, 0.0f);
508     float angle = 0.0f;
509 
510     if (sel.deepsky() != NULL)
511         sel.deepsky()->getOrientation().getAxisAngle(axis, angle);
512     else if (sel.body() != NULL)
513         sel.body()->getOrientation().getAxisAngle(axis, angle);
514 
515     cout << sel.getName() << '\n';
516     cout << _("Orientation: ") << '[' << axis.x << ',' << axis.y << ',' << axis.z << "], " << radToDeg(angle) << '\n';
517 }
518 
519 
cancelScript()520 void CelestiaCore::cancelScript()
521 {
522     if (runningScript != NULL)
523     {
524         delete runningScript;
525         scriptState = ScriptCompleted;
526         runningScript = NULL;
527     }
528 #ifdef CELX
529     if (celxScript != NULL)
530     {
531         celxScript->cleanup();
532         if (textEnterMode & KbPassToScript)
533             setTextEnterMode(textEnterMode & ~KbPassToScript);
534         scriptState = ScriptCompleted;
535     }
536 #endif
537 }
538 
539 
runScript(CommandSequence * script)540 void CelestiaCore::runScript(CommandSequence* script)
541 {
542     cancelScript();
543     if (runningScript == NULL && script != NULL && scriptState == ScriptCompleted)
544     {
545         scriptState = ScriptRunning;
546         runningScript = new Execution(*script, *execEnv);
547     }
548 }
549 
550 
runScript(const string & filename)551 void CelestiaCore::runScript(const string& filename)
552 {
553     cancelScript();
554     string localeFilename = LocaleFilename(filename);
555     ContentType type = DetermineFileType(localeFilename);
556 
557     if (type == Content_CelestiaLegacyScript)
558     {
559         ifstream scriptfile(localeFilename.c_str());
560         if (!scriptfile.good())
561         {
562             if (alerter != NULL)
563                 alerter->fatalError(_("Error opening script file."));
564             else
565                 flash(_("Error opening script file."));
566         }
567         else
568         {
569             CommandParser parser(scriptfile);
570             CommandSequence* script = parser.parse();
571             if (script == NULL)
572             {
573                 const vector<string>* errors = parser.getErrors();
574                 const char* errorMsg = "";
575                 if (errors->size() > 0)
576                     errorMsg = (*errors)[0].c_str();
577                 if (alerter != NULL)
578                     alerter->fatalError(errorMsg);
579                 else
580                     flash(errorMsg);
581             }
582             else
583             {
584                 runningScript = new Execution(*script, *execEnv);
585                 scriptState = sim->getPauseState()?ScriptPaused:ScriptRunning;
586             }
587         }
588     }
589 #ifdef CELX
590     else if (type == Content_CelestiaScript)
591     {
592         ifstream scriptfile(localeFilename.c_str());
593         if (!scriptfile.good())
594         {
595             char errMsg[1024];
596             sprintf(errMsg, _("Error opening script '%s'"), localeFilename.c_str());
597             if (alerter != NULL)
598                 alerter->fatalError(errMsg);
599             else
600                 flash(errMsg);
601         }
602 
603         if (celxScript == NULL)
604         {
605             celxScript = new LuaState();
606             celxScript->init(this);
607         }
608 
609         int status = celxScript->loadScript(scriptfile, localeFilename);
610         if (status != 0)
611         {
612             string errMsg = celxScript->getErrorMessage();
613             if (errMsg.empty())
614                 errMsg = _("Unknown error opening script");
615             if (alerter != NULL)
616                 alerter->fatalError(errMsg);
617             else
618                 flash(errMsg);
619         }
620         else
621         {
622             // Coroutine execution; control may be transferred between the
623             // script and Celestia's event loop
624             if (!celxScript->createThread())
625             {
626                 const char* errMsg = _("Script coroutine initialization failed");
627                 if (alerter != NULL)
628                     alerter->fatalError(errMsg);
629                 else
630                     flash(errMsg);
631             }
632             else
633             {
634                 scriptState = sim->getPauseState()?ScriptPaused:ScriptRunning;
635             }
636         }
637     }
638 #endif
639     else
640     {
641         if (alerter != NULL)
642             alerter->fatalError(_("Invalid filetype"));
643         else
644             flash(_("Invalid filetype"));
645     }
646 }
647 
648 
checkMask(int modifiers,int mask)649 bool checkMask(int modifiers, int mask)
650 {
651     return (modifiers & mask) == mask;
652 }
653 
mouseButtonDown(float x,float y,int button)654 void CelestiaCore::mouseButtonDown(float x, float y, int button)
655 {
656     setViewChanged();
657 
658     mouseMotion = 0.0f;
659 
660 #ifdef CELX
661     if (celxScript != NULL)
662     {
663         if (celxScript->handleMouseButtonEvent(x, y, button, true))
664             return;
665     }
666 
667     if (luaHook && luaHook->callLuaHook(this, "mousebuttondown", x, y, button))
668         return;
669 #endif
670 
671     if (views.size() > 1)
672     {
673         // To select the clicked into view before a drag.
674         pickView(x, y);
675     }
676 
677     if (views.size() > 1 && button == LeftButton) // look if click is near a view border
678     {
679         View *v1 = 0, *v2 = 0;
680         for (list<View*>::iterator i = views.begin(); i != views.end(); i++)
681         {
682             View* v = *i;
683             if (v->type == View::ViewWindow)
684             {
685                 float vx, vy, vxp, vyp;
686                 vx = ( x / width - v->x ) / v->width;
687                 vy = ( (1 - y / height ) - v->y ) / v->height;
688                 vxp = vx * v->width * width;
689                 vyp = vy * v->height * height;
690                 if ( (vx >=0 && vx <= 1 && ( abs(vyp) <= 2 || abs(vyp - v->height * height) <= 2))
691                   || (vy >=0 && vy <= 1 && ( abs(vxp) <= 2 || abs(vxp - v->width * width) <= 2)) )
692                 {
693                     if (v1 == 0)
694                     {
695                         v1 = v;
696                     }
697                     else
698                     {
699                         v2 = v;
700                         break;
701                     }
702                 }
703             }
704         }
705         if (v2 != 0) {
706              // Look for common ancestor to v1 & v2 = split being draged.
707              View *p1 = v1, *p2 = v2;
708              while ( (p1 = p1->parent) )
709              {
710                  p2 = v2;
711                  while ( (p2 = p2->parent) && p1 != p2) ;
712                  if (p2 != 0) break;
713              }
714              if (p2 != 0)
715              {
716                  resizeSplit = p1;
717              }
718         }
719     }
720 
721 }
722 
mouseButtonUp(float x,float y,int button)723 void CelestiaCore::mouseButtonUp(float x, float y, int button)
724 {
725     setViewChanged();
726 
727     // Four pixel tolerance for picking
728     float pickTolerance = sim->getActiveObserver()->getFOV() / height * 4.0f;
729 
730     if (resizeSplit)
731     {
732         resizeSplit = 0;
733         return;
734     }
735 
736 #ifdef CELX
737     if (celxScript != NULL)
738     {
739         if (celxScript->handleMouseButtonEvent(x, y, button, false))
740             return;
741     }
742 
743     if (luaHook && luaHook->callLuaHook(this,"mousebuttonup", x, y, button))
744         return;
745 #endif
746 
747     // If the mouse hasn't moved much since it was pressed, treat this
748     // as a selection or context menu event.  Otherwise, assume that the
749     // mouse was dragged and ignore the event.
750     if (mouseMotion < DragThreshold)
751     {
752         if (button == LeftButton)
753         {
754             pickView(x, y);
755 
756             float pickX, pickY;
757             float aspectRatio = ((float) width / (float) height);
758             (*activeView)->mapWindowToView((float) x / (float) width,
759                                         (float) y / (float) height,
760                                         pickX, pickY);
761             Vec3f pickRay =
762                 sim->getActiveObserver()->getPickRay(pickX * aspectRatio, pickY);
763 
764             Selection oldSel = sim->getSelection();
765             Selection newSel = sim->pickObject(pickRay, renderer->getRenderFlags(), pickTolerance);
766             addToHistory();
767             sim->setSelection(newSel);
768             if (!oldSel.empty() && oldSel == newSel)
769                 sim->centerSelection();
770         }
771         else if (button == RightButton)
772         {
773             float pickX, pickY;
774             float aspectRatio = ((float) width / (float) height);
775             (*activeView)->mapWindowToView((float) x / (float) width,
776                                         (float) y / (float) height,
777                                         pickX, pickY);
778             Vec3f pickRay =
779                 sim->getActiveObserver()->getPickRay(pickX * aspectRatio, pickY);
780 
781             Selection sel = sim->pickObject(pickRay, renderer->getRenderFlags(), pickTolerance);
782             if (!sel.empty())
783             {
784                 if (contextMenuCallback != NULL)
785                     contextMenuCallback(x, y, sel);
786             }
787         }
788         else if (button == MiddleButton)
789 	    {
790             if ((*activeView)->zoom != 1)
791 	        {
792                 (*activeView)->alternateZoom = (*activeView)->zoom;
793                 (*activeView)->zoom = 1;
794             }
795             else
796             {
797                 (*activeView)->zoom = (*activeView)->alternateZoom;
798             }
799             setFOVFromZoom();
800 
801             // If AutoMag, adapt the faintestMag to the new fov
802             if((renderer->getRenderFlags() & Renderer::ShowAutoMag) != 0)
803 	        setFaintestAutoMag();
804 	    }
805     }
806 }
807 
mouseWheel(float motion,int modifiers)808 void CelestiaCore::mouseWheel(float motion, int modifiers)
809 {
810     setViewChanged();
811 
812     if (config->reverseMouseWheel) motion = -motion;
813     if (motion != 0.0)
814     {
815         if ((modifiers & ShiftKey) != 0)
816         {
817             zoomTime = currentTime;
818             zoomMotion = 0.25f * motion;
819         }
820         else
821         {
822             dollyTime = currentTime;
823             dollyMotion = 0.25f * motion;
824         }
825     }
826 }
827 
828 /// Handles cursor shape changes on view borders if the cursorHandler is defined.
829 /// This must be called on mouse move events on the OpenGL Widget.
830 /// x and y are the pixel coordinates relative to the widget.
mouseMove(float x,float y)831 void CelestiaCore::mouseMove(float x, float y)
832 {
833 #ifdef CELX
834     if (luaHook && luaHook->callLuaHook(this, "mousemove", x, y))
835         return;
836 #endif
837 
838     if (views.size() > 1 && cursorHandler != NULL)
839     {
840         /*View* v1 = 0;     Unused*/
841         /*View* v2 = 0;     Unused*/
842 
843         for (list<View*>::iterator i = views.begin(); i != views.end(); i++)
844         {
845             View* v = *i;
846             if (v->type == View::ViewWindow)
847             {
848                 float vx, vy, vxp, vyp;
849                 vx = (x / width - v->x) / v->width;
850                 vy = ((1 - y / height) - v->y ) / v->height;
851                 vxp = vx * v->width * width;
852                 vyp = vy * v->height * height;
853 
854                 if (vx >=0 && vx <= 1 && (abs(vyp) <= 2 || abs(vyp - v->height * height) <= 2))
855                 {
856                     cursorHandler->setCursorShape(CelestiaCore::SizeVerCursor);
857                     return;
858                 }
859                 else if (vy >=0 && vy <= 1 && (abs(vxp) <= 2 || abs(vxp - v->width * width) <= 2))
860                 {
861                     cursorHandler->setCursorShape(CelestiaCore::SizeHorCursor);
862                     return;
863                 }
864             }
865         }
866         cursorHandler->setCursorShape(defaultCursorShape);
867     }
868     return;
869 }
870 
mouseMove(float dx,float dy,int modifiers)871 void CelestiaCore::mouseMove(float dx, float dy, int modifiers)
872 {
873     if (modifiers != 0)
874         setViewChanged();
875 
876     if (resizeSplit != 0)
877     {
878         switch(resizeSplit->type) {
879         case View::HorizontalSplit:
880             if (   resizeSplit->walkTreeResizeDelta(resizeSplit->child1, dy / height, true)
881                 && resizeSplit->walkTreeResizeDelta(resizeSplit->child2, dy / height, true))
882             {
883                 resizeSplit->walkTreeResizeDelta(resizeSplit->child1, dy / height, false);
884                 resizeSplit->walkTreeResizeDelta(resizeSplit->child2, dy / height, false);
885             }
886             break;
887         case View::VerticalSplit:
888             if (   resizeSplit->walkTreeResizeDelta(resizeSplit->child1, dx / width, true)
889                 && resizeSplit->walkTreeResizeDelta(resizeSplit->child2, dx / width, true)
890             )
891             {
892                 resizeSplit->walkTreeResizeDelta(resizeSplit->child1, dx / width, false);
893                 resizeSplit->walkTreeResizeDelta(resizeSplit->child2, dx / width, false);
894             }
895             break;
896         case View::ViewWindow:
897             break;
898         }
899         setFOVFromZoom();
900         return;
901     }
902 
903 #ifdef CELX
904 	if (luaHook &&
905             luaHook->callLuaHook(this,"mousebuttonmove", dx, dy, modifiers))
906         {
907             return;
908         }
909 #endif
910 
911     if ((modifiers & (LeftButton | RightButton)) != 0)
912     {
913         if (editMode && checkMask(modifiers, LeftButton | ShiftKey | ControlKey))
914         {
915             // Rotate the selected object
916             Selection sel = sim->getSelection();
917             Quatf q(1);
918             if (sel.getType() == Selection::Type_DeepSky)
919                 q = sel.deepsky()->getOrientation();
920             else if (sel.getType() == Selection::Type_Body)
921                 q = sel.body()->getOrientation();
922 
923             q.yrotate(dx / width);
924             q.xrotate(dy / height);
925 
926             if (sel.getType() == Selection::Type_DeepSky)
927                 sel.deepsky()->setOrientation(q);
928             else if (sel.getType() == Selection::Type_Body)
929                 sel.body()->setOrientation(q);
930         }
931         else if (editMode && checkMask(modifiers, RightButton | ShiftKey | ControlKey))
932         {
933             // Rotate the selected object about an axis from its center to the
934             // viewer.
935             Selection sel = sim->getSelection();
936             if (sel.deepsky() != NULL)
937             {
938                 double t = sim->getTime();
939                 Vec3d v = sel.getPosition(t) - sim->getObserver().getPosition();
940                 Vec3f axis((float) v.x, (float) v.y, (float) v.z);
941                 axis.normalize();
942 
943                 Quatf r;
944                 r.setAxisAngle(axis, dx / width);
945 
946                 Quatf q = sel.deepsky()->getOrientation();
947                 sel.deepsky()->setOrientation(r * q);
948             }
949         }
950         else if (checkMask(modifiers, LeftButton | RightButton) ||
951                  checkMask(modifiers, LeftButton | ControlKey))
952         {
953             // Y-axis controls distance (exponentially), and x-axis motion
954             // rotates the camera about the view normal.
955             float amount = dy / height;
956             sim->changeOrbitDistance(amount * 5);
957             if (dx * dx > dy * dy)
958             {
959                 Observer& observer = sim->getObserver();
960                 Vec3d v = Vec3d(0, 0, dx * -MouseRotationSensitivity);
961 
962                 Quatd obsOrientation = observer.getOrientation();
963                 Quatd dr = 0.5 * (v * obsOrientation);
964                 obsOrientation += dr;
965                 obsOrientation.normalize();
966                 observer.setOrientation(obsOrientation);
967             }
968         }
969         else if (checkMask(modifiers, LeftButton | ShiftKey))
970         {
971             // Mouse zoom control
972             float amount = dy / height;
973             float minFOV = MinimumFOV;
974             float maxFOV = MaximumFOV;
975             float fov = sim->getActiveObserver()->getFOV();
976 
977             // In order for the zoom to have the right feel, it should be
978             // exponential.
979             float newFOV = minFOV + (float) exp(log(fov - minFOV) + amount * 4);
980             if (newFOV > maxFOV)
981                 newFOV = maxFOV;
982             if (newFOV > minFOV)
983             {
984                 sim->getActiveObserver()->setFOV(newFOV);
985                 setZoomFromFOV();
986             }
987 
988     	    if ((renderer->getRenderFlags() & Renderer::ShowAutoMag))
989 	        {
990 	            setFaintestAutoMag();
991     		    char buf[128];
992         		sprintf(buf, _("Magnitude limit: %.2f"), sim->getFaintestVisible());
993         		flash(buf);
994 	        }
995         }
996         else
997         {
998             Quatf q(1);
999             // For a small field of view, rotate the camera more finely
1000             float coarseness = 1.5f;
1001             if ((modifiers & RightButton) == 0)
1002             {
1003                 coarseness = radToDeg(sim->getActiveObserver()->getFOV()) / 30.0f;
1004             }
1005             else
1006             {
1007                 // If right dragging to rotate, adjust the rotation rate
1008                 // based on the distance from the reference object.
1009                 coarseness = ComputeRotationCoarseness(*sim);
1010             }
1011             q.yrotate(dx / width * coarseness);
1012             q.xrotate(dy / height * coarseness);
1013             if ((modifiers & RightButton) != 0)
1014                 sim->orbit(q);
1015             else
1016                 sim->rotate(~q);
1017         }
1018 
1019         mouseMotion += abs(dy) + abs(dx);
1020     }
1021 }
1022 
1023 /// Makes the view under x, y the active view.
pickView(float x,float y)1024 void CelestiaCore::pickView(float x, float y)
1025 {
1026     if (x+2 < (*activeView)->x * width || x-2 > ((*activeView)->x + (*activeView)->width) * width
1027         || (height - y)+2 < (*activeView)->y * height ||  (height - y)-2 > ((*activeView)->y + (*activeView)->height) * height)
1028     {
1029         activeView = views.begin();
1030         while ( (activeView != views.end())
1031                 &&
1032                 ( (x+2 < (*activeView)->x * width || x-2 > ((*activeView)->x + (*activeView)->width) * width || (height - y)+2 < (*activeView)->y * height ||  (height - y)-2 > ((*activeView)->y + (*activeView)->height) * height)
1033                   ||
1034                   ((*activeView)->type != View::ViewWindow)
1035                 )
1036               )
1037         {
1038                 activeView++;
1039         }
1040 
1041         // Make sure that we're left with a valid view
1042         if (activeView == views.end())
1043         {
1044             activeView = views.begin();
1045         }
1046 
1047         sim->setActiveObserver((*activeView)->observer);
1048         if (!showActiveViewFrame)
1049             flashFrameStart = currentTime;
1050         return;
1051     }
1052 }
1053 
joystickAxis(int axis,float amount)1054 void CelestiaCore::joystickAxis(int axis, float amount)
1055 {
1056     setViewChanged();
1057 
1058     float deadZone = 0.25f;
1059 
1060     if (abs(amount) < deadZone)
1061         amount = 0.0f;
1062     else
1063         amount = (amount - deadZone) * (1.0f / (1.0f - deadZone));
1064 
1065     amount = sign(amount) * square(amount);
1066 
1067     if (axis == Joy_XAxis)
1068         joystickRotation.y = amount;
1069     else if (axis == Joy_YAxis)
1070         joystickRotation.x = -amount;
1071 }
1072 
1073 
joystickButton(int button,bool down)1074 void CelestiaCore::joystickButton(int button, bool down)
1075 {
1076     setViewChanged();
1077 
1078     if (button >= 0 && button < JoyButtonCount)
1079         joyButtonsPressed[button] = down;
1080 }
1081 
1082 
scrollConsole(Console & con,int lines)1083 static void scrollConsole(Console& con, int lines)
1084 {
1085     int topRow = con.getWindowRow();
1086     int height = con.getHeight();
1087 
1088     if (lines < 0)
1089     {
1090         if (topRow + lines > -height)
1091             console.setWindowRow(topRow + lines);
1092         else
1093             console.setWindowRow(-(height - 1));
1094     }
1095     else
1096     {
1097         if (topRow + lines <= -ConsolePageRows)
1098             console.setWindowRow(topRow + lines);
1099         else
1100             console.setWindowRow(-ConsolePageRows);
1101     }
1102 }
1103 
1104 
keyDown(int key,int modifiers)1105 void CelestiaCore::keyDown(int key, int modifiers)
1106 {
1107     setViewChanged();
1108 
1109 #ifdef CELX
1110     // TODO: should pass modifiers as a Lua table
1111     if (luaHook && luaHook->callLuaHook(this,
1112                                         "keydown",
1113                                         (float) key, (float) modifiers))
1114     {
1115         return;
1116     }
1117 #endif
1118     switch (key)
1119     {
1120     case Key_F1:
1121         sim->setTargetSpeed(0);
1122         break;
1123     case Key_F2:
1124         sim->setTargetSpeed(astro::kilometersToMicroLightYears(1.0f));
1125         break;
1126     case Key_F3:
1127         sim->setTargetSpeed(astro::kilometersToMicroLightYears(1000.0f));
1128         break;
1129     case Key_F4:
1130         sim->setTargetSpeed((float) astro::kilometersToMicroLightYears(astro::speedOfLight));
1131         break;
1132     case Key_F5:
1133         sim->setTargetSpeed((float) astro::kilometersToMicroLightYears(astro::speedOfLight * 10.0));
1134         break;
1135     case Key_F6:
1136         sim->setTargetSpeed(astro::AUtoMicroLightYears(1.0f));
1137         break;
1138     case Key_F7:
1139         sim->setTargetSpeed(1e6);
1140         break;
1141     case Key_F11:
1142         if (movieCapture != NULL)
1143         {
1144             if (isRecording())
1145                 recordPause();
1146             else
1147                 recordBegin();
1148         }
1149         break;
1150     case Key_F12:
1151         if (movieCapture != NULL)
1152             recordEnd();
1153         break;
1154     case Key_NumPad2:
1155     case Key_NumPad4:
1156     case Key_NumPad6:
1157     case Key_NumPad7:
1158     case Key_NumPad8:
1159     case Key_NumPad9:
1160         sim->setTargetSpeed(sim->getTargetSpeed());
1161         break;
1162 
1163     case Key_Down:
1164         if (showConsole)
1165             scrollConsole(console, 1);
1166         break;
1167 
1168     case Key_Up:
1169         if (showConsole)
1170             scrollConsole(console, -1);
1171         break;
1172 
1173     case Key_PageDown:
1174         if (showConsole)
1175             scrollConsole(console, ConsolePageRows);
1176         else
1177             back();
1178         break;
1179 
1180     case Key_PageUp:
1181         if (showConsole)
1182             scrollConsole(console, -ConsolePageRows);
1183         else
1184             forward();
1185         break;
1186     }
1187 
1188     if (KeyAccel < fMaxKeyAccel)
1189         KeyAccel *= 1.1;
1190 
1191     // Only process alphanumeric keys if we're not in text enter mode
1192     if (islower(key))
1193         key = toupper(key);
1194     if (!(key >= 'A' && key <= 'Z' && (textEnterMode != KbNormal) ))
1195     {
1196         if (modifiers & ShiftKey)
1197             shiftKeysPressed[key] = true;
1198         else
1199             keysPressed[key] = true;
1200     }
1201 }
1202 
keyUp(int key,int)1203 void CelestiaCore::keyUp(int key, int)
1204 {
1205     setViewChanged();
1206     KeyAccel = 1.0;
1207     if (islower(key))
1208         key = toupper(key);
1209     keysPressed[key] = false;
1210     shiftKeysPressed[key] = false;
1211 }
1212 
1213 #ifdef CELX
getKeyName(const char * c,int modifiers,char * keyName,unsigned int keyNameLength)1214 static bool getKeyName(const char* c, int modifiers, char* keyName, unsigned int keyNameLength)
1215 {
1216     unsigned int length = strlen(c);
1217 
1218     // Translate control characters
1219     if (length == 1 && c[0] >= '\001' && c[0] <= '\032')
1220     {
1221         if (keyNameLength < 4)
1222             return false;
1223         sprintf(keyName, "C-%c", '\140' + c[0]);
1224     }
1225     else if (modifiers & CelestiaCore::ControlKey)
1226     {
1227         if (keyNameLength < length + 4)
1228             return false;
1229         sprintf(keyName, "C-%s", c);
1230     }
1231     else
1232     {
1233         if (keyNameLength < length + 1)
1234             return false;
1235         strcpy(keyName, c);
1236     }
1237 
1238     return true;
1239 }
1240 #endif
1241 
charEntered(char c,int modifiers)1242 void CelestiaCore::charEntered(char c, int modifiers)
1243 {
1244     setViewChanged();
1245     char C[2];
1246     C[0] = c;
1247     C[1] = '\0';
1248     charEntered(C, modifiers);
1249 }
1250 
charEntered(const char * c_p,int modifiers)1251 void CelestiaCore::charEntered(const char *c_p, int modifiers)
1252 {
1253     setViewChanged();
1254 
1255     Observer* observer = sim->getActiveObserver();
1256 
1257     char c = *c_p;
1258 
1259 
1260 #ifdef CELX
1261     if (celxScript != NULL && (textEnterMode & KbPassToScript))
1262     {
1263         if (c != '\033' && celxScript->charEntered(c_p))
1264         {
1265             return;
1266         }
1267     }
1268 
1269 #endif
1270 
1271     if (textEnterMode & KbAutoComplete)
1272     {
1273         wchar_t wc = 0; // Null wide character
1274         UTF8Decode(c_p, 0, strlen(c_p), wc);
1275 #ifdef TARGET_OS_MAC
1276         if ( wc && (!iscntrl(wc)) )
1277 #else
1278         if ( wc && (!iswcntrl(wc)) )
1279 #endif
1280         {
1281             typedText += string(c_p);
1282             typedTextCompletion = sim->getObjectCompletion(typedText, (renderer->getLabelMode() & Renderer::LocationLabels) != 0);
1283             typedTextCompletionIdx = -1;
1284 #ifdef AUTO_COMPLETION
1285             if (typedTextCompletion.size() == 1)
1286             {
1287                 string::size_type pos = typedText.rfind('/', typedText.length());
1288                 if (pos != string::npos)
1289                     typedText = typedText.substr(0, pos + 1) + typedTextCompletion[0];
1290                 else
1291                     typedText = typedTextCompletion[0];
1292             }
1293 #endif
1294         }
1295         else if (c == '\b')
1296         {
1297             typedTextCompletionIdx = -1;
1298             if (typedText.size() > 0)
1299             {
1300 #ifdef AUTO_COMPLETION
1301                 do
1302                 {
1303 #endif
1304                     // We remove bytes like b10xxx xxxx at the end of typeText
1305                     // these are guarantied to not be the first byte of a UTF-8 char
1306                     while (typedText.size() && ((typedText[typedText.size() - 1] & 0xC0) == 0x80)) {
1307                         typedText = string(typedText, 0, typedText.size() - 1);
1308                     }
1309                     // We then remove the first byte of the last UTF-8 char of typedText.
1310                     typedText = string(typedText, 0, typedText.size() - 1);
1311                     if (typedText.size() > 0)
1312                     {
1313                         typedTextCompletion = sim->getObjectCompletion(typedText, (renderer->getLabelMode() & Renderer::LocationLabels) != 0);
1314                     } else {
1315                         typedTextCompletion.clear();
1316                     }
1317 #ifdef AUTO_COMPLETION
1318                 } while (typedText.size() > 0 && typedTextCompletion.size() == 1);
1319 #endif
1320             }
1321         }
1322         else if (c == '\011') // TAB
1323         {
1324             if (typedTextCompletionIdx + 1 < (int) typedTextCompletion.size())
1325                 typedTextCompletionIdx++;
1326             else if ((int) typedTextCompletion.size() > 0 && typedTextCompletionIdx + 1 == (int) typedTextCompletion.size())
1327                 typedTextCompletionIdx = 0;
1328             if (typedTextCompletionIdx >= 0) {
1329                 string::size_type pos = typedText.rfind('/', typedText.length());
1330                 if (pos != string::npos)
1331                     typedText = typedText.substr(0, pos + 1) + typedTextCompletion[typedTextCompletionIdx];
1332                 else
1333                     typedText = typedTextCompletion[typedTextCompletionIdx];
1334             }
1335         }
1336         else if (c == Key_BackTab)
1337         {
1338             if (typedTextCompletionIdx > 0)
1339                 typedTextCompletionIdx--;
1340             else if (typedTextCompletionIdx == 0)
1341                 typedTextCompletionIdx = typedTextCompletion.size() - 1;
1342             else if (typedTextCompletion.size() > 0)
1343                 typedTextCompletionIdx = typedTextCompletion.size() - 1;
1344             if (typedTextCompletionIdx >= 0) {
1345                 string::size_type pos = typedText.rfind('/', typedText.length());
1346                 if (pos != string::npos)
1347                     typedText = typedText.substr(0, pos + 1) + typedTextCompletion[typedTextCompletionIdx];
1348                 else
1349                     typedText = typedTextCompletion[typedTextCompletionIdx];
1350             }
1351         }
1352         else if (c == '\033') // ESC
1353         {
1354             setTextEnterMode(textEnterMode & ~KbAutoComplete);
1355         }
1356         else if (c == '\n' || c == '\r')
1357         {
1358             if (typedText != "")
1359             {
1360                 Selection sel = sim->findObjectFromPath(typedText, true);
1361                 if (!sel.empty())
1362                 {
1363                     addToHistory();
1364                     sim->setSelection(sel);
1365                 }
1366                 typedText = "";
1367             }
1368             setTextEnterMode(textEnterMode & ~KbAutoComplete);
1369         }
1370         return;
1371     }
1372 
1373 #ifdef CELX
1374     if (celxScript != NULL)
1375     {
1376         if (c != '\033')
1377         {
1378             char keyName[8];
1379             getKeyName(c_p, modifiers, keyName, sizeof(keyName));
1380             if (celxScript->handleKeyEvent(keyName))
1381                 return;
1382         }
1383     }
1384 
1385     if (luaHook)
1386     {
1387         char keyName[8];
1388         getKeyName(c_p, modifiers, keyName, sizeof(keyName));
1389         if (luaHook->callLuaHook(this, "charentered", keyName))
1390         {
1391             return;
1392         }
1393     }
1394 #endif
1395 
1396     char C = toupper(c);
1397     switch (C)
1398     {
1399     case '\001': // Ctrl+A
1400         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowAtmospheres);
1401         notifyWatchers(RenderFlagsChanged);
1402         break;
1403 
1404     case '\002': // Ctrl+B
1405         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowBoundaries);
1406         notifyWatchers(RenderFlagsChanged);
1407         break;
1408 
1409     case '\n':
1410     case '\r':
1411         setTextEnterMode(textEnterMode | KbAutoComplete);
1412         break;
1413 
1414     case '\b':
1415         sim->setSelection(sim->getSelection().parent());
1416         break;
1417 
1418     case '\014': // Ctrl+L
1419         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowNightMaps);
1420         notifyWatchers(RenderFlagsChanged);
1421         break;
1422 
1423     case '\013': // Ctrl+K
1424         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowMarkers);
1425         if (renderer->getRenderFlags() & Renderer::ShowMarkers)
1426 	{
1427             flash(_("Markers enabled"));
1428 	}
1429         else
1430             flash(_("Markers disabled"));
1431         notifyWatchers(RenderFlagsChanged);
1432         break;
1433 
1434     case '\005':  // Ctrl+E
1435         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowEclipseShadows);
1436         notifyWatchers(RenderFlagsChanged);
1437         break;
1438 
1439     case '\007':  // Ctrl+G
1440         flash(_("Goto surface"));
1441         addToHistory();
1442         //if (sim->getFrame().coordSys == astro::Universal)
1443             sim->geosynchronousFollow();
1444         sim->gotoSurface(5.0);
1445         // sim->gotoSelection(0.0001, Vec3f(0, 1, 0), astro::ObserverLocal);
1446         break;
1447 
1448     case '\006': // Ctrl+F
1449         addToHistory();
1450         altAzimuthMode = !altAzimuthMode;
1451         if (altAzimuthMode)
1452         {
1453             flash(_("Alt-azimuth mode enabled"));
1454         }
1455         else
1456             flash(_("Alt-azimuth mode disabled"));
1457         break;
1458 
1459     case 127: // Delete
1460         deleteView();
1461         break;
1462 
1463     case '\011': // TAB
1464         do
1465         {
1466             activeView++;
1467             if (activeView == views.end())
1468                 activeView = views.begin();
1469         } while ((*activeView)->type != View::ViewWindow);
1470         sim->setActiveObserver((*activeView)->observer);
1471         if (!showActiveViewFrame)
1472             flashFrameStart = currentTime;
1473         break;
1474 
1475     case '\020':  // Ctrl+P
1476         if (!sim->getSelection().empty())
1477         {
1478             Selection sel = sim->getSelection();
1479             if (sim->getUniverse()->isMarked(sel, 1))
1480             {
1481                 sim->getUniverse()->unmarkObject(sel, 1);
1482             }
1483             else
1484             {
1485                 MarkerRepresentation markerRep(MarkerRepresentation::Diamond);
1486                 markerRep.setSize(10.0f);
1487                 markerRep.setColor(Color(0.0f, 1.0f, 0.0f, 0.9f));
1488 
1489                 sim->getUniverse()->markObject(sel, markerRep, 1);
1490             }
1491         }
1492         break;
1493 
1494     case '\025': // Ctrl+U
1495         splitView(View::VerticalSplit);
1496         break;
1497 
1498     case '\022': // Ctrl+R
1499         splitView(View::HorizontalSplit);
1500         break;
1501 
1502     case '\004': // Ctrl+D
1503         singleView();
1504         break;
1505 
1506     case '\023':  // Ctrl+S
1507         renderer->setStarStyle((Renderer::StarStyle) (((int) renderer->getStarStyle() + 1) %
1508                                                       (int) Renderer::StarStyleCount));
1509         switch (renderer->getStarStyle())
1510         {
1511         case Renderer::FuzzyPointStars:
1512             flash(_("Star style: fuzzy points"));
1513             break;
1514         case Renderer::PointStars:
1515             flash(_("Star style: points"));
1516             break;
1517         case Renderer::ScaledDiscStars:
1518             flash(_("Star style: scaled discs"));
1519             break;
1520         default:
1521             break;
1522         }
1523 
1524         notifyWatchers(RenderFlagsChanged);
1525         break;
1526 
1527     case '\024':  // Ctrl+T
1528         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowCometTails);
1529         if (renderer->getRenderFlags() & Renderer::ShowCometTails)
1530 	{
1531             flash(_("Comet tails enabled"));
1532 	}
1533         else
1534             flash(_("Comet tails disabled"));
1535         notifyWatchers(RenderFlagsChanged);
1536         break;
1537 
1538     case '\026':  // Ctrl+V
1539         {
1540             GLContext* context = renderer->getGLContext();
1541             GLContext::GLRenderPath path = context->getRenderPath();
1542             GLContext::GLRenderPath newPath = context->nextRenderPath();
1543 
1544             if (newPath != path)
1545             {
1546                 switch (newPath)
1547                 {
1548                 case GLContext::GLPath_Basic:
1549                     flash(_("Render path: Basic"));
1550                     break;
1551                 case GLContext::GLPath_Multitexture:
1552                     flash(_("Render path: Multitexture"));
1553                     break;
1554                 case GLContext::GLPath_NvCombiner:
1555                     flash(_("Render path: NVIDIA combiners"));
1556                     break;
1557                 case GLContext::GLPath_DOT3_ARBVP:
1558                     flash(_("Render path: OpenGL vertex program"));
1559                     break;
1560                 case GLContext::GLPath_NvCombiner_NvVP:
1561                     flash(_("Render path: NVIDIA vertex program and combiners"));
1562                     break;
1563                 case GLContext::GLPath_NvCombiner_ARBVP:
1564                     flash(_("Render path: OpenGL vertex program/NVIDIA combiners"));
1565                     break;
1566                 case GLContext::GLPath_ARBFP_ARBVP:
1567                     flash(_("Render path: OpenGL 1.5 vertex/fragment program"));
1568                     break;
1569                 case GLContext::GLPath_NV30:
1570                     flash(_("Render path: NVIDIA GeForce FX"));
1571                     break;
1572                 case GLContext::GLPath_GLSL:
1573                     flash(_("Render path: OpenGL 2.0"));
1574                     break;
1575                 }
1576                 context->setRenderPath(newPath);
1577                 notifyWatchers(RenderFlagsChanged);
1578             }
1579         }
1580         break;
1581 
1582     case '\027':  // Ctrl+W
1583         wireframe = !wireframe;
1584         renderer->setRenderMode(wireframe ? GL_LINE : GL_FILL);
1585         break;
1586 
1587     case '\030':  // Ctrl+X
1588         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowSmoothLines);
1589         notifyWatchers(RenderFlagsChanged);
1590         break;
1591 
1592     case '\031':  // Ctrl+Y
1593         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowAutoMag);
1594         if (renderer->getRenderFlags() & Renderer::ShowAutoMag)
1595         {
1596             flash(_("Auto-magnitude enabled"));
1597             setFaintestAutoMag();
1598         }
1599         else
1600         {
1601             flash(_("Auto-magnitude disabled"));
1602         }
1603         notifyWatchers(RenderFlagsChanged);
1604         break;
1605 
1606 
1607     case '\033': // Escape
1608         cancelScript();
1609         addToHistory();
1610         if (textEnterMode != KbNormal)
1611         {
1612             setTextEnterMode(KbNormal);
1613         }
1614         else
1615         {
1616             if (sim->getObserverMode() == Observer::Travelling)
1617                 sim->setObserverMode(Observer::Free);
1618             else
1619                 sim->setFrame(ObserverFrame::Universal, Selection());
1620             if (!sim->getTrackedObject().empty())
1621                 sim->setTrackedObject(Selection());
1622         }
1623         flash(_("Cancel"));
1624         break;
1625 
1626     case ' ':
1627         if (sim->getPauseState() == true)
1628         {
1629             if (scriptState == ScriptPaused)
1630                 scriptState = ScriptRunning;
1631             sim->setPauseState(false);
1632         }
1633         else
1634         {
1635             sim->setPauseState(true);
1636 
1637             // If there's a script running then pause it.  This has the
1638             // potentially confusing side effect of rendering nonfunctional
1639             // goto, center, and other movement commands.
1640 #ifdef CELX
1641             if (runningScript != NULL || celxScript != NULL)
1642 #else
1643             if (runningScript != NULL)
1644 #endif
1645             {
1646                 if (scriptState == ScriptRunning)
1647                     scriptState = ScriptPaused;
1648             }
1649             else
1650             {
1651                 if (scriptState == ScriptPaused)
1652                     scriptState = ScriptRunning;
1653             }
1654         }
1655 
1656         if (sim->getPauseState() == true)
1657         {
1658             if (scriptState == ScriptPaused)
1659                 flash(_("Time and script are paused"));
1660             else
1661                 flash(_("Time is paused"));
1662         }
1663         else
1664         {
1665             flash(_("Resume"));
1666         }
1667         break;
1668 
1669     case '!':
1670         if (editMode)
1671         {
1672             showSelectionInfo(sim->getSelection());
1673         }
1674         else
1675         {
1676             time_t t = time(NULL);
1677             struct tm *gmt = gmtime(&t);
1678             if (gmt != NULL)
1679             {
1680                 astro::Date d;
1681                 d.year = gmt->tm_year + 1900;
1682                 d.month = gmt->tm_mon + 1;
1683                 d.day = gmt->tm_mday;
1684                 d.hour = gmt->tm_hour;
1685                 d.minute = gmt->tm_min;
1686                 d.seconds = (int) gmt->tm_sec;
1687                 sim->setTime(astro::UTCtoTDB(d));
1688             }
1689         }
1690         break;
1691 
1692     case '%':
1693         {
1694             const ColorTemperatureTable* current =
1695                 renderer->getStarColorTable();
1696             if (current == GetStarColorTable(ColorTable_Enhanced))
1697             {
1698                 renderer->setStarColorTable(GetStarColorTable(ColorTable_Blackbody_D65));
1699             }
1700             else if (current == GetStarColorTable(ColorTable_Blackbody_D65))
1701             {
1702                 renderer->setStarColorTable(GetStarColorTable(ColorTable_Enhanced));
1703             }
1704             else
1705             {
1706                 // Unknown color table
1707             }
1708         }
1709         break;
1710 
1711     case '^':
1712         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowNebulae);
1713         notifyWatchers(RenderFlagsChanged);
1714         break;
1715 
1716 
1717     case '&':
1718         renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::LocationLabels);
1719         notifyWatchers(LabelFlagsChanged);
1720         break;
1721 
1722     case '*':
1723         addToHistory();
1724 	sim->reverseObserverOrientation();
1725         break;
1726 
1727     case '?':
1728         addToHistory();
1729         if (!sim->getSelection().empty())
1730         {
1731             Vec3d v = sim->getSelection().getPosition(sim->getTime()) -
1732             sim->getObserver().getPosition();
1733             int hours, mins;
1734             float secs;
1735             char buf[128];
1736 	    if (astro::microLightYearsToKilometers(v.length()) >=
1737                 86400.0 * astro::speedOfLight)
1738 	    {
1739 	        // Light travel time in years, if >= 1day
1740 	        sprintf(buf, _("Light travel time:  %.4f yr "),
1741                         v.length() * 1.0e-6);
1742                 flash(buf, 2.0);
1743 	    }
1744 	    else
1745 	    {
1746 	        // If Light travel delay < 1 day, display in [ hr : min : sec ]
1747                 getLightTravelDelay(v.length(), hours, mins, secs);
1748                 if (hours == 0)
1749                     sprintf(buf, _("Light travel time:  %d min  %.1f s"),
1750                             mins, secs);
1751                 else
1752 		    sprintf(buf, _("Light travel time:  %d h  %d min  %.1f s")
1753                             ,hours, mins, secs);
1754                 flash(buf, 2.0);
1755 	    }
1756         }
1757         break;
1758 
1759     case '-':
1760         addToHistory();
1761 
1762         if (sim->getSelection().body() &&
1763             (sim->getTargetSpeed() < 0.99 *
1764             astro::kilometersToMicroLightYears(astro::speedOfLight)))
1765         {
1766             Vec3d v = sim->getSelection().getPosition(sim->getTime()) -
1767                       sim->getObserver().getPosition();
1768             lightTravelFlag = !lightTravelFlag;
1769             if (lightTravelFlag)
1770             {
1771                 flash(_("Light travel delay included"),2.0);
1772                 setLightTravelDelay(v.length());
1773             }
1774             else
1775             {
1776                 flash(_("Light travel delay switched off"),2.0);
1777                 setLightTravelDelay(-v.length());
1778             }
1779         }
1780         else
1781         {
1782             flash(_("Light travel delay ignored"));
1783         }
1784         break;
1785 
1786     case ',':
1787         addToHistory();
1788         if (observer->getFOV() > MinimumFOV)
1789 	{
1790 	    observer->setFOV(observer->getFOV() / 1.05f);
1791             setZoomFromFOV();
1792 	    if((renderer->getRenderFlags() & Renderer::ShowAutoMag))
1793 	    {
1794 	        setFaintestAutoMag();
1795 		char buf[128];
1796                 setlocale(LC_NUMERIC, "");
1797 		sprintf(buf, _("Magnitude limit: %.2f"), sim->getFaintestVisible());
1798                 setlocale(LC_NUMERIC, "C");
1799 		flash(buf);
1800 	    }
1801 	}
1802         break;
1803 
1804     case '.':
1805         addToHistory();
1806         if (observer->getFOV() < MaximumFOV)
1807 	{
1808 	    observer->setFOV(observer->getFOV() * 1.05f);
1809             setZoomFromFOV();
1810 	    if((renderer->getRenderFlags() & Renderer::ShowAutoMag) != 0)
1811 	    {
1812 	        setFaintestAutoMag();
1813 		char buf[128];
1814                 setlocale(LC_NUMERIC, "");
1815 		sprintf(buf, _("Magnitude limit: %.2f"), sim->getFaintestVisible());
1816                 setlocale(LC_NUMERIC, "C");
1817 		flash(buf);
1818 	    }
1819 	}
1820         break;
1821 
1822     case '+':
1823         addToHistory();
1824         if (observer->getDisplayedSurface() != "")
1825         {
1826             observer->setDisplayedSurface("");
1827             flash(_("Using normal surface textures."));
1828         }
1829         else
1830         {
1831             observer->setDisplayedSurface("limit of knowledge");
1832             flash(_("Using limit of knowledge surface textures."));
1833         }
1834         break;
1835 
1836     case '/':
1837         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowDiagrams);
1838         notifyWatchers(RenderFlagsChanged);
1839         break;
1840 
1841     case '0':
1842         addToHistory();
1843         sim->selectPlanet(-1);
1844         break;
1845 
1846     case '1':
1847     case '2':
1848     case '3':
1849     case '4':
1850     case '5':
1851     case '6':
1852     case '7':
1853     case '8':
1854     case '9':
1855         addToHistory();
1856         if (!(modifiers & ControlKey))
1857             sim->selectPlanet(c - '1');
1858         break;
1859 
1860     case ';':
1861         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowCelestialSphere);
1862         notifyWatchers(RenderFlagsChanged);
1863         break;
1864 
1865     case '=':
1866         renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::ConstellationLabels);
1867         notifyWatchers(LabelFlagsChanged);
1868         break;
1869 
1870     case 'B':
1871         renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::StarLabels);
1872         notifyWatchers(LabelFlagsChanged);
1873         break;
1874 
1875     case 'C':
1876         addToHistory();
1877         if (c == 'c')
1878             sim->centerSelection();
1879         else
1880             sim->centerSelectionCO();
1881         break;
1882 
1883     case 'D':
1884         addToHistory();
1885         if (config->demoScriptFile != "")
1886            runScript(config->demoScriptFile);
1887         break;
1888 
1889 	case 'E':
1890 		if (c == 'e')
1891 			renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::GalaxyLabels);
1892 		else
1893         	renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::GlobularLabels);
1894         notifyWatchers(LabelFlagsChanged);
1895 		break;
1896 
1897     case 'F':
1898         addToHistory();
1899         flash(_("Follow"));
1900         sim->follow();
1901         break;
1902 
1903     case 'G':
1904         addToHistory();
1905         if (sim->getFrame()->getCoordinateSystem() == ObserverFrame::Universal)
1906             sim->follow();
1907         sim->gotoSelection(5.0, Vec3f(0, 1, 0), ObserverFrame::ObserverLocal);
1908         break;
1909 
1910     case 'H':
1911         addToHistory();
1912         sim->setSelection(sim->getUniverse()->getStarCatalog()->find(0));
1913         break;
1914 
1915     case 'I':
1916         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowCloudMaps);
1917         notifyWatchers(RenderFlagsChanged);
1918         break;
1919 
1920     case 'J':
1921         addToHistory();
1922         sim->setTimeScale(-sim->getTimeScale());
1923         if (sim->getTimeScale() >= 0)
1924             flash(_("Time: Forward"));
1925         else
1926             flash(_("Time: Backward"));
1927         break;
1928 
1929     case 'K':
1930         addToHistory();
1931         if (abs(sim->getTimeScale()) > MinimumTimeRate)
1932         {
1933             if (c == 'k')
1934                 sim->setTimeScale(sim->getTimeScale() / CoarseTimeScaleFactor);
1935             else
1936                 sim->setTimeScale(sim->getTimeScale() / FineTimeScaleFactor);
1937             char buf[128];
1938             setlocale(LC_NUMERIC, "");
1939             sprintf(buf, "%s: " TIMERATE_PRINTF_FORMAT,  _("Time rate"), sim->getTimeScale());
1940             setlocale(LC_NUMERIC, "C");
1941             flash(buf);
1942         }
1943         break;
1944 
1945     case 'L':
1946         addToHistory();
1947         if (abs(sim->getTimeScale()) < MaximumTimeRate)
1948         {
1949             if (c == 'l')
1950                 sim->setTimeScale(sim->getTimeScale() * CoarseTimeScaleFactor);
1951             else
1952                 sim->setTimeScale(sim->getTimeScale() * FineTimeScaleFactor);
1953             char buf[128];
1954             setlocale(LC_NUMERIC, "");
1955             sprintf(buf, "%s: " TIMERATE_PRINTF_FORMAT,  _("Time rate"), sim->getTimeScale());
1956             setlocale(LC_NUMERIC, "C");
1957             flash(buf);
1958         }
1959         break;
1960 
1961     case 'M':
1962         if (c == 'm')
1963             renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::MoonLabels);
1964         else
1965             renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::MinorMoonLabels);
1966         notifyWatchers(LabelFlagsChanged);
1967         break;
1968 
1969     case 'N':
1970         renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::SpacecraftLabels);
1971         notifyWatchers(LabelFlagsChanged);
1972         break;
1973 
1974     case 'O':
1975         renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowOrbits);
1976         notifyWatchers(RenderFlagsChanged);
1977         break;
1978 
1979     case 'P':
1980         if (c == 'p')
1981             renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::PlanetLabels);
1982         else
1983             renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::DwarfPlanetLabels);
1984         notifyWatchers(LabelFlagsChanged);
1985         break;
1986 
1987     case 'Q':
1988         sim->setTargetSpeed(-sim->getTargetSpeed());
1989         break;
1990 
1991     case 'S':
1992         sim->setTargetSpeed(0);
1993         break;
1994 
1995     case 'T':
1996         addToHistory();
1997         if (sim->getTrackedObject().empty())
1998             sim->setTrackedObject(sim->getSelection());
1999         else
2000             sim->setTrackedObject(Selection());
2001         break;
2002 
2003 	case 'U':
2004 		if (c == 'u')
2005 			renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowGalaxies);
2006 		else
2007 			renderer->setRenderFlags(renderer->getRenderFlags() ^ Renderer::ShowGlobulars);
2008         notifyWatchers(RenderFlagsChanged);
2009         break;
2010 
2011     case 'V':
2012         setHudDetail((getHudDetail() + 1) % 3);
2013         break;
2014 
2015     case 'W':
2016         if (c == 'w')
2017             renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::AsteroidLabels);
2018         else
2019             renderer->setLabelMode(renderer->getLabelMode() ^ Renderer::CometLabels);
2020         notifyWatchers(LabelFlagsChanged);
2021         break;
2022 
2023     case 'X':
2024         sim->setTargetSpeed(sim->getTargetSpeed());
2025         break;
2026 
2027     case 'Y':
2028         flash(_("Sync Orbit"));
2029         addToHistory();
2030         sim->geosynchronousFollow();
2031         break;
2032 
2033     case ':':
2034         flash(_("Lock"));
2035         addToHistory();
2036         sim->phaseLock();
2037         break;
2038 
2039     case '"':
2040         flash(_("Chase"));
2041         addToHistory();
2042         sim->chase();
2043         break;
2044 
2045 
2046 
2047     case '[':
2048         if ((renderer->getRenderFlags() & Renderer::ShowAutoMag) == 0)
2049         {
2050             if (sim->getFaintestVisible() > 1.0f)
2051             {
2052                 setFaintest(sim->getFaintestVisible() - 0.2f);
2053                 notifyWatchers(FaintestChanged);
2054                 char buf[128];
2055                 setlocale(LC_NUMERIC, "");
2056                 sprintf(buf, _("Magnitude limit: %.2f"),sim->getFaintestVisible());
2057                 setlocale(LC_NUMERIC, "C");
2058                 flash(buf);
2059             }
2060         }
2061         else if (renderer->getFaintestAM45deg() > 6.0f)
2062         {
2063             renderer->setFaintestAM45deg(renderer->getFaintestAM45deg() - 0.1f);
2064             setFaintestAutoMag();
2065             char buf[128];
2066             setlocale(LC_NUMERIC, "");
2067             sprintf(buf, _("Auto magnitude limit at 45 degrees:  %.2f"),renderer->getFaintestAM45deg());
2068             setlocale(LC_NUMERIC, "C");
2069             flash(buf);
2070         }
2071         break;
2072 
2073     case '\\':
2074         addToHistory();
2075         sim->setTimeScale(1.0f);
2076         break;
2077 
2078     case ']':
2079         if((renderer->getRenderFlags() & Renderer::ShowAutoMag) == 0)
2080         {
2081             if (sim->getFaintestVisible() < 15.0f)
2082 	          {
2083 	              setFaintest(sim->getFaintestVisible() + 0.2f);
2084                 notifyWatchers(FaintestChanged);
2085                 char buf[128];
2086                 setlocale(LC_NUMERIC, "");
2087                 sprintf(buf, _("Magnitude limit: %.2f"),sim->getFaintestVisible());
2088                 setlocale(LC_NUMERIC, "C");
2089                 flash(buf);
2090             }
2091         }
2092         else if (renderer->getFaintestAM45deg() < 12.0f)
2093         {
2094             renderer->setFaintestAM45deg(renderer->getFaintestAM45deg() + 0.1f);
2095             setFaintestAutoMag();
2096             char buf[128];
2097             setlocale(LC_NUMERIC, "");
2098             sprintf(buf, _("Auto magnitude limit at 45 degrees:  %.2f"),renderer->getFaintestAM45deg());
2099             setlocale(LC_NUMERIC, "C");
2100             flash(buf);
2101         }
2102         break;
2103 
2104     case '`':
2105         showFPSCounter = !showFPSCounter;
2106         break;
2107 
2108     case '{':
2109         {
2110             if (renderer->getAmbientLightLevel() > 0.05f)
2111                 renderer->setAmbientLightLevel(renderer->getAmbientLightLevel() - 0.05f);
2112             else
2113                 renderer->setAmbientLightLevel(0.0f);
2114             notifyWatchers(AmbientLightChanged);
2115             char buf[128];
2116             setlocale(LC_NUMERIC, "");
2117             sprintf(buf, _("Ambient light level:  %.2f"),renderer->getAmbientLightLevel());
2118             setlocale(LC_NUMERIC, "C");
2119             flash(buf);
2120         }
2121         break;
2122 
2123     case '}':
2124         {
2125             if (renderer->getAmbientLightLevel() < 0.95f)
2126                 renderer->setAmbientLightLevel(renderer->getAmbientLightLevel() + 0.05f);
2127             else
2128                 renderer->setAmbientLightLevel(1.0f);
2129             notifyWatchers(AmbientLightChanged);
2130             char buf[128];
2131             setlocale(LC_NUMERIC, "");
2132             sprintf(buf, _("Ambient light level:  %.2f"),renderer->getAmbientLightLevel());
2133             setlocale(LC_NUMERIC, "C");
2134             flash(buf);
2135         }
2136         break;
2137 
2138     case '(':
2139         {
2140             char buf[128];
2141             Galaxy::decreaseLightGain();
2142             setlocale(LC_NUMERIC, "");
2143             sprintf(buf, "%s:  %3.0f %%", _("Light gain"), Galaxy::getLightGain() * 100.0f);
2144             setlocale(LC_NUMERIC, "C");
2145             flash(buf);
2146             notifyWatchers(GalaxyLightGainChanged);
2147         }
2148         break;
2149 
2150     case ')':
2151         {
2152             char buf[128];
2153             Galaxy::increaseLightGain();
2154             setlocale(LC_NUMERIC, "");
2155             sprintf(buf, "%s:  %3.0f %%", _("Light gain"), Galaxy::getLightGain() * 100.0f);
2156             setlocale(LC_NUMERIC, "C");
2157             flash(buf);
2158             notifyWatchers(GalaxyLightGainChanged);
2159         }
2160         break;
2161 
2162     case '~':
2163         showConsole = !showConsole;
2164         break;
2165 
2166     case '@':
2167         // TODO: 'Edit mode' should be eliminated; it can be done better
2168         // with a Lua script.
2169         editMode = !editMode;
2170         break;
2171 #ifdef USE_HDR
2172     case '|':
2173         renderer->setBloomEnabled(!renderer->getBloomEnabled());
2174         if (renderer->getBloomEnabled())
2175             flash(_("Bloom enabled"));
2176         else
2177             flash(_("Bloom disabled"));
2178         break;
2179     case '<':
2180         {
2181             char buf[64];
2182             renderer->decreaseBrightness();
2183             sprintf(buf, "%s:  %+3.2f", _("Exposure"), -renderer->getBrightness());
2184             flash(buf);
2185         }
2186         break;
2187     case '>':
2188         {
2189             char buf[64];
2190             renderer->increaseBrightness();
2191             sprintf(buf, "%s:  %+3.2f", _("Exposure"), -renderer->getBrightness());
2192             flash(buf);
2193         }
2194         break;
2195 #endif
2196     }
2197 }
2198 
2199 
getLightTravelDelay(double distance,int & hours,int & mins,float & secs)2200 void CelestiaCore::getLightTravelDelay(double distance, int& hours, int& mins,
2201                                     float& secs)
2202 {
2203     // light travel time in hours
2204     double lt = astro::microLightYearsToKilometers(distance)/
2205 	        (3600.0 * astro::speedOfLight);
2206     hours = (int) lt;
2207     double mm    = (lt - hours) * 60;
2208     mins = (int) mm;
2209     secs = (float) ((mm  - mins) * 60);
2210 }
2211 
setLightTravelDelay(double distance)2212 void CelestiaCore::setLightTravelDelay(double distance)
2213 {
2214     // light travel time in days
2215     double lt = astro::microLightYearsToKilometers(distance)/
2216 	        (86400.0 * astro::speedOfLight);
2217     sim->setTime(sim->getTime() - lt);
2218 }
2219 
getAltAzimuthMode() const2220 bool CelestiaCore::getAltAzimuthMode() const
2221 {
2222     return altAzimuthMode;
2223 }
2224 
setAltAzimuthMode(bool enable)2225 void CelestiaCore::setAltAzimuthMode(bool enable)
2226 {
2227     altAzimuthMode = enable;
2228 }
2229 
start(double t)2230 void CelestiaCore::start(double t)
2231 {
2232     if (config->initScriptFile != "")
2233     {
2234         // using the KdeAlerter in runScript would create an infinite loop,
2235         // break it here by resetting config->initScriptFile:
2236         string filename = config->initScriptFile;
2237         config->initScriptFile = "";
2238         runScript(filename);
2239     }
2240 
2241     // Set the simulation starting time to the current system time
2242     sim->setTime(t);
2243     sim->update(0.0);
2244 
2245     sysTime = timer->getTime();
2246 
2247     if (startURL != "")
2248         goToUrl(startURL);
2249 }
2250 
setStartURL(string url)2251 void CelestiaCore::setStartURL(string url)
2252 {
2253     if (!url.substr(0,4).compare("cel:"))
2254     {
2255         startURL = url;
2256         config->initScriptFile = "";
2257     }
2258     else
2259     {
2260         config->initScriptFile = url;
2261     }
2262 }
2263 
2264 
tick()2265 void CelestiaCore::tick()
2266 {
2267     double lastTime = sysTime;
2268     sysTime = timer->getTime();
2269 
2270     // The time step is normally driven by the system clock; however, when
2271     // recording a movie, we fix the time step the frame rate of the movie.
2272     double dt = 0.0;
2273     if (movieCapture != NULL && recording)
2274     {
2275         dt = 1.0 / movieCapture->getFrameRate();
2276     }
2277     else
2278     {
2279         dt = sysTime - lastTime;
2280     }
2281 
2282     // Pause script execution
2283     if (scriptState == ScriptPaused)
2284         dt = 0.0;
2285 
2286     currentTime += dt;
2287 
2288     // Mouse wheel zoom
2289     if (zoomMotion != 0.0f)
2290     {
2291         double span = 0.1;
2292         double fraction;
2293 
2294         if (currentTime - zoomTime >= span)
2295             fraction = (zoomTime + span) - (currentTime - dt);
2296         else
2297             fraction = dt / span;
2298 
2299         // sim->changeOrbitDistance(zoomMotion * (float) fraction);
2300         if (currentTime - zoomTime >= span)
2301             zoomMotion = 0.0f;
2302     }
2303 
2304     // Mouse wheel dolly
2305     if (dollyMotion != 0.0)
2306     {
2307         double span = 0.1;
2308         double fraction;
2309 
2310         if (currentTime - dollyTime >= span)
2311             fraction = (dollyTime + span) - (currentTime - dt);
2312         else
2313             fraction = dt / span;
2314 
2315         sim->changeOrbitDistance((float) (dollyMotion * fraction));
2316         if (currentTime - dollyTime >= span)
2317             dollyMotion = 0.0f;
2318     }
2319 
2320     // Keyboard dolly
2321     if (keysPressed[Key_Home])
2322         sim->changeOrbitDistance((float) (-dt * 2));
2323     if (keysPressed[Key_End])
2324         sim->changeOrbitDistance((float) (dt * 2));
2325 
2326     // Keyboard rotate
2327     Vec3d av = sim->getObserver().getAngularVelocity();
2328 
2329     av = av * exp(-dt * RotationDecay);
2330 
2331     float fov = sim->getActiveObserver()->getFOV() / stdFOV;
2332     Selection refObject = sim->getFrame()->getRefObject();
2333 
2334     // Handle arrow keys; disable them when the log console is displayed,
2335     // because then they're used to scroll up and down.
2336     if (!showConsole)
2337     {
2338         if (!altAzimuthMode)
2339         {
2340             if (keysPressed[Key_Left])
2341                 av += Vec3d(0.0, 0.0, dt * -KeyRotationAccel);
2342             if (keysPressed[Key_Right])
2343                 av += Vec3d(0.0, 0.0, dt * KeyRotationAccel);
2344             if (keysPressed[Key_Down])
2345                 av += Vec3d(dt * fov * -KeyRotationAccel, 0.0, 0.0);
2346             if (keysPressed[Key_Up])
2347                 av += Vec3d(dt * fov * KeyRotationAccel, 0.0, 0.0);
2348         }
2349         else
2350         {
2351             if (!refObject.empty())
2352             {
2353                 Quatd orientation = sim->getObserver().getOrientation();
2354                 Vec3d up = sim->getObserver().getPosition() - refObject.getPosition(sim->getTime());
2355                 up.normalize();
2356 
2357                 Vec3d v = up * (KeyRotationAccel * dt);
2358                 v = v * (~orientation).toMatrix3();
2359 
2360                 if (keysPressed[Key_Left])
2361                     av -= v;
2362                 if (keysPressed[Key_Right])
2363                     av += v;
2364                 if (keysPressed[Key_Down])
2365                     av += Vec3d(dt * fov * -KeyRotationAccel, 0.0, 0.0);
2366                 if (keysPressed[Key_Up])
2367                     av += Vec3d(dt * fov * KeyRotationAccel, 0.0, 0.0);
2368             }
2369         }
2370     }
2371 
2372     if (keysPressed[Key_NumPad4])
2373         av += Vec3d(0.0, dt * fov * -KeyRotationAccel, 0.0);
2374     if (keysPressed[Key_NumPad6])
2375         av += Vec3d(0.0, dt * fov * KeyRotationAccel, 0.0);
2376     if (keysPressed[Key_NumPad2])
2377         av += Vec3d(dt * fov * -KeyRotationAccel, 0.0, 0.0);
2378     if (keysPressed[Key_NumPad8])
2379         av += Vec3d(dt * fov * KeyRotationAccel, 0.0, 0.0);
2380     if (keysPressed[Key_NumPad7] || joyButtonsPressed[JoyButton7])
2381         av += Vec3d(0.0, 0.0, dt * -KeyRotationAccel);
2382     if (keysPressed[Key_NumPad9] || joyButtonsPressed[JoyButton8])
2383         av += Vec3d(0.0, 0.0, dt * KeyRotationAccel);
2384 
2385     //Use Boolean to indicate if sim->setTargetSpeed() is called
2386     bool bSetTargetSpeed = false;
2387     if (joystickRotation != Vec3f(0.0f, 0.0f, 0.0f))
2388     {
2389         bSetTargetSpeed = true;
2390 
2391         av += (dt * KeyRotationAccel) * Vec3d(joystickRotation.x, joystickRotation.y, joystickRotation.z);
2392         sim->setTargetSpeed(sim->getTargetSpeed());
2393     }
2394 
2395     if (keysPressed[Key_NumPad5])
2396         av = av * exp(-dt * RotationBraking);
2397 
2398     sim->getObserver().setAngularVelocity(av);
2399 
2400     if (keysPressed[(int)'A'] || joyButtonsPressed[JoyButton2])
2401     {
2402         bSetTargetSpeed = true;
2403 
2404         if (sim->getTargetSpeed() == 0.0f)
2405             sim->setTargetSpeed(astro::kilometersToMicroLightYears(0.1f));
2406         else
2407             sim->setTargetSpeed(sim->getTargetSpeed() * (float) exp(dt * 3));
2408     }
2409     if (keysPressed[(int)'Z'] || joyButtonsPressed[JoyButton1])
2410     {
2411         bSetTargetSpeed = true;
2412 
2413         sim->setTargetSpeed(sim->getTargetSpeed() / (float) exp(dt * 3));
2414     }
2415     if (!bSetTargetSpeed && av.length() > 0.0f)
2416     {
2417         //Force observer velocity vector to align with observer direction if an observer
2418         //angular velocity still exists.
2419         sim->setTargetSpeed(sim->getTargetSpeed());
2420     }
2421 
2422     if (!refObject.empty())
2423     {
2424         Quatf q(1.0f);
2425         float coarseness = ComputeRotationCoarseness(*sim);
2426 
2427         if (shiftKeysPressed[Key_Left])
2428             q = q * Quatf::yrotation((float) (dt * -KeyRotationAccel * coarseness));
2429         if (shiftKeysPressed[Key_Right])
2430             q = q * Quatf::yrotation((float) (dt *  KeyRotationAccel * coarseness));
2431         if (shiftKeysPressed[Key_Up])
2432             q = q * Quatf::xrotation((float) (dt * -KeyRotationAccel * coarseness));
2433         if (shiftKeysPressed[Key_Down])
2434             q = q * Quatf::xrotation((float) (dt *  KeyRotationAccel * coarseness));
2435         sim->orbit(q);
2436     }
2437 
2438     // If there's a script running, tick it
2439     if (runningScript != NULL)
2440     {
2441         bool finished = runningScript->tick(dt);
2442         if (finished)
2443             cancelScript();
2444     }
2445 
2446 #ifdef CELX
2447     if (celxScript != NULL)
2448     {
2449         celxScript->handleTickEvent(dt);
2450         if (scriptState == ScriptRunning)
2451         {
2452             bool finished = celxScript->tick(dt);
2453             if (finished)
2454                 cancelScript();
2455         }
2456     }
2457 
2458     if (luaHook != NULL)
2459         luaHook->callLuaHook(this, "tick", dt);
2460 #endif // CELX
2461 
2462     sim->update(dt);
2463 }
2464 
2465 
draw()2466 void CelestiaCore::draw()
2467 {
2468     if (!viewUpdateRequired())
2469         return;
2470     viewChanged = false;
2471 
2472     if (views.size() == 1)
2473     {
2474         // I'm not certain that a special case for one view is required; but,
2475         // it's possible that there exists some broken hardware out there
2476         // that has to fall back to software rendering if the scissor test
2477         // is enable.  To keep performance on this hypothetical hardware
2478         // reasonable in the typical single view case, we'll use this
2479         // scissorless special case.  I'm only paranoid because I've been
2480         // burned by crap hardware so many times. cjl
2481         glViewport(0, 0, width, height);
2482         renderer->resize(width, height);
2483         sim->render(*renderer);
2484     }
2485     else
2486     {
2487         glEnable(GL_SCISSOR_TEST);
2488         for (list<View*>::iterator iter = views.begin();
2489              iter != views.end(); iter++)
2490         {
2491             View* view = *iter;
2492             if (view->type == View::ViewWindow)
2493             {
2494                 glScissor((GLint) (view->x * width),
2495                           (GLint) (view->y * height),
2496                           (GLsizei) (view->width * width),
2497                           (GLsizei) (view->height * height));
2498                 glViewport((GLint) (view->x * width),
2499                            (GLint) (view->y * height),
2500                            (GLsizei) (view->width * width),
2501                            (GLsizei) (view->height * height));
2502                 renderer->resize((int) (view->width * width),
2503                                  (int) (view->height * height));
2504                 sim->render(*renderer, *view->observer);
2505             }
2506         }
2507         glDisable(GL_SCISSOR_TEST);
2508         glViewport(0, 0, width, height);
2509     }
2510 
2511     renderOverlay();
2512 	if (showConsole)
2513     {
2514         console.setFont(font);
2515         glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
2516         console.begin();
2517         glTranslatef(0.0f, 200.0f, 0.0f);
2518         console.render(ConsolePageRows);
2519         console.end();
2520     }
2521 
2522 
2523     if (movieCapture != NULL && recording)
2524         movieCapture->captureFrame();
2525 
2526     // Frame rate counter
2527     nFrames++;
2528     if (nFrames == 100 || sysTime - fpsCounterStartTime > 10.0)
2529     {
2530         fps = (double) nFrames / (sysTime - fpsCounterStartTime);
2531         nFrames = 0;
2532         fpsCounterStartTime = sysTime;
2533     }
2534 
2535 #if 0
2536     GLenum err = glGetError();
2537     if (err != GL_NO_ERROR)
2538     {
2539         cout << _("GL error: ") << gluErrorString(err) << '\n';
2540     }
2541 #endif
2542 }
2543 
2544 
resize(GLsizei w,GLsizei h)2545 void CelestiaCore::resize(GLsizei w, GLsizei h)
2546 {
2547     if (h == 0)
2548 	h = 1;
2549 
2550     glViewport(0, 0, w, h);
2551     if (renderer != NULL)
2552         renderer->resize(w, h);
2553     if (overlay != NULL)
2554         overlay->setWindowSize(w, h);
2555     console.setScale(w, h);
2556     width = w;
2557     height = h;
2558 
2559     setFOVFromZoom();
2560 #ifdef CELX
2561     if (luaHook && luaHook->callLuaHook(this,"resize", (float) w, (float) h))
2562         return;
2563 #endif
2564 }
2565 
2566 
2567 // Return true if anything changed that requires re-rendering. Otherwise, we
2568 // can skip rendering, keep the GPU idle, and save power.
viewUpdateRequired() const2569 bool CelestiaCore::viewUpdateRequired() const
2570 {
2571 #if 1
2572     // Enable after 1.5.0
2573     return true;
2574 #else
2575     bool isPaused = sim->getPauseState() || sim->getTimeScale() == 0.0;
2576 
2577     // See if the camera in any of the views is moving
2578     bool observersMoving = false;
2579     for (vector<View*>::const_iterator iter = views.begin(); iter != views.end(); iter++)
2580     {
2581         View* v = *iter;
2582         if (v->observer->getAngularVelocity().length() > 1.0e-10 ||
2583             v->observer->getVelocity().length() > 1.0e-12)
2584         {
2585             observersMoving = true;
2586             break;
2587         }
2588     }
2589 
2590     if (viewChanged ||
2591         !isPaused ||
2592         observersMoving ||
2593         dollyMotion != 0.0 ||
2594         zoomMotion != 0.0 ||
2595         scriptState == ScriptRunning ||
2596         renderer->settingsHaveChanged())
2597     {
2598         return true;
2599     }
2600     else
2601     {
2602         return false;
2603     }
2604 #endif
2605 }
2606 
2607 
setViewChanged()2608 void CelestiaCore::setViewChanged()
2609 {
2610     viewChanged = true;
2611 }
2612 
2613 
splitView(View::Type type,View * av,float splitPos)2614 void CelestiaCore::splitView(View::Type type, View* av, float splitPos)
2615 {
2616     setViewChanged();
2617 
2618     if (av == NULL)
2619         av = (*activeView);
2620     bool vertical = ( type == View::VerticalSplit );
2621     Observer* o = sim->addObserver();
2622     bool tooSmall = false;
2623 
2624     switch (type) // If active view is too small, don't split it.
2625     {
2626     case View::HorizontalSplit:
2627         if (av->height < 0.2f) tooSmall = true;
2628         break;
2629     case View::VerticalSplit:
2630         if (av->width < 0.2f) tooSmall = true;
2631         break;
2632     case View::ViewWindow:
2633         return;
2634         break;
2635     }
2636 
2637     if (tooSmall)
2638     {
2639         flash(_("View too small to be split"));
2640         return;
2641     }
2642     flash(_("Added view"));
2643 
2644     // Make the new observer a copy of the old one
2645     // TODO: This works, but an assignment operator for Observer
2646     // should be defined.
2647     *o = *(sim->getActiveObserver());
2648 
2649     float w1, h1, w2, h2;
2650     if (vertical)
2651     {
2652         w1 = av->width * splitPos;
2653         w2 = av->width - w1;
2654         h1 = av->height;
2655         h2 = av->height;
2656     }
2657     else
2658     {
2659         w1 = av->width;
2660         w2 = av->width;
2661         h1 = av->height * splitPos;
2662         h2 = av->height - h1;
2663     }
2664 
2665     View* split = new View(type,
2666                            0,
2667                            av->x,
2668                            av->y,
2669                            av->width,
2670                            av->height);
2671     split->parent = av->parent;
2672     if (av->parent != 0)
2673     {
2674         if (av->parent->child1 == av)
2675             av->parent->child1 = split;
2676         else
2677             av->parent->child2 = split;
2678     }
2679     split->child1 = av;
2680 
2681     av->width = w1;
2682     av->height = h1;
2683     av->parent = split;
2684 
2685     View* view = new View(View::ViewWindow,
2686                           o,
2687                           av->x + (vertical ? w1 : 0),
2688                           av->y + (vertical ? 0  : h1),
2689                           w2, h2);
2690     split->child2 = view;
2691     view->parent = split;
2692     view->zoom = av->zoom;
2693 
2694     views.insert(views.end(), split);
2695     views.insert(views.end(), view);
2696 
2697     setFOVFromZoom();
2698 }
2699 
setFOVFromZoom()2700 void CelestiaCore::setFOVFromZoom()
2701 {
2702     for (list<View*>::iterator i = views.begin(); i != views.end(); i++)
2703         if ((*i)->type == View::ViewWindow)
2704         {
2705             double fov = 2 * atan(height * (*i)->height / (screenDpi / 25.4) / 2. / distanceToScreen) / (*i)->zoom;
2706             (*i)->observer->setFOV((float) fov);
2707         }
2708 }
2709 
setZoomFromFOV()2710 void CelestiaCore::setZoomFromFOV()
2711 {
2712     for (list<View*>::iterator i = views.begin(); i != views.end(); i++)
2713         if ((*i)->type == View::ViewWindow)
2714         {
2715             (*i)->zoom = (float) (2 * atan(height * (*i)->height / (screenDpi / 25.4) / 2. / distanceToScreen) /  (*i)->observer->getFOV());
2716         }
2717 }
2718 
singleView(View * av)2719 void CelestiaCore::singleView(View* av)
2720 {
2721     setViewChanged();
2722 
2723     if (av == NULL)
2724         av = (*activeView);
2725 
2726     list<View*>::iterator i = views.begin();
2727     while(i != views.end())
2728     {
2729         if ((*i) != av)
2730         {
2731             sim->removeObserver((*i)->observer);
2732             delete (*i)->observer;
2733             delete (*i);
2734             i=views.erase(i);
2735         }
2736         else
2737             i++;
2738     }
2739 
2740     av->x = 0.0f;
2741     av->y = 0.0f;
2742     av->width = 1.0f;
2743     av->height = 1.0f;
2744     av->parent = 0;
2745     av->child1 = 0;
2746     av->child2 = 0;
2747 
2748     activeView = views.begin();
2749     sim->setActiveObserver((*activeView)->observer);
2750     setFOVFromZoom();
2751 }
2752 
setActiveView(View * v)2753 void CelestiaCore::setActiveView(View* v)
2754 {
2755     activeView = find(views.begin(),views.end(),v);
2756     sim->setActiveObserver((*activeView)->observer);
2757 }
2758 
deleteView(View * v)2759 void CelestiaCore::deleteView(View* v)
2760 {
2761     if (v == NULL)
2762         v = (*activeView);
2763 
2764     if (v->parent == 0) return;
2765 
2766     //Erase view and parent view from views
2767     list<View*>::iterator i = views.begin();
2768     while(i != views.end())
2769     {
2770         if ((*i == v) || (*i == v->parent))
2771             i=views.erase(i);
2772         else
2773             i++;
2774     }
2775 
2776     int sign;
2777     View *sibling;
2778     if (v->parent->child1 == v)
2779     {
2780         sibling = v->parent->child2;
2781         sign = -1;
2782     }
2783     else
2784     {
2785         sibling = v->parent->child1;
2786         sign = 1;
2787     }
2788     sibling->parent = v->parent->parent;
2789     if (v->parent->parent != 0) {
2790         if (v->parent->parent->child1 == v->parent)
2791             v->parent->parent->child1 = sibling;
2792         else
2793             v->parent->parent->child2 = sibling;
2794     }
2795 
2796     v->walkTreeResize(sibling, sign);
2797 
2798     sim->removeObserver(v->observer);
2799     delete(v->observer);
2800     View* nextActiveView = sibling;
2801     while (nextActiveView->type != View::ViewWindow)
2802         nextActiveView = nextActiveView->child1;
2803     activeView = find(views.begin(),views.end(),nextActiveView);
2804     sim->setActiveObserver((*activeView)->observer);
2805 
2806     delete(v->parent);
2807     delete(v);
2808 
2809     if (!showActiveViewFrame)
2810         flashFrameStart = currentTime;
2811     setFOVFromZoom();
2812 }
2813 
getFramesVisible() const2814 bool CelestiaCore::getFramesVisible() const
2815 {
2816     return showViewFrames;
2817 }
2818 
setFramesVisible(bool visible)2819 void CelestiaCore::setFramesVisible(bool visible)
2820 {
2821     setViewChanged();
2822 
2823     showViewFrames = visible;
2824 }
2825 
getActiveFrameVisible() const2826 bool CelestiaCore::getActiveFrameVisible() const
2827 {
2828     return showActiveViewFrame;
2829 }
2830 
setActiveFrameVisible(bool visible)2831 void CelestiaCore::setActiveFrameVisible(bool visible)
2832 {
2833     setViewChanged();
2834 
2835     showActiveViewFrame = visible;
2836 }
2837 
2838 
setContextMenuCallback(ContextMenuFunc callback)2839 void CelestiaCore::setContextMenuCallback(ContextMenuFunc callback)
2840 {
2841     contextMenuCallback = callback;
2842 }
2843 
2844 
getRenderer() const2845 Renderer* CelestiaCore::getRenderer() const
2846 {
2847     return renderer;
2848 }
2849 
getSimulation() const2850 Simulation* CelestiaCore::getSimulation() const
2851 {
2852     return sim;
2853 }
2854 
showText(string s,int horig,int vorig,int hoff,int voff,double duration)2855 void CelestiaCore::showText(string s,
2856                             int horig, int vorig,
2857                             int hoff, int voff,
2858                             double duration)
2859 {
2860     messageText = s;
2861     messageHOrigin = horig;
2862     messageVOrigin = vorig;
2863     messageHOffset = hoff;
2864     messageVOffset = voff;
2865     messageStart = currentTime;
2866     messageDuration = duration;
2867 }
2868 
getTextWidth(string s) const2869 int CelestiaCore::getTextWidth(string s) const
2870 {
2871     return titleFont->getWidth(s);
2872 }
2873 
SigDigitNum(double v,int digits)2874 static FormattedNumber SigDigitNum(double v, int digits)
2875 {
2876     return FormattedNumber(v, digits,
2877                            FormattedNumber::GroupThousands |
2878                            FormattedNumber::SignificantDigits);
2879 }
2880 
2881 
displayDistance(Overlay & overlay,double distance)2882 static void displayDistance(Overlay& overlay, double distance)
2883 {
2884     const char* units = "";
2885 
2886     if (abs(distance) >= astro::parsecsToLightYears(1e+6))
2887     {
2888         units = "Mpc";
2889         distance = astro::lightYearsToParsecs(distance) / 1e+6;
2890     }
2891     else if (abs(distance) >= 0.5 * astro::parsecsToLightYears(1e+3))
2892     {
2893         units = "Kpc";
2894         distance = astro::lightYearsToParsecs(distance) / 1e+3;
2895     }
2896     else if (abs(distance) >= astro::AUtoLightYears(1000.0f))
2897     {
2898         units = _("ly");
2899     }
2900     else if (abs(distance) >= astro::kilometersToLightYears(10000000.0))
2901     {
2902         units = _("au");
2903         distance = astro::lightYearsToAU(distance);
2904     }
2905     else if (abs(distance) > astro::kilometersToLightYears(1.0f))
2906     {
2907         units = "km";
2908         distance = astro::lightYearsToKilometers(distance);
2909     }
2910     else
2911     {
2912         units = "m";
2913         distance = astro::lightYearsToKilometers(distance) * 1000.0f;
2914     }
2915 
2916     overlay << SigDigitNum(distance, 5) << ' ' << units;
2917 }
2918 
2919 
displayDuration(Overlay & overlay,double days)2920 static void displayDuration(Overlay& overlay, double days)
2921 {
2922     if (days > 1.0)
2923         overlay << FormattedNumber(days, 3, FormattedNumber::GroupThousands) << _(" days");
2924     else if (days > 1.0 / 24.0)
2925         overlay << FormattedNumber(days * 24.0, 3, FormattedNumber::GroupThousands) << _(" hours");
2926     else if (days > 1.0 / (24.0 * 60.0))
2927         overlay << FormattedNumber(days * 24.0 * 60.0, 3, FormattedNumber::GroupThousands) << _(" minutes");
2928     else
2929         overlay << FormattedNumber(days * 24.0 * 60.0 * 60.0, 3, FormattedNumber::GroupThousands) << _(" seconds");
2930 }
2931 
2932 
2933 // Display a positive angle as degrees, minutes, and seconds. If the angle is less than one
2934 // degree, only minutes and seconds are shown; if the angle is less than one minute, only
2935 // seconds are displayed.
displayAngle(Overlay & overlay,double angle)2936 static void displayAngle(Overlay& overlay, double angle)
2937 {
2938     int degrees, minutes;
2939     double seconds;
2940     astro::decimalToDegMinSec(angle, degrees, minutes, seconds);
2941 
2942     if (degrees > 0)
2943     {
2944         overlay.oprintf("%d%s %02d' %.1f\"",
2945                         degrees, UTF8_DEGREE_SIGN, abs(minutes), abs(seconds));
2946     }
2947     else if (minutes > 0)
2948     {
2949         overlay.oprintf("%02d' %.1f\"", abs(minutes), abs(seconds));
2950     }
2951     else
2952     {
2953         overlay.oprintf("%.2f\"", abs(seconds));
2954     }
2955 }
2956 
displayDeclination(Overlay & overlay,double angle)2957 static void displayDeclination(Overlay& overlay, double angle)
2958 {
2959     int degrees, minutes;
2960     double seconds;
2961     astro::decimalToDegMinSec(angle, degrees, minutes, seconds);
2962 
2963     char sign = '+';
2964     if (angle < 0.0)
2965         sign = '-';
2966 
2967     overlay.oprintf("%c%d%s %02d' %.1f\"",
2968                     sign, abs(degrees), UTF8_DEGREE_SIGN, abs(minutes), abs(seconds));
2969 }
2970 
2971 
displayRightAscension(Overlay & overlay,double angle)2972 static void displayRightAscension(Overlay& overlay, double angle)
2973 {
2974     int hours, minutes;
2975     double seconds;
2976     astro::decimalToHourMinSec(angle, hours, minutes, seconds);
2977 
2978     overlay.oprintf("%dh %02dm %.1fs",
2979                         hours, abs(minutes), abs(seconds));
2980 }
2981 
displayApparentDiameter(Overlay & overlay,double radius,double distance)2982 static void displayApparentDiameter(Overlay& overlay,
2983                                     double radius,
2984                                     double distance)
2985 {
2986     if (distance > radius)
2987     {
2988         double arcSize = radToDeg(asin(radius / distance) * 2.0);
2989 
2990         // Only display the arc size if it's less than 160 degrees and greater
2991         // than one second--otherwise, it's probably not interesting data.
2992         if (arcSize < 160.0 && arcSize > 1.0 / 3600.0)
2993         {
2994             overlay << _("Apparent diameter: ");
2995             displayAngle(overlay, arcSize);
2996             overlay << '\n';
2997         }
2998     }
2999 }
3000 
displayApparentMagnitude(Overlay & overlay,float absMag,double distance)3001 static void displayApparentMagnitude(Overlay& overlay,
3002                                      float absMag,
3003                                      double distance)
3004 {
3005     float appMag = absMag;
3006     if (distance > 32.6167)
3007     {
3008         appMag = astro::absToAppMag(absMag, (float) distance);
3009         overlay << _("Apparent magnitude: ");
3010     }
3011     else
3012     {
3013         overlay << _("Absolute magnitude: ");
3014     }
3015 
3016     overlay.oprintf("%.1f\n", appMag);
3017 }
3018 
3019 
displayRADec(Overlay & overlay,Vec3d v)3020 static void displayRADec(Overlay& overlay, Vec3d v)
3021 {
3022     double phi = atan2(v.x, v.z) - PI / 2;
3023     if (phi < 0)
3024         phi = phi + 2 * PI;
3025 
3026     double theta = atan2(sqrt(v.x * v.x + v.z * v.z), v.y);
3027     if (theta > 0)
3028         theta = PI / 2 - theta;
3029     else
3030         theta = -PI / 2 - theta;
3031 
3032     double ra = radToDeg(phi);
3033     double dec = radToDeg(theta);
3034 
3035     overlay << _("RA: ");
3036     overlay << " ";
3037     displayRightAscension(overlay, ra);
3038     overlay << endl;
3039     overlay << _("Dec: ");
3040     displayDeclination(overlay, dec);
3041     overlay << endl;
3042 }
3043 
3044 
3045 // Display nicely formatted planetocentric/planetographic coordinates.
3046 // The latitude and longitude parameters are angles in radians, altitude
3047 // is in kilometers.
displayPlanetocentricCoords(Overlay & overlay,const Body & body,double longitude,double latitude,double altitude,bool showAltitude)3048 static void displayPlanetocentricCoords(Overlay& overlay,
3049                                         const Body& body,
3050                                         double longitude,
3051                                         double latitude,
3052                                         double altitude,
3053                                         bool showAltitude)
3054 {
3055     char ewHemi = ' ';
3056     char nsHemi = ' ';
3057     double lon = 0.0;
3058     double lat = 0.0;
3059 
3060     // Terrible hack for Earth and Moon longitude conventions.  Fix by
3061     // adding a field to specify the longitude convention in .ssc files.
3062     if (body.getName() == "Earth" || body.getName() == "Moon")
3063     {
3064         if (latitude < 0.0)
3065             nsHemi = 'S';
3066         else if (latitude > 0.0)
3067             nsHemi = 'N';
3068 
3069         if (longitude < 0.0)
3070             ewHemi = 'W';
3071         else if (longitude > 0.0f)
3072             ewHemi = 'E';
3073 
3074         lon = (float) abs(radToDeg(longitude));
3075         lat = (float) abs(radToDeg(latitude));
3076     }
3077     else
3078     {
3079         // Swap hemispheres if the object is a retrograde rotator
3080         Quatd q = ~body.getEclipticToEquatorial(astro::J2000);
3081         bool retrograde = (Vec3d(0.0, 1.0, 0.0) * q.toMatrix3()).y < 0.0;
3082 
3083         if ((latitude < 0.0) ^ retrograde)
3084             nsHemi = 'S';
3085         else if ((latitude > 0.0) ^ retrograde)
3086             nsHemi = 'N';
3087 
3088         if (retrograde)
3089             ewHemi = 'E';
3090         else
3091             ewHemi = 'W';
3092 
3093         lon = -radToDeg(longitude);
3094         if (lon < 0.0)
3095             lon += 360.0;
3096         lat = abs(radToDeg(latitude));
3097     }
3098 
3099     overlay.unsetf(ios::fixed);
3100     overlay << setprecision(6);
3101     overlay << lat << nsHemi << ' ' << lon << ewHemi;
3102     if (showAltitude)
3103         overlay << ' ' << altitude << _("km") << endl;
3104     overlay << endl;
3105 }
3106 
3107 
3108 #if 0
3109 // Show the planetocentric latitude, longitude, and altitude of a
3110 // observer.
3111 static void displayObserverPlanetocentricCoords(Overlay& overlay,
3112                                                 Body& body,
3113                                                 const UniversalCoord& observerPos,
3114                                                 double tdb)
3115 {
3116     // Get the observer position in body-centered ecliptical coordinates
3117     Vec3d ecl = observerPos - Selection(&body).getPosition(tdb);
3118     ecl *= astro::microLightYearsToKilometers(1.0);
3119     Vec3d pc = body.eclipticToPlanetocentric(ecl, tdb);
3120 
3121     displayPlanetocentricCoords(overlay, body, pc.x, pc.y, pc.z, true);
3122 }
3123 #endif
3124 
3125 
displayStarInfo(Overlay & overlay,int detail,Star & star,const Universe & universe,double distance)3126 static void displayStarInfo(Overlay& overlay,
3127                             int detail,
3128                             Star& star,
3129                             const Universe& universe,
3130                             double distance)
3131 {
3132     overlay << _("Distance: ");
3133     displayDistance(overlay, distance);
3134     overlay << '\n';
3135 
3136     if (!star.getVisibility())
3137     {
3138         overlay << _("Star system barycenter\n");
3139     }
3140     else
3141     {
3142         overlay.oprintf(_("Abs (app) mag: %.2f (%.2f)\n"),
3143                        star.getAbsoluteMagnitude(),
3144                        astro::absToAppMag(star.getAbsoluteMagnitude(),
3145                                           (float) distance));
3146 
3147         if (star.getLuminosity() > 1.0e-10f)
3148             overlay << _("Luminosity: ") << SigDigitNum(star.getLuminosity(), 3) << _("x Sun") << "\n";
3149         overlay << _("Class: ");
3150         if (star.getSpectralType()[0] == 'Q')
3151             overlay << _("Neutron star");
3152         else if (star.getSpectralType()[0] == 'X')
3153             overlay << _("Black hole");
3154         else
3155             overlay << star.getSpectralType();
3156         overlay << '\n';
3157 
3158         displayApparentDiameter(overlay, star.getRadius(),
3159                                 astro::lightYearsToKilometers(distance));
3160 
3161         if (detail > 1)
3162         {
3163             overlay << _("Surface temp: ") << SigDigitNum(star.getTemperature(), 3) << " K\n";
3164             float solarRadii = star.getRadius() / 6.96e5f;
3165 
3166             overlay << _("Radius: ");
3167             if (solarRadii > 0.01f)
3168             {
3169                 overlay << SigDigitNum(star.getRadius() / 696000.0f, 2) << " " << _("Rsun")
3170                         << "  (" << SigDigitNum(star.getRadius(), 3) << " km" << ")\n";
3171             }
3172             else
3173             {
3174                 overlay << SigDigitNum(star.getRadius(), 3) << " km\n";
3175             }
3176 
3177             if (star.getRotationModel()->isPeriodic())
3178             {
3179                 overlay << _("Rotation period: ");
3180                 float period = (float) star.getRotationModel()->getPeriod();
3181                 displayDuration(overlay, period);
3182                 overlay << '\n';
3183             }
3184 
3185 
3186         }
3187     }
3188 
3189     if (detail > 1)
3190     {
3191         SolarSystem* sys = universe.getSolarSystem(&star);
3192         if (sys != NULL && sys->getPlanets()->getSystemSize() != 0)
3193             overlay << _("Planetary companions present\n");
3194     }
3195 }
3196 
3197 
displayDSOinfo(Overlay & overlay,const DeepSkyObject & dso,double distance)3198 static void displayDSOinfo(Overlay& overlay, const DeepSkyObject& dso, double distance)
3199 {
3200     char descBuf[128];
3201 
3202     dso.getDescription(descBuf, sizeof(descBuf));
3203     overlay << descBuf << '\n';
3204     if (distance >= 0)
3205     {
3206     	overlay << _("Distance: ");
3207     	displayDistance(overlay, distance);
3208     }
3209     else
3210     {
3211         overlay << _("Distance from center: ");
3212         displayDistance(overlay, distance + dso.getRadius());
3213      }
3214     overlay << '\n';
3215     overlay << _("Radius: ");
3216     displayDistance(overlay, dso.getRadius());
3217     overlay << '\n';
3218 
3219     displayApparentDiameter(overlay, dso.getRadius(), distance);
3220     if (dso.getAbsoluteMagnitude() > DSO_DEFAULT_ABS_MAGNITUDE)
3221     {
3222         displayApparentMagnitude(overlay,
3223                                  dso.getAbsoluteMagnitude(),
3224                                  distance);
3225     }
3226 }
3227 
3228 
displayPlanetInfo(Overlay & overlay,int detail,Body & body,double t,double distance,Vec3d viewVec)3229 static void displayPlanetInfo(Overlay& overlay,
3230                               int detail,
3231                               Body& body,
3232                               double t,
3233                               double distance,
3234                               Vec3d viewVec)
3235 {
3236     double kmDistance = astro::lightYearsToKilometers(distance);
3237 
3238     overlay << _("Distance: ");
3239     distance = astro::kilometersToLightYears(kmDistance - body.getRadius());
3240     displayDistance(overlay, distance);
3241     overlay << '\n';
3242 
3243     if (body.getClassification() == Body::Invisible)
3244     {
3245         return;
3246     }
3247 
3248     overlay << _("Radius: ");
3249     distance = astro::kilometersToLightYears(body.getRadius());
3250     displayDistance(overlay, distance);
3251     overlay << '\n';
3252 
3253     displayApparentDiameter(overlay, body.getRadius(), kmDistance);
3254 
3255 	// Display the phase angle
3256 
3257 	// Find the parent star of the body. This can be slightly complicated if
3258 	// the body orbits a barycenter instead of a star.
3259 	Selection parent = Selection(&body).parent();
3260 	while (parent.body() != NULL)
3261 		parent = parent.parent();
3262 
3263 	if (parent.star() != NULL)
3264 	{
3265 		bool showPhaseAngle = false;
3266 
3267 		Star* sun = parent.star();
3268 		if (sun->getVisibility())
3269 		{
3270 			showPhaseAngle = true;
3271 		}
3272 		else if (sun->getOrbitingStars())
3273 		{
3274 			// The planet's orbit is defined with respect to a barycenter. If there's
3275 			// a single star orbiting the barycenter, we'll compute the phase angle
3276 			// for the planet with respect to that star. If there are no stars, the
3277 			// planet is an orphan, drifting through space with no star. We also skip
3278 			// displaying the phase angle when there are multiple stars (for now.)
3279 			if (sun->getOrbitingStars()->size() == 1)
3280 			{
3281 				sun = sun->getOrbitingStars()->at(0);
3282 				showPhaseAngle = sun->getVisibility();
3283 			}
3284 		}
3285 
3286 		if (showPhaseAngle)
3287 		{
3288 			Vec3d sunVec = Selection(&body).getPosition(t) - Selection(sun).getPosition(t);
3289 			sunVec.normalize();
3290 			double cosPhaseAngle = sunVec * ((1.0 / viewVec.length()) * viewVec);
3291 			double phaseAngle = acos(cosPhaseAngle);
3292 			overlay.oprintf(_("Phase angle: %.1f%s\n"), radToDeg(phaseAngle), UTF8_DEGREE_SIGN);
3293 		}
3294 	}
3295 
3296     if (detail > 1)
3297     {
3298         if (body.getRotationModel(t)->isPeriodic())
3299         {
3300             overlay << _("Rotation period: ");
3301             displayDuration(overlay, body.getRotationModel(t)->getPeriod());
3302             overlay << '\n';
3303         }
3304 
3305         PlanetarySystem* system = body.getSystem();
3306         if (system != NULL)
3307         {
3308             const Star* sun = system->getStar();
3309             if (sun != NULL)
3310             {
3311                 double distFromSun = body.getAstrocentricPosition(t).distanceFromOrigin();
3312                 float planetTemp = sun->getTemperature() *
3313                     (float) (::pow(1.0 - body.getAlbedo(), 0.25) *
3314                              sqrt(sun->getRadius() / (2.0 * distFromSun)));
3315                 overlay << setprecision(0);
3316                 overlay << _("Temperature: ") << planetTemp << " K\n";
3317                 overlay << setprecision(3);
3318             }
3319 
3320 #if 0
3321             // Code to display apparent magnitude.  Disabled because it's not very
3322             // accurate.  Too many simplifications are used when computing the amount
3323             // of light reflected from a body.
3324             Point3d bodyPos = body.getAstrocentricPosition(t);
3325             float appMag = body.getApparentMagnitude(*sun,
3326                                                      bodyPos - Point3d(0, 0, 0),
3327                                                      viewVec);
3328             overlay.oprintf(_("Apparent mag: %.2f\n"), appMag);
3329 #endif
3330         }
3331     }
3332 }
3333 
3334 
displayLocationInfo(Overlay & overlay,Location & location,double distance)3335 static void displayLocationInfo(Overlay& overlay,
3336                                 Location& location,
3337                                 double distance)
3338 {
3339     overlay << _("Distance: ");
3340     displayDistance(overlay, distance);
3341     overlay << '\n';
3342 
3343     Body* body = location.getParentBody();
3344     if (body != NULL)
3345     {
3346         Vec3f locPos = location.getPosition();
3347         Vec3d lonLatAlt = body->cartesianToPlanetocentric(Vec3d(locPos.x, locPos.y, locPos.z));
3348         displayPlanetocentricCoords(overlay, *body,
3349                                     lonLatAlt.x, lonLatAlt.y, lonLatAlt.z, false);
3350     }
3351 }
3352 
3353 
displaySelectionName(Overlay & overlay,const Selection & sel,const Universe & univ)3354 static void displaySelectionName(Overlay& overlay,
3355                                  const Selection& sel,
3356                                  const Universe& univ)
3357 {
3358     switch (sel.getType())
3359     {
3360     case Selection::Type_Body:
3361         overlay << sel.body()->getName(true).c_str();
3362         break;
3363     case Selection::Type_DeepSky:
3364         overlay << univ.getDSOCatalog()->getDSOName(sel.deepsky(), true);
3365         break;
3366     case Selection::Type_Star:
3367         //displayStarName(overlay, *(sel.star()), *univ.getStarCatalog());
3368         overlay << ReplaceGreekLetterAbbr(univ.getStarCatalog()->getStarName(*sel.star(), true));
3369         break;
3370     case Selection::Type_Location:
3371         overlay << sel.location()->getName(true).c_str();
3372         break;
3373     default:
3374         break;
3375     }
3376 }
3377 
3378 
showViewFrame(const View * v,int width,int height)3379 static void showViewFrame(const View* v, int width, int height)
3380 {
3381     glBegin(GL_LINE_LOOP);
3382     glVertex3f(v->x * width, v->y * height, 0.0f);
3383     glVertex3f(v->x * width, (v->y + v->height) * height - 1, 0.0f);
3384     glVertex3f((v->x + v->width) * width - 1, (v->y + v->height) * height - 1, 0.0f);
3385     glVertex3f((v->x + v->width) * width - 1, v->y * height, 0.0f);
3386     glEnd();
3387 }
3388 
3389 
renderOverlay()3390 void CelestiaCore::renderOverlay()
3391 {
3392 
3393 #ifdef CELX
3394     if (luaHook) luaHook->callLuaHook(this,"renderoverlay");
3395 #endif
3396     if (font == NULL)
3397         return;
3398 
3399     overlay->setFont(font);
3400 
3401     int fontHeight = font->getHeight();
3402     int emWidth = font->getWidth("M");
3403 
3404     overlay->begin();
3405 
3406 
3407     if (views.size() > 1)
3408     {
3409         // Render a thin border arround all views
3410         if (showViewFrames || resizeSplit)
3411         {
3412             glLineWidth(1.0f);
3413             glDisable(GL_TEXTURE_2D);
3414             glColor4f(0.5f, 0.5f, 0.5f, 1.0f);
3415             for(list<View*>::iterator i = views.begin(); i != views.end(); i++)
3416                 if ((*i)->type == View::ViewWindow)
3417                     showViewFrame(*i, width, height);
3418         }
3419         glLineWidth(1.0f);
3420 
3421         // Render a very simple border around the active view
3422         View* av = (*activeView);
3423 
3424         if (showActiveViewFrame)
3425         {
3426             glLineWidth(2.0f);
3427             glDisable(GL_TEXTURE_2D);
3428             glColor4f(0.5f, 0.5f, 1.0f, 1.0f);
3429             showViewFrame(av, width, height);
3430             glLineWidth(1.0f);
3431         }
3432 
3433         if (currentTime < flashFrameStart + 0.5)
3434         {
3435             glLineWidth(8.0f);
3436             glColor4f(0.5f, 0.5f, 1.0f,
3437                       (float) (1.0 - (currentTime - flashFrameStart) / 0.5));
3438             showViewFrame(av, width, height);
3439             glLineWidth(1.0f);
3440         }
3441     }
3442 
3443     setlocale(LC_NUMERIC, "");
3444 
3445     if (hudDetail > 0 && (overlayElements & ShowTime))
3446     {
3447         double lt = 0.0;
3448 
3449         if (sim->getSelection().getType() == Selection::Type_Body &&
3450             (sim->getTargetSpeed() < 0.99 *
3451              astro::kilometersToMicroLightYears(astro::speedOfLight)))
3452         {
3453     	    if (lightTravelFlag)
3454     	    {
3455     	        Vec3d v = sim->getSelection().getPosition(sim->getTime()) -
3456                               sim->getObserver().getPosition();
3457     	        // light travel time in days
3458                 lt = astro::microLightYearsToKilometers(v.length()) / (86400.0 * astro::speedOfLight);
3459     	    }
3460     	}
3461         else
3462     	{
3463     	    lt = 0.0;
3464     	}
3465 
3466         double tdb = sim->getTime() + lt;
3467         astro::Date d = timeZoneBias != 0?astro::TDBtoLocal(tdb):astro::TDBtoUTC(tdb);
3468         const char* dateStr = d.toCStr(dateFormat);
3469         int dateWidth = (font->getWidth(dateStr)/(emWidth * 3) + 2) * emWidth * 3;
3470         if (dateWidth > dateStrWidth) dateStrWidth = dateWidth;
3471 
3472         // Time and date
3473         glPushMatrix();
3474         glColor4f(0.7f, 0.7f, 1.0f, 1.0f);
3475         glTranslatef( (float) (width - dateStrWidth),
3476                       (float) (height - fontHeight),
3477                       0.0f);
3478         overlay->beginText();
3479 
3480         overlay->print(dateStr);
3481 
3482         if (lightTravelFlag && lt > 0.0)
3483         {
3484             glColor4f(0.42f, 1.0f, 1.0f, 1.0f);
3485             *overlay << _("  LT");
3486             glColor4f(0.7f, 0.7f, 1.0f, 1.0f);
3487         }
3488         *overlay << '\n';
3489 
3490         {
3491             if (abs(abs(sim->getTimeScale()) - 1) < 1e-6)
3492             {
3493                 if (sign(sim->getTimeScale()) == 1)
3494                     *overlay << _("Real time");
3495                 else
3496                     *overlay << _("-Real time");
3497             }
3498             else if (abs(sim->getTimeScale()) < MinimumTimeRate)
3499             {
3500                 *overlay << _("Time stopped");
3501             }
3502             else if (abs(sim->getTimeScale()) > 1.0)
3503             {
3504                 overlay->oprintf(TIMERATE_PRINTF_FORMAT, sim->getTimeScale());
3505                 *overlay << UTF8_MULTIPLICATION_SIGN << _(" faster");
3506             }
3507             else
3508             {
3509                 overlay->oprintf(TIMERATE_PRINTF_FORMAT, 1.0 / sim->getTimeScale());
3510                 *overlay << UTF8_MULTIPLICATION_SIGN << _(" slower");
3511             }
3512 
3513             if (sim->getPauseState() == true)
3514             {
3515                 glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
3516                 *overlay << _(" (Paused)");
3517             }
3518         }
3519 
3520         overlay->endText();
3521         glPopMatrix();
3522     }
3523 
3524     if (hudDetail > 0 && (overlayElements & ShowVelocity))
3525     {
3526         // Speed
3527         glPushMatrix();
3528         glTranslatef(0.0f, (float) (fontHeight * 2 + 5), 0.0f);
3529         glColor4f(0.7f, 0.7f, 1.0f, 1.0f);
3530 
3531         overlay->beginText();
3532         *overlay << '\n';
3533         if (showFPSCounter)
3534             *overlay << _("FPS: ") << SigDigitNum(fps, 3);
3535         overlay->setf(ios::fixed);
3536         *overlay << _("\nSpeed: ");
3537 
3538         double speed = sim->getObserver().getVelocity().length();
3539         if (speed < astro::kilometersToMicroLightYears(1.0f))
3540             *overlay << SigDigitNum(astro::microLightYearsToKilometers(speed) * 1000.0f, 3) << _(" m/s");
3541         else if (speed < astro::kilometersToMicroLightYears(10000.0f))
3542             *overlay << SigDigitNum(astro::microLightYearsToKilometers(speed), 3) << _(" km/s");
3543         else if (speed < astro::kilometersToMicroLightYears((float) astro::speedOfLight * 100.0f))
3544             *overlay << SigDigitNum(astro::microLightYearsToKilometers(speed) / astro::speedOfLight, 3) << 'c';
3545         else if (speed < astro::AUtoMicroLightYears(1000.0f))
3546             *overlay << SigDigitNum(astro::microLightYearsToAU(speed), 3) << _(" AU/s");
3547         else
3548             *overlay << SigDigitNum(speed * 1e-6, 3) << _(" ly/s");
3549 
3550         overlay->endText();
3551         glPopMatrix();
3552     }
3553 
3554     if (hudDetail > 0 && (overlayElements & ShowFrame))
3555     {
3556         // Field of view and camera mode in lower right corner
3557         glPushMatrix();
3558         glTranslatef((float) (width - emWidth * 15),
3559                      (float) (fontHeight * 3 + 5), 0.0f);
3560         overlay->beginText();
3561         glColor4f(0.6f, 0.6f, 1.0f, 1);
3562 
3563         if (sim->getObserverMode() == Observer::Travelling)
3564         {
3565             *overlay << _("Travelling ");
3566             double timeLeft = sim->getArrivalTime() - sim->getRealTime();
3567             if (timeLeft >= 1)
3568                 *overlay << '(' << FormattedNumber(timeLeft, 0, FormattedNumber::GroupThousands) << ')';
3569             *overlay << '\n';
3570         }
3571         else
3572         {
3573             *overlay << '\n';
3574         }
3575 
3576         if (!sim->getTrackedObject().empty())
3577         {
3578             *overlay << _("Track ");
3579             displaySelectionName(*overlay, sim->getTrackedObject(),
3580                                  *sim->getUniverse());
3581         }
3582         *overlay << '\n';
3583 
3584         {
3585             //FrameOfReference frame = sim->getFrame();
3586             Selection refObject = sim->getFrame()->getRefObject();
3587             ObserverFrame::CoordinateSystem coordSys = sim->getFrame()->getCoordinateSystem();
3588 
3589             switch (coordSys)
3590             {
3591             case ObserverFrame::Ecliptical:
3592                 *overlay << _("Follow ");
3593                 displaySelectionName(*overlay, refObject,
3594                                      *sim->getUniverse());
3595                 break;
3596             case ObserverFrame::BodyFixed:
3597                 *overlay << _("Sync Orbit ");
3598                 displaySelectionName(*overlay, refObject,
3599                                      *sim->getUniverse());
3600                 break;
3601             case ObserverFrame::PhaseLock:
3602                 *overlay << _("Lock ");
3603                 displaySelectionName(*overlay, refObject,
3604                                      *sim->getUniverse());
3605                 *overlay << " -> ";
3606                 displaySelectionName(*overlay, sim->getFrame()->getTargetObject(),
3607                                      *sim->getUniverse());
3608                 break;
3609 
3610             case ObserverFrame::Chase:
3611                 *overlay << _("Chase ");
3612                 displaySelectionName(*overlay, refObject,
3613                                      *sim->getUniverse());
3614                 break;
3615 
3616 	    default:
3617 		break;
3618             }
3619 
3620             *overlay << '\n';
3621         }
3622 
3623         glColor4f(0.7f, 0.7f, 1.0f, 1.0f);
3624 
3625         // Field of view
3626         float fov = radToDeg(sim->getActiveObserver()->getFOV());
3627         *overlay << _("FOV: ");
3628         displayAngle(*overlay, fov);
3629         overlay->oprintf(" (%.2f%s)\n", (*activeView)->zoom,
3630                         UTF8_MULTIPLICATION_SIGN);
3631         overlay->endText();
3632         glPopMatrix();
3633     }
3634 
3635     // Selection info
3636     Selection sel = sim->getSelection();
3637     if (!sel.empty() && hudDetail > 0 && (overlayElements & ShowSelection))
3638     {
3639         glPushMatrix();
3640         glColor4f(0.7f, 0.7f, 1.0f, 1.0f);
3641         glTranslatef(0.0f, (float) (height - titleFont->getHeight()), 0.0f);
3642 
3643         overlay->beginText();
3644         Vec3d v = sel.getPosition(sim->getTime()) -
3645             sim->getObserver().getPosition();
3646         switch (sel.getType())
3647         {
3648         case Selection::Type_Star:
3649             {
3650                 if (sel != lastSelection)
3651                 {
3652                     lastSelection = sel;
3653                     selectionNames = sim->getUniverse()->getStarCatalog()->getStarNameList(*sel.star());
3654                     // Skip displaying the English name if a localized version is present.
3655                     string starName = sim->getUniverse()->getStarCatalog()->getStarName(*sel.star());
3656                     string locStarName = sim->getUniverse()->getStarCatalog()->getStarName(*sel.star(), true);
3657                     if (sel.star()->getCatalogNumber() == 0 && selectionNames.find("Sun") != string::npos && (const char*) "Sun" != _("Sun"))
3658                     {
3659                         string::size_type startPos = selectionNames.find("Sun");
3660                         string::size_type endPos = selectionNames.find(_("Sun"));
3661                         selectionNames = selectionNames.erase(startPos, endPos - startPos);
3662                     }
3663                     else if (selectionNames.find(starName) != string::npos && starName != locStarName)
3664                     {
3665                         string::size_type startPos = selectionNames.find(locStarName);
3666                         selectionNames = selectionNames.substr(startPos);
3667                     }
3668                 }
3669 
3670                 overlay->setFont(titleFont);
3671                 *overlay << selectionNames;
3672                 overlay->setFont(font);
3673                 *overlay << '\n';
3674                 displayStarInfo(*overlay,
3675                                 hudDetail,
3676                                 *(sel.star()),
3677                                 *(sim->getUniverse()),
3678                                 v.length() * 1e-6);
3679             }
3680             break;
3681 
3682         case Selection::Type_DeepSky:
3683             {
3684                 if (sel != lastSelection)
3685                 {
3686                     lastSelection = sel;
3687                     selectionNames = sim->getUniverse()->getDSOCatalog()->getDSONameList(sel.deepsky());
3688                     // Skip displaying the English name if a localized version is present.
3689                     string DSOName = sim->getUniverse()->getDSOCatalog()->getDSOName(sel.deepsky());
3690                     string locDSOName = sim->getUniverse()->getDSOCatalog()->getDSOName(sel.deepsky(), true);
3691                     if (selectionNames.find(DSOName) != string::npos && DSOName != locDSOName)
3692                     {
3693                         string::size_type startPos = selectionNames.find(locDSOName);
3694                         selectionNames = selectionNames.substr(startPos);
3695                     }
3696                 }
3697 
3698                 overlay->setFont(titleFont);
3699                 *overlay << selectionNames;
3700                 overlay->setFont(font);
3701                 *overlay << '\n';
3702                 displayDSOinfo(*overlay, *sel.deepsky(),
3703                                v.length() * 1e-6 - sel.deepsky()->getRadius());
3704             }
3705             break;
3706 
3707         case Selection::Type_Body:
3708             {
3709                 // Show all names for the body
3710                 if (sel != lastSelection)
3711                 {
3712                     lastSelection = sel;
3713                     selectionNames = "";
3714                     const vector<string>& names = sel.body()->getNames();
3715 
3716                     // Skip displaying the primary name if there's a localized version
3717                     // of the name.
3718                     vector<string>::const_iterator firstName = names.begin();
3719                     if (sel.body()->hasLocalizedName())
3720                         firstName++;
3721 
3722                     for (vector<string>::const_iterator iter = firstName; iter != names.end(); iter++)
3723                     {
3724                         if (iter != firstName)
3725                             selectionNames += " / ";
3726 
3727                         // Use localized version of parent name in alternative names.
3728                         string alias = *iter;
3729                         Selection parent = sel.parent();
3730                         if (parent.body() != NULL)
3731                         {
3732                             string parentName = parent.body()->getName();
3733                             string locParentName = parent.body()->getName(true);
3734                             string::size_type startPos = alias.find(parentName);
3735                             if (startPos != string::npos)
3736                                 alias.replace(startPos, parentName.length(), locParentName);
3737                         }
3738 
3739                         selectionNames += alias;
3740                     }
3741                 }
3742 
3743                 overlay->setFont(titleFont);
3744                 *overlay << selectionNames;
3745                 overlay->setFont(font);
3746                 *overlay << '\n';
3747                 displayPlanetInfo(*overlay,
3748                                   hudDetail,
3749                                   *(sel.body()),
3750                                   sim->getTime(),
3751                                   v.length() * 1e-6,
3752                                   v * astro::microLightYearsToKilometers(1.0));
3753             }
3754             break;
3755 
3756         case Selection::Type_Location:
3757             overlay->setFont(titleFont);
3758             *overlay << sel.location()->getName(true).c_str();
3759             overlay->setFont(font);
3760             *overlay << '\n';
3761             displayLocationInfo(*overlay,
3762                                 *(sel.location()),
3763                                 v.length() * 1e-6);
3764             break;
3765 
3766 	      default:
3767 	          break;
3768         }
3769 
3770 
3771         // Display RA/Dec for the selection, but only when the observer is near
3772         // the Earth.
3773         Selection refObject = sim->getFrame()->getRefObject();
3774         if (refObject.body() && refObject.body()->getName() == "Earth")
3775         {
3776             Body* earth = refObject.body();
3777 
3778             UniversalCoord observerPos = sim->getObserver().getPosition();
3779             double distToEarth = (observerPos - refObject.getPosition(sim->getTime())).length();
3780             distToEarth = astro::microLightYearsToKilometers(distToEarth) - earth->getRadius();
3781             if (distToEarth < 1000.0)
3782             {
3783 #if 1
3784                 // Code to show the geocentric RA/Dec
3785 
3786                 // Only show the coordinates for stars and deep sky objects, where
3787                 // the geocentric values will match the apparent values for observers
3788                 // near the Earth.
3789                 if (sel.star() != NULL || sel.deepsky() != NULL)
3790                 {
3791                     Vec3d v = sel.getPosition(sim->getTime()) - Selection(earth).getPosition(sim->getTime());
3792                     v = v * Mat3d::xrotation(-astro::J2000Obliquity);
3793                     displayRADec(*overlay, v);
3794                 }
3795 #else
3796                 // Code to display the apparent RA/Dec for the observer
3797 
3798                 // Don't show RA/Dec for the Earth itself
3799                 if (sel.body() != earth)
3800                 {
3801                     Vec3d vect = sel.getPosition(sim->getTime()) - observerPos;
3802                     vect = vect * Mat3d::xrotation(-astro::J2000Obliquity);
3803                     displayRADec(*overlay, vect);
3804                 }
3805 
3806                 // Show the geocentric coordinates of the observer, required for
3807                 // converting the selection RA/Dec from observer-centric to some
3808                 // other coordinate system.
3809                 // TODO: We should really show the planetographic (for Earth, geodetic)
3810                 // coordinates.
3811                 displayObserverPlanetocentricCoords(*overlay,
3812                                                     *earth,
3813                                                     observerPos,
3814                                                     sim->getTime());
3815 #endif
3816             }
3817         }
3818 
3819         overlay->endText();
3820 
3821         glPopMatrix();
3822     }
3823 
3824     // Text input
3825     if (textEnterMode & KbAutoComplete)
3826     {
3827         overlay->setFont(titleFont);
3828         glPushMatrix();
3829         glColor4f(0.7f, 0.7f, 1.0f, 0.2f);
3830         overlay->rect(0.0f, 0.0f, (float) width, 100.0f);
3831         glTranslatef(0.0f, fontHeight * 3.0f + 35.0f, 0.0f);
3832         glColor4f(0.6f, 0.6f, 1.0f, 1.0f);
3833         overlay->beginText();
3834         *overlay << _("Target name: ") << ReplaceGreekLetterAbbr(typedText);
3835         overlay->endText();
3836         overlay->setFont(font);
3837         if (typedTextCompletion.size() >= 1)
3838         {
3839             int nb_cols = 4;
3840             int nb_lines = 3;
3841             int start = 0;
3842             glTranslatef(3.0f, -font->getHeight() - 3.0f, 0.0f);
3843             vector<std::string>::const_iterator iter = typedTextCompletion.begin();
3844             if (typedTextCompletionIdx >= nb_cols * nb_lines)
3845             {
3846                start = (typedTextCompletionIdx / nb_lines + 1 - nb_cols) * nb_lines;
3847                iter += start;
3848             }
3849             for (int i=0; iter < typedTextCompletion.end() && i < nb_cols; i++)
3850             {
3851                 glPushMatrix();
3852                 overlay->beginText();
3853                 for (int j = 0; iter < typedTextCompletion.end() && j < nb_lines; iter++, j++)
3854                 {
3855                     if (i * nb_lines + j == typedTextCompletionIdx - start)
3856                         glColor4f(1.0f, 0.6f, 0.6f, 1);
3857                     else
3858                         glColor4f(0.6f, 0.6f, 1.0f, 1);
3859                     *overlay << ReplaceGreekLetterAbbr(*iter) << "\n";
3860                 }
3861                 overlay->endText();
3862                 glPopMatrix();
3863                 glTranslatef((float) (width/nb_cols), 0.0f, 0.0f);
3864            }
3865         }
3866         glPopMatrix();
3867         overlay->setFont(font);
3868     }
3869 
3870     // Text messages
3871     if (messageText != "" && currentTime < messageStart + messageDuration)
3872     {
3873         int emWidth = titleFont->getWidth("M");
3874         int fontHeight = titleFont->getHeight();
3875         int x = messageHOffset * emWidth;
3876         int y = messageVOffset * fontHeight;
3877 
3878         if (messageHOrigin == 0)
3879             x += width / 2;
3880         else if (messageHOrigin > 0)
3881             x += width;
3882         if (messageVOrigin == 0)
3883             y += height / 2;
3884         else if (messageVOrigin > 0)
3885             y += height;
3886         else if (messageVOrigin < 0)
3887             y -= fontHeight;
3888 
3889         overlay->setFont(titleFont);
3890         glPushMatrix();
3891 
3892         float alpha = 1.0f;
3893         if (currentTime > messageStart + messageDuration - 0.5)
3894             alpha = (float) ((messageStart + messageDuration - currentTime) / 0.5);
3895         glColor4f(textColor.red(), textColor.green(), textColor.blue(), alpha);
3896         glTranslatef((float) x, (float) y, 0.0f);
3897         overlay->beginText();
3898         *overlay << messageText.c_str();
3899         overlay->endText();
3900         glPopMatrix();
3901         overlay->setFont(font);
3902     }
3903 
3904     if (movieCapture != NULL)
3905     {
3906         int movieWidth = movieCapture->getWidth();
3907         int movieHeight = movieCapture->getHeight();
3908         glPushMatrix();
3909         glColor4f(1, 0, 0, 1);
3910         overlay->rect((float) ((width - movieWidth) / 2 - 1),
3911                       (float) ((height - movieHeight) / 2 - 1),
3912                       (float) (movieWidth + 1),
3913                       (float) (movieHeight + 1), false);
3914         glTranslatef((float) ((width - movieWidth) / 2),
3915                      (float) ((height + movieHeight) / 2 + 2), 0.0f);
3916         *overlay << movieWidth << 'x' << movieHeight << _(" at ") <<
3917             movieCapture->getFrameRate() << _(" fps");
3918         if (recording)
3919             *overlay << _("  Recording");
3920         else
3921             *overlay << _("  Paused");
3922 
3923         glPopMatrix();
3924 
3925         glPushMatrix();
3926         glTranslatef((float) ((width + movieWidth) / 2 - emWidth * 5),
3927                      (float) ((height + movieHeight) / 2 + 2),
3928                      0.0f);
3929         float sec = movieCapture->getFrameCount() /
3930             movieCapture->getFrameRate();
3931         int min = (int) (sec / 60);
3932         sec -= min * 60.0f;
3933         overlay->oprintf("%3d:%05.2f", min, sec);
3934         glPopMatrix();
3935 
3936         glPushMatrix();
3937         glTranslatef((float) ((width - movieWidth) / 2),
3938                      (float) ((height - movieHeight) / 2 - fontHeight - 2),
3939                      0.0f);
3940         *overlay << _("F11 Start/Pause    F12 Stop");
3941         glPopMatrix();
3942 
3943         glPopMatrix();
3944     }
3945 
3946     if (editMode)
3947     {
3948         glPushMatrix();
3949         glTranslatef((float) ((width - font->getWidth(_("Edit Mode"))) / 2),
3950                      (float) (height - fontHeight), 0.0f);
3951         glColor4f(1, 0, 1, 1);
3952         *overlay << _("Edit Mode");
3953         glPopMatrix();
3954     }
3955 
3956     // Show logo at start
3957     if (logoTexture != NULL)
3958     {
3959         glEnable(GL_TEXTURE_2D);
3960         if (currentTime < 5.0)
3961         {
3962             int xSize = (int) (logoTexture->getWidth() * 0.8f);
3963             int ySize = (int) (logoTexture->getHeight() * 0.8f);
3964             int left = (width - xSize) / 2;
3965             int bottom = height / 2;
3966 
3967             float topAlpha, botAlpha;
3968             if (currentTime < 4.0)
3969             {
3970                 botAlpha = (float) clamp(currentTime / 1.0);
3971                 topAlpha = (float) clamp(currentTime / 4.0);
3972             }
3973             else
3974             {
3975                 botAlpha = topAlpha = (float) (5.0 - currentTime);
3976             }
3977 
3978             logoTexture->bind();
3979             glBegin(GL_QUADS);
3980             glColor4f(0.8f, 0.8f, 1.0f, botAlpha);
3981             //glColor4f(1.0f, 1.0f, 1.0f, botAlpha);
3982             glTexCoord2f(0.0f, 1.0f);
3983             glVertex2i(left, bottom);
3984             glTexCoord2f(1.0f, 1.0f);
3985             glVertex2i(left + xSize, bottom);
3986             glColor4f(0.6f, 0.6f, 1.0f, topAlpha);
3987             //glColor4f(1.0f, 1.0f, 1.0f, topAlpha);
3988             glTexCoord2f(1.0f, 0.0f);
3989             glVertex2i(left + xSize, bottom + ySize);
3990             glTexCoord2f(0.0f, 0.0f);
3991             glVertex2i(left, bottom + ySize);
3992             glEnd();
3993         }
3994         else
3995         {
3996             delete logoTexture;
3997             logoTexture = NULL;
3998         }
3999     }
4000 
4001     overlay->end();
4002     setlocale(LC_NUMERIC, "C");
4003 }
4004 
4005 
4006 class SolarSystemLoader : public EnumFilesHandler
4007 {
4008  public:
4009     Universe* universe;
4010     ProgressNotifier* notifier;
SolarSystemLoader(Universe * u,ProgressNotifier * pn)4011     SolarSystemLoader(Universe* u, ProgressNotifier* pn) : universe(u), notifier(pn) {};
4012 
process(const string & filename)4013     bool process(const string& filename)
4014     {
4015         if (DetermineFileType(filename) == Content_CelestiaCatalog)
4016         {
4017             string fullname = getPath() + '/' + filename;
4018             clog << _("Loading solar system catalog: ") << fullname << '\n';
4019             if (notifier)
4020                 notifier->update(filename);
4021 
4022             ifstream solarSysFile(fullname.c_str(), ios::in);
4023             if (solarSysFile.good())
4024             {
4025                 LoadSolarSystemObjects(solarSysFile,
4026                                        *universe,
4027                                        getPath());
4028             }
4029         }
4030 
4031         return true;
4032     };
4033 };
4034 
4035 template <class OBJDB> class CatalogLoader : public EnumFilesHandler
4036 {
4037 public:
4038     OBJDB*      objDB;
4039     string      typeDesc;
4040     ContentType contentType;
4041     ProgressNotifier* notifier;
4042 
CatalogLoader(OBJDB * db,const std::string & typeDesc,const ContentType & contentType,ProgressNotifier * pn)4043     CatalogLoader(OBJDB* db,
4044                   const std::string& typeDesc,
4045                   const ContentType& contentType,
4046                   ProgressNotifier* pn) :
4047         objDB      (db),
4048         typeDesc   (typeDesc),
4049         contentType(contentType),
4050         notifier(pn)
4051     {
4052     }
4053 
process(const string & filename)4054     bool process(const string& filename)
4055     {
4056         if (DetermineFileType(filename) == contentType)
4057         {
4058             string fullname = getPath() + '/' + filename;
4059             clog << _("Loading ") << typeDesc << " catalog: " << fullname << '\n';
4060             if (notifier)
4061                 notifier->update(filename);
4062 
4063             ifstream catalogFile(fullname.c_str(), ios::in);
4064             if (catalogFile.good())
4065             {
4066                 bool success = objDB->load(catalogFile, getPath());
4067                 if (!success)
4068                 {
4069                     //DPRINTF(0, _("Error reading star file: %s\n"), fullname.c_str());
4070                     DPRINTF(0, "Error reading %s catalog file: %s\n", typeDesc.c_str(), fullname.c_str());
4071                 }
4072             }
4073         }
4074         return true;
4075     }
4076 };
4077 
4078 typedef CatalogLoader<StarDatabase> StarLoader;
4079 typedef CatalogLoader<DSODatabase>  DeepSkyLoader;
4080 
4081 
initSimulation(const string * configFileName,const vector<string> * extrasDirs,ProgressNotifier * progressNotifier)4082 bool CelestiaCore::initSimulation(const string* configFileName,
4083                                   const vector<string>* extrasDirs,
4084                                   ProgressNotifier* progressNotifier)
4085 {
4086     // Say we're not ready to render yet.
4087     // bReady = false;
4088 #ifdef REQUIRE_LICENSE_FILE
4089     // Check for the presence of the license file--don't run unless it's there.
4090     {
4091         ifstream license("License.txt");
4092         if (!license.good())
4093         {
4094             fatalError(_("License file 'License.txt' is missing!"));
4095             return false;
4096         }
4097     }
4098 #endif
4099 
4100     if (configFileName != NULL)
4101     {
4102         config = ReadCelestiaConfig(*configFileName);
4103     }
4104     else
4105     {
4106         config = ReadCelestiaConfig("celestia.cfg");
4107 
4108         string localConfigFile = WordExp("~/.celestia.cfg");
4109         if (localConfigFile != "")
4110             ReadCelestiaConfig(localConfigFile.c_str(), config);
4111     }
4112 
4113     if (config == NULL)
4114     {
4115         fatalError(_("Error reading configuration file."));
4116         return false;
4117     }
4118 
4119     // Set the console log size; ignore any request to use less than 100 lines
4120     if (config->consoleLogRows > 100)
4121         console.setRowCount(config->consoleLogRows);
4122 
4123 #ifdef USE_SPICE
4124     if (!InitializeSpice())
4125     {
4126         fatalError(_("Initialization of SPICE library failed."));
4127         return false;
4128     }
4129 #endif
4130 
4131     // Insert additional extras directories into the configuration. These
4132     // additional directories typically come from the command line. It may
4133     // be useful to permit other command line overrides of config file fields.
4134     if (extrasDirs != NULL)
4135     {
4136         // Only insert the additional extras directories that aren't also
4137         // listed in the configuration file. The additional directories are added
4138         // after the ones from the config file and the order in which they were
4139         // specified is preserved. This process in O(N*M), but the number of
4140         // additional extras directories should be small.
4141         for (vector<string>::const_iterator iter = extrasDirs->begin();
4142              iter != extrasDirs->end(); iter++)
4143         {
4144             if (find(config->extrasDirs.begin(), config->extrasDirs.end(), *iter) ==
4145                 config->extrasDirs.end())
4146             {
4147                 config->extrasDirs.push_back(*iter);
4148             }
4149         }
4150     }
4151 
4152 #ifdef CELX
4153     initLuaHook(progressNotifier);
4154 #endif
4155 
4156     KeyRotationAccel = degToRad(config->rotateAcceleration);
4157     MouseRotationSensitivity = degToRad(config->mouseRotationSensitivity);
4158 
4159     readFavoritesFile();
4160 
4161     // If we couldn't read the favorites list from a file, allocate
4162     // an empty list.
4163     if (favorites == NULL)
4164         favorites = new FavoritesList();
4165 
4166     universe = new Universe();
4167 
4168 
4169     /***** Load star catalogs *****/
4170 
4171     if (!readStars(*config, progressNotifier))
4172     {
4173         fatalError(_("Cannot read star database."));
4174         return false;
4175     }
4176 
4177 
4178     /***** Load the deep sky catalogs *****/
4179 
4180     DSONameDatabase* dsoNameDB  = new DSONameDatabase;
4181     DSODatabase*     dsoDB      = new DSODatabase;
4182     dsoDB->setNameDatabase(dsoNameDB);
4183 
4184 	// Load first the vector of dsoCatalogFiles in the data directory (deepsky.dsc, globulars.dsc,...):
4185 
4186 	for (vector<string>::const_iterator iter = config->dsoCatalogFiles.begin();
4187     iter != config->dsoCatalogFiles.end(); iter++)
4188     {
4189     	if (progressNotifier)
4190         	progressNotifier->update(*iter);
4191 
4192 		ifstream dsoFile(iter->c_str(), ios::in);
4193         if (!dsoFile.good())
4194         {
4195         	cerr<< _("Error opening deepsky catalog file.") << '\n';
4196             delete dsoDB;
4197             return false;
4198 		}
4199         else if (!dsoDB->load(dsoFile, ""))
4200 	    {
4201     		cerr << "Cannot read Deep Sky Objects database." << '\n';
4202         	delete dsoDB;
4203            	return false;
4204         }
4205     }
4206 
4207     // Next, read all the deep sky files in the extras directories
4208     {
4209         for (vector<string>::const_iterator iter = config->extrasDirs.begin();
4210              iter != config->extrasDirs.end(); iter++)
4211         {
4212             if (*iter != "")
4213             {
4214                 Directory* dir = OpenDirectory(*iter);
4215 
4216                 DeepSkyLoader loader(dsoDB,
4217                                      "deep sky object",
4218                                      Content_CelestiaDeepSkyCatalog,
4219                                      progressNotifier);
4220                 loader.pushDir(*iter);
4221                 dir->enumFiles(loader, true);
4222 
4223                 delete dir;
4224             }
4225         }
4226     }
4227     dsoDB->finish();
4228     universe->setDSOCatalog(dsoDB);
4229 
4230 
4231     /***** Load the solar system catalogs *****/
4232     // First read the solar system files listed individually in the
4233     // config file.
4234     {
4235         SolarSystemCatalog* solarSystemCatalog = new SolarSystemCatalog();
4236         universe->setSolarSystemCatalog(solarSystemCatalog);
4237         for (vector<string>::const_iterator iter = config->solarSystemFiles.begin();
4238              iter != config->solarSystemFiles.end();
4239              iter++)
4240         {
4241             if (progressNotifier)
4242                 progressNotifier->update(*iter);
4243 
4244             ifstream solarSysFile(iter->c_str(), ios::in);
4245             if (!solarSysFile.good())
4246             {
4247                 warning(_("Error opening solar system catalog.\n"));
4248             }
4249             else
4250             {
4251                 LoadSolarSystemObjects(solarSysFile, *universe, "");
4252             }
4253         }
4254     }
4255 
4256     // Next, read all the solar system files in the extras directories
4257     {
4258         for (vector<string>::const_iterator iter = config->extrasDirs.begin();
4259              iter != config->extrasDirs.end(); iter++)
4260         {
4261             if (*iter != "")
4262             {
4263                 Directory* dir = OpenDirectory(*iter);
4264 
4265                 SolarSystemLoader loader(universe, progressNotifier);
4266                 loader.pushDir(*iter);
4267                 dir->enumFiles(loader, true);
4268 
4269                 delete dir;
4270             }
4271         }
4272     }
4273 
4274     // Load asterisms:
4275     if (config->asterismsFile != "")
4276     {
4277         ifstream asterismsFile(config->asterismsFile.c_str(), ios::in);
4278         if (!asterismsFile.good())
4279         {
4280             warning(_("Error opening asterisms file."));
4281         }
4282         else
4283         {
4284             AsterismList* asterisms = ReadAsterismList(asterismsFile,
4285                                                        *universe->getStarCatalog());
4286             universe->setAsterisms(asterisms);
4287         }
4288     }
4289 
4290     if (config->boundariesFile != "")
4291     {
4292         ifstream boundariesFile(config->boundariesFile.c_str(), ios::in);
4293         if (!boundariesFile.good())
4294         {
4295             warning(_("Error opening constellation boundaries files."));
4296         }
4297         else
4298         {
4299             ConstellationBoundaries* boundaries = ReadBoundaries(boundariesFile);
4300             universe->setBoundaries(boundaries);
4301         }
4302     }
4303 
4304     // Load destinations list
4305     if (config->destinationsFile != "")
4306     {
4307         string localeDestinationsFile = LocaleFilename(config->destinationsFile);
4308         ifstream destfile(localeDestinationsFile.c_str());
4309         if (destfile.good())
4310         {
4311             destinations = ReadDestinationList(destfile);
4312         }
4313     }
4314 
4315     sim = new Simulation(universe);
4316     if((renderer->getRenderFlags() & Renderer::ShowAutoMag) == 0)
4317     sim->setFaintestVisible(config->faintestVisible);
4318 
4319     View* view = new View(View::ViewWindow, sim->getActiveObserver(), 0.0f, 0.0f, 1.0f, 1.0f);
4320     views.insert(views.end(), view);
4321     activeView = views.begin();
4322 
4323     if (!compareIgnoringCase(getConfig()->cursor, "inverting crosshair"))
4324     {
4325         defaultCursorShape = CelestiaCore::InvertedCrossCursor;
4326     }
4327 
4328     if (!compareIgnoringCase(getConfig()->cursor, "arrow"))
4329     {
4330         defaultCursorShape = CelestiaCore::ArrowCursor;
4331     }
4332 
4333     if (cursorHandler != NULL)
4334     {
4335         cursorHandler->setCursorShape(defaultCursorShape);
4336     }
4337 
4338     return true;
4339 }
4340 
4341 
initRenderer()4342 bool CelestiaCore::initRenderer()
4343 {
4344     renderer->setRenderFlags(Renderer::ShowStars |
4345                              Renderer::ShowPlanets |
4346                              Renderer::ShowAtmospheres |
4347                              Renderer::ShowAutoMag);
4348 
4349     GLContext* context = new GLContext();
4350     assert(context != NULL);
4351     if (context == NULL)
4352         return false;
4353 
4354     context->init(config->ignoreGLExtensions);
4355     // Choose the render path, starting with the least desirable
4356     context->setRenderPath(GLContext::GLPath_Basic);
4357     context->setRenderPath(GLContext::GLPath_Multitexture);
4358     context->setRenderPath(GLContext::GLPath_DOT3_ARBVP);
4359     context->setRenderPath(GLContext::GLPath_NvCombiner_NvVP);
4360     context->setRenderPath(GLContext::GLPath_NvCombiner_ARBVP);
4361     context->setRenderPath(GLContext::GLPath_GLSL);
4362     cout << _("render path: ") << context->getRenderPath() << '\n';
4363 
4364     Renderer::DetailOptions detailOptions;
4365     detailOptions.ringSystemSections = config->ringSystemSections;
4366     detailOptions.orbitPathSamplePoints = config->orbitPathSamplePoints;
4367     detailOptions.shadowTextureSize = config->shadowTextureSize;
4368     detailOptions.eclipseTextureSize = config->eclipseTextureSize;
4369 
4370     // Prepare the scene for rendering.
4371     if (!renderer->init(context, (int) width, (int) height, detailOptions))
4372     {
4373         fatalError(_("Failed to initialize renderer"));
4374         return false;
4375     }
4376 
4377     if ((renderer->getRenderFlags() & Renderer::ShowAutoMag) != 0)
4378     {
4379         renderer->setFaintestAM45deg(renderer->getFaintestAM45deg());
4380         setFaintestAutoMag();
4381     }
4382 
4383     if (config->mainFont == "")
4384         font = LoadTextureFont("fonts/default.txf");
4385     else
4386         font = LoadTextureFont(string("fonts/") + config->mainFont);
4387     if (font == NULL)
4388     {
4389         cout << _("Error loading font; text will not be visible.\n");
4390     }
4391     else
4392     {
4393         font->buildTexture();
4394     }
4395 
4396     if (config->titleFont != "")
4397         titleFont = LoadTextureFont(string("fonts") + "/" + config->titleFont);
4398     if (titleFont != NULL)
4399         titleFont->buildTexture();
4400     else
4401         titleFont = font;
4402 
4403     // Set up the overlay
4404     overlay = new Overlay();
4405     overlay->setWindowSize(width, height);
4406 
4407     if (config->labelFont == "")
4408     {
4409         renderer->setFont(Renderer::FontNormal, font);
4410     }
4411     else
4412     {
4413         TextureFont* labelFont = LoadTextureFont(string("fonts") + "/" + config->labelFont);
4414         if (labelFont == NULL)
4415         {
4416             renderer->setFont(Renderer::FontNormal, font);
4417         }
4418         else
4419         {
4420             labelFont->buildTexture();
4421             renderer->setFont(Renderer::FontNormal, labelFont);
4422         }
4423     }
4424 
4425     renderer->setFont(Renderer::FontLarge, titleFont);
4426 
4427     if (config->logoTextureFile != "")
4428     {
4429         logoTexture = LoadTextureFromFile(string("textures") + "/" + config->logoTextureFile);
4430     }
4431 
4432     return true;
4433 }
4434 
4435 
loadCrossIndex(StarDatabase * starDB,StarDatabase::Catalog catalog,const string & filename)4436 static void loadCrossIndex(StarDatabase* starDB,
4437                            StarDatabase::Catalog catalog,
4438                            const string& filename)
4439 {
4440     if (!filename.empty())
4441     {
4442         ifstream xrefFile(filename.c_str(), ios::in | ios::binary);
4443         if (xrefFile.good())
4444         {
4445             if (!starDB->loadCrossIndex(catalog, xrefFile))
4446                 cerr << _("Error reading cross index ") << filename << '\n';
4447             else
4448                 clog << _("Loaded cross index ") << filename << '\n';
4449         }
4450     }
4451 }
4452 
4453 
readStars(const CelestiaConfig & cfg,ProgressNotifier * progressNotifier)4454 bool CelestiaCore::readStars(const CelestiaConfig& cfg,
4455                              ProgressNotifier* progressNotifier)
4456 {
4457     StarDetails::SetStarTextures(cfg.starTextures);
4458 
4459     ifstream starNamesFile(cfg.starNamesFile.c_str(), ios::in);
4460     if (!starNamesFile.good())
4461     {
4462 	cerr << _("Error opening ") << cfg.starNamesFile << '\n';
4463         return false;
4464     }
4465 
4466     StarNameDatabase* starNameDB = StarNameDatabase::readNames(starNamesFile);
4467     if (starNameDB == NULL)
4468     {
4469         cerr << _("Error reading star names file\n");
4470         return false;
4471     }
4472 
4473     // First load the binary star database file.  The majority of stars
4474     // will be defined here.
4475     StarDatabase* starDB = new StarDatabase();
4476     if (!cfg.starDatabaseFile.empty())
4477     {
4478         if (progressNotifier)
4479             progressNotifier->update(cfg.starDatabaseFile);
4480 
4481         ifstream starFile(cfg.starDatabaseFile.c_str(), ios::in | ios::binary);
4482         if (!starFile.good())
4483         {
4484             cerr << _("Error opening ") << cfg.starDatabaseFile << '\n';
4485             delete starDB;
4486             return false;
4487         }
4488 
4489         if (!starDB->loadBinary(starFile))
4490         {
4491             delete starDB;
4492             cerr << _("Error reading stars file\n");
4493             return false;
4494         }
4495     }
4496 
4497     starDB->setNameDatabase(starNameDB);
4498 
4499     loadCrossIndex(starDB, StarDatabase::HenryDraper, cfg.HDCrossIndexFile);
4500     loadCrossIndex(starDB, StarDatabase::SAO,         cfg.SAOCrossIndexFile);
4501     loadCrossIndex(starDB, StarDatabase::Gliese,      cfg.GlieseCrossIndexFile);
4502 
4503     // Next, read any ASCII star catalog files specified in the StarCatalogs
4504     // list.
4505     if (!cfg.starCatalogFiles.empty())
4506     {
4507         for (vector<string>::const_iterator iter = config->starCatalogFiles.begin();
4508              iter != config->starCatalogFiles.end(); iter++)
4509         {
4510             if (*iter != "")
4511             {
4512                 ifstream starFile(iter->c_str(), ios::in);
4513                 if (starFile.good())
4514                 {
4515                     starDB->load(starFile, "");
4516                 }
4517                 else
4518                 {
4519                     cerr << _("Error opening star catalog ") << *iter << '\n';
4520                 }
4521             }
4522         }
4523     }
4524 
4525     // Now, read supplemental star files from the extras directories
4526     for (vector<string>::const_iterator iter = config->extrasDirs.begin();
4527          iter != config->extrasDirs.end(); iter++)
4528     {
4529         if (*iter != "")
4530         {
4531             Directory* dir = OpenDirectory(*iter);
4532 
4533             StarLoader loader(starDB, "star", Content_CelestiaStarCatalog, progressNotifier);
4534             loader.pushDir(*iter);
4535             dir->enumFiles(loader, true);
4536 
4537             delete dir;
4538         }
4539     }
4540 
4541     starDB->finish();
4542 
4543     universe->setStarCatalog(starDB);
4544 
4545     return true;
4546 }
4547 
4548 
4549 /// Set the faintest visible star magnitude; adjust the renderer's
4550 /// brightness parameters appropriately.
setFaintest(float magnitude)4551 void CelestiaCore::setFaintest(float magnitude)
4552 {
4553     sim->setFaintestVisible(magnitude);
4554 }
4555 
4556 /// Set faintest visible star magnitude and saturation magnitude
4557 /// for a given field of view;
4558 /// adjust the renderer's brightness parameters appropriately.
setFaintestAutoMag()4559 void CelestiaCore::setFaintestAutoMag()
4560 {
4561     float faintestMag;
4562     renderer->autoMag(faintestMag);
4563     sim->setFaintestVisible(faintestMag);
4564 }
4565 
fatalError(const string & msg)4566 void CelestiaCore::fatalError(const string& msg)
4567 {
4568     if (alerter == NULL)
4569         cout << msg;
4570     else
4571         alerter->fatalError(msg);
4572 }
4573 
4574 
setAlerter(Alerter * a)4575 void CelestiaCore::setAlerter(Alerter* a)
4576 {
4577     alerter = a;
4578 }
4579 
getAlerter() const4580 CelestiaCore::Alerter* CelestiaCore::getAlerter() const
4581 {
4582     return alerter;
4583 }
4584 
4585 /// Sets the cursor handler object.
4586 /// This must be set before calling initSimulation
4587 /// or the default cursor will not be used.
setCursorHandler(CursorHandler * handler)4588 void CelestiaCore::setCursorHandler(CursorHandler* handler)
4589 {
4590     cursorHandler = handler;
4591 }
4592 
getCursorHandler() const4593 CelestiaCore::CursorHandler* CelestiaCore::getCursorHandler() const
4594 {
4595     return cursorHandler;
4596 }
4597 
getTimeZoneBias() const4598 int CelestiaCore::getTimeZoneBias() const
4599 {
4600     return timeZoneBias;
4601 }
4602 
getLightDelayActive() const4603 bool CelestiaCore::getLightDelayActive() const
4604 {
4605     return lightTravelFlag;
4606 }
4607 
setLightDelayActive(bool lightDelayActive)4608 void CelestiaCore::setLightDelayActive(bool lightDelayActive)
4609 {
4610     lightTravelFlag = lightDelayActive;
4611 }
4612 
setTextEnterMode(int mode)4613 void CelestiaCore::setTextEnterMode(int mode)
4614 {
4615     if (mode != textEnterMode)
4616     {
4617         if ((mode & KbAutoComplete) != (textEnterMode & KbAutoComplete))
4618         {
4619             typedText = "";
4620             typedTextCompletion.clear();
4621             typedTextCompletionIdx = -1;
4622         }
4623         textEnterMode = mode;
4624         notifyWatchers(TextEnterModeChanged);
4625     }
4626 }
4627 
getTextEnterMode() const4628 int CelestiaCore::getTextEnterMode() const
4629 {
4630     return textEnterMode;
4631 }
4632 
setScreenDpi(int dpi)4633 void CelestiaCore::setScreenDpi(int dpi)
4634 {
4635     screenDpi = dpi;
4636     setFOVFromZoom();
4637     renderer->setScreenDpi(dpi);
4638 }
4639 
getScreenDpi() const4640 int CelestiaCore::getScreenDpi() const
4641 {
4642     return screenDpi;
4643 }
4644 
setDistanceToScreen(int dts)4645 void CelestiaCore::setDistanceToScreen(int dts)
4646 {
4647     distanceToScreen = dts;
4648     setFOVFromZoom();
4649 }
4650 
getDistanceToScreen() const4651 int CelestiaCore::getDistanceToScreen() const
4652 {
4653     return distanceToScreen;
4654 }
4655 
setTimeZoneBias(int bias)4656 void CelestiaCore::setTimeZoneBias(int bias)
4657 {
4658     timeZoneBias = bias;
4659     notifyWatchers(TimeZoneChanged);
4660 }
4661 
4662 
getTimeZoneName() const4663 string CelestiaCore::getTimeZoneName() const
4664 {
4665     return timeZoneName;
4666 }
4667 
4668 
setTimeZoneName(const string & zone)4669 void CelestiaCore::setTimeZoneName(const string& zone)
4670 {
4671     timeZoneName = zone;
4672 }
4673 
4674 
getHudDetail()4675 int CelestiaCore::getHudDetail()
4676 {
4677     return hudDetail;
4678 }
4679 
setHudDetail(int newHudDetail)4680 void CelestiaCore::setHudDetail(int newHudDetail)
4681 {
4682     hudDetail = newHudDetail%3;
4683     notifyWatchers(VerbosityLevelChanged);
4684 }
4685 
4686 
getTextColor()4687 Color CelestiaCore::getTextColor()
4688 {
4689     return textColor;
4690 }
4691 
setTextColor(Color newTextColor)4692 void CelestiaCore::setTextColor(Color newTextColor)
4693 {
4694     textColor = newTextColor;
4695 }
4696 
4697 
getDateFormat() const4698 astro::Date::Format CelestiaCore::getDateFormat() const
4699 {
4700     return dateFormat;
4701 }
4702 
setDateFormat(astro::Date::Format format)4703 void CelestiaCore::setDateFormat(astro::Date::Format format)
4704 {
4705     dateStrWidth = 0;
4706     dateFormat = format;
4707 }
4708 
getOverlayElements() const4709 int CelestiaCore::getOverlayElements() const
4710 {
4711     return overlayElements;
4712 }
4713 
setOverlayElements(int _overlayElements)4714 void CelestiaCore::setOverlayElements(int _overlayElements)
4715 {
4716     overlayElements = _overlayElements;
4717 }
4718 
initMovieCapture(MovieCapture * mc)4719 void CelestiaCore::initMovieCapture(MovieCapture* mc)
4720 {
4721     if (movieCapture == NULL)
4722         movieCapture = mc;
4723 }
4724 
recordBegin()4725 void CelestiaCore::recordBegin()
4726 {
4727     if (movieCapture != NULL) {
4728         recording = true;
4729         movieCapture->recordingStatus(true);
4730     }
4731 }
4732 
recordPause()4733 void CelestiaCore::recordPause()
4734 {
4735     recording = false;
4736     if (movieCapture != NULL) movieCapture->recordingStatus(false);
4737 }
4738 
recordEnd()4739 void CelestiaCore::recordEnd()
4740 {
4741     if (movieCapture != NULL)
4742     {
4743         recordPause();
4744         movieCapture->end();
4745         delete movieCapture;
4746         movieCapture = NULL;
4747     }
4748 }
4749 
isCaptureActive()4750 bool CelestiaCore::isCaptureActive()
4751 {
4752     return movieCapture != NULL;
4753 }
4754 
isRecording()4755 bool CelestiaCore::isRecording()
4756 {
4757     return recording;
4758 }
4759 
flash(const string & s,double duration)4760 void CelestiaCore::flash(const string& s, double duration)
4761 {
4762     if (hudDetail > 0)
4763         showText(s, -1, -1, 0, 5, duration);
4764 }
4765 
4766 
getConfig() const4767 CelestiaConfig* CelestiaCore::getConfig() const
4768 {
4769     return config;
4770 }
4771 
4772 
addWatcher(CelestiaWatcher * watcher)4773 void CelestiaCore::addWatcher(CelestiaWatcher* watcher)
4774 {
4775     assert(watcher != NULL);
4776     watchers.insert(watchers.end(), watcher);
4777 }
4778 
removeWatcher(CelestiaWatcher * watcher)4779 void CelestiaCore::removeWatcher(CelestiaWatcher* watcher)
4780 {
4781     vector<CelestiaWatcher*>::iterator iter =
4782         find(watchers.begin(), watchers.end(), watcher);
4783     if (iter != watchers.end())
4784         watchers.erase(iter);
4785 }
4786 
notifyWatchers(int property)4787 void CelestiaCore::notifyWatchers(int property)
4788 {
4789     for (vector<CelestiaWatcher*>::iterator iter = watchers.begin();
4790          iter != watchers.end(); iter++)
4791     {
4792         (*iter)->notifyChange(this, property);
4793     }
4794 }
4795 
4796 
goToUrl(const string & urlStr)4797 void CelestiaCore::goToUrl(const string& urlStr)
4798 {
4799     Url url(urlStr, this);
4800     url.goTo();
4801     notifyWatchers(RenderFlagsChanged | LabelFlagsChanged);
4802 }
4803 
4804 
addToHistory()4805 void CelestiaCore::addToHistory()
4806 {
4807     Url url(this);
4808     if (!history.empty() && historyCurrent < history.size() - 1)
4809     {
4810         // truncating history to current position
4811         while (historyCurrent != history.size() - 1)
4812         {
4813             history.pop_back();
4814         }
4815     }
4816     history.push_back(url);
4817     historyCurrent = history.size() - 1;
4818     notifyWatchers(HistoryChanged);
4819 }
4820 
4821 
back()4822 void CelestiaCore::back()
4823 {
4824     if (historyCurrent == 0) return;
4825     if (historyCurrent == history.size() - 1)
4826     {
4827         addToHistory();
4828         historyCurrent = history.size()-1;
4829     }
4830     historyCurrent--;
4831     history[historyCurrent].goTo();
4832     notifyWatchers(HistoryChanged|RenderFlagsChanged|LabelFlagsChanged);
4833 }
4834 
4835 
forward()4836 void CelestiaCore::forward()
4837 {
4838     if (history.size() == 0) return;
4839     if (historyCurrent == history.size()-1) return;
4840     historyCurrent++;
4841     history[historyCurrent].goTo();
4842     notifyWatchers(HistoryChanged|RenderFlagsChanged|LabelFlagsChanged);
4843 }
4844 
4845 
getHistory() const4846 const vector<Url>& CelestiaCore::getHistory() const
4847 {
4848     return history;
4849 }
4850 
getHistoryCurrent() const4851 vector<Url>::size_type CelestiaCore::getHistoryCurrent() const
4852 {
4853     return historyCurrent;
4854 }
4855 
setHistoryCurrent(vector<Url>::size_type curr)4856 void CelestiaCore::setHistoryCurrent(vector<Url>::size_type curr)
4857 {
4858     if (curr >= history.size()) return;
4859     if (historyCurrent == history.size()) {
4860         addToHistory();
4861     }
4862     historyCurrent = curr;
4863     history[curr].goTo();
4864     notifyWatchers(HistoryChanged|RenderFlagsChanged|LabelFlagsChanged);
4865 }
4866 
4867 
4868 
4869 
4870 /*! Toggle the specified reference mark for a selection.
4871  *  a selection. The default value for the selection argument is
4872  *  the current simulation selection. This method does nothing
4873  *  if the selection isn't a solar system body.
4874  */
toggleReferenceMark(const string & refMark,Selection sel)4875 void CelestiaCore::toggleReferenceMark(const string& refMark, Selection sel)
4876 {
4877     Body* body = NULL;
4878 
4879     if (sel.empty())
4880         body = getSimulation()->getSelection().body();
4881     else
4882         body = sel.body();
4883 
4884     // Reference marks can only be set for solar system bodies.
4885     if (body == NULL)
4886         return;
4887 
4888     if (body->findReferenceMark(refMark))
4889     {
4890         body->removeReferenceMark(refMark);
4891     }
4892     else
4893     {
4894         if (refMark == "body axes")
4895         {
4896             body->addReferenceMark(new BodyAxisArrows(*body));
4897         }
4898         else if (refMark == "frame axes")
4899         {
4900             body->addReferenceMark(new FrameAxisArrows(*body));
4901         }
4902         else if (refMark == "sun direction")
4903         {
4904             body->addReferenceMark(new SunDirectionArrow(*body));
4905         }
4906         else if (refMark == "velocity vector")
4907         {
4908             body->addReferenceMark(new VelocityVectorArrow(*body));
4909         }
4910         else if (refMark == "spin vector")
4911         {
4912             body->addReferenceMark(new SpinVectorArrow(*body));
4913         }
4914         else if (refMark == "frame center direction")
4915         {
4916             double now = getSimulation()->getTime();
4917             BodyToBodyDirectionArrow* arrow = new BodyToBodyDirectionArrow(*body, body->getOrbitFrame(now)->getCenter());
4918             arrow->setTag(refMark);
4919             body->addReferenceMark(arrow);
4920         }
4921         else if (refMark == "planetographic grid")
4922         {
4923             body->addReferenceMark(new PlanetographicGrid(*body));
4924         }
4925         else if (refMark == "terminator")
4926         {
4927             double now = getSimulation()->getTime();
4928             Star* sun = NULL;
4929             Body* b = body;
4930             while (b != NULL)
4931             {
4932                 Selection center = b->getOrbitFrame(now)->getCenter();
4933                 if (center.star() != NULL)
4934                     sun = center.star();
4935                 b = center.body();
4936             }
4937 
4938             if (sun != NULL)
4939             {
4940                 VisibleRegion* visibleRegion = new VisibleRegion(*body, Selection(sun));
4941                 visibleRegion->setTag("terminator");
4942                 body->addReferenceMark(visibleRegion);
4943             }
4944         }
4945     }
4946 }
4947 
4948 
4949 /*! Return whether the specified reference mark is enabled for a
4950  *  a selection. The default value for the selection argument is
4951  *  the current simulation selection.
4952  */
referenceMarkEnabled(const string & refMark,Selection sel) const4953 bool CelestiaCore::referenceMarkEnabled(const string& refMark, Selection sel) const
4954 {
4955     Body* body = NULL;
4956 
4957     if (sel.empty())
4958         body = getSimulation()->getSelection().body();
4959     else
4960         body = sel.body();
4961 
4962     // Reference marks can only be set for solar system bodies.
4963     if (body == NULL)
4964         return false;
4965     else
4966         return body->findReferenceMark(refMark) != NULL;
4967 }
4968 
4969 
4970 #ifdef CELX
4971 class LuaPathFinder : public EnumFilesHandler
4972 {
4973  public:
4974     string luaPath;
LuaPathFinder(string s)4975     LuaPathFinder(string s) : luaPath(s) {};
4976     string lastPath;
4977 
process(const string & filename)4978     bool process(const string& filename)
4979     {
4980         if (getPath() != lastPath)
4981         {
4982             int extPos = filename.rfind('.');
4983             if (extPos != (int)string::npos)
4984             {
4985                 string ext = string(filename, extPos, filename.length() - extPos + 1);
4986                 if (ext == ".lua")
4987                 {
4988                     lastPath = getPath();
4989                     string newPatt = getPath()+"/?.lua;";
4990                     extPos = luaPath.rfind(newPatt);
4991                     if (extPos < 0)
4992                     {
4993                         luaPath = luaPath + newPatt;
4994                     }
4995                 }
4996             }
4997 	    }
4998         return true;
4999     };
5000 };
5001 
5002 
5003 // Initialize the Lua hook table as well as the Lua state for scripted
5004 // objects. The Lua hook operates in a different Lua state than user loaded
5005 // scripts. It always has file system access via the IO package. If the script
5006 // system access policy is "allow", then scripted objects will run in the same
5007 // Lua context as the Lua hook. Sharing state between scripted objects and the
5008 // hook can be very useful, but it gives system access to scripted objects,
5009 // and therefore must be restricted based on the system access policy.
initLuaHook(ProgressNotifier * progressNotifier)5010 bool CelestiaCore::initLuaHook(ProgressNotifier* progressNotifier)
5011 {
5012     luaHook = new LuaState();
5013     luaHook->init(this);
5014 
5015     string LuaPath = "?.lua;celxx/?.lua;";
5016 
5017     // Find the path for lua files in the extras directories
5018     {
5019         for (vector<string>::const_iterator iter = config->extrasDirs.begin();
5020              iter != config->extrasDirs.end(); iter++)
5021         {
5022             if (*iter != "")
5023             {
5024                 Directory* dir = OpenDirectory(*iter);
5025 
5026                 LuaPathFinder loader("");
5027                 loader.pushDir(*iter);
5028                 dir->enumFiles(loader, true);
5029                 LuaPath += loader.luaPath;
5030                 delete dir;
5031             }
5032         }
5033     }
5034 
5035     // Always grant access for the Lua hook
5036     luaHook->allowSystemAccess();
5037 
5038     luaHook->setLuaPath(LuaPath);
5039 
5040     int status = 0;
5041 
5042     // Execute the Lua hook initialization script
5043     if (config->luaHook != "")
5044     {
5045         string filename = config->luaHook;
5046         ifstream scriptfile(filename.c_str());
5047         if (!scriptfile.good())
5048         {
5049             char errMsg[1024];
5050             sprintf(errMsg, "Error opening LuaHook '%s'",  filename.c_str());
5051             if (alerter != NULL)
5052                 alerter->fatalError(errMsg);
5053             else
5054                 flash(errMsg);
5055         }
5056 
5057         if (progressNotifier)
5058             progressNotifier->update(config->luaHook);
5059 
5060         status = luaHook->loadScript(scriptfile, filename);
5061     }
5062     else
5063     {
5064         status = luaHook->loadScript("");
5065     }
5066 
5067     if (status != 0)
5068     {
5069         cout << "lua hook load failed\n";
5070         string errMsg = luaHook->getErrorMessage();
5071         if (errMsg.empty())
5072             errMsg = "Unknown error loading hook script";
5073         if (alerter != NULL)
5074             alerter->fatalError(errMsg);
5075         else
5076             flash(errMsg);
5077         delete luaHook;
5078         luaHook = NULL;
5079     }
5080     else
5081     {
5082         // Coroutine execution; control may be transferred between the
5083         // script and Celestia's event loop
5084         if (!luaHook->createThread())
5085         {
5086             const char* errMsg = "Script coroutine initialization failed";
5087             cout << "hook thread failed\n";
5088             if (alerter != NULL)
5089                 alerter->fatalError(errMsg);
5090             else
5091                 flash(errMsg);
5092             delete luaHook;
5093             luaHook = NULL;
5094         }
5095 
5096         if (luaHook)
5097         {
5098             while (!luaHook->tick(0.1)) ;
5099         }
5100     }
5101 
5102     // Set up the script context; if the system access policy is allow,
5103     // it will share the same context as the Lua hook. Otherwise, we
5104     // create a private context.
5105     if (config->scriptSystemAccessPolicy == "allow")
5106     {
5107         SetScriptedObjectContext(luaHook->getState());
5108     }
5109     else
5110     {
5111         luaSandbox = new LuaState();
5112         luaSandbox->init(this);
5113 
5114         // Allow access to functions in package because we need 'require'
5115         // But, loadlib is prohibited.
5116         luaSandbox->allowLuaPackageAccess();
5117         luaSandbox->setLuaPath(LuaPath);
5118 
5119         status = luaSandbox->loadScript("");
5120         if (status != 0)
5121         {
5122             delete luaSandbox;
5123             luaSandbox = NULL;
5124         }
5125 
5126         SetScriptedObjectContext(luaSandbox->getState());
5127     }
5128 
5129     return true;
5130 }
5131 #endif
5132