my FPS camera tweaks

Discuss about anything related to the Irrlicht Engine, or read announcements about any significant features or usage changes.
Post Reply
FBMachine
Posts: 7
Joined: Tue May 03, 2005 8:38 pm

my FPS camera tweaks

Post by FBMachine »

Hey all, I've been using Irrlicht since this weekend as I've decided to use it to demo an AI toolkit I'm working on. It's great! One thing I just HAD to change before working on anything else though was the awkward FPS cam, so I figured I'd brain dump here what I changed and how, in case anyone else is interested in these changes. Sorry if it's poorly organized, I'm just trying to get the information out, I may clean it up later.

First of all, in Irrlicht the FPS camera pushes you in the direction you are looking ( which is why pushing the forward key will make you hop if you look up ). In an FPS game it actually pushes you perpendicular to the normal of the floor, that way you stay hugging the floor even on slopes, and movement speed is uniform. To fix this, I add a vector to the ISceneNode class called BaseNormal which represents the normal of the floor the node is currently resting on. In CSceneNodeAnimatorCollisionResponce::animateNode() I set the BaseNormal of the camera's node by taking the normal of the triangle returned from the collision manager in the call to getCollisionResultPosition() if it's not too steep to walk on ( ie if triangleNormal.dotProduct( core::vector3df(0,1,0) ) > 0.65 or so ).
Now, instead of using the rotation of the camera to determine the forward vector in CCameraFPSSceneNode::animate(), I use the crossproduct of the BaseNormal and the strafe vector.

Another thing that bothered me about the Irrlicht FPS camera was the instantanious starting and stopped of movement. To fix this, I added very simple physics to the node, and now I simply add to the Acceleration in the movement direction by an acceleration factor up to a maximum acceleration ( ie the max run speed ), which smooths out the feel a lot. I also add a deceleration factor that decelerates the movement over time to let the camera come to a smooth stop. Even with very high acceleration and deceleration factors, it improves the feel of the camera a lot. By the way, instead of directly applying this acceleration to the position in the camera code, I add it to the velocity in the collision responce animator so all the translation is done in one spot.

I also added jumping, which after adding the simple physics to the node was simply a matter of adding a z acceleration ( I check first if the node is resting on the ground, which I copy the falling variable from the collision responce animator to the node ). I seperate the movement acceleration and jump z acceleration so I can put a limit on the movement acceleration without effecting the jump speed. When I go to get the nodes acceleration in the collision responce animator, I just add the movement acceleration and z acceleration together.

Sorry if this is hard to follow, I typed it up pretty fast. Let me know if any of you want code samples. I'll try to document my other changes eventually.
Daniel
Guest

Post by Guest »

ooh examples, examples, pwitty pweese!
pax_tempo

Post by pax_tempo »

Hi, I would also like to see code.
FBMachine
Posts: 7
Joined: Tue May 03, 2005 8:38 pm

Post by FBMachine »

My sleeping pill is kicking my rear right now, so I'll just try to dump the relevant sources here.

Code: Select all

