SWTOR: .gr2 3D model file

From XentaxWiki
Jump to: navigation, search

This article contains a file format specification for the custom .gr2 file format used by the MMORPG Star Wars: The Old Republic to store the 3D models of the game.

General information

All model files can be found in the .tor archives swtor_main_art_*_1.tor and they are placed in the folder /resources/art/.

There are different kinds of model files:

  • Dynamic files are files that have an animation. They consist of creatures, NPCs, body parts as well as helmets, belts and other clothes.
  • FX (effects) files are used for animations, abilities like detonations or computer screens.
  • Static files are inanimate models. These include weapons, vehicles, buildings, furniture and small items. Static items are grouped by planet and can be divided into:
    • Arch models are big models like models that are used to create the game world.
    • Item models are smaller models. These can include streetlights, tables, benches, drinking cups, etc.
    • Speedtree models are trees or bushes. Only in a few cases (like branches or dead trees) are .gr2 files used; otherwise, .spt Speedtree files are used to describe the trees.

Each .gr2 file that has textures also references one or more Material files (these .mat files can all be found in /resources/art/shaders/materials/). The material files in turn contain the path of the diffuse and normal textures.

Notes on implementation

A good file reader should not read the whole file from top to bottom. Instead, it should just read the lengths and offsets in the header and initialize the arrays accordingly. Then, it should seek directly to the positions given in the offsets, and read the file in there.

File layout

Header

position: 0x00

  • byte[4]: file identifier, always GAWB (47 41 57 42), which stands for GAWB = BWAG = BioWare Austin Geometry
  • uint32: major version, always 4
  • uint32: minor version, always 3
  • uint32 offsetBNRY: offset of the BNRY / LTLE section

position: 0x10

  • uint32 numCachedOffsets: The number of offsets in the Cached offsets section
  • uint32: type of .gr2 file:
    • 00 = default .gr2 file
    • 01 = model has a .clo file. The .clo file has the same path as the .gr2 file, but ends with .clo instead of .gr2
    • 02 = this is a skeleton file (see /resources/art/dynamic/spec/)
  • uint16 numMeshes: The number of meshes that the model is made up of
  • uint16 numMaterials: The number of materials referenced by all meshes of the model
  • uint16 numBones: if it is a skeleton files, read the number of skeleton bones here
  • uint16 numAttachs: The number of objects that are attached to bones
  • byte[16]: 16 zero bytes

position: 0x30

  • BoundingBox: The global bounding box, the whole model is contained inside it

position: 0x50

  • uint32 offsetCachedOffsets: Offset of the Cached offsets section
  • uint32 offsetMeshHeader: Offset of the Mesh information section, usually 0x70, or zero, if there are no meshes.
  • uint32 offsetMaterialNameOffsets: Offset of the Material offsets section where the offsets of the material names are given, or zero if there are no materials.
  • uint32 offsetBoneStructure: Offset of the Skeleton structure section (usually 0x70), only when this file is a skeleton file, otherwise zero
  • uint32 offsetAttachments: Offset of the Attached objects section, or zero, if there are not attached objects.
  • (zero padding bytes to next 16-byte block)

Skeleton structure

This section is only contained if the file is a skeleton file (the number of bones is given in numBones). Each bone takes up 0x88 = 136 bytes.

position: offsetBoneStructure = 0x70

  • LOOP (FOR EACH bone IN numBones) {
    • uint32 offsetBoneName: The offset of the bone name, stored as string and terminated by a 00 byte
    • int32 parentBoneIndex: The index of the parent bone, or -1 if it is the root. Indices start at zero.
    • float32[32]: 32 unknown floats
  • } END LOOP
  • (zero padding bytes to next 16-byte block)

Mesh information

The mesh headers contain information on each mesh (the number of meshes is given in numMeshes). Each mesh takes up 0x28 = 40 bytes.

