1 // Copyright (C) 2002-2012 Nikolaus Gebhardt
\r
2 // This file is part of the "Irrlicht Engine".
\r
3 // For conditions of distribution and use, see copyright notice in irrlicht.h
\r
5 #include "CSceneNodeAnimatorCameraFPS.h"
\r
6 #include "IVideoDriver.h"
\r
7 #include "ISceneManager.h"
\r
8 #include "Keycodes.h"
\r
9 #include "ICursorControl.h"
\r
10 #include "ICameraSceneNode.h"
\r
11 #include "ISceneNodeAnimatorCollisionResponse.h"
\r
20 CSceneNodeAnimatorCameraFPS::CSceneNodeAnimatorCameraFPS(gui::ICursorControl* cursorControl,
\r
21 f32 rotateSpeed, f32 moveSpeed, f32 jumpSpeed,
\r
22 SKeyMap* keyMapArray, u32 keyMapSize, bool noVerticalMovement, bool invertY, float rotateSpeedKeyboard)
\r
23 : CursorControl(cursorControl), MaxVerticalAngle(88.0f), NoVerticalMovement(noVerticalMovement),
\r
24 MoveSpeed(moveSpeed), RotateSpeedKeyboard(rotateSpeedKeyboard), RotateSpeed(rotateSpeed),
\r
25 JumpSpeed(jumpSpeed),
\r
26 MouseYDirection(invertY ? -1.0f : 1.0f),
\r
27 LastAnimationTime(0), firstUpdate(true), firstInput(true)
\r
30 setDebugName("CCameraSceneNodeAnimatorFPS");
\r
34 CursorControl->grab();
\r
39 if (!keyMapArray || !keyMapSize)
\r
41 // create default key map
\r
42 KeyMap.push_back(SKeyMap(EKA_MOVE_FORWARD, irr::KEY_UP));
\r
43 KeyMap.push_back(SKeyMap(EKA_MOVE_BACKWARD, irr::KEY_DOWN));
\r
44 KeyMap.push_back(SKeyMap(EKA_STRAFE_LEFT, irr::KEY_LEFT));
\r
45 KeyMap.push_back(SKeyMap(EKA_STRAFE_RIGHT, irr::KEY_RIGHT));
\r
46 KeyMap.push_back(SKeyMap(EKA_JUMP_UP, irr::KEY_KEY_J));
\r
50 // create custom key map
\r
51 setKeyMap(keyMapArray, keyMapSize);
\r
57 CSceneNodeAnimatorCameraFPS::~CSceneNodeAnimatorCameraFPS()
\r
60 CursorControl->drop();
\r
64 //! It is possible to send mouse and key events to the camera. Most cameras
\r
65 //! may ignore this input, but camera scene nodes which are created for
\r
66 //! example with scene::ISceneManager::addMayaCameraSceneNode or
\r
67 //! scene::ISceneManager::addFPSCameraSceneNode, may want to get this input
\r
68 //! for changing their position, look at target or whatever.
\r
69 bool CSceneNodeAnimatorCameraFPS::OnEvent(const SEvent& evt)
\r
71 switch(evt.EventType)
\r
73 case EET_KEY_INPUT_EVENT:
\r
74 for (u32 i=0; i<KeyMap.size(); ++i)
\r
76 if (KeyMap[i].KeyCode == evt.KeyInput.Key)
\r
78 CursorKeys[KeyMap[i].Action] = evt.KeyInput.PressedDown;
\r
84 case EET_MOUSE_INPUT_EVENT:
\r
85 if ( evt.MouseInput.Event == EMIE_MOUSE_ENTER_CANVAS && CursorControl)
\r
87 CursorControl->setPosition(0.5f, 0.5f);
\r
88 CenterCursor = CursorControl->getRelativePosition(false);
\r
89 CursorPos = CenterCursor;
\r
101 void CSceneNodeAnimatorCameraFPS::animateNode(ISceneNode* node, u32 timeMs)
\r
103 if (!node || node->getType() != ESNT_CAMERA)
\r
106 timeMs = os::Timer::getRealTime(); // User input is always in real-time
\r
108 ICameraSceneNode* camera = static_cast<ICameraSceneNode*>(node);
\r
112 camera->updateAbsolutePosition();
\r
113 if (CursorControl )
\r
115 CursorControl->setPosition(0.5f, 0.5f);
\r
116 CursorPos = CenterCursor = CursorControl->getRelativePosition(false);
\r
119 LastAnimationTime = timeMs;
\r
121 firstUpdate = false;
\r
124 // If the camera isn't the active camera, and receiving input, then don't process it.
\r
125 if(!camera->isInputReceiverEnabled())
\r
134 firstInput = false;
\r
137 scene::ISceneManager * smgr = camera->getSceneManager();
\r
138 if(smgr && smgr->getActiveCamera() != camera)
\r
141 if ( CursorControl )
\r
142 CursorPos = CursorControl->getRelativePosition();
\r
145 f32 timeDiff = (f32) ( timeMs - LastAnimationTime );
\r
146 LastAnimationTime = timeMs;
\r
149 core::vector3df target = (camera->getTarget() - camera->getAbsolutePosition());
\r
150 core::vector3df relativeRotation = target.getHorizontalAngle();
\r
154 bool reset = false;
\r
156 if (CursorPos != CenterCursor)
\r
158 relativeRotation.Y -= (CenterCursor.X - CursorPos.X) * RotateSpeed;
\r
159 relativeRotation.X -= (CenterCursor.Y - CursorPos.Y) * RotateSpeed * MouseYDirection;
\r
166 // TODO: not sure if this case is still needed. Might be it was only something
\r
167 // that was necessary when someone tried to use mouse-events in the past.
\r
168 // But not too expensive, test on all platforms before removing.
\r
170 // Special case, mouse is whipped outside of window before it can update.
\r
171 video::IVideoDriver* driver = smgr->getVideoDriver();
\r
172 core::vector2d<u32> mousepos(u32(CursorPos.X), u32(CursorPos.Y));
\r
173 core::rect<u32> screenRect(0, 0, driver->getScreenSize().Width, driver->getScreenSize().Height);
\r
175 // Only if we are moving outside quickly.
\r
176 reset = !screenRect.isPointInside(mousepos);
\r
181 CursorControl->setPosition(0.5f, 0.5f);
\r
182 CenterCursor = CursorControl->getRelativePosition(false); // often no longer 0.5 due to int/float conversions
\r
183 CursorPos = CenterCursor;
\r
187 // keyboard rotation
\r
188 if (CursorKeys[EKA_ROTATE_LEFT])
\r
189 relativeRotation.Y -= timeDiff * RotateSpeedKeyboard;
\r
191 if (CursorKeys[EKA_ROTATE_RIGHT])
\r
192 relativeRotation.Y += timeDiff * RotateSpeedKeyboard;
\r
194 // X < MaxVerticalAngle or X > 360-MaxVerticalAngle
\r
196 if (relativeRotation.X > MaxVerticalAngle*2 &&
\r
197 relativeRotation.X < 360.0f-MaxVerticalAngle)
\r
199 relativeRotation.X = 360.0f-MaxVerticalAngle;
\r
202 if (relativeRotation.X > MaxVerticalAngle &&
\r
203 relativeRotation.X < 360.0f-MaxVerticalAngle)
\r
205 relativeRotation.X = MaxVerticalAngle;
\r
209 core::vector3df pos = camera->getPosition();
\r
210 target.set(0,0, core::max_(1.f, pos.getLength())); // better float precision than (0,0,1) in target-pos calculation in camera
\r
211 core::vector3df movedir(target);
\r
214 mat.setRotationDegrees(core::vector3df(relativeRotation.X, relativeRotation.Y, 0));
\r
215 mat.transformVect(target);
\r
217 if (NoVerticalMovement)
\r
219 mat.setRotationDegrees(core::vector3df(0, relativeRotation.Y, 0));
\r
220 mat.transformVect(movedir);
\r
227 movedir.normalize();
\r
229 if (CursorKeys[EKA_MOVE_FORWARD])
\r
230 pos += movedir * timeDiff * MoveSpeed;
\r
232 if (CursorKeys[EKA_MOVE_BACKWARD])
\r
233 pos -= movedir * timeDiff * MoveSpeed;
\r
237 core::vector3df strafevect(target);
\r
238 strafevect = strafevect.crossProduct(camera->getUpVector());
\r
240 if (NoVerticalMovement)
\r
241 strafevect.Y = 0.0f;
\r
243 strafevect.normalize();
\r
245 if (CursorKeys[EKA_STRAFE_LEFT])
\r
246 pos += strafevect * timeDiff * MoveSpeed;
\r
248 if (CursorKeys[EKA_STRAFE_RIGHT])
\r
249 pos -= strafevect * timeDiff * MoveSpeed;
\r
251 // For jumping, we find the collision response animator attached to our camera
\r
252 // and if it's not falling, we tell it to jump.
\r
253 if (CursorKeys[EKA_JUMP_UP])
\r
255 const ISceneNodeAnimatorList& animators = camera->getAnimators();
\r
256 ISceneNodeAnimatorList::ConstIterator it = animators.begin();
\r
257 while(it != animators.end())
\r
259 if(ESNAT_COLLISION_RESPONSE == (*it)->getType())
\r
261 ISceneNodeAnimatorCollisionResponse * collisionResponse =
\r
262 static_cast<ISceneNodeAnimatorCollisionResponse *>(*it);
\r
264 if(!collisionResponse->isFalling())
\r
265 collisionResponse->jump(JumpSpeed);
\r
272 // write translation
\r
273 camera->setPosition(pos);
\r
275 // write right target
\r
277 camera->setTarget(target);
\r
280 void CSceneNodeAnimatorCameraFPS::allKeysUp()
\r
282 for (u32 i=0; i<EKA_COUNT; ++i)
\r
283 CursorKeys[i] = false;
\r
287 //! Sets the rotation speed
\r
288 void CSceneNodeAnimatorCameraFPS::setRotateSpeed(f32 speed)
\r
290 RotateSpeed = speed;
\r
294 //! Sets the movement speed
\r
295 void CSceneNodeAnimatorCameraFPS::setMoveSpeed(f32 speed)
\r
301 //! Gets the rotation speed
\r
302 f32 CSceneNodeAnimatorCameraFPS::getRotateSpeed() const
\r
304 return RotateSpeed;
\r
308 // Gets the movement speed
\r
309 f32 CSceneNodeAnimatorCameraFPS::getMoveSpeed() const
\r
314 //! Sets the keyboard mapping for this animator
\r
315 void CSceneNodeAnimatorCameraFPS::setKeyMap(SKeyMap *map, u32 count)
\r
317 // clear the keymap
\r
321 for (u32 i=0; i<count; ++i)
\r
323 KeyMap.push_back(map[i]);
\r
327 void CSceneNodeAnimatorCameraFPS::setKeyMap(const core::array<SKeyMap>& keymap)
\r
332 const core::array<SKeyMap>& CSceneNodeAnimatorCameraFPS::getKeyMap() const
\r
338 //! Sets whether vertical movement should be allowed.
\r
339 void CSceneNodeAnimatorCameraFPS::setVerticalMovement(bool allow)
\r
341 NoVerticalMovement = !allow;
\r
345 //! Sets whether the Y axis of the mouse should be inverted.
\r
346 void CSceneNodeAnimatorCameraFPS::setInvertMouse(bool invert)
\r
349 MouseYDirection = -1.0f;
\r
351 MouseYDirection = 1.0f;
\r
355 ISceneNodeAnimator* CSceneNodeAnimatorCameraFPS::createClone(ISceneNode* node, ISceneManager* newManager)
\r
357 CSceneNodeAnimatorCameraFPS * newAnimator =
\r
358 new CSceneNodeAnimatorCameraFPS(CursorControl, RotateSpeed, MoveSpeed, JumpSpeed,
\r
359 0, 0, NoVerticalMovement);
\r
360 newAnimator->cloneMembers(this);
\r
361 newAnimator->setKeyMap(KeyMap);
\r
362 return newAnimator;
\r
365 void CSceneNodeAnimatorCameraFPS::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const
\r
367 ISceneNodeAnimator::serializeAttributes(out, options);
\r
369 out->addFloat("MaxVerticalAngle", MaxVerticalAngle);
\r
370 out->addBool("NoVerticalMovement", NoVerticalMovement);
\r
371 out->addFloat("MoveSpeed", MoveSpeed);
\r
372 out->addFloat("RotateSpeedKeyboard", RotateSpeedKeyboard);
\r
373 out->addFloat("RotateSpeed", RotateSpeed);
\r
374 out->addFloat("JumpSpeed", JumpSpeed);
\r
375 out->addFloat("MouseYDirection", MouseYDirection);
\r
377 out->addInt("KeyMapSize", (s32)KeyMap.size());
\r
378 for ( u32 i=0; i < KeyMap.size(); ++i )
\r
380 core::stringc name("Action");
\r
381 name += core::stringc(i);
\r
382 out->addInt(name.c_str(), (int)KeyMap[i].Action);
\r
383 name = core::stringc("KeyCode") + core::stringc(i);
\r
384 out->addInt(name.c_str(), (int)KeyMap[i].KeyCode);
\r
388 void CSceneNodeAnimatorCameraFPS::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options)
\r
390 ISceneNodeAnimator::deserializeAttributes(in, options);
\r
392 MaxVerticalAngle = in->getAttributeAsFloat("MaxVerticalAngle", MaxVerticalAngle);
\r
393 NoVerticalMovement = in->getAttributeAsBool("NoVerticalMovement", NoVerticalMovement);
\r
394 MoveSpeed = in->getAttributeAsFloat("MoveSpeed", MoveSpeed);
\r
395 RotateSpeedKeyboard = in->getAttributeAsFloat("RotateSpeedKeyboard", RotateSpeedKeyboard);
\r
396 RotateSpeed = in->getAttributeAsFloat("RotateSpeed", RotateSpeed);
\r
397 JumpSpeed = in->getAttributeAsFloat("JumpSpeed", JumpSpeed);
\r
398 MouseYDirection = in->getAttributeAsFloat("MouseYDirection", MouseYDirection);
\r
400 if ( in->findAttribute("KeyMapSize") )
\r
403 s32 keyMapSize = in->getAttributeAsInt("KeyMapSize");
\r
404 for ( u32 i=0; i < (u32)keyMapSize; ++i )
\r
406 SKeyMap keyMapEntry;
\r
407 core::stringc name("Action");
\r
408 name += core::stringc(i);
\r
409 keyMapEntry.Action = static_cast<EKEY_ACTION>(in->getAttributeAsInt(name.c_str()));
\r
410 name = core::stringc("KeyCode") + core::stringc(i);
\r
411 keyMapEntry.KeyCode = static_cast<EKEY_CODE>(in->getAttributeAsInt(name.c_str()));
\r
412 KeyMap.push_back(keyMapEntry);
\r
418 } // namespace scene
\r