easy_geometry 0.1.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 +7 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +26 -0
- data/README +2 -0
- data/easy_geometry.gemspec +17 -0
- data/lib/.ruby-version +1 -0
- data/lib/easy_geometry.rb +12 -0
- data/lib/easy_geometry/d2/line.rb +73 -0
- data/lib/easy_geometry/d2/linear_entity.rb +355 -0
- data/lib/easy_geometry/d2/point.rb +193 -0
- data/lib/easy_geometry/d2/polygon.rb +638 -0
- data/lib/easy_geometry/d2/ray.rb +111 -0
- data/lib/easy_geometry/d2/segment.rb +119 -0
- data/lib/easy_geometry/d2/triangle.rb +375 -0
- data/lib/easy_geometry/d2/vector.rb +71 -0
- data/spec/d2/line_spec.rb +339 -0
- data/spec/d2/point_spec.rb +211 -0
- data/spec/d2/polygon_spec.rb +291 -0
- data/spec/d2/ray_spec.rb +439 -0
- data/spec/d2/segment_spec.rb +453 -0
- data/spec/d2/triangle_spec.rb +224 -0
- data/spec/d2/vector_spec.rb +91 -0
- data/spec/spec_helper.rb +101 -0
- metadata +88 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c1fb6f95cfb1ee017a62c9f6ed39a3ec916fadcaf966291e239de58da7d81307
|
|
4
|
+
data.tar.gz: fe76ffe30725d0ff656ae85b706b5481ac1959eebe7482fdb608424762c59629
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 525e81099138a5fad5f62e165e4fba932225a5a84cccf430a3033ba6d31e17467ddaa34777eb3cb06d15c8ce2fc13ce65e62f3d9371ce2ca97102aa0dfc9bcb8
|
|
7
|
+
data.tar.gz: 7941f809a621cbeeeaa7b753bb1890115d0d7fbbbf5c29837f28d7ec83ea8159ea7751f8638e34b345bde8448e4136d8e39124389fc34bdb2d3fbc3ba9a9a3d6
|
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--require spec_helper
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.6.3
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
GEM
|
|
2
|
+
remote: https://rubygems.org/
|
|
3
|
+
specs:
|
|
4
|
+
diff-lcs (1.3)
|
|
5
|
+
rspec (3.8.0)
|
|
6
|
+
rspec-core (~> 3.8.0)
|
|
7
|
+
rspec-expectations (~> 3.8.0)
|
|
8
|
+
rspec-mocks (~> 3.8.0)
|
|
9
|
+
rspec-core (3.8.1)
|
|
10
|
+
rspec-support (~> 3.8.0)
|
|
11
|
+
rspec-expectations (3.8.4)
|
|
12
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
13
|
+
rspec-support (~> 3.8.0)
|
|
14
|
+
rspec-mocks (3.8.1)
|
|
15
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
16
|
+
rspec-support (~> 3.8.0)
|
|
17
|
+
rspec-support (3.8.2)
|
|
18
|
+
|
|
19
|
+
PLATFORMS
|
|
20
|
+
ruby
|
|
21
|
+
|
|
22
|
+
DEPENDENCIES
|
|
23
|
+
rspec
|
|
24
|
+
|
|
25
|
+
BUNDLED WITH
|
|
26
|
+
1.16.4
|
data/README
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Gem::Specification.new do |s|
|
|
2
|
+
s.name = "easy_geometry"
|
|
3
|
+
s.version = "0.1.0"
|
|
4
|
+
s.author = ["Henry Metlov"]
|
|
5
|
+
s.date = '2019-10-20'
|
|
6
|
+
s.homepage = "https://github.com/Metloff/easy_geometry"
|
|
7
|
+
s.description = "Geometric primitives and algorithms for Ruby"
|
|
8
|
+
s.summary = "Geometric primitives and algorithms for Ruby"
|
|
9
|
+
s.license = "MIT"
|
|
10
|
+
|
|
11
|
+
s.files = `git ls-files`.split($/)
|
|
12
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
|
13
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
14
|
+
s.require_paths = ["lib"]
|
|
15
|
+
|
|
16
|
+
s.add_development_dependency 'rspec'
|
|
17
|
+
end
|
data/lib/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.6.3
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module EasyGeometry
|
|
2
|
+
require 'matrix'
|
|
3
|
+
require 'bigdecimal'
|
|
4
|
+
require_relative 'easy_geometry/d2/point'
|
|
5
|
+
require_relative 'easy_geometry/d2/vector'
|
|
6
|
+
require_relative 'easy_geometry/d2/linear_entity'
|
|
7
|
+
require_relative 'easy_geometry/d2/line'
|
|
8
|
+
require_relative 'easy_geometry/d2/segment'
|
|
9
|
+
require_relative 'easy_geometry/d2/ray'
|
|
10
|
+
require_relative 'easy_geometry/d2/polygon'
|
|
11
|
+
require_relative 'easy_geometry/d2/triangle'
|
|
12
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module EasyGeometry
|
|
2
|
+
module D2
|
|
3
|
+
# An infinite line in 2-dimensional Euclidean space.
|
|
4
|
+
class Line < LinearEntity
|
|
5
|
+
|
|
6
|
+
#
|
|
7
|
+
# Parameters:
|
|
8
|
+
# GeometryEntity
|
|
9
|
+
#
|
|
10
|
+
# Returns:
|
|
11
|
+
# true if `other` is on this Line.
|
|
12
|
+
# false otherwise.
|
|
13
|
+
#
|
|
14
|
+
def contains?(other)
|
|
15
|
+
if other.is_a?(Point)
|
|
16
|
+
return Point.is_collinear?(other, self.p1, self.p2)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
if other.is_a?(LinearEntity)
|
|
20
|
+
return Point.is_collinear?(other.p1, other.p2, self.p1, self.p2)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
return false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Finds the shortest distance between a line and a point.
|
|
27
|
+
#
|
|
28
|
+
# Raises
|
|
29
|
+
# ======
|
|
30
|
+
# TypeError is raised if `other` is not a Point
|
|
31
|
+
def distance(other)
|
|
32
|
+
other = Point.new(other[0], other[1]) if other.is_a?(Array)
|
|
33
|
+
raise TypeError, "Distance between Line and #{ other.class } is not defined" unless other.is_a?(Point)
|
|
34
|
+
|
|
35
|
+
return 0 if self.contains?(other)
|
|
36
|
+
self.perpendicular_segment(other).length
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns True if self and other are the same mathematical entities
|
|
40
|
+
def ==(other)
|
|
41
|
+
return false unless other.is_a?(Line)
|
|
42
|
+
|
|
43
|
+
Point.is_collinear?(self.p1, other.p1, self.p2, other.p2)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The equation of the line: ax + by + c.
|
|
47
|
+
def equation
|
|
48
|
+
if p1.x == p2.x
|
|
49
|
+
return "x - #{p1.x}"
|
|
50
|
+
elsif p1.y == p2.y
|
|
51
|
+
return "#{p2.y} - p1.y"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
"#{a}*x + #{b}*y + #{c} = 0"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The coefficients 'a' for ax + by + c = 0.
|
|
58
|
+
def a
|
|
59
|
+
@a ||= self.p1.y - self.p2.y
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The coefficients 'b' for ax + by + c = 0.
|
|
63
|
+
def b
|
|
64
|
+
@b ||= self.p2.x - self.p1.x
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The coefficients 'c' for ax + by + c = 0.
|
|
68
|
+
def c
|
|
69
|
+
@c ||= self.p1.x * self.p2.y - self.p1.y * self.p2.x
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
module EasyGeometry
|
|
2
|
+
module D2
|
|
3
|
+
# A base class for all linear entities (Line, Ray and Segment)
|
|
4
|
+
# in 2-dimensional Euclidean space.
|
|
5
|
+
class LinearEntity
|
|
6
|
+
attr_reader :p1, :p2
|
|
7
|
+
|
|
8
|
+
# Examples:
|
|
9
|
+
# LinearEntity.new(Point.new(0, 0), Point.new(1, 2))
|
|
10
|
+
# LinearEntity.new([0, 0], [1, 2])
|
|
11
|
+
def initialize(point1, point2)
|
|
12
|
+
@p1 = point1; @p2 = point2
|
|
13
|
+
|
|
14
|
+
check_input_points!
|
|
15
|
+
validate!
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# The direction vector of the LinearEntity.
|
|
19
|
+
# Returns:
|
|
20
|
+
# Point; the ray from the origin to this point is the
|
|
21
|
+
# direction of `self`.
|
|
22
|
+
#
|
|
23
|
+
def direction
|
|
24
|
+
@direction ||= Vector.new(p2.x - p1.x, p2.y - p1.y)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Return the non-reflex angle formed by rays emanating from
|
|
28
|
+
# the origin with directions the same as the direction vectors
|
|
29
|
+
# of the linear entities.
|
|
30
|
+
#
|
|
31
|
+
# From the dot product of vectors v1 and v2 it is known that:
|
|
32
|
+
#
|
|
33
|
+
# ``dot(v1, v2) = |v1|*|v2|*cos(A)``
|
|
34
|
+
#
|
|
35
|
+
# where A is the angle formed between the two vectors. We can
|
|
36
|
+
# get the directional vectors of the two lines and readily
|
|
37
|
+
# find the angle between the two using the above formula.
|
|
38
|
+
#
|
|
39
|
+
#
|
|
40
|
+
# Parameters:
|
|
41
|
+
# LinearEntity
|
|
42
|
+
#
|
|
43
|
+
# Returns:
|
|
44
|
+
# angle in radians
|
|
45
|
+
#
|
|
46
|
+
def angle_between(other)
|
|
47
|
+
raise TypeError, 'Must pass only LinearEntity objects.' unless other.is_a?(LinearEntity)
|
|
48
|
+
|
|
49
|
+
v1 = self.direction
|
|
50
|
+
v2 = other.direction
|
|
51
|
+
|
|
52
|
+
# Convert numerator to BigDecimal for more precision.
|
|
53
|
+
numerator = BigDecimal(v1.dot(v2).to_f.to_s)
|
|
54
|
+
denominator = v1.to_point.abs * v2.to_point.abs
|
|
55
|
+
|
|
56
|
+
return Math.acos(numerator / denominator)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Are two LinearEntity parallel?
|
|
60
|
+
#
|
|
61
|
+
# Parameters:
|
|
62
|
+
# LinearEntity
|
|
63
|
+
#
|
|
64
|
+
# Returns:
|
|
65
|
+
# true if self and other LinearEntity are parallel.
|
|
66
|
+
# false otherwise.
|
|
67
|
+
#
|
|
68
|
+
def parallel_to?(other)
|
|
69
|
+
raise TypeError, 'Must pass only LinearEntity objects.' unless other.is_a?(LinearEntity)
|
|
70
|
+
self.direction.cross_product(other.direction) == 0
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Are two linear entities perpendicular?
|
|
74
|
+
#
|
|
75
|
+
# Parameters:
|
|
76
|
+
# LinearEntity
|
|
77
|
+
#
|
|
78
|
+
# Returns:
|
|
79
|
+
# true if self and other LinearEntity are perpendicular.
|
|
80
|
+
# false otherwise.
|
|
81
|
+
#
|
|
82
|
+
def perpendicular_to?(other)
|
|
83
|
+
raise TypeError, 'Must pass only LinearEntity objects.' unless other.is_a?(LinearEntity)
|
|
84
|
+
self.direction.dot(other.direction) == 0
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Are two linear entities similar?
|
|
88
|
+
#
|
|
89
|
+
# Return:
|
|
90
|
+
# true if self and other are contained in the same line.
|
|
91
|
+
#
|
|
92
|
+
def similar_to?(other)
|
|
93
|
+
raise TypeError, 'Must pass only LinearEntity objects.' unless other.is_a?(LinearEntity)
|
|
94
|
+
|
|
95
|
+
l = Line.new(p1, p2)
|
|
96
|
+
l.contains?(other)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# The intersection with another geometrical entity
|
|
100
|
+
#
|
|
101
|
+
# Parameters:
|
|
102
|
+
# Point or LinearEntity
|
|
103
|
+
#
|
|
104
|
+
# Returns:
|
|
105
|
+
# Array of geometrical entities
|
|
106
|
+
#
|
|
107
|
+
def intersection(other)
|
|
108
|
+
other = Point.new(other[0], other[1]) if other.is_a?(Array)
|
|
109
|
+
|
|
110
|
+
# Other is a Point.
|
|
111
|
+
if other.is_a?(Point)
|
|
112
|
+
return [other] if self.contains?(other)
|
|
113
|
+
return []
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Other is a LinearEntity
|
|
117
|
+
if other.is_a?(LinearEntity)
|
|
118
|
+
# break into cases based on whether
|
|
119
|
+
# the lines are parallel, non-parallel intersecting, or skew
|
|
120
|
+
rank = Point.affine_rank(self.p1, self.p2, other.p1, other.p2)
|
|
121
|
+
if rank == 1
|
|
122
|
+
# we're collinear
|
|
123
|
+
return [other] if self.is_a?(Line)
|
|
124
|
+
return [self] if other.is_a?(Line)
|
|
125
|
+
|
|
126
|
+
if self.is_a?(Ray) && other.is_a?(Ray)
|
|
127
|
+
return intersect_parallel_rays(self, other)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
if self.is_a?(Ray) && other.is_a?(Segment)
|
|
131
|
+
return intersect_parallel_ray_and_segment(self, other)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
if self.is_a?(Segment) && other.is_a?(Ray)
|
|
135
|
+
return intersect_parallel_ray_and_segment(other, self)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
if self.is_a?(Segment) && other.is_a?(Segment)
|
|
139
|
+
return intersect_parallel_segments(self, other)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
elsif rank == 2
|
|
143
|
+
# we're in the same plane
|
|
144
|
+
l1 = Line.new(self.p1, self.p2)
|
|
145
|
+
l2 = Line.new(other.p1, other.p2)
|
|
146
|
+
|
|
147
|
+
# check to see if we're parallel. If we are, we can't
|
|
148
|
+
# be intersecting, since the collinear case was already
|
|
149
|
+
# handled
|
|
150
|
+
return [] if l1.parallel_to?(l2)
|
|
151
|
+
|
|
152
|
+
# Use Cramers rule:
|
|
153
|
+
# https://en.wikipedia.org/wiki/Cramer%27s_rule
|
|
154
|
+
det = l1.a * l2.b - l2.a * l1.b
|
|
155
|
+
det = det
|
|
156
|
+
x = (l1.b * l2.c - l1.c * l2.b) / det
|
|
157
|
+
y = (l2.a * l1.c - l2.c * l1.a ) / det
|
|
158
|
+
|
|
159
|
+
intersection_point = Point.new(x, y)
|
|
160
|
+
|
|
161
|
+
# if we're both lines, we can skip a containment check
|
|
162
|
+
return [intersection_point] if self.is_a?(Line) && other.is_a?(Line)
|
|
163
|
+
|
|
164
|
+
if self.contains?(intersection_point) && other.contains?(intersection_point)
|
|
165
|
+
return [intersection_point]
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
return []
|
|
169
|
+
else
|
|
170
|
+
# we're skew
|
|
171
|
+
return []
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
if other.respond_to?(:intersection)
|
|
176
|
+
return other.intersection(self)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
raise TypeError, "Intersection between LinearEntity and #{ other.class } is not defined"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Create a new Line parallel to this linear entity which passes
|
|
183
|
+
# through the point `p`
|
|
184
|
+
#
|
|
185
|
+
# Parameters:
|
|
186
|
+
# Point
|
|
187
|
+
#
|
|
188
|
+
# Returns:
|
|
189
|
+
# Line
|
|
190
|
+
#
|
|
191
|
+
def parallel_line(point)
|
|
192
|
+
raise TypeError, 'Must pass only Point.' unless point.is_a?(Point)
|
|
193
|
+
Line.new(point, point + self.direction.to_point)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Create a new Line perpendicular to this linear entity which passes
|
|
197
|
+
# through the point `point`.
|
|
198
|
+
#
|
|
199
|
+
# Parameters:
|
|
200
|
+
# Point
|
|
201
|
+
#
|
|
202
|
+
# Returns:
|
|
203
|
+
# Line
|
|
204
|
+
#
|
|
205
|
+
def perpendicular_line(point)
|
|
206
|
+
raise TypeError, 'Must pass only Point.' unless point.is_a?(Point)
|
|
207
|
+
|
|
208
|
+
# any two lines in R^2 intersect, so blindly making
|
|
209
|
+
# a line through p in an orthogonal direction will work
|
|
210
|
+
Line.new(point, point + self.direction.orthogonal_direction.to_point)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Create a perpendicular line segment from `point` to this line.
|
|
214
|
+
# The enpoints of the segment are `point` and the closest point in
|
|
215
|
+
# the line containing self. (If self is not a line, the point might
|
|
216
|
+
# not be in self.)
|
|
217
|
+
#
|
|
218
|
+
# Parameters:
|
|
219
|
+
# Point
|
|
220
|
+
#
|
|
221
|
+
# Returns:
|
|
222
|
+
# Segment or Point (if `point` is on this linear entity.)
|
|
223
|
+
#
|
|
224
|
+
def perpendicular_segment(point)
|
|
225
|
+
raise TypeError, 'Must pass only Point.' unless point.is_a?(Point)
|
|
226
|
+
|
|
227
|
+
return point if self.contains?(point)
|
|
228
|
+
|
|
229
|
+
l = self.perpendicular_line(point)
|
|
230
|
+
p = Line.new(self.p1, self.p2).intersection(l).first
|
|
231
|
+
|
|
232
|
+
Segment.new(point, p)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# The slope of this linear entity, or infinity if vertical.
|
|
236
|
+
#
|
|
237
|
+
# Returns:
|
|
238
|
+
# number or BigDecimal('Infinity')
|
|
239
|
+
#
|
|
240
|
+
def slope
|
|
241
|
+
return @slope if defined?(@slope)
|
|
242
|
+
|
|
243
|
+
dx = p1.x - p2.x
|
|
244
|
+
dy = p1.y - p2.y
|
|
245
|
+
|
|
246
|
+
if dy == 0
|
|
247
|
+
@slope = 0.0
|
|
248
|
+
elsif dx == 0
|
|
249
|
+
@slope = BigDecimal('Infinity')
|
|
250
|
+
else
|
|
251
|
+
@slope = dy / dx
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
@slope
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Test whether the point `other` lies in the positive span of `self`.
|
|
258
|
+
# A point x is 'in front' of a point y if x.dot(y) >= 0.
|
|
259
|
+
#
|
|
260
|
+
# Return
|
|
261
|
+
# -1 if `other` is behind `self.p1`,
|
|
262
|
+
# 0 if `other` is `self.p1`
|
|
263
|
+
# 1 if `other` is in front of `self.p1`.
|
|
264
|
+
#
|
|
265
|
+
def span_test(other)
|
|
266
|
+
raise TypeError, 'Must pass only Point.' unless other.is_a?(Point)
|
|
267
|
+
return 0 if self.p1 == other
|
|
268
|
+
|
|
269
|
+
rel_pos = other - self.p1
|
|
270
|
+
return 1 if self.direction.to_point.dot(rel_pos) > 0
|
|
271
|
+
|
|
272
|
+
return -1
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Project a point onto this linear entity.
|
|
276
|
+
#
|
|
277
|
+
# Parameters:
|
|
278
|
+
# Point
|
|
279
|
+
#
|
|
280
|
+
# Returns:
|
|
281
|
+
# Point
|
|
282
|
+
#
|
|
283
|
+
def projection_point(p)
|
|
284
|
+
Point.project(p - p1, self.direction.to_point) + p1
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
private
|
|
288
|
+
|
|
289
|
+
def intersect_parallel_rays(ray1, ray2)
|
|
290
|
+
if ray1.direction.dot(ray2.direction) > 0
|
|
291
|
+
# rays point in the same direction
|
|
292
|
+
# so return the one that is "in front"
|
|
293
|
+
return [ray2] if ray1.span_test(ray2.p1) >= 0
|
|
294
|
+
return [ray1]
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# rays point in opposite directions
|
|
298
|
+
st = ray1.span_test(ray2.p1)
|
|
299
|
+
return [] if st < 0
|
|
300
|
+
return [ray2.p1] if st == 0
|
|
301
|
+
|
|
302
|
+
[Segment.new(ray1.p1, ray2.p1)]
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def intersect_parallel_ray_and_segment(ray, seg)
|
|
306
|
+
st1 = ray.span_test(seg.p1)
|
|
307
|
+
st2 = ray.span_test(seg.p2)
|
|
308
|
+
|
|
309
|
+
if st1 < 0 && st2 < 0
|
|
310
|
+
return []
|
|
311
|
+
elsif st1 >= 0 && st2 >= 0
|
|
312
|
+
return [seg]
|
|
313
|
+
elsif st1 >= 0 # st2 < 0
|
|
314
|
+
return [ray.p1] if ray.p1 == seg.p1
|
|
315
|
+
return [Segment.new(ray.p1, seg.p1)]
|
|
316
|
+
elsif st2 >= 0 # st1 < 0
|
|
317
|
+
return [ray.p1] if ray.p1 == seg.p2
|
|
318
|
+
return [Segment.new(ray.p1, seg.p2)]
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def intersect_parallel_segments(seg1, seg2)
|
|
323
|
+
return [seg2] if seg1.contains?(seg2)
|
|
324
|
+
return [seg1] if seg2.contains?(seg1)
|
|
325
|
+
|
|
326
|
+
# direct the segments so they're oriented the same way
|
|
327
|
+
if seg1.direction.dot(seg2.direction) < 0
|
|
328
|
+
seg2 = Segment.new(seg2.p2, seg2.p1)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# order the segments so seg1 is "behind" seg2
|
|
332
|
+
if seg1.span_test(seg2.p1) < 0
|
|
333
|
+
seg1, seg2 = seg2, seg1
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
return [] if seg2.span_test(seg1.p2) < 0
|
|
337
|
+
return [seg2.p1] if seg2.p1 == seg1.p2
|
|
338
|
+
|
|
339
|
+
[Segment.new(seg2.p1, seg1.p2)]
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def check_input_points!
|
|
343
|
+
@p1 = Point.new(p1[0], p1[1]) if p1.is_a?(Array)
|
|
344
|
+
raise TypeError, "Point should be array or instance of class Point." unless p1.is_a?(Point)
|
|
345
|
+
|
|
346
|
+
@p2 = Point.new(p2[0], p2[1]) if p2.is_a?(Array)
|
|
347
|
+
raise TypeError, "Point should be array or instance of class Point." unless p2.is_a?(Point)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def validate!
|
|
351
|
+
raise ArgumentError, "Segment requires two unique Points." if p1 == p2
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
end
|