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是如何作到的呢?node

image

先看看FeatureEditor的繼承圖函數

image

FeatureEditor繼承自AnnotationEditorAnnotationEditor繼承自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所要編輯的FeatureNode3d

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都建立了一個draggerdragger又是個什麼東東?這個咱們如今還不知道,接下來咱們的內容就是研究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對象

image

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函數,二者仍是挺類似的,都用到了投影的方法

image

若爲水平模式,那就不須要投影了,在drag時,直接根據鼠標的位置,獲得地面座標就ok了。

image

故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的位置,以上!

建議再重頭看一下上面的剖析過程。Open-mouthed smile

相關文章
相關標籤/搜索