stylet_support 0.0.5

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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 12e01d3fd7833198068d5740bff66a76001630134c6987f496617ba499a5beb3
4
+ data.tar.gz: 40630818ff1a9324a7536f0372d8efcb6927e0bc8cd70e761090b9f2c6a8622d
5
+ SHA512:
6
+ metadata.gz: 123937855380833c85ee6f40318ddcf31f9372a5f5cd1d74e305acbcf4d42fae1407ede0ef370ee5ce400132f8fedf9d134aa5214d020e5a38fc8de299498df1
7
+ data.tar.gz: b9e199aa2dc1ec62c47451050f636632f58bfb9f057d2db55108ce55f9ad2c35566190ce173f96e84fd4527b053eb1152b1b1bfcc886021919f9165a36fa6822
@@ -0,0 +1,6 @@
1
+ /README.html
2
+ /Gemfile.lock
3
+ /*.gem
4
+ /doc
5
+ /.yardoc
6
+ /TODO
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.5.1
4
+ script: bundle exec rake
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,106 @@
1
+ * GUIに依存しない数学関連ライブラリ
2
+
3
+ #+BEGIN_SRC ruby
4
+ Point.new # => [0.0, 0.0]
5
+ Point[1, 2] # => [1, 2]
6
+ Point[1, 2].members # => [:x, :y]
7
+ Point[1, 2].values # => [1, 2]
8
+ Point[1, 2] # => [1, 2]
9
+
10
+ Vector.superclass # => Stylet::Point2
11
+
12
+ Point.new(Point[1,2]) # => [1, 2]
13
+ Point[Point[1,2]] # => [1, 2]
14
+
15
+ Vector.new # => [0.0, 0.0]
16
+ Vector.new # => [0.0, 0.0]
17
+ Vector.new(1, 2) # => [1, 2]
18
+
19
+ Vector.zero # => [0.0, 0.0]
20
+ Vector.one # => [1.0, 1.0]
21
+
22
+ a = Vector[1, 2]
23
+ b = Vector[3, 4]
24
+
25
+ a + b # => [4.0, 6.0]
26
+ a - b # => [-2.0, -2.0]
27
+ a * 2 # => [2.0, 4.0]
28
+ a / 2 # => [0.5, 1.0]
29
+
30
+ a.add(b) # => [4.0, 6.0]
31
+ a.sub(b) # => [-2.0, -2.0]
32
+ a.scale(2) # => [2.0, 4.0]
33
+ a.mul(2) # => [2.0, 4.0]
34
+ a.div(2) # => [0.5, 1.0]
35
+
36
+ a + [3, 4] # => [4.0, 6.0]
37
+ a - [3, 4] # => [-2.0, -2.0]
38
+
39
+ Vector.one.reverse # => [-1.0, -1.0]
40
+ -Vector.one # => [-1.0, -1.0]
41
+
42
+ Vector[3, 4].normalize # => [0.6, 0.8]
43
+
44
+ Vector.one.normalize # => [0.7071067811865475, 0.7071067811865475]
45
+ Vector.one.magnitude # => 1.4142135623730951
46
+ Vector.one.magnitude_sq # => 2.0
47
+
48
+ v = Vector.rand
49
+ v.round(2) # => [-1.0, -0.88]
50
+ v.round # => [-1, -1]
51
+ v.floor # => [-1, -1]
52
+ v.ceil # => [0, 0]
53
+ v.truncate # => [0, 0]
54
+
55
+ Vector.rand # => [-0.5758140223421724, -0.23276547457672714]
56
+ Vector.rand(3) # => [1, 1]
57
+ Vector.rand(3..4) # => [4, 4]
58
+ Vector.rand(3.0..4) # => [3.9402342251571714, 3.550203689124972]
59
+ Vector.rand(-2.0..2.0) # => [-1.181268220930995, 1.4942116900421252]
60
+
61
+ Vector[1, 0].dot_product(Vector[1, 0]) # => 1
62
+ Vector[1, 0].dot_product(Vector[-1, 0]) # => -1
63
+
64
+ Vector.cross_product(Vector.rand, Vector.rand) # => -0.7348374986070235
65
+
66
+ Vector.rand.distance_to(Vector.rand) # => 1.7226903872525836
67
+
68
+ v = Vector.new
69
+ v.object_id # => 70298637530620
70
+ v.replace(Vector.rand) # => [-0.2619785178209082, -0.4396795163426983]
71
+ v.object_id # => 70298637530620
72
+
73
+ Vector.zero.distance_to(Vector.one) # => 1.4142135623730951
74
+
75
+ Vector.zero.zero? # => true
76
+ Vector.one.nonzero? # => true
77
+
78
+ Vector.zero.inspect # => "[0.0, 0.0]"
79
+ Vector.zero.to_s # => "[0.0, 0.0]"
80
+
81
+ Vector[1,2].prep # => [-2, 1]
82
+ #+END_SRC
83
+
84
+ ** TIPS
85
+
86
+ *** 当たり判定を高速化するには?
87
+
88
+ 当たり判定を次のようにしているとき
89
+
90
+ #+BEGIN_SRC ruby
91
+ if v.magnitude < r
92
+ end
93
+ #+END_SRC
94
+
95
+ 次のようにすると sqrt を省略できる
96
+
97
+ #+BEGIN_SRC ruby
98
+ if v.magnitude_sq < r ** 2
99
+ end
100
+ #+END_SRC
101
+
102
+ *** a地点からb地点へのベクトルを求めるには?
103
+
104
+ #+BEGIN_SRC ruby
105
+ b - a
106
+ #+END_SRC
@@ -0,0 +1,9 @@
1
+ require "bundler"
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require "rake/testtask"
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ end
8
+
9
+ task :default => :test
@@ -0,0 +1,85 @@
1
+ require "./setup"
2
+ include Stylet
3
+
4
+ Point.new # => [0.0, 0.0]
5
+ Point.new(1, 2) # => [1, 2]
6
+ Point[1, 2] # => [1, 2]
7
+ Point[1, 2].members # => [:x, :y]
8
+ Point[1, 2].values # => [1, 2]
9
+ Point[1, 2] # => [1, 2]
10
+
11
+ Point[1, 2] == Point[1, 2] # => true
12
+ Point[1, 2] == Point[3, 4] # => false
13
+
14
+ Vector.superclass # => Stylet::Point2
15
+
16
+ Point.new(Point[1, 2]) # => [1, 2]
17
+ Point[Point[1, 2]] # => [1, 2]
18
+
19
+ Vector.new # => [0.0, 0.0]
20
+ Vector.new # => [0.0, 0.0]
21
+ Vector.new(1, 2) # => [1, 2]
22
+
23
+ Vector.zero # => [0.0, 0.0]
24
+ Vector.one # => [1.0, 1.0]
25
+
26
+ a = Vector[1, 2]
27
+ b = Vector[3, 4]
28
+
29
+ a + b # => [4.0, 6.0]
30
+ a - b # => [-2.0, -2.0]
31
+ a * 2 # => [2.0, 4.0]
32
+ a / 2 # => [0.5, 1.0]
33
+
34
+ a.add(b) # => [4.0, 6.0]
35
+ a.sub(b) # => [-2.0, -2.0]
36
+ a.scale(2) # => [2.0, 4.0]
37
+ a.mul(2) # => [2.0, 4.0]
38
+ a.div(2) # => [0.5, 1.0]
39
+
40
+ a + [3, 4] # => [4.0, 6.0]
41
+ a - [3, 4] # => [-2.0, -2.0]
42
+
43
+ Vector.one.reverse # => [-1.0, -1.0]
44
+ -Vector.one # => [-1.0, -1.0]
45
+
46
+ Vector[3, 4].normalize # => [0.6, 0.8]
47
+
48
+ Vector.one.normalize # => [0.7071067811865475, 0.7071067811865475]
49
+ Vector.one.magnitude # => 1.4142135623730951
50
+ Vector.one.magnitude_sq # => 2.0
51
+
52
+ v = Vector.rand
53
+ v.round(2) # => [0.48, 0.6]
54
+ v.round # => [0, 1]
55
+ v.floor # => [0, 0]
56
+ v.ceil # => [1, 1]
57
+ v.truncate # => [0, 0]
58
+
59
+ Vector.rand # => [0.37182089855094613, -0.99052832152511]
60
+ Vector.rand(3) # => [1, 1]
61
+ Vector.rand(3..4) # => [4, 4]
62
+ Vector.rand(3.0..4) # => [3.655724436092496, 3.3356907533135023]
63
+ Vector.rand(-2.0..2.0) # => [-0.6592404423144278, 1.5211933133588427]
64
+
65
+ Vector[1, 0].dot_product(Vector[1, 0]) # => 1
66
+ Vector[1, 0].dot_product(Vector[-1, 0]) # => -1
67
+
68
+ Vector.cross_product(Vector.rand, Vector.rand) # => 0.7501561461552074
69
+
70
+ Vector.rand.distance_to(Vector.rand) # => 0.7463133439748652
71
+
72
+ v = Vector.new
73
+ v.object_id # => 70119757980160
74
+ v.replace(Vector.rand) # => [0.29626268422451485, 0.8924141847532647]
75
+ v.object_id # => 70119757980160
76
+
77
+ Vector.zero.distance_to(Vector.one) # => 1.4142135623730951
78
+
79
+ Vector.zero.zero? # => true
80
+ Vector.one.nonzero? # => true
81
+
82
+ Vector.zero.inspect # => "[0.0, 0.0]"
83
+ Vector.zero.to_s # => "[0.0, 0.0]"
84
+
85
+ Vector[1, 2].prep # => [-2, 1]
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift "#{__dir__}/../lib"
2
+ require "stylet_support"
@@ -0,0 +1,5 @@
1
+ require "stylet_support/version"
2
+
3
+ require "stylet_support/vector"
4
+ require "stylet_support/chore"
5
+ require "stylet_support/magic"
@@ -0,0 +1,40 @@
1
+ module Stylet
2
+ module Chore
3
+ extend self
4
+
5
+ def clamp(v, range = (0.0..1.0))
6
+ v.clamp(range.min, range.max)
7
+ end
8
+
9
+ def max_clamp(v, r = 1.0)
10
+ [v, r].min
11
+ end
12
+
13
+ def min_clamp(v, r = 0.0)
14
+ [v, r].max
15
+ end
16
+
17
+ def abs_clamp(v, r = 1.0)
18
+ clamp(v, (-r..r))
19
+ end
20
+
21
+ # b から a へ行きたいときの最短の角度差
22
+ def shortest_angular_difference(a, b)
23
+ d = a.modulo(1.0) - b.modulo(1.0)
24
+ if d < -1.0 / 2
25
+ d = 1.0 + d
26
+ elsif d > 1.0 / 2
27
+ d = -1.0 + d
28
+ end
29
+ d
30
+ end
31
+ end
32
+ end
33
+
34
+ if $0 == __FILE__
35
+ Stylet::Chore.clamp(0.5) # => 0.5
36
+ Stylet::Chore.max_clamp(1.5) # => 1.0
37
+ Stylet::Chore.min_clamp(-0.5) # => 0.0
38
+ Stylet::Chore.shortest_angular_difference(0.2, 0.5) # => -0.3
39
+ Stylet::Chore.shortest_angular_difference(0.8, 1.2) # => -0.3999999999999999
40
+ end
@@ -0,0 +1,246 @@
1
+ require "singleton"
2
+
3
+ module Stylet
4
+ ONE = 4096 # sin cos の精度
5
+ AROUND = 4096 * 2 # 4096の場合、64分割以上のときにズレが生じる。8092なら256分割でズレるようだ
6
+
7
+ class Magic
8
+ include Singleton
9
+
10
+ def initialize
11
+ @dir_offset = 0
12
+ end
13
+
14
+ prepend Module.new {
15
+ def initialize
16
+ super
17
+ @sin_table = AROUND.times.collect { |i| (Math.sin(2 * Math::PI * i / AROUND) * ONE).round }
18
+ end
19
+
20
+ def _rsin(a)
21
+ @sin_table[a.modulo(@sin_table.size)]
22
+ end
23
+
24
+ def _rcos(a)
25
+ _rsin(a + @sin_table.size / 4)
26
+ end
27
+
28
+ def rsin(a)
29
+ a = a.modulo(1.0) # ruby の場合、一周せずに a * @sin_table.size で無限に桁がでかくなる、のを防ぐため
30
+ @sin_table[(a * @sin_table.size).modulo(@sin_table.size)] * 1.0 / ONE
31
+ end
32
+
33
+ def rcos(a)
34
+ rsin(a + 1.0 / 4)
35
+ end
36
+ }
37
+
38
+ prepend Module.new {
39
+ AtanAreaInfo = Struct.new(:base_dir, :sign)
40
+
41
+ def initialize
42
+ super
43
+ part = AROUND / 8
44
+ @atan_table = (0..part).collect {|i|
45
+ (Math.atan2(i, part) * part / (2 * Math::PI / 8)).round
46
+ }
47
+ @atan_area_info_table = [
48
+ AtanAreaInfo.new(AROUND / 4 * 3 + @dir_offset, +1),
49
+ AtanAreaInfo.new(AROUND / 4 * 4 + @dir_offset, -1),
50
+ AtanAreaInfo.new(AROUND / 4 * 1 + @dir_offset, -1),
51
+ AtanAreaInfo.new(AROUND / 4 * 0 + @dir_offset, +1),
52
+ AtanAreaInfo.new(AROUND / 4 * 3 + @dir_offset, -1),
53
+ AtanAreaInfo.new(AROUND / 4 * 2 + @dir_offset, +1),
54
+ AtanAreaInfo.new(AROUND / 4 * 1 + @dir_offset, +1),
55
+ AtanAreaInfo.new(AROUND / 4 * 2 + @dir_offset, -1),
56
+ ]
57
+ end
58
+
59
+ def angle(ox, oy, tx, ty)
60
+ iangle(ox, oy, tx, ty).to_f / AROUND
61
+ # v % 1.0 # 一周したとき 1.0 にならないようにするため
62
+ end
63
+
64
+ # 4 0
65
+ # 5 1
66
+ # 7 3
67
+ # 6 2
68
+ def iangle(ox, oy, tx, ty)
69
+ dir = nil
70
+
71
+ sx = tx - ox
72
+ sy = ty - oy
73
+
74
+ dx = sx
75
+ dy = sy
76
+
77
+ dx = dx.abs
78
+ dy = dy.abs
79
+ if sx.zero? && sy.zero?
80
+ dir = 0
81
+ else
82
+ if sx >= 0
83
+ if sy <= 0
84
+ # p [sx, sy]
85
+ # p [dx, dy]
86
+ if dx < dy
87
+ # p :koko
88
+ dir = local_dir(dx, dy, 0)
89
+ else
90
+ dir = local_dir(dy, dx, 1)
91
+ # p dir
92
+ end
93
+ else
94
+ if dx < dy
95
+ dir = local_dir(dx, dy, 2)
96
+ else
97
+ dir = local_dir(dy, dx, 3)
98
+ end
99
+ end
100
+ else
101
+ if sy <= 0
102
+ if dx < dy
103
+ dir = local_dir(dx, dy, 4)
104
+ else
105
+ dir = local_dir(dy, dx, 5)
106
+ end
107
+ else
108
+ if dx < dy
109
+ dir = local_dir(dx, dy, 6)
110
+ else
111
+ dir = local_dir(dy, dx, 7)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ # dir.modulo(AROUND)
117
+ dir
118
+ end
119
+
120
+ def local_dir(value, div_value, area_no)
121
+ # p [value, div_value, area_no]
122
+ unless value <= div_value
123
+ raise ArgumentError, "#{value} <= #{div_value}"
124
+ end
125
+ ip = @atan_area_info_table[area_no]
126
+ dir = ip.base_dir
127
+ if div_value.nonzero?
128
+ if value != div_value
129
+ index = (value.to_f * AROUND / 8 / div_value).round
130
+ raise unless index < @atan_table.size
131
+ dirsub = ip.sign * @atan_table[index]
132
+ dir += dirsub
133
+ else
134
+ dir += ip.sign * AROUND / 8
135
+ end
136
+ end
137
+ # dir.modulo(AROUND) の場合は、振り子の左右の移動量が均等にならない
138
+ # dir.round.modulo(AROUND)
139
+ dir.modulo(AROUND)
140
+ end
141
+ }
142
+
143
+ class << self
144
+ [:_rsin, :_rcos, :iangle, :rsin, :rcos, :angle].each do |method|
145
+ define_method(method) do |*args, &block|
146
+ instance.send(method, *args, &block)
147
+ end
148
+ end
149
+
150
+ def one
151
+ ONE
152
+ end
153
+
154
+ def one_round
155
+ AROUND
156
+ end
157
+
158
+ # 一周をアナログ時計の単位と考えたときの角度(抽象化のため)
159
+ def clock(hour = 6, minute = 0)
160
+ t = -(1.0 / 4) + (1.0 * (hour % 12) / 12)
161
+ t += minute.fdiv(60 * 12)
162
+ t.modulo(1.0) / 1.0
163
+ end
164
+
165
+ # 一周を360度と考えたときの角度(抽象化のため)
166
+ def r0; 0; end
167
+ def r45; 1.0 / 8.0; end
168
+ def r90; 1.0 / 4.0; end
169
+ def r180; 1.0 / 2.0; end
170
+ def r270; r90 * 3; end
171
+
172
+ def degree(r)
173
+ r / 360.0
174
+ end
175
+
176
+ # 円の右側か?
177
+ def cright?(v)
178
+ !cleft?(v)
179
+ end
180
+
181
+ # 円の左側か?
182
+ def cleft?(v)
183
+ (r90...r270).include?(v % 1.0)
184
+ end
185
+
186
+ # from から to への差分
187
+ def angle_diff(from: nil, to: nil)
188
+ v = to.modulo(1.0) - from.modulo(1.0)
189
+ if v < -1.0 / 2
190
+ 1.0 + v
191
+ elsif v > 1.0 / 2
192
+ -1.0 + v
193
+ else
194
+ v
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
200
+
201
+ if $0 == __FILE__
202
+ require "pp"
203
+ # p Stylet::Magic.rcos(0)
204
+ # exit
205
+
206
+ # p Stylet::Magic._rsin(0)
207
+ # p Stylet::Magic._rcos(0)
208
+ # p Stylet::Magic.iangle(0, 0, 0, 1)
209
+ # p Stylet::Magic.rsin(0)
210
+ # p Stylet::Magic.rcos(0)
211
+ # p Stylet::Magic.iangle(320.0, 240.0, 447.990361835411, 240.429243)
212
+
213
+ # n = Stylet::AROUND
214
+ # pp (0..(n*2)).collect{|i|
215
+ # if i == 4 || true
216
+ # # r = (Stylet::Magic.one_round / n * i) % Stylet::Magic.one_round
217
+ # r = (Stylet::Magic.one_round / n * i)
218
+ # x = Stylet::Magic._rcos(r)
219
+ # y = Stylet::Magic._rsin(r)
220
+ # dir = Stylet::Magic.iangle(0, 0, x, y)
221
+ # [i, [x, y], r, dir, (r == dir)]
222
+ # end
223
+ # }.compact
224
+
225
+ # n = 32
226
+ # (0..n).collect{|i|
227
+ # if true
228
+ # r = ((Stylet::Magic.one_round.to_f * i / n) % Stylet::Magic.one_round)
229
+ # x = Stylet::Magic._rcos(r)
230
+ # y = Stylet::Magic._rsin(r)
231
+ # dir = Stylet::Magic.iangle(0, 0, x, y)
232
+ # p [x, y, r, dir, (r == dir)]
233
+ # r == dir
234
+ # end
235
+ # }
236
+
237
+ # pp (0..8).collect{|i|
238
+ # r = 1.0 / 8 * i % 1.0
239
+ # x = Stylet::Magic.rcos(r)
240
+ # y = Stylet::Magic.rsin(r)
241
+ # dir = Stylet::Magic.angle(0, 0, x, y)
242
+ # [i, r, dir, (r == dir)]
243
+ # }
244
+ # pp 8.times.collect{|i|[i, Stylet::Magic.rsin(1.0 / 8 * i)]}
245
+ # pp (0..12).collect{|i|[i, Stylet::Magic.clock(i)]}
246
+ end
@@ -0,0 +1,439 @@
1
+ require "active_support/concern"
2
+
3
+ module Stylet
4
+ module Shared
5
+ # Point.new # => [nil, nil]
6
+ # Point[1, 2] # => [1, 2]
7
+ # Point[1, 2].members # => [:x, :y]
8
+ # Point[1, 2].values # => [1, 2]
9
+ # Point[1, 2] # => [1, 2]
10
+ # Point.new(Point[1,2]) # => [1, 2]
11
+ # Point[Point[1,2]] # => [1, 2]
12
+ # Vector.new.to_h # => {:x=>0.0, :y=>0.0}
13
+ # Vector.new(1, 2).to_h # => {:x=>1, :y=>2}
14
+ def initialize(*args)
15
+ if args.empty?
16
+ args = [0.0] * members.size
17
+ elsif args.size == 1 && args.first.respond_to?(:values)
18
+ args = args.first.values
19
+ end
20
+ super(*args)
21
+ end
22
+
23
+ # Vector.zero.inspect # => "[0.0, 0.0]"
24
+ def inspect
25
+ values.inspect
26
+ end
27
+
28
+ # Vector.zero.to_s # => "[0.0, 0.0]"
29
+ def to_s
30
+ values.inspect
31
+ end
32
+ end
33
+
34
+ class Point2 < Struct.new(:x, :y)
35
+ include Shared
36
+ end
37
+
38
+ class Point3 < Struct.new(:x, :y, :z)
39
+ include Shared
40
+ end
41
+
42
+ Point = Point2
43
+
44
+ class ZeroVectorError < StandardError; end
45
+
46
+ module BasicVector
47
+ extend ActiveSupport::Concern
48
+
49
+ included do
50
+ end
51
+
52
+ class_methods do
53
+ # ゼロベクトルを返す
54
+ # Vector.zero.to_a # => [0.0, 0.0]
55
+ def zero
56
+ new(*Array.new(members.size) { 0.0 })
57
+ end
58
+
59
+ # メンバーが 1.0 のベクトルを返す
60
+ # Vector.one.to_a # => [1.0, 1.0]
61
+ def one
62
+ new(*Array.new(members.size) { 1.0 })
63
+ end
64
+
65
+ # ランダムを返す
66
+ # Vector.rand # => [0.017480344343668852, 0.8764385584061907]
67
+ # Vector.rand(3) # => [2, 2]
68
+ # Vector.rand(3..4) # => [4, 3]
69
+ # Vector.rand(3.0..4) # => [3.969678485069191, 3.4220406563055144]
70
+ # Vector.rand(-2.0..2.0) # => [-1.6587999052626938, 0.5996185615991392]
71
+ def rand(*args)
72
+ if args.empty?
73
+ args = [-1.0..1.0]
74
+ end
75
+ new(*members.collect { Kernel.rand(*args) })
76
+ end
77
+
78
+ # ゼロベクトルを作ろうとすると例外を出す new
79
+ def safe_new(*args)
80
+ new(*args).tap do |obj|
81
+ raise ZeroVectorError if obj.zero?
82
+ end
83
+ end
84
+
85
+ # # 1.0 と -1.0 で構成したランダム値
86
+ # def nonzero_random_new
87
+ # new(*members.collect{Chore.nonzero_random})
88
+ # end
89
+
90
+ # 内積
91
+ def dot_product(a, b)
92
+ members.collect { |m| a.send(m) * b.send(m) }.reduce(0, :+)
93
+ end
94
+
95
+ # # 外積
96
+ # def cross_product(a, b)
97
+ # members.collect {|m|
98
+ # (members - [m]).collect {|n| a.send(m) * b.send(n) }
99
+ # }.flatten.reduce(0, :+)
100
+ # end
101
+ end
102
+
103
+ [
104
+ { :name => :add, :sym => :+ },
105
+ { :name => :sub, :sym => :- },
106
+ ].each do |attr|
107
+ define_method(attr[:name]) do |o|
108
+ unless o.is_a? self.class
109
+ o = self.class.new(*o.to_ary)
110
+ end
111
+ self.class.new(*members.collect { |m| Float(send(m)).send(attr[:sym], o.send(m)) })
112
+ end
113
+ define_method("#{attr[:name]}!") do |*args|
114
+ replace(send(attr[:name], *args))
115
+ end
116
+ alias_method attr[:sym], attr[:name]
117
+ end
118
+
119
+ [
120
+ { :name => :scale, :sym => :* },
121
+ { :name => :div, :sym => :/ },
122
+ ].each do |attr|
123
+ define_method(attr[:name]) { |*args| apply(attr[:sym], *args) }
124
+ define_method("#{attr[:name]}!") { |*args| apply!(attr[:sym], *args) }
125
+ alias_method attr[:sym], attr[:name]
126
+ end
127
+
128
+ alias mul scale
129
+ alias mul! scale!
130
+
131
+ # Vector.rand.round.to_a # => [1, 0]
132
+ # Vector.rand.round(2).to_a # => [0.37, 0.92]
133
+ # Vector.rand.floor.to_a # => [0, 0]
134
+ # Vector.rand.ceil.to_a # => [1, 1]
135
+ # Vector.rand.truncate.to_a # => [0, 0]
136
+ [:ceil, :floor, :round, :truncate].each do |name|
137
+ define_method(name) { |*args| apply(name, *args) }
138
+ define_method("#{name}!") { |*args| apply!(name, *args) }
139
+ end
140
+
141
+ # メンバーだけ更新(主に内部用)
142
+ #
143
+ # v = Vector.new
144
+ # v.object_id # => 70228805905160
145
+ # v.replace(Vector.rand) # => [-0.5190386805455354, -0.5679474000175717]
146
+ # v.object_id # => 70228805905160
147
+ #
148
+ def replace(other)
149
+ tap { members.each { |m| send("#{m}=", other.send(m)) } }
150
+ end
151
+
152
+ # 単位ベクトル化
153
+ # Vector.one.normalize # => [0.7071067811865475, 0.7071067811865475]
154
+ def normalize
155
+ raise ZeroVectorError if zero?
156
+ c = magnitude
157
+ self.class.safe_new(*values.collect { |v| Float(v) / c })
158
+ end
159
+
160
+ def normalize!
161
+ replace(normalize)
162
+ end
163
+
164
+ # ベクトルの大きさを返す
165
+ # Vector.one.magnitude # => 1.4142135623730951
166
+ def magnitude
167
+ Math.sqrt(magnitude_sq)
168
+ rescue Errno::EDOM => error
169
+ warn "(p - p).magnitude と書いているコードがあるはず。それをやると sqrt(0) になるので if p != p を入れよう: #{values.inspect}"
170
+ raise error
171
+ end
172
+
173
+ def magnitude_sq
174
+ values.collect { |v| v**2 }.inject(0, &:+)
175
+ end
176
+
177
+ alias length magnitude
178
+ alias length_sq magnitude_sq
179
+
180
+ # 反対方向のベクトルを返す
181
+ # Vector.one.reverse # => [-1.0, -1.0]
182
+ def reverse
183
+ self.class.new(*values.collect(&:-@))
184
+ end
185
+
186
+ # -Vector.one # => [-1.0, -1.0]
187
+ alias -@ reverse
188
+
189
+ # 内積
190
+ # Vector[1, 0].dot_product(Vector[1, 0]) # => 1.0
191
+ # Vector[1, 0].dot_product(Vector[-1, 0]) # => -1.0
192
+ def dot_product(other)
193
+ self.class.send(__method__, self, other)
194
+ end
195
+
196
+ # 相手との距離を取得
197
+ # Vector.zero.distance_to(Vector.one) # => 1.4142135623730951
198
+ def distance_to(target)
199
+ (target - self).magnitude
200
+ end
201
+
202
+ # 0ベクトルか?
203
+ # Vector.zero.zero? # => true
204
+ def zero?
205
+ values.all?(&:zero?)
206
+ end
207
+
208
+ # 0ベクトルではない?
209
+ # Vector.one.nonzero? # => true
210
+ def nonzero?
211
+ !zero?
212
+ end
213
+
214
+ private
215
+
216
+ def apply(method, *args)
217
+ self.class.new(*apply_values(method, *args))
218
+ end
219
+
220
+ def apply!(method, *args)
221
+ replace(apply(method, *args))
222
+ end
223
+
224
+ def apply_values(method, *args)
225
+ members.collect { |m| Float(send(m)).send(method, *args) }
226
+ end
227
+ end
228
+
229
+ #
230
+ # ベクトル
231
+ #
232
+ # 移動制限のつけ方
233
+ #
234
+ # if vec.magnitude > n
235
+ # vec.normalize.scale(n)
236
+ # end
237
+ #
238
+ # 摩擦
239
+ #
240
+ # vec.scale(0.9)
241
+ #
242
+ class Vector < Point
243
+ include BasicVector
244
+
245
+ # 外積
246
+ # x1*y2-x2*y1 = |v1||v2|sin(θ)
247
+ def self.cross_product(a, b)
248
+ a.x * b.y - b.x * a.y
249
+ end
250
+
251
+ def cross_product(b)
252
+ self.class.cross_product(self, b)
253
+ end
254
+
255
+ # 方向ベクトル
256
+ #
257
+ # これを使うと次のように簡単に書ける
258
+ # cursor.x += Stylet::Magic.rcos(dir) * speed
259
+ # cursor.y += Stylet::Magic.rsin(dir) * speed
260
+ # ↓
261
+ # cursor += Stylet::Magic.angle_at(dir) * speed
262
+ #
263
+ def self.angle_at(x, y = x)
264
+ new(Magic.rcos(x), Magic.rsin(y))
265
+ end
266
+
267
+ # 反射ベクトルの取得
268
+ #
269
+ # s: スピードベクトル
270
+ # n: 法線ベクトル
271
+ #
272
+ # 定石
273
+ # 速度ベクトル += 速度ベクトル.reflect(法線ベクトル).scale(反射係数)
274
+ # @speed += @speed.reflect(@normal).scale(0.5)
275
+ #
276
+ # 反射係数
277
+ # ×1.5: 謎の力によってありえない反射をする
278
+ # ◎1.0: 摩擦なし(標準)
279
+ # ◎0.8: 少し滑る
280
+ # ○0.6: かなり滑ってほんの少しだけ反射する
281
+ # ○0.5: 線に沿って滑る
282
+ # ×0.4: 線にわずかにめり込んだまま滑る
283
+ # ○0.0: 線に沿って滑る(突き抜けるかと思ったけど滑る)
284
+ #
285
+ # hakuhin.jp/as/collide.html#COLLIDE_02 の方法だと x + t * n.x * 2.0 * 0.8 と一気に書いていたけど
286
+ # メソッド化するときには分解した方がわかりやすそうなのでこうした。
287
+ #
288
+ # 参考: 反射ベクトルと壁ずりベクトル
289
+ # http://marupeke296.com/COL_Basic_No5_WallVector.html
290
+ #
291
+ def reflect(n)
292
+ slide(n) * 2.0
293
+ end
294
+
295
+ # スライドベクトル
296
+ #
297
+ # self: スピードベクトル
298
+ # n: 法線ベクトル
299
+ #
300
+ def slide(n)
301
+ t = -(n.x * x + n.y * y) / (n.x**2 + n.y**2)
302
+ n * t
303
+ end
304
+
305
+ # p: 現在地点
306
+ # s: スピードベクトル
307
+ # a: 線の片方
308
+ # n: 法線ベクトル
309
+ #
310
+ # としたとき何倍したら線にぶつかるか
311
+ #
312
+ # 式の意味がまだ理解できてない
313
+ #
314
+ def self.collision_power_scale(p, s, a, n)
315
+ d = -(a.x * n.x + a.y * n.y)
316
+ -(n.x * p.x + n.y * p.y + d) / (n.x * s.x + n.y * s.y)
317
+ end
318
+
319
+ # 法線ベクトルの取得(方法1)
320
+ # どう見ても遅い
321
+ def slowly_normal(t)
322
+ a = angle(t) + Magic.r90
323
+ self.class.new(Magic.rcos(a), Magic.rsin(a))
324
+ end
325
+
326
+ # 法線ベクトルの取得(方法2)
327
+ def normal(t)
328
+ dx = t.x - x
329
+ dy = t.y - y
330
+ self.class.new(-dy, dx)
331
+ end
332
+
333
+ # 法線ベクトルの取得
334
+ def prep
335
+ self.class.new(-y, x)
336
+ end
337
+
338
+ #
339
+ # 相手の方向を取得
340
+ #
341
+ def angle_to(target)
342
+ (target - self).angle
343
+ end
344
+
345
+ # ベクトルから角度に変換
346
+ #
347
+ # Math.atan2(y, x) * 180 / Math.PI に相当する
348
+ #
349
+ def angle
350
+ Magic.angle(0, 0, x, y)
351
+ end
352
+
353
+ # 線分 A B の距離 1.0 をしたとき途中の位置ベクトルを取得
354
+ #
355
+ # a と b の位置が同じ場合いろいろおかしくなる
356
+ #
357
+ def self.pos_vector_ratio(a, b, rate)
358
+ a + angle_at(a.angle_to(b)) * (a.distance_to(b) * rate) # FIXME: ダメなコード
359
+ end
360
+
361
+ # 指定の角度だけ回転する
362
+ #
363
+ # 自分で最初に考えた方法
364
+ #
365
+ def rotate(a)
366
+ self.class.angle_at(angle + a) * magnitude
367
+ end
368
+
369
+ def rotate!(*args)
370
+ replace(rotate(*args))
371
+ end
372
+
373
+ # 指定の角度だけ回転する(方法2)
374
+ # 他のいくつかのライブラリで使われている方法。
375
+ def rotate2(a)
376
+ tx = (x * Magic.rcos(a)) - (y * Magic.rsin(a))
377
+ ty = (x * Magic.rsin(a)) + (y * Magic.rcos(a))
378
+ self.class.new(tx, ty)
379
+ end
380
+
381
+ def rotate2!(*args)
382
+ replace(rotate2(*args))
383
+ end
384
+
385
+ def to_2dv
386
+ self
387
+ end
388
+
389
+ def to_3dv
390
+ Vector3.new(*values, 0)
391
+ end
392
+ end
393
+
394
+ class Vector3 < Point3
395
+ include BasicVector
396
+
397
+ def to_2dv
398
+ Vector.new(*values.take(2))
399
+ end
400
+
401
+ def to_3dv
402
+ self
403
+ end
404
+ end
405
+ end
406
+
407
+ if $0 == __FILE__
408
+ # Stylet::Vector.rand * 2
409
+
410
+ p0 = Stylet::Vector.new(1, 1)
411
+ p1 = Stylet::Vector.new(1, 1)
412
+ p(p0 + p1)
413
+ p(p0.add(p1))
414
+ p(p0.add!(p1))
415
+ p(p0)
416
+ p(Stylet::Vector.new(3, 4).magnitude)
417
+ p(Stylet::Vector.new(3, 4).normalize.scale(5))
418
+ p(-Stylet::Vector.new(3, 4))
419
+
420
+ # p0 = Stylet::Vector.new(1, 1)
421
+ # p1 = Stylet::Vector.new(1, 1)
422
+ # p(p0 == p1)
423
+ # p p0
424
+ # p p0 + p1
425
+ # p p0.add(p1)
426
+ # p p0
427
+ # p p0.add!(p1)
428
+ # p p0
429
+ #
430
+ # p0 = Stylet::Vector.zero
431
+ # p p0.normalize
432
+ end
433
+ # >> [2.0, 2.0]
434
+ # >> [2.0, 2.0]
435
+ # >> [2.0, 2.0]
436
+ # >> [2.0, 2.0]
437
+ # >> 5.0
438
+ # >> [3.0, 4.0]
439
+ # >> [-3, -4]
@@ -0,0 +1,3 @@
1
+ module StyletSupport
2
+ VERSION = "0.0.5"
3
+ end
@@ -0,0 +1,20 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require "stylet_support/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "stylet_support"
6
+ s.version = StyletSupport::VERSION
7
+ s.summary = "Vector library"
8
+ s.description = "Vector library"
9
+ s.author = "akicho8"
10
+ s.homepage = "http://github.com/akicho8/stylet_support"
11
+ s.email = "akicho8@gmail.com"
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {s,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
16
+ s.require_paths = ["lib"]
17
+
18
+ s.add_dependency "activesupport"
19
+ s.add_development_dependency "test-unit"
20
+ end
@@ -0,0 +1,16 @@
1
+ require "test_helper"
2
+
3
+ class TestEtc < Test::Unit::TestCase
4
+ test "all" do
5
+ assert { Stylet::Chore.clamp(0.5) == 0.5 }
6
+
7
+ assert { Stylet::Chore.clamp(0.5) == 0.5 }
8
+ assert { Stylet::Chore.max_clamp(1.5) == 1.0 }
9
+ assert { Stylet::Chore.min_clamp(-0.5) == 0.0 }
10
+ assert { Stylet::Chore.abs_clamp(-1.5) == -1.0 }
11
+
12
+ assert { Stylet::Chore.shortest_angular_difference(0.2, 0.8).round(3) == 0.4 }
13
+ assert { Stylet::Chore.shortest_angular_difference(0.3, 0.8).round(3) == -0.5 }
14
+ assert { Stylet::Chore.shortest_angular_difference(0.4, 0.8).round(3) == -0.4 }
15
+ end
16
+ end
@@ -0,0 +1,6 @@
1
+ require 'test/unit'
2
+
3
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
4
+ require 'stylet_support'
5
+
6
+ include Stylet
@@ -0,0 +1,66 @@
1
+ require "test_helper"
2
+
3
+ class TestMagic < Test::Unit::TestCase
4
+ sub_test_case "整数系" do
5
+ test "sin/cos" do
6
+ assert_equal 0, Magic._rsin(0)
7
+ assert_equal Magic.one, Magic._rcos(0)
8
+ end
9
+
10
+ test "sinとcosとatan2の整合性確認" do
11
+ n = 64
12
+ r = (0..n).collect {|i|
13
+ r = (Magic.one_round * i / n) % Magic.one_round
14
+ x = Magic._rcos(r)
15
+ y = Magic._rsin(r)
16
+ dir = Magic.iangle(0, 0, x, y)
17
+ r == dir
18
+ }.all?
19
+
20
+ assert { r }
21
+ end
22
+
23
+ test "二点間の角度を求める" do
24
+ assert_equal Magic.one_round / 4, Magic.iangle(0, 0, 0, 1)
25
+ end
26
+ end
27
+
28
+ sub_test_case "一周も角度も小数で表す系" do
29
+ test "sin/cos" do
30
+ assert { Magic.rsin(0) == 0.0 }
31
+ assert { Magic.rcos(0) == 1.0 }
32
+ end
33
+
34
+ test "sinとcosとatan2の整合性確認" do
35
+ n = 32
36
+ r = (0..n).collect {|i|
37
+ r = 1.0 * i / n % 1.0
38
+ x = Magic.rcos(r)
39
+ y = Magic.rsin(r)
40
+ dir = Magic.angle(0, 0, x, y)
41
+ r == dir
42
+ }.all?
43
+ assert { r }
44
+ end
45
+ end
46
+
47
+ test "時計の時で方向指定" do
48
+ assert { Magic.clock(0) == 0.75 }
49
+ assert { Magic.clock(3) == 0.0 }
50
+ assert { Magic.clock(6) == 0.25 }
51
+ assert { Magic.clock(9) == 0.5 }
52
+ assert { Magic.clock(12) == 0.75 }
53
+ end
54
+
55
+ test "方向を抽象化" do
56
+ assert { Magic.r0 == Magic.clock(3) }
57
+ assert { Magic.r45 * 10000 == (Magic.clock(4, 30) * 10000).round }
58
+ assert { Magic.r90 == Magic.clock(6) }
59
+ assert { Magic.r180 == Magic.clock(9) }
60
+ end
61
+
62
+ test "左右どちらにいるか?" do
63
+ assert { Magic.cright?(Magic.clock(3)) }
64
+ assert { Magic.cleft?(Magic.clock(9)) }
65
+ end
66
+ end
@@ -0,0 +1,69 @@
1
+ require "test_helper"
2
+
3
+ class TestVector < Test::Unit::TestCase
4
+ setup do
5
+ @p0 = Vector.new(10, 20)
6
+ @p1 = Vector.new(100, 200)
7
+ @obj = Vector.new(3, 4)
8
+ end
9
+
10
+ test "ランダム" do
11
+ Vector.rand
12
+ end
13
+
14
+ sub_test_case "加減演算" do
15
+ test "加減算" do
16
+ assert { (@obj + @obj) == Vector.new(6, 8) }
17
+ assert { (@obj - @obj) == Vector.zero }
18
+ end
19
+
20
+ test "右辺は配列でも可" do
21
+ assert { (@obj + [3, 4]) == Vector.new(6, 8) }
22
+ assert { (@obj - [3, 4]) == Vector.zero }
23
+ end
24
+ end
25
+
26
+ test "スケーリング" do
27
+ assert { @obj.scale(2) == Vector.new(6, 8) }
28
+ assert { (@obj * 2) == Vector.new(6, 8) }
29
+ assert { (@obj * 0.5) == Vector.new(1.5, 2.0) }
30
+ end
31
+
32
+ test "正規化" do
33
+ assert { Vector.new(3, 4).normalize == Vector.new(0.6, 0.8) }
34
+ end
35
+
36
+ sub_test_case "破壊的メソッド" do
37
+ test "add!" do
38
+ @p0.add!(@p1)
39
+ assert { @p0 == Vector.new(110, 220) }
40
+ end
41
+
42
+ test "sub!" do
43
+ @p0.sub!(@p1)
44
+ assert { @p0 == Vector.new(-90, -180) }
45
+ end
46
+
47
+ test "scale!" do
48
+ @p0.scale!(2)
49
+ assert { @p0 == Vector.new(20, 40) }
50
+ end
51
+
52
+ test "normalize!" do
53
+ @p0.normalize!
54
+ @p0
55
+ end
56
+ end
57
+
58
+ test "長さ" do
59
+ assert { Vector.new(3, 4).magnitude == 5 }
60
+ end
61
+
62
+ test "距離" do
63
+ assert { @p0.distance_to(@p1).to_i == 201 }
64
+ end
65
+
66
+ test "方向" do
67
+ assert { @p0.angle_to(@p1) < 1.0 }
68
+ end
69
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stylet_support
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - akicho8
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-07-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: test-unit
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Vector library
42
+ email: akicho8@gmail.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - ".gitignore"
48
+ - ".travis.yml"
49
+ - Gemfile
50
+ - README.org
51
+ - Rakefile
52
+ - examples/0100_vector.rb
53
+ - examples/setup.rb
54
+ - lib/stylet_support.rb
55
+ - lib/stylet_support/chore.rb
56
+ - lib/stylet_support/magic.rb
57
+ - lib/stylet_support/vector.rb
58
+ - lib/stylet_support/version.rb
59
+ - stylet_support.gemspec
60
+ - test/test_etc.rb
61
+ - test/test_helper.rb
62
+ - test/test_magic.rb
63
+ - test/test_vector.rb
64
+ homepage: http://github.com/akicho8/stylet_support
65
+ licenses: []
66
+ metadata: {}
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 2.7.6
84
+ signing_key:
85
+ specification_version: 4
86
+ summary: Vector library
87
+ test_files: []