【エクスプレッション】パスのエクスプレッション講座

AEのパスのエクスプレッションは扱い方を知らないと思い通りにならないので、コツを書いていきます。

基本の形

パスのエクスプレッションには大きく分けて3つの形があります。

1.単純にパスのストップウォッチをAltクリックして出てくる『content(“シェイプ 1”).content(“パス 1”).path』といったものか、明示的に『thisProperty』と書くもの。これは「元のパス点をさわれはするが動かせなくする」効果があります。

2.別パスから引っ張ってきて『 content(“シェイプ 1”).content(“パス 2”).path 』などとし、別パスと同じ形状にするもの。

3.これがメインですが、何やらごにょごにょと書いて最後に『createPath(何たら)』と書くものです。そうです。 パスのエクスプレッションは「自身パス、他パスを参照する以外には、 createPath()を使って新たなパスを作るしかない」んですね。「ここの1点だけ位置を変えたい」というときも、自身パスを元にして新たなパスを作るしか方法はありません!

以下がその書き方のテンプレです。

mPath = content("シェイプ 1").content("パス 1").path;
mVtxs = mPath.points();
mIns = mPath.inTangents();
mOuts = mPath.outTangents();
mCls = mPath.isClosed();

//ここで点,イン、アウトに何か処理する。

createPath( mVtxs, mIns, mOuts, mCls);

*変数名は自由。

最初に元になるパスを指定します。自身パスでも、他パスを引っ張ってきてもかまいません。全部1から作るなら無くてもかまいません。
次に、新パスに必要な要素を元パスから引っ張ってきます。pointsはパス点。inTangentsとoutTangentsはパス点が持つハンドル点2つのこと。isClosedはオープンパスかクローズパスかです。

『ここで点,イン、アウトに何か処理する』と書いてあるところにやりたい処理を書けば、新しいパスができます。

パスのとある1点だけ、ヌルでコントロールできるようにしましょう。
ヌルを作って以下のエクスプレッションをパスに適用します。

mPath = content("シェイプ 1").content("パス 1").path;
mVtxs = mPath.points();
mIns = mPath.inTangents();
mOuts = mPath.outTangents();
mCls = mPath.isClosed();

//点,イン、アウトに何か処理する。
mNullPt = thisComp.layer("ヌル 1").transform.position;
mNullLyrPt = fromComp(mNullPt);
mVtxs[0] = mNullLyrPt;

createPath( mVtxs, mIns, mOuts, mCls);

まずはヌルの位置を引っ張ってきていますね。

ヌルの位置はそのままではコンポ座標なので、パスの座標空間に変換します。それが『mNullLyrPt = fromComp(mNullPt);』の部分です。fromCompはfromCompCoordToLayerCoordの略で、コンポ座標をレイヤー座標に変換してくれます。シェイプのグループトランスフォームがデフォルトでない場合はズレてしまうことに注意してください。

*変換関数についてはこちら
*グループトランスフォームがデフォルトでない場合の対処法はこちらあるいはAEスクリプト『Exte』の『Fix GpTfm』ボタンを使う。

そして、パス点群が入っているmVtxsの[0]番目を、変換したヌル位置に書き換えています。
*パスのポイント番号を得る方法はこちら

最終的に、他の書き換えてない情報も含めて新たにパスを作っている、ということですね!

以上です!もうこれだけで、あとは色々とやりたいことを『//点,イン、アウトに何か処理する』のところに書いて試してみるだけですね!たいていは全てのパス点について処理するので、for文を使うかと思います。

以下に、つまづきやすい点と参考になるサイトを書いておきます。

数々のトラップ

inTangentsとoutTangentsはどっちがどっちか
始点が左、中間点が真ん中、終点が右の3点オープンパスがあるとします。進行方向は左から右だと言えますね。真ん中の点について、左からインしている側のハンドルがインタンジェント、右へアウトしている側のハンドルがアウトタンジェントです。つまり、パス番号が若い順から年寄り順へ進むのを進行方向とし、点に入る側にあるのがイン、出る側にあるのがアウトです。
オープンパスの始点のインと終点のアウトは『あってもいいけど使っていない』状態ですね。

