Basic tutorial 4 (frame listener and non-buffered input)

Source: Internet
Author: User
Prerequisites

This tutorial assumes that you already have C ++ programming knowledge and have installed and compiled an ogre application (if you have difficulties in setting your application, please refer to this Guide for more detailed compilation steps ). This tutorial is also based on the previous chapter. By default, you have learned about the content of the previous tutorial.

[Edit] Introduction

This chapter describes the most useful construction of ogre: framelistener ). At the end of this Guide, you will learn about frame monitoring, how to use frame monitoring to implement something that requires every frame update, and how to use ogre's no-buffer input system.

You can find the code in this Guide. After reading this tutorial, you should try to add some code to your project and check the results.

[Edit] from here

Like in the previous tutorial, we will use a previously created code as our starting point. Create a project in the compiler and add the following source code:

#include "ExampleApplication.h"  class TutorialFrameListener : public ExampleFrameListener{public:    TutorialFrameListener(RenderWindow* win, Camera* cam, SceneManager *sceneMgr)  : ExampleFrameListener(win, cam, false, false)    {    }       bool frameStarted(const FrameEvent &evt)    {        return ExampleFrameListener::frameStarted(evt);    }protected:    bool mMouseDown;       // Whether or not the left mouse button was down last frame    Real mToggle;          // The time left until next toggle    Real mRotate;          // The rotate constant    Real mMove;            // The movement constant    SceneManager *mSceneMgr;   // The current SceneManager    SceneNode *mCamNode;   // The SceneNode the camera is currently attached to};  class TutorialApplication : public ExampleApplication{public:    TutorialApplication()    {    }       ~TutorialApplication()     {    }protected:    void createCamera(void)    {    }     void createScene(void)    {    }     void createFrameListener(void)    {    }};#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32#define WIN32_LEAN_AND_MEAN#include "windows.h"   INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)#elseint main(int argc, char **argv)#endif{    // Create application object    TutorialApplication app;    try {        app.go();    } catch(Exception& e) {#if OGRE_PLATFORM == PLATFORM_WIN32 || OGRE_PLATFORM == OGRE_PLATFORM_WIN32        MessageBox(NULL, e.getFullDescription().c_str(), "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);#else        fprintf(stderr, "An exception has occurred: %s/n",                e.getFullDescription().c_str());#endif    }    return 0;}

If you are using ogresdk in windows, please add the "[ogresdk_directory]/samples/include" directory to this project (exampleapplication. h file location) except for standard inclusion. If you are using the source code package of ogre, this file should be in the "[ogresource_directory]/samples/common/include" directory. Do not try to run the program because we have not defined the keyboard behavior. If you have any questions, please check this Wiki page to obtain the information for setting your compiler. If you still have any questions, please try to see the help bar.

We will define the Program Controller in this chapter.

[Edit] frame monitoring

[Edit] Introduction

In the previous guide, when we add code to the scenario creation method, we only consider what we can do. In ogre, we can register a class to receive messages when a frame is rendered before and after the screen. The framelistener interface defines two functions:

bool frameStarted(const FrameEvent& evt)bool frameEnded(const FrameEvent& evt)

The main cycle (root: startrendering) of ogre is similar to this:

  1. The root object calls the framestarted method in all registered framelisteners.
  2. Render A frame as a root object.
  3. The root object calls the frameended method in all registered framelisteners.

This loop ends until any framelistener's framestarted or frameended function returns false. These function return values mainly mean "Keep rendering ". If you return no, the program will exit. The frameevent object contains two parameters, but only timesincelastframe is useful in frame monitoring. This variable indicates the time when framestarted or frameended was last activated. Note that in the framestarted method, frameevent: timesincelastframe will contain the time when framestarted was last activated (instead of the recently activated frameended method ).

An important concept is that you cannot determine the sequence in which framelistener is called. If you need to determine the exact sequence of calls to framelisteners, you should register only one framelistener and then call all objects in the appropriate order.

