Last server records
Pro Nub

Wallbug physics

Posted by Kpoluk 14 Mar in 18:37

Slide at the junction

Let's start by expanding our understanding of the slide. An example for us will be this place on the map kzpl_desert_wellspring:


By jumping into the left junction in duck we can slide the crack without losing speed. How is this possible? In the article about Edgebug and JumpBug physics we discussed the PM_CategorizePosition function, which used the PM_PlayerTrace to check how much the plane under the player was tilted and set the onground flag depending on the tilt. But what if the contact occurs with two planes at once? As we found out from the article about Pixelwalk physics, PM_PlayerTrace can find a different plane depending on the order in which the planes are written in the binary tree. It turns out that if one of the planes has a slide slope, and the other does not, then the same junction of planes will be defined by the engine either as a slide or as the ground, depending on the work of the map compiler. Moreover, since the binary trees for the standing and sitting models are different, then one place might be possible to slide only in duck or only while standing, and sometimes both options work.

Now let's figure out what happens with our speed at the junction of two planes. We have already looked at how PM_FlyMove projects the velocity vector when it sequentially intersects several planes. This implied that after a collision with each plane, the player moves along it a non-zero distance. However, if such a movement is impossible, then PM_FlyMove increases the plane counter numplanes, and then starts processing the simultaneous intersection of planes. In the past we intentionally omitted this code to keep the article simple, but now it's time to look at the missing piece:


In the case of our junction, this code will project the velocity vector first onto one plane, then onto the other, and then everything will depend on the angle between the planes. If it is less than 90°, then the scalar product DotProduct of the velocity vector velocity and the normal to the plane planes[j] will be negative at each step of the cycle:


This means that j will not reach the value of numplanes, but i will, and we will get to the next part of the code. It calculates the magnitude of the projection of the velocity vector onto the direction parallel to both planes, after which the resulting vector is scaled to this magnitude. Concequently the greater the portion of the velocity remaining after projecting it onto the planes (i.e. the smaller the angle between the planes), the faster the portion of the velocity parallel to the planes will decrease. This braking effect is familiar to anyone who has ever been at the junction between two slides.

A special case is when the planes are exactly 90° apart. Then, after projecting onto the planes, only the horizontal component remains from the velocity vector, perpendicular to both normals, which means the scalar product with these normals will be zero, j will reach the value of numplanes, and then the speed will not change. This is exactly the case we see on kzpl_desert_wellspring, where the horizontal speed is not lost when moving along the junction (and when the strafe key is used, it can even increase).


WallBug

Actually, we've already encountered a technique very similar to WallBug at the end of the article about Cliptype physics. It was about the first jump on holy_lame, for which we had to break through a 0.03125 unit thick barrier between the model and the brush plane. To do this, we had to duck during the jump and, looking towards the block, hold down W and D (or W and A). Thanks to PM_AirAccelerate, the speed in the direction of the brush was so small that the end of its vector did not intersect the brush plane, and this allowed us to move closer to the block. Actually, exactly the same principle works for WallBug, except that this technique is usually performed with your back to the wall (although you can just as easily perform WallBug with your face or side to the wall, only the key combination changes). It remains to find out why the speed is reset at some point when approaching the wall.

In theory, after PM_AirAccelerate, the calculated velocity vector should be projected onto the brush plane in PM_FlyMove, after which, at the next step of the cycle, the trace in PM_PlayerTrace no longer touches anything, and the fall should continue. However, if we are too close to the wall, the PM_HullPointContents function from PM_PlayerTrace, which checks whether the trace point is inside the brush, may not work correctly. Let's see what exactly it does:


As we can see, it runs through the brush tree and determines the value d along the way, equal in absolute value to the distance from the given point to the corresponding plane, while the sign of d indicates on which side of the plane the point is located. The plane type plane->type determines the orientation of its normal. Types from 0 to 2 are planes with normals parallel to the X, Y, Z axes, for them d is determined extremely simply. But for the remaining planes, the calculation uses the scalar product DotProduct, and here a classic programmer error occurs when working with a mathematical function. The thing is that mathematically everything is written here absolutely correctly, however, due to the fact that the float type has a finite precision, when working with variables specified in float an error accumulates (especially when multiplying), and any comparisons must be carried out taking this error into account. In theory, an indent of 0.03125 from the wall should save us from these problems, but the implementation of PM_PlayerTrace allowed us to overcome this indent.

So, this is what we get in the end. The d calculation gives the wrong sign, PM_HullPointContents claims that we ended up inside a brush, PM_PlayerTrace returns fraction = 0.0, in PM_FlyMove on the next iterations of the loop we get collisions with the same plane. As a result, we will either reach the code that we've just observed for the slide at the junction, and there the vector product CrossProduct of the normal on itself will give zero, which will then be multiplied by the velocity vector, or after four iterations of the cycle the trace will not allow us to move forward, the total allFraction will remain zero, and the velocity will again be zeroed.

It turns out that any plane that is not parallel to the base directions can cause a sudden jam. And while it is often possible to stay away from the walls, there is no escape from such jams on slides.


Epilogue

This concludes the series of articles on the physics of the game. When I started writing, I didn't expect it to go this far :) Initially, I wanted to share my knowledge about how strafes and bhop work, but more and more techniques were added along the way, and each of them, seemingly known to everyone for a long time, revealed something new and interesting. When I figured out how pixelwalk works, I realized that I would need to do a few more preparatory parts, and what I learned during their creation turned out to be truly impressive. Just as physicists study nature and predict phenomena, we immersed ourselves in the study of the creation of people themselves, and it was no less exciting.