OsgEearh 中的 FeatureEditor的实现原理
先来看看FeatureEditor的用法:
const osgEarth::SpatialReference* mapSRS = mapNode->getMapSRS(); osgEarth::Symbology::Style geomStyle; geomStyle.getOrCreate<osgEarth::LineSymbol>()->stroke()->color() = osgEarth::Symbology::Color::Cyan; geomStyle.getOrCreate<osgEarth::LineSymbol>()->stroke()->width() = 5.0f; geomStyle.getOrCreate<osgEarth::LineSymbol>()->tessellationSize() = 75000; geomStyle.getOrCreate<osgEarth::AltitudeSymbol>()->clamping() = osgEarth::AltitudeSymbol::CLAMP_TO_TERRAIN; geomStyle.getOrCreate<osgEarth::AltitudeSymbol>()->technique() = osgEarth::AltitudeSymbol::TECHNIQUE_DRAPE; osg::ref_ptr<osgEarth::Symbology::Polygon> polygon = new osgEarth::Symbology::Polygon(); polygon->push_back(osg::Vec3d(0, 40, 0)); polygon->push_back(osg::Vec3d(-60, 40, 0)); polygon->push_back(osg::Vec3d(-60, 60, 0)); polygon->push_back(osg::Vec3d(0, 60, 0)); osg::ref_ptr<osgEarth::Features::Feature> feature = new osgEarth::Features::Feature(polygon, mapSRS); osg::ref_ptr<osgEarth::Annotation::FeatureNode> featureNode = new osgEarth::Annotation::FeatureNode(feature, geomStyle); geometryGroup->addChild(featureNode); osg::ref_ptr<osgEarth::Annotation::FeatureEditor> editor = new osgEarth::Annotation::FeatureEditor(featureNode); mapNode->addChild(editor);
用FeatureNode作为参数来构造出一个FeatureEditor,然后将该FeatureEditor添加到MapNode中,即可实现通过鼠标拖动对FeatureNode的编辑功能。那么FeatureEditor是如何做到的呢?
先看看FeatureEditor的继承图
FeatureEditor继承自AnnotationEditor,AnnotationEditor继承自Group,看看AnnotationEditor的代码
class OSGEARTHANNO_EXPORT AnnotationEditor : public osg::Group { protected: AnnotationEditor(); virtual ~AnnotationEditor() { } };
AnnotationEditor::AnnotationEditor() : osg::Group() { // editor geometry should always be visible. osg::StateSet* stateSet = this->getOrCreateStateSet(); stateSet->setMode(GL_DEPTH_TEST, 0); stateSet->setRenderBinDetails(99, "RenderBin"); }
AnnotationEditor这个类并没有做太多的事情,仅仅是在构造函数中设置了一下渲染状态而已。
接着看看FeatureEditor这个类
class OSGEARTHANNO_EXPORT FeatureEditor : public AnnotationEditor { public: /** * Constructs a new FeatureEditor * @param featureNode * The FeatureNode to edit */ FeatureEditor( FeatureNode* featureNode ); /** *Gets the color of the draggers when they are selected */ const osg::Vec4f& getPickColor() const; /** *Sets the color of the draggers when they are selected */ void setPickColor( const osg::Vec4f& pickColor ); /** *Gets the color of the draggers */ const osg::Vec4f& getColor() const; /** *Sets the color of the draggers */ void setColor( const osg::Vec4f& color ); /** *Gets the dragger size */ float getSize() const; /** *Sets the dragger size */ void setSize( float size ); protected: void init(); osg::Vec4f _pickColor; osg::Vec4f _color; float _size; osg::ref_ptr< FeatureNode > _featureNode; };
这个类的代码也比较简单,只包含四个成员变量,两个color,一个size,以及该Editor所要编辑的FeatureNode。
FeatureEditor::FeatureEditor( FeatureNode* featureNode): _featureNode( featureNode ), _color(osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f)), _pickColor(osg::Vec4(1.0f, 1.0f, 0.0f, 1.0f)), _size( 5.0f ) { init(); }
构造函数初始化了四个成员变量,然后调用init()函数。
void FeatureEditor::init() { removeChildren( 0, getNumChildren() ); Feature* feature = _featureNode->getFeatures().front().get(); //Create a dragger for each point for (unsigned int i = 0; i < feature->getGeometry()->size(); i++) { SphereDragger* dragger = new SphereDragger( _featureNode->getMapNode() ); dragger->setColor( _color ); dragger->setPickColor( _pickColor ); dragger->setSize( _size ); dragger->setPosition(GeoPoint(feature->getSRS(), (*feature->getGeometry())[i].x(), (*feature->getGeometry())[i].y())); dragger->addPositionChangedCallback(new MoveFeatureDraggerCallback( _featureNode.get(), i) ); addChild(dragger); } }
正如官方所给的注释:这个函数为geometry中的每个point都创建了一个dragger,dragger又是个什么东东?这个我们现在还不知道,接下来我们的内容就是研究dragger,在此处,我们主要需要注意这四句代码,此时不懂不要紧,看完后面,就会串起来了。
void FeatureEditor::init() { for (unsigned int i = 0; i < feature->getGeometry()->size(); i++) { //构造函数 SphereDragger* dragger = new SphereDragger( _featureNode->getMapNode() ); //根据point的位置来设置dragger的位置 dragger->setPosition(GeoPoint(feature->getSRS(), (*feature->getGeometry())[i].x(), (*feature->getGeometry())[i].y())); //为dragger添加回调 dragger->addPositionChangedCallback(new MoveFeatureDraggerCallback( _featureNode.get(), i) ); //将dragger添加到场景中 addChild(dragger); } }
下面来看看dragger
dragger继承自GeoPositionNode,对于GeoPositionNode,我们不需要知道太多,GeoPositionNode继承自Group,它的特殊之处在于它具有地理信息(毕竟人家叫 Geo Positon嘛),它提供了一个setPosition接口,我们可以通过这个接口来设置GeoPositionNode的位置。
/** * Dragger is a handle you can use to control things in the scene. * You drag it around with the mouse and it fires PositionChangedCallback's * that you can listen to to repond to. */ class OSGEARTHANNO_EXPORT Dragger : public GeoPositionNode { public: /** * Callback that is fired when the position changes */ struct PositionChangedCallback : public osg::Referenced { public: virtual void onPositionChanged(const Dragger* sender, const osgEarth::GeoPoint& position) {}; virtual ~PositionChangedCallback() { } }; typedef std::list< osg::ref_ptr<PositionChangedCallback> > PositionChangedCallbackList; enum DragMode { DRAGMODE_HORIZONTAL, DRAGMODE_VERTICAL }; Dragger( MapNode* mapNode, int modKeyMask=0, const DragMode& defaultMode=DRAGMODE_HORIZONTAL ); /** dtor */ virtual ~Dragger(); /** Sets the map position of the dragger, optionally firing a PositionChanged event. */ void setPosition(const osgEarth::GeoPoint& position, bool fireEvents); /** Drag mode */ void setDefaultDragMode(const DragMode& mode) { _defaultMode = mode; } DragMode& getDefaultDragMode() { return _defaultMode; } /** Add a callback that runs whenever the user moves the dragger */ void addPositionChangedCallback( PositionChangedCallback* callback ); /** Remove a callback. */ void removePositionChangedCallback( PositionChangedCallback* callback ); public: // GeoPositionNode virtual void setPosition(const GeoPoint& point); public: // osg::Node virtual void traverse(osg::NodeVisitor& nv); protected: virtual bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa); void firePositionChanged(); bool _dragging; bool _hovered; PositionChangedCallbackList _callbacks; osg::ref_ptr< osgManipulator::LineProjector > _projector; osgManipulator::PointerInfo _pointer; osg::Vec3d _startProjectedPoint; bool _elevationDragging; int _modKeyMask; DragMode _defaultMode; double _verticalMinimum; };
dragger的代码有一丢丢长,为求直观,上面的代码中我删除了其中一部分代码。
/** * Dragger is a handle you can use to control things in the scene. * You drag it around with the mouse and it fires PositionChangedCallback's * that you can listen to to repond to. */
官方给的注释是:可以利用dragger来control scene中的对象,当你用鼠标拖动dragger时,会触发PositionChangedCallback回调。
还记得之前FeatureEditor中init函数中的这句代码么?
//为dragger添加回调 dragger->addPositionChangedCallback(new MoveFeatureDraggerCallback( _featureNode.get(), i) );
我们先不管MoveFeatureDraggerCallback具体是什么,只需要知道它继承自PositionChangedCallback(PostionChangedCallback的代码在上面的代码里),PositionChangedCallback这个函数包含一个虚函数:
virtual void onPositionChanged(const Dragger* sender, const osgEarth::GeoPoint& position) {};
显然MoveFeatureChangedCallback对这个函数进行了重写,重写的内容具体是什么,我们先不管,先将注意力放在addPositionChangedCallback这个函数上。
void Dragger::addPositionChangedCallback( PositionChangedCallback* callback ) { _callbacks.push_back( callback ); }
这个函数的功能也很直观,将PositionChangedCallback添加进一个列表中,这个列表何时被用到?
void Dragger::firePositionChanged() { for( PositionChangedCallbackList::iterator i = _callbacks.begin(); i != _callbacks.end(); i++ ) { i->get()->onPositionChanged(this, getPosition()); } }
firePositonChanged函数遍历列表中的PositionChangedCallback对象,调用其onPositionChanged函数。firePositionChanged函数何时被调用?
void Dragger::setPosition(const GeoPoint& position, bool fireEvents) { GeoPositionNode::setPosition( position ); if ( fireEvents ) firePositionChanged(); }
再看看handle函数。handle函数是用来处理gui事件的,在handle函数中,firePositionChanged()和setPosition()多次出现过。
bool Dragger::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) { if (ea.getHandled()) return false; osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa); if (!view) return false; if (!getMapNode()) return false; if (ea.getEventType() == osgGA::GUIEventAdapter::PUSH) { IntersectionPicker picker( view, this ); IntersectionPicker::Hits hits; if ( picker.pick( ea.getX(), ea.getY(), hits ) ) { const GeoPoint& position = getPosition(); _dragging = true; //Check for and handle vertical dragging if necessary bool pressedAlt = _modKeyMask && (ea.getModKeyMask() & _modKeyMask) > 0; _elevationDragging = (_defaultMode == Dragger::DRAGMODE_VERTICAL && !pressedAlt) || (_defaultMode == Dragger::DRAGMODE_HORIZONTAL && pressedAlt); if (_elevationDragging) { _pointer.reset(); // set movement range // TODO: values 0.0 and 300000.0 are rather experimental GeoPoint posStart(position.getSRS(), position.x(), position.y(), 0.0, ALTMODE_ABSOLUTE); osg::Vec3d posStartXYZ; posStart.toWorld(posStartXYZ); GeoPoint posEnd(position.getSRS(), position.x(), position.y(), 300000.0, ALTMODE_ABSOLUTE); osg::Vec3d posEndXYZ; posEnd.toWorld(posEndXYZ); _projector->setLine(posStartXYZ, posEndXYZ); // set camera osgUtil::LineSegmentIntersector::Intersections intersections; osg::Node::NodeMask intersectionMask = 0xffffffff; osgViewer::View* view = dynamic_cast<osgViewer::View*>(&aa); if ( !view ) return true; if (view->computeIntersections(ea.getX(),ea.getY(),intersections, intersectionMask)) { for (osgUtil::LineSegmentIntersector::Intersections::iterator hitr = intersections.begin(); hitr != intersections.end(); ++hitr) { _pointer.addIntersection(hitr->nodePath, hitr->getLocalIntersectPoint()); } bool draggerFound = false; for (osgManipulator::PointerInfo::IntersectionList::iterator piit = _pointer._hitList.begin(); piit != _pointer._hitList.end(); ++piit) { for (osg::NodePath::iterator itr = piit->first.begin(); itr != piit->first.end(); ++itr) { Dragger* dragger = dynamic_cast<Dragger*>(*itr); if (dragger==this) { draggerFound = true; osg::Camera *rootCamera = view->getCamera(); osg::NodePath nodePath = _pointer._hitList.front().first; osg::NodePath::reverse_iterator ritr; for (ritr = nodePath.rbegin(); ritr != nodePath.rend(); ++ritr) { osg::Camera* camera = dynamic_cast<osg::Camera*>(*ritr); if (camera && (camera->getReferenceFrame()!=osg::Transform::RELATIVE_RF || camera->getParents().empty())) { rootCamera = camera; break; } } _pointer.setCamera(rootCamera); _pointer.setMousePosition(ea.getX(), ea.getY()); break; } } if (draggerFound) break; } } } aa.requestRedraw(); return true; } } else if (ea.getEventType() == osgGA::GUIEventAdapter::RELEASE) { _elevationDragging = false; if ( _dragging ) { _dragging = false; firePositionChanged(); } aa.requestRedraw(); } else if (ea.getEventType() == osgGA::GUIEventAdapter::DRAG) { if (_elevationDragging) { _pointer._hitIter = _pointer._hitList.begin(); _pointer.setMousePosition(ea.getX(), ea.getY()); if (_projector->project(_pointer, _startProjectedPoint)) { const GeoPoint& position = getPosition(); //Get the absolute mapPoint that they've drug it to. GeoPoint projectedPos; projectedPos.fromWorld(position.getSRS(), _startProjectedPoint); // make sure point is not dragged down below // TODO: think of a better solution / HeightAboveTerrain performance issues? if (projectedPos.z() >= _verticalMinimum) { //If the current position is relative, we need to convert the absolute world point to relative. //If the point is absolute then just emit the absolute point. if (position.altitudeMode() == ALTMODE_RELATIVE) { projectedPos.transformZ(ALTMODE_RELATIVE, getMapNode()->getTerrain()); } setPosition( projectedPos ); aa.requestRedraw(); } } return true; } if (_dragging) { osg::Vec3d world; if ( getMapNode() && getMapNode()->getTerrain()->getWorldCoordsUnderMouse(view, ea.getX(), ea.getY(), world) ) { const GeoPoint& position = getPosition(); //Get the absolute mapPoint that they've drug it to. GeoPoint mapPoint; mapPoint.fromWorld( getMapNode()->getMapSRS(), world ); //If the current position is relative, we need to convert the absolute world point to relative. //If the point is absolute then just emit the absolute point. if (position.altitudeMode() == ALTMODE_RELATIVE) { mapPoint.alt() = position.alt(); mapPoint.altitudeMode() = ALTMODE_RELATIVE; } setPosition( mapPoint ); aa.requestRedraw(); return true; } } } else if (ea.getEventType() == osgGA::GUIEventAdapter::MOVE) { IntersectionPicker picker( view, this ); IntersectionPicker::Hits hits; if ( picker.pick( ea.getX(), ea.getY(), hits ) ) { setHover( true ); } else { setHover( false ); } aa.requestRedraw(); } return false; }
dragger有两种模式,水平模式和垂直模式,这两种模式的差异在handle()中得到了体现。
enum DragMode { DRAGMODE_HORIZONTAL, DRAGMODE_VERTICAL };
handle函数的内容我大体说明一下:当鼠标push时,判断是否点击到了dragger上,如果击中的话,判断当前拖动模式是水平模式还是垂直模式,如果是垂直模式的话,就创建一个projector对象,根据dragger当前的位置得到一条直线,将projector的投影对象设为该直线。之后drag时,将鼠标所处位置的世界坐标投影到该直线上。(顺带一提,这部分代码可以参考一下osg中的dragger类的handle函数,两者还是挺相似的,都用到了投影的方法)
若为水平模式,那就不需要投影了,在drag时,直接根据鼠标的位置,得到地面坐标就ok了。
故handle函数的主要作用就是当drag dragger时,利用setPosition函数实时地更新dragger的位置,并触发回调。
最后再回头看看MoveFeatureChangedCallback()这个类。
class MoveFeatureDraggerCallback : public Dragger::PositionChangedCallback { public: MoveFeatureDraggerCallback(FeatureNode* featureNode, int point): _featureNode( featureNode ), _point(point) {} virtual void onPositionChanged(const Dragger* sender, const osgEarth::GeoPoint& position) { Feature* feature = _featureNode->getFeatures().front().get(); (*feature->getGeometry())[_point] = osg::Vec3d(position.x(), position.y(), 0); _featureNode->init(); } osg::ref_ptr< FeatureNode > _featureNode; int _point; };
重点关注onPositionChanged这个重写的虚函数,它有两个参数,sender代表调用这个函数的dragger,position表示dragger的位置,函数内容很简单,就是根据dragger的位置来更改geometry中point的位置,最后再重绘geometry。
剖析到此结束,我们总结一下:假设一个geometry有十个point,当我们根据这个geometry创建一个FeatureEditor是,FeatureEditor会创建十个Dragger,每个Dragger的初始位置都是Point所处的位置,当Draggr被移动时,会触发回调,从而改变这个Dragger所绑定的point的位置,以上!
建议再重头看一下上面的剖析过程。