我要发帖 回复

管理员

735

主题

2万

积分

30

专家分

忠于职守杰出贡献鼓励

兴趣点(最多三项):

建模技术

私信
发表时间 : 2007-11-23 12:48:30 | 浏览 : 2369    评论 : 0
译者:array
本章目标:
上一个教程我们讲解了键盘事件处理器类,它用于注册响应函数。本章提供了用于键盘输入的更方便的方案。我们将重载一个GUIEventHandler类,而不必再创建和注册函数。在这个类中我们将添加新的代码,以便执行特定的键盘和鼠标事件响应动作。我们还将提出一种键盘事件处理器与更新回调通讯的方法。

----------------------------------------------------------------------

问题的提出:
教程08演示了如何将回调与DOF节点相关联,以实现场景中DOF节点位置的持续更新。那么,如果我们希望使用键盘输入来控制场景图形中的节点,应该如何处理呢?例如,如果我们有一个基于位置变换节点的坦克模型,并希望在按下w键的时候控制坦克向前运动,我们需要进行如下一些操作:
1. 读取键盘事件;
2. 保存键盘事件的结果;
3. 在更新回调中响应键盘事件。

解决方案:
第一步:基类osgGA::GUIEventHandler用于定义用户自己的GUI键盘和鼠标事件动作。我们可以从基类派生自己的类并重载其handle方法,以创建自定义的动作。同时还编写accept方法来实现GUIEventHandlerVisitor(OSG 2.0版本中此类已经废弃)的功能。其基本的框架结构如下所示:

class myKeyboardEventHandler : public osgGA::GUIEventHandler
{
public:
virtual bool handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter&);
virtual void accept(osgGA::GUIEventHandlerVisitor& v) { v.visit(*this); };
};

bool myKeyboardEventHandler::handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& aa)
{
switch(ea.getEventType())
{
case(osgGA::GUIEventAdapter::KEYDOWN):
{
switch(ea.getKey())
{
case 'w':
std::cout << " w key pressed" << std::endl;
return false;
break;
default:
return false;
}
}
default:
return false;
}
}

上述类的核心部分就是我们从基类中重载的handle方法。这个方法有两个参数:一个GUIEventAdapter类的实例,用于接收GUI事件;另一个是GUIActionAdapter类的实例,用于生成并向GUI系统发送请求,例如重绘请求和持续更新请求。
我们需要根据第一个参数编写代码以包含更多的事件,例如KEYUP,DOUBLECLICK,DRAG等。如果要处理按下按键的事件,则应针对KEYDOWN这个分支条件来扩展相应的代码。
事件处理函数的返回值与事件处理器列表中当前处理器触发的键盘和鼠标事件相关。如果返回值为true,则系统认为事件已经处理,不再传递给下一个事件处理器。如果返回值为false,则传递给下一个事件处理器,继续执行对事件的响应。

为了“安装”我们的事件处理器,我们需要创建它的实例并添加到osgViewer::Viewer的事件处理器列表。代码如下:

myKeyboardEventHandler* myFirstEventHandler = new myKeyboardEventHandler();
viewer.getEventHandlerList().push_front(myFirstEventHandler);

第二步:到目前为止,我们的键盘处理器还并不完善。它的功能仅仅是在每次按下w键时向控制窗口输出。如果我们希望按下键时可以控制场景图形中的元素,则需要在键盘处理器和更新回调之间建立一个通讯结构。为此,我们将创建一个用于保存键盘状态的类。这个事件处理器类用于记录最近的键盘和鼠标事件状态。而更新回调类也需要建立与键盘处理器类的接口,以实现场景图形的正确更新。现在我们开始创建基本的框架结构。用户可以在此基础上进行自由的扩展。下面的代码是一个类的定义,用于允许键盘事件处理器和更新回调之间通讯。

class tankInputDeviceStateType
{
public:
tankInputDeviceStateType::tankInputDeviceStateType() :
moveFwdRequest(false) {}
bool moveFwdRequest;
};

