【OSG学习笔记】Day 40: EventCallback(事件回调)

EventCallback 事件回调

在 OSG 开发中,EventCallback 是实现键盘、鼠标、窗口交互 的核心机制。它和我们之前学的 UpdateCallback(自动帧更新)完全不同,有着严格的使用规则、继承体系、挂载限制


前置核心

先明确 OSG 事件回调的类层级,这是理解它的基础:

复制代码
osg::Referenced
        └── osg::Callback
                └── osg::NodeCallback
                        └── 你自定义的 EventCallback(本文:MyEventCallback)
  • NodeCallback节点回调基类
  • EventCallback 本质是处理事件的 NodeCallback
  • 必须重写 operator(),在事件遍历阶段被调用

EventCallback 到底是什么?

定义

EventCallback 是 OSG 提供的事件接收回调

作用:接收键盘、鼠标、触摸、窗口事件,并执行自定义交互逻辑。

关键特性

  1. 不是每帧自动调用,只有事件触发时才调用
  2. 只能挂载在能接收系统事件的节点上
  3. 必须通过 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 是为了接收事件;
矩阵相乘是为了叠加变换实现连续移动;
接收事件和修改坐标是两个独立逻辑,必须分开设计!

相关推荐
世人万千丶2 小时前
开源鸿蒙跨平台Flutter开发:步数统计应用
学习·flutter·华为·开源·harmonyos·鸿蒙
爱宇阳2 小时前
Supabase Self-Hosting with Docker 学习笔记
笔记·学习·docker
朱一头zcy2 小时前
Java基础复习07:异常处理(编译时异常处理、运行时异常处理、try-catch-finally、自定义异常)
java·笔记·异常处理
盟接之桥2 小时前
盟接之桥®说制造:从“制造”到“智造”,以品类品牌重塑制造业的生态未来
大数据·网络·人工智能·学习·制造
迷你可可小生2 小时前
图像视觉面经学习(一)
图像处理·人工智能·python·学习
自信150413057592 小时前
重生之从0开始学习c++之类与对象(中)
c++·学习
AI_零食2 小时前
开源鸿蒙跨平台Flutter开发:快递单号批量查询应用
学习·flutter·华为·开源·harmonyos·鸿蒙
四谎真好看2 小时前
Redis学习笔记(高级篇2)
redis·笔记·学习·学习笔记
鱼鳞_2 小时前
Java学习笔记_Day26(不可变集合)
java·笔记·学习