perfect-shape 0.3.3 → 0.3.4

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: 42bff3742b2697349882d865b7c5da9c06ce4a259850996bb92f291e8a74130e
4
- data.tar.gz: 5a2f007129f1e0f483a48ea607a7ee3e44cbdf01b400a656f406b412dc8e9524
3
+ metadata.gz: 4dc25ad218f1860bfff0fccc88b81ea4b6ef71795c737b740b998385f4c7bf7d
4
+ data.tar.gz: 31b52e8b765db80edde7ede8487f579a5de33b51fa1939bc1601dbad093741b1
5
5
  SHA512:
6
- metadata.gz: 9bf87bebe682dfbab8a4b082caa321ac0b7051510d86e946e3f214337e818a18da7cee51bc12432510567deaa68f65a69cb3c7da10b7c9153045803e1d25f0b7
7
- data.tar.gz: fa32aa79d500055b81b572e17ded38429503a3757430bba1c0fea021853e4377d7e025071cf52ed8c2f965c8bc8ecd35601276f2a1bcda847202c6f49c8422a0
6
+ metadata.gz: 949d9a223e08cf4b9154f63f575908707c257620e18fdcd4a2820855c305400f418038b5ec53b56c4a4baea7c38065df8da71158dc29c76a5a19a809bf38da33
7
+ data.tar.gz: '049d0fb36f1b633ebb20cf9fae4731688a19197a4bcbf29cbf55a7f837a5e4a9916b9b3a396a2f859387d3f80fcc7d8ebb9c6a23757dc3b389b0c70e79c7b845'
data/CHANGELOG.md CHANGED
@@ -1,11 +1,18 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.3.4
4
+
5
+ - 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)`)
6
+ - `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.
7
+ - `Shape#center_point` as `[center_x, center_y]`
8
+ - Rename `#point_segment_distance` to `#point_distance` everywhere
9
+
3
10
  ## 0.3.3
4
11
 
5
12
  - 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)`)
6
13
  - `PerfectShape::QuadraticBezierCurve#curve_center_point`, `PerfectShape::QuadraticBezierCurve#curve_center_x`, `PerfectShape::QuadraticBezierCurve#curve_center_y`
7
14
  - `PerfectShape::QuadraticBezierCurve#subdivisions(level=1)`
8
- - `PerfectShape::QuadraticBezierCurve#point_segment_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)`
15
+ - `PerfectShape::QuadraticBezierCurve#point_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)`
9
16
  - `PerfectShape::Polygon#edges` returns edges of polygon as `PerfectShape::Line` objects
10
17
  - `PerfectShape::Rectangle#edges` returns edges of rectangle as `PerfectShape::Line` objects
11
18
  - `PerfectShape::Square#edges` returns edges of square as `PerfectShape::Line` objects
@@ -16,7 +23,7 @@
16
23
  - 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)`)
17
24
  - `PerfectShape::CubicBezierCurve#curve_center_point`, `PerfectShape::CubicBezierCurve#curve_center_x`, `PerfectShape::CubicBezierCurve#curve_center_y`
18
25
  - `PerfectShape::CubicBezierCurve#subdivisions(level=1)`
19
- - `PerfectShape::CubicBezierCurve#point_segment_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)`
26
+ - `PerfectShape::CubicBezierCurve#point_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)`
20
27
 
21
28
  ## 0.3.1
22
29
 
@@ -80,7 +87,7 @@
80
87
  - `PerfectShape::Line`
81
88
  - `PerfectShape::Line#contain?(x_or_point, y=nil)`
82
89
  - `PerfectShape::Line#relative_counterclockwise`
83
- - `PerfectShape::Line#point_segment_distance`
90
+ - `PerfectShape::Line#point_distance`
84
91
  - Update `PerfectShape::Math::radians_to_degrees`, `PerfectShape::Math::degrees_to_radians`, and `PerfectShape::Math::normalize_degrees` to normalize numbers to `BigDecimal`
85
92
 
86
93
  ## 0.0.7
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Perfect Shape 0.3.3
1
+ # Perfect Shape 0.3.4
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.3.3
17
+ gem install perfect-shape -v 0.3.4
18
18
  ```
19
19
 
20
20
  Or include in Bundler `Gemfile`:
21
21
 
22
22
  ```ruby
