celestia/src/celengine/frametree.cpp

198 lines
5.7 KiB
C++

// frametree.cpp
//
// Reference frame tree
//
// Copyright (C) 2008, the Celestia Development Team
// Initial version by Chris Laurel, claurel@gmail.com
//
// 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 <algorithm>
#include <cassert>
#include "celengine/frametree.h"
#include "celengine/timeline.h"
#include "celengine/timelinephase.h"
#include "celengine/frame.h"
/* A FrameTree is hierarchy of solar system bodies organized according to
* the relationship of their reference frames. An object will appear in as
* a child in the tree of whatever object is the center of its orbit frame.
* Since an object may have several orbit frames in its timeline, the
* structure is a bit more complicated than a straightforward tree
* of Body objects. A Body has exactly a single parent in the frame tree
* at a given time, but may have many over it's lifespan. An object's
* timeline contains a list of timeline phases; each phase can point to
* a different parent. Thus, the timeline can be thought of as a list of
* parents.
*
* The FrameTree hiearchy is designed for fast visibility culling. There are
* two values stored in each node for this purpose: the bounding sphere
* radius, and the maximum child object radius. The bounding sphere is large
* enough to contain the orbits of all child objects, as well as the child
* objects themselves. Change tracking is performed whenever the frame tree
* is modified: adding a node, removing a node, or changing the radius of an
* object will all cause the tree to be marked as changed.
*/
/*! Create a frame tree associated with a star.
*/
FrameTree::FrameTree(Star* star) :
starParent(star),
bodyParent(nullptr),
m_changed(true),
defaultFrame(nullptr)
{
// Default frame for a star is J2000 ecliptical, centered
// on the star.
defaultFrame = new J2000EclipticFrame(Selection(star));
defaultFrame->addRef();
}
/*! Create a frame tree associated with a planet or other solar system body.
*/
FrameTree::FrameTree(Body* body) :
starParent(nullptr),
bodyParent(body),
m_changed(true),
defaultFrame(nullptr)
{
// Default frame for a solar system body is the mean equatorial frame of the body.
defaultFrame = new BodyMeanEquatorFrame(Selection(body), Selection(body));
defaultFrame->addRef();
}
FrameTree::~FrameTree()
{
defaultFrame->release();
}
/*! Return the default reference frame for the object a frame tree is associated
* with.
*/
ReferenceFrame*
FrameTree::getDefaultReferenceFrame() const
{
return defaultFrame;
}
/*! Mark this node of the frame hierarchy as changed. The changed flag
* is propagated up toward the root of the tree.
*/
void
FrameTree::markChanged()
{
if (!m_changed)
{
m_changed = true;
if (bodyParent != nullptr)
bodyParent->markChanged();
}
}
/*! Mark this node of the frame hierarchy as updated. The changed flag
* is marked false in this node and in all child nodes that
* were marked changed.
*/
void
FrameTree::markUpdated()
{
if (m_changed)
{
m_changed = false;
for (const auto child : children)
child->body()->markUpdated();
}
}
/*! Recompute the bounding sphere for this tree and all subtrees marked
* as having changed. The bounding sphere is large enough to accommodate
* the orbits (and radii) of all child bodies. This method also recomputes
* the maximum child radius, secondary illuminator status, and child
* class mask.
*/
void
FrameTree::recomputeBoundingSphere()
{
if (m_changed)
{
m_boundingSphereRadius = 0.0;
m_maxChildRadius = 0.0;
m_containsSecondaryIlluminators = false;
m_childClassMask = 0;
for (const auto phase : children)
{
double bodyRadius = phase->body()->getRadius();
double r = phase->body()->getCullingRadius() + phase->orbit()->getBoundingRadius();
m_maxChildRadius = max(m_maxChildRadius, bodyRadius);
m_containsSecondaryIlluminators = m_containsSecondaryIlluminators || phase->body()->isSecondaryIlluminator();
m_childClassMask |= phase->body()->getClassification();
FrameTree* tree = phase->body()->getFrameTree();
if (tree != nullptr)
{
tree->recomputeBoundingSphere();
r += tree->m_boundingSphereRadius;
m_maxChildRadius = max(m_maxChildRadius, tree->m_maxChildRadius);
m_containsSecondaryIlluminators = m_containsSecondaryIlluminators || tree->containsSecondaryIlluminators();
m_childClassMask |= tree->childClassMask();
}
m_boundingSphereRadius = max(m_boundingSphereRadius, r);
}
}
}
/*! Add a new phase to this tree.
*/
void
FrameTree::addChild(TimelinePhase* phase)
{
phase->addRef();
children.push_back(phase);
markChanged();
}
/*! Remove a phase from the tree. This method does nothing if the specified
* phase doesn't exist in the tree.
*/
void
FrameTree::removeChild(TimelinePhase* phase)
{
vector<TimelinePhase*>::iterator iter = find(children.begin(), children.end(), phase);
if (iter != children.end())
{
(*iter)->release();
children.erase(iter);
markChanged();
}
}
/*! Return the child at the specified index. */
TimelinePhase*
FrameTree::getChild(unsigned int n) const
{
return children[n];
}
/*! Get the number of immediate children of this tree. */
unsigned int
FrameTree::childCount() const
{
return children.size();
}