geom2d 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/CONTRIBUTERS +1 -1
- data/Rakefile +2 -0
- data/VERSION +1 -1
- data/lib/geom2d.rb +4 -4
- data/lib/geom2d/algorithms/polygon_operation.rb +12 -19
- data/lib/geom2d/polygon.rb +3 -5
- data/lib/geom2d/segment.rb +18 -13
- data/lib/geom2d/utils.rb +1 -1
- data/lib/geom2d/utils/sorted_list.rb +86 -0
- data/lib/geom2d/version.rb +1 -1
- data/test/geom2d/algorithms/test_polygon_operation.rb +2 -2
- data/test/geom2d/test_polygon.rb +2 -0
- data/test/geom2d/utils/{test_sorted_linked_list.rb → test_sorted_list.rb} +18 -22
- metadata +26 -5
- data/lib/geom2d/utils/sorted_linked_list.rb +0 -154
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ef94841dc1fefba33e2abbd4303cea33d3d1cfd4db5727a828300b12e7e405d6
|
|
4
|
+
data.tar.gz: d65cf9ae9017e07c0faf92cccd21cc918d323c31479bfc2f3a99b0a65162d33b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 18dfb48248517e32415185c17e1bc6bf5eab076a6ae43d5302dbc972c88c1da0bb00c70ac1889dcc7d9f8b3ed3596a485e89e4ca4b4b7168b348914ee16b9143
|
|
7
|
+
data.tar.gz: 93ed02cc3c1037a04bfbe97274c0acc65f2f1ada3417f3de8b47b517664069793757df8a7e6b1ea2cd455a6b569f36ef1aabfed67c39bd07140ace59901fc2b0
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## 0.2.0 - Unreleased
|
|
2
|
+
|
|
3
|
+
### Changed
|
|
4
|
+
|
|
5
|
+
* Make Segment#intersect ~1.71x faster by avoiding unnecessary object creation
|
|
6
|
+
* Refactor and simplify the sorted list implementation used by the polygon
|
|
7
|
+
operations, making the latter ~1.15x faster
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
* Fix off-by-one error in Polygon#ccw? calculation
|
|
12
|
+
|
|
13
|
+
## 0.1.0 - 2018-03-28
|
|
14
|
+
|
|
15
|
+
* Initial release
|
data/CONTRIBUTERS
CHANGED
data/Rakefile
CHANGED
|
@@ -21,6 +21,7 @@ namespace :dev do
|
|
|
21
21
|
'lib/**/*.rb',
|
|
22
22
|
'test/**/*',
|
|
23
23
|
'Rakefile',
|
|
24
|
+
'CHANGELOG.md',
|
|
24
25
|
'LICENSE',
|
|
25
26
|
'VERSION',
|
|
26
27
|
'CONTRIBUTERS',
|
|
@@ -51,6 +52,7 @@ namespace :dev do
|
|
|
51
52
|
|
|
52
53
|
s.require_path = 'lib'
|
|
53
54
|
s.required_ruby_version = '>= 2.4'
|
|
55
|
+
s.add_development_dependency('rubocop', '~> 0.58', '>= 0.58.2')
|
|
54
56
|
|
|
55
57
|
s.author = 'Thomas Leitner'
|
|
56
58
|
s.email = 't_leitner@gmx.at'
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.2.0
|
data/lib/geom2d.rb
CHANGED
|
@@ -29,7 +29,7 @@ module Geom2D
|
|
|
29
29
|
# Creates a new Point object from the given coordinates.
|
|
30
30
|
#
|
|
31
31
|
# See: Point.new
|
|
32
|
-
def self
|
|
32
|
+
def self.Point(x, y = nil)
|
|
33
33
|
if x.kind_of?(Point)
|
|
34
34
|
x
|
|
35
35
|
elsif y
|
|
@@ -43,7 +43,7 @@ module Geom2D
|
|
|
43
43
|
# +start_point+ to +start_point+ + +vector+.
|
|
44
44
|
#
|
|
45
45
|
# See: Segment.new
|
|
46
|
-
def self
|
|
46
|
+
def self.Segment(start_point, end_point = nil, vector: nil)
|
|
47
47
|
if end_point
|
|
48
48
|
Segment.new(start_point, end_point)
|
|
49
49
|
elsif vector
|
|
@@ -56,14 +56,14 @@ module Geom2D
|
|
|
56
56
|
# Creates a new Polygon object from the given vertices.
|
|
57
57
|
#
|
|
58
58
|
# See: Polygon.new
|
|
59
|
-
def self
|
|
59
|
+
def self.Polygon(*vertices)
|
|
60
60
|
Polygon.new(vertices)
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
# Creates a PolygonSet from the given array of Polygon instances.
|
|
64
64
|
#
|
|
65
65
|
# See: PolygonSet.new
|
|
66
|
-
def self
|
|
66
|
+
def self.PolygonSet(*polygons)
|
|
67
67
|
PolygonSet.new(polygons)
|
|
68
68
|
end
|
|
69
69
|
|
|
@@ -66,12 +66,12 @@ module Geom2D
|
|
|
66
66
|
attr_accessor :prev_in_result
|
|
67
67
|
|
|
68
68
|
# Creates a new SweepEvent.
|
|
69
|
-
def initialize(left, point, polygon_type, other_event
|
|
69
|
+
def initialize(left, point, polygon_type, other_event = nil)
|
|
70
70
|
@left = left
|
|
71
71
|
@point = point
|
|
72
72
|
@other_event = other_event
|
|
73
73
|
@polygon_type = polygon_type
|
|
74
|
-
@edge_type =
|
|
74
|
+
@edge_type = :normal
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
# Returns +true+ if this event's line #segment is below the point +p+.
|
|
@@ -184,9 +184,9 @@ module Geom2D
|
|
|
184
184
|
@operation = operation
|
|
185
185
|
|
|
186
186
|
@result = PolygonSet.new
|
|
187
|
-
@event_queue = Utils::
|
|
187
|
+
@event_queue = Utils::SortedList.new {|a, b| a.process_after?(b) }
|
|
188
188
|
# @sweep_line should really be a sorted data structure with O(log(n)) for insert/search!
|
|
189
|
-
@sweep_line = Utils::
|
|
189
|
+
@sweep_line = Utils::SortedList.new {|a, b| a.segment_below?(b) }
|
|
190
190
|
@sorted_events = []
|
|
191
191
|
end
|
|
192
192
|
|
|
@@ -212,9 +212,7 @@ module Geom2D
|
|
|
212
212
|
|
|
213
213
|
@event_queue.pop
|
|
214
214
|
if event.left # the segment hast to be inserted into status line
|
|
215
|
-
|
|
216
|
-
prev_event = (node.prev_node.anchor? ? nil : node.prev_node.value)
|
|
217
|
-
next_event = (node.next_node.anchor? ? nil : node.next_node.value)
|
|
215
|
+
prevprev_event, prev_event, next_event = @sweep_line.insert(event)
|
|
218
216
|
|
|
219
217
|
compute_event_fields(event, prev_event)
|
|
220
218
|
if next_event && possible_intersection(event, next_event) == 2
|
|
@@ -222,19 +220,14 @@ module Geom2D
|
|
|
222
220
|
compute_event_fields(next_event, event)
|
|
223
221
|
end
|
|
224
222
|
if prev_event && possible_intersection(prev_event, event) == 2
|
|
225
|
-
|
|
226
|
-
compute_event_fields(prev_event, prevprev_ev)
|
|
223
|
+
compute_event_fields(prev_event, prevprev_event)
|
|
227
224
|
compute_event_fields(event, prev_event)
|
|
228
225
|
end
|
|
229
226
|
else # the segment has to be removed from the status line
|
|
230
227
|
event = event.other_event # use left event
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
prev_node = node.prev_node
|
|
235
|
-
node.delete
|
|
236
|
-
unless prev_node.anchor? || next_node.anchor?
|
|
237
|
-
possible_intersection(prev_node.value, next_node.value)
|
|
228
|
+
prev_ev, next_ev = @sweep_line.delete(event)
|
|
229
|
+
if prev_ev && next_ev
|
|
230
|
+
possible_intersection(prev_ev, next_ev)
|
|
238
231
|
end
|
|
239
232
|
end
|
|
240
233
|
end
|
|
@@ -271,7 +264,7 @@ module Geom2D
|
|
|
271
264
|
return if segment.degenerate?
|
|
272
265
|
start_point_is_left = (segment.start_point == segment.min)
|
|
273
266
|
e1 = SweepEvent.new(start_point_is_left, segment.start_point, polygon_type)
|
|
274
|
-
e2 = SweepEvent.new(!start_point_is_left, segment.end_point, polygon_type,
|
|
267
|
+
e2 = SweepEvent.new(!start_point_is_left, segment.end_point, polygon_type, e1)
|
|
275
268
|
e1.other_event = e2
|
|
276
269
|
@event_queue.push(e1).push(e2)
|
|
277
270
|
end
|
|
@@ -363,8 +356,8 @@ module Geom2D
|
|
|
363
356
|
# Divides the event's segment at the given point (which has to be inside the segment) and adds
|
|
364
357
|
# the resulting events to the event queue.
|
|
365
358
|
def divide_segment(event, point)
|
|
366
|
-
right = SweepEvent.new(false, point, event.polygon_type,
|
|
367
|
-
left = SweepEvent.new(true, point, event.polygon_type,
|
|
359
|
+
right = SweepEvent.new(false, point, event.polygon_type, event)
|
|
360
|
+
left = SweepEvent.new(true, point, event.polygon_type, event.other_event)
|
|
368
361
|
event.other_event.other_event = left
|
|
369
362
|
event.other_event = right
|
|
370
363
|
@event_queue.push(left).push(right)
|
data/lib/geom2d/polygon.rb
CHANGED
|
@@ -64,10 +64,8 @@ module Geom2D
|
|
|
64
64
|
return to_enum(__method__) unless block_given?
|
|
65
65
|
return unless @vertices.size > 1
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
end
|
|
70
|
-
yield(Geom2D::Segment(@vertices[-1], @vertices[0]))
|
|
67
|
+
@vertices.each_cons(2) {|v1, v2| yield(Geom2D::Segment.new(v1, v2)) }
|
|
68
|
+
yield(Geom2D::Segment.new(@vertices[-1], @vertices[0]))
|
|
71
69
|
end
|
|
72
70
|
|
|
73
71
|
# Returns the BoundingBox of this polygon, or an empty BoundingBox if the polygon has no
|
|
@@ -83,7 +81,7 @@ module Geom2D
|
|
|
83
81
|
def ccw?
|
|
84
82
|
return true if @vertices.empty?
|
|
85
83
|
area = @vertices[-1].wedge(@vertices[0])
|
|
86
|
-
|
|
84
|
+
0.upto(@vertices.size - 2) {|i| area += @vertices[i].wedge(@vertices[i + 1]) }
|
|
87
85
|
area >= 0
|
|
88
86
|
end
|
|
89
87
|
|
data/lib/geom2d/segment.rb
CHANGED
|
@@ -110,27 +110,32 @@ module Geom2D
|
|
|
110
110
|
def intersect(segment)
|
|
111
111
|
p0 = start_point
|
|
112
112
|
p1 = segment.start_point
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
d0x = end_point.x - start_point.x
|
|
114
|
+
d0y = end_point.y - start_point.y
|
|
115
|
+
d1x = segment.end_point.x - segment.start_point.x
|
|
116
|
+
d1y = segment.end_point.y - segment.start_point.y
|
|
117
|
+
ex = p1.x - p0.x
|
|
118
|
+
ey = p1.y - p0.y
|
|
116
119
|
|
|
117
|
-
cross =
|
|
120
|
+
cross = (d0x * d1y - d1x * d0y).to_f # cross product of direction vectors
|
|
118
121
|
|
|
119
122
|
if cross.abs > Utils.precision # segments are not parallel
|
|
120
|
-
s =
|
|
123
|
+
s = (ex * d1y - d1x * ey) / cross
|
|
121
124
|
return nil if s < 0 || s > 1
|
|
122
|
-
t =
|
|
125
|
+
t = (ex * d0y - d0x * ey) / cross
|
|
123
126
|
return nil if t < 0 || t > 1
|
|
124
127
|
|
|
125
|
-
result = p0 +
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
result = p0 + Point.new(s * d0x, s * d0y)
|
|
129
|
+
return case result
|
|
130
|
+
when start_point then start_point
|
|
131
|
+
when end_point then end_point
|
|
132
|
+
when segment.start_point then segment.start_point
|
|
133
|
+
when segment.end_point then segment.end_point
|
|
134
|
+
else result
|
|
135
|
+
end
|
|
131
136
|
end
|
|
132
137
|
|
|
133
|
-
return nil if
|
|
138
|
+
return nil if (ex * d0y - d0x * ey).abs > Utils.precision # non-intersecting parallel segment lines
|
|
134
139
|
|
|
135
140
|
e0 = end_point
|
|
136
141
|
e1 = segment.end_point
|
data/lib/geom2d/utils.rb
CHANGED
|
@@ -13,7 +13,7 @@ module Geom2D
|
|
|
13
13
|
# Contains utility methods and classes.
|
|
14
14
|
module Utils
|
|
15
15
|
|
|
16
|
-
autoload(:
|
|
16
|
+
autoload(:SortedList, 'geom2d/utils/sorted_list')
|
|
17
17
|
|
|
18
18
|
# The precision when comparing two floats, defaults to 1e-10.
|
|
19
19
|
singleton_class.attr_accessor :precision
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# -*- frozen_string_literal: true -*-
|
|
2
|
+
#
|
|
3
|
+
#--
|
|
4
|
+
# geom2d - 2D Geometric Objects and Algorithms
|
|
5
|
+
# Copyright (C) 2018 Thomas Leitner <t_leitner@gmx.at>
|
|
6
|
+
#
|
|
7
|
+
# This software may be modified and distributed under the terms
|
|
8
|
+
# of the MIT license. See the LICENSE file for details.
|
|
9
|
+
#++
|
|
10
|
+
|
|
11
|
+
module Geom2D
|
|
12
|
+
module Utils
|
|
13
|
+
|
|
14
|
+
# A list that keeps its items sorted. Currently only used by
|
|
15
|
+
# Geom2D::Algorithms::PolygonOperation and therefore with special methods for the latter.
|
|
16
|
+
class SortedList
|
|
17
|
+
|
|
18
|
+
include Enumerable
|
|
19
|
+
|
|
20
|
+
# Creates a new SortedList using the +comparator+ or the given block as compare function.
|
|
21
|
+
#
|
|
22
|
+
# The comparator has to respond to +call(a, b)+ where +a+ is the value to be inserted and +b+
|
|
23
|
+
# is the value to which it is compared. The return value should be +true+ if the value +a+
|
|
24
|
+
# should be inserted before +b+, i.e. at the position of +b+.
|
|
25
|
+
def initialize(comparator = nil, &block)
|
|
26
|
+
@list = []
|
|
27
|
+
@comparator = comparator || block
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns +true+ if the list is empty?
|
|
31
|
+
def empty?
|
|
32
|
+
@list.empty?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns the last value in the list.
|
|
36
|
+
def last
|
|
37
|
+
@list.last
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Yields each value in sorted order.
|
|
41
|
+
#
|
|
42
|
+
# If no block is given, an enumerator is returned.
|
|
43
|
+
def each(&block) #:yield: value
|
|
44
|
+
@list.each(&block)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Inserts the value and returns self.
|
|
48
|
+
def push(value)
|
|
49
|
+
insert(value)
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Inserts a new value into the list (at a position decided by the compare function) and
|
|
54
|
+
# returns the previous-previous, previous and next values.
|
|
55
|
+
def insert(value)
|
|
56
|
+
i = @list.bsearch_index {|el| @comparator.call(value, el) } || @list.size
|
|
57
|
+
@list.insert(i, value)
|
|
58
|
+
[(i <= 1 ? nil : @list[i - 2]), (i == 0 ? nil : @list[i - 1]), @list[i + 1]]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Deletes the given value and returns the previous and next values.
|
|
62
|
+
def delete(value)
|
|
63
|
+
i = @list.index(value)
|
|
64
|
+
result = [(i == 0 ? nil : @list[i - 1]), @list[i + 1]]
|
|
65
|
+
@list.delete_at(i)
|
|
66
|
+
result
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Clears the list.
|
|
70
|
+
def clear
|
|
71
|
+
@list.clear
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Removes the top value from the list and returns it.
|
|
75
|
+
def pop
|
|
76
|
+
@list.pop
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def inspect #:nodoc:
|
|
80
|
+
"#<#{self.class.name}:0x#{object_id.to_s(16).rjust(0.size * 2, '0')} #{to_a}>"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
end
|
data/lib/geom2d/version.rb
CHANGED
|
@@ -7,7 +7,7 @@ describe Geom2D::Algorithms::PolygonOperation::SweepEvent do
|
|
|
7
7
|
before do
|
|
8
8
|
@klass = Geom2D::Algorithms::PolygonOperation::SweepEvent
|
|
9
9
|
@left = @klass.new(true, Geom2D::Point(5, 3), :subject)
|
|
10
|
-
@right = @klass.new(false, Geom2D::Point(8, 5), :subject,
|
|
10
|
+
@right = @klass.new(false, Geom2D::Point(8, 5), :subject, @left)
|
|
11
11
|
@left.other_event = @right
|
|
12
12
|
end
|
|
13
13
|
|
|
@@ -29,7 +29,7 @@ describe Geom2D::Algorithms::PolygonOperation::SweepEvent do
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
it "detects vertical segments" do
|
|
32
|
-
assert(@klass.new(false, Geom2D::Point(5, 8), :subject,
|
|
32
|
+
assert(@klass.new(false, Geom2D::Point(5, 8), :subject, @left).vertical?)
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
data/test/geom2d/test_polygon.rb
CHANGED
|
@@ -56,6 +56,8 @@ describe Geom2D::Polygon do
|
|
|
56
56
|
assert(@polygon.ccw?)
|
|
57
57
|
@polygon.reverse!
|
|
58
58
|
refute(@polygon.ccw?)
|
|
59
|
+
refute(Geom2D::Polygon([2, 2], [20, 5], [10, 3.0]).ccw?)
|
|
60
|
+
assert(Geom2D::Polygon([2, 2], [20, 5], [10, 3.4]).ccw?)
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
it "reverses the vertex list" do
|
|
@@ -1,29 +1,38 @@
|
|
|
1
1
|
# -*- frozen_string_literal: true -*-
|
|
2
2
|
|
|
3
3
|
require 'test_helper'
|
|
4
|
-
require 'geom2d/utils/
|
|
4
|
+
require 'geom2d/utils/sorted_list'
|
|
5
5
|
|
|
6
|
-
describe Geom2D::Utils::
|
|
6
|
+
describe Geom2D::Utils::SortedList do
|
|
7
7
|
before do
|
|
8
|
-
@list = Geom2D::Utils::
|
|
8
|
+
@list = Geom2D::Utils::SortedList.new {|a, b| a < b }
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
describe "insert" do
|
|
12
|
-
it "inserts a value and returns
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
it "inserts a value and returns surrounding values" do
|
|
13
|
+
ppv, pv, nv = @list.insert(1)
|
|
14
|
+
assert_nil(ppv)
|
|
15
|
+
assert_nil(pv)
|
|
16
|
+
assert_nil(nv)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
it "uses the comparator for choosing the place to insert the value" do
|
|
20
20
|
@list.insert(10)
|
|
21
21
|
@list.insert(5)
|
|
22
22
|
@list.insert(8)
|
|
23
|
-
|
|
23
|
+
ppv, pv, nv = @list.insert(9)
|
|
24
|
+
assert_equal([5, 8, 10], [ppv, pv, nv])
|
|
25
|
+
assert_equal([5, 8, 9, 10], @list.to_a)
|
|
24
26
|
end
|
|
25
27
|
end
|
|
26
28
|
|
|
29
|
+
it "deletes a value from the list and returns the neighbouring values" do
|
|
30
|
+
@list.push(8).push(5).push(3).push(4).push(6)
|
|
31
|
+
assert_equal([6, nil], @list.delete(8))
|
|
32
|
+
assert_equal([nil, 4], @list.delete(3))
|
|
33
|
+
assert_equal([4, 6], @list.delete(5))
|
|
34
|
+
end
|
|
35
|
+
|
|
27
36
|
it "returns whether it is empty" do
|
|
28
37
|
assert(@list.empty?)
|
|
29
38
|
@list.insert(5)
|
|
@@ -41,14 +50,6 @@ describe Geom2D::Utils::SortedLinkedList do
|
|
|
41
50
|
assert_equal(10, @list.last)
|
|
42
51
|
end
|
|
43
52
|
|
|
44
|
-
it "returns the first node found for a given value" do
|
|
45
|
-
@list.push(5).push(8)
|
|
46
|
-
node = @list.find_node(8)
|
|
47
|
-
assert_equal(8, node.value)
|
|
48
|
-
assert_equal(5, node.prev_node.value)
|
|
49
|
-
assert(node.next_node.anchor?)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
53
|
it "pops the top value of the list" do
|
|
53
54
|
@list.push(8).push(5)
|
|
54
55
|
assert_equal(8, @list.pop)
|
|
@@ -60,11 +61,6 @@ describe Geom2D::Utils::SortedLinkedList do
|
|
|
60
61
|
assert(@list.empty?)
|
|
61
62
|
end
|
|
62
63
|
|
|
63
|
-
it "deletes a value from the list" do
|
|
64
|
-
@list.push(8).push(5)
|
|
65
|
-
assert_equal(8, @list.delete(8))
|
|
66
|
-
end
|
|
67
|
-
|
|
68
64
|
it "can be inspected" do
|
|
69
65
|
@list.push(8).push(5)
|
|
70
66
|
assert_match(/[5, 8]/, @list.inspect)
|
metadata
CHANGED
|
@@ -1,21 +1,42 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: geom2d
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Thomas Leitner
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2018-
|
|
12
|
-
dependencies:
|
|
11
|
+
date: 2018-12-16 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rubocop
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.58'
|
|
20
|
+
- - ">="
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: 0.58.2
|
|
23
|
+
type: :development
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - "~>"
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '0.58'
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 0.58.2
|
|
13
33
|
description:
|
|
14
34
|
email: t_leitner@gmx.at
|
|
15
35
|
executables: []
|
|
16
36
|
extensions: []
|
|
17
37
|
extra_rdoc_files: []
|
|
18
38
|
files:
|
|
39
|
+
- CHANGELOG.md
|
|
19
40
|
- CONTRIBUTERS
|
|
20
41
|
- LICENSE
|
|
21
42
|
- README.md
|
|
@@ -30,7 +51,7 @@ files:
|
|
|
30
51
|
- lib/geom2d/polygon_set.rb
|
|
31
52
|
- lib/geom2d/segment.rb
|
|
32
53
|
- lib/geom2d/utils.rb
|
|
33
|
-
- lib/geom2d/utils/
|
|
54
|
+
- lib/geom2d/utils/sorted_list.rb
|
|
34
55
|
- lib/geom2d/version.rb
|
|
35
56
|
- test/geom2d/algorithms/test_polygon_operation.rb
|
|
36
57
|
- test/geom2d/test_algorithms.rb
|
|
@@ -39,7 +60,7 @@ files:
|
|
|
39
60
|
- test/geom2d/test_polygon.rb
|
|
40
61
|
- test/geom2d/test_polygon_set.rb
|
|
41
62
|
- test/geom2d/test_segment.rb
|
|
42
|
-
- test/geom2d/utils/
|
|
63
|
+
- test/geom2d/utils/test_sorted_list.rb
|
|
43
64
|
- test/test_helper.rb
|
|
44
65
|
homepage: https://geom2d.gettalong.org
|
|
45
66
|
licenses:
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
# -*- frozen_string_literal: true -*-
|
|
2
|
-
#
|
|
3
|
-
#--
|
|
4
|
-
# geom2d - 2D Geometric Objects and Algorithms
|
|
5
|
-
# Copyright (C) 2018 Thomas Leitner <t_leitner@gmx.at>
|
|
6
|
-
#
|
|
7
|
-
# This software may be modified and distributed under the terms
|
|
8
|
-
# of the MIT license. See the LICENSE file for details.
|
|
9
|
-
#++
|
|
10
|
-
|
|
11
|
-
module Geom2D
|
|
12
|
-
module Utils
|
|
13
|
-
|
|
14
|
-
# A doubly linked list that keeps its items sorted.
|
|
15
|
-
class SortedLinkedList
|
|
16
|
-
|
|
17
|
-
include Enumerable
|
|
18
|
-
|
|
19
|
-
# A node of the double linked list.
|
|
20
|
-
class Node
|
|
21
|
-
|
|
22
|
-
AnchorValue = Object.new #:nodoc:
|
|
23
|
-
|
|
24
|
-
# Creates a Node object that can be used as the anchor of the doubly linked list.
|
|
25
|
-
def self.create_anchor
|
|
26
|
-
Node.new(AnchorValue).tap {|anchor| anchor.next_node = anchor.prev_node = anchor }
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# The previous node in the list. The first node points to the anchor node.
|
|
30
|
-
attr_accessor :prev_node
|
|
31
|
-
|
|
32
|
-
# The next node in the list. The last node points to the anchor node.
|
|
33
|
-
attr_accessor :next_node
|
|
34
|
-
|
|
35
|
-
# The value of the node.
|
|
36
|
-
attr_accessor :value
|
|
37
|
-
|
|
38
|
-
# Creates a new Node for the given value, with optional previous and next nodes to point to.
|
|
39
|
-
def initialize(value, prev_node = nil, next_node = nil)
|
|
40
|
-
@prev_node = prev_node
|
|
41
|
-
@next_node = next_node
|
|
42
|
-
@value = value
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# Returns +true+ if this node is an anchor node, i.e. the start and end of this list.
|
|
46
|
-
def anchor?
|
|
47
|
-
@value == AnchorValue
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Inserts this node before the given node.
|
|
51
|
-
def insert_before(node)
|
|
52
|
-
@prev_node = node.prev_node
|
|
53
|
-
@next_node = node
|
|
54
|
-
node.prev_node.next_node = self
|
|
55
|
-
node.prev_node = self
|
|
56
|
-
self
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Deletes this node from the linked list.
|
|
60
|
-
def delete
|
|
61
|
-
@prev_node.next_node = @next_node
|
|
62
|
-
@next_node.prev_node = @prev_node
|
|
63
|
-
@prev_node = @next_node = nil
|
|
64
|
-
@value
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Creates a new SortedLinkedList using the +comparator+ or the given block as compare
|
|
70
|
-
# function.
|
|
71
|
-
#
|
|
72
|
-
# The comparator has to respond to +call(a, b)+ where +a+ is the value to be inserted and +b+
|
|
73
|
-
# is the value to which it is compared. The return value should be +true+ if the value +a+
|
|
74
|
-
# should be inserted before +b+, i.e. at the position of +b+.
|
|
75
|
-
def initialize(comparator = nil, &block)
|
|
76
|
-
@anchor = Node.create_anchor
|
|
77
|
-
@comparator = comparator || block
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# Returns +true+ if the list is empty?
|
|
81
|
-
def empty?
|
|
82
|
-
@anchor.next_node == @anchor
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# Returns the last value in the list.
|
|
86
|
-
def last
|
|
87
|
-
empty? ? nil : @anchor.prev_node.value
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# Yields each value in sorted order.
|
|
91
|
-
#
|
|
92
|
-
# If no block is given, an enumerator is returned.
|
|
93
|
-
def each #:yield: value
|
|
94
|
-
return to_enum(__method__) unless block_given?
|
|
95
|
-
current = @anchor.next_node
|
|
96
|
-
while current != @anchor
|
|
97
|
-
yield(current.value)
|
|
98
|
-
current = current.next_node
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# Returns the node with the given value, or +nil+ if no such value is found.
|
|
103
|
-
def find_node(value)
|
|
104
|
-
current = @anchor.next_node
|
|
105
|
-
while current != @anchor
|
|
106
|
-
return current if current.value == value
|
|
107
|
-
current = current.next_node
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# Inserts the value and returns self.
|
|
112
|
-
def push(value)
|
|
113
|
-
insert(value)
|
|
114
|
-
self
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
# Inserts a new node with the given value into the list (at a position decided by the compare
|
|
118
|
-
# function) and returns it.
|
|
119
|
-
def insert(value)
|
|
120
|
-
node = Node.new(value)
|
|
121
|
-
current = @anchor.next_node
|
|
122
|
-
while current != @anchor
|
|
123
|
-
if @comparator.call(node.value, current.value)
|
|
124
|
-
return node.insert_before(current)
|
|
125
|
-
end
|
|
126
|
-
current = current.next_node
|
|
127
|
-
end
|
|
128
|
-
node.insert_before(@anchor)
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Clears the list.
|
|
132
|
-
def clear
|
|
133
|
-
@anchor = Node.create_anchor
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Removes the top node from the list and returns its value.
|
|
137
|
-
def pop
|
|
138
|
-
return nil if empty?
|
|
139
|
-
@anchor.prev_node.delete
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
# Deletes the node with the given value from the list and returns its value.
|
|
143
|
-
def delete(value)
|
|
144
|
-
find_node(value)&.delete
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def inspect #:nodoc:
|
|
148
|
-
"#<#{self.class.name}:0x#{object_id.to_s(16).rjust(0.size * 2, '0')} #{to_a}>"
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
end
|
|
154
|
-
end
|