Glow tags are an object widget used to create a particle-based glowing effect, where camera-facing sprite particles travel along and/or emit from a path defined by markers in the gbxmodel. This tag is capable of much more complex effects than used in the energy sword, though has some known issues too.

Particle types

Glows can contain two types of particles. Normal particles follow the glow path and can be distanced from it radially, as well as animated in colour, speed, rotation, and distance. Once a normal particle reaches the end of the path they can bounce back or wrap to the beginning again. Trailing particles are emitted at a given rate from a defineable segment of the path, either in random directions or vertically. They have limited lifetimes and can fade, scale down, and slow down over the lifetime.

In the case that the glow path is a single marker or the root node default, only trailing particles will be emitted from that single point.

Glow path

The glow path is a smooth 3D spline connecting up to 5 object markers with the same name, determined by the attachment marker field. Any more will be ignored. It's also possible to define a "path" of a single marker or leave the field blank to use the root node as the default, in which case only trailing particles can emit.

A model can have multiple sets of glow markers so multiple widgets can be added. For example, the energy sword has 5 glow 1 markers for the upper blade and and 5 glow 2 markers for the lower.

Blender doesn't allow multiple objects to have the same name, so you will need to use the Blender toolset's marker name override feature. You can find it under the Object Data Properties pane when an object's name begins with #.

A glow effect with many pink particles shows how a smooth path is created between markers.

Marker rotation matters! A marker's local +X axis (red) is its "forward" direction and +Z (blue) is "up". The ordering of markers is determined by how they point forward to each other in a chain (see initialization). The up direction can matter for trailing particle distribution, and introducing twist to markers along the path allows for more complex radial rotations and "pinching" than can be achieved with effect rotational velocity alone. Markers can also be slightly misaligned with the resulting path to create oval cross sections.

Initialization

Since the markers all have the same name, the game needs to determine their actual ordering and the total path length at runtime in an initialization step. This step is only done once and not redone if the model animates its marker positions.

  1. Get up to 5 markers from the model.
  2. For each marker, find which other marker it points at the best to be its "next" marker. This is done using the highest dot product between marker forward and direction to another marker. Negative dot products are ignored, meaning any other markers "behind" it won't be the next.
  3. Store a lookup table of marker indices in order in the glow effect's gamestate.
  4. Calculate total length as the sum of segment lengths.

Improper placement and rotation of markers can lead to different failure modes:

  • Crash of the game with exception when looking at the glow: render_cameras.c,#1086: bounds->x0<=bounds->x1.
  • Game becoming unresponsive. This happens if markers form a closed loop.
  • Some markers being unused.
  • Paths looping back on themselves in unexpected ways.
  • Sharp discontinuities at some markers in the smooth path.

Some general tips to avoid these issues are:

  • Avoid high curvature.
  • Point the +X axis of each marker in the path toward the next marker.
  • Avoid circular loops.
  • Don't use more than 5 markers.

When creating glow paths it's helpful to set small size bounds, 0 distance, and a high particle count to make the curve easy to see. You should also enable render_model_markers 1 so you can make sure all markers are being used as intended.

Animation

Since markers can be parented to nodes and animated with a model_animations, it's possible to have an animated glow path. The path will continue to be smoothly interpolated between the markers. Note that it's still possible to make the game unresponsive if some frames put the path into a bad shape.

Limits

Glow particles are distinct from other particles. Halo's gamestate can store up to 512 glow particles, which are shared across up to 8 glows. Note that a single model with two glow widgets still counts as 2 glows. Each glow effect can have up to 5 markers defining its path.

Be sure to budget number of particles and particle generation freq according to how many glows you expect will exist simultaneously. Although normal particles stop spawning once the limit is reached, creating too many trailing particles with long lifetimes will cause a crash:

EXCEPTION halt in c:\mcc\main\h1\code\h1a2\sources\objects\widgets\glow.c,#547: the map limit for the number of active glow particles has been reached

Known issues

The glow functionality does not appear to be fully implemented by the engine. It's only used for the energy sword, which doesn't make use of all the features of the tag. See the tag structure descriptions below.

This effect does not render on first person models.

Structure and fields

FieldTypeComments
attachment markerTagString

The name of the gbxmodel marker(s) which make up the glow path. The model can have up 5 markers with the same name. A model can have multiple sets of markers, e.g. glow 1 and glow 2.

If no markers have this name, or the field is left empty, a marker will be derived from the object's root node as the default.

When the path is only defined by a single marker (including the root default) then then normal particles will not be generated. Trailing particles can still be generated.

