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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f9bd6a8a2c7c3d3ec4ec3fd3e5b311a9eec08fa63e35666547534a2e19273c6
4
- data.tar.gz: 4dbcd6b139e118c555d50681efe4e770cf796774a904459994a64c2bb19d5140
3
+ metadata.gz: 13f257899f946daccf27fa951ed4208ce8143d2060b24fcb6198ba0e8f738646
4
+ data.tar.gz: 1f9a0d7a1f210da0ee0e7b88514352b7d90c7893b05fe9b84145672989cd8eae
5
5
  SHA512:
6
- metadata.gz: e30baa5bb362e5e540af6f67ba9c99ff0f5f0469f48a724823a76522111d6ea86e1bce6cb6b273b056cc9bf7e8f92bd667103aa348abbc237e4ffec082154434
7
- data.tar.gz: 7627cabd6ee3ef671ed802d6846ad1c192065cc4a5bf6d00062a7d8da7d3376c3d84b836664413a5033644dfa839d19a8634badf851e4aa050e6f03cdef031bc
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(number=2)`
8
- - `PerfectShape::CubicBezierCurve#point_segment_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)`
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#point_segment_distance`
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
@@ -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
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
- # Perfect Shape 0.3.2
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.3.2
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.3.2'
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 % modulo operator as it operates on floats and could return a negative result)
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
- - `#center_x`: center x
88
- - `#center_y`: center y
89
- - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
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 used by Java 2D, 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)”.
144
- - `::point_segment_distance_square(x1, y1, x2, y2, px, py)`: Returns the square of distance from a point to a line segment.
145
- - `::point_segment_distance(x1, y1, x2, y2, px, py)`: Returns the distance from a point to a line segment.
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 used by Java 2D, 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)”.
159
- - `#point_segment_distance(x_or_point, y=nil)`: Returns the distance from a point to a line segment.
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(number=2)`: subdivides cubic bezier curve at its center into into 2 cubic bezier curves by default, or more if `number` is specified. `number` must be an even number, or it will be rounded up to the closest even number.
238
- - `#point_segment_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.
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?(225, 160) # => true
678
- shape.contain?([225, 160]) # => true
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, 190) # => true
719
- shape.contain?([170, 190]) # => true
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
- - AWT Geom Javadoc (inspiration): https://docs.oracle.com/javase/8/docs/api/java/awt/geom/package-summary.html
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.3.2
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