Tuesday, 23 February 2016

Copy a TRLE animation frame to an XNALara pose


Run animation

In this tutorial I will be using XNALara Posing Studio (XPS) 11.4 by XNAaraL from http://xnalaraitalia.deviantart.com/ and replicate a pose from a Tomb Raider Level Editor (TRLE) wad animation.

The first step is to change the model’s bind pose in XNALara to match the “No animating” pose as shown in Wadmerger’s Animation Editor.
Usually this will mean changing the position of the arms so they are beside the model’s body and not in a T-pose.

No animation pose

For best results you must use an XNALara model that is similar to the TRLE model such as the “too_lara_1996” model included with XPS which I use here.

too_lara_1996

Position the arms beside the body.

Arms beside body

Now go to the Modify menu and click Save Generic_Item and select “modify armature according to the current pose”.

Modify the armature

Click OK and the confirm you want to modify the armature and then save the model into a new folder inside the “data” folder.

Reset the scene and load the model you just saved.

In Wadmerger’s Animation Editor select an animation and export it from the Animation menu as a .TRW file.

Open the animation in my TRW Editor program which displays the rotation values for all the meshes for each keyframe of the animation.

TRW Editor

Now what you do is match a mesh to a bone in XPS, select that bone and enter –1 x RotX for the X axis value, –1 x RotY for the Y axis value and RotZ for the Z axis value.

The angles in TRW Editor are from 0 to 1024 representing 0 to 360 degrees but if you right click on an angle the value in degrees will be shown.

Convert the angles in degrees between 360 and 180 to a value between 0 and –180. Not necessary!

I matched the bones as follows.
  • Mesh 0 – root hips
  • Mesh 1 – leg left thigh
  • Mesh 2 – leg left knee
  • Mesh 3 – leg left ankle
  • Mesh 4 – leg right thigh
  • Mesh 5 – leg right knee
  • Mesh 6 – leg right ankle
  • Mesh 7 – spine lower
  • Mesh 8 – arm right shoulder 2
  • Mesh 9 – arm right elbow
  • Mesh10 – arm right wrist
  • Mesh11 – arm left shoulder 2
  • Mesh12 – arm left elbow
  • Mesh13 – arm left wrist
  • Mesh14 – head neck upper
Once you have made your pose, save the pose with File>Save selected pose.

You can load the pose for other models to see how it looks but if the proportions of the model do not match the TRLE model the pose may not be perfect.

Pose used for other model

