Synopsis

This tutorial will change the samplesDotScene project in Visual Studio 7.1 to allow for a mouse look and local camera movements. Written by regress, vectrex, and the crazy Russian, mj, with contributions from Olme, and from public support from viewers like you! Also, the first portion of the mouse look code was ripped, shamelessly, from the guts of OGRE. But we'll ignore that.

The Setup

We'll be taking the samplesDotScene, which in its current state only allows that camera to move forward and backwards, upwards and downwards, and strafe left to right.

What's that you say? You want to be able to fly around, with the freedom any script-kiddie in quake enjoys? Well my friend, let's have at it then.

How-To

The Project

Fire up the samplesDotScene project inside Visual Studio, and you'll see the general structure of the program is not terrible difficult to understand, but we'll reiterate just to make sure we're comfortable with the setup.

mVPs = multiple View ports

     Vector< std::pair<IViewport*,ICamera*> >    mVPs;

All this declaration means is we're pairing an IViewport* and an ICamera into one easy-to-use data type. Thus, when we go to access the IViewport* of the pair, we would do as such: mVPs.first. Likewise, mVPs.second is the ICamera*. Finally, since we have multiple view ports, we'll separate them into mVPs[], which gives us a clean way to access the IViewport and ICamera of each view port. Unfortunately, when using these std::pair combos, not even the might of visual tomato can break through the fog, so you won't be able to see the various commands/attributes associated with each variable.

For example, whereas before typing in

  ICamera* mCamera;
  mCamera->

would list all the functions, it will now pull up nothing. Just a word of caution for that…

iCam = Current Camera/View port
  static  unsigned iCam = 0;

OK, I think we're ready to move on…

The Setup

The app itself follows the fairly standard practice of setting up a class, calling and initialize function, then a run function or it, then exiting. We won't spend too much time on that, but rather just focusing on the run function for our class.

Baby Steps - Moving with the Keyboard

Firstly, since we're moving only camera about, we get to cheat a little bit, at the expense of having some flexibility of moving a camera attached to a scene node (see “Further Reading” at the bottom for that tutorial, and the advantages therein).

Now this is simple, really.

We have to start by checking for a KeyEvent, here is the Left Key:

  if (getKeyboard()->isKeyDown(input::KC_RIGHT))

Then we apply the appropriate transformation:

  mVPs[iCam].second->rotate( Vector3 ( 0, 1, 0), distance);  //Rotate Right

In this case, we're cheating a bit.
rotate( Vector3 ( 0, 1, 0), -distance); specifically, this is saying “rotate around the y-axis by amount of -distance ”. if we wanted to rotate around the z-axis we would write
rotate( Vector3 ( 0, 0, 1), -distance); But we don't want to, do we? Similarly, to rotate left, all we have to do is negate the distance variable

  if (getKeyboard()->isKeyDown(input::KC_LEFT))
      mVPs[iCam].second->rotate( Vector3 ( 0, 1, 0), -distance); //Rotate Left                

Basically, these are shortcuts for transforming along the local space (for an overview of different transform spaces and why we need them, check out the “further reading section”)

That's all well and good, we can turn now, but what about moving? Alright, so we'll check how to strafe left.

  if (getKeyboard()->isKeyDown(input::KC_D))
     mVPs[iCam].second->moveRelative( -distance*Vector3::kUnitX );//Strafe Left (TS_LOCAL)

Vector3::kUnitX
Vector3::kUnitY
Vector3::kUnitZ

These are all shortcuts which are Vector3 data types. When we want to translate upon a specific axis, we simple use the distance*Vector3::kUnitAxis notation. Just for the sake of completeness, and to give you an idea of the different kind of transforms you'll get in different spaces, let's add two more checks.

  if (getKeyboard()->isKeyDown(input::KC_W))
      mVPs[iCam].second->translate( Vector3( 0, 0, distance ) ); //Move forward in TS_WORLD
  if (getKeyboard()->isKeyDown(input::KC_S))
      mVPs[iCam].second->translate( Vector3( 0, 0, -distance ) );//Move Backwards in TS_WORLD

These two check will move you forward along the Z axis in the world transform space (thus TS_WORLD), regardless of the camera's orientation.

Now that we've added these simple checks, let's compile and see how it works.

Code Checkup 1

