(Lightwave3D:Expression)ベクターファンクション、"angle"ファンクションの解説(目標を注視の再現)
使用ソフト
- Lightwave2020
Lightwave3Dの"エクスプレッションビルダー"にある"ベクターファンクション"の"angle"の使い方がいまいち分からなかったので、読み解いたものを解説した記事です。"angle"は"モーションオプション"パネルにある"目標を注視"と同じ挙動をエクスプレッションで再現しようと思って調べている際に自分が可能性を見出したモノなのでその点についても後半で言及しています。
ベクターファンクションの"angle"については過去記事を参照してください。
- "angle"ファンクション
- サンプルシーンの作成
- エクスプレッションの作成と適用
- エクスプレッションの挙動検証
- "selector"ファンクションを使って360°回転させる
- 他軸の"angle"ファンクション
- まとめ
"angle"ファンクション
"angle"ファンクションを過去記事で翻訳した解説文から赤文字にした部分を手がかりに読み解いていきます。
angle (X axis, Standard)
◇記述( Description)
この関数は、2つの入力ベクトルと原点<0,0,0>を比較して、(X軸またはYZ平面と比較した)角度値を返します。入力は、位置ベクトル、スケールベクトル、またはワールドポジションベクトルのみです。
angle( InputA , InputB , 1 )
inputA には、位置ベクトル、スケールベクトル、またはワールドポジションベクトルを指定できる。
inputB には、位置ベクトル、スケールベクトル、またはワールドポジションベクトルを指定できる。1はX軸(またはZY平面)を表す。
◇サンプル(Example) ~angle X axis共通~
angle( < 0, 1, 0 > , < 0, 0, 1 > , 1 )
上記のangle()関数は、X軸と原点<0,0,0>を基準とした2つの入力位置ベクトルを比較し、90度の値を出力します。angle( < 0, 1, 1 > , < 0, 0, 1 > , 1 )
上記のangle()関数は45度の値を出力します。angle( < 0, 0, -1 > , < 0, 0, 1 > , 1)
上記のangle()関数は180度の値を出力します。*上記の例では、ビューポートを'Right'に切り替え、上で指定した値を持つ2つのNullオブジェクトを使用し、LightのPitchにangle()関数を適用します。これは、angle()関数がどのように動作するかを説明するものです。
サンプルシーンの作成
まず"記述( Description)"に
この関数は、2つの入力ベクトルと原点<0,0,0>を比較して、(X軸またはYZ平面と比較した)角度値を返します。
とあるのですが、イマイチ意味がよくわかりません。なので"サンプル(Example)"に記述がある「2つのNullとLightを使ったサンプルシーン」を作成して読み解いて行こうと思います。"2つのNull"が"記述( Description)"にある"2つの入力ベクトル"に当たると思われます。"Light"は"angle"エクスプレッションを適用して結果が出力されるアイテムです。
下画像のように青いNull_01(ピラミッド形状・Y軸)と赤いNull_02(ピラミッド形状・Z軸)を作りました。デフォルトで作成されるライトは回転の値をすべて0にしておきます。"YZ平面"がわかりやすいようにグリッド形状のNull_planeを追加してあります。

エクスプレッションの作成と適用
グラフ編集パネルを開いて①"各アイテムのチャンネル"タブで"Light"の"Rotation.P"をダブルクリックして上のチャンネルリストに追加しておきます。
②"エクスプレッション"タブから"ビルダー"ボタンを押して"エクスプレッションビルダー"を開きます。③"条件ファンクション"ドロップダウンメニューから"ベクターファンクション"の"angle(X axis, Standard)"を選びます。

"angle"ファンクションの作成メニューから"InputA"のドロップダウンメニューで"Vector"を選びます。開いた"入力選択"パネルで"Null_01"の"Position_X"を選びます。(X,Y,Zどれを選んでも可)

同様に"InputB"には"Null_02"の"Position_X"を選びます。(X,Y,Zどれを選んでも可)

Input欄の入力が終わったら⑥"有効"ボタンを押すと⑦"名称"入力フィールドに仮名が付くので任意の名前に変えます。今回は"angleX"と、しました。
angle([Null_01.Position],[Null_02.Position], 1 )

すべて入力し終えたら⑧"エクスプレッションの作成"ボタンを押すとグラフ編集パネルに作成したエクスプレッションが追加されます。今回、バグか仕様なのかわかりませんが「エクスプレッションはコンパイルされません」というエラーアラートがでます。とりあえず"OK”を押して閉じます。これでエクスプレッションビルダーでの作業は終了なので⑩パネルを閉じます。

