# # anuim_trk.rb # Track File Definitions # # Author:: Luke Openshaw # # 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('') 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