Class: BezierCurve

Inherits:
Object
  • Object
show all
Defined in:
lib/bezier_curve.rb,
lib/bezier_curve/version.rb

Overview

bezier_curve/version.rb Just the version information for this library

Defined Under Namespace

Classes: DifferingDimensionError, InsufficientPointsError, ZeroDimensionError

Constant Summary collapse

VERSION =
"0.9.2"
RELEASE_DATE =
"2015-06-22"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*controls) ⇒ BezierCurve

create a new curve, from a list of points.



19
20
21
22
23
24
25
26
# File 'lib/bezier_curve.rb', line 19

def initialize(*controls)
  # check for argument errors
  ZeroDimensionError.check! controls
  DifferingDimensionError.check! controls
  InsufficientPointsError.check! controls

  @controls = controls.map(&:to_np)
end

Instance Attribute Details

#controlsObject (readonly)

Returns the value of attribute controls.



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

def controls
  @controls
end

Instance Method Details

#degreeObject Also known as: order

the degree of the curve



38
39
40
# File 'lib/bezier_curve.rb', line 38

def degree
  controls.size - 1
end

#dimensionsObject

the number of dimensions given



43
44
45
# File 'lib/bezier_curve.rb', line 43

def dimensions
  controls[0].size
end

#divergenceObject

How much this curve diverges from straight, measuring from ‘t=0.5`



127
128
129
# File 'lib/bezier_curve.rb', line 127

def divergence
  first.angle_to(self[0.5],last)
end

#firstObject Also known as: start

the first control point



31
# File 'lib/bezier_curve.rb', line 31

def first() controls.first; end

#index(t) ⇒ Object Also known as: []

find the point for a given value of ‘t`.



48
49
50
51
52
53
54
55
56
# File 'lib/bezier_curve.rb', line 48

def index(t)
  pts = controls
  while pts.size > 1
    pts = (0..pts.size-2).map do |i|
      pts[i].zip(pts[i+1]).map{|a,b| t*(b-a)+a}
    end
  end
  pts[0].to_np
end

#is_straight?(tolerance) ⇒ Boolean

test this curve to see of it can be considered straight, optionally within the given angular tolerance, in radians

Returns:

  • (Boolean)


111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/bezier_curve.rb', line 111

def is_straight?(tolerance)
  # normal check for tolerance 
  if divergence <= tolerance
    # maximum wavyness is `degree` - 1; split at `degree` points
    pts = points(count:degree)
    # size-3, because we ignore the last 2 points as starting points;
    # check all angles against `tolerance`
    (0..pts.size-3).all? do |i|
      pts[i].angle_to(pts[i+1], pts[i+2]) < tolerance
    end
  else
    false
  end
end

#lastObject Also known as: end

the last control point



34
# File 'lib/bezier_curve.rb', line 34

def last()  controls.last;  end

#points(count: nil, tolerance: Math::PI/64) ⇒ Object

Returns a list of points on this curve. If you specify ‘count`, returns that many points, evenly spread over values of `t`. If you specify `tolerance`, no adjoining line segments will deviate from 180 by an angle of more than the value given (in radians). If unspecified, defaults to `tolerance: 1/64pi` (~3 deg)



79
80
81
82
83
84
85
86
# File 'lib/bezier_curve.rb', line 79

def points(count:nil, tolerance:Math::PI/64)
  if count
    (0...count).map{|i| index i/(count-1.0)}
  else
    lines = subdivide(tolerance)
    lines.map{|seg|seg.first} + [lines.last.last]
  end
end

#split_at(t) ⇒ Object

divide this bezier curve into two curves, at the given ‘t`



60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/bezier_curve.rb', line 60

def split_at(t)
  pts = controls
  a,b = [pts.first],[pts.last]
  while pts.size > 1
    pts = (0..pts.size-2).map do |i|
      pts[i].zip(pts[i+1]).map{|a,b| t*(b-a)+a}
    end
    a<<pts.first
    b<<pts.last
  end
  [BezierCurve.new(*a), BezierCurve.new(*b.reverse)]
end

#subdivide(tolerance) ⇒ Object

recursively subdivides the curve until each is straight within the given tolerance value, in radians. Then, subdivides further as needed to remove remaining corners.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/bezier_curve.rb', line 91

def subdivide(tolerance)
  if is_straight? tolerance
    [self]
  else
    a,b = split_at(0.5).map{|c| c.subdivide(tolerance)}
    # now make sure the angle from a to b is good
    while a.last.first.angle_to(a.last.last,b.first.last) > tolerance
      if a.last.divergence > b.first.divergence
        a[-1,1] = a[-1].split_at(0.5)
      else
        b[0,1]  = b[0].split_at(0.5)
      end
    end
    a+b
  end
end