inTangentとoutTangentはpointからの位置(つまりベクトル)
インとアウトの2つは同じ番号のパス点を[0,0]とした位置(つまりベクトル)が入っています。なので、パス点と同じ『座標』にするには、パス点+インとか、パス点+アウトとしなければなりません。これで困るのは他座標への変換時などです。いったんインとアウトを座標にし、パス点とともに3つとも変換、変換後にまたインやアウトからパス点を引いてベクトルにする、という処理をしないと、変換結果がズレてしまいます。
あるいは、3つのヌルをパス点、イン、アウトにするという場合です。3つとも座標なので、これらの座標をパス座標(レイヤー座標 or シェイプグループ座標)に変換、そしてインやアウトからパス点を引いてベクトルにする、といった処理が必要です。

■パスを反転するには?
ちょうど配列を反転させるreverse()というメソッドがあるので、これでパス点群、イン群、アウト群を反転させればよい、と思ってしまいますが、これだけではうまくいきません。
進行方向が変わるので、インとアウトも逆になるんですね。なので3つともreverse()してから『createPath( mVtxs, mIns, mOuts, mCls);』のところを 『createPath( mVtxs, mOuts, mIns, mCls);』 と、インとアウトを逆にして入れねばなりません。

■隠しコンテンツの罠
シェイプレイヤーの階層構造を見てみると、『シェイプ 1』や『グループ 1』という名前のグループがあり、パスやら線やらとグループトランスフォームがそのグループ内にあるように見えます。が、本当の構造はこうではありません。グループトランスフォームはグループの直下にあるのは正しいです。が、グループ直下には隠された『コンテンツ』があり、パスやら線やらはその中にあるんです。
プロパティ.propertyGroup(countUp = 1)というメソッドでそのプロパティの××個上のプロパティを得ることができるんですが、そのようにして別プロパティを指定する場合、隠されたコンテンツを数えないと別のプロパティを指定してしまうことがあります。
隠しコンテンツが確かにあるのを知るためには、シェイプ関連プロパティのデフォルトエクスプレッションを見ればいいだけす。例えばパスなら『content(“シェイプ 1”).content(“パス 1”).path』です。見えているコンテンツはレイヤー直下のものだけなのに、contentという名前が2つ出てきていますね。この2つ目が隠しコンテンツです。
「なぜコンテンツを隠しているか」ですが、おそらく見やすさのためでしょうね…。
コンテンツの上には必ずグループかレイヤーがあり、グループの上には必ずコンテンツがあるという性質はプロパティ探しのときに役立ったりします。

■シェイプグループ座標の罠
シェイプグループはそれぞれ独自にトランスフォームを持っています。が、toCompやfromCompではこのシェイプグループトランスフォームは扱えません。そのため、 シェイプグループトランスフォームをデフォルトから変えてしまうと、パス点をヌル位置に合わせたりするのが困難になります。
変換関数についてはこちら
グループトランスフォームがデフォルトでない場合の対処法はこちらあるいはAEスクリプト『Exte』の『Fix GpTfm』ボタンを使う。

■ソースレクトは必ずレイヤー座標になる
パスの変換をしていると混乱してしまうのですが、ソースレクトはグループトランスフォームがあろうがなかろうが、必ずレイヤー座標となります。

■pointOnPathなどはパスのエクスプレッションで使うものではない
pointOnPath()、tangentOnPath()、normalOnPath()は、パスの全長を1として、例えば()に0.5と入れれば、その地点、接線、法線を得られる便利なメソッドです。詳しくはこちら
しかしこれらは基準がパスの全長であり、点やハンドルとは関係ないものなので、パスのエクスプレッションで使うことはあまりありません。これらはパスではない別レイヤーに使ってパスと連携させるためのメソッドだと言えるでしょう。

