greiner_hormann 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|