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.
Files changed (51) hide show
  1. data/.gitignore +6 -0
  2. data/Gemfile +7 -0
  3. data/LICENSE +21 -0
  4. data/README.markdown +105 -0
  5. data/Rakefile +24 -0
  6. data/geometry-in-ruby.gemspec +23 -0
  7. data/lib/geometry/arc.rb +94 -0
  8. data/lib/geometry/circle.rb +122 -0
  9. data/lib/geometry/cluster_factory.rb +15 -0
  10. data/lib/geometry/edge.rb +140 -0
  11. data/lib/geometry/line.rb +154 -0
  12. data/lib/geometry/obround.rb +238 -0
  13. data/lib/geometry/path.rb +67 -0
  14. data/lib/geometry/point.rb +163 -0
  15. data/lib/geometry/point_zero.rb +107 -0
  16. data/lib/geometry/polygon.rb +368 -0
  17. data/lib/geometry/polyline.rb +318 -0
  18. data/lib/geometry/rectangle.rb +378 -0
  19. data/lib/geometry/regular_polygon.rb +136 -0
  20. data/lib/geometry/rotation.rb +190 -0
  21. data/lib/geometry/size.rb +75 -0
  22. data/lib/geometry/size_zero.rb +70 -0
  23. data/lib/geometry/square.rb +113 -0
  24. data/lib/geometry/text.rb +24 -0
  25. data/lib/geometry/transformation/composition.rb +39 -0
  26. data/lib/geometry/transformation.rb +171 -0
  27. data/lib/geometry/triangle.rb +78 -0
  28. data/lib/geometry/vector.rb +34 -0
  29. data/lib/geometry.rb +22 -0
  30. data/test/geometry/arc.rb +25 -0
  31. data/test/geometry/circle.rb +112 -0
  32. data/test/geometry/edge.rb +132 -0
  33. data/test/geometry/line.rb +132 -0
  34. data/test/geometry/obround.rb +25 -0
  35. data/test/geometry/path.rb +66 -0
  36. data/test/geometry/point.rb +258 -0
  37. data/test/geometry/point_zero.rb +177 -0
  38. data/test/geometry/polygon.rb +214 -0
  39. data/test/geometry/polyline.rb +266 -0
  40. data/test/geometry/rectangle.rb +154 -0
  41. data/test/geometry/regular_polygon.rb +120 -0
  42. data/test/geometry/rotation.rb +108 -0
  43. data/test/geometry/size.rb +97 -0
  44. data/test/geometry/size_zero.rb +153 -0
  45. data/test/geometry/square.rb +66 -0
  46. data/test/geometry/transformation/composition.rb +49 -0
  47. data/test/geometry/transformation.rb +169 -0
  48. data/test/geometry/triangle.rb +32 -0
  49. data/test/geometry/vector.rb +41 -0
  50. data/test/geometry.rb +5 -0
  51. metadata +117 -0
