nannou のベクトルライブラリ glam の使い方
コンストラクタ
基本
vec2(2.0, 3.0); // => Vec2(2.0, 3.0)
Vec2::new(2.0, 3.0); // => Vec2(2.0, 3.0)
全成分が同じでよい時
Vec2::splat(2.0); // => Vec2(2.0, 2.0)
他の型から生成
いろんな型を受け付ける
Vec2::from((2.0, 3.0)); // => Vec2(2.0, 3.0)
Vec2::from([2.0, 3.0]); // => Vec2(2.0, 3.0)
Vec2::from(vec2(2.0, 3.0)); // => Vec2(2.0, 3.0)
Vec2::from(Vec3::new(2.0, 3.0, 4.0)); // => Vec2(2.0, 3.0)
配列の参照から生成
Vec2::from_slice(&[2.0, 3.0]); // => Vec2(2.0, 3.0)
他の型に変換
3次元化
vec2(2.0, 3.0).extend(4.0); // => Vec3(2.0, 3.0, 4.0)
逆に2次元化
vec3(2.0, 3.0, 4.0).truncate(); // => Vec2(2.0, 3.0)
精度を変更する (D→f64 I→i32 U→u32)
vec2(2.0, 3.0).as_f64(); // => DVec2(2.0, 3.0)
vec2(2.0, 3.0).as_i32(); // => IVec2(2, 3)
vec2(2.0, 3.0).as_u32(); // => UVec2(2, 3)
配列化
vec2(2.0, 3.0).to_array(); // => [2.0, 3.0]
文字列化
vec2(2.0, 3.0).to_string(); // => "[2, 3]"
四則演算
vec2(2.0, 3.0) + 1.0; // => Vec2(3.0, 4.0)
vec2(2.0, 3.0) - 1.0; // => Vec2(1.0, 2.0)
vec2(2.0, 3.0) * 2.0; // => Vec2(4.0, 6.0)
vec2(2.0, 3.0) / 2.0; // => Vec2(1.0, 1.5)
vec2(2.0, 3.0) + vec2(4.0, 5.0); // => Vec2(6.0, 8.0)
vec2(2.0, 3.0) - vec2(4.0, 5.0); // => Vec2(-2.0, -2.0)
vec2(2.0, 3.0) * vec2(4.0, 5.0); // => Vec2(8.0, 15.0)
vec2(2.0, 3.0) / vec2(4.0, 5.0); // => Vec2(0.5, 0.6)
特徴として
Vector + 値
Vector - 値
Vector * Vector
Vector / Vector
がそのままできるのがめちゃくちゃありがたい。こうなってほしいがそのまま書ける理想形である。
その点、Ruby の標準ライブラリだった matrix は使いづらかった(小声)
require "matrix"
Vector[2, 3] + 1 rescue $! # => #<TypeError: 1 can't be coerced into Vector>
Vector[2, 3] * Vector[4, 5] rescue $! # => #<ExceptionForMatrix::ErrOperationNotDefined: Operation(*) can't be defined: Vector op Vector>
両方に1を足してほしいと考えて Vector[2, 3] + 1
と書いても受け入れてくれない。成分同士の掛け算をしてほしくて Vector[2, 3] * Vector[4, 5]
と書いても拒まれる。結局、こう書かないといけない。
require "matrix"
Vector[2, 3] + Vector[1, 1] # => Vector[3, 4]
Vector[2, 3].map2(Vector[4, 5]) {|a, b| a * b } # => Vector[8, 15]
累乗・自然対数・反転
vec2(2.0, 3.0).powf(2.0); // => Vec2(4.0, 9.0)
vec2(2.0, 3.0).exp(); // => Vec2(7.389056, 20.085537)
-vec2(2.0, 3.0); // => Vec2(-2.0, -3.0)
小数部の丸め
vec2(2.4, 3.5).floor(); // => Vec2(2.0, 3.0)
vec2(2.4, 3.5).ceil(); // => Vec2(3.0, 4.0)
vec2(2.4, 3.5).round(); // => Vec2(2.0, 4.0)
範囲補正
vec2(2.0, 5.0).clamp(vec2(3.0, 3.0), vec2(4.0, 4.0)); // => Vec2(3.0, 4.0)
-
clamp(min, max)
→(x.clamp(min.x, max.x), y.clamp(min.y, max.y)
成分を元にした変換
絶対値
vec2(-2.0, -3.0).abs(); // => Vec2(2.0, 3.0)
符号だけ
vec2(-2.0, 3.0).signum(); // => Vec2(-1.0, 1.0)
vec2(2.0, -3.0).signum(); // => Vec2(1.0, -1.0)
vec2(0.0, 0.0).signum(); // => Vec2(1.0, 1.0)
0 のときも 1.0 になるようだ
成分から自身の floor を引く
vec2(-2.4, 5.8).fract(); // => Vec2(0.5999999, 0.8000002)
-2.4 - -3 = 0.6
5.8 - 5 = 0.8
- fract は断片を表わす fraction の略と思われる
有限・非数
vec2(2.0, 3.0).is_finite(); // => true
vec2(2.0, f32::NAN).is_nan(); // => true
次のは有限成分が false で非数は true として bvec2(false, true) を返す。
vec2(2.0, f32::NAN).is_nan_mask(); // => BVec2(0x0, 0xffffffff)
どういうときに使うのかはわからない。
最大最小
単に成分が対象
vec2(2.0, 3.0).min_element(); // => 2.0
vec2(2.0, 3.0).max_element(); // => 3.0
各成分同士の最大・最小
vec2(1.0, 2.0).max(vec2(3.0, 1.0)); // => Vec2(3.0, 2.0)
vec2(1.0, 2.0).min(vec2(3.0, 1.0)); // => Vec2(1.0, 1.0)
max = (x.max(other.x), y.max(other.y))
min = (x.min(other.x), y.min(other.y))
- どちらかのベクトルを返すのではない
- 各成分毎の結果でマージされる感じになる
一致・不一致
vec2(2.0, 3.0) == vec2(2.0, 3.0); // => true
vec2(2.0, 3.0) != vec2(4.0, 5.0); // => true
小数の誤差に注意する
誤差を許容した比較
vec2(2.0, 3.0).abs_diff_eq(vec2(2.0, 3.1), 0.09); // => false
vec2(2.0, 3.0).abs_diff_eq(vec2(2.0, 3.1), 0.1); // => true
比較した結果を BVec2 型で返す
vec2(2.0, 3.0).cmpeq(vec2(2.0, 3.0)); // => BVec2(0xffffffff, 0xffffffff)
vec2(2.0, 3.0).cmpne(vec2(2.0, 3.0)); // => BVec2(0x0, 0x0)
vec2(2.0, 3.0).cmpge(vec2(2.0, 3.0)); // => BVec2(0xffffffff, 0xffffffff)
vec2(2.0, 3.0).cmpgt(vec2(2.0, 3.0)); // => BVec2(0x0, 0x0)
vec2(2.0, 3.0).cmple(vec2(2.0, 3.0)); // => BVec2(0xffffffff, 0xffffffff)
vec2(2.0, 3.0).cmplt(vec2(2.0, 3.0)); // => BVec2(0x0, 0x0)
TODO: これはどういうときに使う?
長さ(大きさ)
x**2 + y**2 → 13
vec2(2.0, 3.0).length_squared(); // => 13.0
上の結果の平方根が長さ
13_f32.sqrt(); // => 3.6055512
vec2(2.0, 3.0).length(); // => 3.6055512
1.0 / length
vec2(2.0, 3.0).length_recip(); // => 0.2773501
-
使用頻度の高い便利メソッドだけど各ライブラリでなかなか名前が統一されない
-
Unity →
magnitude
-
DirectX →
length
-
Ruby →
magnitude
r
norm
-
bevy →
length
-
Wiki →
ノルム
-
長さの大小比較であれば length_squared の結果を使えば sqrt 2回ぶん計算を省ける
-
TODO:
1.0 / length
を行う length_recip はどういうときに使う?
長さ補正
まず (5, 5) の長さは 7.07
vec2(5.0, 5.0).length(); // => 7.071068
(6, 6) の長さは 8.48 だけど最大 7.07 までとしたら (5, 5) に下げられる
vec2(6.0, 6.0).length(); // => 8.485281
vec2(6.0, 6.0).clamp_length_max(7.071068); // => Vec2(5.0, 5.0)
同様に (4, 4) の長さは 5.65 だけど最小 7.07 に達っしてないので (5, 5) に上げられる
vec2(4.0, 4.0).length(); // => 5.656854
vec2(4.0, 4.0).clamp_length_min(7.071068); // => Vec2(5.0, 5.0)
上を同時に指定する場合(5, 5) の長さは 7.07 で 7〜8 に補正なので補正されてないことがわかる
vec2(5.0, 5.0).clamp_length(7.0, 8.0); // => Vec2(5.0, 5.0)
距離
vec2(10.0, 10.0).distance(vec2(12.0, 13.0)); // => 3.6055512
(vec2(12.0, 13.0) - vec2(10.0, 10.0)).length(); // => 3.6055512
vec2(10.0, 10.0).distance_squared(vec2(12.0, 13.0)); // => 13.0
- 引数を原点と考えるだけであとは length と同じ
a.distance(b) → (b - a).length
- こちらも比較だけなら sqrt しないぶんだけ distance_squared の方が速い
正規化
基本
vec2(2.0, 3.0).normalize(); // => Vec2(0.5547002, 0.8320503)
正規化すると長さが 1.0 になる
vec2(2.0, 3.0).normalize().length(); // => 1.0
ので方向を維持したまま長さを変更できる
vec2(2.0, 3.0).normalize() * 1.5; // => Vec2(0.8320503, 1.2480755)
正規化してある?
vec2(2.0, 3.0).is_normalized(); // => false
vec2(2.0, 3.0).normalize().is_normalized(); // => true
(x / length, y / length)
-
is_normalized
はnormalize().length() == 1.0
相当
正規化 0ベクトルの扱い
vec2(2.0, 3.0).normalize(); // => Vec2(0.5547002, 0.8320503)
vec2(0.0, 0.0).normalize(); // => Vec2(NaN, NaN)
vec2(2.0, 3.0).try_normalize(); // => Some(Vec2(0.5547002, 0.8320503))
vec2(0.0, 0.0).try_normalize(); // => None
vec2(2.0, 3.0).normalize_or_zero(); // => Vec2(0.5547002, 0.8320503)
vec2(0.0, 0.0).normalize_or_zero(); // => Vec2(0.0, 0.0)
- 0ベクトルの場合 length が 0 になるので 0 除算することになってしまう
- 3種類あってそれぞれ戻値の形式が異なる
- 0ベクトルは想定外のときもあれば0ベクトルのままにしといてもらいたいときもあって、それを呼び出し側で対応しているとコードが煩雑になってしまうので、柔軟に選択できるのはありがたい
正規化っぽい何か
vec2(2.0, 3.0).recip(); // => Vec2(0.5, 0.33333334)
-
(1.0 / x, 1.0 / y)
を行う - recip は相互的なを表わす reciprocal の略と思われる
- 何に使うのかはわからない
線形補間
vec2(10.0, 10.0).lerp(vec2(30.0, 30.0), -0.5); // => Vec2(0.0, 0.0)
vec2(10.0, 10.0).lerp(vec2(30.0, 30.0), 0.0); // => Vec2(10.0, 10.0)
vec2(10.0, 10.0).lerp(vec2(30.0, 30.0), 0.5); // => Vec2(20.0, 20.0)
vec2(10.0, 10.0).lerp(vec2(30.0, 30.0), 1.0); // => Vec2(30.0, 30.0)
vec2(10.0, 10.0).lerp(vec2(30.0, 30.0), 1.5); // => Vec2(40.0, 40.0)
-
a.lerp(b, s)
→a + (b - a) * s
- なので
0.0
のとき(10,10)
で1.0
のとき(30,30)
になっているのがわかる
角度
右下・真下・左下
vec2(2.0, 2.0).angle(); // => 0.7853982
vec2(0.0, 2.0).angle(); // => 1.5707963
vec2(-2.0, 2.0).angle(); // => 2.3561945
以下と同じ
2.0.atan2(2.0); // => 0.7853981633974483
2.0.atan2(0.0); // => 1.5707963267948966
2.0.atan2(-2.0); // => 2.356194490192345
方向から求める
PI * 0.25; // => 0.7853982
PI * 0.50; // => 1.5707964
PI * 0.75; // => 2.3561945
-
vec2(x, y).angle()
→y.atan2(x)
-
PI
を使うときはuse nannou::prelude::*;
とする - 一周は
PI * 2
角度 2点間
vec2(2.0, 2.0).angle_between(vec2(0.0, 2.0)); // => 0.7853982
冗長に書くと
vec2(0.0, 2.0).angle() - vec2(2.0, 2.0).angle(); // => 0.78539807
-
a.angle_between(b)
→b.angle - a.angle
相当 -
右下.angle_between(真下)
なのでPI * 0.25
→ 0.7853982
回転
vec2(2.0, 0.0).rotate(PI * 0.5).round(); // => Vec2(-0.0, 2.0)
- 右向き
(2,0)
を90度
回転すると真下(0,2)
になったことがわかる
法線
vec2(2.0, 3.0).perp(); // => Vec2(-3.0, 2.0)
本当に回転するとこう
vec2(2.0, 3.0).rotate(2.0 * PI * 90.0 / 360.0); // => Vec2(-3.0, 1.9999999)
- 長さを維持したまま90度回転する
-
(-y, x)
と同じなので一瞬で求まる - perp は垂直を表わす perpendicular の略と思われる
- 法線は長さ1なので perp のことを法線と言うのは違うのかもしれない
内積
vec2(2.0, 3.0).dot(vec2(4.0, 5.0)); // => 23.0
2 * 4 + 3 * 5; // => 23
-
a.dot(b)
→a.x * b.x + a.y * b.y
-
a.dot(b.normalize())
で a を b に投射したときの長さが求まる
外積
vec2(2.0, 3.0).perp_dot(vec2(4.0, 5.0)); // => -2.0
2 * 5 - 4 * 3; // => -2
-
a.perp_dot(b)
→a.x * b.y - b.x * a.y
投影
let A = vec2(2.0, 5.0);
let B = vec2(6.0, 3.0);
let BN = B.normalize();
AをBに投影する
A.project_onto(B); // => Vec2(3.6000001, 1.8000001)
これはBを地面と考えたとき太陽視点でのAの影またはBを地面として垂直にジャンプしたAの着地点に相当する。
project_onto がやっていることは、
- 地面の方を正規化して内積を求めると影の長さが求まる
- それを地面の方向に伸ばす(正規化したBだけスケールする)と位置が求まる
let length = A.dot(BN);
length; // => 4.0249224
length * BN; // => Vec2(3.6, 1.8)
投影先が正規化されていればその処理を省けるので専用のメソッドがある
A.project_onto_normalized(BN); // => Vec2(3.6, 1.8)
垂直方向への投影
A.reject_from(B); // => Vec2(-1.6000001, 3.1999998)
これはBを地面と考えたときの真横から強い光を当てて壁にできる影またはBを地面と考えたときのジャンプしたAの高さに相当する。
これは単に A - 着地点(AからBへの投射)
でも求まる。
A - A.project_onto(B); // => Vec2(-1.6000001, 3.1999998)
上の投影と同様に地面が正規化されている版もある。
A.reject_from_normalized(B.normalize()); // => Vec2(-1.5999999, 3.2)
成分の入れ替え
vec2(1.0, 2.0).xy(); // => Vec2(1.0, 2.0)
vec2(1.0, 2.0).yx(); // => Vec2(2.0, 1.0)
vec2(1.0, 2.0).xx(); // => Vec2(1.0, 1.0)
vec2(1.0, 2.0).yy(); // => Vec2(2.0, 2.0)
vec2(1.0, 2.0).xxx(); // => Vec3(1.0, 1.0, 1.0)
vec2(1.0, 2.0).xxy(); // => Vec3(1.0, 1.0, 2.0)
vec2(1.0, 2.0).xyx(); // => Vec3(1.0, 2.0, 1.0)
vec2(1.0, 2.0).xyy(); // => Vec3(1.0, 2.0, 2.0)
vec2(1.0, 2.0).yxx(); // => Vec3(2.0, 1.0, 1.0)
vec2(1.0, 2.0).yxy(); // => Vec3(2.0, 1.0, 2.0)
vec2(1.0, 2.0).yyx(); // => Vec3(2.0, 2.0, 1.0)
vec2(1.0, 2.0).yyy(); // => Vec3(2.0, 2.0, 2.0)
vec2(1.0, 2.0).xxxx(); // => Vec4(1.0, 1.0, 1.0, 1.0)
vec2(1.0, 2.0).xxxy(); // => Vec4(1.0, 1.0, 1.0, 2.0)
vec2(1.0, 2.0).xxyx(); // => Vec4(1.0, 1.0, 2.0, 1.0)
vec2(1.0, 2.0).xxyy(); // => Vec4(1.0, 1.0, 2.0, 2.0)
vec2(1.0, 2.0).xyxx(); // => Vec4(1.0, 2.0, 1.0, 1.0)
vec2(1.0, 2.0).xyxy(); // => Vec4(1.0, 2.0, 1.0, 2.0)
vec2(1.0, 2.0).xyyx(); // => Vec4(1.0, 2.0, 2.0, 1.0)
vec2(1.0, 2.0).xyyy(); // => Vec4(1.0, 2.0, 2.0, 2.0)
vec2(1.0, 2.0).yxxx(); // => Vec4(2.0, 1.0, 1.0, 1.0)
vec2(1.0, 2.0).yxxy(); // => Vec4(2.0, 1.0, 1.0, 2.0)
vec2(1.0, 2.0).yxyx(); // => Vec4(2.0, 1.0, 2.0, 1.0)
vec2(1.0, 2.0).yxyy(); // => Vec4(2.0, 1.0, 2.0, 2.0)
vec2(1.0, 2.0).yyxx(); // => Vec4(2.0, 2.0, 1.0, 1.0)
vec2(1.0, 2.0).yyxy(); // => Vec4(2.0, 2.0, 1.0, 2.0)
vec2(1.0, 2.0).yyyx(); // => Vec4(2.0, 2.0, 2.0, 1.0)
vec2(1.0, 2.0).yyyy(); // => Vec4(2.0, 2.0, 2.0, 2.0)
定数
Vec2::ZERO; // => Vec2(0.0, 0.0)
Vec2::ONE; // => Vec2(1.0, 1.0)
Vec2::X; // => Vec2(1.0, 0.0)
Vec2::Y; // => Vec2(0.0, 1.0)
Vec2::AXES; // => [Vec2(1.0, 0.0), Vec2(0.0, 1.0)]
Vec2::default(); // => Vec2(0.0, 0.0)
十字コントローラー的なものを扱うときに重宝しそうな Vec2::AXES
まで入っている。
さまざまな型のベクトルがある
ivec2(-2, -3); // => IVec2(-2, -3)
uvec2(2, 3); // => UVec2(2, 3)
dvec2(2.0, 3.0); // => DVec2(2.0, 3.0)
- 2D符号あり整数 → IVec2
- 2D符号なし整数 → UVec2
- 3D用 → Vec3
- 64bit → DVec2
- などいろんな型が用意されている
SIMD対応
Vec3A::new(2.0, 3.0, 4.0); // => Vec3A(2.0, 3.0, 4.0)
A
がついたタイプを使うと SIMD 命令が使われる
論理型ベクトル
生成
BVec2::new(true, false); // => BVec2(0xffffffff, 0x0)
どれかが true か?
BVec2::new(true, false).any(); // => true
全部 true か?
BVec2::new(true, true).all(); // => true
論理演算 (AND, OR, NOT)
BVec2::new(true, true) & BVec2::new(true, false); // => BVec2(0xffffffff, 0x0)
BVec2::new(true, false) | BVec2::new(false, true); // => BVec2(0xffffffff, 0xffffffff)
!BVec2::new(true, false); // => BVec2(0x0, 0xffffffff)
LSB から順に x y と並べたときの値を返す
BVec2::new(false, false).bitmask(); // => 0
BVec2::new(true, false).bitmask(); // => 1
BVec2::new(false, true).bitmask(); // => 2
BVec2::new(true, true).bitmask(); // => 3
nannou の Vertex* 型への変換
vec2(2.0, 3.0).point2(); // => [2.0, 3.0]
vec3(2.0, 3.0, 4.0).point3(); // => [2.0, 3.0, 4.0]
- ベクトルと点(頂点)は明確に型が分けられている
- これは nannou が glam を拡張しているようだ
Discussion