Page 1 of 2

### Approaches of Parsing Bone Representations

Posted: Fri Feb 01, 2019 7:46 am
As far as I know, bones are a set of linked nodes with transformation info includes translation, rotation and scaling, and due to the various representations of rotation, there're mostly 3 forms of representing a bone: Euler angle, rotation matrix and quaternion combined with the rest transformation.

Recognizing these forms is not a big deal, but parsing them correctly is. Therefore I decided to create a thread for an in-depth discussion of the common approaches of parsing different forms of bone representations, hopefully to give those who have only a scanty knowledge to this topic, me included, a hint of parsing a specific bone format.

For those who're completely new to these stuffs, you can refer to the Background/Basic Knowledge for a rough understanding.

Since I'd like to focus on parsing bones correctly, I will use minimum dumps of bone data only as samples, and a quick approach to build the skeleton, which in my case would be SkelBuilder — a lightweight ASCII FBX creator I made for constructing skeleton only, after countless failures with Noesis. Guess I may also post the Noesis python code to see if someone could spot the issues. But any other approaches are fine, so long as it's easy for understanding the process.

Source of SkelBuilder(Bone data not included).
• SkelBuilder_VS2015.zip
There're several working demonstrations in the source so I'm not exploring the usage here.

Quick entrance:
• Right-Handed System
• Quaternion Rotation
• 4x3 Matrices
• 3x4 Matrices
• 4x4 Matrices
• Left-Handed System
• Quaternion Rotation
• 4x3 Matrices
• 4x4 Matrices
Conclusion:
So far, seems there's no extra things needed to do for right-handed formats. For left-handed formats, you'll need to convert the transformation in world space of every node to right-handed one. In some circumstances, swapping the x-z axis can do the trick.

### Re: Approaches of Parsing Bone Representations

Posted: Fri Feb 01, 2019 7:54 am
car_pagani_huayra.zip

Code: Select all

``````# Dump Name: car_pagani_huayra.pig
# From Game: Asphalt 8: Airborne
# Platform: Android
# Bone Format: Quaternion Rotation, Translation
# Coordinate System: Right-Hand
# Endian: Little
``````
Bone data structure:

Code: Select all

``````long	Signature
word	boneCount
for i = 0 < boneCount
long	Signature
word	nameLen
char	boneName[nameLen]
char	Zero
short	parentID
float	Translation[3]
float	Rotation[4] // Quaternion Rotation
float	Scaling[3]
char	NULL[6]``````
Very simple format and since it's already in right-handed system, you don't even need to worry about the convertion between different coordinate systems.
Transformation is referenced to parent space so just need to convert quaternion to Euler angle. It's even working with Noesis.

Noesis python code:

Code: Select all

``````#load the model
bs = NoeBitStream(data)
bones = []
for i in range(0, boneCount):
bs.seek(4, NOESEEK_REL)
bs.seek(1, NOESEEK_REL)
bs.seek(6, NOESEEK_REL)
boneMat = Rot.toMat43(transposed = 1)
boneMat[3] = Tran
bones.append( NoeBone(i, boneName, boneMat, None, bonePIndex) )
# Converting local matrix to world space
for i in range(0, boneCount):
j = bones[i].parentIndex
if j != -1:
bones[i].setMatrix( bones[i].getMatrix() * bones[j].getMatrix() )

mdl = NoeModel()
mdl.setBones(bones)
mdlList.append(mdl)
rapi.setPreviewOption("setAngOfs", "-90 0 0")
return 1``````

### Re: Approaches of Parsing Bone Representations

Posted: Fri Feb 01, 2019 8:01 am
Similiar format like Asphalt 8.
Genty_Akylone_car.json.zip

Code: Select all

``````# Dump Name: Genty_Akylone_car.jmodel
# From Game: Asphalt 9: Legends
# Platform: Android
# Bone Format: Quaternion Rotation, Translation
# Coordinate System: Right-Hand
# Engine: Jet Engine
# Endian: Little``````
Bone data format:

Code: Select all

``````byte	Skip1[0x11]
long	boneDataOffset
word	boneCount
byte	Skip2[0x1C]
for i = 0 < boneCount
word	nameLen
char	boneName[nameLen]
short	parentID
float	Translation[3]
float	Rotation[4] // Quaternion Rotation
float	Scaling[3]
word	NULL``````

Noesis python code:

Code: Select all

