Arbitrary Scene Node FPS Animator
Posted: Wed Aug 05, 2009 5:31 pm
This is a class I wrote to control an arbitrary scene node using FPS-style controls. It's basically a rip-off of the animator for the FPS camera.
Here's a screenshot to help you understand my motivation:
Look in the bottom right split screen. Basically the little red-green-blue thing has three orthogonal cameras attached to it. I wanted to control that object with the FPS style controls, not any particular one of the cameras. The other three parts of the split screen are the view from the three cameras on the character.
Anyway, the code is below, comments are appreciated.
CSceneNodeAnimatorFPS.h
CSceneNodeAnimator.cpp
Here's a screenshot to help you understand my motivation:
Look in the bottom right split screen. Basically the little red-green-blue thing has three orthogonal cameras attached to it. I wanted to control that object with the FPS style controls, not any particular one of the cameras. The other three parts of the split screen are the view from the three cameras on the character.
Anyway, the code is below, comments are appreciated.
CSceneNodeAnimatorFPS.h
Code: Select all
#ifndef __C_SCENE_NODE_ANIMATOR_FPS_H_INCLUDED__
#define __C_SCENE_NODE_ANIMATOR_FPS_H_INCLUDED__
#include "ISceneNodeAnimatorFPS.h"
#include "vector2d.h"
#include "SKeyMap.h"
#include "irrArray.h"
namespace irr
{
namespace gui
{
class ICursorControl;
}
namespace scene
{
/**
* \brief Provides first-person like controls for an arbitrary scene
* node
*/
class CSceneNodeAnimatorFPS : public ISceneNodeAnimatorFPS
{
public:
/**
* \brief Creates a new animator
* \param smgr the scene manager managing this
* animator's node
* \param cursorControl the cursor control object
* \param rotateSpeed responsiveness of the mouse
* \param moveSpeed how far to move per millisecond
* \param jumpSpeed
* \param keyMapArray array of key inputs to listen for
* \param keyMapSize size of the above
* \param noVerticalMovement boolean indicating vertical movement
* is not allowed
* @return
*/
CSceneNodeAnimatorFPS( ISceneManager *smgr,
gui::ICursorControl* cursorControl,
f32 rotateSpeed = 100.0f,
f32 moveSpeed = 0.01f,
f32 jumpSpeed = 0.f,
SKeyMap* keyMapArray = 0,
u32 keyMapSize = 0,
bool noVerticalMovement = false);
/**
* \brief
*/
virtual ~CSceneNodeAnimatorFPS();
/**
* \brief updates the indicated scene node based on registered
* mouse and keyboard inputs
* \param node the node to update
* \param timeMs the time segment over which the update occured
*/
virtual void animateNode(ISceneNode* node, u32 timeMs);
/**
* \brief is called by the event handler when an input event
* occurs
* \param event the event that occured
* \return true if the function handles the event and it should not
* be propagted to other handlers
*/
virtual bool OnEvent(const SEvent& event);
/**
* \brief
*/
virtual f32 getMoveSpeed() const;
/**
* \brief Sets the speed of movement in units per second
* \param moveSpeed units per second
*/
virtual void setMoveSpeed(f32 moveSpeed);
/**
* \brief Returns the rotation speed
*/
virtual f32 getRotateSpeed() const;
/**
* \brief Set the rotation speed
*/
virtual void setRotateSpeed(f32 rotateSpeed);
/**
* \brief Sets the keyboard mapping for this animator
* \param keymap an array of keyboard mappings, see SKeyMap
* \param count the size of the keyboard map array
*/
virtual void setKeyMap(SKeyMap *map, u32 count);
/**
* \brief Sets whether vertical movement should be allowed.
*/
virtual void setVerticalMovement(bool allow);
/**
* \brief This animator will receive events when attached to the
* active camera
* \return true if this animator will receive events
*/
virtual bool isEventReceiverEnabled() const
{
return true;
}
/**
* \brief
* \return
*/
virtual ESCENE_NODE_ANIMATOR_TYPE getType() const
{
return ESNAT_CAMERA_FPS;
}
/**
* \brief Creates a clone of this animator.
*
* Please note that you will have to drop
* (IReferenceCounted::drop()) the returned pointer after calling
* this.
*
* \return a pointer to the newly created objec that is a clone of
* this one
*/
virtual ISceneNodeAnimator* createClone(ISceneNode* node, ISceneManager* newManager=0);
/**
* \brief a container for a pairing of an action with a key code
*/
struct SCamKeyMap
{
SCamKeyMap() {};
SCamKeyMap(s32 a, EKEY_CODE k) : action(a), keycode(k) {}
s32 action;
EKEY_CODE keycode;
};
/**
* \brief Sets the keyboard mapping for this animator
*
* Helper function for the clone method.
*
* \param keymap the new keymap array
*/
void setKeyMap(const core::array<SCamKeyMap>& keymap);
private:
/**
* \brief sets the state of all keys to "up"
*/
void allKeysUp();
scene::ISceneManager* smgr; ///< the scene manager
gui::ICursorControl* CursorControl; ///< the mouse controller
f32 MaxVerticalAngle; ///< the maximimum angle for looking up
f32 MoveSpeed; ///< number of units to move while key press
f32 RotateSpeed; ///< how far to turn when mouse is moved
f32 JumpSpeed; ///< how far to move up when jump is pressed
s32 LastAnimationTime; ///< the timestamp of the last step through
/// the animate function
core::vector3df TargetVector;
core::array<SCamKeyMap> KeyMap;
core::position2d<f32> CenterCursor, CursorPos;
bool CursorKeys[6]; ///< true if the key at index [i] is dn
bool firstUpdate; ///< true if this is the furst run
bool NoVerticalMovement; ///< if true, no vertical movement is
/// allowed
};
} // end namespace scene
} // end namespace irr
#endif // __C_SCENE_NODE_ANIMATOR_FPS_H_INCLUDED__
CSceneNodeAnimator.cpp
Code: Select all
// Copyright (C) 2002-2008 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "CSceneNodeAnimatorFPS.h"
#include "IVideoDriver.h"
#include "ISceneManager.h"
#include "Keycodes.h"
#include "ICursorControl.h"
#include "ICameraSceneNode.h"
#include "ISceneNodeAnimatorCollisionResponse.h"
#include <iostream>
namespace irr
{
namespace scene
{
CSceneNodeAnimatorFPS::CSceneNodeAnimatorFPS( ISceneManager *smgr,
gui::ICursorControl* cursorControl,
f32 rotateSpeed, f32 moveSpeed, f32 jumpSpeed,
SKeyMap* keyMapArray, u32 keyMapSize, bool noVerticalMovement)
: smgr(smgr), CursorControl(cursorControl), MaxVerticalAngle(88.0f),
MoveSpeed(moveSpeed), RotateSpeed(rotateSpeed), JumpSpeed(jumpSpeed),
LastAnimationTime(0), firstUpdate(true), NoVerticalMovement(noVerticalMovement)
{
#ifdef _DEBUG
setDebugName("CSceneNodeAnimatorFPS");
#endif
if (CursorControl)
CursorControl->grab();
if (smgr)
smgr->grab();
allKeysUp();
// create key map
if (!keyMapArray || !keyMapSize)
{
// create default key map
KeyMap.push_back(SCamKeyMap(EKA_MOVE_FORWARD, irr::KEY_KEY_W));
KeyMap.push_back(SCamKeyMap(EKA_MOVE_BACKWARD, irr::KEY_KEY_S));
KeyMap.push_back(SCamKeyMap(EKA_STRAFE_LEFT, irr::KEY_KEY_A));
KeyMap.push_back(SCamKeyMap(EKA_STRAFE_RIGHT, irr::KEY_KEY_D));
KeyMap.push_back(SCamKeyMap(EKA_JUMP_UP, irr::KEY_SPACE));
}
else
{
// create custom key map
setKeyMap(keyMapArray, keyMapSize);
}
}
CSceneNodeAnimatorFPS::~CSceneNodeAnimatorFPS()
{
if (CursorControl)
CursorControl->drop();
if(smgr)
smgr->drop();
}
/**
* Records the key state and mouse position at the time of the event
*/
bool CSceneNodeAnimatorFPS::OnEvent(const SEvent& evt)
{
switch(evt.EventType)
{
case EET_KEY_INPUT_EVENT:
for (u32 i=0; i<KeyMap.size(); ++i)
{
if (KeyMap[i].keycode == evt.KeyInput.Key)
{
CursorKeys[KeyMap[i].action] = evt.KeyInput.PressedDown;
return true;
}
}
break;
case EET_MOUSE_INPUT_EVENT:
if (evt.MouseInput.Event == EMIE_MOUSE_MOVED)
{
CursorPos = CursorControl->getRelativePosition();
return true;
}
break;
default:
break;
}
return false;
}
void CSceneNodeAnimatorFPS::animateNode(ISceneNode* node, u32 timeMs)
{
if (firstUpdate)
{
node->updateAbsolutePosition();
if (CursorControl && node)
{
CursorControl->setPosition(0.5f, 0.5f);
CursorPos = CenterCursor = CursorControl->getRelativePosition();
}
LastAnimationTime = timeMs;
firstUpdate = false;
}
// get time
f32 timeDiff = (f32) ( timeMs - LastAnimationTime );
LastAnimationTime = timeMs;
// get current position and rotation relative to the parent node
core::vector3df currentPos = node->getPosition();
core::vector3df relativeRotation = node->getRotation();
// calculate the incremental rotation as proportional to the distance the
// mouse cursor was away from the center of the screen
if (CursorControl)
{
if (CursorPos != CenterCursor)
{
relativeRotation.Y -= (0.5f - CursorPos.X) * RotateSpeed;
relativeRotation.X -= (0.5f - CursorPos.Y) * RotateSpeed;
// X < MaxVerticalAngle or X > 360-MaxVerticalAngle
if (relativeRotation.X > MaxVerticalAngle*2 &&
relativeRotation.X < 360.0f-MaxVerticalAngle)
{
relativeRotation.X = 360.0f-MaxVerticalAngle;
}
else
if (relativeRotation.X > MaxVerticalAngle &&
relativeRotation.X < 360.0f-MaxVerticalAngle)
{
relativeRotation.X = MaxVerticalAngle;
}
// reset cursor position
CursorControl->setPosition(0.5f, 0.5f);
CenterCursor = CursorControl->getRelativePosition();
// needed to avoid problems when the event receiver is
// disabled
CursorPos = CenterCursor;
}
}
// Since a FPS control works by changing the position of the character
// with respect to his local frame, we calculate here the change in position
// of the node in his local frame
float deltaZ = 0;
if( CursorKeys[EKA_MOVE_FORWARD] )
deltaZ += timeDiff * MoveSpeed;
if( CursorKeys[EKA_MOVE_BACKWARD])
deltaZ -= timeDiff * MoveSpeed;
float deltaX = 0;
if( CursorKeys[EKA_STRAFE_RIGHT])
deltaX += timeDiff * MoveSpeed;
if( CursorKeys[EKA_STRAFE_LEFT])
deltaX -= timeDiff * MoveSpeed;
// and then we put a new empty node in that position, by making it a child
// of the node we are animating, and putting it at the relative position
// we just calculated
scene::ISceneNode *target = smgr->addEmptySceneNode(node);
target->setPosition(core::vector3df(deltaX,0,deltaZ));
// then we calculate the global position of this empty node
target->updateAbsolutePosition();
// then we can calculate the incremental positino change of the node we
// are animating by subtracting the global position of the node we are
// animating from the global position of that empty node we just created
core::vector3df deltaPos = target->getAbsolutePosition() -
node->getAbsolutePosition();
// then we can tell the animated node exactly how far to move
node->setPosition(node->getPosition() + deltaPos);
// and the rotation is just set with the new relative rotation
node->setRotation(relativeRotation);
// and we can now remove that empty scene node since it was just a temporary
// way for us to easily do the linear algebra
target->remove();
}
void CSceneNodeAnimatorFPS::allKeysUp()
{
for (u32 i=0; i<6; ++i)
CursorKeys[i] = false;
}
void CSceneNodeAnimatorFPS::setRotateSpeed(f32 speed)
{
RotateSpeed = speed;
}
void CSceneNodeAnimatorFPS::setMoveSpeed(f32 speed)
{
MoveSpeed = speed;
}
f32 CSceneNodeAnimatorFPS::getRotateSpeed() const
{
return RotateSpeed;
}
f32 CSceneNodeAnimatorFPS::getMoveSpeed() const
{
return MoveSpeed;
}
void CSceneNodeAnimatorFPS::setKeyMap(SKeyMap *map, u32 count)
{
// clear the keymap
KeyMap.clear();
// add actions
for (u32 i=0; i<count; ++i)
{
switch(map[i].Action)
{
case EKA_MOVE_FORWARD: KeyMap.push_back(SCamKeyMap(EKA_MOVE_FORWARD, map[i].KeyCode));
break;
case EKA_MOVE_BACKWARD: KeyMap.push_back(SCamKeyMap(EKA_MOVE_BACKWARD, map[i].KeyCode));
break;
case EKA_STRAFE_LEFT: KeyMap.push_back(SCamKeyMap(EKA_STRAFE_LEFT, map[i].KeyCode));
break;
case EKA_STRAFE_RIGHT: KeyMap.push_back(SCamKeyMap(EKA_STRAFE_RIGHT, map[i].KeyCode));
break;
case EKA_JUMP_UP: KeyMap.push_back(SCamKeyMap(EKA_JUMP_UP, map[i].KeyCode));
break;
default:
break;
}
}
}
void CSceneNodeAnimatorFPS::setVerticalMovement(bool allow)
{
NoVerticalMovement = !allow;
}
ISceneNodeAnimator* CSceneNodeAnimatorFPS::createClone(ISceneNode* node, ISceneManager* newManager)
{
CSceneNodeAnimatorFPS * newAnimator =
new CSceneNodeAnimatorFPS(smgr, CursorControl, RotateSpeed, MoveSpeed,
JumpSpeed, 0, 0, NoVerticalMovement);
newAnimator->setKeyMap(KeyMap);
return newAnimator;
}
void CSceneNodeAnimatorFPS::setKeyMap(const core::array<SCamKeyMap>& keymap)
{
KeyMap=keymap;
}
} // namespace scene
} // namespace irr