position: offsetMeshHeader = 0x70

  • LOOP (FOR EACH mesh IN numMeshes) {
    • uint32 offsetMeshName: The offset of the mesh name, stored as string and terminated by a 00 byte
    • float32: unknown
    • uint16 numPieces: The number of pieces this mesh is made up, equal to the number of materials used by this mesh (required for reading mesh piece information section)
    • uint16 numUsedBones: The number of bones from the whole skeleton that are connected to this mesh
    • uint16: unknown, seems to be related to bones
    • uint16 vertexSize: The number of bytes used for storing a vertex (required for reading vertex section)
      • 12 = just the X/Y/Z coordinates of this vertex (e.g. for collision meshes)
      • 24 = X/Y/Z coordinates and texture positions of this vertex (e.g. for static models)
      • 32 = X/Y/Z coordinates, texture positions and bone connections of this vertex (e.g. for dynamic, animated models)
    • uint32 numVertices: The total number of vertices used by this mesh (not required for reading model since this information is also given in mesh piece information)
    • uint32 numIndices: The total number of vertex indices used by this mesh. Divide this number by 3 to get number of faces (not required for reading model since this information is also given in mesh piece information)
    • uint32 offsetMeshVertices: The start address (offset) of the vertices of this mesh (required for reading vertices)
    • uint32 offsetMeshPieces: The start address (offset) of the Mesh piece information of this mesh (required for reading models with textures)
    • uint32 offsetIndices: The start address (offset) of the indices of this mesh (required for reading face indices)
    • uint32 offsetBones: The start address (offset) of the bone list of this mesh
  • } END LOOP
  • (zero padding bytes to next 16-byte block)

Mesh piece information

This section contains information on which faces of the mesh display which material/texture. Keep in mind that the number of faces given here stands for the faces; if you need the number of vertices for your model viewer, you'll have to multiple the values by 3. To get the material names, you also need to read the Material offsets section.

  • LOOP (FOR EACH mesh IN numMeshes) {
  • position: mesh.offsetMeshPieces
    • LOOP (FOR EACH piece IN mesh.numPieces) {
      • uint32: The starting index where the faces for this piece start, values range from 0 to mesh.numFaces/3 - 1
      • uint32: The number of faces that this piece is made up of
      • int32: If this piece is textured, this field specifies the id (related to the Material offsets section) for this material, otherwise -1
      • int32: This field is just an enumerator/loop index. It starts with 0 for each mesh and increases by 1 for each piece, or is set to -1, if it is not textured
      • BoundingBox: The bounding box for this mesh piece
    • } END LOOP
  • } END LOOP

Material name offsets

This section contains a list of the offsets to the material names.

  • position: offsetMaterialNameOffsets
  • LOOP (FOR EACH texture IN numTextures) {
    • uint32 offsetMaterialName: The offset where the material name is stored, terminated by a 00 byte
  • } END LOOP
  • (zero padding bytes to next 16-byte block)

Attached objects

This section is optional. It contains a list of objects that are attached to certain bones.

  • position: offsetAttachments
  • LOOP (FOR EACH attachedObject IN numAttachs) {
    • uint32: offset where the name of the attached object is stored, string of variable length, terminated by a 00 byte
    • uint32: offset where the name of the bone the object attaches to is stored, string of variable length, terminated by a 00 byte
    • float32[16]: 16 floats for a 4x4 matrix
  • } END LOOP
  • (zero padding bytes to next 16-byte block)

Vertices

Depending on the vertex length (given in numVertexBytes), each vertex takes up 12, 24 and 32 bytes. This section contains not only the x/y/z positions, but also (for textured meshes) the texture u/v coordinates as well as the normals and tangents.

  • LOOP (FOR EACH mesh IN numMeshes) {
  • position: mesh.offsetVertices
    • LOOP (FOR EACH vertex IN mesh.numVertices) {
      • IF (mesh.vertexSize == 12) {
        • float32: a float value specifying the x coordinate of the vertex
        • float32: a float value specifying the y coordinate of the vertex
        • float32: a float value specifying the z coordinate of the vertex
      • } ELSE IF (mesh.vertexSize == 24) {
        • float32: a float value specifying the x coordinate of the vertex
        • float32: a float value specifying the y coordinate of the vertex
        • float32: a float value specifying the z coordinate of the vertex
        • byte[8]: possibly normals and tangents in an unknown encoding
        • float16: specifying the u coordinate for the texture
        • float16: specifying the v coordinate for the texture
      • } ELSE IF (mesh.vertexSize == 32) {
        • float32: a float value specifying the x coordinate of the vertex
        • float32: a float value specifying the y coordinate of the vertex
        • float32: a float value specifying the z coordinate of the vertex
        • byte: weight of the first bone joint (0 to 255)
        • byte: weight of the second bone joint (0 to 255)
        • byte: weight of the third bone joint (0 to 255)
        • byte: weight of the fourth bone joint (0 to 255)
        • byte: index of the first bone joint that influences the vertex's position
        • byte: index of the second bone joint that influences the vertex's position
        • byte: index of the third bone joint that influences the vertex's position
        • byte: index of the fourth bone joint that influences the vertex's position
        • byte[8]: possibly normals and tangents in an unknown encoding
        • float16: specifying the u coordinate for the texture
        • float16: specifying the v coordinate for the texture
      • } END IF
    • } END LOOP
  • } END LOOP
  • (zero padding bytes to next 16-byte block)

Faces