This is the complete run () function as we have modified it thus far, it should compile and run without any issues. You'll be able to turn left and right, and move in that direction.

  virtual void run()
    {
        // setup event input generators
        mKeyboardEventGenerator.subscribeToKeyDown( Bind1( &TheApp::onKey, this ) );
        mMouseEventGenerator.subscribeToMouseButtonDown( Bind1( &TheApp::onMB, this ) );
        // graphics
        mGWorld = getGraphicsSystem().createWorld();
        YAKE_ASSERT( mGWorld );
        createCameraViewportPair( 0.0, 0.0, 1, 1, 10 );
        if (mVPs[0].second)
        {
            mVPs[0].second->translate( Vector3(0,100,700) );
            mVPs[0].second->pitch(-10);
        }
        if (mVPs.size() > 1 && mVPs[1].second)
            mVPs[1].second->setPosition( Vector3(0,2,-80) );
        if (mVPs.size() > 2 && mVPs[2].second)
        {
            mVPs[2].second->translate( Vector3(0,15,30) );
            mVPs[2].second->pitch(-30);
        }
        if (mVPs.size() > 3 && mVPs[3].second)
        {
            mVPs[3].second->setPosition( Vector3(0,50,0) );
            mVPs[3].second->pitch(-90);
        }
        // objects, this function also loads everything out of the dotScene file.
        setupScene();
    // main loop
    real lastTime = base::native::getTime();
    while (!shutdownRequested())
    {
        // timing
        real time = base::native::getTime();
        real timeElapsed = time - lastTime;//timer->getSeconds();
        lastTime = time;
        // handle input
        getInputSystem().update();
        mMouseEventGenerator.update();
        mKeyboardEventGenerator.update();
        //
        YAKE_ASSERT( getKeyboard() );
        if ( getKeyboard() )
            {
                static  unsigned iCam = 0;
                real distance = -200. * timeElapsed;
                if (getKeyboard()->isKeyDown(input::KC_LEFT))
                    mVPs[iCam].second->rotate( Vector3 ( 0, 1, 0), -distance); //Rotate Left
                if (getKeyboard()->isKeyDown(input::KC_RIGHT))
                    mVPs[iCam].second->rotate( Vector3 ( 0, 1, 0), distance);  //Rotate Right
                if (getKeyboard()->isKeyDown(input::KC_W))
                    mVPs[iCam].second->translate( Vector3( 0, 0, distance ) ); //Move forward in TS_WORLD
                if (getKeyboard()->isKeyDown(input::KC_S))
                    mVPs[iCam].second->translate( Vector3( 0, 0, -distance ) );//Move Backwards in TS_WORLD
                if (getKeyboard()->isKeyDown(input::KC_D))
                    mVPs[iCam].second->moveRelative( -distance*Vector3::kUnitX );//Strafe Left (TS_LOCAL)
                if (getKeyboard()->isKeyDown(input::KC_A))
                    mVPs[iCam].second->moveRelative( distance*Vector3::kUnitX );//Strafe Right (TS_LOCAL)
                if (getKeyboard()->isKeyDown(input::KC_UP))
                    mVPs[iCam].second->moveRelative( distance*Vector3::kUnitZ );//Move Forward in TS_LOCAL
                if (getKeyboard()->isKeyDown(input::KC_DOWN))
                    mVPs[iCam].second->moveRelative( -distance*Vector3::kUnitZ );//Move Backwards in TS_LOCAL
                if (getKeyboard()->isKeyDown(input::KC_Z))
                    mVPs[iCam].second->translate( Vector3( 0, distance, 0 ) );// Move up
                if (getKeyboard()->isKeyDown(input::KC_X))
                    mVPs[iCam].second->translate( Vector3( 0, -distance, 0 ) );//Move Down
            }
            // render the scene
            if (!shutdownRequested())
                mGWorld->render( timeElapsed );
        }
        YAKE_SAFE_DELETE( mGraphical );
        mGWorld.reset();
    }
  if (getKeyboard()->isKeyDown(input::KC_LEFT))
      mVPs[iCam].second->rotate( Vector3 ( 0, 1, 0), -distance);

Wasn't that simple enough?

A Man's Camera - Mouse look

I hear what you're saying at this point though, Mr.”We're-not-living-in-1992-anymore”. So you wanna move beyond Doom style keyboard interactions, huh? We'll, I guess we may as well add some mouse looking, that seems to be the latest craze in these new-fangled games.