``````#load the model
bs = NoeBitStream(data)
bs.seek(0x11, NOESEEK_ABS)
bs.seek(boneOffset, NOESEEK_ABS)
bones = []
for i in range(0, boneCount):
bs.seek(2, NOESEEK_REL)
boneMat = Rot.toMat43(transposed = 0)
boneMat[3] = Tran
bones.append( NoeBone(i, boneName, boneMat, None, bonePIndex) )
# Converting local matrix to world space
for i in range(0, boneCount):
j = bones[i].parentIndex
if j != -1:
bones[i].setMatrix( bones[i].getMatrix() * bones[j].getMatrix() )

mdl = NoeModel()
mdl.setBones(bones)
mdlList.append(mdl)
rapi.setPreviewOption("setAngOfs", "0 0 180")
return 1``````

### Re: Approaches of Parsing Bone Representations

Posted: Fri Feb 01, 2019 8:06 am
VenomSkel.zip

Code: Select all

``````# Dump Name: VenomSkel.AFM
# From Game: Assalt Fire
# Platform: PC
# Bone Format: Quaternion Rotation, Translation
# Coordinate System: Left-Hand
# Engine: Unreal Engine
# Endian: Little``````
Bone data structure:

Code: Select all

``````long	boneCount
for i = 0 < boneCount
long	boneNameIdx // Indexing to original string buffer
long	NULL
long	NULL
float	Rotation[4] // Quaternion Rotation
float	Translation[3]
long	Unknown
long	parentID
long	Marker // FFFFFFFF
for i = 0 < boneCount
string	boneName	// Mapping to bone[i]``````
Unreal Engine use left-handed system so need to flip the XZ-axis to fit with right-handed system. Transformation is also in parent space.

Noesis code works now thanks to shakotay2's correction(lack convertion to right-handed though):

Code: Select all

``````#load the model
bs = NoeBitStream(data)
bs.seek(boneCount * 0x34, NOESEEK_REL)
boneNames = []
for i in range(0, boneCount):
bs.seek(0x4, NOESEEK_ABS)
bones = []
for i in range(0, boneCount):
bs.seek(12, NOESEEK_REL)
bs.seek(4, NOESEEK_REL)
if bonePIndex == i:
bonePIndex = -1
bs.seek(4, NOESEEK_REL)
boneMat = Rot.toMat43(1)
boneMat[3] = Tran
bones.append(NoeBone(i, boneNames[i], boneMat, None, bonePIndex))
# Converting local matrix to world space
for i in range(0, boneCount):
j = bones[i].parentIndex
if j != -1:
bones[i].setMatrix( bones[i].getMatrix() * bones[j].getMatrix() )
mdl = NoeModel()
mdl.setBones(bones)
mdlList.append(mdl)
rapi.setPreviewOption("setAngOfs", "0 90 0")
return 1
``````

### Re: Approaches of Parsing Bone Representations

Posted: Fri Feb 01, 2019 8:12 am
CHAR_Bloxx.zip

Code: Select all

``````# Dump Name: CHAR_Bloxx.ve2
# From Game: Ben 10 Omniverse
# Platform: Xbox 360
# Bone Format: 4x3 Matrices
# Coordinate System: Right-Hand
# Engine: Vicious Engine 2
# Endian: Big``````
Bone data structure:

Code: Select all

``````long	boneCount
for i = 0 < boneCount
long	parentID
float	boneMat[4][3] // Translation[3], Rotation[3][3]
long	nameLen
char	boneName[nameLen]
long	Unknown
``````
The 3x3 rotation matries are in column-major order so need to transpose them before converting to Euler angles.

Noesis code works now thanks to shakotay2's correction:

Code: Select all

``````#load the model
bs = NoeBitStream(data, NOE_BIGENDIAN)
bones = []
for i in range(0, boneCount):
Matrix = []
for j in range(0, 12):
boneMat = NoeMat43( [(Matrix[3],Matrix[4],Matrix[5]),
(Matrix[6],Matrix[7],Matrix[8]),
(Matrix[9],Matrix[10],Matrix[11]),
(Matrix[0],Matrix[1],Matrix[2])] ).transpose()
bs.seek(4, NOESEEK_REL)
bones.append( NoeBone(i, boneName, boneMat, None, bonePIndex) )
# Converting local matrix to world space
for i in range(0, boneCount):
j = bones[i].parentIndex
if j != -1:
bones[i].setMatrix( bones[i].getMatrix() * bones[j].getMatrix() )

mdl = NoeModel()
mdl.setBones(bones)
mdlList.append(mdl)
rapi.setPreviewOption("setAngOfs", "0 90 0")
return 1
``````

### Re: Approaches of Parsing Bone Representations

Posted: Fri Feb 01, 2019 8:19 am
suitUpTony.zip

