lib/interpolate.rb in interpolate-0.2.0 vs lib/interpolate.rb in interpolate-0.2.1
- old
+ new
@@ -1,247 +1,60 @@
-=begin rdoc
+# Library for generic interpolation objects. Useful for such things as generating
+# linear motion between points (or arrays of points), multi-channel color
+# gradients, piecewise functions, or even just placing values within intervals.
+#
+# The only requirement is that each interpolation point value must be able to
+# figure out how to interpolate itself to its neighbor value(s). Numeric
+# objects and uniformly sized arrays are automatically endowed with this
+# ability by this gem, but other classes will require an implementation
+# of +interpolate+. See the example color.rb in the examples directory for
+# a brief demonstration using Color objects provided by the 'color' gem.
+#
+# Interpolation objects are constructed with a Hash object, wherein each key
+# is a real number value and each value is can respond to +interpolate+ and
+# determine the resulting value based on its neighbor value and the balance
+# ratio between the two points.
+#
+# At or below the lower bounds of the interpolation, the result will be equal to
+# the value of the lower bounds interpolation point. At or above the upper
+# bounds of the graient, the result will be equal to the value of the upper
+# bounds interpolation point.
+#
+#
+# ==Author
+#
+# {Adam Collins}[mailto:adam.w.collins@gmail.com]
+#
+#
+# ==License
+#
+# Licensed under the MIT license.
+#
-Library for generic interpolation objects. Useful for such things as generating
-linear motion between points (or arrays of points), multi-channel color
-gradients, piecewise functions, or even just placing values within intervals.
-
-The only requirement is that each interpolation point value must be able to
-figure out how to interpolate itself to its neighbor value(s). Numeric
-objects and uniformly sized arrays are automatically endowed with this
-ability by this gem, but other classes will require an implementation
-of #interpolate. See the second example below for a brief demonstration
-using Color objects.
-
-Interpolation objects are constructed with a Hash object, wherein each key
-is a real number value and each value is can respond to #interpolate and
-determine the resulting value based on its neighbor value and the balance
-ratio between the two points.
-
-At or below the lower bounds of the interpolation, the result will be equal to
-the value of the lower bounds interpolation point. At or above the upper
-bounds of the graient, the result will be equal to the value of the upper
-bounds interpolation point.
-
-
-==Author
-
-{Adam Collins}[mailto:adam.w.collins@gmail.com]
-
-
-==General Usage
-
-Specify the interpolation as a Hash, where keys represent numeric points
-along the gradient and values represent the known values along that gradient.
-
-Here's an example for determining which of 7 zones a set of values fall into:
-
- require 'rubygems'
- require 'interpolate'
-
- points = {
- 0.000 => 0,
- 0.427 => 1,
- 1.200 => 2,
- 3.420 => 3,
- 27.50 => 4,
- 45.20 => 5,
- 124.4 => 6,
- }
-
- zones = Interpolation.new(points)
-
- values = [
- -20.2,
- 0.234,
- 65.24,
- 9.234,
- 398.4,
- 4000
- ]
-
- values.each do |value|
- zone = zones.at(value).floor
- puts "A value of #{value} falls into zone #{zone}"
- end
-
-
-==Non-Numeric Gradients
-
-For non-Numeric gradient value objects, you'll need to implement :interpolate
-for the class in question. Here's an example using an RGB color gradient with
-the help of the 'color' gem:
-
- require 'rubygems'
- require 'interpolate'
- require 'color'
-
- # we need to implement :interpolate for Color::RGB
- # in order for Interpolation to work
- class Color::RGB
- def interpolate(other, balance)
- mix_with(other, balance * 100.0)
- end
- end
-
- # a nice weathermap-style color gradient
- points = {
- 0 => Color::RGB::White,
- 1 => Color::RGB::Lime,
- # 2 => ? (something between Lime and Yellow)
- 3 => Color::RGB::Yellow,
- 4 => Color::RGB::Orange,
- 5 => Color::RGB::Red,
- 6 => Color::RGB::Magenta,
- 7 => Color::RGB::DarkGray
- }
-
-
- gradient = Interpolation.new(points)
-
- # what are the colors of the gradient from 0 to 7
- # in increments of 0.2?
- (0).step(7, 0.2) do |value|
- color = gradient.at(value)
- puts "A value of #{value} means #{color.html}"
- end
-
-
-==Array-based Interpolations
-
-Aside from single value gradient points, you can interpolate over uniformly sized
-arrays. Between two interpolation points, let's say +a+ and +b+, the final
-result will be +c+ where +c[0]+ is the interpolation of +a[0]+ and +b[0]+ and
-+c[1]+ is interpolated between +a[1]+ and +b[1]+ and so on up to +c[n]+.
-
-Here is an example:
-
- require 'rubygems'
- require 'interpolate'
- require 'pp'
-
- # a non-linear set of multi-dimensional points;
- # perhaps the location of some actor in relation to time
- time_frames = {
- 0 => [0, 0, 0],
- 1 => [1, 0, 0],
- 2 => [0, 1, 0],
- 3 => [0, 0, 2],
- 4 => [3, 0, 1],
- 5 => [1, 2, 3],
- 6 => [0, 0, 0]
- }
-
- path = Interpolation.new(time_frames)
-
- # play the actors positions in time increments of 0.25
- (0).step(6, 0.25) do |time|
- position = path.at(time)
- puts ">> At #{time}s, actor is at:"
- p position
- end
-
-
-==Nested Array Interpolations
-
-As long as each top level array is uniformly sized in the first dimension
-and each nested array is uniformly sized in the second dimension (and so
-on...), multidimensional interpolation point values will just work.
-
-Here's an example of a set of 2D points being morphed:
-
- require 'rubygems'
- require 'interpolate'
- require 'pp'
-
-
- # a non-linear set of 2D vertexes;
- # the shape changes at each frame
- time_frames = {
- 0 => [[0, 0], [1, 0], [2, 0], [3, 0], [4, 0]], # a horizontal line
- 1 => [[0, 0], [1, 0], [3, 0], [0, 4], [0, 0]], # a triangle
- 2 => [[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]], # a square
- 3 => [[0, 0], [1, 0], [2, 0], [3, 0], [4, 0]], # a horizontal line, again
- 4 => [[0, 0], [0, 1], [0, 2], [0, 3], [0, 4]] # a vertical line
- }
-
-
- paths = Interpolation.new(time_frames)
-
- # show the vertex positions in time increments of 0.25
- (0).step(4, 0.25) do |time|
- points = paths.at(time)
- puts ">> At #{time}s, points are:"
- p points
- end
-
-
-==License
-
-Licensed under the MIT license.
-
-=end
-
-
-# all numeric objects should be supported out of the box
-class Numeric
- def interpolate(other, balance)
- left = self.to_f
- right = other.to_f
- delta = (right - left).to_f
- return left + (delta * balance)
- end
-end
-
-
-# a little more complicated, but there's no reason why we can't
-# interpolate between two equal length arrays as long as each element
-# responds to :interpolate
-class Array
- def interpolate(other, balance)
- if (self.length < 1) then
- raise ArgumentError, "cannot interpolate array with no values"
- end
-
- if (self.length != other.length) then
- raise ArgumentError, "cannot interpolate between arrays of different length"
- end
-
- final = Array.new
-
- self.each_with_index do |left, index|
- unless (left.respond_to? :interpolate) then
- raise "array element does not respond to :interpolate"
- end
-
- right = other[index]
-
- final[index] = left.interpolate(right, balance)
- end
-
- return final
- end
-end
-
-
class Interpolation
- VERSION = '0.2.0'
+ VERSION = '0.2.1' # :nodoc:
+ # creates an Interpolation object with Hash object that specifies
+ # each point location (Numeric) and value (up to you)
def initialize(points = {})
@points = {}
- add!(points)
+ merge!(points)
end
- def add(points = {})
+ # creates an Interpolation object from the receiver object,
+ # merged with the interpolated points you specify
+ def merge(points = {})
Interpolation.new(points.merge(@points))
end
- def add!(points = {})
+ # merges the interpolation points with the receiver object
+ def merge!(points = {})
@points.merge!(points)
normalize_data
end
-
+ # returns the interpolated value of the receiver object at the point specified
def at(point)
# deal with the two out-of-bounds cases first
if (point <= @min_point)
return @data.first.last
elsif (point >= @max_point)
@@ -283,11 +96,11 @@
raise "couldn't come up with a value for some reason!"
end
private
- def normalize_data
+ def normalize_data # :nodoc:
@data = @points.sort
@min_point = @data.first.first
@max_point = @data.last.first
# make sure that all values respond_to? :interpolate
@@ -300,5 +113,44 @@
end
end
+# all numeric objects should be supported
+class Numeric # :nodoc:
+ def interpolate(other, balance)
+ left = self.to_f
+ right = other.to_f
+ delta = (right - left).to_f
+ return left + (delta * balance)
+ end
+end
+
+
+# a little more complicated, but there's no reason why we can't
+# interpolate between two equal length arrays as long as each element
+# responds to +interpolate+
+class Array # :nodoc:
+ def interpolate(other, balance)
+ if (self.length < 1) then
+ raise ArgumentError, "cannot interpolate array with no values"
+ end
+
+ if (self.length != other.length) then
+ raise ArgumentError, "cannot interpolate between arrays of different length"
+ end
+
+ final = Array.new
+
+ self.each_with_index do |left, index|
+ unless (left.respond_to? :interpolate) then
+ raise "array element does not respond to :interpolate"
+ end
+
+ right = other[index]
+
+ final[index] = left.interpolate(right, balance)
+ end
+
+ return final
+ end
+end