greiner_hormann 0.1.1
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 +7 -0
- data/.gitignore +10 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/greiner_hormann.gemspec +21 -0
- data/lib/greiner_hormann/intersection.rb +23 -0
- data/lib/greiner_hormann/polygon.rb +200 -0
- data/lib/greiner_hormann/version.rb +3 -0
- data/lib/greiner_hormann/vertex.rb +56 -0
- data/lib/greiner_hormann.rb +23 -0
- metadata +54 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b0fb4f4ca683eca1dfa23c892c3fc01ecdb2c36b
|
4
|
+
data.tar.gz: b28af8294d6e6b45517cb3a8cb8b0db6ec89df48
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6dbd569660992e7a94330b22129f80c9d87feec1585090de3ba3dae11ed147d766f67e4b7f1397a328d31e936b97faefb82931f7a7b86b6c2e5c02db315d362a
|
7
|
+
data.tar.gz: 43aea13139e732bebd17c0f5050a1a1bc56338035b6f84d61b42ddae1e6c857a1bf14c7791bdc6e863dda72adb699e2607efd4b3a4c5c98b9882f1b88ad87cca
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Dave Allie
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'greiner_hormann/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "greiner_hormann"
|
8
|
+
spec.version = GreinerHormann::VERSION
|
9
|
+
spec.authors = ["Dave Allie"]
|
10
|
+
spec.email = ["dave@daveallie.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Polygon clipping using Greiner-Hormann algorithm.}
|
13
|
+
spec.description = %q{Polygon clipping using Greiner-Hormann algorithm.}
|
14
|
+
spec.homepage = "https://github.com/daveallie/greiner_hormann"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module GreinerHormann
|
2
|
+
class Intersection
|
3
|
+
attr_accessor :x, :y, :to_source, :to_clip
|
4
|
+
|
5
|
+
def initialize(s1, s2, c1, c2)
|
6
|
+
self.x = self.y = self.to_source = self.to_clip = 0.0
|
7
|
+
d = (c2.y - c1.y) * (s2.x - s1.x) - (c2.x - c1.x) * (s2.y - s1.y)
|
8
|
+
return if d == 0
|
9
|
+
|
10
|
+
self.to_source = ((c2.x - c1.x) * (s1.y - c1.y) - (c2.y - c1.y) * (s1.x - c1.x)) / d
|
11
|
+
self.to_clip = ((s2.x - s1.x) * (s1.y - c1.y) - (s2.y - s1.y) * (s1.x - c1.x)) / d
|
12
|
+
|
13
|
+
if valid
|
14
|
+
self.x = s1.x + self.to_source * (s2.x - s1.x)
|
15
|
+
self.y = s1.y + self.to_source * (s2.y - s1.y)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def valid
|
20
|
+
0 < to_source && to_source < 1 && 0 < to_clip && to_clip < 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
module GreinerHormann
|
2
|
+
class Polygon
|
3
|
+
attr_accessor :first, :verticies, :last_unprocessed, :first_intersect
|
4
|
+
|
5
|
+
def initialize(pnts)
|
6
|
+
self.first = self.first_intersect = self.last_unprocessed = nil
|
7
|
+
self.verticies = 0
|
8
|
+
|
9
|
+
pnts.each do |pnt|
|
10
|
+
add_vertex(Vertex.new(*pnt))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_vertex(vert)
|
15
|
+
if self.first.nil?
|
16
|
+
self.first = vert
|
17
|
+
self.first.next_node = vert
|
18
|
+
self.first.prev_node = vert
|
19
|
+
else
|
20
|
+
next_n = self.first
|
21
|
+
prev_n = next_n.prev_node
|
22
|
+
|
23
|
+
next_n.prev_node = vert
|
24
|
+
vert.next_node = next_n
|
25
|
+
vert.prev_node = prev_n
|
26
|
+
prev_n.next_node = vert
|
27
|
+
end
|
28
|
+
|
29
|
+
self.verticies += 1
|
30
|
+
end
|
31
|
+
|
32
|
+
def insert_vertex(vert, start, finish)
|
33
|
+
curr_n = start
|
34
|
+
|
35
|
+
while curr_n != finish && curr_n.distance < vert.distance
|
36
|
+
curr_n = curr_n.next_node
|
37
|
+
end
|
38
|
+
|
39
|
+
vert.next_node = curr_n
|
40
|
+
prev_n = curr_n.prev_node
|
41
|
+
|
42
|
+
vert.prev_node = prev_n
|
43
|
+
prev_n.next_node = vert
|
44
|
+
curr_n.prev_node = vert
|
45
|
+
|
46
|
+
self.verticies += 1
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_next(v)
|
50
|
+
c = v
|
51
|
+
while c.is_intersection
|
52
|
+
c = c.next_node
|
53
|
+
end
|
54
|
+
c
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_first_intersect
|
58
|
+
v = self.first_intersect || self.first
|
59
|
+
|
60
|
+
loop do
|
61
|
+
break if v.is_intersection && !v.visited
|
62
|
+
v = v.next_node
|
63
|
+
break if v == self.first
|
64
|
+
end
|
65
|
+
|
66
|
+
self.first_intersect = v
|
67
|
+
v
|
68
|
+
end
|
69
|
+
|
70
|
+
def has_unprocessed
|
71
|
+
v = self.last_unprocessed || self.first
|
72
|
+
|
73
|
+
loop do
|
74
|
+
if v.is_intersection && !v.visited
|
75
|
+
self.last_unprocessed = v
|
76
|
+
return true
|
77
|
+
end
|
78
|
+
|
79
|
+
v = v.next_node
|
80
|
+
break if v == self.first
|
81
|
+
end
|
82
|
+
|
83
|
+
self.last_unprocessed = nil
|
84
|
+
false
|
85
|
+
end
|
86
|
+
|
87
|
+
def get_points
|
88
|
+
points = []
|
89
|
+
v = self.first
|
90
|
+
|
91
|
+
loop do
|
92
|
+
points << [v.x, v.y]
|
93
|
+
v = v.next_node
|
94
|
+
break if v == self.first
|
95
|
+
end
|
96
|
+
|
97
|
+
points
|
98
|
+
end
|
99
|
+
|
100
|
+
# Clip polygon against another one.
|
101
|
+
# Result depends on algorithm direction:
|
102
|
+
#
|
103
|
+
# Intersection: forwards forwards
|
104
|
+
# Union: backwars backwards
|
105
|
+
# Diff: backwards forwards
|
106
|
+
def clip(clip, source_forwards, clip_forwards)
|
107
|
+
source_vert = self.first
|
108
|
+
clip_vert = clip.first
|
109
|
+
|
110
|
+
loop do
|
111
|
+
unless source_vert.is_intersection
|
112
|
+
loop do
|
113
|
+
unless clip_vert.is_intersection
|
114
|
+
i = Intersection.new(source_vert, get_next(source_vert.next_node), clip_vert, clip.get_next(clip_vert.next_node))
|
115
|
+
|
116
|
+
if i.valid
|
117
|
+
source_intersection = Vertex.create_intersection(i.x, i.y, i.to_source)
|
118
|
+
clip_intersection = Vertex.create_intersection(i.x, i.y, i.to_clip)
|
119
|
+
source_intersection.corresponding = clip_intersection
|
120
|
+
clip_intersection.corresponding = source_intersection
|
121
|
+
|
122
|
+
insert_vertex(source_intersection, source_vert, get_next(source_vert.next_node))
|
123
|
+
clip.insert_vertex(clip_intersection, clip_vert, clip.get_next(clip_vert.next_node))
|
124
|
+
end
|
125
|
+
end
|
126
|
+
clip_vert = clip_vert.next_node
|
127
|
+
break if clip_vert == clip.first
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
source_vert = source_vert.next_node
|
132
|
+
break if source_vert == self.first
|
133
|
+
end
|
134
|
+
|
135
|
+
source_vert = self.first
|
136
|
+
clip_vert = clip.first
|
137
|
+
|
138
|
+
source_in_clip = source_vert.is_inside(clip)
|
139
|
+
clip_in_source = clip_vert.is_inside(self)
|
140
|
+
|
141
|
+
source_forwards ^= source_in_clip
|
142
|
+
clip_forwards ^= clip_in_source
|
143
|
+
|
144
|
+
loop do
|
145
|
+
if source_vert.is_intersection
|
146
|
+
source_vert.is_entry = source_forwards
|
147
|
+
source_forwards = !source_forwards
|
148
|
+
end
|
149
|
+
source_vert = source_vert.next_node
|
150
|
+
break if source_vert == self.first
|
151
|
+
end
|
152
|
+
|
153
|
+
loop do
|
154
|
+
if clip_vert.is_intersection
|
155
|
+
clip_vert.is_entry = clip_forwards
|
156
|
+
clip_forwards = !clip_forwards
|
157
|
+
end
|
158
|
+
clip_vert = clip_vert.next_node
|
159
|
+
break if clip_vert == clip.first
|
160
|
+
end
|
161
|
+
|
162
|
+
list = []
|
163
|
+
|
164
|
+
while has_unprocessed
|
165
|
+
current = get_first_intersect
|
166
|
+
clipped = Polygon.new([])
|
167
|
+
clipped.add_vertex(Vertex.new(current.x, current.y))
|
168
|
+
|
169
|
+
loop do
|
170
|
+
current.visit
|
171
|
+
if current.is_entry
|
172
|
+
loop do
|
173
|
+
current = current.next_node
|
174
|
+
clipped.add_vertex(Vertex.new(current.x, current.y))
|
175
|
+
break if current.is_intersection
|
176
|
+
end
|
177
|
+
else
|
178
|
+
loop do
|
179
|
+
current = current.prev_node
|
180
|
+
clipped.add_vertex(Vertex.new(current.x, current.y))
|
181
|
+
break if current.is_intersection
|
182
|
+
end
|
183
|
+
end
|
184
|
+
current = current.corresponding
|
185
|
+
break if current.visited
|
186
|
+
end
|
187
|
+
|
188
|
+
list << clipped.get_points
|
189
|
+
end
|
190
|
+
|
191
|
+
if list.length == 0
|
192
|
+
list << self.get_points if source_in_clip
|
193
|
+
list << clip.get_points if clip_in_source
|
194
|
+
list = nil if list.length == 0
|
195
|
+
end
|
196
|
+
|
197
|
+
list
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module GreinerHormann
|
2
|
+
class Vertex
|
3
|
+
attr_accessor :x, :y, :next_node, :prev_node, :corresponding, :distance, :is_entry, :is_intersection, :visited
|
4
|
+
|
5
|
+
def initialize(x, y)
|
6
|
+
self.x = x
|
7
|
+
self.y = y
|
8
|
+
|
9
|
+
self.next_node = self.prev_node = self.corresponding = nil
|
10
|
+
self.distance = 0.0
|
11
|
+
self.is_entry = true
|
12
|
+
self.is_intersection = self.visited = false
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.create_intersection(x, y, distance)
|
16
|
+
vert = Vertex.new(x, y)
|
17
|
+
vert.distance = distance
|
18
|
+
vert.is_intersection = true
|
19
|
+
vert.is_entry = false
|
20
|
+
vert
|
21
|
+
end
|
22
|
+
|
23
|
+
def visit
|
24
|
+
self.visited = true
|
25
|
+
if !corresponding.nil? && !corresponding.visited
|
26
|
+
corresponding.visit
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def ==(v)
|
31
|
+
x == v.x && y == v.y
|
32
|
+
end
|
33
|
+
|
34
|
+
def equals(v)
|
35
|
+
self == v
|
36
|
+
end
|
37
|
+
|
38
|
+
def is_inside(poly)
|
39
|
+
odd_nodes = false
|
40
|
+
vert = poly.first
|
41
|
+
next_n = vert.next_node
|
42
|
+
|
43
|
+
loop do
|
44
|
+
if (vert.y < y && next_n.y >= y || next_n.y < y && vert.y >= y) && (vert.x <= x || next_n.x <= x)
|
45
|
+
odd_nodes ^= vert.x + (y - vert.y) / (next_n.y - vert.y) * (next_n.x - vert.x) < x
|
46
|
+
end
|
47
|
+
|
48
|
+
vert = vert.next_node
|
49
|
+
next_n = vert.next_node || poly.first
|
50
|
+
|
51
|
+
break if vert == poly.first
|
52
|
+
end
|
53
|
+
odd_nodes
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'greiner_hormann/intersection'
|
2
|
+
require 'greiner_hormann/polygon'
|
3
|
+
require 'greiner_hormann/vertex'
|
4
|
+
|
5
|
+
module GreinerHormann
|
6
|
+
def self.clip(poly_a, poly_b, source_forwards, clip_forwards)
|
7
|
+
source = Polygon.new(poly_a)
|
8
|
+
clip = Polygon.new(poly_b)
|
9
|
+
source.clip(clip, source_forwards, clip_forwards)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.union(poly_a, poly_b)
|
13
|
+
clip(poly_a, poly_b, false, false)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.intersection(poly_a, poly_b)
|
17
|
+
clip(poly_a, poly_b, true, true)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.difference(poly_a, poly_b)
|
21
|
+
clip(poly_a, poly_b, false, true)
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: greiner_hormann
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dave Allie
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-09-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Polygon clipping using Greiner-Hormann algorithm.
|
14
|
+
email:
|
15
|
+
- dave@daveallie.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".gitignore"
|
21
|
+
- Gemfile
|
22
|
+
- LICENSE
|
23
|
+
- greiner_hormann.gemspec
|
24
|
+
- lib/greiner_hormann.rb
|
25
|
+
- lib/greiner_hormann/intersection.rb
|
26
|
+
- lib/greiner_hormann/polygon.rb
|
27
|
+
- lib/greiner_hormann/version.rb
|
28
|
+
- lib/greiner_hormann/vertex.rb
|
29
|
+
homepage: https://github.com/daveallie/greiner_hormann
|
30
|
+
licenses:
|
31
|
+
- MIT
|
32
|
+
metadata: {}
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
requirements: []
|
48
|
+
rubyforge_project:
|
49
|
+
rubygems_version: 2.4.8
|
50
|
+
signing_key:
|
51
|
+
specification_version: 4
|
52
|
+
summary: Polygon clipping using Greiner-Hormann algorithm.
|
53
|
+
test_files: []
|
54
|
+
has_rdoc:
|