Difference between revisions of "KCL (File Format)"

From Custom Mario Kart
Jump to navigation Jump to search
m (More documentation and typo fix)
 
(38 intermediate revisions by 15 users not shown)
Line 1: Line 1:
'''KCL Files''' are collision files used in [[Mario Kart Wii]] and many other games. They have been used since at least [[Mario Kart DS]]. The documentation here is specifically for the [[Mario Kart Wii]] implmentation, although other implmentations are likely to be similar.<br />
+
'''KCL Files''' are collision files used in [[Mario Kart Wii]] and many other games. They have been used since at least [[Mario Kart DS]]. The documentation here is specifically for the Mario Kart Wii implementation, although other implementations are likely to be similar.<br />
 
The KCL files contain simplified versions of model files in order to allow rapid collision detection. They use an octree for efficient spatial indexing, as well as storing the actual triangles of the models in a more rapidly accessible format.  
 
The KCL files contain simplified versions of model files in order to allow rapid collision detection. They use an octree for efficient spatial indexing, as well as storing the actual triangles of the models in a more rapidly accessible format.  
  
Available editors:
 
*[[SZS Modifier]]
 
*[[CTools]]
 
 
== File Format ==
 
== File Format ==
 
The basic file format consists of a header, and four data sections.
 
The basic file format consists of a header, and four data sections.
 +
 
=== Header ===
 
=== Header ===
The header is a 0x3c byte structure as follows.
+
The header is a 0x38 or 0x3c byte structure as follows.
 
{| class="wikitable"
 
{| class="wikitable"
! Offset !! Type !! Description
+
! Offset !! Type !! Name !! Description
 
|-
 
|-
| 0x00 || u32 || Offset to section 1
+
| 0x00 || u32 || pos_data_offset || 0-indexed offset to array of position vectors.
 
|-
 
|-
| 0x04 || u32 || Offset to section 2
+
| 0x04 || u32 || nrm_data_offset || 0-indexed offset to array of normal vectors.
 
|-
 
|-
| 0x08 || u32 || Offset to section 3
+
| 0x08 || u32 || prism_data_offset || 1-indexed offset to array of triangular prisms.
 
|-
 
|-
| 0x0c || u32 || Offset to section 4
+
| 0x0c || u32 || block_data_offset || Offset to collision octree blocks.
 
|-  
 
|-  
| 0x10 || single || '''Unknown''' (300.0 typical)
+
| 0x10 || f32 || prism_thickness || Depth of a triangular prism along its normal vector.
 
|-
 
|-
| 0x14 || single3 || Spatial grid first coordinate
+
| 0x14 || Vec3 || area_min_pos || Smallest possible coordinate in the model
 
|-
 
|-
| 0x20 || u32 || X mask
+
| 0x20 || u32 || area_x_width_mask || X mask. X coordinates with bits outside this mask are outside the model (See section 4)
 
|-
 
|-
| 0x24 || u32 || Y mask
+
| 0x24 || u32 || area_y_width_mask || Y mask. Y coordinates with bits outside this mask are outside the model (See section 4)
 
|-
 
|-
| 0x28 || u32 || Z mask
+
| 0x28 || u32 || area_z_width_mask || Z mask. Z coordinates with bits outside this mask are outside the model (See section 4)
 
|-
 
|-
| 0x2c || u32 || Coordinate shift
+
| 0x2c || u32 || block_width_shift || Octree leaf block size shift (size = 2^block_width_shift = 1 << block_width_shift)
 
|-
 
|-
| 0x30 || u32 || Y shift
+
| 0x30 || u32 || area_x_blocks_shift || Y shift. Used to form the index of the root block's children (See section 4 algorithm)
 
|-
 
|-
| 0x34 || u32 || Z shift
+
| 0x34 || u32 || area_xy_blocks_shift || Z shift. Used to form the index of the root block's children (See section 4 algorithm)
|-
 
| 0x38 || single || '''Unknown''' (250.0 typical)
 
 
|-
 
|-
 +
| 0x38 || f32 || OPTIONAL[ sphere_radius ] || Maximum radius of a sphere that can be collided with this file. This field must be used when computing the octree; e.g., to consider which triangles are inside an octree 32x32 block, it is insufficient to just consider a 32x32 cube since it would miss prisms a player could hit while being on the edge of the box. Therefore, it is necessary to consider a distance field with constant distance of sphere_radius. From analysis of official files, this is not just a larger cube, but actually a curvy, beveled figure (especially noticeable with small cubes where sphere_radius > width). Official tools will only include prisms that are actually reachable in leaf nodes which produces more efficient files than homebrew. This field is not provided by KCL files from [[Super Mario Galaxy]]/other platformers. Therefore, using those tools for KCL may be problematic as [[Mario Kart Wii]] expects this value.
 
|}
 
|}
 
