euclidean 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9a73c24a00cc5b70d91928313b88a92fd4b7b911
4
+ data.tar.gz: 90fc4f8ec1974af5a8e7663e89d99564791ad673
5
+ SHA512:
6
+ metadata.gz: 5ec8e7d0cc9e81d4d974cd7b3d0a0bef4f636d2e0c17981937ab8fbeb48c8814763af0d61ec426536ffe2e0ffd300b497b12bc437d2aeca68c3f9f06e2db83c8
7
+ data.tar.gz: b31be403bbaaa9510a7efe67d3a40427fd7440488445955cc32ec938c18b5c3315f4e2ce3c809c94a4b19ad5d9d26332d4026cd8eb6c9a1c98ea4ed199091d81
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in euclidean.gemspec
4
+ gemspec
5
+
6
+ group :development, :test do
7
+ gem 'guard-rspec', require: false
8
+ end
data/Guardfile ADDED
@@ -0,0 +1,8 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', cmd: 'bundle exec rspec', all_on_start: false, all_after_pass: false do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Jeremy Ward
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # Euclidean
2
+
3
+ Euclidean adds classes and methods to handle basic geometry in Ruby.
4
+
5
+ The classes in this library are based on the Vector class provided in the Ruby Standard Library.
6
+ Geometric primitives are generally assumed to lie in 2-Dimensional space but are not necessarily
7
+ restricted to it.
8
+
9
+ Like Euclid himself, the work in this gem builds upon the work of others. Most notably:
10
+ * [geometry](https://github.com/bfoz/geometry)
11
+ * [ruby-geometry](https://github.com/DanielVartanov/ruby-geometry)
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ gem 'euclidean'
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install euclidean
26
+
27
+ ## Defined Geometric Primitives
28
+ * Circle
29
+ * Edge
30
+ * Point
31
+ * Rectangle
32
+ * Size
33
+ * Vector
34
+
35
+ ## Usage
36
+
37
+ ### Point
38
+ ```ruby
39
+ point = Euclidean::Point[3,4] # 2D Point at coordinate 3, 4
40
+
41
+ # Copy constructors
42
+ point2 = Euclidean::Point[point]
43
+ point2 = Euclidean::Point[Vector[5,6]]
44
+
45
+ # Accessors
46
+ point.x
47
+ point.y
48
+ point[2] # Same as point.z
49
+
50
+ # Zero
51
+ Euclidean::PointZero.new # A Point full of zeros of unspecified length
52
+ Euclidean::Point.zero # Another way to do the same thing
53
+ Euclidean::Point.zero(3) # => Point[0,0,0]
54
+ ```
55
+
56
+ ### Circle
57
+ ```ruby
58
+ # A circle at Point[1,2] with a radius of 3
59
+ circle = Euclidean::Circle.new [1,2], 3
60
+ circle = Euclidean::Circle.new center:[1,2], radius: 3
61
+
62
+ # A circle at Point[1,2] with a diameter of 3
63
+ circle = Euclidean::Circle.new center:[1,2], diameter: 3
64
+
65
+ # A circle at Point[0,0] with a diameter of 4
66
+ circle = Euclidean::Circle.new diameter: 4
67
+ ```
68
+
69
+ ### Rectangle
70
+ ```ruby
71
+ # A Rectangle made from two corner points
72
+ rectangle = Euclidean::Rectangle.new [1,2], [2,3]
73
+ rectangle = Euclidean::Rectangle.new from:[1,2], to:[2,3]
74
+
75
+ rectangle = Euclidean::Rectangle.new center:[1,2], size:[1,1] # Using a center point and a size
76
+ rectangle = Euclidean::Rectangle.new origin:[1,2], size:[1,1] # Using an origin point and a size
77
+
78
+ # A Rectangle with its origin at [0, 0] and a size of [10, 20]
79
+ rectangle = Euclidean::Rectangle.new size: [10, 20]
80
+ rectangle = Euclidean::Rectangle.new size: Euclidean::Size[10, 20]
81
+ rectangle = Euclidean::Rectangle.new width: 10, height: 20
82
+ ```
83
+
84
+ ## Contributing
85
+
86
+ 1. Fork it ( http://github.com/<my-github-username>/euclidean/fork )
87
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
88
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
89
+ 4. Push to the branch (`git push origin my-new-feature`)
90
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/euclidean.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'euclidean/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "euclidean"
8
+ spec.version = Euclidean::VERSION
9
+ spec.authors = ["Jeremy Ward"]
10
+ spec.email = ["jeremy.ward@digital-ocd.com"]
11
+ spec.summary = %q{Basic Geometric primitives and algoritms for Ruby}
12
+ spec.description = %q{Basic Geometric primitives and algoritms for Ruby}
13
+ spec.homepage = "https://github.com/jrmyward/euclidean"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency 'rspec'
24
+ end
data/lib/euclidean.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'euclidean/cluster_factory'
2
+ require 'euclidean/circle'
3
+ require 'euclidean/point'
4
+ require 'euclidean/point_zero'
5
+ require 'euclidean/rectangle'
6
+ require 'euclidean/size'
7
+ require "euclidean/vector"
8
+ require "euclidean/version"
9
+
10
+ module Euclidean
11
+
12
+ end
@@ -0,0 +1,118 @@
1
+ module Euclidean
2
+ =begin rdoc
3
+ Circles come in all shapes and sizes, but they're usually round.
4
+
5
+ == Usage
6
+ circle = Euclidean::Circle.new [1,2], 3
7
+ circle = Euclidean::Circle.new center:[1,2], radius:3
8
+ circle = Euclidean::Circle.new center:[1,2], diameter:6
9
+ circle = Euclidean::Circle.new diameter:6
10
+ =end
11
+
12
+ class Circle
13
+ include ClusterFactory
14
+
15
+ # @return [Point] The {Circle}'s center point
16
+ attr_reader :center
17
+ # @return [Number] The {Circle}'s radius
18
+ attr_reader :radius
19
+
20
+ # @overload new(center, radius)
21
+ # Construct a {Circle} using a centerpoint and radius
22
+ # @param [Point] center The center point of the {Circle}
23
+ # @param [Number] radius The radius of the {Circle}
24
+ # @overload new(center, radius)
25
+ # Construct a circle using named center and radius parameters
26
+ # @option options [Point] :center (PointZero)
27
+ # @option options [Number] :radius
28
+ # @overload new(center, diameter)
29
+ # Construct a circle using named center and diameter parameters
30
+ # @option options [Point] :center (PointZero)
31
+ # @option options [Number] :diameter
32
+ def self.new(*args, &block)
33
+ options, args = args.partition {|a| a.is_a? Hash}
34
+ options = options.reduce({}, :merge)
35
+ center, radius = args[0..1]
36
+
37
+ center ||= (options[:center] || PointZero.new)
38
+ radius ||= options[:radius]
39
+
40
+ if radius
41
+ self.allocate.tap {|circle| circle.send :initialize, center, radius, &block }
42
+ elsif options.has_key?(:diameter)
43
+ CenterDiameterCircle.new center, options[:diameter], &block
44
+ else
45
+ raise ArgumentError, "Circle.new requires a radius or a diameter"
46
+ end
47
+ end
48
+
49
+ # Construct a new {Circle} from a centerpoint and radius
50
+ # @param [Point] center The center point of the {Circle}
51
+ # @param [Number] radius The radius of the {Circle}
52
+ # @return [Circle] A new {Circle} object
53
+ def initialize(center, radius)
54
+ @center = Point[center]
55
+ @radius = radius
56
+ end
57
+
58
+ def eql?(other)
59
+ (self.center == other.center) && (self.radius == other.radius)
60
+ end
61
+ alias :== :eql?
62
+
63
+ # @!group Accessors
64
+ # @return [Rectangle] The smallest axis-aligned {Rectangle} that bounds the receiver
65
+ def bounds
66
+ return Euclidean::Rectangle.new(self.min, self.max)
67
+ end
68
+
69
+ # @!attribute [r] diameter
70
+ # @return [Numeric] The diameter of the {Circle}
71
+ def diameter
72
+ @radius*2
73
+ end
74
+
75
+ # @return [Point] The upper right corner of the bounding {Rectangle}
76
+ def max
77
+ @center+radius
78
+ end
79
+
80
+ # @return [Point] The lower left corner of the bounding {Rectangle}
81
+ def min
82
+ @center-radius
83
+ end
84
+
85
+ # @return [Array<Point>] The lower left and upper right corners of the bounding {Rectangle}
86
+ def minmax
87
+ [self.min, self.max]
88
+ end
89
+ # @!endgroup
90
+ end
91
+
92
+ class CenterDiameterCircle < Circle
93
+ # @return [Number] The {Circle}'s diameter
94
+ attr_reader :diameter
95
+
96
+ # Construct a new {Circle} from a centerpoint and a diameter
97
+ # @param [Point] center The center point of the {Circle}
98
+ # @param [Number] diameter The radius of the {Circle}
99
+ # @return [Circle] A new {Circle} object
100
+ def initialize(center, diameter)
101
+ @center = Point[center]
102
+ @diameter = diameter
103
+ end
104
+
105
+ def eql?(other)
106
+ (self.center == other.center) && (self.diameter == other.diameter)
107
+ end
108
+ alias :== :eql?
109
+
110
+ # @!group Accessors
111
+ # @return [Number] The {Circle}'s radius
112
+ def radius
113
+ @diameter/2
114
+ end
115
+ # @!endgroup
116
+ end
117
+
118
+ 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,143 @@
1
+ require 'mathn'
2
+ require_relative 'point'
3
+
4
+ module Euclidean
5
+ =begin rdoc
6
+ An edge. It's a line segment between 2 points. Generally part of a {Polygon}.
7
+
8
+ == Usage
9
+ edge = Geometry::Edge([1,1], [2,2])
10
+
11
+ =end
12
+ class Edge
13
+ attr_reader :first, :last
14
+
15
+ # Construct a new {Edge} object from any two things that can be converted
16
+ # to a {Point}.
17
+ def initialize(point0, point1)
18
+ @first, @last = [Point[point0], Point[point1]]
19
+ end
20
+
21
+ # Two Edges are equal if both have equal {Point}s in the same order
22
+ def ==(other)
23
+ (@first == other.first) && (@last == other.last)
24
+ end
25
+
26
+ # @param [Point] point A {Point} to spaceship with
27
+ # @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
28
+ def <=>(point)
29
+ case point
30
+ when Point
31
+ k = (@last.x - @first.x) * (point.y - @first.y) - (point.x - @first.x) * (@last.y - @first.y)
32
+ if 0 == k
33
+ (((@first.x <=> point.x) + (@last.x <=> point.x)).abs <= 1) && (((@first.y <=> point.y) + (@last.y <=> point.y)).abs <= 1) ? 0 : nil
34
+ else
35
+ k <=> 0
36
+ end
37
+ else
38
+ raise ArgumentError, "Can't spaceship with #{point.class}"
39
+ end
40
+ end
41
+
42
+ # @param [Edge] other The other {Edge} to check
43
+ # @return [Bool] Returns true if the receiver and the passed {Edge} share an endpoint
44
+ def connected?(other)
45
+ (@first == other.last) || (@last == other.first) || (@first == other.first) || (@last == other.last)
46
+ end
47
+
48
+ # @return [Vector] A unit {Vector} pointing from first to last
49
+ def direction
50
+ self.vector.normalize
51
+ end
52
+
53
+ # Return the {Edge}'s length along the Y axis
54
+ def height
55
+ (@first.y - @last.y).abs
56
+ end
57
+
58
+ def inspect
59
+ 'Edge(' + @first.inspect + ', ' + @last.inspect + ')'
60
+ end
61
+ alias :to_s :inspect
62
+
63
+ # Find the intersection of two {Edge}s (http://bloggingmath.wordpress.com/2009/05/29/line-segment-intersection/)
64
+ # @param [Edge] other The other {Edge}
65
+ # @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
66
+ def intersection(other)
67
+ return self.first if (self.first == other.first) or (self.first == other.last)
68
+ return self.last if (self.last == other.first) or (self.last == other.last)
69
+
70
+ p0, p1 = self.first, self.last
71
+ p2, p3 = other.first, other.last
72
+ v1, v2 = self.vector, other.vector
73
+
74
+ denominator = v1[0] * v2[1] - v2[0] * v1[1] # v1 x v2
75
+ p = p0 - p2
76
+
77
+ if denominator == 0 # collinear, so check for overlap
78
+ if 0 == (-v1[1] * p.x + v1[0] * p.y) # collinear?
79
+ # The edges are collinear, but do they overlap?
80
+ # Project them onto the x and y axes to find out
81
+ left1, right1 = [self.first[0], self.last[0]].sort
82
+ bottom1, top1 = [self.first[1], self.last[1]].sort
83
+ left2, right2 = [other.first[0], other.last[0]].sort
84
+ bottom2, top2 = [other.first[1], other.last[1]].sort
85
+
86
+ !((left2 > right1) || (right2 < left1) || (top2 < bottom1) || (bottom2 > top1))
87
+ else
88
+ nil
89
+ end
90
+ else
91
+ s = (-v1[1] * p.x + v1[0] * p.y) / denominator # v1 x (p0 - p2) / denominator
92
+ t = ( v2[0] * p.y - v2[1] * p.x) / denominator # v2 x (p0 - p2) / denominator
93
+
94
+ p0 + v1 * t if ((0..1) === s) && ((0..1) === t)
95
+ end
96
+ end
97
+
98
+ # @return [Bool] Returns true if the passed {Edge} is parallel to the receiver
99
+ def parallel?(edge)
100
+ v1, v2 = self.direction, edge.direction
101
+ winding = v1[0]*v2[1] - v1[1]*v2[0]
102
+ if 0 == winding # collinear?
103
+ if v1 == v2
104
+ true # 1 # same direction
105
+ else
106
+ true # -1 # opposite direction
107
+ end
108
+ else
109
+ false
110
+ end
111
+ end
112
+
113
+ # Return a new {Edge} with swapped endpoints
114
+ def reverse
115
+ Edge.new(@last, @first)
116
+ end
117
+
118
+ # In-place swap the endpoints
119
+ def reverse!
120
+ @first, @last = @last, @first
121
+ self
122
+ end
123
+
124
+ def to_a
125
+ [@first, @last]
126
+ end
127
+
128
+ # @return [Vector] A {Vector} pointing from first to last
129
+ def vector
130
+ Vector[*((last-first).to_a)]
131
+ end
132
+
133
+ # Return the {Edge}'s length along the X axis
134
+ def width
135
+ (@first.x - @last.x).abs
136
+ end
137
+
138
+ end
139
+ end
140
+
141
+ def Edge(*args)
142
+ Euclidean::Edge.new(*args)
143
+ end