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