bvh 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,86 +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
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
@@ -1,19 +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
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
@@ -1,79 +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
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
@@ -1,147 +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
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