Collision precision and deltaT
I've been struggling quite a while with a very annoying precision issue.
Here's my case:
- player/ mesh gets a predicted change in position
- collision is checked
- if no collision, player is moved (change position)
Now in step 1 the velocity is calculated using deltaT, and in step 3 the same. But due to precision changes (I think), in rare occasions the player gets stuck in corners where they shouldn't (don't think it matters, but I do aabb collision checks).
What's your advice on this?
I also tried to switch to move always and undo last movement when collision occurs, but although possible, isn't optimal because after collision checking there are several other parameters that 'decide' if the player should move and in what direction.
I also tried to use a "local deltaT", saving it in the beginning of the update function and reuse it for steps1 and 3. But no luck so far.
Any input is appreciated
... due to precision changes (I think) ...
I understand it's rare which makes it difficult, but I would suggest, before you make changes to your code, etc., that you determine exactly what the problem is. Fixing a problem is ever so much easier when you know what it is.
Memory is cheap compared to debugging time and effort. So, if you can detect the "stuck" condition, you can save the state of the character (or 10 or 15 previous states) before it's moved, and data to tell you how it got into the "stuck" condition. When the condition occurs, examine the data and determine if your guess at the problem is correct.
I'll let you know the results.
You could very well have a precision problem in the collision detection code but it's hard to tell based on the limited information that you've given us. I recommend reading this article either way: https://randomascii.wordpress.com/2012/02/13/dont-store-that-in-a-float/
#1: Store current player position.
#2: Advance player.
#3: If there is a collision, put the player back at saved position.
Then your order of operations is wrong.I also tried to switch to move always and undo last movement when collision occurs, but although possible, isn't optimal because after collision checking there are several other parameters that 'decide' if the player should move and in what direction.
Also, this is not a graphics question.
Isn’t input exactly what causes you to get stuck in walls etc.?Any input is appreciated
L. Spiro
Thanks all.
@LSpiro: probably right on the input :)
Here's the current/ old code, I'm gonna dig into it and see if I can do the movement always and undo the movement if there's a collision.
bool CGame::UpdateGameTop()
{
/*1* determine wanted direction (keyboard input or no changes/ keep moving) **/
int currentDir = mPlayer.mDirection;
int wantedDir = GetUserInputTop();
if(wantedDir == -1)
return false;
/*2* check collision by 'predicting movement' **/
CGame_Player tempPlayer = mPlayer;
tempPlayer.SetDirection(wantedDir);
VECTOR3 tempMovement = tempPlayer.GetMovement() * mTimer.GetDelta();
tempPlayer.Move(tempMovement); // * 0.75f); // HACK: TO PREVENT GETTING 'STUCK' IN CORNERS => 27-4, was 1.2f
bool collides = PlayerCollidesWall(tempPlayer);
/*3* move the player, if no collision **/
// COLLISION + DIR NOT CHANGED => DON'T MOVE UPDATE DIR => 0 OK
// COLLISION + DIR CHANGED => MOVE DON'T UPDATE DIR OK
// NO COLLISION + DIR NOT CHANGED => MOVE DON'T UPDATE DIR OK
// NO COLLISION + DIR CHANGED => MOVE UPDATE DIR = WANTED OK
if(collides && currentDir == wantedDir) // collision, direction unchanged
{
mPlayer.SetDirection(0);
return true; // STOP
}
if(collides && currentDir != wantedDir) // collision, changed direction
{
CGame_Player tempPlayer = mPlayer;
tempPlayer.SetDirection(currentDir);
VECTOR3 tempMovement = tempPlayer.GetMovement();
tempPlayer.Move(tempMovement * mTimer.GetDelta());
if(PlayerCollidesWall(tempPlayer)) // collision with old direction?
{
mPlayer.SetDirection(0);
return true; // STOP
}
}
if(!collides && currentDir != wantedDir) mPlayer.SetDirection(wantedDir); // change direction
// MOVE THE PLAYER
VECTOR3 movement = mPlayer.GetMovement();
mPlayer.Move(movement * mTimer.GetDelta());
// update player mesh inst position & player look rotation
mSceneManager.GetCurrentD3dsceneRef().mMeshInst[mPlayerMeshInstId].SetWorldPos(DirectX::XMFLOAT3(mPlayer.GetPosition().x, mPlayer.GetPosition().y, mPlayer.GetPosition().z));
mSceneManager.GetCurrentD3dsceneRef().mMeshInst[mPlayerMeshInstId].SetRot(DirectX::XMFLOAT3(0.0f, mPlayer.GetYRotLookDir(), 0.0f));
/*4* check if player went through the mazehole and return to other side **/
PlayerThroughMazeHole();
return true;
}
To illustrate, in First Person view / view from camera, I don't have the issue.
The big difference here is that movement is not 'automatic', only when the player pressed a key/ keeps a key pressed:
bool CGame::UpdateGameFP()
{
mPlayer.Update();
VECTOR3 movement = VECTOR3(0.0f, 0.0f, 0.0f);
if(mInput.KeyDown(VkKeyScan('w')&0xFF)) movement.z = 1.0f; // DOWN
if(mInput.KeyDown(VkKeyScan('s')&0xFF)) movement.z = -1.0f; // DOWN
if(mInput.KeyDown(VkKeyScan('a')&0xFF)) movement.x = -1.0f; // DOWN
if(mInput.KeyDown(VkKeyScan('d')&0xFF)) movement.x = 1.0f; // DOWN
// TESTING ONLY
if(mInput.KeyDown(VK_PRIOR)) movement.y = 1.0f; // DOWN
if(mInput.KeyDown(VK_NEXT)) movement.y = -1.0f; // DOWN
movement = mPlayer.NormalizeMovement(movement);
mSceneManager.GetCurrentD3dcamRef().Move(movement * mTimer.GetDelta());
mPlayer.SetPosition(mSceneManager.GetCurrentD3dcamRef().GetPositionV3());
mSceneManager.GetCurrentD3dsceneRef().mMeshInst[mPlayerMeshInstId].SetWorldPos(mSceneManager.GetCurrentD3dcamRef().GetPosition());
// collision with walls/ maze?
if(PlayerCollidesWall(mPlayer)) // COLLISION: move back the player
{
mSceneManager.GetCurrentD3dcamRef().SetPosition(mSceneManager.GetCurrentD3dcamRef().GetLastPosition());
mPlayer.SetPosition(mSceneManager.GetCurrentD3dcamRef().GetPositionV3());
mSceneManager.GetCurrentD3dsceneRef().mMeshInst[mPlayerMeshInstId].SetWorldPos(mSceneManager.GetCurrentD3dcamRef().GetPosition());
}
PlayerThroughMazeHole();
// Mouselook input
if(mInput.MouseMoved()) mSceneManager.GetCurrentD3dcamRef().FreeLook(mInput.GetMouseMoveX(), mInput.GetMouseMoveY(), mPlayer.GetLookSpeed());
// Update ghost indicator
if(!UpdateGhostIndicator()) return false;
return true;
}
Problem solved, no more hacks and done 'the way it should be'.
New approach:
bool CGame::UpdateGameTop()
{
// a. check if direction is changed to determine 'wanted direction'
// b. try to move in wanted direction
// c. if possible, movement is done
// d. if not possible, undo movement
// e. try to move in old direction if direction != 0 (not moving)
// f. if possible, movement is done
// g. if not possible, new direction = 0 (not moving)
int currentDir = mPlayer.mDirection;
int wantedDir = GetUserInputTop();
if(wantedDir == -1) return false;
bool dirChanged = currentDir != wantedDir;
bool done = false;
if(dirChanged) // move in new/ wanted direction
{
mPlayer.SetDirection(wantedDir);
VECTOR3 movement = mPlayer.GetMovement() * mTimer.GetDelta();
mPlayer.Move(movement);
bool collides = PlayerCollidesWall(mPlayer);
if(collides)
{
mPlayer.SetPosition(mPlayer.GetLastPosition()); // move back player
mPlayer.SetDirection(mPlayer.mLastDirection);
}
else done = true;
}
if(!done && mPlayer.mDirection != 0) // move in old direction (automatically)
{
VECTOR3 movement = mPlayer.GetMovement() * mTimer.GetDelta();
mPlayer.Move(movement);
bool collides = PlayerCollidesWall(mPlayer);
if(collides)
{
mPlayer.SetPosition(mPlayer.GetLastPosition()); // move back player
mPlayer.SetDirection(0);
}
}
// update player mesh inst position & player look rotation
mSceneManager.GetCurrentD3dsceneRef().mMeshInst[mPlayerMeshInstId].SetWorldPos(DirectX::XMFLOAT3(mPlayer.GetPosition().x, mPlayer.GetPosition().y, mPlayer.GetPosition().z));
mSceneManager.GetCurrentD3dsceneRef().mMeshInst[mPlayerMeshInstId].SetRot(DirectX::XMFLOAT3(0.0f, mPlayer.GetYRotLookDir(), 0.0f));
/*4* check if player went through the mazehole and return to other side **/
PlayerThroughMazeHole();
return true;
}
Time for playtesting now. Thanks again for the help.