static bool CreateTimeline(Body* body, PlanetarySystem* system, Universe& universe, Hash* planetData, const string& path, Disposition disposition, BodyType bodyType) { FrameTree* parentFrameTree = NULL; Selection parentObject = GetParentObject(system); bool orbitsPlanet = false; if (parentObject.body()) { parentFrameTree = parentObject.body()->getOrCreateFrameTree(); orbitsPlanet = true; } else if (parentObject.star()) { SolarSystem* solarSystem = universe.getSolarSystem(parentObject.star()); if (solarSystem == NULL) solarSystem = universe.createSolarSystem(parentObject.star()); parentFrameTree = solarSystem->getFrameTree(); } else { // Bad orbit barycenter specified return false; } ReferenceFrame* defaultOrbitFrame = NULL; ReferenceFrame* defaultBodyFrame = NULL; if (bodyType == SurfaceObject) { defaultOrbitFrame = new BodyFixedFrame(parentObject, parentObject); defaultBodyFrame = CreateTopocentricFrame(parentObject, parentObject, Selection(body)); defaultOrbitFrame->addRef(); defaultBodyFrame->addRef(); } else { defaultOrbitFrame = parentFrameTree->getDefaultReferenceFrame(); defaultBodyFrame = parentFrameTree->getDefaultReferenceFrame(); } // If there's an explicit timeline definition, parse that. Otherwise, we'll do // things the old way. Value* value = planetData->getValue("Timeline"); if (value != NULL) { if (value->getType() != Value::ArrayType) { clog << "Error: Timeline must be an array\n"; return false; } Timeline* timeline = CreateTimelineFromArray(body, universe, value->getArray(), path, defaultOrbitFrame, defaultBodyFrame); if (timeline == NULL) { return false; } else { body->setTimeline(timeline); return true; } } // Information required for the object timeline. ReferenceFrame* orbitFrame = NULL; ReferenceFrame* bodyFrame = NULL; Orbit* orbit = NULL; RotationModel* rotationModel = NULL; double beginning = -numeric_limits<double>::infinity(); double ending = numeric_limits<double>::infinity(); // If any new timeline values are specified, we need to overrideOldTimeline will // be set to true. bool overrideOldTimeline = false; // The interaction of Modify with timelines is slightly complicated. If the timeline // is specified by putting the OrbitFrame, Orbit, BodyFrame, or RotationModel directly // in the object definition (i.e. not inside a Timeline structure), it will completely // replace the previous timeline if it contained more than one phase. Otherwise, the // properties of the single phase will be modified individually, for compatibility with // Celestia versions 1.5.0 and earlier. if (disposition == ModifyObject) { const Timeline* timeline = body->getTimeline(); if (timeline->phaseCount() == 1) { const TimelinePhase* phase = timeline->getPhase(0); orbitFrame = phase->orbitFrame(); bodyFrame = phase->bodyFrame(); orbit = phase->orbit(); rotationModel = phase->rotationModel(); beginning = phase->startTime(); ending = phase->endTime(); } } // Get the object's orbit reference frame. bool newOrbitFrame = false; Value* frameValue = planetData->getValue("OrbitFrame"); if (frameValue != NULL) { ReferenceFrame* frame = CreateReferenceFrame(universe, frameValue, parentObject, body); if (frame != NULL) { orbitFrame = frame; newOrbitFrame = true; overrideOldTimeline = true; } } // Get the object's body frame. bool newBodyFrame = false; Value* bodyFrameValue = planetData->getValue("BodyFrame"); if (bodyFrameValue != NULL) { ReferenceFrame* frame = CreateReferenceFrame(universe, bodyFrameValue, parentObject, body); if (frame != NULL) { bodyFrame = frame; newBodyFrame = true; overrideOldTimeline = true; } } // If no orbit or body frame was specified, use the default ones if (orbitFrame == NULL) orbitFrame = defaultOrbitFrame; if (bodyFrame == NULL) bodyFrame = defaultBodyFrame; // If the center of the is a star, orbital element units are // in AU; otherwise, use kilometers. if (orbitFrame->getCenter().star() != NULL) orbitsPlanet = false; else orbitsPlanet = true; Orbit* newOrbit = CreateOrbit(orbitFrame->getCenter(), planetData, path, !orbitsPlanet); if (newOrbit == NULL && orbit == NULL) { if (body->getTimeline() && disposition == ModifyObject) { // The object definition is modifying an existing object with a multiple phase // timeline, but no orbit definition was given. This can happen for completely // sensible reasons, such a Modify definition that just changes visual properties. // Or, the definition may try to change other timeline phase properties such as // the orbit frame, but without providing an orbit. In both cases, we'll just // leave the original timeline alone. return true; } else { clog << "No valid orbit specified for object '" << body->getName() << "'. Skipping.\n"; return false; } } // If a new orbit was given, override any old orbit if (newOrbit != NULL) { orbit = newOrbit; overrideOldTimeline = true; } // Get the rotation model for this body double syncRotationPeriod = orbit->getPeriod(); RotationModel* newRotationModel = CreateRotationModel(planetData, path, syncRotationPeriod); // If a new rotation model was given, override the old one if (newRotationModel != NULL) { rotationModel = newRotationModel; overrideOldTimeline = true; } // If there was no rotation model specified, nor a previous rotation model to // override, create the default one. if (rotationModel == NULL) { // If no rotation model is provided, use a default rotation model-- // a uniform rotation that's synchronous with the orbit (appropriate // for nearly all natural satellites in the solar system.) rotationModel = CreateDefaultRotationModel(syncRotationPeriod); } if (ParseDate(planetData, "Beginning", beginning)) overrideOldTimeline = true; if (ParseDate(planetData, "Ending", ending)) overrideOldTimeline = true; // Something went wrong if the disposition isn't modify and no timeline // is to be created. assert(disposition == ModifyObject || overrideOldTimeline); if (overrideOldTimeline) { if (beginning >= ending) { clog << "Beginning time must be before Ending time.\n"; return false; } // We finally have an orbit, rotation model, frames, and time range. Create // the object timeline. TimelinePhase* phase = TimelinePhase::CreateTimelinePhase(universe, body, beginning, ending, *orbitFrame, *orbit, *bodyFrame, *rotationModel); // We've already checked that beginning < ending; nothing else should go // wrong during the creation of a TimelinePhase. assert(phase != NULL); if (phase == NULL) { clog << "Internal error creating TimelinePhase.\n"; return false; } Timeline* timeline = new Timeline(); timeline->appendPhase(phase); body->setTimeline(timeline); // Check for circular references in frames; this can only be done once the timeline // has actually been set. // TIMELINE-TODO: This check is not comprehensive; it won't find recursion in // multiphase timelines. if (newOrbitFrame && isFrameCircular(*body->getOrbitFrame(0.0), ReferenceFrame::PositionFrame)) { clog << "Orbit frame for " << body->getName() << " is nested too deep (probably circular)\n"; return false; } if (newBodyFrame && isFrameCircular(*body->getBodyFrame(0.0), ReferenceFrame::OrientationFrame)) { clog << "Body frame for " << body->getName() << " is nested too deep (probably circular)\n"; return false; } } return true; }
TimelinePhase* CreateTimelinePhase(Body* body, Universe& universe, Hash* phaseData, const string& path, ReferenceFrame* defaultOrbitFrame, ReferenceFrame* defaultBodyFrame, bool isFirstPhase, bool isLastPhase, double previousPhaseEnd) { double beginning = previousPhaseEnd; double ending = numeric_limits<double>::infinity(); // Beginning is optional for the first phase of a timeline, and not // allowed for the other phases, where beginning is always the ending // of the previous phase. bool hasBeginning = ParseDate(phaseData, "Beginning", beginning); if (!isFirstPhase && hasBeginning) { clog << "Error: Beginning can only be specified for initial phase of timeline.\n"; return NULL; } // Ending is required for all phases except for the final one. bool hasEnding = ParseDate(phaseData, "Ending", ending); if (!isLastPhase && !hasEnding) { clog << "Error: Ending is required for all timeline phases other than the final one.\n"; return NULL; } // Get the orbit reference frame. ReferenceFrame* orbitFrame; Value* frameValue = phaseData->getValue("OrbitFrame"); if (frameValue != NULL) { orbitFrame = CreateReferenceFrame(universe, frameValue, defaultOrbitFrame->getCenter(), body); if (orbitFrame == NULL) { return NULL; } } else { // No orbit frame specified; use the default frame. orbitFrame = defaultOrbitFrame; } orbitFrame->addRef(); // Get the body reference frame ReferenceFrame* bodyFrame; Value* bodyFrameValue = phaseData->getValue("BodyFrame"); if (bodyFrameValue != NULL) { bodyFrame = CreateReferenceFrame(universe, bodyFrameValue, defaultBodyFrame->getCenter(), body); if (bodyFrame == NULL) { orbitFrame->release(); return NULL; } } else { // No body frame specified; use the default frame. bodyFrame = defaultBodyFrame; } bodyFrame->addRef(); // Use planet units (AU for semimajor axis) if the center of the orbit // reference frame is a star. bool usePlanetUnits = orbitFrame->getCenter().star() != NULL; // Get the orbit Orbit* orbit = CreateOrbit(orbitFrame->getCenter(), phaseData, path, usePlanetUnits); if (!orbit) { clog << "Error: missing orbit in timeline phase.\n"; bodyFrame->release(); orbitFrame->release(); return NULL; } // Get the rotation model // TIMELINE-TODO: default rotation model is UniformRotation with a period // equal to the orbital period. Should we do something else? RotationModel* rotationModel = CreateRotationModel(phaseData, path, orbit->getPeriod()); if (!rotationModel) { // TODO: Should distinguish between a missing rotation model (where it's // appropriate to use a default one) and a bad rotation model (where // we should report an error.) rotationModel = new ConstantOrientation(Quaterniond::Identity()); } TimelinePhase* phase = TimelinePhase::CreateTimelinePhase(universe, body, beginning, ending, *orbitFrame, *orbit, *bodyFrame, *rotationModel); // Frame ownership transfered to phase; release local references orbitFrame->release(); bodyFrame->release(); return phase; }