perfect-shape 0.1.1 → 0.1.2

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: a4eb7bc277a02c0795966623e05ee57514483d982bc0d80f264a525f6b3d5afa
4
- data.tar.gz: f070a8292b835f6d2c68ff4ccddd3073b7a83d36e882d4e5c34c6e91a85d88ee
3
+ metadata.gz: cfa0c67a9d8a4b23b53bdd7895121fa51ee3da17e1d22fa9bb56c0890a47844b
4
+ data.tar.gz: 81cd3fe5766f023fc250951cab6541142d80ff70b8af3d8cbbfcefcb56154cba
5
5
  SHA512:
6
- metadata.gz: 1add8519bdd16f38f8fb8431d8342c77df9f5d8d76f0dc6f1481bba5d9c0a15b653c56a9a0e372082f1590b55f5897ebac3f6a4d4505acd6ba017f257261e80d
7
- data.tar.gz: fc3e2fce45f0a46ad7869fd5fec07d0dcf826e3bd36676439144eb3fd0198b8224d816a26db54858b43d0bb5332ff6b4108950efca3e71c1d5e42cebde4de63f
6
+ metadata.gz: ea4539cd6122363483988ef9de8b712ebd981c2e7102d5510e94f9d093bad1c02d17cd0e1c4eebe5ebc3e4211a67d22ca7ee2630cdf561404a9adc36031de220
7
+ data.tar.gz: 4a9fcfa3accf7ca519c403e24076bfc76b1bd9e2b47fda886e0012af610d3e142257d09c6508a04deccf665b536c41b60cc68047ba2c76f06a667be2625b0c40
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.1.2
4
+
5
+ - `PerfectShape::CubicBezierCurve` (two end points and two control points)
6
+ - `PerfectShape::CubicBezierCurve#contain?(x_or_point, y=nil)`
7
+ - `PerfectShape::CubicBezierCurve#==`
8
+ - `PerfectShape::Path` having cubic bezier curves in addition to points, lines, and quadratic bezier curves
9
+
3
10
  ## 0.1.1
4
11
 
5
12
  - `PerfectShape::QuadraticBezierCurve` (two end points and one control point)
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
- # Perfect Shape 0.1.1
1
+ # Perfect Shape 0.1.2
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 bézier curves (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 bézier 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)).
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.1.1
17
+ gem install perfect-shape -v 0.1.2
18
18
  ```
19
19
 
20
20
  Or include in Bundler `Gemfile`:
21
21
 
22
22
  ```ruby
23
- gem 'perfect-shape', '~> 0.1.1'
23
+ gem 'perfect-shape', '~> 0.1.2'
24
24
  ```
25
25
 
26
26
  And, run:
@@ -133,7 +133,7 @@ Includes `PerfectShape::MultiPoint`
133
133
  - `::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)”.
134
134
  - `::point_segment_distance_square(x1, y1, x2, y2, px, py)`: Returns the square of distance from a point to a line segment.
135
135
  - `::point_segment_distance(x1, y1, x2, y2, px, py)`: Returns the distance from a point to a line segment.
136
- - `::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 values
136
+ - `::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
137
137
  - `#min_x`: min x
138
138
  - `#min_y`: min y
139
139
  - `#max_x`: max x
@@ -164,7 +164,8 @@ Includes `PerfectShape::MultiPoint`
164
164
 
165
165
  ![quadratic_bezier_curve](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/quadratic_bezier_curve.png)
166
166
 
167
- - `::new(points: [])`: constructs a quadratic bézier curve with three `points` (two end points and one control point) as `Array` of `Array`s of `[x,y]` pairs or flattened `Array` of alternating x and y values
167
+ - `::new(points: [])`: constructs a quadratic bézier curve with three `points` (start point, control point, and end point) as `Array` of `Array`s of `[x,y]` pairs or flattened `Array` of alternating x and y coordinates
168
+ - `#points`: points (start point, control point, and end point)
168
169
  - `#min_x`: min x
169
170
  - `#min_y`: min y
170
171
  - `#max_x`: max x
@@ -183,6 +184,36 @@ Example:
183
184
  shape = PerfectShape::QuadraticBezierCurve.new(points: [[200, 150], [270, 220], [180, 170]]) # start point, control point, and end point
