vector2d 1.1.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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