Offtopic: Into Commodore 64 (6502) coding, pixeling or music?
Xentax is looking for new members for the C64 activities!
Just drop us a message at forum@xentax.com and join the Scene Team!

Forum rules: Click here

Approaches of Parsing Bone Representations

Read or post any tutorial related to file format analysis for modding purposes.
User avatar
Bigchillghost
ultra-veteran
ultra-veteran
Posts: 534
Joined: Tue Jul 05, 2016 9:37 am
Has thanked: 22 times
Been thanked: 449 times

Approaches of Parsing Bone Representations

Post by Bigchillghost » 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: 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.
You do not have the required permissions to view the files attached to this post.
Last edited by Bigchillghost on Fri Oct 11, 2019 11:57 am, edited 10 times in total.
May you find peace in this puzzle-solving game. Say it with action: click the Image when you get helped.:)

User avatar
Bigchillghost
ultra-veteran
ultra-veteran
Posts: 534
Joined: Tue Jul 05, 2016 9:37 am
Has thanked: 22 times
Been thanked: 449 times

Re: Approaches of Parsing Bone Representations

Post by Bigchillghost » Fri Feb 01, 2019 7:54 am

Think I'll start with a few working examples first. :D
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.

Image

Noesis python code:

Code: Select all

#load the model
def noepyLoadModel(data, mdlList):
	bs = NoeBitStream(data)
	boneCount = bs.readUShort()
	bones = []
	for i in range(0, boneCount):
		bs.seek(4, NOESEEK_REL)
		NameLen = bs.readUShort()
		boneName = noeStrFromBytes(bs.readBytes(NameLen), "ASCII")
		bs.seek(1, NOESEEK_REL)
		bonePIndex = bs.readShort()
		Tran = NoeVec3.fromBytes(bs.readBytes(12))
		Rot = NoeQuat.fromBytes(bs.readBytes(16))
		Scal = NoeVec3.fromBytes(bs.readBytes(12))
		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
You do not have the required permissions to view the files attached to this post.
Last edited by Bigchillghost on Wed Oct 09, 2019 9:24 am, edited 3 times in total.
May you find peace in this puzzle-solving game. Say it with action: click the Image when you get helped.:)

User avatar
Bigchillghost
ultra-veteran
ultra-veteran
Posts: 534
Joined: Tue Jul 05, 2016 9:37 am
Has thanked: 22 times
Been thanked: 449 times

Re: Approaches of Parsing Bone Representations

Post by Bigchillghost » 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
Image

Noesis python code:

Code: Select all

#load the model
def noepyLoadModel(data, mdlList):
	bs = NoeBitStream(data)
	bs.seek(0x11, NOESEEK_ABS)
	boneOffset = bs.readUInt()
	boneCount = bs.readUShort()
	bs.seek(boneOffset, NOESEEK_ABS)
	bones = []
	for i in range(0, boneCount):
		NameLen = bs.readUShort()
		boneName = noeStrFromBytes(bs.readBytes(NameLen), "ASCII")
		bonePIndex = bs.readShort()
		Tran = NoeVec3.fromBytes(bs.readBytes(12))
		Rot = NoeQuat.fromBytes(bs.readBytes(16))
		Scal = NoeVec3.fromBytes(bs.readBytes(12))
		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
You do not have the required permissions to view the files attached to this post.
Last edited by Bigchillghost on Wed Oct 09, 2019 9:24 am, edited 3 times in total.
May you find peace in this puzzle-solving game. Say it with action: click the Image when you get helped.:)

User avatar
Bigchillghost
ultra-veteran
ultra-veteran
Posts: 534
Joined: Tue Jul 05, 2016 9:37 am
Has thanked: 22 times
Been thanked: 449 times

Re: Approaches of Parsing Bone Representations

Post by Bigchillghost » 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.

Image

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

Code: Select all

#load the model
def noepyLoadModel(data, mdlList):
	bs = NoeBitStream(data)
	boneCount = bs.readUInt()
	bs.seek(boneCount * 0x34, NOESEEK_REL)
	boneNames = []
	for i in range(0, boneCount):
		boneNames.append(bs.readString())
	bs.seek(0x4, NOESEEK_ABS)
	bones = []
	for i in range(0, boneCount):
		bs.seek(12, NOESEEK_REL)
		Rot = NoeQuat.fromBytes(bs.readBytes(16))
		Tran = NoeVec3.fromBytes(bs.readBytes(12))
		bs.seek(4, NOESEEK_REL)
		bonePIndex = bs.readInt()
		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
Image
You do not have the required permissions to view the files attached to this post.
Last edited by Bigchillghost on Wed Oct 09, 2019 9:25 am, edited 3 times in total.
May you find peace in this puzzle-solving game. Say it with action: click the Image when you get helped.:)

