➡️

Nannou の一次元の範囲を表す Range の使い方

2023/08/11に公開

早見表

種類 意味 Methods 同類・備考
生成 pos ± (len / 2) from_pos_and_len 推奨
生成 start..end new(start, end)
参照 左, 右, 中央 start end middle()
参照 強さ (end - start) magnitude()
参照 長さ (強さの絶対値) len()
向き 向き direction() -1, 0, +1 を返す
向き 相手と同じ向きか? has_same_direction(b)
向き 反転 invert()
向き 「←」なら「→」にする absolute()
対領域 OR a.max(b)
対領域 OR (向きを維持する) a.max_directed(b)
対領域 AND a.overlap(b)
移動 相手の軸に揃える a.align_start_of(b) align_end_of / align_middle_of
移動 相手の軸の ? に揃える a.align_to(?, b)
移動 相手の隣に並べる a.align_after(b) a.align_before(b)
移動 v だけ移動する shift(v)
変形 縮小 pad(v) pad_start / pad_end / pad_ends
変形 v に近い方の端を広げる stretch_to_value(v)
その他 整形補完 a.lerp(v) a.map_value(v, &b)
その他 領域に含まれるか? a.contains(v)
その他 相手を補正する clamp_value(v) v.clamp(range) に類似
その他 v に近い方の Edge を返す closest_edge(v)
その他 小数補正 round() floor()

https://docs.rs/nannou/0.18.1/nannou/geom/range/struct.Range.html

特徴

  • 2方向の向きを持っている
    • 1次元ベクトルとも言える
  • Immutable
    • 破壊的メソッドは一つもない
    • とはいえ start end は外部から更新できてしまう
  • 整数を使っていると急に怒り出す
    • コンストラクタでは整数が使えるのに割り算などが絡むとめちゃくちゃ怒られる
    • なので基本小数で指定した方がいい
  • Ruby の Range クラスとは根本的に考え方が異なる
    • start <= end の関係である必要はない
    • 範囲というより単純に start 地点と end 地点を持つ
    • end は行き先の地点そのものを指すので終端を含む・含まない問題とは無縁
    • 無縁だが「向き」を持つため複雑さはたいして変わらない

説明文章の補足

  • イメージしやすいように start, end を「左右」と呼んだりしている
  • 実際は end < start の関係になることもある

コンストラクタ

基本
Range::new(1, 2);  // => Range { start: 1, end: 2 }
pos を中心に半径 len / 2 の幅とする (重要)
Range::from_pos_and_len(100.0, 10.0);  // => Range { start: 95.0, end: 105.0 }

Rect 型の x y 要素はこれで作られる。

それぞれの値

Range::new(100.0, 200.0).start;     // => 100.0
Range::new(100.0, 200.0).middle();  // => 150.0
Range::new(100.0, 200.0).end;       // => 200.0

ベクトルの強さと長さ

強さ (end - start)
Range::new(100, -200).magnitude();  // => -300
長さ (強さの絶対値)
Range::new(100, -200).len();  // => 300

小数の補正

Range::new(0.4, 0.5).round();  // => Range { start: 0.0, end: 1.0 }
Range::new(0.4, 0.5).floor();  // => Range { start: 0.0, end: 0.0 }

範囲

OR (向きを破壊する)
let a = Range::new(5.0, 3.0);
let b = Range::new(4.0, 6.0);
a.max(b);  // => Range { start: 3.0, end: 6.0 }
OR (向きを維持する)
let a = Range::new(5.0, 3.0);
let b = Range::new(4.0, 6.0);
a.max_directed(b);  // => Range { start: 6.0, end: 3.0 }

a が右向きなら max と同じだが左向きなら max の invert になる。

AND (向きを破壊する)
let a = Range::new(5.0, 3.0);
let b = Range::new(4.0, 6.0);
a.overlap(b);  // => Some(Range { start: 4.0, end: 5.0 })