さらなる発展へのヒント

■より複雑な操作をするには?
パス点、イン、アウトが別々の配列に入っていると、パスを2つに分割したり順番を入れ替えたりする際、いちいちfor文を3つ(あるいは1つのfor文で3個分同じような処理)を書かねばなりません。
なので「これは本格的にパスを操作するぞ…」となった場合、『パス点&イン&アウト』の3点を1セットにした『パス情報セット』オブジェクトを作り、それを配列にして扱うことをお勧めします。

イメージとしては
[P,P,P][I,I,I][O,O,O] を [PIOセット,PIOセット,PIOセット] にする、といった具合ですね。

始めにこれを行い、最後にまた分割するのが若干面倒ですが、それらの処理はそれぞれ関数化しておけば問題なしです。
イラストレーターのスクリプトではパス点とハンドルはこの形で入っており、こちらのほうが扱いやすいですね…。

さらに、以下はここまでくるとスクリプトの話なのでさらりと書きます。
■参照渡しの罠
上記のようなオブジェクトにしての操作をしているとします。パスを2つに分割して一部を同じ点にした場合、参照渡しにより同じ点は共有してしまっています。その上でその点のインアウトを[0,0]にしたりすると分割した別パスの同じ点も同じように変わってしまう、という問題が起きます。この場合はオブジェクトごと新しいものを作ってそこに新値を入れ、配列内のオブジェクトを書き換えせねばなりません。
この参照渡しの罠はけっこうハマるポイントなので、スクリプトを書く際は参考にしてみてください。

参考になるサイト

パスのエクスプレッションの参考ではなく、ベジェ曲線やその他パスエクスプレッション内で役立つ情報が載っているサイトの紹介です(JavaScriptの形になっていないものもあります)。

■ベジェ曲線
s.h’s page様 ベジエ曲線について
ベジェ曲線とは?ベジェ曲線の分割や曲線自体の式が分かる。『数式で表すの巻』や『分割の巻』の説明&図をそのままJavaScriptにすれば、最も基本的で重要なベジェ曲線を扱う関数が出来上がる。「載っている数式をJavaScriptに」しようとすると難しく感じるので「説明している内容や図を」そのままバカ正直にJavaScriptにするのがポイントです。そうすることで、実は分割点を得るだけでなく一緒に分割点用インアウト、さらに分割点前後の変化したアウト、インも得られる関数になります。

■交点
画像処理ソリューション様 4点からなる交点の求め方
パス点とその方向であるインorアウト点のセットが2つあれば、それらが延長してクロスする場所を得られる。

■交差判定
Algorithm様 もっと簡単に-線分交差判定-
直線ではなく線分なので、限定された範囲の線2つが交差しているか分かる。それ以外にも「とある点が、別2点が作る直線のどちら側にあるか」といった処理にもできる。

■単位ベクトル
GAMEWORKS LAB様 ベクトルと正規化
単位ベクトルを得ると『斜めの1ピクセル分の[X,Y]』が分かる。単位ベクトル×好きな長さで、好きな長さ分だけ斜めに移動できる。

■回転
dA-tools.comアニメの道具箱様 逆正接 atan2() 位置から角度
Qiita内 Fumio Nonaka様 三角関数で角度から座標を導くふたつの式の使い途 角度から位置
交点を求める際「そこから90度回転した線と交差させたい」といった場合に回転の式が役立ちます。

■その他
サイトは思い出せませんが、『内外判定』『時計回り、反時計回り判定』『atan2 360度』などのキーワード検索が役に立つやもです。

■この記事の出典
AfterEffects compZero様 シェイプレイヤーを作る

After Effects スクリプトリファレンス様 Shape object 他

よかったらシェアしてね!
  • URLをコピーしました!