Right after we setupScene(), let's add this bit:

Vector3 mTranslateVector;
Vector3 mOldMousePosition;
Vector3 mCurrentMousePosition;
real mRotX, mRotY;

After the handle input section, add this:

  mCurrentMousePosition = getMouse()->getPosition();

At the end of the keyboard check, we'll add this:

  if (getMouse()->isButtonDown(1) )
    {
    mTranslateVector.x += ( mCurrentMousePosition.x - mOldMousePosition.x ) * 0.13;
    mTranslateVector.y -= ( mCurrentMousePosition.y - mOldMousePosition.y ) * 0.13;
    }
    else
    {
    mRotX = -(( mCurrentMousePosition.x - mOldMousePosition.x ) * 0.13);
    mRotY = -(( mCurrentMousePosition.y - mOldMousePosition.y ) * 0.13);
    }
    mVPs[iCam].second->yaw(mRotX);
    mVPs[iCam].second->pitch(mRotY);
    mVPs[iCam].second->moveRelative(mTranslateVector);
    /*Get Mouse position*/
    mOldMousePosition = getMouse()->getPosition();

You'll notice, if you run the program now, that when moving the mouse around in circles, the camera will begin to actually tilt, when we just want it to be turning side-to-side, and up and down.

This is because yaw acts on the local axis of the camera. When we pitch the camera (look up), the yaw axis is now changed. When we go to yaw, it will be affected by our previous pitch, and be slightly off to an angle, and we will get a rolled view.

Thus, yaw axis should be performed in world space axis or on the parent transform space.

For a better explanation of why this happens, and other 3d-space concepts, please check out the further reading section, or Understanding Transform Spaces, in particular.

Mouse Look 2

Once we understand the idea of how the mouse and camera will interact, let's see if we can clean it up a bit… Go back to the original “Moving with the keyboard” section, ignoring the previous mouse method.

Now, under the run() function, you'll see the following

  // setup event input generators
  mKeyboardEventGenerator.subscribeToKeyDown( Bind1( &TheApp::onKey, this ) );
  mMouseEventGenerator.subscribeToMouseButtonDown( Bind1( &TheApp::onMB, this ) );

Let's add a line directly below those two in order to subscribe, or attach, a function to the MouseMoved event (not sure why this isn't subscribeToMouseMove event, but that's psy's thing)

  mMouseEventGenerator.subscribeToMouseMoved( Bind1( &TheApp::onMMove, this ) );

Just ignore the semantics for the time being, we'll look at the bindings and whatnot later. A little underneath that line however, let's add a bit to set our yawAxis fixed to a certain direction, so we don't affect roll (go ahead, try it without this line and see what happend when you look around a bit - the roll will be slowly affected).

  //Camera Stuff
  mVPs[0].second->setFixedYawAxis( Vector3::kUnitY );

OK, we're done modifying that bit, let's go back up to the onMB() function, and underneath paste the following

   void onMMove(Vector3 mVector)
   {
   static iCam = 0;
   std::cout << "MouseMove.x: " << static_cast<int>( mVector.x ) << std::endl;
   std::cout << "MouseMove.y: " << static_cast<int>( mVector.y ) << std::endl;	
   mVPs[iCam].second->yaw(-( mVector.x ) * 0.13);
   mVPs[iCam].second->pitch(-( mVector.y ) * 0.13);
  }

The mVector that we're receiving here is the mouse movement relative to the last tick, exactly what we need! The iCam variable is there in case you wanted to work with separate viewports at a later time. For now it's just the one, so we'll set it to be 0.

And that's it! See how simple this method is? That's the power of YAKE!

Code Check 2

