
EventCallback 事件回调
在 OSG 开发中,EventCallback 是实现键盘、鼠标、窗口交互 的核心机制。它和我们之前学的 UpdateCallback(自动帧更新)完全不同,有着严格的使用规则、继承体系、挂载限制。
前置核心
先明确 OSG 事件回调的类层级,这是理解它的基础:
osg::Referenced
└── osg::Callback
└── osg::NodeCallback
└── 你自定义的 EventCallback(本文:MyEventCallback)
NodeCallback:节点回调基类EventCallback本质是处理事件的 NodeCallback- 必须重写
operator(),在事件遍历阶段被调用
EventCallback 到底是什么?
定义
EventCallback 是 OSG 提供的事件接收回调 。
作用:接收键盘、鼠标、触摸、窗口事件,并执行自定义交互逻辑。
关键特性
- 不是每帧自动调用,只有事件触发时才调用
- 只能挂载在能接收系统事件的节点上
- 必须通过
EventVisitor访问器分发事件
为什么 EventCallback 只能挂给 Camera?
最核心结论(必须背)
普通场景节点(Group / MatrixTransform / Geode)不接收系统事件!
只有 Camera / Viewer / RenderSurface 可以接收键盘鼠标事件!
1. OSG 事件传递流程(固定不可修改)
操作系统(键盘/鼠标)
↓
OSG Viewer(接收事件)
↓
Camera(事件分发中心)
↓
EventVisitor 遍历 → 触发 EventCallback
↓
场景树(只负责渲染,不负责接收事件)
2. 为什么不能挂给 MatrixTransform?
MatrixTransform是场景空间变换节点- 它的作用是:改变子节点坐标
- 它没有事件接收能力
- 你强行挂载:回调永远不会执行
模型移动为什么是两个矩阵相乘?
先看代码
cpp
osg::Matrix currentMat = mt->getMatrix(); // 当前矩阵
osg::Matrix moveMat = osg::Matrix::translate(0, 0, step); // 移动增量
mt->setMatrix(currentMat * moveMat); // 矩阵相乘 → 叠加移动
结论
矩阵相乘 = 变换叠加
currentMat * moveMat = 在当前位置基础上继续移动
1. 错误写法(直接覆盖)
cpp
mt->setMatrix(moveMat); // ❌ 错误
后果:每次都重置到原点再移动,无法连续移动
相当于:每次移动都把模型放回 (0,0,0) 再挪一步。
2. 正确写法(矩阵相乘)
新矩阵 = 当前位置矩阵 × 移动增量矩阵
3. 矩阵乘法的意义(3D 数学核心)
在 3D 数学中:
- 矩阵 = 空间变换(平移/旋转/缩放)
- A × B = 先执行 A 变换,再执行 B 变换
- 矩阵相乘 = 变换叠加
所以:
当前位置 × 移动增量 = 新位置(连续移动)
这就是模型能连续移动的根本原因。
完整可运行代码
cpp
#include <osgViewer/Viewer>
#include <osg/MatrixTransform>
#include <osgDB/ReadFile>
#include <osgGA/GUIEventAdapter>
#include <osgGA/GUIActionAdapter>
#include <osgGA/EventVisitor>
#include <iostream>
// ==========================
// 事件回调类
// ==========================
class MyEventCallback : public osg::NodeCallback
{
public:
MyEventCallback() : _step(1.0f) {}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) override
{
// 只处理事件遍历
if (nv->getVisitorType() == osg::NodeVisitor::EVENT_VISITOR)
{
osgGA::EventVisitor* ev = dynamic_cast<osgGA::EventVisitor*>(nv);
if (!ev) return;
osgGA::GUIActionAdapter* aa = ev->getActionAdapter();
osgGA::EventQueue::Events& events = ev->getEvents();
// 遍历事件 → 转为 GUIEventAdapter
for (auto& event : events)
{
osgGA::GUIEventAdapter* ea = dynamic_cast<osgGA::GUIEventAdapter*>(event.get());
if (ea) handle(*ea, *aa);
}
}
traverse(node, nv); // 必须遍历子节点
}
// 事件处理
bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
{
osgViewer::Viewer* viewer = dynamic_cast<osgViewer::Viewer*>(&aa);
if (!viewer) return false;
// 获取根节点(MatrixTransform)
osg::MatrixTransform* mt = dynamic_cast<osg::MatrixTransform*>(viewer->getSceneData());
if (!mt) return false;
// 键盘按下事件
if (ea.getEventType() == osgGA::GUIEventAdapter::KEYDOWN)
{
// 向上箭头:Z轴前进
if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Up)
{
osg::Matrix curr = mt->getMatrix();
mt->setMatrix(curr * osg::Matrix::translate(0, 0, _step));
return true;
}
// 向下箭头:Z轴后退
if (ea.getKey() == osgGA::GUIEventAdapter::KEY_Down)
{
osg::Matrix curr = mt->getMatrix();
mt->setMatrix(curr * osg::Matrix::translate(0, 0, -_step));
return true;
}
}
return false;
}
private:
float _step;
};
// ==========================
// 主函数
// ==========================
int main()
{
osgViewer::Viewer viewer;
// 加载模型
osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("cow.osg");
if (!model) {
std::cerr << "模型加载失败!" << std::endl;
return -1;
}
// 变换节点
osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform;
mt->addChild(model);
// ========================
// ✅ 只能挂给 Camera!
// ========================
viewer.getCamera()->setEventCallback(new MyEventCallback);
viewer.setSceneData(mt);
return viewer.run();
}

总结
EventCallback 挂 Camera 是为了接收事件;
矩阵相乘是为了叠加变换实现连续移动;
接收事件和修改坐标是两个独立逻辑,必须分开设计!