Code: Select all

``````# Dump Name: suitUpTony.SKELETON
# From Game: Iron Man 2
# Platform: PS3
# Bone Format: 4x4 Matrices
# Coordinate System: Left-Hand
# Endian: Big``````
Bone data structure:

Code: Select all

``````long64	unknown
long	indexOffset // Relative from 0x10
long	boneNameOffset // Relative from 0x10
long	boneCount
for i = 0 < boneCount
long	boneID
long	parentID
long	RelBoneNameOffset // Relative from boneNameOffset
float	boneMat[4][4]
long	boneIdx[boneCount]
for i = 0 < boneCount
string	boneName``````
If you'd tried loading the data as ordinary matrices but seen something like this:

then the matries should probably have been inversed.

Also the transformation is in world space so altogether you need to inverse the matrices first, then convert them to right-handed system and finally, to parent space for FBX format.

Noesis code (lack convertion to right-handed):

Code: Select all

``````#load the model
bs = NoeBitStream(data, NOE_BIGENDIAN)
bs.seek(8, NOESEEK_ABS)
boneIDs=[]
boneNames = []
bs.seek(indexOffset, NOESEEK_ABS)
for i in range(0, boneCount):
for i in range(0, boneCount):
bs.seek(0x14, NOESEEK_ABS)
bones = []
for i in range(0, boneCount):
if boneIndex == bonePIndex:
bonePIndex = -1
Mat44 = NoeMat44.fromBytes( bs.readBytes(0x40), NOE_BIGENDIAN )
boneMat = Mat44.toMat43().inverse()
bones.append( NoeBone(boneIndex, boneNames[boneIndex], boneMat, None, bonePIndex) )#boneIDs[i]

mdl = NoeModel()
mdl.setBones(bones)
mdlList.append(mdl)
#rapi.setPreviewOption("setAngOfs", "0 0 0")
return 1
``````

### Re: Approaches of Parsing Bone Representations

Posted: Sun Feb 03, 2019 9:40 pm
(pmed you) seems you need to replace "while" like such for .ve2/.AFM:
j = bones.parentIndex
if j != -1:

### Re: Approaches of Parsing Bone Representations

Posted: Tue Feb 12, 2019 6:02 am
leonopteryx.zip

Code: Select all

``````# Dump Name: leonopteryx.xbg
# From Game: James Cameron's AVATAR THE GAME
# Platform: PC
# Bone Format: Quaternion Rotation, Translation
# Coordinate System: Left-Hand
# Engine: Dunia Engine
# Endian: Little
``````
Bone data structure:

Code: Select all

``````long	boneCount
for i = 0 < boneCount
long 	CRC?
long 	boneIndex // sometimes -1
long 	Unknown
long 	parentIndex
float	Rotation[4] // Quaternion
float	Tanslation[3]
float	Scaling[3]
long 	Unknown[3]
long 	boneNameLen
char 	boneName[boneNameLen + 1] // null-terminated
``````
Same procedure as for Assalt Fire.

Noesis code(lack convertion to right-handed):

Code: Select all

``````#load the model
bs = NoeBitStream(data)
bones = []
for i in range(0, boneCount):
bs.seek(0xC, NOESEEK_REL)
bs.seek(0x18, NOESEEK_REL)
boneMat = Rot.toMat43(transposed = 1)
boneMat[3] = Tran
bones.append( NoeBone(i, boneName, boneMat, None, bonePIndex) )
# Converting local matrix to world space
for i in range(0, boneCount):
j = bones[i].parentIndex
if j != -1:
bones[i].setMatrix( bones[i].getMatrix() * bones[j].getMatrix() )

mdl = NoeModel()
mdl.setBones(bones)
mdlList.append(mdl)
rapi.setPreviewOption("setAngOfs", "0 -90 0")
return 1
``````

### Re: Approaches of Parsing Bone Representations

Posted: Tue Feb 12, 2019 6:09 am
Rath.zip

Code: Select all

``````# Dump Name: Rath.rn
# From Game: Ben 10 Omniverse 2
# Platform: 3DS
# Bone Format: 3x4 Matrix
# Coordinate System: Right-Hand
# Endian: Little
``````
Bone data structure:

Code: Select all

``````long	Magic
long	boneDataOffset // Absolute
long	boneCount
long	Skip[4]

for i = 0 < boneCount
long 	Unknown
short	UnknownID1
short	UnknownID2
long 	UnknownOffset // useless
long 	boneDataOffset // relative from this field

