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.
@@ -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
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  class Vector2d
4
- VERSION = "1.1.2"
4
+ VERSION = "2.0.0"
5
5
  end
@@ -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