max_directed があるなら overlap_directed もあってよさそうだが見つからなかった。

向き

現在の向きを返す
Range::new(0, 10).direction();   // => 1
Range::new(10, 0).direction();   // => -1
Range::new(10, 10).direction();  // => 0

(end - start) の符号に相当する。

向きが同じか?
let a = Range::new(1, 2);
let b = Range::new(3, 4);
a.has_same_direction(b);  // => true
向きを反転する
Range::new(0, 100).invert();  // => Range { start: 100, end: 0 }
正の向きにする
Range::new(10, 0).absolute();  // => Range { start: 0, end: 10 }
  • start > end なら invert する。

スケーリング

let a = Range::new(0.0, 1.0);
let b = Range::new(0.0, 100.0);
a.map_value(0.9, &b);  // => 90.0

a の 0.9 は b では 90 になる。

元の範囲が 0..1 の場合 lerp 使うと簡潔に書ける。

b.lerp(0.9);  // => 90.0

指定の軸で整列する

相手の左端に揃える
let a = Range::new(0, 100);
let b = Range::new(50, 100);
a.align_start_of(b);  // => Range { start: 50, end: 150 }
相手の右端に揃える
let a = Range::new(0, 50);
let b = Range::new(0, 100);
a.align_end_of(b);  // => Range { start: 50, end: 100 }
相手の中央に揃える
let a = Range::new(0.0, 50.0);
let b = Range::new(0.0, 100.0);
a.align_middle_of(b);  // => Range { start: 25.0, end: 75.0 }
相手のどこかに揃える
let a = Range::new(0.0, 5.0);
let b = Range::new(10.0, 20.0);
a.align_to(Align::Start, b);   // => Range { start: 10.0, end: 15.0 }
a.align_to(Align::End, b);     // => Range { start: 15.0, end: 20.0 }
a.align_to(Align::Middle, b);  // => Range { start: 12.5, end: 17.5 }

横に並べる

相手の左隣り並べる
let a = Range::new(0.0, 5.0);
let b = Range::new(0.0, 10.0);
a.align_after(b);  // => Range { start: 10.0, end: 15.0 }
相手の右隣り並べる
let a = Range::new(0.0, 5.0);
let b = Range::new(0.0, 0.0);
a.align_before(b);  // => Range { start: -5.0, end: 0.0 }

Edge を寄せる (サイズが変わる)

左端を内側に寄せる
Range::new(10, 0).pad_start(3);  // => Range { start: 7, end: 0 }
右端を内側に寄せる
Range::new(10, 0).pad_end(3);  // => Range { start: 10, end: 3 }
両端を内側に寄せる
Range::new(10, 0).pad(3);  // => Range { start: 7, end: 3 }
両端を内側に寄せる (個別指定)
Range::new(10, 0).pad_ends(3, 4);  // => Range { start: 7, end: 4 }

この範囲に含むか?

Range::new(1, 2).contains(2);  // => true

対象を補正する

Range::new(10, 0).clamp_value(-1);  // => 0
Range::new(10, 0).clamp_value(11);  // => 10

Ruby で言うところの value.clamp(range) の向きを逆に適用したもの

ずらす (サイズ不変)

Range::new(2, 3).shift(10);  // => Range { start: 12, end: 13 }

近い方の端を引き伸ばす

Range::new(10, 20).stretch_to_value(5);   // => Range { start: 5, end: 20 }
Range::new(10, 20).stretch_to_value(25);  // => Range { start: 10, end: 25 }

範囲内を指定した場合は何も変化しない。

Range::new(10, 20).stretch_to_value(15);  // => Range { start: 10, end: 20 }

値に近い方の Edge を返す

Range::new(0.0, 10.0).closest_edge(4.0);  // => Start
Range::new(0.0, 10.0).closest_edge(6.0);  // => End

戻値: Edge::Start, Edge::End

Discussion