User avatar
Bigchillghost
ultra-veteran
ultra-veteran
Posts: 534
Joined: Tue Jul 05, 2016 9:37 am
Has thanked: 22 times
Been thanked: 449 times

Re: Approaches of Parsing Bone Representations

Post by Bigchillghost » 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.

Image

Noesis code works now thanks to shakotay2's correction:

Code: Select all

#load the model
def noepyLoadModel(data, mdlList):
	bs = NoeBitStream(data, NOE_BIGENDIAN)
	boneCount = bs.readUInt()
	bones = []
	for i in range(0, boneCount):
		bonePIndex = bs.readInt()
		Matrix = []
		for j in range(0, 12):
			Matrix.append(bs.readFloat())
		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()
		NameLen = bs.readInt()
		boneName = noeStrFromBytes(bs.readBytes(NameLen), "ASCII")
		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
Image
You do not have the required permissions to view the files attached to this post.
Last edited by Bigchillghost on Wed Oct 09, 2019 9:26 am, edited 3 times in total.
May you find peace in this puzzle-solving game. Say it with action: click the Image when you get helped.:)

User avatar
Bigchillghost
ultra-veteran
ultra-veteran
Posts: 534
Joined: Tue Jul 05, 2016 9:37 am
Has thanked: 22 times
Been thanked: 449 times

Re: Approaches of Parsing Bone Representations

Post by Bigchillghost » Fri Feb 01, 2019 8:19 am

Now for a yet not working case:
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 matries but seen something like this:

Image

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.

Image

Noesis code (lack convertion to right-handed):

Code: Select all

#load the model
def noepyLoadModel(data, mdlList):
	bs = NoeBitStream(data, NOE_BIGENDIAN)
	bs.seek(8, NOESEEK_ABS)
	indexOffset = bs.readUInt() + 0x10
	boneNameOffset = bs.readUInt() + 0x10
	boneCount = bs.readUInt()
	boneIDs=[]
	boneNames = []
	bs.seek(indexOffset, NOESEEK_ABS)
	for i in range(0, boneCount):
		boneIDs.append(bs.readUInt())
	for i in range(0, boneCount):
		boneNames.append(bs.readString())
	bs.seek(0x14, NOESEEK_ABS)
	bones = []
	for i in range(0, boneCount):
		boneIndex = bs.readInt()
		bonePIndex = bs.readInt()
		RelBoneNameOffset = bs.readInt()
		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
Image
You do not have the required permissions to view the files attached to this post.
Last edited by Bigchillghost on Wed Oct 09, 2019 8:45 am, edited 2 times in total.
May you find peace in this puzzle-solving game. Say it with action: click the Image when you get helped.:)

User avatar
shakotay2
MEGAVETERAN
MEGAVETERAN
Posts: 2804
Joined: Fri Apr 20, 2012 9:24 am
Location: Nexus, searching for Jim Kirk
Has thanked: 699 times
Been thanked: 1443 times

Re: Approaches of Parsing Bone Representations

Post by shakotay2 » 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:
Bigchillghost, Reverse Engineering a Game Model: viewtopic.php?f=29&t=17889
extracting simple models: viewtopic.php?f=29&t=10894
Make_H2O-ForzaHor3-jm9.zip
"You quoted the whole thing, what a mess."

User avatar
Bigchillghost
ultra-veteran
ultra-veteran
Posts: 534
Joined: Tue Jul 05, 2016 9:37 am
Has thanked: 22 times
Been thanked: 449 times

Re: Approaches of Parsing Bone Representations

Post by Bigchillghost » 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.

Image

Noesis code(lack convertion to right-handed):

Code: Select all

#load the model
def noepyLoadModel(data, mdlList):
	bs = NoeBitStream(data)
	boneCount = bs.readUInt()
	bones = []
	for i in range(0, boneCount):
		bs.seek(0xC, NOESEEK_REL)
		bonePIndex = bs.readInt()
		Rot = NoeQuat.fromBytes(bs.readBytes(16))
		Tran = NoeVec3.fromBytes(bs.readBytes(12))
		bs.seek(0x18, NOESEEK_REL)
		NameLen = bs.readUInt() + 1
		boneName = noeStrFromBytes(bs.readBytes(NameLen), "ASCII")
		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
Image
You do not have the required permissions to view the files attached to this post.
Last edited by Bigchillghost on Wed Oct 09, 2019 9:27 am, edited 2 times in total.
May you find peace in this puzzle-solving game. Say it with action: click the Image when you get helped.:)

User avatar
Bigchillghost
ultra-veteran
ultra-veteran
Posts: 534
Joined: Tue Jul 05, 2016 9:37 am
Has thanked: 22 times
Been thanked: 449 times