@@ -0,0 +1,120 @@
1
+ require 'minitest/autorun'
2
+ require 'geometry/regular_polygon'
3
+
4
+ describe Geometry::RegularPolygon do
5
+ RegularPolygon = Geometry::RegularPolygon
6
+
7
+ describe "when constructed with named center and radius arguments" do
8
+ let(:polygon) { RegularPolygon.new sides:4, center:[1,2], radius:3 }
9
+ subject { RegularPolygon.new sides:4, center:[1,2], radius:3 }
10
+
11
+ it "must create a RegularPolygon" do
12
+ polygon.must_be_instance_of(RegularPolygon)
13
+ end
14
+
15
+ it "must have the correct number of sides" do
16
+ polygon.edge_count.must_equal 4
17
+ end
18
+
19
+ it "must have a center point accessor" do
20
+ polygon.center.must_equal Point[1,2]
21
+ end
22
+
23
+ it "must have a radius accessor" do
24
+ polygon.radius.must_equal 3
25
+ end
26
+
27
+ it "must compare equal" do
28
+ polygon.must_equal RegularPolygon.new(sides:4, center:[1,2], radius:3)
29
+ end
30
+
31
+ describe "properties" do
32
+ it "must have vertices" do
33
+ subject.vertices.must_equal [Point[4.0, 2.0], Point[1.0000000000000002, 5.0], Point[-2.0, 2.0000000000000004], Point[0.9999999999999994, -1.0]]
34
+ end
35
+ end
36
+ end
37
+
38
+ describe "when constructed with a center and diameter" do
39
+ let(:polygon) { RegularPolygon.new sides:4, center:[1,2], diameter:4 }
40
+
41
+ it "must be a DiameterRegularPolygon" do
42
+ polygon.must_be_instance_of(Geometry::DiameterRegularPolygon)
43
+ polygon.must_be_kind_of(RegularPolygon)
44
+ end
45
+
46
+ it "must have the correct number of sides" do
47
+ polygon.edge_count.must_equal 4
48
+ end
49
+
50
+ it "must have a center" do
51
+ polygon.center.must_equal Point[1,2]
52
+ end
53
+
54
+ it "must have a diameter" do
55
+ polygon.diameter.must_equal 4
56
+ end
57
+
58
+ it "must calculate the correct radius" do
59
+ polygon.radius.must_equal 2
60
+ end
61
+
62
+ it "must compare equal" do
63
+ polygon.must_equal RegularPolygon.new(sides:4, center:[1,2], diameter:4)
64
+ end
65
+ end
66
+
67
+ describe "when constructed with a diameter and no center" do
68
+ let(:polygon) { RegularPolygon.new sides:4, diameter:4 }
69
+
70
+ it "must be a DiameterRegularPolygon" do
71
+ polygon.must_be_instance_of(Geometry::DiameterRegularPolygon)
72
+ polygon.must_be_kind_of(RegularPolygon)
73
+ end
74
+
75
+ it "must have the correct number of sides" do
76
+ polygon.edge_count.must_equal 4
77
+ end
78
+
79
+ it "must be at the origin" do
80
+ polygon.center.must_equal Point.zero
81
+ end
82
+
83
+ it "must have a diameter" do
84
+ polygon.diameter.must_equal 4
85
+ end
86
+
87
+ it "must calculate the correct radius" do
88
+ polygon.radius.must_equal 2
89
+ end
90
+ end
91
+
92
+ describe "properties" do
93
+ subject { RegularPolygon.new sides:6, diameter:4 }
94
+
95
+ it "must have edges" do
96
+ expected_edges = [Edge(Point[2, 0], Point[1, 1.732]), Edge(Point[1, 1.732], Point[-1, 1.732]), Edge(Point[-1, 1.732], Point[-2, 0]), Edge(Point[-2, 0], Point[-1, -1.732]), Edge(Point[-1, -1.732], Point[1, -1.732]), Edge(Point[1, -1.732], Point[2, 0])]
97
+ subject.edges.zip(expected_edges) do |edge1, edge2|
98
+ edge1.to_a.zip(edge2.to_a) do |point1, point2|
99
+ point1.to_a.zip(point2.to_a) {|a, b| a.must_be_close_to b }
100
+ end
101
+ end
102
+ end
103
+
104
+ it "must have a bounds property that returns a Rectangle" do
105
+ subject.bounds.must_equal Rectangle.new([-2,-2], [2,2])
106
+ end
107
+
108
+ it "must have a minmax property that returns the corners of the bounding rectangle" do
109
+ subject.minmax.must_equal [Point[-2,-2], Point[2,2]]
110
+ end
111
+
112
+ it "must have a max property that returns the upper right corner of the bounding rectangle" do
113
+ subject.max.must_equal Point[2,2]
114
+ end
115
+
116
+ it "must have a min property that returns the lower left corner of the bounding rectangle" do
117
+ subject.min.must_equal Point[-2,-2]
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,108 @@
1
+ require 'minitest/autorun'
2
+ require 'geometry/rotation'
3
+
4
+ describe Geometry::Rotation do
5
+ Point = Geometry::Point
6
+ Rotation = Geometry::Rotation
7
+ RotationAngle = Geometry::RotationAngle
8
+
9
+ describe "when constructed" do
10
+ it "must accept a rotation angle" do
11
+ rotation = Rotation.new angle:Math::PI/2
12
+ rotation.must_be_instance_of(RotationAngle)
13
+ rotation.angle.must_equal Math::PI/2
14
+ rotation.x.x.must_be_close_to 0
15
+ rotation.x.y.must_be_close_to 1
16
+ rotation.y.x.must_be_close_to -1
17
+ rotation.y.y.must_be_close_to 0
18
+ end
19
+
20
+ it "must accept an X axis" do
21
+ rotation = Rotation.new x:[1,0]
22
+ rotation.must_be_instance_of(RotationAngle)
23
+ rotation.angle.must_equal 0
24
+ rotation.x.must_equal Point[1,0]
25
+ rotation.y.must_equal Point[0,1]
26
+ end
27
+ end
28
+
29
+ it "must accept x and y axes" do
30
+ rotation = Geometry::Rotation.new :x => [1,2,3], :y => [4,5,6]
31
+ rotation.x.must_equal [1,2,3]
32
+ rotation.y.must_equal [4,5,6]
33
+ end
34
+
35
+ it "must accept 3-element x and y axes and a dimensionality of 3" do
36
+ rotation = Geometry::Rotation.new(:dimensions => 3, :x => [1,2,3], :y => [4,5,6])
37
+ rotation.dimensions.must_equal 3
38
+ end
39
+
40
+ it "must reject 3-element x and y axes and a dimensionality of 2" do
41
+ lambda { Geometry::Rotation.new(:dimensions => 2, :x => [1,2,3], :y => [4,5,6]) }.must_raise ArgumentError
42
+ end
43
+
44
+ it "must promote 2-element Vectors to dimensionality of 3" do
45
+ rotation = Geometry::Rotation.new(:dimensions => 3, :x => [1,2], :y => [4,5])
46
+ rotation.dimensions.must_equal 3
47
+ rotation.x.must_equal [1,2,0]
48
+ rotation.y.must_equal [4,5,0]
49
+ end
50
+
51
+ it "must be the identity rotation if no axes are given" do
52
+ Geometry::Rotation.new.identity?.must_equal true
53
+ Geometry::Rotation.new(:dimensions => 3).identity?.must_equal true
54
+ end
55
+
56
+ it "must be the identity rotation when identity axes are given" do
57
+ Geometry::Rotation.new(:x => [1,0,0], :y => [0,1,0])
58
+ end
59
+
60
+ it "must have a matrix accessor" do
61
+ r = Geometry::Rotation.new(:x => [1,0,0], :y => [0,1,0])
62
+ r.matrix.must_equal Matrix[[1,0,0],[0,1,0],[0,0,1]]
63
+ end
64
+
65
+ describe "when comparing" do
66
+ it "must equate equal objects" do
67
+ Rotation.new(x:[1,2,3], y:[4,5,6]).must_equal Rotation.new(x:[1,2,3], y:[4,5,6])
68
+ end
69
+ end
70
+
71
+ describe "comparison" do
72
+ it "must equate equal angles" do
73
+ Rotation.new(angle:45).must_equal Rotation.new(angle:45)
74
+ end
75
+
76
+ it "must not equate unequal angles" do
77
+ Rotation.new(angle:10).wont_equal Rotation.new(angle:45)
78
+ end
79
+ end
80
+
81
+ describe "composition" do
82
+ it "must add angles" do
83
+ (Rotation.new(angle:45) + Rotation.new(angle:45)).must_equal Rotation.new(angle:90)
84
+ end
85
+
86
+ it "must subtract angles" do
87
+ (Rotation.new(angle:45) - Rotation.new(angle:45)).must_equal Rotation.new(angle:0)
88
+ end
89
+
90
+ it "must negate angles" do
91
+ (-Rotation.new(angle:45)).must_equal Rotation.new(angle:-45)
92
+ end
93
+ end
94
+
95
+ describe "when transforming a Point" do
96
+ describe "when no rotation is set" do
97
+ it "must return the Point" do
98
+ Rotation.new.transform(Point[1,0]).must_equal Point[1,0]
99
+ end
100
+ end
101
+
102
+ it "must rotate" do
103
+ rotated_point = Rotation.new(angle:Math::PI/2).transform(Point[1,0])
104
+ rotated_point.x.must_be_close_to 0
105
+ rotated_point.y.must_be_close_to 1
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,97 @@
1
+ require 'minitest/autorun'
2
+ require 'geometry/size'
3
+
4
+ describe Geometry::Size do
5
+ describe "when constructed" do
6
+ it "create a Size object using list syntax" do
7
+ size = Geometry::Size[2,1]
8
+ assert_equal(2, size.size)
9
+ assert_equal(2, size.x)
10
+ assert_equal(1, size.y)
11
+ end
12
+
13
+ it "create a Size object from an array" do
14
+ size = Geometry::Size[[3,4]]
15
+ assert_equal(2, size.size)
16
+ assert_equal(3, size.x)
17
+ assert_equal(4, size.y)
18
+ end
19
+
20
+ it "create a Size object from individual parameters" do
21
+ size = Geometry::Size[3,4]
22
+ assert_equal(2, size.size)
23
+ assert_equal(3, size.x)
24
+ assert_equal(4, size.y)
25
+ end
26
+
27
+ it "create a Size object from a Size" do
28
+ size = Geometry::Size[Geometry::Size[3,4]]
29
+ assert_equal(2, size.size)
30
+ assert_equal(3, size.x)
31
+ assert_equal(4, size.y)
32
+ end
33
+
34
+ it "create a Size object from a Vector" do
35
+ size = Geometry::Size[Vector[3,4]]
36
+ assert_equal(2, size.size)
37
+ assert_equal(3, size.x)
38
+ assert_equal(4, size.y)
39
+ end
40
+ end
41
+
42
+ it "allow indexed element access" do
43
+ size = Geometry::Size[5,6]
44
+ assert_equal(2, size.size)
45
+ assert_equal(5, size[0])
46
+ assert_equal(6, size[1])
47
+ end
48
+ it "allow named element access" do
49
+ size = Geometry::Size[5,6,7]
50
+ assert_equal(3, size.size)
51
+ assert_equal(5, size.x)
52
+ assert_equal(6, size.y)
53
+ assert_equal(7, size.z)
54
+ end
55
+
56
+ it "have a width accessor" do
57
+ size = Geometry::Size[5,6,7]
58
+ assert_equal(5, size.width)
59
+ end
60
+
61
+ it "have a height accessor" do
62
+ size = Geometry::Size[5,6,7]
63
+ assert_equal(6, size.height)
64
+ end
65
+
66
+ it "have a depth accessor" do
67
+ size = Geometry::Size[5,6,7]
68
+ assert_equal(7, size.depth)
69
+ end
70
+
71
+ it "compare equal" do
72
+ size1 = Geometry::Size[1,2]
73
+ size2 = Geometry::Size[1,2]
74
+ size3 = Geometry::Size[3,4]
75
+ assert_equal(size1, size2)
76
+ size2.wont_equal size3
77
+ end
78
+
79
+ it "compare equal to an array with equal elements" do
80
+ size1 = Size[1,2]
81
+ assert_equal(size1, [1,2])
82
+ end
83
+
84
+ it "not compare equal to an array with unequal elements" do
85
+ size1 = Size[1,2]
86
+ size1.wont_equal [3,2]
87
+ end
88
+
89
+ it "implement inspect" do
90
+ size = Geometry::Size[8,9]
91
+ assert_equal('Size[8, 9]', size.inspect)
92
+ end
93
+ it "implement to_s" do
94
+ size = Geometry::Size[10,11]
95
+ assert_equal('Size[10, 11]', size.to_s)
96
+ end
97
+ end
@@ -0,0 +1,153 @@
1
+ require 'minitest/autorun'
2
+ require 'geometry/size_zero'
3
+
4
+ describe Geometry::SizeZero do
5
+ Size = Geometry::Size
6
+
7
+ let(:zero) { Geometry::SizeZero.new }
8
+
9
+ describe "arithmetic" do
10
+ let(:left) { Size[1,2] }
11
+ let(:right) { Size[3,4] }
12
+
13
+ it "must have +@" do
14
+ (+zero).must_be :eql?, 0
15
+ (+zero).must_be_instance_of(Geometry::SizeZero)
16
+ end
17
+
18
+ it "must have unary negation" do
19
+ (-zero).must_be :eql?, 0
20
+ (-zero).must_be_instance_of(Geometry::SizeZero)
21
+ end
22
+
23
+ describe "when adding" do
24
+ it "must return a number" do
25
+ (zero + 3).must_equal 3
26
+ (3 + zero).must_equal 3
27
+ end
28
+
29
+ it "return a Size when adding two Sizes" do
30
+ (zero + right).must_be_kind_of Size
31
+ # (left + zero).must_be_kind_of Size
32
+ end
33
+
34
+ it "must return a Size when adding an array" do
35
+ (zero + [5,6]).must_equal [5,6]
36
+ # ([5,6] + zero).must_equal [5,6]
37
+ end
38
+ end
39
+
40
+ describe "when subtracting" do
41
+ it "must return a number" do
42
+ (zero - 3).must_equal -3
43
+ (3 - zero).must_equal 3
44
+ end
45
+
46
+ it "return a Size when subtracting two Size" do
47
+ (zero - right).must_equal Size[-3,-4]
48
+ (left - zero).must_equal Size[1,2]
49
+ end
50
+
51
+ it "must return a Size when subtracting an array" do
52
+ (zero - [5,6]).must_equal [-5, -6]
53
+ # ([5,6] - zero).must_equal [5,6]
54
+ end
55
+ end
56
+
57
+ describe "multiplication" do
58
+ it "must return 0 for scalars" do
59
+ (zero * 3).must_equal 0
60
+ (zero * 3.0).must_equal 0.0
61
+ end
62
+
63
+ it "must return 0 for Sizes" do
64
+ (zero * Size[1,2]).must_equal 0
65
+ end
66
+
67
+ it "must return 0 for Vectors" do
68
+ (zero * Vector[2,3]).must_equal 0
69
+ end
70
+ end
71
+
72
+ describe "division" do
73
+ it "must return 0 for non-zero scalars" do
74
+ (zero / 3).must_equal 0
75
+ (zero / 4.0).must_equal 0
76
+ end
77
+
78
+ it "must raise an exception when divided by 0" do
79
+ lambda { zero / 0 }.must_raise ZeroDivisionError
80
+ end
81
+
82
+ it "must raise an exception for Sizes" do
83
+ lambda { zero / Size[1,2] }.must_raise Geometry::OperationNotDefined
84
+ end
85
+
86
+ it "must raise an exception for Vectors" do
87
+ lambda { zero / Vector[1,2] }.must_raise Geometry::OperationNotDefined
88
+ end
89
+
90
+ end
91
+
92
+ end
93
+
94
+ describe "coercion" do
95
+ it "must coerce Arrays into Sizes" do
96
+ zero.coerce([3,4]).must_equal [Size[3,4], Size[0,0]]
97
+ end
98
+
99
+ it "must coerce Vectors into Vectors" do
100
+ zero.coerce(Vector[3,4]).must_equal [Vector[3,4], Vector[0,0]]
101
+ end
102
+
103
+ it "must coerce Size into Size" do
104
+ zero.coerce(Size[5,6]).must_equal [Size[5,6], Size[0,0]]
105
+ end
106
+ end
107
+
108
+ describe "comparison" do
109
+ let(:zero) { Geometry::PointZero.new }
110
+
111
+ it "must be equal to 0 and 0.0" do
112
+ zero.must_be :eql?, 0
113
+ zero.must_be :eql?, 0.0
114
+ end
115
+
116
+ it "must not be equal to a non-zero number" do
117
+ 1.wont_equal zero
118
+ 3.14.wont_equal zero
119
+ end
120
+
121
+ it "must be equal to an Array of zeros" do
122
+ zero.must_be :==, [0,0]
123
+ zero.must_be :eql?, [0,0]
124
+ [0,0].must_equal zero
125
+ end
126
+
127
+ it "must not be equal to a non-zero Array" do
128
+ zero.wont_equal [3,2]
129
+ [3,2].wont_equal zero
130
+ end
131
+
132
+ it "must be equal to a zero Size" do
133
+ zero.must_be :==, Size[0,0]
134
+ zero.must_be :eql?, Size[0,0]
135
+ Size[0,0].must_equal zero
136
+ end
137
+
138
+ it "must not be equal to a non-zero Size" do
139
+ zero.wont_equal Size[3,2]
140
+ Size[3,2].wont_equal zero
141
+ end
142
+
143
+ it "must be equal to an Vector of zeroes" do
144
+ zero.must_be :eql?, Vector[0,0]
145
+ Vector[0,0].must_equal zero
146
+ end
147
+
148
+ it "must not be equal to a non-zero Vector" do
149
+ zero.wont_equal Vector[3,2]
150
+ Vector[3,2].wont_equal zero
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,66 @@
1
+ require 'minitest/autorun'
2
+ require 'geometry/square'
3
+
4
+ describe Geometry::Square do
5
+ Square = Geometry::Square
6
+
7
+ describe "when constructed" do
8
+ it "must create a Square from two Points" do
9
+ square = Square.new from:[1,2], to:[3,4]
10
+ square.must_be_kind_of Geometry::Square
11
+ end
12
+
13
+ it "must reorder swapped points when constructed from two Points" do
14
+ square = Geometry::Square.new from:[3,4], to:[1,2]
15
+ square.must_be_kind_of Geometry::Square
16
+ square.instance_eval('@points[0]').must_equal Point[1,2]
17
+ square.instance_eval('@points[1]').must_equal Point[3,4]
18
+ end
19
+
20
+ it "must accept an origin Point and a size" do
21
+ square = Square.new origin:[1,2], size:5
22
+ square.must_be_kind_of Geometry::Square
23
+ square.origin.must_equal Point[1,2]
24
+ square.height.must_equal 5
25
+ square.width.must_equal 5
26
+ end
27
+ end
28
+
29
+ describe "properties" do
30
+ subject { Square.new from:[2,3], to:[3,4] }
31
+
32
+ it "must have an origin accessor" do
33
+ subject.origin.must_equal Point[2,3]
34
+ end
35
+ end
36
+ end
37
+
38
+ describe Geometry::CenteredSquare do
39
+ describe "when constructed" do
40
+ it "must create a CenteredSquare from a center point and a size" do
41
+ square = Geometry::CenteredSquare.new [2,3], 5
42
+ square.must_be_instance_of Geometry::CenteredSquare
43
+ square.must_be_kind_of Geometry::Square
44
+ end
45
+ end
46
+
47
+ describe "properties" do
48
+ let(:square) { Geometry::CenteredSquare.new [2,3], 4 }
49
+
50
+ it "must have a center property" do
51
+ square.center.must_equal Point[2,3]
52
+ end
53
+
54
+ it "must have a points property" do
55
+ square.points.must_equal [Point[0,1], Point[4,1], Point[4,5], Point[0,5]]
56
+ end
57
+
58
+ it "must have a height property" do
59
+ square.height.must_equal 4
60
+ end
61
+
62
+ it "must have a width property" do
63
+ square.width.must_equal 4
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,49 @@
1
+ require 'minitest/autorun'
2
+ require 'geometry/transformation/composition'
3
+
4
+ describe Geometry::Transformation::Composition do
5
+ Composition = Geometry::Transformation::Composition
6
+ Transformation = Geometry::Transformation
7
+
8
+ subject { Composition.new }
9
+
10
+ describe "when constructed" do
11
+ it "must accept multiple transformations" do
12
+ composition = Composition.new(Transformation.new, Transformation.new)
13
+ composition.size.must_equal 2
14
+ end
15
+
16
+ it "must reject anything that isn't a Transformation" do
17
+ -> { Composition.new :foo }.must_raise TypeError
18
+ end
19
+ end
20
+
21
+ describe "attributes" do
22
+ describe "has_rotation?" do
23
+ it "must properly be true" do
24
+ Composition.new(Transformation.new angle:90).has_rotation?.must_equal true
25
+ end
26
+
27
+ it "must properly be false" do
28
+ subject.has_rotation?.must_equal false
29
+ end
30
+ end
31
+ end
32
+
33
+ describe "when composing" do
34
+ it "must append a Transformation" do
35
+ (Composition.new(Transformation.new) + Transformation.new).size.must_equal 2
36
+ end
37
+
38
+ it "must merge with a Composition" do
39
+ (Composition.new(Transformation.new) + Composition.new(Transformation.new)).size.must_equal 2
40
+ end
41
+ end
42
+
43
+ describe "when transforming a Point" do
44
+ it "must handle composed translations" do
45
+ composition = Composition.new(Transformation.new origin:[1,2]) + Composition.new(Transformation.new [3,4])
46
+ composition.transform(Point[5,6]).must_equal Point[9, 12]
47
+ end
48
+ end
49
+ end