perfect-shape 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +43 -3
- data/LICENSE.txt +1 -1
- data/README.md +152 -32
- data/VERSION +1 -1
- data/lib/perfect-shape.rb +2 -2
- data/lib/perfect_shape/affine_transform.rb +235 -0
- data/lib/perfect_shape/arc.rb +5 -5
- data/lib/perfect_shape/circle.rb +1 -1
- data/lib/perfect_shape/composite_shape.rb +5 -4
- data/lib/perfect_shape/cubic_bezier_curve.rb +53 -46
- data/lib/perfect_shape/ellipse.rb +2 -3
- data/lib/perfect_shape/line.rb +13 -13
- data/lib/perfect_shape/math.rb +21 -0
- data/lib/perfect_shape/multi_point.rb +21 -6
- data/lib/perfect_shape/path.rb +65 -16
- data/lib/perfect_shape/point.rb +24 -8
- data/lib/perfect_shape/point_location.rb +1 -1
- data/lib/perfect_shape/polygon.rb +10 -6
- data/lib/perfect_shape/quadratic_bezier_curve.rb +176 -89
- data/lib/perfect_shape/rectangle.rb +14 -7
- data/lib/perfect_shape/rectangular_shape.rb +1 -1
- data/lib/perfect_shape/shape.rb +7 -16
- data/lib/perfect_shape/square.rb +1 -1
- data/perfect-shape.gemspec +5 -4
- metadata +8 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 13f257899f946daccf27fa951ed4208ce8143d2060b24fcb6198ba0e8f738646
|
4
|
+
data.tar.gz: 1f9a0d7a1f210da0ee0e7b88514352b7d90c7893b05fe9b84145672989cd8eae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2af7d835fdaf126b8ebb630fbc08400aab9a7b464c5d18ad3f09035f142940bb1db1fbe4c0a2a1bc33bf4a773f2ce920fb34e36d57a191143020cdd36357b483
|
7
|
+
data.tar.gz: 693a20f1d100e70a8d429c8c02fddcfbdb0b2f2507cff92a0ddbc8c962db7758be5cee94df954ba5a89bba7619bdd9b2ee95b31b17896289e31d90a22d7eeb53
|
data/CHANGELOG.md
CHANGED
@@ -1,11 +1,51 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 0.4.0
|
4
|
+
|
5
|
+
- `PerfectShape::AffineTransform#new`
|
6
|
+
- `PerfectShape::AffineTransform#==`
|
7
|
+
- `PerfectShape::AffineTransform#transform_point`
|
8
|
+
- `PerfectShape::AffineTransform#transform_points`
|
9
|
+
- `PerfectShape::AffineTransform#identity!` (alias: `reset!`)
|
10
|
+
- `PerfectShape::AffineTransform#invert!`
|
11
|
+
- `PerfectShape::AffineTransform#invertible?`
|
12
|
+
- `PerfectShape::AffineTransform#multiply!`
|
13
|
+
- `PerfectShape::AffineTransform#translate!`
|
14
|
+
- `PerfectShape::AffineTransform#scale!`
|
15
|
+
- `PerfectShape::AffineTransform#rotate!`
|
16
|
+
- `PerfectShape::AffineTransform#shear!` (alias: `skew!`)
|
17
|
+
- `PerfectShape::AffineTransform#clone`
|
18
|
+
- `PerfectShape::AffineTransform#inverse_transform_point`
|
19
|
+
- `PerfectShape::AffineTransform#inverse_transform_points`
|
20
|
+
|
21
|
+
## 0.3.5
|
22
|
+
|
23
|
+
- Check point containment in composite shape outline with distance tolerance (new method signature: `PerfectShape::CompositeShape#contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)`)
|
24
|
+
|
25
|
+
## 0.3.4
|
26
|
+
|
27
|
+
- Check point containment in path outline with distance tolerance (new method signature: `PerfectShape::Path#contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)`)
|
28
|
+
- `PerfectShape::Path#disconnected_shapes`: Disconnected shapes have their start point filled in so that each shape does not depend on the previous shape to determine its start point.
|
29
|
+
- `Shape#center_point` as `[center_x, center_y]`
|
30
|
+
- Rename `#point_segment_distance` to `#point_distance` everywhere
|
31
|
+
|
32
|
+
## 0.3.3
|
33
|
+
|
34
|
+
- Check point containment in quadratic bezier curve outline with distance tolerance (new method signature: `PerfectShape::QuadraticBezierCurve#contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)`)
|
35
|
+
- `PerfectShape::QuadraticBezierCurve#curve_center_point`, `PerfectShape::QuadraticBezierCurve#curve_center_x`, `PerfectShape::QuadraticBezierCurve#curve_center_y`
|
36
|
+
- `PerfectShape::QuadraticBezierCurve#subdivisions(level=1)`
|
37
|
+
- `PerfectShape::QuadraticBezierCurve#point_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)`
|
38
|
+
- `PerfectShape::Polygon#edges` returns edges of polygon as `PerfectShape::Line` objects
|
39
|
+
- `PerfectShape::Rectangle#edges` returns edges of rectangle as `PerfectShape::Line` objects
|
40
|
+
- `PerfectShape::Square#edges` returns edges of square as `PerfectShape::Line` objects
|
41
|
+
- Rename `number` arg to `level` in `CubicBezierCurve#subdivisions(level=1)`, making it signify the level of subdivision recursion to perform.
|
42
|
+
|
3
43
|
## 0.3.2
|
4
44
|
|
5
45
|
- Check point containment in cubic bezier curve outline with distance tolerance (new method signature: `PerfectShape::CubicBezierCurve#contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)`)
|
6
46
|
- `PerfectShape::CubicBezierCurve#curve_center_point`, `PerfectShape::CubicBezierCurve#curve_center_x`, `PerfectShape::CubicBezierCurve#curve_center_y`
|
7
|
-
- `PerfectShape::CubicBezierCurve#subdivisions(
|
8
|
-
- `PerfectShape::CubicBezierCurve#
|
47
|
+
- `PerfectShape::CubicBezierCurve#subdivisions(level=1)`
|
48
|
+
- `PerfectShape::CubicBezierCurve#point_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)`
|
9
49
|
|
10
50
|
## 0.3.1
|
11
51
|
|
@@ -69,7 +109,7 @@
|
|
69
109
|
- `PerfectShape::Line`
|
70
110
|
- `PerfectShape::Line#contain?(x_or_point, y=nil)`
|
71
111
|
- `PerfectShape::Line#relative_counterclockwise`
|
72
|
-
- `PerfectShape::Line#
|
112
|
+
- `PerfectShape::Line#point_distance`
|
73
113
|
- Update `PerfectShape::Math::radians_to_degrees`, `PerfectShape::Math::degrees_to_radians`, and `PerfectShape::Math::normalize_degrees` to normalize numbers to `BigDecimal`
|
74
114
|
|
75
115
|
## 0.0.7
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
# Perfect Shape 0.
|
1
|
+
# Perfect Shape 0.4.0
|
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)
|
5
5
|
|
6
|
-
[`PerfectShape`](https://rubygems.org/gems/perfect-shape) is a collection of pure Ruby geometric algorithms that are mostly useful for GUI (Graphical User Interface) manipulation like checking containment of a mouse click [point](#perfectshapepoint) in popular geometry shapes such as [rectangle](#perfectshaperectangle), [square](#perfectshapesquare), [arc](#perfectshapearc) (open, chord, and pie), [ellipse](#perfectshapeellipse), [circle](#perfectshapecircle), [polygon](#perfectshapepolygon), and [paths](#perfectshapepath) containing [lines](#perfectshapeline), [quadratic bézier curves](#perfectshapequadraticbeziercurve), and [cubic bezier curves](#perfectshapecubicbeziercurve) (including both [Ray Casting Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm), aka [Even-odd Rule](https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule), and [Winding Number Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon#Winding_number_algorithm), aka [Nonzero Rule](https://en.wikipedia.org/wiki/Nonzero-rule)).
|
6
|
+
[`PerfectShape`](https://rubygems.org/gems/perfect-shape) is a collection of pure Ruby geometric algorithms that are mostly useful for GUI (Graphical User Interface) manipulation like checking containment of a mouse click [point](#perfectshapepoint) in popular geometry shapes such as [rectangle](#perfectshaperectangle), [square](#perfectshapesquare), [arc](#perfectshapearc) (open, chord, and pie), [ellipse](#perfectshapeellipse), [circle](#perfectshapecircle), [polygon](#perfectshapepolygon), and [paths](#perfectshapepath) containing [lines](#perfectshapeline), [quadratic bézier curves](#perfectshapequadraticbeziercurve), and [cubic bezier curves](#perfectshapecubicbeziercurve), potentially with [affine transforms](#perfectshapeaffinetransform) applied like translation, scale, rotation, shear/skew, and inversion (including both [Ray Casting Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm), aka [Even-odd Rule](https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule), and [Winding Number Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon#Winding_number_algorithm), aka [Nonzero Rule](https://en.wikipedia.org/wiki/Nonzero-rule)).
|
7
7
|
|
8
8
|
Additionally, [`PerfectShape::Math`](#perfectshapemath) contains some purely mathematical algorithms, like [IEEE 754-1985 Remainder](https://en.wikipedia.org/wiki/IEEE_754-1985).
|
9
9
|
|
@@ -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.
|
17
|
+
gem install perfect-shape -v 0.4.0
|
18
18
|
```
|
19
19
|
|
20
20
|
Or include in Bundler `Gemfile`:
|
21
21
|
|
22
22
|
```ruby
|
23
|
-
gem 'perfect-shape', '~> 0.
|
23
|
+
gem 'perfect-shape', '~> 0.4.0'
|
24
24
|
```
|
25
25
|
|
26
26
|
And, run:
|
@@ -38,7 +38,7 @@ Module
|
|
38
38
|
- `::degrees_to_radians(angle)`: converts degrees to radians
|
39
39
|
- `::radians_to_degrees(angle)`: converts radians to degrees
|
40
40
|
- `::normalize_degrees(angle)`: normalizes the specified angle into the range -180 to 180.
|
41
|
-
- `::ieee_remainder(x, y)` (alias: `ieee754_remainder`): [IEEE 754-1985 Remainder](https://en.wikipedia.org/wiki/IEEE_754-1985) (different from standard
|
41
|
+
- `::ieee_remainder(x, y)` (alias: `ieee754_remainder`): [IEEE 754-1985 Remainder](https://en.wikipedia.org/wiki/IEEE_754-1985) (different from standard `%` modulo operator as it operates on floats and could return a negative result)
|
42
42
|
|
43
43
|
### `PerfectShape::Shape`
|
44
44
|
|
@@ -52,11 +52,11 @@ This is a base class for all shapes. It is not meant to be used directly. Subcla
|
|
52
52
|
- `#max_y`: max y
|
53
53
|
- `#width`: width
|
54
54
|
- `#height`: height
|
55
|
+
- `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
|
55
56
|
- `#center_x`: center x
|
56
57
|
- `#center_y`: center y
|
57
58
|
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height just as those of shape
|
58
59
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
59
|
-
- `#normalize_point(x_or_point, y = nil)`: normalizes point into an `Array` of `[x,y]` coordinates
|
60
60
|
- `#contain?(x_or_point, y=nil, outline: false, distance_tolerance: 0)`: checks if point is inside if `outline` is `false` or if point is on the outline if `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 shape from its outline more successfully
|
61
61
|
|
62
62
|
### `PerfectShape::PointLocation`
|
@@ -84,9 +84,71 @@ Includes `PerfectShape::PointLocation`
|
|
84
84
|
- `#min_y`: min y
|
85
85
|
- `#max_x`: max x
|
86
86
|
- `#max_y`: max y
|
87
|
-
|
88
|
-
|
89
|
-
|
87
|
+
|
88
|
+
### `PerfectShape::AffineTransform`
|
89
|
+
|
90
|
+
Class
|
91
|
+
|
92
|
+
Affine transforms have the following matrix:
|
93
|
+
|
94
|
+
[ xxp xyp xt ]<br>
|
95
|
+
[ yxp yyp yt ]
|
96
|
+
|
97
|
+
The matrix is used to transform (x,y) point coordinates as follows:
|
98
|
+
|
99
|
+
[ xxp xyp xt ] * [x] = [ xxp * x + xyp * y + xt ]<br>
|
100
|
+
[ yxp yyp yt ] * [y] = [ yxp * x + yyp * y + yt ]
|
101
|
+
|
102
|
+
`xxp` is the x coordinate x product (`m11`)<br>
|
103
|
+
`xyp` is the x coordinate y product (`m12`)<br>
|
104
|
+
`yxp` is the y coordinate x product (`m21`)<br>
|
105
|
+
`yyp` is the y coordinate y product (`m22`)<br>
|
106
|
+
`xt` is the x coordinate translation (`m13`)<br>
|
107
|
+
`yt` is the y coordinate translation (`m23`)
|
108
|
+
|
109
|
+
Affine transform mutation operations ending with `!` can be chained as they all return `self`.
|
110
|
+
|
111
|
+
- `::new(xxp_element = nil, xyp_element = nil, yxp_element = nil, yyp_element = nil, xt_element = nil, yt_element = nil,
|
112
|
+
xxp: nil, xyp: nil, yxp: nil, yyp: nil, xt: nil, yt: nil,
|
113
|
+
m11: nil, m12: nil, m21: nil, m22: nil, m13: nil, m23: nil)`:
|
114
|
+
The constructor accepts either the (x,y)-operation related argument/kwarg names or traditional matrix element kwarg names. If no arguments are supplied, it constructs an identity matrix (i.e. like calling `::new(xxp: 1, xyp: 0, yxp: 0, yyp: 1, xt: 0, yt: 0)`).
|
115
|
+
- `#matrix_3d`: Returns Ruby `Matrix` object representing affine transform in 3D (used internally for performing multiplication)
|
116
|
+
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
117
|
+
- `#identity!` (alias: `reset!`): Resets to identity matrix (i.e. like calling `::new(xxp: 1, xyp: 0, yxp: 0, yyp: 1, xt: 0, yt: 0)`)
|
118
|
+
- `#invertible?` Returns `true` if matrix is invertible and `false` otherwise
|
119
|
+
- `#invert!`: Inverts affine transform matrix if invertible or raises an error otherwise
|
120
|
+
- `#multiply!(other)`: Multiplies affine transform with another affine transform, storing resulting changes in matrix elements
|
121
|
+
- `#translate!(x_or_point, y=nil)`: Translates affine transform with (x, y) translation values
|
122
|
+
- `#scale!(x_or_point, y=nil)`: Scales affine transform with (x, y) scale values
|
123
|
+
- `#rotate!(degrees)`: Rotates by angle degrees counter-clockwise if angle value is positive or clockwise if angle value is negative. Note that it returns very close approximate results for rotations that are 90/180/270 degrees (good enough for inverse-transform GUI point containment checks needed when checking if mouse-click-point is inside a transformed shape).
|
124
|
+
- `#shear!(x_or_point, y=nil)`: Shears by x and y factors
|
125
|
+
- `#clone`: Returns a new AffineTransform with the same matrix elements
|
126
|
+
- `#transform_point(x_or_point, y=nil)`: returns `[xxp * x + xyp * y + xt, yxp * x + yyp * y + yt]`. Note that result is a close approximation, but should be good enough for GUI mouse-click-point containment checks.
|
127
|
+
- `#transform_points(*xy_coordinates_or_points)`: returns `Array` of (x,y) pair `Array`s transformed with `#transform_point` method
|
128
|
+
- `#inverse_transform_point(x_or_point, y=nil)`: returns inverse transform of a point (x,y) coordinates (clones self and inverts clone, and then transforms point). Note that result is a close approximation, but should be good enough for GUI mouse-click-point containment checks.
|
129
|
+
- `#inverse_transform_points(*xy_coordinates_or_points)`: returns inverse transforms of a point `Array` of (x,y) coordinates
|
130
|
+
|
131
|
+
Example:
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
xxp = 2
|
135
|
+
xyp = 3
|
136
|
+
yxp = 4
|
137
|
+
yyp = 5
|
138
|
+
xt = 6
|
139
|
+
yt = 7
|
140
|
+
affine_transform1 = PerfectShape::AffineTransform.new(xxp: xxp, xyp: xyp, yxp: yxp, yyp: yyp, xt: xt, yt: yt) # (x,y)-operation kwarg names
|
141
|
+
affine_transform2 = PerfectShape::AffineTransform.new(m11: xxp, m12: xyp, m21: yxp, m22: yyp, m13: xt, m23: yt) # traditional matrix element kwarg names
|
142
|
+
affine_transform3 = PerfectShape::AffineTransform.new(xxp, xyp, yxp, yyp, xt, yt) # standard arguments
|
143
|
+
|
144
|
+
affine_transform2.matrix_3d == affine_transform1.matrix_3d # => true
|
145
|
+
affine_transform3.matrix_3d == affine_transform1.matrix_3d # => true
|
146
|
+
|
147
|
+
affine_transform = PerfectShape::AffineTransform.new.translate!(30, 20).scale!(2, 3)
|
148
|
+
|
149
|
+
affine_transform.transform_point(10, 10) # => approximately [50, 50]
|
150
|
+
affine_transform.inverse_transform_point(50, 50) # => approximately [10, 10]
|
151
|
+
```
|
90
152
|
|
91
153
|
### `PerfectShape::Point`
|
92
154
|
|
@@ -101,6 +163,7 @@ Includes `PerfectShape::PointLocation`
|
|
101
163
|
Points are simply represented by an `Array` of `[x,y]` coordinates when used within other shapes, but when needing point-specific operations like `point_distance`, the `PerfectShape::Point` class can come in handy.
|
102
164
|
|
103
165
|
- `::point_distance(x, y, px, py)`: Returns the distance from a point to another point
|
166
|
+
- `::normalize_point(x_or_point, y = nil)`: Normalizes point args whether two-number `point` `Array` or `x`, `y` args, returning normalized point `Array` of two `BigDecimal`'s
|
104
167
|
- `::new(x_or_point=nil, y_arg=nil, x: nil, y: nil)`: constructs a point with (x,y) pair (default: 0,0) whether specified as `Array` of (x,y) pair, flat `x,y` args, or `x:, y:` kwargs.
|
105
168
|
- `#min_x`: min x (always x)
|
106
169
|
- `#min_y`: min y (always y)
|
@@ -108,11 +171,12 @@ Points are simply represented by an `Array` of `[x,y]` coordinates when used wit
|
|
108
171
|
- `#max_y`: max y (always y)
|
109
172
|
- `#width`: width (always 0)
|
110
173
|
- `#height`: height (always 0)
|
174
|
+
- `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
|
111
175
|
- `#center_x`: center x (always x)
|
112
176
|
- `#center_y`: center y (always y)
|
113
177
|
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
114
178
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
115
|
-
- `#contain?(x_or_point, y=nil, distance_tolerance: 0)`: checks if point matches self, with a distance tolerance (0 by default). Distance tolerance provides a fuzz factor that for example enables GUI users to mouse-click-select a point shape more successfully.
|
179
|
+
- `#contain?(x_or_point, y=nil, outline: true, distance_tolerance: 0)`: checks if point matches self, with a distance tolerance (0 by default). Distance tolerance provides a fuzz factor that for example enables GUI users to mouse-click-select a point shape more successfully. `outline` option makes no difference on point
|
116
180
|
- `#point_distance(x_or_point, y=nil)`: Returns the distance from a point to another point
|
117
181
|
|
118
182
|
Example:
|
@@ -140,9 +204,9 @@ Includes `PerfectShape::MultiPoint`
|
|
140
204
|
|
141
205
|
![line](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/line.png)
|
142
206
|
|
143
|
-
- `::relative_counterclockwise(x1, y1, x2, y2, px, py)`: 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
|
144
|
-
- `::
|
145
|
-
- `::
|
207
|
+
- `::relative_counterclockwise(x1, y1, x2, y2, px, py)`: 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)”.
|
208
|
+
- `::point_distance_square(x1, y1, x2, y2, px, py)`: Returns the square of distance from a point to a line segment.
|
209
|
+
- `::point_distance(x1, y1, x2, y2, px, py)`: Returns the distance from a point to a line segment.
|
146
210
|
- `::new(points: [])`: constructs a line with two `points` as `Array` of `Array`s of `[x,y]` pairs or flattened `Array` of alternating x and y coordinates
|
147
211
|
- `#min_x`: min x
|
148
212
|
- `#min_y`: min y
|
@@ -150,13 +214,14 @@ Includes `PerfectShape::MultiPoint`
|
|
150
214
|
- `#max_y`: max y
|
151
215
|
- `#width`: width (from min x to max x)
|
152
216
|
- `#height`: height (from min y to max y)
|
217
|
+
- `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
|
153
218
|
- `#center_x`: center x
|
154
219
|
- `#center_y`: center y
|
155
220
|
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
156
221
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
157
|
-
- `#contain?(x_or_point, y=nil, distance_tolerance: 0)`: checks if point lies on line, with a distance tolerance (0 by default). Distance tolerance provides a fuzz factor that for example enables GUI users to mouse-click-select a line shape more successfully.
|
158
|
-
- `#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
|
159
|
-
- `#
|
222
|
+
- `#contain?(x_or_point, y=nil, outline: true, distance_tolerance: 0)`: checks if point lies on line, with a distance tolerance (0 by default). Distance tolerance provides a fuzz factor that for example enables GUI users to mouse-click-select a line shape more successfully. `outline` option makes no difference on line
|
223
|
+
- `#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)”.
|
224
|
+
- `#point_distance(x_or_point, y=nil)`: Returns the distance from a point to a line segment.
|
160
225
|
|
161
226
|
Example:
|
162
227
|
|
@@ -191,11 +256,17 @@ Includes `PerfectShape::MultiPoint`
|
|
191
256
|
- `#max_y`: max y
|
192
257
|
- `#width`: width (from min x to max x)
|
193
258
|
- `#height`: height (from min y to max y)
|
259
|
+
- `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
|
194
260
|
- `#center_x`: center x
|
195
261
|
- `#center_y`: center y
|
196
262
|
- `#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)
|
197
263
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
198
|
-
- `#contain?(x_or_point, y=nil)`: checks if point is inside
|
264
|
+
- `#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 quadratic bezier curve shape from its outline more successfully
|
265
|
+
- `#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`)
|
266
|
+
- `#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`)
|
267
|
+
- `#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`)
|
268
|
+
- `#subdivisions(level=1)`: subdivides quadratic bezier curve at its center into into 2 quadratic bezier curves by default, or more if `level` of recursion is specified. The resulting number of subdivisions is `2` to the power of `level`.
|
269
|
+
- `#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.
|
199
270
|
|
200
271
|
Example:
|
201
272
|
|
@@ -206,6 +277,14 @@ shape = PerfectShape::QuadraticBezierCurve.new(points: [[200, 150], [270, 320],
|
|
206
277
|
|
207
278
|
shape.contain?(270, 220) # => true
|
208
279
|
shape.contain?([270, 220]) # => true
|
280
|
+
shape.contain?(270, 220, outline: true) # => false
|
281
|
+
shape.contain?([270, 220], outline: true) # => false
|
282
|
+
shape.contain?(280, 235, outline: true) # => true
|
283
|
+
shape.contain?([280, 235], outline: true) # => true
|
284
|
+
shape.contain?(281, 235, outline: true) # => false
|
285
|
+
shape.contain?([281, 235], outline: true) # => false
|
286
|
+
shape.contain?(281, 235, outline: true, distance_tolerance: 1) # => true
|
287
|
+
shape.contain?([281, 235], outline: true, distance_tolerance: 1) # => true
|
209
288
|
```
|
210
289
|
|
211
290
|
### `PerfectShape::CubicBezierCurve`
|
@@ -226,16 +305,17 @@ Includes `PerfectShape::MultiPoint`
|
|
226
305
|
- `#max_y`: max y
|
227
306
|
- `#width`: width (from min x to max x)
|
228
307
|
- `#height`: height (from min y to max y)
|
308
|
+
- `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
|
229
309
|
- `#center_x`: center x
|
230
310
|
- `#center_y`: center y
|
231
311
|
- `#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)
|
232
312
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
233
313
|
- `#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
|
234
|
-
- `#curve_center_point`: point at the center of the curve (not the center of the bounding box area like `center_x` and `center_y`)
|
235
|
-
- `#curve_center_x`: point x coordinate at the center of the curve (not the center of the bounding box area like `center_x` and `center_y`)
|
236
|
-
- `#curve_center_y`: point y coordinate at the center of the curve (not the center of the bounding box area like `center_x` and `center_y`)
|
237
|
-
- `#subdivisions(
|
238
|
-
- `#
|
314
|
+
- `#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`)
|
315
|
+
- `#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`)
|
316
|
+
- `#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`)
|
317
|
+
- `#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`.
|
318
|
+
- `#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.
|
239
319
|
|
240
320
|
Example:
|
241
321
|
|
@@ -271,6 +351,7 @@ Includes `PerfectShape::RectangularShape`
|
|
271
351
|
- `#y`: top-left y
|
272
352
|
- `#width`: width
|
273
353
|
- `#height`: height
|
354
|
+
- `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
|
274
355
|
- `#center_x`: center x
|
275
356
|
- `#center_y`: center y
|
276
357
|
- `#min_x`: min x
|
@@ -280,6 +361,7 @@ Includes `PerfectShape::RectangularShape`
|
|
280
361
|
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
281
362
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
282
363
|
- `#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 rectangle shape from its outline more successfully
|
364
|
+
- `#edges`: edges of rectangle as `PerfectShape::Line` objects
|
283
365
|
|
284
366
|
Example:
|
285
367
|
|
@@ -314,6 +396,7 @@ Extends `PerfectShape::Rectangle`
|
|
314
396
|
- `#length`: length
|
315
397
|
- `#width`: width (equal to length)
|
316
398
|
- `#height`: height (equal to length)
|
399
|
+
- `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
|
317
400
|
- `#center_x`: center x
|
318
401
|
- `#center_y`: center y
|
319
402
|
- `#min_x`: min x
|
@@ -323,6 +406,7 @@ Extends `PerfectShape::Rectangle`
|
|
323
406
|
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
324
407
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
325
408
|
- `#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 square shape from its outline more successfully
|
409
|
+
- `#edges`: edges of square as `PerfectShape::Line` objects
|
326
410
|
|
327
411
|
Example:
|
328
412
|
|
@@ -365,6 +449,7 @@ Open Arc | Chord Arc | Pie Arc
|
|
365
449
|
- `#height`: height
|
366
450
|
- `#start`: start angle in degrees
|
367
451
|
- `#extent`: extent angle in degrees
|
452
|
+
- `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
|
368
453
|
- `#center_x`: center x
|
369
454
|
- `#center_y`: center y
|
370
455
|
- `#radius_x`: radius along the x-axis
|
@@ -484,6 +569,7 @@ Extends `PerfectShape::Arc`
|
|
484
569
|
- `#y`: top-left y
|
485
570
|
- `#width`: width
|
486
571
|
- `#height`: height
|
572
|
+
- `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
|
487
573
|
- `#center_x`: center x
|
488
574
|
- `#center_y`: center y
|
489
575
|
- `#radius_x`: radius along the x-axis
|
@@ -543,6 +629,7 @@ Extends `PerfectShape::Ellipse`
|
|
543
629
|
- `#diameter`: diameter
|
544
630
|
- `#width`: width (equal to diameter)
|
545
631
|
- `#height`: height (equal to diameter)
|
632
|
+
- `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
|
546
633
|
- `#center_x`: center x
|
547
634
|
- `#center_y`: center y
|
548
635
|
- `#radius`: radius
|
@@ -608,11 +695,13 @@ A polygon can be thought of as a special case of [path](#perfectshapepath) that
|
|
608
695
|
- `#max_y`: max y
|
609
696
|
- `#width`: width (from min x to max x)
|
610
697
|
- `#height`: height (from min y to max y)
|
698
|
+
- `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
|
611
699
|
- `#center_x`: center x
|
612
700
|
- `#center_y`: center y
|
613
701
|
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
614
702
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
615
703
|
- `#contain?(x_or_point, y=nil, outline: false, distance_tolerance: 0)`: When `outline` is `false`, it checks if point is inside using the [Ray Casting Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon) (aka [Even-Odd Rule](https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule)). Otherwise, when `outline` is `true`, it checks if point is on the outline. `distance_tolerance` can be used as a fuzz factor when `outline` is `true`, for example, to help GUI users mouse-click-select a polygon shape from its outline more successfully
|
704
|
+
- `#edges`: edges of polygon as `PerfectShape::Line` objects
|
616
705
|
|
617
706
|
Example:
|
618
707
|
|
@@ -654,12 +743,14 @@ Includes `PerfectShape::MultiPoint`
|
|
654
743
|
- `#max_y`: max y
|
655
744
|
- `#width`: width (from min x to max x)
|
656
745
|
- `#height`: height (from min y to max y)
|
746
|
+
- `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
|
657
747
|
- `#center_x`: center x
|
658
748
|
- `#center_y`: center y
|
659
749
|
- `#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)
|
660
750
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
661
|
-
- `#contain?(x_or_point, y=nil)`: checks if point is inside path utilizing the configured winding rule, which can be the [Nonzero-Rule](https://en.wikipedia.org/wiki/Nonzero-rule) (aka [Winding Number Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon#Winding_number_algorithm)) or the [Even-Odd Rule](https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule) (aka [Ray Casting Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm))
|
751
|
+
- `#contain?(x_or_point, y=nil, outline: false, distance_tolerance: 0)`: When `outline` is `false`, it checks if point is inside path utilizing the configured winding rule, which can be the [Nonzero-Rule](https://en.wikipedia.org/wiki/Nonzero-rule) (aka [Winding Number Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon#Winding_number_algorithm)) or the [Even-Odd Rule](https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule) (aka [Ray Casting Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm)). Otherwise, when `outline` is `true`, it checks if point is on the outline. `distance_tolerance` can be used as a fuzz factor when `outline` is `true`, for example, to help GUI users mouse-click-select a path shape from its outline more successfully
|
662
752
|
- `#point_crossings(x_or_point, y=nil)`: calculates the number of times the given path crosses the ray extending to the right from (x,y)
|
753
|
+
- `#disconnected_shapes`: Disconnected shapes have their start point filled in so that each shape does not depend on the previous shape to determine its start point. Also, if a point is followed by a non-point shape, it is removed since it is augmented to the following shape as its start point. Lastly, if the path is closed, an extra shape is added to represent the line connecting the last point to the first
|
663
754
|
|
664
755
|
Example:
|
665
756
|
|
@@ -674,8 +765,16 @@ path_shapes << PerfectShape::CubicBezierCurve.new(points: [[370, 50], [430, 220]
|
|
674
765
|
|
675
766
|
shape = PerfectShape::Path.new(shapes: path_shapes, closed: false, winding_rule: :wind_even_odd)
|
676
767
|
|
677
|
-
shape.contain?(
|
678
|
-
shape.contain?([
|
768
|
+
shape.contain?(275, 165) # => true
|
769
|
+
shape.contain?([275, 165]) # => true
|
770
|
+
shape.contain?(275, 165, outline: true) # => false
|
771
|
+
shape.contain?([275, 165], outline: true) # => false
|
772
|
+
shape.contain?(shape.disconnected_shapes[1].curve_center_x, shape.disconnected_shapes[1].curve_center_y, outline: true) # => true
|
773
|
+
shape.contain?([shape.disconnected_shapes[1].curve_center_x, shape.disconnected_shapes[1].curve_center_y], outline: true) # => true
|
774
|
+
shape.contain?(shape.disconnected_shapes[1].curve_center_x + 1, shape.disconnected_shapes[1].curve_center_y, outline: true) # => false
|
775
|
+
shape.contain?([shape.disconnected_shapes[1].curve_center_x + 1, shape.disconnected_shapes[1].curve_center_y], outline: true) # => false
|
776
|
+
shape.contain?(shape.disconnected_shapes[1].curve_center_x + 1, shape.disconnected_shapes[1].curve_center_y, outline: true, distance_tolerance: 1) # => true
|
777
|
+
shape.contain?([shape.disconnected_shapes[1].curve_center_x + 1, shape.disconnected_shapes[1].curve_center_y], outline: true, distance_tolerance: 1) # => true
|
679
778
|
```
|
680
779
|
|
681
780
|
### `PerfectShape::CompositeShape`
|
@@ -696,11 +795,12 @@ A composite shape is simply an aggregate of multiple shapes (e.g. square and pol
|
|
696
795
|
- `#max_y`: max y
|
697
796
|
- `#width`: width (from min x to max x)
|
698
797
|
- `#height`: height (from min y to max y)
|
798
|
+
- `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
|
699
799
|
- `#center_x`: center x
|
700
800
|
- `#center_y`: center y
|
701
801
|
- `#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)
|
702
802
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
703
|
-
- `#contain?(x_or_point, y=nil)`: checks if point is inside any of the shapes owned by the composite shape
|
803
|
+
- `#contain?(x_or_point, y=nil, outline: false, distance_tolerance: 0)`: When `outline` is `false`, it checks if point is inside any of the shapes owned by the composite shape. Otherwise, when `outline` is `true`, it checks if point is on the outline of any of the shapes owned by the composite shape. `distance_tolerance` can be used as a fuzz factor when `outline` is `true`, for example, to help GUI users mouse-click-select a composite shape from its outline more successfully
|
704
804
|
|
705
805
|
Example:
|
706
806
|
|
@@ -713,10 +813,27 @@ shapes << PerfectShape::Polygon.new(points: [[120, 215], [170, 165], [220, 215]]
|
|
713
813
|
|
714
814
|
shape = PerfectShape::CompositeShape.new(shapes: shapes)
|
715
815
|
|
716
|
-
shape.contain?(170, 265) # => true
|
717
|
-
shape.contain?([170, 265]) # => true
|
718
|
-
shape.contain?(170,
|
719
|
-
shape.contain?([170,
|
816
|
+
shape.contain?(170, 265) # => true inside square
|
817
|
+
shape.contain?([170, 265]) # => true inside square
|
818
|
+
shape.contain?(170, 265, outline: true) # => false
|
819
|
+
shape.contain?([170, 265], outline: true) # => false
|
820
|
+
shape.contain?(170, 315, outline: true) # => true
|
821
|
+
shape.contain?([170, 315], outline: true) # => true
|
822
|
+
shape.contain?(170, 316, outline: true) # => false
|
823
|
+
shape.contain?([170, 316], outline: true) # => false
|
824
|
+
shape.contain?(170, 316, outline: true, distance_tolerance: 1) # => true
|
825
|
+
shape.contain?([170, 316], outline: true, distance_tolerance: 1) # => true
|
826
|
+
|
827
|
+
shape.contain?(170, 190) # => true inside polygon
|
828
|
+
shape.contain?([170, 190]) # => true inside polygon
|
829
|
+
shape.contain?(170, 190, outline: true) # => false
|
830
|
+
shape.contain?([170, 190], outline: true) # => false
|
831
|
+
shape.contain?(145, 190, outline: true) # => true
|
832
|
+
shape.contain?([145, 190], outline: true) # => true
|
833
|
+
shape.contain?(145, 189, outline: true) # => false
|
834
|
+
shape.contain?([145, 189], outline: true) # => false
|
835
|
+
shape.contain?(145, 189, outline: true, distance_tolerance: 1) # => true
|
836
|
+
shape.contain?([145, 189], outline: true, distance_tolerance: 1) # => true
|
720
837
|
```
|
721
838
|
|
722
839
|
## Process
|
@@ -726,7 +843,10 @@ shape.contain?([170, 190]) # => true
|
|
726
843
|
## Resources
|
727
844
|
|
728
845
|
- Rubydoc: https://www.rubydoc.info/gems/perfect-shape
|
729
|
-
-
|
846
|
+
- Point in Polygon: https://en.wikipedia.org/wiki/Point_in_polygon
|
847
|
+
- Even-Odd Rule: https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule
|
848
|
+
- Nonzero Rule: https://en.wikipedia.org/wiki/Nonzero-rule
|
849
|
+
- IEEE 754-1985 Remainder: https://en.wikipedia.org/wiki/IEEE_754-1985
|
730
850
|
|
731
851
|
## TODO
|
732
852
|
|
@@ -756,5 +876,5 @@ shape.contain?([170, 190]) # => true
|
|
756
876
|
|
757
877
|
[MIT](LICENSE.txt)
|
758
878
|
|
759
|
-
Copyright (c) 2021 Andy Maleh. See
|
879
|
+
Copyright (c) 2021-2022 Andy Maleh. See
|
760
880
|
[LICENSE.txt](LICENSE.txt) for further details.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/lib/perfect-shape.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
|
@@ -23,8 +23,8 @@ $LOAD_PATH.unshift File.expand_path('.', __dir__)
|
|
23
23
|
|
24
24
|
require 'bigdecimal'
|
25
25
|
require 'equalizer'
|
26
|
+
require 'matrix'
|
26
27
|
|
27
|
-
# Perfect Shape algorithms are mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/package-summary.html
|
28
28
|
module PerfectShape
|
29
29
|
end
|
30
30
|
|