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


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
87
# 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 = @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

#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



136
137
138
139
# File 'lib/geometry/polyline.rb', line 136

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



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

def close
    clone.close!
end

#close!Polyline

Close the receiver and return it

Returns:

  • (Polyline)

    the receiver after closing



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

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)



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

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:



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

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



144
145
146
# File 'lib/geometry/polyline.rb', line 144

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:



170
171
172
173
174
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
# File 'lib/geometry/polyline.rb', line 170

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:



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

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

#reverse!Polyline

Reverse the receiver and return it

Returns:



124
125
126
127
128
# File 'lib/geometry/polyline.rb', line 124

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



151
152
153
# File 'lib/geometry/polyline.rb', line 151

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:



220
221
222
# File 'lib/geometry/polyline.rb', line 220

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



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

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