Node与NodeVisitor

本文详细解析了osg库中Node与NodeVisitor的多态使用,通过实例展示了Node类及其子类如何通过accept方法调用NodeVisitor的apply方法进行遍历。讲解了在不同情况下,虚函数调用的匹配原则,并通过代码示例说明了继承和多态在Visitor模式中的应用。

osg::ref_ptr< osg::Node> model;

NodeVisitor vv( TRAVERSE_ALL_CHILDREN);  

model->accept( vv);

假设model的模型结构如图:

则model->accept( vv);多态,相当于调用Group::accept(NodeVisitor)。

注意,我开始误认为Group没有对accept()进行重写,

但后来在遍历Geode结点时,发现Geode类的定义中有一个META_Node(osg, Geode),才发现这些节点都调用了META_Node宏,而

META_Node宏里有个virtual void accept(osg::NodeVisitor& nv)

{ if (nv.validNodeMask(*this)) { nv.pushOntoNodePath(this); nv.apply(*this); nv.popFromNodePath(); }

原来这些节点都重写accept虚函数。这样才是正确的。这个宏定义代码和voidNode::accept(NodeVisitor& nv)的实现完全相同。

 

将Group的META_Node宏中的accept展开,为:

void Group::accept(NodeVisitor& nv)

{

    if (nv.validNodeMask(*this))

    {

        nv.pushOntoNodePath(this);

        nv.apply(*this);

        nv.popFromNodePath();

    }

}

其中nv.apply(*this);因为this指针是Group*型,所以实际调用的是NodeVisitor::apply( Group& node)其中node指向Group对象

(注:如果是void Node::accept(NodeVisitor& nv){nv.apply(*this);} 则调用的不是NodeVisitor::apply( Group& node),因为nv.apply(*this)中的this参数是Node*型,

应与apply( Node*)匹配,而apply( Group*)则无法匹配,因为Node*不能隐式转换为Group*。示例程序如文章最后):

void NodeVisitor::apply(Group & node)

{

    traverse(node);

}

NodeVisitor类中

inline void traverse(Node& node)

        {

            if (_traversalMode==TRAVERSE_PARENTS) node.ascend(*this);

            else if (_traversalMode!=TRAVERSE_NONE) node.traverse(*this);

        }

由于此程序代码中NodeVisitor设置的遍历模式为TRAVERSE_ALL_CHILDREN(而不是默认的TRAVERSE_NONE),故调用Group::traverse(NodeVisitor):

void Group::traverse(NodeVisitor& nv)

{

    for(NodeList::iterator itr=_children.begin();

        itr!=_children.end();

        ++itr)

    {

        (*itr)->accept(nv);

    }

}

表示对其每一个孩子进行遍历,递归调用。相当于深搜。

附:

Node类的traverse()函数为空:

virtual void traverse(NodeVisitor& /*nv*/) {}     

递归调用过程如下图:

对以下代码:

#include "stdafx.h"

#include <iostream>

#include <string>

using namespace std;

 

class A;

class B;

 

class NodeVisitor

{

public:

    void apply( A& a)

    {

       cout<< "apply( A& a)"<< endl;     

    }

    void apply( B& b)

    {

       cout<< "apply( B& b)"<< endl;     

    }

};

 

class A

{

public:

    void accept( NodeVisitor& nv)

    {

       nv.apply( *this);//this是A*型,故调用apply( A*),即apply( A& a)

    }

};

 

class B: public A

{};

 

int _tmain(int argc, _TCHAR* argv[])

{

    B b;

    NodeVisitor nv;

    b.accept( nv);//最终调用apply( A& a)

 

    system("pause");

    return 0;

}

 

如果上述代码中实现了B的accept(),则:

#include "stdafx.h"

#include <iostream>

#include <string>

using namespace std;

 

class A;

class B;

 

class NodeVisitor

{

public:

    void apply( A& a)

    {

       cout<< "apply( A& a)"<< endl;     

    }

    void apply( B& b)

    {

       cout<< "apply( B& b)"<< endl;     

    }

};

 

class A

{

public:

    void accept( NodeVisitor& nv)

    {

       nv.apply( *this);//this是A*型,故调用apply( A*),即apply( A& a)

    }

};

 

class B: public A

{

public:

    void accept( NodeVisitor& nv)

    {

       nv.apply( *this);//this是B*型,故调用apply( B*),即apply( B& b)

    }

};

 

int _tmain(int argc, _TCHAR* argv[])

{

    B b;

    NodeVisitor nv;

    b.accept( nv);//调用B::accept(),最终调用apply( B& b)。

 

    system("pause");

    return 0;

}

 

―――――――

Visitor模型示例如下:

class Node;

class Node_derived1;

 

class NodeVisitor

{

public:

    virtual void apply( Node& n)

    {

       cout<< "NodeVisitor::apply( Node& n)"<< endl;   

    }

    virtual void apply( Node_derived1& n)

    {

       cout<< "NodeVisitor::apply( Node_derived1& n)"<< endl;     

    }

};

class NodeVisitor_Derived1: public NodeVisitor

{

public:   

virtual void apply( Node& n)//重写有关Node基类的函数

    {

       cout<< "NodeVisitor_Derived1::apply( Node& n)"<< endl;     

    }

    virtual void apply( Node_derived1& n)//写自定义的函数

    {

       cout<< "NodeVisitor_Derived1::apply( Node_derived1& n)"<< endl;      

    }

};

 

class Node

{

public:

    virtual void accept( NodeVisitor& nv)

    {

       nv.apply( *this);//this为Node*,与apply( Node*)匹配,调用apply( Node & n)

    }

};

class Node_derived1: public Node

{

public:

    virtual void accept( NodeVisitor& nv)

    {

       nv.apply( *this);//this为Node_derived1*,与apply(Node_derived1*)匹配,调用apply( Node_derived1& n)

    }

};

 

int _tmain(int argc, _TCHAR* argv[])

{  

    Node n;

    NodeVisitor nv;

    n.accept( nv);//调用NodeVisitor::apply( Node& n)

 

    Node_derived1 n1;

    NodeVisitor_Derived1 nv1;

    n1.accept( nv1);//调用NodeVisitor_Derived1::apply( Node_derived1& n)

 

    system("pause");

    return 0;

}

 

Node和NodeVisitor分别是节点基类,节点访问器基类。可以从它们继承,如Node_derived1对应NodeVisitor_Derived1。

如果想再添加Node_derived2~NodeVisitor_Derived2、Node_derived3~NodeVisitor_Derived3。但有个缺点,

就是每次除了加2个类,还需修改NodeVisitor基类,即在NodeVisitor类中加入virtualvoid apply( Node_derived_N& n)。

 

―――――――――――

OSG中的Node、Group、Geode的访问如下:

void NodeVisitor::apply(Node& node)

{

    traverse(node);

}

void NodeVisitor::apply(Group& node)

{

    traverse(node);

}

void NodeVisitor::apply(Geode& node)

{

    apply(static_cast<Node&>(node));

}

inline void NodeVisitor::traverse(Node& node)

        {

            if (_traversalMode==TRAVERSE_PARENTS) node.ascend(*this);

            else if (_traversalMode!=TRAVERSE_NONE) node.traverse(*this);

        }

 

void Node::accept(NodeVisitor& nv)

{    if (nv.validNodeMask(*this))

    {

        nv.pushOntoNodePath(this);

        nv.apply(*this);

        nv.popFromNodePath();

}}

virtual void Node::traverse(NodeVisitor& /*nv*/) {} 

 

virtual void Group::accept(osg::NodeVisitor& nv)

{ if (nv.validNodeMask(*this)) { nv.pushOntoNodePath(this); nv.apply(*this); nv.popFromNodePath(); }}

void Group::traverse(NodeVisitor& nv)

{

    for(NodeList::iterator itr=_children.begin();

        itr!=_children.end();

        ++itr)

    {

        (*itr)->accept(nv);

}}

 

virtual void Geode::accept(osg::NodeVisitor& nv)

{ if (nv.validNodeMask(*this)) { nv.pushOntoNodePath(this); nv.apply(*this); nv.popFromNodePath(); }}

 

 

 

 

<think>我们正在讨论OpenSceneGraph(OSG)中如何获取节点的世界矩阵。世界矩阵表示节点在整个场景图中的全局变换,它由所有父节点的变换累积而成。 在OSG中,获取节点的世界矩阵(即模型矩阵)通常有两种方法: 1. 使用`osg::ComputeBoundsVisitor`计算包围盒时获取世界矩阵 2. 通过节点路径(NodePath)计算世界矩阵 ### 方法1:通过节点路径计算世界矩阵 这是最直接的方法。我们可以通过以下步骤获取: ```cpp // 获取节点路径(从根节点到目标节点的路径) osg::NodePath nodePath = node->getParentalNodePaths()[0]; // 注意:可能有多个路径,通常取第一个 // 计算世界矩阵 osg::Matrix worldMat = osg::computeWorldToLocal(nodePath); // 注意:这个函数返回的是世界坐标系到该节点局部坐标系的变换矩阵 osg::Matrix localToWorld = osg::Matrix::inverse(worldMat); // 所以我们需要取逆来得到节点局部坐标系到世界坐标系的变换 ``` 或者,更直接地使用: ```cpp osg::Matrix localToWorld = osg::computeLocalToWorld(nodePath); ``` ### 方法2:使用`osg::NodeVisitor`遍历时计算 我们可以自定义一个NodeVisitor,在遍历过程中累积变换矩阵。 ```cpp class GetWorldMatrixVisitor : public osg::NodeVisitor { public: GetWorldMatrixVisitor(osg::Node* node) : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_PARENTS), _worldMatrix(osg::Matrix::identity()) { // 从目标节点开始向上遍历父节点 traverse(*node); } void apply(osg::Node& node) { // 检查节点是否是Transform类型(如MatrixTransform) if (osg::Transform* transform = dynamic_cast<osg::Transform*>(&node)) { // 获取当前节点的变换矩阵 osg::Matrix localMat; if (transform->computeLocalToWorldMatrix(localMat, this)) { _worldMatrix = localMat * _worldMatrix; } else { // 如果计算失败,则只累积之前的矩阵 } } // 继续向上遍历 traverse(node); } osg::Matrix getWorldMatrix() const { return _worldMatrix; } private: osg::Matrix _worldMatrix; }; // 使用示例: GetWorldMatrixVisitor visitor(node); osg::Matrix worldMat = visitor.getWorldMatrix(); ``` ### 注意事项: 1. **节点路径的获取**:`getParentalNodePaths`返回一个节点路径列表,因为一个节点可能有多个父节点(在OSG中,一个节点可以被多个父节点引用,但通常我们只考虑一个父节点路径)。如果节点只有一个父节点路径,那么我们可以直接取第一个。 2. **变换的累积顺序**:OSG使用前乘(pre-multiplication)顺序。在遍历父节点时,矩阵的累积顺序是从目标节点向上直到根节点,然后我们需要将矩阵按从根节点到目标节点的顺序相乘(即从后往前乘)。但是,在`GetWorldMatrixVisitor`中,我们是从目标节点向上遍历,所以每次将当前节点的变换矩阵乘以前面累积的矩阵(即后遍历的节点矩阵乘在左边)是正确的。 3. **性能考虑**:获取世界矩阵是一个相对昂贵的操作,因为它需要遍历节点的父节点链。在每一帧中频繁调用可能会影响性能。 ### 调试世界矩阵 我们可以打印出世界矩阵的值来检查: ```cpp osg::Matrix worldMat = ...; std::cout << "World Matrix: " << worldMat << std::endl; ``` ### 示例:在更新回调中获取世界矩阵 如果我们想在节点的更新回调中获取其世界矩阵,可以这样做: ```cpp class UpdateCallback : public osg::NodeCallback { public: virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) { // 获取当前节点的世界矩阵 osg::NodePath nodePath = nv->getNodePath(); // 注意:nv->getNodePath()返回的是从根节点到当前回调节点的路径(不包括当前节点?) // 因此,我们需要将当前节点加入路径? // 实际上,getNodePath()返回的路径已经包含了当前节点(即回调被调用的节点) // 但是,如果我们想要当前节点的世界矩阵,可以直接计算: osg::Matrix worldMat = osg::computeLocalToWorld(nodePath); // 或者,如果我们有多个父节点路径,可以使用: // osg::NodePathList nodePaths = node->getParentalNodePaths(); // if (!nodePaths.empty()) { // osg::Matrix worldMat = osg::computeLocalToWorld(nodePaths[0]); // } // 然后使用worldMat... traverse(node, nv); } }; ``` ### 引用说明 在OSG中,矩阵变换的累积顺序是重要的,特别是涉及到不同类型的变换节点(如`osg::Transform`、`osg::PositionAttitudeTransform`)时。请记住OSG使用前乘顺序(pre-multiplication)[^1]。 ### 总结 获取节点的世界矩阵有两种主要方法: 1. **通过节点路径**:使用`osg::computeLocalToWorld(nodePath)`直接计算。 2. **通过自定义访问器**:遍历父节点并累积变换矩阵。 在性能要求较高的场景中,建议使用方法1,因为它更直接且通常更快。但在某些特殊情况下(如需要同时获取多个节点的世界矩阵),方法2可能更灵活。 §§相关问题§§ 1. 如何将世界坐标转换为节点局部坐标? 2. 在OSG中如何实现相机跟随节点(即让相机始终以某个节点为中心)? 3. 如何优化场景图中频繁获取世界矩阵的操作?</think>### 在OpenSceneGraph中获取节点的世界矩阵 在OSG中获取节点的世界矩阵(即全局变换矩阵)是场景操作的关键技术。以下是三种核心方法: #### 方法1:通过节点路径计算世界矩阵(推荐) ```cpp // 获取节点路径 osg::NodePath nodePath = node->getParentalNodePaths()[0]; // 计算世界矩阵 osg::Matrix worldMatrix = osg::computeLocalToWorld(nodePath); ``` #### 方法2:使用`osg::ComputeBoundsVisitor`(自动计算) ```cpp osg::ComputeBoundsVisitor boundVisitor; node->accept(boundVisitor); // 从访问器中获取世界矩阵 osg::Matrix worldMatrix = boundVisitor.getMatrix(); ``` #### 方法3:自定义访问器(灵活控制) ```cpp class WorldMatrixVisitor : public osg::NodeVisitor { public: WorldMatrixVisitor() : NodeVisitor(TRAVERSE_PARENTS) {} void apply(osg::Node& node) { if (osg::Transform* transform = dynamic_cast<osg::Transform*>(&node)) { transform->computeLocalToWorldMatrix(_matrix, this); } traverse(node); } osg::Matrix getMatrix() const { return _matrix; } private: osg::Matrix _matrix = osg::Matrix::identity(); }; // 使用示例 WorldMatrixVisitor visitor; node->accept(visitor); osg::Matrix worldMatrix = visitor.getMatrix(); ``` ### 关键注意事项 1. **矩阵累积顺序**: - OSG使用**前乘(pre-multiply)** 顺序:`M_total = M_parent × M_local`[^1] - 验证矩阵:`std::cout << "World Matrix: " << worldMatrix << std::endl;` 2. **坐标系关系**: ```mermaid graph LR A[模型坐标系] -->|localMatrix| B[父节点坐标系] B -->|parentMatrix| C[世界坐标系] ``` 3. **性能优化**: - 避免每帧获取矩阵,建议在更新回调中缓存结果 - 使用`osgViewer --stats`监控变换计算性能[^1] ### 典型应用场景 1. **坐标转换**: ```cpp // 局部坐标转世界坐标 osg::Vec3d localPos(0,0,0); osg::Vec3d worldPos = localPos * worldMatrix; ``` 2. **碰撞检测**: ```cpp // 获取物体在世界空间中的包围盒 osg::BoundingSphere bs = node->getBound(); osg::BoundingSphere worldBs = bs.transform(worldMatrix); ``` 3. **相机跟踪**: ```cpp // 让相机跟随节点 camera->setViewMatrix(osg::Matrix::inverse(worldMatrix)); ``` ### 调试技巧 1. **可视化坐标轴**: ```cpp osg::ref_ptr<osg::Geode> createAxes(float size) { osg::ref_ptr<osg::Geode> geode = new osg::Geode; geode->addDrawable(new osg::ShapeDrawable(new osg::Cylinder(osg::Vec3(0,0,0), 0.05*size, size))); // 添加X/Y/Z轴... return geode; } node->addChild(createAxes(1.0f)); // 附加到节点 ``` 2. **矩阵验证**: - 检查行列式:`fabs(worldMatrix.determinant()) > 1e-6`(避免奇异矩阵) - 验证平移分量:`worldMatrix.getTrans()`应匹配预期位置
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值