geometry 6.2 → 6.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,85 @@
1
+ require_relative 'point'
2
+
3
+ module Geometry
4
+ =begin rdoc
5
+ An object repesenting a {Size} of 1, in N-dimensional space
6
+
7
+ A {SizeOne} object is a {Size} that will always compare equal to one and unequal to
8
+ everything else, regardless of dimensionality. It's similar to the
9
+ {http://en.wikipedia.org/wiki/Null_Object_pattern Null Object Pattern}, but for ones.
10
+ =end
11
+ class SizeOne
12
+ def eql?(other)
13
+ if other.respond_to? :all?
14
+ other.all? {|e| e.eql? 1}
15
+ else
16
+ other == 1
17
+ end
18
+ end
19
+ alias == eql?
20
+
21
+ def coerce(other)
22
+ if other.is_a? Numeric
23
+ [other, 1]
24
+ elsif other.is_a? Array
25
+ [other, Array.new(other.size,1)]
26
+ elsif other.is_a? Vector
27
+ [other, Vector[*Array.new(other.size,1)]]
28
+ else
29
+ [Size[other], Size[Array.new(other.size,1)]]
30
+ end
31
+ end
32
+
33
+ # @group Arithmetic
34
+
35
+ # @group Unary operators
36
+ def +@
37
+ self
38
+ end
39
+
40
+ def -@
41
+ -1
42
+ end
43
+ # @endgroup
44
+
45
+ def +(other)
46
+ if other.respond_to?(:map)
47
+ other.map {|a| a + 1 }
48
+ else
49
+ other + 1
50
+ end
51
+ end
52
+
53
+ def -(other)
54
+ if other.is_a? Numeric
55
+ 1 - other
56
+ elsif other.respond_to? :map
57
+ other.map {|a| 1 - a }
58
+ else
59
+ 1 - other
60
+ end
61
+ end
62
+
63
+ def *(other)
64
+ raise OperationNotDefined unless other.is_a? Numeric
65
+ other
66
+ end
67
+
68
+ def /(other)
69
+ raise OperationNotDefined unless other.is_a? Numeric
70
+ raise ZeroDivisionError if 0 == other
71
+ 1 / other
72
+ end
73
+ # @endgroup
74
+
75
+ # @group Enumerable
76
+
77
+ # Return the first, or first n, elements (always 0)
78
+ # @param n [Number] the number of elements to return
79
+ def first(n=nil)
80
+ Array.new(n, 1) rescue 1
81
+ end
82
+ # @endgroup
83
+ end
84
+ end
85
+
@@ -65,6 +65,14 @@ everything else, regardless of dimensionality. You can think of it as an applica
65
65
  end
66
66
  # @endgroup
67
67
 
68
+ # @group Enumerable
69
+
70
+ # Return the first, or first n, elements (always 0)
71
+ # @param n [Number] the number of elements to return
72
+ def first(n=nil)
73
+ Array.new(n, 0) rescue 0
74
+ end
75
+ # @endgroup
68
76
  end
69
77
  end
70
78
 
@@ -69,6 +69,12 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
69
69
  end
70
70
 
71
71
  # !@group Accessors
72
+ # @!attribute closed?
73
+ # @return [Bool] always true
74
+ def closed?
75
+ true
76
+ end
77
+
72
78
  # @return [Point] The upper right corner of the bounding {Rectangle}
73
79
  def max
74
80
  @points.last
@@ -35,6 +35,27 @@ An isoscoles right {Triangle} created with an origin and leg length
35
35
  RightTriangle.new args[0], args[1], args[1]
36
36
  end
37
37
  end
38
+
39
+ # @!attribute closed?
40
+ # @return [Bool] always true
41
+ def closed?
42
+ true
43
+ end
44
+
45
+ # @return [Point] The upper-right corner of the bounding rectangle that encloses the {Polyline}
46
+ def max
47
+ points.reduce {|memo, vertex| Point[[memo.x, vertex.x].max, [memo.y, vertex.y].max] }
48
+ end
49
+
50
+ # @return [Point] The lower-left corner of the bounding rectangle that encloses the {Polyline}
51
+ def min
52
+ points.reduce {|memo, vertex| Point[[memo.x, vertex.x].min, [memo.y, vertex.y].min] }
53
+ end
54
+
55
+ # @return [Array<Point>] The lower-left and upper-right corners of the enclosing bounding rectangle
56
+ def minmax
57
+ points.reduce([points.first, points.first]) {|memo, vertex| [Point[[memo.first.x, vertex.x].min, [memo.first.y, vertex.y].min], Point[[memo.last.x, vertex.x].max, [memo.last.y, vertex.y].max]] }
58
+ end
38
59
  end
39
60
 
40
61
  # {http://en.wikipedia.org/wiki/Equilateral_triangle Equilateral Triangle}
@@ -0,0 +1,69 @@
1
+ require 'minitest/autorun'
2
+ require 'geometry/annulus'
3
+
4
+ describe Geometry::Annulus do
5
+ it 'must complain when constructed with only a center' do
6
+ -> { Geometry::Annulus.new center:Point[1,2] }.must_raise ArgumentError
7
+ end
8
+
9
+ it 'must also be known as a Ring' do
10
+ Geometry::Ring.new(Point[1,2], inner_radius:5, radius:10).must_be_instance_of Geometry::Annulus
11
+ end
12
+
13
+ describe 'when constructed with a named center' do
14
+ subject { Geometry::Annulus.new center:Point[1,2], inner_radius:5, radius:10 }
15
+
16
+ it 'must have a center' do
17
+ subject.center.must_equal Point[1,2]
18
+ end
19
+ end
20
+
21
+ describe 'when constructed with a center, inner_radius and radius' do
22
+ subject { Geometry::Annulus.new Point[1,2], inner_radius:5, radius:10 }
23
+
24
+ it 'must have a center' do
25
+ subject.center.must_equal Point[1,2]
26
+ end
27
+
28
+ it 'must have an inner diameter' do
29
+ subject.inner_diameter.must_equal 10
30
+ end
31
+
32
+ it 'must have an inner radius' do
33
+ subject.inner_radius.must_equal 5
34
+ end
35
+
36
+ it 'must have an outer diameter' do
37
+ subject.outer_diameter.must_equal 20
38
+ end
39
+
40
+ it 'must have a radius' do
41
+ subject.radius.must_equal 10
42
+ subject.outer_radius.must_equal 10
43
+ end
44
+ end
45
+
46
+ describe 'when constructed with a center, inner_diameter and diameter' do
47
+ subject { Geometry::Annulus.new Point[1,2], inner_diameter:5, diameter:10 }
48
+
49
+ it 'must have a center' do
50
+ subject.center.must_equal Point[1,2]
51
+ end
52
+
53
+ it 'must have an inner diameter' do
54
+ subject.inner_diameter.must_equal 5
55
+ end
56
+
57
+ it 'must have an inner radius' do
58
+ subject.inner_radius.must_equal 2.5
59
+ end
60
+
61
+ it 'must have an outer diameter' do
62
+ subject.outer_diameter.must_equal 10
63
+ end
64
+
65
+ it 'must have a radius' do
66
+ subject.radius.must_equal 5
67
+ end
68
+ end
69
+ end
@@ -88,6 +88,38 @@ describe Geometry::Circle do
88
88
  it "must calculate the correct radius" do
89
89
  circle.radius.must_equal 2
90
90
  end
91
+
92
+ it 'must have the correct min values' do
93
+ circle.min.must_equal Point[-2, -2]
94
+ circle.min.must_be_instance_of Geometry::PointIso
95
+ end
96
+
97
+ it 'must have the correct max values' do
98
+ circle.max.must_equal Point[2, 2]
99
+ circle.max.must_be_instance_of Geometry::PointIso
100
+ end
101
+
102
+ it 'must have the correct minmax values' do
103
+ circle.minmax.must_equal [Point[-2, -2], Point[2,2]]
104
+ end
105
+ end
106
+
107
+ describe 'when constructed with a Rational diameter and no center' do
108
+ let(:circle) { Circle.new :diameter => Rational(5,3) }
109
+
110
+ it 'must have the correct min values' do
111
+ circle.min.must_equal Point[-5/6, -5/6]
112
+ circle.min.must_be_instance_of Geometry::PointIso
113
+ end
114
+
115
+ it 'must have the correct max values' do
116
+ circle.max.must_equal Point[5/6, 5/6]
117
+ circle.max.must_be_instance_of Geometry::PointIso
118
+ end
119
+
120
+ it 'must have the correct minmax values' do
121
+ circle.minmax.must_equal [Point[-5/6, -5/6], Point[5/6,5/6]]
122
+ end
91
123
  end
92
124
 
93
125
  describe "properties" do
@@ -97,6 +129,10 @@ describe Geometry::Circle do
97
129
  subject.bounds.must_equal Rectangle.new([-1,0], [3,4])
98
130
  end
99
131
 
132
+ it 'must always be closed' do
133
+ subject.closed?.must_equal true
134
+ end
135
+
100
136
  it "must have a minmax property that returns the corners of the bounding rectangle" do
101
137
  subject.minmax.must_equal [Point[-1,0], Point[3,4]]
102
138
  end
@@ -22,4 +22,9 @@ describe Geometry::Obround do
22
22
  obround.must_equal Obround.new([1,2], [3,4])
23
23
  end
24
24
  end
25
+
26
+ it 'must always be closed' do
27
+ obround = Geometry::Obround.new 2, 3
28
+ obround.closed?.must_equal true
29
+ end
25
30
  end
@@ -2,9 +2,18 @@ require 'minitest/autorun'
2
2
  require 'geometry/point'
3
3
 
4
4
  describe Geometry::Point do
5
+ PointOne = Geometry::PointOne
5
6
  PointZero = Geometry::PointZero
6
7
 
7
8
  describe "class methods" do
9
+ it 'must generate a PointOne' do
10
+ Point.one.must_be_instance_of PointOne
11
+ end
12
+
13
+ it 'must generate a Point full of ones' do
14
+ Point.one(3).must_equal Point[1,1,1]
15
+ end
16
+
8
17
  it "must generate a PointZero" do
9
18
  Point.zero.must_be_instance_of(PointZero)
10
19
  end
@@ -56,12 +65,22 @@ describe Geometry::Point do
56
65
  assert_equal(13, point.x)
57
66
  assert_equal(14, point.y)
58
67
  end
59
- it "allow indexed element access" do
60
- point = Geometry::Point[5,6]
61
- assert_equal(2, point.size)
62
- assert_equal(5, point[0])
63
- assert_equal(6, point[1])
68
+
69
+ describe 'when array access' do
70
+ it 'must allow indexed access' do
71
+ point = Geometry::Point[5,6]
72
+ point.size.must_equal 2
73
+ point[0].must_equal 5
74
+ point[1].must_equal 6
75
+ end
76
+
77
+ it 'must slize with a start index and a length' do
78
+ point = Geometry::Point[5, 6, 7]
79
+ slice = point[1,2]
80
+ slice.length.must_equal 2
81
+ end
64
82
  end
83
+
65
84
  it "allow named element access" do
66
85
  point = Geometry::Point[5,6,7]
67
86
  assert_equal(3, point.size)
@@ -132,6 +151,10 @@ describe Geometry::Point do
132
151
  (Vector[5,6] + right).must_equal Vector[8,10]
133
152
  end
134
153
 
154
+ it 'must add a PointOne' do
155
+ (left + Point.one).must_equal Point[2,3]
156
+ end
157
+
135
158
  it "must return self when adding a PointZero" do
136
159
  (left + Point.zero).must_equal left
137
160
  end
@@ -159,6 +182,10 @@ describe Geometry::Point do
159
182
  lambda { left - [1,2,3,4] }.must_raise Geometry::DimensionMismatch
160
183
  end
161
184
 
185
+ it 'must subtract a PointOne' do
186
+ (left - Point.one).must_equal Point[0,1]
187
+ end
188
+
162
189
  it "must return self when subtracting a PointZero" do
163
190
  (left - Point.zero).must_equal left
164
191
  end
@@ -220,6 +247,14 @@ describe Geometry::Point do
220
247
  Point[3,2].wont_equal point
221
248
  end
222
249
 
250
+ it 'must compare to a PointOne' do
251
+ point.wont_equal Point.one
252
+ Point.one.wont_equal point
253
+
254
+ Point[1,1].must_equal Point.one
255
+ Point.one.must_equal Point[1,1]
256
+ end
257
+
223
258
  it "must compare equal to an equal Vector" do
224
259
  point.must_equal Vector[1,2]
225
260
  Vector[1,2].must_equal point
@@ -0,0 +1,189 @@
1
+ require 'minitest/autorun'
2
+ require 'geometry/point_iso'
3
+
4
+ describe Geometry::PointIso do
5
+ let(:value) { 5 }
6
+ subject { Geometry::PointIso.new(5) }
7
+
8
+ describe 'arithmetic' do
9
+ let(:left) { Point[1,2] }
10
+ let(:right) { Point[3,4] }
11
+
12
+ it 'must pretend to be a Point' do
13
+ subject.is_a?(Point).must_equal true
14
+ subject.kind_of?(Point).must_equal true
15
+
16
+ subject.is_a?(Geometry::PointIso).must_equal true
17
+ subject.kind_of?(Geometry::PointIso).must_equal true
18
+
19
+ subject.instance_of?(Point).must_equal false
20
+ subject.instance_of?(Geometry::PointIso).must_equal true
21
+ end
22
+
23
+ it 'must have +@' do
24
+ (+subject).must_be :eql?, value
25
+ (+subject).must_be_instance_of(Geometry::PointIso)
26
+ end
27
+
28
+ it 'must have unary negation' do
29
+ (-subject).must_be :eql?, -value
30
+ (-subject).must_be_instance_of(Geometry::PointIso)
31
+ end
32
+
33
+ describe 'Accessors' do
34
+ it 'must return 1 for array access' do
35
+ subject[3].must_equal value
36
+ end
37
+
38
+ it 'must return 1 for named element access' do
39
+ subject.x.must_equal value
40
+ subject.y.must_equal value
41
+ subject.z.must_equal value
42
+ end
43
+ end
44
+
45
+ it 'must add a number' do
46
+ (subject + 3).must_equal (value + 3)
47
+ (3 + subject).must_equal (3 + value)
48
+ end
49
+
50
+ it 'return a Point when adding two Points' do
51
+ (subject + right).must_be_kind_of Point
52
+ (left + subject).must_be_kind_of Point
53
+ end
54
+
55
+ it 'must return an Array when adding an array' do
56
+ (subject + [5,6]).must_equal [value+5, value+6]
57
+ # ([5,6] + subject).must_equal [10, 11]
58
+ end
59
+
60
+ it 'must return a Point when adding a Size' do
61
+ (subject + Size[5,6]).must_be_instance_of(Point)
62
+ (subject + Size[5,6]).must_equal Point[value+5, value+6]
63
+ end
64
+
65
+ describe 'when subtracting' do
66
+ it 'must subtract a number' do
67
+ (subject - 3).must_equal (value - 3)
68
+ (3 - subject).must_equal -2
69
+ end
70
+
71
+ it 'return a Point when subtracting two Points' do
72
+ (subject - right).must_equal Point[value - right.x, value - right.y]
73
+ (left - subject).must_equal Point[left.x - value, left.y - value]
74
+ end
75
+
76
+ it 'must return a Point when subtracting an array' do
77
+ (subject - [5,6]).must_equal [0, -1]
78
+ # ([5,6] - subject).must_equal [4,5]
79
+ end
80
+
81
+ it 'must return a Point when subtracting a Size' do
82
+ (subject - Size[5,6]).must_be_instance_of(Point)
83
+ (subject - Size[5,6]).must_equal Point[0,-1]
84
+ end
85
+ end
86
+
87
+ it 'must multiply by a scalar' do
88
+ (subject * 3).must_equal 15
89
+ (subject * 3.0).must_equal 15.0
90
+ end
91
+
92
+ it 'must refuse to multiply by a Point' do
93
+ -> { subject * Point[1, 2] }.must_raise Geometry::OperationNotDefined
94
+ end
95
+
96
+ it 'must refuse to multiply by a Vector' do
97
+ -> { subject * Vector[2, 3] }.must_raise Geometry::OperationNotDefined
98
+ end
99
+
100
+ it 'must divide by a scalar' do
101
+ (subject / 3).must_equal 5/3
102
+ (subject / 4.0).must_equal 5/4.0
103
+ end
104
+
105
+ it 'must raise an exception when divided by 0' do
106
+ -> { subject / 0 }.must_raise ZeroDivisionError
107
+ end
108
+
109
+ describe 'division' do
110
+ it 'must raise an exception for Points' do
111
+ lambda { subject / Point[1,2] }.must_raise Geometry::OperationNotDefined
112
+ end
113
+
114
+ it 'must raise an exception for Vectors' do
115
+ lambda { subject / Vector[1,2] }.must_raise Geometry::OperationNotDefined
116
+ end
117
+ end
118
+ end
119
+
120
+ describe 'coercion' do
121
+ it 'must coerce Arrays into Points' do
122
+ subject.coerce([3,4]).must_equal [Point[3,4], Point[5, 5]]
123
+ end
124
+
125
+ it 'must coerce Vectors into Vectors' do
126
+ subject.coerce(Vector[3,4]).must_equal [Vector[3,4], Vector[5, 5]]
127
+ end
128
+
129
+ it 'must coerce Points into Points' do
130
+ subject.coerce(Point[5,6]).must_equal [Point[5,6], Point[5, 5]]
131
+ end
132
+ end
133
+
134
+ describe 'comparison' do
135
+ it 'must be equal to the same value' do
136
+ subject.must_be :eql?, 5
137
+ subject.must_be :eql?, 5.0
138
+ end
139
+
140
+ it 'must not be equal to a number of a different value' do
141
+ 0.wont_equal subject
142
+ 3.14.wont_equal subject
143
+ end
144
+
145
+ it 'must be equal to an Array of the same value' do
146
+ subject.must_be :==, [5,5]
147
+ subject.must_be :eql?, [5,5]
148
+ subject.must_be :===, [5,5]
149
+ [5,5].must_equal subject
150
+ subject.must_equal [5,5]
151
+ end
152
+
153
+ it 'must not be equal to an Array of other values' do
154
+ subject.wont_equal [3, 2, 1]
155
+ [3, 2, 1].wont_equal subject
156
+ end
157
+
158
+ it 'must not be equal to a Point at the origin' do
159
+ subject.wont_be :==, Point[0,0]
160
+ subject.wont_be :eql?, Point[0,0]
161
+ subject.wont_be :===, Point[0,0]
162
+ Point[0,0].wont_equal subject
163
+ subject.wont_equal Point[0,0]
164
+ end
165
+
166
+ it 'must not be equal to a Point not at the origin' do
167
+ subject.wont_equal Point[3,2]
168
+ Point[3,2].wont_equal subject
169
+ end
170
+
171
+ it 'must be equal to a Point of subjects' do
172
+ subject.must_be :==, Point[value, value]
173
+ subject.must_be :eql?, Point[value, value]
174
+ subject.must_be :===, Point[value, value]
175
+ Point[value, value].must_equal subject
176
+ subject.must_equal Point[value, value]
177
+ end
178
+
179
+ it 'must be equal to an Vector of the same value' do
180
+ subject.must_be :eql?, Vector[value, value]
181
+ Vector[5, 5].must_equal subject
182
+ end
183
+
184
+ it 'must not be equal to a Vector of other values' do
185
+ subject.wont_equal Vector[3,2]
186
+ Vector[3,2].wont_equal subject
187
+ end
188
+ end
189
+ end