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.
@@ -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