bvh 1.0.0

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.
@@ -0,0 +1,10 @@
1
+ #ifndef BVH_EXT_H
2
+ #define BVH_EXT_H
3
+
4
+ #include "ruby.h"
5
+
6
+ extern void bvh_init_parser();
7
+
8
+ extern VALUE rb_cBvh;
9
+
10
+ #endif//BVH_EXT_H
@@ -0,0 +1,63 @@
1
+ #include "bvh.h"
2
+
3
+ static VALUE rb_fParseChannelData(VALUE self, VALUE channels, VALUE bone);
4
+
5
+ static VALUE rb_cBvhParser = Qnil;
6
+ static VALUE rb_cMotion = Qnil;
7
+ static VALUE rb_cChannelData = Qnil;
8
+
9
+ void bvh_init_parser()
10
+ {
11
+ rb_cBvhParser = rb_define_class_under(rb_cBvh, "Parser", rb_cObject);
12
+ rb_cMotion = rb_define_class_under(rb_cBvh, "Motion", rb_cObject);
13
+ rb_cChannelData = rb_define_class_under(rb_cMotion, "ChannelData", rb_cHash);
14
+
15
+ rb_define_private_method(rb_cBvhParser, "channel_data", rb_fParseChannelData, 2);
16
+ }
17
+
18
+ static void debug(const char *text)
19
+ {
20
+ rb_funcall(rb_cObject, rb_intern("puts"), rb_str_new2(text));
21
+ }
22
+
23
+ static VALUE rb_fParseChannelData(VALUE self, VALUE channels, VALUE bone)
24
+ {
25
+ char buf[256];
26
+
27
+ VALUE data;
28
+ struct RArray *rchannels, *joints;
29
+ long i;
30
+ VALUE channel, joint;
31
+ VALUE r = rb_ary_new();
32
+
33
+ // return [] unless bone.respond_to? :channels
34
+ if (!rb_respond_to(bone, rb_intern("channels")))
35
+ return r;
36
+
37
+ // data = Bvh::Motion::ChannelData.new(bone)
38
+ data = rb_funcall(rb_cChannelData, rb_intern("new"), 1, bone);
39
+
40
+ // bone.channels.each { |channel| data[channel] = channels.shift }
41
+ rchannels = RARRAY(rb_funcall(bone, rb_intern("channels"), 0));
42
+ for (i = 0; i < rchannels->len; i++)
43
+ {
44
+ channel = *(rchannels->ptr+i);
45
+ //... data[channel] = channels.shift ...
46
+ rb_hash_aset(data, channel, rb_funcall(channels, rb_intern("shift"), 0));
47
+ }
48
+
49
+ // r = [data]
50
+ rb_ary_push(r, data);
51
+
52
+ // bone.joints.each { |joint| r.concat channel_data(channels, joint) }
53
+ joints = RARRAY(rb_funcall(bone, rb_intern("joints"), 0));
54
+ for (i = 0; i < joints->len; i++)
55
+ {
56
+ joint = *(joints->ptr+i);
57
+ // ... r.concat(channel_data(channels, joint)) ...
58
+ rb_funcall(r, rb_intern("concat"), 1, rb_fParseChannelData(self, channels, joint));
59
+ }
60
+
61
+ // r
62
+ return r;
63
+ }
@@ -0,0 +1,4 @@
1
+ require 'mkmf'
2
+
3
+ dir_config('bvh')
4
+ create_makefile('bvh')
@@ -0,0 +1,74 @@
1
+ require 'bvh.so'
2
+ require 'bvh/matrix'
3
+ require 'bvh/motion'
4
+ require 'bvh/motion/channel_data'
5
+ require 'bvh/motion/frame'
6
+ require 'bvh/skeleton'
7
+ require 'bvh/parser'
8
+ require 'bvh/exporter'
9
+
10
+
11
+ class Bvh
12
+ # The array of skeletons associated with this BVH.
13
+ attr_reader :skeletons
14
+
15
+ # The motion capture data associated with this BVH.
16
+ attr_reader :motion
17
+
18
+ def initialize
19
+ @motion = Bvh::Motion.new
20
+ @skeletons = []
21
+ end
22
+
23
+ # Assigns the root bone in #skeleton
24
+ def root=(a); skeleton.root = a; end
25
+
26
+ # Returns the root bone in #skeleton
27
+ def root; skeleton.root; end
28
+
29
+ # Returns the frame data associated with this BVH file.
30
+ def frames; motion.frames; end
31
+
32
+ # Returns the last frame associated with this animation.
33
+ def last_frame; frames.last; end
34
+
35
+ # Returns the amount of time, in seconds, each frame occupies. Most BVH files have this set to
36
+ # 0.333333, which is equal to 30 frames per second.
37
+ def frame_time; motion.frame_time; end
38
+
39
+ # Sets the amount of time, in seconds, each frame occupies. Most BVH files have this set to
40
+ # 0.333333, which is equal to 30 frames per second.
41
+ def frame_time=(a); motion.frame_time = a; end
42
+
43
+ # Returns the frames per second calculated by (1 / frame_time)
44
+ def frames_per_second; motion.frames_per_second; end
45
+
46
+ # Assigns the frame_time by calculating it from (1 / frames_per_second)
47
+ def frames_per_second=(a); motion.frames_per_second = a; end
48
+
49
+ # Returns the number of frames in the motion capture data.
50
+ def frame_count; motion.frame_count; end
51
+
52
+ # Returns the first (and usually the only) skeleton in the #skeletons array.
53
+ def skeleton; @skeletons.first; end
54
+
55
+ # Creates a new skeleton, adds it to the #skeletons array, and returns it.
56
+ def create_skeleton!; (@skeletons << Bvh::Skeleton.new).last; end
57
+
58
+ class << self
59
+ # Instantiates a new BVH and imports its data from the specified file
60
+ def import(file)
61
+ bvh = self.new
62
+ parser = Bvh::Parser.new(file)
63
+ parser.parse(bvh)
64
+ bvh
65
+ end
66
+ end
67
+
68
+ # Exports this BVH into the specified file.
69
+ def export(file)
70
+ exporter = Bvh::Exporter.new(file)
71
+ exporter.export(self)
72
+ self
73
+ end
74
+ end
@@ -0,0 +1,86 @@
1
+ class Bvh
2
+ class Exporter
3
+ attr_reader :filename
4
+ attr_reader :source
5
+
6
+ def initialize(file)
7
+ @filename = file
8
+ end
9
+
10
+ def export(bvh)
11
+ #raise "File exists" if File.exist?(filename)
12
+ unless bvh.frame_count == 0
13
+ raise ArgumentError, "Frame time is 0; this would result in infinite FPS!" if bvh.frame_time == 0
14
+ raise ArgumentError, "Frame time is #{bvh.frame_time}! Should be a positive number." if bvh.frame_time < 0
15
+ end
16
+ @bvh = bvh
17
+ File.open(filename, "w+") do |file|
18
+ @file = file
19
+ file.puts "HIERARCHY"
20
+ bvh.skeletons.each { |skel| export_skeleton(skel) }
21
+ export_motion(bvh.motion)
22
+ file.puts # end with a blank line
23
+ end
24
+ end
25
+
26
+ private
27
+ attr_reader :bvh, :file
28
+
29
+ # export HIERARCHY data
30
+ def export_skeleton(skele)
31
+ export_root skele.root
32
+ end
33
+
34
+ # export ROOT node and its joints
35
+ def export_root(root)
36
+ file.puts "ROOT #{root.name}"
37
+ file.puts "{"
38
+ export_bone_data(root)
39
+ file.puts "}"
40
+ end
41
+
42
+ # export JOINT node, or End Site if joint has no joints
43
+ # level defines number of preceding tab stops
44
+ def export_joint(bone, level = 1)
45
+ tabs = "\t"*level
46
+ header = if bone.end_site? then "End Site"
47
+ else "JOINT #{bone.name}"
48
+ end
49
+ file.puts "#{tabs}#{header}"
50
+ file.puts "#{tabs}{"
51
+ export_bone_data(bone, level)
52
+ file.puts "#{tabs}}"
53
+ end
54
+
55
+ # exports bone data, regardless of type, including its joints
56
+ # level defines number of preceding tab stops
57
+ def export_bone_data(bone, level = 0)
58
+ level += 1 # we're within a node, so it's one more tab than expected
59
+ tabs = "\t"*level
60
+ file.puts "#{tabs}OFFSET\t #{bone.offset.collect { |i| "%.6f" % i }.join("\t ")}"
61
+ if bone.channels.length > 0
62
+ chans = bone.channels.join(" ")
63
+ file.puts("#{tabs}CHANNELS #{bone.channels.length} #{chans}")
64
+ end
65
+ bone.joints.each { |joint| export_joint(joint, level) }
66
+ end
67
+
68
+ # export MOTION data
69
+ def export_motion(motion)
70
+ file.puts "MOTION"
71
+ file.puts "Frames: #{motion.frame_count}"
72
+ file.puts "Frame Time: #{motion.frame_time}"
73
+ motion.frames.each do |frame|
74
+ line = ""
75
+ # chan.bone.channels vs. chan.channels to maintain order
76
+ frame.channel_data.each do |channel_data|
77
+ channel_data.bone.channels.each do |i|
78
+ chan = "%.6f" % channel_data[i]
79
+ line = "#{line}#{chan}\t"
80
+ end
81
+ end
82
+ file.puts line.strip
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,19 @@
1
+ require 'matrix'
2
+
3
+ # This adds a few methods to the default Ruby Matrix implementation.
4
+ class Matrix
5
+ # Sets row "i", column "j", to the value "x".
6
+ def []=(i,j,x)
7
+ @rows[i][j] = x
8
+ end
9
+
10
+ # Turns this matrix into an identity matrix, erasing all previous values.
11
+ def load_identity!
12
+ row_size.times do |r|
13
+ column_size.times do |c|
14
+ self[r, c] = (r == c ? 1 : 0)
15
+ end
16
+ end
17
+ self
18
+ end
19
+ end
@@ -0,0 +1,79 @@
1
+ class Bvh
2
+ class Motion
3
+ # The amount of time that passes for each frame. Most BVH files have this set to 0.0333, or 30 frames per second.
4
+ # The following calculations might also be useful to you:
5
+ # frame_time = 1 / frames_per_second # => 0.0333 = 1 / 30
6
+ # frames_per_second = 1 / frame_time # => 30 = 1 / 0.0333
7
+ attr_accessor :frame_time
8
+
9
+ # The array of frames in this animation. You can modify this directly if you wish.
10
+ attr_reader :frames
11
+
12
+ # Returns the frames per second calculated by (1 / frame_time)
13
+ def frames_per_second; 1 / frame_time; end
14
+
15
+ # Assigns the frame_time by calculating it from (1 / frames_per_second)
16
+ def frames_per_second=(a); self.frame_time = 1 / a; end
17
+
18
+ def initialize
19
+ @frames = []
20
+ end
21
+
22
+ # Returns the number of frames in this animation.
23
+ def frame_count
24
+ frames.length
25
+ end
26
+
27
+ # Adds the specified frame or frames to the end of this animation.
28
+ def add_frame(*frames)
29
+ self.frames.concat frames.flatten
30
+ end
31
+
32
+ # Creates a single frame that is an exact copy of the last frame in this animation.
33
+ def create_frame
34
+ frames.last.copy
35
+ end
36
+
37
+ # Creates N frames via #create_frame.
38
+ def create_frames(n)
39
+ r = []
40
+ n.times { r << create_frame }
41
+ r
42
+ end
43
+
44
+ # Creates enough frames to fill the specified number of seconds, adds them to the animation, and returns self.
45
+ #
46
+ # target_frame is the state at which the animation should be in upon reaching the final frame of the alotted time.
47
+ # If it contains data that is different from the last frame in this animation, then the result will be an
48
+ # interpolation between the two over the course of the alotted time.
49
+ #
50
+ # You can use this method to specify key points in the animation, and produce a fluid result.
51
+ def add_time(seconds, target_frame = frames.last)
52
+ raise "Can't calculate frames from seconds: frame_time is zero!" if frame_time == 0
53
+ num_frames = (seconds / frame_time).to_i
54
+ return self if num_frames == 0
55
+
56
+ # buf holds the amount to change each frame
57
+ buf = (frames.last - target_frame) / num_frames.to_f
58
+
59
+ num_frames.times do |i|
60
+ if i == (num_frames-1) then add_frame(target_frame.copy) # last iteration, last frame... we can cheat.
61
+ else add_frame(create_frame + buf) # create_frame clones the last frame, and buf moves it closer to target
62
+ end
63
+ end
64
+
65
+ self
66
+ end
67
+
68
+ # Chops the specified amount of time off of the end of this animation and then returns self.
69
+ def truncate_time(seconds)
70
+ raise "Can't calculate frames from seconds: frame_time is zero!" if frame_time == 0
71
+ num_frames = (seconds / frame_time).to_i
72
+ return self if num_frames == 0
73
+ frames[-1..-num_frames].each { |i| frames.delete i }
74
+ self
75
+ end
76
+
77
+ alias add_frames add_frame
78
+ end
79
+ end
@@ -0,0 +1,147 @@
1
+ class Bvh
2
+ class Motion
3
+ class ChannelData < Hash
4
+ # The bone that is related to this channel data.
5
+ attr_reader :bone
6
+
7
+ def initialize(bone, *args, &block)
8
+ @bone = bone
9
+ super(*args, &block)
10
+ end
11
+
12
+ # call-seq:
13
+ # channel_data + channel_data => new_channel_data
14
+ # channel_data - channel_data => new_channel_data
15
+ # channel_data / channel_data => new_channel_data
16
+ # channel_data * channel_data => new_channel_data
17
+ # channel_data + number => new_channel_data
18
+ # channel_data - number => new_channel_data
19
+ # channel_data / number => new_channel_data
20
+ # channel_data * number => new_channel_data
21
+ #
22
+ # Performs arithmetic on this frame with the target. The second operand may be either a number or another
23
+ # ChannelData. If the target is a number, then that number is added to, subtracted from, multiplied with, or
24
+ # divided against each channel of this ChannelData.
25
+ #
26
+ # If the target is another ChannelData, the arithmetic looks something like this:
27
+ # return_value['Xposition'] = channel_data_one['Xposition'] * channel_data_two['Xposition']
28
+ # return_value['Yposition'] = channel_data_one['Yposition'] * channel_data_two['Yposition']
29
+ # return_value['Zposition'] = channel_data_one['Zposition'] * channel_data_two['Zposition']
30
+ # . . .
31
+ #
32
+ # Both objects must contain the same number of channels, and must also reference the same bone.
33
+ #
34
+ # Returns a new instance of ChannelData containing the result.
35
+ #
36
+ def arithmetic_proc(target)
37
+ # Fooled you again! I metaprogrammed it to save some typing!
38
+ end
39
+ undef arithmetic_proc
40
+
41
+ [:+, :-, :/, :*].each do |operator|
42
+ define_method operator do |target|
43
+ ret = ChannelData.new(bone)
44
+ if target.kind_of?(ChannelData)
45
+ target.each { |key, value| ret[key] = 0.send(operator, value) }
46
+ self.each do |key, value|
47
+ ret[key] = value.send(operator, 0)
48
+ ret[key] = value.send(operator, target[key]) if target.key?(key)
49
+ end
50
+ else
51
+ self.each { |key, value| ret[key] = value.send(operator, target) }
52
+ end
53
+ ret
54
+ end
55
+ end
56
+
57
+ # Sets the specified channel to a value of theta, and then returns self.
58
+ def set_channel(channel, theta)
59
+ channel = channel.to_s if channel.kind_of? Symbol
60
+ raise "Channel not found: #{channel} (expected one of #{self.keys.inspect})" unless self.keys.include? channel
61
+ self[channel] = theta
62
+ end
63
+
64
+ # Retrieves and returns the specified channel datum, raising an error if its key is not found.
65
+ # If the channel key is a symbol, it is converted to a string before the lookup is performed.
66
+ def get_channel(channel)
67
+ channel = channel.to_s if channel.kind_of?(Symbol)
68
+ raise "Channel not found: #{channel} (expected one of #{self.keys.inspect})" unless self.keys.include?(channel)
69
+ self[channel]
70
+ end
71
+
72
+ # Modifies the specified channel datum, resulting in a rotation around the specified channel.
73
+ def rotate!(channel, theta)
74
+ theta = get_channel(channel)+theta
75
+ # this doesn't really have an effect, but may help keep the file from getting junked.
76
+ if theta >= 360 then theta %= 360
77
+ elsif theta <= -360 then theta %= -360
78
+ end
79
+ set_channel(channel, theta)
80
+ end
81
+
82
+ # Adds x, y and z to the X, Y and Z position channels, resulting in a "movement" or translation.
83
+ def translate!(x, y, z)
84
+ set_channel('Xposition', get_channel('Xposition')+x)
85
+ set_channel('Yposition', get_channel('Yposition')+y)
86
+ set_channel('Zposition', get_channel('Zposition')+z)
87
+ end
88
+
89
+ # Returns the transform matrix for this bone's channel data. See also
90
+ # Frame#relative_transform_matrix and Frame#absolute_transform_matrix.
91
+ #
92
+ # The resultant matrix needs to be multiplied against the parent bone's transform matrix in order
93
+ # to be accurate to worldspace. Otherwise it's only accurate in local space. If you're not sure what
94
+ # that means, then use Frame#absolute_transform_matrix instead.
95
+ def relative_transform_matrix()
96
+ # theta is retrieved from the set of numbers loaded from the BVH file, or is supplied directly
97
+ #
98
+ # R is the matrix calculated for a rotation of theta degrees around the vector V
99
+ # This is performed on right, view and up vectors, multiplying the three matrices together
100
+ # to construct the rotation matrix for this bone. Since the calculations are done in world
101
+ # space, the right, up and view vectors are [1,0,0]; [0,1,0]; [0,0,1], respectively. No reason
102
+ # we couldn't attach a Camera object to this bone and figure it out that way, but it'd actually
103
+ # be more work to create and maintain the camera than to just calculate the matrix. Obviously,
104
+ # the resultant matrix needs to be multiplied against the parent bone's rotation matrix in order
105
+ # to be accurate to worldspace. Otherwise it's only accurate in local space.
106
+ #
107
+ # R = [tx^2 + c, txy - sz, txz + sy, 0]
108
+ # [txy + sz, ty^2 + c, tyz - sx, 0]
109
+ # [txz - sy, tyz + sx, tz^2 + c, 0]
110
+ # [ 0, 0, 0, 1]
111
+ #
112
+ # where c = cos theta,
113
+ # s = sin theta,
114
+ # t = 1 - c
115
+ # [x,y,z] = a unit vector on the axis of rotation
116
+
117
+ # bone.channels.each so that we don't lose order of operation
118
+ r = Matrix.identity(4)
119
+ bone.channels.each do |chan|
120
+ v = nil
121
+ case chan
122
+ when 'Xrotation' then v = [1,0,0] # right vector
123
+ when 'Yrotation' then v = [0,1,0] # up vector
124
+ when 'Zrotation' then v = [0,0,1] # view vector
125
+ else next # ignore nonrotational values. To my knowledge, this includes only position values.
126
+ end
127
+ theta = self[chan]
128
+ x, y, z = v
129
+ c, s, t = Math.cos(theta), Math.sin(theta), 1 - Math.cos(theta)
130
+ mat = Matrix.identity(4)
131
+ mat[0,0], mat[0,1], mat[0,2] = t*(x**2) + c, t*x*y - s*z, t*x*z + s*y
132
+ mat[1,0], mat[1,1], mat[1,2] = t*x*y + s*z, t*(y**2) + c, t*y*z - s*x
133
+ mat[2,0], mat[2,1], mat[2,2] = t*x*z - s*y, t*y*z + s*x, t*(z**2) + c
134
+ r *= mat
135
+ end
136
+
137
+ # Finally, the last row is simply set to the translation and/or bone offset.
138
+ r[0,3] = bone.offset[0] + (self.key?("Xposition") ? self['Xposition'] : 0)
139
+ r[1,3] = bone.offset[1] + (self.key?("Yposition") ? self['Yposition'] : 0)
140
+ r[2,3] = bone.offset[2] + (self.key?("Zposition") ? self['Zposition'] : 0)
141
+ r
142
+ end
143
+
144
+ alias local_transform_matrix relative_transform_matrix
145
+ end
146
+ end
147
+ end