bvh 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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