You should also notice that the main loop actually does three things. Nothing happens between the frameended method and the framestarted method, and you can even use it again. Your code can also be stored with you. You can put it in a large framestarted or framestarted method, or you can insert it between the two.

[Edit] register a frame listener

Now the above Code can be compiled, but we have not considered creating a frame listener in exampleapplication and createcamera, So if you run this program, you cannot end her. We will resolve this issue before continuing with other content in this Guide.

Find the tutorialapplication: createcamera method and add the following code:

       // create camera, but leave at default position       mCamera = mSceneMgr->createCamera("PlayerCam");        mCamera->setNearClipDistance(5);

We did nothing except the basic one. The createcamera method in exampleapplication is used to move the camera and change the camera position. Here we will discuss this in detail.

Because the root class needs to be updated per frame, he also understands framelisteners. First, we need to create a tutorialframelistener instance and then register it to the root object. The Code is as follows:

       // Create the FrameListener       mFrameListener = new TutorialFrameListener(mWindow, mCamera, mSceneMgr);       mRoot->addFrameListener(mFrameListener);

The mroot and mframelistener variables are defined in the exampleapplication class. The addframelistener method adds a frame listener. The removeframelistener method removes the frame listener (that is, framelistener does not need to be updated ). Note: The add | removeframelistener method only gets a pointer to framelistener (that is, framelistener does not have a name and you can remove it ). This means you need to hold a pointer to each framelistener so that you can remove them later.

This exampleframelistener (Our tutorialframelistener is inherited from it) also provides a showdebugoverlay (bool) method to tell exampleapplication whether to display the frame rate prompt box in the lower left corner. We will open it:

       // Show the frame stats overlay       mFrameListener->showDebugOverlay(true);

Are you sure you can compile and run this application before continuing.

[Edit] Create a scenario

[Edit] Introduction

Before our in-depth study of the code, I will briefly introduce what I will do, so that you can understand the purpose of creating and adding something to the scene.

We will place an object (a ninja) on the screen and add a light source to the scene. When you click the left mouse button, the light will be turned on or off. Press and hold the right mouse button to enable the "Mouse observation" mode (that is, you can watch around with the camera ). We will place scene nodes in the scenario to attach the cameras acting on different sdks. Press the keys 1 and 2 to select the target view.

[Edit] Code

Find the tutorialapplication: createscene method. First, we need to set the environment light of the scenario very low. We want the objects in the scene to remain visible when the light is off, and we still want to change the scene when the light is switched:

       mSceneMgr->setAmbientLight(ColourValue(0.25, 0.25, 0.25));

Now add a ninja to the screen:

       Entity *ent = mSceneMgr->createEntity("Ninja", "ninja.mesh");       SceneNode *node = mSceneMgr->getRootSceneNode()->createChildSceneNode("NinjaNode");       node->attachObject(ent);

Now we create a white light and place it in the scene, which has a relatively small distance from Ninja:

       Light *light = mSceneMgr->createLight("Light1");       light->setType(Light::LT_POINT);       light->setPosition(Vector3(250, 150, 250));       light->setDiffuseColour(ColourValue::White);       light->setSpecularColour(ColourValue::White);

Now you need to create a scene node for camera binding:

       // Create the scene node       node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode1", Vector3(-400, 200, 400));       node->yaw(Degree(-45));       node->attachObject(mCamera);       // create the second camera node       node = mSceneMgr->getRootSceneNode()->createChildSceneNode("CamNode2", Vector3(0, 200, 400));

Now we have done almost the same thing in the tutorialapplication class. Go to tutorialframelistener...

[Edit] frame Monitoring Guide

[Edit] variable

Before going further, let's introduce some variables in the tutorialframelistener class:

Bool mmousedown; // specifies whether the left mouse button is pressed on the previous frame. The value is real mtoggle. // It is returned until the next trigger time is real mrotate. // The Rolling constant is real mmove; // move the constant scenemanager * mscenemgr; // scenenode * mcamnode of the current scenario manager; // scene node attached to the current camera

