Class: Bezier::Curve

Inherits:
Object
  • Object
show all
Defined in:
lib/beziercurve.rb

Constant Summary collapse

DeCasteljau =

defaults

:decasteljau
Bernstein =
:bernstein
@@fact_memoize =
Hash.new
@@binomial_memoize =
Hash.new
@@pascaltriangle_memoize =
Hash.new

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*controlpoints) ⇒ Curve

Returns Creates a new Bézier curve object. The minimum number of control points is currently 3.

Examples:

initialize(p1, p2, p3)
initialize(p1, [20, 30], p3)

Parameters:

  • controlpoints (Array<ControlPoints>, Array<(Fixnum, Fixnum)>)

    list of ControlPoints defining the hull for the Bézier curve. A point can be of class ControlPoint or an Array containig 2 Numerics, which will be converted to ControlPoint.


103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/beziercurve.rb', line 103

def initialize(*controlpoints)

	# need at least 3 control points
	# this constraint has to be lifted, to allow adding Curves together like a 1 point curve to a 3 point curve
	if controlpoints.size < 3
		raise ArgumentError, 'Cannot create curve with less than 3 control points'
	end

	@controlpoints = controlpoints.map { |point|
		if point.class == Array
			ControlPoint.new(*point[0..1]) # ControlPoint.new gets no more than 2 arguments, excess values are ignored
		elsif point.class == ControlPoint
			point
		else
			raise 'Control points should be type of ControlPoint or Array'
		end
	  }
end

Instance Attribute Details

#controlpointsArray<ControlPoints>

Returns the Bezier curve control points

Returns:

  • (Array<ControlPoints>)

96
97
98
# File 'lib/beziercurve.rb', line 96

def controlpoints
  @controlpoints
end

Class Method Details

.binomial(n, k) ⇒ Object

standard 'n choose k'

Parameters:

  • n (Fixnum)
  • k (Fixnum)

78
79
80
81
82
# File 'lib/beziercurve.rb', line 78

def self.binomial(n,k)
			return 1 if n-k <= 0
			return 1 if k <= 0
			@@binomial_memoize[[n,k]] ||= fact(n) / ( fact(k) * fact( n - k ) )
end

.fact(n) ⇒ Object

Ye' olde factorial function

Examples:

> fact(5)

Parameters:

  • n (Fixnum)

71
72
73
# File 'lib/beziercurve.rb', line 71

def self.fact(n)
			@@fact_memoize[n] ||= (1..n).reduce(:*)
end

.pascaltriangle(nth_line) ⇒ Array

Returns the specified line from the Pascal triangle as an Array

Examples:

> pascaltriangle(6)

Returns:

  • (Array)

    A line from the Pascal triangle


88
89
90
# File 'lib/beziercurve.rb', line 88

def self.pascaltriangle(nth_line) # Classic Pascal triangle
			@@pascaltriangle_memoize[nth_line] ||= (0..nth_line).map { |e| binomial(nth_line, e) }
end

Instance Method Details

#add(point) ⇒ Object

Adds a new control point to the Bezier curve as endpoint.

Parameters:


125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/beziercurve.rb', line 125

def add(point)
    @controlpoints << case point
                      when ControlPoint
                        point
                      when Array
                        ControlPoint.new(*point[0..1])
                      else
                        raise(TypeError, 'Point should be type of ControlPoint')
                      end

	# if point.class == ControlPoint
	# 	@controlpoints << point
 #    elsif point.class == Array
 #      @controlpoints << ControlPoint.new(*point[0..1])
	# else
	# 	raise TypeError, 'Point should be type of ControlPoint'
	# end
end

#enumerated(start_t, delta_t) ⇒ Object

returns a new Enumerator that iterates over the Bezier curve from [start_t] to 1 by [delta_t] steps.


209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/beziercurve.rb', line 209

