FPS animator cursor offset

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
Seven
Posts: 1030
Joined: Mon Nov 14, 2005 2:03 pm

FPS animator cursor offset

Post by Seven »

an issue I ran into with the FPS camera is that I dont always use the entire window as my rendering viewport and the FPS animator assumes that I do. In my case, I render the scene inside a custom gui window that is resizable independent of the main window. In order to have the FPS animator work correctly, I need to know the center of the rendering viewport, not the center of the main window.

in the FPS animator, the cursor position is always rest to 0.5,0.5 (center of the window). in my case, the rendering area may be only a small area let's say rect(30,30,300,300) for example. To map the cursor position correctly, I use the following code which adds two variables (m_CursorOffsetX and m_CursorOffsetY), adds a function to set those variables each frame if needed (void setCursorOffset(float x, float y)) and modifies the void CSSceneNodeAnimatorFPS::animateNode(ISceneNode* node, u32 timeMs) function to utilize the offsets instead of assuming 0.5,0.5 for the center of the screen.

I still need to tweak it (for example it checks the entire window for mouse quick moves) but it works for now.

it is used like this in my case

Code: Select all

 
        virtual bool setLevel(CSLevel* level)
        {
            CS_INIT(m_Level);
            m_Level = level;
            if (m_Level)
            {
                irr::SKeyMap fpsControls[5];
                fpsControls[0].Action = EKA_MOVE_FORWARD;
                fpsControls[0].KeyCode = KEY_KEY_W;
                fpsControls[1].Action = EKA_MOVE_BACKWARD;
                fpsControls[1].KeyCode = KEY_KEY_S;
                fpsControls[2].Action = EKA_STRAFE_LEFT;
                fpsControls[2].KeyCode = KEY_KEY_A;
                fpsControls[3].Action = EKA_STRAFE_RIGHT;
                fpsControls[3].KeyCode = KEY_KEY_D;
 
                m_Camera = m_Level->getSmgr()->addCameraSceneNode();
                m_CameraAnimator = new SSceneNodeAnimatorFPS(m_Device->getCursorControl(),100.0f,0.5f,0.0f,fpsControls,5,false,false);
                m_Camera->addAnimator(m_CameraAnimator);
                m_Camera->setInputReceiverEnabled(false);
                m_Level->getSmgr()->setActiveCamera(m_Camera);
                m_Level->setCamera(m_Camera);
 
                return true;
            }
            return false;
        }
 

Code: Select all

 
        // rendering functions
        virtual void drawClientArea()
        {
            m_isActive = true;
        
            // if the window is large enough to actually see something
            if ((m_ClientRect.getWidth() > 20) && (m_ClientRect.getHeight() > 20))
            {
                // if the level and camera are valid
                if ( (getLevel()) && (m_Camera))
                {
                    // the client rectangle of the rendering window
                    const rect<s32> r = m_ClientRect;
 
                    // remember the old viewport so tht we can restore it
                    rect<s32> oldViewport = getDesktop()->m_Device->getVideoDriver()->getViewPort();
 
                    // set the aspect ration to our current rendering area
                    m_Camera->setAspectRatio((f32)m_ClientRect.getWidth() / (f32)m_ClientRect.getHeight());
 
                    // make sure we are using our camera as the active camera
                    getLevel()->getSmgr()->setActiveCamera(m_Camera);
 
                    // set the viewport for our rendering window
                    getDesktop()->m_Device->getVideoDriver()->setViewPort(r);
 
                    // render the smgr or whatever we use for scene rendering (could be XEffects for example)
                    if (m_Level) m_Level->render();
 
                    // restore the old viewport
                    getDesktop()->m_Device->getVideoDriver()->setViewPort(oldViewport);
                    
                    // get the center of the window
                    vector2d<f32> center1; 
                    center1.X = (float)r.getCenter().X / oldViewport.getWidth();    // assume old viewport is entire screen (could use driver screensize instead)
                    center1.Y = (float)r.getCenter().Y / oldViewport.getHeight();   // assume old viewport is entire screen (could use driver screensize instead)
 
                    // let the camera animator know the center of our rendering window for cursor control correctness
                    if (m_CameraAnimator) m_CameraAnimator->setCursorOffset(center1.X, center1.Y);
                }
                else
                {
                    CSGUI_Window::drawClientArea();
                }
            }
        }
 

Code: Select all

 
// 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
 
#ifndef __CS_SCENE_NODE_ANIMATOR_CAMERA_FPS_H_INCLUDED__
#define __CS_SCENE_NODE_ANIMATOR_CAMERA_FPS_H_INCLUDED__
 
#include "ISceneNodeAnimatorCameraFPS.h"
#include "vector2d.h"
#include "position2d.h"
#include "SKeyMap.h"
#include "irrArray.h"
 