Mscenemgr has a pointer to scenemanager. mcamnode has scenenode and is bound to a camera. Mrotate and mmove are variables for rotation and movement. If you want to move or rotate faster or slower, you can change the size of the two variables. The other two variables (mtoggle and mmousedown) control our input. We will use the unbuffered mouse and keyboard input (buffered input) in this chapter as the topic of the next chapter. This means that we will call the method when the frame listener queries the mouse and keyboard status.

When we try to use a keyboard to change the state of an object on the screen, we may encounter some interesting problems. If we know that a key is being pressed, We will process it, but what if we get to the next frame? Will we still see it being pressed and repeat it? In some cases (such as moving with a direction key), we do this. However, when we want to use the "T" key to control the switch of the light, we press the T key in the first frame to switch the switch of the light, but the T key is still pressed in the next frame, so the light turned on again... repeat until the button is released. We must track the buttons between frames to avoid this problem. I will introduce two different methods to solve it.

Mmousedown is used to track whether the mouse is also pressed in the previous frame (so if mmousedown is true, we will not perform the same operation before the mouse is released ). Mtoggle specifies the time until we can perform the next operation. That is, when a key is pressed, other actions are not allowed during the period specified by mtoggle.

[Edit] Constructor

The first thing to note about this constructor is that we need to call the constructor of exampleframelistener: first, we need to inherit the constructor of exampleframelistener.

  : ExampleFrameListener(win, cam, false, false)

Note that the third and fourth parameters must be set to false. The third parameter specifies whether to use a keyboard with a buffer, and the fourth parameter specifies whether to use a mouse with a buffer (we do not use it in this lesson ).

In the tutorialframelistener constructor, we set all the variables to the default values:

// Keyboard and mouse status tracking mmousedown = false; mtoggle = 0.0; // populate the camera and Scene Manager containers mcamnode = cam-> getparentscenenode (); mscenemgr = scenemgr; // set the rotation and movement speed to mrotate = 0.13; mmove = 250;

Okay. The mcamnode variable is initialized to any current parent node of the camera.

[Edit] frame start Method

Now we are at the core of the Tutorial: execute actions in each frame. Currently, the following code is available in our framestarted method:

       return ExampleFrameListener::frameStarted(evt);

This code is very important. Exampleframelistener: The framestarted method defines many actions (such as binding all buttons and moving all cameras ).Clear tutorialframelistener: framestarted method.

The open Input System (OIS) provides three main classes for obtaining input: keyboard, mouse, and joystick. In the tutorial, we only need to know how to use keyboard and mouse objects. If you are interested in using the joystick in ogre, refer to the joystick class.

When we use a non-buffered input, the first thing we need to do is to get the current mouse and keyboard status. To achieve this goal, we call the capture method of the mouse and keyboard objects. The sample framework has helped us create these objects in the mmouse and mkeyboard variables respectively. Add the following code to the currently empty tutorialframelistener: framestarted member method.

       mMouse->capture();      mKeyboard->capture();

Next, make sure that the program exits when the Escape key is pressed. Call the iskeydown method of inputreader and specify a keycode to check whether a button is pressed. When the Escape key is pressed, we only need to return false to the end of the program.

       if(mKeyboard->isKeyDown(OIS::KC_ESCAPE))          return false;

To continue rendering, The framestarted method must return a positive Boolean value. We will add this line at the end of the method.

       return true;

All the code we will discuss is above "Return true" in the last line.

The first thing we do with framelistener is to let the left mouse click to control the light switch. By calling the getmousebutton method of inputreader and passing in the button we want to query, we can check whether the button is pressed. Usually 0 is the left mouse button, 1 is the right mouse, 2 is the middle mouse. In some systems, 1 is the middle key and 2 is the right-click. If the buttons do not work well, you can set them as follows:

       bool currMouse = mMouse->getMouseState().buttonDown(OIS::MB_Left);