下一步的工作是确认键盘事件处理器和更新回调都有正确的数据接口。这些数据将封装到tankInputdeviceStateType的实例中。因为我们仅使用一个事件处理器来控制坦克,因此可以在事件处理器中提供指向tankInputDeviceStateType实例的指针。我们将向事件处理器添加一个数据成员(指向tankInputDeviceStateType的实例)。同时我们还会将指针设置为构造函数的输入参量。以上所述的改动,即指向tankInputDeviceStateType实例的指针,以及新的构造函数如下所示:

class myKeyboardEventHandler : public osgGA::GUIEventHandler {
public:
myKeyboardEventHandler(tankInputDeviceStateType* tids)
{
tankInputDeviceState = tids;
}
// ……
protected:
tankInputDeviceStateType* tankInputDeviceState;
};

我们还需要修改handle方法,以实现除了输出到控制台之外更多的功能。我们通过修改标志参量的值,来发送坦克向前运动的请求。

bool myKeyboardEventHandler::handle(const osgGA::GUIEventAdapter& ea,osgGA::GUIActionAdapter& aa)
{
switch(ea.getEventType())
{
case(osgGA::GUIEventAdapter::KEYDOWN):
{
switch(ea.getKey())
{
case 'w':
tankInputDeviceState->moveFwdRequest = true;
return false;
break;
default:
return false;
}
}
default:
return false;
}
}

第三步:用于更新位置的回调类也需要编写键盘状态数据的接口。我们为更新回调添加与上述相同的参数。这其中包括一个指向同一tankInputDeviceStateType实例的指针。类的构造函数则负责将这个指针传递给成员变量。获得指针之后,我们就可以在回调内部使用其数值了。目前的回调只具备使坦克向前运动的代码,前提是用户执行了相应的键盘事件。回调类的内容如下所示:

class updateTankPosCallback : public osg::NodeCallback {
public:
updateTankPosCallback::updateTankPosCallback(tankInputDeviceStateType* tankIDevState)
: rotation(0.0) , tankPos(-15.,0.,0.)
{
tankInputDeviceState = tankIDevState;
}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osg::PositionAttitudeTransform* pat =
dynamic_cast<osg::PositionAttitudeTransform*> (node);
if(pat)
{
if (tankInputDeviceState->moveFwdRequest)
{
tankPos.set(tankPos.x()+.01,0,0);
pat->setPosition(tankPos);
}
}
traverse(node, nv);
}
protected:
osg::Vec3d tankPos;
tankInputDeviceStateType* tankInputDeviceState;
};

现在,键盘和更新回调之间的通讯框架已经基本完成。下一步是创建一个tankInputDeviceStateType的实例。这个实例将作为事件处理器构造函数的参数传入。同时它也是模型位置更新回调类的构造函数参数。当事件处理器添加到视口的事件处理器列表中之后,我们就可以进入仿真循环并执行相应的功能了。

// 定义用于记录键盘事件的类的实例。
tankInputDeviceStateType* tIDevState = new tankInputDeviceStateType;

// 设置坦克的更新回调。
// 其构造函数将传递上面的实例指针作为实参。
tankPAT->setUpdateCallback(new updateTankPosCallback(tIDevState));

// 键盘处理器类的构造函数同样传递上面的实例指针作为实参。
myKeyboardEventHandler* tankEventHandler = new myKeyboardEventHandler(tIDevState);

// 将事件处理器压入处理器列表。
viewer.getEventHandlerList().push_front(tankEventHandler);

// 设置视口并进入仿真循环。
viewer.setSceneData( root );
viewer.realize();

while( !viewer.done() )
{
viewer.frame();
}

完成了!不过用户还有更多可以扩展的地方:例如使坦克在按键松开的时候停止运动,转向,加速等等。
祝编程愉快

[ 本帖最后由 obuil 于 2007-11-23 01:41 PM 编辑 ]

最近VR访客

手机版|VR开发网 统计 津ICP备18009691号
网安备12019202000257

GMT+8, 2021-4-23 11:07 AM

返回顶部