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.
-
#vertices ⇒ Object
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
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
#edges ⇒ Object (readonly)
Returns the value of attribute edges.
16 17 18 |
# File 'lib/geometry/polyline.rb', line 16 def edges @edges end |
#vertices ⇒ Object (readonly)
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
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 |
#close ⇒ Polyline
Clone the receiver, close it, then return it
117 118 119 |
# File 'lib/geometry/polyline.rb', line 117 def close clone.close! end |
#close! ⇒ Polyline
Close the receiver and return it
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?)
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.
91 92 93 |
# File 'lib/geometry/polyline.rb', line 91 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
191 192 193 |
# File 'lib/geometry/polyline.rb', line 191 def left_bisectors bisector_map end |
#max ⇒ Point
Returns The upper-right corner of the bounding rectangle that encloses the Geometry::Polyline.
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 |
#min ⇒ Point
Returns The lower-left corner of the bounding rectangle that encloses the Geometry::Polyline.
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 |
#minmax ⇒ Array<Point>
Returns 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
A positive distance will offset to the left, and a negative distance to the right.
Offset the receiver by the specified distance
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 |
#reverse ⇒ Polyline
Clone the receiver, reverse it, then return it
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
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_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
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
267 268 269 |
# File 'lib/geometry/polyline.rb', line 267 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)
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 |