geom_craft 0.0.1

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,17 @@
1
+ require "geom_craft"
2
+
3
+ RandNorm = GeomCraft::RandNorm
4
+ V = GeomCraft::Vec2
5
+ Rect = GeomCraft::Rect
6
+ Range2 = GeomCraft::Range2
7
+
8
+ Kernel.module_eval do
9
+ private
10
+ def rand_norm(...)
11
+ GeomCraft::RandNorm.call(...)
12
+ end
13
+ end
14
+
15
+ if $0 == __FILE__
16
+ rand_norm # => 0.2171834669473525
17
+ end
@@ -0,0 +1,610 @@
1
+ # https://zenn.dev/megeton/articles/b407350ad51562
2
+ # https://docs.rs/glam/latest/glam/f32/struct.Vec2.html
3
+
4
+ require "forwardable"
5
+ require "geom_craft/rand_norm"
6
+
7
+ module GeomCraft
8
+ class Vec2
9
+ # https://github.com/godotengine/godot/blob/44e399ed5fa895f760b2995e59788bdb49782666/core/math/math_defs.h#L53
10
+ UNIT_EPSILON = 0.00001
11
+ private_constant :UNIT_EPSILON
12
+
13
+ class << self
14
+ def [](...)
15
+ new(...)
16
+ end
17
+
18
+ def splat(v); new(v, v); end
19
+
20
+ def zero_element; 0.0; end
21
+ def one_element; 1.0; end
22
+
23
+ def zero; splat(zero_element); end
24
+ def one; splat(one_element); end
25
+ def neg_one; splat(-one_element); end
26
+
27
+ def max; splat(Float::INFINITY); end
28
+ def min; splat(-Float::INFINITY); end
29
+
30
+ def infinity; max; end
31
+ def neg_infinity; min; end
32
+
33
+ def nan; splat(0.0 / 0.0); end
34
+
35
+ def x; new(one_element, zero_element); end
36
+ def y; new(zero_element, one_element); end
37
+ def neg_x; new(-one_element, zero_element); end
38
+ def neg_y; new(zero_element, -one_element); end
39
+
40
+ def left; new(-one_element, zero_element); end
41
+ def right; new(+one_element, zero_element); end
42
+ def up; new(zero_element, -one_element); end
43
+ def down; new(zero_element, +one_element); end
44
+
45
+ def axes; [x, y]; end
46
+
47
+ def rand(...); new(Kernel.rand(...), Kernel.rand(...)); end
48
+ def rand_norm(...); new(RandNorm.call(...), RandNorm.call(...)); end # mu: 0, sigma: 1.0, random: Random.new
49
+ end
50
+
51
+ extend Forwardable
52
+ def_delegators :to_a, :each
53
+ def_delegators :to_a, :==, :hash, :<=>;
54
+ def_delegators :to_a, :to_ary, :sum, :[]
55
+
56
+ include Enumerable
57
+
58
+ attr_accessor :x, :y
59
+
60
+ def initialize(x = self.class.zero_element, y = self.class.zero_element)
61
+ @x = x
62
+ @y = y
63
+ end
64
+
65
+ def eql?(other)
66
+ self == other
67
+ end
68
+
69
+ def to_a
70
+ [x, y]
71
+ end
72
+
73
+ def as_json
74
+ { x: x, y: y }
75
+ end
76
+
77
+ def to_s
78
+ "(#{x}, #{y})"
79
+ end
80
+
81
+ def to_h(prefix: "", suffix: "")
82
+ {
83
+ "#{prefix}x#{suffix}".to_sym => x,
84
+ "#{prefix}y#{suffix}".to_sym => y,
85
+ }
86
+ end
87
+
88
+ def inspect
89
+ to_s
90
+ end
91
+
92
+ # def scale(s)
93
+ # self * s
94
+ # end
95
+
96
+ def min(other); self.class.new([x, other.x].min, [y, other.y].min); end
97
+ def max(other); self.class.new([x, other.x].max, [y, other.y].max); end
98
+
99
+ def clamp(min, max)
100
+ min.cmple(max).all? or raise "clamp: expected min <= max"
101
+ max(min).min(max)
102
+ end
103
+
104
+ # https://github.com/godotengine/godot/blob/44e399ed5fa895f760b2995e59788bdb49782666/core/math/vector2.cpp#L138
105
+ def snapped(step)
106
+ self.class.new(Math.snapped(x, step.x), Math.snapped(y, step.y))
107
+ end
108
+
109
+ def min_element; to_a.min; end
110
+ def max_element; to_a.max; end
111
+
112
+ def cmpeq(other); self.class.new(x == other.x, y == other.y); end
113
+ def cmpne(other); self.class.new(x != other.x, y != other.y); end
114
+ def cmpge(other); self.class.new(x >= other.x, y >= other.y); end
115
+ def cmpgt(other); self.class.new(x > other.x, y > other.y); end
116
+ def cmple(other); self.class.new(x <= other.x, y <= other.y); end
117
+ def cmplt(other); self.class.new(x < other.x, y < other.y); end
118
+
119
+ def abs
120
+ self.class.new(x.abs, y.abs)
121
+ end
122
+
123
+ def sign
124
+ self.class.new(
125
+ x.negative? ? -1 : 1,
126
+ y.negative? ? -1 : 1,
127
+ )
128
+ end
129
+
130
+ def signum
131
+ sign
132
+ end
133
+
134
+ def copysign(other)
135
+ abs * other.signum
136
+ end
137
+
138
+ def finite?
139
+ all?(&:finite?)
140
+ end
141
+
142
+ def nan?
143
+ any?(&:nan?)
144
+ end
145
+
146
+ def nan_mask
147
+ self.class.new(x.nan?, y.nan?)
148
+ end
149
+
150
+ def zero?; all?(&:zero?); end
151
+ def nonzero?; all?(&:nonzero?); end
152
+
153
+ def length_squared; dot(self); end
154
+ def length; Math.sqrt(dot(self)); end
155
+
156
+ def norm; length; end
157
+ def mag; length; end
158
+ def magnitude; length; end
159
+
160
+ def length_recip; 1.0 / length; end
161
+
162
+ def distance_to(other); (other - self).length; end
163
+ def distance_squared_to(other); (other - self).length_squared; end
164
+
165
+ ################################################################################
166
+
167
+ def normalize
168
+ normalized = self * length_recip
169
+ normalized.finite? or raise
170
+ normalized
171
+ end
172
+
173
+ def try_normalize
174
+ rcp = length_recip
175
+ if rcp.finite? && rcp.positive?
176
+ self * rcp
177
+ end
178
+ end
179
+
180
+ def normalize_or_zero
181
+ try_normalize || self.class.zero
182
+ end
183
+
184
+ def normalized?
185
+ (length_squared - 1.0).abs < UNIT_EPSILON
186
+ end
187
+
188
+ ################################################################################
189
+
190
+ def project_onto(other)
191
+ other_len_sq_rcp = 1.0 / other.length_squared
192
+ other_len_sq_rcp.finite? or raise
193
+ other * dot(other) * other_len_sq_rcp
194
+ end
195
+
196
+ def reject_from(other)
197
+ self - project_onto(other)
198
+ end
199
+
200
+ def project_onto_normalized(other)
201
+ other.normalized? or raise
202
+ other * dot(other)
203
+ end
204
+
205
+ def reject_from_normalized(other)
206
+ self - project_onto_normalized(other)
207
+ end
208
+
209
+ ################################################################################
210
+
211
+ def slide(normal)
212
+ self - project_onto_normalized(normal)
213
+ end
214
+
215
+ def bounce(normal)
216
+ self - project_onto_normalized(normal) * 2
217
+ end
218
+
219
+ def reflect(normal)
220
+ project_onto_normalized(normal) * 2 - self
221
+ end
222
+
223
+ ################################################################################
224
+
225
+ def round(...); self.class.new(x.round(...), y.round(...)); end
226
+ def floor(...); self.class.new(x.floor(...), y.floor(...)); end
227
+ def ceil(...); self.class.new(x.ceil(...), y.ceil(...)); end
228
+ def truncate(...); self.class.new(x.truncate(...), y.truncate(...)); end
229
+ def trunc(...); truncate(...); end
230
+ def fract; self - floor; end
231
+
232
+ ################################################################################
233
+
234
+ def exp
235
+ self.class.new(Math.exp(x), Math.exp(y))
236
+ end
237
+
238
+ def pow(...)
239
+ self.class.new(x.pow(...), y.pow(...))
240
+ end
241
+
242
+ def recip
243
+ self.class.new(1.0 / x, 1.0 / y)
244
+ end
245
+
246
+ def lerp(other, s)
247
+ self + (other - self) * s
248
+ end
249
+
250
+ def mix(...)
251
+ lerp(...)
252
+ end
253
+
254
+ def abs_diff_eq(other, max_abs_diff)
255
+ sub(other).abs.cmple(self.class.splat(max_abs_diff)).all?
256
+ end
257
+
258
+ ################################################################################
259
+
260
+ def clamp_length(min, max)
261
+ min <= max or raise
262
+ length_sq = length_squared
263
+ if length_sq < min * min
264
+ min * self / Math.sqrt(length_sq)
265
+ elsif length_sq > max * max
266
+ max * self / Math.sqrt(length_sq)
267
+ else
268
+ self
269
+ end
270
+ end
271
+
272
+ def clamp_length_max(max)
273
+ length_sq = length_squared
274
+ if length_sq > max * max
275
+ max * self / Math.sqrt(length_sq)
276
+ else
277
+ self
278
+ end
279
+ end
280
+
281
+ def clamp_length_min(min)
282
+ length_sq = length_squared
283
+ if length_sq < min * min
284
+ min * self / Math.sqrt(length_sq)
285
+ else
286
+ self
287
+ end
288
+ end
289
+
290
+ ################################################################################
291
+
292
+ class << self
293
+ def from_angle(angle)
294
+ new(Math.cos(angle), Math.sin(angle))
295
+ end
296
+ end
297
+
298
+ # https://github.com/godotengine/godot/blob/44e399ed5fa895f760b2995e59788bdb49782666/core/math/vector2.cpp#L81
299
+ def angle_to(other)
300
+ Math.atan2(cross(other), dot(other))
301
+ end
302
+
303
+ def angle_between(other)
304
+ angle_to(other)
305
+ end
306
+
307
+ # https://github.com/godotengine/godot/blob/44e399ed5fa895f760b2995e59788bdb49782666/core/math/vector2.cpp#L84
308
+ def angle_to_point(other)
309
+ (other - self).angle
310
+ end
311
+
312
+ def angle
313
+ Math.atan2(y, x)
314
+ end
315
+
316
+ def rotate(other)
317
+ # https://docs.rs/nannou_core/0.18.0/src/nannou_core/math.rs.html#92
318
+ unless other.kind_of?(self.class)
319
+ other = V.from_angle(other)
320
+ end
321
+
322
+ # https://docs.rs/glam/lait/src/glam/f32/vec2.rs.html#662
323
+ self.class.new(
324
+ x * other.x - y * other.y,
325
+ y * other.x + x * other.y,
326
+ )
327
+ end
328
+
329
+ # Experimental
330
+ def rotate_angle_add(rad)
331
+ self.class.from_angle(angle + rad) * length
332
+ end
333
+
334
+ ################################################################################
335
+
336
+ def dot(other)
337
+ x * other.x + y * other.y
338
+ end
339
+
340
+ def inner_product(...); dot(...); end
341
+
342
+ ################################################################################
343
+
344
+ def cross(other)
345
+ x * other.y - y * other.x
346
+ end
347
+
348
+ def perp_dot(...); cross(...); end
349
+ def outer_product(...); cross(...); end
350
+
351
+ ################################################################################
352
+
353
+ def perp
354
+ right90
355
+ end
356
+
357
+ def right90
358
+ self.class.new(-y, x)
359
+ end
360
+
361
+ def left90
362
+ self.class.new(y, -x)
363
+ end
364
+
365
+ ################################################################################
366
+
367
+ def add(other)
368
+ if other.kind_of?(self.class)
369
+ self.class.new(x + other.x, y + other.y)
370
+ else
371
+ self.class.new(x + other, y + other)
372
+ end
373
+ end
374
+
375
+ def sub(other)
376
+ if other.kind_of?(self.class)
377
+ self.class.new(x - other.x, y - other.y)
378
+ else
379
+ self.class.new(x - other, y - other)
380
+ end
381
+ end
382
+
383
+ def mul(other)
384
+ if other.kind_of?(self.class)
385
+ self.class.new(x * other.x, y * other.y)
386
+ else
387
+ self.class.new(x * other, y * other)
388
+ end
389
+ end
390
+
391
+ # pow and `**` are different
392
+ # float doesn't have pow, but it does have `**`.
393
+ def **(other)
394
+ if other.kind_of?(self.class)
395
+ self.class.new(x**other.x, y**other.y)
396
+ else
397
+ self.class.new(x**other, y**other)
398
+ end
399
+ end
400
+
401
+ def /(other)
402
+ if other.kind_of?(self.class)
403
+ self.class.new(x / other.x, y / other.y)
404
+ else
405
+ self.class.new(x / other, y / other)
406
+ end
407
+ end
408
+
409
+ def div(other)
410
+ if other.kind_of?(self.class)
411
+ self.class.new(x.div(other.x), y.div(other.y))
412
+ else
413
+ self.class.new(x.div(other), y.div(other))
414
+ end
415
+ end
416
+
417
+ def fdiv(other)
418
+ if other.kind_of?(self.class)
419
+ self.class.new(x.fdiv(other.x), y.fdiv(other.y))
420
+ else
421
+ self.class.new(x.fdiv(other), y.fdiv(other))
422
+ end
423
+ end
424
+
425
+ def ceildiv(other)
426
+ if other.kind_of?(self.class)
427
+ self.class.new(x.ceildiv(other.x), y.ceildiv(other.y))
428
+ else
429
+ self.class.new(x.ceildiv(other), y.ceildiv(other))
430
+ end
431
+ end
432
+
433
+ def modulo(other)
434
+ if other.kind_of?(self.class)
435
+ self.class.new(x.modulo(other.x), y.modulo(other.y))
436
+ else
437
+ self.class.new(x.modulo(other), y.modulo(other))
438
+ end
439
+ end
440
+
441
+ def +(other)
442
+ add(other)
443
+ end
444
+
445
+ def -(other)
446
+ sub(other)
447
+ end
448
+
449
+ def *(other)
450
+ mul(other)
451
+ end
452
+
453
+ def %(other)
454
+ modulo(other)
455
+ end
456
+
457
+ def coerce(other)
458
+ [self, other]
459
+ end
460
+
461
+ def -@
462
+ self.class.new(-x, -y)
463
+ end
464
+
465
+ def +@
466
+ self
467
+ end
468
+
469
+ def neg
470
+ -self
471
+ end
472
+
473
+ # def reverse
474
+ # -self
475
+ # end
476
+
477
+ # def mul_add(a, b)
478
+ # self * a + b
479
+ # end
480
+
481
+ ################################################################################
482
+
483
+ def xy; self; end
484
+ def yx; self.class.new(y, x); end
485
+ def xx; self.class.new(x, x); end
486
+ def yy; self.class.new(y, y); end
487
+
488
+ ################################################################################
489
+
490
+ def center; self * 0.5; end
491
+ def middle; self * 0.5; end
492
+
493
+ def cover?(other, padding: 0)
494
+ true &&
495
+ ((x - padding)..(x + padding)).cover?(other.x) &&
496
+ ((y - padding)..(y + padding)).cover?(other.y)
497
+ end
498
+
499
+ ################################################################################
500
+
501
+ def replace(other)
502
+ self.x, self.y = other.to_a
503
+ self
504
+ end
505
+
506
+ # def add!(other) = replace(add(other))
507
+ # def sub!(other) = replace(sub(other))
508
+ # def mul!(other) = replace(mul(other))
509
+ # def div!(other) = replace(div(other))
510
+ # def fdiv!(other) = replace(fdiv(other))
511
+ # def modulo!(other) = replace(modulo(other))
512
+
513
+ if false
514
+ prepend Module.new {
515
+ def initialize(...)
516
+ super
517
+ freeze
518
+ end
519
+ }
520
+ end
521
+ end
522
+
523
+ V = Vec2
524
+ end
525
+
526
+ if $0 == __FILE__ && ENV["ATCODER"] != "1"
527
+ V = GeomCraft::V
528
+
529
+ $LOAD_PATH.unshift("..")
530
+ require "geom_craft/core_ext"
531
+
532
+ require "rspec/autorun"
533
+
534
+ RSpec.configure do |config|
535
+ config.expect_with :test_unit
536
+ end
537
+
538
+ describe V do
539
+ it "sort" do
540
+ assert { [V[3, 4], V[1, 2]].sort == [V[1, 2], V[3, 4]] }
541
+ end
542
+
543
+ it "hash key" do
544
+ assert { V[3, 4] == V[3, 4] }
545
+ assert { V[3, 4].hash == V[3, 4].hash }
546
+ assert { V[3, 4].eql?(V[3, 4]) }
547
+ a = {V[3, 4] => true}
548
+ assert { a[V[3, 4]] }
549
+ end
550
+
551
+ it "rand_norm" do
552
+ assert { V.rand_norm.finite? }
553
+ end
554
+
555
+ describe "rotation" do
556
+ before do
557
+ @v0 = V.from_angle(45.deg_to_rad).mul(5)
558
+ end
559
+
560
+ it "rotate with radian" do
561
+ v1 = @v0.rotate(45.deg_to_rad)
562
+ assert { v1.angle.rad_to_deg.round(2) == 90.0 }
563
+ assert { v1.length == 5.0 }
564
+ end
565
+
566
+ it "rotate_angle_add" do
567
+ v1 = @v0.rotate_angle_add(45.deg_to_rad)
568
+ assert { v1.angle.rad_to_deg.round(2) == 90.0 }
569
+ assert { v1.length == 5.0 }
570
+ end
571
+ end
572
+
573
+ it "Enumerable" do
574
+ assert { V[true, true].all? }
575
+ assert { V[true, true].any? }
576
+ assert { V[false, false].none? }
577
+ assert { V.one.sum == 2 }
578
+ end
579
+
580
+ it "min, max" do
581
+ assert { V[3, 6].min(V[4, 5]) == V[3, 5] }
582
+ assert { V[3, 6].max(V[4, 5]) == V[4, 6] }
583
+ end
584
+
585
+ it "normalized?" do
586
+ assert { 1000.times.collect.all? { V.rand.normalize.normalized? } }
587
+ end
588
+
589
+ it "pow" do
590
+ assert { V[2, 3]**2 == V[4, 9] }
591
+ assert { V[2, 3].pow(2) == V[4, 9] }
592
+ end
593
+
594
+ it "**" do
595
+ assert { V[2.0, 3.0]**2.0 == V[4.0, 9.0] }
596
+ end
597
+
598
+ it "distance_to" do
599
+ a = V.zero
600
+ b = V.one
601
+ assert { a.distance_to(b) == b.distance_to(a) }
602
+ end
603
+ end
604
+ end
605
+
606
+ # >> ...........
607
+ # >>
608
+ # >> Finished in 0.02384 seconds (files took 0.06057 seconds to load)
609
+ # >> 11 examples, 0 failures
610
+ # >>
@@ -0,0 +1,3 @@
1
+ module GeomCraft
2
+ VERSION = "0.0.1"
3
+ end
data/lib/geom_craft.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "geom_craft/version"
2
+
3
+ require "geom_craft/rand_norm"
4
+ require "geom_craft/vec2"
5
+ require "geom_craft/range2"
6
+ require "geom_craft/rect"
7
+
8
+ require "geom_craft/core_ext"
@@ -0,0 +1,4 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe GeomCraft do
4
+ end
@@ -0,0 +1,6 @@
1
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
2
+ require "geom_craft"
3
+
4
+ RSpec.configure do |config|
5
+ config.expect_with :test_unit
6
+ end
@@ -0,0 +1,21 @@
1
+ require "./setup"
2
+
3
+ #+title2: ついでに面倒な角度の扱い簡単にする
4
+
5
+ # - 一周を 2π とする人間にはわかりにくいがコンピュータにはわかりやすい radian 単位
6
+ # - 一周を 360 度とする人間にはわかりやすいがコンピュータはわかりにくい degree 単位
7
+ # - 一周を 1.0 とする人間にはまぁまぁわかりやすいがコンピュータはわかりにくい単位
8
+
9
+ # これらをメソッドチェインで相互変換するため Numeric を拡張する。
10
+
11
+ Math::PI.rad_to_rad # => 3.141592653589793
12
+ Math::PI.rad_to_deg # => 180.0
13
+ Math::PI.rad_to_turn # => 0.5
14
+
15
+ 180.deg_to_rad # => 3.141592653589793
16
+ 180.deg_to_deg # => 180
17
+ 180.deg_to_turn # => 0.5
18
+
19
+ 0.5.turn_to_rad # => 3.141592653589793
20
+ 0.5.turn_to_deg # => 180.0
21
+ 0.5.turn_to_turn # => 0.5