Last server records
Pro Nub

Cliptype physics

Posted by Kpoluk 6 Aug 2023 in 11:04
Let's dive a little deeper into the topic of map compilation. For compilation, zhlt (Zoner's Half-Life Tools) and its newer, improved version vhlt (Vluzacn's Half-Life Tools) are commonly used. These compilers consist of four utilities executed sequentially:

hlcsg.exe - according to information from the *.map file, which compactly stores information about the entities, brushes and textures used, prepares sections related to the map structure (binary trees , planes, faces, vertices, edges, connections between them), receiving as output a preliminary version of the *.bsp file (it cannot be run in the game yet), as well as a number of temporary files for the subsequent work of other utilities (for hlbsp files *.p0/1/2/3 with information about planes, files *.b0/1/2/3 with auxiliary information about brushes, file [code ]*.hsz[/code] with hull sizes, *.wa_ file with information about textures for hlrad, etc.).

hlbsp.exe - based on the files received from hlcsg, it checks for leaks, splits or merges faces, deletes unnecessary tree nodes and produces a *.bsp file as an output, which can already be used in the game, as well as temporary files (*.ext for hlrad and *.prt for hlvis with information about portals for other games besides CS, *.lin and *.pts with information about leaks).

hlvis.exe - splits the entire map into zones allowing the game engine to exclude from rendering areas that are obviously not in the player’s field of view, thereby improving performance (critical for large maps). The received information is entered into the LUMP_VISIBILITY section of the *.bsp file.

hlrad.exe - calculates lighting by adding data to the LUMP_LIGHTING section of the *.bsp file.

As the utilities work, information about the progress of operations is recorded in a *.log file, while in case of error you can check *.err file to find out exactly what went wrong. At the same time, the operation of the utilities can be configured using numerous launch keys, which can significantly influence the structure of the brushes. In particular, the -cliptype key of the hlcsg utility, among other things, determines how the planes of the cliptrees will be shifted relative to the planes of the main tree, and is accompanied by one of the following parameters: smallest, normalized, simple, precise, legacy.


Simple and Legacy


We have already talked about shifts of vertical and horizontal planes, but for inclined surfaces several options are possible. Let's look at hlcsg code of vhlt compiler, for simplicity taking the hull of standing player and substituting the constants:


Let's start with option 1, which works for simple and smallest types, as the most intuitive. Here for inclined planes we simply combine horizontal and vertical shifts:


Note that if in this case you move from a horizontal surface to an inclined one and look down, you can see how the distance to the ground changes noticeably. Apparently, the compiler creators did not like this effect, and they added option 2, which works for legacy and normalized types. This option implies that the shift of the clip planes depends on the direction of the normal, that is inclination of the surface. An example of a 45° slope gives the following shifts for option 2:


The standing player's hull is buried in the visible brush by (36 - 36 * cos(45°)) + (16 - 16 * cos(45°)) = 15.23 units horizontally. At that the distance from the center of the bottom of player’s parallelepiped to the slope is only 0.77 units horizontally and vertically. For a sitting player, the parallelepiped will be buried by (18 - 18 * cos(45°)) + (16 - 16 * cos(45°)) = 9.96 units horizontally, and the distance from the center of the player's bottom to the ground will be 6.04 units, that is, on a slope the difference between the player’s center in sitting and standing positions is less than 18 units. So the visual transition to a slope changes the distance to the ground much less, which seems to be a good result, but here nuances begin to appear.

Let's take a look at the test project hull_test.jmf and two maps compiled from it hull_simple.bsp and hull_legacy.bsp (the project and map files can be downloaded here):


At first glance, the map contains two rows of identical objects, and on hull_simple.bsp, compiled with the simple type, you really won’t notice any difference between them. But on hull_legacy.bsp, compiled with the legacy type, the objects in the front row, consisting of one brush, are significantly different from the objects behind, which consist of a pair of brushes. Let's start with the already familiar platform with a 45° slope:


Dividing into two brushes results in the platform in the back row receiving an additional clipnode plane:


Hence an invisible step with a height of 15.23 units awaits us at the end of the climb. From the article about EdgeBug and JumpBug we already know that when passing a step no higher than 18 units, the engine will simply throw us up to the desired height. It is this jump that we can observe on our platform, which is especially noticeable during a slow rise.

Next, on the same hull_legacy.bsp we will try wedge-shaped brushes in the wall on the left. In the top view they look like this:


The planes located at 45° to the wall received shifts in X and Y (the same for a standing and sitting model), and the wedge in the back of the room, consisting of two brushes, has an additional plane, which we will run into if we try to move close to wedge. This invisible ledge is vertical, so unlike it was with a step the engine does not help us with its passage.

Next in line are blocks with a sloping surface at the top:


The height of the lower part of these blocks is equal to 64 units, so on the hull_simple.bsp version it was impossible to jump on any of them, but on hull_legacy.bsp the block in the near row due to the shift of the slope turns out to be higher for a sitting model not by 18 units, but only by the already familiar 6.04 units:


At the same time, the block in the back row consisting of two brushes has an additional horizontal plance, which makes it impregnable:


To the right are blocks with a slide:


You can climb onto the nearest block without problems, but the block from the back row does not allow you to slide up due to the presence of an additional vertical plane. Thus, these legacy features are an important reminder to mappers of how much internal block layout affects game physics.

The legacy type is the most common among maps in CS 1.6, including standard maps. This is most likely due to the fact that it was used by zhlt compiler by default. At the same time, vhlt uses the simple type by default, but players are already so accustomed to legacy physics that most mappers manually add -cliptype legacy to the hlcsg launch keys.


Precise


Although there are quite a few examples of using the precise type (from well known hfr_anoyo and hfr_unshape), it is worth saying a few words about it. In the code snippet above, the part under #ifdef HLCSG_CLIPTYPEPRECISE_EPSILON_FIX appeared in vhlt, while zhlt used the old version for precise, which actually coincides with legacy for slopes steeper than 45° and with simple in all other cases. You can also find comments in the code pointing out the disadvantages of this approach, in particular problems with indentation in eps, which we discussed in the article about pixelwalk. In this regard, vhlt for the precise type in the case of slopes that are slides (that is, with an inclination angle greater than acrcos(0.7) = 45.573°), makes a standard shift of 18 or 36 units vertically, without making any horizontal shift at all; for all other planes the shift is similar to the simple type.


Maximum block height


Let's start with something simple. Let there be a cube in front of us with a side of 63 units. From the article about bhop we know that the estimated height of a standing jump is 45 units. We add 18 here due to squatting, and we get exactly 63 units. However, in reality the player model moves discretely, so we don't actually reach 45 units. If we denote the initial vertical speed after the jump as V0 and recall that each frame this speed decreases by 8 units/s, it turns out that we will reach the maximum height h after [code ]N = [V0 / 8][/code] frames (square brackets here mean the integer part of the number). Next we calculate:

h = V0 * 0.01 + (V0 - 8) * 0.01 + (V0 - 8 * 2) * 0.01 + ... + (V0 - N * 8) * 0.01
h = V0 * (N + 1) * 0.01 - 0.01 * 8 * (1 + 2 + ... + N)
h = 0.01 * (N + 1) * (V0 - 4 * N)

From the same article about bhop, we know that the initial vertical velocity is equal to sqrt(2 * 800 * 45) = 268.3281573 units/s, plus do not forget that at the time of the trace PM_FixupGravityVelocity manages to take away 4 units/s, totaling V0 = 264.3281573 units/s. Hence N = 33 and h = 44.991573 units. It turns out that with such a jump height we will not reach 63 units. So why are we able to jump to such heights?

And here the indentation eps = 0.03125 units from the article about pixelwalk comes to our aid. This gap between the player's model and the floor level provides the missing hundredths and even allows the cube to exceed the height of 63 units by 0.022823 units. True, in this case, after jumping onto the cube, the gap between the model and the top of the cube will remain exactly 0.022823, and not 0.03125. This means that if we try to jump from this cube onto another similar one, then height reserve will be smaller, and the new gap will also become smaller. After the third such cube in a row, the gap will almost disappear, and for the fourth cube the jump height will no longer be enough. To reset the gap to eps, we can just perform a doubleduck or a regular jump after the third cube. This effect is a direct consequence of the work of PM_RecursiveHullCheck function, which we discussed in the article about pixelwalk: if the starting point of the trace is closer to the brush plane than eps, then the function won't allow us to move further, but also it won't move us back to the border of the eps-area.

Let's move on. Using the example of the hull_legacy.bsp map, we saw that the legacy type, due to the slopes, significantly affects the height of the block you can jump on. For example, a 45° plane gave us a margin of 9.96 units. This means that the lower part of the block could be up to 72 units high, and 73 looks impossible even taking into account the gap eps between the player model and the floor. But in fact, under certain conditions, you can still jump to this height:


By drawing pink eps areas around the clip nodes, we find ourselves in a situation similar to the pixelwalk trace. The only difference is that the intersection of eps-areas here gives not a red square, but a red diamond, and thanks to this the trace from the starting point p1, which does not reach the level of 73 + 18 units (we add 18 because we are talking about the interaction of the player’s center with clip nodes for a sitting model), still intersects both the vertical and inclined planes. Well, then the situation is already familiar to us - if a clipnode with a vertical plane is encountered earlier while traversing the cliptree than a clipnode with a slope, then the slope will be recorded in the resulting trace, which means we will be able to jump.

To demonstrate this phenomenon, I created a map hull_73_legacy.bsp of the legacy type (you can download it here), where I placed a brush with a height of 74 units and made two bevels at 45° with a height of 1 unit on two opposite edges:


The picture shows the indices of the clip nodes corresponding to the planes of the block. The complete cliptree for a sitting player looks like this:


As you can see, when traversing the tree, clipnode #36 will be encountered later than #33, so we will be able to jump onto the block in front. Also clipnode #37 will be encountered later than #34, which means you cannot jump onto the block from the back side.

How are things going in a similar situation for the simple type? Of course, you won’t be able to jump as high as on legacy, but the height of 63 units should be possible, right? It turns out that not everything is so obvious here either. Let's look at the test map hull_63_simple.bsp with cliptype simple (you can download it here). Here I created a brush with a height of 64 units and made exactly the same bevels as in the previous example. As a result, I got a similar cliptree, so from the front you can jump onto the block without any problems, but the backside... It would seem that just like in the previous example the start point of the trace here falls into the red diamond at the intersection of eps-areas, and the order of the clipnodes in the tree indicates to us that it will not be possible to jump. However, we actually have two ways around this. Since at the top point of the jump we are at a height above 63 + 18 units, then firstly we can accelerate and jump towards the block, hoping that, thanks to the horizontal speed, not a single point on the trace will fall into eps[/code ]-area, and we will simply skip it, so that the next trace will encounter only a slope:


Another way is to come close to the block facing it, jump with W held down, and then during the jump also hold down D or A. This simple manipulation allows, due to the work of the PM_AirAccelerate function (we discussed it in the article about strafe physics), reduce the velocity component in the direction of the block so much that at some point the end point of the trace ends up inside the [code]eps
-area, which means there is no intersection of the clipnode plane, and we move closer to the block. Thus, we gradually pass through the eps-area and at the top point of the jump we go beyond its boundaries:


As a matter of fact, it is the very first block of the famous map holy_lame. And this, by the way, immediately tells us that the cliptype simple was used during compilation.

The End?