aurora-geometry 0.0.2
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 +7 -0
- data/.gitignore +6 -0
- data/Gemfile +7 -0
- data/LICENSE +21 -0
- data/README.markdown +105 -0
- data/Rakefile +24 -0
- data/aurora-geometry.gemspec +23 -0
- data/lib/geometry.rb +22 -0
- data/lib/geometry/arc.rb +94 -0
- data/lib/geometry/circle.rb +122 -0
- data/lib/geometry/cluster_factory.rb +15 -0
- data/lib/geometry/edge.rb +140 -0
- data/lib/geometry/line.rb +154 -0
- data/lib/geometry/obround.rb +238 -0
- data/lib/geometry/path.rb +67 -0
- data/lib/geometry/point.rb +163 -0
- data/lib/geometry/point_zero.rb +107 -0
- data/lib/geometry/polygon.rb +368 -0
- data/lib/geometry/polyline.rb +318 -0
- data/lib/geometry/rectangle.rb +378 -0
- data/lib/geometry/regular_polygon.rb +136 -0
- data/lib/geometry/rotation.rb +190 -0
- data/lib/geometry/size.rb +75 -0
- data/lib/geometry/size_zero.rb +70 -0
- data/lib/geometry/square.rb +113 -0
- data/lib/geometry/text.rb +24 -0
- data/lib/geometry/transformation.rb +171 -0
- data/lib/geometry/transformation/composition.rb +39 -0
- data/lib/geometry/triangle.rb +78 -0
- data/lib/geometry/vector.rb +34 -0
- data/test/geometry.rb +5 -0
- data/test/geometry/arc.rb +25 -0
- data/test/geometry/circle.rb +112 -0
- data/test/geometry/edge.rb +132 -0
- data/test/geometry/line.rb +132 -0
- data/test/geometry/obround.rb +25 -0
- data/test/geometry/path.rb +66 -0
- data/test/geometry/point.rb +258 -0
- data/test/geometry/point_zero.rb +177 -0
- data/test/geometry/polygon.rb +214 -0
- data/test/geometry/polyline.rb +266 -0
- data/test/geometry/rectangle.rb +154 -0
- data/test/geometry/regular_polygon.rb +120 -0
- data/test/geometry/rotation.rb +108 -0
- data/test/geometry/size.rb +97 -0
- data/test/geometry/size_zero.rb +153 -0
- data/test/geometry/square.rb +66 -0
- data/test/geometry/transformation.rb +169 -0
- data/test/geometry/transformation/composition.rb +49 -0
- data/test/geometry/triangle.rb +32 -0
- data/test/geometry/vector.rb +41 -0
- metadata +115 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
# Include this module in the base class of a class cluster to handle swizzling
|
2
|
+
# of ::new
|
3
|
+
module ClusterFactory
|
4
|
+
def self.included(parent)
|
5
|
+
class << parent
|
6
|
+
alias :original_new :new
|
7
|
+
|
8
|
+
def inherited(subclass)
|
9
|
+
class << subclass
|
10
|
+
alias :new :original_new
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'mathn'
|
2
|
+
|
3
|
+
require_relative 'point'
|
4
|
+
|
5
|
+
module Geometry
|
6
|
+
|
7
|
+
=begin rdoc
|
8
|
+
An edge. It's a line segment between 2 points. Generally part of a {Polygon}.
|
9
|
+
|
10
|
+
== Usage
|
11
|
+
edge = Geometry::Edge([1,1], [2,2])
|
12
|
+
|
13
|
+
=end
|
14
|
+
|
15
|
+
class Edge
|
16
|
+
attr_reader :first, :last
|
17
|
+
|
18
|
+
# Construct a new {Edge} object from any two things that can be converted
|
19
|
+
# to a {Point}.
|
20
|
+
def initialize(point0, point1)
|
21
|
+
@first, @last = [Point[point0], Point[point1]]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Two Edges are equal if both have equal {Point}s in the same order
|
25
|
+
def ==(other)
|
26
|
+
(@first == other.first) && (@last == other.last)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param [Point] point A {Point} to spaceship with
|
30
|
+
# @return [Boolean] Returns 1 if the {Point} is strictly to the left of the receiver, -1 to the right, and 0 if the point is on the receiver
|
31
|
+
def <=>(point)
|
32
|
+
case point
|
33
|
+
when Point
|
34
|
+
k = (@last.x - @first.x) * (point.y - @first.y) - (point.x - @first.x) * (@last.y - @first.y)
|
35
|
+
if 0 == k
|
36
|
+
(((@first.x <=> point.x) + (@last.x <=> point.x)).abs <= 1) && (((@first.y <=> point.y) + (@last.y <=> point.y)).abs <= 1) ? 0 : nil
|
37
|
+
else
|
38
|
+
k <=> 0
|
39
|
+
end
|
40
|
+
else
|
41
|
+
raise ArgumentError, "Can't spaceship with #{point.class}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Return a new {Edge} with swapped endpoints
|
46
|
+
def reverse
|
47
|
+
Edge.new(@last, @first)
|
48
|
+
end
|
49
|
+
|
50
|
+
# In-place swap the endpoints
|
51
|
+
def reverse!
|
52
|
+
@first, @last = @last, @first
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return the {Edge}'s length along the Y axis
|
57
|
+
def height
|
58
|
+
(@first.y - @last.y).abs
|
59
|
+
end
|
60
|
+
|
61
|
+
# Return the {Edge}'s length along the X axis
|
62
|
+
def width
|
63
|
+
(@first.x - @last.x).abs
|
64
|
+
end
|
65
|
+
|
66
|
+
def inspect
|
67
|
+
'Edge(' + @first.inspect + ', ' + @last.inspect + ')'
|
68
|
+
end
|
69
|
+
alias :to_s :inspect
|
70
|
+
|
71
|
+
# @return [Bool] Returns true if the passed {Edge} is parallel to the receiver
|
72
|
+
def parallel?(edge)
|
73
|
+
v1, v2 = self.direction, edge.direction
|
74
|
+
winding = v1[0]*v2[1] - v1[1]*v2[0]
|
75
|
+
if 0 == winding # collinear?
|
76
|
+
if v1 == v2
|
77
|
+
1 # same direction
|
78
|
+
else
|
79
|
+
-1 # opposite direction
|
80
|
+
end
|
81
|
+
else
|
82
|
+
false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param [Edge] other The other {Edge} to check
|
87
|
+
# @return [Bool] Returns true if the receiver and the passed {Edge} share an endpoint
|
88
|
+
def connected?(other)
|
89
|
+
(@first == other.last) || (@last == other.first) || (@first == other.first) || (@last == other.last)
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [Vector] A unit {Vector} pointing from first to last
|
93
|
+
def direction
|
94
|
+
self.vector.normalize
|
95
|
+
end
|
96
|
+
|
97
|
+
# Find the intersection of two {Edge}s (http://bloggingmath.wordpress.com/2009/05/29/line-segment-intersection/)
|
98
|
+
# @param [Edge] other The other {Edge}
|
99
|
+
# @return [Point] The intersection of the two {Edge}s, nil if they don't intersect, true if they're collinear and overlapping, and false if they're collinear and non-overlapping
|
100
|
+
def intersection(other)
|
101
|
+
return self.first if (self.first == other.first) or (self.first == other.last)
|
102
|
+
return self.last if (self.last == other.first) or (self.last == other.last)
|
103
|
+
|
104
|
+
p0, p1 = self.first, self.last
|
105
|
+
p2, p3 = other.first, other.last
|
106
|
+
v1, v2 = self.vector, other.vector
|
107
|
+
|
108
|
+
denominator = v1[0] * v2[1] - v2[0] * v1[1] # v1 x v2
|
109
|
+
p = p0 - p2
|
110
|
+
if denominator == 0 # collinear, so check for overlap
|
111
|
+
if 0 == (-v1[1] * p.x + v1[0] * p.y) # collinear?
|
112
|
+
# The edges are collinear, but do they overlap?
|
113
|
+
# Project them onto the x and y axes to find out
|
114
|
+
left1, right1 = [self.first[0], self.last[0]].sort
|
115
|
+
bottom1, top1 = [self.first[1], self.last[1]].sort
|
116
|
+
left2, right2 = [other.first[0], other.last[0]].sort
|
117
|
+
bottom2, top2 = [other.first[1], other.last[1]].sort
|
118
|
+
|
119
|
+
!((left2 > right1) || (right2 < left1) || (top2 < bottom1) || (bottom2 > top1))
|
120
|
+
else
|
121
|
+
nil
|
122
|
+
end
|
123
|
+
else
|
124
|
+
s = (-v1[1] * p.x + v1[0] * p.y) / denominator # v1 x (p0 - p2) / denominator
|
125
|
+
t = ( v2[0] * p.y - v2[1] * p.x) / denominator # v2 x (p0 - p2) / denominator
|
126
|
+
|
127
|
+
p0 + v1 * t if ((0..1) === s) && ((0..1) === t)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# @return [Vector] A {Vector} pointing from first to last
|
132
|
+
def vector
|
133
|
+
Vector[*((last-first).to_a)]
|
134
|
+
end
|
135
|
+
|
136
|
+
def to_a
|
137
|
+
[@first, @last]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require_relative 'cluster_factory'
|
2
|
+
require_relative 'point'
|
3
|
+
|
4
|
+
module Geometry
|
5
|
+
|
6
|
+
=begin rdoc
|
7
|
+
A cluster of objects representing a Line of infinite length
|
8
|
+
|
9
|
+
Supports two-point, slope-intercept, and point-slope initializer forms
|
10
|
+
|
11
|
+
== Usage
|
12
|
+
|
13
|
+
=== Two-point constructors
|
14
|
+
line = Geometry::Line[[0,0], [10,10]]
|
15
|
+
line = Geometry::Line[Geometry::Point[0,0], Geometry::Point[10,10]]
|
16
|
+
line = Geometry::Line[Vector[0,0], Vector[10,10]]
|
17
|
+
|
18
|
+
=== Slope-intercept constructors
|
19
|
+
Geometry::Line[Rational(3,4), 5] # Slope = 3/4, Intercept = 5
|
20
|
+
Geometry::Line[0.75, 5]
|
21
|
+
|
22
|
+
=== Point-slope constructors
|
23
|
+
Geometry::Line(Geometry::Point[0,0], 0.75)
|
24
|
+
Geometry::Line(Vector[0,0], Rational(3,4))
|
25
|
+
|
26
|
+
=== Special constructors (2D only)
|
27
|
+
Geometry::Line.horizontal(y=0)
|
28
|
+
Geometry::Line.vertical(x=0)
|
29
|
+
=end
|
30
|
+
|
31
|
+
class Line
|
32
|
+
include ClusterFactory
|
33
|
+
|
34
|
+
# @overload [](Array, Array)
|
35
|
+
# @return [TwoPointLine]
|
36
|
+
# @overload [](Point, Point)
|
37
|
+
# @return [TwoPointLine]
|
38
|
+
# @overload [](Vector, Vector)
|
39
|
+
# @return [TwoPointLine]
|
40
|
+
# @overload [](y-intercept, slope)
|
41
|
+
# @return [SlopeInterceptLine]
|
42
|
+
# @overload [](point, slope)
|
43
|
+
# @return [PointSlopeLine]
|
44
|
+
def self.[](*args)
|
45
|
+
if( 2 == args.size )
|
46
|
+
args.map! {|x| x.is_a?(Array) ? Point[*x] : x}
|
47
|
+
|
48
|
+
# If both args are Points, create a TwoPointLine
|
49
|
+
return TwoPointLine.new(*args) if args.all? {|x| x.is_a?(Vector)}
|
50
|
+
|
51
|
+
# If only the first arg is a Point, create a PointSlopeLine
|
52
|
+
return PointSlopeLine.new(*args) if args.first.is_a?(Vector)
|
53
|
+
|
54
|
+
# Otherise, create a SlopeInterceptLine
|
55
|
+
return SlopeInterceptLine.new(*args)
|
56
|
+
else
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# @overload new(from, to)
|
62
|
+
# @option options [Point] :from A starting {Point}
|
63
|
+
# @option options [Point] :to An end {Point}
|
64
|
+
# @return [TwoPointLine]
|
65
|
+
# @overload new(start, end)
|
66
|
+
# @option options [Point] :start A starting {Point}
|
67
|
+
# @option options [Point] :end An end {Point}
|
68
|
+
# @return [TwoPointLine]
|
69
|
+
def self.new(options={})
|
70
|
+
from = options[:from] || options[:start]
|
71
|
+
to = options[:end] || options[:to]
|
72
|
+
|
73
|
+
if from and to
|
74
|
+
TwoPointLine.new(from, to)
|
75
|
+
else
|
76
|
+
raise ArgumentError, "Start and end Points must be provided"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.horizontal(y_intercept=0)
|
81
|
+
SlopeInterceptLine.new(0, y_intercept)
|
82
|
+
end
|
83
|
+
def self.vertical(x_intercept=0)
|
84
|
+
SlopeInterceptLine.new(1/0.0, x_intercept)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# @private
|
89
|
+
class PointSlopeLine < Line
|
90
|
+
# @return [Number] the slope of the {Line}
|
91
|
+
attr_reader :slope
|
92
|
+
|
93
|
+
def initialize(point, slope)
|
94
|
+
@point = Point[point]
|
95
|
+
@slope = slope
|
96
|
+
end
|
97
|
+
def to_s
|
98
|
+
'Line(' + @slope.to_s + ',' + @point.to_s + ')'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# @private
|
103
|
+
class SlopeInterceptLine < Line
|
104
|
+
# @return [Number] the slope of the {Line}
|
105
|
+
attr_reader :slope
|
106
|
+
|
107
|
+
def initialize(slope, intercept)
|
108
|
+
@slope = slope
|
109
|
+
@intercept = intercept
|
110
|
+
end
|
111
|
+
|
112
|
+
def horizontal?
|
113
|
+
0 == @slope
|
114
|
+
end
|
115
|
+
def vertical?
|
116
|
+
(1/0.0) == @slope
|
117
|
+
end
|
118
|
+
|
119
|
+
def intercept(axis=:y)
|
120
|
+
case axis
|
121
|
+
when :x
|
122
|
+
vertical? ? @intercept : (horizontal? ? nil : (-@intercept/@slope))
|
123
|
+
when :y
|
124
|
+
vertical? ? nil : @intercept
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def to_s
|
129
|
+
'Line(' + @slope.to_s + ',' + @intercept.to_s + ')'
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# @private
|
134
|
+
class TwoPointLine < Line
|
135
|
+
attr_reader :first, :last
|
136
|
+
|
137
|
+
def initialize(point0, point1)
|
138
|
+
@first, @last = [Point[point0], Point[point1]]
|
139
|
+
end
|
140
|
+
def inspect
|
141
|
+
'Line(' + @first.inspect + ', ' + @last.inspect + ')'
|
142
|
+
end
|
143
|
+
alias :to_s :inspect
|
144
|
+
|
145
|
+
# @group Accessors
|
146
|
+
# !@attribute [r[ slope
|
147
|
+
# @return [Number] the slope of the {Line}
|
148
|
+
def slope
|
149
|
+
(last.y - first.y)/(last.x - first.x)
|
150
|
+
end
|
151
|
+
# @endgroup
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
@@ -0,0 +1,238 @@
|
|
1
|
+
require_relative 'cluster_factory'
|
2
|
+
require_relative 'point'
|
3
|
+
|
4
|
+
module Geometry
|
5
|
+
|
6
|
+
=begin
|
7
|
+
The {Obround} class cluster represents a rectangle with semicircular end caps
|
8
|
+
|
9
|
+
{http://en.wiktionary.org/wiki/obround}
|
10
|
+
=end
|
11
|
+
|
12
|
+
class Obround
|
13
|
+
include ClusterFactory
|
14
|
+
|
15
|
+
# @overload new(width, height)
|
16
|
+
# Creates a {Obround} of the given width and height, centered on the origin
|
17
|
+
# @param [Number] height Height
|
18
|
+
# @param [Number] width Width
|
19
|
+
# @return [CenteredObround]
|
20
|
+
# @overload new(size)
|
21
|
+
# Creates a {Obround} of the given {Size} centered on the origin
|
22
|
+
# @param [Size] size Width and height
|
23
|
+
# @return [CenteredObround]
|
24
|
+
# @overload new(point0, point1)
|
25
|
+
# Creates a {Obround} using the given {Point}s
|
26
|
+
# @param [Point] point0 A corner
|
27
|
+
# @param [Point] point1 The other corner
|
28
|
+
# @overload new(origin, size)
|
29
|
+
# Creates a {Obround} from the given origin and size
|
30
|
+
# @param [Point] origin Lower-left corner
|
31
|
+
# @param [Size] size Width and height
|
32
|
+
# @return [SizedObround]
|
33
|
+
# @overload new(left, bottom, right, top)
|
34
|
+
# Creates a {Obround} from the locations of each side
|
35
|
+
# @param [Number] left X-coordinate of the left side
|
36
|
+
# @param [Number] bottom Y-coordinate of the bottom edge
|
37
|
+
# @param [Number] right X-coordinate of the right side
|
38
|
+
# @param [Number] top Y-coordinate of the top edge
|
39
|
+
# @return [Obround]
|
40
|
+
def self.new(*args)
|
41
|
+
case args.size
|
42
|
+
when 1
|
43
|
+
CenteredObround.new(args[0])
|
44
|
+
when 2
|
45
|
+
if args.all? {|a| a.is_a?(Numeric) }
|
46
|
+
CenteredObround.new(Size[*args])
|
47
|
+
elsif args.all? {|a| a.is_a?(Array) || a.is_a?(Point) }
|
48
|
+
original_new(*args)
|
49
|
+
elsif (args[0].is_a?(Point) or args[0].is_a?(Array))and args[1].is_a?(Size)
|
50
|
+
SizedObround.new(*args)
|
51
|
+
else
|
52
|
+
raise ArgumentError, "Invalid arguments #{args}"
|
53
|
+
end
|
54
|
+
when 4
|
55
|
+
raise ArgumentError unless args.all? {|a| a.is_a?(Numeric)}
|
56
|
+
left, bottom, right, top = *args
|
57
|
+
original_new(Point[left, bottom], Point[right, top])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Create a {Obround} using the given {Point}s
|
62
|
+
# @param [Point0] point0 The bottom-left corner (closest to the origin)
|
63
|
+
# @param [Point1] point1 The top-right corner (farthest from the origin)
|
64
|
+
def initialize(point0, point1)
|
65
|
+
point0, point1 = Point[point0], Point[point1]
|
66
|
+
raise(ArgumentError, "Point sizes must match") unless point0.size == point1.size
|
67
|
+
|
68
|
+
# Reorder the points to get lower-left and upper-right
|
69
|
+
if (point0.x > point1.x) && (point0.y > point1.y)
|
70
|
+
point0, point1 = point1, point0
|
71
|
+
else
|
72
|
+
p0x, p1x = [point0.x, point1.x].minmax
|
73
|
+
p0y, p1y = [point0.y, point1.y].minmax
|
74
|
+
point0 = Point[p0x, p0y]
|
75
|
+
point1 = Point[p1x, p1y]
|
76
|
+
end
|
77
|
+
@points = [point0, point1]
|
78
|
+
end
|
79
|
+
|
80
|
+
def eql?(other)
|
81
|
+
self.points == other.points
|
82
|
+
end
|
83
|
+
alias :== :eql?
|
84
|
+
|
85
|
+
# @group Accessors
|
86
|
+
|
87
|
+
# @return [Point] The {Obround}'s center
|
88
|
+
def center
|
89
|
+
min, max = @points.minmax {|a,b| a.y <=> b.y}
|
90
|
+
Point[(max.x+min.x)/2, (max.y+min.y)/2]
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [Array<Point>] The {Obround}'s four points (counterclockwise)
|
94
|
+
def points
|
95
|
+
point0, point2 = *@points
|
96
|
+
point1 = Point[point2.x, point0.y]
|
97
|
+
point3 = Point[point0.x, point2.y]
|
98
|
+
[point0, point1, point2, point3]
|
99
|
+
end
|
100
|
+
|
101
|
+
def origin
|
102
|
+
minx = @points.min {|a,b| a.x <=> b.x}
|
103
|
+
miny = @points.min {|a,b| a.y <=> b.y}
|
104
|
+
Point[minx.x, miny.y]
|
105
|
+
end
|
106
|
+
|
107
|
+
def height
|
108
|
+
min, max = @points.minmax {|a,b| a.y <=> b.y}
|
109
|
+
max.y - min.y
|
110
|
+
end
|
111
|
+
|
112
|
+
def width
|
113
|
+
min, max = @points.minmax {|a,b| a.x <=> b.x}
|
114
|
+
max.x - min.x
|
115
|
+
end
|
116
|
+
# @endgroup
|
117
|
+
end
|
118
|
+
|
119
|
+
class CenteredObround < Obround
|
120
|
+
# @return [Point] The {Obround}'s center
|
121
|
+
attr_accessor :center
|
122
|
+
attr_reader :origin
|
123
|
+
# @return [Size] The {Size} of the {Obround}
|
124
|
+
attr_accessor :size
|
125
|
+
|
126
|
+
# @overload new(width, height)
|
127
|
+
# Creates a {Obround} of the given width and height, centered on the origin
|
128
|
+
# @param [Number] height Height
|
129
|
+
# @param [Number] width Width
|
130
|
+
# @return [CenteredObround]
|
131
|
+
# @overload new(size)
|
132
|
+
# Creates a {Obround} of the given {Size} centered on the origin
|
133
|
+
# @param [Size] size Width and height
|
134
|
+
# @return [CenteredObround]
|
135
|
+
# @overload new(center, size)
|
136
|
+
# Creates a {Obround} with the given center point and size
|
137
|
+
# @param [Point] center
|
138
|
+
# @param [Size] size
|
139
|
+
def initialize(*args)
|
140
|
+
if args[0].is_a?(Size)
|
141
|
+
@center = Point[0,0]
|
142
|
+
@size = args[0]
|
143
|
+
elsif args[0].is_a?(Geometry::Point) and args[1].is_a?(Geometry::Size)
|
144
|
+
@center, @size = args[0,1]
|
145
|
+
elsif (2 == args.size) and args.all? {|a| a.is_a?(Numeric)}
|
146
|
+
@center = Point[0,0]
|
147
|
+
@size = Geometry::Size[*args]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def eql?(other)
|
152
|
+
(self.center == other.center) && (self.size == other.size)
|
153
|
+
end
|
154
|
+
alias :== :eql?
|
155
|
+
|
156
|
+
# @group Accessors
|
157
|
+
# @return [Array<Point>] The {Obround}'s four points (clockwise)
|
158
|
+
def points
|
159
|
+
point0 = @center - @size/2.0
|
160
|
+
point2 = @center + @size/2.0
|
161
|
+
point1 = Point[point0.x,point2.y]
|
162
|
+
point3 = Point[point2.x, point0.y]
|
163
|
+
[point0, point1, point2, point3]
|
164
|
+
end
|
165
|
+
|
166
|
+
def height
|
167
|
+
@size.height
|
168
|
+
end
|
169
|
+
|
170
|
+
def width
|
171
|
+
@size.width
|
172
|
+
end
|
173
|
+
# @endgroup
|
174
|
+
end
|
175
|
+
|
176
|
+
class SizedObround < Obround
|
177
|
+
# @return [Point] The {Obround}'s center
|
178
|
+
attr_reader :center
|
179
|
+
# @return [Point] The {Obround}'s origin
|
180
|
+
attr_accessor :origin
|
181
|
+
# @return [Size] The {Size} of the {Obround}
|
182
|
+
attr_accessor :size
|
183
|
+
|
184
|
+
# @overload new(width, height)
|
185
|
+
# Creates an {Obround} of the given width and height with its origin at [0,0]
|
186
|
+
# @param [Number] height Height
|
187
|
+
# @param [Number] width Width
|
188
|
+
# @return SizedObround
|
189
|
+
# @overload new(size)
|
190
|
+
# Creates an {Obround} of the given {Size} with its origin at [0,0]
|
191
|
+
# @param [Size] size Width and height
|
192
|
+
# @return SizedObround
|
193
|
+
# @overload new(origin, size)
|
194
|
+
# Creates an {Obround} with the given origin point and size
|
195
|
+
# @param [Point] origin
|
196
|
+
# @param [Size] size
|
197
|
+
# @return SizedObround
|
198
|
+
def initialize(*args)
|
199
|
+
if args[0].is_a?(Size)
|
200
|
+
@origin = Point[0,0]
|
201
|
+
@size = args[0]
|
202
|
+
elsif (args[0].is_a?(Point) or args[0].is_a?(Array)) and args[1].is_a?(Geometry::Size)
|
203
|
+
@origin, @size = Point[args[0]], args[1]
|
204
|
+
elsif (2 == args.size) and args.all? {|a| a.is_a?(Numeric)}
|
205
|
+
@origin = Point[0,0]
|
206
|
+
@size = Geometry::Size[*args]
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def eql?(other)
|
211
|
+
(self.origin == other.origin) && (self.size == other.size)
|
212
|
+
end
|
213
|
+
alias :== :eql?
|
214
|
+
|
215
|
+
# @group Accessors
|
216
|
+
def center
|
217
|
+
@origin + @size/2
|
218
|
+
end
|
219
|
+
|
220
|
+
# @return [Array<Point>] The {Obround}'s four points (clockwise)
|
221
|
+
def points
|
222
|
+
point0 = @origin
|
223
|
+
point2 = @origin + @size
|
224
|
+
point1 = Point[point0.x,point2.y]
|
225
|
+
point3 = Point[point2.x, point0.y]
|
226
|
+
[point0, point1, point2, point3]
|
227
|
+
end
|
228
|
+
|
229
|
+
def height
|
230
|
+
@size.height
|
231
|
+
end
|
232
|
+
|
233
|
+
def width
|
234
|
+
@size.width
|
235
|
+
end
|
236
|
+
# @endgroup
|
237
|
+
end
|
238
|
+
end
|