All offsets are relative to the start of the file. <br/>
 
All offsets are relative to the start of the file. <br/>
The meaning of the shift and mask values is explained in section 4.  
+
The meaning of the shift and mask values is explained in section 4.
=== Section 1 - Verticies ===
+
 
Section 1 is simply a large array of verticies, stored as 3 successive singles for x, y and z. The length of this array is not stored, but can usually be calculated by subtracting the section 1 offset from the section 2 offset and dividing by 0xc.  
+
An official KCL generation algorithm can be observed in Super Mario Galaxy's <code>DynamicCollisionObj::updateCollisionHeader()</code>. A decompilation is provided below:
 +
<pre>
 +
void DynamicCollisionObj::updateCollisionHeader()
 +
{
 +
  Vec min, max, extent;
 +
  int masks[3];
 +
  int extent_int_x, extent_int_y, extent_int_z;
 +
  int max_entropy = 0, component = 0, component_ = 0, bits_sel, mask_sel, i, v8, bit_shift, area_y_width_mask, area_z_width_mask;
 +
 
 +
  // Compute bounding box and extent
 +
  MR::createBoundingBox(this->mVertices, this->mNumVertices, &min, &max);
 +
  JGeometry::TVec3<f>::TVec3<f>(&extent, &max);
 +
  JGeometry::TVec3<f>::__ami(&extent, &min);
 +
 
 +
  // Initialize masks with extent
 +
  extent_int_x = (int)extent.x;
 +
  extent_int_y = (int)extent.y;
 +
  extent_int_z = (int)extent.z;
 +
  masks[0] = extent_int_x;
 +
  masks[1] = extent_int_y;
 +
  masks[2] = extent_int_z;
 +
  if ( !extent_int_x ) masks[0] = 1;
 +
  if ( !masks[1] ) masks[1] = 1;
 +
  if ( !masks[2] ) masks[2] = 1;
 +
 
 +
  // Find highest number of bits to shift
 +
  for (component = 0; component < 3; ++component)
 +
  {
 +
    bits_sel = 0x80000000;
 +
    mask_sel = 0xFFFFFFFF;
 +
    i = 0;
 +
    v8 = 32;
 +
    while ( (masks[component] & bits_sel) == 0 )
 +
    {
 +
      bits_sel >>= 1;
 +
      mask_sel >>= 1;
 +
      ++i;
 +
      if ( !--v8 )
 +
        goto CONTINUE;
 +
    }
 +
    masks[component] = ~mask_sel;
 +
    if ( max_entropy < i )
 +
      max_entropy = i;
 +
    CONTINUE:;
 +
  }
 +
 
 +
  // Update header fields
 +
  bit_shift = 33 - max_entropy;
 +
  area_y_width_mask = masks[1];
 +
  this->mpHeader->area_x_width_mask = masks[0];
 +
  area_z_width_mask = masks[2];
 +
  this->mpHeader->area_y_width_mask = area_y_width_mask;
 +
  this->mpHeader->area_z_width_mask = area_z_width_mask;
 +
  this->mpHeader->block_width_shift = bit_shift;
 +
  JGeometry::TVec3<f>::__as(&this->mpHeader->area_min_pos, &min);
 +
}
 +
</pre>
 +
 
 +
=== Section 1 - Position Vectors ===
 +
Section 1 is simply a large array of position vectors, stored as 3 successive floats for X, Y and Z. The length of this array is not stored. Do not compute the size of this section by subtracting the address of Section 2 from the start of Section 1; these sections may appear in any order. You may compute an upper bound by sorting a list containing all section offsets and the filesize; to actually determine the size of this section, though, you must parse the prisms list and keep track of the highest position index.
 +
 
 
=== Section 2 - Normals ===
 
=== Section 2 - Normals ===
Section 2 is much the same as section 1, in that it is a large array of normals. Again the values are stored as 3 successive singles for x, y and z. The length of this array is not stored, but can usually be calculated by subtracting the section 2 offset from the section 3 offset + 0x10 and dividing by 0xc.
+
Section 2 is much the same as section 1, in that it is a large array of normal vectors. Again the values are stored as 3 successive floats for X, Y and Z. The length of this array is not stored. Do not compute the size of this section by subtracting the address of Section 3 from the start of Section 2; these sections may appear in any order. You may compute an upper bound by sorting a list containing all section offsets and the filesize; to actually determine the size of this section, though, you must parse the prisms list and keep track of the highest normal index across every field (face normals + edge normals).
 +
 
 +
