]> git.lizzy.rs Git - irrlicht.git/blob - source/Irrlicht/CSceneNodeAnimatorCameraFPS.cpp
Merging r5975 through r6036 from trunk to ogl-es branch.
[irrlicht.git] / source / Irrlicht / CSceneNodeAnimatorCameraFPS.cpp
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
4 \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
12 #include "os.h"\r
13 \r
14 namespace irr\r
15 {\r
16 namespace scene\r
17 {\r
18 \r
19 //! constructor\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
28 {\r
29         #ifdef _DEBUG\r
30         setDebugName("CCameraSceneNodeAnimatorFPS");\r
31         #endif\r
32 \r
33         if (CursorControl)\r
34                 CursorControl->grab();\r
35 \r
36         allKeysUp();\r
37 \r
38         // create key map\r
39         if (!keyMapArray || !keyMapSize)\r
40         {\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
47         }\r
48         else\r
49         {\r
50                 // create custom key map\r
51                 setKeyMap(keyMapArray, keyMapSize);\r
52         }\r
53 }\r
54 \r
55 \r
56 //! destructor\r
57 CSceneNodeAnimatorCameraFPS::~CSceneNodeAnimatorCameraFPS()\r
58 {\r
59         if (CursorControl)\r
60                 CursorControl->drop();\r
61 }\r
62 \r
63 \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
70 {\r
71         switch(evt.EventType)\r
72         {\r
73         case EET_KEY_INPUT_EVENT:\r
74                 for (u32 i=0; i<KeyMap.size(); ++i)\r
75                 {\r
76                         if (KeyMap[i].KeyCode == evt.KeyInput.Key)\r
77                         {\r
78                                 CursorKeys[KeyMap[i].Action] = evt.KeyInput.PressedDown;\r
79                                 return true;\r
80                         }\r
81                 }\r
82                 break;\r
83 \r
84         case EET_MOUSE_INPUT_EVENT:\r
85                 if ( evt.MouseInput.Event == EMIE_MOUSE_ENTER_CANVAS && CursorControl)\r
86                 {\r
87                         CursorControl->setPosition(0.5f, 0.5f);\r
88                         CenterCursor = CursorControl->getRelativePosition(false);\r
89                         CursorPos = CenterCursor;\r
90                 }\r
91                 break;\r
92 \r
93         default:\r
94                 break;\r
95         }\r
96 \r
97         return false;\r
98 }\r
99 \r
100 \r
101 void CSceneNodeAnimatorCameraFPS::animateNode(ISceneNode* node, u32 timeMs)\r
102 {\r
103         if (!node || node->getType() != ESNT_CAMERA)\r
104                 return;\r
105 \r
106         timeMs = os::Timer::getRealTime(); // User input is always in real-time\r
107 \r
108         ICameraSceneNode* camera = static_cast<ICameraSceneNode*>(node);\r
109 \r
110         if (firstUpdate)\r
111         {\r
112                 camera->updateAbsolutePosition();\r
113                 if (CursorControl )\r
114                 {\r
115                         CursorControl->setPosition(0.5f, 0.5f);\r
116                         CursorPos = CenterCursor = CursorControl->getRelativePosition(false);\r
117                 }\r
118 \r
119                 LastAnimationTime = timeMs;\r
120 \r
121                 firstUpdate = false;\r
122         }\r
123 \r
124         // If the camera isn't the active camera, and receiving input, then don't process it.\r
125         if(!camera->isInputReceiverEnabled())\r
126         {\r
127                 firstInput = true;\r
128                 return;\r
129         }\r
130 \r
131         if ( firstInput )\r
132         {\r
133                 allKeysUp();\r
134                 firstInput = false;\r
135         }\r
136 \r
137         scene::ISceneManager * smgr = camera->getSceneManager();\r
138         if(smgr && smgr->getActiveCamera() != camera)\r
139                 return;\r
140 \r
141         if ( CursorControl )\r
142                 CursorPos = CursorControl->getRelativePosition();\r
143 \r
144         // get time\r
145         f32 timeDiff = (f32) ( timeMs - LastAnimationTime );\r
146         LastAnimationTime = timeMs;\r
147 \r
148         // Update rotation\r
149         core::vector3df target = (camera->getTarget() - camera->getAbsolutePosition());\r
150         core::vector3df relativeRotation = target.getHorizontalAngle();\r
151 \r
152         if (CursorControl)\r
153         {\r
154                 bool reset = false;\r
155 \r
156                 if (CursorPos != CenterCursor)\r
157                 {\r
158                         relativeRotation.Y -= (CenterCursor.X - CursorPos.X) * RotateSpeed;\r
159                         relativeRotation.X -= (CenterCursor.Y - CursorPos.Y) * RotateSpeed * MouseYDirection;\r
160 \r
161                         reset = true;\r
162                 }\r
163 \r
164                 if ( !reset )\r
165                 {\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
169 \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
174 \r
175                         // Only if we are moving outside quickly.\r
176                         reset = !screenRect.isPointInside(mousepos);\r
177                 }\r
178 \r
179                 if(reset)\r
180                 {\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
184                 }\r
185         }\r
186 \r
187         // keyboard rotation\r
188         if (CursorKeys[EKA_ROTATE_LEFT])\r
189                 relativeRotation.Y -= timeDiff * RotateSpeedKeyboard;\r
190 \r
191         if (CursorKeys[EKA_ROTATE_RIGHT])\r
192                 relativeRotation.Y += timeDiff * RotateSpeedKeyboard;\r
193 \r
194         // X < MaxVerticalAngle or X > 360-MaxVerticalAngle\r
195 \r
196         if (relativeRotation.X > MaxVerticalAngle*2 &&\r
197                 relativeRotation.X < 360.0f-MaxVerticalAngle)\r
198         {\r
199                 relativeRotation.X = 360.0f-MaxVerticalAngle;\r
200         }\r
201         else\r
202         if (relativeRotation.X > MaxVerticalAngle &&\r
203                 relativeRotation.X < 360.0f-MaxVerticalAngle)\r
204         {\r
205                 relativeRotation.X = MaxVerticalAngle;\r
206         }\r
207 \r
208         // set target\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
212 \r
213         core::matrix4 mat;\r
214         mat.setRotationDegrees(core::vector3df(relativeRotation.X, relativeRotation.Y, 0));\r
215         mat.transformVect(target);\r
216 \r
217         if (NoVerticalMovement)\r
218         {\r
219                 mat.setRotationDegrees(core::vector3df(0, relativeRotation.Y, 0));\r
220                 mat.transformVect(movedir);\r
221         }\r
222         else\r
223         {\r
224                 movedir = target;\r
225         }\r
226 \r
227         movedir.normalize();\r
228 \r
229         if (CursorKeys[EKA_MOVE_FORWARD])\r
230                 pos += movedir * timeDiff * MoveSpeed;\r
231 \r
232         if (CursorKeys[EKA_MOVE_BACKWARD])\r
233                 pos -= movedir * timeDiff * MoveSpeed;\r
234 \r
235         // strafing\r
236 \r
237         core::vector3df strafevect(target);\r
238         strafevect = strafevect.crossProduct(camera->getUpVector());\r
239 \r
240         if (NoVerticalMovement)\r
241                 strafevect.Y = 0.0f;\r
242 \r
243         strafevect.normalize();\r
244 \r
245         if (CursorKeys[EKA_STRAFE_LEFT])\r
246                 pos += strafevect * timeDiff * MoveSpeed;\r
247 \r
248         if (CursorKeys[EKA_STRAFE_RIGHT])\r
249                 pos -= strafevect * timeDiff * MoveSpeed;\r
250 \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
254         {\r
255                 const ISceneNodeAnimatorList& animators = camera->getAnimators();\r
256                 ISceneNodeAnimatorList::ConstIterator it = animators.begin();\r
257                 while(it != animators.end())\r
258                 {\r
259                         if(ESNAT_COLLISION_RESPONSE == (*it)->getType())\r
260                         {\r
261                                 ISceneNodeAnimatorCollisionResponse * collisionResponse =\r
262                                         static_cast<ISceneNodeAnimatorCollisionResponse *>(*it);\r
263 \r
264                                 if(!collisionResponse->isFalling())\r
265                                         collisionResponse->jump(JumpSpeed);\r
266                         }\r
267 \r
268                         it++;\r
269                 }\r
270         }\r
271 \r
272         // write translation\r
273         camera->setPosition(pos);\r
274 \r
275         // write right target\r
276         target += pos;\r
277         camera->setTarget(target);\r
278 }\r
279 \r
280 void CSceneNodeAnimatorCameraFPS::allKeysUp()\r
281 {\r
282         for (u32 i=0; i<EKA_COUNT; ++i)\r
283                 CursorKeys[i] = false;\r
284 }\r
285 \r
286 \r
287 //! Sets the rotation speed\r
288 void CSceneNodeAnimatorCameraFPS::setRotateSpeed(f32 speed)\r
289 {\r
290         RotateSpeed = speed;\r
291 }\r
292 \r
293 \r
294 //! Sets the movement speed\r
295 void CSceneNodeAnimatorCameraFPS::setMoveSpeed(f32 speed)\r
296 {\r
297         MoveSpeed = speed;\r
298 }\r
299 \r
300 \r
301 //! Gets the rotation speed\r
302 f32 CSceneNodeAnimatorCameraFPS::getRotateSpeed() const\r
303 {\r
304         return RotateSpeed;\r
305 }\r
306 \r
307 \r
308 // Gets the movement speed\r
309 f32 CSceneNodeAnimatorCameraFPS::getMoveSpeed() const\r
310 {\r
311         return MoveSpeed;\r
312 }\r
313 \r
314 //! Sets the keyboard mapping for this animator\r
315 void CSceneNodeAnimatorCameraFPS::setKeyMap(SKeyMap *map, u32 count)\r
316 {\r
317         // clear the keymap\r
318         KeyMap.clear();\r
319 \r
320         // add actions\r
321         for (u32 i=0; i<count; ++i)\r
322         {\r
323                 KeyMap.push_back(map[i]);\r
324         }\r
325 }\r
326 \r
327 void CSceneNodeAnimatorCameraFPS::setKeyMap(const core::array<SKeyMap>& keymap)\r
328 {\r
329         KeyMap=keymap;\r
330 }\r
331 \r
332 const core::array<SKeyMap>& CSceneNodeAnimatorCameraFPS::getKeyMap() const\r
333 {\r
334         return KeyMap;\r
335 }\r
336 \r
337 \r
338 //! Sets whether vertical movement should be allowed.\r
339 void CSceneNodeAnimatorCameraFPS::setVerticalMovement(bool allow)\r
340 {\r
341         NoVerticalMovement = !allow;\r
342 }\r
343 \r
344 \r
345 //! Sets whether the Y axis of the mouse should be inverted.\r
346 void CSceneNodeAnimatorCameraFPS::setInvertMouse(bool invert)\r
347 {\r
348         if (invert)\r
349                 MouseYDirection = -1.0f;\r
350         else\r
351                 MouseYDirection = 1.0f;\r
352 }\r
353 \r
354 \r
355 ISceneNodeAnimator* CSceneNodeAnimatorCameraFPS::createClone(ISceneNode* node, ISceneManager* newManager)\r
356 {\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
363 }\r
364 \r
365 void CSceneNodeAnimatorCameraFPS::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const\r
366 {\r
367         ISceneNodeAnimator::serializeAttributes(out, options);\r
368 \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
376 \r
377         out->addInt("KeyMapSize", (s32)KeyMap.size());\r
378         for ( u32 i=0; i < KeyMap.size(); ++i )\r
379         {\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
385         }\r
386 }\r
387 \r
388 void CSceneNodeAnimatorCameraFPS::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options)\r
389 {\r
390         ISceneNodeAnimator::deserializeAttributes(in, options);\r
391 \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
399 \r
400         if ( in->findAttribute("KeyMapSize") )\r
401         {\r
402                 KeyMap.clear();\r
403                 s32 keyMapSize = in->getAttributeAsInt("KeyMapSize");\r
404                 for ( u32 i=0; i < (u32)keyMapSize; ++i )\r
405                 {\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
413                 }\r
414         }\r
415 }\r
416 \r
417 \r
418 } // namespace scene\r
419 } // namespace irr\r
420 \r