グラフ編集パネルに戻ります。
⑪"確認"ボタンがグレイアウトしておらず押せる状態の時はエクスプレッションに何らかのエラーがある場合が多いです。押すと先ほどと同じエラーアラートが表示されるので、確認したら"OK"を押して閉じます。(エラーがある状態だとエクスプレッションをチャンネルに適用することが出来ないため、問題を解決する必要があります。)

検証の結果、今回のエラーはエクスプレッションに問題があるのではなく、参照元であるNullが原点にあることが原因のようです。⑫とりあえずNull_01とNull_02を適当に移動させた後に⑬"確認"ボタンを押すとエラーは出ずにコンパイルされるようになります。(コンパイルされると"確認"ボタンはグレイアウトします。)その後、移動させたNullは再び原点に戻しておきます。

グラフ編集パネルのチャンネルリストで"Light.Rotation.P"を選択します。(もし、リストにない場合はもう一度"各アイテムのチャネル"パネルから登録してください。)
エクスプレッションタブの⑭"適用"ボタンを押すと選択したチャンネルにエクスプレッションが適用され、⑮チャンネルリストの名前の前には●印が付きます。

以上で準備完了です
エクスプレッションの挙動検証
サンプルシーンを使い、試しにNullを動かしてみると。ライトのピッチが回転するのが確認できます。ライトが回転するのはNullをY軸かZ軸に動かした時のみ(YZ平面上)のようで、X軸に動かしてもライトは動きません。
そこで、今後はサンプルにあるように、「ビューポートを'Right'に切り替え」ます。右面表示はちょうどYZ平面に対してカメラを垂直に置いた状態です。

それでは改めて「2つの入力ベクトルと原点<0,0,0>を比較して、角度値を返します。」にある"返す角度値"とは何かを考えます。結果が表示されるライトには回転角がわかりやすいようにカスタムオブジェクト"分度器"を追加したNullを配置しておきます。

サンプルにある値にNull01とNull02の位置を動かしてみます。
angle( < 0, 1, 0 > , < 0, 0, 1 > , 1 )
上記のangle()関数は、X軸と原点<0,0,0>を基準とした2つの入力位置ベクトルを比較し、90度の値を出力します。

angle( < 0, 1, 1 > , < 0, 0, 1 > , 1 )
上記のangle()関数は45度の値を出力します。

angle( < 0, 0, -1 > , < 0, 0, 1 > , 1)
上記のangle()関数は180度の値を出力します。

このことからサンプルの記述にある
2つの入力ベクトルと原点<0,0,0>を比較して、(X軸またはYZ平面と比較した)角度値を返します。
の"返す角度値"とは以下の角度だと言うことがわかりました。

挙動がわかりやすいように少し工夫してみます。原点にライトと同様にカスタムオブジェクト"分度器"を追加したNullを配置します。そのNullを親とするボーンを2つ追加して其々の"目標アイテム("モーションオプション"パネル)"に"Null_01"、"Null_02"を指定します。Null_01は< 0, 0, 1 >の位置に固定して、Null_02を動かすと分度器の目盛りを針が指すような見た目を作れます。
Null_02をY座標の正負どちらの位置に動かしてもライトの回転が180度を超えない事を確認しておいてください。"angle"ファンクションで得られる"返す角度値の範囲は"0度~180度に限られるようです。180度~360度を得るには少し工夫が必要になります。
※固定するNullはangleファンクションの"InputA"の値にする必要があります。"InputB"を固定すると挙動が意図しないものになります。

