Day 50: Movement code bug

So, please correct me if I'm wrong, but I think there seem to be a bug with the movement code that would potentially allow the player to move slightly faster when moving in contact with a wall.

The tMin that get used every iteration and that get reset to 1.0f every time is the source of the bug if I’m not mistaken.

What get removed from the delta is only the part of the delta that went against the normal. It still moves the whole distance every iteration, so the frame that you move in contact with the wall, your movement will basically get extended with the amount of time you moved for before hitting the wall. It was correct for a while, it got reduced every iteration, but due to another bug (with updating the player position), that resulted in you getting stuck. To solve that the tMin got set to 1 every time, and it did maybe seem to solve the issue, but it only actually created a workaround.

I’m very short on time, so I can’t describe this in more detail now and I don’t actually have the code to copy paste from to correct the issue... so I'm sorry if it’s not clear what I’m getting at. I tried to do a quick image describing it as well, but don’t know if it helps at all.

Edited by Gafgar (Anders Davallius) on
1
playerDelta = ( playerDelta - dot( playerDelta, wallNormal ) * wallNormal ) * ( 1.0f - minT );

Would fix the problem I think (I can't test it because of the changes in day 51).

Another solution would be to set minT = remainingT at the start of each iteration remainingT -= minT at the end of each iteration and multiply the deltaX by minT in the test wall function.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
bool32 testWall( real32* minT ) {
	...
	real32 resultT = ( wallX - relativeX ) / ( deltaX * ( *minT ) );
	...
}
...
real32 remainingT = 1.0f;
for ( iterations ){
	real32 minT = remainingT;
	for ( tiles ) {
		testWall( &minT )
	}
	remainingT -= minT;
}
Yes, that seem to be just about right. Both those changes should work. (Though I'm not a 100% sure on the last suggestion… as I don’t have the code to reference... but it's not that advanced, so I trust it's right)

Edited by Gafgar (Anders Davallius) on
Yes, I think that this is a bug - the problem was that I did not recompute PlayerDelta, which I kind of thought in my head I was doing, but which I never actually did. We can fix this on tomorrow's stream.

- Casey
Okay ^^ it should be a really quick fix and not take any time at all really.. except if you decide to explain it... but it's probably time spent better elsewhere.

BTW, I bet many games could have shipped with a bug like that without anyone noticing it, except for maybe speed runners after months of playing... and it would only really possible to exploit with TAS probably. So no real harm, it’s more just for “correctness”.

Edited by Gafgar (Anders Davallius) on
*EDIT* Oh... this ended up much longer than I initially thought ^^; sorry.. manages to say very little with a lot of text... OTL

Nice to see that you addressed the bug in the stream : ) (Just saw the video).
The solution you did was pretty interesting and not how I would have anticipated you to do it. It works though, so no issue there, but there are another potential small things I would like to bring up.

The current solution is a little wasteful, as you will try to do one more move after hitting a wall even if the resulting remaining delta is zero. So moving straight into a wall will always result in two move iterations even though you don’t get anywhere. It will not lead to any bugs… but a bit wasteful in my opinion, and a fix would be very easy with a simple dot product.

All movement algorithms like this that I’ve done before always stops iteration if the movement delta left is smaller than a threshold (movement collision is “expensive” when done a lot... and computing the squared magnitude of the dealt is pretty cheap anyway. But, if you want to be able to move very small distances, it could be mandatory to always do at least one move iteration as long as the distance greater than zero).

I often like to end the iterations if the transformed remaining delta returns a zero or negative value on a dot product with the initial delta (with other words… on a move action, don’t move backwards... you are not a ball, you are something that tries to deliberately move). Though, unless you have some kind of small restitution on collisions, there is no risk to actually move backwards, but, I often have a very very small restitution, as it will help to not get repeated collisions too close to each other on curved surfaces (stopping just a little bit shy of the walls when doing next iteration is also an alternative to achieve similar results). But doing something like this would probably not be necessary for you with these very large and square tile collisions … and more of a side tangent… so just ignore it if you want… and it's anyway just more of a "hax" to help a more problematic special case while also stopping iterations earlier.

If you intend to move around a potentially large number of entities, it would at least be good to try and not do unnecessary collision iteration I would think. Especially if you are to support circle-box and ellipsoid-box collision later on that will be more expensive, but it’s bad to just waste iterations with collision detection either way in my opinion. Especially when the solution is so easy and quick.

Edited by Gafgar (Anders Davallius) on