1 // stardb.cpp
2 //
3 // Copyright (C) 2001-2008, Chris Laurel <claurel@shatters.net>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9
10 #include <cstring>
11 #include <cmath>
12 #include <cstdlib>
13 #include <cstdio>
14 #include <cassert>
15 #include <algorithm>
16 #include <celmath/mathlib.h>
17 #include <celmath/plane.h>
18 #include <celutil/util.h>
19 #include <celutil/bytes.h>
20 #include <celengine/stardb.h>
21 #include "celestia.h"
22 #include "astro.h"
23 #include "parser.h"
24 #include "parseobject.h"
25 #include "multitexture.h"
26 #include "meshmanager.h"
27 #include <celutil/debug.h>
28
29 using namespace std;
30
31
32 static string HDCatalogPrefix("HD ");
33 static string HIPPARCOSCatalogPrefix("HIP ");
34 static string GlieseCatalogPrefix("Gliese ");
35 static string RossCatalogPrefix("Ross ");
36 static string LacailleCatalogPrefix("Lacaille ");
37 static string TychoCatalogPrefix("TYC ");
38 static string SAOCatalogPrefix("SAO ");
39
40 // The size of the root star octree node is also the maximum distance
41 // distance from the Sun at which any star may be located. The current
42 // setting of 1.0e7 light years is large enough to contain the entire
43 // local group of galaxies. A larger value should be OK, but the
44 // performance implications for octree traversal still need to be
45 // investigated.
46 static const float STAR_OCTREE_ROOT_SIZE = 10000000.0f;
47
48 static const float STAR_OCTREE_MAGNITUDE = 6.0f;
49 static const float STAR_EXTRA_ROOM = 0.01f; // Reserve 1% capacity for extra stars
50
51 const char* StarDatabase::FILE_HEADER = "CELSTARS";
52 const char* StarDatabase::CROSSINDEX_FILE_HEADER = "CELINDEX";
53
54
55 // Used to sort stars by catalog number
56 struct CatalogNumberOrderingPredicate
57 {
58 int unused;
59
CatalogNumberOrderingPredicateCatalogNumberOrderingPredicate60 CatalogNumberOrderingPredicate() {};
61
operator ()CatalogNumberOrderingPredicate62 bool operator()(const Star& star0, const Star& star1) const
63 {
64 return (star0.getCatalogNumber() < star1.getCatalogNumber());
65 }
66 };
67
68
69 struct CatalogNumberEquivalencePredicate
70 {
71 int unused;
72
CatalogNumberEquivalencePredicateCatalogNumberEquivalencePredicate73 CatalogNumberEquivalencePredicate() {};
74
operator ()CatalogNumberEquivalencePredicate75 bool operator()(const Star& star0, const Star& star1) const
76 {
77 return (star0.getCatalogNumber() == star1.getCatalogNumber());
78 }
79 };
80
81
82 // Used to sort star pointers by catalog number
83 struct PtrCatalogNumberOrderingPredicate
84 {
85 int unused;
86
PtrCatalogNumberOrderingPredicatePtrCatalogNumberOrderingPredicate87 PtrCatalogNumberOrderingPredicate() {};
88
operator ()PtrCatalogNumberOrderingPredicate89 bool operator()(const Star* const & star0, const Star* const & star1) const
90 {
91 return (star0->getCatalogNumber() < star1->getCatalogNumber());
92 }
93 };
94
95
parseSimpleCatalogNumber(const string & name,const string & prefix,uint32 * catalogNumber)96 static bool parseSimpleCatalogNumber(const string& name,
97 const string& prefix,
98 uint32* catalogNumber)
99 {
100 char extra[4];
101 if (compareIgnoringCase(name, prefix, prefix.length()) == 0)
102 {
103 unsigned int num;
104 // Use scanf to see if we have a valid catalog number; it must be
105 // of the form: <prefix> <non-negative integer> No additional
106 // characters other than whitespace are allowed after the number.
107 if (sscanf(name.c_str() + prefix.length(), " %u %c", &num, extra) == 1)
108 {
109 *catalogNumber = (uint32) num;
110 return true;
111 }
112 }
113
114 return false;
115 }
116
117
parseHIPPARCOSCatalogNumber(const string & name,uint32 * catalogNumber)118 static bool parseHIPPARCOSCatalogNumber(const string& name,
119 uint32* catalogNumber)
120 {
121 return parseSimpleCatalogNumber(name,
122 HIPPARCOSCatalogPrefix,
123 catalogNumber);
124 }
125
126
parseHDCatalogNumber(const string & name,uint32 * catalogNumber)127 static bool parseHDCatalogNumber(const string& name,
128 uint32* catalogNumber)
129 {
130 return parseSimpleCatalogNumber(name,
131 HDCatalogPrefix,
132 catalogNumber);
133 }
134
135
parseTychoCatalogNumber(const string & name,uint32 * catalogNumber)136 static bool parseTychoCatalogNumber(const string& name,
137 uint32* catalogNumber)
138 {
139 if (compareIgnoringCase(name, TychoCatalogPrefix, TychoCatalogPrefix.length()) == 0)
140 {
141 unsigned int tyc1 = 0, tyc2 = 0, tyc3 = 0;
142 if (sscanf(string(name, TychoCatalogPrefix.length(),
143 string::npos).c_str(),
144 " %u-%u-%u", &tyc1, &tyc2, &tyc3) == 3)
145 {
146 *catalogNumber = (uint32) (tyc3 * 1000000000 + tyc2 * 10000 + tyc1);
147 return true;
148 }
149 }
150
151 return false;
152 }
153
154
parseCelestiaCatalogNumber(const string & name,uint32 * catalogNumber)155 static bool parseCelestiaCatalogNumber(const string& name,
156 uint32* catalogNumber)
157 {
158 char extra[4];
159
160 if (name[0] == '#')
161 {
162 unsigned int num;
163 if (sscanf(name.c_str(), "#%u %c", &num, extra) == 1)
164 {
165 *catalogNumber = (uint32) num;
166 return true;
167 }
168 }
169
170 return false;
171 }
172
173
operator <(const StarDatabase::CrossIndexEntry & e) const174 bool StarDatabase::CrossIndexEntry::operator<(const StarDatabase::CrossIndexEntry& e) const
175 {
176 return catalogNumber < e.catalogNumber;
177 }
178
179
StarDatabase()180 StarDatabase::StarDatabase():
181 nStars (0),
182 stars (NULL),
183 namesDB (NULL),
184 octreeRoot (NULL),
185 nextAutoCatalogNumber(0xfffffffe),
186 binFileCatalogNumberIndex(NULL),
187 binFileStarCount(0)
188 {
189 crossIndexes.resize(MaxCatalog);
190 }
191
192
~StarDatabase()193 StarDatabase::~StarDatabase()
194 {
195 if (stars != NULL)
196 delete [] stars;
197
198 if (catalogNumberIndex != NULL)
199 delete [] catalogNumberIndex;
200
201 for (vector<CrossIndex*>::iterator iter = crossIndexes.begin(); iter != crossIndexes.end(); ++iter)
202 {
203 if (*iter != NULL)
204 delete *iter;
205 }
206 }
207
208
find(uint32 catalogNumber) const209 Star* StarDatabase::find(uint32 catalogNumber) const
210 {
211 Star refStar;
212 refStar.setCatalogNumber(catalogNumber);
213
214 Star** star = lower_bound(catalogNumberIndex,
215 catalogNumberIndex + nStars,
216 &refStar,
217 PtrCatalogNumberOrderingPredicate());
218
219 if (star != catalogNumberIndex + nStars && (*star)->getCatalogNumber() == catalogNumber)
220 return *star;
221 else
222 return NULL;
223 }
224
225
findCatalogNumberByName(const string & name) const226 uint32 StarDatabase::findCatalogNumberByName(const string& name) const
227 {
228 if (name.empty())
229 return Star::InvalidCatalogNumber;
230
231 uint32 catalogNumber = Star::InvalidCatalogNumber;
232
233 if (namesDB != NULL)
234 {
235 catalogNumber = namesDB->findCatalogNumberByName(name);
236 if (catalogNumber != Star::InvalidCatalogNumber)
237 return catalogNumber;
238 }
239
240 if (parseCelestiaCatalogNumber(name, &catalogNumber))
241 {
242 return catalogNumber;
243 }
244 else if (parseHIPPARCOSCatalogNumber(name, &catalogNumber))
245 {
246 return catalogNumber;
247 }
248 else if (parseTychoCatalogNumber(name, &catalogNumber))
249 {
250 return catalogNumber;
251 }
252 else if (parseHDCatalogNumber(name, &catalogNumber))
253 {
254 return searchCrossIndexForCatalogNumber(HenryDraper, catalogNumber);
255 }
256 else if (parseSimpleCatalogNumber(name, SAOCatalogPrefix,
257 &catalogNumber))
258 {
259 return searchCrossIndexForCatalogNumber(SAO, catalogNumber);
260 }
261 else
262 {
263 return Star::InvalidCatalogNumber;
264 }
265 }
266
267
find(const string & name) const268 Star* StarDatabase::find(const string& name) const
269 {
270 uint32 catalogNumber = findCatalogNumberByName(name);
271 if (catalogNumber != Star::InvalidCatalogNumber)
272 return find(catalogNumber);
273 else
274 return NULL;
275 }
276
277
crossIndex(const Catalog catalog,const uint32 celCatalogNumber) const278 uint32 StarDatabase::crossIndex(const Catalog catalog, const uint32 celCatalogNumber) const
279 {
280 if (static_cast<uint32>(catalog) >= crossIndexes.size())
281 return Star::InvalidCatalogNumber;
282
283 CrossIndex* xindex = crossIndexes[catalog];
284 if (xindex == NULL)
285 return Star::InvalidCatalogNumber;
286
287 // A simple linear search. We could store cross indices sorted by
288 // both catalog numbers and trade memory for speed
289 for (CrossIndex::const_iterator iter = xindex->begin(); iter != xindex->end(); iter++)
290 {
291 if (celCatalogNumber == iter->celCatalogNumber)
292 return iter->catalogNumber;
293 }
294
295 return Star::InvalidCatalogNumber;
296 }
297
298
299 // Return the Celestia catalog number for the star with a specified number
300 // in a cross index.
searchCrossIndexForCatalogNumber(const Catalog catalog,const uint32 number) const301 uint32 StarDatabase::searchCrossIndexForCatalogNumber(const Catalog catalog, const uint32 number) const
302 {
303 if (static_cast<unsigned int>(catalog) >= crossIndexes.size())
304 return Star::InvalidCatalogNumber;
305
306 CrossIndex* xindex = crossIndexes[catalog];
307 if (xindex == NULL)
308 return Star::InvalidCatalogNumber;
309
310 CrossIndexEntry xindexEnt;
311 xindexEnt.catalogNumber = number;
312
313 CrossIndex::iterator iter = lower_bound(xindex->begin(), xindex->end(),
314 xindexEnt);
315 if (iter == xindex->end() || iter->catalogNumber != number)
316 return Star::InvalidCatalogNumber;
317 else
318 return iter->celCatalogNumber;
319 }
320
321
searchCrossIndex(const Catalog catalog,const uint32 number) const322 Star* StarDatabase::searchCrossIndex(const Catalog catalog, const uint32 number) const
323 {
324 uint32 celCatalogNumber = searchCrossIndexForCatalogNumber(catalog, number);
325 if (celCatalogNumber != Star::InvalidCatalogNumber)
326 return find(celCatalogNumber);
327 else
328 return NULL;
329 }
330
331
getCompletion(const string & name) const332 vector<string> StarDatabase::getCompletion(const string& name) const
333 {
334 vector<string> completion;
335
336 // only named stars are supported by completion.
337 if (!name.empty() && namesDB != NULL)
338 return namesDB->getCompletion(name);
339 else
340 return completion;
341 }
342
343
catalogNumberToString(uint32 catalogNumber,char * buf,unsigned int bufSize)344 static void catalogNumberToString(uint32 catalogNumber, char* buf, unsigned int bufSize)
345 {
346 // Just return an empty string if there's any chance that the buffer is too small
347 if (bufSize < 20 && bufSize > 0)
348 {
349 buf[0] = '\0';
350 }
351
352 if (catalogNumber <= StarDatabase::MAX_HIPPARCOS_NUMBER)
353 {
354 sprintf(buf, "HIP %d", catalogNumber);
355 }
356 else
357 {
358 uint32 tyc3 = catalogNumber / 1000000000;
359 catalogNumber -= tyc3 * 1000000000;
360 uint32 tyc2 = catalogNumber / 10000;
361 catalogNumber -= tyc2 * 10000;
362 uint32 tyc1 = catalogNumber;
363 sprintf(buf, "TYC %d-%d-%d", tyc1, tyc2, tyc3);
364 }
365 }
366
367
368 // Return the name for the star with specified catalog number. The returned
369 // string will be:
370 // the common name if it exists, otherwise
371 // the Bayer or Flamsteed designation if it exists, otherwise
372 // the HD catalog number if it exists, otherwise
373 // the HIPPARCOS catalog number.
374 //
375 // CAREFUL:
376 // If the star name is not present in the names database, a new
377 // string is constructed to contain the catalog number--keep in
378 // mind that calling this method could possibly incur the overhead
379 // of a memory allocation (though no explcit deallocation is
380 // required as it's all wrapped in the string class.)
getStarName(const Star & star,bool i18n) const381 string StarDatabase::getStarName(const Star& star, bool i18n) const
382 {
383 uint32 catalogNumber = star.getCatalogNumber();
384
385 if (namesDB != NULL)
386 {
387 StarNameDatabase::NumberIndex::const_iterator iter = namesDB->getFirstNameIter(catalogNumber);
388 if (iter != namesDB->getFinalNameIter() && iter->first == catalogNumber)
389 {
390 if (i18n && iter->second != _(iter->second.c_str()))
391 return _(iter->second.c_str());
392 else
393 return iter->second;
394 }
395 }
396
397 char buf[20];
398 /*
399 // Get the HD catalog name
400 if (star.getCatalogNumber() != Star::InvalidCatalogNumber)
401 sprintf(buf, "HD %d", star.getCatalogNumber(Star::HDCatalog));
402 else
403 */
404 catalogNumberToString(catalogNumber, buf, sizeof(buf));
405
406 return string(buf);
407 }
408
409 // A less convenient version of getStarName that writes to a char
410 // array instead of a string. The advantage is that no memory allocation
411 // will every occur.
getStarName(const Star & star,char * nameBuffer,unsigned int bufferSize,bool i18n) const412 void StarDatabase::getStarName(const Star& star, char* nameBuffer, unsigned int bufferSize, bool i18n) const
413 {
414 assert(bufferSize != 0);
415
416 uint32 catalogNumber = star.getCatalogNumber();
417
418 if (namesDB != NULL)
419 {
420 StarNameDatabase::NumberIndex::const_iterator iter = namesDB->getFirstNameIter(catalogNumber);
421 if (iter != namesDB->getFinalNameIter() && iter->first == catalogNumber)
422 {
423 if (i18n && iter->second != _(iter->second.c_str()))
424 strncpy(nameBuffer, _(iter->second.c_str()), bufferSize);
425 else
426 strncpy(nameBuffer, iter->second.c_str(), bufferSize);
427
428 nameBuffer[bufferSize - 1] = '\0';
429 return;
430 }
431 }
432
433 catalogNumberToString(catalogNumber, nameBuffer, bufferSize);
434 }
435
436
getStarNameList(const Star & star,const unsigned int maxNames) const437 string StarDatabase::getStarNameList(const Star& star, const unsigned int maxNames) const
438 {
439 string starNames;
440 char numString[32];
441
442 unsigned int catalogNumber = star.getCatalogNumber();
443
444 StarNameDatabase::NumberIndex::const_iterator iter = namesDB->getFirstNameIter(catalogNumber);
445
446 unsigned int count = 0;
447 while (iter != namesDB->getFinalNameIter() && iter->first == catalogNumber && count < maxNames)
448 {
449 if (count != 0)
450 starNames += " / ";
451
452 starNames += ReplaceGreekLetterAbbr(iter->second.c_str());
453 ++iter;
454 ++count;
455 }
456
457 uint32 hip = catalogNumber;
458 if (hip != Star::InvalidCatalogNumber && hip != 0 && count < maxNames)
459 {
460 if (hip <= Star::MaxTychoCatalogNumber)
461 {
462 if (count != 0)
463 starNames += " / ";
464 if (hip >= 1000000)
465 {
466 uint32 h = hip;
467 uint32 tyc3 = h / 1000000000;
468 h -= tyc3 * 1000000000;
469 uint32 tyc2 = h / 10000;
470 h -= tyc2 * 10000;
471 uint32 tyc1 = h;
472
473 sprintf(numString, "TYC %u-%u-%u", tyc1, tyc2, tyc3);
474 starNames += numString;
475 }
476 else
477 {
478 sprintf(numString, "HIP %u", hip);
479 starNames += numString;
480 }
481
482 ++count;
483 }
484 }
485
486 uint32 hd = crossIndex(StarDatabase::HenryDraper, hip);
487 if (count < maxNames && hd != Star::InvalidCatalogNumber)
488 {
489 if (count != 0)
490 starNames += " / ";
491 sprintf(numString, "HD %u", hd);
492 starNames += numString;
493 }
494
495 uint32 sao = crossIndex(StarDatabase::SAO, hip);
496 if (count < maxNames && sao != Star::InvalidCatalogNumber)
497 {
498 if (count != 0)
499 starNames += " / ";
500 sprintf(numString, "SAO %u", sao);
501 starNames += numString;
502 }
503
504 return starNames;
505 }
506
507
findVisibleStars(StarHandler & starHandler,const Point3f & position,const Quatf & orientation,float fovY,float aspectRatio,float limitingMag) const508 void StarDatabase::findVisibleStars(StarHandler& starHandler,
509 const Point3f& position,
510 const Quatf& orientation,
511 float fovY,
512 float aspectRatio,
513 float limitingMag) const
514 {
515 // Compute the bounding planes of an infinite view frustum
516 Planef frustumPlanes[5];
517 Vec3f planeNormals[5];
518 Mat3f rot = orientation.toMatrix3();
519 float h = (float) tan(fovY / 2);
520 float w = h * aspectRatio;
521 planeNormals[0] = Vec3f(0, 1, -h);
522 planeNormals[1] = Vec3f(0, -1, -h);
523 planeNormals[2] = Vec3f(1, 0, -w);
524 planeNormals[3] = Vec3f(-1, 0, -w);
525 planeNormals[4] = Vec3f(0, 0, -1);
526 for (int i = 0; i < 5; i++)
527 {
528 planeNormals[i].normalize();
529 planeNormals[i] = planeNormals[i] * rot;
530 frustumPlanes[i] = Planef(planeNormals[i], position);
531 }
532
533 octreeRoot->processVisibleObjects(starHandler,
534 position,
535 frustumPlanes,
536 limitingMag,
537 STAR_OCTREE_ROOT_SIZE);
538 }
539
540
findCloseStars(StarHandler & starHandler,const Point3f & position,float radius) const541 void StarDatabase::findCloseStars(StarHandler& starHandler,
542 const Point3f& position,
543 float radius) const
544 {
545 octreeRoot->processCloseObjects(starHandler,
546 position,
547 radius,
548 STAR_OCTREE_ROOT_SIZE);
549 }
550
551
getNameDatabase() const552 StarNameDatabase* StarDatabase::getNameDatabase() const
553 {
554 return namesDB;
555 }
556
557
setNameDatabase(StarNameDatabase * _namesDB)558 void StarDatabase::setNameDatabase(StarNameDatabase* _namesDB)
559 {
560 namesDB = _namesDB;
561 }
562
563
loadCrossIndex(const Catalog catalog,istream & in)564 bool StarDatabase::loadCrossIndex(const Catalog catalog, istream& in)
565 {
566 if (static_cast<unsigned int>(catalog) >= crossIndexes.size())
567 return false;
568
569 if (crossIndexes[catalog] != NULL)
570 delete crossIndexes[catalog];
571
572 // Verify that the star database file has a correct header
573 {
574 int headerLength = strlen(CROSSINDEX_FILE_HEADER);
575 char* header = new char[headerLength];
576 in.read(header, headerLength);
577 if (strncmp(header, CROSSINDEX_FILE_HEADER, headerLength))
578 {
579 cerr << _("Bad header for cross index\n");
580 return false;
581 }
582 delete[] header;
583 }
584
585 // Verify the version
586 {
587 uint16 version;
588 in.read((char*) &version, sizeof version);
589 LE_TO_CPU_INT16(version, version);
590 if (version != 0x0100)
591 {
592 cerr << _("Bad version for cross index\n");
593 return false;
594 }
595 }
596
597 CrossIndex* xindex = new CrossIndex();
598 if (xindex == NULL)
599 return false;
600
601 unsigned int record = 0;
602 for (;;)
603 {
604 CrossIndexEntry ent;
605 in.read((char *) &ent.catalogNumber, sizeof ent.catalogNumber);
606 LE_TO_CPU_INT32(ent.catalogNumber, ent.catalogNumber);
607 if (in.eof())
608 break;
609
610 in.read((char *) &ent.celCatalogNumber, sizeof ent.celCatalogNumber);
611 LE_TO_CPU_INT32(ent.celCatalogNumber, ent.celCatalogNumber);
612 if (in.fail())
613 {
614 cerr << _("Loading cross index failed at record ") << record << '\n';
615 delete xindex;
616 return false;
617 }
618
619 xindex->insert(xindex->end(), ent);
620
621 record++;
622 }
623
624 sort(xindex->begin(), xindex->end());
625
626 crossIndexes[catalog] = xindex;
627
628 return true;
629 }
630
631
loadBinary(istream & in)632 bool StarDatabase::loadBinary(istream& in)
633 {
634 uint32 nStarsInFile = 0;
635
636 // Verify that the star database file has a correct header
637 {
638 int headerLength = strlen(FILE_HEADER);
639 char* header = new char[headerLength];
640 in.read(header, headerLength);
641 if (strncmp(header, FILE_HEADER, headerLength))
642 return false;
643 delete[] header;
644 }
645
646 // Verify the version
647 {
648 uint16 version;
649 in.read((char*) &version, sizeof version);
650 LE_TO_CPU_INT16(version, version);
651 if (version != 0x0100)
652 return false;
653 }
654
655 // Read the star count
656 in.read((char *) &nStarsInFile, sizeof nStarsInFile);
657 LE_TO_CPU_INT32(nStarsInFile, nStarsInFile);
658 if (!in.good())
659 return false;
660
661 unsigned int totalStars = nStars + nStarsInFile;
662
663 while (((unsigned int) nStars) < totalStars)
664 {
665 uint32 catNo = 0;
666 float x = 0.0f, y = 0.0f, z = 0.0f;
667 int16 absMag;
668 uint16 spectralType;
669
670 in.read((char *) &catNo, sizeof catNo);
671 LE_TO_CPU_INT32(catNo, catNo);
672 in.read((char *) &x, sizeof x);
673 LE_TO_CPU_FLOAT(x, x);
674 in.read((char *) &y, sizeof y);
675 LE_TO_CPU_FLOAT(y, y);
676 in.read((char *) &z, sizeof z);
677 LE_TO_CPU_FLOAT(z, z);
678 in.read((char *) &absMag, sizeof absMag);
679 LE_TO_CPU_INT16(absMag, absMag);
680 in.read((char *) &spectralType, sizeof spectralType);
681 LE_TO_CPU_INT16(spectralType, spectralType);
682 if (in.bad())
683 break;
684
685 Star star;
686 star.setPosition(x, y, z);
687 star.setAbsoluteMagnitude((float) absMag / 256.0f);
688
689 StarDetails* details = NULL;
690 StellarClass sc;
691 if (sc.unpack(spectralType))
692 details = StarDetails::GetStarDetails(sc);
693
694 if (details == NULL)
695 {
696 cerr << _("Bad spectral type in star database, star #") << nStars << "\n";
697 return false;
698 }
699
700 star.setDetails(details);
701 star.setCatalogNumber(catNo);
702 unsortedStars.add(star);
703
704 nStars++;
705 }
706
707 if (in.bad())
708 return false;
709
710 DPRINTF(0, "StarDatabase::read: nStars = %d\n", nStarsInFile);
711 clog << nStars << _(" stars in binary database\n");
712
713 // Create the temporary list of stars sorted by catalog number; this
714 // will be used to lookup stars during file loading. After loading is
715 // complete, the stars are sorted into an octree and this list gets
716 // replaced.
717 if (unsortedStars.size() > 0)
718 {
719 binFileStarCount = unsortedStars.size();
720 binFileCatalogNumberIndex = new Star*[binFileStarCount];
721 for (unsigned int i = 0; i < binFileStarCount; i++)
722 {
723 binFileCatalogNumberIndex[i] = &unsortedStars[i];
724 }
725 sort(binFileCatalogNumberIndex, binFileCatalogNumberIndex + binFileStarCount,
726 PtrCatalogNumberOrderingPredicate());
727 }
728
729 return true;
730 }
731
732
finish()733 void StarDatabase::finish()
734 {
735 clog << _("Total star count: ") << nStars << endl;
736
737 buildOctree();
738 buildIndexes();
739
740 // Delete the temporary indices used only during loading
741 delete binFileCatalogNumberIndex;
742 stcFileCatalogNumberIndex.clear();
743
744 // Resolve all barycenters; this can't be done before star sorting. There's
745 // still a bug here: final orbital radii aren't available until after
746 // the barycenters have been resolved, and these are required when building
747 // the octree. This will only rarely cause a problem, but it still needs
748 // to be addressed.
749 for (vector<BarycenterUsage>::const_iterator iter = barycenters.begin();
750 iter != barycenters.end(); iter++)
751 {
752 Star* star = find(iter->catNo);
753 Star* barycenter = find(iter->barycenterCatNo);
754 assert(star != NULL);
755 assert(barycenter != NULL);
756 if (star != NULL && barycenter != NULL)
757 {
758 star->setOrbitBarycenter(barycenter);
759 barycenter->addOrbitingStar(star);
760 }
761 }
762
763 barycenters.clear();
764 }
765
766
errorMessagePrelude(const Tokenizer & tok)767 static void errorMessagePrelude(const Tokenizer& tok)
768 {
769 cerr << _("Error in .stc file (line ") << tok.getLineNumber() << "): ";
770 }
771
stcError(const Tokenizer & tok,const string & msg)772 static void stcError(const Tokenizer& tok,
773 const string& msg)
774 {
775 errorMessagePrelude(tok);
776 cerr << msg << '\n';
777 }
778
779
780 /*! Load star data from a property list into a star instance.
781 */
createStar(Star * star,StcDisposition disposition,uint32 catalogNumber,Hash * starData,const string & path,bool isBarycenter)782 bool StarDatabase::createStar(Star* star,
783 StcDisposition disposition,
784 uint32 catalogNumber,
785 Hash* starData,
786 const string& path,
787 bool isBarycenter)
788 {
789 StarDetails* details = NULL;
790 string spectralType;
791
792 // Get the magnitude and spectral type; if the star is actually
793 // a barycenter placeholder, these fields are ignored.
794 if (isBarycenter)
795 {
796 details = StarDetails::GetBarycenterDetails();
797 }
798 else
799 {
800 if (starData->getString("SpectralType", spectralType))
801 {
802 StellarClass sc = StellarClass::parse(spectralType);
803 details = StarDetails::GetStarDetails(sc);
804 if (details == NULL)
805 {
806 cerr << _("Invalid star: bad spectral type.\n");
807 return false;
808 }
809 }
810 else
811 {
812 // Spectral type is required for new stars
813 if (disposition != ModifyStar)
814 {
815 cerr << _("Invalid star: missing spectral type.\n");
816 return false;
817 }
818 }
819 }
820
821 bool modifyExistingDetails = false;
822 if (disposition == ModifyStar)
823 {
824 StarDetails* existingDetails = star->getDetails();
825
826 // If we're modifying an existing star and it already has a
827 // customized details record, we'll just modify that.
828 if (!existingDetails->shared())
829 {
830 modifyExistingDetails = true;
831 if (details != NULL)
832 {
833 // If the spectral type was modified, copy the new data
834 // to the custom details record.
835 existingDetails->setSpectralType(details->getSpectralType());
836 existingDetails->setTemperature(details->getTemperature());
837 existingDetails->setBolometricCorrection(details->getBolometricCorrection());
838 if ((existingDetails->getKnowledge() & StarDetails::KnowTexture) == 0)
839 existingDetails->setTexture(details->getTexture());
840 if ((existingDetails->getKnowledge() & StarDetails::KnowRotation) == 0)
841 existingDetails->setRotationModel(details->getRotationModel());
842 existingDetails->setVisibility(details->getVisibility());
843 }
844
845 details = existingDetails;
846 }
847 else if (details == NULL)
848 {
849 details = existingDetails;
850 }
851 }
852
853 string modelName;
854 string textureName;
855 bool hasTexture = starData->getString("Texture", textureName);
856 bool hasModel = starData->getString("Mesh", modelName);
857
858 RotationModel* rm = CreateRotationModel(starData, path, 1.0);
859 bool hasRotationModel = (rm != NULL);
860
861 Vec3d semiAxes;
862 bool hasSemiAxes = starData->getVector("SemiAxes", semiAxes);
863 bool hasBarycenter = false;
864 Point3f barycenterPosition;
865
866 double radius;
867 bool hasRadius = starData->getNumber("Radius", radius);
868
869 string infoURL;
870 bool hasInfoURL = starData->getString("InfoURL", infoURL);
871
872 Orbit* orbit = CreateOrbit(Selection(), starData, path, true);
873
874 if (hasTexture ||
875 hasModel ||
876 orbit != NULL ||
877 hasSemiAxes ||
878 hasRadius ||
879 hasRotationModel ||
880 hasInfoURL)
881 {
882 // If the star definition has extended information, clone the
883 // star details so we can customize it without affecting other
884 // stars of the same spectral type.
885 if (!modifyExistingDetails)
886 details = new StarDetails(*details);
887
888 if (hasTexture)
889 {
890 details->setTexture(MultiResTexture(textureName, path));
891 details->addKnowledge(StarDetails::KnowTexture);
892 }
893
894 if (hasModel)
895 {
896 ResourceHandle geometryHandle = GetGeometryManager()->getHandle(GeometryInfo(modelName, path, Vec3f(0.0f, 0.0f, 0.0f), 1.0f, true));
897 details->setGeometry(geometryHandle);
898 }
899
900 if (hasSemiAxes)
901 {
902 details->setEllipsoidSemiAxes(Vec3f((float) semiAxes.x,
903 (float) semiAxes.y,
904 (float) semiAxes.z));
905 }
906
907 if (hasRadius)
908 {
909 details->setRadius((float) radius);
910 details->addKnowledge(StarDetails::KnowRadius);
911 }
912
913 if (hasInfoURL)
914 {
915 details->setInfoURL(infoURL);
916 }
917
918 if (orbit != NULL)
919 {
920 details->setOrbit(orbit);
921
922 // See if a barycenter was specified as well
923 uint32 barycenterCatNo = Star::InvalidCatalogNumber;
924 bool barycenterDefined = false;
925
926 string barycenterName;
927 if (starData->getString("OrbitBarycenter", barycenterName))
928 {
929 barycenterCatNo = findCatalogNumberByName(barycenterName);
930 barycenterDefined = true;
931 }
932 else if (starData->getNumber("OrbitBarycenter", barycenterCatNo))
933 {
934 barycenterDefined = true;
935 }
936
937 if (barycenterDefined)
938 {
939 if (barycenterCatNo != Star::InvalidCatalogNumber)
940 {
941 // We can't actually resolve the barycenter catalog number
942 // to a Star pointer until after all stars have been loaded
943 // and spatially sorted. Just store it in a list to be
944 // resolved after sorting.
945 BarycenterUsage bc;
946 bc.catNo = catalogNumber;
947 bc.barycenterCatNo = barycenterCatNo;
948 barycenters.push_back(bc);
949
950 // Even though we can't actually get the Star pointer for
951 // the barycenter, we can get the star information.
952 Star* barycenter = findWhileLoading(barycenterCatNo);
953 if (barycenter != NULL)
954 {
955 hasBarycenter = true;
956 barycenterPosition = barycenter->getPosition();
957 }
958 }
959
960 if (!hasBarycenter)
961 {
962 cerr << _("Barycenter ") << barycenterName << _(" does not exist.\n");
963 return false;
964 }
965 }
966 }
967
968 if (hasRotationModel)
969 details->setRotationModel(rm);
970 }
971
972 if (!modifyExistingDetails)
973 star->setDetails(details);
974 if (disposition != ModifyStar)
975 star->setCatalogNumber(catalogNumber);
976
977 // Compute the position in rectangular coordinates. If a star has an
978 // orbit and barycenter, it's position is the position of the barycenter.
979 if (hasBarycenter)
980 {
981 star->setPosition(barycenterPosition);
982 }
983 else
984 {
985 double ra = 0.0;
986 double dec = 0.0;
987 double distance = 0.0;
988 if (disposition == ModifyStar)
989 {
990 Point3f pos = star->getPosition();
991 // Convert from Celestia's coordinate system
992 Vec3f v = Vec3f(pos.x, -pos.z, pos.y);
993 v = v * Mat3f::xrotation((float) -astro::J2000Obliquity);
994 distance = v.length();
995 if (distance > 0.0)
996 {
997 v.normalize();
998 ra = radToDeg(std::atan2(v.y, v.x));
999 dec = radToDeg(std::asin(v.z));
1000 }
1001 }
1002
1003 bool modifyPosition = false;
1004 if (!starData->getNumber("RA", ra))
1005 {
1006 if (disposition != ModifyStar)
1007 {
1008 cerr << _("Invalid star: missing right ascension\n");
1009 return false;
1010 }
1011 }
1012 else
1013 {
1014 modifyPosition = true;
1015 }
1016
1017 if (!starData->getNumber("Dec", dec))
1018 {
1019 if (disposition != ModifyStar)
1020 {
1021 cerr << _("Invalid star: missing declination.\n");
1022 return false;
1023 }
1024 }
1025 else
1026 {
1027 modifyPosition = true;
1028 }
1029
1030 if (!starData->getNumber("Distance", distance))
1031 {
1032 if (disposition != ModifyStar)
1033 {
1034 cerr << _("Invalid star: missing distance.\n");
1035 return false;
1036 }
1037 }
1038 else
1039 {
1040 modifyPosition = true;
1041 }
1042
1043 // Truncate to floats to match behavior of reading from binary file.
1044 // The conversion to rectangular coordinates is still performed at
1045 // double precision, however.
1046 if (modifyPosition)
1047 {
1048 float raf = ((float) (ra * 24.0 / 360.0));
1049 float decf = ((float) dec);
1050 float distancef = ((float) distance);
1051 Point3d pos = astro::equatorialToCelestialCart((double) raf, (double) decf, (double) distancef);
1052 star->setPosition(Point3f((float) pos.x, (float) pos.y, (float) pos.z));
1053 }
1054 }
1055
1056 if (isBarycenter)
1057 {
1058 star->setAbsoluteMagnitude(30.0f);
1059 }
1060 else
1061 {
1062 double magnitude = 0.0;
1063 bool magnitudeModified = true;
1064 if (!starData->getNumber("AbsMag", magnitude))
1065 {
1066 if (!starData->getNumber("AppMag", magnitude))
1067 {
1068 if (disposition != ModifyStar)
1069 {
1070 clog << _("Invalid star: missing magnitude.\n");
1071 return false;
1072 }
1073 else
1074 {
1075 magnitudeModified = false;
1076 }
1077 }
1078 else
1079 {
1080 float distance = star->getPosition().distanceFromOrigin();
1081
1082 // We can't compute the intrinsic brightness of the star from
1083 // the apparent magnitude if the star is within a few AU of the
1084 // origin.
1085 if (distance < 1e-5f)
1086 {
1087 clog << _("Invalid star: absolute (not apparent) magnitude must be specified for star near origin\n");
1088 return false;
1089 }
1090 magnitude = astro::appToAbsMag((float) magnitude, distance);
1091 }
1092 }
1093
1094 if (magnitudeModified)
1095 star->setAbsoluteMagnitude((float) magnitude);
1096 }
1097
1098 return true;
1099 }
1100
1101
1102 /*! Load an STC file with star definitions. Each definition has the form:
1103 *
1104 * [disposition] [object type] [catalog number] [name]
1105 * {
1106 * [properties]
1107 * }
1108 *
1109 * Disposition is either Add, Replace, or Modify; Add is the default.
1110 * Object type is either Star or Barycenter, with Star the default
1111 * It is an error to omit both the catalog number and the name.
1112 *
1113 * The dispositions are slightly more complicated than suggested by
1114 * their names. Every star must have an unique catalog number. But
1115 * instead of generating an error, Adding a star with a catalog
1116 * number that already exists will actually replace that star. Here
1117 * are how all of the possibilities are handled:
1118 *
1119 * <name> or <number> already exists:
1120 * Add <name> : new star
1121 * Add <number> : replace star
1122 * Replace <name> : replace star
1123 * Replace <number> : replace star
1124 * Modify <name> : modify star
1125 * Modify <number> : modify star
1126 *
1127 * <name> or <number> doesn't exist:
1128 * Add <name> : new star
1129 * Add <number> : new star
1130 * Replace <name> : new star
1131 * Replace <number> : new star
1132 * Modify <name> : error
1133 * Modify <number> : error
1134 */
load(istream & in,const string & resourcePath)1135 bool StarDatabase::load(istream& in, const string& resourcePath)
1136 {
1137 Tokenizer tokenizer(&in);
1138 Parser parser(&tokenizer);
1139
1140 while (tokenizer.nextToken() != Tokenizer::TokenEnd)
1141 {
1142 bool isStar = true;
1143
1144 // Parse the disposition--either Add, Replace, or Modify. The disposition
1145 // may be omitted. The default value is Add.
1146 StcDisposition disposition = AddStar;
1147 if (tokenizer.getTokenType() == Tokenizer::TokenName)
1148 {
1149 if (tokenizer.getNameValue() == "Modify")
1150 {
1151 disposition = ModifyStar;
1152 tokenizer.nextToken();
1153 }
1154 else if (tokenizer.getNameValue() == "Replace")
1155 {
1156 disposition = ReplaceStar;
1157 tokenizer.nextToken();
1158 }
1159 else if (tokenizer.getNameValue() == "Add")
1160 {
1161 disposition = AddStar;
1162 tokenizer.nextToken();
1163 }
1164 }
1165
1166 // Parse the object type--either Star or Barycenter. The object type
1167 // may be omitted. The default is Star.
1168 if (tokenizer.getTokenType() == Tokenizer::TokenName)
1169 {
1170 if (tokenizer.getNameValue() == "Star")
1171 {
1172 isStar = true;
1173 }
1174 else if (tokenizer.getNameValue() == "Barycenter")
1175 {
1176 isStar = false;
1177 }
1178 else
1179 {
1180 stcError(tokenizer, "unrecognized object type");
1181 return false;
1182 }
1183 tokenizer.nextToken();
1184 }
1185
1186 // Parse the catalog number; it may be omitted if a name is supplied.
1187 uint32 catalogNumber = Star::InvalidCatalogNumber;
1188 if (tokenizer.getTokenType() == Tokenizer::TokenNumber)
1189 {
1190 catalogNumber = (uint32) tokenizer.getNumberValue();
1191 tokenizer.nextToken();
1192 }
1193
1194 string objName;
1195 string firstName;
1196 if (tokenizer.getTokenType() == Tokenizer::TokenString)
1197 {
1198 // A star name (or names) is present
1199 objName = tokenizer.getStringValue();
1200 tokenizer.nextToken();
1201 if (!objName.empty())
1202 {
1203 string::size_type next = objName.find(':', 0);
1204 firstName = objName.substr(0, next);
1205 }
1206 }
1207
1208 Star* star = NULL;
1209
1210 switch (disposition)
1211 {
1212 case AddStar:
1213 // Automatically generate a catalog number for the star if one isn't
1214 // supplied.
1215 if (catalogNumber == Star::InvalidCatalogNumber)
1216 {
1217 catalogNumber = nextAutoCatalogNumber--;
1218 }
1219 else
1220 {
1221 star = findWhileLoading(catalogNumber);
1222 }
1223 break;
1224
1225 case ReplaceStar:
1226 if (catalogNumber == Star::InvalidCatalogNumber)
1227 {
1228 if (!firstName.empty())
1229 {
1230 catalogNumber = findCatalogNumberByName(firstName);
1231 }
1232 }
1233
1234 if (catalogNumber == Star::InvalidCatalogNumber)
1235 {
1236 catalogNumber = nextAutoCatalogNumber--;
1237 }
1238 else
1239 {
1240 star = findWhileLoading(catalogNumber);
1241 }
1242 break;
1243
1244 case ModifyStar:
1245 // If no catalog number was specified, try looking up the star by name
1246 if (catalogNumber == Star::InvalidCatalogNumber && !firstName.empty())
1247 {
1248 catalogNumber = findCatalogNumberByName(firstName);
1249 }
1250
1251 if (catalogNumber != Star::InvalidCatalogNumber)
1252 {
1253 star = findWhileLoading(catalogNumber);
1254 }
1255
1256 break;
1257 }
1258
1259 bool isNewStar = star == NULL;
1260
1261 tokenizer.pushBack();
1262
1263 Value* starDataValue = parser.readValue();
1264 if (starDataValue == NULL)
1265 {
1266 clog << "Error reading star." << endl;
1267 return false;
1268 }
1269
1270 if (starDataValue->getType() != Value::HashType)
1271 {
1272 DPRINTF(0, "Bad star definition.\n");
1273 delete starDataValue;
1274 return false;
1275 }
1276 Hash* starData = starDataValue->getHash();
1277
1278 if (isNewStar)
1279 star = new Star();
1280
1281 bool ok = false;
1282 if (isNewStar && disposition == ModifyStar)
1283 {
1284 clog << "Modify requested for nonexistent star." << endl;
1285 }
1286 else
1287 {
1288 ok = createStar(star, disposition, catalogNumber, starData, resourcePath, !isStar);
1289 }
1290 delete starDataValue;
1291
1292 if (ok)
1293 {
1294 if (isNewStar)
1295 {
1296 unsortedStars.add(*star);
1297 nStars++;
1298 delete star;
1299
1300 // Add the new star to the temporary (load time) index.
1301 stcFileCatalogNumberIndex[catalogNumber] = &unsortedStars[unsortedStars.size() - 1];
1302 }
1303
1304 if (namesDB != NULL && !objName.empty())
1305 {
1306 // List of namesDB will replace any that already exist for
1307 // this star.
1308 namesDB->erase(catalogNumber);
1309
1310 // Iterate through the string for names delimited
1311 // by ':', and insert them into the star database.
1312 // Note that db->add() will skip empty namesDB.
1313 string::size_type startPos = 0;
1314 while (startPos != string::npos)
1315 {
1316 string::size_type next = objName.find(':', startPos);
1317 string::size_type length = string::npos;
1318 if (next != string::npos)
1319 {
1320 length = next - startPos;
1321 ++next;
1322 }
1323 string starName = objName.substr(startPos, length);
1324 namesDB->add(catalogNumber, starName);
1325 if (starName != _(starName.c_str()))
1326 namesDB->add(catalogNumber, _(starName.c_str()));
1327 startPos = next;
1328 }
1329 }
1330 }
1331 else
1332 {
1333 if (isNewStar)
1334 delete star;
1335 DPRINTF(1, "Bad star definition--will continue parsing file.\n");
1336 }
1337 }
1338
1339 return true;
1340 }
1341
1342
buildOctree()1343 void StarDatabase::buildOctree()
1344 {
1345 // This should only be called once for the database
1346 // ASSERT(octreeRoot == NULL);
1347
1348 DPRINTF(1, "Sorting stars into octree . . .\n");
1349 float absMag = astro::appToAbsMag(STAR_OCTREE_MAGNITUDE,
1350 STAR_OCTREE_ROOT_SIZE * (float) sqrt(3.0));
1351 DynamicStarOctree* root = new DynamicStarOctree(Point3f(1000, 1000, 1000),
1352 absMag);
1353 for (unsigned int i = 0; i < unsortedStars.size(); ++i)
1354 {
1355 root->insertObject(unsortedStars[i], STAR_OCTREE_ROOT_SIZE);
1356 }
1357
1358 DPRINTF(1, "Spatially sorting stars for improved locality of reference . . .\n");
1359 Star* sortedStars = new Star[nStars];
1360 Star* firstStar = sortedStars;
1361 root->rebuildAndSort(octreeRoot, firstStar);
1362
1363 // ASSERT((int) (firstStar - sortedStars) == nStars);
1364 DPRINTF(1, "%d stars total\n", (int) (firstStar - sortedStars));
1365 DPRINTF(1, "Octree has %d nodes and %d stars.\n",
1366 1 + octreeRoot->countChildren(), octreeRoot->countObjects());
1367 #if PROFILE_OCTREE
1368 vector<OctreeLevelStatistics> stats;
1369 octreeRoot->computeStatistics(stats);
1370 for (vector<OctreeLevelStatistics>::const_iterator iter = stats.begin(); iter != stats.end(); ++iter)
1371 {
1372 int level = iter - stats.begin();
1373 clog << "Level " << level << ", "
1374 << STAR_OCTREE_ROOT_SIZE / pow(2.0, (double) level) << "ly, "
1375 << iter->nodeCount << " nodes, "
1376 << iter->objectCount << " stars\n";
1377 }
1378 #endif
1379
1380 // Clean up . . .
1381 //delete[] stars;
1382 unsortedStars.clear();
1383 delete root;
1384
1385 stars = sortedStars;
1386 }
1387
1388
buildIndexes()1389 void StarDatabase::buildIndexes()
1390 {
1391 // This should only be called once for the database
1392 // assert(catalogNumberIndexes[0] == NULL);
1393
1394 DPRINTF(1, "Building catalog number indexes . . .\n");
1395
1396 catalogNumberIndex = new Star*[nStars];
1397 for (int i = 0; i < nStars; ++i)
1398 catalogNumberIndex[i] = &stars[i];
1399
1400 sort(catalogNumberIndex, catalogNumberIndex + nStars, PtrCatalogNumberOrderingPredicate());
1401 }
1402
1403
1404 /*! While loading the star catalogs, this function must be called instead of
1405 * find(). The final catalog number index for stars cannot be built until
1406 * after all stars have been loaded. During catalog loading, there are two
1407 * separate indexes: one for the binary catalog and another index for stars
1408 * loaded from stc files. They binary catalog index is a sorted array, while
1409 * the stc catalog index is an STL map. Since the binary file can be quite
1410 * large, we want to avoid creating a map with as many nodes as there are
1411 * stars. Stc files should collectively contain many fewer stars, and stars
1412 * in an stc file may reference each other (barycenters). Thus, a dynamic
1413 * structure like a map is both practical and essential.
1414 */
findWhileLoading(uint32 catalogNumber) const1415 Star* StarDatabase::findWhileLoading(uint32 catalogNumber) const
1416 {
1417 // First check for stars loaded from the binary database
1418 if (binFileCatalogNumberIndex != NULL)
1419 {
1420 Star refStar;
1421 refStar.setCatalogNumber(catalogNumber);
1422
1423 Star** star = lower_bound(binFileCatalogNumberIndex,
1424 binFileCatalogNumberIndex + binFileStarCount,
1425 &refStar,
1426 PtrCatalogNumberOrderingPredicate());
1427
1428 if (star != binFileCatalogNumberIndex + binFileStarCount && (*star)->getCatalogNumber() == catalogNumber)
1429 return *star;
1430 }
1431
1432 // Next check for stars loaded from an stc file
1433 map<uint32, Star*>::const_iterator iter = stcFileCatalogNumberIndex.find(catalogNumber);
1434 if (iter != stcFileCatalogNumberIndex.end())
1435 {
1436 return iter->second;
1437 }
1438
1439 // Star not found
1440 return NULL;
1441 }
1442
1443