bvh 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|