model_collision_geometry

Model collision geometry tags contain collision data for an object. This is in contrast to model/gbxmodel tags, which mostly contain the renderable data. Collision meshes tend to be less detailed than render meshes, and are used to check collisions within the object's bounding radius.

Beyond having a collision mesh, these tags can also contain:

  • Pathfinding spheres which prevent AI from trying to walk through the object
  • Damage ratios for each part of the object (e.g. weak points)
  • Shield and health values

Collision geometry, rather than the model, is used to cast scenery shadows in lightmaps.

Pathfinding spheres

Pathfinding spheres (blue) for a50 shown in Sapien after running debug_objects_pathfinding_spheres 1

AI can figure out where to go by checking the pathfinding data on the BSP. However, since objects like scenery and units are not part of the BSP, Bungie implemented pathfinding spheres: spherical markers on objects that AI actively avoid walking into.

By placing these spheres in an object's collision model, artists can tell the AI exactly where not to go. As far as we know, all object types can make use of pathfinding spheres. The object's bounding sphere does not seem to affect AI avoidance of them.

How to add them

Pathfinding spheres are imported from the collision JMS file of your object. They are marked with #pathfinder and their radius is the actual radius that the AI will avoid walking in relation to the mid-point.

Pathfinding spheres can also be created automatically in some cases:

  • When an artist doesn't specify any pathfinding spheres, the game will assume one at the object's origin at half the size of the bounding sphere (which can be either too small or too big).
  • Vehicle mass points (see physics) also count as pathfinding spheres. AI will actively avoid these.
  • Bipeds by default also have a pathfinding sphere around their feet with the same width as their physics pill.

Limits

model_collision_geometry tags can only have up to 32 pathfinding spheres in legacy (256 in H1A), up to 8 regions, and up to 33 permutations to a region.

Animation

Unlike BSPs, collision geometry can have a self-intersecting mesh. However, this is only permitted between meshes parented by different nodes (e.g. limbs of a biped intersecting each other or the torso). Collision geometry cannot have weighted skinning for animations, so rigidly follows parent nodes in animations.

Phantom BSP

Phantom BSP exists in the collision model of covenant crates.

Although phantom BSP is typically seen in the context of level geometry, it can also affect model collision geometry because this tag uses the same collision data structures as a scenario_structure_bsp. In the case of models, phantom BSP is limited to the object's bounding radius.

Like with level geometry, these can be troubleshooted in Sapien by running the console commands:

collision_debug 1
collision_debug_phantom_bsp 1

To fix them, use similar tricks as fixing level phantom BSP: fixing cases of nearly co-planar faces reported in your WRL file and/or slightly altering the collision model around the problematic location. If changes to the source geometry do not resolve the phantom BSP, you can use H1A Tool with the fix-phantom-bsp option enabled or phantom_tool to compile the collision.

Related script functions and globals

The following are related functions that you can use in your scenario scripts and/or debug globals that you can enter into the developer console for troubleshooting.

Function/global

Type

(collision_debug [boolean])

If enabled, a ray is continually shot from the camera (by default) to troubleshoot ray-object and ray-BSP collisions. A red normal-aligned marker will be shown where the ray collides with a surface. The collision surface itself, whether BSP or model, will be outline in red. Information about the collision surface will be shown in the top left corner of the screen, including plane and surface indices from the BSP structure, material type, and how many degrees the surface's normal differs from vertical.

The types of surfaces which the test ray hits or ignores can be toggled with the collision_debug_flag_* commands. The maximum range of the ray is controlled with collision_debug_length.

This feature can be frozen in place with collision_debug_repeat and also moved directly with the collision_debug_point_* and collision_debug_vector_* globals. A red ray is visible in these situations when the ray is no longer shot from the camera's point of view.

Global
(collision_debug_features [boolean])

Toggles the display of collision features near the camera, which can be spheres (red), cylinders (blue), or prisms (green). Collision size can be adjusted with collision_debug_width and collision_debug_height. The test point can be frozen in place using collision_debug_repeat.

Global
(collision_debug_phantom_bsp [boolean])

Causes a floating pink cube and label "phantom bsp" to appear whenever a test ray from the center of the screen intersects with phantom BSP. It can be helpful to pair this with collision_debug_spray.

Global
(collision_debug_spray [boolean])

Setting this to true will cause collision ray tests to be performed in a dense grid from the viewport. This operates independently of the collision_debug setting, and only the destination hit markers are shown. Can be affected by collision flags, length, and frozen with collision_debug_repeat.

Global
(debug_objects_collision_models [boolean])

When debug_objects is enabled, displays green meshes for object collision models. This setting defaults to true in H1CE Sapien but not H1A Sapien.

Global
(debug_objects_pathfinding_spheres [boolean])

When debug_objects is enabled, displays object pathfinding spheres in blue.

Global

Structure and fields

FieldTypeComments
flagsbitfield
FlagMaskComments
takes shield damage for children0x1
takes body damage for children0x2
always shields friendly damage0x4
passes area damage to children0x8
parent never takes body damage for us0x10
only damaged by explosives0x20
only damaged while occupied0x40
indirect damage materialuint16
maximum body vitalityfloat

