THREE.IKでIK入門してみた

[Three.js Advent Calendar 2019](https://qiita.com/advent-calendar/2019/threejs)の6日目の記事です！
今回は[Three.js Meetup Tokyo #0](https://threejs.connpass.com/event/153560/)で話せなかったIKライブラリ**「THREE.IK」**の話です。

## 開発環境
Three.js: v0.111.0
THREE.IK: v0.1.0

## [THREE.IK](https://github.com/jsantell/THREE.IK)とは
![Dec-06-2019 23-54-46.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15028/1680d440-98b0-23f1-8b60-7da00db47881.gif)

- Three.jsベースで提供されているIKライブラリ
 - IKの説明は[Shade3D 第9回「FKとIK」](https://shade3d.jp/store/marketplace/articles/startanimation/vol9.html)が分かりやすいので見てみて下さい
- 開発者は元GoogleでDaydream WebXR R&D担当してた人（[Jordan Santell](https://jsantell.com/about)）
- [クラスのドキュメント](https://jsantell.github.io/THREE.IK/docs/IK.html)もある
- **FABRIK**対応している
 - いわゆるフルボディIKのこと
 - 任意の数のボーンに対して影響を与える事ができる

**FABRIK**の解説は以下がわかりやすいので参考にして下さい。さすが、ゲームエンジンだと普通に実装されてますねっ！

- [Unreal Engine4ドキュメント - FABRIK](https://docs.unrealengine.com/ja/Engine/Animation/NodeReference/Fabrik/index.html)
- [FABRIKによる逆運動学](http://www.thothchildren.com/chapter/5b9133ff2787593b86372157)

Three.jsでは標準で[CCDIKSolver](https://threejs.org/docs/#examples/en/animations/CCDIKSolver)クラスでIK対応してるようです。**CCDIK**は**FABRIK**とまた違ったアルゴリズムで、用途によって使い分けてると思われます。

- [Unreal Engine4ドキュメント - CCDIK](https://docs.unrealengine.com/ja/Engine/Animation/NodeReference/SkeletalControls/CCDIK/index.html)
- [Unity Blog 2D インバースキネマティクススターティングガイド](https://blogs.unity3d.com/jp/2018/12/04/getting-started-with-2d-inverse-kinematics/)

>FABRIK は一般的に、CCD よりも少ない反復回数でターゲットに到達することができますが、チェーンに回転の制約がかけられている場合は、1 回の反復処理が CCD よりも遅くなります。

Unity Blogから上記の気になる一文を見つけましたが、今回はFABRIK対応のTHREE.IKの方を解説していきます。
ちなみに他のFABRIK JSライブラリには、物理演算ライブラリのOimo.js開発者の[fullik](https://github.com/lo-th/fullik)もあります（こちらはドキュメントがない）

## IKの専門用語
いくつかIKの専門用語があります。こちらを把握しておくとIKのコード理解がはかどるかと思います。

- エフェクター：実際に動かすボーンなどジョイント情報
- ソルバー：動く範囲の影響を受けるボーン。THREE.IKではボーンの状態（回転・位置）を更新する
- コンストレイント：ボーンの回転に制約を加えるもの。THREE.IKではボーンの回転角度を制限します

コンストレイントは分かりやすいリンクがあったので貼っておきます。

 - [CGWORLD CG用語辞典 コンストレイン](https://entry.cgworld.jp/terms/%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%AC%E3%82%A4%E3%83%B3.html)
 - [AUTODESKチュートリアル コンストレイントは○○だと考えるとわかりやすいかも？](https://area.autodesk.jp/column/tutorial/maya_atoz/constraint/)
 

ソルバーとコンストレイントは後で関数名に登場するので、心の片隅に置いてもらえればと。

## THREE.IKのクラス達
THREE.IKのクラスは以下5つです。

- `THREE.IK`・・・IKを実現する親クラス
- `THREE.IKChain`・・・複数のジョイントを繋ぐクラス
- `THREE.IKJoint`・・・ボーンを繋ぐ関節クラス
- `THREE.IKHelper`・・・IKを視覚化するヘルパークラス
- `THREE.IKBallCXonstraint`・・・ボーンの回転角度を制限するクラス
<img width="420" alt="スクリーンショット 2019-12-07 1.04.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15028/5158f655-deb7-2adb-d2f6-5e5924154721.png">

最終的な親子関係は以下の感じですね（`THREE.Bone`はThree.js標準のクラス）
`THREE.IK`
　　└──`THREE.IKChain`
　　　　　　　└──`THREE.IKJoint`
　　　　　　　　　　　　└──`THREE.Bone`


## サンプルコードと解説
ポイントだけ解説します。全コードはgithubをご覧ください（[three-ik-practice](https://github.com/naotaro0123/three-ik-practice/blob/master/src/Simple.ts)）

まずは`THREE.IK`クラスをimportします。

```typescript
import { IK, IKChain, IKJoint, IKBallConstraint, IKHelper } from 'three-ik';
```
つぎにIKの向き先用のMeshを生成します。後でこのMeshを動かしてIKの向きを動的に動かします。

```typescript
const movingTarget = new THREE.Mesh(
  new THREE.SphereBufferGeometry(0.1),
  new THREE.MeshBasicMaterial({ color: 0xff0000 })
);
movingTarget.position.z = 2;

const pivot = new THREE.Object3D();
pivot.add(movingTarget);
this.scene.add(pivot);
```

つぎは親となるIKを作ります。isIKはtrueデフォルトです（[IKHelperでエラー](https://github.com/jsantell/THREE.IK/blob/master/src/IKHelper.js#L67)になるため）
`ik.add()`の引数の中身は`IKChain`が渡ります。

```typescript
const ik = new IK();
ik.isIK = true;
ik.add(createBonesAndChain(movingTarget));
```
`ik.add()`に渡していた`createBonesAndChain()`の中身です。最終的には`IKChain`を返します。


```typescript
createBonesAndChain(movingTarget: THREE.Mesh): IKChain {
  const chain = new IKChain();
  // Constraintに90と渡した事で円形で90°以上回転しなくなる
  const constraints = [new IKBallConstraint(90)];
  const bones: THREE.Bone[] = [];
  
  // Max6つのBoneを生成する 
  for (let i = 0; i < 7; i++) {
    const bone = new THREE.Bone();
    // 最初のBone以外は位置をずらして配置
    bone.position.y = i === 0 ? 0 : 0.5;
    if (bones[i - 1]) {
      bones[i - 1].add(bone);
    }
    bones.push(bone);

    // IKの向く方向となるMeshは最後のBoneのみセットする
    const target = i === 7 - 1 ? movingTarget : null;
    // IKJointは関節なので複数addすれば指の動きも可能
    chain.add(new IKJoint(bone, { constraints }), { target });
  }
  return chain;
}
```

最後にマイフレームrenderする処理で`ik.solve()`するとIKが更新されます。

```typescript
render(ik:IK, pivot: THREE.Object3D) {
  // IKの向き先を回転させる
  pivot.rotation.x += 0.03;
  pivot.rotation.y += 0.03;
  pivot.rotation.z += 0.03;
  // IKの状態を更新する
  ik.solve();

  this.renderer.render(this.scene, this.camera);
  requestAnimationFrame(() => this.render(ik, pivot));
}
```
最終的にはこんな動きになります。
![Dec-07-2019 02-46-47.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15028/0b0c9275-505b-4b45-45db-a34330936d58.gif)

## その他
他にももっと枝分かれしたボーンを制御したり、FBXをスキニング制御するサンプルもあるので実装の参考になると思います。
![Dec-07-2019 02-52-58.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15028/1807d5a7-6238-fc02-a0b1-e07faf70ab16.gif)

## 最後に
IKを使う事でキャラクターの腕を引いて動かしたり、VRでコントローラーとキャラクターの腕の動きを同期したりできます。
本当はthree-vrmと組み合わせてVRで遊びたかったけど、時間が足りないので冬休みに遊んでみようかと。

ちなみにすでにVRMとIKを組み合わせて自前実装してる人もいて、VRM界隈のエンジニアは強いですねっ！

- [YATTA!駆動開発 No.01 「VRMお手軽ポーズ/ポーズ共有機能」](https://scrapbox.io/ke456memo/YATTA!%E9%A7%86%E5%8B%95%E9%96%8B%E7%99%BA_No.01_%E3%80%8CVRM%E3%81%8A%E6%89%8B%E8%BB%BD%E3%83%9D%E3%83%BC%E3%82%BA%2F%E3%83%9D%E3%83%BC%E3%82%BA%E5%85%B1%E6%9C%89%E6%A9%9F%E8%83%BD%E3%80%8D)
- [Three.js でIKを実装してみた](https://codelabo.com/1268/)

以上、最後まで読んで頂きありがとうございました。

##### 2019/12/18追記
VTuber Techアドカレの方でもっと便利なIKライブラリについて書かれてました
[exokitxr/avatarsでWebVRなVtuberのシステムを動かしてみた](https://qiita.com/ShinYoshiaki/items/08757eb5256fe66b20ff)