If the mouse is pressed, The currmouse variable is set to true. Now we need to turn the light switch ON or OFF, depending on whether currmouse is true, and the mouse is not pressed in the previous frame (because we only want to let the light switch be switched once every time we press the mouse ). At the same time, note that the setvisible method in the Light Class determines whether the object actually emits light:

       if (currMouse && ! mMouseDown)       {           Light *light = mSceneMgr->getLight("Light1");           light->setVisible(! light->isVisible());       } // if

Now, we set the mmousedown value to be the same as the currmouse variable. In the next frame, it indicates whether or not the mouse is pressed last time.

       mMouseDown = currMouse;

Compile and run the program. Now you can switch the light by clicking the left button! Note that because we no longer call the framestarted method of exampleframelistener, we cannot turn the camera (currently ).

This method is very useful for saving the last mouse state, because we know that we have done an action for the mouse state.

The disadvantage is that you need to set a Boolean variable for each button bound to an action. One way to avoid this situation is to track the time when any button is clicked last time, and then trigger the event only after a period of time. We use the mtoggle variable for tracking. If mtoggle is greater than 0, we will not execute any action. If mtoggle is smaller than 0, we will execute the action. We will use this method to bind the following two buttons.

The first thing we need to do is take the mtoggle variable minus the time from the previous frame to the present.

       mToggle -= evt.timeSinceLastFrame;

Now we have updated mtoggle and we can operate on it. Next we will bind the "1" Key to the camera and attach it to the first scene node. Before that, check the mtoggle variable to ensure that it is smaller than 0:

       if ((mToggle < 0.0f ) && mKeyboard->isKeyDown(OIS::KC_1))       {

Now we need to set the mtoggle variable so that the next action is at least one second apart:

           mToggle = 0.5f;

Next, we will take the camera from the current affiliated node, then let the mcamnode variable contain the "camnode1" node, and then put the camera.

          mCamera->getParentSceneNode()->detachObject(mCamera);          mCamNode = mSceneMgr->getSceneNode("CamNode1");          mCamNode->attachObject(mCamera);      }

When you press the 2 key, we do the same for camnode2. Except for changing 1 to 2 and if to else if, the other code is the same:

       else if ((mToggle < 0.0f) && mKeyboard->isKeyDown(OIS::KC_2))       {          mToggle = 0.5f;          mCamera->getParentSceneNode()->detachObject(mCamera);          mCamNode = mSceneMgr->getSceneNode("CamNode2");          mCamNode->attachObject(mCamera);      }

Compile and run. By pressing keys 1 and 2, we can convert the camera's view.

Our next goal is to translate mcamnode when you press the direction key or W, A, S, and D.

Unlike the above Code, we don't have to track the last time the camera was moved, because we had to translate every frame when the buttons were pressed. In this way, our code will be relatively simple. First, we will create a vector3 to save the orientation of the Translation:

       Vector3 transVector = Vector3::ZERO;

Okay, when you press W or the up arrow, we want to move the front and back (that is, the Z axis, remember that the negative direction of the Z axis is pointing to the screen ):

      if (mKeyboard->isKeyDown(OIS::KC_UP) || mKeyboard->isKeyDown(OIS::KC_W))          transVector.z -= mMove;

For the s key and the down arrow key, we basically do the same, but the direction is toward the positive Z axis:

      if (mKeyboard->isKeyDown(OIS::KC_DOWN) || mKeyboard->isKeyDown(OIS::KC_S))          transVector.z += mMove;

We are moving in the left and right directions in the positive and negative directions of X:

      if (mKeyboard->isKeyDown(OIS::KC_LEFT) || mKeyboard->isKeyDown(OIS::KC_A))          transVector.x -= mMove;      if (mKeyboard->isKeyDown(OIS::KC_RIGHT) || mKeyboard->isKeyDown(OIS::KC_D))          transVector.x += mMove;

