flame_channel_parser 1.1.1 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,9 +10,7 @@ lib/parser_2012.rb
10
10
  lib/segments.rb
11
11
  plots.numbers
12
12
  plots_2012.numbers
13
- test/baked.csv
14
13
  test/channel_with_constants.dat
15
- test/curve.csv
16
14
  test/sample_channel.dat
17
15
  test/snaps/FLEM_BrokenTangents.action
18
16
  test/snaps/FLEM_advanced_curve.png
@@ -4,15 +4,18 @@
4
4
 
5
5
  == DESCRIPTION:
6
6
 
7
- Includes a small library for parsing and baking anmation curves made on Discrodesk Floke/Inflinto, also known as flame.
7
+ Includes a small library for extracting, parsing and baking animation curves made on Discrodesk Floke/Inflinto, also known as flame.
8
+ Thanks to Marijn Eken, Philippe Soeiro and Andre Gagnon for their support and advice.
8
9
 
9
10
  == FEATURES/PROBLEMS:
10
11
 
11
- * Only constant extrapolation for now, no looping or pingpong or linear
12
+ * No pingpong or loop extrapolation for now
12
13
  * Expressions on channels won't be evaluated (obviously!)
13
14
 
14
15
  == SYNOPSIS:
15
16
 
17
+ Currently there is no application that you can use directly, the parser is now a library that you can use in your own scripts
18
+
16
19
  require "flame_channel_parser"
17
20
  channels = File.open("TW_Setup.timewarp") do | f |
18
21
  FlameChannelParser.parse(f)
@@ -33,11 +36,11 @@ Includes a small library for parsing and baking anmation curves made on Discrode
33
36
 
34
37
  == REQUIREMENTS:
35
38
 
36
- * FIX (list of requirements)
39
+ * Ruby 1.8.6 and above
37
40
 
38
41
  == INSTALL:
39
42
 
40
- * FIX (sudo gem install, anything else)
43
+ * gem install flame_channel_parser
41
44
 
42
45
  == LICENSE:
43
46
 
@@ -1,10 +1,17 @@
1
1
  require "delegate"
2
2
 
3
3
  module FlameChannelParser
4
- VERSION = '1.1.1'
4
+ VERSION = '1.1.2'
5
5
 
6
6
  # Parse a Flame setup into an array of ChannelBlock objects
7
7
  def self.parse(io)
8
+ parser_class = detect_parser_class_from(io)
9
+ parser_class.new.parse(io)
10
+ end
11
+
12
+ private
13
+
14
+ def self.detect_parser_class_from(io)
8
15
  # Scan the IO
9
16
  parser_class = Parser2011
10
17
  until io.eof?
@@ -15,10 +22,12 @@ module FlameChannelParser
15
22
  end
16
23
  end
17
24
  io.rewind
18
- parser_class.new.parse(io)
25
+
26
+ return parser_class
19
27
  end
20
28
  end
21
29
 
22
- require File.dirname(__FILE__) + "/parser_2011"
23
- require File.dirname(__FILE__) + "/parser_2012"
24
- require File.dirname(__FILE__) + "/interpolator"
30
+ require File.expand_path(File.dirname(__FILE__)) + "/parser_2011"
31
+ require File.expand_path(File.dirname(__FILE__)) + "/parser_2012"
32
+ require File.expand_path(File.dirname(__FILE__)) + "/segments"
33
+ require File.expand_path(File.dirname(__FILE__)) + "/interpolator"
@@ -6,44 +6,42 @@ require File.expand_path(File.dirname(__FILE__)) + "/segments"
6
6
  #
7
7
  # i = Interpolator.new(parsed_channel)
8
8
  # i.value_at(245.5) # => will interpolate and return the value
9
- #
10
9
  class FlameChannelParser::Interpolator
11
10
  include FlameChannelParser::Segments
12
11
 
13
- attr_reader :segments
14
-
15
12
  NEG_INF = (-1.0/0.0)
16
13
  POS_INF = (1.0/0.0)
17
14
 
18
15
  # The constructor will accept a ChannelBlock object and convert it internally to a number of
19
16
  # segments from which samples can be made
20
17
  def initialize(channel)
18
+ @segments = []
21
19
 
