perfect-shape 0.5.4 → 0.5.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 +6 -0
- data/README.md +8 -4
- data/VERSION +1 -1
- data/lib/perfect_shape/arc.rb +102 -2
- data/lib/perfect_shape/cubic_bezier_curve.rb +2 -2
- data/lib/perfect_shape/ellipse.rb +2 -2
- data/lib/perfect_shape/line.rb +2 -2
- data/lib/perfect_shape/point.rb +2 -2
- data/lib/perfect_shape/polygon.rb +2 -2
- data/lib/perfect_shape/quadratic_bezier_curve.rb +6 -6
- data/lib/perfect_shape/rectangle.rb +2 -2
- 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: f32696a2e0ecd7c1aceb16d9211b38e977b4dd15c7ba57acb8ac99a769d75036
|
4
|
+
data.tar.gz: 50c458cfcf134ce86bc11e0b22f1650b2faf2146c79c288d7b2501a2d7d9264c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e15378328df2eaad3769ce087118f7990fcb834963cbc56b738ea1cc292a92df0f86d77506e1a2f94985bc03211b5436b9965e8b6b5c97c80f9d7ad59f6cb20
|
7
|
+
data.tar.gz: cb64f0b3951ca4628f96e265070a88684060be6ffa38b5a45deea611bce0b2d20b72e43ef6c5d70b969925a31fe0017f4ec84407927f23a1b7824e0352bc9732
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Perfect Shape 0.5.
|
1
|
+
# Perfect Shape 0.5.5
|
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.5
|
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.5'
|
24
24
|
```
|
25
25
|
|
26
26
|
And, run:
|
@@ -253,7 +253,7 @@ Includes `PerfectShape::MultiPoint`
|
|
253
253
|
|
254
254
|
- `::tag(coord, low, high)`: Determine where coord lies with respect to the range from low to high. It is assumed that low < high. The return value is one of the 5 values BELOW, LOWEDGE, INSIDE, HIGHEDGE, or ABOVE.
|
255
255
|
- `::eqn(val, c1, cp, c2)`: Fill an array with the coefficients of the parametric equation in t, ready for solving against val with solve_quadratic. We currently have: val = Py(t) = C1*(1-t)^2 + 2*CP*t*(1-t) + C2*t^2 = C1 - 2*C1*t + C1*t^2 + 2*CP*t - 2*CP*t^2 + C2*t^2 = C1 + (2*CP - 2*C1)*t + (C1 - 2*CP + C2)*t^2; 0 = (C1 - val) + (2*CP - 2*C1)*t + (C1 - 2*CP + C2)*t^2; 0 = C + Bt + At^2; C = C1 - val; B = 2*CP - 2*C1; A = C1 - 2*CP + C2
|
256
|
-
- `::solve_quadratic(eqn)`: Solves the quadratic whose coefficients are in the eqn array and places the non-complex roots into the res array, returning the number of roots. The quadratic solved is represented by the equation: <pre>eqn = {C, B, A}; ax^2 + bx + c = 0</pre> A return value of
|
256
|
+
- `::solve_quadratic(eqn)`: Solves the quadratic whose coefficients are in the eqn array and places the non-complex roots into the res array, returning the number of roots. The quadratic solved is represented by the equation: <pre>eqn = {C, B, A}; ax^2 + bx + c = 0</pre> A return value of `-1` is used to distinguish a constant equation, which might be always 0 or never 0, from an equation that has no zeroes.
|
257
257
|
- `::eval_quadratic(vals, num, include0, include1, inflect, c1, ctrl, c2)`: Evaluate the t values in the first num slots of the vals[] array and place the evaluated values back into the same array. Only evaluate t values that are within the range <, >, including the 0 and 1 ends of the range iff the include0 or include1 booleans are true. If an "inflection" equation is handed in, then any points which represent a point of inflection for that quadratic equation are also ignored.
|
258
258
|
- `::new(points: [])`: constructs a quadratic bézier curve with three `points` (start point, control point, and end point) as `Array` of `Array`s of `[x,y]` pairs or flattened `Array` of alternating x and y coordinates
|
259
259
|
- `#points`: points (start point, control point, and end point)
|
@@ -477,6 +477,8 @@ Open Arc | Chord Arc | Pie Arc
|
|
477
477
|
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
478
478
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
479
479
|
- `#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 an arc shape from its outline more successfully
|
480
|
+
- `#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
|
481
|
+
- `#contain_angle?(angle)`: returns `true` if the angle is within the angular extents of the arc and `false` otherwise
|
480
482
|
|
481
483
|
Example:
|
482
484
|
|
@@ -600,6 +602,7 @@ Extends `PerfectShape::Arc`
|
|
600
602
|
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
601
603
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
602
604
|
- `#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 an ellipse shape from its outline more successfully
|
605
|
+
- `#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
|
603
606
|
|
604
607
|
Example:
|
605
608
|
|
@@ -661,6 +664,7 @@ Extends `PerfectShape::Ellipse`
|
|
661
664
|
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
662
665
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
663
666
|
- `#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 circle shape from its outline more successfully
|
667
|
+
- `#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
|
664
668
|
|
665
669
|
Example:
|
666
670
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.5.
|
1
|
+
0.5.5
|
data/lib/perfect_shape/arc.rb
CHANGED
@@ -141,8 +141,8 @@ module PerfectShape
|
|
141
141
|
# @param x The X coordinate of the point to test.
|
142
142
|
# @param y The Y coordinate of the point to test.
|
143
143
|
#
|
144
|
-
# @return
|
145
|
-
# the arc,
|
144
|
+
# @return true if the point lies within the bound of
|
145
|
+
# the arc, false if the point lies outside of the
|
146
146
|
# arc's bounds.
|
147
147
|
def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
|
148
148
|
x, y = Point.normalize_point(x_or_point, y)
|
@@ -212,5 +212,105 @@ module PerfectShape
|
|
212
212
|
|
213
213
|
(angle >= 0.0) && (angle < ang_ext)
|
214
214
|
end
|
215
|
+
|
216
|
+
def intersect?(rectangle)
|
217
|
+
x = rectangle.x
|
218
|
+
y = rectangle.y
|
219
|
+
w = rectangle.width
|
220
|
+
h = rectangle.height
|
221
|
+
aw = self.width
|
222
|
+
ah = self.height
|
223
|
+
|
224
|
+
return false if w <= 0 || h <= 0 || aw <= 0 || ah <= 0
|
225
|
+
ext = self.extent
|
226
|
+
return false if ext == 0
|
227
|
+
|
228
|
+
ax = self.x
|
229
|
+
ay = self.y
|
230
|
+
axw = ax + aw
|
231
|
+
ayh = ay + ah
|
232
|
+
xw = x + w
|
233
|
+
yh = y + h
|
234
|
+
|
235
|
+
# check bbox
|
236
|
+
return false if x >= axw || y >= ayh || xw <= ax || yh <= ay
|
237
|
+
|
238
|
+
# extract necessary data
|
239
|
+
axc = self.center_x
|
240
|
+
ayc = self.center_y
|
241
|
+
sx, sy = self.start_point
|
242
|
+
ex, ey = self.end_point
|
243
|
+
|
244
|
+
# Try to catch rectangles that intersect arc in areas
|
245
|
+
# outside of rectagle with left top corner coordinates
|
246
|
+
# (min(center x, start point x, end point x),
|
247
|
+
# min(center y, start point y, end point y))
|
248
|
+
# and rigth bottom corner coordinates
|
249
|
+
# (max(center x, start point x, end point x),
|
250
|
+
# max(center y, start point y, end point y)).
|
251
|
+
# So we'll check axis segments outside of rectangle above.
|
252
|
+
if ayc >= y && ayc <= yh # 0 and 180
|
253
|
+
return true if (sx < xw && ex < xw && axc < xw &&
|
254
|
+
axw > x && contain_angle?(0)) ||
|
255
|
+
(sx > x && ex > x && axc > x &&
|
256
|
+
ax < xw && contain_angle?(180))
|
257
|
+
end
|
258
|
+
if axc >= x && axc <= xw # 90 and 270
|
259
|
+
return true if (sy > y && ey > y && ayc > y &&
|
260
|
+
ay < yh && contain_angle?(90)) ||
|
261
|
+
(sy < yh && ey < yh && ayc < yh &&
|
262
|
+
ayh > y && contain_angle?(270))
|
263
|
+
end
|
264
|
+
|
265
|
+
# For PIE we should check intersection with pie slices
|
266
|
+
# also we should do the same for arcs with extent is greater
|
267
|
+
# than 180, because we should cover case of rectangle, which
|
268
|
+
# situated between center of arc and chord, but does not
|
269
|
+
# intersect the chord.
|
270
|
+
rect = PerfectShape::Rectangle.new(x: x, y: y, width: w, height: h)
|
271
|
+
if type == :pie || ext.abs > 180
|
272
|
+
# for PIE: try to find intersections with pie slices
|
273
|
+
line1 = PerfectShape::Line.new(points: [[axc, ayc], [sx, sy]])
|
274
|
+
line2 = PerfectShape::Line.new(points: [[axc, ayc], [ex, ey]])
|
275
|
+
return true if line1.intersect?(rect) || line2.intersect?(rect)
|
276
|
+
else
|
277
|
+
# for CHORD and OPEN: try to find intersections with chord
|
278
|
+
line = PerfectShape::Line.new(points: [[sx, sy], [ex, ey]])
|
279
|
+
return true if line.intersect?(rect)
|
280
|
+
end
|
281
|
+
|
282
|
+
# finally check the rectangle corners inside the arc
|
283
|
+
return true if contain?(x, y) || contain?(x + w, y) ||
|
284
|
+
contain?(x, y + h) || contain?(x + w, y + h)
|
285
|
+
|
286
|
+
false
|
287
|
+
end
|
288
|
+
|
289
|
+
# Returns the starting point of the arc. This point is the
|
290
|
+
# intersection of the ray from the center defined by the
|
291
|
+
# starting angle and the elliptical boundary of the arc.
|
292
|
+
#
|
293
|
+
# @return An (x,y) pair Array object representing the
|
294
|
+
# x,y coordinates of the starting point of the arc.
|
295
|
+
def start_point
|
296
|
+
angle = Math.degrees_to_radians(-self.start)
|
297
|
+
x = self.x + (Math.cos(angle) * 0.5 + 0.5) * self.width
|
298
|
+
y = self.y + (Math.sin(angle) * 0.5 + 0.5) * self.height
|
299
|
+
[x, y]
|
300
|
+
end
|
301
|
+
|
302
|
+
# Returns the ending point of the arc. This point is the
|
303
|
+
# intersection of the ray from the center defined by the
|
304
|
+
# starting angle plus the angular extent of the arc and the
|
305
|
+
# elliptical boundary of the arc.
|
306
|
+
#
|
307
|
+
# @return An (x,y) pair Array object representing the
|
308
|
+
# x,y coordinates of the ending point of the arc.
|
309
|
+
def end_point
|
310
|
+
angle = Math.degrees_to_radians(-self.start - self.extent)
|
311
|
+
x = self.x + (Math.cos(angle) * 0.5 + 0.5) * self.width
|
312
|
+
y = self.y + (Math.sin(angle) * 0.5 + 0.5) * self.height
|
313
|
+
[x, y]
|
314
|
+
end
|
215
315
|
end
|
216
316
|
end
|
@@ -82,8 +82,8 @@ module PerfectShape
|
|
82
82
|
# @param x The X coordinate of the point to test.
|
83
83
|
# @param y The Y coordinate of the point to test.
|
84
84
|
#
|
85
|
-
# @return
|
86
|
-
# the cubic bézier curve,
|
85
|
+
# @return true if the point lies within the bound of
|
86
|
+
# the cubic bézier curve, false if the point lies outside of the
|
87
87
|
# cubic bézier curve's bounds.
|
88
88
|
def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
|
89
89
|
x, y = Point.normalize_point(x_or_point, y)
|
@@ -59,8 +59,8 @@ module PerfectShape
|
|
59
59
|
# @param x The X coordinate of the point to test.
|
60
60
|
# @param y The Y coordinate of the point to test.
|
61
61
|
#
|
62
|
-
# @return
|
63
|
-
# the ellipse,
|
62
|
+
# @return true if the point lies within the bound of
|
63
|
+
# the ellipse, false if the point lies outside of the
|
64
64
|
# ellipse's bounds.
|
65
65
|
def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
|
66
66
|
# This is implemented again even though super would have just worked to have an optimized algorithm for Ellipse.
|
data/lib/perfect_shape/line.rb
CHANGED
@@ -209,8 +209,8 @@ module PerfectShape
|
|
209
209
|
# @param y The Y coordinate of the point to test.
|
210
210
|
# @param distance_tolerance The distance from line to tolerate (0 by default)
|
211
211
|
#
|
212
|
-
# @return
|
213
|
-
# the line,
|
212
|
+
# @return true if the point lies within the bound of
|
213
|
+
# the line, false if the point lies outside of the
|
214
214
|
# line's bounds.
|
215
215
|
def contain?(x_or_point, y = nil, outline: true, distance_tolerance: 0)
|
216
216
|
x, y = Point.normalize_point(x_or_point, y)
|
data/lib/perfect_shape/point.rb
CHANGED
@@ -80,8 +80,8 @@ module PerfectShape
|
|
80
80
|
# @param y The Y coordinate of the point to test.
|
81
81
|
# @param distance_tolerance The distance from point to tolerate (0 by default)
|
82
82
|
#
|
83
|
-
# @return
|
84
|
-
#
|
83
|
+
# @return true if the point is close enough within distance tolerance,
|
84
|
+
# false if the point is too far.
|
85
85
|
def contain?(x_or_point, y = nil, outline: true, distance_tolerance: 0)
|
86
86
|
x, y = Point.normalize_point(x_or_point, y)
|
87
87
|
return unless x && y
|
@@ -34,8 +34,8 @@ module PerfectShape
|
|
34
34
|
# @param x The X coordinate of the point to test.
|
35
35
|
# @param y The Y coordinate of the point to test.
|
36
36
|
#
|
37
|
-
# @return
|
38
|
-
# the polygon,
|
37
|
+
# @return true if the point lies within the bound of
|
38
|
+
# the polygon, false if the point lies outside of the
|
39
39
|
# polygon's bounds.
|
40
40
|
def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
|
41
41
|
x, y = Point.normalize_point(x_or_point, y)
|
@@ -94,22 +94,22 @@ module PerfectShape
|
|
94
94
|
]
|
95
95
|
end
|
96
96
|
|
97
|
-
# Solves the quadratic whose coefficients are in the
|
98
|
-
# array and places the non-complex roots into the
|
97
|
+
# Solves the quadratic whose coefficients are in the eqn
|
98
|
+
# array and places the non-complex roots into the res
|
99
99
|
# array, returning the number of roots.
|
100
100
|
# The quadratic solved is represented by the equation:
|
101
101
|
# <pre>
|
102
102
|
# eqn = {C, B, A}
|
103
103
|
# ax^2 + bx + c = 0
|
104
104
|
# </pre>
|
105
|
-
# A return value of
|
105
|
+
# A return value of -1 is used to distinguish a constant
|
106
106
|
# equation, which might be always 0 or never 0, from an equation that
|
107
107
|
# has no zeroes.
|
108
108
|
# @param eqn the specified array of coefficients to use to solve
|
109
109
|
# the quadratic equation
|
110
110
|
# @param res the array that contains the non-complex roots
|
111
111
|
# resulting from the solution of the quadratic equation
|
112
|
-
# @return the number of roots, or
|
112
|
+
# @return the number of roots, or -1 if the equation is
|
113
113
|
# a constant.
|
114
114
|
def solve_quadratic(eqn, res)
|
115
115
|
a = eqn[2]
|
@@ -187,8 +187,8 @@ module PerfectShape
|
|
187
187
|
# @param x The X coordinate of the point to test.
|
188
188
|
# @param y The Y coordinate of the point to test.
|
189
189
|
#
|
190
|
-
# @return
|
191
|
-
# the quadratic bézier curve,
|
190
|
+
# @return true if the point lies within the bound of
|
191
|
+
# the quadratic bézier curve, false if the point lies outside of the
|
192
192
|
# quadratic bézier curve's bounds.
|
193
193
|
def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
|
194
194
|
x, y = Point.normalize_point(x_or_point, y)
|
@@ -48,8 +48,8 @@ module PerfectShape
|
|
48
48
|
# @param x The X coordinate of the point to test.
|
49
49
|
# @param y The Y coordinate of the point to test.
|
50
50
|
#
|
51
|
-
# @return
|
52
|
-
# the rectangle,
|
51
|
+
# @return true if the point lies within the bound of
|
52
|
+
# the rectangle, false if the point lies outside of the
|
53
53
|
# rectangle's bounds.
|
54
54
|
def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
|
55
55
|
x, y = Point.normalize_point(x_or_point, y)
|
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.5 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.5"
|
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-22"
|
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.5
|
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-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: equalizer
|