mirror of
https://github.com/minetest/irrlicht.git
synced 2024-12-28 11:40:27 +01:00
8dbeba57cd
Cameras can render even when they are not in the scenemanager or onAnimate didn't get called for example because they are their parent are invisible. So let's be safe and add another call to updateAbsolutePosition(). Some cost, but usually we don't have that many rendering cameras, so shouldn't matter (if it ever matters I suppose we could override OnAnimate and add a flag if it _was_ animated since last render call. Maybe that's even useful in general for SceneNodes?). Similar CSceneNodeAnimatorCameraFPS was using getAbsolutePosition which was only updated after animators, so it was one frame behind. And documented ICameraSceneNode functions a bit. Especially updateMatrices is a bit of a confusing name unfortunately. git-svn-id: svn://svn.code.sf.net/p/irrlicht/code/trunk@6406 dfc29bdd-3216-0410-991c-e03cc46cb475
420 lines
11 KiB
C++
420 lines
11 KiB
C++
// Copyright (C) 2002-2012 Nikolaus Gebhardt
|
|
// This file is part of the "Irrlicht Engine".
|
|
// For conditions of distribution and use, see copyright notice in irrlicht.h
|
|
|
|
#include "CSceneNodeAnimatorCameraFPS.h"
|
|
#include "IVideoDriver.h"
|
|
#include "ISceneManager.h"
|
|
#include "Keycodes.h"
|
|
#include "ICursorControl.h"
|
|
#include "ICameraSceneNode.h"
|
|
#include "ISceneNodeAnimatorCollisionResponse.h"
|
|
|
|
namespace irr
|
|
{
|
|
namespace scene
|
|
{
|
|
|
|
//! constructor
|
|
CSceneNodeAnimatorCameraFPS::CSceneNodeAnimatorCameraFPS(gui::ICursorControl* cursorControl,
|
|
f32 rotateSpeed, f32 moveSpeed, f32 jumpSpeed,
|
|
SKeyMap* keyMapArray, u32 keyMapSize, bool noVerticalMovement, bool invertY, float rotateSpeedKeyboard)
|
|
: CursorControl(cursorControl),
|
|
// On X11 we get events even when mouse is not inside the Irrlicht window, on Windows we don't.
|
|
// It might be possible to add grabbing on Windows as well in which case this has to be somewhat changed.
|
|
// TODO: I don't know about OSX, but in theory it should be like old Irrlicht 1.8 behavior whatever that was there.
|
|
#ifdef _IRR_COMPILE_WITH_X11_DEVICE_
|
|
GrabMouse(false),
|
|
#else
|
|
GrabMouse(true),
|
|
#endif
|
|
MaxVerticalAngle(88.0f), NoVerticalMovement(noVerticalMovement),
|
|
MoveSpeed(moveSpeed),
|
|
RotateSpeedKeyboard(rotateSpeedKeyboard), RotateSpeed(rotateSpeed),
|
|
JumpSpeed(jumpSpeed),
|
|
MouseYDirection(invertY ? -1.0f : 1.0f),
|
|
LastAnimationTime(0), HadMouseEvent(false), firstUpdate(true), firstInput(true)
|
|
{
|
|
#ifdef _DEBUG
|
|
setDebugName("CCameraSceneNodeAnimatorFPS");
|
|
#endif
|
|
|
|
if (CursorControl)
|
|
CursorControl->grab();
|
|
|
|
allKeysUp();
|
|
|
|
// create key map
|
|
if (!keyMapArray || !keyMapSize)
|
|
{
|
|
// create default key map
|
|
KeyMap.push_back(SKeyMap(EKA_MOVE_FORWARD, irr::KEY_UP));
|
|
KeyMap.push_back(SKeyMap(EKA_MOVE_BACKWARD, irr::KEY_DOWN));
|
|
KeyMap.push_back(SKeyMap(EKA_STRAFE_LEFT, irr::KEY_LEFT));
|
|
KeyMap.push_back(SKeyMap(EKA_STRAFE_RIGHT, irr::KEY_RIGHT));
|
|
KeyMap.push_back(SKeyMap(EKA_JUMP_UP, irr::KEY_KEY_J));
|
|
}
|
|
else
|
|
{
|
|
// create custom key map
|
|
setKeyMap(keyMapArray, keyMapSize);
|
|
}
|
|
}
|
|
|
|
|
|
//! destructor
|
|
CSceneNodeAnimatorCameraFPS::~CSceneNodeAnimatorCameraFPS()
|
|
{
|
|
if (CursorControl)
|
|
CursorControl->drop();
|
|
}
|
|
|
|
|
|
//! It is possible to send mouse and key events to the camera. Most cameras
|
|
//! may ignore this input, but camera scene nodes which are created for
|
|
//! example with scene::ISceneManager::addMayaCameraSceneNode or
|
|
//! scene::ISceneManager::addFPSCameraSceneNode, may want to get this input
|
|
//! for changing their position, look at target or whatever.
|
|
bool CSceneNodeAnimatorCameraFPS::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:
|
|
HadMouseEvent = true;
|
|
return true;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void CSceneNodeAnimatorCameraFPS::animateNode(ISceneNode* node, u32 timeMs)
|
|
{
|
|
if (!node || node->getType() != ESNT_CAMERA)
|
|
return;
|
|
|
|
ICameraSceneNode* camera = static_cast<ICameraSceneNode*>(node);
|
|
|
|
if (firstUpdate)
|
|
{
|
|
if (CursorControl )
|
|
{
|
|
CursorControl->setPosition(0.5f, 0.5f);
|
|
CursorPos = CenterCursor = CursorControl->getRelativePosition(false);
|
|
}
|
|
|
|
LastAnimationTime = timeMs;
|
|
|
|
firstUpdate = false;
|
|
}
|
|
|
|
// If the camera isn't the active camera, and receiving input, then don't process it.
|
|
if(!camera->isInputReceiverEnabled())
|
|
{
|
|
firstInput = true;
|
|
return;
|
|
}
|
|
|
|
if ( firstInput )
|
|
{
|
|
allKeysUp();
|
|
firstInput = false;
|
|
}
|
|
|
|
scene::ISceneManager * smgr = camera->getSceneManager();
|
|
if(smgr && smgr->getActiveCamera() != camera)
|
|
return;
|
|
|
|
// get time
|
|
f32 timeDiff = (f32) ( timeMs - LastAnimationTime );
|
|
LastAnimationTime = timeMs;
|
|
|
|
// Update rotation
|
|
camera->updateAbsolutePosition();
|
|
core::vector3df target = (camera->getTarget() - camera->getAbsolutePosition());
|
|
core::vector3df relativeRotation = target.getHorizontalAngle();
|
|
|
|
if (CursorControl)
|
|
{
|
|
bool reset = false;
|
|
|
|
if ( HadMouseEvent || GrabMouse)
|
|
CursorPos = CursorControl->getRelativePosition();
|
|
|
|
if (CursorPos != CenterCursor)
|
|
{
|
|
relativeRotation.Y -= (CenterCursor.X - CursorPos.X) * RotateSpeed;
|
|
relativeRotation.X -= (CenterCursor.Y - CursorPos.Y) * RotateSpeed * MouseYDirection;
|
|
|
|
reset = true;
|
|
}
|
|
|
|
if ( GrabMouse && !reset)
|
|
{
|
|
// Special case, mouse is whipped outside of window before it can update.
|
|
video::IVideoDriver* driver = smgr->getVideoDriver();
|
|
core::vector2d<u32> mousepos(u32(CursorPos.X), u32(CursorPos.Y));
|
|
core::rect<u32> screenRect(0, 0, driver->getScreenSize().Width, driver->getScreenSize().Height);
|
|
|
|
// Only if we are moving outside quickly.
|
|
reset = !screenRect.isPointInside(mousepos);
|
|
}
|
|
|
|
if(reset)
|
|
{
|
|
CursorControl->setPosition(0.5f, 0.5f);
|
|
CenterCursor = CursorControl->getRelativePosition(false); // often no longer 0.5 due to int/float conversions
|
|
CursorPos = CenterCursor;
|
|
}
|
|
}
|
|
HadMouseEvent = false;
|
|
|
|
// keyboard rotation
|
|
if (CursorKeys[EKA_ROTATE_LEFT])
|
|
relativeRotation.Y -= timeDiff * RotateSpeedKeyboard;
|
|
|
|
if (CursorKeys[EKA_ROTATE_RIGHT])
|
|
relativeRotation.Y += timeDiff * RotateSpeedKeyboard;
|
|
|
|
// 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;
|
|
}
|
|
|
|
// set target
|
|
core::vector3df pos = camera->getPosition();
|
|
target.set(0,0, core::max_(1.f, pos.getLength())); // better float precision than (0,0,1) in target-pos calculation in camera
|
|
core::vector3df movedir(target);
|
|
|
|
core::matrix4 mat;
|
|
mat.setRotationDegrees(core::vector3df(relativeRotation.X, relativeRotation.Y, 0));
|
|
mat.transformVect(target);
|
|
|
|
if (NoVerticalMovement)
|
|
{
|
|
mat.setRotationDegrees(core::vector3df(0, relativeRotation.Y, 0));
|
|
mat.transformVect(movedir);
|
|
}
|
|
else
|
|
{
|
|
movedir = target;
|
|
}
|
|
|
|
movedir.normalize();
|
|
|
|
if (CursorKeys[EKA_MOVE_FORWARD])
|
|
pos += movedir * timeDiff * MoveSpeed;
|
|
|
|
if (CursorKeys[EKA_MOVE_BACKWARD])
|
|
pos -= movedir * timeDiff * MoveSpeed;
|
|
|
|
// strafing
|
|
|
|
core::vector3df strafevect(target);
|
|
strafevect = strafevect.crossProduct(camera->getUpVector());
|
|
|
|
if (NoVerticalMovement)
|
|
strafevect.Y = 0.0f;
|
|
|
|
strafevect.normalize();
|
|
|
|
if (CursorKeys[EKA_STRAFE_LEFT])
|
|
pos += strafevect * timeDiff * MoveSpeed;
|
|
|
|
if (CursorKeys[EKA_STRAFE_RIGHT])
|
|
pos -= strafevect * timeDiff * MoveSpeed;
|
|
|
|
// For jumping, we find the collision response animator attached to our camera
|
|
// and if it's not falling, we tell it to jump.
|
|
if (CursorKeys[EKA_JUMP_UP])
|
|
{
|
|
const ISceneNodeAnimatorList& animators = camera->getAnimators();
|
|
ISceneNodeAnimatorList::ConstIterator it = animators.begin();
|
|
while(it != animators.end())
|
|
{
|
|
if(ESNAT_COLLISION_RESPONSE == (*it)->getType())
|
|
{
|
|
ISceneNodeAnimatorCollisionResponse * collisionResponse =
|
|
static_cast<ISceneNodeAnimatorCollisionResponse *>(*it);
|
|
|
|
if(!collisionResponse->isFalling())
|
|
collisionResponse->jump(JumpSpeed);
|
|
}
|
|
|
|
it++;
|
|
}
|
|
}
|
|
|
|
// write translation
|
|
camera->setPosition(pos);
|
|
|
|
// write right target
|
|
target += pos;
|
|
camera->setTarget(target);
|
|
}
|
|
|
|
|
|
void CSceneNodeAnimatorCameraFPS::allKeysUp()
|
|
{
|
|
for (u32 i=0; i<EKA_COUNT; ++i)
|
|
CursorKeys[i] = false;
|
|
}
|
|
|
|
|
|
//! Sets the rotation speed
|
|
void CSceneNodeAnimatorCameraFPS::setRotateSpeed(f32 speed)
|
|
{
|
|
RotateSpeed = speed;
|
|
}
|
|
|
|
|
|
//! Sets the movement speed
|
|
void CSceneNodeAnimatorCameraFPS::setMoveSpeed(f32 speed)
|
|
{
|
|
MoveSpeed = speed;
|
|
}
|
|
|
|
|
|
//! Gets the rotation speed
|
|
f32 CSceneNodeAnimatorCameraFPS::getRotateSpeed() const
|
|
{
|
|
return RotateSpeed;
|
|
}
|
|
|
|
|
|
// Gets the movement speed
|
|
f32 CSceneNodeAnimatorCameraFPS::getMoveSpeed() const
|
|
{
|
|
return MoveSpeed;
|
|
}
|
|
|
|
//! Sets the keyboard mapping for this animator
|
|
void CSceneNodeAnimatorCameraFPS::setKeyMap(SKeyMap *map, u32 count)
|
|
{
|
|
// clear the keymap
|
|
KeyMap.clear();
|
|
|
|
// add actions
|
|
for (u32 i=0; i<count; ++i)
|
|
{
|
|
KeyMap.push_back(map[i]);
|
|
}
|
|
}
|
|
|
|
void CSceneNodeAnimatorCameraFPS::setKeyMap(const core::array<SKeyMap>& keymap)
|
|
{
|
|
KeyMap=keymap;
|
|
}
|
|
|
|
const core::array<SKeyMap>& CSceneNodeAnimatorCameraFPS::getKeyMap() const
|
|
{
|
|
return KeyMap;
|
|
}
|
|
|
|
|
|
//! Sets whether vertical movement should be allowed.
|
|
void CSceneNodeAnimatorCameraFPS::setVerticalMovement(bool allow)
|
|
{
|
|
NoVerticalMovement = !allow;
|
|
}
|
|
|
|
|
|
//! Sets whether the Y axis of the mouse should be inverted.
|
|
void CSceneNodeAnimatorCameraFPS::setInvertMouse(bool invert)
|
|
{
|
|
if (invert)
|
|
MouseYDirection = -1.0f;
|
|
else
|
|
MouseYDirection = 1.0f;
|
|
}
|
|
|
|
|
|
ISceneNodeAnimator* CSceneNodeAnimatorCameraFPS::createClone(ISceneNode* node, ISceneManager* newManager)
|
|
{
|
|
CSceneNodeAnimatorCameraFPS * newAnimator =
|
|
new CSceneNodeAnimatorCameraFPS(CursorControl, RotateSpeed, MoveSpeed, JumpSpeed,
|
|
0, 0, NoVerticalMovement);
|
|
newAnimator->cloneMembers(this);
|
|
newAnimator->setKeyMap(KeyMap);
|
|
return newAnimator;
|
|
}
|
|
|
|
void CSceneNodeAnimatorCameraFPS::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const
|
|
{
|
|
ISceneNodeAnimator::serializeAttributes(out, options);
|
|
|
|
out->addFloat("MaxVerticalAngle", MaxVerticalAngle);
|
|
out->addBool("NoVerticalMovement", NoVerticalMovement);
|
|
out->addFloat("MoveSpeed", MoveSpeed);
|
|
out->addFloat("RotateSpeedKeyboard", RotateSpeedKeyboard);
|
|
out->addFloat("RotateSpeed", RotateSpeed);
|
|
out->addFloat("JumpSpeed", JumpSpeed);
|
|
out->addFloat("MouseYDirection", MouseYDirection);
|
|
|
|
out->addInt("KeyMapSize", (s32)KeyMap.size());
|
|
for ( u32 i=0; i < KeyMap.size(); ++i )
|
|
{
|
|
core::stringc name("Action");
|
|
name += core::stringc(i);
|
|
out->addInt(name.c_str(), (int)KeyMap[i].Action);
|
|
name = core::stringc("KeyCode") + core::stringc(i);
|
|
out->addInt(name.c_str(), (int)KeyMap[i].KeyCode);
|
|
}
|
|
}
|
|
|
|
void CSceneNodeAnimatorCameraFPS::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options)
|
|
{
|
|
ISceneNodeAnimator::deserializeAttributes(in, options);
|
|
|
|
MaxVerticalAngle = in->getAttributeAsFloat("MaxVerticalAngle", MaxVerticalAngle);
|
|
NoVerticalMovement = in->getAttributeAsBool("NoVerticalMovement", NoVerticalMovement);
|
|
MoveSpeed = in->getAttributeAsFloat("MoveSpeed", MoveSpeed);
|
|
RotateSpeedKeyboard = in->getAttributeAsFloat("RotateSpeedKeyboard", RotateSpeedKeyboard);
|
|
RotateSpeed = in->getAttributeAsFloat("RotateSpeed", RotateSpeed);
|
|
JumpSpeed = in->getAttributeAsFloat("JumpSpeed", JumpSpeed);
|
|
MouseYDirection = in->getAttributeAsFloat("MouseYDirection", MouseYDirection);
|
|
|
|
if ( in->findAttribute("KeyMapSize") )
|
|
{
|
|
KeyMap.clear();
|
|
s32 keyMapSize = in->getAttributeAsInt("KeyMapSize");
|
|
for ( u32 i=0; i < (u32)keyMapSize; ++i )
|
|
{
|
|
SKeyMap keyMapEntry;
|
|
core::stringc name("Action");
|
|
name += core::stringc(i);
|
|
keyMapEntry.Action = static_cast<EKEY_ACTION>(in->getAttributeAsInt(name.c_str()));
|
|
name = core::stringc("KeyCode") + core::stringc(i);
|
|
keyMapEntry.KeyCode = static_cast<EKEY_CODE>(in->getAttributeAsInt(name.c_str()));
|
|
KeyMap.push_back(keyMapEntry);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
} // namespace scene
|
|
} // namespace irr
|
|
|