Problems in CCameraSceneNode

You discovered a bug in the engine, and you are sure that it is not a problem of your code? Just post it in here. Please read the bug posting guidelines first.
Post Reply
stevebondy
Posts: 20
Joined: Thu Aug 13, 2009 5:47 pm
Location: British Columbia, Canada

Problems in CCameraSceneNode

Post by stevebondy »

This will be a long post, but it includes a solution, so I hope it will be worth it.

Like many others before me I was experimenting with creating a flight/space-sim type camera, and ran into a number of issues with camera positioning and rotation. After searching the forums and Internet I found some workarounds, but no real solutions. On investigating I have found a number of bugs in the CCameraSceneNode code that are responsible for these problems.

The code provided by arras in http://irrlicht.sourceforge.net/phpBB2/ ... ht&start=0 works specifically because it compensates for these bugs, and is mostly what I've used to update CCameraSceneNode.

The bugs.

First, there are two issues in setTarget. Here is the original code.

Code: Select all

//! sets the look at target of the camera
//! \param pos: Look at target of the camera.
void CCameraSceneNode::setTarget(const core::vector3df& pos)
{
	Target = pos;

	if(TargetAndRotationAreBound)
	{
		const core::vector3df toTarget = Target - getAbsolutePosition();
		ISceneNode::setRotation(toTarget.getHorizontalAngle());
	}
}
The use of getHorizontalAngle sets the Z rotation to 0. This makes it impossible to 'roll' the camera around it's Z axis, since the first call to setTarget will wipe out any Z rotation anyway.

Secondly, changing the target changes the camera's up vector, but no account is made for this.

Next, there are similar problems in setRotation. Original code:

Code: Select all

//! Sets the rotation of the node.
void CCameraSceneNode::setRotation(const core::vector3df& rotation)
{
	if(TargetAndRotationAreBound)
		Target = getAbsolutePosition() + rotation.rotationToDirection();

	ISceneNode::setRotation(rotation);
}
The first problem occurs because rotationToDirection returns a unit vector. This resets the target to be one unit in front of the camera, leading to unexpected behaviours. If the target was 100 units away, moving the camera 1 unit left or right would cause a rotation of about one half of one degree. After calling setRotation, moving the camera one unit left or right would cause a rotation of 45 degrees.

The second problem here is again the up vector. Changing the rotation changes the up vector, but this is not handled.


A related issue exists in setUpVector:

Code: Select all

void CCameraSceneNode::setUpVector(const core::vector3df& pos)
{
	UpVector = pos;
}
The code sets the up vector, but changing the up vector also (presumably) changes the camera's direction vector, and target, neither of which are updated.

Another, minor issue exists in the code for OnRegisterSceneNode:

Code: Select all

