Particle collisions and cutoff

From The DarkMod Wiki
Jump to navigationJump to search

This article describes the collision and cutoff features of particle systems, introduced in TDM 2.08. If you want to know what various particle keywords mean, please read Particle Editor article.

Particle lifetime cutoff

The whole feature relies on the idea of lifetime "cutoff".

Normally, every emitted particle lives for the duration specified by time keyword in the particle stage declaration. Different particle are emitted at different time moments (controlled by bunching keyword), but every particle lives for the same time. A particle can go through various transformations during its lifetime, including fading in and out, animations, shape and color changes, etc.

This feature introduces an additional event in particle lifetime called cutoff. A particle is no longer rendered after the cutoff moment. Most importantly, this cutoff moment is specified by cutoff texture and varies among particles. This flexibility allows to bake collisions with static objects into the texture.


Here are all the necessary bits:

  • surface used as emitter in .map
  • particle-emitting material in .mtr
  • particle effect definition in .prt
  • cutoff texture (not needed with collisionStatic)

Specify cutoff texture manually

One approach is to specify cutoff texture manually. Note that you should never do this if you simply want particles to stop at collisions: use collisionStatic keyword instead! However, this would serve as a good warmup before the next section. Besides, this way can be used to set cutoff for some artistic reasons not related to collisions.

First of all, draw a particle-emitting surface in DarkRadiant. It can be either a patch or a brush with 5 nodraw sides.

In most cases the emitting surface must have [0..1] x [0..1] texture coordinates. So select the created surface (i.e. the patch or single side of the brush), open Surface Inspector, and click on Fit button on Fix Texture: 1.00 x 1.00 line.

