【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 是为了接收事件;
矩阵相乘是为了叠加变换实现连续移动;
接收事件和修改坐标是两个独立逻辑,必须分开设计!

相关推荐
遇乐的果园1 小时前
前端学习笔记-vue加载渲染优化
前端·笔记·学习
遇乐的果园2 小时前
前端学习笔记-vue状态管理优化
前端·笔记·学习
从零开始的代码生活_3 小时前
C++ 继承详解:访问控制、对象模型、菱形继承与设计取舍
开发语言·c++·后端·学习·算法
可乐奶茶sky3 小时前
AI Agent 学习
人工智能·学习
摇滚侠3 小时前
Java 全栈开发实战教程 课程笔记 29-33
笔记
茯苓gao4 小时前
嵌入式开发笔记:EtherCAT协议从硬件到软件完整配置指南——从零搭建一套EtherCAT通信系统
笔记·嵌入式硬件·学习
whyTeaFo4 小时前
GAMES101: Lecture 9: Shading 3(Texture Mapping cont.) ppt笔记
笔记
RD_daoyi5 小时前
外链权重暴跌至13%,品牌提及反超传统链接——2026年不做Digital PR,你的独立站等于隐形
运维·网络·学习·机器学习·搜索引擎
chase。6 小时前
【学习笔记】PointWorld:迈向通用机器人操控的3D世界模型
笔记·学习·机器人
AOwhisky6 小时前
Python 学习笔记(第十三期)——运维自动化(下·前篇):远程命令执行——paramiko基础篇
运维·python·学习·云原生·自动化·运维开发·paramiko