Files
2025-09-29 00:52:08 +02:00

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