void CCameraFPSSceneNode::animate()
{
	if (SceneManager->getActiveCamera() != this)
		return;

	if (firstUpdate)
	{
		if (CursorControl)
			CursorControl->setPosition(0.5f, 0.5f);

		LastAnimationTime = os::Timer::getTime();

		firstUpdate = false;
		return;
	}

	// get time

	s32 now = os::Timer::getTime();
	s32 timeDiff = now - LastAnimationTime;
	LastAnimationTime = now;

	// Update rotation

	Target.set(0,0,1);

	if (!CursorControl)
		return;

	RelativeRotation.X *= -1.0f;
	RelativeRotation.Y *= -1.0f;

	if (InputReceiverEnabled)
	{
		core::position2d<f32> cursorpos = CursorControl->getRelativePosition();

		if(cursorpos.X != 0.5f || cursorpos.Y != 0.5f)
		{
			RelativeRotation.Y += (0.5f - cursorpos.X) * RotateSpeed;
			RelativeRotation.X += (0.5f - cursorpos.Y) * RotateSpeed;
			CursorControl->setPosition(0.5f, 0.5f);

			if (RelativeRotation.X > MAX_VERTICAL_ANGLE) RelativeRotation.X = MAX_VERTICAL_ANGLE;
			if (RelativeRotation.X < -MAX_VERTICAL_ANGLE) RelativeRotation.X = -MAX_VERTICAL_ANGLE;
		}
	}

	// set target

	core::matrix4 mat;
	mat.setRotationDegrees(core::vector3df(-RelativeRotation.X, -RelativeRotation.Y, 0));
	mat.transformVect(Target);

	// update position

	core::vector3df baseNorm = getBaseNormal();
	core::vector3df pos = getPosition();	

	core::vector3df forwardvect = Target;
   	forwardvect.normalize();
	core::vector3df strafevect = Target;

	strafevect = strafevect.crossProduct(UpVector);
	strafevect.normalize();

    f32 MaxSpeed = MoveSpeed; //AirSpeed;
    if( !isFalling() ) //&& ( ( now - LastJumpTime ) > 100 ) )
    {
        AccelZ = ( JumpScale / 1000.0 ) * CursorKeys[4];
        MaxSpeed = MoveSpeed;
    	LastJumpTime = now;
    }
   	forwardvect = baseNorm.crossProduct( strafevect );
   	forwardvect.normalize();

    core::vector3df movedir = core::vector3df(0,0,0);

    movedir += forwardvect * CursorKeys[0];
    movedir -= forwardvect * CursorKeys[1];
    movedir += strafevect * CursorKeys[2];
    movedir -= strafevect * CursorKeys[3];

    DecelRate = 120.0;
    if( Acceleration.getLength() < MaxSpeed )
    {
        Acceleration += movedir.normalize() * ( (f32)timeDiff/10000.0 * AccelRate );
        if( Acceleration.getLength() > MaxSpeed )
            Acceleration.setLength( MaxSpeed );
    }

	// write right target

	TargetVector = Target;
	Target += pos;

	RelativeRotation.X *= -1.0f;
	RelativeRotation.Y *= -1.0f;
}


oh it caught up to me... falling asleep... will do this tomorow...
FBMachine
Posts: 7
Joined: Tue May 03, 2005 8:38 pm

Post by FBMachine »

Ok I'm going to dump some more code, hopefully it will clear things up.

Code: Select all

void CSceneNodeAnimatorCollisionResponse::animateNode(ISceneNode* node, u32 timeMs)
{
	if (node != Object)
	{
		os::Printer::log("CollisionResponseAnimator only works with same scene node as set as object during creation", ELL_ERROR);
		return;
	}

	if (!World)
		return;

	u32 diff = timeMs - LastTime;
	LastTime = timeMs;

	core::vector3df pos = Object->getPosition();
	core::vector3df vel = pos - LastPosition;
	core::vector3df g = Gravity;// * (f32)diff;

	if (Falling) 
      g = Gravity * (f32)((timeMs - FallStartTime) * diff);

	vel += Object->getAcceleration() * (f32)diff;

	core::triangle3df triangle = RefTriangle;

	bool f = false;
	if (vel+g != core::vector3df(0,0,0) ) //|| vel + ( Object->getAcceleration() * (f32)diff ) != core::vector3df(0,0,0) )
	{
		// TODO: divide SlidingSpeed by frame time

		pos = SceneManager->getSceneCollisionManager()->getCollisionResultPosition(
				World, LastPosition-Translation,
				Radius, vel, triangle, f, SlidingSpeed, g);

		pos += Translation;

		if( f ) // (triangle == RefTriangle)
		{
			if (!Falling)
				FallStartTime = timeMs;

			Falling = true;
		}
		else
		{
		 	// only set the ground normal if it's not too steep
		 	core::vector3df triNorm = (triangle.getNormal()).normalize();
		 	if( triNorm.dotProduct( core::vector3df(0,1,0) ) > 0.65 )
					 	Object->setBaseNormal( triNorm );
			Falling = false;
		}


		Object->setPosition(pos);
	}

    Object->setFalling( f );

	LastPosition = Object->getPosition();
}

Code: Select all

CCameraFPSSceneNode::CCameraFPSSceneNode(ISceneNode* parent, ISceneManager* mgr,
		gui::ICursorControl* cursorControl, s32 id, f32 rotateSpeed , f32 moveSpeed,
		SKeyMap* keyMapArray, s32 keyMapSize)
