vector2d 1.1.2 → 2.0.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 +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 [](https://travis-ci.org/elektronaut/vector2d) [](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
|