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

Instance Method Summary collapse

Constructor Details

#initialize(Edge, Edge, ...) ⇒ Polyline #initialize(Point, Point, ...) ⇒ Polyline

Construct a new Polyline from Points and/or Edges

The constructor will try to convert all of its arguments into {Point}s and
 {Edge}s. Then successive {Point}s will be collpased into {Edge}s. Successive
 {Edge}s that share a common vertex will be added to the new {Polyline}. If
 there's a gap between {Edge}s it will be automatically filled with a new
 {Edge}.

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

#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

#offset(distance) ⇒ Polygon Also known as: leftset

Offset the receiver by the specified distance. A positive distance

will offset to the left, and a negative distance to the right.

Parameters:

  • distance (Number)

    The distance to offset by

Returns:



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/geometry/polyline.rb', line 101

def offset(distance)
    bisectors = offset_bisectors(distance)
    offsets = bisectors.each_cons(2).to_a

    # Create the offset edges and then wrap them in Hashes so the edges
    #  can be altered while walking the array
    active_edges = edges.zip(offsets).map do |e,offset|
	offset = Edge.new(e.first+offset.first.vector, e.last+offset.last.vector)

	# Skip zero-length edges
	{:edge => (offset.first == offset.last) ? nil : offset}
    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

#rightset(distance) ⇒ Polygon

Rightset the receiver by the specified distance

Parameters:

  • distance (Number)

    The distance to offset by

Returns:



147
148
149
# File 'lib/geometry/polyline.rb', line 147

def rightset(distance)
    offset(-distance)
end