22
20
  # Edge case - channel has no anim at all
23
21
  if (channel.length == 0)
24
- @segments = [ConstantFunction.new(channel.base_value)]
22
+ @segments << [ConstantFunction.new(channel.base_value)]
25
23
  elsif (channel.length == 1)
26
- @segments = [ConstantFunction.new(channel[0].value)]
24
+ @segments << [ConstantFunction.new(channel[0].value)]
27
25
  else
28
- @segments = []
29
26
 
30
- # TODO: extrapolation is set for the whole channel, both begin and end.
31
27
  # First the prepolating segment
32
- @segments << ConstantPrepolate.new(channel[0].frame, channel[0].value)
28
+ @segments << pick_prepolation(channel.extrapolation, channel[0])
33
29
 
34
- # The last key defines extrapolation for the rest of the curve...
30
+ # Then all the intermediate segments, one segment between each pair of keys
35
31
  channel[0..-2].each_with_index do | key, index |
36
32
  @segments << key_pair_to_segment(key, channel[index + 1])
37
33
  end
38
34
 
39
35
  # so we just output it separately
40
- @segments << ConstantExtrapolate.new(@segments[-1].end_frame, channel[-1].value)
36
+ @segments << pick_extrapolation(channel.extrapolation, channel[-1])
41
37
  end
42
38
  end
43
39
 
44
40
  # Sample the value of the animation curve at this frame
45
41
  def sample_at(frame)
46
42
  segment = @segments.find{|s| s.defines?(frame) }
43
+ raise "No segment on this curve that can interpolate the value at #{frame}" unless segment
44
+
47
45
  segment.value_at(frame)
48
46
  end
49
47
 
@@ -65,6 +63,23 @@ class FlameChannelParser::Interpolator
65
63
 
66
64
  private
67
65
 
66
+ def pick_prepolation(extrap_symbol, first_key)
67
+ if extrap_symbol == :linear
68
+ LinearPrepolate.new(first_key.frame, first_key.value, first_key.left_slope)
69
+ else
70
+ ConstantPrepolate.new(first_key.frame, first_key.value)
71
+ end
72
+ end
73
+
74
+ def pick_extrapolation(extrap_symbol, last_key)
75
+ if extrap_symbol == :linear
76
+ LinearExtrapolate.new(last_key.frame, last_key.value, last_key.right_slope)
77
+ else
78
+ ConstantExtrapolate.new(last_key.frame, last_key.value)
79
+ end
80
+ end
81
+
82
+
68
83
  # We need both the preceding and the next key
69
84
  def key_pair_to_segment(key, next_key)
70
85
  case key.interpolation
@@ -75,7 +90,7 @@ class FlameChannelParser::Interpolator
75
90
  key.r_handle_y,
76
91
  next_key.l_handle_x, next_key.l_handle_y)
77
92
  when :natural, :hermite
78
- HermiteSegment.new(key.frame, next_key.frame, key.value, next_key.value, key.right_slope, incoming_slope(next_key))
93
+ HermiteSegment.new(key.frame, next_key.frame, key.value, next_key.value, key.right_slope, next_key.left_slope)
79
94
  when :constant
80
95
  ConstantSegment.new(key.frame, next_key.frame, key.value)
81
96
  else # Linear and safe
@@ -83,10 +98,5 @@ class FlameChannelParser::Interpolator
83
98
  end
84
99
  end
85
100
 
86
- # Flame uses the right slope for both left and right unless the BrokenSlope tag is set
87
- def incoming_slope(key)
88
- key.broken? ? key.left_slope : key.right_slope
89
- end
90
-
91
101
  end
92
102
 
@@ -1,16 +1,26 @@
1
1
  require "delegate"
2
2
 
3
+ # Basic parser used for setups from versions up to 2011
3
4
  class FlameChannelParser::Parser2011
4
5
 
6
+ # Represents a keyframe
5
7
  class Key
6
8
  attr_accessor :frame, :value, :interpolation, :extrapolation, :left_slope, :right_slope, :break_slope
7
9
  alias_method :to_s, :inspect
8
10
 