namespace irr
{
    namespace gui
    {
        class ICursorControl;
    }
 
    namespace scene
    {
 
        //! Special scene node animator for FPS cameras
        class CSSceneNodeAnimatorFPS : public ISceneNodeAnimatorCameraFPS
        {
        public:
 
            //! Constructor
            CSSceneNodeAnimatorFPS(gui::ICursorControl* cursorControl,
                f32 rotateSpeed = 100.0f, f32 moveSpeed = .5f, f32 jumpSpeed = 0.f,
                SKeyMap* keyMapArray = 0, u32 keyMapSize = 0, bool noVerticalMovement = false,
                bool invertY = false);
 
            //! Destructor
            virtual ~CSSceneNodeAnimatorFPS();
 
            //! Animates the scene node, currently only works on cameras
            virtual void animateNode(ISceneNode* node, u32 timeMs);
 
            //! Event receiver
            virtual bool OnEvent(const SEvent& event);
 
            //! Returns the speed of movement in units per second
            virtual f32 getMoveSpeed() const;
 
            //! Sets the speed of movement in units per second
            virtual void setMoveSpeed(f32 moveSpeed);
 
            //! Returns the rotation speed
            virtual f32 getRotateSpeed() const;
 
            //! Set the rotation speed
            virtual void setRotateSpeed(f32 rotateSpeed);
 
            //! Sets the keyboard mapping for this animator (old style)
            //! \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);
 
            //! Sets the keyboard mapping for this animator
            //! \param keymap The new keymap array 
            virtual void setKeyMap(const core::array<SKeyMap>& keymap);
 
            //! Gets the keyboard mapping for this animator
            virtual const core::array<SKeyMap>& getKeyMap() const;
 
            //! Sets whether vertical movement should be allowed.
            virtual void setVerticalMovement(bool allow);
 
            //! Sets whether the Y axis of the mouse should be inverted.
            /** If enabled then moving the mouse down will cause
            the camera to look up. It is disabled by default. */
            virtual void setInvertMouse(bool invert);
 
            //! This animator will receive events when attached to the active camera
            virtual bool isEventReceiverEnabled() const
            {
                return true;
            }
 
            //! Returns the type of this animator
            virtual ESCENE_NODE_ANIMATOR_TYPE getType() const
            {
                return ESNAT_CAMERA_FPS;
            }
 
            //! Creates a clone of this animator.
            /** Please note that you will have to drop
            (IReferenceCounted::drop()) the returned pointer once you're
            done with it. */
            virtual ISceneNodeAnimator* createClone(ISceneNode* node, ISceneManager* newManager = 0);
 
            void setCursorOffset(float x, float y) 
            {
                m_CursorOffsetX = x;
                m_CursorOffsetY = y;
            }
 
        private:
            void allKeysUp();
 
            gui::ICursorControl *CursorControl;
 
            float m_CursorOffsetX;
            float m_CursorOffsetY;
 
            f32 MaxVerticalAngle;
 
            f32 MoveSpeed;
            f32 RotateSpeed;
            f32 JumpSpeed;
            // -1.0f for inverted mouse, defaults to 1.0f
            f32 MouseYDirection;
 
            s32 LastAnimationTime;
 
            core::array<SKeyMap> KeyMap;
            core::position2d<f32> CenterCursor, CursorPos;
 
            bool CursorKeys[EKA_COUNT];
 
            bool firstUpdate;
            bool firstInput;
            bool NoVerticalMovement;
        };
 
    } // end namespace scene
} // end namespace irr
 
#endif // __CS_SCENE_NODE_ANIMATOR_CAMERA_FPS_H_INCLUDED__
 
 

Code: Select all

 
// 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 "stdafx.h"
#include "CSSceneNodeAnimatorFPS.h"
#include "IVideoDriver.h"
#include "ISceneManager.h"
#include "Keycodes.h"
#include "ICursorControl.h"
#include "ICameraSceneNode.h"
#include "ISceneNodeAnimatorCollisionResponse.h"
 
namespace irr
{
    namespace scene
    {
 
