Created
July 12, 2012 22:53
-
-
Save mrwonko/3101655 to your computer and use it in GitHub Desktop.
Raven Software Ghoul 2 Animation retargetting script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/python | |
import struct | |
#### | |
#### Matrix Math | |
#### | |
# N-Dimensional vector with operators == and * (dot product) | |
class Vector: | |
# Constructor | |
# Either supply something that can be indexed and supports len() or the components | |
def __init__( self, data, *args ): | |
if len(args) == 0: | |
self.data = [ float(x) for x in data ] | |
else: | |
self.data = [ float(data) ] | |
self.data.extend( [ float(x) for x in args ] ) | |
# To string | |
def __repr__( self ): | |
return repr( self.data ) | |
# Indexing (read) | |
def __getitem__( self, index ): | |
return self.data[ index ] | |
# Indexing (write) | |
def __setitem__( self, index, data ): | |
self.data[ index ] = data | |
# Get length | |
def __len__( self ): | |
return len( self.data ) | |
# Dot product | |
def __mul__( self, rhs ): | |
return sum( [ x * y for x, y in zip( self, rhs ) ] ) | |
# Equality | |
def __eq__( self, rhs ): | |
return all( [ x == y for x, y in zip( self, rhs ) ] ) | |
# 4x4 Matrix with Matrix Multiplication and Inverse operators | |
class Matrix: | |
# Constructor | |
# data can be omitted, a Matrix to copy, a list of lists or bytes containing 12 floats / a compQuat | |
# if data is omitted, defaults to the identity matrix | |
def __init__( self, data=None ): | |
# copying a matrix (uses list) | |
if isinstance( data, Matrix ): | |
data = data.data | |
# initialize from list | |
if isinstance( data, list ): | |
self.data = [ Vector( row ) for row in data ] | |
for row in self.data: | |
assert( len(row) == 4 ) | |
if len( self.data ) == 3: | |
self.data.append( Vector( 0, 0, 0, 1 ) ) | |
else: | |
assert( len( self.data ) == 4 ) | |
assert( self.data[3] == Vector( 0, 0, 0, 1 ) ) | |
# read from bytes | |
elif isinstance( data, bytes ): | |
# 12 floats (first 3 rows) | |
if len(data) == 4*3 * 4: | |
self.data = [ Vector( struct.unpack( "4f", data[ 4*4*i : 4*4*(i+1) ] ) ) for i in range(3) ] | |
self.data.append( Vector( 0, 0, 0, 1 ) ) | |
# quaternion + location | |
elif len(data) == 14: | |
# 0 = w, 1 = x, 2 = y, 3 = z | |
quat = [ float( struct.unpack( "H", data[ i : i+2 ] )[ 0 ] ) / 16383.0 - 2.0 for i in range( 0, 8, 2 ) ] | |
loc = [ float( struct.unpack( "H", data[ i : i+2 ] )[ 0 ] ) / 64.0 - 512.0 for i in range( 8, 14, 2) ] | |
# names from jk2's code, may make no sense | |
T = [ 2 * x for x in quat[1:] ] | |
Tw = [ quat[ 0 ] * x for x in T ] | |
Tx = [ quat[ 1 ] * x for x in T ] | |
Ty = [ quat[ 2 ] * x for x in T ] | |
Tz = [ quat[ 3 ] * x for x in T ] | |
self.data = [ | |
Vector( 1.0 - ( Ty[1] + Tz[2] ), Tx[1] - Tw[2], Tx[2] + Tw[1], loc[0] ), | |
Vector( Tx[1] + Tw[2], 1.0 - ( Tx[0] + Tz[2] ), Ty[2] - Tw[0], loc[1] ), | |
Vector( Tx[2] - Tw[1], Ty[2] + Tw[0], 1.0 - ( Tx[0] + Ty[1] ), loc[2] ), | |
Vector( 0, 0, 0, 1 ) | |
] | |
# invalid format | |
else: | |
raise RuntimeError("Invalid byte count!") | |
# invalid format | |
elif data != None: | |
raise RuntimeError("Invalid parameter!") | |
# default: identity matrix | |
else: | |
self.data = [] | |
for i in range(4): | |
row = Vector( 0, 0, 0, 0 ) | |
row[i] = 1 | |
self.data.append(row) | |
# packs as 12 floats (first 3 rows) | |
def pack( self ): | |
assert( self.data[ 3 ] == Vector( 0, 0, 0, 1 ) ) | |
return b"".join( [ struct.pack( "4f", *x ) for x in self.data[:3] ] ) | |
# packs as compressed quaternion + translation | |
def toCompQuat( self ): | |
# rotation matrix to quaternion | |
quat = [ | |
( self.data[0][0] + self.data[1][1] + self.data[2][2] + 1.0 ) / 4.0, | |
( self.data[0][0] - self.data[1][1] - self.data[2][2] + 1.0 ) / 4.0, | |
( - self.data[0][0] + self.data[1][1] - self.data[2][2] + 1.0 ) / 4.0, | |
( - self.data[0][0] - self.data[1][1] + self.data[2][2] + 1.0 ) / 4.0, | |
] | |
quat = [ max( 0, x ) for x in quat ] | |
import math | |
quat = [ math.sqrt( x ) for x in quat ] | |
def sign( x ): | |
if x >= 0: | |
return 1 | |
else: | |
return -1 | |
if all( [ quat[0] >= x for x in quat ] ): | |
quat[1] *= sign( self.data[2][1] - self.data[1][2] ) | |
quat[2] *= sign( self.data[0][2] - self.data[2][0] ) | |
quat[3] *= sign( self.data[1][0] - self.data[0][1] ) | |
elif all( [ quat[1] >= x for x in quat ] ): | |
quat[0] *= sign( self.data[2][1] - self.data[1][2] ) | |
quat[2] *= sign( self.data[1][0] + self.data[0][1] ) | |
quat[3] *= sign( self.data[0][2] + self.data[2][0] ) | |
elif all( [ quat[2] >= x for x in quat ] ): | |
quat[0] *= sign( self.data[0][2] - self.data[2][0] ) | |
quat[1] *= sign( self.data[1][0] + self.data[0][1] ) | |
quat[3] *= sign( self.data[2][1] + self.data[1][2] ) | |
elif all( [ quat[3] >= x for x in quat ] ): | |
quat[0] *= sign( self.data[1][0] - self.data[0][1] ) | |
quat[1] *= sign( self.data[2][0] + self.data[0][2] ) | |
quat[2] *= sign( self.data[2][1] + self.data[1][2] ) | |
else: | |
raise RuntimeError( "Coding error" ) | |
norm = math.sqrt( sum( [ x * x for x in quat ] ) ) | |
quat = [ x / norm for x in quat ] | |
# compression as short | |
quat = [ ( x + 2 ) * 16383 for x in quat ] | |
loc = [ ( row[3] + 512) * 64 for row in self.data[:3] ] | |
return struct.pack( "7H", *[ round( x ) for x in quat + loc] ) | |
# Matrix Multiplication | |
def __mul__( self, rhs ): | |
return Matrix( [ [ self.row(y) * rhs.col(x) for x in range(4) ] for y in range(3) ] ) | |
# To String | |
def __repr__( self ): | |
return "[ " + "\n ".join( [ repr( row ) for row in self.data ] ) + " ]" | |
def __eq__( self, rhs ): | |
return all( [ x == y for x, y in zip( self.data, rhs.data ) ] ) | |
def __getitem__( self, index ): | |
return self.row( index ) | |
# Returns copy of given row | |
def row( self, index ): | |
return Vector( self.data[ index ] ) | |
# Returns copy of given column | |
def col( self, index ): | |
return Vector( [ row[ index ] for row in self.data ] ) | |
# Inverts the matrix | |
def invert( self ): | |
denom = - self.data[0][2]*self.data[1][1]*self.data[2][0] + self.data[0][1]*self.data[1][2]*self.data[2][0] + self.data[0][2]*self.data[1][0]*self.data[2][1] - self.data[0][0]*self.data[1][2]*self.data[2][1] - self.data[0][1]*self.data[1][0]*self.data[2][2] + self.data[0][0]*self.data[1][1]*self.data[2][2] | |
self.data = [ | |
Vector( [ | |
(self.data[1][1]*self.data[2][2]-self.data[1][2]*self.data[2][1])/denom, | |
(self.data[0][2]*self.data[2][1]-self.data[0][1]*self.data[2][2])/denom, | |
(self.data[0][1]*self.data[1][2]-self.data[0][2]*self.data[1][1])/denom, | |
(self.data[0][3]*self.data[1][2]*self.data[2][1]-self.data[0][2]*self.data[1][3]*self.data[2][1]-self.data[0][3]*self.data[1][1]*self.data[2][2]+self.data[0][1]*self.data[1][3]*self.data[2][2]+self.data[0][2]*self.data[1][1]*self.data[2][3]-self.data[0][1]*self.data[1][2]*self.data[2][3])/denom | |
] ), | |
Vector( [ | |
(self.data[1][2]*self.data[2][0]-self.data[1][0]*self.data[2][2])/denom, | |
(self.data[0][0]*self.data[2][2]-self.data[0][2]*self.data[2][0])/denom, | |
(self.data[0][2]*self.data[1][0]-self.data[0][0]*self.data[1][2])/denom, | |
(-self.data[0][3]*self.data[1][2]*self.data[2][0]+self.data[0][2]*self.data[1][3]*self.data[2][0]+self.data[0][3]*self.data[1][0]*self.data[2][2]-self.data[0][0]*self.data[1][3]*self.data[2][2]-self.data[0][2]*self.data[1][0]*self.data[2][3]+self.data[0][0]*self.data[1][2]*self.data[2][3])/denom | |
] ), | |
Vector( [ | |
(self.data[1][0]*self.data[2][1]-self.data[1][1]*self.data[2][0])/denom, | |
(self.data[0][1]*self.data[2][0]-self.data[0][0]*self.data[2][1])/denom, | |
(self.data[0][0]*self.data[1][1]-self.data[0][1]*self.data[1][0])/denom, | |
(self.data[0][3]*self.data[1][1]*self.data[2][0]-self.data[0][1]*self.data[1][3]*self.data[2][0]-self.data[0][3]*self.data[1][0]*self.data[2][1]+self.data[0][0]*self.data[1][3]*self.data[2][1]+self.data[0][1]*self.data[1][0]*self.data[2][3]-self.data[0][0]*self.data[1][1]*self.data[2][3])/denom | |
] ), | |
Vector( 0, 0, 0, 1 ) | |
] | |
# Returns this matrix inverted | |
def inverted( self ): | |
res = Matrix( self ) | |
res.invert() | |
return res | |
#### | |
#### GLA Reading | |
#### | |
GLA_IDENT = b"2LGA" | |
GLA_VERSION = 6 | |
class FileFormatError( RuntimeError ): | |
def __init__( self, message ): | |
self.message = message | |
def __str__( self ): | |
return self.message | |
# converts a NULL-terminated binary string to an ordinary string. | |
def decodeCString(bs): | |
end = bs.find(b"\0") #find null termination | |
if end == -1: #if none exists, there is no end. | |
return bs.decode() | |
return bs[:end].decode() # otherwise cut it off at end | |
class MdxaHeader: | |
def __init__( self ): | |
self.name = "" | |
self.scale = 1 # does not seem to be used by Jedi Academy anyway - or is it? I need it in import! | |
self.numFrames = -1 | |
self.ofsFrames = -1 | |
self.numBones = -1 | |
self.ofsCompBonePool = -1 | |
self.ofsSkel = -1 # this is also MdxaSkelOffsets.baseOffset + MdxaSkelOffsets.boneOffsets[0] - probably a historic leftover | |
self.ofsEnd = -1 | |
def loadFromFile( self, file ): | |
# check ident | |
ident, = struct.unpack( "4s", file.read( 4 ) ) | |
if ident != GLA_IDENT: | |
raise FileFormatError( "File does not start with " + str( GLA_IDENT ) + " but " + str( ident ) + " - no GLA!" ) | |
version, = struct.unpack( "i", file.read( 4 ) ) | |
if version != GLA_VERSION: | |
raise FileFormatError( "Wrong gla file version! (" + str( version ) + " should be " + str( mrw_g2_constants.GLA_VERSION) + ")" ) | |
self.name = decodeCString( file.read( 64 ) ) | |
self.scale, self.numFrames, self.ofsFrames, self.numBones, self.ofsCompBonePool, self.ofsSkel, self.ofsEnd = struct.unpack( "f6i", file.read( 7*4 ) ) | |
def saveToFile( self, file ): | |
file.write( struct.pack( "4si64sf6i", GLA_IDENT, GLA_VERSION, self.name.encode(), self.scale, self.numFrames, self.ofsFrames, self.numBones, self.ofsCompBonePool, self.ofsSkel, self.ofsEnd ) ) | |
def getSize( self ): | |
return 2*4 + 64*1 + 7*4 | |
def setOffsets( self, hierarchy, frames, pool ): | |
self.ofsSkel = hierarchy.getOfsSkel() | |
self.ofsFrames = self.ofsSkel + hierarchy.getSize() | |
self.ofsCompBonePool = self.ofsFrames + frames.getSize() | |
self.ofsEnd = self.ofsCompBonePool + pool.getSize() | |
class MdxaBone: | |
def __init__( self ): | |
pass | |
def loadFromFile( self, file ): | |
self.name = decodeCString( file.read( 64 ) ) | |
self.flags, self.parent = struct.unpack( "Ii", file.read( 2*4 ) ) | |
self.basePoseMat = Matrix( file.read( 12*4 ) ) | |
self.basePoseMatInv = Matrix( file.read( 12*4 ) ) | |
assert( all( [ abs(self.basePoseMat.inverted()[y][x] - self.basePoseMatInv[y][x]) < 0.01 for x in range(4) for y in range(3) ] ) ) | |
self.numChildren, = struct.unpack( "I", file.read( 4 ) ) | |
self.children = [ struct.unpack( "I", file.read( 4 ) )[ 0 ] for i in range( self.numChildren ) ] | |
def saveToFile( self, file ): | |
file.write( struct.pack( "64sIi", self.name.encode(), self.flags, self.parent ) ) | |
file.write( self.basePoseMat.pack() ) | |
file.write( self.basePoseMatInv.pack() ) | |
file.write( struct.pack( "{}I".format( 1 + self.numChildren ), self.numChildren, *self.children ) ) | |
def getSize( self ): | |
return 64 + 4 + 4 + 12*4 + 12*4 + 4 + self.numChildren*4 | |
class MdxaHierarchy: | |
def __init__( self, header ): | |
self.header = header | |
self.bonesByIndex = [] | |
def loadFromFile( self, file ): | |
file.seek( self.header.getSize() ) | |
boneOffsets = list( struct.unpack("{}I".format( self.header.numBones ), file.read( self.header.numBones * 4 ) ) ) | |
for offset in boneOffsets: | |
file.seek( self.header.getSize() + offset ) | |
bone = MdxaBone() | |
bone.loadFromFile( file ) | |
self.bonesByIndex.append( bone ) | |
def saveToFile( self, file ): | |
offset = len( self ) * 4 | |
for bone in self.bonesByIndex: | |
file.write( struct.pack( "I", offset ) ) | |
offset += bone.getSize() | |
for bone in self.bonesByIndex: | |
bone.saveToFile( file ) | |
# size relative to OfsBones, ignoring bone offsets (before that) | |
def getSize( self ): | |
return sum( [ bone.getSize() for bone in self.bonesByIndex ] ) | |
def getOfsSkel( self ): | |
return self.header.getSize() + 4 * len( self.bonesByIndex ) | |
def __len__( self ): | |
return len( self.bonesByIndex ) | |
def __getitem__( self, index ): | |
return self.bonesByIndex[ index ] | |
class MdxaFrame: | |
def __init__( self, numBones ): | |
self.numBones = numBones | |
self.indices = [] | |
# returns maximum compQuat index | |
def loadFromFile( self, file ): | |
self.indices = [ struct.unpack( "I", file.read( 3 ) + b"\x00" )[0] for i in range( self.numBones ) ] | |
return max( self.indices ) | |
def saveToFile( self, file ): | |
for index in self.indices: | |
# only write 3 bytes per index | |
file.write( struct.pack( "I", index)[ :3 ] ) | |
# bones are not MdxaBones | |
def loadFromBones( self, bonesByIndex, compBonePool ): | |
self.indices = [ compBonePool.getIndex( bone.getRelativeOffset() ) for bone in bonesByIndex ] | |
def getSize( self ): | |
return 3 * self.numBones | |
class MdxaFrames: | |
def __init__( self, numBones ): | |
self.numBones = numBones | |
self.frames = [] | |
def loadFromFile( self, file, numFrames ): | |
class Mutable: | |
pass | |
data = Mutable() | |
data.maxIndex = -1 # I can't change upvalues | |
def loadFrame(): | |
frame = MdxaFrame( self.numBones ) | |
data.maxIndex = max(frame.loadFromFile( file ), data.maxIndex ) | |
return frame | |
self.frames = [ loadFrame() for i in range( numFrames ) ] | |
return data.maxIndex | |
def saveToFile( self, file ): | |
for frame in self.frames: | |
frame.saveToFile( file ) | |
# add 32 bit padding if required | |
if len( self.frames ) % 4 != 0: | |
file.write( b"\x00" * ( 4 - len (self.frames ) % 4 ) ) | |
def __len__( self ): | |
return len( self.frames ) | |
def __getitem__( self, index ): | |
return self.frames[ index ] | |
def append( self, frame ): | |
self.frames.append( frame ) | |
def getSize( self ): | |
if len( self.frames ) == 0: | |
return 0 | |
else: | |
size = len( self.frames) * self.frames[ 0 ].getSize() | |
if size % 4 != 0: | |
size += 4 - size % 4 | |
return size | |
class MdxaBonePool: | |
def __init__( self ): | |
self.pool = [] | |
self.boneIndex = {} | |
def getIndex( self, matrix ): | |
compQuat = matrix.toCompQuat() | |
if compQuat in self.boneIndex: | |
return self.boneIndex[ compQuat ] | |
else: | |
index = len( self.pool ) | |
self.boneIndex[ compQuat ] = index | |
self.pool.append( compQuat ) | |
return index | |
def __getitem__( self, index ): | |
return Matrix( self.pool[ index ] ) | |
def __len__( self ): | |
return len( self.pool ) | |
def loadFromFile( self, file, numCompQuats ): | |
self.pool = [ file.read( 14 ) for i in range( numCompQuats ) ] | |
def saveToFile( self, file ): | |
for compQuat in self.pool: | |
file.write( compQuat ) | |
def getSize( self ): | |
return 14 * len ( self.pool ) | |
def saveToFile( file, header, hierarchy, frames, pool ): | |
header.setOffsets( hierarchy, frames, pool ) | |
header.numFrames = len( frames ) | |
header.saveToFile( file ) | |
assert( file.tell() == header.getSize() ) | |
hierarchy.saveToFile( file ) | |
assert( file.tell() == header.ofsFrames ) | |
frames.saveToFile( file ) | |
assert( file.tell() == header.ofsCompBonePool ) | |
pool.saveToFile( file ) | |
def loadHeaderAndHierarchyFromFile( file ): | |
# header | |
header = MdxaHeader() | |
header.loadFromFile( file ) | |
# hierarchy | |
hierarchy = MdxaHierarchy( header ) | |
hierarchy.loadFromFile( file ) | |
return header, hierarchy | |
def loadFromFile( file ): | |
header, hierarchy = loadHeaderAndHierarchyFromFile( file ) | |
# frames | |
file.seek( header.ofsFrames ) | |
frames = MdxaFrames( header.numBones ) | |
maxIndex = frames.loadFromFile( file, header.numFrames ) | |
# compressed bone pool | |
file.seek( header.ofsCompBonePool ) | |
pool = MdxaBonePool() | |
pool.loadFromFile( file, maxIndex + 1 ) | |
return header, hierarchy, frames, pool | |
#### | |
#### Program invokation | |
#### | |
import sys | |
usage = "Usage: gla_convert.py <sourcefile.gla> <targetfile.gla> <bonemappings.txt> <output.gla>\n\ | |
<sourcefile.tga> Ghoul 2 Animation file to convert\n\ | |
<targetfile.tga> Ghoul 2 Animation file whose skeleton is to be converted to\n\ | |
<bonemappings.txt> File describing which bone in the sourcefile is to be mapped to which one in the targetfile. Source bones mapped to nothing get dropped, target bones that nothing maps to get a default value. File format:\n\ | |
<source bone name 1> <target bone name 1>\n\ | |
<source bone name 2> <target bone name 2>\n\ | |
...\n\ | |
<output.gla> Filename of file to save. Will be overwritten if it already exists." | |
if any( [ x in ( "-h", "--help", "/?", "/help") for x in sys.argv[1:] ] ) or len( sys.argv ) != 5: | |
print(usage) | |
exit() | |
# Load original file | |
try: | |
print( "Loading source data..." ) | |
sourceFile = open( sys.argv[ 1 ], "rb" ) | |
sourceHeader, sourceHierarchy, sourceFrames, sourcePool = loadFromFile( sourceFile ) | |
sourceFile.close() | |
print( "done." ) | |
except FileFormatError as err: | |
print( "Error reading source file \"{}\": {}".format( sys.argv[ 1 ], err ) ) | |
exit() | |
except IOError as err: | |
print( err ) | |
exit() | |
# load target hierarchy | |
try: | |
print( "Loading target hierarchy..." ) | |
targetFile = open( sys.argv[ 2 ], "rb" ) | |
targetHeader, targetHierarchy = loadHeaderAndHierarchyFromFile( targetFile ) | |
targetFile.close() | |
print( "done." ) | |
except FileFormatError as err: | |
print( "Error reading target file \"{}\": {}".format( sys.argv[ 2 ], err ) ) | |
exit() | |
except IOError as err: | |
print( err ) | |
exit() | |
# load bone mappings | |
try: | |
print( "Loading bone mappings..." ) | |
bonesFile = open( sys.argv[ 3 ], "r" ) | |
boneMappings = [] | |
for line in bonesFile: | |
parts = line.split() | |
if len( parts ) == 0: | |
pass | |
elif len( parts ) == 2: | |
boneMappings.append( [ x.lower() for x in parts ] ) | |
else: | |
print( "Warning: Invalid line \"{}\" in bonenames file! Format: sourcename targetname".format(line) ) | |
print( "done." ) | |
except IOError as err: | |
print( err ) | |
exit() | |
sourceBoneNameByTargetBoneName = {} | |
for boneMapping in boneMappings: | |
sourceBoneNameByTargetBoneName[ boneMapping[ 1 ] ] = boneMapping[ 0 ] | |
# build bone hierarchy | |
print( "Converting..." ) | |
class Bone: | |
def __init__( self, targetOnly ): | |
self.targetOnly = targetOnly | |
self.sourceChildren = [] | |
self.targetChildren = [] | |
self.basePoseMat = None # for checks source vs. target | |
self.absoluteOffset = Matrix() # for those not in the source hierarchy | |
self.absoluteOffsetInverted = Matrix() | |
# set from source | |
self.sourceRelativeOffset = Matrix() # identity matrix if nothing else supplied (e.g. does not exist in source) | |
# read for target | |
self.targetRelativeOffset = None | |
def calculateAbsoluteOffsetFromSource( self, parentAbsoluteOffset ): | |
self.absoluteOffset = self.sourceRelativeOffset * parentAbsoluteOffset | |
self.absoluteOffset = parentAbsoluteOffset * self.sourceRelativeOffset | |
self.absoluteOffsetInverted = self.absoluteOffset.inverted() | |
for child in self.sourceChildren: | |
child.calculateAbsoluteOffsetFromSource( self.absoluteOffset ) | |
def calculateTargetRelativeOffset( self, parentAbsoluteOffsetInverted ): | |
if self.targetOnly: | |
self.targetRelativeOffset = Matrix() | |
for child in self.targetChildren: | |
child.calculateTargetRelativeOffset( parentAbsoluteOffsetInverted ) | |
else: | |
self.targetRelativeOffset = self.absoluteOffset * parentAbsoluteOffsetInverted | |
self.targetRelativeOffset = parentAbsoluteOffsetInverted * self.absoluteOffset | |
for child in self.targetChildren: | |
child.calculateTargetRelativeOffset( self.absoluteOffsetInverted ) | |
def getRelativeOffset( self ): | |
return self.targetRelativeOffset | |
# add source bones | |
bonesBySourceIndex = [] | |
bonesBySourceName = {} | |
for mdxaBone in sourceHierarchy: | |
bone = Bone( False ) | |
bonesBySourceIndex.append( bone ) | |
bonesBySourceName[ mdxaBone.name.lower() ] = bone | |
bone.basePoseMat = mdxaBone.basePoseMat | |
bone.basePoseMatInv = mdxaBone.basePoseMatInv | |
# add target bones, using the same where required by mapping | |
bonesByTargetIndex = [] | |
bonesByTargetName = {} | |
for mdxaBone in targetHierarchy: | |
name = mdxaBone.name.lower() | |
bone = None | |
if name in sourceBoneNameByTargetBoneName: | |
sourceName = sourceBoneNameByTargetBoneName[ name ] | |
if sourceName not in bonesBySourceName: | |
print( "Error: No source bone called \"{}\" in source skeleton!".format( sourceName ) ) | |
exit() | |
bone = bonesBySourceName[ sourceName ] | |
# check if the base pose mat changed much (more than 0.1) | |
if any( [ abs(mdxaBone.basePoseMat[y][x] - bone.basePoseMat[y][x]) > 0.1 for x in range(4) for y in range(3) ] ): | |
print( "Warning: Target bone {}'s base pose changed! Expect bad results.".format( mdxaBone.name ) ) | |
else: | |
bone = Bone( True ) | |
bone.basePoseMat = mdxaBone.basePoseMat | |
bone.basePoseMatInv = mdxaBone.basePoseMatInv | |
bonesByTargetName[ name ] = bone | |
bonesByTargetIndex.append( bone ) | |
# build hierarchy | |
sourceRoots = [] | |
for mdxaBone, bone in zip(sourceHierarchy, bonesBySourceIndex): | |
bone.sourceChildren = [ bonesBySourceIndex[ index ] for index in mdxaBone.children ] | |
if mdxaBone.parent == -1: | |
sourceRoots.append(bone) | |
targetRoots = [] | |
for mdxaBone, bone in zip(targetHierarchy, bonesByTargetIndex): | |
bone.targetChildren = [ bonesByTargetIndex[ index ] for index in mdxaBone.children ] | |
if mdxaBone.parent == -1: | |
targetRoots.append(bone) | |
# convert! | |
targetFrames = MdxaFrames( targetHeader.numBones ) | |
targetPool = MdxaBonePool() | |
identityMatrix = Matrix() | |
for frame in sourceFrames: | |
# set relative bone offsets (source parent space) | |
for index, bone in zip( frame.indices, bonesBySourceIndex ): | |
bone.sourceRelativeOffset = sourcePool[ index ] | |
# calculate absolute offsets from them (model/world space) | |
for bone in sourceRoots: | |
bone.calculateAbsoluteOffsetFromSource( identityMatrix ) | |
# calculate relative offsets (target parent space) | |
for bone in targetRoots: | |
bone.calculateTargetRelativeOffset( identityMatrix ) | |
# save relative offsets | |
targetFrame = MdxaFrame( targetHeader.numBones ) | |
targetFrame.loadFromBones( bonesByTargetIndex, targetPool ) | |
targetFrames.append( targetFrame ) | |
print( "done." ) | |
# write result | |
print( "Writing output file..." ) | |
outputFile = open( sys.argv[ 4 ], "wb" ) | |
saveToFile( outputFile, targetHeader, targetHierarchy, targetFrames, targetPool ) | |
outputFile.close() | |
print( "done." ) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
model_root model_root | |
pelvis pelvis | |
Motion Motion | |
lfemurYZ lfemurYZ | |
lfemurX lfemurX | |
ltibia ltibia | |
ltalus ltalus | |
rfemurYZ rfemurYZ | |
rfemurX rfemurX | |
rtibia rtibia | |
rtalus rtalus | |
lower_lumbar lower_lumbar | |
upper_lumbar upper_lumbar | |
thoracic thoracic | |
cervical cervical | |
cranium cranium | |
ceyebrow ceyebrow | |
jaw jaw | |
lblip2 lblip2 | |
leye leye | |
rblip2 rblip2 | |
ltlip2 ltlip2 | |
rtlip2 rtlip2 | |
reye reye | |
rclavical rclavical | |
rhumerus rhumerus | |
rhumerusX rhumerusX | |
rradius rradius | |
rradiusX rradiusX | |
rhand rhand | |
r_d1_j1 r_d1_j1 | |
r_d1_j2 r_d1_j2 | |
r_d2_j1 r_d2_j1 | |
r_d2_j2 r_d2_j2 | |
r_d4_j1 r_d4_j1 | |
r_d4_j2 r_d4_j2 | |
rhang_tag_bone rhang_tag_bone | |
lclavical lclavical | |
lhumerus lhumerus | |
lhumerusX lhumerusX | |
lradius lradius | |
lradiusX lradiusX | |
lhand lhand | |
l_d4_j1 l_d4_j1 | |
l_d4_j2 l_d4_j2 | |
l_d2_j1 l_d2_j1 | |
l_d2_j2 l_d2_j2 | |
l_d1_j1 l_d1_j1 | |
l_d1_j2 l_d1_j2 | |
face face |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment