Class: Geometry::Polyline
- Inherits:
-
Object
- Object
- Geometry::Polyline
- 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
Instance Attribute Summary collapse
-
#edges ⇒ Object
readonly
Returns the value of attribute edges.
-
#points ⇒ Array<Point>
All of the vertices of the Polyline (alias of #vertices).
-
#vertices ⇒ Object
(also: #points)
readonly
Returns the value of attribute vertices.
Attributes collapse
-
#max ⇒ Point
The upper-right corner of the bounding rectangle that encloses the Polyline.
-
#min ⇒ Point
The lower-left corner of the bounding rectangle that encloses the Polyline.
-
#minmax ⇒ Array<Point>
The lower-left and upper-right corners of the enclosing bounding rectangle.
Bisectors collapse
-
#bisectors ⇒ Array<Vector>
Generate the angle bisector unit vectors for each vertex.
-
#left_bisectors ⇒ Array<Vector>
Generate left-side angle bisector unit vectors for each vertex.
-
#right_bisectors ⇒ Array<Vector>
Generate right-side angle bisector unit vectors for each vertex.
-
#spokes ⇒ Array<Vector>
Generate the spokes for each vertex.
Instance Method Summary collapse
-
#close ⇒ Polyline
Clone the receiver, close it, then return it.
-
#close! ⇒ Polyline
Close the receiver and return it.
- #closed? ⇒ Bool
-
#eql?(other) ⇒ Bool
(also: #==)
Check the equality of two Polylines.
-
#initialize(*args) ⇒ Polyline
constructor
Construct a new Polyline from Points and/or Edges.
-
#offset(distance) ⇒ Polyline
(also: #leftset)
Offset the receiver by the specified distance.
-
#reverse ⇒ Polyline
Clone the receiver, reverse it, then return it.
-
#reverse! ⇒ Polyline
Reverse the receiver and return it.
-
#rightset(distance) ⇒ Polyline
Rightset the receiver by the specified distance.
Constructor Details
#initialize(Edge, Edge, ...) ⇒ Polyline #initialize(Point, Point, ...) ⇒ Polyline
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
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 88 89 90 |
# File 'lib/geometry/polyline.rb', line 32 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
#edges ⇒ Object (readonly)
Returns the value of attribute edges.
16 17 18 |
# File 'lib/geometry/polyline.rb', line 16 def edges @edges end |
#points=(value) ⇒ Array<Point>
Returns all of the vertices of the Geometry::Polyline (alias of #vertices).
20 21 22 |
# File 'lib/geometry/polyline.rb', line 20 def vertices @vertices end |
#vertices ⇒ Object (readonly) Also known as: points
Returns the value of attribute vertices.
16 17 18 |
# File 'lib/geometry/polyline.rb', line 16 def vertices @vertices end |
Instance Method Details
#bisectors ⇒ Array<Vector>
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
187 188 189 190 |
# File 'lib/geometry/polyline.rb', line 187 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 |
#close ⇒ Polyline
Clone the receiver, close it, then return it
121 122 123 |
# File 'lib/geometry/polyline.rb', line 121 def close clone.close! end |
#close! ⇒ Polyline
Close the receiver and return it
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 156 157 158 159 |
# File 'lib/geometry/polyline.rb', line 127 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?)
163 164 165 |
# File 'lib/geometry/polyline.rb', line 163 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.
95 96 97 |
# File 'lib/geometry/polyline.rb', line 95 def eql?(other) @vertices.zip(other.vertices).all? {|a,b| a == b} end |
#left_bisectors ⇒ Array<Vector>
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
195 196 197 |
# File 'lib/geometry/polyline.rb', line 195 def left_bisectors bisector_map end |
#max ⇒ Point
Returns The upper-right corner of the bounding rectangle that encloses the Geometry::Polyline.
103 104 105 |
# File 'lib/geometry/polyline.rb', line 103 def max vertices.reduce {|memo, vertex| Point[[memo.x, vertex.x].max, [memo.y, vertex.y].max] } end |
#min ⇒ Point
Returns The lower-left corner of the bounding rectangle that encloses the Geometry::Polyline.
108 109 110 |
# File 'lib/geometry/polyline.rb', line 108 def min vertices.reduce {|memo, vertex| Point[[memo.x, vertex.x].min, [memo.y, vertex.y].min] } end |
#minmax ⇒ Array<Point>
Returns The lower-left and upper-right corners of the enclosing bounding rectangle.
113 114 115 |
# File 'lib/geometry/polyline.rb', line 113 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
A positive distance will offset to the left, and a negative distance to the right.
Offset the receiver by the specified distance
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 262 263 264 265 |
# File 'lib/geometry/polyline.rb', line 221 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 |
#reverse ⇒ Polyline
Clone the receiver, reverse it, then return it
169 170 171 |
# File 'lib/geometry/polyline.rb', line 169 def reverse self.class.new *(edges.reverse.map! {|edge| edge.reverse! }) end |
#reverse! ⇒ Polyline
Reverse the receiver and return it
175 176 177 178 179 |
# File 'lib/geometry/polyline.rb', line 175 def reverse! vertices.reverse! edges.reverse!.map! {|edge| edge.reverse! } self end |
#right_bisectors ⇒ Array<Vector>
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
202 203 204 |
# File 'lib/geometry/polyline.rb', line 202 def right_bisectors bisector_map {|b, k| -1 } end |
#rightset(distance) ⇒ Polyline
Rightset the receiver by the specified distance
271 272 273 |
# File 'lib/geometry/polyline.rb', line 271 def rightset(distance) offset(-distance) end |
#spokes ⇒ Array<Vector>
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)
210 211 212 213 |
# File 'lib/geometry/polyline.rb', line 210 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 |