def enumerated(start_t, delta_t)
 		Enumerator.new do |yielder|
 			#TODO only do the conversion if start_t is not Float, Fractional or Bigfloat
 			point_position = start_t.to_f
   		number = point_on_curve(point_position)
   		loop do
     			yielder.yield(number)
     			point_position += delta_t.to_f
     			raise StopIteration if point_position > 1.0
     			number = point_on_curve(point_position)
   		end
 		end
end

#gnuplot_hullObject

was recently 'display_points'. just a helper, for quickly put ControlPoints to STDOUT in a gnuplottable format


201
202
203
# File 'lib/beziercurve.rb', line 201

def gnuplot_hull # was recently 'display_points'. just a helper, for quickly put ControlPoints to STDOUT in a gnuplottable format
	@controlpoints.map{|point| [point.x, point.y] }
end

#gnuplot_points(precision) ⇒ Object


205
206
# File 'lib/beziercurve.rb', line 205

def gnuplot_points(precision)
end

#orderObject

returns the order of the Bezier curve, aka the number of control points.


224
225
226
# File 'lib/beziercurve.rb', line 224

def order
	@controlpoints.size
end

#poc_with_bernstein(t) ⇒ Object


157
158
159
160
161
162
163
164
165
166
# File 'lib/beziercurve.rb', line 157

def poc_with_bernstein(t)
			n = @controlpoints.size-1
  sum = [0,0]
  for k in 0..n
  	sum[0] += @controlpoints[k].x * self.class.binomial(n,k) * (1-t)**(n-k) * t**k
  	sum[1] += @controlpoints[k].y * self.class.binomial(n,k) * (1-t)**(n-k) * t**k
  end
  return ControlPoint.new(*sum)
  #poc_with_decasteljau(t)
end

#poc_with_decasteljau(t) ⇒ Object


177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/beziercurve.rb', line 177

def poc_with_decasteljau(t)
  # imperatively ugly, but works, refactor later. point_on_curve and point_on_hull should be one method
  ary = @controlpoints
  return ary if ary.length <= 1 # zero or one element as argument, return unmodified

  while ary.length > 1
    temp = []
    0.upto(ary.length-2) do |index|
      memoize1 = point_on_hull(ary[index], ary[index+1], t)
      temp << ary[index+0] - memoize1
    end
    ary = temp
  end
  temp[0].to_curvepoint
end

#point_on_curve(t) ⇒ Object Also known as: poc

Parameters:


145
146
147
148
149
150
151
152
153
154
# File 'lib/beziercurve.rb', line 145

def point_on_curve(t) # calculates the 'x,y' coordinates of a point on the curve, at the ratio 't' (0 <= t <= 1)
  case
    when @@calculation_method == DeCasteljau
      poc_with_decasteljau(t)
    when @@calculation_method == Bernstein
      poc_with_bernstein(t)
    else
      poc_with_bernstein(t) # default
  end
end

#point_on_curve_binom(t) ⇒ Object


193
194
195
196
197
198
# File 'lib/beziercurve.rb', line 193

def point_on_curve_binom(t)
	coeffs = self.class.pascaltriangle(self.order)
	coeffs.reduce do |memo, obj|
		memo += t**obj * (1-t)** (n - obj)
	end
end

#point_on_hull(point1, point2, t) ⇒ Object

just realized this was nested (geez), Jörg.W.Mittag would have cried. So it is moved out from poc_with_decasteljau()


168
169
170
171
172
173
174
175
# File 'lib/beziercurve.rb', line 168

def point_on_hull(point1, point2, t) # just realized this was nested (geez), Jörg.W.Mittag would have cried. So it is moved out from poc_with_decasteljau()
  if (point1.class != ControlPoint) or (point2.class != ControlPoint)
    raise TypeError, 'Both points should be type of ControlPoint'
  end
  new_x = (1-t) * point1.x + t * point2.x
  new_y = (1-t) * point1.y + t * point2.y
  return ControlPoint.new(new_x, new_y)
end