perfect-shape 0.5.2 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +7 -3
- data/VERSION +1 -1
- data/lib/perfect_shape/cubic_bezier_curve.rb +113 -0
- data/lib/perfect_shape/line.rb +86 -14
- data/lib/perfect_shape/rectangle.rb +2 -0
- data/perfect-shape.gemspec +3 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d107db62b52ceb89ecb570f5bb1b77a8adde17cd6d2000e55d0ede5c88c37170
|
4
|
+
data.tar.gz: a785b27dd723cb869b64b37eb3bceef4e159f78e37b0a95816c05c340b7def8a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aef22594d36b2dec69b4685b3040530b838f7eb37749e74ec9ad58a9586b55a302298c68a9ef7f11639975d5284899975059aa3e33b76506ef5e749be7ea11a7
|
7
|
+
data.tar.gz: 9a59946ad112068bd8e74ff49f843c58b2e73b07216d5ac6d06e5a3911ce006587cc895ba961b5e1eacb26bd997c43aebbba1a3640efee753fab093e4131087d
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Perfect Shape 0.5.
|
1
|
+
# Perfect Shape 0.5.3
|
2
2
|
## Geometric Algorithms
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/perfect-shape.svg)](http://badge.fury.io/rb/perfect-shape)
|
4
4
|
[![Test](https://github.com/AndyObtiva/perfect-shape/actions/workflows/ruby.yml/badge.svg)](https://github.com/AndyObtiva/perfect-shape/actions/workflows/ruby.yml)
|
@@ -14,13 +14,13 @@ To ensure high accuracy, this library does all its mathematical operations with
|
|
14
14
|
Run:
|
15
15
|
|
16
16
|
```
|
17
|
-
gem install perfect-shape -v 0.5.
|
17
|
+
gem install perfect-shape -v 0.5.3
|
18
18
|
```
|
19
19
|
|
20
20
|
Or include in Bundler `Gemfile`:
|
21
21
|
|
22
22
|
```ruby
|
23
|
-
gem 'perfect-shape', '~> 0.5.
|
23
|
+
gem 'perfect-shape', '~> 0.5.3'
|
24
24
|
```
|
25
25
|
|
26
26
|
And, run:
|
@@ -224,6 +224,7 @@ Includes `PerfectShape::MultiPoint`
|
|
224
224
|
- `#intersect?(rectangle)`: Returns `true` if intersecting with interior of rectangle or `false` otherwise. This is useful for GUI optimization checks of whether a shape appears in a GUI viewport rectangle and needs redrawing
|
225
225
|
- `#relative_counterclockwise(x_or_point, y=nil)`: Returns an indicator of where the specified point (px,py) lies with respect to the line segment from (x1,y1) to (x2,y2). The return value can be either 1, -1, or 0 and indicates in which direction the specified line must pivot around its first end point, (x1,y1), in order to point at the specified point (px,py). A return value of 1 indicates that the line segment must turn in the direction that takes the positive X axis towards the negative Y axis. In the default coordinate system, this direction is counterclockwise. A return value of -1 indicates that the line segment must turn in the direction that takes the positive X axis towards the positive Y axis. In the default coordinate system, this direction is clockwise. A return value of 0 indicates that the point lies exactly on the line segment. Note that an indicator value of 0 is rare and not useful for determining collinearity because of floating point rounding issues. If the point is colinear with the line segment, but not between the end points, then the value will be -1 if the point lies “beyond (x1,y1)” or 1 if the point lies “beyond (x2,y2)”.
|
226
226
|
- `#point_distance(x_or_point, y=nil)`: Returns the distance from a point to a line segment.
|
227
|
+
- `#rect_crossings(rxmin, rymin, rxmax, rymax, crossings = 0)`: rectangle crossings (adds to crossings arg)
|
227
228
|
|
228
229
|
Example:
|
229
230
|
|
@@ -318,11 +319,14 @@ Includes `PerfectShape::MultiPoint`
|
|
318
319
|
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape (bounding box only guarantees that the shape is within it, but it might be bigger than the shape)
|
319
320
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
320
321
|
- `#contain?(x_or_point, y=nil, outline: false, distance_tolerance: 0)`: checks if point is inside when `outline` is `false` or if point is on the outline when `outline` is `true`. `distance_tolerance` can be used as a fuzz factor when `outline` is `true`, for example, to help GUI users mouse-click-select a cubic bezier curve shape from its outline more successfully
|
322
|
+
- `#intersect?(rectangle)`: Returns `true` if intersecting with interior of rectangle or `false` otherwise. This is useful for GUI optimization checks of whether a shape appears in a GUI viewport rectangle and needs redrawing
|
321
323
|
- `#curve_center_point`: point at the center of the curve outline (not the center of the bounding box area like `center_x` and `center_y`)
|
322
324
|
- `#curve_center_x`: point x coordinate at the center of the curve outline (not the center of the bounding box area like `center_x` and `center_y`)
|
323
325
|
- `#curve_center_y`: point y coordinate at the center of the curve outline (not the center of the bounding box area like `center_x` and `center_y`)
|
324
326
|
- `#subdivisions(level=1)`: subdivides cubic bezier curve at its center into into 2 cubic bezier curves by default, or more if `level` of recursion is specified. The resulting number of subdivisions is `2` to the power of `level`.
|
325
327
|
- `#point_distance(x_or_point, y=nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)`: calculates distance from point to curve segment. It does so by subdividing curve into smaller curves and checking against the curve center points until the distance is less than `minimum_distance_threshold`, to avoid being an overly costly operation.
|
328
|
+
- `#rectangle_crossings(rectangle)`: rectangle crossings (used to determine rectangle interior intersection), optimized to check if line represented by cubic bezier curve crosses the rectangle first, and if not then perform expensive check with `#rect_crossings`
|
329
|
+
- `#rect_crossings(rxmin, rymin, rxmax, rymax, level, crossings = 0)`: rectangle crossings (adds to crossings arg)
|
326
330
|
|
327
331
|
Example:
|
328
332
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.5.
|
1
|
+
0.5.3
|
@@ -211,5 +211,118 @@ module PerfectShape
|
|
211
211
|
last_minimum_distance
|
212
212
|
end
|
213
213
|
end
|
214
|
+
|
215
|
+
def intersect?(rectangle)
|
216
|
+
x = rectangle.x
|
217
|
+
y = rectangle.y
|
218
|
+
w = rectangle.width
|
219
|
+
h = rectangle.height
|
220
|
+
|
221
|
+
# Trivially reject non-existant rectangles
|
222
|
+
return false if w <= 0 || h <= 0
|
223
|
+
|
224
|
+
num_crossings = rectangle_crossings(rectangle)
|
225
|
+
# the intended return value is
|
226
|
+
# num_crossings != 0 || num_crossings == Rectangle::RECT_INTERSECTS
|
227
|
+
# but if (num_crossings != 0) num_crossings == INTERSECTS won't matter
|
228
|
+
# and if !(num_crossings != 0) then num_crossings == 0, so
|
229
|
+
# num_crossings != RECT_INTERSECT
|
230
|
+
num_crossings != 0
|
231
|
+
end
|
232
|
+
|
233
|
+
def rectangle_crossings(rectangle)
|
234
|
+
x = rectangle.x
|
235
|
+
y = rectangle.y
|
236
|
+
w = rectangle.width
|
237
|
+
h = rectangle.height
|
238
|
+
x1 = points[0][0]
|
239
|
+
y1 = points[0][1]
|
240
|
+
x2 = points[3][0]
|
241
|
+
y2 = points[3][1]
|
242
|
+
|
243
|
+
crossings = 0
|
244
|
+
if !(x1 == x2 && y1 == y2)
|
245
|
+
line = PerfectShape::Line.new(points: [[x1, y1], [x2, y2]])
|
246
|
+
crossings = line.rect_crossings(x, y, x+w, y+h, crossings)
|
247
|
+
return crossings if crossings == Rectangle::RECT_INTERSECTS
|
248
|
+
end
|
249
|
+
# we call this with the curve's direction reversed, because we wanted
|
250
|
+
# to call rectCrossingsForLine first, because it's cheaper.
|
251
|
+
rect_crossings(x, y, x+w, y+h, 0, crossings)
|
252
|
+
end
|
253
|
+
|
254
|
+
# Accumulate the number of times the cubic crosses the shadow
|
255
|
+
# extending to the right of the rectangle. See the comment
|
256
|
+
# for the RECT_INTERSECTS constant for more complete details.
|
257
|
+
#
|
258
|
+
# crossings arg is the initial crossings value to add to (useful
|
259
|
+
# in cases where you want to accumulate crossings from multiple
|
260
|
+
# shapes)
|
261
|
+
def rect_crossings(rxmin, rymin, rxmax, rymax, level, crossings = 0)
|
262
|
+
x0 = points[0][0]
|
263
|
+
y0 = points[0][1]
|
264
|
+
xc0 = points[1][0]
|
265
|
+
yc0 = points[1][1]
|
266
|
+
xc1 = points[2][0]
|
267
|
+
yc1 = points[2][1]
|
268
|
+
x1 = points[3][0]
|
269
|
+
y1 = points[3][1]
|
270
|
+
|
271
|
+
return crossings if y0 >= rymax && yc0 >= rymax && yc1 >= rymax && y1 >= rymax
|
272
|
+
return crossings if y0 <= rymin && yc0 <= rymin && yc1 <= rymin && y1 <= rymin
|
273
|
+
return crossings if x0 <= rxmin && xc0 <= rxmin && xc1 <= rxmin && x1 <= rxmin
|
274
|
+
if x0 >= rxmax && xc0 >= rxmax && xc1 >= rxmax && x1 >= rxmax
|
275
|
+
# Cubic is entirely to the right of the rect
|
276
|
+
# and the vertical range of the 4 Y coordinates of the cubic
|
277
|
+
# overlaps the vertical range of the rect by a non-empty amount
|
278
|
+
# We now judge the crossings solely based on the line segment
|
279
|
+
# connecting the endpoints of the cubic.
|
280
|
+
# Note that we may have 0, 1, or 2 crossings as the control
|
281
|
+
# points may be causing the Y range intersection while the
|
282
|
+
# two endpoints are entirely above or below.
|
283
|
+
if y0 < y1
|
284
|
+
# y-increasing line segment...
|
285
|
+
crossings += 1 if (y0 <= rymin && y1 > rymin)
|
286
|
+
crossings += 1 if (y0 < rymax && y1 >= rymax)
|
287
|
+
elsif y1 < y0
|
288
|
+
# y-decreasing line segment...
|
289
|
+
crossings -= 1 if (y1 <= rymin && y0 > rymin)
|
290
|
+
crossings -= 1 if (y1 < rymax && y0 >= rymax)
|
291
|
+
end
|
292
|
+
return crossings
|
293
|
+
end
|
294
|
+
# The intersection of ranges is more complicated
|
295
|
+
# First do trivial INTERSECTS rejection of the cases
|
296
|
+
# where one of the endpoints is inside the rectangle.
|
297
|
+
return Rectangle::RECT_INTERSECTS if ((x0 > rxmin && x0 < rxmax && y0 > rymin && y0 < rymax) ||
|
298
|
+
(x1 > rxmin && x1 < rxmax && y1 > rymin && y1 < rymax))
|
299
|
+
|
300
|
+
# Otherwise, subdivide and look for one of the cases above.
|
301
|
+
# double precision only has 52 bits of mantissa
|
302
|
+
return PerfectShape::Line.new(points: [[x0, y0], [x1, y1]]).rect_crossings(rxmin, rymin, rxmax, rymax, crossings) if (level > 52)
|
303
|
+
xmid = BigDecimal((xc0 + xc1).to_s) / 2
|
304
|
+
ymid = BigDecimal((yc0 + yc1).to_s) / 2
|
305
|
+
xc0 = BigDecimal((x0 + xc0).to_s) / 2
|
306
|
+
yc0 = BigDecimal((y0 + yc0).to_s) / 2
|
307
|
+
xc1 = BigDecimal((xc1 + x1).to_s) / 2
|
308
|
+
yc1 = BigDecimal((yc1 + y1).to_s) / 2
|
309
|
+
xc0m = BigDecimal((xc0 + xmid).to_s) / 2
|
310
|
+
yc0m = BigDecimal((yc0 + ymid).to_s) / 2
|
311
|
+
xmc1 = BigDecimal((xmid + xc1).to_s) / 2
|
312
|
+
ymc1 = BigDecimal((ymid + yc1).to_s) / 2
|
313
|
+
xmid = BigDecimal((xc0m + xmc1).to_s) / 2
|
314
|
+
ymid = BigDecimal((yc0m + ymc1).to_s) / 2
|
315
|
+
# [xy]mid are NaN if any of [xy]c0m or [xy]mc1 are NaN
|
316
|
+
# [xy]c0m or [xy]mc1 are NaN if any of [xy][c][01] are NaN
|
317
|
+
# These values are also NaN if opposing infinities are added
|
318
|
+
return 0 if xmid.nan? || ymid.nan?
|
319
|
+
cubic1 = CubicBezierCurve.new(points: [[x0, y0], [xc0, yc0], [xc0m, yc0m], [xmid, ymid]])
|
320
|
+
crossings = cubic1.rect_crossings(rxmin, rymin, rxmax, rymax, level + 1, crossings)
|
321
|
+
if crossings != Rectangle::RECT_INTERSECTS
|
322
|
+
cubic2 = CubicBezierCurve.new(points: [[xmid, ymid], [xmc1, ymc1], [xc1, yc1], [x1, y1]])
|
323
|
+
crossings = cubic2.rect_crossings(rxmin, rymin, rxmax, rymax, level + 1, crossings)
|
324
|
+
end
|
325
|
+
crossings
|
326
|
+
end
|
214
327
|
end
|
215
328
|
end
|
data/lib/perfect_shape/line.rb
CHANGED
@@ -54,11 +54,11 @@ module PerfectShape
|
|
54
54
|
# coordinates with respect to the line segment formed
|
55
55
|
# by the first two specified coordinates.
|
56
56
|
def relative_counterclockwise(x1, y1, x2, y2, px, py)
|
57
|
-
x2 -= x1
|
58
|
-
y2 -= y1
|
59
|
-
px -= x1
|
60
|
-
py -= y1
|
61
|
-
ccw = px * y2 - py * x2
|
57
|
+
x2 -= x1
|
58
|
+
y2 -= y1
|
59
|
+
px -= x1
|
60
|
+
py -= y1
|
61
|
+
ccw = px * y2 - py * x2
|
62
62
|
if ccw == 0.0
|
63
63
|
# The point is colinear, classify based on which side of
|
64
64
|
# the segment the point falls on. We can calculate a
|
@@ -66,7 +66,7 @@ module PerfectShape
|
|
66
66
|
# segment - a negative value indicates the point projects
|
67
67
|
# outside of the segment in the direction of the particular
|
68
68
|
# endpoint used as the origin for the projection.
|
69
|
-
ccw = px * x2 + py * y2
|
69
|
+
ccw = px * x2 + py * y2
|
70
70
|
if ccw > 0.0
|
71
71
|
# Reverse the projection to be relative to the original x2,y2
|
72
72
|
# x2 and y2 are simply negated.
|
@@ -75,13 +75,13 @@ module PerfectShape
|
|
75
75
|
# Since we really want to get a positive answer when the
|
76
76
|
# point is "beyond (x2,y2)", then we want to calculate
|
77
77
|
# the inverse anyway - thus we leave x2 & y2 negated.
|
78
|
-
px -= x2
|
79
|
-
py -= y2
|
80
|
-
ccw = px * x2 + py * y2
|
78
|
+
px -= x2
|
79
|
+
py -= y2
|
80
|
+
ccw = px * x2 + py * y2
|
81
81
|
ccw = 0.0 if ccw < 0.0
|
82
82
|
end
|
83
83
|
end
|
84
|
-
(ccw < 0.0) ? -1 : ((ccw > 0.0) ? 1 : 0)
|
84
|
+
(ccw < 0.0) ? -1 : ((ccw > 0.0) ? 1 : 0)
|
85
85
|
end
|
86
86
|
|
87
87
|
# Returns the square of the distance from a point to a line segment.
|
@@ -120,12 +120,12 @@ module PerfectShape
|
|
120
120
|
# px,py becomes relative vector from x1,y1 to test point
|
121
121
|
px -= x1
|
122
122
|
py -= y1
|
123
|
-
dot_product = px * x2 + py * y2
|
123
|
+
dot_product = px * x2 + py * y2
|
124
124
|
if dot_product <= 0.0
|
125
125
|
# px,py is on the side of x1,y1 away from x2,y2
|
126
126
|
# distance to segment is length of px,py vector
|
127
127
|
# "length of its (clipped) projection" is now 0.0
|
128
|
-
projected_length_square = BigDecimal('0.0')
|
128
|
+
projected_length_square = BigDecimal('0.0')
|
129
129
|
else
|
130
130
|
# switch to backwards vectors relative to x2,y2
|
131
131
|
# x2,y2 are already the negative of x1,y1=>x2,y2
|
@@ -191,10 +191,10 @@ module PerfectShape
|
|
191
191
|
def point_crossings(x1, y1, x2, y2, px, py)
|
192
192
|
return 0 if (py < y1 && py < y2)
|
193
193
|
return 0 if (py >= y1 && py >= y2)
|
194
|
-
# assert(y1 != y2)
|
194
|
+
# assert(y1 != y2)
|
195
195
|
return 0 if (px >= x1 && px >= x2)
|
196
196
|
return ((y1 < y2) ? 1 : -1) if (px < x1 && px < x2)
|
197
|
-
xintercept = x1 + (py - y1) * (x2 - x1) / (y2 - y1)
|
197
|
+
xintercept = x1 + (py - y1) * (x2 - x1) / (y2 - y1)
|
198
198
|
return 0 if (px >= xintercept)
|
199
199
|
(y1 < y2) ? 1 : -1
|
200
200
|
end
|
@@ -242,6 +242,78 @@ module PerfectShape
|
|
242
242
|
Line.point_crossings(points[0][0], points[0][1], points[1][0], points[1][1], x, y)
|
243
243
|
end
|
244
244
|
|
245
|
+
# Accumulate the number of times the line crosses the shadow
|
246
|
+
# extending to the right of the rectangle. See the comment
|
247
|
+
# for the Rectangle::RECT_INTERSECTS constant for more complete details.
|
248
|
+
#
|
249
|
+
# crossings arg is the initial crossings value to add to (useful
|
250
|
+
# in cases where you want to accumulate crossings from multiple
|
251
|
+
# shapes)
|
252
|
+
def rect_crossings(rxmin, rymin, rxmax, rymax, crossings = 0)
|
253
|
+
x0 = points[0][0]
|
254
|
+
y0 = points[0][1]
|
255
|
+
x1 = points[1][0]
|
256
|
+
y1 = points[1][1]
|
257
|
+
return crossings if y0 >= rymax && y1 >= rymax
|
258
|
+
return crossings if y0 <= rymin && y1 <= rymin
|
259
|
+
return crossings if x0 <= rxmin && x1 <= rxmin
|
260
|
+
if x0 >= rxmax && x1 >= rxmax
|
261
|
+
# Line is entirely to the right of the rect
|
262
|
+
# and the vertical ranges of the two overlap by a non-empty amount
|
263
|
+
# Thus, this line segment is partially in the "right-shadow"
|
264
|
+
# Path may have done a complete crossing
|
265
|
+
# Or path may have entered or exited the right-shadow
|
266
|
+
if y0 < y1
|
267
|
+
# y-increasing line segment...
|
268
|
+
# We know that y0 < rymax and y1 > rymin
|
269
|
+
crossings += 1 if (y0 <= rymin)
|
270
|
+
crossings += 1 if (y1 >= rymax)
|
271
|
+
elsif y1 < y0
|
272
|
+
# y-decreasing line segment...
|
273
|
+
# We know that y1 < rymax and y0 > rymin
|
274
|
+
crossings -= 1 if (y1 <= rymin)
|
275
|
+
crossings -= 1 if (y0 >= rymax)
|
276
|
+
end
|
277
|
+
return crossings
|
278
|
+
end
|
279
|
+
# Remaining case:
|
280
|
+
# Both x and y ranges overlap by a non-empty amount
|
281
|
+
# First do trivial INTERSECTS rejection of the cases
|
282
|
+
# where one of the endpoints is inside the rectangle.
|
283
|
+
return Rectangle::RECT_INTERSECTS if ((x0 > rxmin && x0 < rxmax && y0 > rymin && y0 < rymax) ||
|
284
|
+
(x1 > rxmin && x1 < rxmax && y1 > rymin && y1 < rymax))
|
285
|
+
# Otherwise calculate the y intercepts and see where
|
286
|
+
# they fall with respect to the rectangle
|
287
|
+
xi0 = x0
|
288
|
+
if y0 < rymin
|
289
|
+
xi0 += ((rymin - y0) * (x1 - x0) / (y1 - y0))
|
290
|
+
elsif y0 > rymax
|
291
|
+
xi0 += ((rymax - y0) * (x1 - x0) / (y1 - y0))
|
292
|
+
end
|
293
|
+
xi1 = x1
|
294
|
+
if y1 < rymin
|
295
|
+
xi1 += ((rymin - y1) * (x0 - x1) / (y0 - y1))
|
296
|
+
elsif y1 > rymax
|
297
|
+
xi1 += ((rymax - y1) * (x0 - x1) / (y0 - y1))
|
298
|
+
end
|
299
|
+
return crossings if xi0 <= rxmin && xi1 <= rxmin
|
300
|
+
if xi0 >= rxmax && xi1 >= rxmax
|
301
|
+
if y0 < y1
|
302
|
+
# y-increasing line segment...
|
303
|
+
# We know that y0 < rymax and y1 > rymin
|
304
|
+
crossings += 1 if (y0 <= rymin)
|
305
|
+
crossings += 1 if (y1 >= rymax)
|
306
|
+
elsif y1 < y0
|
307
|
+
# y-decreasing line segment...
|
308
|
+
# We know that y1 < rymax and y0 > rymin
|
309
|
+
crossings -= 1 if (y1 <= rymin)
|
310
|
+
crossings -= 1 if (y0 >= rymax)
|
311
|
+
end
|
312
|
+
return crossings
|
313
|
+
end
|
314
|
+
Rectangle::RECT_INTERSECTS
|
315
|
+
end
|
316
|
+
|
245
317
|
def intersect?(rectangle)
|
246
318
|
require 'perfect_shape/rectangle'
|
247
319
|
x1 = points[0][0]
|
data/perfect-shape.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: perfect-shape 0.5.
|
5
|
+
# stub: perfect-shape 0.5.3 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "perfect-shape".freeze
|
9
|
-
s.version = "0.5.
|
9
|
+
s.version = "0.5.3"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib".freeze]
|
13
13
|
s.authors = ["Andy Maleh".freeze]
|
14
|
-
s.date = "2022-01-
|
14
|
+
s.date = "2022-01-21"
|
15
15
|
s.description = "Perfect Shape is a collection of pure Ruby geometric algorithms that are mostly useful for GUI manipulation like checking viewport rectangle intersection or containment of a mouse click point in popular geometry shapes such as rectangle, square, arc (open, chord, and pie), ellipse, circle, polygon, and paths containing lines, quadratic b\u00E9zier curves, and cubic bezier curves, potentially with affine transforms applied like translation, scale, rotation, shear/skew, and inversion (including both Ray Casting Algorithm, aka Even-odd Rule, and Winding Number Algorithm, aka Nonzero Rule). Additionally, it contains some purely mathematical algorithms like IEEEremainder (also known as IEEE-754 remainder).".freeze
|
16
16
|
s.email = "andy.am@gmail.com".freeze
|
17
17
|
s.extra_rdoc_files = [
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: perfect-shape
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Maleh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-01-
|
11
|
+
date: 2022-01-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: equalizer
|