Finally, we want to move up and down along the Y axis. I personally like to move down with the E or Pagedown key, and move up with the Q or Pageup key:

      if (mKeyboard->isKeyDown(OIS::KC_PGUP) || mKeyboard->isKeyDown(OIS::KC_Q))          transVector.y += mMove;      if (mKeyboard->isKeyDown(OIS::KC_PGDOWN) || mKeyboard->isKeyDown(OIS::KC_E))          transVector.y -= mMove;

Well, our transvector variable saves the translation that acts on the camera scene node. The first drawback of this is that if you rotate the scene node and then perform the translation, our X, Y, and zcoordinates are incorrect. To correct it, we must rotate the scene node and our translation. This is actually easier than it sounds.

To indicate rotation, ogre does not use Transform Matrices as some graphics engines do, but uses tuples. The mathematical meaning of tuples is a four-dimensional linear algebra that is hard to understand. Fortunately, you don't have to understand its digital knowledge and how to use it. It is very simple. to rotate a vector with four tuples, you only need to multiply the two. Now, we want to rotate all the operations on the scene node and also apply them to the translation vector. By calling scenenode: getorientation (), we can get the representation of these four rotating elements, and then use multiplication to make them work on the translation node.

Another drawback is that we must scale the translation size from the last frame to the current time. Otherwise, your movement speed depends on the application's frame rate. This is indeed not what we want. Here is a function for us to call to avoid the problems caused by camera Translation:

       mCamNode->translate(transVector*evt.timeSinceLastFrame,Node::TS_LOCAL);

Let's introduce something new. When you translate a node or rotate it around an axis, you can specify which space to use to move it. Generally, you do not need to specify this parameter when moving an object. The default value is ts_parent, which indicates that this object uses the space of the transformation of the parent node. Here, the parent node is the root node of the scenario. When we press the W key (move forward), we move toward the negative direction of the Z axis. If we do not specify ts_local before the code, the camera will move toward the global Negative Z axis. However, since we want the camera to go forward when we press W, we need it to go in the direction of the node. Therefore, we use "local" to change the space.

There is another way we can achieve it (though not directly ). We get the orientation of the node. a quad-tuple is multiplied by the Direction vector to get the same result. This is completely legal:

      // Do not add this to the program      mCamNode->translate(mCamNode->getOrientation()*transVector*evt.timeSinceLastFrame, Node::TS_WORLD);

This also moves in the local space. In fact, this is not necessary. Ogre defines three types of transformation spaces: ts_local, ts_parent, and ts_world. In other scenarios, you may need to use another vector space for translation or rotation. If so, you can do the same as the above Code. Find a triplet that represents the Vector Space (or the orientation of the object you want to operate on) and use the translation vector to multiply it to obtain a correct translation vector, then use it to move in the ts_world space. At present, this situation should not be encountered, and the subsequent tutorials will not involve such content.

Okay, we have the keyboard-controlled movement. We also want to achieve a mouse effect to control the direction of our observation, but this is only when the right mouse is pressed. To achieve this goal, we must first check whether the right-click button is pressed:

       if (mMouse->getMouseState().buttonDown(OIS::MB_Right))       {

If yes, we can control the camera's pitch skew based on the distance from the previous frame. To achieve this goal, we obtain the relative changes of X and Y and pass them to the pitch and yaw functions:

          mCamNode->yaw(Degree(-mRotate * mMouse->getMouseState().X.rel), Node::TS_WORLD);          mCamNode->pitch(Degree(-mRotate * mMouse->getMouseState().Y.rel), Node::TS_LOCAL);       }

Note that we use the ts_world vector coordinates for Skew (if not specified, the rotation function always uses ts_local as the default value ). We need to ensure that the random pitch of an object does not affect its skew, and we want to always rotate it around a fixed axis. If we set yaw to ts_local, we can get this:

Http://www.idleengineer.net/images/beginner04_rot.png

Compile and run the program.

This course is not a complete tutorial on rotation and tuples (that would constitute another series of tutorials ). In the next lesson, we will use an input with a buffer, instead of checking whether the key is pressed in each frame.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.