        //! constructor
        CSSceneNodeAnimatorFPS::CSSceneNodeAnimatorFPS(gui::ICursorControl* cursorControl,
            f32 rotateSpeed, f32 moveSpeed, f32 jumpSpeed,
            SKeyMap* keyMapArray, u32 keyMapSize, bool noVerticalMovement, bool invertY)
            : CursorControl(cursorControl), MaxVerticalAngle(88.0f),
            MoveSpeed(moveSpeed), RotateSpeed(rotateSpeed), JumpSpeed(jumpSpeed),
            MouseYDirection(invertY ? -1.0f : 1.0f),
            LastAnimationTime(0), firstUpdate(true), firstInput(true), NoVerticalMovement(noVerticalMovement)
        {
#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
        CSSceneNodeAnimatorFPS::~CSSceneNodeAnimatorFPS()
        {
            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 CSSceneNodeAnimatorFPS::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 CSSceneNodeAnimatorFPS::animateNode(ISceneNode* node, u32 timeMs)
        {
            if (!node || node->getType() != ESNT_CAMERA)
                return;
 
            ICameraSceneNode* camera = static_cast<ICameraSceneNode*>(node);
 
            if (firstUpdate)
            {
                camera->updateAbsolutePosition();
                if (CursorControl)
                {
                    CursorControl->setPosition(m_CursorOffsetX, m_CursorOffsetY);
                    CursorPos = CenterCursor = CursorControl->getRelativePosition();
                }
 
                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 position
            core::vector3df pos = camera->getPosition();
 
            // Update rotation
            core::vector3df target = (camera->getTarget() - camera->getAbsolutePosition());
            core::vector3df relativeRotation = target.getHorizontalAngle();
 
            if (CursorControl)
            {
                if (CursorPos != CenterCursor)
                {
 
                    relativeRotation.Y -= (m_CursorOffsetX - CursorPos.X) * RotateSpeed;
                    relativeRotation.X -= (m_CursorOffsetY - CursorPos.Y) * RotateSpeed * MouseYDirection;
 
                    // 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;
                        }
 
                    // Do the fix as normal, special case below
                    // reset cursor position to the centre of the window.
                    CursorControl->setPosition(m_CursorOffsetX, m_CursorOffsetY);
                    CenterCursor = CursorControl->getRelativePosition();
 
                    // needed to avoid problems when the event receiver is disabled
                    CursorPos = CenterCursor;
                }
 
                // Special case, mouse is whipped outside of window before it can update.
                video::IVideoDriver* driver = smgr->getVideoDriver();
                core::vector2d<u32> mousepos(u32(CursorControl->getPosition().X), u32(CursorControl->getPosition().Y));
                core::rect<u32> screenRect(0, 0, driver->getScreenSize().Width, driver->getScreenSize().Height);
 
                // Only if we are moving outside quickly.
                bool reset = !screenRect.isPointInside(mousepos);
 
                if (reset)
                {
                    // Force a reset.
                    CursorControl->setPosition(m_CursorOffsetX, m_CursorOffsetY);
                    CenterCursor = CursorControl->getRelativePosition();
                    CursorPos = CenterCursor;
                }
            }
 
            // set target
 
            target.set(0, 0, core::max_(1.f, pos.getLength()));
            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 CSSceneNodeAnimatorFPS::allKeysUp()
        {
            for (u32 i = 0; i<EKA_COUNT; ++i)
                CursorKeys[i] = false;
        }
 
 
        //! Sets the rotation speed
        void CSSceneNodeAnimatorFPS::setRotateSpeed(f32 speed)
        {
            RotateSpeed = speed;
        }
 
 
        //! Sets the movement speed
        void CSSceneNodeAnimatorFPS::setMoveSpeed(f32 speed)
        {
            MoveSpeed = speed;
        }
 
 
        //! Gets the rotation speed
        f32 CSSceneNodeAnimatorFPS::getRotateSpeed() const
        {
            return RotateSpeed;
        }
 
 
        // Gets the movement speed
        f32 CSSceneNodeAnimatorFPS::getMoveSpeed() const
        {
            return MoveSpeed;
        }
 
 
        //! Sets the keyboard mapping for this animator
        void CSSceneNodeAnimatorFPS::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 CSSceneNodeAnimatorFPS::setKeyMap(const core::array<SKeyMap>& keymap)
        {
            KeyMap = keymap;
        }
 
        const core::array<SKeyMap>& CSSceneNodeAnimatorFPS::getKeyMap() const
        {
            return KeyMap;
        }
 
 
        //! Sets whether vertical movement should be allowed.
        void CSSceneNodeAnimatorFPS::setVerticalMovement(bool allow)
        {
            NoVerticalMovement = !allow;
        }
 
 
        //! Sets whether the Y axis of the mouse should be inverted.
        void CSSceneNodeAnimatorFPS::setInvertMouse(bool invert)
        {
            if (invert)
                MouseYDirection = -1.0f;
            else
                MouseYDirection = 1.0f;
        }
 
 
        ISceneNodeAnimator* CSSceneNodeAnimatorFPS::createClone(ISceneNode* node, ISceneManager* newManager)
        {
            CSSceneNodeAnimatorFPS * newAnimator =
                new CSSceneNodeAnimatorFPS(CursorControl, RotateSpeed, MoveSpeed, JumpSpeed,
                0, 0, NoVerticalMovement);
            newAnimator->setKeyMap(KeyMap);
            return newAnimator;
        }
 
 
    } // namespace scene
} // namespace irr
 
 
Post Reply