void CCameraSceneNode::OnRegisterSceneNode()
{
<snip>
	// if upvector and vector to the target are the same, we have a
	// problem. so solve this problem:
	core::vector3df up = UpVector;
	up.normalize();

	f32 dp = tgtv.dotProduct(up);

	if ( core::equals(fabsf(dp), 1.f) )
	{
		up.X += 0.5f;
	}
<snip>
The "fix" used here is to add .5 to the X value of the up vector. Unfortunately this simply makes the two vectors different, and actually introduces a small Z rotation. Fortunately, if the up vector and target are updated properly, this should never happen anyway.


Using the code from arras as referenced above I have updated the code for CCameraSceneNode::setRotation() and CCamerSceneNode::setTarget(). In addition I have added two new member functions, moveCamera() and rotateCamera(), that move and rotate the camera by a relative amount.

These changes make implementing a flight-sim type camera intuitive and easy. My new code will be posted in the next couple posts, along with a demo program.

Steve
stevebondy
Posts: 20
Joined: Thu Aug 13, 2009 5:47 pm
Location: British Columbia, Canada

Corrected code for CCameraSceneNode

Post by stevebondy »

Here are the updated routines for CCameraSceneNode:

Code: Select all

//! sets the look at target of the camera
//! \param pos: Look at target of the camera in world coordinates.
void CCameraSceneNode::setTarget(const core::vector3df& pos)
{
	Target = pos;

	if(TargetAndRotationAreBound)
	{
        // set target but maintain current Z rotation
	core::vector3df toTarget = Target - getAbsolutePosition();
        toTarget = toTarget.getHorizontalAngle();
        toTarget.Z = RelativeRotation.Z;    // Keep current Z rotation

        // And adjust the up vector
        ISceneNode::setRotation(toTarget);
        core::matrix4 mTemp;
        mTemp.setRotationDegrees(toTarget);
        core::vector3df uTemp(0,1,0);
        mTemp.transformVect(uTemp);
        UpVector = uTemp;
	}
}


//! Sets the rotation of the node.
/** This only modifies the relative rotation of the node.
If the camera's target and rotation are bound ( @see bindTargetAndRotation() )
then calling this will also change the camera's target to match the rotation.
\param rotation New rotation of the node in degrees. */
void CCameraSceneNode::setRotation(const core::vector3df& rotation)
{
    core::matrix4 mTemp;
    core::vector3df uTemp(0,1,0);

	ISceneNode::setRotation(rotation);

    // Update the up vector
    mTemp.setRotationDegrees(rotation);
    mTemp.transformVect(uTemp);
    UpVector = uTemp;
	if(TargetAndRotationAreBound) { // I believe these should always be bound (steve)
	    // Update the target maintaining the same distance between the camera and target
	    double dist = (Target - getAbsolutePosition()).getLength();
	    uTemp = core::vector3df(0,0,1);   // Standard forward vector
        mTemp.transformVect(uTemp);  // Trasnformed forward vector
	    uTemp.setLength(dist);
		Target = getAbsolutePosition() + uTemp;
	}
}
Here is the code for the new moveCamera and rotateCamera functions

Code: Select all

//! Move the camera relative to it's current position
/** Moves the camera an incremental amount from its current position relative to its parent.
\param vector giving amounts to move - X = left/right, Y = up/down, Z = forward/backward. */
void CCameraSceneNode::moveCamera(core::vector3df& vRel) {
    irr::core::matrix4 m;
    core::vector3df vTarg;

    vTarg = Target - getAbsolutePosition(); // location of target relative to parent

    m.setRotationDegrees(RelativeRotation);
    m.transformVect(vRel);
    setPosition(getPosition() + vRel);  // update the position

    vTarg += vRel;    // Now we have to update the target.
    Target = vTarg + getAbsolutePosition();  // Convert back to world coordinates

    updateAbsolutePosition();
}

//! Rotate the camera an incremental amount
/** Rotates the camera an incremental amount from its current rotation relative to its parent.
This is equivalent to adding the specified amounts to the current rotation.
\param vector giving amounts to rotate - X = left/right, Y = up/down, Z = forward/backward. */
void CCameraSceneNode::rotateCamera(core::vector3df& vRel)
{
    irr::core::matrix4 m;
    m.setRotationDegrees(RelativeRotation);
    irr::core::matrix4 n;
    n.setRotationDegrees(vRel);
    m *= n;
    setRotation( m.getRotationDegrees() );
    updateAbsolutePosition();
 }


Adding these functions of course requires adding a few lines to the ICameraSceneNode.h and CCameraSceneNode.h files.

ICameraSceneNode.h

Code: Select all

// Copyright (C) 2002-2009 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#ifndef __I_CAMERA_SCENE_NODE_H_INCLUDED__
#define __I_CAMERA_SCENE_NODE_H_INCLUDED__

#include "ISceneNode.h"
#include "IEventReceiver.h"

namespace irr
{
namespace scene
{
	struct SViewFrustum;

	//! Scene Node which is a (controlable) camera.
	/** The whole scene will be rendered from the cameras point of view.
	Because the ICameraScenNode is a SceneNode, it can be attached to any
	other scene node, and will follow its parents movement, rotation and so
	on.
	*/
	class ICameraSceneNode : public ISceneNode, public IEventReceiver
	{
	public:

		//! Constructor
<snip>
		//! Queries if the camera scene node's rotation and its target position are bound together.
		/** @see bindTargetAndRotation() */
		virtual bool getTargetAndRotationBinding(void) const = 0;

        //! Move the camera relative to it's current position
        virtual void moveCamera(core::vector3df& vRel) = 0;

        //! Rotate the camera an incremental amount
        virtual void rotateCamera(core::vector3df& vRel) = 0;

	protected:

		bool IsOrthogonal;
	};

} // end namespace scene
} // end namespace irr

#endif


CCameraSceneNode.h

Code: Select all

// Copyright (C) 2002-2009 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#ifndef __C_CAMERA_SCENE_NODE_H_INCLUDED__
#define __C_CAMERA_SCENE_NODE_H_INCLUDED__

#include "ICameraSceneNode.h"
#include "SViewFrustum.h"

namespace irr
{
namespace scene
{

	class CCameraSceneNode : public ICameraSceneNode
	{
	public:

		//! constructor
		CCameraSceneNode(ISceneNode* parent, ISceneManager* mgr, s32 id,
			const core::vector3df& position = core::vector3df(0,0,0),
			const core::vector3df& lookat = core::vector3df(0,0,100));

<snip>
		//! Queries if the camera scene node's rotation and its target position are bound together.
		virtual bool getTargetAndRotationBinding(void) const;

        //! Move the camera relative to it's current position
        virtual void moveCamera(core::vector3df& vRel);

        //! Rotate the camera an incremental amount
        virtual void rotateCamera(core::vector3df& vRel);

	protected:

		void recalculateProjectionMatrix();
		void recalculateViewArea();

		core::vector3df Target;
		core::vector3df UpVector;

		f32 Fovy;	// Field of view, in radians.
		f32 Aspect;	// Aspect ratio.
		f32 ZNear;	// value of the near view-plane.
		f32 ZFar;	// Z-value of the far view-plane.

		SViewFrustum ViewArea;

		bool InputReceiverEnabled;

		bool TargetAndRotationAreBound;
	};

} // end namespace
} // end namespace

#endif

Last edited by stevebondy on Sun Aug 23, 2009 2:11 am, edited 1 time in total.
stevebondy
Posts: 20
Joined: Thu Aug 13, 2009 5:47 pm
Location: British Columbia, Canada

Demo program

Post by stevebondy »

And finally, here's a demo program to show the use of the new functions, and that everything actually works. The textures used are from the standard irrlicht media.

Oh, and I almost forgot - I have not coded any updates for setUpVector.

Code: Select all

#include <irrlicht.h>
#include <ICameraSceneNode.h>

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

// Define some global variables
bool keys[irr::KEY_KEY_CODES_COUNT];

IrrlichtDevice *irrDevice;
IVideoDriver* irrDriver;
ISceneManager* smgr;

ISceneNode* sphere;
ISceneNode* skyBox;
ICameraSceneNode* mainCamera;


// This is the movement speed in units per second.
const f32 MOVEMENT_SPEED = 50.f;
const f32 ROTATE_SPEED = 25.f;

class MyEventReceiver : public IEventReceiver {
 public:
    virtual bool OnEvent(const SEvent& event) {
    if(event.EventType == irr::EET_KEY_INPUT_EVENT){
        keys[event.KeyInput.Key] = event.KeyInput.PressedDown;
        return false;
    }
    return false;
  }
};

void HandleInput(f32);


int main(int argc, char** argv)
{
    MyEventReceiver rv;

    irrDevice = createDevice(EDT_OPENGL);
    irrDevice->setResizeAble(true);
    irrDriver = irrDevice->getVideoDriver();

    smgr = irrDevice->getSceneManager();
    // Initialize key state
    for(int x=0; x<irr::KEY_KEY_CODES_COUNT; x++) keys[x] = false;
    irrDevice->setEventReceiver(&rv);

    /* Add some stuff to the scene */
    sphere = smgr->addSphereSceneNode();
    sphere->setPosition(vector3df(0,0,100));
    sphere->setScale(vector3df(10,10,10));
    sphere->setMaterialTexture(0, irrDriver->getTexture("earth.bmp"));
    sphere->setMaterialFlag(video::EMF_LIGHTING, false);

    mainCamera = smgr->addCameraSceneNode(0, vector3df(0,0,-300),sphere->getAbsolutePosition(),-1);
    if (mainCamera )  mainCamera->bindTargetAndRotation(true);    // FPS style camera

    skyBox = smgr->addSkyDomeSceneNode(irrDriver->getTexture("skydome.jpg"));

	// In order to do framerate independent movement, we have to know
	// how long it was since the last frame
	u32 then = irrDevice->getTimer()->getTime();

    while(irrDevice && irrDevice->run()) {
		// Work out a frame delta time.
		const u32 now = irrDevice->getTimer()->getTime();
		const f32 frameDeltaTime = (f32)(now - then) / 1000.f; // Time in seconds
		then = now;

        HandleInput(frameDeltaTime);

        irrDriver->beginScene(true, true, video::SColor(255,0,0,255));
        smgr->drawAll();
        irrDriver->endScene();

    }
    irrDevice->drop();

    return 0;
}

void HandleInput(f32 delta) {
        vector3df vRel;

        if(keys[KEY_F5]) {  // Look at fixed reference object
            mainCamera->setPosition(vector3df(0,0,-300));
            mainCamera->setRotation(vector3df(0,0,0));
            mainCamera->setTarget(sphere->getAbsolutePosition());
        }

        // Handle changes in position
        vRel = vector3df(0.,0.,0.);
        if (keys[KEY_KEY_A]) { // Move camera left
            vRel += vector3df(-(MOVEMENT_SPEED*delta),0,0);
        }
        else if (keys[KEY_KEY_D]) { // Move camera right
            vRel += vector3df((MOVEMENT_SPEED*delta),0,0);
        }

        if (keys[KEY_KEY_W]) { // Move camera Up. Up and down are easy since we just follow the up vector
            vRel += vector3df(0,(MOVEMENT_SPEED*delta),0);
        }
        else if (keys[KEY_KEY_S]) { // Move camera down
            vRel += vector3df(0,-(MOVEMENT_SPEED*delta),0);
        }

        if(keys[KEY_KEY_F]) {  // Move forward
            vRel += vector3df(0,0,(MOVEMENT_SPEED*delta));
        }
        else if(keys[KEY_KEY_B]) { // Move back
            vRel += vector3df(0,0,-(MOVEMENT_SPEED*delta));
        }
        if (vRel.getLength() > 0.0) mainCamera->moveCamera(vRel);

        // Now update rotations
        vRel = vector3df(0.,0.,0.);
        if(keys[KEY_LEFT]) {  // turn left
            vRel += vector3df(0,-(ROTATE_SPEED*delta),0);
        }
        else if(keys[KEY_RIGHT]) { // turn right
            vRel += vector3df(0,(ROTATE_SPEED*delta),0);
        }
        if(keys[KEY_UP]) { // Look up
            vRel += vector3df((ROTATE_SPEED*delta),0,0);
        }
        else if(keys[KEY_DOWN]) { // Look down
            vRel += vector3df(-(ROTATE_SPEED*delta),0,0);
        }
        if(keys[KEY_KEY_Q]) { // Roll Left
            vRel += vector3df(0,0,-(ROTATE_SPEED*delta));
        }
        else if(keys[KEY_KEY_E]) { // Roll Right
            vRel += vector3df(0,0,(ROTATE_SPEED*delta));
        }
        if (vRel.getLength() > 0.0) mainCamera->rotateCamera(vRel);
}
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Post by hybrid »

I didn't check all the code, but you're definitely right that the default camera behavior is kind of wrong. My idea was to strip all the automated stuff to a minimal set of behavior which just keeps everything coherent. All the rest should go into camera animators, which will provide the different kinds of cameras.
Due to the many legacy applications we may have to define some extra things which are not necessary for a most basic camera. But hopefully, all of these compatibility methods can go into a legacy animator.
Since these changes will go into Irrlicht 1.7, it might help to add a ticket on the patch tracker, so we can keep this fix in mind when planning the new camera layout. Please post it here http://sourceforge.net/tracker/?group_i ... tid=540678
Post Reply