euclidean 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,78 @@
1
+ require 'matrix'
2
+
3
+ module Euclidean
4
+ =begin
5
+ An object representing the size of something.
6
+
7
+ Supports all of the familiar {Vector} methods as well as a few convenience
8
+ methods (width, height and depth).
9
+
10
+ == Usage
11
+
12
+ === Constructor
13
+ size = Geometry::Size[x,y,z]
14
+ =end
15
+
16
+ class Size < Vector
17
+ attr_reader :x, :y, :z
18
+
19
+ # Allow vector-style initialization, but override to support copy-init
20
+ # from Vector, Point or another Size
21
+ #
22
+ # @overload [](x,y,z,...)
23
+ # @overload [](Point)
24
+ # @overload [](Size)
25
+ # @overload [](Vector)
26
+ # @return [Size] A new {Size} object
27
+ def self.[](*array)
28
+ array = array[0].to_a unless array[0].is_a?(Numeric)
29
+ super *array
30
+ end
31
+
32
+ # Allow comparison with an Array, otherwise do the normal thing
33
+ def ==(other)
34
+ return @elements == other if other.is_a?(Array)
35
+ super other
36
+ end
37
+
38
+ # @return [Number] The size along the Z axis
39
+ def depth
40
+ z
41
+ end
42
+
43
+ # @return [Number] The size along the Y axis
44
+ def height
45
+ y
46
+ end
47
+
48
+ def inspect
49
+ 'Size' + @elements.inspect
50
+ end
51
+
52
+ def to_s
53
+ 'Size' + @elements.to_s
54
+ end
55
+
56
+ # @return [Number] The size along the X axis
57
+ def width
58
+ x
59
+ end
60
+
61
+ # @return [Number] X-component (width)
62
+ def x
63
+ @elements[0]
64
+ end
65
+
66
+ # @return [Number] Y-component (height)
67
+ def y
68
+ @elements[1]
69
+ end
70
+
71
+ # @return [Number] Z-component (depth)
72
+ def z
73
+ @elements[2]
74
+ end
75
+
76
+ end
77
+
78
+ end
@@ -0,0 +1,34 @@
1
+ require 'matrix'
2
+
3
+ # Monkeypatch Vector to overcome some deficiencies
4
+ class Vector
5
+ X = Vector[1,0,0]
6
+ Y = Vector[0,1,0]
7
+ Z = Vector[0,0,1]
8
+
9
+ # @group Unary operators
10
+ def +@
11
+ self
12
+ end
13
+
14
+ def -@
15
+ Vector[*(@elements.map {|e| -e })]
16
+ end
17
+ # @endgroup
18
+
19
+ # Cross-product of two {Vector}s
20
+ # @return [Vector]
21
+ def cross(other)
22
+ Vector.Raise ErrDimensionMismatch unless @elements.size == other.size
23
+
24
+ case @elements.size
25
+ when 0 then raise ArgumentError, "Can't multply zero-length Vectors"
26
+ when 1 then @elements.first * other.first
27
+ when 2 then @elements.first * other[1] - @elements.last * other.first
28
+ when 3 then Vector[ @elements[1]*other[2] - @elements[2]*other[1],
29
+ @elements[2]*other[0] - @elements[0]*other[2],
30
+ @elements[0]*other[1] - @elements[1]*other[0]]
31
+ end
32
+ end
33
+ alias ** cross
34
+ end
@@ -0,0 +1,3 @@
1
+ module Euclidean
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+ require 'euclidean/circle'
3
+
4
+ describe Euclidean::Circle do
5
+ Circle = Euclidean::Circle
6
+ Point = Euclidean::Point
7
+ Rectangle = Euclidean::Rectangle
8
+
9
+ context "When contstructed with center and radius arguments" do
10
+ let(:circle) { Circle.new [1,2], 3 }
11
+
12
+ it "must create a Circle" do
13
+ expect(circle).to be_an_instance_of Circle
14
+ end
15
+
16
+ it "must have a center point accessor" do
17
+ expect(circle.center).to eq Point[1,2]
18
+ end
19
+
20
+ it "must have a radius accessor" do
21
+ expect(circle.radius).to eq 3
22
+ end
23
+
24
+ it "must compare equal" do
25
+ expect(circle).to eq Circle.new([1,2], 3)
26
+ end
27
+ end
28
+
29
+ context "When constructed with named center and radius arguments" do
30
+ let(:circle) { Circle.new :center => [1,2], :radius => 3 }
31
+
32
+ it "must create a Circle" do
33
+ expect(circle).to be_an_instance_of Euclidean::Circle
34
+ end
35
+
36
+ it "must have a center point accessor" do
37
+ expect(circle.center).to eq Point[1,2]
38
+ end
39
+
40
+ it "must have a radius accessor" do
41
+ expect(circle.radius).to eq 3
42
+ end
43
+
44
+ it "must compare equal" do
45
+ expect((circle == Circle.new(:center => [1,2], :radius => 3))).to eq true
46
+ end
47
+ end
48
+
49
+ context "When constructed with named center and diameter arguments" do
50
+ let(:circle) { Circle.new center:[1,2], diameter:4 }
51
+
52
+ it "must be a CenterDiameterCircle" do
53
+ expect(circle).to be_an_instance_of Euclidean::CenterDiameterCircle
54
+ expect(circle).to be_a_kind_of Euclidean::Circle
55
+ end
56
+
57
+ it "must have a center" do
58
+ expect(circle.center).to eq Point[1,2]
59
+ end
60
+
61
+ it "must have a diameter" do
62
+ expect(circle.diameter).to eq 4
63
+ end
64
+
65
+ it "must calculate the correct radius" do
66
+ expect(circle.radius).to eq 2
67
+ end
68
+
69
+ it "must compare equal" do
70
+ expect(circle).to eq Circle.new([1,2], :diameter => 4)
71
+ end
72
+ end
73
+
74
+ context "When constructed with a diameter and no center" do
75
+ let(:circle) { Circle.new :diameter => 4 }
76
+
77
+ it "must be a CenterDiameterCircle" do
78
+ expect(circle).to be_an_instance_of Euclidean::CenterDiameterCircle
79
+ expect(circle).to be_a_kind_of Euclidean::Circle
80
+ end
81
+
82
+ it "must have a nil center" do
83
+ expect(circle.center).to be_a_kind_of Euclidean::PointZero
84
+ end
85
+
86
+ it "must have a diameter" do
87
+ expect(circle.diameter).to eq 4
88
+ end
89
+
90
+ it "must calculate the correct radius" do
91
+ expect(circle.radius).to eq 2
92
+ end
93
+ end
94
+
95
+ describe "Properties" do
96
+ subject { Circle.new center:[1,2], :diameter => 4 }
97
+
98
+ it "must have a bounds property that returns a Rectangle" do
99
+ expect(subject.bounds).to eq Rectangle.new([-1,0], [3,4])
100
+ end
101
+
102
+ it "must have a minmax property that returns the corners of the bounding rectangle" do
103
+ expect(subject.minmax).to eq [Point[-1,0], Point[3,4]]
104
+ end
105
+
106
+ it "must have a max property that returns the upper right corner of the bounding rectangle" do
107
+ expect(subject.max).to eq Point[3,4]
108
+ end
109
+
110
+ it "must have a min property that returns the lower left corner of the bounding rectangle" do
111
+ expect(subject.min).to eq Point[-1,0]
112
+ end
113
+ end
114
+
115
+ end
@@ -0,0 +1,129 @@
1
+ require 'spec_helper'
2
+ require 'euclidean/edge'
3
+
4
+ describe Euclidean::Edge do
5
+ Edge = Euclidean::Edge
6
+ subject { Edge.new [0,0], [1,1] }
7
+
8
+ it "must create an Edge object" do
9
+ edge = Edge.new([0,0], [1,0])
10
+ expect(edge).to be_a_kind_of Euclidean::Edge
11
+ expect(edge.first).to eq Euclidean::Point[0,0]
12
+ expect(edge.last).to eq Euclidean::Point[1,0]
13
+ end
14
+
15
+ it "must handle equality" do
16
+ edge1 = Edge.new([1,0], [0,1])
17
+ edge2 = Edge.new([1,0], [0,1])
18
+ edge3 = Edge.new([1,1], [5,5])
19
+ expect(edge1==edge2).to be true
20
+ expect(edge1==edge3).to be false
21
+ end
22
+
23
+ it "must return the height of the edge" do
24
+ edge = Edge([0,0], [1,1])
25
+ expect(edge.height).to eq 1
26
+ end
27
+
28
+ it "must return the width of the edge" do
29
+ edge = Edge([0,0], [1,1])
30
+ expect(edge.width).to eq 1
31
+ end
32
+
33
+ it "must convert an Edge to a Vector" do
34
+ expect(Edge.new([0,0], [1,0]).vector).to eq Vector[1,0]
35
+ end
36
+
37
+ it "must return the normalized direction of a vector" do
38
+ expect(Edge.new([0,0], [1,0]).direction).to eq Vector[1,0]
39
+ end
40
+
41
+ it "must return true for parallel edges" do
42
+ expect( Edge.new([0,0], [1,0]).parallel?(Edge.new([0,0], [1,0])) ).to be true
43
+ expect( Edge.new([0,0], [1,0]).parallel?(Edge.new([1,0], [2,0])) ).to be true
44
+ expect( Edge.new([0,0], [1,0]).parallel?(Edge.new([3,0], [4,0])) ).to be true
45
+ expect( Edge.new([0,0], [1,0]).parallel?(Edge.new([3,1], [4,1])) ).to be true
46
+ end
47
+
48
+ it "must return false for non-parallel edges" do
49
+ expect( Edge.new([0,0], [2,0]).parallel?(Edge.new([1,-1], [1,1])) ).to be false
50
+ end
51
+
52
+ it "must clone and reverse" do
53
+ reversed = subject.reverse
54
+ expect(reversed.to_a).to eq subject.to_a.reverse
55
+ expect(reversed==subject).to be false
56
+ end
57
+
58
+ it "must reverse itself" do
59
+ original = subject.to_a
60
+ subject.reverse!
61
+ expect(subject.to_a).to eq original.reverse
62
+ end
63
+
64
+ describe "Spaceship" do
65
+ it "ascending with a Point" do
66
+ edge = Edge.new [0,0], [1,1]
67
+ expect(edge <=> Point[0,0]).to eq 0
68
+ expect(edge <=> Point[1,0]).to eq -1
69
+ expect(edge <=> Point[0,1]).to eq 1
70
+ expect(edge <=> Point[2,2]).to be_nil
71
+ end
72
+
73
+ it "descending with a Point" do
74
+ edge = Edge.new [1,1], [0,0]
75
+ expect(edge <=> Point[0,0]).to eq 0
76
+ expect(edge <=> Point[1,0]).to eq 1
77
+ expect(edge <=> Point[0,1]).to eq -1
78
+ expect(edge <=> Point[2,2]).to be_nil
79
+ end
80
+ end
81
+
82
+ describe "Intersection" do
83
+ it "must find the intersection of two end-intersecting Edges" do
84
+ intersection = Edge.new([0,0],[1,1]).intersection(Edge.new([0,1],[1,1]))
85
+ expect(intersection).to be_a_kind_of Euclidean::Point
86
+ expect(intersection).to eq Euclidean::Point[1,1]
87
+ end
88
+
89
+ it "must find the intersection of two collinear end-intersecting Edges" do
90
+ intersection = Edge.new([2,2], [0,2]).intersection(Edge.new([3,2], [2,2]))
91
+ expect(intersection).to be_a_kind_of Euclidean::Point
92
+ expect(intersection).to eq Euclidean::Point[2,2]
93
+
94
+ intersection = Edge.new([0,2], [2,2]).intersection(Edge.new([2,2], [3,2]))
95
+ expect(intersection).to be_a_kind_of Euclidean::Point
96
+ expect(intersection).to eq Euclidean::Point[2,2]
97
+ end
98
+
99
+ it "must find the itersection of two crossed Edges" do
100
+ edge1 = Edge.new [0.0, 0], [2.0, 2.0]
101
+ edge2 = Edge.new [2.0, 0], [0.0, 2.0]
102
+ intersection = edge1.intersection edge2
103
+ expect(intersection).to be_a_kind_of Euclidean::Point
104
+ expect(intersection).to eq Euclidean::Point[1,1]
105
+ end
106
+
107
+ it "must return nil for two edges that do not intersect" do
108
+ expect( Edge.new([0,0],[1,0]).intersection(Edge.new([0,1],[1,1])) ).to be_nil
109
+ end
110
+
111
+ it "must return true for two collinear and overlapping edges" do
112
+ expect( Edge.new([0,0],[2,0]).intersection(Edge.new([1,0],[3,0])) ).to be true
113
+ end
114
+
115
+ it "must return false for collinear but non-overlapping edges" do
116
+ expect( Edge.new([0,0],[2,0]).intersection(Edge.new([3,0],[4,0])) ).to be false
117
+ expect( Edge.new([0,0],[0,2]).intersection(Edge.new([0,3],[0,4])) ).to be false
118
+ end
119
+
120
+ it "must return nil for two parallel but not collinear edges" do
121
+ expect( Edge.new([0,0],[2,0]).intersection(Edge.new([1,1],[3,1])) ).to be_nil
122
+ end
123
+
124
+ it "must return nil for two perpendicular but not interseting edges" do
125
+ expect( Edge.new([0, 0], [2, 0]).intersection(Edge.new([3, 3], [3, 1])) ).to be_nil
126
+ end
127
+ end
128
+
129
+ end
@@ -0,0 +1,264 @@
1
+ require 'spec_helper'
2
+ require 'euclidean/point'
3
+
4
+ describe Euclidean::Point do
5
+ PointZero = Euclidean::PointZero
6
+ Point = Euclidean::Point
7
+
8
+ it "must generate a PointZero" do
9
+ expect(Point.zero).to be_an_instance_of(PointZero)
10
+ end
11
+
12
+ it "must generate a Point full of zeros" do
13
+ expect( Point.zero(3) ).to eq Point[0,0,0]
14
+ end
15
+
16
+ describe "constructor" do
17
+ it "must return the Point when constructed from a Point" do
18
+ original_point = Point[3,4]
19
+ point = Point[original_point]
20
+ expect( point ).to eq original_point
21
+ expect( point.size ).to eq 2
22
+ expect( point.x ).to eq 3
23
+ expect( point.y ).to eq 4
24
+ end
25
+
26
+ it "must return the PointZero when constructed from a PointZero" do
27
+ original_point = PointZero.new
28
+ point = Point[original_point]
29
+ expect( point ).to eq original_point
30
+ end
31
+ end
32
+
33
+ it "create a Point object from an array" do
34
+ point = Point[[3,4]]
35
+ expect(point.size).to eq 2
36
+ expect(point.x).to eq 3
37
+ expect(point.y).to eq 4
38
+ end
39
+
40
+ it "create a Point object from individual parameters" do
41
+ point = Point[3,4]
42
+ expect(point.size).to eq 2
43
+ expect(point.x).to eq 3
44
+ expect(point.y).to eq 4
45
+ end
46
+
47
+ it "create a Point object from a Vector" do
48
+ point = Point[Vector[3,4]]
49
+ expect(point.size).to eq 2
50
+ expect(point.x).to eq 3
51
+ expect(point.y).to eq 4
52
+ end
53
+
54
+ it "create a Point object from a Point using list syntax" do
55
+ point = Point[Point[13,14]]
56
+ expect(point.size).to eq 2
57
+ expect(point.x).to eq 13
58
+ expect(point.y).to eq 14
59
+ end
60
+
61
+ it "allow indexed element access" do
62
+ point = Point[5,6]
63
+ expect(point.size).to eq 2
64
+ expect(point[0]).to eq 5
65
+ expect(point[1]).to eq 6
66
+ end
67
+
68
+ it "allow named element access" do
69
+ point = Point[5,6,7]
70
+ expect(point.size).to eq 3
71
+ expect(point.x).to eq 5
72
+ expect(point.y).to eq 6
73
+ expect(point.z).to eq 7
74
+ end
75
+
76
+ it "implement inspect" do
77
+ point = Point[8,9]
78
+ expect(point.inspect).to eq 'Point[8, 9]'
79
+ end
80
+
81
+ it "implement to_s" do
82
+ point = Point[10,11]
83
+ expect(point.to_s).to eq 'Point[10, 11]'
84
+ end
85
+
86
+ it "must support array access" do
87
+ expect( Point[1,2][0] ).to eq 1
88
+ expect( Point[1,2][1] ).to eq 2
89
+ expect( Point[1,2][2] ).to eq nil
90
+ end
91
+
92
+ it "must clone" do
93
+ expect(Point[1,2].clone).to be_an_instance_of(Point)
94
+ expect( Point[1,2].clone ).to eq Point[1,2]
95
+ end
96
+
97
+ it "must duplicate" do
98
+ expect(Point[1,2].dup).to be_an_instance_of(Point)
99
+ expect( Point[1,2].dup ).to eq Point[1,2]
100
+ end
101
+
102
+ describe "Artrithmetic" do
103
+ let(:left) { Point[1,2] }
104
+ let(:right) { Point[3,4] }
105
+
106
+ it "must have +@" do
107
+ expect(+left).to eq Point[1,2]
108
+ expect(+left).to be_an_instance_of(Point)
109
+ end
110
+
111
+ it "must have unary negation" do
112
+ expect(-left).to eq Point[-1,-2]
113
+ expect(-left).to be_an_instance_of(Point)
114
+ end
115
+
116
+ context "When adding" do
117
+ it "return a Point when adding two Points" do
118
+ expect(left+right).to be_a_kind_of Point
119
+ end
120
+
121
+ it "must return a Point when adding an array to a Point" do
122
+ expect(left + [5,6]).to eq Point[6,8]
123
+ end
124
+
125
+ it "must add a Numeric to all elements" do
126
+ expect(left + 2).to eq Point[3,4]
127
+ expect(2 + left).to eq Point[3,4]
128
+ end
129
+
130
+ it "must raise an exception when adding mismatched sizes" do
131
+ expect { left + [1,2,3,4] }.to raise_error Euclidean::DimensionMismatch
132
+ end
133
+
134
+ it "must return a Point when adding a Vector" do
135
+ expect(left + Vector[5,6]).to eq Point[6,8]
136
+ expect(Vector[5,6] + right).to eq Vector[8,10]
137
+ end
138
+
139
+ it "must return self when adding a PointZero" do
140
+ expect(left + Point.zero).to eq left
141
+ end
142
+
143
+ it "must return self when adding a NilClass" do
144
+ expect(left + nil).to eq left
145
+ end
146
+ end
147
+
148
+ context "When subtracting" do
149
+ it "return a Point when subtracting two Points" do
150
+ expect(left-right).to be_a_kind_of Point
151
+ end
152
+
153
+ it "must return a Point when subtracting an array from a Point" do
154
+ expect(left - [5,6]).to eq Point[-4, -4]
155
+ end
156
+
157
+ it "must subtract a Numeric from all elements" do
158
+ expect(left - 2).to eq Point[-1, 0]
159
+ expect(2 - left).to eq Point[1,0]
160
+ end
161
+
162
+ it "must raise an exception when subtracting mismatched sizes" do
163
+ expect{ left - [1,2,3,4] }.to raise_error Euclidean::DimensionMismatch
164
+ end
165
+
166
+ it "must return self when subtracting a PointZero" do
167
+ expect(left - Point.zero).to eq left
168
+ end
169
+
170
+ it "must return self when subtracting a NilClass" do
171
+ expect(left - nil).to eq left
172
+ end
173
+ end
174
+
175
+ context "When multiplying" do
176
+ it "must return a Point when multiplied by a Matrix" do
177
+ expect(Matrix[[1,2],[3,4]]*Point[5,6]).to eq Point[17, 39]
178
+ end
179
+ end
180
+
181
+ end
182
+
183
+ describe "Coercion" do
184
+ subject { Point[1,2] }
185
+
186
+ it "must coerce Arrays into Points" do
187
+ expect( subject.coerce([3,4]) ).to eq [Point[3,4], subject]
188
+ end
189
+
190
+ it "must coerce Vectors into Points" do
191
+ expect( subject.coerce(Vector[3,4]) ).to eq [Point[3,4], subject]
192
+ end
193
+
194
+ it "must coerce a Numeric into a Point" do
195
+ expect( subject.coerce(42) ).to eq [Point[42,42], subject]
196
+ end
197
+
198
+ it "must reject anything that can't be coerced" do
199
+ expect{ subject.coerce(NilClass) }.to raise_error TypeError
200
+ end
201
+ end
202
+
203
+ describe "Comparison" do
204
+ let(:point) { Point[1,2] }
205
+
206
+ it "must compare equal to an equal Array" do
207
+ expect(point).to eq [1,2]
208
+ expect(point).to eql [1,2]
209
+ expect([1,2]).to eq point.to_a
210
+ end
211
+
212
+ it "must not compare equal to an unequal Array" do
213
+ expect(point==[3,2]).to be false
214
+ expect([3,2]==point).to be false
215
+ end
216
+
217
+ it "must compare equal to an equal Point" do
218
+ expect(point).to eq Point[1,2]
219
+ expect(point).to eql Point[1,2]
220
+ expect(Point[1,2]).to eq point
221
+ end
222
+
223
+ it "must not compare equal to an unequal Point" do
224
+ expect(point==Point[3,2]).to be false
225
+ expect(Point[3,2]==point).to be false
226
+ end
227
+
228
+ it "must compare equal to an equal Vector" do
229
+ expect(point).to eq Vector[1,2]
230
+ expect(Vector[1,2]).to eq point
231
+ end
232
+
233
+ it "must not compare equal to an unequal Vector" do
234
+ expect(point == Vector[3,2]).to be false
235
+ expect(Vector[3,2] == point).to be false
236
+ end
237
+
238
+ it "must think that floats == ints" do
239
+ expect(Point[1,2]).to eq Point[1.0,2.0]
240
+ expect(Point[1.0,2.0]).to eq Point[1,2]
241
+ end
242
+
243
+ it "must not think that floats eql? ints" do
244
+ expect(Point[1,2].eql? Point[1.0,2.0]).to be false
245
+ expect(Point[1.0,2.0].eql? Point[1,2]).to be false
246
+ end
247
+ end
248
+
249
+ describe "spaceship" do
250
+ it "must spaceship with another Point of the same length" do
251
+ expect(Point[1,2] <=> Point[0,3]).to eq Point[1,-1]
252
+ end
253
+
254
+ it "must spaceship with another Point of different length" do
255
+ expect(Point[1,2] <=> Point[0,3,4]).to eq Point[1,-1]
256
+ expect(Point[1,2,4] <=> Point[0,3]).to eq Point[1,-1]
257
+ end
258
+
259
+ it "must spaceship with an Array" do
260
+ expect(Point[1,2] <=> [0,3]).to eq Point[1,-1]
261
+ end
262
+ end
263
+
264
+ end