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是如何作到的呢?node
先看看FeatureEditor的繼承圖函數
FeatureEditor繼承自AnnotationEditor,AnnotationEditor繼承自Group,看看AnnotationEditor的代碼ui
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這個類並無作太多的事情,僅僅是在構造函數中設置了一下渲染狀態而已。this
接着看看FeatureEditor這個類spa
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。3d
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()函數。code
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,在此處,咱們主要須要注意這四句代碼,此時不懂沒關係,看完後面,就會串起來了。orm
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的位置。blog
/** * 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的位置,以上!
建議再重頭看一下上面的剖析過程。