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
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ .yardoc
4
+ Gemfile.lock
5
+ pkg/*
6
+ doc
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'rake'
7
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2012, Brandon Fosdick <bfoz@bfoz.net>
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided
5
+ that the following conditions are met:
6
+
7
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the
8
+ following disclaimer.
9
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
10
+ the following disclaimer in the documentation and/or other materials provided with the distribution.
11
+
12
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
16
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
18
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
19
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
20
+ TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
21
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.markdown ADDED
@@ -0,0 +1,105 @@
1
+ Geometry for Ruby
2
+ =================
3
+
4
+ [![Build Status](https://travis-ci.org/bfoz/geometry.png)](https://travis-ci.org/bfoz/geometry)
5
+
6
+ Classes and methods for the handling of all of the basic geometry that you
7
+ learned in high school (and then forgot).
8
+
9
+ The classes in this libary are based on the Vector class provided by the Ruby
10
+ standard library. Geometric primitives are generally assumed to lie in 2D space,
11
+ but aren't necessarily restricted to it. Please let me know if you find cases
12
+ that don't work in higher dimensions and I'll do my best to fix them.
13
+
14
+ License
15
+ -------
16
+
17
+ Copyright 2012-2014 Brandon Fosdick <bfoz@bfoz.net> and released under the BSD license.
18
+
19
+ Primitives
20
+ ----------
21
+
22
+ - Point
23
+ - Size
24
+ - Line
25
+ - Edge
26
+ - [Arc](http://en.wikipedia.org/wiki/Arc_(geometry)), Circle
27
+ - Rectangle, Square
28
+ - Path, [Polyline](http://en.wikipedia.org/wiki/Polyline), [Polygon](http://en.wikipedia.org/wiki/Polygon), [RegularPolygon](http://en.wikipedia.org/wiki/Regular_polygon)
29
+ - Transformation
30
+ - [Triangle](http://en.wikipedia.org/wiki/Triangle)
31
+ - [Obround](http://en.wiktionary.org/wiki/obround)
32
+
33
+ Examples
34
+ --------
35
+
36
+ ### Point
37
+ ```ruby
38
+ point = Geometry::Point[3,4] # 2D Point at coordinate 3, 4
39
+
40
+ # Copy constructors
41
+ point2 = Geometry::Point[point]
42
+ point2 = Geometry::Point[Vector[5,6]]
43
+
44
+ # Accessors
45
+ point.x
46
+ point.y
47
+ point[2] # Same as point.z
48
+
49
+ # Zero
50
+ PointZero.new # A Point full of zeros of unspecified length
51
+ Point.zero # Another way to do the same thing
52
+ Point.zero(3) # => Point[0,0,0]
53
+ ```
54
+
55
+ ### Line
56
+ ```ruby
57
+ # Two-point constructors
58
+ line = Geometry::Line[[0,0], [10,10]]
59
+ line = Geometry::Line[Geometry::Point[0,0], Geometry::Point[10,10]]
60
+ line = Geometry::Line[Vector[0,0], Vector[10,10]]
61
+
62
+ # Slope-intercept constructors
63
+ Geometry::Line[Rational(3,4), 5] # Slope = 3/4, Intercept = 5
64
+ Geometry::Line[0.75, 5]
65
+
66
+ # Point-slope constructors
67
+ Geometry::Line(Geometry::Point[0,0], 0.75)
68
+ Geometry::Line(Vector[0,0], Rational(3,4))
69
+
70
+ # Special constructors (2D only)
71
+ Geometry::Line.horizontal(y=0)
72
+ Geometry::Line.vertical(x=0)
73
+ ```
74
+
75
+ ### Rectangle
76
+ ```ruby
77
+ # A Rectangle made from two corner points
78
+ Geometry::Rectangle.new [1,2], [2,3]
79
+ Geometry::Rectangle.new from:[1,2], to:[2,3]
80
+
81
+ Geometry::Rectangle.new center:[1,2], size:[1,1] # Using a center point and a size
82
+ Geometry::Rectangle.new origin:[1,2], size:[1,1] # Using an origin point and a size
83
+
84
+ # A Rectangle with its origin at [0, 0] and a size of [10, 20]
85
+ Geometry::Rectangle.new size: [10, 20]
86
+ Geometry::Rectangle.new size: Size[10, 20]
87
+ Geometry::Rectangle.new width: 10, height: 20
88
+ ```
89
+
90
+ ### Circle
91
+ ```ruby
92
+ # A circle at Point[1,2] with a radius of 3
93
+ circle = Geometry::Circle.new center:[1,2], radius:3
94
+ ```
95
+
96
+ ### Polygon
97
+ ```ruby
98
+ # A polygon that looks a lot like a square
99
+ polygon = Geometry::Polygon.new [0,0], [1,0], [1,1], [0,1]
100
+ ```
101
+ ### Regular Polygon
102
+ ```ruby
103
+ # Everyone loves a good hexagon
104
+ hexagon = Geometry::RegularPolygon.new 6, :diameter => 3
105
+ ```
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ task :default => :test
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs.push "lib"
8
+ t.test_files = FileList['test/**/*.rb']
9
+ t.verbose = true
10
+ end
11
+
12
+ task :fixdates do
13
+ branch = `git branch --no-color -r --merged`.strip
14
+ `git fix-dates #{branch}..HEAD`
15
+ end
16
+
17
+ task :fixdates_f do
18
+ branch = `git branch --no-color -r --merged`.strip
19
+ `git fix-dates -f #{branch}..HEAD`
20
+ end
21
+
22
+ task :trim_whitespace do
23
+ system(%Q[git status --short | awk '{if ($1 != "D" && $1 != "R") print $2}' | grep -e '.*\.rb$' | xargs sed -i '' -e 's/[ \t]*$//g;'])
24
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "geometry-in-ruby"
6
+ s.version = '0.0.1'
7
+ s.authors = ["Brandon Fosdick", "Meseker Yohannes"]
8
+ s.email = ["meseker.yohannes@gmail.com"]
9
+ s.homepage = "http://github.com/meseker/geometry"
10
+ s.summary = %q{Geometric primitives and algoritms}
11
+ s.description = %q{Geometric primitives and algorithms for Ruby}
12
+
13
+ s.rubyforge_project = "aurora_geometry"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ # specify any dependencies here; for example:
21
+ # s.add_development_dependency "rspec"
22
+ # s.add_runtime_dependency "rest-client"
23
+ end
@@ -0,0 +1,94 @@
1
+ require_relative 'point'
2
+
3
+ require_relative 'cluster_factory'
4
+ require_relative 'point'
5
+
6
+ module Geometry
7
+
8
+ =begin rdoc
9
+ {http://en.wikipedia.org/wiki/Arc_(geometry) Arcs} are Circles that don't quite go all the way around
10
+
11
+ == Usage
12
+ An {Arc} with its center at [1,1] and a radius of 2 that starts at the X-axis and goes to the Y-axis (counter-clockwise)
13
+ arc = Geometry::Arc.new center:[1,1], radius:2, start:0, end:90
14
+ =end
15
+
16
+ class Arc
17
+ include ClusterFactory
18
+
19
+ attr_reader :center
20
+ attr_reader :radius
21
+ attr_reader :start_angle, :end_angle
22
+
23
+ # @overload new(center, start, end)
24
+ # Create a new {Arc} given center, start and end {Point}s
25
+ # @option options [Point] :center (PointZero) The {Point} at the center
26
+ # @option options [Point] :start The {Arc} starts at the start {Point}
27
+ # @option options [Point] :end The {Point} where it all ends
28
+ # @return [Arc]
29
+ # @overload new(center, radius, start, end)
30
+ # Create a new {Arc} given a center {Point}, a radius and start and end angles
31
+ # @option options [Point] :center (PointZero) The {Point} at the center of it all
32
+ # @option options [Numeric] :radius Radius
33
+ # @option options [Numeric] :start Starting angle
34
+ # @option options [Numeric] :end Ending angle
35
+ # @return [ThreePointArc]
36
+ def self.new(options={})
37
+ center = options.delete(:center) || PointZero.new
38
+
39
+ if options.has_key?(:radius)
40
+ original_new(center, options[:radius], options[:start], options[:end])
41
+ else
42
+ ThreePointArc.new(center, options[:start], options[:end])
43
+ end
44
+ end
45
+
46
+ # Construct a new {Arc}
47
+ # @overload initialize(center, radius, start_angle, end_angle)
48
+ # @param [Point] center The {Point} at the center of it all
49
+ # @param [Numeric] radius Radius
50
+ # @param [Numeric] start_angle Starting angle
51
+ # @param [Numeric] end_angle Ending angle
52
+ def initialize(center, radius, start_angle, end_angle)
53
+ @center = Point[center]
54
+ @radius = radius
55
+ @start_angle = start_angle
56
+ @end_angle = end_angle
57
+ end
58
+
59
+ # @return [Point] The starting point of the {Arc}
60
+ def first
61
+ @center + @radius * Vector[Math.cos(@start_angle), Math.sin(@start_angle)]
62
+ end
63
+
64
+ # @return [Point] The end point of the {Arc}
65
+ def last
66
+ @center + @radius * Vector[Math.cos(@end_angle), Math.sin(@end_angle)]
67
+ end
68
+ end
69
+
70
+ class ThreePointArc < Arc
71
+ attr_reader :center
72
+ attr_reader :start, :end
73
+
74
+ # Contruct a new {Arc} given center, start and end {Point}s
75
+ # Always assumes that the {Arc} is counter-clockwise. Reverse the order
76
+ # of the start and end points to get an {Arc} that goes around the other way.
77
+ # @overload initialize(center_point, start_point, end_point)
78
+ # @param [Point] center_point The {Point} at the center
79
+ # @param [Point] start_point The {Arc} starts at the start {Point}
80
+ # @param [Point] end_point The {Point} where it all ends
81
+ def initialize(center_point, start_point, end_point)
82
+ @center, @start, @end = [center_point, start_point, end_point].map {|p| Point[p]}
83
+ raise ArgumentError unless [@center, @start, @end].all? {|p| p.is_a?(Point)}
84
+ end
85
+
86
+ # The starting point of the {Arc}
87
+ # @return [Point]
88
+ alias :first :start
89
+
90
+ # The end point of the {Arc}
91
+ # @return [Point]
92
+ alias :last :end
93
+ end
94
+ end
@@ -0,0 +1,122 @@
1
+ require_relative 'cluster_factory'
2
+ require_relative 'point'
3
+
4
+ module Geometry
5
+
6
+ =begin rdoc
7
+ Circles come in all shapes and sizes, but they're usually round.
8
+
9
+ == Usage
10
+ circle = Geometry::Circle.new [1,2], 3
11
+ circle = Geometry::Circle.new center:[1,2], radius:3
12
+ circle = Geometry::Circle.new center:[1,2], diameter:6
13
+ circle = Geometry::Circle.new diameter:6
14
+ =end
15
+
16
+ class Circle
17
+ include ClusterFactory
18
+
19
+ # @return [Point] The {Circle}'s center point
20
+ attr_reader :center
21
+
22
+ # @return [Number] The {Circle}'s radius
23
+ attr_reader :radius
24
+
25
+ # @overload new(center, radius)
26
+ # Construct a {Circle} using a centerpoint and radius
27
+ # @param [Point] center The center point of the {Circle}
28
+ # @param [Number] radius The radius of the {Circle}
29
+ # @overload new(center, radius)
30
+ # Construct a circle using named center and radius parameters
31
+ # @option options [Point] :center (PointZero)
32
+ # @option options [Number] :radius
33
+ # @overload new(center, diameter)
34
+ # Construct a circle using named center and diameter parameters
35
+ # @option options [Point] :center (PointZero)
36
+ # @option options [Number] :diameter
37
+ def self.new(*args, &block)
38
+ options, args = args.partition {|a| a.is_a? Hash}
39
+ options = options.reduce({}, :merge)
40
+ center, radius = args[0..1]
41
+
42
+ center ||= (options[:center] || PointZero.new)
43
+ radius ||= options[:radius]
44
+
45
+ if radius
46
+ self.allocate.tap {|circle| circle.send :initialize, center, radius, &block }
47
+ elsif options.has_key?(:diameter)
48
+ CenterDiameterCircle.new center, options[:diameter], &block
49
+ else
50
+ raise ArgumentError, "Circle.new requires a radius or a diameter"
51
+ end
52
+ end
53
+
54
+ # Construct a new {Circle} from a centerpoint and radius
55
+ # @param [Point] center The center point of the {Circle}
56
+ # @param [Number] radius The radius of the {Circle}
57
+ # @return [Circle] A new {Circle} object
58
+ def initialize(center, radius)
59
+ @center = Point[center]
60
+ @radius = radius
61
+ end
62
+
63
+ def eql?(other)
64
+ (self.center == other.center) && (self.radius == other.radius)
65
+ end
66
+ alias :== :eql?
67
+
68
+ # @!group Accessors
69
+ # @return [Rectangle] The smallest axis-aligned {Rectangle} that bounds the receiver
70
+ def bounds
71
+ return Rectangle.new(self.min, self.max)
72
+ end
73
+
74
+ # @!attribute [r] diameter
75
+ # @return [Numeric] The diameter of the {Circle}
76
+ def diameter
77
+ @radius*2
78
+ end
79
+
80
+ # @return [Point] The upper right corner of the bounding {Rectangle}
81
+ def max
82
+ @center+radius
83
+ end
84
+
85
+ # @return [Point] The lower left corner of the bounding {Rectangle}
86
+ def min
87
+ @center-radius
88
+ end
89
+
90
+ # @return [Array<Point>] The lower left and upper right corners of the bounding {Rectangle}
91
+ def minmax
92
+ [self.min, self.max]
93
+ end
94
+ # @!endgroup
95
+ end
96
+
97
+ class CenterDiameterCircle < Circle
98
+ # @return [Number] The {Circle}'s diameter
99
+ attr_reader :diameter
100
+
101
+ # Construct a new {Circle} from a centerpoint and a diameter
102
+ # @param [Point] center The center point of the {Circle}
103
+ # @param [Number] diameter The radius of the {Circle}
104
+ # @return [Circle] A new {Circle} object
105
+ def initialize(center, diameter)
106
+ @center = Point[center]
107
+ @diameter = diameter
108
+ end
109
+
110
+ def eql?(other)
111
+ (self.center == other.center) && (self.diameter == other.diameter)
112
+ end
113
+ alias :== :eql?
114
+
115
+ # @!group Accessors
116
+ # @return [Number] The {Circle}'s radius
117
+ def radius
118
+ @diameter/2
119
+ end
120
+ # @!endgroup
121
+ end
122
+ end
@@ -0,0 +1,15 @@
1
+ # Include this module in the base class of a class cluster to handle swizzling
2
+ # of ::new
3
+ module ClusterFactory
4
+ def self.included(parent)
5
+ class << parent
6
+ alias :original_new :new
7
+
8
+ def inherited(subclass)
9
+ class << subclass
10
+ alias :new :original_new
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,140 @@
1
+ require 'mathn'
2
+
3
+ require_relative 'point'
4
+
5
+ module Geometry
6
+
7
+ =begin rdoc
8
+ An edge. It's a line segment between 2 points. Generally part of a {Polygon}.
9
+
10
+ == Usage
11
+ edge = Geometry::Edge([1,1], [2,2])
12
+
13
+ =end
14
+
15
+ class Edge
16
+ attr_reader :first, :last
17
+
18
+ # Construct a new {Edge} object from any two things that can be converted
19
+ # to a {Point}.
20
+ def initialize(point0, point1)
21
+ @first, @last = [Point[point0], Point[point1]]
22
+ end
23
+
24
+ # Two Edges are equal if both have equal {Point}s in the same order
25
+ def ==(other)
26
+ (@first == other.first) && (@last == other.last)
27
+ end
28
+
29
+ # @param [Point] point A {Point} to spaceship with
30
+ # @return [Boolean] Returns 1 if the {Point} is strictly to the left of the receiver, -1 to the right, and 0 if the point is on the receiver
31
+ def <=>(point)
32
+ case point
33
+ when Point
34
+ k = (@last.x - @first.x) * (point.y - @first.y) - (point.x - @first.x) * (@last.y - @first.y)
35
+ if 0 == k
36
+ (((@first.x <=> point.x) + (@last.x <=> point.x)).abs <= 1) && (((@first.y <=> point.y) + (@last.y <=> point.y)).abs <= 1) ? 0 : nil
37
+ else
38
+ k <=> 0
39
+ end
40
+ else
41
+ raise ArgumentError, "Can't spaceship with #{point.class}"
42
+ end
43
+ end
44
+
45
+ # Return a new {Edge} with swapped endpoints
46
+ def reverse
47
+ Edge.new(@last, @first)
48
+ end
49
+
50
+ # In-place swap the endpoints
51
+ def reverse!
52
+ @first, @last = @last, @first
53
+ self
54
+ end
55
+
56
+ # Return the {Edge}'s length along the Y axis
57
+ def height
58
+ (@first.y - @last.y).abs
59
+ end
60
+
61
+ # Return the {Edge}'s length along the X axis
62
+ def width
63
+ (@first.x - @last.x).abs
64
+ end
65
+
66
+ def inspect
67
+ 'Edge(' + @first.inspect + ', ' + @last.inspect + ')'
68
+ end
69
+ alias :to_s :inspect
70
+
71
+ # @return [Bool] Returns true if the passed {Edge} is parallel to the receiver
72
+ def parallel?(edge)
73
+ v1, v2 = self.direction, edge.direction
74
+ winding = v1[0]*v2[1] - v1[1]*v2[0]
75
+ if 0 == winding # collinear?
76
+ if v1 == v2
77
+ 1 # same direction
78
+ else
79
+ -1 # opposite direction
80
+ end
81
+ else
82
+ false
83
+ end
84
+ end
85
+
86
+ # @param [Edge] other The other {Edge} to check
87
+ # @return [Bool] Returns true if the receiver and the passed {Edge} share an endpoint
88
+ def connected?(other)
89
+ (@first == other.last) || (@last == other.first) || (@first == other.first) || (@last == other.last)
90
+ end
91
+
92
+ # @return [Vector] A unit {Vector} pointing from first to last
93
+ def direction
94
+ self.vector.normalize
95
+ end
96
+
97
+ # Find the intersection of two {Edge}s (http://bloggingmath.wordpress.com/2009/05/29/line-segment-intersection/)
98
+ # @param [Edge] other The other {Edge}
99
+ # @return [Point] The intersection of the two {Edge}s, nil if they don't intersect, true if they're collinear and overlapping, and false if they're collinear and non-overlapping
100
+ def intersection(other)
101
+ return self.first if (self.first == other.first) or (self.first == other.last)
102
+ return self.last if (self.last == other.first) or (self.last == other.last)
103
+
104
+ p0, p1 = self.first, self.last
105
+ p2, p3 = other.first, other.last
106
+ v1, v2 = self.vector, other.vector
107
+
108
+ denominator = v1[0] * v2[1] - v2[0] * v1[1] # v1 x v2
109
+ p = p0 - p2
110
+ if denominator == 0 # collinear, so check for overlap
111
+ if 0 == (-v1[1] * p.x + v1[0] * p.y) # collinear?
112
+ # The edges are collinear, but do they overlap?
113
+ # Project them onto the x and y axes to find out
114
+ left1, right1 = [self.first[0], self.last[0]].sort
115
+ bottom1, top1 = [self.first[1], self.last[1]].sort
116
+ left2, right2 = [other.first[0], other.last[0]].sort
117
+ bottom2, top2 = [other.first[1], other.last[1]].sort
118
+
119
+ !((left2 > right1) || (right2 < left1) || (top2 < bottom1) || (bottom2 > top1))
120
+ else
121
+ nil
122
+ end
123
+ else
124
+ s = (-v1[1] * p.x + v1[0] * p.y) / denominator # v1 x (p0 - p2) / denominator
125
+ t = ( v2[0] * p.y - v2[1] * p.x) / denominator # v2 x (p0 - p2) / denominator
126
+
127
+ p0 + v1 * t if ((0..1) === s) && ((0..1) === t)
128
+ end
129
+ end
130
+
131
+ # @return [Vector] A {Vector} pointing from first to last
132
+ def vector
133
+ Vector[*((last-first).to_a)]
134
+ end
135
+
136
+ def to_a
137
+ [@first, @last]
138
+ end
139
+ end
140
+ end