bvh 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -5
- data/.gitignore +29 -27
- data/EXAMPLES.rdoc +1 -1
- data/LICENSE +20 -20
- data/README.rdoc +97 -97
- data/Rakefile +58 -58
- data/VERSION +1 -1
- data/bvh.gemspec +69 -69
- data/ext/bvh/Makefile +96 -58
- data/ext/bvh/bvh.c +10 -10
- data/ext/bvh/bvh.h +18 -10
- data/ext/bvh/bvh_parser.c +65 -63
- data/ext/bvh/extconf.rb +4 -4
- data/lib/bvh.rb +74 -74
- data/lib/bvh/exporter.rb +86 -86
- data/lib/bvh/matrix.rb +19 -19
- data/lib/bvh/motion.rb +79 -79
- data/lib/bvh/motion/channel_data.rb +147 -147
- data/lib/bvh/motion/frame.rb +124 -124
- data/lib/bvh/parser.rb +177 -177
- data/lib/bvh/skeleton.rb +84 -84
- data/test/bvh_test.rb +62 -62
- data/test/data/in/karate.bvh +767 -767
- data/test/helper.rb +13 -13
- metadata +21 -9
data/lib/bvh/motion/frame.rb
CHANGED
@@ -1,124 +1,124 @@
|
|
1
|
-
class Bvh
|
2
|
-
class Motion
|
3
|
-
|
4
|
-
class Frame
|
5
|
-
# The array of ChannelData objects for this frame: one ChannelData instance for each bone.
|
6
|
-
attr_reader :channel_data
|
7
|
-
|
8
|
-
def initialize()
|
9
|
-
@channel_data = []
|
10
|
-
end
|
11
|
-
|
12
|
-
# Creates a copy of this frame, including a dup of its channel data.
|
13
|
-
def copy
|
14
|
-
ret = self.class.new
|
15
|
-
channel_data.each do |datum|
|
16
|
-
ret.channel_data << datum.dup
|
17
|
-
end
|
18
|
-
ret
|
19
|
-
end
|
20
|
-
|
21
|
-
# call-seq:
|
22
|
-
# frame + frame => new_frame
|
23
|
-
# frame - frame => new_frame
|
24
|
-
# frame / frame => new_frame
|
25
|
-
# frame * frame => new_frame
|
26
|
-
# frame + number => new_frame
|
27
|
-
# frame - number => new_frame
|
28
|
-
# frame / number => new_frame
|
29
|
-
# frame * number => new_frame
|
30
|
-
#
|
31
|
-
# Performs arithmetic on this frame with the target. The second operand may be either a number or another Frame.
|
32
|
-
# If the target is a number, then that number is added to, subtracted from, multiplied with, or divided against
|
33
|
-
# each channel of each ChannelData object in this frame.
|
34
|
-
#
|
35
|
-
# If the target is another Frame, the arithmetic looks something like this (simplified):
|
36
|
-
# return_value.channel_data[0] = frame1.channel_data[0] * frame2.channel_data[0]
|
37
|
-
# return_value.channel_data[0] = frame1.channel_data[1] * frame2.channel_data[1]
|
38
|
-
# return_value.channel_data[0] = frame1.channel_data[2] * frame2.channel_data[2]
|
39
|
-
# . . .
|
40
|
-
#
|
41
|
-
# Both frames must contain the same number of ChannelData instances, and each instance must
|
42
|
-
# have the same number of channels, and each instance must also reference the same bone.
|
43
|
-
#
|
44
|
-
# Returns a new frame containing the result.
|
45
|
-
#
|
46
|
-
def arithmetic_proc(target)
|
47
|
-
# Fooled you! I metaprogrammed it to save some typing!
|
48
|
-
end
|
49
|
-
undef arithmetic_proc
|
50
|
-
|
51
|
-
[:+, :-, :/, :*].each do |operator|
|
52
|
-
define_method operator do |target|
|
53
|
-
if target.kind_of?(Frame)
|
54
|
-
unless channel_data.length == target.channel_data.length
|
55
|
-
raise "Expected both frames to have the same amount of data"
|
56
|
-
end
|
57
|
-
ret = Frame.new
|
58
|
-
channel_data.each_with_index do |datum, index|
|
59
|
-
ret.channel_data << datum.send(operator, target.channel_data[index])
|
60
|
-
end
|
61
|
-
ret
|
62
|
-
else
|
63
|
-
ret = Frame.new
|
64
|
-
channel_data.each { |datum| ret.channel_data << datum.send(operator, target) }
|
65
|
-
end
|
66
|
-
ret
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
# Returns the channel data for the specified bone.
|
71
|
-
def channel_data_for(bone)
|
72
|
-
@channel_data.each { |c| return c if c.bone == bone }
|
73
|
-
raise "Channel data for bone not found: #{bone.name}"
|
74
|
-
end
|
75
|
-
|
76
|
-
# Returns the relative, or local, transform matrix for the specified bone in this frame.
|
77
|
-
def relative_transform_matrix(bone)
|
78
|
-
return channel_data_for(bone).relative_transform_matrix
|
79
|
-
end
|
80
|
-
|
81
|
-
# Returns the transform matrix of the root node multiplied with its children recursively
|
82
|
-
# down to the specified bone, the result being the total transformation in worldspace for this frame.
|
83
|
-
def absolute_transform_matrix(bone)
|
84
|
-
relative = relative_transform_matrix(bone)
|
85
|
-
if bone.parent then absolute_transform_matrix(bone.parent) * relative
|
86
|
-
else relative
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
# Modifies a single attribute of the channel data for this bone in this frame. Returns self.
|
91
|
-
#
|
92
|
-
# Ex:
|
93
|
-
# bvh.last_frame.set_channel bone, 'Xrotation', 180 # => sets the bone to a 180 deg rotation around the X-axis
|
94
|
-
def set_channel(bone, channel, theta)
|
95
|
-
channel_data_for(bone).set_channel(channel, theta)
|
96
|
-
end
|
97
|
-
|
98
|
-
# Retrives the value for a specific channel for a specific bone within this frame.
|
99
|
-
def get_channel(bone, channel)
|
100
|
-
channel_data_for(bone).get_channel(channel)
|
101
|
-
end
|
102
|
-
|
103
|
-
# Modifies the channel data for this bone in this frame, resulting in a rotation around the specified
|
104
|
-
# channel.
|
105
|
-
#
|
106
|
-
# Ex:
|
107
|
-
# bvh.last_frame.set_channel bone, 'Xrotation', 180 # => rotates the bone by 180 deg rotation around the X-axis
|
108
|
-
def rotate!(bone, channel, theta)
|
109
|
-
set_channel(bone, channel, channel_data_for(bone).get_channel(channel) + theta)
|
110
|
-
end
|
111
|
-
|
112
|
-
# Adds x, y and z to the X, Y and Z position channels for this bone in this frame, resulting in a "movement" or
|
113
|
-
# translation.
|
114
|
-
def translate!(bone, x, y, z)
|
115
|
-
set_channel(bone, 'Xposition', channel_data_for(bone).get_channel('Xposition')+x)
|
116
|
-
set_channel(bone, 'Yposition', channel_data_for(bone).get_channel('Yposition')+y)
|
117
|
-
set_channel(bone, 'Zposition', channel_data_for(bone).get_channel('Zposition')+z)
|
118
|
-
end
|
119
|
-
|
120
|
-
alias transform_matrix absolute_transform_matrix
|
121
|
-
alias local_transform_matrix relative_transform_matrix
|
122
|
-
end
|
123
|
-
end
|
124
|
-
end
|
1
|
+
class Bvh
|
2
|
+
class Motion
|
3
|
+
|
4
|
+
class Frame
|
5
|
+
# The array of ChannelData objects for this frame: one ChannelData instance for each bone.
|
6
|
+
attr_reader :channel_data
|
7
|
+
|
8
|
+
def initialize()
|
9
|
+
@channel_data = []
|
10
|
+
end
|
11
|
+
|
12
|
+
# Creates a copy of this frame, including a dup of its channel data.
|
13
|
+
def copy
|
14
|
+
ret = self.class.new
|
15
|
+
channel_data.each do |datum|
|
16
|
+
ret.channel_data << datum.dup
|
17
|
+
end
|
18
|
+
ret
|
19
|
+
end
|
20
|
+
|
21
|
+
# call-seq:
|
22
|
+
# frame + frame => new_frame
|
23
|
+
# frame - frame => new_frame
|
24
|
+
# frame / frame => new_frame
|
25
|
+
# frame * frame => new_frame
|
26
|
+
# frame + number => new_frame
|
27
|
+
# frame - number => new_frame
|
28
|
+
# frame / number => new_frame
|
29
|
+
# frame * number => new_frame
|
30
|
+
#
|
31
|
+
# Performs arithmetic on this frame with the target. The second operand may be either a number or another Frame.
|
32
|
+
# If the target is a number, then that number is added to, subtracted from, multiplied with, or divided against
|
33
|
+
# each channel of each ChannelData object in this frame.
|
34
|
+
#
|
35
|
+
# If the target is another Frame, the arithmetic looks something like this (simplified):
|
36
|
+
# return_value.channel_data[0] = frame1.channel_data[0] * frame2.channel_data[0]
|
37
|
+
# return_value.channel_data[0] = frame1.channel_data[1] * frame2.channel_data[1]
|
38
|
+
# return_value.channel_data[0] = frame1.channel_data[2] * frame2.channel_data[2]
|
39
|
+
# . . .
|
40
|
+
#
|
41
|
+
# Both frames must contain the same number of ChannelData instances, and each instance must
|
42
|
+
# have the same number of channels, and each instance must also reference the same bone.
|
43
|
+
#
|
44
|
+
# Returns a new frame containing the result.
|
45
|
+
#
|
46
|
+
def arithmetic_proc(target)
|
47
|
+
# Fooled you! I metaprogrammed it to save some typing!
|
48
|
+
end
|
49
|
+
undef arithmetic_proc
|
50
|
+
|
51
|
+
[:+, :-, :/, :*].each do |operator|
|
52
|
+
define_method operator do |target|
|
53
|
+
if target.kind_of?(Frame)
|
54
|
+
unless channel_data.length == target.channel_data.length
|
55
|
+
raise "Expected both frames to have the same amount of data"
|
56
|
+
end
|
57
|
+
ret = Frame.new
|
58
|
+
channel_data.each_with_index do |datum, index|
|
59
|
+
ret.channel_data << datum.send(operator, target.channel_data[index])
|
60
|
+
end
|
61
|
+
ret
|
62
|
+
else
|
63
|
+
ret = Frame.new
|
64
|
+
channel_data.each { |datum| ret.channel_data << datum.send(operator, target) }
|
65
|
+
end
|
66
|
+
ret
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the channel data for the specified bone.
|
71
|
+
def channel_data_for(bone)
|
72
|
+
@channel_data.each { |c| return c if c.bone == bone }
|
73
|
+
raise "Channel data for bone not found: #{bone.name}"
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns the relative, or local, transform matrix for the specified bone in this frame.
|
77
|
+
def relative_transform_matrix(bone)
|
78
|
+
return channel_data_for(bone).relative_transform_matrix
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns the transform matrix of the root node multiplied with its children recursively
|
82
|
+
# down to the specified bone, the result being the total transformation in worldspace for this frame.
|
83
|
+
def absolute_transform_matrix(bone)
|
84
|
+
relative = relative_transform_matrix(bone)
|
85
|
+
if bone.parent then absolute_transform_matrix(bone.parent) * relative
|
86
|
+
else relative
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Modifies a single attribute of the channel data for this bone in this frame. Returns self.
|
91
|
+
#
|
92
|
+
# Ex:
|
93
|
+
# bvh.last_frame.set_channel bone, 'Xrotation', 180 # => sets the bone to a 180 deg rotation around the X-axis
|
94
|
+
def set_channel(bone, channel, theta)
|
95
|
+
channel_data_for(bone).set_channel(channel, theta)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Retrives the value for a specific channel for a specific bone within this frame.
|
99
|
+
def get_channel(bone, channel)
|
100
|
+
channel_data_for(bone).get_channel(channel)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Modifies the channel data for this bone in this frame, resulting in a rotation around the specified
|
104
|
+
# channel.
|
105
|
+
#
|
106
|
+
# Ex:
|
107
|
+
# bvh.last_frame.set_channel bone, 'Xrotation', 180 # => rotates the bone by 180 deg rotation around the X-axis
|
108
|
+
def rotate!(bone, channel, theta)
|
109
|
+
set_channel(bone, channel, channel_data_for(bone).get_channel(channel) + theta)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Adds x, y and z to the X, Y and Z position channels for this bone in this frame, resulting in a "movement" or
|
113
|
+
# translation.
|
114
|
+
def translate!(bone, x, y, z)
|
115
|
+
set_channel(bone, 'Xposition', channel_data_for(bone).get_channel('Xposition')+x)
|
116
|
+
set_channel(bone, 'Yposition', channel_data_for(bone).get_channel('Yposition')+y)
|
117
|
+
set_channel(bone, 'Zposition', channel_data_for(bone).get_channel('Zposition')+z)
|
118
|
+
end
|
119
|
+
|
120
|
+
alias transform_matrix absolute_transform_matrix
|
121
|
+
alias local_transform_matrix relative_transform_matrix
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/bvh/parser.rb
CHANGED
@@ -1,177 +1,177 @@
|
|
1
|
-
class Bvh
|
2
|
-
class Parser
|
3
|
-
attr_reader :filename
|
4
|
-
attr_reader :source
|
5
|
-
|
6
|
-
def initialize(file)
|
7
|
-
@filename = file
|
8
|
-
@source = File.read(file)
|
9
|
-
end
|
10
|
-
|
11
|
-
def parse(bvh)
|
12
|
-
@bvh = bvh
|
13
|
-
|
14
|
-
# a little touch-up...
|
15
|
-
s = source
|
16
|
-
# it's tempting to just downcase the whole string, but that'd change the case of object names, which might be
|
17
|
-
# bad for the end user. So we'll swap out specific keywords instead.
|
18
|
-
s.gsub!(/^((\s*)(root|offset|channels|joint|end site|hierarchy)(([^\n\{]*)(\n|\{)))/mi) do
|
19
|
-
match = $~
|
20
|
-
"#{match[2]}#{match[3].downcase.gsub(/\s/, '_')} \"#{match[5].strip}\"#{match[6]}"
|
21
|
-
end
|
22
|
-
# make { . . . } into proper Ruby blocks
|
23
|
-
s.gsub!(/[\n\s]*\{/m, ' do').gsub!(/[\n\s]*\}/m, "\nend")
|
24
|
-
|
25
|
-
# Finally, handle the MOTION segment, which can be treated as a single method call.
|
26
|
-
s.gsub!(/^((\s*)(motion)(.*))/mi) do
|
27
|
-
"#{$~[2]}#{$~[3].downcase} <<-EOF\n#{$~[4].strip}\nEOF\n"
|
28
|
-
end
|
29
|
-
|
30
|
-
eval(s, binding, @filename, 1)
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
attr_reader :bvh, :mode, :current_node
|
35
|
-
|
36
|
-
def bone(type, name, &block)
|
37
|
-
raise ArgumentError, "#{type} #{name} is unexpected at this time" unless mode == :hierarchy
|
38
|
-
bone = type.new(name)
|
39
|
-
|
40
|
-
if current_node.nil?
|
41
|
-
bvh.create_skeleton!.root = bone
|
42
|
-
else
|
43
|
-
current_node.add_joint!(bone)
|
44
|
-
end
|
45
|
-
|
46
|
-
@current_node, bone = bone, @current_node
|
47
|
-
instance_eval(&block)
|
48
|
-
@current_node = bone
|
49
|
-
end
|
50
|
-
|
51
|
-
# Ex:
|
52
|
-
# root "joint-pelvis" do
|
53
|
-
# offset "0.00 0.00 0.00"
|
54
|
-
# channels "6 Xposition Ypoisition Zposition Zrotation Xrotation Yrotation"
|
55
|
-
# joint "joint-spine3" do
|
56
|
-
# . . .
|
57
|
-
# end
|
58
|
-
# end
|
59
|
-
def root(name, &block)
|
60
|
-
@current_node = nil
|
61
|
-
bone Bvh::Skeleton::Bone, name, &block
|
62
|
-
end
|
63
|
-
|
64
|
-
# Ex:
|
65
|
-
# root "joint-pelvis" do
|
66
|
-
# . . .
|
67
|
-
# joint "joint-spine3" do
|
68
|
-
# offset "0.00 -10.65 0.00"
|
69
|
-
# channels "3 Zrotation Xrotation Yrotation"
|
70
|
-
# end_site do
|
71
|
-
# . . .
|
72
|
-
# end
|
73
|
-
# end
|
74
|
-
# end
|
75
|
-
def joint(name, &block)
|
76
|
-
bone Bvh::Skeleton::Bone, name, &block
|
77
|
-
end
|
78
|
-
|
79
|
-
# Ex:
|
80
|
-
# root "joint-pelvis" do
|
81
|
-
# . . .
|
82
|
-
# joint "joint-spine3" do
|
83
|
-
# . . .
|
84
|
-
# end_site do
|
85
|
-
# offset "0.00 -7.00 0.00"
|
86
|
-
# end
|
87
|
-
# end
|
88
|
-
# end
|
89
|
-
def end_site(*unused, &block)
|
90
|
-
bone Bvh::Skeleton::Bone, nil, &block
|
91
|
-
end
|
92
|
-
|
93
|
-
def hierarchy(*unused)
|
94
|
-
@mode = :hierarchy
|
95
|
-
end
|
96
|
-
|
97
|
-
def offset(val)
|
98
|
-
raise ArgumentError, "OFFSET is unexpected at this time" unless current_node
|
99
|
-
raise ArgumentError, "Already have OFFSET data for this node" unless current_node.offset.length == 0
|
100
|
-
current_node.offset.concat val.split.collect { |i| i.to_f }
|
101
|
-
end
|
102
|
-
|
103
|
-
def channels(val)
|
104
|
-
raise ArgumentError, "CHANNELS is unexpected at this time" unless current_node
|
105
|
-
raise ArgumentError, "Already have CHANNELS data for this node" unless current_node.channels.length == 0
|
106
|
-
vals = val.split
|
107
|
-
count = vals.shift.to_i # how many?
|
108
|
-
raise ArgumentError, "Expected #{count} channels, found #{vals.length}" if vals.length != count
|
109
|
-
current_node.channels.concat vals
|
110
|
-
end
|
111
|
-
|
112
|
-
def motion(motion_data)
|
113
|
-
raise ArgumentError, "Motion data is unexpected at this time" unless mode == :hierarchy
|
114
|
-
@mode = :motion
|
115
|
-
frame_count = nil
|
116
|
-
motion_data.each_line do |line|
|
117
|
-
line = line.downcase.strip
|
118
|
-
words = line.split
|
119
|
-
case words.first
|
120
|
-
when 'frames:'
|
121
|
-
if words[1] =~ /[^0-9]/
|
122
|
-
raise ArgumentError, "Only one positive numeric integer value expected at this time (not #{words[1]})"
|
123
|
-
end
|
124
|
-
frame_count = words[1].to_i
|
125
|
-
when 'frame'
|
126
|
-
case words[1]
|
127
|
-
when 'time:'
|
128
|
-
if words[2] =~ /[^0-9\.]/
|
129
|
-
raise ArgumentError, "Only one positive numeric decimal value expected at this time (not #{words[2]})"
|
130
|
-
end
|
131
|
-
bvh.frame_time = words[2].to_f
|
132
|
-
else
|
133
|
-
raise ArgumentError, "Motion data not understood: #{line}"
|
134
|
-
end
|
135
|
-
when nil, '' # blank line, do nothing
|
136
|
-
else
|
137
|
-
channels = words.collect do |w|
|
138
|
-
raise ArgumentError, "Only numeric values are expected at this time (not #{w})" if w =~ /[^0-9\.\-]/
|
139
|
-
w.to_f
|
140
|
-
end
|
141
|
-
add_frame(channels)
|
142
|
-
end
|
143
|
-
end
|
144
|
-
unless frame_count.nil? or frame_count == bvh.frame_count
|
145
|
-
raise ArgumentError, "Expected %s frames, found %s" % [frame_count, bvh.frame_count]
|
146
|
-
end
|
147
|
-
unless frame_count == 0
|
148
|
-
raise ArgumentError, "Frame time is 0; this would result in infinite FPS!" if bvh.frame_time == 0
|
149
|
-
raise ArgumentError, "Frame time is #{bvh.frame_time}! Should be a positive number." if bvh.frame_time < 0
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def add_frame(channels)
|
154
|
-
frame = Bvh::Motion::Frame.new
|
155
|
-
frame.channel_data.concat channel_data(channels, bvh.root)
|
156
|
-
bvh.frames << frame
|
157
|
-
raise ArgumentError, "Not enough channels: Need #{-channels.length} more" if channels.length < 0
|
158
|
-
raise ArgumentError, "Too many channels: #{channels.length} unaccounted for" if channels.length > 0
|
159
|
-
end
|
160
|
-
|
161
|
-
# this is now a binding.
|
162
|
-
# def channel_data(channels, bone)
|
163
|
-
# return [] unless bone.respond_to? :channels
|
164
|
-
# data = Bvh::Motion::ChannelData.new(bone)
|
165
|
-
# bone.channels.each do |channel|
|
166
|
-
# data[channel] = channels.shift
|
167
|
-
# end
|
168
|
-
# r = [ data ]
|
169
|
-
# if bone.respond_to? :joints
|
170
|
-
# bone.joints.each do |child|
|
171
|
-
# r.concat channel_data(channels, child)
|
172
|
-
# end
|
173
|
-
# end
|
174
|
-
# r
|
175
|
-
# end
|
176
|
-
end
|
177
|
-
end
|
1
|
+
class Bvh
|
2
|
+
class Parser
|
3
|
+
attr_reader :filename
|
4
|
+
attr_reader :source
|
5
|
+
|
6
|
+
def initialize(file)
|
7
|
+
@filename = file
|
8
|
+
@source = File.read(file)
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse(bvh)
|
12
|
+
@bvh = bvh
|
13
|
+
|
14
|
+
# a little touch-up...
|
15
|
+
s = source
|
16
|
+
# it's tempting to just downcase the whole string, but that'd change the case of object names, which might be
|
17
|
+
# bad for the end user. So we'll swap out specific keywords instead.
|
18
|
+
s.gsub!(/^((\s*)(root|offset|channels|joint|end site|hierarchy)(([^\n\{]*)(\n|\{)))/mi) do
|
19
|
+
match = $~
|
20
|
+
"#{match[2]}#{match[3].downcase.gsub(/\s/, '_')} \"#{match[5].strip}\"#{match[6]}"
|
21
|
+
end
|
22
|
+
# make { . . . } into proper Ruby blocks
|
23
|
+
s.gsub!(/[\n\s]*\{/m, ' do').gsub!(/[\n\s]*\}/m, "\nend")
|
24
|
+
|
25
|
+
# Finally, handle the MOTION segment, which can be treated as a single method call.
|
26
|
+
s.gsub!(/^((\s*)(motion)(.*))/mi) do
|
27
|
+
"#{$~[2]}#{$~[3].downcase} <<-EOF\n#{$~[4].strip}\nEOF\n"
|
28
|
+
end
|
29
|
+
|
30
|
+
eval(s, binding, @filename, 1)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
attr_reader :bvh, :mode, :current_node
|
35
|
+
|
36
|
+
def bone(type, name, &block)
|
37
|
+
raise ArgumentError, "#{type} #{name} is unexpected at this time" unless mode == :hierarchy
|
38
|
+
bone = type.new(name)
|
39
|
+
|
40
|
+
if current_node.nil?
|
41
|
+
bvh.create_skeleton!.root = bone
|
42
|
+
else
|
43
|
+
current_node.add_joint!(bone)
|
44
|
+
end
|
45
|
+
|
46
|
+
@current_node, bone = bone, @current_node
|
47
|
+
instance_eval(&block)
|
48
|
+
@current_node = bone
|
49
|
+
end
|
50
|
+
|
51
|
+
# Ex:
|
52
|
+
# root "joint-pelvis" do
|
53
|
+
# offset "0.00 0.00 0.00"
|
54
|
+
# channels "6 Xposition Ypoisition Zposition Zrotation Xrotation Yrotation"
|
55
|
+
# joint "joint-spine3" do
|
56
|
+
# . . .
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
def root(name, &block)
|
60
|
+
@current_node = nil
|
61
|
+
bone Bvh::Skeleton::Bone, name, &block
|
62
|
+
end
|
63
|
+
|
64
|
+
# Ex:
|
65
|
+
# root "joint-pelvis" do
|
66
|
+
# . . .
|
67
|
+
# joint "joint-spine3" do
|
68
|
+
# offset "0.00 -10.65 0.00"
|
69
|
+
# channels "3 Zrotation Xrotation Yrotation"
|
70
|
+
# end_site do
|
71
|
+
# . . .
|
72
|
+
# end
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
def joint(name, &block)
|
76
|
+
bone Bvh::Skeleton::Bone, name, &block
|
77
|
+
end
|
78
|
+
|
79
|
+
# Ex:
|
80
|
+
# root "joint-pelvis" do
|
81
|
+
# . . .
|
82
|
+
# joint "joint-spine3" do
|
83
|
+
# . . .
|
84
|
+
# end_site do
|
85
|
+
# offset "0.00 -7.00 0.00"
|
86
|
+
# end
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
def end_site(*unused, &block)
|
90
|
+
bone Bvh::Skeleton::Bone, nil, &block
|
91
|
+
end
|
92
|
+
|
93
|
+
def hierarchy(*unused)
|
94
|
+
@mode = :hierarchy
|
95
|
+
end
|
96
|
+
|
97
|
+
def offset(val)
|
98
|
+
raise ArgumentError, "OFFSET is unexpected at this time" unless current_node
|
99
|
+
raise ArgumentError, "Already have OFFSET data for this node" unless current_node.offset.length == 0
|
100
|
+
current_node.offset.concat val.split.collect { |i| i.to_f }
|
101
|
+
end
|
102
|
+
|
103
|
+
def channels(val)
|
104
|
+
raise ArgumentError, "CHANNELS is unexpected at this time" unless current_node
|
105
|
+
raise ArgumentError, "Already have CHANNELS data for this node" unless current_node.channels.length == 0
|
106
|
+
vals = val.split
|
107
|
+
count = vals.shift.to_i # how many?
|
108
|
+
raise ArgumentError, "Expected #{count} channels, found #{vals.length}" if vals.length != count
|
109
|
+
current_node.channels.concat vals
|
110
|
+
end
|
111
|
+
|
112
|
+
def motion(motion_data)
|
113
|
+
raise ArgumentError, "Motion data is unexpected at this time" unless mode == :hierarchy
|
114
|
+
@mode = :motion
|
115
|
+
frame_count = nil
|
116
|
+
motion_data.each_line do |line|
|
117
|
+
line = line.downcase.strip
|
118
|
+
words = line.split
|
119
|
+
case words.first
|
120
|
+
when 'frames:'
|
121
|
+
if words[1] =~ /[^0-9]/
|
122
|
+
raise ArgumentError, "Only one positive numeric integer value expected at this time (not #{words[1]})"
|
123
|
+
end
|
124
|
+
frame_count = words[1].to_i
|
125
|
+
when 'frame'
|
126
|
+
case words[1]
|
127
|
+
when 'time:'
|
128
|
+
if words[2] =~ /[^0-9\.]/
|
129
|
+
raise ArgumentError, "Only one positive numeric decimal value expected at this time (not #{words[2]})"
|
130
|
+
end
|
131
|
+
bvh.frame_time = words[2].to_f
|
132
|
+
else
|
133
|
+
raise ArgumentError, "Motion data not understood: #{line}"
|
134
|
+
end
|
135
|
+
when nil, '' # blank line, do nothing
|
136
|
+
else
|
137
|
+
channels = words.collect do |w|
|
138
|
+
raise ArgumentError, "Only numeric values are expected at this time (not #{w})" if w =~ /[^0-9\.\-]/
|
139
|
+
w.to_f
|
140
|
+
end
|
141
|
+
add_frame(channels)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
unless frame_count.nil? or frame_count == bvh.frame_count
|
145
|
+
raise ArgumentError, "Expected %s frames, found %s" % [frame_count, bvh.frame_count]
|
146
|
+
end
|
147
|
+
unless frame_count == 0
|
148
|
+
raise ArgumentError, "Frame time is 0; this would result in infinite FPS!" if bvh.frame_time == 0
|
149
|
+
raise ArgumentError, "Frame time is #{bvh.frame_time}! Should be a positive number." if bvh.frame_time < 0
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def add_frame(channels)
|
154
|
+
frame = Bvh::Motion::Frame.new
|
155
|
+
frame.channel_data.concat channel_data(channels, bvh.root)
|
156
|
+
bvh.frames << frame
|
157
|
+
raise ArgumentError, "Not enough channels: Need #{-channels.length} more" if channels.length < 0
|
158
|
+
raise ArgumentError, "Too many channels: #{channels.length} unaccounted for" if channels.length > 0
|
159
|
+
end
|
160
|
+
|
161
|
+
# this is now a binding.
|
162
|
+
# def channel_data(channels, bone)
|
163
|
+
# return [] unless bone.respond_to? :channels
|
164
|
+
# data = Bvh::Motion::ChannelData.new(bone)
|
165
|
+
# bone.channels.each do |channel|
|
166
|
+
# data[channel] = channels.shift
|
167
|
+
# end
|
168
|
+
# r = [ data ]
|
169
|
+
# if bone.respond_to? :joints
|
170
|
+
# bone.joints.each do |child|
|
171
|
+
# r.concat channel_data(channels, child)
|
172
|
+
# end
|
173
|
+
# end
|
174
|
+
# r
|
175
|
+
# end
|
176
|
+
end
|
177
|
+
end
|