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