number of particlesuint16

How many normal particles to distribute along the path. The game is limited to 512 glow particles total, across up to 8 glow effects, so budget according to how many glow effects you expect to exist simultaneously.

You can set this to 0 if you only want to use trailing particles.

boundary effectenum

Determines the behaviour of particles which reach the end of the path.

OptionValueComments
bounce0x0

When glow particles reach the end of the path, they will change direction and return.

wrap0x1

When glow particles reach the end of the path, they will reappear at its start.

normal particle distributionenum

Controls how particles are distributed along the glow path.

OptionValueComments
distributed randomly0x0

Particles will be randomly spaced along the glow path.

distributed uniformly0x1

Particles will be evenly spaced along the glow path. Glow markers do not need to be uniformly distanced for this to work; the game can calculate the total path length and space particles accordingly.

trailing particle distributionenum

Controls how trailing particles are generated from the path segment defined at the end of the tag. Unaffected by particles move backwards and particles move in both directions.

OptionValueComments
emit vertically0x0

Trailing particles emit in the global Z direction.

emit normal up0x1

Trailing particles emit in the marker-local Z direction.

emit randomly0x2

Trailing particles emit spherically in random directions.

glow flagsbitfield
FlagMaskComments
modify particle color in range0x1

If set, the particle color will fade as they translate along the path. The fade behaviour is somewhat unintuitive; rather than fade between color_bound_0 and color_bound_1 from start to end, each color channel fades at a different rate depending on both color rate of change and the per-channel difference between color bounds.

The color c at distance d from the path start, having color bound values c0 and c1, and color rate of change v, is calculated with:

let c = (c0 + v * d * (c1 - c0)) % 1

For example: assume a bound 0 red of 0.8, a bound 1 red of 1, rate of change 10, and distance 1.37. The red value will be:

c = (0.8 + 10 * 1.37 * (1 - 0.8)) % 1
c = 3.54 % 1
c = 0.54

If not set, particle colors are randomly selected between the color bounds or interpolated via function attachment.

particles move backwards0x2

Reverses the direction particles translate along the path.

partices move in both directions0x4

Every second particle travels in the reverse direction.

trailing particles fade over time0x8

Trailing particles fade out to 0 opacity over their lifetime.

trailing particles shrink over time0x10

If set, trailing particles will shrink from an initial randomly-selected bounds size to 0 over their lifetime, at which point for a single frame the particle texture will render at actual resolution before disappearing. This seems to be due to the size 0 defaulting to actual pixel scale rather than scaled by distance. This can be mitigated by also using trailing particles fade over time so the particle is invisible for that frame anyway.

If not set, trailing particles are always shown at actual resolution.

trailing particles slow over time0x20

Trailing particles gradually slow down and come to a stop at the end of their lifetime.

attachment 0enum
  • Shifted by one
  • Unused

Seemingly unused. Setting this to an object function has no effect.

particle rotational velocityfloat
  • Unused

No effect on normal or trailing particles. With a non-zero value, particle sprites did not rotate in screen space or radially around the glow path. Neither an object function attachment nor multiplier values changed this.

particle rot vel mul lowfloat
  • Unused

No visible effect.

particle rot vel mul highfloat
  • Unused

No visible effect.

attachment 1enum?
  • Shifted by one

Sets an object function which controls the rotational velocity.

effect rotational velocityfloat

Controls how quickly normal particles rotate radially around the path, in radians/sec.

effect rot vel mul lowfloat

Rotational velocity multipler when the function value is at 0. This only takes effect when a function attachment is set. It's not known exactly how this multiplier works; it has a gradually varying effect over the length of the glow path.

effect rot vel mul highfloat

As above, but for a high function value.

attachment 2enum?
  • Shifted by one

Sets an object function which controls the translational velocity.

effect translational velocityfloat

Controls how quickly particles move along the path, in world units/sec. This is not used for trailing particles, which instead use velocity of trailing particles.

effect trans vel mul lowfloat

Multiplier for the translational velocity for the function low value. Must be non-zero when a function is being used, or else the game will crash on render_cameras.c,#1086: bounds->x0<=bounds->x1.

effect trans vel mul highfloat

Multiplier for the translational velocity for the function high value. This value can be 0 safely.

attachment 3enum?
  • Shifted by one

Allows the radial distance of normal particles to be scaled by an object function. No effect on trailing particles.

min distance particle to objectfloat

When the distance attachment function is NONE, sets the minimum radial distance from the glow path that particles will be randomly placed. When distance is scaled by a function, sets the minimum distance of all particles. No effect on trailing particles.

