行列やらアフィン変換、射影変換を調べまして、座標変換についてちょっと詳しくなったので説明してみます。
あくまで「基本的にはこうやるとできるよ」という説明なので、実際にAEが行っている処理とは違うやもです。
かんたんに理解するため、2Dに限った話にします。
レイヤー座標からコンポ座標へ
実はコンポ座標っていうのは特殊なときがあるんですが、話がややこしいのでこの記事では「画面上の左上が原点[0,0]、縦横が水平垂直、1マスが1ピクセル、全てのおおもととなる座標空間を『コンポ座標』という表現で統一します。
*特殊なときが気になる方は記事の最後にちょっと書いてあります。
さて本題ですが、以下のような状態があったとします。
★は、重なっているレイヤーの子です。そして右レイヤーは左レイヤーの子です。これをコンポ座標にするにはどうするのか(toCompfromLayer)?
我々ユーザーに与えられた情報は各トランスフォーム情報です。さらに、子は親レイヤー座標空間での値となっています。
答えは「子も親も、生まれたとき(かつて原点&マス目がコンポ座標と合致していたとき)までさかのぼり、そこから1からトランスフォームを組んでいく。その作業に★点を付き合わせる」です。それが以下の動画です。
ややこしい話なんですが、全てのレイヤーのマス目と原点がコンポ空間と一致しているところから始めるので、「一番最初に映っている★点の場所指定方法は、親である右レイヤー空間での値でかまわない」ということです。なぜなら原初は右レイヤー空間はコンポ空間と揃っているから、実質コンポ空間座標として扱って問題ないからですね。
そこから始めて、まずは子から。★をレイヤーのトランスフォームに付き合わせるように、アンカーポイント分逆方向に進ませ、スケール分伸ばした位置へ移動、回転させた位置へ移動、最後に位置分移動させます。そして親でも同じことをします。すると、元の形に戻りつつ、★の位置変化は変形に付き合わせる過程でずっと位置計算をしていたので、コンポ座標として出るということです。
注意してほしいのは「レイヤーが生まれた場所とはどこか?」です。レイヤーを新規作成するとコンポ座標の真ん中にできますが、ここではありません。真ん中にできた時点で位置やアンカーポイントに数値が入ってしまっていますね。それを[0,0]にした地点が生まれた場所です。そこにあってスケールが[100,100]、回転がゼロなら、レイヤー座標空間=コンポ座標空間となります。
また、トランスフォームをかける順、親子の順にも注意してください。コンポ座標が欲しいので、ずっとかんたんにコンポ座標基準で計算できる順番が望ましいです。のちの処理に影響を与えてしまう処理は後からのほうがいいです。例えば、スケール、回転ではなく、回転、スケールにしてしまうと、回転して斜めになったレイヤーをスケールして、斜めに伸びた位置の取得計算はかなりめんどう、といった具合です。アンカーポイントを最初にすることで、スケールと回転の計算が『原点から』の計算で済みます。位置が最後なのは、他のプロパティの影響を受けないからです。「そういえば回転させた方向に移動させたいときはアンカーポイントを使ったなあ」と思い出す人もいるやもです。
親子もまた、影響を受けてしまうもの(子)から先にやったほうが、形が変わらないうちに簡単な計算を使うことができます。もしも親からやってしまうと、子の位置や形状(空間)がコンポ空間とズレてしまい、子のトランスフォーム情報を単純に使うことができなくなります。
■まとめ
toCompは「一から組み上げる作業にレイヤー座標点を付き合わせる」。
子~親の順。
アンカーポイント、スケール、回転、位置の順。
アンカーポイントだけは入っている値のマイナスに移動させる。
(アンカーポイントを正の値ぶん増やすと、空間がマイナスに移動するため、点を空間に追従させるには逆移動が必要)
*マイナスに移動させるとは、元からマイナス値が入っているものはプラスに移動させるということです。
コンポ座標からレイヤー座標へ
これは単純な話で、上記の逆を行います。
「組み上がっているものを元に戻し、その作業にコンポ座標点を付き合わせる」ってことですね。
下の動画の★点はコンポ座標です。
最終的に元に戻った(生まれた場所に還った)レイヤーの空間はコンポ空間とマス目も原点も一緒です。
レイヤー空間=コンポ空間ですね。
なのでそれに付き合わせたコンポ座標点は、そのレイヤー空間にあると言っても問題ない、ということです。
(まさに『レイヤー座標からコンポ座標へ』で「ややこしい話ですがーー」と書いた説明と同じ状態です)
注意点は全てを逆順にやること。トランスフォームはマイナスの数値で計算し(アンカーポイントはマイナスからのマイナスなのでプラス)、スケールは逆数を用います。逆数とは1/?なので、例えば45%であれば 1 / (45/100)で計算ですね。このようにすれば、また簡単な計算で済みます。
■まとめ
fromCompは「 組み上がっているものを元に戻し、その作業にコンポ座標点を付き合わせる 」。
親~子の順。
位置、回転、スケール、アンカーポイントの順(マイナス値にする、スケールは逆数を使う)。
アンカーポイントだけは入っている値そのままに移動させる。
(アンカーポイントを正の値ぶん増やすと、空間がマイナスに移動するため、点を空間に追従させるには逆移動が必要)
*マイナスに移動させるとは、元からマイナス値が入っているものはプラスに移動させるということです。
コンポ座標とは?
レイヤーが全て2Dならば、コンポ座標はワールド座標の2D版とみなして問題なしです。
が、3Dレイヤーがあると(カメラを作ってなくても)デフォルトカメラで視点を動かせるようになり、もちろんカメラを作った場合も同様に視点が動かせるようになります。
そして、その中の2Dレイヤーはカメラを動かしても動かない、というかカメラにぴったり追従しますね。そうです。2Dレイヤーはカメラの撮像面に自動でピタッとくっついてきている、とも言えるんです。
そうなると、2Dレイヤーの座標はカメラが映す撮像面の左上を原点とした座標(Z位置省略版)となります。カメラが動いているので、もはやそこはワールド原点とは別の位置です。これがコンポ座標なのです。
まとめると、
レイヤーが全部2Dならコンポ座標=ワールド座標2D版。
3Dレイヤーが1つでもあるとカメラができる。
が、カメラがデフォルトから動いてなければコンポ座標=ワールド座標2D版。
カメラが動くとコンポ座標≠ワールド座標2D版。 コンポ座標は撮像版というレイヤーのレイヤー座標となる。
まあただ、変換関数を自作するんでもなければ、たいして重要な話ではないです。
さらなる情報(変換関数を作りたい人向け)
行列計算の場合はそのまたさらに逆順で行う必要がある。
何事もワールド座標を通して変換する。
toComp,fromComp,fromCompToSurfaceはカメラが関わっているので、toWorldやfromWorldで解決するならそっち経由の方が計算が速い(もしもAEが、カメラが無い状態のtoCompという場合分けをしているのであれば別)。
カメラにはアンカーポイントとかが無いので、単純な親子計算のコードにするとき普通のレイヤーとの場合分けがめんどう。
2ノードカメラには逆に目標点があり、それと位置によるYX回転が加わる(Yのほうが先)。
射影変換(toComp3D)にはカメラ情報が必要だが、カメラがヌルとかの子になってるとその変換もせねばならない。
シェイプグループトランスフォームの歪曲(Skew)はスケールと回転の間に入る。
歪曲軸というものがあり、これを考慮するにはいったん歪曲軸ぶん回転を戻してXSkewしてまた回転させる必要がある。
その歪曲軸はfromCompでも逆順(マイナス値)にしなくてよい。
なぜかAEのSkewはMath.tan()を使ったものの逆となっており、歪曲軸も逆。それを考慮せねばならない。
射影変換(toComp3D)は行列で最後までできるわけではなく、最後にXとYをZで割るという割り算をしなければならない。
fromCompToSurfaceは行列でやる方法が見当たらない。カメラと該当点(2DなのでZをカメラのズームにする)のレイヤー座標を出し、レイヤー座標上でのトップビューとサイドビューで見て、それぞれレイヤーの横幅線と縦幅線と、カメラ点&該当点をつなぐ線の交点を出すことで算出は一応可能。
カメラも自身を[0,0]とするレイヤー、そして撮像面(AEのビューワー)はカメラの子レイヤーで、原点はカメラ座標から[-W/2,-H/2,Zoom]である、と考えると射影変換等、カメラが関わるものへの理解が速い。
コンポ上に1つでも3Dレイヤーがあると(カメラが無くとも)activeCameraはnullではなくなる(デフォルトカメラを指す)。
2Dと3Dレイヤーの親子の組み合わせ問題。2Dが挟まると、その前の親すべてが2D扱いとなる。その後3Dが続き、最終的に子が3Dレイヤーならば、それまでの値はZ情報無しのワールド座標とみなされる。つまり最終的に2Dレイヤーであれば、全て2D情報とみなされる。
〇〇.toWorldの〇〇を2DレイヤーにするとtoCompになってしまうのはこの仕様によるもののよう。説明が難しいが、なぜなら〇〇は2Dレイヤーのため、カメラの撮像面に勝手に追従した形で表示されている。そして変換計算はワールド[0,0]からの2D情報のみで行われる。その結果はワールド[0,0]からの変換だが、それを2Dレイヤーに適用すると、これも勝手に撮像面に追従した形で表示される。すると変換した位置と元レイヤーの変換前位置が合う。実質toCompとなるのである。AEではこれは考慮されておらず、2Dレイヤー.toWorldでもワールド座標が得られるようにはなっていないことに注意。