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

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


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
87
88
89
90
91
92
# File 'lib/geometry/polyline.rb', line 33

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 = @edges.pop		# Remove the previous Edge
			@vertices.pop(@edges.size ? 1 : 2)	# Remove the now unused vertex, or vertices
			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

#optionsObject



18
19
20
21
# File 'lib/geometry/polyline.rb', line 18

def options
	@options = {} if !@options
	@options
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



141
142
143
144
# File 'lib/geometry/polyline.rb', line 141

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



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

def close
    clone.close!
end

#close!Polyline

Close the receiver and return it

Returns:

  • (Polyline)

    the receiver after closing



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

def close!
    push_edge Edge.new(@edges.last.last, @edges.first.first) unless @edges.empty? || closed?
    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)



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

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:



97
98
99
# File 'lib/geometry/polyline.rb', line 97

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



149
150
151
# File 'lib/geometry/polyline.rb', line 149

def left_bisectors
    bisector_map
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:



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/geometry/polyline.rb', line 175

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:



123
124
125
# File 'lib/geometry/polyline.rb', line 123

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

#reverse!Polyline

Reverse the receiver and return it

Returns:



129
130
131
132
133
# File 'lib/geometry/polyline.rb', line 129

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



156
157
158
# File 'lib/geometry/polyline.rb', line 156

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:



225
226
227
# File 'lib/geometry/polyline.rb', line 225

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



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

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