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.
- data/.gitignore +6 -0
- data/Gemfile +7 -0
- data/LICENSE +21 -0
- data/README.markdown +105 -0
- data/Rakefile +24 -0
- data/geometry-in-ruby.gemspec +23 -0
- data/lib/geometry/arc.rb +94 -0
- data/lib/geometry/circle.rb +122 -0
- data/lib/geometry/cluster_factory.rb +15 -0
- data/lib/geometry/edge.rb +140 -0
- data/lib/geometry/line.rb +154 -0
- data/lib/geometry/obround.rb +238 -0
- data/lib/geometry/path.rb +67 -0
- data/lib/geometry/point.rb +163 -0
- data/lib/geometry/point_zero.rb +107 -0
- data/lib/geometry/polygon.rb +368 -0
- data/lib/geometry/polyline.rb +318 -0
- data/lib/geometry/rectangle.rb +378 -0
- data/lib/geometry/regular_polygon.rb +136 -0
- data/lib/geometry/rotation.rb +190 -0
- data/lib/geometry/size.rb +75 -0
- data/lib/geometry/size_zero.rb +70 -0
- data/lib/geometry/square.rb +113 -0
- data/lib/geometry/text.rb +24 -0
- data/lib/geometry/transformation/composition.rb +39 -0
- data/lib/geometry/transformation.rb +171 -0
- data/lib/geometry/triangle.rb +78 -0
- data/lib/geometry/vector.rb +34 -0
- data/lib/geometry.rb +22 -0
- data/test/geometry/arc.rb +25 -0
- data/test/geometry/circle.rb +112 -0
- data/test/geometry/edge.rb +132 -0
- data/test/geometry/line.rb +132 -0
- data/test/geometry/obround.rb +25 -0
- data/test/geometry/path.rb +66 -0
- data/test/geometry/point.rb +258 -0
- data/test/geometry/point_zero.rb +177 -0
- data/test/geometry/polygon.rb +214 -0
- data/test/geometry/polyline.rb +266 -0
- data/test/geometry/rectangle.rb +154 -0
- data/test/geometry/regular_polygon.rb +120 -0
- data/test/geometry/rotation.rb +108 -0
- data/test/geometry/size.rb +97 -0
- data/test/geometry/size_zero.rb +153 -0
- data/test/geometry/square.rb +66 -0
- data/test/geometry/transformation/composition.rb +49 -0
- data/test/geometry/transformation.rb +169 -0
- data/test/geometry/triangle.rb +32 -0
- data/test/geometry/vector.rb +41 -0
- data/test/geometry.rb +5 -0
- metadata +117 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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
|
+
[](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
|
data/lib/geometry/arc.rb
ADDED
@@ -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
|