interpolate 0.2.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -15,9 +15,9 @@ time_frames = {
15
15
  6 => [0, 0, 0]
16
16
  }
17
17
 
18
- path = Interpolation.new(time_frames)
18
+ path = Interpolate::Points.new(time_frames)
19
19
 
20
- # play the actors positions in time increments of 0.25
20
+ # play the actor's positions in time increments of 0.25
21
21
  (0).step(6, 0.25) do |time|
22
22
  position = path.at(time)
23
23
  puts ">> At #{time}s, actor is at:"
@@ -4,16 +4,14 @@ require 'interpolate'
4
4
  # min_value => bucket
5
5
  buckets = {
6
6
  0.000 => 1,
7
- 0.427 => 2,
8
- 1.200 => 3,
9
- 3.420 => 4,
10
- 27.50 => 5,
11
- 45.20 => 6,
12
- 124.4 => 7,
7
+ 0.500 => 2,
8
+ 1.250 => 3,
9
+ 7.725 => 4,
10
+ 28.85 => 5,
11
+ 50.00 => 6,
12
+ 127.5 => 7
13
13
  }
14
14
 
15
- bucketizer = Interpolation.new(buckets)
16
-
17
15
  values = [
18
16
  -20.2,
19
17
  0.234,
@@ -23,6 +21,11 @@ values = [
23
21
  4000
24
22
  ]
25
23
 
24
+ # using Interpolate::Points to place values within discrete intervals
25
+ bucketizer = Interpolate::Points.new(buckets)
26
+ # the blending function will mimic the mathematical floor function
27
+ bucketizer.blend_with {|low, high, balance| low }
28
+
26
29
  values.each do |value|
27
30
  bucket = bucketizer.at(value).floor
28
31
  puts "A value of #{value} falls into bucket #{bucket}"
@@ -1,33 +1,35 @@
1
1
  require 'rubygems'
2
- require 'color'
3
2
  require 'interpolate'
4
-
5
- # we need to implement +interpolate+ for Color::RGB
6
- # in order for Interpolation to work
7
- class Color::RGB
8
- def interpolate(other, balance)
9
- mix_with(other, balance * 100.0)
10
- end
11
- end
3
+ require 'color'
12
4
 
13
5
  # a nice weathermap-style color gradient
14
6
  points = {
15
- 1 => Color::RGB::White,
7
+ 1 => Color::RGB::Cyan,
16
8
  2 => Color::RGB::Lime,
17
9
  # 3 => ? (between Lime and Yellow; Interpolate will figure it out)
18
10
  4 => Color::RGB::Yellow,
19
11
  5 => Color::RGB::Orange,
20
12
  6 => Color::RGB::Red,
21
- 7 => Color::RGB::Magenta
13
+ 7 => Color::RGB::Magenta,
14
+ 8 => Color::RGB::White,
22
15
  }
23
16
 
24
- gradient = Interpolation.new(points)
17
+ # we need to implement a blending function in order for Interpolate::Points to
18
+ # work properly
19
+ #
20
+ # fortunately, Color::RGB includes +mix_with+, which is almost functionally
21
+ # identical to what we need
22
+
23
+ gradient = Interpolate::Points.new(points)
24
+ gradient.blend_with {|color, other, balance|
25
+ color.mix_with(other, balance * 100.0)
26
+ }
25
27
 
26
- # what are the colors of the gradient from 1 to 7
28
+ # what are the colors of the gradient from 1 to 8
27
29
  # in increments of 0.2?
28
30
  (1).step(7, 0.2) do |value|
29
31
  color = gradient.at(value)
30
- puts "A value of #{value} means #{color.html}"
32
+ puts "A value of #{value.round(3)} means #{color.html}"
31
33
  end
32
34
 
33
35
 
@@ -14,7 +14,7 @@ time_frames = {
14
14
  }
15
15
 
16
16
 
17
- paths = Interpolation.new(time_frames)
17
+ paths = Interpolate::Points.new(time_frames)
18
18
 
19
19
  # show the vertex positions in time increments of 0.25
20
20
  (0).step(4, 0.25) do |time|
@@ -1,65 +1,30 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
1
  # -*- encoding: utf-8 -*-
2
+ $LOAD_PATH.unshift File.expand_path('../lib/', __FILE__)
3
+ require 'interpolate/version'
5
4
 
6
- Gem::Specification.new do |s|
7
- s.name = %q{interpolate}
8
- s.version = "0.2.4"
5
+ Gem::Specification.new do |gem|
6
+ gem.required_rubygems_version = '>= 1.3.6'
9
7
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Adam Collins"]
12
- s.date = %q{2011-04-10}
13
- s.description = %q{Description
8
+ # Gem metadata
9
+ gem.name = 'interpolate'
10
+ gem.version = Interpolate::VERSION
11
+ gem.platform = Gem::Platform::RUBY
14
12
 
15
- Library for generic Interpolation objects. Useful for such things as generating
16
- linear motion between points (or arrays of points), multi-channel color
17
- gradients, piecewise functions, or even just placing values within intervals.
18
- }
19
- s.email = %q{adam@m104.us}
20
- s.extra_rdoc_files = [
21
- "LICENSE",
22
- "README.md"
23
- ]
24
- s.files = [
25
- "CHANGELOG.md",
26
- "LICENSE",
27
- "Manifest.txt",
28
- "README.md",
29
- "Rakefile",
30
- "VERSION",
31
- "examples/arrays.rb",
32
- "examples/buckets.rb",
33
- "examples/colors.rb",
34
- "examples/nested.rb",
35
- "interpolate.gemspec",
36
- "lib/interpolate.rb",
37
- "lib/interpolate/add/core.rb",
38
- "lib/interpolate/add/core/array.rb",
39
- "lib/interpolate/add/core/numeric.rb",
40
- "lib/interpolate/interpolation.rb",
41
- "test/test_all.rb"
42
- ]
43
- s.homepage = %q{http://github.com/m104/interpolate}
44
- s.require_paths = ["lib"]
45
- s.rubygems_version = %q{1.3.7}
46
- s.summary = %q{Create linear interpolations from key points and values}
47
- s.test_files = [
48
- "examples/arrays.rb",
49
- "examples/buckets.rb",
50
- "examples/colors.rb",
51
- "examples/nested.rb",
52
- "test/test_all.rb"
53
- ]
13
+ gem.summary = 'Create arbitrary interpolations from key points and values'
14
+ gem.description = 'Interpolate is a library for generic linear interpolation objects. Useful for such things as calculating linear motion between locations (or arrays of locations), multi-channel color gradients, piecewise functions, or even just placing values within intervals.'
54
15
 
55
- if s.respond_to? :specification_version then
56
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
57
- s.specification_version = 3
16
+ gem.authors = ['Adam Collins']
17
+ gem.email = ['adam@m104.us']
18
+ gem.homepage = 'http://github.com/m104/interpolate'
58
19
 
59
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
60
- else
61
- end
62
- else
63
- end
20
+ # Dependencies
21
+ gem.require_paths = ['lib']
22
+ #gem.add_development_dependency "rspec"
23
+ #gem.add_runtime_dependency "whatever"
24
+
25
+ # Manifest
26
+ all_files = `git ls-files`.split("\n")
27
+ gem.files = all_files
28
+ gem.test_files = all_files.grep(/^test\//)
64
29
  end
65
30
 
@@ -1,2 +1,15 @@
1
- require 'interpolate/interpolation'
1
+ require 'interpolate/base'
2
2
  require 'interpolate/add/core'
3
+
4
+ # deprecated as of 0.3.0; use Interpolate::Points instead
5
+ class Interpolation
6
+ class << self
7
+ # metaclass :new override method to return an instance of
8
+ # Interpolate::Points
9
+ def new(*args)
10
+ warn "::Interpolation has been deprecated as of 0.3.0; use Interpolate::Points"
11
+ Interpolate::Points.new(*args)
12
+ end
13
+ end
14
+ end
15
+
@@ -24,17 +24,17 @@ class Array
24
24
  #
25
25
  # This method is intentionally abstract to allow for the interpolation
26
26
  # of nested arrays. In this case, both arrays need to have the same array
27
- # structure (same number of dimensions, equal length in each dimension),
27
+ # structure (same number of dimensions, equal length in each dimension),
28
28
  # but the contents can, of course, be different.
29
29
  #
30
- # A balance greater than or equal to 0.0 returns +self+, while a
31
- # balance less than or equal to 1.0 returns +other+.
30
+ # A balance less than or equal to 0.0 returns +self+, while a
31
+ # balance greater than or equal to 1.0 returns +other+.
32
32
  def interpolate(other, balance)
33
33
  if (self.length < 1) then
34
- raise ArgumentError, "cannot interpolate array with no values"
34
+ raise ArgumentError, "cannot interpolate empty array"
35
35
  end
36
36
 
37
- if (self.length != other.length) then
37
+ if self.length != other.length then
38
38
  raise ArgumentError, "cannot interpolate between arrays of different length"
39
39
  end
40
40
 
@@ -44,16 +44,14 @@ class Array
44
44
 
45
45
  final = Array.new
46
46
 
47
- self.each_with_index do |left, index|
48
- unless (left.respond_to? :interpolate) then
47
+ self.each_with_index do |value, index|
48
+ unless value.respond_to? :interpolate then
49
49
  raise "array element does not respond to :interpolate"
50
50
  end
51
51
 
52
- right = other[index]
53
-
54
- final[index] = left.interpolate(right, balance)
52
+ final[index] = value.interpolate(other[index], balance)
55
53
  end
56
54
 
57
- return final
55
+ final
58
56
  end
59
57
  end
@@ -16,17 +16,18 @@ class Numeric
16
16
  # +self+ and +other+. +balance+ should be a Float from 0.0 to 1.0,
17
17
  # where the value is a ratio between +self+ and +other+.
18
18
  #
19
- # A balance greater than or equal to 0.0 returns +self+, while a
20
- # balance less than or equal to 1.0 returns +other+.
19
+ # A balance less than or equal to 0.0 returns +self+, while a
20
+ # balance greater than or equal to 1.0 returns +other+.
21
21
  def interpolate(other, balance)
22
+ # everything should be a Float
22
23
  balance = balance.to_f
23
24
  left = self.to_f
24
25
  right = other.to_f
25
-
26
+
26
27
  # catch the easy cases
27
28
  return left if (balance <= 0.0)
28
29
  return right if (balance >= 1.0)
29
-
30
+
30
31
  delta = (right - left).to_f
31
32
  return left + (delta * balance)
32
33
  end
@@ -0,0 +1,179 @@
1
+ # Interpolate is a library for generic linear interpolation objects. Useful for
2
+ # such things as calculating linear motion between locations (or arrays of
3
+ # locations), multi-channel color gradients, piecewise functions, or even just
4
+ # placing values within intervals.
5
+ #
6
+ # Interpolation generators can be created with the Interpolate::Points class,
7
+ # given a set of interpolation "key points" and associated key values. By
8
+ # default, the key values should be able to calculate their own blending
9
+ # function (by defining an +interpolate+ instance method). Alternatively, the
10
+ # Interpolate::Points object can be passed a block that takes three arguments:
11
+ # the lower value, the higher value, and the balance ratio between the two.
12
+ #
13
+ # For the balance ratio, 0.0 means 100% of the lower value and a balance ratio
14
+ # of 1.0 means 100% of the higher value. A balance ratio of 0.5 means that the
15
+ # Interpolate::Points object has determined that the interpolated value should
16
+ # be a 50%/50% mixture of the lower and higher values. It is up to the blending
17
+ # function to determine how to calculate the interpolated value.
18
+ #
19
+ # A Interpolate::Points objects is constructed with a Hash object, wherein each
20
+ # key is a Numeric and each value is an object (which itself may respond to
21
+ # +interpolate+). If any of the value objects do not respond to +interpolate+, a
22
+ # blending function (+blend_with+) must be used to determine the interpolated
23
+ # values.
24
+ #
25
+ # The default blending function, Interpolate::Points::DEFAULT_BLEND, simply
26
+ # calls the +interpolate+ method on the lower key value with the arguments of 1)
27
+ # the higher key value and 2) the balance ratio.
28
+ #
29
+ #
30
+ # ==Author
31
+ #
32
+ # {Adam Collins}[mailto:adam@m104.us]
33
+ #
34
+ #
35
+ # ==License
36
+ #
37
+ # Licensed under the MIT license.
38
+ #
39
+
40
+ module Interpolate
41
+
42
+ class Points
43
+ attr_reader :points
44
+
45
+ # default blending function, which delegates to the :interpolate method on
46
+ # each given value object
47
+ DEFAULT_BLEND = lambda { |value, other, balance|
48
+ unless value.respond_to? :interpolate
49
+ raise "found an object (#{value.inspect}) that doesn't respond to :interpolate"
50
+ end
51
+ value.interpolate(other, balance)
52
+ }
53
+
54
+ # creates an Interpolate::Points object with an optional Hash object that
55
+ # specifies key points (Numeric) and associated value objects
56
+ #
57
+ # the blend_with Proc can also be given when creating a new
58
+ # Interpolate::Points object
59
+ def initialize(points = {}, &block)
60
+ @points = {}
61
+ @blend_with = block
62
+ merge!(points)
63
+ end
64
+
65
+ # define the blending function to use with this Interpolate::Points object
66
+ #
67
+ # setting this to +nil+ will reset the blending behavior back to calling
68
+ # the default blending function
69
+ def blend_with(&block)
70
+ @blend_with = block
71
+ end
72
+
73
+ # returns a new Interpolate::Points object with the given key points merged
74
+ # with the original points
75
+ def merge(points = {})
76
+ Points.new(points.merge(@points))
77
+ end
78
+
79
+ # merges the given key points with the original points
80
+ def merge!(points = {})
81
+ # points must be a Hash
82
+ raise ArgumentError, "key points must be a Hash object" unless points.is_a? Hash
83
+ # ensure the points are all keyed Numeric-ally
84
+ points.each do |key, value|
85
+ raise ArgumentError, "found a point key that is not a Numeric object: #{key.inspect}" unless key.is_a? Numeric
86
+ end
87
+
88
+ @points.merge!(points)
89
+ normalize_data
90
+ self
91
+ end
92
+
93
+ # returns the interpolated value at the Numeric point specified, optionally
94
+ # using a given block as the blending function
95
+ #
96
+ # if no key points have been specified, the return value is +nil+
97
+ #
98
+ # if one key point has been specified, the return value is the value
99
+ # of that key point
100
+ #
101
+ # if the given point falls outside the interpolation key range (lower than
102
+ # the lowest key point or higher than the highest key point), the nearest
103
+ # point value is used; in other words, no extrapolation is performed
104
+ #
105
+ # otherwise, the interpolated value is calculated in accordance with the
106
+ # first of:
107
+ #
108
+ # * the given block
109
+ # * the stored blending function, :blend_with
110
+ # * a call to :interpolate on a key value object
111
+ #
112
+ def at(point, &block)
113
+ # obvious cases first
114
+ if @sorted.empty?
115
+ # no key points
116
+ return nil
117
+ elsif @sorted.size == 1
118
+ # one key point
119
+ return @sorted.first.last
120
+ end
121
+
122
+ # out-of-bounds cases next
123
+ if point <= @min_point
124
+ # lower than lowest key point
125
+ return @sorted.first.last
126
+ elsif point >= @max_point
127
+ # higher than highest key point
128
+ return @sorted.last.last
129
+ end
130
+
131
+ # binary search to find the right interpolation key point/value interval
132
+ left = 0
133
+ right = @sorted.length - 2 # highest point will be included
134
+ low_point = nil
135
+ low_value = nil
136
+ high_point = nil
137
+ high_value = nil
138
+
139
+ while left <= right
140
+ middle = (right - left) / 2 + left
141
+
142
+ (low_point, low_value) = @sorted[middle]
143
+ (high_point, high_value) = @sorted[middle + 1]
144
+
145
+ break if low_point <= point and point <= high_point
146
+
147
+ if point < low_point
148
+ right = middle - 1
149
+ else
150
+ left = middle + 1
151
+ end
152
+ end
153
+
154
+ # determine the balance ratio
155
+ span = high_point - low_point
156
+ balance = (point.to_f - low_point) / span
157
+
158
+ # choose and call the blending function
159
+ blend = block || @blend_with || DEFAULT_BLEND
160
+ blend.call(low_value, high_value, balance)
161
+ end
162
+
163
+ alias :[] :at
164
+
165
+ private
166
+
167
+ def normalize_data # :nodoc:
168
+ @sorted = @points.sort
169
+ unless @sorted.empty?
170
+ @min_point = @sorted.first.first
171
+ @max_point = @sorted.last.first
172
+ end
173
+ end
174
+
175
+ end # class Points
176
+
177
+ end # module Interpolate
178
+
179
+