573 lines
17 KiB
C++
573 lines
17 KiB
C++
// solarsys.h
|
|
//
|
|
// Copyright (C) 2001 Chris Laurel <claurel@shatters.net>
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
|
|
#include <cassert>
|
|
#ifndef _WIN32
|
|
#include "config.h"
|
|
#endif
|
|
#include "celutil/debug.h"
|
|
#include "celmath/mathlib.h"
|
|
#include "astro.h"
|
|
#include "parser.h"
|
|
#include "customorbit.h"
|
|
#include "texmanager.h"
|
|
#include "meshmanager.h"
|
|
#include "solarsys.h"
|
|
|
|
using namespace std;
|
|
|
|
|
|
static Surface* CreateSurface(Hash* surfaceData)
|
|
{
|
|
Surface* surface = new Surface();
|
|
|
|
surface->color = Color(1.0f, 1.0f, 1.0f);
|
|
surfaceData->getColor("Color", surface->color);
|
|
|
|
Color hazeColor;
|
|
float hazeDensity = 0.0f;
|
|
surfaceData->getColor("HazeColor", hazeColor);
|
|
surfaceData->getNumber("HazeDensity", hazeDensity);
|
|
surface->hazeColor = Color(hazeColor.red(), hazeColor.green(),
|
|
hazeColor.blue(), hazeDensity);
|
|
|
|
surfaceData->getColor("SpecularColor", surface->specularColor);
|
|
surfaceData->getNumber("SpecularPower", surface->specularPower);
|
|
|
|
string baseTexture;
|
|
string bumpTexture;
|
|
string nightTexture;
|
|
bool applyBaseTexture = surfaceData->getString("Texture", baseTexture);
|
|
bool applyBumpMap = surfaceData->getString("BumpMap", bumpTexture);
|
|
bool applyNightMap = surfaceData->getString("NightTexture", nightTexture);
|
|
|
|
float bumpHeight = 2.5f;
|
|
surfaceData->getNumber("BumpHeight", bumpHeight);
|
|
|
|
bool blendTexture = false;
|
|
surfaceData->getBoolean("BlendTexture", blendTexture);
|
|
|
|
bool compressTexture = false;
|
|
surfaceData->getBoolean("CompressTexture", compressTexture);
|
|
|
|
if (blendTexture)
|
|
surface->appearanceFlags |= Surface::BlendTexture;
|
|
if (applyBaseTexture)
|
|
surface->appearanceFlags |= Surface::ApplyBaseTexture;
|
|
if (applyBumpMap)
|
|
surface->appearanceFlags |= Surface::ApplyBumpMap;
|
|
if (applyNightMap)
|
|
surface->appearanceFlags |= Surface::ApplyNightMap;
|
|
if (surface->specularColor != Color(0.0f, 0.0f, 0.0f))
|
|
surface->appearanceFlags |= Surface::SpecularReflection;
|
|
|
|
TextureManager* texMan = GetTextureManager();
|
|
if (applyBaseTexture)
|
|
surface->baseTexture = texMan->getHandle(TextureInfo(baseTexture,
|
|
compressTexture));
|
|
if (applyBumpMap)
|
|
surface->bumpTexture = texMan->getHandle(TextureInfo(bumpTexture,
|
|
bumpHeight));
|
|
if (applyNightMap)
|
|
surface->nightTexture = texMan->getHandle(TextureInfo(nightTexture));
|
|
|
|
return surface;
|
|
}
|
|
|
|
|
|
static EllipticalOrbit* CreateEllipticalOrbit(Hash* orbitData,
|
|
bool usePlanetUnits)
|
|
{
|
|
// SemiMajorAxis and Period are absolutely required; everything
|
|
// else has a reasonable default.
|
|
double pericenterDistance = 0.0;
|
|
double semiMajorAxis = 0.0;
|
|
if (!orbitData->getNumber("SemiMajorAxis", semiMajorAxis))
|
|
{
|
|
if (!orbitData->getNumber("PericenterDistance", pericenterDistance))
|
|
{
|
|
DPRINTF("SemiMajorAxis/PericenterDistance missing! Skipping planet . . .\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
double period = 0.0;
|
|
if (!orbitData->getNumber("Period", period))
|
|
{
|
|
DPRINTF("Period missing! Skipping planet . . .\n");
|
|
return NULL;
|
|
}
|
|
|
|
double eccentricity = 0.0;
|
|
orbitData->getNumber("Eccentricity", eccentricity);
|
|
|
|
double inclination = 0.0;
|
|
orbitData->getNumber("Inclination", inclination);
|
|
|
|
double ascendingNode = 0.0;
|
|
orbitData->getNumber("AscendingNode", ascendingNode);
|
|
|
|
double argOfPericenter = 0.0;
|
|
if (!orbitData->getNumber("ArgOfPericenter", argOfPericenter))
|
|
{
|
|
double longOfPericenter = 0.0;
|
|
if (orbitData->getNumber("LongOfPericenter", longOfPericenter))
|
|
argOfPericenter = longOfPericenter - ascendingNode;
|
|
}
|
|
|
|
double epoch = astro::J2000;
|
|
orbitData->getNumber("Epoch", epoch);
|
|
|
|
// Accept either the mean anomaly or mean longitude--use mean anomaly
|
|
// if both are specified.
|
|
double anomalyAtEpoch = 0.0;
|
|
if (!orbitData->getNumber("MeanAnomaly", anomalyAtEpoch))
|
|
{
|
|
double longAtEpoch = 0.0;
|
|
if (orbitData->getNumber("MeanLongitude", longAtEpoch))
|
|
anomalyAtEpoch = longAtEpoch - (argOfPericenter + ascendingNode);
|
|
}
|
|
|
|
if (usePlanetUnits)
|
|
{
|
|
semiMajorAxis = astro::AUtoKilometers(semiMajorAxis);
|
|
pericenterDistance = astro::AUtoKilometers(pericenterDistance);
|
|
period = period * 365.25f;
|
|
}
|
|
|
|
// If we read the semi-major axis, use it to compute the pericenter
|
|
// distance.
|
|
if (semiMajorAxis != 0.0)
|
|
pericenterDistance = semiMajorAxis * (1.0 - eccentricity);
|
|
|
|
return new EllipticalOrbit(pericenterDistance,
|
|
eccentricity,
|
|
degToRad(inclination),
|
|
degToRad(ascendingNode),
|
|
degToRad(argOfPericenter),
|
|
degToRad(anomalyAtEpoch),
|
|
period,
|
|
epoch);
|
|
}
|
|
|
|
|
|
static RotationElements CreateRotationElements(Hash* rotationData,
|
|
float orbitalPeriod)
|
|
{
|
|
RotationElements re;
|
|
|
|
// The default is synchronous rotation (rotation period == orbital period)
|
|
float period = orbitalPeriod * 24.0f;
|
|
rotationData->getNumber("RotationPeriod", period);
|
|
re.period = period / 24.0f;
|
|
|
|
float offset = 0.0f;
|
|
rotationData->getNumber("RotationOffset", offset);
|
|
re.offset = degToRad(offset);
|
|
|
|
rotationData->getNumber("RotationEpoch", re.epoch);
|
|
|
|
float obliquity = 0.0f;
|
|
rotationData->getNumber("Obliquity", obliquity);
|
|
re.obliquity = degToRad(obliquity);
|
|
|
|
float axisLongitude = 0.0f;
|
|
rotationData->getNumber("LongOfRotationAxis", axisLongitude);
|
|
re.axisLongitude = degToRad(axisLongitude);
|
|
|
|
return re;
|
|
}
|
|
|
|
|
|
// Create a body (planet or moon) using the values from a hash
|
|
// The usePlanetsUnits flags specifies whether period and semi-major axis
|
|
// are in years and AU rather than days and kilometers
|
|
static Body* CreatePlanet(PlanetarySystem* system,
|
|
Hash* planetData,
|
|
bool usePlanetUnits = true)
|
|
{
|
|
Body* body = new Body(system);
|
|
|
|
string name("Unnamed");
|
|
planetData->getString("Name", name);
|
|
body->setName(name);
|
|
|
|
DPRINTF("Reading planet %s\n", name.c_str());
|
|
|
|
Orbit* orbit = NULL;
|
|
string customOrbitName;
|
|
if (planetData->getString("CustomOrbit", customOrbitName))
|
|
{
|
|
orbit = GetCustomOrbit(customOrbitName);
|
|
if (orbit == NULL)
|
|
DPRINTF("Could not find custom orbit named '%s'\n",
|
|
customOrbitName.c_str());
|
|
}
|
|
if (orbit == NULL)
|
|
{
|
|
Value* orbitDataValue = planetData->getValue("EllipticalOrbit");
|
|
if (orbitDataValue != NULL)
|
|
{
|
|
if (orbitDataValue->getType() != Value::HashType)
|
|
{
|
|
DPRINTF("Object '%s' has incorrect elliptical orbit syntax.\n",
|
|
name.c_str());
|
|
}
|
|
else
|
|
{
|
|
orbit = CreateEllipticalOrbit(orbitDataValue->getHash(),
|
|
usePlanetUnits);
|
|
}
|
|
}
|
|
}
|
|
if (orbit == NULL)
|
|
{
|
|
DPRINTF("No valid orbit specified for object '%s'; skipping . . .\n",
|
|
name.c_str());
|
|
delete body;
|
|
return NULL;
|
|
}
|
|
body->setOrbit(orbit);
|
|
|
|
double albedo = 0.5;
|
|
planetData->getNumber("Albedo", albedo);
|
|
body->setAlbedo(albedo);
|
|
|
|
double radius = 10000.0;
|
|
planetData->getNumber("Radius", radius);
|
|
body->setRadius(radius);
|
|
|
|
double oblateness = 0.0;
|
|
planetData->getNumber("Oblateness", oblateness);
|
|
body->setOblateness(oblateness);
|
|
|
|
body->setRotationElements(CreateRotationElements(planetData, orbit->getPeriod()));
|
|
|
|
Surface* surface = CreateSurface(planetData);
|
|
body->setSurface(*surface);
|
|
delete surface;
|
|
|
|
|
|
|
|
{
|
|
string mesh("");
|
|
if (planetData->getString("Mesh", mesh))
|
|
{
|
|
ResourceHandle meshHandle = GetMeshManager()->getHandle(MeshInfo(mesh));
|
|
body->setMesh(meshHandle);
|
|
}
|
|
}
|
|
|
|
// Read the atmosphere
|
|
{
|
|
Value* atmosDataValue = planetData->getValue("Atmosphere");
|
|
if (atmosDataValue != NULL)
|
|
{
|
|
if (atmosDataValue->getType() != Value::HashType)
|
|
{
|
|
cout << "ReadSolarSystem: Atmosphere must be an assoc array.\n";
|
|
}
|
|
else
|
|
{
|
|
Hash* atmosData = atmosDataValue->getHash();
|
|
assert(atmosData != NULL);
|
|
|
|
Atmosphere* atmosphere = new Atmosphere();
|
|
atmosData->getNumber("Height", atmosphere->height);
|
|
atmosData->getColor("Lower", atmosphere->lowerColor);
|
|
atmosData->getColor("Upper", atmosphere->upperColor);
|
|
atmosData->getColor("Sky", atmosphere->skyColor);
|
|
atmosData->getNumber("CloudHeight", atmosphere->cloudHeight);
|
|
atmosData->getNumber("CloudSpeed", atmosphere->cloudSpeed);
|
|
atmosphere->cloudSpeed = degToRad(atmosphere->cloudSpeed);
|
|
|
|
string cloudTexture;
|
|
if (atmosData->getString("CloudMap", cloudTexture))
|
|
{
|
|
atmosphere->cloudTex =
|
|
GetTextureManager()->getHandle(TextureInfo(cloudTexture));
|
|
}
|
|
|
|
body->setAtmosphere(*atmosphere);
|
|
delete atmosphere;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read the ring system
|
|
{
|
|
Value* ringsDataValue = planetData->getValue("Rings");
|
|
if (ringsDataValue != NULL)
|
|
{
|
|
if (ringsDataValue->getType() != Value::HashType)
|
|
{
|
|
cout << "ReadSolarSystem: Rings must be an assoc array.\n";
|
|
}
|
|
else
|
|
{
|
|
Hash* ringsData = ringsDataValue->getHash();
|
|
// ASSERT(ringsData != NULL);
|
|
|
|
double inner = 0.0, outer = 0.0;
|
|
ringsData->getNumber("Inner", inner);
|
|
ringsData->getNumber("Outer", outer);
|
|
|
|
Color color(1.0f, 1.0f, 1.0f);
|
|
ringsData->getColor("Color", color);
|
|
|
|
string textureName;
|
|
ringsData->getString("Texture", textureName);
|
|
ResourceHandle texture = GetTextureManager()->getHandle(TextureInfo(textureName));
|
|
|
|
body->setRings(RingSystem((float) inner, (float) outer,
|
|
color, texture));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read the moons
|
|
// TODO: This is largely cut-and-pasted from the ReadSolarSystem function
|
|
// It would be better if they actually used the same code
|
|
|
|
Value* moonsDataValue = planetData->getValue("Moons");
|
|
if (moonsDataValue != NULL)
|
|
{
|
|
if (moonsDataValue->getType() != Value::ArrayType)
|
|
{
|
|
cout << "ReadSolarSystem: Moons must be an array.\n";
|
|
}
|
|
else
|
|
{
|
|
Array* moonsData = moonsDataValue->getArray();
|
|
// ASSERT(moonsData != NULL);
|
|
|
|
PlanetarySystem* satellites = NULL;
|
|
if (moonsData->size() != 0)
|
|
satellites = new PlanetarySystem(body);
|
|
|
|
for (unsigned int i = 0; i < moonsData->size(); i++)
|
|
{
|
|
Value* moonValue = (*moonsData)[i];
|
|
if (moonValue != NULL &&
|
|
moonValue->getType() == Value::HashType)
|
|
{
|
|
Body* moon = CreatePlanet(satellites,
|
|
moonValue->getHash(),
|
|
false);
|
|
if (moon != NULL)
|
|
satellites->addBody(moon);
|
|
}
|
|
else
|
|
{
|
|
cout << "ReadSolarSystem: Moon data must be an assoc array.\n";
|
|
}
|
|
}
|
|
body->setSatellites(satellites);
|
|
}
|
|
}
|
|
|
|
return body;
|
|
}
|
|
|
|
|
|
static SolarSystem* ReadSolarSystem(Parser& parser,
|
|
const StarDatabase& starDB)
|
|
{
|
|
Value* starName = parser.readValue();
|
|
if (starName == NULL)
|
|
{
|
|
cout << "ReadSolarSystem: Error reading star name.\n";
|
|
return NULL;
|
|
}
|
|
|
|
if (starName->getType() != Value::StringType)
|
|
{
|
|
cout << "ReadSolarSystem: Star name is not a string.\n";
|
|
delete starName;
|
|
return NULL;
|
|
}
|
|
|
|
Value* solarSystemDataValue = parser.readValue();
|
|
if (solarSystemDataValue == NULL)
|
|
{
|
|
cout << "ReadSolarSystem: Error reading solar system data\n";
|
|
delete starName;
|
|
return NULL;
|
|
}
|
|
|
|
if (solarSystemDataValue->getType() != Value::HashType)
|
|
{
|
|
cout << "ReadSolarSystem: Solar system data must be an assoc array\n";
|
|
delete starName;
|
|
delete solarSystemDataValue;
|
|
return NULL;
|
|
}
|
|
|
|
Hash* solarSystemData = solarSystemDataValue->getHash();
|
|
// ASSERT(solarSystemData != NULL);
|
|
|
|
Star* star = starDB.find(starName->getString());
|
|
if (star == NULL)
|
|
{
|
|
cout << "Cannot find star named '" << starName->getString() << "'\n";
|
|
delete starName;
|
|
delete solarSystemDataValue;
|
|
return NULL;
|
|
}
|
|
|
|
SolarSystem* solarSys = new SolarSystem(star);
|
|
|
|
Value* planetsDataValue = solarSystemData->getValue("Planets");
|
|
if (planetsDataValue != NULL)
|
|
{
|
|
if (planetsDataValue->getType() != Value::ArrayType)
|
|
{
|
|
cout << "ReadSolarSystem: Planets must be an array.\n";
|
|
}
|
|
else
|
|
{
|
|
Array* planetsData = planetsDataValue->getArray();
|
|
// ASSERT(planetsData != NULL);
|
|
for (unsigned int i = 0; i < planetsData->size(); i++)
|
|
{
|
|
Value* planetValue = (*planetsData)[i];
|
|
if (planetValue != NULL &&
|
|
planetValue->getType() == Value::HashType)
|
|
{
|
|
Body* body = CreatePlanet(solarSys->getPlanets(),
|
|
planetValue->getHash());
|
|
if (body != NULL)
|
|
solarSys->getPlanets()->addBody(body);
|
|
}
|
|
else
|
|
{
|
|
cout << "ReadSolarSystem: Planet data must be an assoc array.\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
delete starName;
|
|
delete solarSystemDataValue;
|
|
|
|
return solarSys;
|
|
}
|
|
|
|
|
|
SolarSystemCatalog* ReadSolarSystemCatalog(istream& in,
|
|
StarDatabase& starDB)
|
|
{
|
|
Tokenizer tokenizer(&in);
|
|
Parser parser(&tokenizer);
|
|
|
|
SolarSystemCatalog* catalog = new SolarSystemCatalog();
|
|
|
|
while (tokenizer.nextToken() != Tokenizer::TokenEnd)
|
|
{
|
|
tokenizer.pushBack();
|
|
SolarSystem* solarSystem = ReadSolarSystem(parser, starDB);
|
|
if (solarSystem != NULL)
|
|
{
|
|
catalog->insert(SolarSystemCatalog::value_type(solarSystem->getStar()->getCatalogNumber(),
|
|
solarSystem));
|
|
}
|
|
}
|
|
|
|
return catalog;
|
|
}
|
|
|
|
|
|
bool ReadSolarSystems(istream& in,
|
|
const StarDatabase& starDB,
|
|
SolarSystemCatalog& catalog)
|
|
{
|
|
Tokenizer tokenizer(&in);
|
|
Parser parser(&tokenizer);
|
|
|
|
while (tokenizer.nextToken() != Tokenizer::TokenEnd)
|
|
{
|
|
tokenizer.pushBack();
|
|
SolarSystem* solarSystem = ReadSolarSystem(parser, starDB);
|
|
if (solarSystem != NULL)
|
|
{
|
|
catalog.insert(SolarSystemCatalog::value_type(solarSystem->getStar()->getCatalogNumber(),
|
|
solarSystem));
|
|
}
|
|
}
|
|
|
|
// TODO: Return some notification if there's an error parsring the file
|
|
return true;
|
|
}
|
|
|
|
|
|
SolarSystem::SolarSystem(const Star* _star) : star(_star)
|
|
{
|
|
planets = new PlanetarySystem(star);
|
|
}
|
|
|
|
|
|
const Star* SolarSystem::getStar() const
|
|
{
|
|
return star;
|
|
}
|
|
|
|
Point3f SolarSystem::getCenter() const
|
|
{
|
|
// TODO: This is a very simple method at the moment, but it will get
|
|
// more complex when planets around multistar systems are supported
|
|
// where the planets may orbit the center of mass of two stars.
|
|
return star->getPosition();
|
|
}
|
|
|
|
PlanetarySystem* SolarSystem::getPlanets() const
|
|
{
|
|
return planets;
|
|
}
|
|
|
|
|
|
#if 0
|
|
int SolarSystem::getSystemSize() const
|
|
{
|
|
return bodies.size();
|
|
}
|
|
|
|
Body* SolarSystem::getBody(int n) const
|
|
{
|
|
return bodies[n];
|
|
}
|
|
|
|
void SolarSystem::addBody(Body* sat)
|
|
{
|
|
bodies.insert(bodies.end(), sat);
|
|
}
|
|
|
|
|
|
Body* SolarSystem::find(string _name, bool deepSearch) const
|
|
{
|
|
for (int i = 0; i < bodies.size(); i++)
|
|
{
|
|
if (bodies[i]->getName() == _name)
|
|
{
|
|
return bodies[i];
|
|
}
|
|
else if (deepSearch && bodies[i]->getSatellites() != NULL)
|
|
{
|
|
Body* body = bodies[i]->getSatellites()->find(_name, deepSearch);
|
|
if (body != NULL)
|
|
return body;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
|
|
|