perfect-shape 0.1.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -3
- data/README.md +300 -21
- data/VERSION +1 -1
- data/lib/perfect_shape/arc.rb +48 -34
- data/lib/perfect_shape/composite_shape.rb +72 -0
- data/lib/perfect_shape/cubic_bezier_curve.rb +120 -0
- data/lib/perfect_shape/ellipse.rb +12 -8
- data/lib/perfect_shape/line.rb +9 -9
- data/lib/perfect_shape/path.rb +74 -61
- data/lib/perfect_shape/point.rb +4 -4
- data/lib/perfect_shape/polygon.rb +68 -62
- data/lib/perfect_shape/quadratic_bezier_curve.rb +22 -14
- data/lib/perfect_shape/rectangle.rb +10 -2
- data/perfect-shape.gemspec +7 -5
- metadata +6 -4
@@ -37,79 +37,85 @@ module PerfectShape
|
|
37
37
|
# @return {@code true} if the point lies within the bound of
|
38
38
|
# the polygon, {@code false} if the point lies outside of the
|
39
39
|
# polygon's bounds.
|
40
|
-
def contain?(x_or_point, y = nil)
|
40
|
+
def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
|
41
41
|
x, y = normalize_point(x_or_point, y)
|
42
42
|
return unless x && y
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
return false if npoints <= 2 || !bounding_box.contain?(x, y)
|
47
|
-
hits = 0
|
48
|
-
|
49
|
-
lastx = xpoints[npoints - 1]
|
50
|
-
lasty = ypoints[npoints - 1]
|
51
|
-
|
52
|
-
# Walk the edges of the polygon
|
53
|
-
npoints.times do |i|
|
54
|
-
curx = xpoints[i]
|
55
|
-
cury = ypoints[i]
|
56
|
-
|
57
|
-
if cury == lasty
|
58
|
-
lastx = curx
|
59
|
-
lasty = cury
|
60
|
-
next
|
61
|
-
end
|
62
|
-
|
63
|
-
if curx < lastx
|
64
|
-
if x >= lastx
|
65
|
-
lastx = curx
|
66
|
-
lasty = cury
|
67
|
-
next
|
68
|
-
end
|
69
|
-
leftx = curx
|
70
|
-
else
|
71
|
-
if x >= curx
|
72
|
-
lastx = curx
|
73
|
-
lasty = cury
|
74
|
-
next
|
75
|
-
end
|
76
|
-
leftx = lastx
|
43
|
+
if outline
|
44
|
+
points.zip(points.rotate(1)).any? do |point1, point2|
|
45
|
+
Line.new(points: [[point1.first, point1.last], [point2.first, point2.last]]).contain?(x, y, distance_tolerance: distance_tolerance)
|
77
46
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
47
|
+
else
|
48
|
+
npoints = points.count
|
49
|
+
xpoints = points.map(&:first)
|
50
|
+
ypoints = points.map(&:last)
|
51
|
+
return false if npoints <= 2 || !bounding_box.contain?(x, y)
|
52
|
+
hits = 0
|
53
|
+
|
54
|
+
lastx = xpoints[npoints - 1]
|
55
|
+
lasty = ypoints[npoints - 1]
|
56
|
+
|
57
|
+
# Walk the edges of the polygon
|
58
|
+
npoints.times do |i|
|
59
|
+
curx = xpoints[i]
|
60
|
+
cury = ypoints[i]
|
61
|
+
|
62
|
+
if cury == lasty
|
81
63
|
lastx = curx
|
82
64
|
lasty = cury
|
83
65
|
next
|
84
66
|
end
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
67
|
+
|
68
|
+
if curx < lastx
|
69
|
+
if x >= lastx
|
70
|
+
lastx = curx
|
71
|
+
lasty = cury
|
72
|
+
next
|
73
|
+
end
|
74
|
+
leftx = curx
|
75
|
+
else
|
76
|
+
if x >= curx
|
77
|
+
lastx = curx
|
78
|
+
lasty = cury
|
79
|
+
next
|
80
|
+
end
|
81
|
+
leftx = lastx
|
90
82
|
end
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
83
|
+
|
84
|
+
if cury < lasty
|
85
|
+
if y < cury || y >= lasty
|
86
|
+
lastx = curx
|
87
|
+
lasty = cury
|
88
|
+
next
|
89
|
+
end
|
90
|
+
if x < leftx
|
91
|
+
hits += 1
|
92
|
+
lastx = curx
|
93
|
+
lasty = cury
|
94
|
+
next
|
95
|
+
end
|
96
|
+
test1 = x - curx
|
97
|
+
test2 = y - cury
|
98
|
+
else
|
99
|
+
if y < lasty || y >= cury
|
100
|
+
lastx = curx
|
101
|
+
lasty = cury
|
102
|
+
next
|
103
|
+
end
|
104
|
+
if x < leftx
|
105
|
+
hits += 1
|
106
|
+
lastx = curx
|
107
|
+
lasty = cury
|
108
|
+
next
|
109
|
+
end
|
110
|
+
test1 = x - lastx
|
111
|
+
test2 = y - lasty
|
98
112
|
end
|
99
|
-
|
100
|
-
|
101
|
-
lastx = curx
|
102
|
-
lasty = cury
|
103
|
-
next
|
104
|
-
end
|
105
|
-
test1 = x - lastx
|
106
|
-
test2 = y - lasty
|
113
|
+
|
114
|
+
hits += 1 if (test1 < (test2 / (lasty - cury) * (lastx - curx)))
|
107
115
|
end
|
108
|
-
|
109
|
-
hits
|
116
|
+
|
117
|
+
(hits & 1) != 0
|
110
118
|
end
|
111
|
-
|
112
|
-
(hits & 1) != 0
|
113
119
|
end
|
114
120
|
end
|
115
121
|
end
|
@@ -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
|
31
|
-
return
|
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
|
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
|
44
|
+
return 1 if (py < y2)
|
37
45
|
else
|
38
46
|
# py < y1
|
39
|
-
return
|
47
|
+
return -1 if (py >= y2)
|
40
48
|
end
|
41
49
|
# py outside of y11 range, and/or y1==y2
|
42
|
-
return
|
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) /
|
47
|
-
y1c = (y1 + yc) /
|
48
|
-
xc1 = (xc + x2) /
|
49
|
-
yc1 = (yc + y2) /
|
50
|
-
xc = (x1c + xc1) /
|
51
|
-
yc = (y1c + yc1) /
|
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
|
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
|
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
|
|
@@ -21,6 +21,7 @@
|
|
21
21
|
|
22
22
|
require 'perfect_shape/shape'
|
23
23
|
require 'perfect_shape/rectangular_shape'
|
24
|
+
require 'perfect_shape/line'
|
24
25
|
|
25
26
|
module PerfectShape
|
26
27
|
# Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/Rectangle2D.html
|
@@ -36,10 +37,17 @@ module PerfectShape
|
|
36
37
|
# @return {@code true} if the point lies within the bound of
|
37
38
|
# the rectangle, {@code false} if the point lies outside of the
|
38
39
|
# rectangle's bounds.
|
39
|
-
def contain?(x_or_point, y = nil)
|
40
|
+
def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
|
40
41
|
x, y = normalize_point(x_or_point, y)
|
41
42
|
return unless x && y
|
42
|
-
|
43
|
+
if outline
|
44
|
+
Line.new(points: [[self.x, self.y], [self.x + width, self.y]]).contain?(x, y, distance_tolerance: distance_tolerance) or
|
45
|
+
Line.new(points: [[self.x + width, self.y], [self.x + width, self.y + height]]).contain?(x, y, distance_tolerance: distance_tolerance) or
|
46
|
+
Line.new(points: [[self.x + width, self.y + height], [self.x, self.y + height]]).contain?(x, y, distance_tolerance: distance_tolerance) or
|
47
|
+
Line.new(points: [[self.x, self.y + height], [self.x, self.y]]).contain?(x, y, distance_tolerance: distance_tolerance)
|
48
|
+
else
|
49
|
+
x.between?(self.x, self.x + width) && y.between?(self.y, self.y + height)
|
50
|
+
end
|
43
51
|
end
|
44
52
|
end
|
45
53
|
end
|
data/perfect-shape.gemspec
CHANGED
@@ -2,17 +2,17 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: perfect-shape 0.
|
5
|
+
# stub: perfect-shape 0.3.1 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "perfect-shape".freeze
|
9
|
-
s.version = "0.
|
9
|
+
s.version = "0.3.1"
|
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 = "
|
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
|
14
|
+
s.date = "2022-01-08"
|
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 = [
|
18
18
|
"CHANGELOG.md",
|
@@ -27,6 +27,8 @@ 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/composite_shape.rb",
|
31
|
+
"lib/perfect_shape/cubic_bezier_curve.rb",
|
30
32
|
"lib/perfect_shape/ellipse.rb",
|
31
33
|
"lib/perfect_shape/line.rb",
|
32
34
|
"lib/perfect_shape/math.rb",
|
@@ -44,7 +46,7 @@ Gem::Specification.new do |s|
|
|
44
46
|
]
|
45
47
|
s.homepage = "http://github.com/AndyObtiva/perfect-shape".freeze
|
46
48
|
s.licenses = ["MIT".freeze]
|
47
|
-
s.rubygems_version = "3.
|
49
|
+
s.rubygems_version = "3.3.1".freeze
|
48
50
|
s.summary = "Perfect Shape - Geometric Algorithms".freeze
|
49
51
|
|
50
52
|
if s.respond_to? :specification_version then
|
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.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Maleh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: equalizer
|
@@ -98,7 +98,7 @@ description: Perfect Shape is a collection of pure Ruby geometric algorithms tha
|
|
98
98
|
are mostly useful for GUI manipulation like checking containment of a mouse click
|
99
99
|
point in popular geometry shapes such as rectangle, square, arc (open, chord, and
|
100
100
|
pie), ellipse, circle, polygon, and paths containing lines, quadratic bézier curves,
|
101
|
-
and cubic
|
101
|
+
and cubic bezier curves (including both Ray Casting Algorithm, aka Even-odd Rule,
|
102
102
|
and Winding Number Algorithm, aka Nonzero Rule). Additionally, it contains some
|
103
103
|
purely mathematical algorithms like IEEEremainder (also known as IEEE-754 remainder).
|
104
104
|
email: andy.am@gmail.com
|
@@ -116,6 +116,8 @@ 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/composite_shape.rb
|
120
|
+
- lib/perfect_shape/cubic_bezier_curve.rb
|
119
121
|
- lib/perfect_shape/ellipse.rb
|
120
122
|
- lib/perfect_shape/line.rb
|
121
123
|
- lib/perfect_shape/math.rb
|
@@ -149,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
149
151
|
- !ruby/object:Gem::Version
|
150
152
|
version: '0'
|
151
153
|
requirements: []
|
152
|
-
rubygems_version: 3.
|
154
|
+
rubygems_version: 3.3.1
|
153
155
|
signing_key:
|
154
156
|
specification_version: 4
|
155
157
|
summary: Perfect Shape - Geometric Algorithms
|