flame_channel_parser 1.1.1 → 1.1.2

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.
@@ -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