A few notes on the Gamecube model format. The format seems to be best described as an indexed buffer geometry. What that means is that for a specific geometry, the game will declare a list of attributes that can consist of position, normals, vertex color or uvs.
Code:
for attr in attrs:
self.bs.seek(attr['offset'])
for i in range(attr['count']):
if attr['type'] == 'POS':
bytes = self.bs.readBytes(0x0c)
pos = noesis.vec3FromBytes(bytes, NOE_BIGENDIAN)
pos = self.bone['matrix'].transformPoint(pos)
self.pos.append(pos)
elif attr['type'] == 'NORM':
bytes = self.bs.readBytes(0x0c)
norm = noesis.vec3FromBytes(bytes, NOE_BIGENDIAN)
norm = self.bone['matrix'].transformNormal(norm)
self.norm.append(norm)
elif attr['type'] == 'COLOR':
r = self.bs.readUInt() / 255
g = self.bs.readUInt() / 255
b = self.bs.readUInt() / 255
a = self.bs.readUInt() / 255
color = NoeVec4((b, g, r, a))
self.color.append(color)
elif attr['type'] == 'UV':
u = self.bs.readShort() / 255
v = self.bs.readShort() / 255
uv = NoeVec3((u, v, 0.0))
self.uv.append(uv)
For position and normals the data is in the format of a vector 3 (x, y, z) floats. Vertex colors are declared as four bytes in the order of bgra. And uv's are declared as a two byte short that is divided by 255.
As mentioned this is an indexed buffer format, so to draw the faces the game will provide a list of indexes referencing each position in its respective buffer to create a list of attributes to pass into the shader. Which specific attributes are defined by the material before the face list.
Code:
if self.face_flags == 0x28:
format = ['pos', 'norm']
elif self.face_flags == 0x828:
format = ['pos', 'norm', 'uv']
elif self.face_flags == 0x888:
format = ['pos', 'color', 'uv']
elif self.face_flags == 0x8a8:
format = ['pos', 'norm', 'color', 'uv']
elif self.face_flags == 0x8fc:
readShortA = True
format = ['pos', 'norm', 'color', 'uv']
elif self.face_flags == 0x08cc:
readShortA = True
format = ['pos', 'color', 'uv']
elif self.face_flags == 0xc28:
format = ['pos', 'norm', 'unknown1', 'uv']
elif self.face_flags == 0xc88:
format = ['pos', 'color', 'unknown1', 'uv']
elif self.face_flags == 0xca8:
format = ['pos', 'color', 'norm', 'unknown1', 'uv']
elif self.face_flags == 0xccc:
readShortA = True
readShortB = True
format = ['pos', 'color', 'uv']
elif self.face_flags == 0xcfc:
readShortA = True
readShortB = True
format = ['pos', 'norm', 'color', 'uv']
else:
print("New strip format, stopping")
self.stop_trace = True
return None
This is definitely not the cleanest way to write this, but without any forward knowledge of what any given bitflag is, i figured it would be easier to take each flag on a case-by-case basis and stop if a new flag or combination was found. That way I could open open the file, view that location and try to determine what that combinations was before moving on.
From what I can tell the flags seem to be as follows:
0x08 position (always present)
0x20 normals
0x80 vertex color
0x800 uv's
Then there seems to be flags:
0x400 adds an 'unknown' attribute into the face list. This value tends to be zero, so what I think this is an area for keeping track of animated textures
0x04 Seems to be used for declaring positions, normals, and colors as a short instead of a single byte in the list
0x40 Seems to be used for declaring uv and the 'unknown' attribute as a short instead of a single byte in the list
In the case of both 0x04 and 0x40 these bytes should be present when their respective attribute lists are longer than 255. But i think there was a situation where the uv list was longer than 255 and this bitflag wasn't set because the face list only used values 0 - 255.
For the faces themselves the game has two flags for setting the geometry type. 0x98 is a strip list and 0x90 is raw triangles. In the case of a strip list, the same pattern as the .nj models applies where the order is ABC BDC alternating until the end of the list. And the triangles are individual triangles which will always have a length divisible by 3. Following the type of geometry (triangles or strip) which will be a two byte value that gives the length of the list.
And then the list itself is made of list of indexed attributes given by the bitflags from the material model. One note that's interesting to mention is probably a result of porting the models, the index for the position, color and normals will always be the same. So for example in the case of the bitflag 0x828 a sample triangle might be A(01, 01, 05) B(02, 02, 24), C(03, 03, 17) where the position and the normal are the same index, and the uv is some other index.
The order of the attributes will always be in the order of the bitflags from low to high so, Position (0x08), Normals (0x20), Color (0x80), Unknown (0x400) and UV(0x800). And by default these indexes will be defined as bytes, unless the 0x04 and / or 0x40 bits are set. In which case those specific attributes will be defined as two byte shorts.
Last note, in the case of the flag 0xcfc, in think that might indicate that there is also a 0x10 flag, but i didn't notice any difference.