Radial distances are perpendicular to the path markers' +X axes, meaning marker rotation matters. Distance cannot vary over the length of the path, even when using a function, but you can twist some of the markers in the path to introduce a pinching effect.

max distance particle to objectfloat

When the distance attachment function is NONE, sets the maximum radial distance from the glow path that particles will be randomly placed. When distance is scaled by a function, sets the maximum distance of all particles. No effect on trailing particles.

distance to object mul lowfloat

When distance is scaled by an attachment function, determines the how a function value of 0 maps to a linear interpolation between min and max distances. For example, a multiplier of 0 means when the function is low, the distance will be equal to min distance particle to object.

distance to object mul highfloat

When distance is scaled by an attachment function, determines the how a function value of 1 maps to a linear interpolation between min and max distances. For example, a multiplier of 1 means when the function is high, the distance will be equal to max distance particle to object.

attachment 4enum?
  • Shifted by one

Intended to make particle size scale with a function, but does not work. When not NONE, all particle spites render at their actual resolution regardless of distance rather than scale with the function.

This does not affect trailing particles, which continue to be randomly selected between size bounds if trailing particles shrink over time is set.

particle size boundsBounds
  • Unit: world units

When particle size is not scaled by a function attachment, particle sizes will be randomly chosen from this range. When scaled by a function, or when size bounds are both 0, particle sprites are rendered at their actual resolution regardless of distance.

Trailing particles will always render at actual resolution unless trailing particles shrink over time is set, in which case size is selected randomly from these bounds regardless of any function attachment.

FieldTypeComments
minfloat
maxfloat
size attachment multiplierBounds
  • Unused

Does not work as intended. When a function is used, these values don't affect the sprite size.

attachment 5enum?
  • Shifted by one

If set to a function, all normal particles will blend between color bound 0 and color bound 1 according to the function. Otherwise particle colors are randomly selected from the range. When modify particle color in range is set, both cases are overridden and particles blend over the length of the glow path according to color rate of change.

No effect on trailing particles, which always randomly select from the bounds.

color bound 0ColorARGB

Sets a lower bound for particle color selection. Particle colors are interpolated in RGB space, not HSL. The chosen color is multiplied with the sprite texture. The alpha value has no effect, unlike in some tags where it controls tint vs. modulation.

FieldTypeComments
alphafloat
redfloat
greenfloat
bluefloat
color bound 1ColorARGB?

Sets an upper bound for particle color selection.

scale color 0ColorARGB
  • Unused

No visible effect in any color selection modes.

scale color 1ColorARGB
  • Unused

No visible effect in any color selection modes.

color rate of changefloat

When modify particle color in range is set, controls how many times the per-channel difference between color bounds is added per world unit of distance along the glow path.

fading percentage of glowfloat

Controls the distance that particles fade in and out at the path boundaries. A value of 0 means they do not fade in/out at all, while a value of 1 means the particles reach full opacity only at the middle of the path before beginning to fade out again.

particle generation freqfloat
  • Unit: Hz

The frequency that trailing particles are generated, in particles/sec. This is likely limited by the tick rate since values over 30 have no visual difference.

lifetime of trailing particlesfloat
  • Unit: seconds

How long trailing particles live. If particle generation freq is non-zero, then this field must be too. An undefined lifetime will cause a crash on render_cameras.c,#1086: bounds->x0<=bounds->x1.

velocity of trailing particlesfloat
  • Unit: world units per second

Initial trailing particle velocity at generation. Although this is labeled "wu/s", testing found that the the actual speed is roughly 1/40th the value given here. The exact factor or what factors might apply is unknown. Unaffected by effect translational velocity.

trailing particle minimum tfloat

Trailing particles will spawn from this minimum distance along the glow path, where 0 is the start of the path and 1 is the end of the path. This has no effect if the path is made of a single marker.

trailing particle maximum tfloat

Trailing particles will spawn up to this maximum distance along the glow path, where 0 is the start of the path and 1 is the end of the path. This has no effect if the path is made of a single marker.

textureTagDependency: bitmap

The sprite texture of particles. The referenced bitmap must have type sprites. It will not be randomly selected from sprite sheets (just the first). The particle is always shown camera-facing and with additive framebuffer blending. Alpha channel is ignored.

Acknowledgements

Thanks to the following individuals for their research or contributions to this topic:

  • Conscars (Testing behaviour of tag fields and glow path creation)
  • Kavawuvi (Invader tag definitions)
  • Kornman (Limits and how path construction works in glow updates)
  • MosesOfEgypt (Tag structure research)