// Add children to item, but group objects of certain classes // into subtrees to avoid clutter. Stars, planets, and moons // are shown as direct children of the parent. Small moons, // asteroids, and spacecraft a grouped together, as there tend to be // large collections of such objects. void SolarSystemTreeModel::addTreeItemChildrenGrouped(TreeItem* item, PlanetarySystem* sys, const vector<Star*>* orbitingStars, Selection parent) { vector<Body*> asteroids; vector<Body*> spacecraft; vector<Body*> minorMoons; vector<Body*> surfaceFeatures; vector<Body*> components; vector<Body*> other; vector<Body*> normal; bool groupAsteroids = true; bool groupSpacecraft = true; bool groupComponents = true; bool groupSurfaceFeatures = true; if (parent.body()) { // Don't put asteroid moons in the asteroid group; make them // immediate children of the parent. if (parent.body()->getClassification() == Body::Asteroid) groupAsteroids = false; if (parent.body()->getClassification() == Body::Spacecraft) groupSpacecraft = false; } for (int i = 0; i < sys->getSystemSize(); i++) { Body* body = sys->getBody(i); switch (body->getClassification()) { case Body::Planet: case Body::DwarfPlanet: case Body::Invisible: normal.push_back(body); break; case Body::Moon: normal.push_back(body); break; case Body::MinorMoon: minorMoons.push_back(body); break; case Body::Asteroid: case Body::Comet: if (groupAsteroids) asteroids.push_back(body); else normal.push_back(body); break; case Body::Spacecraft: if (groupSpacecraft) spacecraft.push_back(body); else normal.push_back(body); break; case Body::Component: if (groupComponents) components.push_back(body); else normal.push_back(body); break; case Body::SurfaceFeature: if (groupSurfaceFeatures) surfaceFeatures.push_back(body); else normal.push_back(body); break; default: other.push_back(body); break; } } // Calculate the total number of children item->nChildren = 0; if (orbitingStars != NULL) item->nChildren += orbitingStars->size(); item->nChildren += normal.size(); if (!asteroids.empty()) item->nChildren++; if (!spacecraft.empty()) item->nChildren++; if (!minorMoons.empty()) item->nChildren++; if (!surfaceFeatures.empty()) item->nChildren++; if (!components.empty()) item->nChildren++; if (!other.empty()) item->nChildren++; if (item->nChildren != 0) { int childIndex = 0; item->children = new TreeItem*[item->nChildren]; { // Add the stars if (orbitingStars != NULL) { for (unsigned int i = 0; i < orbitingStars->size(); i++) { Selection child(orbitingStars->at(i)); item->children[childIndex] = createTreeItem(child, item, childIndex); childIndex++; } } // Add the direct children for (int i = 0; i < (int) normal.size(); i++) { item->children[childIndex] = createTreeItem(normal[i], item, childIndex); childIndex++; } // Add the groups if (!minorMoons.empty()) { item->children[childIndex] = createGroupTreeItem(Body::MinorMoon, minorMoons, item, childIndex); childIndex++; } if (!asteroids.empty()) { item->children[childIndex] = createGroupTreeItem(Body::Asteroid, asteroids, item, childIndex); childIndex++; } if (!spacecraft.empty()) { item->children[childIndex] = createGroupTreeItem(Body::Spacecraft, spacecraft, item, childIndex); childIndex++; } if (!surfaceFeatures.empty()) { item->children[childIndex] = createGroupTreeItem(Body::SurfaceFeature, surfaceFeatures, item, childIndex); childIndex++; } if (!components.empty()) { item->children[childIndex] = createGroupTreeItem(Body::Component, components, item, childIndex); childIndex++; } if (!other.empty()) { item->children[childIndex] = createGroupTreeItem(Body::Unknown, other, item, childIndex); childIndex++; } } } }
// Create a body (planet, moon, spacecraft, etc.) using the values from a // property list. The usePlanetsUnits flags specifies whether period and // semi-major axis are in years and AU rather than days and kilometers. static Body* CreateBody(const string& name, PlanetarySystem* system, Universe& universe, Body* existingBody, Hash* planetData, const string& path, Disposition disposition, BodyType bodyType) { Body* body = NULL; if (disposition == ModifyObject || disposition == ReplaceObject) { body = existingBody; } if (body == NULL) { body = new Body(system, name); // If the body doesn't exist, always treat the disposition as 'Add' disposition = AddObject; // Set the default classification for new objects based on the body type. // This may be overridden by the Class property. if (bodyType == SurfaceObject) { body->setClassification(Body::SurfaceFeature); } } if (!CreateTimeline(body, system, universe, planetData, path, disposition, bodyType)) { // No valid timeline given; give up. if (body != existingBody) delete body; return NULL; } // Three values control the shape and size of an ellipsoidal object: // semiAxes, radius, and oblateness. It is an error if neither the // radius nor semiaxes are set. If both are set, the radius is // multipled by each of the specified semiaxis to give the shape of // the body ellipsoid. Oblateness is ignored if semiaxes are provided; // otherwise, the ellipsoid has semiaxes: ( radius, radius, 1-radius ). // These rather complex rules exist to maintain backward compatibility. // // If the body also has a mesh, it is always scaled in x, y, and z by // the maximum semiaxis, never anisotropically. double radius = (double) body->getRadius(); bool radiusSpecified = false; if (planetData->getLength("Radius", radius)) { body->setSemiAxes(Vector3f::Constant((float) radius)); radiusSpecified = true; } Vector3d semiAxes = Vector3d::Ones(); if (planetData->getVector("SemiAxes", semiAxes)) { if (radiusSpecified) { // if the radius has been specified, treat SemiAxes as dimensionless // (i.e. ignore units) and multiply the radius by the SemiAxes semiAxes *= radius; } else { double semiAxesScale = 1.0; planetData->getLengthScale("SemiAxes", semiAxesScale); semiAxes *= semiAxesScale; } // Swap y and z to match internal coordinate system body->setSemiAxes(Vector3f((float) semiAxes.x(), (float) semiAxes.z(), (float) semiAxes.y())); } else { double oblateness = 0.0; if (planetData->getNumber("Oblateness", oblateness)) { body->setSemiAxes((float) body->getRadius() * Vector3f(1.0f, 1.0f - (float) oblateness, 1.0f)); } } int classification = body->getClassification(); string classificationName; if (planetData->getString("Class", classificationName)) classification = GetClassificationId(classificationName); if (classification == Body::Unknown) { // Try to guess the type if (system->getPrimaryBody() != NULL) { if(radius > 0.1) classification = Body::Moon; else classification = Body::Spacecraft; } else { if (radius < 1000.0) classification = Body::Asteroid; else classification = Body::Planet; } } body->setClassification(classification); if (classification == Body::Invisible) body->setVisible(false); // Set default properties for the object based on its classification if (classification & CLASSES_INVISIBLE_AS_POINT) body->setVisibleAsPoint(false); if ((classification & CLASSES_SECONDARY_ILLUMINATOR) == 0) body->setSecondaryIlluminator(false); if (classification & CLASSES_UNCLICKABLE) body->setClickable(false); string infoURL; if (planetData->getString("InfoURL", infoURL)) { if (infoURL.find(':') == string::npos) { // Relative URL, the base directory is the current one, // not the main installation directory if (path[1] == ':') // Absolute Windows path, file:/// is required infoURL = "file:///" + path + "/" + infoURL; else if (!path.empty()) infoURL = path + "/" + infoURL; } body->setInfoURL(infoURL); } double albedo = 0.5; if (planetData->getNumber("Albedo", albedo)) body->setAlbedo((float) albedo); // TODO - add mass units double mass = 0.0; if (planetData->getNumber("Mass", mass)) body->setMass((float) mass); Quaternionf orientation = Quaternionf::Identity(); if (planetData->getRotation("Orientation", orientation)) { body->setGeometryOrientation(orientation); } Surface surface; if (disposition == ModifyObject) { surface = body->getSurface(); } else { surface.color = Color(1.0f, 1.0f, 1.0f); surface.hazeColor = Color(0.0f, 0.0f, 0.0f, 0.0f); } FillinSurface(planetData, &surface, path); body->setSurface(surface); { string geometry(""); if (planetData->getString("Mesh", geometry)) { Vector3f geometryCenter(Vector3f::Zero()); if (planetData->getVector("MeshCenter", geometryCenter)) { // TODO: Adjust bounding radius if model center isn't // (0.0f, 0.0f, 0.0f) } bool isNormalized = true; planetData->getBoolean("NormalizeMesh", isNormalized); float geometryScale = 1.0f; planetData->getLength("MeshScale", geometryScale); ResourceHandle geometryHandle = GetGeometryManager()->getHandle(GeometryInfo(geometry, path, geometryCenter, 1.0f, isNormalized)); body->setGeometry(geometryHandle); body->setGeometryScale(geometryScale); } } // 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 = NULL; if (disposition == ModifyObject) { atmosphere = body->getAtmosphere(); if (atmosphere == NULL) { Atmosphere atm; body->setAtmosphere(atm); atmosphere = body->getAtmosphere(); } } else { atmosphere = new Atmosphere(); } atmosData->getLength("Height", atmosphere->height); atmosData->getColor("Lower", atmosphere->lowerColor); atmosData->getColor("Upper", atmosphere->upperColor); atmosData->getColor("Sky", atmosphere->skyColor); atmosData->getColor("Sunset", atmosphere->sunsetColor); atmosData->getNumber("Mie", atmosphere->mieCoeff); atmosData->getLength("MieScaleHeight", atmosphere->mieScaleHeight); atmosData->getNumber("MieAsymmetry", atmosphere->miePhaseAsymmetry); atmosData->getVector("Rayleigh", atmosphere->rayleighCoeff); //atmosData->getNumber("RayleighScaleHeight", atmosphere->rayleighScaleHeight); atmosData->getVector("Absorption", atmosphere->absorptionCoeff); // Get the cloud map settings atmosData->getLength("CloudHeight", atmosphere->cloudHeight); if (atmosData->getNumber("CloudSpeed", atmosphere->cloudSpeed)) atmosphere->cloudSpeed = degToRad(atmosphere->cloudSpeed); string cloudTexture; if (atmosData->getString("CloudMap", cloudTexture)) { atmosphere->cloudTexture.setTexture(cloudTexture, path, TextureInfo::WrapTexture); } string cloudNormalMap; if (atmosData->getString("CloudNormalMap", cloudNormalMap)) { atmosphere->cloudNormalMap.setTexture(cloudNormalMap, path, TextureInfo::WrapTexture); } double cloudShadowDepth = 0.0; if (atmosData->getNumber("CloudShadowDepth", cloudShadowDepth)) { cloudShadowDepth = max(0.0, min(1.0, cloudShadowDepth)); // clamp to [0, 1] atmosphere->cloudShadowDepth = (float) cloudShadowDepth; } body->setAtmosphere(*atmosphere); if (disposition != ModifyObject) 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); RingSystem rings(0.0f, 0.0f); if (body->getRings() != NULL) rings = *body->getRings(); double inner = 0.0, outer = 0.0; if (ringsData->getLength("Inner", inner)) rings.innerRadius = (float) inner; if (ringsData->getLength("Outer", outer)) rings.outerRadius = (float) outer; Color color(1.0f, 1.0f, 1.0f); if (ringsData->getColor("Color", color)) rings.color = color; string textureName; if (ringsData->getString("Texture", textureName)) rings.texture = MultiResTexture(textureName, path); body->setRings(rings); } } } bool clickable = true; if (planetData->getBoolean("Clickable", clickable)) { body->setClickable(clickable); } bool visible = true; if (planetData->getBoolean("Visible", visible)) { body->setVisible(visible); } Color orbitColor; if (planetData->getColor("OrbitColor", orbitColor)) { body->setOrbitColorOverridden(true); body->setOrbitColor(orbitColor); } return body; }