geometry-in-ruby 0.0.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.
- data/.gitignore +6 -0
- data/Gemfile +7 -0
- data/LICENSE +21 -0
- data/README.markdown +105 -0
- data/Rakefile +24 -0
- data/geometry-in-ruby.gemspec +23 -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/composition.rb +39 -0
- data/lib/geometry/transformation.rb +171 -0
- data/lib/geometry/triangle.rb +78 -0
- data/lib/geometry/vector.rb +34 -0
- data/lib/geometry.rb +22 -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/composition.rb +49 -0
- data/test/geometry/transformation.rb +169 -0
- data/test/geometry/triangle.rb +32 -0
- data/test/geometry/vector.rb +41 -0
- data/test/geometry.rb +5 -0
- metadata +117 -0
@@ -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
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'geometry/arc'
|
2
|
+
require 'geometry/edge'
|
3
|
+
|
4
|
+
module Geometry
|
5
|
+
=begin
|
6
|
+
An object representing a set of connected elements, each of which could be an
|
7
|
+
{Edge} or an {Arc}. Unlike a {Polygon}, a {Path} is not guaranteed to be closed.
|
8
|
+
=end
|
9
|
+
class Path
|
10
|
+
attr_reader :elements
|
11
|
+
|
12
|
+
# Construct a new Path from {Point}s, {Edge}s, and {Arc}s
|
13
|
+
# Successive {Point}s will be converted to {Edge}s.
|
14
|
+
def initialize(*args)
|
15
|
+
args.map! {|a| (a.is_a?(Array) or a.is_a?(Vector)) ? Point[a] : a}
|
16
|
+
args.each {|a| raise ArgumentError, "Unknown argument type #{a.class}" unless a.is_a?(Point) or a.is_a?(Edge) or a.is_a?(Arc) }
|
17
|
+
|
18
|
+
@elements = []
|
19
|
+
|
20
|
+
first = args.shift
|
21
|
+
push first if first.is_a?(Edge) or first.is_a?(Arc)
|
22
|
+
|
23
|
+
args.reduce(first) do |previous, n|
|
24
|
+
case n
|
25
|
+
when Point
|
26
|
+
case previous
|
27
|
+
when Point then push Edge.new(previous, n)
|
28
|
+
when Arc, Edge then push Edge.new(previous.last, n) unless previous.last == n
|
29
|
+
end
|
30
|
+
last
|
31
|
+
when Edge
|
32
|
+
case previous
|
33
|
+
when Point then push Edge.new(previous, n.first)
|
34
|
+
when Arc, Edge then push Edge.new(previous.last, n.first) unless previous.last == n.first
|
35
|
+
end
|
36
|
+
push(n).last
|
37
|
+
when Arc
|
38
|
+
case previous
|
39
|
+
when Point
|
40
|
+
if previous == n.first
|
41
|
+
raise ArgumentError, "Duplicated point before an Arc"
|
42
|
+
else
|
43
|
+
push Edge.new(previous, n.first)
|
44
|
+
end
|
45
|
+
when Arc, Edge
|
46
|
+
push Edge.new(previous.last, n.first) unless previous.last == n.first
|
47
|
+
end
|
48
|
+
push(n).last
|
49
|
+
else
|
50
|
+
raise ArgumentError, "Unsupported argument type: #{n}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [Geometry] The last element in the {Path}
|
56
|
+
def last
|
57
|
+
@elements.last
|
58
|
+
end
|
59
|
+
|
60
|
+
# Append a new geometry element to the {Path}
|
61
|
+
# @return [Path]
|
62
|
+
def push(arg)
|
63
|
+
@elements.push arg
|
64
|
+
self
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'matrix'
|
2
|
+
|
3
|
+
require_relative 'point_zero'
|
4
|
+
|
5
|
+
module Geometry
|
6
|
+
DimensionMismatch = Class.new(StandardError)
|
7
|
+
OperationNotDefined = Class.new(StandardError)
|
8
|
+
|
9
|
+
=begin rdoc
|
10
|
+
An object repesenting a Point in N-dimensional space
|
11
|
+
|
12
|
+
Supports all of the familiar Vector methods and adds convenience
|
13
|
+
accessors for those variables you learned to hate in your high school
|
14
|
+
geometry class (x, y, z).
|
15
|
+
|
16
|
+
== Usage
|
17
|
+
|
18
|
+
=== Constructor
|
19
|
+
point = Geometry::Point[x,y]
|
20
|
+
=end
|
21
|
+
class Point < Vector
|
22
|
+
attr_reader :x, :y, :z
|
23
|
+
|
24
|
+
# Allow vector-style initialization, but override to support copy-init
|
25
|
+
# from Vector or another Point
|
26
|
+
#
|
27
|
+
# @overload [](x,y,z,...)
|
28
|
+
# @overload [](Array)
|
29
|
+
# @overload [](Point)
|
30
|
+
# @overload [](Vector)
|
31
|
+
def self.[](*array)
|
32
|
+
return array[0] if array[0].is_a?(Point) or array[0].is_a?(PointZero)
|
33
|
+
array = array[0] if array[0].is_a?(Array)
|
34
|
+
array = array[0].to_a if array[0].is_a?(Vector)
|
35
|
+
super *array
|
36
|
+
end
|
37
|
+
|
38
|
+
# Creates and returns a new {PointZero} instance. Or, a {Point} full of zeros if the size argument is given.
|
39
|
+
# @param size [Number] the size of the new {Point} full of zeros
|
40
|
+
# @return [PointZero] A new {PointZero} instance
|
41
|
+
def self.zero(size=nil)
|
42
|
+
size ? Point[Array.new(size, 0)] : PointZero.new
|
43
|
+
end
|
44
|
+
|
45
|
+
# Return a copy of the {Point}
|
46
|
+
def clone
|
47
|
+
Point[@elements.clone]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Allow comparison with an Array, otherwise do the normal thing
|
51
|
+
def eql?(other)
|
52
|
+
if other.is_a?(Array)
|
53
|
+
@elements.eql? other
|
54
|
+
elsif other.is_a?(PointZero)
|
55
|
+
@elements.all? {|e| e.eql? 0 }
|
56
|
+
else
|
57
|
+
super other
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Allow comparison with an Array, otherwise do the normal thing
|
62
|
+
def ==(other)
|
63
|
+
if other.is_a?(Array)
|
64
|
+
@elements.eql? other
|
65
|
+
elsif other.is_a?(PointZero)
|
66
|
+
@elements.all? {|e| e.eql? 0 }
|
67
|
+
else
|
68
|
+
super other
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Combined comparison operator
|
73
|
+
# @return [Point] The <=> operator is applied to the elements of the arguments pairwise and the results are returned in a Point
|
74
|
+
def <=>(other)
|
75
|
+
Point[self.to_a.zip(other.to_a).map {|a,b| a <=> b}.compact]
|
76
|
+
end
|
77
|
+
|
78
|
+
def coerce(other)
|
79
|
+
case other
|
80
|
+
when Array then [Point[*other], self]
|
81
|
+
when Numeric then [Point[Array.new(self.size, other)], self]
|
82
|
+
when Vector then [Point[*other], self]
|
83
|
+
else
|
84
|
+
raise TypeError, "#{self.class} can't be coerced into #{other.class}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def inspect
|
89
|
+
'Point' + @elements.inspect
|
90
|
+
end
|
91
|
+
def to_s
|
92
|
+
'Point' + @elements.to_s
|
93
|
+
end
|
94
|
+
|
95
|
+
# @group Accessors
|
96
|
+
# @param [Integer] i Index into the {Point}'s elements
|
97
|
+
# @return [Numeric] Element i (starting at 0)
|
98
|
+
def [](i)
|
99
|
+
@elements[i]
|
100
|
+
end
|
101
|
+
|
102
|
+
# @attribute [r] x
|
103
|
+
# @return [Numeric] X-component
|
104
|
+
def x
|
105
|
+
@elements[0]
|
106
|
+
end
|
107
|
+
|
108
|
+
# @attribute [r] y
|
109
|
+
# @return [Numeric] Y-component
|
110
|
+
def y
|
111
|
+
@elements[1]
|
112
|
+
end
|
113
|
+
|
114
|
+
# @attribute [r] z
|
115
|
+
# @return [Numeric] Z-component
|
116
|
+
def z
|
117
|
+
@elements[2]
|
118
|
+
end
|
119
|
+
# @endgroup
|
120
|
+
|
121
|
+
# @group Arithmetic
|
122
|
+
|
123
|
+
# @group Unary operators
|
124
|
+
def +@
|
125
|
+
self
|
126
|
+
end
|
127
|
+
|
128
|
+
def -@
|
129
|
+
Point[@elements.map {|e| -e }]
|
130
|
+
end
|
131
|
+
# @endgroup
|
132
|
+
|
133
|
+
def +(other)
|
134
|
+
case other
|
135
|
+
when Numeric
|
136
|
+
Point[@elements.map {|e| e + other}]
|
137
|
+
when PointZero, NilClass
|
138
|
+
self.dup
|
139
|
+
else
|
140
|
+
raise OperationNotDefined, "#{other.class} must respond to :size and :[]" unless other.respond_to?(:size) && other.respond_to?(:[])
|
141
|
+
raise DimensionMismatch, "Can't add #{other} to #{self}" if size != other.size
|
142
|
+
Point[Array.new(size) {|i| @elements[i] + other[i] }]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def -(other)
|
147
|
+
case other
|
148
|
+
when Numeric
|
149
|
+
Point[@elements.map {|e| e - other}]
|
150
|
+
when PointZero, NilClass
|
151
|
+
self.dup
|
152
|
+
else
|
153
|
+
raise OperationNotDefined, "#{other.class} must respond to :size and :[]" unless other.respond_to?(:size) && other.respond_to?(:[])
|
154
|
+
raise DimensionMismatch, "Can't subtract #{other} from #{self}" if size != other.size
|
155
|
+
Point[Array.new(size) {|i| @elements[i] - other[i] }]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# @endgroup
|
160
|
+
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|