184
185
  ```
185
186
 
187
+ ### `PerfectShape::CubicBezierCurve`
188
+
189
+ Class
190
+
191
+ Extends `PerfectShape::Shape`
192
+
193
+ Includes `PerfectShape::MultiPoint`
194
+
195
+ ![cubic_bezier_curve](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/cubic_bezier_curve.png)
196
+
197
+ - `::new(points: [])`: constructs a cubic bézier curve with four `points` (start point, two control points, and end point) as `Array` of `Array`s of `[x,y]` pairs or flattened `Array` of alternating x and y coordinates
198
+ - `#points`: points (start point, two control points, and end point)
199
+ - `#min_x`: min x
200
+ - `#min_y`: min y
201
+ - `#max_x`: max x
202
+ - `#max_y`: max y
203
+ - `#width`: width (from min x to max x)
204
+ - `#height`: height (from min y to max y)
205
+ - `#center_x`: center x
206
+ - `#center_y`: center y
207
+ - `#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)
208
+ - `#contain?(x_or_point, y=nil)`: checks if point is inside
209
+ - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
210
+
211
+ Example:
212
+
213
+ ```ruby
214
+ shape = PerfectShape::CubicBezierCurve.new(points: [[200, 150], [230, 50], [270, 220], [180, 170]]) # start point, two control points, and end point
215
+ ```
216
+
186
217
  ### `PerfectShape::Rectangle`
187
218
 
188
219
  Class
