Skip to content
Jocelyn Beedie edited this page Jan 12, 2022 · 7 revisions

MESH files

MESH files contain 3D model data. MESH files on all platforms contain vertex coordinates. Only Gamecube MESH files contain texture coordinate and normal vector data, while PS2 MESH files do not. It is possible that this data is stored in SKIN files instead.

This file format typically ends in ".TMESH".

The Mesh_Z format

struct Mesh_Z {
    TransformationHeader transform;
    uint32_t num_vertices; // Number of vertices
    Vector3 vertices[num_vertices]; // Vertex data
    uint32_t num_texcoords; // Number of texture coordinates (Always 0 on Playstation 2)
    Vector2 texcoords[num_texcoords]; // Texture coordinate data
    uint32_t num_normals; // Number of normals (Always 0 on Playstation 2)
    Vector3 normals[num_normals]; // Normal data
    uint32_t num_strips; // Number of strips
    Strip strips[num_strips]; // Strip data (Note that they are not equally sized)
    // if `transform.subtype` is 4:
    int32_t vertex_groups[num_strips]; // Vertex groups applied per-strip (face groups?)
    // end if
    uint32_t num_stripdata; // Number of StripData elements (should be equal to num_strips on Gamecube, or 0 on Playstation2)
    StripData stripdata[num_stripdata]; // StripData (Note that they are not equally sized)
    uint32_t num_materials; // The number of materials that this mesh refers to
    uint32_t materials[num_materials]; // Material IDs; referred to by CRC32 hash of filename
    uint32_t num_sphere_shapes;
    MeshSphere sphere_shapes[sphere_shapes]; // Unknown data; each element is 16 bytes
    uint32_t num_cuboid_shapes;
    MeshCuboid cuboid_shapes[num_cuboid_shapes]; // Unknown data; each element is 80 bytes
    uint32_t num_cylinder_shapes;
    MeshCylinder cylinder_shapes[num_cylinder_shapes]; // Unknown data; each element is 36 bytes
    // Possibly GCN only?
    // uint32_t unk_footer4_num; // Always 0 (until proven otherwise); might be another shape type?
    // uint32_t strip_order_num;
    // uint32_t strip_order[strip_order_num]; // Order of triangle strips; unsure of purpose
}
struct ElementData {
    uint16_t texcoord_id;
    uint16_t normal_id;
};

Collision data

If Mesh.transform.subtype is 4, then num_cuboid_shapes, num_sphere_shapes, and num_cylinder_shapes will be 0. My assumption is that a subtype of 4 indicates that the mesh will generate a convex hull from the mesh's vertices.

struct MeshSphere {
    Vector3 center;
    float radius;
}

A spherical collision shape.

struct MeshCuboid {
    Mat4x4 transform;
    char junk[16];
}

A cuboid collision shape. The base cuboid is a rectangular prism with the extents {-1, 0, -1} to {1, 1, 1}, and MeshCuboid.transform transforms this cuboid accordingly.

struct MeshCylinder {
    Vector3 position;
    float height;
    Vector3 normal;
    char junk[4];
    float radius;
}

A cylindrical collision shape. The cylinder has its base position at MeshCylinder.position, and extends towards the direction specified by MeshCylinder.normal.

Strip data

The model is divided into strips; a strip is just a list of elements that are drawn as a triangle strip; for example, consider a strip with elements [0, 1, 3, 5, 8]. Every 3 elements of the strip will be drawn as a triangle on screen; in this case, triangles will be drawn for (0, 1, 3), (1, 3, 5), and (3, 5, 8). The order that the triangles will be drawn will differ for every other triangle; this is because, on screen, the front of a triangle is drawn in clock-wise order, but drawing triangle strips would reverse the order every other triangle.

If tri_order is 1, then the triangles would be drawn in the order [(0, 3, 1), (1, 3, 5), (3, 8, 5)]. If tri_order is 2, then the triangles would be drawn in the oder [(0, 1, 3), (1, 5, 3), (3, 5, 8)].

struct Strip {
    uint32_t num_elements; // The number of vertices in this strip
    uint16_t vertex_id[num_elements]; // List of vertex IDs
    uint32_t material; // material index.
    uint32_t tri_order; // The order that the triangles will be built in (1 or 2)
};

If TMesh.transform.subtype is 0, Strip.material this is always less than TMesh.num_materials.

struct StripData {
    uint32_t num_elements; // Number of elements (should be equivalent to corresponding Strip.num_elements
    ElementData elements[num_elements]; // Contains texture coordinate and normal information
};

Building triangle faces

Assume you have the following data:

Vector3 vertices[]; // size of these is ignored for now
Vector3 normals[];
Vector2 texcoords[];
uint32_t num_strips;
Strip strips[num_strips];
StripData stripdata[num_strips];

To build a 3d model out of this data, the following process must be performed:

for (uint32_t i = 0; i < num_strips; ++i) {
    Strip *strip = &strips[i];
    StripData *data = &stripdata[i];
    uint32_t triorder = strip->tri_order;
    for (uint32_t j = 0; j < strip->num_elements-2; ++j) {
        // The order that the vertices will be built will depend on the tri_order of the strip
        uint32_t index0 = j;
        uint32_t index1, index2;
        if (j % 2 == 0) {
            index1 = 3 - triorder + j;
            index2 = triorder + j;
        } else {
            index1 = triorder + j;
            index2 = 3 - triorder + j;
        }
        // Build face
        Vector3 face_vert[3] = {
            vertices[strip->vertex_id[index0]],
            vertices[strip->vertex_id[index1]],
            vertices[strip->vertex_id[index2]],
        };
        Vector2 face_texcoords[3] = {
            texcoords[data->elements[index0].texcoord_id],
            texcoords[data->elements[index1].texcoord_id],
            texcoords[data->elements[index2].texcoord_id],
        };
        Vector3 face_normals[3] = {
            normals[data->elements[index0].normal_id],
            normals[data->elements[index1].normal_id],
            normals[data->elements[index2].normal_id],
        };
    }
}

PS2 format

struct MeshPs2_Z {
    Mesh_Z super;
    uint32_t num_submeshes;
    SubMeshPs2_Z submeshes[num_submeshes];
}
struct SubMeshPs2_Z {
    uint8_t material_index; // index into super.materials
    uint32_t num_values;
    float unk[4][num_values];
}
Clone this wiki locally