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,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