Re: Approaches of Parsing Bone Representations

Post by Bigchillghost » 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.

Image

Noesis code:

Code: Select all

#load the model
def noepyLoadModel(data, mdlList):
	bs = NoeBitStream(data)
	bs.seek(8, NOESEEK_REL)
	boneCount = bs.readUInt()
	bs.seek(boneCount * 0x10 + 0x10, NOESEEK_REL)
	bones = []
	#noesis.logPopup()
	for i in range(0, boneCount):
		bs.seek(8, NOESEEK_REL)
		boneIndex = bs.readInt()
		bonePIndex = bs.readInt()
		bs.seek(0x34, NOESEEK_REL)
		Matrix = []
		for j in range(0, 12):
			Matrix.append(bs.readFloat())
		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
Image
You do not have the required permissions to view the files attached to this post.
Last edited by Bigchillghost on Wed Oct 09, 2019 9:27 am, edited 3 times in total.
May you find peace in this puzzle-solving game. Say it with action: click the Image when you get helped.:)

User avatar
Bigchillghost
ultra-veteran
ultra-veteran
Posts: 534
Joined: Tue Jul 05, 2016 9:37 am
Has thanked: 22 times
Been thanked: 449 times

Re: Approaches of Parsing Bone Representations

Post by Bigchillghost » 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.

Image

Noesis code:

Code: Select all

#load the model
def noepyLoadModel(data, mdlList):
	bs = NoeBitStream(data)
	boneCount = bs.readUShort()
	bones = []
	for i in range(0, boneCount):
		NameLen = bs.readInt()
		boneName = noeStrFromBytes(bs.readBytes(NameLen), "ASCII")
		bonePIndex = bs.readShort()
		bs.seek(28, NOESEEK_REL)
		Tran = NoeVec3.fromBytes(bs.readBytes(12))
		Rot = NoeQuat.fromBytes(bs.readBytes(16))
		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
Image
You do not have the required permissions to view the files attached to this post.
Last edited by Bigchillghost on Wed Oct 09, 2019 9:28 am, edited 1 time in total.
May you find peace in this puzzle-solving game. Say it with action: click the Image when you get helped.:)

User avatar
Bigchillghost
ultra-veteran
ultra-veteran
Posts: 534
Joined: Tue Jul 05, 2016 9:37 am
Has thanked: 22 times
Been thanked: 449 times

Re: Approaches of Parsing Bone Representations

Post by Bigchillghost » 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.

Image

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

Code: Select all

#load the model
def noepyLoadModel(data, mdlList):
	bs = NoeBitStream(data)
	boneCount = bs.readShort()
	pIDs= []
	for i in range(0, boneCount):
		aByte = bs.readUByte()
		if aByte != 0xFF:
			pIDs.append(aByte & 0xFF)
		else:
			pIDs.append(-1)
	
	nameList = []
	for i in range(0, boneCount):
		boneName = noeStrFromBytes(bs.readBytes(32), "ASCII")
		nameList.append(boneName)
	
	bones = []
	for i in range(0, boneCount):
		Matrix = []
		for j in range(0, 16):
			Matrix.append(bs.readFloat())
		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
Image
You do not have the required permissions to view the files attached to this post.
May you find peace in this puzzle-solving game. Say it with action: click the Image when you get helped.:)

jayn23
advanced
Posts: 50
Joined: Sun Jul 17, 2011 9:30 pm
Has thanked: 16 times
Been thanked: 34 times

Re: Approaches of Parsing Bone Representations

Post by jayn23 » 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
1. read the translation vector,
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]

User avatar
Bigchillghost
ultra-veteran
ultra-veteran
Posts: 534
Joined: Tue Jul 05, 2016 9:37 am
Has thanked: 22 times
Been thanked: 449 times

Re: Approaches of Parsing Bone Representations

Post by Bigchillghost » 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.
May you find peace in this puzzle-solving game. Say it with action: click the Image when you get helped.:)

jayn23
advanced
Posts: 50
Joined: Sun Jul 17, 2011 9:30 pm
Has thanked: 16 times
Been thanked: 34 times

Re: Approaches of Parsing Bone Representations

Post by jayn23 » Sun Aug 18, 2019 12:48 pm

Thanks for the fast reply, i guess i should read about FBX format now :D
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?

thnaks for all your help

User avatar
Bigchillghost
ultra-veteran
ultra-veteran
Posts: 534
Joined: Tue Jul 05, 2016 9:37 am
Has thanked: 22 times
Been thanked: 449 times

Re: Approaches of Parsing Bone Representations

Post by Bigchillghost » 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. :D
May you find peace in this puzzle-solving game. Say it with action: click the Image when you get helped.:)

Post Reply