This section contains the faces, that is the number of surfaces of the model. Three vertices (= three point) generate one face, so for each face you will have to read three integers to get the index for each point.

Note that mesh.numFaces given under Mesh information stands for the number of indices, so you'll have to divide it by 3 if you want to get the number of faces. In the Mesh piece information section howver, mesh.piece.numFaces stands for the number of the faces. To get the number of indices, multiply it by three.

Please note that the vertex indices are zero-based (that is, the first vertex is numbered 0 and not 1). Some programs (for example Wavefront .obj files) start counting the index at 1, so you'll have to increase the indices by one if you are using such a program.

  • LOOP (FOR EACH mesh IN numMeshes) {
  • position: mesh.offsetFaces
    • LOOP (FOR EACH face in mesh.numFaces/3) {
      • uint16: index of first vertex
      • uint16: index of second vertex
      • uint16: index of third vertex
    • } END LOOP
    • (zero padding bytes to next 16-byte block)
  • } END LOOP

Bone list

If this file is a normal file, not a skeleton file, but is animated (like humans or creatures), the file will contain a list of the bones used by the current model.

  • position: offsetBones
  • LOOP (FOR EACH bone IN numBones) {
    • uint32: offset of bone name as string, terminated by 00
    • float32[6]: six unknown floats, maybe related to position/rotation of bone
  • } END LOOP
  • (zero padding bytes to next 16-byte block)

List of strings

This section contains multiple strings. It is not necessary to try and read this whole section by itself; instead, it is best to just seek here whenever you encounter a string offset.

  • LOOP (FOR EACH string) {
    • position: different for each string
    • string of variable length, but always terminated by a 00 byte
    • the 00 byte
  • } END LOOP
  • (zero padding bytes to next 16-byte block)

Cached offsets section

This section contains a list of offset addresses along with the value that is found at that address. Each of these values is in turn an offset again. The first offset is always 50 00 00 00, which is the offset of this section.

This section may be important for reading the bones, but right now it appears to be redundant to me.

  • position: offsetCachedOffsets
  • LOOP (for each tmpOffset in numCachedOffsets) {
    • uint32 offsetAddress: An offset in the value
    • uint32 offsetValue: The value that is stored at that offset, always another offset
  • } END LOOP
    • (zero padding bytes to next 16-byte block)

BNRY / LTLE section

This section is only present if the model contains a collision mesh. To be on the safe side, read the first length field. If it is zero, you can ignore this section, otherwise you can read it.

This section is completely unknown to me. The specification will allow you to parse the BNRY section without errors, but there is not much to do with the data yet, so you can just as well skip this section.

Regarding the BNRY / LTLE acronym, the only meaningful words I could come up with were binary / little but I am not sure about that.

BNRY header

  • position: bnryOffset
  • uint32 bnryLength: length of the whole BNRY section, excluding these four bytes
  • bytes(4): always BNRY (42 4E 52 59)
  • bytes(4): always 00 00 00 02
  • bytes(4): always LTLE (4C 54 4C 45)

  • position: bnryOffset + 16
  • uint32: always 01 00 00 00
  • uint32: always 02 00 00 00
  • uint32 numBNRYsections: number of sections in the BNRY section
  • uint32: some unknown offset/length


  • uint32 numLongLines: the number of long lines in the BNRY section
  • uint32 totalNum21Lines: the total number of lines starting with 0x21/0xA1; this number is the sum of the 0x21 lines from each BNRY section
  • uint32: always 01 00 00 00
  • uint32: always 01 00 00 00


  • float32: unknown, x coordinate?
  • float32: unknown, y coordinate?
  • float32: unknown, z coordinate?
  • uint32: always 01 00 00 00


  • float32: unknown, x coordinate?
  • float32: unknown, y coordinate?
  • float32: unknown, z coordinate?
  • uint32: always 00 00 00 00

  • position: bnryOffset + 80
  • uint32: always 01 00 00 00
  • uint32: always 04 00 00 00
  • uint32: always 01 00 00 00
  • uint32: always 01 00 00 00


  • uint32: unknown
  • uint32 totalNum21Lines: the total number of lines starting with 0x21/0xA1; same as above
  • uint32: always 01 00 00 00
  • uint32: always 01 00 00 00


  • float32: unknown, x coordinate?
  • float32: unknown, y coordinate?
  • float32: unknown, z coordinate?
  • uint32: always 01 00 00 00


  • float32: unknown, x coordinate?
  • float32: unknown, y coordinate?
  • float32: unknown, z coordinate?
  • uint32: numBNRYsections: number of sections in the BNRY section; same as above

  • position: bnryOffset + 128
  • uint32: numBNRYsections: number of sections in the BNRY section; same as above
  • uint32 totalNum21Lines: the total number of lines starting with 0x21/0xA1; same as above
  • uint32 totalNum21Lines: the total number of lines starting with 0x21/0xA1; same as above
  • uint32: always 00 00 00 00


  • uint32: some unknown offset/length
  • uint32: always 10 00 00 00
  • uint16: always 00 00
  • uint16: always 80 00
  • byte: always 01
  • uint32: always 01 00 00 00


  • uint32 numLongLines: the number of long lines in the BNRY section; same as above
  • uint32 totalNum21Lines: the total number of lines starting with 0x21/0xA1; same as above
  • uint32: always 01 00 00 00
  • uint32: always 01 00 00 00


  • float32: unknown, x coordinate?
  • float32: unknown, y coordinate?
  • float32: unknown, z coordinate?
  • uint32: always 01 00 00 00


  • float32: unknown, x coordinate?
  • float32: unknown, y coordinate?
  • float32: unknown, z coordinate?
  • uint32: always 01 00 00 00