Sets the maximum amount of health that a unit has (damage hit points). For example, the cyborg biped has a value of 75. This value can also be changed at runtime for individual units by using unit_set_maximum_vitality in scripts.

body system shockfloat
friendly damage resistancefloat
  • Min: 0
  • Max: 1
localized damage effectTagDependency: effect
area damage effect thresholdfloat
  • Min: 0
  • Max: 1
area damage effectTagDependency: effect
body damaged thresholdfloat
body damaged effectTagDependency: effect
body depleted effectTagDependency: effect
body destroyed thresholdfloat
body destroyed effectTagDependency: effect
maximum shield vitalityfloat

Sets the maximum amount of shields that a unit has (damage hit points). For example, the cyborg biped has a value of 75. This value can also be changed at runtime for individual units by using unit_set_maximum_vitality in scripts.

shield material typeenum

Determines which damage_effect material modifier is used when applying damage to the shield.

OptionValueComments
dirt0x0
sand0x1
stone0x2
snow0x3
wood0x4
metal hollow0x5
metal thin0x6
metal thick0x7
rubber0x8
glass0x9
force field0xA
grunt0xB
hunter armor0xC
hunter skin0xD
elite0xE
jackal0xF
jackal energy shield0x10
engineer skin0x11
engineer force field0x12
flood combat form0x13
flood carrier form0x14
cyborg armor0x15
cyborg energy shield0x16
human armor0x17
human skin0x18
sentinel0x19
monitor0x1A
plastic0x1B
water0x1C
leaves0x1D
elite energy shield0x1E
ice0x1F
hunter shield0x20
shield failure functionenum
OptionValueComments
linear0x0
early0x1
very early0x2
late0x3
very late0x4
cosine0x5
shield failure thresholdfloat
failing shield leak fractionfloat
minimum stun damagefloat
stun timefloat
  • Unit: seconds
recharge timefloat
  • Unit: seconds
shield damaged thresholdfloat
shield damaged effectTagDependency: effect
shield depleted effectTagDependency: effect
shield recharging effectTagDependency: effect
shield recharge ratefloat
  • Cache only
materialsBlock
  • HEK max count: 32
  • Read-only
FieldTypeComments
nameTagString
  • Read-only
flagsbitfield
FlagMaskComments
head0x1
material typeenum?
shield leak percentagefloat
shield damage multiplierfloat
body damage multiplierfloat
regionsBlock
  • HEK max count: 8
  • Read-only
FieldTypeComments
nameTagString
  • Read-only
flagsbitfield
FlagMaskComments
lives until object dies0x1
forces object to die0x2
dies when object dies0x4
dies when object is damaged0x8
disappears when shield is off0x10
inhibits melee attack0x20

Prevents melee attacks by the unit when this region is destroyed. Used for the Flood's arms, for example.

inhibits weapon attack0x40
inhibits walking0x80
forces drop weapon0x100

Causes the unit to drop its held weapon when this region is destroyed, such as shooting the arms off of Flood.

causes head maimed scream0x200
damage thresholdfloat
destroyed effectTagDependency: effect
permutationsBlock
  • HEK max count: 32
  • Read-only
  • Read-only
FieldTypeComments
nameTagString
modifiersBlock
  • Read-only
FieldTypeComments
xBounds
FieldTypeComments
minfloat
maxfloat
yBounds?
zBounds?
pathfinding spheresBlock
  • HEK max count: 32
  • H1A-EK max count: 256
  • Volatile

Spheres which approximate the shape of the model for AI pathfinding avoidance. The sphere limit was increased to match Reach's in H1A.

FieldTypeComments
nodeuint16
centerPoint3D
FieldTypeComments
xfloat
yfloat
zfloat
radiusfloat
nodesBlock
  • HEK max count: 64
  • Read-only
  • Read-only
FieldTypeComments
nameTagString
regionuint16
parent nodeuint16
next sibling nodeuint16
first child nodeuint16
name thingint16
  • Cache only
bspsBlock
  • HEK max count: 32
  • Read-only
  • Processed during compile
FieldTypeComments
bsp3d nodesBlock
  • HEK max count: 131072

A block of nodes used to efficiently find collideable surfaces. Each node divides space with an infinite plane and references two child nodes by index into this block. The first element in the block is the root node. A test point can be recursively tested against planes to find a leaf of potentially colliding surfaces.

  • Read-only
FieldTypeComments
planeuint32

An index into the planes block. The plane divides 3D space into front and back half-spaces. A point can be tested against this plane to determine which half-space to descend into.

Tool will create new planes or use existing surface planes when generating the BSP tree based on a heuristic of how many surfaces remain in the node. For 1024 and above, axis-aligned planes based on vertex bounds are used. Otherwise, the surface within the remaining set whose plane minimizes the difference between front and back surface counts is used.

back childuint32
  • Flagged

