.XG
.XG is the 3D model file format used in Gitaroo Man and Gitaroo Man Wan.
Data types used in this article (all little-endian):
- PString: a Pascal string, i.e. a unsigned byte (0 to 255) followed by that many bytes of text (Shift-JIS encoding)
- int32: signed 4-byte integer
- uint8: unsigned 1-byte integer
- uint32: unsigned 4-byte integer
- float32: 4-byte floating-point decimal
All xgNode types and names and stuff are stored as Pstrings.
- In particular, node names occasionally contain Shift-JIS encoded Japanese text. For XG files from the original game, it is safe to assume all node names are Shift-JIS encoded.
Todo:
- add note on how to correct winding order (for stuff like Blender)
- add section on how to convert stuff to a different coordinate system (XG has left-handed Y-up, stuff like Blender has right-handed Z-up)
File structure
Header
8 bytes XGBv1.00
(In addition to XGB, XGA also exists, presumably some sort of human-readable ascii version, but the format is unknown.)
Node declarations
Every node that is used in the model needs to be declared beforehand.
The declarations are made of 3 Pstrings:
- Pstring xgNode_type
- Pstring xgNode_name (a unique identifier for this node)
- Pstring ";"
Example: xgDagTransform BaNorenBip ;
Node definitions
After a node is declared, it can be filled with data.
The definitions are made of:
- PString xgNode_type (same as in the declaration)
- Pstring xgNode_name (same as in the declaration)
- Pstring"{"
- properties of this xgNode (see Node Types for the properties each xgNode type can have, their data types, and their expected formats)
- Pstring "}"
Examples: (newlines and indents are added for readability)
xgDagTransform BaNorenBip { inputMatrix BaNorenBip$matrix_0 outputMatrix }
xgTexture puma_nt$texture_0 { url puma_nt.imx mipmap_depth <uint32: 127> }
DAG setup
The DAG setup determines which nodes actually show up visibly when the model is loaded. Only DAG nodes (i.e. xgDagMesh and xgDagTransform nodes) can be added here; any other nodes have to be part of a DAG node's hierarchy (connected as an input attribute, like inputGeometry or inputTexture)
The DAG setup is made of:
- Pstring "dag"
- Pstring "{"
- Any number of DAG setup entries. A single entry can be either:
- PStrings xgDagMesh_name, "[", "]"
- PStrings xgDagTransform_name, "[", any number of DAGNode_name, "]"
- Pstring "}"
Example: (newlines and indentation are added for readability)
dag { T_puma_parts01$mesh_0 [ ] T_puma_speakerLB [ T_puma_speakerLB$mesh_0 ] T_puma_speakerRB [ T_puma_speakerRB$mesh_0 ] T_puma_speakerRF [ T_puma_speakerRF$mesh_0 ] T_puma_speakerLF [ T_puma_speakerLF$mesh_0 ] }
A single model's DAG setup can contain multiple chains of nested xgDagTransform nodes.
Node Types
xgBgGeometry
density
float32 density; //always 1000.0?
vertices
uint32 vertexFlags; //bitwise combination of vertex attributes
uint32 numVerts;
float vertices[numVerts][?];
vertexFlags: Indicates the contents of each vertex (and thereby its size). Only the first byte (LSB) is used. If the bits of that byte go 87654321:
- 1: enable position (x, y, z, w_unused*)
- 2: enable normal (x, y, z)
- 3: enable color (r, g, b, a)
- 4: enable texture coordinate (s, t)
numVerts: number of vertices in the vertex array
vertices: a list of numVerts vertices. Contents of each vertex depends on vertexType. The order is always position, normal, color, texcoord. The values are all float32s. Colors and texcoords are from 0.0 to 1.0.
*w_unused: The 4th coordinate/position appears to be unused. Replacing all instances in a model with 0, random values, negative and positive infinity, and NaN had no visible effect when testing in an emulator.
inputGeometry
(optional, and there can be more than 1)
Syntax:
inputGeometry <name of a xgEnvelope or xgVertexInterpolator/xgNormalInterpolator/xgTexCoordInterpolator/xgShapeInterpolator> outputGeometry
The cases I've seen have been:
- multiple xgEnvelopes (any time a mesh is deformed by multiple bones)
- xgVertexInterpolator + xgNormalInterpolator + xgTexCoordInterpolator (FLYING_O.XG)
- just one xgShapeInterpolator (Stage 2 U1_FIN_A.XG)
(I may have missed some interpolator types. such as xgColorInterpolator which I don't think any model uses)
xgBgMatrix
An xgBgMatrix is used to transform an xgBone or another xgBgMatrix.
- It is used to set a pose (position, rotation, and scale). This pose deviates from the xgBone's rest pose and will therefore deform the mesh.
- position/rotation/scale and inputPosition/Rotation/Scale both set the pose in a similar manner.
- inputPosition/Rotation/Scale: Made of keyframes (list of values), so it does an animated pose over time. Overrides respective position/rotation/scale if both exist.
- position/rotation/scale: Has a single value, so it sets a still, unanimated pose. If not overridden by a respective inputPosition/Rotation/Scale, it will hold the same value for the duration of the animation (e.g. some bones of BGC3_3PCO.XG).
- ("respective" meaning an inputPosition overrides a position and so on)
position
float position[3]; //xyz
rotation
float rotation[4]; //quaternion XYZW
scale
float scale[3]; //xyz
inputPosition
(optional)
inputPosition <name of a xgVec3Interpolato> outputVec3
inputRotation
(optional)
inputRotation <name of a xgQuatInterpolator> outputQuat
inputScale
(optional)
inputScale <name of a xgVec3Interpolato> outputVec3
inputParentMatrix
(optional) Used if this matrix is the child of (i.e. is transformed by) another xgBgMatrix.
inputParentMatrix <name of a xgBgMatrix> outputMatrix
xgBone
restMatrix
float restMatrix[16];
inputMatrix
inputMatrix <name of a xgBgMatrix> outputMatrix
xgDagMesh
primType
uint32 primType;
- 4 = kick verts separately
- 5 = kick verts as group
This affects what triListData/triStripData/triFanData contains:
- (kick separately) for each triList/Strip/Fan, there is a u32 value, followed by that number of vertex indices.
- (kick as groups) first u32 value is a starting vertex index. Then, for each triList/Strip/Fan, a u32 value for the number of vertices to use (starting from the last index of the previous triList/Strip/Fan).
primCount
uint32 primCount; //always 0 in the game's models
primData
uint32 size; //always 0
uint32 primData[size]; //always 0
triFanCount
uint32 triFanCount; //number of triFans
triFanData
uint32 size;
uint32 triFanData[size];
size: equal to 0, unless triFanCount > 0
- (kick separately) triFanCount + the sum of the vertex counts for each triFan
- (kick as groups) triFanCount + 1
triFanData: see primType
triStripCount
uint32 triStripCount; //number of triStrips
triStripData
uint32 size;
uint32 triStripData[size];
size: equal to 0, unless triStripCount > 0
- (kick separately) triStripCount + the sum of the vertex counts for each triStrip
- (kick as groups) triStripCount + 1
triStripData: see primType
triListCount
uint32 triListCount; //number of triLists
triListData
(A triList is a list where every 3 vertices makes a triangle.)
uint32 size;
uint32 triListData[size];
size: equal to 0, unless triListCount > 0
- (kick separately) triListCount + the sum of the vertex counts for each triList
- (kick as groups) triListCount + 1
triListData: see primType
cullFunc
uint32 cullFunc;
- 0 = disable culling (i.e. triangles are double-sided)
- 1 = cull clockwise-winding triangles (i.e. CCW is front-facing)
- 2 = cull counterclockwise-winding triangles (i.e. CW is front-facing)
inputGeometry
inputGeometry <name of a xgBgGeometry> outputGeometry
Contains the vertices to make this mesh.
inputMaterial
inputMaterial <name of a xgMaterial or xgMultiPassMaterial> outputMaterial
Contains the material for this mesh.
xgDagTransform
inputMatrix
inputMatrix <name of a xgBgMatrix> outputMatrix
It's optional! (ST00 NOR.XG's xgDagTransforms "band14" & "noren_body" contain nothing at all.)
xgEnvelope
Verts are taken from a xgBgGeometry to form an "envelope", sort of an invisible mesh used to deform a visible mesh.
startVertex
uint32 startVertex;
startVertex: vertex offset when using vertices to make this envelope. (i.e. first vertex to use from the xgBgGeometry)
weights
uint32 numweights;
float32 weights[numweights][4];
numweights is equal to the number of vertices that make up this envelope.
vertexTargets
uint32 numRawValues; int32 vertexTargetsRaw[numRawValues];
vertexTargetsRaw contains sequences of one or positive integer values, and after each sequence there is a -1 value before the next sequence begins. As such, the final value in vertexTargetsRaw will always be -1.
So by using -1 as a delimiter, you can turn vertexTargetsRaw into the easier-to-understand vertexTargets. For example:
vertexTargetsRaw = [304, -1, 306, 309, -1, 307, -1, ...]
becomes
vertexTargets = [ [304], [306, 309], [307], ... ]
The purpose of vertexTargets is to use one set of vertices to deform another set of vertices (i.e. to perform animations).
- The first set of vertices come from this xgEnvelope. The second set of vertices come from another (completely different/separate) xgBgGeometry (which often belongs to a visible mesh, thus the mesh will be animated).
- The two must be linked together as follows: otherXgBgGeometry.inputGeometry = thisXgEnvelope.
- Once they are linked, thisXgEnvelope.inputGeometry.vertices will animate otherXgBgGeometry.vertices.
Specifically, each vertex from this xgEnvelope is linked to one or more vertices of the other xgBgGeometry. Take the previous example of:
vertexTargets = [ [304], # index 0 [306, 309], # index 1 [307], # index 2 ... ]
This means:
- thisXgEnvelope's vert 0 is linked to otherXgBgGeometry's vert 304
- thisXgEnvelope's vert 1 is linked to otherXgBgGeometry's verts 306 and 309
- thisXgEnvelope's vert 2 is linked to otherXgBgGeometry's vert 307
effect of xgEnvelope.weights...
Question: Where do the values from xgEnvelope.weights come into play?
Answer, maybe:
- Gitaroo Man models only ever use inputMatrix1. However, an xgEnvelope could potentially have any or all of inputMatrix1, inputMatrix2, inputMatrix3, and inputMatrix4.
- Does that mean the envelope can be deformed by up to 4 different inputMatrixs at once?
- And is the influence of each inputMatrix determined by 'weights' for each envelope vertex?
- e.g. Probably a weight entry of (1.0, 0.0, 0.0, 0.0) means the envelope vertex is deformed only by inputMatrix1 and ignores the other 3 matrices.
- Yes, , it seems plausible that placing a nonzero value in the 2nd/3rd/4th part of a weight entry would cause the weight entry's associated envelope vertex to be deformed by the respective inputMatrix2/3/4.
- (I'm just not sure if all 4 weight entry values are supposed to add up to 1.0 or not...)
- And is the influence of each inputMatrix determined by 'weights' for each envelope vertex?
inputMatrix1
inputMatrix1 <name of a xgBone> envelopeMatrix
Contains the matrix that animates this envelope.
inputGeometry
inputGeometry <name of a xgBgGeometry> outputGeometry
Contains the vertices to make this envelope.
xgMaterial
blendType
uint32 blendType;
- 00 - Normal (aka Mix) **
- 01 - *Add +a
- 02 - *Reverse Subtract -a
- Like Subtract, but (src - dest) instead of (dest - src)
- (only used once, with flags 02 in ST02 EFF_FOKEMURI.XG)
- 03 - *Subtract -a
- (only used once, with flags 02 in ST01 EFEV_F_FADE.XG)
- 04 - *Unknown **
- 05 - *Normal (w/ alpha) [indicates material alpha?] +a
Notes:
- +a : uses diffuse alpha, texture alpha, and vertex color alpha, regardless of flags value
- -a : ignores diffuse alpha, texture alpha, and vertex color alpha, regardless of flags value
- * : Screws up mesh-sorting, just like flags→texalpha
- ** affected by flags→texalpha
shadingType
uint32 shadingType;
- 00 - unshaded
- 01 - shaded
- 02 - shaded
- 03 - vertex colors (unshaded)
- 04 - ambient only? (no diffuse/specular?)
As far as I can tell, 01 is less common than 02 but looks exactly the same. (If there are differences in specular, there's no way of seeing it because Gitaroo Man stages don't really do specular lighting, I think?)
Vertex colors can be used with texture alpha-- vertex colors do not appear on the fully transparent parts. (e.g. ST02BGKA)
diffuse
float32 red;
float32 green;
float32 blue;
float32 alpha;
Effects:
- if shadingType is 0 (unshaded), the entire mesh is multiplied by the diffuse color regardless of lighting.
- shadingTypes 1 and 2 (shaded) are lit normally by diffuse RGB.
- shadingTypes 3 (vertex colors) and 4 (ambient only) ignore diffuse RGBA entirely (ignoring alpha even in the case below).
- alpha is ignored unless flags→texalpha is enabled or blendType is 5 (normal w/ alpha), in which case the entire mesh is multiplied by the alpha value regardless of lighting.
specular
float32 red;
float32 green;
float32 blue;
float32 exponent;
flags
uint32 flags;
flags: if the bits are 87654321...
- bit 1: flags→texalpha
- Indicates this material's use of texture alpha.
- 0 = ignore alpha
- 1 = use alpha
- This value is usually redundant.
- Only if blendType is 0 or 4 does this value make a difference-- in both cases it forces Normal blending w/ alpha (plus sorting).
- Texture alpha is always enabled by blendType 5, regardless of flags (though I think in practice it only gets used for material alpha?). Also regardless of flags, Additive always uses texture alpha, and Multiply & Subtract always ignore i
- bit 2: unknown, it's always 1
- bits 3-8: unknown and always 0, probably nothing
textureEnv
uint32: textureEnv
- 0 = UV coordinates
- 1 = sphere/environment map
uTile
uint32 uTile; //always 0?
vTile
uint32 vTile; //always 0?
inputTexture
(optional)
Determines the texture this xgMaterial uses. Syntax is:
inputTexture <name of a xgTexture> outputTexture
Note: It appears that when multiple inputTextures are provided, only the final one appears visibly on the model. (Tested with FLYING_O.XG)
xgMultiPassMaterial
Layering multiple materials. Frequently used to give models a reflective glass effect with both a regular and sphere-mapped texture.
inputMaterial
(can be one or more inputMaterials.)
- Game is a little weird with this sometimes; some xgMultiPassMaterials have only 1 inputMaterial, in which case you may as well just use a regular xgMaterial by itself. Also the racecars in Stage 1 contain a xgMultiPassMaterial with like 6(?) inputMaterials layered which IIRC taxes performance more, but since some are completely opaque only like the top 2 inputMaterials are even visible.
inputMaterial <name of a xgMaterial> outputMaterial
xgNormalInterpolator
type
uint32 type;
Interpolation between keyframes:
- 0 = no interpolation
- 1 = interpolation (linear?)
- 2+ = likely same as 1
times
Todo:
The times property may be a way for an interpolator of any kind to have relatively few keyframes (keys) cover a much larger range of time than is usually possible.
- For example, FLYING_O.XG has 409 keys in each of its interpolators... except those belonging to UV_ANI_OBJ (the blinking forehead panel). Those interpolators have only 65 keys, but they also have 409 times, presumably so they will still cover the same range of time as the other interpolators.
uint32 numtimes;
float times[numtimes];
keys
uint32 numkeys;
keys[numkeys];
each key is:
- uint32 numNormals; //equal to number of targets, below
- float[numNormals][3];
targets
uint32 numtargets;
uint32 targets[numtargets];
Used with xgVertex/Normal/TexCoord/ShapeInterpolator to match keys with the parent xgBgGeometry's vertices. (Much like vertexTargets, but without -1s to separate them into groups.)
inputTime
inputTime <name of a xgTime> outputTime
xgQuatInterpolator
type
uint32 type;
Interpolation between keyframes:
- 0 = no interpolation
- 1 = interpolation (linear?)
- 2+ = likely same as 1
keys
uint32 numKeys; float keys[numKeys][4]; //quaternions XYZW
inputTime
inputTime <name of a xgTime> outputTime
xgShapeInterpolator
//found in: katestill, st02 eff_fokemuri, st02 u1_fin_a, st03, st08, etc?
type
uint32 type;
Interpolation between keyframes:
- 0 = no interpolation
- 1 = interpolation (linear?)
- 2+ = likely same as 1
times
uint32 size;
float times[size];
keys
uint32 numkeys; //same as number of times?
keys[numkeys];
each key is, um...:
- uint32 vertexType;
- uint32 numVerts;
- float vertices[numVerts][vertSize]; //vertSize is gotten from vertexType
inputTime
inputTime <name of a xgTime> outputTime
xgTexCoordInterpolator
type
uint32 type;
Interpolation between keyframes:
- 0 = no interpolation
- 1 = interpolation (linear?)
- 2+ = likely same as 1
times
uint32 numtimes;
float times[numtimes];
keys
uint32 numkeys;
keys[numkeys]; //same as numtargets, below?
each key is:
- uint32 numVerts
- float texcoords[numVerts][2];
targets
uint32 numtargets; uint32 targets[numtargets];
inputTime
inputTime <name of a xgTime> outputTime
xgTexture
url
PString url; //name of the .IMX file use
mipmap_depth
uint32 mipmap_depth; // always 127?
xgTime
This node appears once per file, and only when necessary i.e. in XG files containing animation data. So it's not present in files like Stage 1's U1ROOM.XG.
numFrames
float numFrames;
This value encompasses the sum of the length of all animations. (The XG file contains just 1 long combined animation, which will get split into separate animations via extra data in the XGM file)
time
float32 time;
Always 0. This value is used by interpolators as the means of knowing what frame to draw. (It is always zero inside a written file, but it will change in memory as a chart progresses.)
xgVec3Interpolator
type
uint32 type;
Interpolation between keyframes:
- 0 = no interpolation
- 1 = interpolation (linear?)
- 2+ = likely same as 1
keys
uint32 numkeys; float keys[numkeys][3]; //position or scale
inputTime
inputTime <name of a xgTime> outputTime
xgVertexInterpolator
Only affects vertex positioning - contrary to its name.
type
uint32 type;
Interpolation between keyframes:
- 0 = no interpolation
- 1 = interpolation (linear?)
- 2+ = likely same as 1
times
uint32 numtimes;
float times[numtimes];
keys
uint32 numkeys;
each key goes like:
- uint32 numPositions;
- float32 [numPositions][3];
targets
like with xgTexCoordInterpolator and xgNormalInterpolator...
Unused node types
Node types that exist in the executable but aren't used in any .XG files:
- xgAnimation
- xgBaseMaterial
- xgBgColor
- xgColor
- xgColorArray
- xgColorInterpolator
- xgDagGroup
- xgDagToonMesh
- xgEnvVertex
- xgEnvVertexArray
- xgFloat
- xgFloatArray
- xgFloatInterpolator
- xgGeometry
- xgGeometryArray
- xgInt
- xgIntArray
- xgMatrix
- xgMatrix3
- xgNodeRef
- xgParticleEmitter
- xgQuat
- xgQuatArray
- xgStringArray
- xgToonEdge
- xgToonEdgeArray
- xgVec2
- xgVec2Array
- xgVec2ArrayArray
- xgVec3
- xgVec3Array
- xgVec3ArrayArray
- xgVec4
- xgVec4Array