系列文章目录
前言
在使用osgEarth做开发的时候大家估计都遇到过:在使用 osgEarth::EarthManipulator 时,可能会遇到在极地(南极和北极)无法进行上下旋转的问题,用户体验特别不好。有人说是因为万向锁的问题,osgEarth默认情况下对旋转做了限制。我也无从考证是不是这个原因造成的。
万向锁(Gimbal lock): 一旦选择±90°作为pitch角,就会导致第一次旋转和第三次旋转等价,整个旋转表示系统被限制在只能绕竖直轴旋转,丢失了一个表示维度。
维基百科的解释:环架锁定(英语:Gimbal lock),也称为万向节锁定,是使用动态欧拉角表示三维物体透过平衡环架旋转时会出现的问题。
欧拉角有两种:
静态:即绕世界坐标系三个轴的旋转,由于物体旋转过程中坐标轴保持静止,所以称为静态。
动态:即绕物体坐标系三个轴的旋转,由于物体旋转过程中坐标轴随着物体做相同的转动,所以称为动态。
使用动态欧拉角会出现万向锁现象;静态欧拉角不存在万向锁的问题。
在动态欧拉角的一次旋转中,需要按照固定的顺序分别绕x、y、z三个轴旋转一次,假设顺序为x-y-z。
在一次旋转中,当按x轴旋转时,y、z轴不动;当按y轴旋转时,为保持x轴在物体坐标系的对应位置,x轴会随物体旋转,z轴不动;同理,当按z轴旋转时,x、y轴随物体旋转。
因此,当绕y轴旋转角度为90°时,此次旋转中x轴与z轴重合(见右图“万向锁”),导致此次旋转无法按原顺序旋转至某些方向,这就是万向锁问题。
事实上,一旦选择±90°作为第二次旋转的角度,就会导致第一次旋转和第三次旋转等价,整个旋转表示系统被限制在只能绕竖直轴旋转,丢失了一个表示维度。这种角度为±90°的第二次旋转使得第一次和第三次旋转的旋转轴相同的现象,称作万向锁。
就像下图一下,osgEarth视口到达地球的南北极,鼠标就不能上下旋转视口了,好像被硬生生卡住了,感觉非常难受。
一、问题原因
从osgEarth代码层面上讲,这是因为默认情况下,EarthManipulator 在极地附近会限制相机的旋转,以避免某些情况下的视角问题。就是下面viewer->setCameraManipulator(manipulator);这行代码造成的,但是去掉这行代码,默认的视口又看不到地球了。
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
viewer->setCameraManipulator(manipulator);
二、问题解决
加入下面2行代码
manipulator->getSettings()->setThrowingEnabled(false);
manipulator->getSettings()->setLockAzimuthWhilePanning(false);
三、完整代码
#include <Windows.h>
#include <iostream>
#include <string>
#include <osgViewer/Viewer>
#include <osgDB/ReadFile>
#include <osgEarth/MapNode>
#include <osgEarthDrivers/cache_filesystem/FileSystemCache>
#include <osgEarth/ImageLayer>
#include <osgEarth/EarthManipulator>
#include <osgEarth/OGRFeatureSource>
#include <osgEarth/FeatureModelLayer>
#include <osgEarth/FeatureImageLayer>
#include <osgEarth/ECEF>
#include <osgEarth/GeoData>
#include <osgEarth/Viewpoint>
#include <osgEarth/TerrainOptions>
#include <osgGA/SphericalManipulator>
using namespace std;
void LoadShape()
{
// Load the base map (globe)
osg::ref_ptr<osg::Node> globe = osgDB::readNodeFile("../vs2022_64bit_3rdParty_osg365_oe32/runtime/test/earthFile/china-simple.earth");
osg::ref_ptr<osgEarth::MapNode> mapNode = osgEarth::MapNode::get(globe);
osg::ref_ptr<osgEarth::Map> map = mapNode->getMap();
// Load the Shapefile
osg::ref_ptr<osgEarth::OGRFeatureSource> features = new osgEarth::OGRFeatureSource;
features->setURL("F:/osg/yangShiXing/019.Earth/builder/data/shpFile/world.shp");
// Define the style for the features
osgEarth::Style style;
// Set visibility options
osg::ref_ptr<osgEarth::RenderSymbol> render = style.getOrCreate<osgEarth::RenderSymbol>();
render->depthTest() = false;
// Set altitude options
osg::ref_ptr<osgEarth::AltitudeSymbol> alt = style.getOrCreate<osgEarth::AltitudeSymbol>();
alt->clamping() = alt->CLAMP_TO_TERRAIN;
alt->technique() = alt->TECHNIQUE_DRAPE;
// Set line color and width
osg::Vec4 color(1.0f, 0.0f, 0.0f, 1.0f);
osg::ref_ptr<osgEarth::LineSymbol> ls = style.getOrCreate<osgEarth::LineSymbol>();
ls->stroke()->color() = color;
ls->stroke()->width() = 2.0f;
ls->tessellationSize()->set(10000.0, osgEarth::Units::KILOMETERS);
// Add the feature source and style to a layer
osg::ref_ptr<osgEarth::FeatureImageLayer> layer = new osgEarth::FeatureImageLayer;
layer->setFeatureSource(features);
osg::ref_ptr<osgEarth::StyleSheet> sheet = new osgEarth::StyleSheet;
sheet->addStyle(style);
layer->setStyleSheet(sheet);
map->addLayer(layer);
// Print the status of added layers
osgEarth::LayerVector layers;
map->getLayers(layers);
for (osgEarth::LayerVector::const_iterator it = layers.begin(); it != layers.end(); it++)
{
std::cout << "Layer Status: " << (*it)->getStatus().toString() << std::endl;
}
// Set up the viewer
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
viewer->setSceneData(mapNode);
// Use EarthManipulator to control the camera
osg::ref_ptr<osgEarth::EarthManipulator> manipulator = new osgEarth::EarthManipulator;
// Adjust pitch constraints to allow free rotation at the poles
manipulator->getSettings()->setMinMaxPitch(-90.0, 90.0);
// Allow free rotation around the poles
manipulator->getSettings()->setThrowingEnabled(false);
manipulator->getSettings()->setLockAzimuthWhilePanning(false);
//manipulator->getSettings()->setLockAzimuthWhileRotating(false);
viewer->setCameraManipulator(new osgGA::SphericalManipulator);
viewer->setCameraManipulator(manipulator);
// Set the initial viewpoint to look at Chengdu
// Longitude: 104.0668, Latitude: 30.5728
osgEarth::Viewpoint vp("Chengdu", 104.0668, 30.5728, 1000000.0, 0.0, -45.0, 25000000.0);
manipulator->setHomeViewpoint(vp);
// Run the viewer
viewer->setUpViewInWindow(100, 100, 800, 600);
viewer->run();
}
int main()
{
LoadShape();
//osg::ref_ptr<osgEarth::OGRFeatureSource> features = new osgEarth::OGRFeatureSource;
//features->setURL("F:/osg/yangShiXing/019.Earth/builder/data/shpFile/world.shp");
//features->open();
//osgEarth::Query query;
//query.expression() = "POP_CNTRY = 67074";
创建进度回调(可以传递 nullptr 如果不需要进度回调)
//osg::ref_ptr<osgEarth::ProgressCallback> progressCallback = nullptr;
//osg::ref_ptr<osgEarth::FeatureCursor> cursor = features->createFeatureCursor(query, progressCallback);
//const osgEarth::SpatialReference* intputSrs = features->getFeatureProfile()->getSRS();
//const osgEarth::SpatialReference* outputSrs = osgEarth::SpatialReference::get("wgs84");
//osg::ref_ptr<osg::Geode> gnode = new osg::Geode;
//osg::Vec4 color(1.0f, 0.0f, 0.0f, 1.0f);
//while (cursor->hasMore()){
// //osgEarth::Feature* feature = cursor->nextFeature();
// //std::string text = feature->getString("Cntry_name");
// //if (text.empty()){
// // continue;
// //}
// //if (feature) {
// // std::string name = feature->getString("NAME");
// // std::string pop = feature->getString("POP_CNTRY");
// // std::string area = feature->getString("AREA_CNTRY");
// // //std::string geom = feature->getGeometry()->asText();
// // std::cout << "Cntry_name:" << text << name << ", Population: " << pop << ", Area: " << area << ", Geometry: " <</* geom <<*/ std::endl;
// //}
// osg::ref_ptr<osgEarth::Feature> feature = cursor->nextFeature();
// //osg::ref_ptr<osgEarth::GeometryIterator> parts = feature->getGeometry()->intersects();
// osgEarth::GeometryIterator parts(feature->getGeometry(), false);
// while (parts.hasMore()){
// osgEarth::Geometry* part = parts.next();
// osg::ref_ptr<osg::Geometry> osgGeom = new osg::Geometry;
// osg::ref_ptr<osg::Vec3Array> allPoints = new osg::Vec3Array;
// //设置顶点
// int totalPoints = part->getTotalPointCount();
// //allPoints = part->toVec3Array();
// //osgEarth::ECEF::transformAndLocalize(part->asVector(), srs->getGeographicSRS(), allPoints);
// osg::Matrixd matrixTemp = osg::Matrixd::identity();
// osgEarth::ECEF::transformAndLocalize(part->asVector(), intputSrs, allPoints, outputSrs, matrixTemp);
// /*for (size_t i = 0; i < part->size(); i++) {
// osg::Vec3d ecef;
// srs->transform(part->at(i), ecef);
// allPoints->push_back(ecef);
// }*/
// osgGeom->setVertexArray(allPoints);
// //osgGeom->setPrimitiveSet(new osg::DrawArrays(GL_LINE_LOOP, 0, part->size()));
// osgGeom->addPrimitiveSet(new osg::DrawArrays(GL_LINE_LOOP, 0, part->size()));
// //设置颜色
// osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array(allPoints->size());
// for (size_t c = 0; c < colors->size(); c++){
// (*colors)[c] = color;
// }
// osgGeom->setColorArray(colors);
// osgGeom->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
// gnode->addDrawable(osgGeom);
// }
//}
//osgViewer::Viewer viewer;
//viewer.setSceneData(gnode);
//return viewer.run();
return 0;
}