for i = 0 < boneCount
long 	Ignore[2]
long 	boneIndex
long 	parentIndex?
long 	Ignore[4]
float	Scaling[3]
float	Rotation[3] // ?
float	Translation[3]

float	Mat34[12] // row-major
float	identityMatrix[12] // row-major
float	Mat34Reverse[12] // row-major

float	Null[3]
``````
Similar to Ben 10 Omniverse on Xbox 360 except the matries are packed. Guess the tricky part would be identifying the parent IDs and the required matries.

Noesis code:

Code: Select all

``````#load the model
bs = NoeBitStream(data)
bs.seek(8, NOESEEK_REL)
bs.seek(boneCount * 0x10 + 0x10, NOESEEK_REL)
bones = []
#noesis.logPopup()
for i in range(0, boneCount):
bs.seek(8, NOESEEK_REL)
bs.seek(0x34, NOESEEK_REL)
Matrix = []
for j in range(0, 12):
boneMat = NoeMat43( [(Matrix[0],Matrix[1],Matrix[2]),
(Matrix[4],Matrix[5],Matrix[6]),
(Matrix[8],Matrix[9],Matrix[10]),
(Matrix[3],Matrix[7],Matrix[11])] )#.transpose()
bs.seek(0x6C, NOESEEK_REL)
bones.append( NoeBone(boneIndex, 'bone%02d'%i, boneMat, None, bonePIndex) )
# Converting local matrix to world space
for i in range(0, boneCount):
j = bones[i].parentIndex
if j != -1:
bones[i].setMatrix( bones[i].getMatrix() * bones[j].getMatrix() )

mdl = NoeModel()
mdl.setBones(bones)
mdlList.append(mdl)
return 1
``````

### Re: Approaches of Parsing Bone Representations

Posted: Tue Feb 12, 2019 6:12 am
mermaid_normal.zip

Code: Select all

``````# Dump Name: mermaid_normal.model
# From Game: GuJian3
# Platform: PC
# Bone Format: Translation, Quaternion
# Coordinate System: Right-Hand
# Engine: Vision Engine
# Endian: Little
``````
Bone data structure:

Code: Select all

``````word	boneCount
for i = 0 < boneCount
long 	nameLen
char 	Name[nameLen]
short	parentID

float	UnknownTranslation[3]
float	UnknownRotation[4] // Quaternion

float	Translation[3]
float	Rotation[4] // Quaternion
``````
Similiar to previous examples.

Noesis code:

Code: Select all

``````#load the model
bs = NoeBitStream(data)
bones = []
for i in range(0, boneCount):
bs.seek(28, NOESEEK_REL)
boneMat = Rot.toMat43(transposed = 0)
boneMat[3] = Tran
bones.append( NoeBone(i, boneName, boneMat, None, bonePIndex) )
# Converting local matrix to world space
for i in range(0, boneCount):
j = bones[i].parentIndex
if j != -1:
bones[i].setMatrix( bones[i].getMatrix() * bones[j].getMatrix() )

mdl = NoeModel()
mdl.setBones(bones)
mdlList.append(mdl)
rapi.setPreviewOption("setAngOfs", "0 -90 0")
return 1
``````

### Re: Approaches of Parsing Bone Representations

Posted: Tue Jun 11, 2019 2:41 pm
base02.zip

Code: Select all

``````# Dump Name: base02.mesh
# From Game: Cyber Hunter
# Platform: Android
# Bone Format: 4x4 Matrices
# Coordinate System: Left-Hand
# Endian: Little
``````
Bone data structure:

Code: Select all

``````short	boneCount
for i = 0 < boneCount
BYTE	parentID // 0xFF means parentless
for i = 0 < boneCount
char	boneName[32]
for i = 0 < boneCount
float	boneMatrix[16] // Column-major``````
The matrices are in world space while FBX parenting is in parent space. So they need to be converted to parent space by premultiply the inverse
of world matrix of their parents'. This format uses DirectX axis system, and swapping the X-Z axis simply won't work. So you need to convert the world space matrix of each node to right-handed before the premultiplication.
Check the code in the source for details.

Noesis python code(lack convertion to right-handed):

Code: Select all

``````#load the model
bs = NoeBitStream(data)
pIDs= []
for i in range(0, boneCount):
if aByte != 0xFF:
pIDs.append(aByte & 0xFF)
else:
pIDs.append(-1)

nameList = []
for i in range(0, boneCount):
nameList.append(boneName)

