Class: Kamelopard::Functions::SplineFunction

Inherits:
FunctionMultiDim show all
Defined in:
lib/kamelopard/spline.rb

Overview

Provides basic support for Catmul-Rom splines, or in other words, calculating a nice smooth path through a series of “control points”. You’ll create a spline, add a bunch of control points, and then call run_function for a series of values between 0 and 1 to get the calculated points on the path.

Mathematically, what this code calls a “point” is in fact a vector, a set of related numbers. Each number in the set represents one “dimension”, the spline supports any number of dimensions, but each control point must have the same number. The returned vectors will also have that number of dimensions. Dimensions can represent whatever the user wants. For instance, the first might be “latitude”, the second “longitude”, and the third “altitude” (though common cases like that are already taken care of with SplineFunction descendants like PointSplineFunction and ViewSplineFunction).

The tension value controls how tight the function’s curves are. The usual resources (Google, Wikipedia, etc.) should provide sufficient discussion of Catmul-Rom splines to answer any detailed questions.

Direct Known Subclasses

PointSplineFunction, ViewSplineFunction

Instance Attribute Summary collapse

Attributes inherited from FunctionMultiDim

#ndims

Attributes inherited from Function

#append, #compose, #end, #max, #min, #start, #verbose

Instance Method Summary collapse

Methods inherited from FunctionMultiDim

#compose=

Methods inherited from Function

#get_value, interpolate

Constructor Details

#initialize(ndims, tension = 0.5) ⇒ SplineFunction

Returns a new instance of SplineFunction.



30
31
32
33
34
35
36
# File 'lib/kamelopard/spline.rb', line 30

def initialize(ndims, tension = 0.5)
    @ndims = ndims
    @control_points = []
    @total_dur = 0
    @tension = tension
    super()
end

Instance Attribute Details

#control_pointsObject (readonly)

Returns the value of attribute control_points.



28
29
30
# File 'lib/kamelopard/spline.rb', line 28

def control_points
  @control_points
end

#tensionObject (readonly)

Returns the value of attribute tension.



28
29
30
# File 'lib/kamelopard/spline.rb', line 28

def tension
  @tension
end

#total_durObject (readonly)

Returns the value of attribute total_dur.



28
29
30
# File 'lib/kamelopard/spline.rb', line 28

def total_dur
  @total_dur
end

Instance Method Details

#add_control_point(point, dur) ⇒ Object

Adds a new control point. :dur is a way of indicating the duration of the journey from the last point to this one, and is ignored for the first control point in the spline. Values for :dur are in whatever units the user wants; a spline with three control points with durations of 0, 10, and 20 will be identical to one with durations of 0, 1, and 2. – XXX: Raise an exception if the control point dimensionality doesn’t match @ndims ++



48
49
50
51
# File 'lib/kamelopard/spline.rb', line 48

def add_control_point(point, dur)
    @total_dur = @total_dur + dur if @control_points.size > 0
    @control_points << [ point, dur ]
end

#run_function(x) ⇒ Object

Evaluates the spline function at a given value, which should be between 0 and 1. Returns an array of the same number of dimensions as each of the control points.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/kamelopard/spline.rb', line 56

def run_function(x)
    # X will be between 0 and 1
    # Find which control points I should am using for the point in
    # question

    dur = 0
    last_dur = 0
    cur_i = 0
    u = 0
    @control_points.each_index do |i|
        next if i == 0
        cur_i = i
        last_dur = dur
        if 1.0 * (dur + @control_points[i][1]) / @total_dur >= x then
            # I've found the correct two control points: cp[i-1] and cp[i]
            # u is the point on the interval between the two control points
            # that we're interested in. 0 would be the first control point,
            # and 1 the second
            u = (x * @total_dur - dur) / @control_points[i][1]
            break
        end
        dur = dur + @control_points[i][1]
    end

    # http://www.cs.cmu.edu/~462/projects/assn2/assn2/catmullRom.pdf

    # cp = control points. cur_i will be at least 1
    # I need two control points on either side of this part of the
    # spline. If they don't exist, duplicate the endpoints of the
    # control points.
    cp1 = @control_points[cur_i-1][0]
    cp2 = @control_points[cur_i][0]
    if cur_i == 1 then
        cpt1 = cp1
    else
        cpt1 = @control_points[cur_i-2][0]
    end
    if cur_i >= @control_points.size - 1 then
        cpt2 = cp2
    else
        cpt2 = @control_points[cur_i+1][0]
    end

    # Can't just say Matrix[cp], because that adds an extra
    # dimension to the matrix, somehow.
    cps = Matrix[cpt1, cp1, cp2, cpt2]

    t = @tension
    h = Matrix[
        [ 0, 1, 0, 0 ],
        [ -t, 0, t, 0 ],
        [ 2*t, t-3, 3-2*t, -t ],
        [ -t, 2-t, t-2, t]
    ]

    p = Matrix[[1, u, u**2, u**3]] * h * cps
    return p.row(0)
end