23
- gem 'perfect-shape', '~> 0.3.3'
23
+ gem 'perfect-shape', '~> 0.3.4'
24
24
  ```
25
25
 
26
26
  And, run:
@@ -52,6 +52,7 @@ 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
@@ -84,9 +85,6 @@ Includes `PerfectShape::PointLocation`
84
85
  - `#min_y`: min y
85
86
  - `#max_x`: max x
86
87
  - `#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
90
88
 
91
89
  ### `PerfectShape::Point`
92
90
 
@@ -108,11 +106,12 @@ Points are simply represented by an `Array` of `[x,y]` coordinates when used wit
108
106
  - `#max_y`: max y (always y)
109
107
  - `#width`: width (always 0)
110
108
  - `#height`: height (always 0)
109
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
111
110
  - `#center_x`: center x (always x)
112
111
  - `#center_y`: center y (always y)
113
112
  - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
114
113
  - `#==(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.
114
+ - `#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
115
  - `#point_distance(x_or_point, y=nil)`: Returns the distance from a point to another point
117
116
 
118
117
  Example:
@@ -141,8 +140,8 @@ Includes `PerfectShape::MultiPoint`
141
140
  ![line](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/line.png)
142
141
 
143
142
  - `::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.
143
+ - `::point_distance_square(x1, y1, x2, y2, px, py)`: Returns the square of distance from a point to a line segment.
144
+ - `::point_distance(x1, y1, x2, y2, px, py)`: Returns the distance from a point to a line segment.
146
145
  - `::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
146
  - `#min_x`: min x
148
147
  - `#min_y`: min y
@@ -150,13 +149,14 @@ Includes `PerfectShape::MultiPoint`
150
149
  - `#max_y`: max y
151
150
  - `#width`: width (from min x to max x)
152
151
  - `#height`: height (from min y to max y)
152
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
153
153
  - `#center_x`: center x
154
154
  - `#center_y`: center y
155
155
  - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
156
156
  - `#==(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.
157
+ - `#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
158
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.
159
+ - `#point_distance(x_or_point, y=nil)`: Returns the distance from a point to a line segment.
160
160
 
161
161
  Example:
162
162
 
@@ -191,16 +191,17 @@ Includes `PerfectShape::MultiPoint`
191
191
  - `#max_y`: max y
192
192
  - `#width`: width (from min x to max x)
193
193
  - `#height`: height (from min y to max y)
194
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
194
195
  - `#center_x`: center x
195
196
  - `#center_y`: center y
196
197
  - `#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
198
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
198
199
  - `#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
199
- - `#curve_center_point`: point at the center of the curve (not the center of the bounding box area like `center_x` and `center_y`)
200
- - `#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`)
201
- - `#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`)
200
+ - `#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`)
201
+ - `#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`)
202
+ - `#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`)
202
203
  - `#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`.
203
- - `#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.
204
+ - `#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.
204
205
 
205
206
  Example:
206
207
 
@@ -239,16 +240,17 @@ Includes `PerfectShape::MultiPoint`
239
240
  - `#max_y`: max y
240
241
  - `#width`: width (from min x to max x)
241
242
  - `#height`: height (from min y to max y)
243
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
242
244
  - `#center_x`: center x
243
245
  - `#center_y`: center y
244
246
  - `#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)
245
247
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
246
248
  - `#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
247
- - `#curve_center_point`: point at the center of the curve (not the center of the bounding box area like `center_x` and `center_y`)
248
- - `#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`)
249
- - `#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`)
249
+ - `#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`)
250
+ - `#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`)
251
+ - `#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`)
250
252
  - `#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`.
251
- - `#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.
253
+ - `#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.
252
254
 
253
255
  Example:
254
256
 
@@ -284,6 +286,7 @@ Includes `PerfectShape::RectangularShape`
284
286
  - `#y`: top-left y
285
287
  - `#width`: width
286
288
  - `#height`: height
289
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
287
290
  - `#center_x`: center x
288
291
  - `#center_y`: center y
289
292
  - `#min_x`: min x
@@ -328,6 +331,7 @@ Extends `PerfectShape::Rectangle`
328
331
  - `#length`: length
329
332
  - `#width`: width (equal to length)
330
333
  - `#height`: height (equal to length)
334
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
331
335
  - `#center_x`: center x
332
336
  - `#center_y`: center y
333
337
  - `#min_x`: min x
@@ -380,6 +384,7 @@ Open Arc | Chord Arc | Pie Arc
380
384
  - `#height`: height