Index of the BSP3D node representing space to the back of the dividing plane. A value of 0xFFFFFFFF is considered null and the space behind the plane is considered outside the sealed BSP (Sapien labels this as "solid" space when using debug_structure). Otherwise, if the highest order bit is set (0x80000000), the remaining 31 bits (mask 0x7FFFFFFF) represent an index into the leaves block.

front childuint32
  • Flagged

Similar to the back child field.

planesBlock
  • HEK max count: 65536

Planes are infinite 2D surfaces in 3D space. They are used both to subdivide the BSP in each bsp3d node and to define collideable surfaces. The first 8 planes in the block seem to serve a special purpose -- the first 4 define the XY bounding box, with the next 4 axis aligned planes unkown. Furthermore, the last 8 planes in the block are used for the first few levels of bsp3d nodes. A single plane may be referenced from multiple bsp3d nodes since the surfaces that planes derive from can also be found in multiple leaves.

  • Read-only
FieldTypeComments
planePlane3D

A plane which divides 3D space in two.

FieldTypeComments
vectorVector3D

A 3-float normal vector.

FieldTypeComments
ifloat
jfloat
kfloat
wfloat

Distance from origin (along normal).

leavesBlock
  • HEK max count: 65536

Leaves mark the transition between the 3D BSP and a collection of convex collideable surfaces in the same localized area. Each set of co-planar surfaces within this leaf is stored within a child 2D BSP.

Note that surfaces may be found under multiple leaves, since any surface which is not completely on either side of a 3D plane will need to belong to both child 3D BSP nodes.

  • Read-only
FieldTypeComments
flagsbitfield

Flags used to optimize collision checks at runtime.

FlagMaskComments
contains double sided surfaces0x1

Indicates if any surface in any of this leaf's 2D BSPs is double-sided (e.g. glass).

bsp2d reference countuint16

Determines how many contiguous 2D BSP references belong to this leaf.

first bsp2d referenceuint32

Index of the first 2D BSP reference associated with this leaf. It will be followed by a number of other 2D BSP references according to the above count.

bsp2d referencesBlock
  • HEK max count: 131072

Represents either a 2D BSP of surfaces, or a singular surface if the node index is flagged. In either case, test points or 3D line traces should be projected onto the basis plane in order to continue.

  • Read-only
FieldTypeComments
planeuint32
  • Flagged

Index for the plane used to decide what basis plane is best to project on (X,Y), (Y,Z) or (X,Z). The basis plane is chosen by the referenced plane's most significant normal component. A flagged plane index means its normal vector is opposite.

bsp2d nodeuint32
  • Flagged

The starting node for the 2D BSP on this plane. If flagged, then the index (masked to 0x7FFFFFFF) refers to a surface instead. Null if 0xFFFFFFFF.

bsp2d nodesBlock
  • HEK max count: 65535
  • Read-only
FieldTypeComments
planePlane2D

A 2D plane (line) subdividing left and right child surfaces. This line is in the space of the 2D BSP reference's basis plane.

FieldTypeComments
vectorVector2D
FieldTypeComments
ifloat
jfloat
wfloat
left childuint32
  • Flagged

refers to a BSP2D node if not signed and a surface if sign bit is set; null if 0xFFFFFFFF.

right childuint32
  • Flagged

refers to a BSP2D node if not signed and a surface if sign bit is set; null if 0xFFFFFFFF.

surfacesBlock
  • HEK max count: 131072

Surfaces are planar collideable polygons. They are not necessarily triangular and may have up to 8 edges. BSP collision surfaces can be visualized in Sapien using debug_structure 1. These surfaces are not used for the rendered geometry.

  • Read-only
FieldTypeComments
planeuint32
  • Flagged

Index into the planes block for this surface's plane. Note that multiple co-planar surfaces may reference the same plane since this plane index is a copy of the parent bsp2d reference's plane index, even when flagged.

first edgeuint32
flagsbitfield
FlagMaskComments
two sided0x1
invisible0x2
climbable0x4

Indicates if the surface is a climbable ladder.

breakable0x8

Indicates if the surface is breakable. The surface must also have a breakable surface index below.bit there cannot be more than 256 unique breakable surfaces in a BSP.

breakable surfaceint8

Index into this tag's breakable surfaces block. It is unknown if this is a signed or flagged field, but since it is 8-bit there cannot be more than 256 unique breakable surfaces in a BSP.

materialuint16
edgesBlock
  • HEK max count: 262144

Edges, surfaces, and vertices form a data structure called a doubly connected edge list (DCEL), also known as a half-edge data structure. The edges and their vertices define the boundaries of the collideable surfaces within a leaf.

  • Read-only
FieldTypeComments
start vertexuint32
end vertexuint32
forward edgeuint32
reverse edgeuint32
left surfaceuint32
right surfaceuint32
verticesBlock
  • HEK max count: 131072

The 3D coordinates used for edge starting and ending locations.

  • Read-only
FieldTypeComments
pointPoint3D?
first edgeuint32

Acknowledgements

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

  • Kavawuvi (Invader tag definitions)
  • Knight Artorias (Region and permutation limits)
  • lag supreme (Visualization of phantom BSP in covie crates)
  • MosesOfEgypt (Tag structure research)