Now it remains only to apply a particle-emitting material to this surface, but we have to create it first. Please look into materials/ file for an example of particle-emitting material:

       deform particle tdm_rain2_heavy
       qer_editorimage textures/editor/rain
       {  //needed to emit particles
           blend   filter
           map      _white

Copy this definition into your own material and rename to something customized to avoid confusion in future. Note that the first line references a particle definition, which we have not created yet.

IMPORTANT: Use deform particle and never deform particle2, since the latter does not ensure uniform particle distribution across the surface!

The next step is to create a particle definition. Open particles/tdm_weather2.prt file, and copy tdm_rain2_heavy particle definition from it into some .prt file in your FM. Better rename the copied particle definition to something custom to avoid confusion.

   particle tdm_rain2_heavy {
           count               10
           material            textures/particles/drop2
           time                0.500
           cycles              0.000
           bunching            1.000
           distribution        rect 0.000 0.000 0.000
           direction           cone "0.000"
           orientation         aimed 0.000 0.040
           speed               "1000.000"
           size                "0.500" 
           aspect              "1.000" 
           randomDistribution  0.000
           fadeIn              0.200
           fadeOut             0.000
           color               0.040 0.040 0.040 1.000
           fadeColor           0.000 0.000 0.000 1.000
           offset              0.000 0.000 0.000
           gravity             0.000

IMPORTANT: Ensure that your particle stage has no additional randomization of spawn location, i.e. it contains the exact line: distribution rect 0.000 0.000 0.000 (the aforementioned rain2 definitions are already OK). Otherwise you will have hard time understanding what happens.

In order to attach cutoff texture, you have to add two keywords to particle stage:

  • cutoffTimeMap {path/to/texture/image.tga}: specifies which texture to use as cutoff.
  • mapLayout texture {W} {H}: specifies how the texture should be applied. {W} and {H} must equal width and height of the texture.

Here is an example of added keywords:

           cutoffTimeMap		textures/cutoff/shapedrain.tga
           mapLayout			texture 256 128

Lastly, draw the cutoff texture itself. This texture would be stretched onto the particle-emitting surface, and the color at each point would define the cutoff moment for all particles emitted from it.

More precisely, the color defines which ratio of the lifetime a particle lives before cutoff. If you set full-black color, then particle will die immediately after spawning, you won't see it. If you set full-white color, then particle will live through its whole lifetime without cutoff. Middle-gray color (128 128 128) will make particles disappear after they live through half of their lifetime.

For the purpose of creating manual cutoff textures, only red channel of the texture matters. The green and blue channels are used for additional precision beyond 8 bits.

For a tutorial, you might want to start with some simple black-and-white image and check how it affects the particle system in-game, then continue to some grayscale texture.

Auto-generate cutoff texture from collisions

Now that you understand how cutoff texture works, let's see how it can be auto-generated in order to stop particles on collisions. We will consider rain as an example.

Once again, copy particle definition tdm_rain2_heavy from particles/tdm_weather2.prt file into your FM. Then add the following keywords:

  • collisionStatic: says that the particles should stop on collisions with static objects.
  • mapLayout texture {W} {H}: specifies how cutoff would be applied, and resolution of the cutoff texture.

The texture resolution affects how precise collisions would be depending on spawn location. For instance, if you apply one 1000 x 1000 rain-emitting patch over the whole map of size 3000 x 3000, then all particles emitted 3 length units from each other will stop at the same height. Such precision is more than enough for rain. If you create small patches for something more localized, better use lower resolution to avoid wasted storage and performance consequences.

The collisionStatic keyword currently imposes many restrictions on the particle definition. The tdm_rain2_XXX definitions adhere to all these restrictions. Here are they for the reference:

  • distribution rect 0 0 0, i.e. no additional randomization of spawn location
  • direction cone 0 and gravity 0, so that all particles travel along parallel lines
  • speed must be constant (for now)
  • worldAxis is forbidden

However, the collisionStatic is not enough on its own, it only marks the particle system as using collision detection. In order to compute the cutoff maps, you must run the runParticle compiler tool in the game engine. This tool is very similar to dmap in nature and usage. Here are the steps:

  1. dmap {mapname} --- makes sure your .proc file is correct, since runParticle uses it.
  2. runParticle {mapname} --- computes the cutoff maps for all collisionStatic particle systems in the map.

All the generated textures will be saved into textures/_prt_gen/ directory.

IMPORTANT: Never store anything important there! runParticle starts with clearing this directory every time you run it.

The texture filename is composed of the name of the model in .proc file (which is either world area or entity), the index of the surface in the model, and the index of the particle stage in the particle definition file.

If you decide to inspect these maps, better mask off green and blue channels in your image viewer, and look only at the red channel, since it contains the high bits of the cutoff ratio.

One important rule for collisionStatic is that different locations on the surface emitter must have different texture coordinates. The tool will throw error if it detects that several locations have same texture coordinates, since it cannot write two values into one texel.

On the other hand, it is perfectly normal if some parts of the cutoff texture are not used by the emitter. Given that dmap carves away some parts of the patch which are inside opaque brushes, this happens almost always. The generated texture has all-zero color at such unused locations, including zero alpha channel.

Another interesting fact is that dmap splits a patch which spans over several areas. In a typical usage scenario, mapper applies a single huge rain-emitting patch over the whole map, and this patch gets splitted into many pieces: one piece per every area it covers. A separate texture is generated for every piece. It is worth noting that runParticle computes minimum bounding rectangle of all the used texels and does not save anything outside of it. So don't be surprised that texture resolution for those pieces is much lower than the resolution you specified: you set resolution for the whole [0..1] x [0..1] region.

The optimization mentioned above can be used to create non-rectangular surface emitters. Let's suppose that you have a map with a very tall building in the center, and outdoors area surrounding it. If you create one rain patch crossing the building, then rain would go inside building. To fix this, you can use CSG/boolean operation to subtract a rectangular region from the patch (well, I'm afraid DR cannot do this, so consider it a mind experiment). The remaining region would consist of many patches with various texcoords rectangles, but everything would work normally, and the effective cutoff texture resolution would remain the same.

The runParticle tool takes everything which looks like static (i.e. never moves or disappears) and solid (e.g. not a light flare) geometry and considers them blockers. In reality, it is quite hard to reliably distinguish static geometry. While the tool has some complicated ruled built in, in some cases it might be necessary for tweak something manually. Of course, better keep such tweaking at minimum level.

There are two entity spawnargs which override automatic decisions:

  • particle_collision_static_blocker {0 or 1}: consider this entity a blocker / not a blocker for particle collision computations.
  • particle_collision_static_emitter {0 or 1}: compute collisions for particle-emitting surfaces of this entity / don't compute them.

Aside from that, you can add collisionStaticWorldOnly keyword to particle definition file: in this case only world geometry (brushes and patches) will be considered blockers.


  • runParticle complains about overlapping texcoords.

Check if you really have this problem, and fix it if you have. If you are sure that there is no such problem, then it might happen due to precision issues or dmap mesh optimization glitches. Please report it on forums.

  • Generated textures have missing triangles.

Such errors happened on first implementations, caused by dmap issues. They resulted in triangular zones without rain, which can be detected in-game using r_showTris 1. Also, they are easy to see in generated textures, since dropped triangles have zero color and alpha. If you have some problems, report them on forums.

  • Rain does not reach ground, i.e. stops before collision.

The collisionStatic feature allows to hide particles prematurely, but cannot extend their lifetime. If rain does not reach ground, then you have to increase time keyword in particle definition. Keep in mind that increasing lifetime in K times effectively decreases particle density in K times, so you might need increasing count too.

  • Rain disappears under some viewing angles.

This is the bug in bounding box computation of particle systems. I hope to fix it soon (#5136). For now, you might hack around the problem by setting a large boundsExpansion value to particle definition. Estimate the maximum size of your particle system and set this value --- that should be enough to avoid particle system being wrongly culled away.

  • Cannot see any particles.

Check that emitting patch is facing the right direction: it should be visible in DR from the side where particles go to. Also, try r_showTris 1 to be sure that they are not invisible.

  • Rain falls through water, even with particle_collision_static_blocker 1.

This happens if the material of the water surface does not have the "water" contents/keyword. A surface is allowed to block particles only if it has either "solid" or "water" content flag, other materials never block particles.

  • cutoffTimeMap does not work.

It seems that cutoffTimeMap does not work yet with com_smp 1 (Multi Core Enhancement). Hope to fix it in future (#5141). Note that collisionStatic does not suffer from this problem.

Possible future improvements

The following extensions are planned for future:

  • mapLayout linear: different texture generation technique which would support all particles, even ones with random and curves paths.
  • Animate to different material after cutoff, which would allow to generate splashes from rain.


You can download a sample test map with collision-aware rain here.

Related threads on developers-only forums: