euclidean 0.1.0
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 +17 -0
- data/.rspec +2 -0
- data/Gemfile +8 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +90 -0
- data/Rakefile +1 -0
- data/euclidean.gemspec +24 -0
- data/lib/euclidean.rb +12 -0
- data/lib/euclidean/circle.rb +118 -0
- data/lib/euclidean/cluster_factory.rb +15 -0
- data/lib/euclidean/edge.rb +143 -0
- data/lib/euclidean/point.rb +160 -0
- data/lib/euclidean/point_zero.rb +104 -0
- data/lib/euclidean/rectangle.rb +378 -0
- data/lib/euclidean/size.rb +78 -0
- data/lib/euclidean/vector.rb +34 -0
- data/lib/euclidean/version.rb +3 -0
- data/spec/euclidean/circle_spec.rb +115 -0
- data/spec/euclidean/edge_spec.rb +129 -0
- data/spec/euclidean/point_spec.rb +264 -0
- data/spec/euclidean/rectangle_spec.rb +155 -0
- data/spec/euclidean/size_spec.rb +98 -0
- data/spec/euclidean/vector_spec.rb +41 -0
- data/spec/spec_helper.rb +15 -0
- metadata +118 -0
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'matrix'
|
2
|
+
|
3
|
+
module Euclidean
|
4
|
+
DimensionMismatch = Class.new(StandardError)
|
5
|
+
OperationNotDefined = Class.new(StandardError)
|
6
|
+
|
7
|
+
=begin rdoc
|
8
|
+
An object repesenting a Point in N-dimensional space
|
9
|
+
|
10
|
+
Supports all of the familiar Vector methods and adds convenience
|
11
|
+
accessors for those variables you learned to hate in your high school
|
12
|
+
geometry class (x, y, z).
|
13
|
+
|
14
|
+
== Usage
|
15
|
+
|
16
|
+
=== Constructor
|
17
|
+
point = Geometry::Point[x,y]
|
18
|
+
=end
|
19
|
+
class Point < Vector
|
20
|
+
attr_reader :x, :y, :z
|
21
|
+
|
22
|
+
# Allow vector-style initialization, but override to support copy-init
|
23
|
+
# from Vector or another Point
|
24
|
+
#
|
25
|
+
# @overload [](x,y,z,...)
|
26
|
+
# @overload [](Array)
|
27
|
+
# @overload [](Point)
|
28
|
+
# @overload [](Vector)
|
29
|
+
def self.[](*array)
|
30
|
+
return array[0] if array[0].is_a?(Point) or array[0].is_a?(PointZero)
|
31
|
+
array = array[0] if array[0].is_a?(Array)
|
32
|
+
array = array[0].to_a if array[0].is_a?(Vector)
|
33
|
+
super *array
|
34
|
+
end
|
35
|
+
|
36
|
+
# Creates and returns a new {PointZero} instance. Or, a {Point} full of zeros if the size argument is given.
|
37
|
+
# @param size [Number] the size of the new {Point} full of zeros
|
38
|
+
# @return [PointZero] A new {PointZero} instance
|
39
|
+
def self.zero(size=nil)
|
40
|
+
size ? Point[Array.new(size, 0)] : PointZero.new
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return a copy of the {Point}
|
44
|
+
def clone
|
45
|
+
Point[@elements.clone]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Allow comparison with an Array, otherwise do the normal thing
|
49
|
+
def eql?(other)
|
50
|
+
if other.is_a?(Array)
|
51
|
+
@elements.eql? other
|
52
|
+
elsif other.is_a?(PointZero)
|
53
|
+
@elements.all? {|e| e.eql? 0 }
|
54
|
+
else
|
55
|
+
super other
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Allow comparison with an Array, otherwise do the normal thing
|
60
|
+
def ==(other)
|
61
|
+
if other.is_a?(Array)
|
62
|
+
@elements.eql? other
|
63
|
+
elsif other.is_a?(PointZero)
|
64
|
+
@elements.all? {|e| e.eql? 0 }
|
65
|
+
else
|
66
|
+
super other
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Combined comparison operator
|
71
|
+
# @return [Point] The <=> operator is applied to the elements of the arguments pairwise and the results are returned in a Point
|
72
|
+
def <=>(other)
|
73
|
+
Point[self.to_a.zip(other.to_a).map {|a,b| a <=> b}.compact]
|
74
|
+
end
|
75
|
+
|
76
|
+
def coerce(other)
|
77
|
+
case other
|
78
|
+
when Array then [Point[*other], self]
|
79
|
+
when Numeric then [Point[Array.new(self.size, other)], self]
|
80
|
+
when Vector then [Point[*other], self]
|
81
|
+
else
|
82
|
+
raise TypeError, "#{self.class} can't be coerced into #{other.class}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def inspect
|
87
|
+
'Point' + @elements.inspect
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_s
|
91
|
+
'Point' + @elements.to_s
|
92
|
+
end
|
93
|
+
|
94
|
+
# @group Accessors
|
95
|
+
# @param [Integer] i Index into the {Point}'s elements
|
96
|
+
# @return [Numeric] Element i (starting at 0)
|
97
|
+
def [](i)
|
98
|
+
@elements[i]
|
99
|
+
end
|
100
|
+
|
101
|
+
# @attribute [r] x
|
102
|
+
# @return [Numeric] X-component
|
103
|
+
def x
|
104
|
+
@elements[0]
|
105
|
+
end
|
106
|
+
|
107
|
+
# @attribute [r] y
|
108
|
+
# @return [Numeric] Y-component
|
109
|
+
def y
|
110
|
+
@elements[1]
|
111
|
+
end
|
112
|
+
|
113
|
+
# @attribute [r] z
|
114
|
+
# @return [Numeric] Z-component
|
115
|
+
def z
|
116
|
+
@elements[2]
|
117
|
+
end
|
118
|
+
# @endgroup
|
119
|
+
|
120
|
+
# @group Arithmetic
|
121
|
+
|
122
|
+
# @group Unary operators
|
123
|
+
def +@
|
124
|
+
self
|
125
|
+
end
|
126
|
+
|
127
|
+
def -@
|
128
|
+
Point[@elements.map {|e| -e }]
|
129
|
+
end
|
130
|
+
# @endgroup
|
131
|
+
|
132
|
+
def +(other)
|
133
|
+
case other
|
134
|
+
when Numeric
|
135
|
+
Point[@elements.map {|e| e + other}]
|
136
|
+
when PointZero, NilClass
|
137
|
+
self.dup
|
138
|
+
else
|
139
|
+
raise OperationNotDefined, "#{other.class} must respond to :size and :[]" unless other.respond_to?(:size) && other.respond_to?(:[])
|
140
|
+
raise DimensionMismatch, "Can't add #{other} to #{self}" if size != other.size
|
141
|
+
Point[Array.new(size) {|i| @elements[i] + other[i] }]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def -(other)
|
146
|
+
case other
|
147
|
+
when Numeric
|
148
|
+
Point[@elements.map {|e| e - other}]
|
149
|
+
when PointZero, NilClass
|
150
|
+
self.dup
|
151
|
+
else
|
152
|
+
raise OperationNotDefined, "#{other.class} must respond to :size and :[]" unless other.respond_to?(:size) && other.respond_to?(:[])
|
153
|
+
raise DimensionMismatch, "Can't subtract #{other} from #{self}" if size != other.size
|
154
|
+
Point[Array.new(size) {|i| @elements[i] - other[i] }]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# @endgroup
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Euclidean
|
2
|
+
=begin rdoc
|
3
|
+
An object repesenting a {Point} at the origin in N-dimensional space
|
4
|
+
|
5
|
+
A {PointZero} object is a {Point} that will always compare equal to zero and unequal to
|
6
|
+
everything else, regardless of size. You can think of it as an application of the
|
7
|
+
{http://en.wikipedia.org/wiki/Null_Object_pattern Null Object Pattern}.
|
8
|
+
=end
|
9
|
+
class PointZero
|
10
|
+
|
11
|
+
def eql?(other)
|
12
|
+
if other.respond_to? :all?
|
13
|
+
other.all? {|e| e.eql? 0}
|
14
|
+
else
|
15
|
+
other == 0
|
16
|
+
end
|
17
|
+
end
|
18
|
+
alias == eql?
|
19
|
+
|
20
|
+
def coerce(other)
|
21
|
+
if other.is_a? Numeric
|
22
|
+
[other, 0]
|
23
|
+
elsif other.is_a? Array
|
24
|
+
[other, Array.new(other.size,0)]
|
25
|
+
elsif other.is_a? Vector
|
26
|
+
[other, Vector[*Array.new(other.size,0)]]
|
27
|
+
else
|
28
|
+
[Point[other], Point[Array.new(other.size,0)]]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# This is a hack to get Array#== to work properly. It works on ruby 2.0 and 1.9.3.
|
33
|
+
def to_a
|
34
|
+
[]
|
35
|
+
end
|
36
|
+
|
37
|
+
# @group Accessors
|
38
|
+
# @param [Integer] i Index into the {Point}'s elements
|
39
|
+
# @return [Numeric] Element i (starting at 0)
|
40
|
+
def [](i)
|
41
|
+
0
|
42
|
+
end
|
43
|
+
|
44
|
+
# @attribute [r] x
|
45
|
+
# @return [Numeric] X-component
|
46
|
+
def x
|
47
|
+
0
|
48
|
+
end
|
49
|
+
|
50
|
+
# @attribute [r] y
|
51
|
+
# @return [Numeric] Y-component
|
52
|
+
def y
|
53
|
+
0
|
54
|
+
end
|
55
|
+
|
56
|
+
# @attribute [r] z
|
57
|
+
# @return [Numeric] Z-component
|
58
|
+
def z
|
59
|
+
0
|
60
|
+
end
|
61
|
+
# @endgroup
|
62
|
+
|
63
|
+
# @group Arithmetic
|
64
|
+
|
65
|
+
# @group Unary operators
|
66
|
+
def +@
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def -@
|
71
|
+
self
|
72
|
+
end
|
73
|
+
# @endgroup
|
74
|
+
|
75
|
+
def +(other)
|
76
|
+
case other
|
77
|
+
when Array, Numeric then other
|
78
|
+
else
|
79
|
+
Point[other]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def -(other)
|
84
|
+
if other.is_a? Size
|
85
|
+
-Point[other]
|
86
|
+
elsif other.respond_to? :-@
|
87
|
+
-other
|
88
|
+
elsif other.respond_to? :map
|
89
|
+
other.map {|a| -a }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def *(other)
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def /(other)
|
98
|
+
raise OperationNotDefined unless other.is_a? Numeric
|
99
|
+
raise ZeroDivisionError if 0 == other
|
100
|
+
self
|
101
|
+
end
|
102
|
+
# @endgroup
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,378 @@
|
|
1
|
+
require_relative 'edge'
|
2
|
+
|
3
|
+
module Euclidean
|
4
|
+
=begin
|
5
|
+
The {Rectangle} class cluster represents your typical arrangement of 4 corners and 4 sides.
|
6
|
+
|
7
|
+
== Usage
|
8
|
+
|
9
|
+
=== Constructors
|
10
|
+
rect = Euclidean::Rectangle.new [1,2], [2,3] # Using two corners
|
11
|
+
rect = Euclidean::Rectangle.new from:[1,2], to:[2,3] # Using two corners
|
12
|
+
|
13
|
+
rect = Euclidean::Rectangle.new center:[1,2], size:[1,1] # Using a center point and a size
|
14
|
+
rect = Euclidean::Rectangle.new origin:[1,2], size:[1,1] # Using an origin point and a size
|
15
|
+
|
16
|
+
rect = Euclidean::Rectangle.new size: [10, 20] # origin = [0,0], size = [10, 20]
|
17
|
+
rect = Euclidean::Rectangle.new size: Size[10, 20] # origin = [0,0], size = [10, 20]
|
18
|
+
rect = Euclidean::Rectangle.new width: 10, height: 20 # origin = [0,0], size = [10, 20]
|
19
|
+
=end
|
20
|
+
|
21
|
+
class Rectangle
|
22
|
+
include ClusterFactory
|
23
|
+
|
24
|
+
# @return [Point] The {Rectangle}'s center
|
25
|
+
attr_reader :center
|
26
|
+
# @return [Number] Height of the {Rectangle}
|
27
|
+
attr_reader :height
|
28
|
+
# @return [Point] The {Rectangle}'s origin
|
29
|
+
attr_reader :origin
|
30
|
+
# @return [Size] The {Size} of the {Rectangle}
|
31
|
+
attr_reader :size
|
32
|
+
# @return [Number] Width of the {Rectangle}
|
33
|
+
attr_reader :width
|
34
|
+
|
35
|
+
# @overload new(width, height)
|
36
|
+
# Creates a {Rectangle} of the given width and height, centered on the origin
|
37
|
+
# @param [Number] height Height
|
38
|
+
# @param [Number] width Width
|
39
|
+
# @return [CenteredRectangle]
|
40
|
+
# @overload new(size)
|
41
|
+
# Creates a {Rectangle} of the given {Size} centered on the origin
|
42
|
+
# @param [Size] size Width and height
|
43
|
+
# @return [CenteredRectangle]
|
44
|
+
# @overload new(point0, point1)
|
45
|
+
# Creates a {Rectangle} using the given {Point}s
|
46
|
+
# @param [Point] point0 A corner
|
47
|
+
# @param [Point] point1 The other corner
|
48
|
+
# @overload new(origin, size)
|
49
|
+
# Creates a {Rectangle} from the given origin and size
|
50
|
+
# @param [Point] origin Lower-left corner
|
51
|
+
# @param [Size] size Width and height
|
52
|
+
# @return [SizedRectangle]
|
53
|
+
# @overload new(left, bottom, right, top)
|
54
|
+
# Creates a {Rectangle} from the locations of each side
|
55
|
+
# @param [Number] left X-coordinate of the left side
|
56
|
+
# @param [Number] bottom Y-coordinate of the bottom edge
|
57
|
+
# @param [Number] right X-coordinate of the right side
|
58
|
+
# @param [Number] top Y-coordinate of the top edge
|
59
|
+
def self.new(*args)
|
60
|
+
options, args = args.partition {|a| a.is_a? Hash}
|
61
|
+
options = options.reduce({}, :merge)
|
62
|
+
|
63
|
+
if options.has_key?(:size)
|
64
|
+
if options.has_key?(:center)
|
65
|
+
CenteredRectangle.new(center: options[:center], size: options[:size])
|
66
|
+
elsif options.has_key?(:origin)
|
67
|
+
SizedRectangle.new(origin: options[:origin], size: options[:size])
|
68
|
+
else
|
69
|
+
SizedRectangle.new(size: options[:size])
|
70
|
+
end
|
71
|
+
elsif options.has_key?(:from) and options.has_key?(:to)
|
72
|
+
original_new(options[:from], options[:to])
|
73
|
+
elsif options.has_key?(:height) and options.has_key?(:width)
|
74
|
+
SizedRectangle.new(height: options[:height], width: options[:width])
|
75
|
+
elsif (2==args.count) and (args.all? {|a| a.is_a?(Array) || a.is_a?(Point) })
|
76
|
+
original_new(*args)
|
77
|
+
elsif options.empty?
|
78
|
+
raise ArgumentError, "#{self} arguments must be named, not: #{args}"
|
79
|
+
else
|
80
|
+
raise ArgumentError, "Bad Rectangle arguments: #{args}, #{options}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Creates a {Rectangle} using the given {Point}s
|
85
|
+
# @param [Point] point0 A corner (ie. bottom-left)
|
86
|
+
# @param [Point] point1 The other corner (ie. top-right)
|
87
|
+
def initialize(point0, point1)
|
88
|
+
point0 = Point[point0]
|
89
|
+
point1 = Point[point1]
|
90
|
+
raise(ArgumentError, "Point sizes must match") unless point0.size == point1.size
|
91
|
+
|
92
|
+
# Reorder the points to get lower-left and upper-right
|
93
|
+
if (point0.x > point1.x) && (point0.y > point1.y)
|
94
|
+
point0, point1 = point1, point0
|
95
|
+
else
|
96
|
+
p0x, p1x = [point0.x, point1.x].minmax
|
97
|
+
p0y, p1y = [point0.y, point1.y].minmax
|
98
|
+
point0 = Point[p0x, p0y]
|
99
|
+
point1 = Point[p1x, p1y]
|
100
|
+
end
|
101
|
+
@points = [point0, point1]
|
102
|
+
end
|
103
|
+
|
104
|
+
def eql?(other)
|
105
|
+
self.points == other.points
|
106
|
+
end
|
107
|
+
alias :== :eql?
|
108
|
+
|
109
|
+
# @group Accessors
|
110
|
+
|
111
|
+
# @return [Rectangle] The smallest axis-aligned {Rectangle} that bounds the receiver
|
112
|
+
def bounds
|
113
|
+
return Rectangle.new(self.min, self.max)
|
114
|
+
end
|
115
|
+
|
116
|
+
# @return [Point] The {Rectangle}'s center
|
117
|
+
def center
|
118
|
+
min, max = @points.minmax {|a,b| a.y <=> b.y}
|
119
|
+
Point[(max.x+min.x)/2, (max.y+min.y)/2]
|
120
|
+
end
|
121
|
+
|
122
|
+
# @return [Array<Edge>] The {Rectangle}'s four edges (counterclockwise)
|
123
|
+
def edges
|
124
|
+
point0, point2 = *@points
|
125
|
+
point1 = Point[point2.x, point0.y]
|
126
|
+
point3 = Point[point0.x, point2.y]
|
127
|
+
[Edge.new(point0, point1),
|
128
|
+
Edge.new(point1, point2),
|
129
|
+
Edge.new(point2, point3),
|
130
|
+
Edge.new(point3, point0)]
|
131
|
+
end
|
132
|
+
|
133
|
+
def height
|
134
|
+
min, max = @points.minmax {|a,b| a.y <=> b.y}
|
135
|
+
max.y - min.y
|
136
|
+
end
|
137
|
+
|
138
|
+
# @return [Point] The upper right corner of the bounding {Rectangle}
|
139
|
+
def max
|
140
|
+
@points.last
|
141
|
+
end
|
142
|
+
|
143
|
+
# @return [Point] The lower left corner of the bounding {Rectangle}
|
144
|
+
def min
|
145
|
+
@points.first
|
146
|
+
end
|
147
|
+
|
148
|
+
# @return [Array<Point>] The lower left and upper right corners of the bounding {Rectangle}
|
149
|
+
def minmax
|
150
|
+
[self.min, self.max]
|
151
|
+
end
|
152
|
+
|
153
|
+
def origin
|
154
|
+
minx = @points.min {|a,b| a.x <=> b.x}
|
155
|
+
miny = @points.min {|a,b| a.y <=> b.y}
|
156
|
+
Point[minx.x, miny.y]
|
157
|
+
end
|
158
|
+
|
159
|
+
# @return [Array<Point>] The {Rectangle}'s four points (counterclockwise)
|
160
|
+
def points
|
161
|
+
point0, point2 = *@points
|
162
|
+
point1 = Point[point2.x, point0.y]
|
163
|
+
point3 = Point[point0.x, point2.y]
|
164
|
+
[point0, point1, point2, point3]
|
165
|
+
end
|
166
|
+
|
167
|
+
def width
|
168
|
+
min, max = @points.minmax {|a,b| a.x <=> b.x}
|
169
|
+
max.x - min.x
|
170
|
+
end
|
171
|
+
# @endgroup
|
172
|
+
|
173
|
+
# Create a new {Rectangle} from the receiver that's inset by the given amount
|
174
|
+
# @overload inset(x, y)
|
175
|
+
# @overload inset(top, left, bottom, right)
|
176
|
+
# @overload inset(x, y)
|
177
|
+
# @option options [Number] :x Inset from the left and right sides
|
178
|
+
# @option options [Number] :y Inset from the top and bottom
|
179
|
+
# @overload inset(top, left, bottom, right)
|
180
|
+
# @option options [Number] :bottom The inset from the bottom of the {Rectangle}
|
181
|
+
# @option options [Number] :left The inset from the left side of the {Rectangle}
|
182
|
+
# @option options [Number] :right The inset from the right side of the {Rectangle}
|
183
|
+
# @option options [Number] :top The inset from the top of the {Rectangle}
|
184
|
+
def inset(*args)
|
185
|
+
options, args = args.partition {|a| a.is_a? Hash}
|
186
|
+
options = options.reduce({}, :merge)
|
187
|
+
raise ArumentError, "Can't specify both arguments and options" if !args.empty? && !options.empty?
|
188
|
+
|
189
|
+
if 1 == args.size
|
190
|
+
distance = args.shift
|
191
|
+
Rectangle.new from:(min + distance), to:(max - distance)
|
192
|
+
elsif 2 == args.size
|
193
|
+
distance = Point[*args]
|
194
|
+
Rectangle.new from:(min + distance), to:(max - distance)
|
195
|
+
elsif 4 == args.size
|
196
|
+
top, left, bottom, right = *args
|
197
|
+
Rectangle.new from:(min + Point[left, bottom]), to:(max - Point[right, top])
|
198
|
+
elsif options[:x] && options[:y]
|
199
|
+
distance = Point[options[:x], options[:y]]
|
200
|
+
Rectangle.new from:(min + distance), to:(max - distance)
|
201
|
+
elsif options[:top] && options[:left] && options[:bottom] && options[:right]
|
202
|
+
Rectangle.new from:(min + Point[options[:left], options[:bottom]]), to:(max - Point[options[:right], options[:top]])
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
class CenteredRectangle < Rectangle
|
209
|
+
# @return [Point] The {Rectangle}'s center
|
210
|
+
attr_accessor :center
|
211
|
+
# @return [Size] The {Size} of the {Rectangle}
|
212
|
+
attr_accessor :size
|
213
|
+
|
214
|
+
# @overload new(width, height)
|
215
|
+
# Creates a {Rectangle} of the given width and height, centered on the origin
|
216
|
+
# @param [Number] height Height
|
217
|
+
# @param [Number] width Width
|
218
|
+
# @return [CenteredRectangle]
|
219
|
+
# @overload new(size)
|
220
|
+
# Creates a {Rectangle} of the given {Size} centered on the origin
|
221
|
+
# @param [Size] size Width and height
|
222
|
+
# @return [CenteredRectangle]
|
223
|
+
# @overload new(center, size)
|
224
|
+
# Creates a {Rectangle} with the given center point and size
|
225
|
+
# @param [Point] center
|
226
|
+
# @param [Size] size
|
227
|
+
def initialize(*args)
|
228
|
+
options, args = args.partition {|a| a.is_a? Hash}
|
229
|
+
options = options.reduce({}, :merge)
|
230
|
+
|
231
|
+
@center = options[:center] ? Point[options[:center]] : PointZero.new
|
232
|
+
|
233
|
+
if options.has_key?(:size)
|
234
|
+
@size = Euclidean::Size[options[:size]]
|
235
|
+
elsif options.has_key?(:height) and options.has_key?(:width)
|
236
|
+
@size = Euclidean::Size[options[:width], options[:height]]
|
237
|
+
else
|
238
|
+
raise ArgumentError, "Bad arguments to CenteredRectangle#new"
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def eql?(other)
|
243
|
+
(self.center == other.center) && (self.size == other.size)
|
244
|
+
end
|
245
|
+
alias :== :eql?
|
246
|
+
|
247
|
+
# @group Accessors
|
248
|
+
# @return [Array<Edge>] The {Rectangle}'s four edges
|
249
|
+
def edges
|
250
|
+
point0 = @center - @size/2.0
|
251
|
+
point2 = @center + @size/2.0
|
252
|
+
point1 = Point[point0.x,point2.y]
|
253
|
+
point3 = Point[point2.x, point0.y]
|
254
|
+
[Edge.new(point0, point1),
|
255
|
+
Edge.new(point1, point2),
|
256
|
+
Edge.new(point2, point3),
|
257
|
+
Edge.new(point3, point0)]
|
258
|
+
end
|
259
|
+
|
260
|
+
def height
|
261
|
+
@size.height
|
262
|
+
end
|
263
|
+
|
264
|
+
# @return [Point] The upper right corner of the bounding {Rectangle}
|
265
|
+
def max
|
266
|
+
@center + @size/2.0
|
267
|
+
end
|
268
|
+
|
269
|
+
# @return [Point] The lower left corner of the bounding {Rectangle}
|
270
|
+
def min
|
271
|
+
@center - @size/2.0
|
272
|
+
end
|
273
|
+
|
274
|
+
# @return [Array<Point>] The {Rectangle}'s four points (clockwise)
|
275
|
+
def points
|
276
|
+
point0 = @center - @size/2.0
|
277
|
+
point2 = @center + @size/2.0
|
278
|
+
point1 = Point[point0.x,point2.y]
|
279
|
+
point3 = Point[point2.x, point0.y]
|
280
|
+
[point0, point1, point2, point3]
|
281
|
+
end
|
282
|
+
|
283
|
+
def width
|
284
|
+
@size.width
|
285
|
+
end
|
286
|
+
# @endgroup
|
287
|
+
|
288
|
+
end
|
289
|
+
|
290
|
+
class SizedRectangle < Rectangle
|
291
|
+
# @return [Point] The {Rectangle}'s origin
|
292
|
+
attr_accessor :origin
|
293
|
+
# @return [Size] The {Size} of the {Rectangle}
|
294
|
+
attr_accessor :size
|
295
|
+
|
296
|
+
# @overload new(width, height)
|
297
|
+
# Creates a {Rectangle} of the given width and height with its origin at [0,0]
|
298
|
+
# @param [Number] height Height
|
299
|
+
# @param [Number] width Width
|
300
|
+
# @return SizedRectangle
|
301
|
+
# @overload new(size)
|
302
|
+
# Creates a {Rectangle} of the given {Size} with its origin at [0,0]
|
303
|
+
# @param [Size] size Width and height
|
304
|
+
# @return SizedRectangle
|
305
|
+
# @overload new(origin, size)
|
306
|
+
# Creates a {Rectangle} with the given origin point and size
|
307
|
+
# @param [Point] origin
|
308
|
+
# @param [Size] size
|
309
|
+
# @return SizedRectangle
|
310
|
+
def initialize(*args)
|
311
|
+
options, args = args.partition {|a| a.is_a? Hash}
|
312
|
+
options = options.reduce({}, :merge)
|
313
|
+
|
314
|
+
@origin = options[:origin] ? Point[options[:origin]] : PointZero.new
|
315
|
+
|
316
|
+
if options.has_key?(:size)
|
317
|
+
@size = Euclidean::Size[options[:size]]
|
318
|
+
elsif options.has_key?(:height) and options.has_key?(:width)
|
319
|
+
@size = Euclidean::Size[options[:width], options[:height]]
|
320
|
+
else
|
321
|
+
raise ArgumentError, "Bad arguments to SizeRectangle#new"
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def eql?(other)
|
326
|
+
(self.origin == other.origin) && (self.size == other.size)
|
327
|
+
end
|
328
|
+
alias :== :eql?
|
329
|
+
|
330
|
+
# @group Accessors
|
331
|
+
# @return [Point] The {Rectangle}'s center
|
332
|
+
def center
|
333
|
+
@origin + @size/2
|
334
|
+
end
|
335
|
+
|
336
|
+
# @return [Array<Edge>] The {Rectangle}'s four edges
|
337
|
+
def edges
|
338
|
+
point0 = @origin
|
339
|
+
point2 = @origin + @size
|
340
|
+
point1 = Point[point0.x,point2.y]
|
341
|
+
point3 = Point[point2.x, point0.y]
|
342
|
+
[Edge.new(point0, point1),
|
343
|
+
Edge.new(point1, point2),
|
344
|
+
Edge.new(point2, point3),
|
345
|
+
Edge.new(point3, point0)]
|
346
|
+
end
|
347
|
+
|
348
|
+
def height
|
349
|
+
@size.height
|
350
|
+
end
|
351
|
+
|
352
|
+
# @return [Point] The upper right corner of the bounding {Rectangle}
|
353
|
+
def max
|
354
|
+
@origin + @size
|
355
|
+
end
|
356
|
+
|
357
|
+
# @return [Point] The lower left corner of the bounding {Rectangle}
|
358
|
+
def min
|
359
|
+
@origin
|
360
|
+
end
|
361
|
+
|
362
|
+
# @return [Array<Point>] The {Rectangle}'s four points (clockwise)
|
363
|
+
def points
|
364
|
+
point0 = @origin
|
365
|
+
point2 = @origin + @size
|
366
|
+
point1 = Point[point0.x,point2.y]
|
367
|
+
point3 = Point[point2.x, point0.y]
|
368
|
+
[point0, point1, point2, point3]
|
369
|
+
end
|
370
|
+
|
371
|
+
def width
|
372
|
+
@size.width
|
373
|
+
end
|
374
|
+
# @endgroup
|
375
|
+
|
376
|
+
end
|
377
|
+
|
378
|
+
end
|