iOS14の変形カーソルを、自前でゼロから実装するアルゴリズム
おおわく、実装方法が検討がついたのでメモ。
基本ロジック
ボタンと実マウスポインターの位置関係にあわせて、物理エンジンを用いて、バーチャルなマウスポインターをスプリングで引っ張って制御していると思われる。
1. ベースの当たり判定
ボタンの周辺には、サイズを拡張した仮想の当たり判定が存在する。当たり判定は、実ボタンだけでなく、この仮想アタリ判定で行う。
2. 仮想ポインターの生成
マウスポインターが、ボタンの仮想アタリ判定に侵入したとする。このとき、マウスポインターの位置に、仮想マウスポインターを生成する。
3. 仮想ポインターと物理エンジンの接続
仮想ポインターに対して、物理エンジンでスプリングを2つ生成し、接続する。接続先は、それぞれマウスポインタ、ボタンの中心。(スプリングは引力でも代替可能だが、初動の動きが速いのでスプリングと想定される)。
A. マウスポインタと、仮想ポインターの間に弱いスプリング(無荷重時全長0pxぐらい)。
B. 仮想ポインターと、ボタンのセンターの間に強いスプリング(無荷重時全長0px)。
4. 物理シミュレーション
2つのバネの力の非対称性から、仮想ポインターは、ボタンのセンターにたいして引き寄せられる。
5. 距離による変形
マウスポインタとボタンの中心の距離に対する、仮想ポインターの現在位置から、interpolationで比率を計算する。ポインター上に仮想ポインタがあれば円形。ボタン中心に仮想ポインタがあればボタンの形に。移動中は、移動距離の比率に応じて、両シェイプのブレンド形状をとる。
6. 仮想ポインターの位置の均衡
最終的に2つのスプリングの強度差から、だいたいボタンのセンター位置に達したところで、仮想ポインターの位置は停止する。ただし仮想ポインターは、マウスポインターとスプリング接続されているので、マウスポインターの位置に応じて、多少引っ張られた状態となる。
また、マウスポインターが完全にボタンの上にのったときは、マウスポインタと、仮想ポインターの間に弱いスプリングを解除し、完全にボタンにカーソルをスナップさせる。
7. ボタンからボタンへのフォーカス移動
マウスポインターが、別ボタンの仮想アタリ領域にロールオーバーした場合、現在のスプリングを解除する。あわせて、新しいボタン・シェイプ・マウスポインターの3者間で、スプリングを貼り直す。
ただし、仮想ポインターの現在のシェイプは保存をし、新しいボタンオブジェクトで取るべき形と、ふたたび位置関係にあわせてシェイプをブレンドする。
たぶん、こんな感じ。
雑な見積もりとして、物理エンジン込みで、JSで2000-3000行、雑実装なら5-600行以内でいけそう。
<追記>
ざっくり書いたら、ざっくり動いたのでだいたいあってそう。チューニングすれば、ちゃんと動きそう感。
いただいたサポートは、コロナでオフィスいけてないので、コロナあけにnoteチームにピザおごったり、サービス設計の参考書籍代にします。