greiner_hormann 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
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,3 @@
1
+ module GreinerHormann
2
+ VERSION = '0.1.1'
3
+ 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: