geometry-in-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,171 @@
1
+ require 'geometry/point'
2
+ require 'geometry/rotation'
3
+
4
+ require_relative 'transformation/composition'
5
+
6
+ module Geometry
7
+ =begin
8
+ {Transformation} represents a relationship between two coordinate frames.
9
+
10
+ To create a pure translation relationship:
11
+
12
+ translate = Geometry::Transformation.new(:translate => Point[4, 2])
13
+
14
+ To create a transformation with an origin and an X-axis aligned with the parent
15
+ coordinate system's Y-axis (the Y and Z axes will be chosen arbitrarily):
16
+
17
+ translate = Geometry::Transformation.new(:origin => [4, 2], :x => [0,1,0])
18
+
19
+ To create a transformation with an origin, an X-axis aligned with the parent
20
+ coordinate system's Y-axis, and a Y-axis aligned with the parent coordinate
21
+ system's X-axis:
22
+
23
+ translate = Geometry::Transformation.new(:origin => [4, 2], :x => [0,1,0], :y => [1,0,0])
24
+ =end
25
+ class Transformation
26
+ attr_reader :dimensions
27
+ attr_reader :rotation
28
+ attr_reader :scale
29
+ attr_reader :translation
30
+
31
+ attr_reader :x_axis, :y_axis, :z_axis
32
+
33
+ # @overload new(translate, rotate, scale)
34
+ # @param [Point] translate Linear displacement
35
+ # @param [Rotation] rotate Rotation
36
+ # @param [Vector] scale Scaling
37
+ # @overload new(options)
38
+ # @param [Hash] options
39
+ # @option options [Integer] :dimensions Dimensionality of the transformation
40
+ # @option options [Point] :origin Same as :translate
41
+ # @option options [Point] :move Same as :translate
42
+ # @option options [Point] :translate Linear displacement
43
+ # @option options [Angle] :angle Rotation angle (assumes planar geometry)
44
+ # @option options [Rotation] :rotate Rotation
45
+ # @option options [Vector] :scale Scaling
46
+ # @option options [Vector] :x X-axis
47
+ # @option options [Vector] :y Y-axis
48
+ # @option options [Vector] :z Z-axis
49
+ def initialize(*args)
50
+ options, args = args.partition {|a| a.is_a? Hash}
51
+ translate, rotate, scale = args
52
+ options = options.reduce({}, :merge)
53
+
54
+ @dimensions = options[:dimensions] || nil
55
+
56
+ rotation_options = options.select {|key, value| [:angle, :x, :y, :z].include? key }
57
+ @rotation = options[:rotate] || rotate || ((rotation_options.size > 0) ? Geometry::Rotation.new(rotation_options) : nil)
58
+ @scale = options[:scale] || scale
59
+
60
+ case options.count {|k,v| [:move, :origin, :translate].include? k }
61
+ when 0
62
+ @translation = translate
63
+ when 1
64
+ @translation = (options[:translate] ||= options.delete(:move) || options.delete(:origin))
65
+ else
66
+ raise ArgumentError, "Too many translation parameters in #{options}"
67
+ end
68
+
69
+ raise ArgumentError, "Bad translation" if @translation.is_a? Hash
70
+ @translation = Point[*@translation]
71
+ if @translation
72
+ @translation = nil if @translation.all? {|v| v == 0}
73
+ raise ArgumentError, ":translate must be a Point or a Vector" if @translation and not @translation.is_a?(Vector)
74
+ end
75
+
76
+ if @dimensions
77
+ biggest = [@translation, @scale].select {|a| a}.map {|a| a.size}.max
78
+
79
+ if biggest and (biggest != 0) and (((biggest != @dimensions)) or (@rotation and (@rotation.dimensions != biggest)))
80
+ raise ArgumentError, "Dimensionality mismatch"
81
+ end
82
+ end
83
+ end
84
+
85
+ def initialize_clone(source)
86
+ super
87
+ @rotation = @rotation.clone if @rotation
88
+ @scale = @scale.clone if @scale
89
+ @translation = @translation.clone if @translation
90
+ end
91
+
92
+ # !@attribute [r] has_rotation?
93
+ # @return [Bool] true if the transformation has any rotation components
94
+ def has_rotation?
95
+ !!@rotation
96
+ end
97
+
98
+ # Returns true if the {Transformation} is the identity transformation
99
+ def identity?
100
+ !(@rotation || @scale || @translation)
101
+ end
102
+
103
+ def eql?(other)
104
+ return false unless other
105
+ return true if !self.dimensions && !other.dimensions && !self.rotation && !other.rotation && !self.translation && !other.translation && !self.scale && !other.scale
106
+ return false if !(self.dimensions && other.dimensions) && !(self.rotation && other.rotation) && !(self.translation && other.translation) && !(self.scale && other.scale)
107
+
108
+ ((self.dimensions && other.dimensions && self.dimensions.eql?(other.dimensions)) || !(self.dimensions && other.dimensions)) &&
109
+ ((self.rotation && other.rotation && self.rotation.eql?(other.rotation)) || !(self.rotation && other.rotation)) &&
110
+ ((self.scale && other.scale && self.scale.eql?(other.scale)) || !(self.scale && other.rotation)) &&
111
+ ((self.translation && other.translation && self.translation.eql?(other.translation)) || !(self.scale && other.rotation))
112
+ end
113
+ alias :== :eql?
114
+
115
+ # Compose the current {Transformation} with another one
116
+ def +(other)
117
+ return self.clone unless other
118
+
119
+ case other
120
+ when Array, Vector
121
+ if @translation
122
+ Transformation.new(@translation+other, @rotation, @scale)
123
+ else
124
+ Transformation.new(other, @rotation, @scale)
125
+ end
126
+ when Composition
127
+ Composition.new(self, *other.transformations)
128
+ when Transformation
129
+ if @rotation || other.rotation
130
+ Composition.new(self, other)
131
+ else
132
+ translation = @translation ? (@translation + other.translation) : other.translation
133
+ Transformation.new(translation, @rotation, @scale)
134
+ end
135
+ end
136
+ end
137
+
138
+ def -(other)
139
+ return self.clone unless other
140
+
141
+ case other
142
+ when Array, Vector
143
+ if @translation
144
+ Transformation.new(@translation-other, @rotation, @scale)
145
+ else
146
+ Transformation.new(other.map {|e| -e}, @rotation, @scale)
147
+ end
148
+ when Transformation
149
+ if @rotation
150
+ rotation = other.rotation ? (@rotation - other.rotation) : @rotation
151
+ elsif other.rotation
152
+ rotation = -other.rotation
153
+ else
154
+ rotation = nil
155
+ end
156
+
157
+ translation = @translation ? (@translation - other.translation) : -other.translation
158
+
159
+ Transformation.new(translation, rotation, @scale)
160
+ end
161
+ end
162
+
163
+ # Transform and return a new {Point}. Rotation is applied before translation.
164
+ # @param [Point] point the {Point} to transform into the parent coordinate frame
165
+ # @return [Point] The transformed {Point}
166
+ def transform(point)
167
+ point = @rotation.transform(point) if @rotation
168
+ @translation ? (@translation + point) : point
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,78 @@
1
+ require_relative 'cluster_factory'
2
+ require_relative 'point'
3
+
4
+ module Geometry
5
+ =begin rdoc
6
+ A {http://en.wikipedia.org/wiki/Triangle Triangle} is not a square.
7
+
8
+ == Usage
9
+ A right {Triangle} with its right angle at the origin and sides of length 1
10
+ triangle = Geometry::Triangle.new [0,0], [1,0], [0,1]
11
+
12
+ An isoscoles right {Triangle} created with an origin and leg length
13
+ triangle = Geometry::Triangle.new [0,0], 1
14
+ =end
15
+
16
+ # @abstract Factory class that instantiates the appropriate subclasses
17
+ class Triangle
18
+
19
+ include ClusterFactory
20
+
21
+ # @overload new(point0, point1, point2)
22
+ # Contruct a {ScaleneTriangle} using three {Point}s
23
+ # @param [Point] point0 The first vertex of the {Triangle}
24
+ # @param [Point] point1 Another vertex of the {Triangle}
25
+ # @param [Point] point2 The final vertex of the {Triangle}
26
+ # @overload new(point, length)
27
+ # Construct a {RightTriangle} using a {Point} and the lengths of the sides
28
+ # @param [Point] point The location of the vertex at {Triangle}'s right angle
29
+ # @param [Number] base The length of the {Triangle}'s base leg
30
+ # @param [Number] height The length of the {Triangle}'s vertical leg
31
+ def self.new(*args)
32
+ if args.size == 3
33
+ ScaleneTriangle.new *args
34
+ elsif args.size == 2
35
+ RightTriangle.new args[0], args[1], args[1]
36
+ end
37
+ end
38
+ end
39
+
40
+ # {http://en.wikipedia.org/wiki/Equilateral_triangle Equilateral Triangle}
41
+ class EquilateralTriangle < Triangle
42
+ def self.new(*args)
43
+ original_new(*args)
44
+ end
45
+ end
46
+
47
+ class IsoscelesTriangle < Triangle
48
+ def self.new(*args)
49
+ original_new(*args)
50
+ end
51
+ end
52
+
53
+ # {http://en.wikipedia.org/wiki/Right_triangle Right Triangle}
54
+ class RightTriangle < Triangle
55
+ attr_reader :origin, :base, :height
56
+
57
+ # Construct a Right Triangle given a {Point} and the leg lengths
58
+ def initialize(origin, base, height)
59
+ @origin = Point[origin]
60
+ @base, @height = base, height
61
+ end
62
+
63
+ # An array of points corresponding to the vertices of the {Triangle} (clockwise)
64
+ # @return [Array<Point>] Vertices
65
+ def points
66
+ [@origin, @origin + Point[0,@height], @origin + Point[@base,0]]
67
+ end
68
+ end
69
+
70
+ class ScaleneTriangle < Triangle
71
+ attr_reader :points
72
+
73
+ # Construct a scalene {Triangle}
74
+ def initialize(point0, point1, point2)
75
+ @points = [point0, point1, point2].map {|p| Point[p] }
76
+ end
77
+ end
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
data/lib/geometry.rb ADDED
@@ -0,0 +1,22 @@
1
+ require_relative 'geometry/arc'
2
+ require_relative 'geometry/circle'
3
+ require_relative 'geometry/line'
4
+ require_relative 'geometry/obround'
5
+ require_relative 'geometry/path'
6
+ require_relative 'geometry/point'
7
+ require_relative 'geometry/point_zero'
8
+ require_relative 'geometry/polygon'
9
+ require_relative 'geometry/polyline'
10
+ require_relative 'geometry/rectangle'
11
+ require_relative 'geometry/regular_polygon'
12
+ require_relative 'geometry/rotation'
13
+ require_relative 'geometry/size'
14
+ require_relative 'geometry/size_zero'
15
+ require_relative 'geometry/square'
16
+ require_relative 'geometry/transformation'
17
+ require_relative 'geometry/triangle'
18
+ require_relative 'geometry/vector'
19
+ require_relative 'geometry/text'
20
+
21
+ module Geometry
22
+ end
@@ -0,0 +1,25 @@
1
+ require 'minitest/autorun'
2
+ require 'geometry/arc'
3
+
4
+ describe Geometry::Arc do
5
+ Arc = Geometry::Arc
6
+
7
+ describe "when constructed" do
8
+ it "must accept a center point, radius, start and end angles" do
9
+ arc = Geometry::Arc.new center:[1,2], radius:3, start:0, end:90
10
+ arc.must_be_kind_of Geometry::Arc
11
+ arc.center.must_equal Point[1,2]
12
+ arc.radius.must_equal 3
13
+ arc.start_angle.must_equal 0
14
+ arc.end_angle.must_equal 90
15
+ end
16
+
17
+ it "must create an Arc from center, start and end points" do
18
+ arc = Geometry::Arc.new center:[1,2], start:[3,4], end:[5,6]
19
+ arc.must_be_kind_of Geometry::Arc
20
+ arc.center.must_equal Point[1,2]
21
+ arc.first.must_equal Point[3,4]
22
+ arc.last.must_equal Point[5,6]
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,112 @@
1
+ require 'minitest/autorun'
2
+ require 'geometry/circle'
3
+
4
+ describe Geometry::Circle do
5
+ Circle = Geometry::Circle
6
+
7
+ describe "when constructed with center and radius arguments" do
8
+ let(:circle) { Circle.new [1,2], 3 }
9
+
10
+ it "must create a Circle" do
11
+ circle.must_be_instance_of(Circle)
12
+ end
13
+
14
+ it "must have a center point accessor" do
15
+ circle.center.must_equal Point[1,2]
16
+ end
17
+
18
+ it "must have a radius accessor" do
19
+ circle.radius.must_equal 3
20
+ end
21
+
22
+ it "must compare equal" do
23
+ circle.must_equal Circle.new([1,2], 3)
24
+ end
25
+ end
26
+
27
+ describe "when constructed with named center and radius arguments" do
28
+ let(:circle) { Circle.new :center => [1,2], :radius => 3 }
29
+
30
+ it "must create a Circle" do
31
+ circle.must_be_instance_of(Circle)
32
+ end
33
+
34
+ it "must have a center point accessor" do
35
+ circle.center.must_equal Point[1,2]
36
+ end
37
+
38
+ it "must have a radius accessor" do
39
+ circle.radius.must_equal 3
40
+ end
41
+
42
+ it "must compare equal" do
43
+ (circle == Circle.new(:center => [1,2], :radius => 3)).must_equal true
44
+ end
45
+ end
46
+
47
+ describe "when constructed with named center and diameter arguments" do
48
+ let(:circle) { Circle.new center:[1,2], diameter:4 }
49
+
50
+ it "must be a CenterDiameterCircle" do
51
+ circle.must_be_instance_of(Geometry::CenterDiameterCircle)
52
+ circle.must_be_kind_of(Circle)
53
+ end
54
+
55
+ it "must have a center" do
56
+ circle.center.must_equal Point[1,2]
57
+ end
58
+
59
+ it "must have a diameter" do
60
+ circle.diameter.must_equal 4
61
+ end
62
+
63
+ it "must calculate the correct radius" do
64
+ circle.radius.must_equal 2
65
+ end
66
+
67
+ it "must compare equal" do
68
+ circle.must_equal Circle.new([1,2], :diameter => 4)
69
+ end
70
+ end
71
+
72
+ describe "when constructed with a diameter and no center" do
73
+ let(:circle) { Circle.new :diameter => 4 }
74
+
75
+ it "must be a CenterDiameterCircle" do
76
+ circle.must_be_instance_of(Geometry::CenterDiameterCircle)
77
+ circle.must_be_kind_of(Circle)
78
+ end
79
+
80
+ it "must have a nil center" do
81
+ circle.center.must_be_kind_of Geometry::PointZero
82
+ end
83
+
84
+ it "must have a diameter" do
85
+ circle.diameter.must_equal 4
86
+ end
87
+
88
+ it "must calculate the correct radius" do
89
+ circle.radius.must_equal 2
90
+ end
91
+ end
92
+
93
+ describe "properties" do
94
+ subject { Circle.new center:[1,2], :diameter => 4 }
95
+
96
+ it "must have a bounds property that returns a Rectangle" do
97
+ subject.bounds.must_equal Rectangle.new([-1,0], [3,4])
98
+ end
99
+
100
+ it "must have a minmax property that returns the corners of the bounding rectangle" do
101
+ subject.minmax.must_equal [Point[-1,0], Point[3,4]]
102
+ end
103
+
104
+ it "must have a max property that returns the upper right corner of the bounding rectangle" do
105
+ subject.max.must_equal Point[3,4]
106
+ end
107
+
108
+ it "must have a min property that returns the lower left corner of the bounding rectangle" do
109
+ subject.min.must_equal Point[-1,0]
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,132 @@
1
+ require 'minitest/autorun'
2
+ require 'geometry/edge'
3
+
4
+ def Edge(*args)
5
+ Geometry::Edge.new(*args)
6
+ end
7
+
8
+ describe Geometry::Edge do
9
+ Edge = Geometry::Edge
10
+ subject { Geometry::Edge.new [0,0], [1,1] }
11
+
12
+ it "must create an Edge object" do
13
+ edge = Edge.new([0,0], [1,0])
14
+ assert_kind_of(Geometry::Edge, edge)
15
+ assert_equal(Geometry::Point[0,0], edge.first)
16
+ assert_equal(Geometry::Point[1,0], edge.last)
17
+ end
18
+
19
+ it "must handle equality" do
20
+ edge1 = Edge.new([1,0], [0,1])
21
+ edge2 = Edge.new([1,0], [0,1])
22
+ edge3 = Edge.new([1,1], [5,5])
23
+ assert_equal(edge1, edge2)
24
+ edge1.wont_equal edge3
25
+ end
26
+
27
+ it "must return the height of the edge" do
28
+ edge = Edge([0,0], [1,1])
29
+ assert_equal(1, edge.height)
30
+ end
31
+
32
+ it "must return the width of the edge" do
33
+ edge = Edge([0,0], [1,1])
34
+ assert_equal(1, edge.width)
35
+ end
36
+
37
+ it "must convert an Edge to a Vector" do
38
+ Edge.new([0,0], [1,0]).vector.must_equal Vector[1,0]
39
+ end
40
+
41
+ it "must return the normalized direction of a vector" do
42
+ Edge.new([0,0], [1,0]).direction.must_equal Vector[1,0]
43
+ end
44
+
45
+ it "must return true for parallel edges" do
46
+ Edge.new([0,0], [1,0]).parallel?(Edge.new([0,0], [1,0])).must_equal 1
47
+ Edge.new([0,0], [1,0]).parallel?(Edge.new([1,0], [2,0])).must_equal 1
48
+ Edge.new([0,0], [1,0]).parallel?(Edge.new([3,0], [4,0])).must_equal 1
49
+ Edge.new([0,0], [1,0]).parallel?(Edge.new([3,1], [4,1])).must_equal 1
50
+ end
51
+
52
+ it "must return false for non-parallel edges" do
53
+ Edge.new([0,0], [2,0]).parallel?(Edge.new([1,-1], [1,1])).must_equal false
54
+ end
55
+
56
+ it "must clone and reverse" do
57
+ reversed = subject.reverse
58
+ reversed.to_a.must_equal subject.to_a.reverse
59
+ reversed.wont_be_same_as subject
60
+ end
61
+
62
+ it "must reverse itself" do
63
+ original = subject.to_a
64
+ subject.reverse!
65
+ subject.to_a.must_equal original.reverse
66
+ end
67
+
68
+ describe "spaceship" do
69
+ it "ascending with a Point" do
70
+ edge = Edge.new [0,0], [1,1]
71
+ (edge <=> Point[0,0]).must_equal 0
72
+ (edge <=> Point[1,0]).must_equal -1
73
+ (edge <=> Point[0,1]).must_equal 1
74
+ (edge <=> Point[2,2]).must_equal nil
75
+ end
76
+
77
+ it "descending with a Point" do
78
+ edge = Edge.new [1,1], [0,0]
79
+ (edge <=> Point[0,0]).must_equal 0
80
+ (edge <=> Point[1,0]).must_equal 1
81
+ (edge <=> Point[0,1]).must_equal -1
82
+ (edge <=> Point[2,2]).must_equal nil
83
+ end
84
+ end
85
+
86
+ describe "when finding an intersection" do
87
+ it "must find the intersection of two end-intersecting Edges" do
88
+ intersection = Edge.new([0,0],[1,1]).intersection(Edge.new([0,1],[1,1]))
89
+ intersection.must_be_kind_of Geometry::Point
90
+ intersection.must_equal Geometry::Point[1,1]
91
+ end
92
+
93
+ it "must find the intersection of two collinear end-intersecting Edges" do
94
+ intersection = Edge.new([2,2], [0,2]).intersection(Edge.new([3,2], [2,2]))
95
+ intersection.must_be_kind_of Geometry::Point
96
+ intersection.must_equal Geometry::Point[2,2]
97
+
98
+ intersection = Edge.new([0,2], [2,2]).intersection(Edge.new([2,2], [3,2]))
99
+ intersection.must_be_kind_of Geometry::Point
100
+ intersection.must_equal Geometry::Point[2,2]
101
+ end
102
+
103
+ it "must find the itersection of two crossed Edges" do
104
+ edge1 = Edge.new [0.0, 0], [2.0, 2.0]
105
+ edge2 = Edge.new [2.0, 0], [0.0, 2.0]
106
+ intersection = edge1.intersection edge2
107
+ intersection.must_be_kind_of Geometry::Point
108
+ intersection.must_equal Geometry::Point[1,1]
109
+ end
110
+
111
+ it "must return nil for two edges that do not intersect" do
112
+ Edge.new([0,0],[1,0]).intersection(Edge.new([0,1],[1,1])).must_equal nil
113
+ end
114
+
115
+ it "must return true for two collinear and overlapping edges" do
116
+ Edge.new([0,0],[2,0]).intersection(Edge.new([1,0],[3,0])).must_equal true
117
+ end
118
+
119
+ it "must return false for collinear but non-overlapping edges" do
120
+ Edge.new([0,0],[2,0]).intersection(Edge.new([3,0],[4,0])).must_equal false
121
+ Edge.new([0,0],[0,2]).intersection(Edge.new([0,3],[0,4])).must_equal false
122
+ end
123
+
124
+ it "must return nil for two parallel but not collinear edges" do
125
+ Edge.new([0,0],[2,0]).intersection(Edge.new([1,1],[3,1])).must_equal nil
126
+ end
127
+
128
+ it "must return nil for two perpendicular but not interseting edges" do
129
+ Edge.new([0, 0], [2, 0]).intersection(Edge.new([3, 3], [3, 1])).must_equal nil
130
+ end
131
+ end
132
+ end