euclidean 0.1.0

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