=== Section 3 - Triangular Prisms ===
 +
The third section is a pool of triangular prisms encoded as face normal vectors and a height. The offset to this section is stored as 0x10 less than the actual location of the data because this section is one indexed in section 4. The structure of each entry in this section is a 0x10 byte structure given below.
 +
 
 +
The length of this array is not stored. Do not compute the size of this section by subtracting the address of Section 4 from the start of Section 3; these sections may appear in any order. You may compute an upper bound by sorting a list containing all section offsets and the filesize; to actually determine the size of this section, though, you must parse the '''octree''' and keep track of the highest prism index.
  
=== Section 3 - Triangles ===
 
The third section is the section containg the actual model information. The offset to this section is stored as 0x10 less than the actual location of the data, because this section is one indexed in section 4. The strcutre of each entry in this section is a 0x10 byte strcutre given below.
 
 
{| class="wikitable"
 
{| class="wikitable"
! Offset !! Type !! Description
+
! Offset !! Type !! name !! Description
|-
 
| 0x00 || single || Length
 
 
|-
 
|-
| 0x04 || u16 || Position index
+
| 0x00 || f32 || height || Triangle altitude from Vertex 1 to the opposite side.
 
|-
 
|-
| 0x06 || u16 || Direction index
+
| 0x04 || u16 || pos_i || Vertex 1 position index (0 based index into section 1).
 
|-
 
|-
| 0x08 || u16 || Normal A index
+
| 0x06 || u16 || fnrm_i || Face normal vector index (0 based index into section 2).
 
|-
 
|-
| 0x0a || u16 || Normal B index
+
| 0x08 || u16 || enrm1_i || First edge normal vector (in counterclockwise order) -- Normal A index (0 based index into section 2).
 
|-
 
|-
| 0x0c || u16 || Normal C index
+
| 0x0a || u16 || enrm2_i || Second edge normal vector (in counterclockwise order) -- Normal B index (0 based index into section 2).
 
|-
 
|-
| 0x0e || u16 || Collision flags
+
| 0x0c || u16 || enrm3_i || Third edge normal vector (in counterclockwise order) -- Normal C index (0 based index into section 2).
 
|-
 
|-
 +
| 0x0e || u16 || attribute || [[KCL flag|Collision attribute]]. In Super Mario Galaxy, this is an attribute *index* into an Attribute Lookup Table. In Mario Kart Wii, the attribute is stored directly in prisms.
 
|}
 
|}
All indicies in this section are 0 indexed. The position index is an index for section 1, and the others are indicies to section 2. The exact manner in which the values are used for collision detection is unknown, however a method for converting this form of triangle to a set of three coordinates is outlined below. The coordinate system is right handed.
+
 
 +
All indices in this section are 0 indexed. The position index is an index for section 1, and the others are indices to section 2. The coordinate system is right handed.
 +
 
 +
The decoding algorithm can be seen in [[Mario Kart 7]]'s <code>KDGndCol::PolygonMesh::parseModel(const KDGndCol::KColData&, bool)</code>. It is described below:
 
<pre>
 
<pre>
Vertex1 = Position
+
CrossA  = cross(enrm1, fnrm)
Vertex2 = Position + Cross(Direction, NormalA) * (Length / Dot(NormalA, NormalC))
+
CrossB  = cross(enrm2, fnrm)
Vertex3 = Position + Cross(NormalB, Direction) * (Length / Dot(NormalB, NormalC))
+
Vertex1 = pos
 +
Vertex2 = pos + CrossB * (height / dot(CrossB, enrm3))
 +
Vertex3 = pos + CrossA * (height / dot(CrossA, enrm3))
 
</pre>
 
</pre>
A method for converting three verticies into the KCL form is given below. This method assumes the verticies are arranged anti clockwise when viewed from the collidable side.
+
An encoding algorithm can be seen in Super Mario Galaxy's <code>DynamicCollisionObj::updateTriangle()</code>. A method for converting three vertices into the KCL form is given below.  
 +
One approach is described below: (this assumes vertices are arranged counter-clockwise when viewed from their front side)
 
<pre>
 
<pre>
Position = Vertex1
+
pos    = Vertex1
Direction = Cross(Normalize(Vertex2 - Vertex1), Normalize(Vertex3 - Vertex1))
+
fnrm  = normalize(cross( Vertex2 - Vertex1, Vertex3 - Vertex1 ))
Length = Dot(Normalize(Vertex2 - Vertex1), Direction)
+
enrm1  = normalize(cross(fnrm, Vertex3 - Vertex1))
NormalA = Cross(Direction, Normalize(Vertex2 - Vertex1))
+
enrm2  = normalize(-cross(fnrm, Vertex2 - Vertex1))
NormalB = Cross(Normalize(Vertex3 - Vertex1), Direction)
+
enrm3  = normalize(cross(fnrm, Vertex2 - Vertex3))
NormalC = Cross(Direction, Normalize(Vertex3 - Vertex2))
+
Length = dot(Vertex2 - Vertex1, enrm3)
 