long lines

  • position: bnryOffset + 209
  • LOOP (for each tmpLongLine in numLongLines) {
    • uint32: some id
    • uint32: some id or small integer
    • uint32: always 01 00 00 00
    • int32: parent id? either FF FF FF FF or an id
  • uint32: unknown
    • int32: parent id? either FF FF FF FF or an id
  • uint32: unknown
  • float32: unknown
  • float32: unknown
  • } END LOOP

BNRY sections

  • uint32: always 00 00 00 00
  • uint32: always 01 00 00 00


  • LOOP (for each tmpBNRYsection in numBNRYsections) {
    • uint32 tmpBNRYsectionOffset: offset of the BNRY section from the beginning of the following loop
  • } END LOOP


  • LOOP (for each tmpBNRYsection in numBNRYsections) {
    • uint16 curNum21Lines: number of 21/A1 lines in this BNRY section
    • uint16 lengthNum21Lines: length of the 21/A1 lines section in this BNRY section
    • uint16 numVertexLines: number of vertex lines in the BNRY section
    • uint16 numVertexLines: number of vertex lines in the BNRY section; same as above
    • uint16: unknown offset/length
    • byte: always 00
    • uint16 numVertexLines: number of vertex lines in the BNRY section; same as above

    • uint32: always 01 00 00 00
    • LOOP (for each tmpVertexLine in numVertexLines) {
      • float32: x coordinate
      • float32: y coordinate
      • float32: z coordinate
    • } END LOOP

    • uint32: always 01 00 00 00
    • LOOP (for each tmp21Line in curNum21Lines) {
      • byte: either 0x21 or 0xA1
      • IF (first byte == 0x21) {
        • read 6 more unknown bytes
      • } ELSE IF (first byte == 0xA1) {
        • read 7 more unknown bytes
      • } END IF
    • } END LOOP
  • } END LOOP
  • uint32: often, but not always 01 00 00 00

String section

According to the length of the BNRY section, this section is no longer part of the BNRY section. However, this section is only present in the .gr2 file if the BNRY section exists.

  • uint32 numStrings: number of strings in this section
  • LOOP (for each tmpString in numStrings) {
    • uint32: tmpStringLength: length of this string
    • string with given length, terminated by a 00 byte
  • } END LOOP

Unknown mesh floats

Not yet confirmed, but this section seems to contain the bounding box for each mesh, as opposed to the global bounding box given at the beginning of the value.

  • LOOP (for each tmpMesh in numMeshes) {
    • float32: The minimum X value of the bounding box of this mesh
    • float32: The minimum Y value of the bounding box of this mesh
    • float32: The minimum Z value of the bounding box of this mesh
    • float32: The maximum X value of the bounding box of this mesh
    • float32: The maximum Y value of the bounding box of this mesh
    • float32: The maximum Z value of the bounding box of this mesh
  • } END LOOP
  • byte[4]: four zero bytes

EGCD section

  • bytes(4): always EGCD (45 47 43 44)
  • uint32: unknown, sometimes 05 00 00 00
  • uint32: offset of BNRY/LTLE section

[END OF FILE]

Custom structures

BoundingBox

  • float32: The minimum X value of the bounding box of this mesh
  • float32: The minimum Y value of the bounding box of this mesh
  • float32: The minimum Z value of the bounding box of this mesh
  • float32: Always 1.0 (00 00 80 3F)
  • float32: The maximum X value of the bounding box of this mesh
  • float32: The maximum Y value of the bounding box of this mesh
  • float32: The maximum Z value of the bounding box of this mesh
  • float32: Always 1.0 (00 00 80 3F)