11
+ # Unless the key is broken? we should just use the right slope
12
+ def left_slope
13
+ return right_slope unless broken?
14
+ @left_slope
15
+ end
16
+
17
+ # Tells whether the slope of this keyframe is broken (not smooth)
9
18
  def broken?
10
19
  break_slope
11
20
  end
12
21
  end
13
22
 
23
+ # Defines a number of regular expression matchers applied to the file as it is being parsed
14
24
  def matchers
15
25
  [
16
26
  [:frame, :to_i, /Frame ([\-\d\.]+)/],
@@ -27,9 +37,13 @@ class FlameChannelParser::Parser2011
27
37
  Key.new
28
38
  end
29
39
 
40
+ # Represents a channel parsed from the Flame setup. Contains
41
+ # the channel metadata and keyframes
30
42
  class ChannelBlock < DelegateClass(Array)
31
43
  attr_accessor :base_value
32
44
  attr_accessor :name
45
+ attr_accessor :extrapolation
46
+
33
47
  def initialize(io, channel_name, parent_parser)
34
48
  super([])
35
49
 
@@ -38,6 +52,7 @@ class FlameChannelParser::Parser2011
38
52
 
39
53
  base_value_matcher = /Value ([\-\d\.]+)/
40
54
  keyframe_count_matcher = /Size (\d+)/
55
+ extrapolation_matcher = /Extrapolation (\w+)/
41
56
  indent = nil
42
57
 
43
58
  while line = io.gets
@@ -51,6 +66,8 @@ class FlameChannelParser::Parser2011
51
66
  $1.to_i.times { push(extract_key_from(io)) }
52
67
  elsif line =~ base_value_matcher && empty?
53
68
  self.base_value = $1.to_f
69
+ elsif line =~ extrapolation_matcher
70
+ self.extrapolation = symbolize_literal($1)
54
71
  elsif line.strip == end_mark
55
72
  break
56
73
  end
@@ -58,7 +75,12 @@ class FlameChannelParser::Parser2011
58
75
 
59
76
  end
60
77
 
61
-
78
+ # Get an Interpolator from this channel
79
+ def to_interpolator
80
+ FlameChannelParser::Inteprolator.new(self)
81
+ end
82
+
83
+ private
62
84
 
63
85
  def create_key
64
86
  Key.new
@@ -89,7 +111,7 @@ class FlameChannelParser::Parser2011
89
111
  raise "Did not detect any keyframes!"
90
112
  end
91
113
 
92
- LITERALS = %w( linear constant natural hermite)
114
+ LITERALS = %w( linear constant natural hermite cubic bezier)
93
115
 
94
116
  def symbolize_literal(v)
95
117
  LITERALS.include?(v) ? v.to_sym : v
@@ -104,18 +126,16 @@ class FlameChannelParser::Parser2011
104
126
  until io.eof?
105
127
  line = io.gets
106
128
  if line =~ CHANNEL_MATCHER && channel_is_useful?($1)
107
- report_progress("Extracting channel #{$1}")
108
129
  channels << ChannelBlock.new(io, $1, self)
109
130
  end
110
131
  end
111
132
  channels
112
133
  end
113
134
 
135
+ # Override this method to skip some channels, this will speedup
136
+ # your code alot
114
137
  def channel_is_useful?(channel_name)
115
138
  true
116
139
  end
117
140
 
118
- def report_progress(message)
119
- # flunk
120
- end
121
141
  end
@@ -1,6 +1,7 @@
1
1
  require "delegate"
2
2
  require File.dirname(__FILE__) + "/interpolator"
3
3
 
4
+ # This parser is automatically used for 2012 setups
4
5
  class FlameChannelParser::Parser2012 < FlameChannelParser::Parser2011
5
6
 
6
7
  class ModernKey
@@ -19,6 +20,8 @@ class FlameChannelParser::Parser2012 < FlameChannelParser::Parser2011
19
20
 
20
21
  # Compute pre-212 slope which we use for interpolations
21
22
  def left_slope
23
+ return right_slope unless broken?
24
+
22
25
  dy = value - l_handle_y
23
26
  dx = l_handle_x - frame
24
27
  dy / dx * -1
@@ -23,7 +23,6 @@ module FlameChannelParser::Segments
23
23
  def initialize(from_frame, to_frame, value)
24
24
  @start_frame = from_frame
25
25
  @end_frame = to_frame
26
-
27
26
  @v1 = value
28
27
  end
29
28
  end
@@ -65,19 +64,22 @@ module FlameChannelParser::Segments
65
64
  # CC = {P1, P2, T1, T2}
66
65
  p1, p2, t1, t2 = value1, value2, tangent1.to_f * frame_interval, tangent2.to_f * frame_interval
67
66
  @hermite = Vector[p1, p2, t1, t2]
67
+ @basis = HERMATRIX * @hermite
68
68
  end
69
69
 
70
70
  # P[s_] = S[s].h.CC where s is 0..1 float interpolant on T (interval)
71
71
  def value_at(frame)
72
-
72
+ return @hermite[0] if frame == @start_frame
73
+
74
+ # Get the 0 < T < 1 interval we will interpolate on
73
75
  # Q[frame_] = P[ ( frame - 149 ) / (time_to - time_from)]
74
- on_t_interval = (frame - @start_frame).to_f / (@end_frame - @start_frame)
76
+ t = (frame - @start_frame).to_f / (@end_frame - @start_frame)
75
77
 
76
78
  # S[s_] = {s^3, s^2, s^1, s^0}
77
- multipliers_vec = Vector[on_t_interval ** 3, on_t_interval ** 2, on_t_interval ** 1, on_t_interval ** 0]
79
+ multipliers_vec = Vector[t ** 3, t ** 2, t ** 1, t ** 0]
78
80
 
79
81
  # P[s_] = S[s].h.CC --> Kaboom!
80
- interpolated_scalar = dot_product(HERMATRIX * @hermite, multipliers_vec)
82
+ interpolated_scalar = dot_product(@basis, multipliers_vec)
81
83
  end
82
84
 
83
85
  private
@@ -95,17 +97,19 @@ module FlameChannelParser::Segments
95
97
  class BezierSegment < LinearSegment
96
98
  def initialize(x1, x2, y1, y2, t1x, t1y, t2x, t2y)
97
99
  @start_frame, @end_frame = x1, x2
98
-
100
+
99
101
  @a = Point.new(x1, y1, t1x, t1y)
100
102
  @b = Point.new(x2, y2, t2x, t2y)
101
103
  end
102
-
104
+
103
105
  def value_at(frame)
106
+ return @a.y if frame == @start_frame
107
+
104
108
  # Solve T from X. This determines the correlation between X and T.
105
109
  t = approximate_t(frame, @a.x, @a.tanx, @b.tanx, @b.x)
106
110
  vy = bezier(t, @a.y, @a.tany, @b.tany, @b.y)
107
111
  end
108
-
112
+
109
113
  private
110
114
 
111
115
  # t is the T interpolant (0 < T < 1)
@@ -116,68 +120,65 @@ module FlameChannelParser::Segments
116
120
  def bezier(t, a, b, c, d)
117
121
  a + (a*(-3) + b*3)*(t) + (a*3 - b*6 + c*3)*(t**2) + (-a + b*3 - c*3 + d)*(t**3)
118
122
  end
119
-
123
+
120
124
  def clamp(value)
121
125
  return 0.0 if value < 0
122
126
  return 1.0 if value > 1
123
127
  return value
124
128
  end
125
-
126
- # /**
127
- # * Returns the approximated parameter of a parametric curve for the value X
128
- # * @param atX At which value should the parameter be evaluated
129
- # * @param p0x The first interpolation point of a curve segment
130
- # * @param c0x The first control point of a curve segment
131
- # * @param c1x The second control point of a curve segment
132
- # * @param P1_x The second interpolation point of a curve segment
133
- # * @return The parametric argument that is used to retrieve atX using the parametric function representation of this curve
134
- # */
135
-
129
+
130
+
136
131
  APPROXIMATION_EPSILON = 1.0e-09
137
132
  VERYSMALL = 1.0e-20
138
- MAXIMUM_ITERATIONS = 1000
139
-
133
+ MAXIMUM_ITERATIONS = 100
134
+
140
135
  # This is how OPENCOLLADA suggests approximating Bezier animation curves
141
136
  # http://www.collada.org/public_forum/viewtopic.php?f=12&t=1132
137
+ # Returns the approximated parameter of a parametric curve for the value X
138
+ # @param atX At which value should the parameter be evaluated
139
+ # @param p0x The first interpolation point of a curve segment
140
+ # @param c0x The first control point of a curve segment
141
+ # @param c1x The second control point of a curve segment
142
+ # @param P1_x The second interpolation point of a curve segment
143
+ # @return The parametric argument that is used to retrieve atX using the parametric function representation of this curve
142
144
  def approximate_t (atX, p0x, c0x, c1x, p1x )
143
-
144
- return 0.0 if (atX - p0x < VERYSMALL)
145
- return 1.0 if (p1x - atX < VERYSMALL)
146
-
147
- u, v = 0.0, 1.0
148
-
149
- # iteratively apply subdivision to approach value atX
150
- MAXIMUM_ITERATIONS.times do
151
-
152
- # de Casteljau Subdivision.
153
- a = (p0x + c0x) / 2.0
154
- b = (c0x + c1x) / 2.0
155
- c = (c1x + p1x) / 2.0
156
- d = (a + b) / 2.0
157
- e = (b + c) / 2.0
158
- f = (d + e) / 2.0 # this one is on the curve!
159
-
160
- # The curve point is close enough to our wanted atX
161
- if ((f - atX).abs < APPROXIMATION_EPSILON)
162
- return clamp((u + v)*0.5)
163
- end
164
-
165
- # dichotomy
166
- if (f < atX)
167
- p0x = f
168
- c0x = e
169
- c1x = c
170
- u = (u + v) / 2.0
171
- else
172
- c0x = a
173
- c1x = d
174
- p1x = f
175
- v = (u + v) / 2.0
176
- end
177
- end
178
-
179
- return ClampToZeroOne((u + v) / 2)
180
-
145
+
146
+ return 0.0 if (atX - p0x < VERYSMALL)
147
+ return 1.0 if (p1x - atX < VERYSMALL)
148
+
149
+ u, v = 0.0, 1.0
150
+
151
+ # iteratively apply subdivision to approach value atX
152
+ MAXIMUM_ITERATIONS.times do
153
+
154
+ # de Casteljau Subdivision.
155
+ a = (p0x + c0x) / 2.0
156
+ b = (c0x + c1x) / 2.0
157
+ c = (c1x + p1x) / 2.0
158
+ d = (a + b) / 2.0
159
+ e = (b + c) / 2.0
160
+ f = (d + e) / 2.0 # this one is on the curve!
161
+
162
+ # The curve point is close enough to our wanted atX
163
+ if ((f - atX).abs < APPROXIMATION_EPSILON)
164
+ return clamp((u + v)*0.5)
165
+ end
166
+
167
+ # dichotomy
168
+ if (f < atX)
169
+ p0x = f
170
+ c0x = e
171
+ c1x = c
172
+ u = (u + v) / 2.0
173
+ else
174
+ c0x = a
175
+ c1x = d
176
+ p1x = f
177
+ v = (u + v) / 2.0
178
+ end
179
+ end
180
+
181
+ clamp((u + v) / 2.0)
181
182
  end
182
183
  end
183
184
 
@@ -192,11 +193,21 @@ module FlameChannelParser::Segments
192
193
  def value_at(frame)
193
194
  @value
194
195
  end
196
+ end
195
197
 
196
- private
197
- def frame_increment
198
- 23.0
199
- end
198
+ # This segment does prepolation with a linear coefficient
199
+ class LinearPrepolate < LinearSegment
200
+ def initialize(upto_frame, base_value, tangent)
201
+ @value = base_value
202
+ @end_frame = upto_frame
203
+ @start_frame = NEG_INF
204
+ @tangent = tangent.to_f
205
+ end
206
+
207
+ def value_at(frame)
208
+ frame_diff = (frame - @end_frame)
209
+ @value + (@tangent * frame_diff)
210
+ end
200
211
  end
201
212
 
202
213
  # This segment does extrapolation using a constant value
@@ -212,6 +223,19 @@ module FlameChannelParser::Segments
212
223
  end
213
224
  end
214
225
 
226
+ # This segment does extrapolation using the tangent from the preceding keyframe
227
+ class LinearExtrapolate < ConstantExtrapolate
228
+ def initialize(from_frame, base_value, tangent)
229
+ super(from_frame, base_value)
230
+ @tangent = tangent
231
+ end
232
+
233
+ def value_at(frame)
234
+ frame_diff = (frame - @start_frame)
235
+ @base_value + (@tangent * frame_diff)
236
+ end
237
+ end
238
+
215
239
  # This can be used for an anim curve that stays constant all along
216
240
  class ConstantFunction < ConstantSegment
217
241
 
Binary file
@@ -1,4 +1,5 @@
1
1
  require "test/unit"
2
+ require File.dirname(__FILE__) + "/../lib/flame_channel_parser"
2
3
 
3
4
  class TestFlameChannelParser < Test::Unit::TestCase
4
5
  D = 0.0001
@@ -6,6 +6,16 @@ require File.dirname(__FILE__) + "/../lib/flame_channel_parser"
6
6
  class TestInterpolator < Test::Unit::TestCase
7
7
  DELTA = 0.05
8
8
 
9
+ def send_curves_to_clipboard(range, ref_i, sample_i)
10
+ # This is handy for plotting
11
+ begin
12
+ IO.popen("pbcopy", "w") do | buf |
13
+ range.map{|f| buf.puts "%03f\t%03f" % [ref_i.sample_at(f), sample_i.sample_at(f)] }
14
+ end
15
+ rescue Errno::EPIPE # There is no pbcopy on this box, sorry
16
+ end
17
+ end
18
+
9
19
  def assert_same_interpolation(range, ref_channel, sample_channel)
10
20
  ref_i, sample_i = [ref_channel, sample_channel].map{|c| FlameChannelParser::Interpolator.new(c) }
11
21
 
@@ -19,10 +29,7 @@ class TestInterpolator < Test::Unit::TestCase
19
29
  end
20
30
  rescue Test::Unit::AssertionFailedError => e
21
31
  STDERR.puts "Curves were not the same so I will now copy the two curves to the clipboard"
22
- # This is handy for plotting
23
- IO.popen("pbcopy", "w") do | buf |
24
- range.map{|f| buf.puts "%03f\t%03f" % [ref_i.sample_at(f), sample_i.sample_at(f)] }
25
- end
32
+ send_curves_to_clipboard(range, ref_i, sample_i)
26
33
  raise e
27
34
  end
28
35
  end
@@ -32,7 +39,8 @@ class TestInterpolator < Test::Unit::TestCase
32
39
  constants = FlameChannelParser.parse(data).find{|c| c.name == "constants"}
33
40
  interp = FlameChannelParser::Interpolator.new(constants)
34
41
 
35
- vs = [770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 1017.36, 1017.36]
42
+ vs = [770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41,
43
+ 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 770.41, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 858.177, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 939.407, 1017.36, 1017.36]
36
44
 
37
45
  values = (-5..116).map{|f| interp.sample_at(f) }
38
46
  assert_equal vs, values
@@ -84,6 +92,6 @@ class TestInterpolator < Test::Unit::TestCase
84
92
 
85
93
  reference = channels_in_action.find{|c| c.name == "position/x" && c.length > 2 }
86
94
  sampled = channels_in_action.find{|c| c.name == "position/y" && c.length > 2 }
87
- assert_same_interpolation(1..330, reference, sampled)
95
+ assert_same_interpolation(-10..300, reference, sampled)
88
96
  end
89
97
  end
@@ -31,7 +31,6 @@ class TestConstantSegment < Test::Unit::TestCase
31
31
  end
32
32
 
33
33
  class TestBezierSegment < Test::Unit::TestCase
34
- D = 0.001
35
34
 
36
35
  def test_segment
37
36
  seg = BezierSegment.new(
@@ -248,14 +247,23 @@ class TestHermiteSegment < Test::Unit::TestCase
248
247
  end
249
248
 
250
249
  class TestLinearPrepolate < Test::Unit::TestCase
251
- def test_fail
252
- flunk
250
+ def test_segment
251
+ seg = LinearPrepolate.new(123, -4, 2)
252
+ assert seg.defines?(122)
253
+ assert seg.defines?(-99999)
254
+ assert_equal( -2, seg.value_at(124))
255
+ assert_equal( -52, seg.value_at(99))
253
256
  end
254
257
  end
255
258
 
256
259
  class TestLinearExtrapolate < Test::Unit::TestCase
257
- def test_fail
258
- flunk
260
+ def test_segment
261
+ seg = LinearExtrapolate.new(123, -4, 2)
262
+ assert seg.defines?(123)
263
+ assert seg.defines?(9999999999)
264
+ assert !seg.defines?(122)
265
+ assert_equal( -2, seg.value_at(124))
266
+ assert_equal 198, seg.value_at(224)
259
267
  end
260
268
  end
261
269
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flame_channel_parser
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
8
  - 1
9
- - 1
10
- version: 1.1.1
9
+ - 2
10
+ version: 1.1.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Julik Tarkhanov
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-05-17 00:00:00 +02:00
18
+ date: 2011-05-20 00:00:00 +02:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -34,7 +34,9 @@ dependencies:
34
34
  version: 2.9.1
35
35
  type: :development
36
36
  version_requirements: *id001
37
- description: Includes a small library for parsing and baking anmation curves made on Discrodesk Floke/Inflinto, also known as flame.
37
+ description: |-
38
+ Includes a small library for extracting, parsing and baking animation curves made on Discrodesk Floke/Inflinto, also known as flame.
39
+ Thanks to Marijn Eken, Philippe Soeiro and Andre Gagnon for their support and advice.
38
40
  email:
39
41
  - me@julik.nl
40
42
  executables: []
@@ -58,9 +60,7 @@ files:
58
60
  - lib/segments.rb
59
61
  - plots.numbers
60
62
  - plots_2012.numbers
61
- - test/baked.csv
62
63
  - test/channel_with_constants.dat
63
- - test/curve.csv
64
64
  - test/sample_channel.dat
65
65
  - test/snaps/FLEM_BrokenTangents.action
66
66
  - test/snaps/FLEM_advanced_curve.png
@@ -108,7 +108,7 @@ rubyforge_project: flame_channel_parser
108
108
  rubygems_version: 1.4.1
109
109
  signing_key:
110
110
  specification_version: 3
111
- summary: Includes a small library for parsing and baking anmation curves made on Discrodesk Floke/Inflinto, also known as flame.
111
+ summary: Includes a small library for extracting, parsing and baking animation curves made on Discrodesk Floke/Inflinto, also known as flame
112
112
  test_files:
113
113
  - test/test_flame_channel_parser.rb
114
114
  - test/test_interpolator.rb
@@ -1,201 +0,0 @@
1
- x,y
2
- 1,-459.0
3
- 2,-426.528
4
- 3,-394.055
5
- 4,-361.583
6
- 5,-329.111
7
- 6,-296.638
8
- 7,-264.166
9
- 8,-231.694
10
- 9,-199.221
11
- 10,-166.749
12
- 11,-134.277
13
- 12,-101.804
14
- 13,-69.3319
15
- 14,-36.8595
16
- 15,-4.38721
17
- 16,28.0851
18
- 17,60.5575
19
- 18,93.0298
20
- 19,125.502
21
- 20,157.975
22
- 21,190.447
23
- 22,222.919
24
- 23,222.543
25
- 24,221.735
26
- 25,220.506
27
- 26,218.867
28
- 27,216.83
29
- 28,214.406
30
- 29,211.608
31
- 30,208.445
32
- 31,204.931
33
- 32,201.075
34
- 33,196.89
35
- 34,192.387
36
- 35,187.578
37
- 36,182.473
38
- 37,177.085
39
- 38,171.425
40
- 39,165.503
41
- 40,159.333
42
- 41,152.924
43
- 42,146.29
44
- 43,139.44
45
- 44,132.386
46
- 45,125.141
47
- 46,117.715
48
- 47,110.119
49
- 48,102.366
50
- 49,94.4661
51
- 50,86.4316
52
- 51,78.2735
53
- 52,70.0034
54
- 53,61.6327
55
- 54,53.1728
56
- 55,44.6353
57
- 56,36.0314
58
- 57,27.3727
59
- 58,18.6705
60
- 59,9.93645
61
- 60,1.18185
62
- 61,-7.58185
63
- 62,-16.3432
64
- 63,-25.0907
65
- 64,-33.813
66
- 65,-42.4985
67
- 66,-51.136
68
- 67,-59.714
69
- 68,-68.2209
70
- 69,-76.6454
71
- 70,-84.9759
72
- 71,-93.2012
73
- 72,-101.31
74
- 73,-109.29
75
- 74,-117.131
76
- 75,-124.82
77
- 76,-132.347
78
- 77,-139.7
79
- 78,-146.868
80
- 79,-153.839
81
- 80,-160.601
82
- 81,-167.144
83
- 82,-173.456
84
- 83,-179.525
85
- 84,-185.34
86
- 85,-190.889
87
- 86,-196.162
88
- 87,-201.146
89
- 88,-205.83
90
- 89,-210.204
91
- 90,-214.254
92
- 91,-217.97
93
- 92,-221.341
94
- 93,-224.355
95
- 94,-227.0
96
- 95,-227.0
97
- 96,-227.0
98
- 97,-227.0
99
- 98,-227.0
100
- 99,-227.0
101
- 100,-227.0
102
- 101,-227.0
103
- 102,-227.0
104
- 103,-227.0
105
- 104,-227.0
106
- 105,-227.0
107
- 106,-227.0
108
- 107,-227.0
109
- 108,-227.0
110
- 109,-227.0
111
- 110,-227.0
112
- 111,-227.0
113
- 112,-227.0
114
- 113,-227.0
115
- 114,-227.0
116
- 115,-227.0
117
- 116,-227.0
118
- 117,1.23907
119
- 118,15.1943
120
- 119,28.8842
121
- 120,42.297
122
- 121,55.421
123
- 122,68.2446
124
- 123,80.7561
125
- 124,92.9438
126
- 125,104.796
127
- 126,116.301
128
- 127,127.447
129
- 128,138.223
130
- 129,148.616
131
- 130,158.616
132
- 131,168.21
133
- 132,177.387
134
- 133,186.135
135
- 134,194.442
136
- 135,202.297
137
- 136,209.689
138
- 137,216.604
139
- 138,223.032
140
- 139,228.961
141
- 140,234.38
142
- 141,239.276
143
- 142,243.638
144
- 143,247.455
145
- 144,250.714
146
- 145,253.404
147
- 146,255.513
148
- 147,257.03
149
- 148,257.942
150
- 149,258.239
151
- 150,257.937
152
- 151,257.074
153
- 152,255.674
154
- 153,253.759
155
- 154,251.352
156
- 155,248.476
157
- 156,245.152
158
- 157,241.405
159
- 158,237.255
160
- 159,232.727
161
- 160,227.842
162
- 161,222.624
163
- 162,217.094
164
- 163,211.276
165
- 164,205.192
166
- 165,198.865
167
- 166,192.317
168
- 167,185.571
169
- 168,178.65
170
- 169,171.576
171
- 170,164.372
172
- 171,157.06
173
- 172,149.664
174
- 173,142.205
175
- 174,134.707
176
- 175,127.192
177
- 176,119.683
178
- 177,112.202
179
- 178,104.772
180
- 179,97.4151
181
- 180,90.1547
182
- 181,83.0132
183
- 182,76.0132
184
- 183,69.1772
185
- 184,62.528
186
- 185,56.0881
187
- 186,49.8802
188
- 187,43.9269
189
- 188,38.2508
190
- 189,32.8746
191
- 190,27.8209
192
- 191,23.1124
193
- 192,18.7715
194
- 193,14.8212
195
- 194,11.2838
196
- 195,8.18198
197
- 196,5.53853
198
- 197,3.37598
199
- 198,1.71692
200
- 199,0.584045
201
- 200,0.0
@@ -1,7 +0,0 @@
1
- frame,value,interpolation,extrapolation,left_slope,right_slope
2
- 1,-459.0,linear,,,
3
- 22,222.919,hermite,,2.49462,-0.156017
4
- 94,-227.0,constant,,-2.33348,-2.45723
5
- 117,1.23907,natural,,14.0841,14.0841
6
- 149,258.239,hermite,,-0.0149286,-0.0149286
7
- 200,0.0,hermite,,,-0.302127