@@ -369,7 +400,7 @@ A polygon can be thought of as a special case of [path](#perfectshapepath) that
369
400
 
370
401
  ![polygon](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/polygon.png)
371
402
 
372
- - `::new(points: [])`: constructs a polygon with `points` as `Array` of `Array`s of `[x,y]` pairs or flattened `Array` of alternating x and y values
403
+ - `::new(points: [])`: constructs a polygon with `points` as `Array` of `Array`s of `[x,y]` pairs or flattened `Array` of alternating x and y coordinates
373
404
  - `#min_x`: min x
374
405
  - `#min_y`: min y
375
406
  - `#max_x`: max x
@@ -398,7 +429,7 @@ Includes `PerfectShape::MultiPoint`
398
429
 
399
430
  ![path](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/path.png)
400
431
 
401
- - `::new(shapes: [], closed: false, winding_rule: :wind_non_zero)`: constructs a path with `shapes` as `Array` of shape objects, which can be `PerfectShape::Point` (or `Array` of `[x, y]` coordinates), `PerfectShape::Line`, or `PerfectShape::QuadraticBezierCurve`. If a path is closed, its last point is automatically connected to its first point with a line segment. The winding rule can be `:wind_non_zero` (default) or `:wind_even_odd`.
432
+ - `::new(shapes: [], closed: false, winding_rule: :wind_non_zero)`: constructs a path with `shapes` as `Array` of shape objects, which can be `PerfectShape::Point` (or `Array` of `[x, y]` coordinates), `PerfectShape::Line`, `PerfectShape::QuadraticBezierCurve`, or `PerfectShape::CubicBezierCurve`. If a path is closed, its last point is automatically connected to its first point with a line segment. The winding rule can be `:wind_non_zero` (default) or `:wind_even_odd`.
402
433
  - `#shapes`: the shapes that the path is composed of (must always start with `PerfectShape::Point` or Array of [x,y] coordinates representing start point)
403
434
  - `#closed?`: returns `true` if closed and `false` otherwise
404
435
  - `#winding_rule`: returns winding rule (`:wind_non_zero` or `:wind_even_odd`)
@@ -423,6 +454,7 @@ path_shapes = []
423
454
  path_shapes << PerfectShape::Point.new(x: 200, y: 150)
424
455
  path_shapes << PerfectShape::Line.new(points: [250, 170]) # no need for start point, just end point
425
456
  path_shapes << PerfectShape::QuadraticBezierCurve.new(points: [[300, 185], [350, 150]]) # no need for start point, just control point and end point
457
+ path_shapes << PerfectShape::CubicBezierCurve.new(points: [[370, 50], [430, 220], [480, 170]]) # no need for start point, just two control points and end point
426
458
 
427
459
  shape = PerfectShape::Path.new(shapes: path_shapes, closed: false, winding_rule: :wind_even_odd)
428
460
  ```
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.1.2
@@ -0,0 +1,120 @@
1
+ # Copyright (c) 2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'perfect_shape/shape'
23
+ require 'perfect_shape/multi_point'
24
+
25
+ module PerfectShape
26
+ # Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/QuadCurve2D.html
27
+ class CubicBezierCurve < Shape
28
+ class << self
29
+ # Calculates the number of times the cubic bézier curve from (x1,y1) to (x2,y2)
30
+ # crosses the ray extending to the right from (x,y).
31
+ # If the point lies on a part of the curve,
32
+ # then no crossings are counted for that intersection.
33
+ # the level parameter should be 0 at the top-level call and will count
34
+ # up for each recursion level to prevent infinite recursion
35
+ # +1 is added for each crossing where the Y coordinate is increasing
36
+ # -1 is added for each crossing where the Y coordinate is decreasing
37
+ def point_crossings(x1, y1, xc1, yc1, xc2, yc2, x2, y2, px, py, level = 0)
38
+ return 0 if (py < y1 && py < yc1 && py < yc2 && py < y2)
39
+ return 0 if (py >= y1 && py >= yc1 && py >= yc2 && py >= y2)
40
+ # Note y1 could equal yc1...
41
+ return 0 if (px >= x1 && px >= xc1 && px >= xc2 && px >= x2)
42
+ if (px < x1 && px < xc1 && px < xc2 && px < x2)
43
+ if (py >= y1)
44
+ return 1 if (py < y2)
45
+ else
46
+ # py < y1
47
+ return -1 if (py >= y2)
48
+ end
49
+ # py outside of y12 range, and/or y1==yc1
50
+ return 0
51
+ end
52
+ # double precision only has 52 bits of mantissa
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;
66
+ # [xy]mid are NaN if any of [xy]c0m or [xy]mc1 are NaN
67
+ # [xy]c0m or [xy]mc1 are NaN if any of [xy][c][01] are NaN
68
+ # These values are also NaN if opposing infinities are added
69
+ return 0 if (xmid.nan? || ymid.nan?)
70
+ point_crossings(x1, y1, xc1, yc1, xc1m, yc1m, xmid, ymid, px, py, level+1) +
71
+ point_crossings(xmid, ymid, xmc1, ymc1, xc2, yc2, x2, y2, px, py, level+1)
72
+ end
73
+ end
74
+
75
+ include MultiPoint
76
+ include Equalizer.new(:points)
77
+
78
+ # Checks if cubic bézier curve contains point (two-number Array or x, y args)
79
+ #
80
+ # @param x The X coordinate of the point to test.
81
+ # @param y The Y coordinate of the point to test.
82
+ #
83
+ # @return {@code true} if the point lies within the bound of
84
+ # the cubic bézier curve, {@code false} if the point lies outside of the
85
+ # cubic bézier curve's bounds.
86
+ def contain?(x_or_point, y = nil)
87
+ x, y = normalize_point(x_or_point, y)
88
+ return unless x && y
89
+
90
+ # Either x or y was infinite or NaN.
91
+ # A NaN always produces a negative response to any test
92
+ # and Infinity values cannot be "inside" any path so
93
+ # they should return false as well.
94
+ return false if (!(x * 0.0 + y * 0.0 == 0.0))
95
+ # We count the "Y" crossings to determine if the point is
96
+ # inside the curve bounded by its closing line.
97
+ x1 = points[0][0]
98
+ y1 = points[0][1]
99
+ x2 = points[3][0]
100
+ y2 = points[3][1]
101
+ line = PerfectShape::Line.new(points: [[x1, y1], [x2, y2]])
102
+ crossings = line.point_crossings(x, y) + point_crossings(x, y);
103
+ (crossings & 1) == 1
104
+ end
105
+
106
+ # Calculates the number of times the cubic bézier curve
107
+ # crosses the ray extending to the right from (x,y).
108
+ # If the point lies on a part of the curve,
109
+ # then no crossings are counted for that intersection.
110
+ # the level parameter should be 0 at the top-level call and will count
111
+ # up for each recursion level to prevent infinite recursion
112
+ # +1 is added for each crossing where the Y coordinate is increasing
113
+ # -1 is added for each crossing where the Y coordinate is decreasing
114
+ def point_crossings(x_or_point, y = nil, level = 0)
115
+ x, y = normalize_point(x_or_point, y)
116
+ return unless x && y
117
+ CubicBezierCurve.point_crossings(points[0][0], points[0][1], points[1][0], points[1][1], points[2][0], points[2][1], points[3][0], points[3][1], x, y, level)
118
+ end
119
+ end
120
+ end
@@ -189,14 +189,14 @@ module PerfectShape
189
189
  # +1 is returned for a crossing where the Y coordinate is increasing
190
190
  # -1 is returned for a crossing where the Y coordinate is decreasing
191
191
  def point_crossings(x1, y1, x2, y2, px, py)
192
- return BigDecimal('0') if (py < y1 && py < y2)
193
- return BigDecimal('0') if (py >= y1 && py >= y2)
192
+ return 0 if (py < y1 && py < y2)
193
+ return 0 if (py >= y1 && py >= y2)
194
194
  # assert(y1 != y2);
195
- return BigDecimal('0') if (px >= x1 && px >= x2)
195
+ return 0 if (px >= x1 && px >= x2)
196
196
  return ((y1 < y2) ? 1 : -1) if (px < x1 && px < x2)
197
197
  xintercept = x1 + (py - y1) * (x2 - x1) / (y2 - y1);
198
- return BigDecimal('0') if (px >= xintercept)
199
- (y1 < y2) ? BigDecimal('1') : BigDecimal('-1')
198
+ return 0 if (px >= xintercept)
199
+ (y1 < y2) ? 1 : -1
200
200
  end
201
201
  end
202
202
 
@@ -23,6 +23,7 @@ require 'perfect_shape/shape'
23
23
  require 'perfect_shape/point'
24
24
  require 'perfect_shape/line'
25
25
  require 'perfect_shape/quadratic_bezier_curve'
26
+ require 'perfect_shape/cubic_bezier_curve'
26
27
  require 'perfect_shape/multi_point'
27
28
 
28
29
  module PerfectShape
@@ -31,7 +32,10 @@ module PerfectShape
31
32
  include MultiPoint
32
33
  include Equalizer.new(:shapes, :closed, :winding_rule)
33
34
 
34
- SHAPE_TYPES = [Array, Point, Line]
35
+ # Available class types for path shapes
36
+ SHAPE_TYPES = [Array, PerfectShape::Point, PerfectShape::Line, PerfectShape::QuadraticBezierCurve, PerfectShape::CubicBezierCurve]
37
+
38
+ # Available winding rules
35
39
  WINDING_RULES = [:wind_non_zero, :wind_even_odd]
36
40
 
37
41
  attr_reader :winding_rule
@@ -61,7 +65,10 @@ module PerfectShape
61
65
  shape.points.each do |point|
62
66
  the_points << point.to_a
63
67
  end
64
- # when CubicBezierCurve # TODO
68
+ when CubicBezierCurve
69
+ shape.points.each do |point|
70
+ the_points << point.to_a
71
+ end
65
72
  end
66
73
  end
67
74
  the_points << @shapes.first.to_a if closed?
@@ -83,7 +90,8 @@ module PerfectShape
83
90
  :line_to
84
91
  when QuadraticBezierCurve
85
92
  :quad_to
86
- # when CubicBezierCurve # TODO
93
+ when CubicBezierCurve
94
+ :cubic_to
87
95
  end
88
96
  end
89
97
  the_drawing_shapes << :close if closed?
@@ -113,7 +121,7 @@ module PerfectShape
113
121
  # Here we know that both x and y are finite.
114
122
  return false if shapes.count < 2
115
123
  mask = winding_rule == :wind_non_zero ? -1 : 1
116
- (point_crossings(x, y).to_i & mask) != 0
124
+ (point_crossings(x, y) & mask) != 0
117
125
  else
118
126
  # Either x or y was infinite or NaN.
119
127
  # A NaN always produces a negative response to any test
@@ -137,67 +145,71 @@ module PerfectShape
137
145
  def point_crossings(x_or_point, y = nil)
138
146
  x, y = normalize_point(x_or_point, y)
139
147
  return unless x && y
140
- return BigDecimal('0') if shapes.count == 0
148
+ return 0 if shapes.count == 0
141
149
  movx = movy = curx = cury = endx = endy = 0
142
150
  coords = points.flatten
143
151
  curx = movx = coords[0]
144
152
  cury = movy = coords[1]
145
- crossings = BigDecimal('0')
146
- ci = BigDecimal('2')
153
+ crossings = 0
154
+ ci = 2
147
155
  1.upto(shapes.count - 1).each do |i|
148
- case drawing_types[i]
149
- when :move_to
150
- if cury != movy
151
- line = PerfectShape::Line.new(points: [[curx, cury], [movx, movy]])
152
- crossings += line.point_crossings(x, y)
153
- end
154
- movx = curx = coords[ci]
155
- ci += 1
156
- movy = cury = coords[ci]
157
- ci += 1
158
- when :line_to
159
- endx = coords[ci]
160
- ci += 1
161
- endy = coords[ci]
162
- ci += 1
163
- line = PerfectShape::Line.new(points: [[curx, cury], [endx, endy]])
156
+ case drawing_types[i]
157
+ when :move_to
158
+ if cury != movy
159
+ line = PerfectShape::Line.new(points: [[curx, cury], [movx, movy]])
164
160
  crossings += line.point_crossings(x, y)
165
- curx = endx;
166
- cury = endy;
167
- when :quad_to
168
- quad_ctrlx = coords[ci]
169
- ci += 1
170
- quad_ctrly = coords[ci]
171
- ci += 1
172
- endx = coords[ci]
173
- ci += 1
174
- endy = coords[ci]
175
- ci += 1
176
- quad = PerfectShape::QuadraticBezierCurve.new(points: [[curx, cury], [quad_ctrlx, quad_ctrly], [endx, endy]])
177
- crossings += quad.point_crossings(x, y, 0)
178
- curx = endx;
179
- cury = endy;
180
- # when :cubic_to # TODO
181
- # crossings +=
182
- # Curve.point_crossings_for_cubic(x, y,
183
- # curx, cury,
184
- # coords[ci++],
185
- # coords[ci++],
186
- # coords[ci++],
187
- # coords[ci++],
188
- # endx = coords[ci++],
189
- # endy = coords[ci++],
190
- # 0);
191
- # curx = endx;
192
- # cury = endy;
193
- when :close
194
- if cury != movy
195
- line = PerfectShape::Line.new(points: [[curx, cury], [movx, movy]])
196
- crossings += line.point_crossings(x, y)
197
- end
198
- curx = movx
199
- cury = movy
200
161
  end
162
+ movx = curx = coords[ci]
163
+ ci += 1
164
+ movy = cury = coords[ci]
165
+ ci += 1
166
+ when :line_to
167
+ endx = coords[ci]
168
+ ci += 1
169
+ endy = coords[ci]
170
+ ci += 1
171
+ line = PerfectShape::Line.new(points: [[curx, cury], [endx, endy]])
172
+ crossings += line.point_crossings(x, y)
173
+ curx = endx;
174
+ cury = endy;
175
+ when :quad_to
176
+ quad_ctrlx = coords[ci]
177
+ ci += 1
178
+ quad_ctrly = coords[ci]
179
+ ci += 1
180
+ endx = coords[ci]
181
+ ci += 1
182
+ endy = coords[ci]
183
+ ci += 1
184
+ quad = PerfectShape::QuadraticBezierCurve.new(points: [[curx, cury], [quad_ctrlx, quad_ctrly], [endx, endy]])
185
+ crossings += quad.point_crossings(x, y)
186
+ curx = endx;
187
+ cury = endy;
188
+ when :cubic_to
189
+ cubic_ctrl1x = coords[ci]
190
+ ci += 1
191
+ cubic_ctrl1y = coords[ci]
192
+ ci += 1
193
+ cubic_ctrl2x = coords[ci]
194
+ ci += 1
195
+ cubic_ctrl2y = coords[ci]
196
+ ci += 1
197
+ endx = coords[ci]
198
+ ci += 1
199
+ endy = coords[ci]
200
+ ci += 1
201
+ cubic = PerfectShape::CubicBezierCurve.new(points: [[curx, cury], [cubic_ctrl1x, cubic_ctrl1y], [cubic_ctrl2x, cubic_ctrl2y], [endx, endy]])
202
+ crossings += cubic.point_crossings(x, y)
203
+ curx = endx;
204
+ cury = endy;
205
+ when :close
206
+ if cury != movy
207
+ line = PerfectShape::Line.new(points: [[curx, cury], [movx, movy]])
208
+ crossings += line.point_crossings(x, y)
209
+ end
210
+ curx = movx
211
+ cury = movy
212
+ end
201
213
  end
202
214
  if cury != movy
203
215
  line = PerfectShape::Line.new(points: [[curx, cury], [movx, movy]])
@@ -26,33 +26,41 @@ module PerfectShape
26
26
  # Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/QuadCurve2D.html
27
27
  class QuadraticBezierCurve < Shape
28
28
  class << self
29
+ # Calculates the number of times the quadratic bézier curve from (x1,y1) to (x2,y2)
30
+ # crosses the ray extending to the right from (x,y).
31
+ # If the point lies on a part of the curve,
32
+ # then no crossings are counted for that intersection.
33
+ # the level parameter should be 0 at the top-level call and will count
34
+ # up for each recursion level to prevent infinite recursion
35
+ # +1 is added for each crossing where the Y coordinate is increasing
36
+ # -1 is added for each crossing where the Y coordinate is decreasing
29
37
  def point_crossings(x1, y1, xc, yc, x2, y2, px, py, level = 0)
30
- return BigDecimal('0') if (py < y1 && py < yc && py < y2)
31
- return BigDecimal('0') if (py >= y1 && py >= yc && py >= y2)
38
+ return 0 if (py < y1 && py < yc && py < y2)
39
+ return 0 if (py >= y1 && py >= yc && py >= y2)
32
40
  # Note y1 could equal y2...
33
- return BigDecimal('0') if (px >= x1 && px >= xc && px >= x2)
41
+ return 0 if (px >= x1 && px >= xc && px >= x2)
34
42
  if (px < x1 && px < xc && px < x2)
35
43
  if (py >= y1)
36
- return BigDecimal('1') if (py < y2)
44
+ return 1 if (py < y2)
37
45
  else
38
46
  # py < y1
39
- return BigDecimal('-1') if (py >= y2)
47
+ return -1 if (py >= y2)
40
48
  end
41
49
  # py outside of y11 range, and/or y1==y2
42
- return BigDecimal('0')
50
+ return 0
43
51
  end
44
52
  # double precision only has 52 bits of mantissa
45
53
  return PerfectShape::Line.point_crossings(x1, y1, x2, y2, px, py) if (level > 52)
46
- x1c = (x1 + xc) / BigDecimal('2')
47
- y1c = (y1 + yc) / BigDecimal('2')
48
- xc1 = (xc + x2) / BigDecimal('2')
49
- yc1 = (yc + y2) / BigDecimal('2')
50
- xc = (x1c + xc1) / BigDecimal('2')
51
- yc = (y1c + yc1) / BigDecimal('2')
54
+ x1c = BigDecimal((x1 + xc).to_s) / 2
55
+ y1c = BigDecimal((y1 + yc).to_s) / 2
56
+ xc1 = BigDecimal((xc + x2).to_s) / 2
57
+ yc1 = BigDecimal((yc + y2).to_s) / 2
58
+ xc = BigDecimal((x1c + xc1).to_s) / 2
59
+ yc = BigDecimal((y1c + yc1).to_s) / 2
52
60
  # [xy]c are NaN if any of [xy]0c or [xy]c1 are NaN
53
61
  # [xy]0c or [xy]c1 are NaN if any of [xy][0c1] are NaN
54
62
  # These values are also NaN if opposing infinities are added
55
- return BigDecimal('0') if (xc.nan? || yc.nan?)
63
+ return 0 if (xc.nan? || yc.nan?)
56
64
  point_crossings(x1, y1, x1c, y1c, xc, yc, px, py, level+1) +
57
65
  point_crossings(xc, yc, xc1, yc1, x2, y2, px, py, level+1);
58
66
  end
@@ -69,7 +77,7 @@ module PerfectShape
69
77
  # @return {@code true} if the point lies within the bound of
70
78
  # the quadratic bézier curve, {@code false} if the point lies outside of the
71
79
  # quadratic bézier curve's bounds.
72
- def contain?(x_or_point, y = nil, distance: 0)
80
+ def contain?(x_or_point, y = nil)
73
81
  x, y = normalize_point(x_or_point, y)
74
82
  return unless x && y
75
83
 
@@ -2,11 +2,11 @@
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.1.1 ruby lib
5
+ # stub: perfect-shape 0.1.2 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "perfect-shape".freeze
9
- s.version = "0.1.1"
9
+ s.version = "0.1.2"
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]
@@ -27,6 +27,7 @@ Gem::Specification.new do |s|
27
27
  "lib/perfect-shape.rb",
28
28
  "lib/perfect_shape/arc.rb",
29
29
  "lib/perfect_shape/circle.rb",
30
+ "lib/perfect_shape/cubic_bezier_curve.rb",
30
31
  "lib/perfect_shape/ellipse.rb",
31
32
  "lib/perfect_shape/line.rb",
32
33
  "lib/perfect_shape/math.rb",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perfect-shape
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
@@ -116,6 +116,7 @@ files:
116
116
  - lib/perfect-shape.rb
117
117
  - lib/perfect_shape/arc.rb
118
118
  - lib/perfect_shape/circle.rb
119
+ - lib/perfect_shape/cubic_bezier_curve.rb
119
120
  - lib/perfect_shape/ellipse.rb
120
121
  - lib/perfect_shape/line.rb
121
122
  - lib/perfect_shape/math.rb