perfect-shape 0.3.1 → 0.3.5
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 +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
|