interpolate 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/CHANGELOG.md +26 -16
- data/Gemfile +5 -0
- data/LICENSE +1 -1
- data/README.md +139 -126
- data/Rakefile +12 -26
- data/TODO +14 -0
- data/examples/arrays.rb +2 -2
- data/examples/buckets.rb +11 -8
- data/examples/colors.rb +16 -14
- data/examples/nested.rb +1 -1
- data/interpolate.gemspec +22 -57
- data/lib/interpolate.rb +14 -1
- data/lib/interpolate/add/core/array.rb +9 -11
- data/lib/interpolate/add/core/numeric.rb +5 -4
- data/lib/interpolate/base.rb +179 -0
- data/lib/interpolate/version.rb +6 -0
- data/test/test_all.rb +118 -44
- metadata +33 -56
- data/Manifest.txt +0 -16
- data/VERSION +0 -1
- data/lib/interpolate/interpolation.rb +0 -117
data/examples/arrays.rb
CHANGED
@@ -15,9 +15,9 @@ time_frames = {
|
|
15
15
|
6 => [0, 0, 0]
|
16
16
|
}
|
17
17
|
|
18
|
-
path =
|
18
|
+
path = Interpolate::Points.new(time_frames)
|
19
19
|
|
20
|
-
# play the
|
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:"
|
data/examples/buckets.rb
CHANGED
@@ -4,16 +4,14 @@ require 'interpolate'
|
|
4
4
|
# min_value => bucket
|
5
5
|
buckets = {
|
6
6
|
0.000 => 1,
|
7
|
-
0.
|
8
|
-
1.
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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}"
|
data/examples/colors.rb
CHANGED
@@ -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::
|
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
|
-
|
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
|
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
|
|
data/examples/nested.rb
CHANGED
data/interpolate.gemspec
CHANGED
@@ -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 |
|
7
|
-
|
8
|
-
s.version = "0.2.4"
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.required_rubygems_version = '>= 1.3.6'
|
9
7
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
# Gem metadata
|
9
|
+
gem.name = 'interpolate'
|
10
|
+
gem.version = Interpolate::VERSION
|
11
|
+
gem.platform = Gem::Platform::RUBY
|
14
12
|
|
15
|
-
|
16
|
-
linear motion between
|
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
|
-
|
56
|
-
|
57
|
-
|
16
|
+
gem.authors = ['Adam Collins']
|
17
|
+
gem.email = ['adam@m104.us']
|
18
|
+
gem.homepage = 'http://github.com/m104/interpolate'
|
58
19
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
|
data/lib/interpolate.rb
CHANGED
@@ -1,2 +1,15 @@
|
|
1
|
-
require 'interpolate/
|
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
|
31
|
-
# balance
|
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
|
34
|
+
raise ArgumentError, "cannot interpolate empty array"
|
35
35
|
end
|
36
36
|
|
37
|
-
if
|
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 |
|
48
|
-
unless
|
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
|
-
|
53
|
-
|
54
|
-
final[index] = left.interpolate(right, balance)
|
52
|
+
final[index] = value.interpolate(other[index], balance)
|
55
53
|
end
|
56
54
|
|
57
|
-
|
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
|
20
|
-
# balance
|
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
|
+
|