#python 2.5
# copy this python file to your XNALara pose folder and run from there
# since pose files are written to the same folder the python file runs from.
# This script copies all keyframes of a TRW file to a pose file each.
#WARNING: will overwrite files of the same name
import os.path, sys
from tkFileDialog import askopenfilename as tkFileGet
import Tkinter as tk
import struct
bonenames =["root hips",
"leg left thigh",
"leg left knee",
"leg left ankle",
"leg right thigh",
"leg right knee" ,
"leg right ankle",
"spine lower",
"arm right shoulder 2",
"arm right elbow" ,
"arm right wrist",
"arm left shoulder 2",
"arm left elbow",
"arm left wrist",
"head neck upper"]
class AppData:
pass
class BBox:
def __init__(self):
self.x1,self.x2 = 0,0
self.y1,self.y2 = 0,0
self.z1,self.z2 = 0,0
self.size = 12
def clear(self):
self.x1,self.x2 = 0,0
self.y1,self.y2 = 0,0
self.z1,self.z2 = 0,0
self.size = 12
return
def __str__(self):
return "%i %i %i %i %i %i"%(self.x1,self.x2,self.y1,self.y2,self.z1,self.z2)
class Offset:
def __init__(self):
self.x = 0
self.y = 0
self.z = 0
self.size = 6
def __str__(self):
return "(%i %i %i)"%(self.x,self.y,self.z)
class Angle:
def __init__(self):
self.rotx = 0
self.roty = 0
self.rotz = 0
self.size = self.calcSize()
def __str__(self):
return "(%4i %4i %4i)"%(self.rotx, self.roty,self.rotz)
def calcSize(self):
l = [self.rotx,self.roty,self.rotz]
if l.count(0) < 2:
size = 4 # 3 axis rotation
else:
size = 2 # single axis rotation
return size
def toDegrees(self):
return self.rotx*360.0/1024, self.roty*360.0/1024, self.rotz*360.0/1024
class Keyframe:
def __init__(self):
self.id = None
self.bb = None
self.offset = None
self.nummeshes = None
self.angles = []
self.size = 0
class AnimCommand:
def __init__(self):
self.cmd = None
self.op1 = None
self.op2 = None
self.op3 = None
class TRW:
def __init__(self):
self.name = ""
self.version = None
self.keyoffset = None
self.frameduration = None
self.keyframesize = None
self.stateid = None
self.unknown1 = None
self.speed = None
self.acc = None
self.unknown2 = None
self.framestart = None
self.frameend = None
self.nextanim = None
self.frameIn = None
self.numStateChanges = None
self.changesIndex = None
self.numCommands = None
self.commandsOffset = None
self.ffff=-1
self.numAnimCommands = None
self.animcommands = []
self.keypackagesize = None
self.numFrames = None
self.numKeys = None
self.keys = []
self.maxkeysize = 0
self.nummeshes = 0
def parseTRW(fn):
ok=False
trw = TRW()
print fn
f =open(fn,"rb")
trw.version = f.read(8) #190 0 5 0 ????
trw.keyoffset, = struct.unpack('I',f.read(4))
print 'keyoffset',trw.keyoffset
trw.frameduration, = struct.unpack('B',f.read(1))
print 'frameduration', trw.frameduration
trw.keyframesize,=struct.unpack('B',f.read(1))
print 'keyframesize %i words'%trw.keyframesize
trw.stateid, = struct.unpack('H',f.read(2))
print 'stateid ',trw.stateid
trw.unknown1 = f.read(2)
trw.speed, = struct.unpack('h',f.read(2))
print 'speed ',trw.speed
trw.acc,=struct.unpack('i',f.read(4))
print 'acc ',trw.acc*1.0/65536
trw.unknown2 = f.read(8)
trw.framestart,=struct.unpack('H',f.read(2))
print 'framestart ',trw.framestart
trw.frameend, = struct.unpack('H',f.read(2))
print 'frameend ',trw.frameend
trw.nextanim,=struct.unpack('h',f.read(2)) #offset
print 'nextanim (offset)',trw.nextanim
trw.frameIn,=struct.unpack('H',f.read(2))
print 'frameIn ',trw.frameIn
trw.numStateChanges,=struct.unpack('H',f.read(2))
print 'numStateChanges ',trw.numStateChanges
trw.changesIndex,=struct.unpack('H',f.read(2))
print 'changesIndex ',trw.changesIndex
trw.numCommands, = struct.unpack('H',f.read(2))
print 'numCommands ',trw.numCommands
trw.commandsOffset, = struct.unpack('H',f.read(2))
print 'commandsOffset ', trw.commandsOffset
trw.ffff = f.read(4) #0xFFFF marker???
trw.numAnimCommands, = struct.unpack('H',f.read(2))
print 'numAnimCommands ',trw.numAnimCommands
for i in range(trw.numAnimCommands):
ac = AnimCommand()
ac.cmd, = struct.unpack('H',f.read(2))
ac.op1,ac.op2,ac.op3=struct.unpack('hhh',f.read(6))
trw.animcommands.append(ac)
print '\tanimcommand %i %i %i %i'%(ac.cmd,ac.op1,ac.op2,ac.op3)
trw.keypackagesize, = struct.unpack('I',f.read(4))
print 'keypackagesize is %i words'%trw.keypackagesize
trw.numFrames, = struct.unpack('I',f.read(4))
print 'numFrames ',trw.numFrames
trw.numKeys, = struct.unpack('I',f.read(4))
print 'numKeys ',trw.numKeys
'''
test = keypackagesize/keyframesize
if numKeys != test:
tkMessageBox.showinfo('Error.','Error with numkeys.')
numKeys = test
'''
keysizelist=[]
keys = []
for i in range(trw.numKeys):
key = Keyframe()
#print '-'*20
key.id = i
#print 'key %i'%i
bb = BBox()
bb.x1,bb.x2,bb.y1,bb.y2,bb.z1,bb.z2 = struct.unpack('6h',f.read(12))
key.bb = bb
key.size += bb.size
off = Offset()
off.x,off.y,off.z = struct.unpack('3h',f.read(6))
key.offset = off
key.size += off.size # should equal 18
#print 'bb: %i %i %i %i %i %i offset: %i %i %i'%(bb.x1,bb.x2,bb.y1,bb.y2,bb.z1,bb.z2,off.x,off.y,off.z)
key.nummeshes, = struct.unpack('H',f.read(2))
trw.nummeshes = key.nummeshes
for i in range(key.nummeshes):
ang = Angle()
ang.rotx,ang.roty,ang.rotz = struct.unpack('HHH',f.read(6)) #360 degrees = 1024
rx,ry,rz = convertAngle(ang.rotx,ang.roty,ang.rotz)
#print 'Mesh#%i %i %i %i (%f %f %f)'%(i,ang.rotx,ang.roty,ang.rotz,rx,ry,rz)
key.size += ang.calcSize()
key.angles.append(ang)
#size += getKeySize(rotx,roty,rotz)
#print 'size in wad: %i bytes'%key.size
keys.append(key)
keysizelist.append(key.size)
f.close()
trw.keys = keys
trw.maxkeysize = max(keysizelist)
print
if trw.maxkeysize%2 != 0:
print 'Error calculating keysize. Not multiple of 2'
return
else:
trw.maxkeysize /=2
print 'maxkeysize %i words'%trw.maxkeysize
AppData.trw = trw
ok=True
return ok
def getKeySize(rx,ry,rz):
list = [rx,ry,rz]
if list.count(0) < 2:
size = 4 # 3 axis rotation
else:
size = 2 # single axis rotation
return size
def convertAngle(rx,ry,rz):
return rx*360.0/1024,ry*360.0/1024,rz*360.0/1024
def openFile():
fn = tkFileGet(title='Select a file',
filetypes=[('Wadmerger .trw','*.TRW'),('All Files','*.*')])
if fn:
if fn[-4:].lower() in ['.trw']:
print "Testing %s"%fn
print
if not parseTRW(fn):
print 'Error reading file'
return False
else:
return True
else:
print fn+' selected\n\nNeed .TRW file.'
return False
else:
print 'No file selected.'
return False
if __name__=='__main__':
if openFile():
for kf in range(AppData.trw.numKeys):
keyframe = AppData.trw.keys[kf]
print "Writing pose file"
f = open("keyframe%i.pose"%kf,"w")
for k in range(keyframe.nummeshes):
ang = keyframe.angles[k]
rx,ry,rz = convertAngle(ang.rotx,ang.roty,ang.rotz)
f.write("%s: %.6f %.6f %.6f 0 0 0\n"%(bonenames[k],-rx,-ry,rz))
f.close()
print "Done"
view raw trw2xna.py hosted with ❤ by GitHub