</pre>
 
</pre>
=== Section 4 ===
 
Section 4 is a spatial index. It is a series of u32 values, followed by lists of u16s. It subdivides three dimensional space using an octree, and indicates which triangles from section 3, if any, appear in each cube of the space. Given a coordinate (x,y,z) in world space, in order to find which triangles are at in range of that location, the spatial gird first coordinate is subtracted from the coordinate. If the value is negative, it is not colliding. If the value is positive, it is rounded to a u32, and then each component is ANDed with the mask. If the value is non zero, it is also not colliding. If not, the the original u32 components are all shifted right by coordinate shift. The y and z coordinates are then shifted left by their shift values. The resulting components are ORed with each other to produce an index into the octree.<br/>
 
The octree is then to be followed until a triangle list is found. At each stage, if the top bit of the current u32 is set, then the remaining 31 bits are an offset to a list of u16 triangle indicies. Each of these is a 1 based index to section 3, which is a triangle that must be checked by an object at the original location. The list is 0 terminated. If the top bit is not set, then the remaining 31 bits are an offset to 8 more of the u32s in the octree. The index into these 8 values is calculated by getting the next least significant bit in the u32 of each component, and then shifting z left by 2, y left by 1 and oring them all together. The procedure then repeats with the value at that offset.
 
== Mario Kart Wii Collision Flags ==
 
In [[Mario Kart Wii]] the collision flag values are normally split. The 5 least significant bits determine the basic type of the flag. The next 11 bits determine the variation upon the type. The list below describes these types and their variants.
 
===Collision Types ===
 
{| class="wikitable"
 
|-
 
! Type
 
! What is it?
 
|-
 
|0x00
 
|Road
 
|-
 
|0x01
 
|Weak off road
 
|-
 
|0x02
 
|Very weak offroad with a dirt effect
 
|-
 
|0x03
 
|Offroad
 
|-
 
|0x04
 
|Heavy offroad with a sand effect
 
|-
 
|0x05
 
|Off road
 
|-
 
|0x06
 
|Boost
 
|-
 
|0x07
 
|Fast trick
 
|-
 
|0x08
 
|Trick ramp
 
|-
 
|0x09
 
|Out of bounds
 
|-
 
|0x0A
 
|Solid fall
 
|-
 
|0x0B
 
|Shallow Water (weak offroad, like on Shy Guy Beach)
 
|-
 
|0x0C
 
|Wall
 
|-
 
|0x0D
 
|Wall, but makes no sound effect when touched. Sparks still appear.
 
|-
 
|0x0E
 
|Does nothing
 
|-
 
|0x0F
 
|Normal wall
 
|-
 
|0x10
 
|Fall boundary
 
|-
 
|0x11
 
|Cannon Activator
 
|-
 
|0x12
 
|Does nothing
 
|-
 
|0x13
 
|Half-Pipe blue ramp (like the ones on DK Summit)
 
|-
 
|0x14
 
|Normal Wall
 
|-
 
|0x15
 
|Moving terrain
 
|-
 
|0x16
 
|Sticky Road
 
|-
 
|0x17
 
|Road, slightly different than 0x00
 
|-
 
|0x18
 
|Sound Trigger
 
|-
 
|0x19
 
|Does nothing
 
|-
 
|0x1A
 
|Music channel activator???
 
|-
 
|0x1B
 
|Does nothing
 
|-
 
|0x1C
 
|Does nothing
 
|-
 
|0x1D
 
|Gravelroad
 
|-
 
|0x1E
 
|Spin-out when touched
 
|-
 
|0x1F
 
|Wall
 
|-
 
|}
 
  
=== Variants ===
+
=== Section 4 - Spatial Index ===
Here comes a list with variants, they are also used by kcl files and control the effects.
+
Offsets in this section are interpreted differently depending on if that offset represents a non-leaf or a leaf block. The first entry represents the root block which is not a leaf block and contains the entire model. Subsequent blocks are subdivisions of the root block until the leaf blocks are reached.
Thanks to bigoto for the list
+
* In non-leaf blocks, the offset points to an array of 8 u32 offsets, which are the offsets of the block's children.
 +