Here's the combined Keyboard and Mouse (method 2) code.

   void onMMove(Vector3 mVector)
   {
   static iCam = 0;
   std::cout << "MouseMove.x: " << static_cast<int>( mVector.x ) << std::endl;
   std::cout << "MouseMove.y: " << static_cast<int>( mVector.y ) << std::endl;	
   mVPs[iCam].second->yaw(-( mVector.x ) * 0.13);
   mVPs[iCam].second->pitch(-( mVector.y ) * 0.13);
  }
  virtual void run()
    {
        // setup event input generators
        mKeyboardEventGenerator.subscribeToKeyDown( Bind1( &TheApp::onKey, this ) );
        mMouseEventGenerator.subscribeToMouseButtonDown( Bind1( &TheApp::onMB, this ) );
        mMouseEventGenerator.subscribeToMouseMoved( Bind1( &TheApp::onMMove, this ) );
        // graphics
        mGWorld = getGraphicsSystem().createWorld();
        YAKE_ASSERT( mGWorld );
        createCameraViewportPair( 0.0, 0.0, 1, 1, 10 );
        if (mVPs[0].second)
        {
            mVPs[0].second->translate( Vector3(0,100,700) );
            mVPs[0].second->pitch(-10);
        }
        if (mVPs.size() > 1 && mVPs[1].second)
            mVPs[1].second->setPosition( Vector3(0,2,-80) );
        if (mVPs.size() > 2 && mVPs[2].second)
        {
            mVPs[2].second->translate( Vector3(0,15,30) );
            mVPs[2].second->pitch(-30);
        }
        if (mVPs.size() > 3 && mVPs[3].second)
        {
            mVPs[3].second->setPosition( Vector3(0,50,0) );
            mVPs[3].second->pitch(-90);
        }
        // objects, this function also loads everything out of the dotScene file.
        setupScene();
    // main loop
    real lastTime = base::native::getTime();
    while (!shutdownRequested())
    {
        // timing
        real time = base::native::getTime();
        real timeElapsed = time - lastTime;//timer->getSeconds();
        lastTime = time;
        // handle input
        getInputSystem().update();
        mMouseEventGenerator.update();
        mKeyboardEventGenerator.update();
        //
        YAKE_ASSERT( getKeyboard() );
        if ( getKeyboard() )
            {
                static  unsigned iCam = 0;
                real distance = -200. * timeElapsed;
                if (getKeyboard()->isKeyDown(input::KC_LEFT))
                    mVPs[iCam].second->rotate( Vector3 ( 0, 1, 0), -distance); //Rotate Left
                if (getKeyboard()->isKeyDown(input::KC_RIGHT))
                    mVPs[iCam].second->rotate( Vector3 ( 0, 1, 0), distance);  //Rotate Right
                if (getKeyboard()->isKeyDown(input::KC_W))
                    mVPs[iCam].second->translate( Vector3( 0, 0, distance ) ); //Move forward in TS_WORLD
                if (getKeyboard()->isKeyDown(input::KC_S))
                    mVPs[iCam].second->translate( Vector3( 0, 0, -distance ) );//Move Backwards in TS_WORLD
                if (getKeyboard()->isKeyDown(input::KC_D))
                    mVPs[iCam].second->moveRelative( -distance*Vector3::kUnitX );//Strafe Left (TS_LOCAL)
                if (getKeyboard()->isKeyDown(input::KC_A))
                    mVPs[iCam].second->moveRelative( distance*Vector3::kUnitX );//Strafe Right (TS_LOCAL)
                if (getKeyboard()->isKeyDown(input::KC_UP))
                    mVPs[iCam].second->moveRelative( distance*Vector3::kUnitZ );//Move Forward in TS_LOCAL
                if (getKeyboard()->isKeyDown(input::KC_DOWN))
                    mVPs[iCam].second->moveRelative( -distance*Vector3::kUnitZ );//Move Backwards in TS_LOCAL
                if (getKeyboard()->isKeyDown(input::KC_Z))
                    mVPs[iCam].second->translate( Vector3( 0, distance, 0 ) );// Move up
                if (getKeyboard()->isKeyDown(input::KC_X))
                    mVPs[iCam].second->translate( Vector3( 0, -distance, 0 ) );//Move Down
            }
            // render the scene
            if (!shutdownRequested())
                mGWorld->render( timeElapsed );
        }
        YAKE_SAFE_DELETE( mGraphical );
        mGWorld.reset();
    }

Compile, and away you go to fly around your dotScene file!

Further Reading

  1. Controlling a Camera with a SceneNode - Understand the advantages and become familiar with controlling a camera by attaching it to a Scene Node.
  2. Understanding Transform Spaces - A YAKE-specific primer on the different TS, and why we have them.
  3. Essential 3D Concepts - Has a very in-depth (though a bit difficult to understand) primer on 3D concepts, you'll find an overview of transformational spaces on here.
 
tutorials/graphics/fps_camera.txt · Last modified: 2008/02/21 21:59 (external edit)
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki