Class: Geometry::Polyline

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

Overview

A Polyline is like a Polygon in that it only contains straight lines, but also like a Path in that it isn’t necessarily closed.

http://en.wikipedia.org/wiki/Polyline

Usage

Direct Known Subclasses

Polygon

Instance Attribute Summary collapse

Attributes collapse

Bisectors collapse

Instance Method Summary collapse

Constructor Details

#initialize(Edge, Edge, ...) ⇒ Polyline #initialize(Point, Point, ...) ⇒ Polyline

Note:

The constructor will try to convert all of its arguments into Geometry::Points and Edges. Then successive Geometry::Points will be collpased into Edges. Successive Edges that share a common vertex will be added to the new Geometry::Polyline. If there’s a gap between Edges it will be automatically filled with a new Edge.

Construct a new Polyline from Points and/or Edges

Overloads:

  • #initialize(Edge, Edge, ...) ⇒ Polyline
  • #initialize(Point, Point, ...) ⇒ Polyline


28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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
# File 'lib/geometry/polyline.rb', line 28

def initialize(*args)
    args.map! {|a| (a.is_a?(Array) || a.is_a?(Vector)) ? Point[a] : a}
    args.each {|a| raise ArgumentError, "Unknown argument type #{a.class}" unless a.is_a?(Point) or a.is_a?(Edge) }

    @edges = [];
    @vertices = [];

    first = args.shift
    if first.is_a?(Point)
  @vertices.push first
    elsif first.is_a?(Edge)
  @edges.push first
  @vertices.push *(first.to_a)
    end

    args.reduce(@vertices.last) do |previous,n|
  if n.is_a?(Point)
      if n == previous # Ignore repeated Points
    previous
      else
    if @edges.last
        new_edge = Edge.new(previous, n)
        if @edges.last.parallel?(new_edge)
      popped_edge = pop_edge   # Remove the previous Edge
      if n == popped_edge.first
          popped_edge.first
      else
          push_edge Edge.new(popped_edge.first, n)
          push_vertex popped_edge.first
          push_vertex n
          n
      end
        else
      push_edge Edge.new(previous, n)
      push_vertex n
      n
        end
    else
        push_edge Edge.new(previous, n)
        push_vertex n
        n
    end
      end
  elsif n.is_a?(Edge)
      if previous == n.first
    push_edge n
    push_vertex n.last
      elsif previous == n.last
    push_edge n.reverse!
    push_vertex n.last
      else
    e = Edge.new(previous, n.first)
    push_edge e, n
    push_vertex *(e.to_a), *(n.to_a)
      end
      n.last
  end
    end
end

Instance Attribute Details

#edgesObject (readonly)

Returns the value of attribute edges.



16
17
18
# File 'lib/geometry/polyline.rb', line 16

def edges
  @edges
end

#verticesObject (readonly)

Returns the value of attribute vertices.



16
17
18
# File 'lib/geometry/polyline.rb', line 16

def vertices
  @vertices
end

Instance Method Details

#bisectorsArray<Vector>

Note:

If the Geometry::Polyline isn’t closed (the normal case), then the first and last vertices will be given bisectors that are perpendicular to themselves.

Generate the angle bisector unit vectors for each vertex

Returns:

  • (Array<Vector>)

    the unit Vectors representing the angle bisector of each vertex



183
184
185
186
# File 'lib/geometry/polyline.rb', line 183

def bisectors
    # Multiplying each bisector by the sign of k flips any bisectors that aren't pointing towards the interior of the angle
    bisector_map {|b, k| k <=> 0 }
end

#closePolyline

Clone the receiver, close it, then return it

Returns:

  • (Polyline)

    the closed clone of the receiver



117
118
119
# File 'lib/geometry/polyline.rb', line 117

def close
    clone.close!
end

#close!Polyline

Close the receiver and return it

Returns:

  • (Polyline)

    the receiver after closing



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/geometry/polyline.rb', line 123

def close!
    unless @edges.empty?
  # NOTE: parallel? is use here instead of collinear? because the
  #  edges are connected, and will therefore be collinear if
  #  they're parallel

  if closed?
      if @edges.first.parallel?(@edges.last)
    unshift_edge Edge.new(@edges.last.first, shift_edge.last)
      end
  elsif
      closing_edge = Edge.new(@edges.last.last, @edges.first.first)

      # If the closing edge is collinear with the last edge, then
      #  simply extened the last edge to fill the gap
      if @edges.last.parallel?(closing_edge)
    closing_edge = Edge.new(pop_edge.first, @edges.first.first)
      end

      # Check that the new closing_edge isn't zero-length
      if closing_edge.first != closing_edge.last
    # If the closing edge is collinear with the first edge, then
    #  extend the first edge "backwards" to fill the gap
    if @edges.first.parallel?(closing_edge)
        unshift_edge Edge.new(closing_edge.first, shift_edge.last)
    else
        push_edge closing_edge
    end
      end
  end
    end
    self
end

#closed?Bool

Check to see if the Geometry::Polyline is closed (ie. is it a Geometry::Polygon?)

Returns:

  • (Bool)

    true if the Geometry::Polyline is closed (the first vertex is equal to the last vertex)



159
160
161
# File 'lib/geometry/polyline.rb', line 159

def closed?
    @edges.last.last == @edges.first.first
end

#eql?(other) ⇒ Bool Also known as: ==

Check the equality of two Geometry::Polylines. Note that if two Geometry::Polylines have

opposite winding, but are otherwise identical, they will be considered unequal.

Returns:



91
92
93
# File 'lib/geometry/polyline.rb', line 91