* In leaf blocks, the offset points to an array of u16 prism indices. Each of these is a 1-based index to section 3, which is a triangle that must be checked by an object at the original location. The first entry of a list is always 0, which serves as a 0 termination for the previous list.
 +
 
 +
As an example, MKW's algorithm for finding the leaf block that contains a point is as follows
 +
<pre>
 +
u16* KCollisionOctree::searchBlock(const EGG::Vector3f& point) {
 +
  // Calculate the x, y, and z offsets of the point from the minimum
 +
  // corner of the tree's bounding box.
 +
  const int x = point.x - this->area_min_pos.x;
  
==== Road (0x00) ====
+
  // Check if the point is outside the tree's bounding box in the x, y,
{| class="wikitable"
+
  // or z dimensions. If it is, return 0.
|-
+
  if ((x & this->area_x_width_mask) != 0) {
! ID
+
    return 0;
! What is it?
+
  }
|-
 
|000
 
|Normal Road
 
|-
 
|001
 
|Dirt Road
 
|-
 
|002
 
|Sand
 
|-
 
|003
 
|Slippery Road
 
|-
 
|004
 
|Wood Road
 
|-
 
|005
 
|Snow Road
 
|-
 
|006
 
|Metal Grid/Chain Link Road
 
|-
 
|008
 
|Concrete Road (slightly different than 000)
 
|-
 
|00A
 
|Sand with shadow effect
 
|-
 
|00B
 
|Ice Road
 
|-
 
|00D
 
|Snow Road
 
|-
 
|10x
 
|Trickable road
 
|-
 
|20x
 
|No drivable road
 
|-
 
|0xy
 
|Effect properties
 
|-
 
|}
 
  
'''Tickable road:''' x = road variant (ex. 104 is trickable wood road) <br>
+
  const int y = point.y - this->area_min_pos.y;
'''No drivable road:''' means you can't drive further, you will be pushed back if you try it. Can be used to determine boundaries or very pronounced slopes. <br>
+
  if ((y & this->area_y_width_mask) != 0) {
'''Effect properties:''' x: Intensity. y: road variant (ex. 045 is Road with a big Snow effect. 145 is Trickable road with a big snow effect).
+
    return 0;
 +
  }
  
==== Weak off road (0x01) ====
+
  const int z = point.z - this->area_min_pos.z;
{| class="wikitable"
+
  if ((z & this->area_z_width_mask) != 0)
|-
+
  {
! ID
+
    return 0;
! What is it?
+
  }
|-
 
|005
 
|Sand
 
|-
 
|00D
 
|Sand with shadow effect
 
|-
 
|0xy
 
|Variant Sound Properties
 
|-
 
|}
 
  
'''Variant Sound Properties''' <br>
+
  // Initialize the current tree node to the root node of the tree.
x: Sound effect intensity (ex. 005 is Sand with a very audible effect, 045 makes the sound almost inaudible). <br>
+
  u32 shift = this->block_width_shift;
y: Variant
+
  u8* curBlock = this->block_data;
 +
  s32 offset;
  
==== Off road (0x03) ====
+
  // Traverse the tree to find the leaf node containing the input point.
{| class="wikitable"
+
  u32 index = 4 * (((u32)z >> shift) << this->area_xy_blocks_shift
|-
+
        | ((u32)y >> shift) << this->area_x_blocks_shift
! ID
+
        | (u32)x >> shift);
! What is it?
 
|-
 
|000
 
|Dirt
 
|-
 
|002
 
|Mud
 
|-
 
|004
 
|Grass
 
|-
 
|008
 
|Weak Dirt
 
|-
 
|00C
 
|Grass with bigger effect (more plants come out)
 
|-
 
|00D
 
|Sand with shadow effect
 
|-
 
|0xy
 
|Intensity properties
 
|}
 
  
'''Intensity properties''' <br>
+
  while (true) {
x: can be either effect strenght or sound strenght <br>
+
    // Get the offset of the current node's child node.
y: offroad variant <br>
+
    offset = *(u32*)(curBlock + index);
  
Example: Variant 050 is a strong dirt-effect offroad. Variant 0CD makes the sand sound almost inaudible.  
+
    // If the offset is negative, the current node is a leaf node.
 +
    if ((offset & 0x80000000) != 0) {
 +
      break;
 +
    }
  
==== Wall (0x0C) ====
+
    // If the offset is non-negative, update the current node to be
{| class="wikitable"
+
    // the child node and continue traversing the tree.
|-
+
    shift--;
! ID
+
    curBlock += offset;
! What is it?
 
|-
 
|000
 
|Normal Wall
 
|-
 
|001
 
|Rock Wall
 
|-
 
|002
 
|Metal Wall
 
|-
 
|003
 
|Guard Rail
 
|-
 
|004
 
|Short tree wall sound effect
 
|-
 
|005
 
|Tree Wall
 
|-
 
|006
 
|Tree Wall without leaf effect
 
|-
 
|007
 
|Rubber Wall
 
|-
 
|008
 
|Hollow Wall
 
|-
 
|009
 
|Wall without being bumped
 
|-
 
|40x
 
|The "bump" depends on the angle you hit the wall.
 
|-
 
|}
 
  
==== Fall Boundary (0x10) ====
+
    index = 4 * ((4 * ((u32)z >> shift)) & 4
{| class="wikitable"
+
        | (2 * ((u32)y >> shift)) & 2
|-
+
        | (1 * ((u32)x >> shift)) & 1);
! ID
+
  }
! What is it?
 
|-
 
| 000
 
| Air fall
 
|-
 
| 001
 
| Water (activates pocha)
 
|-
 
|}
 
  
==== Road (0x17) ====
+
  // Return the address of the leaf node containing the input point.
{| class="wikitable"
+
  return reinterpret_cast<u16*>(curBlock + (offset & 0x7FFFFFFF));
|-
+
}
! ID
+
</pre>
! What is it?
 
|-
 
| 002
 
| Sand sound, with dirt effect
 
|-
 
| 003
 
| Same as 0x00 variant 000, but the sound it makes is in a lower tone
 
|-
 
|005
 
|Same as above, sound is like drifting on glass
 
|-
 
| 00A
 
| Dirt effect
 
|-
 
| 10x
 
| Trickable road
 
|-
 
| 20x
 
| Not drivable road
 
|-
 
|}
 
  
==== Gravelroad (0x1D) ====
+
== Tools ==
{| class="wikitable"
+
The following tools can handle KCL files:
|-
+
* [[Collision Tools]], by Blank: object exporter and importer, designed for [[Super Mario Galaxy]], but usable for [[Mario Kart Wii]]
! ID
+
* [[CTools]], by [[Chadderz]]: editor
! What is it?
+
* [[SZS Modifier]], by [[Chadderz]]: editor
|-
+
* [[un-beancorner]], by [[Atlas]]: allows lowering wall collision to prevent clipping over road with walls around them
|000
+
* [[Wexos's Toolbox]], by [[Wexos]]
|Gravel
+
* [[Wiimms SZS Tools]], by [[Wiimm]]: [[3D Tool]]-compatible exporter and importer (may [[Creating a KCL with Wiimms Tools|create a KCL]] directly from an OBJ file). Wiimms SZS Tools are recommended for KCL creation; see [[Creating a KCL with Wiimms Tools]] for details.
|-
 
|004
 
|Gravel (different sound)
 
|-
 
|00A
 
|Normal Road (like 0x00)
 
|-
 
|00B
 
|Glass road with echo
 
|-
 
|00C
 
|Gravel with shadow effect
 
|}
 
  
[[category:File Format]]
+
[[Category:File Format/MKW]]

Latest revision as of 23:01, 13 December 2023

KCL Files are collision files used in Mario Kart Wii and many other games. They have been used since at least Mario Kart DS. The documentation here is specifically for the Mario Kart Wii implementation, although other implementations are likely to be similar.
The KCL files contain simplified versions of model files in order to allow rapid collision detection. They use an octree for efficient spatial indexing, as well as storing the actual triangles of the models in a more rapidly accessible format.

File Format

The basic file format consists of a header, and four data sections.

Header

The header is a 0x38 or 0x3c byte structure as follows.

Offset Type Name Description
0x00 u32 pos_data_offset 0-indexed offset to array of position vectors.
0x04 u32 nrm_data_offset 0-indexed offset to array of normal vectors.
0x08 u32 prism_data_offset 1-indexed offset to array of triangular prisms.
0x0c u32 block_data_offset Offset to collision octree blocks.
0x10 f32 prism_thickness Depth of a triangular prism along its normal vector.
0x14 Vec3 area_min_pos Smallest possible coordinate in the model
0x20 u32 area_x_width_mask X mask. X coordinates with bits outside this mask are outside the model (See section 4)
0x24 u32 area_y_width_mask Y mask. Y coordinates with bits outside this mask are outside the model (See section 4)
0x28 u32 area_z_width_mask Z mask. Z coordinates with bits outside this mask are outside the model (See section 4)
0x2c u32 block_width_shift Octree leaf block size shift (size = 2^block_width_shift = 1 << block_width_shift)
0x30 u32 area_x_blocks_shift Y shift. Used to form the index of the root block's children (See section 4 algorithm)
0x34 u32 area_xy_blocks_shift Z shift. Used to form the index of the root block's children (See section 4 algorithm)
0x38 f32 OPTIONAL[ sphere_radius ] Maximum radius of a sphere that can be collided with this file. This field must be used when computing the octree; e.g., to consider which triangles are inside an octree 32x32 block, it is insufficient to just consider a 32x32 cube since it would miss prisms a player could hit while being on the edge of the box. Therefore, it is necessary to consider a distance field with constant distance of sphere_radius. From analysis of official files, this is not just a larger cube, but actually a curvy, beveled figure (especially noticeable with small cubes where sphere_radius > width). Official tools will only include prisms that are actually reachable in leaf nodes which produces more efficient files than homebrew. This field is not provided by KCL files from Super Mario Galaxy/other platformers. Therefore, using those tools for KCL may be problematic as Mario Kart Wii expects this value.

All offsets are relative to the start of the file.
The meaning of the shift and mask values is explained in section 4.

An official KCL generation algorithm can be observed in Super Mario Galaxy's DynamicCollisionObj::updateCollisionHeader(). A decompilation is provided below:

void DynamicCollisionObj::updateCollisionHeader()
{
  Vec min, max, extent;
  int masks[3];
  int extent_int_x, extent_int_y, extent_int_z;
  int max_entropy = 0, component = 0, component_ = 0, bits_sel, mask_sel, i, v8, bit_shift, area_y_width_mask, area_z_width_mask;

  // Compute bounding box and extent
  MR::createBoundingBox(this->mVertices, this->mNumVertices, &min, &max);
  JGeometry::TVec3<f>::TVec3<f>(&extent, &max);
  JGeometry::TVec3<f>::__ami(&extent, &min);

  // Initialize masks with extent
  extent_int_x = (int)extent.x;
  extent_int_y = (int)extent.y;
  extent_int_z = (int)extent.z;
  masks[0] = extent_int_x;
  masks[1] = extent_int_y;
  masks[2] = extent_int_z;
  if ( !extent_int_x ) masks[0] = 1;
  if ( !masks[1] ) masks[1] = 1;
  if ( !masks[2] ) masks[2] = 1;

  // Find highest number of bits to shift
  for (component = 0; component < 3; ++component)
  {
    bits_sel = 0x80000000;
    mask_sel = 0xFFFFFFFF;
    i = 0;
    v8 = 32;
    while ( (masks[component] & bits_sel) == 0 )
    {
      bits_sel >>= 1;
      mask_sel >>= 1;
      ++i;
      if ( !--v8 )
        goto CONTINUE;
    }
    masks[component] = ~mask_sel;
    if ( max_entropy < i )
      max_entropy = i;
    CONTINUE:;
  }

  // Update header fields
  bit_shift = 33 - max_entropy;
  area_y_width_mask = masks[1];
  this->mpHeader->area_x_width_mask = masks[0];
  area_z_width_mask = masks[2];
  this->mpHeader->area_y_width_mask = area_y_width_mask;
  this->mpHeader->area_z_width_mask = area_z_width_mask;
  this->mpHeader->block_width_shift = bit_shift;
  JGeometry::TVec3<f>::__as(&this->mpHeader->area_min_pos, &min);
}

Section 1 - Position Vectors

Section 1 is simply a large array of position vectors, stored as 3 successive floats for X, Y and Z. The length of this array is not stored. Do not compute the size of this section by subtracting the address of Section 2 from the start of Section 1; these sections may appear in any order. You may compute an upper bound by sorting a list containing all section offsets and the filesize; to actually determine the size of this section, though, you must parse the prisms list and keep track of the highest position index.

Section 2 - Normals

Section 2 is much the same as section 1, in that it is a large array of normal vectors. Again the values are stored as 3 successive floats for X, Y and Z. The length of this array is not stored. Do not compute the size of this section by subtracting the address of Section 3 from the start of Section 2; these sections may appear in any order. You may compute an upper bound by sorting a list containing all section offsets and the filesize; to actually determine the size of this section, though, you must parse the prisms list and keep track of the highest normal index across every field (face normals + edge normals).

Section 3 - Triangular Prisms

The third section is a pool of triangular prisms encoded as face normal vectors and a height. The offset to this section is stored as 0x10 less than the actual location of the data because this section is one indexed in section 4. The structure of each entry in this section is a 0x10 byte structure given below.

The length of this array is not stored. Do not compute the size of this section by subtracting the address of Section 4 from the start of Section 3; these sections may appear in any order. You may compute an upper bound by sorting a list containing all section offsets and the filesize; to actually determine the size of this section, though, you must parse the octree and keep track of the highest prism index.

Offset Type name Description
0x00 f32 height Triangle altitude from Vertex 1 to the opposite side.
0x04 u16 pos_i Vertex 1 position index (0 based index into section 1).
0x06 u16 fnrm_i Face normal vector index (0 based index into section 2).
0x08 u16 enrm1_i First edge normal vector (in counterclockwise order) -- Normal A index (0 based index into section 2).
0x0a u16 enrm2_i Second edge normal vector (in counterclockwise order) -- Normal B index (0 based index into section 2).
0x0c u16 enrm3_i Third edge normal vector (in counterclockwise order) -- Normal C index (0 based index into section 2).
0x0e u16 attribute Collision attribute. In Super Mario Galaxy, this is an attribute *index* into an Attribute Lookup Table. In Mario Kart Wii, the attribute is stored directly in prisms.

All indices in this section are 0 indexed. The position index is an index for section 1, and the others are indices to section 2. The coordinate system is right handed.

The decoding algorithm can be seen in Mario Kart 7's KDGndCol::PolygonMesh::parseModel(const KDGndCol::KColData&, bool). It is described below:

CrossA  = cross(enrm1, fnrm)
CrossB  = cross(enrm2, fnrm)
Vertex1 = pos
Vertex2 = pos + CrossB * (height / dot(CrossB, enrm3))
Vertex3 = pos + CrossA * (height / dot(CrossA, enrm3))

An encoding algorithm can be seen in Super Mario Galaxy's DynamicCollisionObj::updateTriangle(). A method for converting three vertices into the KCL form is given below. One approach is described below: (this assumes vertices are arranged counter-clockwise when viewed from their front side)

pos    = Vertex1
fnrm   = normalize(cross( Vertex2 - Vertex1, Vertex3 - Vertex1 ))
enrm1  = normalize(cross(fnrm, Vertex3 - Vertex1))
enrm2  = normalize(-cross(fnrm, Vertex2 - Vertex1))
enrm3  = normalize(cross(fnrm, Vertex2 - Vertex3))
Length = dot(Vertex2 - Vertex1, enrm3)

Section 4 - Spatial Index

Offsets in this section are interpreted differently depending on if that offset represents a non-leaf or a leaf block. The first entry represents the root block which is not a leaf block and contains the entire model. Subsequent blocks are subdivisions of the root block until the leaf blocks are reached.

  • In non-leaf blocks, the offset points to an array of 8 u32 offsets, which are the offsets of the block's children.
  • In leaf blocks, the offset points to an array of u16 prism indices. Each of these is a 1-based index to section 3, which is a triangle that must be checked by an object at the original location. The first entry of a list is always 0, which serves as a 0 termination for the previous list.

As an example, MKW's algorithm for finding the leaf block that contains a point is as follows

u16* KCollisionOctree::searchBlock(const EGG::Vector3f& point) {
  // Calculate the x, y, and z offsets of the point from the minimum
  // corner of the tree's bounding box.
  const int x = point.x - this->area_min_pos.x;

  // Check if the point is outside the tree's bounding box in the x, y,
  // or z dimensions. If it is, return 0.
  if ((x & this->area_x_width_mask) != 0) {
    return 0;
  }

  const int y = point.y - this->area_min_pos.y;
  if ((y & this->area_y_width_mask) != 0) {
    return 0;
  }

  const int z = point.z - this->area_min_pos.z;
  if ((z & this->area_z_width_mask) != 0)
  {
    return 0;
  }

  // Initialize the current tree node to the root node of the tree.
  u32 shift = this->block_width_shift;
  u8* curBlock = this->block_data;
  s32 offset;

  // Traverse the tree to find the leaf node containing the input point.
  u32 index = 4 * (((u32)z >> shift) << this->area_xy_blocks_shift
        | ((u32)y >> shift) << this->area_x_blocks_shift
        | (u32)x >> shift);

  while (true) {
    // Get the offset of the current node's child node.
    offset = *(u32*)(curBlock + index);

    // If the offset is negative, the current node is a leaf node.
    if ((offset & 0x80000000) != 0) {
      break;
    }

    // If the offset is non-negative, update the current node to be
    // the child node and continue traversing the tree.
    shift--;
    curBlock += offset;

    index = 4 * ((4 * ((u32)z >> shift)) & 4
        | (2 * ((u32)y >> shift)) & 2
        | (1 * ((u32)x >> shift)) & 1);
  }

  // Return the address of the leaf node containing the input point.
  return reinterpret_cast<u16*>(curBlock + (offset & 0x7FFFFFFF));
}

Tools

The following tools can handle KCL files: