perfect-shape 0.3.1 → 0.3.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -1
- data/LICENSE.txt +1 -1
- data/README.md +99 -34
- data/VERSION +1 -1
- data/lib/perfect-shape.rb +1 -1
- data/lib/perfect_shape/arc.rb +4 -3
- data/lib/perfect_shape/circle.rb +1 -1
- data/lib/perfect_shape/composite_shape.rb +4 -3
- data/lib/perfect_shape/cubic_bezier_curve.rb +123 -28
- data/lib/perfect_shape/ellipse.rb +1 -1
- data/lib/perfect_shape/line.rb +8 -8
- data/lib/perfect_shape/math.rb +21 -0
- data/lib/perfect_shape/multi_point.rb +8 -2
- data/lib/perfect_shape/path.rb +63 -13
- data/lib/perfect_shape/point.rb +7 -6
- data/lib/perfect_shape/point_location.rb +1 -1
- data/lib/perfect_shape/polygon.rb +8 -4
- data/lib/perfect_shape/quadratic_bezier_curve.rb +173 -86
- data/lib/perfect_shape/rectangle.rb +12 -5
- data/lib/perfect_shape/rectangular_shape.rb +1 -1
- data/lib/perfect_shape/shape.rb +13 -3
- data/lib/perfect_shape/square.rb +1 -1
- data/perfect-shape.gemspec +3 -3
- metadata +2 -2
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2021 Andy Maleh
|
1
|
+
# Copyright (c) 2021-2022 Andy Maleh
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
# a copy of this software and associated documentation files (the
|
@@ -51,18 +51,18 @@ module PerfectShape
|
|
51
51
|
end
|
52
52
|
# double precision only has 52 bits of mantissa
|
53
53
|
return PerfectShape::Line.point_crossings(x1, y1, x2, y2, px, py) if (level > 52)
|
54
|
-
xmid = BigDecimal((xc1 + xc2).to_s) / 2
|
55
|
-
ymid = BigDecimal((yc1 + yc2).to_s) / 2
|
56
|
-
xc1 = BigDecimal((x1 + xc1).to_s) / 2
|
57
|
-
yc1 = BigDecimal((y1 + yc1).to_s) / 2
|
58
|
-
xc2 = BigDecimal((xc2 + x2).to_s) / 2
|
59
|
-
yc2 = BigDecimal((yc2 + y2).to_s) / 2
|
60
|
-
xc1m = BigDecimal((xc1 + xmid).to_s) / 2
|
61
|
-
yc1m = BigDecimal((yc1 + ymid).to_s) / 2
|
62
|
-
xmc1 = BigDecimal((xmid + xc2).to_s) / 2
|
63
|
-
ymc1 = BigDecimal((ymid + yc2).to_s) / 2
|
64
|
-
xmid = BigDecimal((xc1m + xmc1).to_s) / 2
|
65
|
-
ymid = BigDecimal((yc1m + ymc1).to_s) / 2
|
54
|
+
xmid = BigDecimal((xc1 + xc2).to_s) / 2
|
55
|
+
ymid = BigDecimal((yc1 + yc2).to_s) / 2
|
56
|
+
xc1 = BigDecimal((x1 + xc1).to_s) / 2
|
57
|
+
yc1 = BigDecimal((y1 + yc1).to_s) / 2
|
58
|
+
xc2 = BigDecimal((xc2 + x2).to_s) / 2
|
59
|
+
yc2 = BigDecimal((yc2 + y2).to_s) / 2
|
60
|
+
xc1m = BigDecimal((xc1 + xmid).to_s) / 2
|
61
|
+
yc1m = BigDecimal((yc1 + ymid).to_s) / 2
|
62
|
+
xmc1 = BigDecimal((xmid + xc2).to_s) / 2
|
63
|
+
ymc1 = BigDecimal((ymid + yc2).to_s) / 2
|
64
|
+
xmid = BigDecimal((xc1m + xmc1).to_s) / 2
|
65
|
+
ymid = BigDecimal((yc1m + ymc1).to_s) / 2
|
66
66
|
# [xy]mid are NaN if any of [xy]c0m or [xy]mc1 are NaN
|
67
67
|
# [xy]c0m or [xy]mc1 are NaN if any of [xy][c][01] are NaN
|
68
68
|
# These values are also NaN if opposing infinities are added
|
@@ -75,6 +75,8 @@ module PerfectShape
|
|
75
75
|
include MultiPoint
|
76
76
|
include Equalizer.new(:points)
|
77
77
|
|
78
|
+
OUTLINE_MINIMUM_DISTANCE_THRESHOLD = BigDecimal('0.001')
|
79
|
+
|
78
80
|
# Checks if cubic bézier curve contains point (two-number Array or x, y args)
|
79
81
|
#
|
80
82
|
# @param x The X coordinate of the point to test.
|
@@ -83,24 +85,30 @@ module PerfectShape
|
|
83
85
|
# @return {@code true} if the point lies within the bound of
|
84
86
|
# the cubic bézier curve, {@code false} if the point lies outside of the
|
85
87
|
# cubic bézier curve's bounds.
|
86
|
-
def contain?(x_or_point, y = nil)
|
88
|
+
def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
|
87
89
|
x, y = normalize_point(x_or_point, y)
|
88
90
|
return unless x && y
|
89
91
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
92
|
+
if outline
|
93
|
+
distance_tolerance = BigDecimal(distance_tolerance.to_s)
|
94
|
+
minimum_distance_threshold = OUTLINE_MINIMUM_DISTANCE_THRESHOLD + distance_tolerance
|
95
|
+
point_distance(x, y, minimum_distance_threshold: minimum_distance_threshold) < minimum_distance_threshold
|
96
|
+
else
|
97
|
+
# Either x or y was infinite or NaN.
|
98
|
+
# A NaN always produces a negative response to any test
|
99
|
+
# and Infinity values cannot be "inside" any path so
|
100
|
+
# they should return false as well.
|
101
|
+
return false if (!(x * 0.0 + y * 0.0 == 0.0))
|
102
|
+
# We count the "Y" crossings to determine if the point is
|
103
|
+
# inside the curve bounded by its closing line.
|
104
|
+
x1 = points[0][0]
|
105
|
+
y1 = points[0][1]
|
106
|
+
x2 = points[3][0]
|
107
|
+
y2 = points[3][1]
|
108
|
+
line = PerfectShape::Line.new(points: [[x1, y1], [x2, y2]])
|
109
|
+
crossings = line.point_crossings(x, y) + point_crossings(x, y)
|
110
|
+
(crossings & 1) == 1
|
111
|
+
end
|
104
112
|
end
|
105
113
|
|
106
114
|
# Calculates the number of times the cubic bézier curve
|
@@ -116,5 +124,92 @@ module PerfectShape
|
|
116
124
|
return unless x && y
|
117
125
|
CubicBezierCurve.point_crossings(points[0][0], points[0][1], points[1][0], points[1][1], points[2][0], points[2][1], points[3][0], points[3][1], x, y, level)
|
118
126
|
end
|
127
|
+
|
128
|
+
# The center point on the outline of the curve
|
129
|
+
def curve_center_point
|
130
|
+
subdivisions.last.points[0]
|
131
|
+
end
|
132
|
+
|
133
|
+
# The center point x on the outline of the curve
|
134
|
+
def curve_center_x
|
135
|
+
subdivisions.last.points[0][0]
|
136
|
+
end
|
137
|
+
|
138
|
+
# The center point y on the outline of the curve
|
139
|
+
def curve_center_y
|
140
|
+
subdivisions.last.points[0][1]
|
141
|
+
end
|
142
|
+
|
143
|
+
# Subdivides CubicBezierCurve exactly at its curve center
|
144
|
+
# returning 2 CubicBezierCurve's as a two-element Array by default
|
145
|
+
#
|
146
|
+
# Optional `level` parameter specifies the level of recursions to
|
147
|
+
# perform to get more subdivisions. The number of resulting
|
148
|
+
# subdivisions is 2 to the power of `level` (e.g. 2 subdivisions
|
149
|
+
# for level=1, 4 subdivisions for level=2, and 8 subdivisions for level=3)
|
150
|
+
def subdivisions(level = 1)
|
151
|
+
level -= 1 # consume 1 level
|
152
|
+
|
153
|
+
x1 = points[0][0]
|
154
|
+
y1 = points[0][1]
|
155
|
+
ctrlx1 = points[1][0]
|
156
|
+
ctrly1 = points[1][1]
|
157
|
+
ctrlx2 = points[2][0]
|
158
|
+
ctrly2 = points[2][1]
|
159
|
+
x2 = points[3][0]
|
160
|
+
y2 = points[3][1]
|
161
|
+
centerx = BigDecimal((ctrlx1 + ctrlx2).to_s) / 2
|
162
|
+
centery = BigDecimal((ctrly1 + ctrly2).to_s) / 2
|
163
|
+
ctrlx1 = BigDecimal((x1 + ctrlx1).to_s) / 2
|
164
|
+
ctrly1 = BigDecimal((y1 + ctrly1).to_s) / 2
|
165
|
+
ctrlx2 = BigDecimal((x2 + ctrlx2).to_s) / 2
|
166
|
+
ctrly2 = BigDecimal((y2 + ctrly2).to_s) / 2
|
167
|
+
ctrlx12 = BigDecimal((ctrlx1 + centerx).to_s) / 2
|
168
|
+
ctrly12 = BigDecimal((ctrly1 + centery).to_s) / 2
|
169
|
+
ctrlx21 = BigDecimal((ctrlx2 + centerx).to_s) / 2
|
170
|
+
ctrly21 = BigDecimal((ctrly2 + centery).to_s) / 2
|
171
|
+
centerx = BigDecimal((ctrlx12 + ctrlx21).to_s) / 2
|
172
|
+
centery = BigDecimal((ctrly12 + ctrly21).to_s) / 2
|
173
|
+
|
174
|
+
first_curve = CubicBezierCurve.new(points: [x1, y1, ctrlx1, ctrly1, ctrlx12, ctrly12, centerx, centery])
|
175
|
+
second_curve = CubicBezierCurve.new(points: [centerx, centery, ctrlx21, ctrly21, ctrlx2, ctrly2, x2, y2])
|
176
|
+
default_subdivisions = [first_curve, second_curve]
|
177
|
+
|
178
|
+
if level == 0
|
179
|
+
default_subdivisions
|
180
|
+
else
|
181
|
+
default_subdivisions.map { |curve| curve.subdivisions(level) }.flatten
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def point_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)
|
186
|
+
x, y = normalize_point(x_or_point, y)
|
187
|
+
return unless x && y
|
188
|
+
|
189
|
+
point = Point.new(x, y)
|
190
|
+
current_curve = self
|
191
|
+
minimum_distance = point.point_distance(curve_center_point)
|
192
|
+
last_minimum_distance = minimum_distance + 1 # start bigger to ensure going through loop once at least
|
193
|
+
while minimum_distance >= minimum_distance_threshold && minimum_distance < last_minimum_distance
|
194
|
+
curve1, curve2 = current_curve.subdivisions
|
195
|
+
curve1_center_point = curve1.curve_center_point
|
196
|
+
distance1 = point.point_distance(curve1_center_point)
|
197
|
+
curve2_center_point = curve2.curve_center_point
|
198
|
+
distance2 = point.point_distance(curve2_center_point)
|
199
|
+
last_minimum_distance = minimum_distance
|
200
|
+
if distance1 < distance2
|
201
|
+
minimum_distance = distance1
|
202
|
+
current_curve = curve1
|
203
|
+
else
|
204
|
+
minimum_distance = distance2
|
205
|
+
current_curve = curve2
|
206
|
+
end
|
207
|
+
end
|
208
|
+
if minimum_distance < minimum_distance_threshold
|
209
|
+
minimum_distance
|
210
|
+
else
|
211
|
+
last_minimum_distance
|
212
|
+
end
|
213
|
+
end
|
119
214
|
end
|
120
215
|
end
|
data/lib/perfect_shape/line.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2021 Andy Maleh
|
1
|
+
# Copyright (c) 2021-2022 Andy Maleh
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
# a copy of this software and associated documentation files (the
|
@@ -104,7 +104,7 @@ module PerfectShape
|
|
104
104
|
# measured against the specified line segment
|
105
105
|
# @return a double value that is the square of the distance from the
|
106
106
|
# specified point to the specified line segment.
|
107
|
-
def
|
107
|
+
def point_distance_square(x1, y1,
|
108
108
|
x2, y2,
|
109
109
|
px, py)
|
110
110
|
x1 = BigDecimal(x1.to_s)
|
@@ -177,10 +177,10 @@ module PerfectShape
|
|
177
177
|
# measured against the specified line segment
|
178
178
|
# @return a double value that is the distance from the specified point
|
179
179
|
# to the specified line segment.
|
180
|
-
def
|
180
|
+
def point_distance(x1, y1,
|
181
181
|
x2, y2,
|
182
182
|
px, py)
|
183
|
-
BigDecimal(::Math.sqrt(
|
183
|
+
BigDecimal(::Math.sqrt(point_distance_square(x1, y1, x2, y2, px, py)).to_s)
|
184
184
|
end
|
185
185
|
|
186
186
|
# Calculates the number of times the line from (x1,y1) to (x2,y2)
|
@@ -212,17 +212,17 @@ module PerfectShape
|
|
212
212
|
# @return {@code true} if the point lies within the bound of
|
213
213
|
# the line, {@code false} if the point lies outside of the
|
214
214
|
# line's bounds.
|
215
|
-
def contain?(x_or_point, y = nil, outline:
|
215
|
+
def contain?(x_or_point, y = nil, outline: true, distance_tolerance: 0)
|
216
216
|
x, y = normalize_point(x_or_point, y)
|
217
217
|
return unless x && y
|
218
218
|
distance_tolerance = BigDecimal(distance_tolerance.to_s)
|
219
|
-
|
219
|
+
point_distance(x, y) <= distance_tolerance
|
220
220
|
end
|
221
221
|
|
222
|
-
def
|
222
|
+
def point_distance(x_or_point, y = nil)
|
223
223
|
x, y = normalize_point(x_or_point, y)
|
224
224
|
return unless x && y
|
225
|
-
Line.
|
225
|
+
Line.point_distance(points[0][0], points[0][1], points[1][0], points[1][1], x, y)
|
226
226
|
end
|
227
227
|
|
228
228
|
def relative_counterclockwise(x_or_point, y = nil)
|
data/lib/perfect_shape/math.rb
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
# Copyright (c) 2021-2022 Andy Maleh
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
1
22
|
module PerfectShape
|
2
23
|
# Perfect Shape Math utility methods
|
3
24
|
#
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2021 Andy Maleh
|
1
|
+
# Copyright (c) 2021-2022 Andy Maleh
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
# a copy of this software and associated documentation files (the
|
@@ -37,7 +37,13 @@ module PerfectShape
|
|
37
37
|
ys = the_points.each_with_index.select {|n, i| i.odd?}.map(&:first)
|
38
38
|
the_points = xs.zip(ys)
|
39
39
|
end
|
40
|
-
@points = the_points.map
|
40
|
+
@points = the_points.map do |pair|
|
41
|
+
[
|
42
|
+
pair.first.is_a?(BigDecimal) ? pair.first : BigDecimal(pair.first.to_s),
|
43
|
+
pair.last.is_a?(BigDecimal) ? pair.last : BigDecimal(pair.last.to_s)
|
44
|
+
]
|
45
|
+
end
|
46
|
+
@points
|
41
47
|
end
|
42
48
|
|
43
49
|
def min_x
|
data/lib/perfect_shape/path.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2021 Andy Maleh
|
1
|
+
# Copyright (c) 2021-2022 Andy Maleh
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
# a copy of this software and associated documentation files (the
|
@@ -114,21 +114,26 @@ module PerfectShape
|
|
114
114
|
# @return true if the point lies within the bound of
|
115
115
|
# the path or false if the point lies outside of the
|
116
116
|
# path's bounds.
|
117
|
-
def contain?(x_or_point, y = nil)
|
117
|
+
def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
|
118
118
|
x, y = normalize_point(x_or_point, y)
|
119
119
|
return unless x && y
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
return false if shapes.count < 2
|
124
|
-
mask = winding_rule == :wind_non_zero ? -1 : 1
|
125
|
-
(point_crossings(x, y) & mask) != 0
|
120
|
+
|
121
|
+
if outline
|
122
|
+
disconnected_shapes.any? {|shape| shape.contain?(x, y, outline: true, distance_tolerance: distance_tolerance) }
|
126
123
|
else
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
124
|
+
if (x * 0.0 + y * 0.0) == 0.0
|
125
|
+
# N * 0.0 is 0.0 only if N is finite.
|
126
|
+
# Here we know that both x and y are finite.
|
127
|
+
return false if shapes.count < 2
|
128
|
+
mask = winding_rule == :wind_non_zero ? -1 : 1
|
129
|
+
(point_crossings(x, y) & mask) != 0
|
130
|
+
else
|
131
|
+
# Either x or y was infinite or NaN.
|
132
|
+
# A NaN always produces a negative response to any test
|
133
|
+
# and Infinity values cannot be "inside" any path so
|
134
|
+
# they should return false as well.
|
135
|
+
false
|
136
|
+
end
|
132
137
|
end
|
133
138
|
end
|
134
139
|
|
@@ -218,5 +223,50 @@ module PerfectShape
|
|
218
223
|
end
|
219
224
|
crossings
|
220
225
|
end
|
226
|
+
|
227
|
+
# Disconnected shapes have their start point filled in
|
228
|
+
# so that each shape does not depend on the previous shape
|
229
|
+
# to determine its start point.
|
230
|
+
#
|
231
|
+
# Also, if a point is followed by a non-point shape, it is removed
|
232
|
+
# since it is augmented to the following shape as its start point.
|
233
|
+
#
|
234
|
+
# Lastly, if the path is closed, an extra shape is
|
235
|
+
# added to represent the line connecting the last point to the first
|
236
|
+
def disconnected_shapes
|
237
|
+
initial_point = start_point = @shapes.first.to_a.map {|n| BigDecimal(n.to_s)}
|
238
|
+
final_point = nil
|
239
|
+
the_disconnected_shapes = @shapes.drop(1).map do |shape|
|
240
|
+
case shape
|
241
|
+
when Point
|
242
|
+
disconnected_shape = Point.new(*shape.to_a)
|
243
|
+
start_point = shape.to_a
|
244
|
+
final_point = disconnected_shape.to_a
|
245
|
+
nil
|
246
|
+
when Array
|
247
|
+
disconnected_shape = Point.new(*shape.map {|n| BigDecimal(n.to_s)})
|
248
|
+
start_point = shape.map {|n| BigDecimal(n.to_s)}
|
249
|
+
final_point = disconnected_shape.to_a
|
250
|
+
nil
|
251
|
+
when Line
|
252
|
+
disconnected_shape = Line.new(points: [start_point.to_a, shape.points.last])
|
253
|
+
start_point = shape.points.last.to_a
|
254
|
+
final_point = disconnected_shape.points.last.to_a
|
255
|
+
disconnected_shape
|
256
|
+
when QuadraticBezierCurve
|
257
|
+
disconnected_shape = QuadraticBezierCurve.new(points: [start_point.to_a] + shape.points)
|
258
|
+
start_point = shape.points.last.to_a
|
259
|
+
final_point = disconnected_shape.points.last.to_a
|
260
|
+
disconnected_shape
|
261
|
+
when CubicBezierCurve
|
262
|
+
disconnected_shape = CubicBezierCurve.new(points: [start_point.to_a] + shape.points)
|
263
|
+
start_point = shape.points.last.to_a
|
264
|
+
final_point = disconnected_shape.points.last.to_a
|
265
|
+
disconnected_shape
|
266
|
+
end
|
267
|
+
end
|
268
|
+
the_disconnected_shapes << Line.new(points: [final_point, initial_point]) if closed?
|
269
|
+
the_disconnected_shapes.compact
|
270
|
+
end
|
221
271
|
end
|
222
272
|
end
|
data/lib/perfect_shape/point.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2021 Andy Maleh
|
1
|
+
# Copyright (c) 2021-2022 Andy Maleh
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
# a copy of this software and associated documentation files (the
|
@@ -27,10 +27,10 @@ module PerfectShape
|
|
27
27
|
class Point < Shape
|
28
28
|
class << self
|
29
29
|
def point_distance(x, y, px, py)
|
30
|
-
x = BigDecimal(x.to_s)
|
31
|
-
y = BigDecimal(y.to_s)
|
32
|
-
px = BigDecimal(px.to_s)
|
33
|
-
py = BigDecimal(py.to_s)
|
30
|
+
x = x.is_a?(BigDecimal) ? x : BigDecimal(x.to_s)
|
31
|
+
y = y.is_a?(BigDecimal) ? y : BigDecimal(y.to_s)
|
32
|
+
px = px.is_a?(BigDecimal) ? px : BigDecimal(px.to_s)
|
33
|
+
py = py.is_a?(BigDecimal) ? py : BigDecimal(py.to_s)
|
34
34
|
BigDecimal(Math.sqrt((px - x)**2 + (py - y)**2).to_s)
|
35
35
|
end
|
36
36
|
end
|
@@ -67,7 +67,7 @@ module PerfectShape
|
|
67
67
|
#
|
68
68
|
# @return {@code true} if the point is close enough within distance tolerance,
|
69
69
|
# {@code false} if the point is too far.
|
70
|
-
def contain?(x_or_point, y = nil, outline:
|
70
|
+
def contain?(x_or_point, y = nil, outline: true, distance_tolerance: 0)
|
71
71
|
x, y = normalize_point(x_or_point, y)
|
72
72
|
return unless x && y
|
73
73
|
distance_tolerance = BigDecimal(distance_tolerance.to_s)
|
@@ -77,6 +77,7 @@ module PerfectShape
|
|
77
77
|
def point_distance(x_or_point, y = nil)
|
78
78
|
x, y = normalize_point(x_or_point, y)
|
79
79
|
return unless x && y
|
80
|
+
|
80
81
|
Point.point_distance(self.x, self.y, x, y)
|
81
82
|
end
|
82
83
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (c) 2021 Andy Maleh
|
1
|
+
# Copyright (c) 2021-2022 Andy Maleh
|
2
2
|
#
|
3
3
|
# Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
# a copy of this software and associated documentation files (the
|
@@ -41,9 +41,7 @@ module PerfectShape
|
|
41
41
|
x, y = normalize_point(x_or_point, y)
|
42
42
|
return unless x && y
|
43
43
|
if outline
|
44
|
-
|
45
|
-
Line.new(points: [[point1.first, point1.last], [point2.first, point2.last]]).contain?(x, y, distance_tolerance: distance_tolerance)
|
46
|
-
end
|
44
|
+
edges.any? { |edge| edge.contain?(x, y, distance_tolerance: distance_tolerance) }
|
47
45
|
else
|
48
46
|
npoints = points.count
|
49
47
|
xpoints = points.map(&:first)
|
@@ -117,5 +115,11 @@ module PerfectShape
|
|
117
115
|
(hits & 1) != 0
|
118
116
|
end
|
119
117
|
end
|
118
|
+
|
119
|
+
def edges
|
120
|
+
points.zip(points.rotate(1)).map do |point1, point2|
|
121
|
+
Line.new(points: [[point1.first, point1.last], [point2.first, point2.last]])
|
122
|
+
end
|
123
|
+
end
|
120
124
|
end
|
121
125
|
end
|