Karlito modified the script to include root translation and to use filenames suitable for XPS animation. [link]

#python 2.5
# copy this python file to your XNALara pose folder and run from there
# since pose files are written to the same folder the python file runs from.
# This script copies all keyframes of a TRW file to a pose file each.
#WARNING: will overwrite files of the same name
import os, shutil
import os.path, sys
from tkFileDialog import askopenfilename as tkFileGet
import Tkinter as tk
import struct
bonenames =["root hips",
"leg left thigh",
"leg left knee",
"leg left ankle",
"leg right thigh",
"leg right knee" ,
"leg right ankle",
"spine lower",
"arm right shoulder 2",
"arm right elbow" ,
"arm right wrist",
"arm left shoulder 2",
"arm left elbow",
"arm left wrist",
"head neck upper"]
filename = "default"
bb0 = [] # y offset value for root hips
bb1 = [] # y offset value for root hips
bb2 = [] # z offset value for root hips
class AppData:
pass
class BBox:
def __init__(self):
self.x1,self.x2 = 0,0
self.y1,self.y2 = 0,0
self.z1,self.z2 = 0,0
self.size = 12
def clear(self):
self.x1,self.x2 = 0,0
self.y1,self.y2 = 0,0
self.z1,self.z2 = 0,0
self.size = 12
return
def __str__(self):
return "%i %i %i %i %i %i"%(self.x1,self.x2,self.y1,self.y2,self.z1,self.z2)
class Offset:
def __init__(self):
self.x = 0
self.y = 0
self.z = 0
self.size = 6
def __str__(self):
return "(%i %i %i)"%(self.x,self.y,self.z)
class Angle:
def __init__(self):
self.rotx = 0
self.roty = 0
self.rotz = 0
self.size = self.calcSize()
def __str__(self):
return "(%4i %4i %4i)"%(self.rotx, self.roty,self.rotz)
def calcSize(self):
l = [self.rotx,self.roty,self.rotz]
if l.count(0) < 2:
size = 4 # 3 axis rotation
else:
size = 2 # single axis rotation
return size
def toDegrees(self):
return self.rotx*360.0/1024, self.roty*360.0/1024, self.rotz*360.0/1024
class Keyframe:
def __init__(self):
self.id = None
self.bb = None
self.offset = None
self.nummeshes = None
self.angles = []
self.size = 0
class AnimCommand:
def __init__(self):
self.cmd = None
self.op1 = None
self.op2 = None
self.op3 = None
class TRW:
def __init__(self):
self.name = ""
self.version = None
self.keyoffset = None
self.frameduration = None
self.keyframesize = None
self.stateid = None
self.unknown1 = None
self.speed = None
self.acc = None
self.unknown2 = None
self.framestart = None
self.frameend = None
self.nextanim = None
self.frameIn = None
self.numStateChanges = None
self.changesIndex = None
self.numCommands = None
self.commandsOffset = None
self.ffff=-1
self.numAnimCommands = None
self.animcommands = []
self.keypackagesize = None
self.numFrames = None
self.numKeys = None
self.keys = []
self.maxkeysize = 0
self.nummeshes = 0
def parseTRW(fn):
ok=False
trw = TRW()
print fn
f =open(fn,"rb")
trw.version = f.read(8) #190 0 5 0 ????
trw.keyoffset, = struct.unpack('I',f.read(4))
print 'keyoffset',trw.keyoffset
trw.frameduration, = struct.unpack('B',f.read(1))
print 'frameduration', trw.frameduration
trw.keyframesize,=struct.unpack('B',f.read(1))
print 'keyframesize %i words'%trw.keyframesize
trw.stateid, = struct.unpack('H',f.read(2))
print 'stateid ',trw.stateid
trw.unknown1 = f.read(2)
trw.speed, = struct.unpack('h',f.read(2))
print 'speed ',trw.speed
trw.acc,=struct.unpack('i',f.read(4))
print 'acc ',trw.acc*1.0/65536
trw.unknown2 = f.read(8)
trw.framestart,=struct.unpack('H',f.read(2))
print 'framestart ',trw.framestart
trw.frameend, = struct.unpack('H',f.read(2))
print 'frameend ',trw.frameend
trw.nextanim,=struct.unpack('h',f.read(2)) #offset
print 'nextanim (offset)',trw.nextanim
trw.frameIn,=struct.unpack('H',f.read(2))
print 'frameIn ',trw.frameIn
trw.numStateChanges,=struct.unpack('H',f.read(2))
print 'numStateChanges ',trw.numStateChanges
trw.changesIndex,=struct.unpack('H',f.read(2))
print 'changesIndex ',trw.changesIndex
trw.numCommands, = struct.unpack('H',f.read(2))
print 'numCommands ',trw.numCommands
trw.commandsOffset, = struct.unpack('H',f.read(2))
print 'commandsOffset ', trw.commandsOffset
trw.ffff = f.read(4) #0xFFFF marker???
trw.numAnimCommands, = struct.unpack('H',f.read(2))
print 'numAnimCommands ',trw.numAnimCommands
for i in range(trw.numAnimCommands):
ac = AnimCommand()
ac.cmd, = struct.unpack('H',f.read(2))
ac.op1,ac.op2,ac.op3=struct.unpack('hhh',f.read(6))
trw.animcommands.append(ac)
print '\tanimcommand %i %i %i %i'%(ac.cmd,ac.op1,ac.op2,ac.op3)
trw.keypackagesize, = struct.unpack('I',f.read(4))
print 'keypackagesize is %i words'%trw.keypackagesize
trw.numFrames, = struct.unpack('I',f.read(4))
print 'numFrames ',trw.numFrames
trw.numKeys, = struct.unpack('I',f.read(4))
print 'numKeys ',trw.numKeys
'''
test = keypackagesize/keyframesize
if numKeys != test:
tkMessageBox.showinfo('Error.','Error with numkeys.')
numKeys = test
'''
keysizelist=[]
keys = []
for i in range(trw.numKeys):
key = Keyframe()
#print '-'*20
key.id = i
#print 'key %i'%i
bb = BBox()
bb.x1,bb.x2,bb.y1,bb.y2,bb.z1,bb.z2 = struct.unpack('6h',f.read(12))
key.bb = bb
key.size += bb.size
off = Offset()
off.x,off.y,off.z = struct.unpack('3h',f.read(6))
key.offset = off
key.size += off.size # should equal 18
#print 'bb: %i %i %i %i %i %i offset: %i %i %i'%(bb.x1,bb.x2,bb.y1,bb.y2,bb.z1,bb.z2,off.x,off.y,off.z)
bb0.append(off.x)
bb1.append(off.y)
bb2.append(off.z)
key.nummeshes, = struct.unpack('H',f.read(2))
trw.nummeshes = key.nummeshes
for i in range(key.nummeshes):
ang = Angle()
ang.rotx,ang.roty,ang.rotz = struct.unpack('HHH',f.read(6)) #360 degrees = 1024
rx,ry,rz = convertAngle(ang.rotx,ang.roty,ang.rotz)
#print 'Mesh#%i %i %i %i (%f %f %f)'%(i,ang.rotx,ang.roty,ang.rotz,rx,ry,rz)
key.size += ang.calcSize()
key.angles.append(ang)
#size += getKeySize(rotx,roty,rotz)
#print 'size in wad: %i bytes'%key.size
keys.append(key)
keysizelist.append(key.size)
f.close()
trw.keys = keys
trw.maxkeysize = max(keysizelist)
print
if trw.maxkeysize%2 != 0:
print 'Error calculating keysize. Not multiple of 2'
return
else:
trw.maxkeysize /=2
print 'maxkeysize %i words'%trw.maxkeysize
AppData.trw = trw
ok=True
return ok
def getKeySize(rx,ry,rz):
list = [rx,ry,rz]
if list.count(0) < 2:
size = 4 # 3 axis rotation
else:
size = 2 # single axis rotation
return size
def convertAngle(rx,ry,rz):
return rx*360.0/1024,ry*360.0/1024,rz*360.0/1024
def openFile():
fn = tkFileGet(title='Select a file',
filetypes=[('Wadmerger .trw','*.TRW'),('All Files','*.*')])
filename = fn
if fn:
if fn[-4:].lower() in ['.trw']:
print "Testing %s"%fn
print
if not parseTRW(fn):
print 'Error reading file'
return False
else:
return fn
else:
print fn+' selected\n\nNeed .TRW file.'
return False
else:
print 'No file selected.'
return False
if __name__=='__main__':
filename = openFile()
if filename:
directory = os.path.splitext(filename)[0] # for testing use: directory = "default"
if not os.path.exists(directory):
os.makedirs(directory)
for the_file in os.listdir(directory):
file_path = os.path.join(directory, the_file)
try:
if os.path.isfile(file_path):
os.unlink(file_path)
#elif os.path.isdir(file_path): shutil.rmtree(file_path)
except Exception as e:
print(e)
for kf in range(AppData.trw.numKeys):
keyframe = AppData.trw.keys[kf]
print "Writing pose file"
#print 'y offset: %i'%(bb1[0])
if kf > 999:
f = open("%s\keyframe%i.pose"%(directory,kf),"w")
else:
if kf > 99:
f = open("%s\keyframe0%i.pose"%(directory,kf),"w")
else:
if kf > 9:
f = open("%s\keyframe00%i.pose"%(directory,kf),"w")
else:
f = open("%s\keyframe000%i.pose"%(directory,kf),"w")
for k in range(keyframe.nummeshes):
ang = keyframe.angles[k]
rx,ry,rz = convertAngle(ang.rotx,ang.roty,ang.rotz)
if k == 0:
f.write("%s: %.6f %.6f %.6f %.6f %.6f %.6f\n"%(bonenames[k],-rx,-ry,rz,-bb0[kf]/1000.0,-bb1[kf]/1000.0,bb2[kf]/1000.0))
else:
f.write("%s: %.6f %.6f %.6f 0 0 0\n"%(bonenames[k],-rx,-ry,rz))
f.close()
print "Done"
view raw trw2xna2.py hosted with ❤ by GitHub


TRLE -> ms3d: Negate RotY and RotZ and convert from yxzr order to xyzs.

No comments:

Post a Comment