: CCameraSceneNode(parent, mgr, id), CursorControl(cursorControl),
	MoveSpeed(moveSpeed), RotateSpeed(rotateSpeed), /*RotationX(0.0f), 
	RotationY(0.0f),*/ firstUpdate(true)
{
	#ifdef _DEBUG
	setDebugName("CCameraFPSSceneNode");
	#endif

	if (CursorControl)
		CursorControl->grab();

	MoveSpeed /= 1000.0f;
    AirSpeed = MoveSpeed * 0.75;
    JumpScale = 480.0f;

	recalculateViewArea();

	allKeysUp();

	// create key map
	if (!keyMapArray || !keyMapSize)
	{
		// create default key map
		KeyMap.push_back(SCamKeyMap(0, irr::KEY_KEY_W));
		KeyMap.push_back(SCamKeyMap(1, irr::KEY_KEY_S));
		KeyMap.push_back(SCamKeyMap(2, irr::KEY_KEY_A));
		KeyMap.push_back(SCamKeyMap(3, irr::KEY_KEY_D));
		KeyMap.push_back(SCamKeyMap(4, irr::KEY_SPACE));
	}
	else
	{
		// create custom key map

		for (s32 i=0; i<keyMapSize; ++i)
		{
			switch(keyMapArray[i].Action)
			{
			case EKA_MOVE_FORWARD: KeyMap.push_back(SCamKeyMap(0, keyMapArray[i].KeyCode));
				break;
			case EKA_MOVE_BACKWARD: KeyMap.push_back(SCamKeyMap(1, keyMapArray[i].KeyCode));
				break;
			case EKA_STRAFE_LEFT: KeyMap.push_back(SCamKeyMap(2, keyMapArray[i].KeyCode));
				break;
			case EKA_STRAFE_RIGHT: KeyMap.push_back(SCamKeyMap(3, keyMapArray[i].KeyCode));
				break;
			case EKA_JUMP: KeyMap.push_back(SCamKeyMap(4, keyMapArray[i].KeyCode));
				break;
			} // end switch
		} // end for
	}// end if
}

Code: Select all

		void ISceneNode::UpdatePhysics( u32 timeMs )
		{
            u32 diffUMs = timeMs - LastMs;
            LastMs = timeMs;

            f32 diffMs = (f32)diffUMs / (f32)10000.0;

		 	 if( PhysType == PHYS_None )
		 	 	 return;

		 	 if( PhysType == PHYS_Simple )
			 {
			  	 if( !isFalling() )
    			  	 Acceleration -= Acceleration * core::min_((f32)1.0,( DecelRate * diffMs ));
			  	 AccelZ -= AccelZ * core::min_((f32)1.0,( (DecelRate/(f32)3.0) * diffMs ));
			 }
	 	}
I think those are the more important parts... the air control factor isn't used yet by the way. Let me know if anything doesn't make sense.
Daniel
Bot_Builder
Posts: 23
Joined: Thu Apr 14, 2005 6:59 pm
Location: Bushland

Post by Bot_Builder »

Nice modifications. Rather than doing physics (users will likely overshoot, or be distracted by drift), I'd suggest keeping track of a target angle that moves around the same way as the original and doing a slerp on the qyaternions, if irrlicht has them... Since the physics are already there might as well leave them in...

Perhaps the original can be renamed to fly cam, with the physics/smoothing in, as I would prefer it as a debugging tool. MAybe even extend that to a debug cam with mouse controls like left button forward, right back, left+right strafe, middle button press/roll-speed forward/back.
ImageXP SP2, AMD Athlon 64 3500+, 1GB HyperX RAM, Radeon X700 Pro (PCI-e), 120GB SATA drive :)
genesisrage
Posts: 93
Joined: Tue Feb 08, 2005 12:19 pm

Post by genesisrage »

to be honest, i would love these changes (but have no idea how to actually change it myself)

i like the idea of maybe getting this official, changing the current FPS cam to FlyCam or something like that. with a new FPS cam it would be nice if you could easily map movement/jump commands to different keys (like in most games now you can select what key does what)

just throwing my $0.02 in... just seems it would be better if it acted more like a FPS, and not having the hopping and speed changes like it has now.
Guest

Post by Guest »

I agree Niko, build it in man...
Post Reply