def eql?(other)
    @vertices.zip(other.vertices).all? {|a,b| a == b}
end

#left_bisectorsArray<Vector>

Note:

This is similar to the #bisector method, but generates vectors that always point to the left side of the Geometry::Polyline instead of towards the inside of each corner

Generate left-side angle bisector unit vectors for each vertex

Returns:

  • (Array<Vector>)

    the unit Vectors representing the left-side angle bisector of each vertex



191
192
193
# File 'lib/geometry/polyline.rb', line 191

def left_bisectors
    bisector_map
end

#maxPoint

Returns The upper-right corner of the bounding rectangle that encloses the Geometry::Polyline.

Returns:



99
100
101
# File 'lib/geometry/polyline.rb', line 99

def max
    vertices.reduce {|memo, vertex| Point[[memo.x, vertex.x].max, [memo.y, vertex.y].max] }
end

#minPoint

Returns The lower-left corner of the bounding rectangle that encloses the Geometry::Polyline.

Returns:



104
105
106
# File 'lib/geometry/polyline.rb', line 104

def min
    vertices.reduce {|memo, vertex| Point[[memo.x, vertex.x].min, [memo.y, vertex.y].min] }
end

#minmaxArray<Point>

Returns The lower-left and upper-right corners of the enclosing bounding rectangle.

Returns:

  • (Array<Point>)

    The lower-left and upper-right corners of the enclosing bounding rectangle



109
110
111
# File 'lib/geometry/polyline.rb', line 109

def minmax
    vertices.reduce([vertices.first, vertices.first]) {|memo, vertex| [Point[[memo.first.x, vertex.x].min, [memo.first.y, vertex.y].min], Point[[memo.last.x, vertex.x].max, [memo.last.y, vertex.y].max]] }
end

#offset(distance) ⇒ Polyline Also known as: leftset

Note:

A positive distance will offset to the left, and a negative distance to the right.

Offset the receiver by the specified distance

Parameters:

  • distance (Number)

    The distance to offset by

Returns:



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/geometry/polyline.rb', line 217

def offset(distance)
    bisector_pairs = if closed?
  bisector_edges = offset_bisectors(distance)
  bisector_edges.push(bisector_edges.first).each_cons(2)
    else
  offset_bisectors(distance).each_cons(2)
    end

    # Create the offset edges and then wrap them in Hashes so the edges
    #  can be altered while walking the array
    active_edges = edges.zip(bisector_pairs).map do |e,offset|
  offset_edge = Edge.new(e.first+offset.first.vector, e.last+offset.last.vector)

  # Skip zero-length edges
  {:edge => (offset_edge.first == offset_edge.last) ? nil : offset_edge}
    end

    # Walk the array and handle any intersections
    for i in 0..(active_edges.count-1) do
  e1 = active_edges[i][:edge]
  next unless e1 # Ignore deleted edges

  intersection, j = find_last_intersection(active_edges, i, e1)
  if intersection
      e2 = active_edges[j][:edge]
      if intersection.is_a? Point
    active_edges[i][:edge] = Edge.new(e1.first, intersection)
    active_edges[j][:edge] = Edge.new(intersection, e2.last)
      else
    # Handle the collinear case
    active_edges[i][:edge] = Edge.new(e1.first, e2.last)
    active_edges[j].delete(:edge)
      end

      # Delete everything between e1 and e2
      for k in i..j do
    next if (k==i) or (k==j)    # Exclude e1 and e2
    active_edges[k].delete(:edge)
      end

      redo    # Recheck the modified edges
  end
    end
    Polyline.new *(active_edges.map {|e| e[:edge]}.compact.map {|e| [e.first, e.last]}.flatten)
end

#reversePolyline

Clone the receiver, reverse it, then return it

Returns:



165
166
167
# File 'lib/geometry/polyline.rb', line 165

def reverse
    self.class.new *(edges.reverse.map! {|edge| edge.reverse! })
end

#reverse!Polyline

Reverse the receiver and return it

Returns:



171
172
173
174
175
# File 'lib/geometry/polyline.rb', line 171

def reverse!
    vertices.reverse!
    edges.reverse!.map! {|edge| edge.reverse! }
    self
end

#right_bisectorsArray<Vector>

Note:

This is similar to the #bisector method, but generates vectors that always point to the right side of the Geometry::Polyline instead of towards the inside of each corner

Generate right-side angle bisector unit vectors for each vertex

Returns:

  • (Array<Vector>)

    the unit Vectors representing the ride-side angle bisector of each vertex



198
199
200
# File 'lib/geometry/polyline.rb', line 198

def right_bisectors
    bisector_map {|b, k| -1 }
end

#rightset(distance) ⇒ Polyline

Rightset the receiver by the specified distance

Parameters:

  • distance (Number)

    The distance to offset by

Returns:



267
268
269
# File 'lib/geometry/polyline.rb', line 267

def rightset(distance)
    offset(-distance)
end

#spokesArray<Vector>

Note:

If the Geometry::Polyline isn’t closed (the normal case), then the first and last vertices will be given bisectors that are perpendicular to themselves.

Generate the spokes for each vertex. A spoke is the same as a bisector, but in the oppostire direction (bisectors point towards the inside of each corner; spokes point towards the outside)

Returns:

  • (Array<Vector>)

    the unit Vectors representing the spoke of each vertex



206
207
208
209
# File 'lib/geometry/polyline.rb', line 206

def spokes
    # Multiplying each bisector by the negated sign of k flips any bisectors that aren't pointing towards the exterior of the angle
    bisector_map {|b, k| 0 <=> k }
end