vector2d 1.1.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +7 -0
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/README.md +29 -0
- data/lib/vector2d.rb +74 -220
- data/lib/vector2d/calculations.rb +141 -0
- data/lib/vector2d/coercions.rb +64 -0
- data/lib/vector2d/fitting.rb +39 -0
- data/lib/vector2d/properties.rb +46 -0
- data/lib/vector2d/transformations.rb +57 -0
- data/lib/vector2d/version.rb +1 -1
- data/spec/lib/vector2d/calculations_spec.rb +126 -0
- data/spec/lib/vector2d/coercions_spec.rb +55 -0
- data/spec/lib/vector2d/fitting_spec.rb +48 -0
- data/spec/lib/vector2d/properties_spec.rb +43 -0
- data/spec/lib/vector2d/transformations_spec.rb +57 -0
- data/spec/lib/vector2d_spec.rb +31 -268
- data/spec/spec_helper.rb +2 -0
- data/vector2d.gemspec +2 -0
- metadata +20 -5
- data/README.rdoc +0 -61
- data/VERSION +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1406edfe102d42ed1f904b284c2d60a3fb7ab377
|
4
|
+
data.tar.gz: ad834281b90b577cb6b9d5e392cd27d8aed630cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5894c2e97725decc4e019eef6fa43b597ab4e5349e09fd036d516763d687eac3d9d9c882cfc785ff6cfcf1f9f5cf1d50a73eb4f7afb3e84f24619cdd23f79bd
|
7
|
+
data.tar.gz: 82bb98f6236e474dffc947c5ea8a95c52bb94d92c0e885cf95886129242b7e13fa4947042571ee3bc362b0414570c7ba95c8268971e3fdf21c8caa7d1e9172d0
|
data/.travis.yml
ADDED
data/Gemfile.lock
CHANGED
data/LICENSE
CHANGED
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Vector2d [![Build Status](https://travis-ci.org/elektronaut/vector2d.png)](https://travis-ci.org/elektronaut/vector2d) [![Code Climate](https://codeclimate.com/github/elektronaut/vector2d.png)](https://codeclimate.com/github/elektronaut/vector2d)
|
2
|
+
|
3
|
+
Vector2d handles two-dimensional coordinates and vectors.
|
4
|
+
Vectors are immutable, meaning this is a purely functional library.
|
5
|
+
|
6
|
+
## Quick example
|
7
|
+
|
8
|
+
require 'vector2d'
|
9
|
+
|
10
|
+
vector = Vector2d(50, 70)
|
11
|
+
|
12
|
+
vector.aspect_ratio # => 0.714285714285714
|
13
|
+
vector.length # => 86.0232526704263
|
14
|
+
|
15
|
+
vector * 2 # => Vector2d(140,100)
|
16
|
+
vector + Vector2d(20, 30) # => Vector2d(100,70)
|
17
|
+
|
18
|
+
vector.fit(Vector(64, 64)) # => Vector2d(64,45)
|
19
|
+
|
20
|
+
Vector2d.parse([50, 70]) # => Vector2d(50,70)
|
21
|
+
Vector2d.parse("50x70") # => Vector2d(50,70)
|
22
|
+
|
23
|
+
## Documentation
|
24
|
+
|
25
|
+
[API documentation](http://rdoc.info/github/elektronaut/vector2d)
|
26
|
+
|
27
|
+
## License
|
28
|
+
|
29
|
+
Vector2d is released under the [MIT License](http://www.opensource.org/licenses/MIT).
|
data/lib/vector2d.rb
CHANGED
@@ -1,238 +1,92 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
attr_accessor :x
|
10
|
-
# Y axis
|
11
|
-
attr_accessor :y
|
3
|
+
require 'vector2d/calculations'
|
4
|
+
require 'vector2d/coercions'
|
5
|
+
require 'vector2d/fitting'
|
6
|
+
require 'vector2d/properties'
|
7
|
+
require 'vector2d/transformations'
|
8
|
+
require 'vector2d/version'
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
37
|
-
if args[0].has_key?(:x)
|
38
|
-
args[0] = args[0][:x]
|
39
|
-
elsif args[0].has_key?("x")
|
40
|
-
args[0] = args[0]["x"]
|
41
|
-
end
|
10
|
+
class Vector2d
|
11
|
+
extend Vector2d::Calculations::ClassMethods
|
12
|
+
include Vector2d::Calculations
|
13
|
+
include Vector2d::Coercions
|
14
|
+
include Vector2d::Fitting
|
15
|
+
include Vector2d::Properties
|
16
|
+
include Vector2d::Transformations
|
17
|
+
|
18
|
+
class << self
|
19
|
+
# Creates a new vector.
|
20
|
+
# The following examples are all valid:
|
21
|
+
#
|
22
|
+
# Vector2d.parse(150, 100)
|
23
|
+
# Vector2d.parse(150.0, 100.0)
|
24
|
+
# Vector2d.parse("150x100")
|
25
|
+
# Vector2d.parse("150.0x100.0")
|
26
|
+
# Vector2d.parse([150,100})
|
27
|
+
# Vector2d.parse({x: 150, y: 100})
|
28
|
+
# Vector2d.parse({"x" => 150.0, "y" => 100.0})
|
29
|
+
# Vector2d.parse(Vector2d(150, 100))
|
30
|
+
def parse(arg, second_arg=nil)
|
31
|
+
if second_arg.nil?
|
32
|
+
parse_single_arg(arg)
|
42
33
|
else
|
43
|
-
|
34
|
+
self.new(arg, second_arg)
|
44
35
|
end
|
45
36
|
end
|
46
|
-
@x, @y = args[0].to_f, args[1].to_f
|
47
|
-
end
|
48
|
-
|
49
|
-
# Compares two vectors.
|
50
|
-
def ==(comp)
|
51
|
-
comp.x == @x && comp.y == y
|
52
|
-
end
|
53
|
-
|
54
|
-
# Returns a string representation of the vector.
|
55
|
-
#
|
56
|
-
# Example:
|
57
|
-
# Vector2d.new( 150, 100 ).to_s # "150x100"
|
58
|
-
def to_s
|
59
|
-
"#{@x}x#{@y}"
|
60
|
-
end
|
61
|
-
|
62
|
-
# Length of vector
|
63
|
-
def length
|
64
|
-
Math.sqrt(length_squared)
|
65
|
-
end
|
66
|
-
|
67
|
-
# Returns the length of this vector before square root. Allows for a faster check.
|
68
|
-
def length_squared
|
69
|
-
@x * @x + @y * @y
|
70
|
-
end
|
71
|
-
|
72
|
-
# Set new length.
|
73
|
-
def length=(new_length)
|
74
|
-
v = self * (new_length/length)
|
75
|
-
@x, @y = v.x, v.y
|
76
|
-
end
|
77
|
-
|
78
|
-
# Returns a normalized (length = 1.0) version of the vector.
|
79
|
-
def normalize
|
80
|
-
self.dup.normalize!
|
81
|
-
end
|
82
|
-
|
83
|
-
# In-place form of Vector2d.normalize.
|
84
|
-
def normalize!
|
85
|
-
self.length = 1.0
|
86
|
-
self
|
87
|
-
end
|
88
|
-
|
89
|
-
def normalized?
|
90
|
-
self.length == 1.0
|
91
|
-
end
|
92
|
-
|
93
|
-
# Rounds coordinates to nearest integer.
|
94
|
-
def round
|
95
|
-
self.dup.round!
|
96
|
-
end
|
97
|
-
|
98
|
-
# In-place form of Vector2d.round.
|
99
|
-
def round!
|
100
|
-
@x, @y = @x.round, @y.round
|
101
|
-
self
|
102
|
-
end
|
103
37
|
|
104
|
-
|
105
|
-
|
106
|
-
(
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
# Add vectors. If args is a single Numeric, it will be added to both axis.
|
122
|
-
def +(*vector_or_number)
|
123
|
-
v = Vector2d::new(vector_or_number)
|
124
|
-
Vector2d.new(@x+v.x, @y+v.y)
|
125
|
-
end
|
126
|
-
|
127
|
-
# Subtract vectors. If args is a single Numeric, it will be subtracted from both axis.
|
128
|
-
def -(*vector_or_number)
|
129
|
-
v = Vector2d::new(vector_or_number)
|
130
|
-
Vector2d.new(@x-v.x, @y-v.y)
|
131
|
-
end
|
132
|
-
|
133
|
-
# return a new vector perpendicular to this one
|
134
|
-
def perpendicular
|
135
|
-
Vector2d.new(-@y, @x)
|
136
|
-
end
|
137
|
-
|
138
|
-
# distance between two vectors
|
139
|
-
def distance(vector2)
|
140
|
-
Math.sqrt(distance_sq(vector2))
|
141
|
-
end
|
142
|
-
|
143
|
-
# Calculate squared distance between vectors. Faster than standard distance.
|
144
|
-
# param vector2 The other vector.
|
145
|
-
# returns the squared distance between the vectors.
|
146
|
-
def distance_sq(vector2)
|
147
|
-
dx = vector2.x - @x
|
148
|
-
dy = vector2.y - @y
|
149
|
-
dx * dx + dy * dy
|
150
|
-
end
|
151
|
-
|
152
|
-
# angle of this vector
|
153
|
-
def angle
|
154
|
-
Math.atan2(@y, @x)
|
155
|
-
end
|
156
|
-
|
157
|
-
# sets the length under the given value. Nothing is done if
|
158
|
-
# the vector is already shorter.
|
159
|
-
def truncate(max)
|
160
|
-
self.length = [max, self.length].min
|
161
|
-
self
|
162
|
-
end
|
163
|
-
|
164
|
-
# Makes the vector face the opposite way.
|
165
|
-
def reverse
|
166
|
-
@x = -@x
|
167
|
-
@y = -@y
|
168
|
-
self
|
169
|
-
end
|
170
|
-
|
171
|
-
# dot product of this vector and another vector
|
172
|
-
def dot_product(vector2)
|
173
|
-
self.class.dot_product(self, vector2)
|
174
|
-
end
|
175
|
-
|
176
|
-
# cross product of this vector and another vector
|
177
|
-
def cross_product(vector2)
|
178
|
-
self.class.cross_product(self, vector2)
|
179
|
-
end
|
180
|
-
|
181
|
-
# angle in radians between this vector and another
|
182
|
-
def angle_between(vector2)
|
183
|
-
self.class.angle_between(self, vector2)
|
184
|
-
end
|
38
|
+
private
|
39
|
+
|
40
|
+
def parse_single_arg(arg)
|
41
|
+
case arg
|
42
|
+
when Vector2d
|
43
|
+
arg
|
44
|
+
when Array
|
45
|
+
self.parse(*arg)
|
46
|
+
when String
|
47
|
+
parse_str(arg)
|
48
|
+
when Hash
|
49
|
+
parse_hash(arg.dup)
|
50
|
+
else
|
51
|
+
self.new(arg, arg)
|
52
|
+
end
|
53
|
+
end
|
185
54
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
55
|
+
def parse_hash(hash)
|
56
|
+
hash[:x] ||= hash['x'] if hash.has_key?('x')
|
57
|
+
hash[:y] ||= hash['y'] if hash.has_key?('y')
|
58
|
+
self.new(hash[:x], hash[:y])
|
59
|
+
end
|
190
60
|
|
191
|
-
|
192
|
-
|
193
|
-
|
61
|
+
def parse_str(str)
|
62
|
+
if str =~ /^[\s]*[\d\.]*[\s]*x[\s]*[\d\.]*[\s]*$/
|
63
|
+
self.new(*str.split("x").map(&:to_f))
|
64
|
+
else
|
65
|
+
raise ArgumentError, "not a valid string input"
|
66
|
+
end
|
67
|
+
end
|
194
68
|
end
|
195
69
|
|
196
|
-
|
197
|
-
def self.angle_between(vector1, vector2)
|
198
|
-
one = vector1.normalized? ? vector1 : vector1.normalize
|
199
|
-
two = vector2.normalized? ? vector2 : vector2.normalize
|
200
|
-
Math.acos(self.dot_product(one, two))
|
201
|
-
end
|
70
|
+
attr_reader :x, :y
|
202
71
|
|
203
|
-
|
204
|
-
|
205
|
-
#
|
206
|
-
# == Examples
|
207
|
-
#
|
208
|
-
# my_image = Vector2d.new("320x200") # Creates a new vector object
|
209
|
-
# my_image.constrain_both(100) # Returns a new vector: x=100, y=63
|
210
|
-
# my_image.constrain_both(150, 50) # Returns a new vector: x=80, y=50
|
211
|
-
# my_image.constrain_both("150x50") # Equal to constrain_both( 150, 50 )
|
212
|
-
# my_image.constrain_both("x100") # Returns a new vector: x=160, y=100
|
213
|
-
#
|
214
|
-
# == Note
|
215
|
-
#
|
216
|
-
# Either axis will be disregarded if zero or nil (see the last example). This is a feature, not a bug. ;)
|
217
|
-
def constrain_both(*vector_or_number)
|
218
|
-
scale = Vector2d::new(vector_or_number) / self
|
219
|
-
(self * ((scale.y==0||(scale.x>0&&scale.x<scale.y)) ? scale.x : scale.y))
|
72
|
+
def initialize(x, y)
|
73
|
+
@x, @y = x, y
|
220
74
|
end
|
221
75
|
|
222
|
-
#
|
76
|
+
# Compares two vectors
|
223
77
|
#
|
224
|
-
# ==
|
78
|
+
# Vector2d(2, 3) == Vector2d(2, 3) # => true
|
79
|
+
# Vector2d(2, 3) == Vector2d(1, 0) # => false
|
225
80
|
#
|
226
|
-
|
227
|
-
|
228
|
-
def constrain_one( *vector_or_number )
|
229
|
-
scale = Vector2d::new(vector_or_number) / self
|
230
|
-
if (scale.x > 0 && scale.y > 0)
|
231
|
-
scale = (scale.x<scale.y) ? scale.y : scale.x
|
232
|
-
self * scale
|
233
|
-
else
|
234
|
-
constrain_both(vector_or_number)
|
235
|
-
end
|
81
|
+
def ==(comp)
|
82
|
+
comp.x === x && comp.y === y
|
236
83
|
end
|
237
|
-
|
238
84
|
end
|
85
|
+
|
86
|
+
# Instantiates a Vector2d
|
87
|
+
#
|
88
|
+
# Vector2d(2, 3) # => Vector2d(2,3)
|
89
|
+
#
|
90
|
+
def Vector2d(*args)
|
91
|
+
Vector2d.parse(*args)
|
92
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Vector2d
|
4
|
+
module Calculations
|
5
|
+
module ClassMethods
|
6
|
+
# Calculates cross product of two vectors.
|
7
|
+
#
|
8
|
+
# v1 = Vector2d(2, 1)
|
9
|
+
# v2 = Vector2d(2, 3)
|
10
|
+
# Vector2d.cross_product(v1, v2) # => 4
|
11
|
+
#
|
12
|
+
def cross_product(vector1, vector2)
|
13
|
+
vector1.x * vector2.y - vector1.y * vector2.x
|
14
|
+
end
|
15
|
+
|
16
|
+
# Calculates dot product of two vectors.
|
17
|
+
#
|
18
|
+
# v1 = Vector2d(2, 1)
|
19
|
+
# v2 = Vector2d(2, 3)
|
20
|
+
# Vector2d.dot_product(v1, v2) # => 10
|
21
|
+
#
|
22
|
+
def dot_product(vector1, vector2)
|
23
|
+
vector1.x * vector2.x + vector1.y * vector2.y
|
24
|
+
end
|
25
|
+
|
26
|
+
# Calculates angle between two vectors in radians.
|
27
|
+
#
|
28
|
+
# v1 = Vector2d(2, 3)
|
29
|
+
# v2 = Vector2d(4, 5)
|
30
|
+
# Vector2d.angle_between(v1, v2) # => 0.0867..
|
31
|
+
#
|
32
|
+
def angle_between(vector1, vector2)
|
33
|
+
one = vector1.normalized? ? vector1 : vector1.normalize
|
34
|
+
two = vector2.normalized? ? vector2 : vector2.normalize
|
35
|
+
Math.acos(self.dot_product(one, two))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Multiplies vectors.
|
40
|
+
#
|
41
|
+
# Vector2d(1, 2) * Vector2d(2, 3) # => Vector2d(2, 6)
|
42
|
+
# Vector2d(1, 2) * 2 # => Vector2d(2, 4)
|
43
|
+
#
|
44
|
+
def *(other)
|
45
|
+
calculate_each(:*, other)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Divides vectors.
|
49
|
+
#
|
50
|
+
# Vector2d(4, 2) / Vector2d(2, 1) # => Vector2d(2, 2)
|
51
|
+
# Vector2d(4, 2) / 2 # => Vector2d(2, 1)
|
52
|
+
#
|
53
|
+
def /(other)
|
54
|
+
calculate_each(:/, other)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Adds vectors.
|
58
|
+
#
|
59
|
+
# Vector2d(1, 2) + Vector2d(2, 3) # => Vector2d(3, 5)
|
60
|
+
# Vector2d(1, 2) + 2 # => Vector2d(3, 4)
|
61
|
+
#
|
62
|
+
def +(other)
|
63
|
+
calculate_each(:+, other)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Subtracts vectors.
|
67
|
+
#
|
68
|
+
# Vector2d(2, 3) - Vector2d(2, 1) # => Vector2d(0, 2)
|
69
|
+
# Vector2d(4, 3) - 1 # => Vector2d(3, 2)
|
70
|
+
#
|
71
|
+
def -(other)
|
72
|
+
calculate_each(:-, other)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Calculates the distance between two vectors.
|
76
|
+
#
|
77
|
+
# v1 = Vector2d(2, 3)
|
78
|
+
# v2 = Vector2d(5, 6)
|
79
|
+
# v1.distance(v2) # => 1.4142..
|
80
|
+
#
|
81
|
+
def distance(other)
|
82
|
+
Math.sqrt(squared_distance(other))
|
83
|
+
end
|
84
|
+
|
85
|
+
# Calculate squared distance between vectors.
|
86
|
+
#
|
87
|
+
# v1 = Vector2d(2, 3)
|
88
|
+
# v2 = Vector2d(5, 6)
|
89
|
+
# v1.distance_squared(v2) # => 18
|
90
|
+
#
|
91
|
+
def squared_distance(other)
|
92
|
+
v, _ = coerce(other)
|
93
|
+
dx = v.x - x
|
94
|
+
dy = v.y - y
|
95
|
+
dx * dx + dy * dy
|
96
|
+
end
|
97
|
+
|
98
|
+
# Dot product of this vector and another vector.
|
99
|
+
#
|
100
|
+
# v1 = Vector2d(2, 1)
|
101
|
+
# v2 = Vector2d(2, 3)
|
102
|
+
# v1.dot_product(v2) # => 10
|
103
|
+
#
|
104
|
+
def dot_product(other)
|
105
|
+
v, _ = coerce(other)
|
106
|
+
self.class.dot_product(self, v)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Cross product of this vector and another vector.
|
110
|
+
#
|
111
|
+
# v1 = Vector2d(2, 1)
|
112
|
+
# v2 = Vector2d(2, 3)
|
113
|
+
# v1.cross_product(v2) # => 4
|
114
|
+
#
|
115
|
+
def cross_product(other)
|
116
|
+
v, _ = coerce(other)
|
117
|
+
self.class.cross_product(self, v)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Angle in radians between this vector and another vector.
|
121
|
+
#
|
122
|
+
# v1 = Vector2d(2, 3)
|
123
|
+
# v2 = Vector2d(4, 5)
|
124
|
+
# v1.angle_between(v2) # => 0.0867..
|
125
|
+
#
|
126
|
+
def angle_between(other)
|
127
|
+
v, _ = coerce(other)
|
128
|
+
self.class.angle_between(self, v)
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def calculate_each(method, other)
|
134
|
+
v, _ = coerce(other)
|
135
|
+
self.class.new(
|
136
|
+
x.send(method, v.x),
|
137
|
+
y.send(method, v.y)
|
138
|
+
)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|