====== LAB5: Human Interface Devices ====== This lab is only loosely based on Chapter 8 on human interface devices in the text book. The focus here is on being able to control the on-screen character with a specialized game interface device. ===== Discussion ===== Discussion thread for this lab is here on Piazza: [[https://piazza.com/class/hq7dlipqggr2qe?cid=27|Lab 5 Discussion Thread]] ===== Goal ====== The primary goal of this lab is to hook a joypad game input device up to your Ogre application and use it to control both the character and the camera. This is also an opportunity to see how you set up buffered input (previously you've been using unbuffered input in this application). ===== Preparation ===== No special preparation is required - preparing the joypad is explained in the project steps below. You do not have to use the latest lab as a base for this one, you can use the base of lab2 or any other **//except lab1//** if you feel like the project has become to bloated. **NOTE:** To get the joypads to start sending data, you may have to press a **Mode** button in the center of the device. This is true for the joypads handed out in the lab class! ===== Lab Project ===== Follow these steps to complete the lab project: - **Create a New Project** Create a new empty project called "Lab4" in the same way you have created new projectsfor other lab projects. - - **Create a folder** somewhere in your filesystem to hold the ''resources_d.cfg, plugins_d.cfg and Models folder'' and copy the above mentioned files and folders to the folder you create. - **Change the following lines in your code:** dont forget to make the backslashes forward slashes, and repeating them for the sake of c++ strings escape characters. MyApplication::startUp { // Make an absalute path to the folder you created, in my instance it was "C:\OgreCfg\filename" _root = new Ogre::Root("C://OgreCfg//plugins_d.cfg"); // .. } MyApplication::loadResources { Ogre::ConfigFile cf; // Make an absalute path to the folder you created, in my instance it was "C:\OgreCfg\filename" cf.load("C://OgreCfg//resources_d.cfg"); // ... } - **From the previous lab** You created a folder Models, and Materials if you did the bonus points. In the ''resources_d.cfg'' you must change the line ''FileSystem=Models'' and Materials respectively, to ''FileSystem=(your_absalutePath)/Models'' - **Now whenever you modify the resources or add files or folders**, do that in the folder you just created and all your lab assignments will reuse the configuration files. - **Plug in and Test Joypad** Plug a USB joypad into your computer and find the device in your system's control panel (usually under "Devices and Printers"). Examine the device properties and find a small test panel for the device. Make sure the device works. Intall necessary drivers if the system is not picking it up. - **Initialize Joystick Input** Your MyFrameListener class needs to receive callbacks from the joystick device and therefore has to inherit joystick callback methods from an abstract class called ''OIS::JoyStickListener''. Luckily you can inherit from multiple classes! Change your declaration of your MyFrameListener into this: class MyFrameListener : public Ogre::FrameListener, public OIS::JoyStickListener { You then have to provide implementations of a few abstract callback methods: bool axisMoved(const OIS::JoyStickEvent &e, int axis); bool buttonPressed(const OIS::JoyStickEvent &arg, int button); bool buttonReleased(const OIS::JoyStickEvent &arg, int button); bool povMoved(const OIS::JoyStickEvent &arg, int pov); bool MyFrameListener::axisMoved(const OIS::JoyStickEvent &e, int axis) { return true; } bool MyFrameListener::buttonPressed(const OIS::JoyStickEvent &arg, int button) { return true; } bool MyFrameListener::buttonReleased(const OIS::JoyStickEvent &arg, int button) { return true; } bool MyFrameListener::povMoved(const OIS::JoyStickEvent &arg, int pov) { return true; } In this class you need a new member variable to hold a pointer to a new joystick input object (e.g. '' OIS::JoyStick* _Joystick;'') and then you have to create this object in your constructor. Because it is not guaranteed that the joypad is connected, you need to catch a possible exception upon creation and let the application gracefully continue even if the creation fails: try { _Joystick = static_cast(_InputManager->createInputObject( OIS::OISJoyStick, true )); _Joystick->setEventCallback(this); std::cout << "Successfuly created Joystick"; } catch(...) { std::cout << "Failed to initialize Joystick"; _Joystick = 0; } Don't forget to also **destroy** the joystick object in your destructor, just like you do with the mouse and keyboard. Make sure this code compiles and check to see if your joypad gets properly initialized. - **Move Character with POV** Now you want to give players the option to move the character by pressing one of the directions on the POV (point of view) digital button on the left of the joypad. You should still retain the keyboard control of the movement. First explore the values you get when you press the POV button. // Create a member variable in the MyFrameListener class: int _povState bool MyFrameListener::povMoved(const OIS::JoyStickEvent &arg, int pov) { _povState = arg.state.mPOV[pov].direction; std::cout << _povState << "\n"; return true; } Notice that you get special codes for each direction and the code 0 when you let go of the button (it goes back into center position). Unlike the keyboard, you don't get continuous events when you press and hold the directions! To use this for moving the character you need to store the last direction code received and reset it when you receive 0. In the same callback (below the two lines above), add the following ''OR'' operation to your keyboard movement if statements: if (_Keyboard->isKeyDown(OIS::KC_UP) || _povState == OIS::Pov::North) { SinbadTranslate += Ogre::Vector3(0, 0, -1); walked = true; _rotation = 3.14f; } if (_Keyboard->isKeyDown(OIS::KC_DOWN) || _povState == OIS::Pov::South) { SinbadTranslate += Ogre::Vector3(0, 0, 1); walked = true; _rotation = 0.0f; } if (_Keyboard->isKeyDown(OIS::KC_LEFT) || _povState == OIS::Pov::West) { SinbadTranslate += Ogre::Vector3(-1, 0, 0); walked = true; _rotation = -1.57f; } if (_Keyboard->isKeyDown(OIS::KC_RIGHT) || _povState == OIS::Pov::East) { SinbadTranslate += Ogre::Vector3(1, 0, 0); walked = true; _rotation = 1.57f; } Finally, you need to capture the joystick input in this ''frameStarted'' method: if( _Joystick ) _Joystick->capture(); - **Rotate Camera with Analog Stick** Now you should try to rotate (or swivel) the camera around the character with the right analog stick on the joypad. You first need to create a new member variable in your MyFrameListener class that stores the current camera angle (e.g. ''float _camangle''). In your constructor, initialize this angle to the value ''-1*Ogre::Math::HALF_PI'' so that this angle corresponds to the initial orientation of the character. As with the POV button, you should examine the values you get from the analog axes of your joypad. Add the following lines to your ''axisMoved'' callback: int value = e.state.mAxes[axis].abs; switch(axis) { case 1: std::cout << "1:" << value << "\n"; break; } You can add more cases to this to observe values along the other axes. You will notice that they each run from a negative 32768 to a positive 32767, which corresponds to the range of a 16-bit signed integer. For one of these axes, we want this range to map onto a camera angle that runs from 0 to 360 degrees (calculated in radians). The camera angle can therefore be calculated as follows: _camangle = Ogre::Math::TWO_PI * ((float(-1*value) / float(_Joystick->MAX_AXIS) + 1.0f)/2.0f)+Ogre::Math::HALF_PI; The ''-1'' in here is to reverse the axis value to let the direction of movement map better onto the perceived camera motion and the HALF_PI offset is just so that we start by facing the ogre model correctly. Finally, in the ''frameStarted'' callback you have to continue to update the camera position and orientation: _Cam->setPosition(_myogrenode->getPosition()+Ogre::Vector3(20.0f*Ogre::Math::Cos(_camangle), 10.0f, 20.0f*Ogre::Math::Sin(_camangle))); _Cam->lookAt(_myogrenode->getPosition()); Test to see if this works. ====== Bonus points ====== - **Make the input a little bit more useful :)** Either make your own modifications to the input or follow the instructions given: - **Remove the code** from frameStarted: Ogre::Vector3 SinbadTranslate(0, 0, 0); float _rotation = 0.0f; if (_Keyboard->isKeyDown(OIS::KC_UP) || _povState == OIS::Pov::North) { SinbadTranslate += Ogre::Vector3(0, 0, -1); walked = true; _rotation = 3.14f; } if (_Keyboard->isKeyDown(OIS::KC_DOWN) || _povState == OIS::Pov::South) { SinbadTranslate += Ogre::Vector3(0, 0, 1); walked = true; _rotation = 0.0f; } if (_Keyboard->isKeyDown(OIS::KC_LEFT) || _povState == OIS::Pov::West) { SinbadTranslate += Ogre::Vector3(-1, 0, 0); walked = true; _rotation = -1.57f; } if (_Keyboard->isKeyDown(OIS::KC_RIGHT) || _povState == OIS::Pov::East) { SinbadTranslate += Ogre::Vector3(1, 0, 0); walked = true; _rotation = 1.57f; } _node->translate(SinbadTranslate * evt.timeSinceLastFrame * _WalkingSpeed); _node->resetOrientation(); _node->yaw(Ogre::Radian(_rotation)); - **Now we will make the Sinbad move with the left analog stick** Add these two member variables to the ''MyFrameListener'' class, they will hold the values for the left analog axis. And make sure to initialize them all to 0. int _walkMagnitude; int _turnMagnitude; float _orientation; - **Capture the state of the left axis** in the function ''axisMoved'' by adding to the previously created switch statement. bool MyFrameListener::axisMoved(const OIS::JoyStickEvent &e, int axis) { int value = e.state.mAxes[axis].abs; switch (axis) { case 1: _camangle = Ogre::Math::TWO_PI * ((float(-1 * value) / float(_Joystick->MAX_AXIS) + 1.0f) / 2.0f) + Ogre::Math::HALF_PI; break; // Add these lines, dont forget to break :) case 2: _walkMagnitude = (float)value / -float(_Joystick->MAX_AXIS); // Map the range to -1 to 1 break; case 3: _turnMagnitude = (float)value / -float(_Joystick->MAX_AXIS); // Map the range to -1 to 1 break; } return true; } - **Now add the new input method for Sinbad** add this code in place for the code removed from section //a// bool walked = false; Ogre::Vector3 SinbadTranslate(0, 0, 0); // If the joystick is not available, use the keyboard as input. if (!_Joystick) { if (_Keyboard->isKeyDown(OIS::KC_UP)) { _walkMagnitude = 1; } else if (_Keyboard->isKeyDown(OIS::KC_DOWN)) { _walkMagnitude = -1; } else { _walkMagnitude = 0; } if (_Keyboard->isKeyDown(OIS::KC_LEFT)) { _turnMagnitude = 1; } else if (_Keyboard->isKeyDown(OIS::KC_RIGHT)) { _turnMagnitude = -1; } else { _turnMagnitude = 0; } } // Create the translation vector. SinbadTranslate = _node->getOrientation().zAxis() * evt.timeSinceLastFrame * _WalkingSpeed * _walkMagnitude; walked = true; // Increment the roation angle. _orientation += evt.timeSinceLastFrame * _turnMagnitude*2; // Now finally apply the rotation and translation. _node->translate(SinbadTranslate); _node->setOrientation(Ogre::Quaternion(Ogre::Radian(_orientation), Ogre::Vector3::UNIT_Y)); - **As of now the camera follows Sinbad in a strange manner.** By adding buffered keyboard input, create a method of toggling the camera from following Sinbad behind his back, and a free roaming camera that you cam move with the mouse and "WASD" \\ **Hints:** \\ // This will make the camera a child of the Sinbad Scene node, and thus all transformation made to Sinbad will be applied to the Camera as well. _node->attachObject(_Cam); Buffered keyboard input, Make the following changes to the MyFrameListener class to enable the buffered keyboard input // Add the inheritance of OIS::KeyListener, this will be required to make your FrameListener handle // the buffered input by providing you with the two virtual functions, keyPressed and keyReleased. class MyFrameListener : public Ogre::FrameListener, OIS::JoyStickListener, OIS::KeyListener { //... // Change the way the frameListener initializes the Keyboard object pointer in the constructor. // And make "MyFrameListener" the event callback Handler for the keyboard. MyFrameListener() : ... { //... // Notice the last parameter in the createInputObject() has changed from false to true!! // That parameter indicates that we want to enable buffered keyboard input. _Keyboard = static_cast(_InputManager->createInputObject(OIS::OISKeyboard, true)); _Keyboard->setEventCallback(this); //... } // Now implement those two virtual functions. virtual bool keyPressed(const OIS::KeyEvent &arg) { return true; } virtual bool keyReleased(const OIS::KeyEvent &arg) { return true; } //... } Now you have those two functions that trigger only once per key press, and are not continuous like before, did I mention buffered input :)? ===== When You Are Finished ===== Upload your **commented source files** into Lab5 in MySchool (zip them up if more than one). The lab projects will not be graded, but their completion counts towards your participation grade.