381
385
  - `#start`: start angle in degrees
382
386
  - `#extent`: extent angle in degrees
387
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
383
388
  - `#center_x`: center x
384
389
  - `#center_y`: center y
385
390
  - `#radius_x`: radius along the x-axis
@@ -499,6 +504,7 @@ Extends `PerfectShape::Arc`
499
504
  - `#y`: top-left y
500
505
  - `#width`: width
501
506
  - `#height`: height
507
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
502
508
  - `#center_x`: center x
503
509
  - `#center_y`: center y
504
510
  - `#radius_x`: radius along the x-axis
@@ -558,6 +564,7 @@ Extends `PerfectShape::Ellipse`
558
564
  - `#diameter`: diameter
559
565
  - `#width`: width (equal to diameter)
560
566
  - `#height`: height (equal to diameter)
567
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
561
568
  - `#center_x`: center x
562
569
  - `#center_y`: center y
563
570
  - `#radius`: radius
@@ -623,6 +630,7 @@ A polygon can be thought of as a special case of [path](#perfectshapepath) that
623
630
  - `#max_y`: max y
624
631
  - `#width`: width (from min x to max x)
625
632
  - `#height`: height (from min y to max y)
633
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
626
634
  - `#center_x`: center x
627
635
  - `#center_y`: center y
628
636
  - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
@@ -670,12 +678,14 @@ Includes `PerfectShape::MultiPoint`
670
678
  - `#max_y`: max y
671
679
  - `#width`: width (from min x to max x)
672
680
  - `#height`: height (from min y to max y)
681
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
673
682
  - `#center_x`: center x
674
683
  - `#center_y`: center y
675
684
  - `#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)
676
685
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
677
- - `#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))
686
+ - `#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
678
687
  - `#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)
688
+ - `#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
679
689
 
680
690
  Example:
681
691
 
@@ -692,6 +702,14 @@ shape = PerfectShape::Path.new(shapes: path_shapes, closed: false, winding_rule:
692
702
 
693
703
  shape.contain?(225, 160) # => true
694
704
  shape.contain?([225, 160]) # => true
705
+ shape.contain?(225, 160, outline: true) # => false
706
+ shape.contain?([225, 160], outline: true) # => false
707
+ shape.contain?(shape.disconnected_shapes[1].curve_center_x, shape.disconnected_shapes[1].curve_center_y, outline: true) # => true
708
+ shape.contain?([shape.disconnected_shapes[1].curve_center_x, shape.disconnected_shapes[1].curve_center_y], outline: true) # => true
709
+ shape.contain?(shape.disconnected_shapes[1].curve_center_x + 1, shape.disconnected_shapes[1].curve_center_y, outline: true) # => false
710
+ shape.contain?([shape.disconnected_shapes[1].curve_center_x + 1, shape.disconnected_shapes[1].curve_center_y], outline: true) # => false
711
+ shape.contain?(shape.disconnected_shapes[1].curve_center_x + 1, shape.disconnected_shapes[1].curve_center_y, outline: true, distance_tolerance: 1) # => true
712
+ shape.contain?([shape.disconnected_shapes[1].curve_center_x + 1, shape.disconnected_shapes[1].curve_center_y], outline: true, distance_tolerance: 1) # => true
695
713
  ```
696
714
 
697
715
  ### `PerfectShape::CompositeShape`
@@ -712,6 +730,7 @@ A composite shape is simply an aggregate of multiple shapes (e.g. square and pol
712
730
  - `#max_y`: max y
713
731
  - `#width`: width (from min x to max x)
714
732
  - `#height`: height (from min y to max y)
733
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
715
734
  - `#center_x`: center x
716
735
  - `#center_y`: center y
717
736
  - `#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)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.3
1
+ 0.3.4
@@ -152,8 +152,8 @@ module PerfectShape
152
152
  true
153
153
  else
154
154
  distance_tolerance = BigDecimal(distance_tolerance.to_s)
155
- outside_inside_radius_difference = DEFAULT_OUTLINE_RADIUS + distance_tolerance * 2.0
156
- outside_radius_difference = inside_radius_difference = outside_inside_radius_difference / 2.0
155
+ outside_inside_radius_difference = DEFAULT_OUTLINE_RADIUS + distance_tolerance * 2
156
+ outside_radius_difference = inside_radius_difference = outside_inside_radius_difference / 2
157
157
  outside_shape = Arc.new(type: type, center_x: center_x, center_y: center_y, radius_x: radius_x + outside_radius_difference, radius_y: radius_y + outside_radius_difference, start: start, extent: extent)
158
158
  inside_shape = Arc.new(type: type, center_x: center_x, center_y: center_y, radius_x: radius_x - inside_radius_difference, radius_y: radius_y - inside_radius_difference, start: start, extent: extent)
159
159
  outside_shape.contain?(x, y, outline: false) and
@@ -51,18 +51,18 @@ module PerfectShape
51
51
  end
52
52
  # double precision only has 52 bits of mantissa
53
53
  return PerfectShape::Line.point_crossings(x1, y1, x2, y2, px, py) if (level > 52)
54
- xmid = BigDecimal((xc1 + xc2).to_s) / 2;
55
- ymid = BigDecimal((yc1 + yc2).to_s) / 2;
56
- xc1 = BigDecimal((x1 + xc1).to_s) / 2;
57
- yc1 = BigDecimal((y1 + yc1).to_s) / 2;
58
- xc2 = BigDecimal((xc2 + x2).to_s) / 2;
59
- yc2 = BigDecimal((yc2 + y2).to_s) / 2;
60
- xc1m = BigDecimal((xc1 + xmid).to_s) / 2;
61
- yc1m = BigDecimal((yc1 + ymid).to_s) / 2;
62
- xmc1 = BigDecimal((xmid + xc2).to_s) / 2;
63
- ymc1 = BigDecimal((ymid + yc2).to_s) / 2;
64
- xmid = BigDecimal((xc1m + xmc1).to_s) / 2;
65
- ymid = BigDecimal((yc1m + ymc1).to_s) / 2;
54
+ xmid = BigDecimal((xc1 + xc2).to_s) / 2
55
+ ymid = BigDecimal((yc1 + yc2).to_s) / 2
56
+ xc1 = BigDecimal((x1 + xc1).to_s) / 2
57
+ yc1 = BigDecimal((y1 + yc1).to_s) / 2
58
+ xc2 = BigDecimal((xc2 + x2).to_s) / 2
59
+ yc2 = BigDecimal((yc2 + y2).to_s) / 2
60
+ xc1m = BigDecimal((xc1 + xmid).to_s) / 2
61
+ yc1m = BigDecimal((yc1 + ymid).to_s) / 2
62
+ xmc1 = BigDecimal((xmid + xc2).to_s) / 2
63
+ ymc1 = BigDecimal((ymid + yc2).to_s) / 2
64
+ xmid = BigDecimal((xc1m + xmc1).to_s) / 2
65
+ ymid = BigDecimal((yc1m + ymc1).to_s) / 2
66
66
  # [xy]mid are NaN if any of [xy]c0m or [xy]mc1 are NaN
67
67
  # [xy]c0m or [xy]mc1 are NaN if any of [xy][c][01] are NaN
68
68
  # These values are also NaN if opposing infinities are added
@@ -90,8 +90,9 @@ module PerfectShape
90
90
  return unless x && y
91
91
 
92
92
  if outline
93
+ distance_tolerance = BigDecimal(distance_tolerance.to_s)
93
94
  minimum_distance_threshold = OUTLINE_MINIMUM_DISTANCE_THRESHOLD + distance_tolerance
94
- point_segment_distance(x, y, minimum_distance_threshold: minimum_distance_threshold) < minimum_distance_threshold
95
+ point_distance(x, y, minimum_distance_threshold: minimum_distance_threshold) < minimum_distance_threshold
95
96
  else
96
97
  # Either x or y was infinite or NaN.
97
98
  # A NaN always produces a negative response to any test
@@ -105,7 +106,7 @@ module PerfectShape
105
106
  x2 = points[3][0]
106
107
  y2 = points[3][1]
107
108
  line = PerfectShape::Line.new(points: [[x1, y1], [x2, y2]])
108
- crossings = line.point_crossings(x, y) + point_crossings(x, y);
109
+ crossings = line.point_crossings(x, y) + point_crossings(x, y)
109
110
  (crossings & 1) == 1
110
111
  end
111
112
  end
@@ -148,6 +149,7 @@ module PerfectShape
148
149
  # for level=1, 4 subdivisions for level=2, and 8 subdivisions for level=3)
149
150
  def subdivisions(level = 1)
150
151
  level -= 1 # consume 1 level
152
+
151
153
  x1 = points[0][0]
152
154
  y1 = points[0][1]
153
155
  ctrlx1 = points[1][0]
@@ -156,22 +158,23 @@ module PerfectShape
156
158
  ctrly2 = points[2][1]
157
159
  x2 = points[3][0]
158
160
  y2 = points[3][1]
159
- centerx = (ctrlx1 + ctrlx2) / 2.0
160
- centery = (ctrly1 + ctrly2) / 2.0
161
- ctrlx1 = (x1 + ctrlx1) / 2.0
162
- ctrly1 = (y1 + ctrly1) / 2.0
163
- ctrlx2 = (x2 + ctrlx2) / 2.0
164
- ctrly2 = (y2 + ctrly2) / 2.0
165
- ctrlx12 = (ctrlx1 + centerx) / 2.0
166
- ctrly12 = (ctrly1 + centery) / 2.0
167
- ctrlx21 = (ctrlx2 + centerx) / 2.0
168
- ctrly21 = (ctrly2 + centery) / 2.0
169
- centerx = (ctrlx12 + ctrlx21) / 2.0
170
- centery = (ctrly12 + ctrly21) / 2.0
171
- default_subdivisions = [
172
- CubicBezierCurve.new(points: [x1, y1, ctrlx1, ctrly1, ctrlx12, ctrly12, centerx, centery]),
173
- CubicBezierCurve.new(points: [centerx, centery, ctrlx21, ctrly21, ctrlx2, ctrly2, x2, y2])
174
- ]
161
+ centerx = BigDecimal((ctrlx1 + ctrlx2).to_s) / 2
162
+ centery = BigDecimal((ctrly1 + ctrly2).to_s) / 2
163
+ ctrlx1 = BigDecimal((x1 + ctrlx1).to_s) / 2
164
+ ctrly1 = BigDecimal((y1 + ctrly1).to_s) / 2
165
+ ctrlx2 = BigDecimal((x2 + ctrlx2).to_s) / 2
166
+ ctrly2 = BigDecimal((y2 + ctrly2).to_s) / 2
167
+ ctrlx12 = BigDecimal((ctrlx1 + centerx).to_s) / 2
168
+ ctrly12 = BigDecimal((ctrly1 + centery).to_s) / 2
169
+ ctrlx21 = BigDecimal((ctrlx2 + centerx).to_s) / 2
170
+ ctrly21 = BigDecimal((ctrly2 + centery).to_s) / 2
171
+ centerx = BigDecimal((ctrlx12 + ctrlx21).to_s) / 2
172
+ centery = BigDecimal((ctrly12 + ctrly21).to_s) / 2
173
+
174
+ first_curve = CubicBezierCurve.new(points: [x1, y1, ctrlx1, ctrly1, ctrlx12, ctrly12, centerx, centery])
175
+ second_curve = CubicBezierCurve.new(points: [centerx, centery, ctrlx21, ctrly21, ctrlx2, ctrly2, x2, y2])
176
+ default_subdivisions = [first_curve, second_curve]
177
+
175
178
  if level == 0
176
179
  default_subdivisions
177
180
  else
@@ -179,7 +182,7 @@ module PerfectShape
179
182
  end
180
183
  end
181
184
 
182
- def point_segment_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)
185
+ def point_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)
183
186
  x, y = normalize_point(x_or_point, y)
184
187
  return unless x && y
185
188
 
@@ -189,8 +192,10 @@ module PerfectShape
189
192
  last_minimum_distance = minimum_distance + 1 # start bigger to ensure going through loop once at least
190
193
  while minimum_distance >= minimum_distance_threshold && minimum_distance < last_minimum_distance
191
194
  curve1, curve2 = current_curve.subdivisions
192
- distance1 = point.point_distance(curve1.curve_center_point)
193
- distance2 = point.point_distance(curve2.curve_center_point)
195
+ curve1_center_point = curve1.curve_center_point
196
+ distance1 = point.point_distance(curve1_center_point)
197
+ curve2_center_point = curve2.curve_center_point
198
+ distance2 = point.point_distance(curve2_center_point)
194
199
  last_minimum_distance = minimum_distance
195
200
  if distance1 < distance2
196
201
  minimum_distance = distance1
@@ -104,7 +104,7 @@ module PerfectShape
104
104
  # measured against the specified line segment
105
105
  # @return a double value that is the square of the distance from the
106
106
  # specified point to the specified line segment.
107
- def point_segment_distance_square(x1, y1,
107
+ def point_distance_square(x1, y1,
108
108
  x2, y2,
109
109
  px, py)
110
110
  x1 = BigDecimal(x1.to_s)
@@ -177,10 +177,10 @@ module PerfectShape
177
177
  # measured against the specified line segment
178
178
  # @return a double value that is the distance from the specified point
179
179
  # to the specified line segment.
180
- def point_segment_distance(x1, y1,
180
+ def point_distance(x1, y1,
181
181
  x2, y2,
182
182
  px, py)
183
- BigDecimal(::Math.sqrt(point_segment_distance_square(x1, y1, x2, y2, px, py)).to_s)
183
+ BigDecimal(::Math.sqrt(point_distance_square(x1, y1, x2, y2, px, py)).to_s)
184
184
  end
185
185
 
186
186
  # Calculates the number of times the line from (x1,y1) to (x2,y2)
@@ -212,17 +212,17 @@ module PerfectShape
212
212
  # @return {@code true} if the point lies within the bound of
213
213
  # the line, {@code false} if the point lies outside of the
214
214
  # line's bounds.
215
- def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
215
+ def contain?(x_or_point, y = nil, outline: true, distance_tolerance: 0)
216
216
  x, y = normalize_point(x_or_point, y)
217
217
  return unless x && y
218
218
  distance_tolerance = BigDecimal(distance_tolerance.to_s)
219
- point_segment_distance(x, y) <= distance_tolerance
219
+ point_distance(x, y) <= distance_tolerance
220
220
  end
221
221
 
222
- def point_segment_distance(x_or_point, y = nil)
222
+ def point_distance(x_or_point, y = nil)
223
223
  x, y = normalize_point(x_or_point, y)
224
224
  return unless x && y
225
- Line.point_segment_distance(points[0][0], points[0][1], points[1][0], points[1][1], x, y)
225
+ Line.point_distance(points[0][0], points[0][1], points[1][0], points[1][1], x, y)
226
226
  end
227
227
 
228
228
  def relative_counterclockwise(x_or_point, y = nil)
@@ -37,7 +37,13 @@ module PerfectShape
37
37
  ys = the_points.each_with_index.select {|n, i| i.odd?}.map(&:first)
38
38
  the_points = xs.zip(ys)
39
39
  end
40
- @points = the_points.map {|pair| [BigDecimal(pair.first.to_s), BigDecimal(pair.last.to_s)]}
40
+ @points = the_points.map do |pair|
41
+ [
42
+ pair.first.is_a?(BigDecimal) ? pair.first : BigDecimal(pair.first.to_s),
43
+ pair.last.is_a?(BigDecimal) ? pair.last : BigDecimal(pair.last.to_s)
44
+ ]
45
+ end
46
+ @points
41
47
  end
42
48
 
43
49
  def min_x
@@ -114,21 +114,26 @@ module PerfectShape
114
114
  # @return true if the point lies within the bound of
115
115
  # the path or false if the point lies outside of the
116
116
  # path's bounds.
117
- def contain?(x_or_point, y = nil)
117
+ def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
118
118
  x, y = normalize_point(x_or_point, y)
119
119
  return unless x && y
120
- if (x * 0.0 + y * 0.0) == 0.0
121
- # N * 0.0 is 0.0 only if N is finite.
122
- # Here we know that both x and y are finite.
123
- return false if shapes.count < 2
124
- mask = winding_rule == :wind_non_zero ? -1 : 1
125
- (point_crossings(x, y) & mask) != 0
120
+
121
+ if outline
122
+ disconnected_shapes.any? {|shape| shape.contain?(x, y, outline: true, distance_tolerance: distance_tolerance) }
126
123
  else
127
- # Either x or y was infinite or NaN.
128
- # A NaN always produces a negative response to any test
129
- # and Infinity values cannot be "inside" any path so
130
- # they should return false as well.
131
- false
124
+ if (x * 0.0 + y * 0.0) == 0.0
125
+ # N * 0.0 is 0.0 only if N is finite.
126
+ # Here we know that both x and y are finite.
127
+ return false if shapes.count < 2
128
+ mask = winding_rule == :wind_non_zero ? -1 : 1
129
+ (point_crossings(x, y) & mask) != 0
130
+ else
131
+ # Either x or y was infinite or NaN.
132
+ # A NaN always produces a negative response to any test
133
+ # and Infinity values cannot be "inside" any path so
134
+ # they should return false as well.
135
+ false
136
+ end
132
137
  end
133
138
  end
134
139
 
@@ -218,5 +223,50 @@ module PerfectShape
218
223
  end
219
224
  crossings
220
225
  end
226
+
227
+ # Disconnected shapes have their start point filled in
228
+ # so that each shape does not depend on the previous shape
229
+ # to determine its start point.
230
+ #
231
+ # Also, if a point is followed by a non-point shape, it is removed
232
+ # since it is augmented to the following shape as its start point.
233
+ #
234
+ # Lastly, if the path is closed, an extra shape is
235
+ # added to represent the line connecting the last point to the first
236
+ def disconnected_shapes
237
+ initial_point = start_point = @shapes.first.to_a.map {|n| BigDecimal(n.to_s)}
238
+ final_point = nil
239
+ the_disconnected_shapes = @shapes.drop(1).map do |shape|
240
+ case shape
241
+ when Point
242
+ disconnected_shape = Point.new(*shape.to_a)
243
+ start_point = shape.to_a
244
+ final_point = disconnected_shape.to_a
245
+ nil
246
+ when Array
247
+ disconnected_shape = Point.new(*shape.map {|n| BigDecimal(n.to_s)})
248
+ start_point = shape.map {|n| BigDecimal(n.to_s)}
249
+ final_point = disconnected_shape.to_a
250
+ nil
251
+ when Line
252
+ disconnected_shape = Line.new(points: [start_point.to_a, shape.points.last])
253
+ start_point = shape.points.last.to_a
254
+ final_point = disconnected_shape.points.last.to_a
255
+ disconnected_shape
256
+ when QuadraticBezierCurve
257
+ disconnected_shape = QuadraticBezierCurve.new(points: [start_point.to_a] + shape.points)
258
+ start_point = shape.points.last.to_a
259
+ final_point = disconnected_shape.points.last.to_a
260
+ disconnected_shape
261
+ when CubicBezierCurve
262
+ disconnected_shape = CubicBezierCurve.new(points: [start_point.to_a] + shape.points)
263
+ start_point = shape.points.last.to_a
264
+ final_point = disconnected_shape.points.last.to_a
265
+ disconnected_shape
266
+ end
267
+ end
268
+ the_disconnected_shapes << Line.new(points: [final_point, initial_point]) if closed?
269
+ the_disconnected_shapes.compact
270
+ end
221
271
  end
222
272
  end
@@ -27,10 +27,10 @@ module PerfectShape
27
27
  class Point < Shape
28
28
  class << self
29
29
  def point_distance(x, y, px, py)
30
- x = BigDecimal(x.to_s)
31
- y = BigDecimal(y.to_s)
32
- px = BigDecimal(px.to_s)
33
- py = BigDecimal(py.to_s)
30
+ x = x.is_a?(BigDecimal) ? x : BigDecimal(x.to_s)
31
+ y = y.is_a?(BigDecimal) ? y : BigDecimal(y.to_s)
32
+ px = px.is_a?(BigDecimal) ? px : BigDecimal(px.to_s)
33
+ py = py.is_a?(BigDecimal) ? py : BigDecimal(py.to_s)
34
34
  BigDecimal(Math.sqrt((px - x)**2 + (py - y)**2).to_s)
35
35
  end
36
36
  end
@@ -67,7 +67,7 @@ module PerfectShape
67
67
  #
68
68
  # @return {@code true} if the point is close enough within distance tolerance,
69
69
  # {@code false} if the point is too far.
70
- def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
70
+ def contain?(x_or_point, y = nil, outline: true, distance_tolerance: 0)
71
71
  x, y = normalize_point(x_or_point, y)
72
72
  return unless x && y
73
73
  distance_tolerance = BigDecimal(distance_tolerance.to_s)
@@ -77,6 +77,7 @@ module PerfectShape
77
77
  def point_distance(x_or_point, y = nil)
78
78
  x, y = normalize_point(x_or_point, y)
79
79
  return unless x && y
80
+
80
81
  Point.point_distance(self.x, self.y, x, y)
81
82
  end
82
83
 
@@ -91,8 +91,9 @@ module PerfectShape
91
91
  y2 = points[2][1]
92
92
 
93
93
  if outline
94
+ distance_tolerance = BigDecimal(distance_tolerance.to_s)
94
95
  minimum_distance_threshold = OUTLINE_MINIMUM_DISTANCE_THRESHOLD + distance_tolerance
95
- point_segment_distance(x, y, minimum_distance_threshold: minimum_distance_threshold) < minimum_distance_threshold
96
+ point_distance(x, y, minimum_distance_threshold: minimum_distance_threshold) < minimum_distance_threshold
96
97
  else
97
98
  # We have a convex shape bounded by quad curve Pc(t)
98
99
  # and ine Pl(t).
@@ -194,8 +195,8 @@ module PerfectShape
194
195
  QuadraticBezierCurve.point_crossings(points[0][0], points[0][1], points[1][0], points[1][1], points[2][0], points[2][1], x, y, level)
195
196
  end
196
197
 
197
-
198
198
  # The center point on the outline of the curve
199
+ # in Array format as pair of (x, y) coordinates
199
200
  def curve_center_point
200
201
  subdivisions.last.points[0]
201
202
  end
@@ -226,12 +227,12 @@ module PerfectShape
226
227
  ctrly = points[1][1]
227
228
  x2 = points[2][0]
228
229
  y2 = points[2][1]
229
- ctrlx1 = (x1 + ctrlx) / 2.0
230
- ctrly1 = (y1 + ctrly) / 2.0
231
- ctrlx2 = (x2 + ctrlx) / 2.0
232
- ctrly2 = (y2 + ctrly) / 2.0
233
- centerx = (ctrlx1 + ctrlx2) / 2.0
234
- centery = (ctrly1 + ctrly2) / 2.0
230
+ ctrlx1 = BigDecimal((x1 + ctrlx).to_s) / 2
231
+ ctrly1 = BigDecimal((y1 + ctrly).to_s) / 2
232
+ ctrlx2 = BigDecimal((x2 + ctrlx).to_s) / 2
233
+ ctrly2 = BigDecimal((y2 + ctrly).to_s) / 2
234
+ centerx = BigDecimal((ctrlx1 + ctrlx2).to_s) / 2
235
+ centery = BigDecimal((ctrly1 + ctrly2).to_s) / 2
235
236
 
236
237
  default_subdivisions = [
237
238
  QuadraticBezierCurve.new(points: [x1, y1, ctrlx1, ctrly1, centerx, centery]),
@@ -245,7 +246,7 @@ module PerfectShape
245
246
  end
246
247
  end
247
248
 
248
- def point_segment_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)
249
+ def point_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)
249
250
  x, y = normalize_point(x_or_point, y)
250
251
  return unless x && y
251
252
 
@@ -51,6 +51,12 @@ module PerfectShape
51
51
  max_y - min_y if max_y && min_y
52
52
  end
53
53
 
54
+ # Center point is `[center_x, center_y]`
55
+ # Returns `nil` if either center_x or center_y are `nil`
56
+ def center_point
57
+ [center_x, center_y] unless center_x.nil? || center_y.nil?
58
+ end
59
+
54
60
  # center_x is min_x + width/2.0 by default
55
61
  # Returns nil if min_x or width are nil
56
62
  def center_x
@@ -79,8 +85,8 @@ module PerfectShape
79
85
  def normalize_point(x_or_point, y = nil)
80
86
  x = x_or_point
81
87
  x, y = x if y.nil? && x_or_point.is_a?(Array) && x_or_point.size == 2
82
- x = BigDecimal(x.to_s)
83
- y = BigDecimal(y.to_s)
88
+ x = x.is_a?(BigDecimal) ? x : BigDecimal(x.to_s)
89
+ y = y.is_a?(BigDecimal) ? y : BigDecimal(y.to_s)
84
90
  [x, y]
85
91
  end
86
92
 
@@ -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.3.3 ruby lib
5
+ # stub: perfect-shape 0.3.4 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "perfect-shape".freeze
9
- s.version = "0.3.3"
9
+ s.version = "0.3.4"
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-10"
14
+ s.date = "2022-01-11"
15
15
  s.description = "Perfect Shape is a collection of pure Ruby geometric algorithms that are mostly useful for GUI manipulation like checking 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 (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.3.3
4
+ version: 0.3.4
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-10 00:00:00.000000000 Z
11
+ date: 2022-01-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: equalizer