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
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Vector2d
|
4
|
+
module Coercions
|
5
|
+
def coerce(other)
|
6
|
+
case other
|
7
|
+
when Vector2d
|
8
|
+
[other, self]
|
9
|
+
when Array, Numeric, String, Hash
|
10
|
+
[Vector2d.parse(other), self]
|
11
|
+
else
|
12
|
+
raise TypeError, "#{self.class} can't be coerced into #{other.class}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Renders vector as a pretty string.
|
17
|
+
#
|
18
|
+
# Vector2d(2, 3).inspect # => "Vector2d(2,3)"
|
19
|
+
#
|
20
|
+
def inspect
|
21
|
+
"Vector2d(#{x},#{y})"
|
22
|
+
end
|
23
|
+
|
24
|
+
# Converts vector to array.
|
25
|
+
#
|
26
|
+
# Vector2d(2, 3).to_a # => [2,3]
|
27
|
+
#
|
28
|
+
def to_a
|
29
|
+
[x, y]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Converts vector to hash.
|
33
|
+
#
|
34
|
+
# Vector2d(2, 3).to_hash # => {x: 2, y: 3}
|
35
|
+
#
|
36
|
+
def to_hash
|
37
|
+
{ x: x, y: y }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Converts vector to fixnums.
|
41
|
+
#
|
42
|
+
# Vector2d(2.0, 3.0).to_i_vector # => Vector2d(2,3)
|
43
|
+
#
|
44
|
+
def to_i_vector
|
45
|
+
self.class.new(x.to_i, y.to_i)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Converts vector to floats.
|
49
|
+
#
|
50
|
+
# Vector2d(2, 3).to_f_vector # => Vector2d(2.0,3.0)
|
51
|
+
#
|
52
|
+
def to_f_vector
|
53
|
+
self.class.new(x.to_f, y.to_f)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Converts vector to string.
|
57
|
+
#
|
58
|
+
# Vector2d.new(150, 100).to_s # => "150x100"
|
59
|
+
#
|
60
|
+
def to_s
|
61
|
+
"#{x}x#{y}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Vector2d
|
4
|
+
module Fitting
|
5
|
+
# Returns a new vector fitting inside another vector, retaining the aspect ratio.
|
6
|
+
#
|
7
|
+
# vector = Vector2d(20, 10)
|
8
|
+
# vector.fit(Vector2d(10, 10)) # => Vector2d(10,5)
|
9
|
+
# vector.fit(Vector2d(20, 20)) # => Vector2d(20,10)
|
10
|
+
# vector.fit(Vector2d(40, 40)) # => Vector2d(40,20)
|
11
|
+
#
|
12
|
+
# Note: Either axis will be disregarded if zero or nil. This is a feature, not a bug.
|
13
|
+
#
|
14
|
+
def fit(other)
|
15
|
+
v, _ = coerce(other)
|
16
|
+
scale = v.to_f_vector / self
|
17
|
+
self * ((scale.y == 0 || (scale.x > 0 && scale.x < scale.y)) ? scale.x : scale.y)
|
18
|
+
end
|
19
|
+
alias_method :constrain_both, :fit
|
20
|
+
|
21
|
+
# Constrain/expand so that one of the coordinates fit within (the square implied by) another vector.
|
22
|
+
#
|
23
|
+
# constraint = Vector2d(5, 5)
|
24
|
+
# Vector2d(20, 10).fit_either(constraint) # => Vector2d(10,5)
|
25
|
+
# Vector2d(10, 20).fit_either(constraint) # => Vector2d(5,10)
|
26
|
+
#
|
27
|
+
def fit_either(other)
|
28
|
+
v, _ = coerce(other)
|
29
|
+
scale = v.to_f_vector / self
|
30
|
+
if (scale.x > 0 && scale.y > 0)
|
31
|
+
scale = (scale.x < scale.y) ? scale.y : scale.x
|
32
|
+
self * scale
|
33
|
+
else
|
34
|
+
fit(v)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
alias_method :constrain_one, :fit_either
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Vector2d
|
4
|
+
module Properties
|
5
|
+
# Angle of vector.
|
6
|
+
#
|
7
|
+
# Vector(2, 3).angle # => 0.9827..
|
8
|
+
#
|
9
|
+
def angle
|
10
|
+
Math.atan2(y, x)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Aspect ratio of vector.
|
14
|
+
#
|
15
|
+
# Vector(2, 3).aspect_ratio # => 0.6667..
|
16
|
+
#
|
17
|
+
def aspect_ratio
|
18
|
+
(x.to_f / y.to_f).abs
|
19
|
+
end
|
20
|
+
|
21
|
+
# Length of vector.
|
22
|
+
#
|
23
|
+
# Vector(2, 3).length # => 3.6055..
|
24
|
+
#
|
25
|
+
def length
|
26
|
+
Math.sqrt(squared_length)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Squared length of vector.
|
30
|
+
#
|
31
|
+
# Vector(2, 3).squared_length # => 13
|
32
|
+
#
|
33
|
+
def squared_length
|
34
|
+
x * x + y * y
|
35
|
+
end
|
36
|
+
|
37
|
+
# Is this a normalized vector?
|
38
|
+
#
|
39
|
+
# Vector(0, 1).normalized? # => true
|
40
|
+
# Vector(2, 3).normalized? # => false
|
41
|
+
#
|
42
|
+
def normalized?
|
43
|
+
self.length.to_f == 1.0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Vector2d
|
4
|
+
module Transformations
|
5
|
+
# Normalizes the vector.
|
6
|
+
#
|
7
|
+
# vector = Vector2d(2, 3)
|
8
|
+
# vector.normalize # => Vector2d(0.5547.., 0.8320..)
|
9
|
+
# vector.normalize.length # => 1.0
|
10
|
+
#
|
11
|
+
def normalize
|
12
|
+
resize(1.0)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns a perpendicular vector.
|
16
|
+
#
|
17
|
+
# Vector2d(2, 3).perpendicular # => Vector2d(-3,2)
|
18
|
+
#
|
19
|
+
def perpendicular
|
20
|
+
Vector2d.new(-y, x)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Changes magnitude of vector.
|
24
|
+
#
|
25
|
+
# Vector2d(2, 3).resize(1.0) # => Vector2d(0.5547.., 0.8320..)
|
26
|
+
#
|
27
|
+
def resize(new_length)
|
28
|
+
self * (new_length / self.length)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Reverses the vector.
|
32
|
+
#
|
33
|
+
# Vector2d(2, 3).reverse # => Vector2d(-2,-3)
|
34
|
+
#
|
35
|
+
def reverse
|
36
|
+
self.class.new(-x, -y)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Rounds vector to nearest integer.
|
40
|
+
#
|
41
|
+
# Vector2d(2.4, 3.6).round # => Vector2d(2,4)
|
42
|
+
#
|
43
|
+
def round
|
44
|
+
self.class.new(x.round, y.round)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Truncates to max length if vector is longer than max.
|
48
|
+
#
|
49
|
+
# vector = Vector2d(2.0, 3.0)
|
50
|
+
# vector.truncate(5.0) # => Vector2d(2.0, 3.0)
|
51
|
+
# vector.truncate(1.0) # => Vector2d(0.5547.., 0.8320..)
|
52
|
+
#
|
53
|
+
def truncate(max)
|
54
|
+
resize([max, self.length].min)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/vector2d/version.rb
CHANGED
@@ -0,0 +1,126 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Vector2d::Calculations do
|
6
|
+
subject(:vector) { Vector2d.new(2, 3) }
|
7
|
+
|
8
|
+
describe ".cross_product" do
|
9
|
+
let(:v1) { Vector2d.new(2, 3) }
|
10
|
+
let(:v2) { Vector2d.new(4, 5) }
|
11
|
+
it "calculates the cross product between two vectors" do
|
12
|
+
expect(Vector2d.cross_product(v1, v2)).to eq(-2.0)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe ".dot_product" do
|
17
|
+
let(:v1) { Vector2d.new(2, 3) }
|
18
|
+
let(:v2) { Vector2d.new(4, 5) }
|
19
|
+
it "calculates the dot product between two vectors" do
|
20
|
+
expect(Vector2d.dot_product(v1, v2)).to eq(23)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe ".angle_between" do
|
25
|
+
let(:v1) { Vector2d.new(2, 3) }
|
26
|
+
let(:v2) { Vector2d.new(4, 5) }
|
27
|
+
it "calculates the angle between two vectors" do
|
28
|
+
expect(Vector2d.angle_between(v1, v2)).to be_within(0.0001).of(0.0867)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "*" do
|
33
|
+
context "with vector" do
|
34
|
+
subject(:vector) { Vector2d.new(2, 3) * Vector2d.new(3, 4) }
|
35
|
+
it "multiplies the vectors" do
|
36
|
+
expect(vector).to eq(Vector2d.new(6, 12))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
context "with number" do
|
40
|
+
subject(:vector) { Vector2d.new(2, 3) * 3 }
|
41
|
+
it "multiplies both members" do
|
42
|
+
expect(vector).to eq(Vector2d.new(6, 9))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "/" do
|
48
|
+
context "with vector" do
|
49
|
+
subject(:vector) { Vector2d.new(6, 12) / Vector2d.new(2, 3) }
|
50
|
+
it "divides the vectors" do
|
51
|
+
expect(vector).to eq(Vector2d.new(3, 4))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
context "with number" do
|
55
|
+
subject(:vector) { Vector2d.new(6, 12) / 3 }
|
56
|
+
it "divides both members" do
|
57
|
+
expect(vector).to eq(Vector2d.new(2, 4))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "+" do
|
63
|
+
context "with vector" do
|
64
|
+
subject(:vector) { Vector2d.new(2, 3) + Vector2d.new(3, 4) }
|
65
|
+
it "adds the vectors" do
|
66
|
+
expect(vector).to eq(Vector2d.new(5, 7))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
context "with number" do
|
70
|
+
subject(:vector) { Vector2d.new(2, 3) + 2 }
|
71
|
+
it "adds to both members" do
|
72
|
+
expect(vector).to eq(Vector2d.new(4, 5))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "-" do
|
78
|
+
context "with vector" do
|
79
|
+
subject(:vector) { Vector2d.new(3, 5) - Vector2d.new(1, 2) }
|
80
|
+
it "subtracts the vectors" do
|
81
|
+
expect(vector).to eq(Vector2d.new(2, 3))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
context "with number" do
|
85
|
+
subject(:vector) { Vector2d.new(2, 3) - 2 }
|
86
|
+
it "subtracts from both members" do
|
87
|
+
expect(vector).to eq(Vector2d.new(0, 1))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "#cross_product" do
|
93
|
+
let(:comp) { Vector2d.new(3, 4) }
|
94
|
+
it "calulates the cross product" do
|
95
|
+
expect(vector.cross_product(comp)).to eq(Vector2d.cross_product(vector, comp))
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "#distance" do
|
100
|
+
let(:comp) { Vector2d.new(3, 4) }
|
101
|
+
it "returns the distance between two vectors" do
|
102
|
+
expect(vector.distance(comp)).to be_within(0.0001).of(1.4142)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "#squared_distance" do
|
107
|
+
let(:comp) { Vector2d.new(5, 6) }
|
108
|
+
it "returns the squared distance between two vectors" do
|
109
|
+
expect(vector.squared_distance(comp)).to eq(18)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "#dot_product" do
|
114
|
+
let(:comp) { Vector2d.new(3, 4) }
|
115
|
+
it "calulates the dot product" do
|
116
|
+
expect(vector.dot_product(comp)).to eq(Vector2d.dot_product(vector, comp))
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "#angle_between" do
|
121
|
+
let(:comp) { Vector2d.new(3, 4) }
|
122
|
+
it "calculates the angle between vectors" do
|
123
|
+
expect(vector.angle_between(comp)).to eq(Vector2d.angle_between(vector, comp))
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Vector2d::Coercions do
|
6
|
+
subject(:vector) { Vector2d.new(2, 3) }
|
7
|
+
|
8
|
+
describe "#inspect" do
|
9
|
+
it "renders a string representation" do
|
10
|
+
expect(vector.inspect).to eq("Vector2d(2,3)")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#to_a" do
|
15
|
+
it "returns an array" do
|
16
|
+
expect(vector.to_a).to eq([2, 3])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#to_f_vector" do
|
21
|
+
it "returns a float vector" do
|
22
|
+
expect(vector.to_f_vector.x).to be_a(Float)
|
23
|
+
expect(vector.to_f_vector.y).to be_a(Float)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#to_hash" do
|
28
|
+
it "returns a hash" do
|
29
|
+
expect(vector.to_hash).to eq({x: 2, y: 3})
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#to_i_vector" do
|
34
|
+
subject(:vector) { Vector2d.new(2.0, 3.0) }
|
35
|
+
it "returns a fixnum vector" do
|
36
|
+
expect(vector.to_i_vector.x).to be_a(Fixnum)
|
37
|
+
expect(vector.to_i_vector.y).to be_a(Fixnum)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#to_s" do
|
42
|
+
context "when fixnum" do
|
43
|
+
subject(:vector) { Vector2d.new(2, 3) }
|
44
|
+
it "renders a string" do
|
45
|
+
expect(vector.to_s).to eq('2x3')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
context "when float" do
|
49
|
+
subject(:vector) { Vector2d.new(2.0, 3.0) }
|
50
|
+
it "renders a string" do
|
51
|
+
expect(vector.to_s).to eq('2.0x3.0')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Vector2d::Fitting do
|
6
|
+
let(:original) { Vector2d.new(300, 300) }
|
7
|
+
|
8
|
+
describe "#fit" do
|
9
|
+
subject(:vector) { original.fit(comp) }
|
10
|
+
|
11
|
+
context "when scaling by height" do
|
12
|
+
let(:comp) { Vector2d.new(200, 150) }
|
13
|
+
it "should fit within the other vector" do
|
14
|
+
expect(vector.x).to eq(150)
|
15
|
+
expect(vector.y).to eq(150)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when scaling by width" do
|
20
|
+
let(:comp) { Vector2d.new(150, 200) }
|
21
|
+
it "should fit within the other vector" do
|
22
|
+
expect(vector.x).to eq(150)
|
23
|
+
expect(vector.y).to eq(150)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#contrain_one" do
|
29
|
+
let(:original) { Vector2d.new(300, 300) }
|
30
|
+
subject(:vector) { original.fit_either(comp) }
|
31
|
+
|
32
|
+
context "when width is largest" do
|
33
|
+
let(:comp) { Vector2d.new(200, 150) }
|
34
|
+
it "should be the same width" do
|
35
|
+
expect(vector.x).to eq(200)
|
36
|
+
expect(vector.y).to eq(200)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when height is largest" do
|
41
|
+
let(:comp) { Vector2d.new(150, 200) }
|
42
|
+
it "should be the same height" do
|
43
|
+
expect(vector.x).to eq(200)
|
44
|
+
expect(vector.y).to eq(200)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|