interpolate 0.2.4 → 0.3.0

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