1 // solarsysxml.cpp
2 //
3 // Copyright (C) 2001 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 <cassert>
11 
12 #include <libxml/parser.h>
13 #include <libxml/parserInternals.h>
14 #include <libxml/SAX.h>
15 
16 #include <celutil/debug.h>
17 #include <celmath/mathlib.h>
18 #include "astro.h"
19 #include "customorbit.h"
20 #include "texmanager.h"
21 #include "meshmanager.h"
22 #include "solarsysxml.h"
23 
24 using namespace std;
25 
26 
27 struct UnitDefinition
28 {
29     char* name;
30     double conversion;
31 };
32 
33 UnitDefinition distanceUnits[] =
34 {
35     { "km", 1.0 },
36     { "m",  0.001 },
37     { "au", 149597870.7 },
38     { "ly", 9466411842000.0 },
39 };
40 
41 UnitDefinition timeUnits[] =
42 {
43     { "s", 1.0 },
44     { "m", 60.0 },
45     { "h", 3600.0 },
46     { "d", 86400.0 },
47     { "y", 86400.0 * 365.25 },
48 };
49 
50 
51 static xmlSAXHandler emptySAXHandler =
52 {
53     NULL, /* internalSubset */
54     NULL, /* isStandalone */
55     NULL, /* hasInternalSubset */
56     NULL, /* hasExternalSubset */
57     NULL, /* resolveEntity */
58     NULL, /* getEntity */
59     NULL, /* entityDecl */
60     NULL, /* notationDecl */
61     NULL, /* attributeDecl */
62     NULL, /* elementDecl */
63     NULL, /* unparsedEntityDecl */
64     NULL, /* setDocumentLocator */
65     NULL, /* startDocument */
66     NULL, /* endDocument */
67     NULL, /* startElement */
68     NULL, /* endElement */
69     NULL, /* reference */
70     NULL, /* characters */
71     NULL, /* ignorableWhitespace */
72     NULL, /* processingInstruction */
73     NULL, /* comment */
74     NULL, /* xmlParserWarning */
75     NULL, /* xmlParserError */
76     NULL, /* xmlParserError */
77     NULL, /* getParameterEntity */
78     NULL, /* cdataBlock; */
79     NULL,  /* externalSubset; */
80     1
81 };
82 
83 static xmlSAXHandler saxHandler;
84 
85 enum ParserState {
86     StartState,
87     EndState,
88     BodyState,
89     SurfaceState,
90     AtmosphereState,
91     RingsState,
92     BodyLeafState,
93     SurfaceLeafState,
94     AtmosphereLeafState,
95     RingsLeafState,
96     ErrorState,
97 };
98 
99 struct ParserContext
100 {
101     ParserState state;
102     Body* body;
103     Universe* universe;
104 };
105 
106 
matchName(const xmlChar * s,const char * name)107 static bool matchName(const xmlChar* s, const char* name)
108 {
109     while ((char) *s == *name)
110     {
111         if (*s == '\0')
112             return true;
113         s++;
114         name++;
115     }
116 
117     return false;
118 }
119 
120 
parseBoolean(const xmlChar * s,bool & b)121 static bool parseBoolean(const xmlChar* s, bool& b)
122 {
123     if (matchName(s, "true") || matchName(s, "1") || matchName(s, "on"))
124     {
125         b = true;
126         return true;
127     }
128     else if (matchName(s, "false") || matchName(s, "0") || matchName(s, "off"))
129     {
130         b = false;
131         return true;
132     }
133     else
134     {
135         return false;
136     }
137 }
138 
139 
parseNumber(const xmlChar * s,double & d)140 static bool parseNumber(const xmlChar* s, double& d)
141 {
142     return sscanf((char*) s, "%lf", &d) == 1;
143 }
144 
145 
parseNumber(const xmlChar * s,float & f)146 static bool parseNumber(const xmlChar* s, float& f)
147 {
148     double d;
149     if (parseNumber(s, d))
150     {
151         f = (float) d;
152         return true;
153     }
154     else
155     {
156         return false;
157     }
158 }
159 
160 
parseNumberUnits(const xmlChar * s,double & d,UnitDefinition * unitTable,int unitTableLength,char * defaultUnitName)161 static bool parseNumberUnits(const xmlChar* s,
162                              double& d,
163                              UnitDefinition* unitTable,
164                              int unitTableLength,
165                              char* defaultUnitName)
166 {
167     char unitName[4];
168     double value;
169     int nMatched = sscanf(reinterpret_cast<const char*>(s),
170                           "%lf%3s", &value, unitName);
171     cout << "parseNumberUnits(" << reinterpret_cast<const char*>(s) << ")\n";
172     if (nMatched == 1)
173     {
174         cout << "matched = 1: " << reinterpret_cast<const char*>(s) << '\n';
175         d = value;
176         return true;
177     }
178     else if (nMatched == 2)
179     {
180         cout << "matched = 2: " << reinterpret_cast<const char*>(s) << '\n';
181         // Found a value and units; multiply the value by a conversion
182         // factor to produce default units
183         UnitDefinition* defaultUnit = NULL;
184         UnitDefinition* unit = NULL;
185         int i;
186 
187         // Get the default unit
188         for (i = 0; i < unitTableLength; i++)
189         {
190             if (strcmp(defaultUnitName, unitTable[i].name) == 0)
191             {
192                 defaultUnit = &unitTable[i];
193                 break;
194             }
195         }
196         assert(defaultUnit != NULL);
197 
198         // Try and the match the unit name specified in the xml attribute
199         for (i = 0; i < unitTableLength; i++)
200         {
201             if (strcmp(unitName, unitTable[i].name) == 0)
202             {
203                 unit = &unitTable[i];
204                 break;
205             }
206         }
207 
208         if (unit != NULL)
209         {
210             // Match found; perform the conversion
211             d = value * unit->conversion  / defaultUnit->conversion;
212             cout << "converting: " << value << unit->name << " = " << d << defaultUnit->name << "\n";
213             return true;
214         }
215         else
216         {
217             return false;
218         }
219     }
220     else
221     {
222         // Error . . . we matched nothing.
223         return false;
224     }
225 }
226 
227 
parseDistance(const xmlChar * s,double & d,char * defaultUnitName)228 static bool parseDistance(const xmlChar* s, double& d, char* defaultUnitName)
229 {
230     return parseNumberUnits(s, d,
231                             distanceUnits,
232                             sizeof distanceUnits / sizeof distanceUnits[0],
233                             defaultUnitName);
234 }
235 
236 
parseDistance(const xmlChar * s,float & f,char * defaultUnitName)237 static bool parseDistance(const xmlChar* s, float& f, char* defaultUnitName)
238 {
239     double d;
240     if (parseDistance(s, d, defaultUnitName))
241     {
242         f = (float) d;
243         return true;
244     }
245     else
246     {
247         return false;
248     }
249 }
250 
251 
parseAngle(const xmlChar * s,double & d)252 static bool parseAngle(const xmlChar* s, double& d)
253 {
254     return parseNumber(s, d);
255 }
256 
257 
parseAngle(const xmlChar * s,float & f)258 static bool parseAngle(const xmlChar* s, float& f)
259 {
260     double d;
261     if (parseAngle(s, d))
262     {
263         f = (float) d;
264         return true;
265     }
266     else
267     {
268         return false;
269     }
270 }
271 
272 
parseTime(const xmlChar * s,double & d,char * defaultUnitName)273 static bool parseTime(const xmlChar* s, double& d, char* defaultUnitName)
274 {
275     return parseNumberUnits(s, d,
276                             timeUnits,
277                             sizeof timeUnits / sizeof timeUnits[0],
278                             defaultUnitName);
279 }
280 
281 
parseTime(const xmlChar * s,float & f,char * defaultUnitName)282 static bool parseTime(const xmlChar* s, float& f, char* defaultUnitName)
283 {
284     double d;
285     if (parseTime(s, d, defaultUnitName))
286     {
287         f = (float) d;
288         return true;
289     }
290     else
291     {
292         return false;
293     }
294 }
295 
296 
parseEpoch(const xmlChar * s,double & d)297 static bool parseEpoch(const xmlChar* s, double& d)
298 {
299     if (matchName(s, "J2000"))
300     {
301         d = astro::J2000;
302         return true;
303     }
304     else
305     {
306         return parseNumber(s, d);
307     }
308 }
309 
310 
hexDigit(char c)311 static int hexDigit(char c)
312 {
313     // Assumes an ASCII character set . . .
314     if (c >= '0' && c <= '9')
315         return c - '0';
316     else if (c >= 'a' && c <= 'f')
317         return 10 + (c - 'a');
318     else if (c >= 'A' && c <= 'F')
319         return 10 + (c - 'A');
320     else
321         return 0; // bad digit
322 }
323 
324 
operator <<(ostream & out,const Color & c)325 ostream& operator<<(ostream& out, const Color& c)
326 {
327     cout << '[' << c.red() << ',' << c.green() << ',' << c.blue() << ']';
328     return cout;
329 }
330 
331 
parseColor(const xmlChar * s,Color & c)332 static bool parseColor(const xmlChar* s, Color& c)
333 {
334     const char* colorName = reinterpret_cast<const char*>(s);
335     char hexColor[7];
336     float r = 0.0f;
337     float g = 0.0f;
338     float b = 0.0f;
339 
340     cout << "parsing color: " << colorName << '\n';
341 
342     if (sscanf(colorName, " #%6[0-9a-fA-F] ", hexColor))
343     {
344         if (strlen(hexColor) == 6)
345         {
346             c = Color((hexDigit(hexColor[0]) * 16 +
347                        hexDigit(hexColor[1])) / 255.0f,
348                       (hexDigit(hexColor[2]) * 16 +
349                        hexDigit(hexColor[3])) / 255.0f,
350                       (hexDigit(hexColor[4]) * 16 +
351                        hexDigit(hexColor[5])) / 255.0f);
352             cout << "1: " << c << '\n';
353             return true;
354         }
355         else if (strlen(hexColor) == 3)
356         {
357             c = Color((hexDigit(hexColor[0]) * 17) / 255.0f,
358                       (hexDigit(hexColor[1]) * 17) / 255.0f,
359                       (hexDigit(hexColor[2]) * 17) / 255.0f);
360             cout << "2: " << c << '\n';
361             return true;
362         }
363         else
364         {
365             return false;
366         }
367     }
368     else if (sscanf(colorName, " rgb( %f , %f , %f ) ", &r, &g, &b) == 3)
369     {
370         c = Color(r / 255.0f, g / 255.0f, b / 255.0f);
371         cout << "3: " << c << '\n';
372         return true;
373     }
374     else if (sscanf(colorName, " rgb( %f%% , %f%% , %f%% ) ", &r, &g, &b) == 3)
375     {
376         c = Color(r / 100.0f, g / 100.0f, b / 100.0f);
377         cout << "4: " << c << '\n';
378         return true;
379     }
380     else
381     {
382         return false;
383     }
384 }
385 
386 
createBody(ParserContext * ctx,const xmlChar ** att)387 static bool createBody(ParserContext* ctx, const xmlChar** att)
388 {
389     const xmlChar* name = NULL;
390     const xmlChar* parentName = NULL;
391 
392     // Get the name and parent attributes
393     if (att != NULL)
394     {
395         for (int i = 0; att[i] != NULL; i += 2)
396         {
397             if (matchName(att[i], "name"))
398                 name = att[i + 1];
399             else if (matchName(att[i], "parent"))
400                 parentName = att[i + 1];
401         }
402     }
403 
404     // Require that both are present
405     if (name == NULL)
406     {
407         return false;
408     }
409     else if (parentName == NULL)
410     {
411         return false;
412     }
413 
414     bool orbitsPlanet = false;
415     Selection parent = ctx->universe->findPath(reinterpret_cast<const char*>(parentName), NULL, 0);
416     PlanetarySystem* parentSystem = NULL;
417     if (parent.star != NULL)
418     {
419         SolarSystem* solarSystem = ctx->universe->getSolarSystem(parent.star);
420         if (solarSystem == NULL)
421         {
422             // No solar system defined for this star yet, so we need
423             // to create it.
424             solarSystem = ctx->universe->createSolarSystem(parent.star);
425         }
426         parentSystem = solarSystem->getPlanets();
427     }
428     else if (parent.body != NULL)
429     {
430         // Parent is a planet or moon
431         parentSystem = parent.body->getSatellites();
432         if (parentSystem == NULL)
433         {
434             // If the planet doesn't already have any satellites, we
435             // have to create a new planetary system for it.
436             parentSystem = new PlanetarySystem(parent.body);
437             parent.body->setSatellites(parentSystem);
438         }
439         orbitsPlanet = true;
440     }
441     else
442     {
443         cout << "Parent body '" << parentName << "' of '" << name << "' not found.\n";
444         return false;
445     }
446 
447     if (parentSystem != NULL)
448     {
449         ctx->body = new Body(parentSystem);
450         ctx->body->setName(reinterpret_cast<const char*>(name));
451         parentSystem->addBody(ctx->body);
452         return true;
453     }
454 
455     return false;
456 }
457 
458 
createTexture(ParserContext * ctx,const xmlChar ** att)459 static ResourceHandle createTexture(ParserContext* ctx, const xmlChar** att)
460 {
461     const xmlChar* type = reinterpret_cast<const xmlChar*>("base");
462     const xmlChar* image = NULL;
463     bool compress = false;
464 
465     // Get the type, image, and compress attributes
466     if (att != NULL)
467     {
468         for (int i = 0; att[i] != NULL; i += 2)
469         {
470             if (matchName(att[i], "type"))
471                 type = att[i + 1];
472             else if (matchName(att[i], "image"))
473                 image = att[i + 1];
474             else if (matchName(att[i], "compress"))
475                 parseBoolean(att[i + 1], compress);
476         }
477     }
478 
479     if (image == NULL)
480     {
481         cout << "Texture has no image source.\n";
482         return false;
483     }
484 
485     ResourceHandle texHandle = GetTextureManager()->getHandle(TextureInfo(reinterpret_cast<const char*>(image), compress));
486     if (ctx->state == SurfaceState)
487     {
488         assert(ctx->body != NULL);
489 
490         if (matchName(type, "base"))
491             ctx->body->getSurface().baseTexture = texHandle;
492         else if (matchName(type, "night"))
493             ctx->body->getSurface().nightTexture = texHandle;
494     }
495     else if (ctx->state == AtmosphereState)
496     {
497         assert(ctx->body != NULL);
498         Atmosphere* atmosphere = ctx->body->getAtmosphere();
499         assert(atmosphere != NULL);
500 
501         if (matchName(type, "base"))
502             atmosphere->cloudTexture = texHandle;
503     }
504     else if (ctx->state == RingsState)
505     {
506         assert(ctx->body != NULL);
507         assert(ctx->body->getRings() != NULL);
508 
509         if (matchName(type, "base"))
510             ctx->body->getRings()->texture = texHandle;
511     }
512 
513     return true;
514 }
515 
516 
createBumpMap(ParserContext * ctx,const xmlChar ** att)517 static ResourceHandle createBumpMap(ParserContext* ctx, const xmlChar** att)
518 {
519     const xmlChar* heightmap = NULL;
520     float bumpHeight = 2.5f;
521 
522     // Get the type, image, and compress attributes
523     if (att != NULL)
524     {
525         for (int i = 0; att[i] != NULL; i += 2)
526         {
527             if (matchName(att[i], "heightmap"))
528                 heightmap = att[i + 1];
529             else if (matchName(att[i], "bump-height"))
530                 parseNumber(att[i + 1], bumpHeight);
531         }
532     }
533 
534     if (heightmap == NULL)
535     {
536         cout << "Bump map has no height map source.\n";
537         return false;
538     }
539 
540     ResourceHandle texHandle = GetTextureManager()->getHandle(TextureInfo(reinterpret_cast<const char*>(heightmap), bumpHeight));
541     if (ctx->state == SurfaceState)
542     {
543         assert(ctx->body != NULL);
544         if (texHandle != InvalidResource)
545         {
546             ctx->body->getSurface().bumpTexture = texHandle;
547             ctx->body->getSurface().appearanceFlags |= Surface::ApplyBumpMap;
548         }
549         return true;
550     }
551     else
552     {
553         return false;
554     }
555 }
556 
557 
createAtmosphere(ParserContext * ctx,const xmlChar ** att)558 static bool createAtmosphere(ParserContext* ctx, const xmlChar** att)
559 {
560     Atmosphere* atmosphere = new Atmosphere();
561 
562     if (att != NULL)
563     {
564         for (int i = 0; att[i] != NULL; i += 2)
565         {
566             if (matchName(att[i], "height"))
567                 parseDistance(att[i + 1], atmosphere->height, "km");
568             else if (matchName(att[i], "lower-color"))
569                 parseColor(att[i + 1], atmosphere->lowerColor);
570             else if (matchName(att[i], "upper-color"))
571                 parseColor(att[i + 1], atmosphere->upperColor);
572             else if (matchName(att[i], "sky-color"))
573                 parseColor(att[i + 1], atmosphere->skyColor);
574             else if (matchName(att[i], "cloud-height"))
575                 parseDistance(att[i + 1], atmosphere->cloudHeight, "km");
576             else if (matchName(att[i], "cloud-speed"))
577                 parseAngle(att[i + 1], atmosphere->cloudSpeed);
578         }
579     }
580 
581     assert(ctx->body != NULL);
582     ctx->body->setAtmosphere(*atmosphere);
583     delete atmosphere;
584 
585     return true;
586 }
587 
588 
createHaze(ParserContext * ctx,const xmlChar ** att)589 static bool createHaze(ParserContext* ctx, const xmlChar** att)
590 {
591     Color hazeColor;
592     float hazeDensity = 0.0f;
593 
594     if (att != NULL)
595     {
596         for (int i = 0; att[i] != NULL; i += 2)
597         {
598             if (matchName(att[i], "density"))
599                 parseNumber(att[i + 1], hazeDensity);
600             else if (matchName(att[i], "color"))
601                 parseColor(att[i + 1], hazeColor);
602         }
603     }
604 
605     assert(ctx->body != NULL);
606     ctx->body->getSurface().hazeColor = Color(hazeColor.red(),
607                                               hazeColor.green(),
608                                               hazeColor.blue(),
609                                               hazeDensity);
610 
611     return true;
612 }
613 
614 
createSurface(ParserContext * ctx,const xmlChar ** att)615 static bool createSurface(ParserContext* ctx, const xmlChar** att)
616 {
617     Color color(1.0f, 1.0f, 1.0f);
618     Color specularColor(0.0f, 0.0f, 0.0f);
619     float specularPower = 0.0f;
620     float albedo = 0.5f;
621     bool blendTexture = false;
622     bool emissive = false;
623 
624     if (att != NULL)
625     {
626         for (int i = 0; att[i] != NULL; i += 2)
627         {
628             if (matchName(att[i], "color"))
629                 parseColor(att[i + 1], color);
630             if (matchName(att[i], "specular-color"))
631                 parseColor(att[i + 1], specularColor);
632             if (matchName(att[i], "specular-power"))
633                 parseNumber(att[i + 1], specularPower);
634             if (matchName(att[i], "blend-texture"))
635                 parseBoolean(att[i + 1], blendTexture);
636             if (matchName(att[i], "emissive"))
637                 parseBoolean(att[i + 1], emissive);
638             if (matchName(att[i], "albedo"))
639                 parseNumber(att[i + 1], albedo);
640         }
641     }
642 
643     assert(ctx->body != NULL);
644     ctx->body->setAlbedo(albedo);
645     ctx->body->getSurface().color = color;
646     ctx->body->getSurface().specularColor = specularColor;
647     ctx->body->getSurface().specularPower = specularPower;
648     if (blendTexture)
649         ctx->body->getSurface().appearanceFlags |= Surface::BlendTexture;
650     if (emissive)
651         ctx->body->getSurface().appearanceFlags |= Surface::Emissive;
652 
653     return true;
654 }
655 
656 
createEllipticalOrbit(ParserContext * ctx,const xmlChar ** att)657 static bool createEllipticalOrbit(ParserContext* ctx, const xmlChar** att)
658 {
659     // SemiMajorAxis and Period are absolutely required; everything
660     // else has a reasonable default.
661     double pericenterDistance = 0.0;
662     double semiMajorAxis = 0.0;
663     double period = 0.0;
664     double eccentricity = 0.0;
665     double inclination = 0.0;
666     double ascendingNode = 0.0;
667     double argOfPericenter = 0.0;
668     double anomalyAtEpoch = 0.0;
669     double epoch = astro::J2000;
670     bool foundPeriod = false;
671     bool foundSMA = false;
672     bool foundPD = false;
673 
674     // On the first pass through the attribute list, extract the
675     // period, epoch, ascending node, semi-major axis, eccentricity,
676     // and inclination.
677     if (att != NULL)
678     {
679         int i;
680         for (i = 0; att[i] != NULL; i += 2)
681         {
682             if (matchName(att[i], "period"))
683             {
684                 foundPeriod = true;
685                 parseTime(att[i + 1], period, "d");
686             }
687             else if (matchName(att[i], "semi-major-axis"))
688             {
689                 foundSMA = true;
690                 parseDistance(att[i + 1], semiMajorAxis, "km");
691                 cout << "SMA: " << semiMajorAxis << '\n';
692             }
693             else if (matchName(att[i], "pericenter-distance"))
694             {
695                 foundPD = true;
696                 parseDistance(att[i + 1], pericenterDistance, "km");
697             }
698             else if (matchName(att[i], "epoch"))
699                 parseEpoch(att[i + 1], epoch);
700             else if (matchName(att[i], "eccentricity"))
701                 parseNumber(att[i + 1], eccentricity);
702             else if (matchName(att[i], "inclination"))
703                 parseAngle(att[i + 1], inclination);
704             else if (matchName(att[i], "ascending-node"))
705                 parseAngle(att[i + 1], ascendingNode);
706         }
707 
708         // On the next pass, get the argument or longitude of pericenter; it's
709         // important that we get the longitude of pericenter after we know the
710         // ascending node, because this value is required to convert to
711         // argument of pericenter
712         for (i = 0; att[i] != NULL; i += 2)
713         {
714             if (matchName(att[i], "arg-of-pericenter"))
715             {
716                 parseAngle(att[i + 1], argOfPericenter);
717             }
718             else if (matchName(att[i + 1], "long-of-pericenter"))
719             {
720                 double longOfPericenter;
721                 parseAngle(att[i + 1], longOfPericenter);
722                 argOfPericenter = longOfPericenter - ascendingNode;
723             }
724         }
725 
726         // On the third pass, get the anomaly or mean longitude; converting
727         // from mean longitude to anomaly requires the arg of pericenter from the
728         // second pass.
729         for (i = 0; att[i] != NULL; i += 2)
730         {
731             if (matchName(att[i], "mean-anomaly"))
732             {
733                 parseAngle(att[i + 1], anomalyAtEpoch);
734             }
735             else if (matchName(att[i + 1], "mean-longitude"))
736             {
737                 double longAtEpoch;
738                 parseAngle(att[i + 1], longAtEpoch);
739                 anomalyAtEpoch = longAtEpoch - (argOfPericenter + ascendingNode);
740             }
741         }
742     }
743 
744     if (!foundPeriod)
745     {
746         return false;
747     }
748     else if (!foundSMA && !foundPD)
749     {
750         return false;
751     }
752 
753     // If we read the semi-major axis, use it to compute the pericenter
754     // distance.
755     if (foundSMA)
756         pericenterDistance = semiMajorAxis * (1.0 - eccentricity);
757 
758     EllipticalOrbit* orbit = new EllipticalOrbit(pericenterDistance,
759                                                  eccentricity,
760                                                  degToRad(inclination),
761                                                  degToRad(ascendingNode),
762                                                  degToRad(argOfPericenter),
763                                                  degToRad(anomalyAtEpoch),
764                                                  period,
765                                                  epoch);
766     assert(ctx->body != NULL);
767 
768     // Custom orbits have precedence over elliptical orbits, so don't set
769     // the orbit if the object already has one assigned.
770     if (ctx->body->getOrbit() == NULL)
771         ctx->body->setOrbit(orbit);
772     else
773         delete orbit;
774 
775     return true;
776 }
777 
778 
createCustomOrbit(ParserContext * ctx,const xmlChar ** att)779 static bool createCustomOrbit(ParserContext* ctx, const xmlChar** att)
780 {
781     const xmlChar* name = NULL;
782 
783     // Get the type, image, and compress attributes
784     if (att != NULL)
785     {
786         for (int i = 0; att[i] != NULL; i += 2)
787         {
788             if (matchName(att[i], "name"))
789                 name = att[i + 1];
790         }
791     }
792 
793     if (name == NULL)
794         return false;
795 
796     Orbit* orbit = GetCustomOrbit(reinterpret_cast<const char*>(name));
797     if (orbit == NULL)
798     {
799         DPRINTF(0, "Could not find custom orbit named '%s'\n",
800                 reinterpret_cast<const char*>(name));
801     }
802     else
803     {
804         assert(ctx->body != NULL);
805         ctx->body->setOrbit(orbit);
806     }
807 
808     return true;
809 }
810 
811 
createRotation(ParserContext * ctx,const xmlChar ** att)812 static bool createRotation(ParserContext* ctx, const xmlChar** att)
813 {
814     double period = 0.0;
815     double obliquity = 0.0;
816     double axisLongitude = 0.0;
817     double offset = 0.0;
818     double epoch = astro::J2000;
819 
820     if (att != NULL)
821     {
822         for (int i = 0; att[i] != NULL; i += 2)
823         {
824             if (matchName(att[i], "period"))
825             {
826                 if (matchName(att[i + 1], "sync"))
827                     period = 0.0;
828                 else
829                     parseTime(att[i + 1], period, "h");
830             }
831             else if (matchName(att[i], "obliquity"))
832                 parseAngle(att[i + 1], obliquity);
833             else if (matchName(att[i], "axis-longitude"))
834                 parseAngle(att[i + 1], axisLongitude);
835             else if (matchName(att[i], "offset"))
836                 parseAngle(att[i + 1], offset);
837             else if (matchName(att[i], "epoch"))
838                 parseEpoch(att[i + 1], epoch);
839         }
840     }
841 
842     assert(ctx->body != NULL);
843 
844     RotationElements re;
845     // A period of 0 means that the object is in synchronous rotation, so
846     // we'll set its rotation period equal to its orbital period.  The catch
847     // is that we require that the orbit was specified before the rotation
848     // elements within the XML file.
849     Orbit* orbit = ctx->body->getOrbit();
850     if (orbit == NULL)
851         return false;
852 
853     if (period == 0.0)
854         re.period = (float) orbit->getPeriod();
855     else
856         re.period = (float) period / 24.0f;
857     re.obliquity = (float) degToRad(obliquity);
858     re.axisLongitude = (float) degToRad(axisLongitude);
859     re.offset = (float) degToRad(offset);
860     re.epoch = epoch;
861     ctx->body->setRotationElements(re);
862 
863     return true;
864 }
865 
866 
createGeometry(ParserContext * ctx,const xmlChar ** att)867 static bool createGeometry(ParserContext* ctx, const xmlChar** att)
868 {
869     double radius = 1.0;
870     double oblateness = 0.0;
871     const xmlChar* meshName = NULL;
872 
873     // Get the radius and mesh attributes
874     if (att != NULL)
875     {
876         for (int i = 0; att[i] != NULL; i += 2)
877         {
878             if (matchName(att[i], "radius"))
879                 parseDistance(att[i + 1], radius, "km");
880             else if (matchName(att[i], "mesh"))
881                 meshName = att[i + 1];
882             else if (matchName(att[i], "oblateness"))
883                 parseNumber(att[i + 1], oblateness);
884         }
885     }
886 
887     assert(ctx->body != NULL);
888 
889     ResourceHandle meshHandle = InvalidResource;
890     if (meshName != NULL)
891         meshHandle = GetMeshManager()->getHandle(MeshInfo(reinterpret_cast<const char*>(meshName)));
892     ctx->body->setMesh(meshHandle);
893     ctx->body->setRadius((float) radius);
894     ctx->body->setOblateness((float) oblateness);
895 
896     return false;
897 }
898 
899 
createRings(ParserContext * ctx,const xmlChar ** att)900 static bool createRings(ParserContext* ctx, const xmlChar** att)
901 {
902     double innerRadius = 0.0;
903     double outerRadius = 0.0;
904     Color color(1.0f, 1.0f, 1.0f);
905 
906     // Get the radius and color attributes
907     if (att != NULL)
908     {
909         for (int i = 0; att[i] != NULL; i += 2)
910         {
911             if (matchName(att[i], "inner-radius"))
912                 parseDistance(att[i + 1], innerRadius, "km");
913             else if (matchName(att[i], "outer-radius"))
914                 parseDistance(att[i + 1], outerRadius, "km");
915             else if (matchName(att[i], "color"))
916                 parseColor(att[i + 1], color);
917         }
918     }
919 
920     assert(ctx->body != NULL);
921     ctx->body->setRings(RingSystem(innerRadius, outerRadius, color));
922 
923     return true;
924 }
925 
926 
927 // SAX startDocument callback
solarSysStartDocument(void * data)928 static void solarSysStartDocument(void* data)
929 {
930     ParserContext* ctx = reinterpret_cast<ParserContext*>(data);
931     ctx->state = StartState;
932     ctx->body = NULL;
933 }
934 
935 
936 // SAX endDocument callback
solarSysEndDocument(void * data)937 static void solarSysEndDocument(void* data)
938 {
939     ParserContext* ctx = reinterpret_cast<ParserContext*>(data);
940     ctx->state = EndState;
941     ctx->body = NULL;
942 }
943 
944 
945 // SAX startElement callback
solarSysStartElement(void * data,const xmlChar * name,const xmlChar ** att)946 static void solarSysStartElement(void* data,
947                                  const xmlChar* name,
948                                  const xmlChar** att)
949 {
950     ParserContext* ctx = reinterpret_cast<ParserContext*>(data);
951 
952     switch (ctx->state)
953     {
954     case ErrorState:
955         return;
956 
957     case StartState:
958         if (matchName(name, "body"))
959         {
960             createBody(ctx, att);
961             ctx->state = BodyState;
962         }
963         else if (!matchName(name, "catalog"))
964         {
965             ctx->state = ErrorState;
966         }
967         break;
968 
969     case BodyState:
970         if (matchName(name, "surface"))
971         {
972             createSurface(ctx, att);
973             ctx->state = SurfaceState;
974         }
975         else if (matchName(name, "geometry"))
976         {
977             createGeometry(ctx, att);
978             ctx->state = BodyLeafState;
979         }
980         else if (matchName(name, "elliptical"))
981         {
982             createEllipticalOrbit(ctx, att);
983             ctx->state = BodyLeafState;
984         }
985         else if (matchName(name, "customorbit"))
986         {
987             createCustomOrbit(ctx, att);
988             ctx->state = BodyLeafState;
989         }
990         else if (matchName(name, "rotation"))
991         {
992             createRotation(ctx, att);
993             ctx->state = BodyLeafState;
994         }
995         else if (matchName(name, "atmosphere"))
996         {
997             createAtmosphere(ctx, att);
998             ctx->state = AtmosphereState;
999         }
1000         else if (matchName(name, "rings"))
1001         {
1002             createRings(ctx, att);
1003             ctx->state = RingsState;
1004         }
1005         else
1006         {
1007             ctx->state = ErrorState;
1008         }
1009         break;
1010 
1011     case SurfaceState:
1012         if (matchName(name, "texture"))
1013         {
1014             createTexture(ctx, att);
1015             ctx->state = SurfaceLeafState;
1016         }
1017         else if (matchName(name, "bumpmap"))
1018         {
1019             createBumpMap(ctx, att);
1020             ctx->state = SurfaceLeafState;
1021         }
1022         else if (matchName(name, "haze"))
1023         {
1024             createHaze(ctx, att);
1025             ctx->state = SurfaceLeafState;
1026         }
1027         else
1028         {
1029             ctx->state = ErrorState;
1030         }
1031         break;
1032 
1033     case RingsState:
1034         if (matchName(name, "texture"))
1035         {
1036             createTexture(ctx, att);
1037             ctx->state = RingsLeafState;
1038         }
1039         break;
1040 
1041     case AtmosphereState:
1042         if (matchName(name, "texture"))
1043         {
1044             createTexture(ctx, att);
1045             ctx->state = AtmosphereLeafState;
1046         }
1047         break;
1048 
1049     case BodyLeafState:
1050     case SurfaceLeafState:
1051     case AtmosphereLeafState:
1052     case RingsLeafState:
1053         ctx->state = ErrorState;
1054         break;
1055 
1056     default:
1057         break;
1058     }
1059 
1060     if (ctx->state == ErrorState)
1061     {
1062         cout << "Error!  " << name << " element not expected.\n";
1063     }
1064 }
1065 
1066 
1067 // SAX endElement callback
solarSysEndElement(void * data,const xmlChar * name)1068 static void solarSysEndElement(void* data, const xmlChar* name)
1069 {
1070     ParserContext* ctx = reinterpret_cast<ParserContext*>(data);
1071     switch (ctx->state)
1072     {
1073     case ErrorState:
1074         return;
1075 
1076     case BodyState:
1077         if (matchName(name, "body"))
1078         {
1079             assert(ctx->body != NULL);
1080             if (ctx->body->getOrbit() == NULL)
1081             {
1082                 DPRINTF(0, "Object %s has no orbit!  Removing . . .\n",
1083                         ctx->body->getName().c_str());
1084 
1085             }
1086             ctx->body = NULL;
1087             ctx->state = StartState;
1088         }
1089         else
1090         {
1091             ctx->state = ErrorState;
1092         }
1093         break;
1094 
1095     case SurfaceState:
1096         if (matchName(name, "surface"))
1097             ctx->state = BodyState;
1098         else
1099             ctx->state = ErrorState;
1100         break;
1101 
1102     case AtmosphereState:
1103         if (matchName(name, "atmosphere"))
1104             ctx->state = BodyState;
1105         else
1106             ctx->state = ErrorState;
1107         break;
1108 
1109     case RingsState:
1110         if (matchName(name, "rings"))
1111             ctx->state = BodyState;
1112         else
1113             ctx->state = ErrorState;
1114         break;
1115 
1116     case BodyLeafState:
1117         if (matchName(name, "geometry") ||
1118             matchName(name, "elliptical") ||
1119             matchName(name, "customorbit") ||
1120             matchName(name, "rotation"))
1121         {
1122             ctx->state = BodyState;
1123         }
1124         break;
1125 
1126     case SurfaceLeafState:
1127         if (matchName(name, "texture") ||
1128             matchName(name, "haze") ||
1129             matchName(name, "bumpmap"))
1130         {
1131             ctx->state = SurfaceState;
1132         }
1133         break;
1134 
1135     case AtmosphereLeafState:
1136         if (matchName(name, "texture"))
1137             ctx->state = AtmosphereState;
1138         break;
1139 
1140     case RingsLeafState:
1141         if (matchName(name, "texture"))
1142             ctx->state = RingsState;
1143         break;
1144 
1145     default:
1146         break;
1147     }
1148 
1149     if (ctx->state == ErrorState)
1150     {
1151         cout << "Error!  End of " << name << " element not expected.\n";
1152     }
1153 }
1154 
1155 
initSAXHandler(xmlSAXHandler & sax)1156 static void initSAXHandler(xmlSAXHandler& sax)
1157 {
1158     sax = emptySAXHandler;
1159     sax.startDocument = solarSysStartDocument;
1160     sax.endDocument = solarSysEndDocument;
1161     sax.startElement = solarSysStartElement;
1162     sax.endElement = solarSysEndElement;
1163 }
1164 
1165 
parseSolarSystemXML(xmlSAXHandlerPtr sax,void * userData,const char * filename)1166 static bool parseSolarSystemXML(xmlSAXHandlerPtr sax,
1167                                 void* userData,
1168                                 const char* filename)
1169 {
1170     xmlParserCtxtPtr ctxt = xmlCreateFileParserCtxt(filename);
1171     if (ctxt == NULL)
1172         return false;
1173 
1174     ctxt->sax = sax;
1175     ctxt->userData = userData;
1176 
1177     xmlParseDocument(ctxt);
1178 
1179     int wellFormed = ctxt->wellFormed;
1180     if (sax != NULL)
1181         ctxt->sax = NULL;
1182     xmlFreeParserCtxt(ctxt);
1183 
1184     cout << "Well formed: " << wellFormed << '\n';
1185 
1186     return wellFormed != 0;
1187 }
1188 
1189 
LoadSolarSystemObjectsXML(const string & source,Universe & universe)1190 bool LoadSolarSystemObjectsXML(const string& source, Universe& universe)
1191 {
1192     initSAXHandler(saxHandler);
1193 
1194     ParserContext ctx;
1195     ctx.universe = &universe;
1196 
1197     if (!parseSolarSystemXML(&saxHandler, &ctx, source.c_str()))
1198     {
1199         cout << "Error parsing " << source << '\n';
1200         return false;
1201     }
1202 
1203     return true;
1204 }
1205