lib/geometry/polyline.rb in geometry-6.1 vs lib/geometry/polyline.rb in geometry-6.2

- old
+ new

@@ -46,12 +46,11 @@ 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 + 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 @@ -92,20 +91,68 @@ def eql?(other) @vertices.zip(other.vertices).all? {|a,b| a == b} end alias :== :eql? + # @group Attributes + + # @return [Point] The upper-right corner of the bounding rectangle that encloses the {Polyline} + def max + vertices.reduce {|memo, vertex| Point[[memo.x, vertex.x].max, [memo.y, vertex.y].max] } + end + + # @return [Point] The lower-left corner of the bounding rectangle that encloses the {Polyline} + def min + vertices.reduce {|memo, vertex| Point[[memo.x, vertex.x].min, [memo.y, vertex.y].min] } + end + + # @return [Array<Point>] The lower-left and upper-right corners of the enclosing bounding rectangle + 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 + + # @endgroup + # Clone the receiver, close it, then return it # @return [Polyline] the closed clone of the receiver def close clone.close! end # Close the receiver and return it # @return [Polyline] the receiver after closing def close! - push_edge Edge.new(@edges.last.last, @edges.first.first) unless @edges.empty? || closed? + 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 # Check to see if the {Polyline} is closed (ie. is it a {Polygon}?) # @return [Bool] true if the {Polyline} is closed (the first vertex is equal to the last vertex) @@ -303,16 +350,44 @@ end [intersection, intersection_at] end # @endgroup + # Pop the last edge, and its associated vertices + # @return [Edge] the popped {Edge} + def pop_edge + old = @edges.pop # Remove the last Edge + if 0 == @edges.length # Remove all vertices if the only Edge was popped + @vertices.clear + elsif old.last == @vertices.last # Remove the last vertex if it was used by the popped Edge + @vertices.pop + end + old + end + def push_edge(*e) @edges.push *e @edges.uniq! end def push_vertex(*v) @vertices.push *v @vertices.uniq! + end + + # Remove the first {Edge} and its associated vertices + # @return [Edge] the shifted {Edge} + def shift_edge + @vertices.shift((@edges.size > 1) ? 1 : 2) + @edges.shift + end + + # Prepend an {Edge} and its vertices + # @param edge [Edge] the {Edge} to unshift + def unshift_edge(edge) + @vertices.unshift(edge.last) unless edge.last == @edges.first.first + @vertices.unshift(edge.first) # unless edge.first == @edges.last.last + @vertices.pop if @vertices.last == @vertices.first + @edges.unshift(edge) end end end