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