471 lines
12 KiB
Ruby
Executable File
471 lines
12 KiB
Ruby
Executable File
#
|
|
# anuim_trk.rb
|
|
# Track File Definitions
|
|
#
|
|
# Author:: Luke Openshaw <luke.openshaw@rockstarnorth.com>
|
|
#
|
|
# Date:: 18 April 2008
|
|
#
|
|
require 'rexml/document'
|
|
require 'pipeline/util/string'
|
|
include REXML
|
|
|
|
module Pipeline
|
|
class AnimTrack
|
|
attr_reader :type
|
|
attr_reader :notes
|
|
|
|
def initialize(type, notes)
|
|
@type = type
|
|
@notes = notes
|
|
end
|
|
|
|
end
|
|
|
|
class AnimNote
|
|
attr_reader :frame
|
|
attr_reader :value
|
|
|
|
def initialize( frame, value )
|
|
@frame = frame
|
|
@value = value
|
|
end
|
|
end
|
|
|
|
class AnimMove3
|
|
attr_reader :numposkeys
|
|
attr_reader :numrotkeys
|
|
attr_reader :parent
|
|
attr_reader :moveentries
|
|
attr_reader :lines
|
|
|
|
ID_POS_KEYS = 1
|
|
ID_ROT_KEYS = 2
|
|
ID_PARENT = 3
|
|
|
|
def initialize(numposkeys, numrotkeys, parent, lines)
|
|
@numposkeys = numposkeys
|
|
@numrotkeys = numrotkeys
|
|
@moveentries = Array.new()
|
|
@parent = parent
|
|
|
|
|
|
|
|
lines.each {|line|
|
|
|
|
@moveentries.push(AnimMoveEntry.new(line))
|
|
}
|
|
#@lines = lines
|
|
end
|
|
|
|
end
|
|
|
|
class AnimMoveEntry
|
|
attr_reader :entrychannels
|
|
attr_reader :timevalue
|
|
|
|
def initialize(line)
|
|
@entrychannels = Array.new()
|
|
tokens = line.split(' ')
|
|
@timevalue = tokens[0]
|
|
|
|
baseoffset = 1
|
|
multiplier = 12
|
|
|
|
|
|
entrychannels = Array.new()
|
|
(0..2).each { |i|
|
|
channelentry = EntryChannel.new(line, (i*multiplier) + baseoffset)
|
|
@entrychannels.push(channelentry)
|
|
}
|
|
end
|
|
|
|
class EntryChannel
|
|
attr_reader :value
|
|
attr_reader :inTangent
|
|
attr_reader :outTangent
|
|
attr_reader :inTangentType
|
|
attr_reader :outTangentType
|
|
attr_reader :inTangentLength
|
|
attr_reader :outTangentLength
|
|
attr_reader :freeHandle
|
|
attr_reader :x_locked
|
|
attr_reader :y_locked
|
|
attr_reader :z_locked
|
|
attr_reader :constantVelocity
|
|
attr_reader :tokenoffset
|
|
|
|
|
|
def initialize( line, tokenoffset )
|
|
|
|
tokens = line.split(' ')
|
|
@value = tokens[tokenoffset]
|
|
@inTangent = tokens[tokenoffset+1]
|
|
@outTangent = tokens[tokenoffset+2]
|
|
@inTangentType = tokens[tokenoffset+3]
|
|
@outTangentType = tokens[tokenoffset+4]
|
|
@inTangentLength = tokens[tokenoffset+5]
|
|
@outTangentLength = tokens[tokenoffset+6]
|
|
@freeHandle = tokens[tokenoffset+7]
|
|
@x_locked = tokens[tokenoffset+8]
|
|
@y_locked = tokens[tokenoffset+9]
|
|
@z_locked = tokens[tokenoffset+10]
|
|
@constantVelocity = tokens[tokenoffset+11]
|
|
|
|
end
|
|
end
|
|
end
|
|
|
|
class Trk
|
|
attr_writer :unwind
|
|
attr_writer :motorbike
|
|
attr_writer :exportblendshape
|
|
attr_reader :tracks
|
|
attr_reader :selectionset
|
|
attr_writer :maxfilename
|
|
attr_writer :transtolerances
|
|
attr_writer :rottolerances
|
|
attr_writer :move3
|
|
attr_reader :trkfile
|
|
|
|
def initialize(trkfile)
|
|
|
|
@trkfile = trkfile
|
|
@tracks = Array.new()
|
|
@selectionset = Array.new()
|
|
@transtolerances = Array.new()
|
|
@rottolerances = Array.new()
|
|
#@move3 = Array.new()
|
|
|
|
end
|
|
|
|
def get_tag_name(tracktype)
|
|
TAGS.each do |tag, id|
|
|
return tag if id == tracktype
|
|
end
|
|
end
|
|
|
|
def get_end_tag(tagname)
|
|
escapetag = '[/' + tagname.slice(1, tagname.size)
|
|
return escapetag
|
|
end
|
|
|
|
private
|
|
|
|
STATE_NONE = -1
|
|
STATE_EVENT_TRACK = 0
|
|
STATE_AMBIENT_TRACK = 2
|
|
STATE_AUDIO_TRACK = 3
|
|
STATE_GESTURE_SPEAKER_TRACK = 4
|
|
STATE_GESTURE_LISTENER_TRACK = 5
|
|
STATE_FACIAL_SPEAKER_TRACK = 6
|
|
STATE_FACIAL_LISTENER_TRACK = 7
|
|
STATE_VISEME_TRACK = 8
|
|
STATE_SELECTION_SET = 9
|
|
STATE_MAX_FILENAME = 10
|
|
STATE_TRANS_TOLERANCES = 11
|
|
STATE_ROT_TOLERANCES = 12
|
|
STATE_MOVE3 = 13
|
|
STATE_UNWIND = 14
|
|
STATE_MOTORBIKE = 15
|
|
STATE_EXPORT_BLENDSHAPES = 16
|
|
|
|
|
|
TAGS = { '[EventTrack]' => STATE_EVENT_TRACK,
|
|
'[AmbientTrack]' => STATE_AMBIENT_TRACK,
|
|
'[AudioTrack]' => STATE_AUDIO_TRACK,
|
|
'[GestureSpeakerTrack]' => STATE_GESTURE_SPEAKER_TRACK,
|
|
'[GestureListenerTrack]' => STATE_GESTURE_LISTENER_TRACK,
|
|
'[FacialSpeakerTrack]' => STATE_FACIAL_SPEAKER_TRACK,
|
|
'[FacialListenerTrack]' => STATE_FACIAL_LISTENER_TRACK,
|
|
'[VisemeTrack]' => STATE_VISEME_TRACK,
|
|
'[SelectionSet]' => STATE_SELECTION_SET,
|
|
'[MaxFilename]' => STATE_MAX_FILENAME,
|
|
'[TransTolerances]' => STATE_TRANS_TOLERANCES,
|
|
'[RotTolerances]' => STATE_ROT_TOLERANCES,
|
|
'[move3' => STATE_MOVE3,
|
|
'[Unwind]' => STATE_UNWIND,
|
|
'[MotorBike]' => STATE_MOTORBIKE,
|
|
'[/' => STATE_NONE }
|
|
|
|
end
|
|
#
|
|
# == Description
|
|
#
|
|
# Old style track file
|
|
#
|
|
class Trk_Old < Trk
|
|
|
|
def initialize(trkfile)
|
|
super(trkfile)
|
|
end
|
|
|
|
|
|
|
|
def populate_generic_array(genarray, state, sourcefile, line)
|
|
tagname = get_tag_name(state)
|
|
tagend = get_end_tag(tagname)
|
|
while line.strip != tagend.strip do
|
|
genarray.push(line.strip)
|
|
line = sourcefile.readline
|
|
end
|
|
end
|
|
|
|
def build_anim_note(line, sourcefile, tracktype)
|
|
animnotes = Array.new()
|
|
tagname = get_tag_name(tracktype)
|
|
escapetag = get_end_tag(tagname)
|
|
|
|
while line.strip != escapetag.strip do
|
|
|
|
if line.slice(0, 5) == '[note' then
|
|
frame = line.slice(6, (line.size - 8))
|
|
value = sourcefile.readline
|
|
value = value.sub(/\n/, '')
|
|
animnote = AnimNote.new( frame, value )
|
|
animnotes.push(animnote)
|
|
end
|
|
line = sourcefile.readline
|
|
end
|
|
animnotes
|
|
end
|
|
|
|
def build_track(line, sourcefile, tracktype)
|
|
track = AnimTrack.new(tracktype, build_anim_note(line, sourcefile, tracktype))
|
|
end
|
|
|
|
def parse()
|
|
|
|
File.open(@trkfile) { |sourcefile|
|
|
state = -1
|
|
#move3line = ''
|
|
begin
|
|
sourcefile.each do |currline|
|
|
line = currline.strip
|
|
|
|
# Skip empty lines
|
|
next if 0 == line.size
|
|
|
|
|
|
TAGS.each do |tag, id|
|
|
|
|
slicedline = line.slice(0, tag.size)
|
|
if tag == slicedline then
|
|
state = id
|
|
#move3line = line if state == STATE_MOVE3
|
|
end
|
|
end
|
|
next if state == STATE_NONE
|
|
|
|
case state
|
|
when STATE_EVENT_TRACK..STATE_VISEME_TRACK
|
|
@tracks.push(build_track(line, sourcefile, state))
|
|
when STATE_SELECTION_SET
|
|
line = sourcefile.readline
|
|
populate_generic_array(@selectionset, state, sourcefile, line)
|
|
when STATE_MAX_FILENAME
|
|
@maxfilename = line
|
|
when STATE_TRANS_TOLERANCES
|
|
line = sourcefile.readline
|
|
populate_generic_array(@transtolerances, state, sourcefile, line)
|
|
when STATE_ROT_TOLERANCES
|
|
line = sourcefile.readline
|
|
populate_generic_array(@rottolerances, state, sourcefile, line)
|
|
when STATE_MOVE3
|
|
tokens = line.split(' ')
|
|
if tokens.size == 4 then
|
|
numrotkeys = tokens[AnimMove3::ID_ROT_KEYS]
|
|
numposkeys = tokens[AnimMove3::ID_POS_KEYS]
|
|
parent = tokens[AnimMove3::ID_PARENT]
|
|
parent = parent.chop
|
|
movelines = Array.new()
|
|
line = sourcefile.readline
|
|
while line.strip != '[end]' do
|
|
|
|
movelines.push(line)
|
|
line = sourcefile.readline
|
|
|
|
end
|
|
@move3 = AnimMove3.new(numposkeys, numrotkeys, parent, movelines)
|
|
end
|
|
state = STATE_NONE
|
|
when STATE_UNWIND
|
|
@unwind = true
|
|
when STATE_MOTORBIKE
|
|
@motorbike = true
|
|
when STATE_EXPORT_BLENDSHAPES
|
|
@exportblendshapes = true
|
|
when STATE_NONE
|
|
#print(line + "\n")
|
|
end
|
|
|
|
end
|
|
|
|
ensure
|
|
sourcefile.close()
|
|
end
|
|
}
|
|
|
|
|
|
end
|
|
|
|
def convert_to_new_format(trkout)
|
|
trknew = Trk_New.new(trkout)
|
|
trknew.tracks = @tracks
|
|
trknew.selectionset = @selectionset
|
|
trknew.transtolerances = @transtolerances
|
|
trknew.rottolerances = @rottolerances
|
|
trknew.move3 = @move3
|
|
trknew.unwind = @unwind
|
|
trknew.motorbike = @motorbike
|
|
trknew.exportblendshape = @exportblendshape
|
|
trknew.maxfilename = @maxfilename
|
|
|
|
trknew.write()
|
|
end
|
|
end
|
|
|
|
class Trk_New < Trk
|
|
|
|
def initialize(trkfile)
|
|
super(trkfile)
|
|
end
|
|
|
|
def write()
|
|
File.open(@trkfile, "w+") { |file|
|
|
file.write('<?xml version = "1.0"?>')
|
|
file.write("\n")
|
|
outputXML = REXML::Document.new()
|
|
root = outputXML.add_element("Animtrk")
|
|
|
|
# MaxFileName
|
|
if @maxfilename != nil then
|
|
maxfileelem = root.add_element("MaxFilename")
|
|
maxfileelem.attributes["Value"] = @maxfilename
|
|
maxfileelem.text = ""
|
|
end
|
|
|
|
# Unwind
|
|
if @unwind != nil then
|
|
unwindelem = root.add_element("Unwind")
|
|
unwindelem.attributes["Value"] = @unwind.to_s
|
|
unwindelem.text = ""
|
|
end
|
|
|
|
# Motorbike
|
|
if @motorbike != nil then
|
|
motorbikeelem = root.add_element("Motorbike")
|
|
motorbikeelem.attributes["Value"] = @motorbike.to_s
|
|
motorbikeelem.text = ""
|
|
end
|
|
|
|
# Export Blendshape
|
|
if @exportblendshape != nil then
|
|
motorbikeelem = root.add_element("ExportBlendShape")
|
|
motorbikeelem.attributes["Value"] = @exportblendshape.to_s
|
|
motorbikeelem.text = ""
|
|
end
|
|
|
|
# Tracks
|
|
@tracks.each { |track|
|
|
trackname = get_tag_name(track.type)
|
|
trackname = trackname.slice(1..(trackname.size - 2))
|
|
trackelem = root.add_element(trackname)
|
|
track.notes.each { |note|
|
|
noteelem = trackelem.add_element("Note")
|
|
noteelem.attributes["Frame"] = note.frame
|
|
noteelem.attributes["Value"] = note.value
|
|
noteelem.text = ""
|
|
}
|
|
trackelem.text = ""
|
|
}
|
|
|
|
# Selection set
|
|
if @selectionset.size > 0 then
|
|
selsetelem = root.add_element("SelectionSet")
|
|
@selectionset.each { |bone|
|
|
|
|
bone.replace_c(/ /, '_')
|
|
boneelem = selsetelem.add_element(bone.sub(/\n/, ''))
|
|
boneelem.text = ""
|
|
#selsetelem.add_text(bone.sub(/\n/, ''))
|
|
}
|
|
end
|
|
|
|
# transtolerance
|
|
if @transtolerances.size > 0 then
|
|
transtolelem = root.add_element("TransTolerances")
|
|
@transtolerances.each { |bone|
|
|
bone = bone.replace_c(/ /, '_')
|
|
boneelem = transtolelem.add_element(bone.sub(/\n/, ''))
|
|
boneelem.text = ""
|
|
}
|
|
|
|
end
|
|
|
|
# rottolerance
|
|
if @rottolerances.size > 0 then
|
|
rottolelem = root.add_element("RotTolerances")
|
|
@rottolerances.each { |bone|
|
|
bone = bone.replace_c(/ /, '_')
|
|
boneelem = rottolelem.add_element(bone.sub(/\n/, ''))
|
|
boneelem.text = ""
|
|
}
|
|
|
|
end
|
|
|
|
if @move3 != nil then
|
|
# move3 - Simple lines of text at present. Ill create a move3 line class if required
|
|
move3elem = root.add_element("move3")
|
|
move3elem.attributes["NumPosKeys"] = @move3.numposkeys
|
|
move3elem.attributes["NumRotKeys"] = @move3.numrotkeys
|
|
move3elem.attributes["Parent"] = @move3.parent
|
|
move3elem.text = ""
|
|
@move3.moveentries.each { |moveentry|
|
|
entrystring = moveentry.timevalue
|
|
channelcount = 0
|
|
moveentryelem = move3elem.add_element("MoveEntry")
|
|
moveentryelem.attributes["Frame"] = entrystring
|
|
moveentry.entrychannels.each { |entrychannel|
|
|
|
|
channelelem = moveentryelem.add_element("Channel")
|
|
channelelem.attributes["Channel"] = channelcount.to_s
|
|
#channelelem.attributes["Value"] = entrychannel.value
|
|
entrystring = entrychannel.value + ' ' +
|
|
entrychannel.inTangent + ' ' +
|
|
entrychannel.outTangent + ' ' +
|
|
entrychannel.inTangentType + ' ' +
|
|
entrychannel.outTangentType + ' ' +
|
|
entrychannel.inTangentLength + ' ' +
|
|
entrychannel.outTangentLength + ' ' +
|
|
entrychannel.freeHandle + ' ' +
|
|
entrychannel.x_locked + ' ' +
|
|
entrychannel.y_locked + ' ' +
|
|
entrychannel.z_locked + ' ' +
|
|
entrychannel.constantVelocity
|
|
|
|
textelem = channelelem.add_text(entrystring)
|
|
|
|
channelcount = channelcount + 1
|
|
}
|
|
|
|
|
|
}
|
|
end
|
|
|
|
fmt = REXML::Formatters::Pretty.new()
|
|
fmt.write(outputXML,file)
|
|
}
|
|
end
|
|
|
|
def read(trkfile)
|
|
File.open( trkfile ) { |file|
|
|
doc = Document.new( file )
|
|
doc.elements.each( 'Animtrk' ) { |var|
|
|
print(var.to_s + "\n")
|
|
}
|
|
}
|
|
end
|
|
end
|
|
|
|
end |