Maya で毛糸玉に追従する糸を表現する方法

## はじめに

まずは以下の動画を御覧ください。

https://vimeo.com/259453255
[![https://vimeo.com/259453255](https://qiita-image-store.s3.amazonaws.com/0/74764/b493c0cd-fc6e-e081-7257-7a12c372dcd8.png)](https://vimeo.com/259453255)

本稿では、この動画で使用されている糸の動きについて、実現するまでのプロセスを紹介します。なお、あくまで自分が試行錯誤して得た結果なので、もっと簡単な方法があるかもしれません。もし他の方法を知っている方がいましたら、コメントで教えてください。

### サンプルシーンのダウンロード

仕組みをすっ飛ばしてシーンだけ見たいという方は、[github](https://github.com/karasusan/yarntrail) にシーンを配置していますので、ダウンロードしてみてください。


## 利用するツール
- Maya 2017 Update5

## 要件の整理

実現方法を検討する前に、要件を整理します。
糸を動きを作る上で、以下の要件を満たす必要があります。

- 糸の柔らかさの表現
- 毛糸玉の軌跡への追従
- 他のモノ（机や椅子など）との衝突
- 工数がかからない方法

あとで説明するように、実現するまでにいくつかの方法を試しています。これらの要件と照らし合わせながら最適解を探っています。

## 実現方法の検討

いくつかの方法を検討しました。

- クラスタデフォーマを使う
- 押し出しのパスカーブを使う
- nHair を使う

### クラスタデフォーマ（Cluster Deformer）を使う

**方法**
 
- NURBS カーブの CV をクラスタ化にする
- クラスタにアニメーションを手付けする

**問題点**

- アニメーションは全て手付けになり工数がかかる

###押し出し（Extrude）のパスカーブを使う

**方法**

- メッシュに対して押し出し（Exclude）を適用する
- [参考動画](https://www.youtube.com/watch?v=c-Zpamrvj6s)

[![https://www.youtube.com/watch?v=c-Zpamrvj6s](http://img.youtube.com/vi/c-Zpamrvj6s/0.jpg)](https://www.youtube.com/watch?v=c-Zpamrvj6s)


**問題点**

- UV も一緒にアニメーションしてしまうため表現力に乏しい
- アニメーションは手付けになるので工数がかかる

###ヘアー（nHair）を使う

**方法**

- NURBS カーブにヘアーを適用する
- 糸のリアルな表現が工数をかけずに実現できる

**問題点**

- 手付けできないので細かい手直しができない



試行錯誤の結果、ヘアーが求めている表現に近く、工数がかからないと判断したので、ヘアーを採用することにしました。しかし、いくつかの問題をクリアする必要がありました。

- どのように糸を毛糸玉に追従させるのか
- 地面に落ちていない糸はどこに隠すのか
- NURBSカーブをどのようにレンダリングするのか

上に挙げた問題を１つずつ解決していきます。

## 実現方法

### 螺旋状のNURBSカーブの作成

ヘアーを使うとなったときに、一番悩ましいのが、まだ地面に落ちていない糸をどのように隠すかということでした。
糸を一箇所にまとめてしまうと、糸自身に衝突してしまい不具合の元になります。
そこで NURBS カーブをコイルのように螺旋状に巻いて、毛糸玉の中に収まるようにしました。

<img src=https://qiita-image-store.s3.amazonaws.com/0/74764/7ab82eaf-c42d-af78-1df6-8b33e7839398.png width=640px>

螺旋のNURBSカーブを作り方を説明します。
Helix のプリミティブを作成します。

<img width="493" alt="Screen Shot 2018-03-05 at 9.06.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/74764/fb7ee344-c469-4532-1f69-825504202900.png">

Helix の外周のエッジを選択します。

<img width="484" alt="Screen Shot 2018-03-05 at 9.06.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/74764/0d14778a-107b-aa63-c5a1-b0dd5353ceda.png">

`Modify → Convert → Polygon Edges to Surface` を適用します。

<img width="562" alt="Screen Shot 2018-03-05 at 9.07.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/74764/28fdc038-13be-417f-85ac-efaabd009af2.png">

これで螺旋の NURBS カーブが作れます。

<img width="486" alt="Screen Shot 2018-03-05 at 9.07.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/74764/50080b23-0240-b3ad-b4e4-d6a28e7c3b29.png">

しかし、ここで重要なのは NURBS カーブの長さです。NURBS カーブの長さが糸の長さになるからです。そして NURBS カーブの長さを決めるのは、Helix のアトリビュートです。必要な糸の長さからアトリビュートに入力する数値を決めていきます。すごく雑に計算すると、以下の式になります。

```math
糸の長さ \fallingdotseq 円周 \times 螺旋を巻いた回数 
```

糸の長さは 2m にします。毛糸玉のサイズは直径 10cm なので、螺旋はその中に隠れる必要があります。ということで直径 6cm 程度にします。螺旋を巻く回数は、

```math
200 = 6 \times 3.14 \times x
```

```math
x = 200 \div (6 \times 3.14)
```
```math
x \fallingdotseq 10.615
```

となり、11回程度巻いておけば 2m になる計算です。Helix のアトリビュートを調整し、約2mのNURBSカーブを作ります。

![Screen Shot 2018-03-11 at 13.28.06.png](https://qiita-image-store.s3.amazonaws.com/0/74764/9ec06eea-0fde-9c9b-111f-c4aa00207636.png)

`Freeze Transform`　を適用して、ヒストリを切っておきます。

<img width="６００" src="https://qiita-image-store.s3.amazonaws.com/0/74764/c7be508b-be3b-49e1-20e1-5395af3d5652.png">

### NURBSカーブのCV数の変更

次に考慮するのは、NURBS カーブのCVの数です。CVの数が多ければ、それだけ柔らかい糸の形状を表現できますが、今度は計算量が増えてしまいます。適切なCV数になるように修正します。今回は 1cm 毎にCVを入れることにします。
NURBSカーブを選択し、　メニューから `Curves → Rebuild` を選択します。

<img width="257" alt="Screen Shot 2018-03-05 at 9.17.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/74764/4a46892e-905d-d9a2-e56e-b93bf620b2cd.png">

1cm 毎にCVを入れるので、オプション画面で `Number of spans` の値を 200 に変更して適用します。

![Screen_Shot_2018-03-11_at_13_38_44.png](https://qiita-image-store.s3.amazonaws.com/0/74764/db15711a-5dc8-5135-8bf9-cf2edb0d2567.png)


これでCV数が203（200+3）の NURBS カーブができます。
（なぜ３足されるのかよく分かってないです…すいません…）

### NURBSカーブのヘアー化

螺旋状の NURBS カーブにダイナミクスを適用します。NURBSカーブを選択した状態で `nHair → Make Selected Curves Dynamic` を実行します。

![Screen Shot 2018-03-11 at 13.42.52.png](https://qiita-image-store.s3.amazonaws.com/0/74764/3aa4724c-c688-c045-032b-cb603264a016.png)

初期状態ではカーブの両端が落下しないので、アトリビュートの内容を編集します。
`Follicle Attributeｓ` の `Point Lock` を `No Attach` に変更します。

![Screen_Shot_2018-03-11_at_13_43_47.png](https://qiita-image-store.s3.amazonaws.com/0/74764/0fa1f3ba-1763-9bfb-5113-db5604ffb753.png)

もう一つ、 `nucleus` の `Space Scale` を調整します。Maya の規定の作業単位はセンチメートルで、重力はメートル単位で解釈されるため、`Space Scale` は `0.01` にしておく必要があります。

![Screen_Shot_2018-03-11_at_13_52_34.png](https://qiita-image-store.s3.amazonaws.com/0/74764/75ec42a7-da67-e870-59e8-baa96a124d23.png)

試しにアニメーションを再生してみます。

![dynamics01.gif](https://qiita-image-store.s3.amazonaws.com/0/74764/6a568e2a-6ac9-6c2e-ff4f-a7bfe2902fe0.gif)

当然ながら、糸はすぐに自由落下します。糸を毛糸玉に追従させるには、CVに対してダイナミクスを反映するかしないかを制御する必要があるのです。

### Transform Constraint の適用
ダイナミクスのオン／オフを制御するために、 `Transform Constraint` を使用します。言葉だと説明が難しいので、`Transform Constraint` の効果の分かりやすい動画があるので紹介します。

https://www.youtube.com/watch?v=TwhTxlFaEO0
[![https://www.youtube.com/watch?v=TwhTxlFaEO0](http://img.youtube.com/vi/TwhTxlFaEO0/0.jpg)](https://www.youtube.com/watch?v=TwhTxlFaEO0)

以下の対応を行います。


1. CV の親になるノードを作成する
2. 出力カーブのCV毎に `Transform Constraint` を適用する
3. `Transform Constraint` を親ノードに `Parent Constraint` させる
4. 入力カーブを親ノードに`Parent Constraint` させる

まず、親ノードを作成します。
空のグループを螺旋の中心に配置します。ノード名は `YarnTrailParent` としておきます。

![Screen Shot 2018-03-11 at 14.15.15.png](https://qiita-image-store.s3.amazonaws.com/0/74764/3d53a122-61a4-7514-b1b7-7cb6b13ce8cc.png)

次に、CV毎にコンストレイントを適用していくのですが、 203個もあるので手動で行うのは大変です。そこで、ここでは `MEL` を使います。
以下のスクリプトを　`Script Editor` に貼り付けて実行します。親ノード（`YarnTrailParent`）を選択した状態で実行してください。

```php:

$select = `ls -sl`;
$parent = $select[0]; 

for($i = 0; $i <= 202; $i++)
{
    // create dynamic constraint
    selectKey -clear ;
    select -r curve1.cv[$i];
    createNConstraint transform 0;

    // create parent constraint
    $number = $i + 1;
    $constraint = "dynamicConstraint" + $number;
    select -cl;
    select -r $parent;
    select -add $constraint;
    doCreateParentConstraintArgList 1 { "0","0","0","0","0","0","0","0","1","","1" };
    parentConstraint -mo -weight 1;

    // disable rest position
    $parentConstraintName = $constraint + "_parentConstraint1";
    string $enableRestPositionName = $parentConstraintName + ".enableRestPosition";
    select -r $parentConstraintName;
    setAttr $enableRestPositionName 0;
}
```

実行すると、大量のコンストレイントがアウトライナ上に表示されます。

![Screen Shot 2018-03-11 at 14.20.10.png](https://qiita-image-store.s3.amazonaws.com/0/74764/7deb57c6-ef35-e366-9ec6-87a5724c4ea0.png)

邪魔なのでグループにまとめて非表示にします。

![Screen Shot 2018-03-11 at 14.21.15.png](https://qiita-image-store.s3.amazonaws.com/0/74764/e206463d-a7fa-a852-8435-768ac0c76f62.png)

最後に入力カーブに対して `Parent Constraint` をかけます。親ノード（`YarnTrailParent`）を選択してから、入力カーブを選択します。

![Screen Shot 2018-03-11 at 15.27.32.png](https://qiita-image-store.s3.amazonaws.com/0/74764/1d377fa8-25e0-fae6-5a88-3f929c0fb16c.png)

`Constrain → Parent Constraint` を実行します。

![Screen Shot 2018-03-11 at 15.29.14.png](https://qiita-image-store.s3.amazonaws.com/0/74764/3c98b1d4-7a65-73c5-f1d2-85f9011a2c09.png)


この状態でアニメーションを実行すると、ダイナミクスは適用されず、糸は静止したままの状態になります。このコンストレイントのオン／オフを制御すれば、糸の軌跡を描くことができそうです。

### 糸制御用コントローラの作成

螺旋がダイナミクスの影響を受けず、親ノードに追従するようになりました。
次に糸制御用のコントローラを作成します。このコントローラには以下のパラメータを持たせます。

- 現在の角度 (`Angle`)
- 初期状態の角度 （`Initial Angle`）
- CV の個数 （`Cv Count`）
- CV 1つの角度 （`Cv Angle`）
- `Angle` と `Initial Angle`　を加算した角度 (`Trail Angle`)

コントローラの形状はなんでもいいのですが、螺旋を覆う感じで円を描きます。ノード名を `YarnTrailCtrl` とします。

![Screen Shot 2018-03-11 at 15.53.02.png](https://qiita-image-store.s3.amazonaws.com/0/74764/004a0a79-6d28-b394-5f0f-e8b9e75d7d15.png)

`Freeze Transform` してヒストリを切ります。そして、親ノード（`YarnTrailParent`） をコントローラの階層に移動します。

![Screen Shot 2018-03-11 at 15.56.32.png](https://qiita-image-store.s3.amazonaws.com/0/74764/42eaeb79-09e9-7ea0-bc8f-306d083c8f83.png)

これでコントローラの移動・回転が螺旋に反映されるようになります。
コントローラのアトリビュートを以下のように編集します。

![Screen_Shot_2018-03-11_at_22_33_29.png](https://qiita-image-store.s3.amazonaws.com/0/74764/fa99bc5f-9a0e-c015-3986-3868bb07cbf5.png)

`Scale` と `Visibility` は使用しないので `Lock and Hide Selected` を適用します。追加分のアトリビュートは　`Add Attribute` で追加します。追加したアトリビュートの名前は、後でスクリプトで使用するので厳密に決めておく必要があります。
まだ何も制御はできていませんが、これで糸制御用のコントローラができました。

### 糸の排出を制御する

球の回転に応じて、頂点毎のコンストレイントのオン／オフを制御します。球の回転角はフレーム毎に変わるので、フレーム単位で値を評価する必要があります。
そこで `Expression` を利用します。以下のような処理をフレーム毎に行う`Expression`を記述します。

1. コントローラの回転角（`Angle`）と初期角度（`Initial Angle`）を加算し現在の角度（`Trail Angle`）を取得する
2. 回転角を単位角（`Cv Angle`）で除算する
3. 除算で得た値の数から、対象となるCVを取得する
4. CVの `Transform Constraint` 及び `Parent Constraint` を無効にする

`Expression Editor`　を開きます。

![Screen Shot 2018-03-11 at 22.44.57.png](https://qiita-image-store.s3.amazonaws.com/0/74764/8b4c147a-af9a-b248-8ac2-e35d5b24b14d.png)

Expression Name は適当に `YarnTrailController` とします。
以下のスクリプトを `Expression Editor` に貼り付けて、`Create` ボタンを押します。

```php

$cvAngle = YarnTrailCtrl.CvAngle;

$cvCount = YarnTrailCtrl.CvCount;

$trailAngle = YarnTrailCtrl.Angle + YarnTrailCtrl.InitialAngle;

YarnTrailCtrl.TrailAngle = $trailAngle;

$count = $trailAngle / $cvAngle;

for($i = 0; $i < $cvCount; $i++)
{
    int $number = $i + 1;
    string $dynamicContraintShape = "dynamicConstraintShape" + $number;
    string $parentContraintWeight = "dynamicConstraint" + $number + "_parentConstraint1.YarnTrailParentW0";
    
    $enableConstraint = $i < $count ? 0 : 1;

    setAttr($dynamicContraintShape + ".enable", $enableConstraint);
    setAttr($parentContraintWeight, $enableConstraint);
}
```

エラーが発生したときは、コントローラのアトリビュートの命名が間違っている可能性があります。 
次にコントローラにパラメータを入力します。`CV Count` には CV の総数を入れるので `203`、`CV Angle`には`19.5`を入力します。

![Screen Shot 2018-03-11 at 17.02.02.png](https://qiita-image-store.s3.amazonaws.com/0/74764/ac508ff6-15fb-0c3e-b64e-4b7e50ba4879.png)

`CV Angle` の `19.5` という値は、以下の大雑把な計算で求めています。厳密な値ではありません。

```math
x = 円の角度 \times 螺旋を巻いた回数 \div CVの数
```

```math
x = 360 \times 11 \div 203
```

```math
x \fallingdotseq 19.5
```

`Angle` にキーを打って動作確認します。ここでは１フレーム目を `０`、120フレーム目を `720` にしました。アニメーションを実行してみます。

![dynamics02.gif](https://qiita-image-store.s3.amazonaws.com/0/74764/f32b7914-819c-716e-64ee-5d84bda15180.gif)

###　回転運動との同期

糸の排出の制御は完成したので、次は糸の排出とコントローラの回転運動を同期させます。
　`YarnTrailCtrl` の　`Rotate X` と　`Angle`　を同期させます。`Angle` に打ったキーを全削除します。再度 `Expression Editor` を開いて、最初の行に以下を追加します。

```php
YarnTrailCtrl.Angle = -YarnTrailCtrl.rotateX;
```

これでコントローラのX軸回転と糸の排出が同期するようになりました。

動作確認のため、ヘアーを床に衝突させます。 `nucleus` の `Use　Plane` を有効にします。

![Screen_Shot_2018-03-11_at_13_47_02.png](https://qiita-image-store.s3.amazonaws.com/0/74764/6b8645bc-91a8-b74f-d67f-2ce8faf3121c.png)

位置を調整して、X軸回転と移動方向にキーを打ち、アニメーションを再生させます。

![dynamics03.gif](https://qiita-image-store.s3.amazonaws.com/0/74764/01ba30c2-a82b-2b5f-e359-5877f2dbcc5c.gif)


### Curve warp デフォーマの適用

糸のカーブが物理で制御できるようになりました。ここからNURBSカーブにメッシュを適用させてレンダリングします。NURBSカーブにメッシュを割り当てる方法として、今回は `Curve Warp` デフォーマを使用します。`Curve Warp` デフォーマの分かりやすい解説動画を貼っておきます。

https://www.youtube.com/watch?v=_Ey8TSduH4U
[![https://www.youtube.com/watch?v=_Ey8TSduH4U](http://img.youtube.com/vi/_Ey8TSduH4U/0.jpg)](https://www.youtube.com/watch?v=_Ey8TSduH4U)

まずは糸のモデルとなるメッシュを用意します。Cylinder を作成し、アトリビュートを編集します。縦の長さは 2m、半径 1mm の糸を作成します。 縦の割りが必要になるので、`Subdivision Heights` を 200 に指定します。

![Screen_Shot_2018-03-11_at_19_13_09.png](https://qiita-image-store.s3.amazonaws.com/0/74764/9446d5a1-3b36-5776-d605-10f69f306177.png)

メッシュが用意できたら、　`Freeze Transform` をかけてヒストリを切っておきます。モデルを選択してから続けて出力カーブを選択します。

![Screen_Shot_2018-03-11_at_19_26_32.png](https://qiita-image-store.s3.amazonaws.com/0/74764/e31c96d4-93f0-8e85-47bc-571ca1081177.png)

`Deform → Curve warp` を選択します。

![Screen Shot 2018-03-11 at 19.25.22.png](https://qiita-image-store.s3.amazonaws.com/0/74764/26c5d7d0-f152-8f74-13d2-9b63e3d05955.png)

メニューに項目がない場合は、 `Plug-in Manager` でチェックが入っているか確認してください。

![Screen_Shot_2018-03-11_at_19_19_56.png](https://qiita-image-store.s3.amazonaws.com/0/74764/4a3621ad-01aa-716c-4c61-30a18a24382b.png)

螺旋にメッシュが巻きついたら成功です。

![Screen Shot 2018-03-11 at 19.34.19.png](https://qiita-image-store.s3.amazonaws.com/0/74764/c91c517e-0d00-2625-fd3e-22d849fabcac.png)

ここでレンダリングを開始したいところですが、 CV数の多い NURBS カーブに対する `Curve Warp` は重い処理で、容易にレンダリングの調整を行うことができません。そこでダイナミクスのキャッシュ化を行います。

### ダイナミクスのキャッシュ化

キャッシュ対象の NURBS カーブを Outliner で選択します。

![Screen Shot 2018-03-11 at 19.54.26.png](https://qiita-image-store.s3.amazonaws.com/0/74764/cc3311d6-7d22-0728-2c18-efd23c2e9af8.png)

`ｎCache → Create New Cache → nObject` でオプション画面を開きます。

![Screen Shot 2018-03-11 at 19.57.29.png](https://qiita-image-store.s3.amazonaws.com/0/74764/c8a8348d-9b17-0147-320b-8b5bd1ad50b9.png)

`Cache Format` は `mcx`、 `File Distribution` は `One File` に変更して書き出します。

![Screen_Shot_2018-03-11_at_22_57_57.png](https://qiita-image-store.s3.amazonaws.com/0/74764/ea1923d4-c121-76d4-6f87-483273abc633.png)

## レンダリング

毛糸のリグに球のメッシュを追加し、床とライトをセットアップして Arnold でレンダリングしてみます。
Arnold で書き出した動画を　gif に変換しているので階調は粗いですが、球から排出される糸の動きが表現できていることが分かります。

![dynamics04.gif](https://qiita-image-store.s3.amazonaws.com/0/74764/733e83b9-9ed3-6591-f5c5-f6c729011343.gif)


## 問題点と対策

今回の方法の大きな問題点は、シーンが重くなることです。シーンが重くなるとアニメーション作業に支障をきたします。
シーンが重くなる原因は、`Curve Warp` デフォーマの変形対象となるメッシュ量が大きいためです。しかし、メッシュ量を減らすと今度は糸の滑らかな形状を失います。なお、ダイナミクスに関しては、アニメーションをつけるときは `nucleus` を切っておけば、それほど重いシーンにはならないです。

**解決案（暫定）**
自分が作業しているときは、レンダリング用のシーンとアニメーション用のシーンを分けて管理しました。
アニメーションのデータは `ATOM` を使用し、ダイナミクスのデータは `nCache` を使用して、アニメーション用のシーンからレンダリング用のシーンにアニメーションデータを流し込んでいました。
`nCache` はファイルパスへの参照なので、アニメーション変更の際はキャッシュを書き出すだけの手間で済むのですが、 `ATOM` は毎回レンダリングシーン側で読み込み作業が発生するので工数がかかります。ここらへんは他に良い方法がないか模索している最中です。

## おわりに

なかなか応用範囲の狭いマニアックなリグが完成しました。
[github](https://github.com/karasusan/yarntrail) に今回作成したシーンを上げておきます。シーンを見たい人はダウンロードしてみてください。