"selector"ファンクションを使って360°回転させる
"angle"ファンクションが返す角度を180度~360度に反映するには単純に返す値を360度から引いてやれば良いのですが、( 360-angle([Null_01.Position],[Null_02.Position], 1 もしくは360-[angleX] )そうすると今度は180度~360度の範囲しか値を返さなくなります。ライトの回転を同期させるにはNull_02のY座標の値が負の場合は0度~180度を返し、値が正の場合は180度~360度を返すようにできれば良いわけです。(Lightwave3Dのデフォルトの回転方向はZ軸+方向からスタートして時計回り)
そのために"selector"というエクスプレッション関数を使います。
"selector"ファンクションを使う
"selector"は"LScriptファンクション"のカテゴリーにある条件分岐で結果を返すことができる関数です。詳細は過去記事も参考にしてください。
"selector"にはパラメーターが4つあります。"InputA"の入力値と"InputB"の入力値を比較して"InputB"より小さければ、"InputC"の入力値を結果として返します。もし小さくない場合(以上)は、"InputD"の入力値を返します。

今回の場合「"Null_02"の"PositionY"が"0"より小さければ"angleX"の結果を返す。"0"以上ならば"360- angleX"の結果を返す。」となります。
それを当てはめると以下のようになります。
selector
selector([Null_02.Position.Y],0.000,[angleX],360-[angleX])

angleX(サブエクスプレッションとしてselectorから参照される。)
angle([Null_01.Position],[Null_02.Position], 1 )

結果は以下の動画のようにNull_02の位置とライトの向く方向が完全に同期するようになります。
制御に使うNullを一つだけにして"目標アイテム"として使う
Null_01は<0,0,1>の座標に固定しているので"angleXエクスプレッション"もNull_01のPosithonを参照せず、<0,0,1>と値を記せば目標アイテムとして使用するNullをNull_02だけにすることが出来ます。そのうえで、新たに原点のNullにボーンを追加して、チャンネル"Rotation.P"に"selectorエクスプレッション"を適用することで、ピッチ回転限定ですが"モーションオプション"パネルの"目標を注視"と同じ挙動をエクスプレッションで得ることが出来ます。以上の事を反映したものが以下になります。(※それぞれ"_V2"を付けた名前にしてあります。)
angleX_v2
angle(<0,0,1>,[Null_02.Position], 1 )

selector_v2
selector([Null_02.Position.Y],0.000,[angleX_v2],360-[angleX_v2])

Bone_03を追加して"Rotation.P"に"selector_v2"を適用する。X軸限定でNull_02を目標アイテムにした"目標を注視"と同様の結果を得られる。
※Null_01,Bone_01,Bone_02は非表示
他軸の"angle"ファンクション
他2軸の"angle"ファンクションも検証方法は同じなので、結果だけ参考に記しておきます。
angle (Y Axis, Standard)
この関数は、2つの入力ベクトルと原点<0,0,0>を比較して、(Y軸またはXZ平面と比較した)角度の値を返します。入力は、位置ベクトル、スケールベクトル、またはワールドポジションベクトルのみです。結果は度単位で、範囲は 0~180 です。
angle( InputA , InputB , 2 )
inputA には、位置ベクトル、スケールベクトル、またはワールドポジションベクトルを指定できる。
inputB には、位置ベクトル、スケールベクトル、またはワールドポジションベクトルを指定できる。2はY軸(またはXZ平面)を表す。
Null_01の固定座標は(0,0,1)
"selector"で360度から引く(180~360を返すようにする)のは、Null_02のX座標の値が負の場合。
angle (Z Axis, Standard)
この関数は、2つの入力ベクトルと原点<0,0,0>を比較して、(Z軸またはXY平面と比較した)角度の値を返します。入力は、位置ベクトル、スケールベクトル、またはワールドポジションベクトルのみです。結果の単位は度であり、範囲は 0~180です。
angle( InputA , InputB , 3 )
InputA には、位置ベクトル、スケールベクトル、またはワールドポジションベクトルを指定できる。
InputB には、位置ベクトル、スケールベクトル、またはワールドポジションベクトルを指定できる。3はZ軸(またはXY平面)を表す。
Null_01の固定座標は(0,1,0)
"selector"で360度から引く(180~360を返すようにする)のは、Null_02のX座標の値が正の場合。
※ライトのバンク回転が分かりづらいので分度器メモリ上にNull(boal形状)を追加してあります。
まとめ
ベクターファンクションの"angle"を使えば、一つの回転軸限定ですが"モーションオプション"パネルの"目標を注視"と同じ挙動をエクスプレッションで得ることが出来ます。
本当は2つの回転軸に"angle"を適用すれば"目標を注視"と全く同じ挙動が得られると考えたのですが、実際にやってみると思うような結果を得られませんでした。通常アイテムの回転軸はH→P→Bのように順番に回転するように設定されているのですが、"angle"をHとPの2軸同時に使うと回転の計算も同時に行うため90度を超えた所で互いに180度まで反転するような挙動が出てしまい、うまくいかないようです。なんらかの解決作が見つかったら改めて記事にしたいと思います。