bones = []
for i in range(0, boneCount):
Matrix = []
for j in range(0, 16):
boneMat = NoeMat43( [(Matrix[0],Matrix[4],Matrix[8]),
(Matrix[1],Matrix[5],Matrix[9]),
(Matrix[2],Matrix[6],Matrix[10]),
(Matrix[12],Matrix[13],Matrix[14])] )
bones.append( NoeBone(i, nameList[i], boneMat, None, pIDs[i]) )

mdl = NoeModel()
mdl.setBones(bones)
mdlList.append(mdl)
return 1``````

### Re: Approaches of Parsing Bone Representations

Posted: Sat Aug 17, 2019 11:01 pm
Hi Bigchillghost,

first id like to thank you for this great tutorial, its the only tutorial i found online i could actually understand regarding bone data
iv been trying to follow all of your examples and can reproduce all the results until i get to the following line in noesis:

bones.setMatrix(bones.getMatrix().__mul__(bones[j].getMatrix()))

could you please explain what it does?

iv also went over you C code, but it seems there you
2. read rotation 3x3 matrix then transpose the 3x3
3. then in function (Matrix33ToRS) calculate what i am assuming is in world coordinates scaling vector and rotation vector - but not sure what to do with this
i would be happy for a small explanation

Thanks allot

at the moment i am trying to work on the following model:
# Dump Name: CHAR_Bloxx.ve2
# From Game: Ben 10 Omniverse
# Platform: Xbox 360
[/i]

### Re: Approaches of Parsing Bone Representations

Posted: Sun Aug 18, 2019 2:32 am
jayn23 wrote:
Sat Aug 17, 2019 11:01 pm
bones.setMatrix(bones.getMatrix().__mul__(bones[j].getMatrix()))

could you please explain what it does?

To multiply the node's local matrix with the one of its parent so as to obtain its world transformation. Technically you need to do it recursively for a node in a tree structure untill you reach the root, but in Noesis for some unknown reasons you just need to do it once. So you might just consider that as a fixed routine to convert a local bone tree to world space.
jayn23 wrote:
Sat Aug 17, 2019 11:01 pm
then in function (Matrix33ToRS) calculate what i am assuming is in world coordinates scaling vector and rotation vector - but not sure what to do with this

You should probably know that FBX nodes are in parent space, which is opposite to Noesis. And what Matrix33ToRS does is to extract the Euler rotation and scaling vectors from a 3x3 row-major matrix, regardless whether it's in parent space or world space. In this case, you got parent space transformation. That's what's required for an FBX node.

### Re: Approaches of Parsing Bone Representations

Posted: Sun Aug 18, 2019 12:48 pm
Bigchillghost wrote:
Sun Aug 18, 2019 2:32 am
multiply the node's local matrix with the one of its parent so as to obtain its world transformation.
the problem with this, is since you read the translation matrix with 3x3 matrix you get a 4x3 matrix, and you cant multiply 4x3 by 4x3 since num of columns on matrix1 should equal num of rows on matrix2.
it seems like a i am missing something here.

Until now iv just been studying the diffrent forms bones can appear in 3d files, and the code needed to extract them.
any tips on recognizing the kind of bone data: Quaternion left or right, Row-major or columns-major. etc.. or is it just experience?

### Re: Approaches of Parsing Bone Representations

Posted: Sun Aug 18, 2019 2:59 pm
jayn23 wrote:
Sun Aug 18, 2019 12:48 pm
the problem with this, is since you read the translation matrix with 3x3 matrix you get a 4x3 matrix,
First of all, it's not a "translation matrix", but a translation "vector". Be careful using proper terms to avoid confusion.
jayn23 wrote:
Sun Aug 18, 2019 12:48 pm
and you cant multiply 4x3 by 4x3 since num of columns on matrix1 should equal num of rows on matrix2.
Mathmatically, you can't. In programing it's not necessarily to store the matrix as a 4x4 one since the last row is constant.
jayn23 wrote:
Sun Aug 18, 2019 12:48 pm
any tips on recognizing the kind of bone data: Quaternion left or right, Row-major or columns-major. etc.. or is it just experience?
For recognition of handedness, just load a mesh into a 3D app and see if it's mirrored compared with its in-game look or if the face normals are inverted. To distinguish the matrix storage, first you need to determine whether it's a 4x4 matrix, or a 3x3 one. You can easily tell by looking for the (0 0 0 1) vector. For a 4x4 matrix, it's easy once you find the translation vector, which could contain values out of the range of -1.0 to +1.0. For a 4x3 matrix, since it can also be a 3x3 matrix followed by a translation vector, there's no universal recipe. Just try to parse them respectively and see which one reveals the correct result. So yes it requires experience, but also knowledge.