前言
osgearth_ephemeris示例,展示了深空场景下,控制仿真时间运动和停止功能。获取太阳和月亮的世界坐标,再转化为经纬度坐标,并将其投影到地球上,用PlaceNode实时代表太阳和月亮的位置。
后续可以优化,加上仿真时间反向、仿真时间速率等功能。目前木有找到支持时间反转、时间速率调整的接口方法。左下角一直保持的sky自带的控制面板。
执行命令
osgearth_ephemerisd.exe earth_image\world.earth --sky
效果
代码分析
记录一个盲点,之前一直没有在意过 app.sky = osgEarth::findTopMostNodeOfType<SkyNode>(node); 这个方法。表示:从[node]开始向下搜索场景图,并返回找到的第一个与模板参数类型匹配的节点。方法的定义如下:
/**
* Searchs the scene graph downward starting at [node] and returns the first node found
* that matches the template parameter type.
*/
template<typename T>
T* findTopMostNodeOfType(osg::Node* node, unsigned traversalMask =~0)
{
if (!node) return 0;
FindTopMostNodeOfTypeVisitor<T> fnotv;
fnotv.setTraversalMode(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN);
fnotv.setTraversalMask(traversalMask);
node->accept(fnotv);
return fnotv._foundNode;
}
类似的用法还有:
// osgearth_lights 示例中的运用
ShadowCaster* caster = osgEarth::findTopMostNodeOfType<ShadowCaster>(root);
// 其他
osgEarth::findTopMostNodeOfType<FeatureSourceIndexNode>(node);
findTopMostNodeOfType<osg::CoordinateSystemNode>(_terrainNode.get());
osgEarth::findTopMostNodeOfType<ControlCanvas>(view->getCamera());
完整代码:
#include <osgViewer/Viewer>
#include <osgEarth/Notify>
#include <osgEarth/NodeUtils>
#include <osgEarthUtil/EarthManipulator>
#include <osgEarthUtil/ExampleResources>
#include <osgEarthUtil/Ephemeris>
#include <osgEarthUtil/Sky>
#include <osgEarthUtil/LatLongFormatter>
#include <osgEarthUtil/Controls>
#include <osgEarthAnnotation/PlaceNode>
#define LC "[osgearth_ephemeris] "
using namespace osgEarth;
using namespace osgEarth::Util;
using namespace osgEarth::Annotation;
// 创建ui命名空间
namespace ui = osgEarth::Util::Controls;
int
usage(const char* name)
{
OE_NOTICE
<< "\nUsage: " << name << " <file.earth> --sky" << std::endl
<< MapNodeHelper().usage() << std::endl;
return 0;
}
struct App
{
App() {
_playing = false;// 初始状态,时间停止
readout = new ui::LabelControl();// 显示
readout->setVertAlign(ui::Control::ALIGN_CENTER);
}
// 在地球上显示 月球和太阳,投影到地球上的经纬度。
osg::ref_ptr<PlaceNode> sunPos;
osg::ref_ptr<PlaceNode> moonPos;
SkyNode* sky;
ui::LabelControl* readout;
void play() { _playing = true; }
void stop() { _playing = false; }
void tick() {
if (_playing) {
TimeStamp t = sky->getDateTime().asTimeStamp() + 1;// 获取UTC时间
sky->setDateTime(DateTime(t));
}
readout->setText(sky->getDateTime().asRFC1123());// 显示时间
}
bool _playing;
};
struct Play : public ui::ControlEventHandler {
Play(App& app) : _app(app) { }
void onClick(ui::Control*) { _app.play(); }
App& _app;
};
struct Stop : public ui::ControlEventHandler {
Stop(App& app) : _app(app) { }
void onClick(ui::Control*) { _app.stop(); }
App& _app;
};
// 创建左上角ui控制面板
ui::Container* createUI(App& app)
{
// 水平布局,开始、停止,两个按钮,还有时间显示标签
ui::HBox* vcr = new ui::HBox();
vcr->addControl(new ui::ButtonControl("Play", new Play(app)));
vcr->addControl(new ui::ButtonControl("Stop", new Stop(app)));
vcr->addControl(app.readout);
return vcr;
}
int
main(int argc, char** argv)
{
osg::ArgumentParser arguments(&argc,argv);
// help?
if ( arguments.read("--help") )
return usage(argv[0]);
// 执行命令时,最后要加上 --sky
if ( arguments.find("--sky") < 0 )
return usage(argv[0]);
osgViewer::Viewer viewer(arguments);
EarthManipulator* em = new EarthManipulator(arguments);
em->getSettings()->setMinMaxPitch(-89, 89);// 设置最大俯仰角
viewer.setCameraManipulator( em );
viewer.getCamera()->setSmallFeatureCullingPixelSize(-1.0f);
// 读取一张图片,定位在地球上
osg::ref_ptr<osg::Image> mark = osgDB::readRefImageFile("D:/FreeXGIS/osgearth_gch/data/placemark32.png");
// 声明app对象
App app;
// load an earth file, and support all or our example command-line options
// and earth file <external> tags
// 加载earth文件
osg::Node* node = MapNodeHelper().load( arguments, &viewer );
if ( node )
{
// 设置根节点
osg::Group* root = new osg::Group();
root->addChild( node );
// 获取mapNode
MapNode* mapNode = MapNode::get(node);
// 创建太阳和月亮标签
app.sunPos = new PlaceNode("Sun", Style(), mark.get());
app.sunPos->setDynamic(true);// 动态改变
mapNode->addChild( app.sunPos.get() );
app.moonPos = new PlaceNode("Moon", Style(), mark.get());
app.moonPos->setDynamic(true);
mapNode->addChild( app.moonPos.get() );
// osgEarth::findTopMostNodeOfType<SkyNode>:从[node]开始向下搜索场景图,并返回找到的第一个与模板参数类型匹配的节点。
// 获取天空对象
app.sky = osgEarth::findTopMostNodeOfType<SkyNode>(node);
const Ephemeris* ephemeris = 0L;
if ( app.sky )
{
ephemeris = app.sky->getEphemeris();// 将获取到的星历表保存下来
}
LatLongFormatter llf;// 经纬度坐标格式器
llf.setOptions( LatLongFormatter::Options(llf.FORMAT_DEGREES_MINUTES_SECONDS) );
llf.setPrecision( 8 );// 控制精度8位
viewer.setSceneData( root );
// 创建左上角的ui控制面板
ui::ControlCanvas* container = ui::ControlCanvas::getOrCreate(&viewer);
container->addChild(createUI(app));
while(!viewer.done())
{
viewer.frame();
if ( ephemeris )
{
// 获取当前sky的时间
const DateTime& dt = app.sky->getDateTime();
// 获取当前时间下太阳天体
CelestialBody sun = ephemeris->getSunPosition(dt);
GeoPoint sunPos;
// 将太阳坐标转化为世界坐标
sunPos.fromWorld(mapNode->getMapSRS(), sun.geocentric);// sun.geocentric:地心直角坐标系的坐标XYZ
// std::cout << "sun alt = " << sunPos.alt() << std::endl; // sun alt = 1.496e+11
sunPos.alt() = 0.0;// 设置高度为0
app.sunPos->setPosition( sunPos );
app.sunPos->setText( "Sun\n" + llf.format(sunPos) );
CelestialBody moon = ephemeris->getMoonPosition(dt);
GeoPoint moonPos;
moonPos.fromWorld(mapNode->getMapSRS(), moon.geocentric);
// std::cout << "moon alt = " << moonPos.alt() << std::endl; // moon alt = 3.98562e+08
moonPos.alt() = 0.0;
app.moonPos->setPosition( moonPos );
app.moonPos->setText( "Moon\n" + llf.format(moonPos) );
}
app.tick();// 保证时间一直运行
}
}
else
{
return usage(argv[0]);
}
}