■VmdScript α版

※レガシー環境用なので利用時は注意!!


●VmdScriptとは？

モーションデータをプラグイン／スクリプトから作成する機能の総称です。

Scriptとありますが実装は特殊な独自スクリプトによるものではなく、
.NETフレームワークによる一般的なクラス実装なので、対応するプログラミング環境から取り扱い可能です。

これらの機能は他のプラグイン機能と同様にインターフェイスからの使用が主となります。

※.NETフレームワークの各対応言語から使うことができますが、以下の説明においてはC#での記述を基本としています。

※現状仕様が固まっていない部分もいくつかあるので、スクリプト(CSScriptプラグイン)としての使用を推奨。




●VMDとVME

VmdScriptは VMD(IPEVmd) 及び VME(IPEVme) という2種類の異なった仕様から構成されています。

○VMD : IPEVmd
MMDのVmd形式を元にした仕様。
ボーン／表情／カメラ／照明をそれぞれ共通リストで一括管理し、MMD上のキー配置と同様の値データとして取り扱う形式。

○VME : IPEVme
独自のモーション操作仕様。
共通のイベント登録による値／速度／加速度制御が可能。複雑な軌道なども簡易に記述することができますが、
反面ラムダ式などの記述が必要になるので言語仕様への慣れが多少必要になります。

VMDにはないボーンのスケール変化なども制御可能（一部機能制限あり／スケール変化はVmdファイルへの出力はできないので、VMDViewでの確認／映像出力のみ）

VMEによる実行結果は Result形式(IPEVmeResult) を経由してVMDへ出力可能です(VMDを基にしてVME側を初期化することも可能)
基本的に VME は VMD の上位互換の仕様となります。




●VMD(IPEVmd)の使い方

//ビルダから作成し
IPEVmd vmd = PEStaticBuilder.SC.Vmd();

//PMDで初期化
vmd.Init(pmd);		// IPEPmd pmd = args.Host.Connector.Pmd.GetCurrentState(); など

//任意のボーンキーを作成し
IPEVmdBoneKey boneKey = PEStaticBuilder.SC.VmdBoneKey();

boneKey.FrameIndex = 0;		// 0フレームに
boneKey.BoneIndex = 0;		// 先頭ボーンを
boneKey.Translation = new V3(0,5,0);	// 上に5移動
boneKey.Rotation = new Q(5,5,5);	// XYZ軸で5度づつ回転

// キーを追加
vmd.Bone.Add(boneKey);

// VMDファイルへ保存
vmd.ToFile(path);	// path = @"C:\test.vmd"; など


という流れで、ボーン(Bone)、表情／モーフ(Morph)、カメラ(Camera)、照明(Light)それぞれにモーションキーを追加、
VMDファイルへ出力することが可能です。

また、
args.Host.Connector.View.PMDView.BootupVmdView(pmd, vmd);
と、VMDViewで直接プレビューを行うことも可能です。

既存のVMDと異なる部分は、ボーン／モーフの各キーでは名称ではなくIndex値にて指定すること。
そのために予めVMDを名称とIndexの関係で初期化しておく必要があります（PMDからの初期化 or 各名称で初期化）

※オリジナルのVMDデータの各キーには全て対応する名称が格納されていますが、管理するデータ量が多くなるので数値(Index)へ変更

他にも既存のVMDファイルから初期化することも可能です（IPEVmd.FromFile(string path)）


●VMDに格納可能なキー形式 

※詳細についてはVisualStudioのオブジェクトブラウザなどからプラグイン機能を参照してください。

名前空間 : PEPlugin.Vmd

○ボーン(Bone)        : IPEVmdBoneKey
○表情／モーフ(Morph) : IPEVmdMorphKey
○カメラ(Camera)      : IPEVmdCameraKey
○照明(Light)         : IPEVmdLightKey
○セルフシャドウ      : IPEVmdSelfShadowKey

これらのキーに任意のフレーム値と各値を設定し対応リストへ追加することで、モーションデータを作成することが可能です。

※カメラ／照明用はMMDでは特定のモデル名にしておく必要があります(VMDViewで使う場合は関係なし)
  void SetModelNameForCameraLight();  // 保存(ToFile())前に呼び出す必要あり


●VMD内の値設定について

VMD(IPEVmd)内の各値(ベクトル／回転量など)はPMXエディタのプラグイン環境と同じく、
IPEVector3 や IPEQuaternion での取り扱いになっています。

これらは実体がなく取り扱いが非常に面倒なので、一般的にSlimDX側との相互運用可能なクラスオブジェクトを介して値設定を行うことを推奨します。
※
IPEVector3 <> V3 <> Vector3
IPEQuaternion <> Q <> Quaternion
など。

詳しくは CSScriptプラグインの"CSScript.txt"内に記述されています。

また以下VME内での取り扱いにおいても、これらの相互運用クラスの機能を想定したものが含まれます。






●VME(IPEVme)の使い方

//ビルダから作成し
IPEVme vme = PEStaticBuilder.SC.Vme();

//PMDで初期化
vme.Init(pmd);		// IPEPmd pmd = args.Host.Connector.Pmd.GetCurrentState(); など

// 先頭ボーンにイベント設定
vme.Bone[0].T.Set(0, new Vector3(0,5,0));  // 上に5移動
vme.Bone[0].R.Set(0, new Q(5,5,5));        // XYZ軸で5度づつ回転

// VmdViewでプレビュー
vme.Preview();

または、

// イベント実行→結果
IPEVmeResult result = vme.Run();

// 結果をモーションVmdへ保存
result.ToMotionVmd().ToFile(path);	// path = @"C:\test.vmd"; など


といった流れでモーションを作成することができます。
VMDと比較して、

・キー単位で値を設定する必要がない
・移動と回転など、各パラメータ値を分離して管理することが可能

などの特徴があります。
反面VMDのようにフレーム間の値を自動計算(補間)する仕様ではないので、それらの変化もユーザー側で管理する必要があります。


●VMEで利用可能なオブジェクト

名前空間 : PEPlugin.Vme

○ボーン(Bone)        : IPEVmeBone
○表情／モーフ(Morph) : IPEVmeSingleValueElement
○カメラ(Camera)      : IPEVmeCamera／IPEVmeCameraPosition
○照明(Light)         : IPEVmeLight
○セルフシャドウ      : 利用不可

これらはイベント要素インターフェイス(IPEVmeEventElement) から派生され、VMEイベントとして個々に管理されています。

各オブジェクトの値設定はプラグイン側と異なり、SlimDXの値型(Vector3やQuaternionなど)をそのまま用います。
そのため外部からプラグイン経由で作成する場合は、PMXエディタ付属の"SlimDX.DLL"を参照アセンブリとして組み込む必要があります(添付は不要)



●イベント管理による利点

例えば30フレーム位置(1秒後)の先頭ボーンにそのときのカメラの方向を向かせたい場合、
キーによる事前登録では予め1秒後のカメラ位置を計算しておく必要があり、その分管理体系が煩雑になります。

対してイベント駆動型の場合、カメラなど他のオブジェクトの状態に関係なく、
「30フレームになったらその値を取得→その方向へ向きを変える」
といった動作指定が可能になり、手軽にオブジェクト同士の連携ができるようになります。

具体的に上記のパターンは、

// 先頭ボーンの30フレーム位置に回転量登録
vme.Bone[0].R.Set(30, (IES state) =>
{
    IBS s = state as IBS;    // 状態変数をボーン用に変更(IES:IPEVmeEventState／IBS:IPEVmeBoneState それぞれのエイリアス)
    return Q.Dir(vme.Camera.GetCameraPos() - s.P.PV);    // 現在の位置->カメラ位置へのベクトル(方向)に向き指定
});

という具合に非常に簡潔に記述することが可能です(実際の運用では必要になるパラメータ設定(IDなど)をいくつか省略しています)

※さらに詰めて書けば、一行でも記述可能(参考)
vme.Bone[0].R.Set(30, (IES state) => Q.Dir(vme.Camera.GetCameraPos() - (state as IBS).P.PV));


また上記イベント設定は汎用形として下記のように記述することも可能です。

vme.Bone[0].SetEvent(30, (IES state) =>
{
    IBS s = state as IBS;
    s.R.Set(Q.Dir(vme.Camera.GetCameraPos() - s.P.PV));   // 現在の状態変数(回転量)を変更
});

実際はこちらの運用方法がより一般的な利用方法(どのオブジェクトでも共通)になりますが、
利便性に合わせていくつかの設定方法を選択することが可能です。


尚、これらイベント駆動の記述には、ラムダ式やデリゲート機構（関数ポインタの汎用形）が必要になるので、
応用するには言語仕様(.NETフレームワーク／C#)を多少理解しておく必要があります。

※.NETフレームワークのeventを経由していないのは記述量的に(object sender)などが無駄なので。



●速度／加速度設定

移動量／回転量などは現在値の直接設定以外に、速度／加速度を設定することが可能になっています。
等速移動や等速回転、また加速移動／回転などもこれらにより簡易に記述することができます。

例：一定速度での回転
vme.Bone[0].R.SetV(0, new Q(0, 5, 0));
→
Y軸周りを5度／フレームの速度で回転し続ける(向きの変化／位置は変わらない)


他にも各値設定では現在値へのオフセット(加算／積算)指定も可能なので、
それらにより擬似的に速度／加速度的な動作を促すことも可能です。

例：現在位置を徐々に移動する
vme.Bone[0].T.Set(0, 120, new Vector3(0, 1, 0), OpType.Add);   // OpType.Add : 現在の移動量にオフセット加算
→
0-120F(4秒間)上方向に+1／フレームの速度で上昇


速度／加速度の時間単位はフレーム。内部的にはフレーム毎に現在値や速度値へ単純加算(積算)を行っています。

移動量や回転量は、現在値／速度値／加速度値をそれぞれ個別に持っているので、
それらを適切に管理することが重要になります。

※例えば静止するために速度を0に戻しても加速度が0に戻っていない場合、静止することにはなりません。


ベクトル値の速度／加速度指定には行列を使うことも可能です。
行列を利用することで、ボーンをある点中心に回転移動するなど、非直線系の運動を記述することができます。

例：回転運動
vme.Bone[0].T.SetV(0, M.RotM(new Vector3(5, 0, 5), Vector3.UnitY, Q.D2R(15)));
→
(5,0,5)を中心点として、現在位置からY周りで15度／フレームで回転運動し続ける（ボーンの向きは一定方向のまま）

ただし上記の場合ボーンの基準位置がフレーム毎に変化したりすると、予想とは異なった軌道になることもあります。ご注意ください。



●状態変数 と イベント変数

VMEのイベント構造では登録されたイベントを、

1. 状態変数を通して各イベントとやり取り
2. イベントにより変更された結果を出力

という形式で処理が行われます。

それぞれのイベント処理は状態変数(IPEVmeEventState(IES)派生)を介して呼び出しが行われ、
イベント内でその状態変数を更新することで、モーション結果として出力することができます。

例えば、ボーンの状態変数である IPEVmeBoneState(IBS) 内のパラメータは、

　IPEVmeVectorValue T { get; }     // T : 移動量
　IPEVmeQuaternionValue R { get; } // R : 回転量
　IPEVmePosition P { get; }        // P : 位置
　IPEVmeDirection D { get; }       // D : 方向
　IPEVmeScale S { get; }           // S : スケール

となっており(短絡系のみ列挙)、移動量なら値(現在値):PV／速度:V／加速度:A が内部に保持されているので、
それらをイベント内で書き換えることになります。

ここで一つ取り扱いがまぎらわしいものがあります。

イベントの登録には主にイベント変数を介して行うわけですが、
例えば、ボーンの主なイベント変数は以下のようになっています(短絡系のみ)

　IPEVmeVectorValueEventOperator T { get; }     // T : 移動量イベント設定
　IPEVmeQuaternionValueEventOperator R { get; } // R : 回転量イベント設定
　IPEVmePositionEventOperator P { get; }        // P : 位置イベント設定
　IPEVmeDirectionEventOperator D { get; }       // D : 方向イベント設定
　IPEVmeScalingEventOperator S { get; }         // S : スケールイベント設定

見て解るとおり状態変数とイベント変数では、インターフェイス対象は異なりますが各名称は共通のものになっています。

ボーンから見ると、

bone.BS.T...   // 状態変数のT
bone.T...      // イベント変数のT

と異なる階層になりますが、これらは非常にまぎらわしいのでご注意ください(他のオブジェクトでも同様です)

※基本的に状態変数はイベント内からの使用に限定しておくことを推奨します。


また状態変数の各値は代表値という形で現在値が共通化されています。

主に ***(型).PV "PV"(または"PrimaryValue")という名称になっています。

一部代表値が他のパラメータと兼用になっているものもありますが、
これらは"PV"などのパラメータ(プロパティ)か、またはGet()／Set()関数から取得／設定することが共通化されています。


尚、各現在値はイベント外でも設定／取得は可能ですが、何ら影響を及ぼさないのでご注意ください。

例：
vme.Bone[0].BS.T.Set(Vector3.Zero);
→
イベント外での呼び出しなので影響しない

vme.Bone[0].SetEvent(0, () => vme.Bone[0].BS.T.Set(Vector3.Zero));
→
0フレームの現在値を初期化(イベント内から操作しているので影響あり)

※この応用として、別オブジェクトへの強制介入操作なども可能ですが管理が難しくなるのであまり推奨はしません。



●イベントの呼び出し順序とID

VMEに登録されたイベントは、ID順に

(開始)
　↓
モーフ:Index順
　↓
ボーン:Index順
　↓
カメラ
　↓
 照明
　↓
<次のIDが残っていれば>→先頭へ戻る↑
　↓
(終了)

という順序でフレーム毎に実行されます。

ほとんどの場合デフォルトのID=0(省略可能)で登録すれば問題なく動作しますが、
例えばIndex順の後の方の値を参照したい場合や、そのフレームでのカメラ位置などをボーンから参照したい場合などは、
イベント登録時に ID=1 などを指定し、実行順序を遅らせる必要があります。

例：
vme.Bone[0].SetEvent(0, (IES state) => (state as IBS).T.Set(vme.Bone[10].BS.T.PV), 1); // 末尾の1がID
→
先頭(0番)ボーンの移動量をそのフレームの10番ボーンの値設定と同じにするイベント設定(ID=1)



●イベント内からのイベント登録

VMEの仕様上、イベント呼び出し内から新たにイベントを登録することは可能ですが、
そのような使い方は推奨されません。ほとんどの場合呼び出しが行われない可能性が高いと思われます。

また実行開始時のイベント中に存在しないIDのイベントを追加しても同様に呼び出しは行われません。

※イベント処理は開始時に確定できるものとして実行されることを前提としています。

※この仕様は今後変更になるかもしれません(検討中)



●短絡(ショートカット)名について

VME関連のオブジェクトには"****(短絡)"といった、名前の非常に短いプロパティ変数や関数が多数存在します。

これらはスクリプトでの記述を想定されたショートカット(エイリアス)として実装されています。
それぞれには意味の解る非短絡系の長い名称の変数／関数も配置されているので、合わせて確認してください。

例：IPEVmePrimaryValue<TValue>  // 各値の基底インターフェイス 
  TValue PV { get; set; }           // 代表値(短絡)
  TValue PrimaryValue { get; set; } // 代表値

実装面から見ると、非短絡系→短絡系と名称の長い非短絡系の方が呼び出し順序が長いので、
ショートカット(エイリアス)側の変数／関数での利用が推奨されます(IPEVmePrimaryValueなら"PrimaryValue"ではなく"PV"の使用を推奨)


またイベント変数などよく使うインターフェイスはエイリアスを作成しておくと記述が楽になります。

CSScriptプラグインでは下記のエイリアスが事前定義されており、本ドキュメント内もこれに準じています。

// エイリアス一覧
using IES = PEPlugin.Vme.IPEVmeEventState;
using IBS = PEPlugin.Vme.IPEVmeBoneState;
using ISS = PEPlugin.Vme.IPEVmeSingleValueState;
using IMS = PEPlugin.Vme.IPEVmeSingleValueState;
using ICS = PEPlugin.Vme.IPEVmeCameraState;
using ILS = PEPlugin.Vme.IPEVmeLightState;



●値型プロパティへの代入

言語仕様としての注意点です。

状態変数の各値は、Vector3やQuaternion、Matrixといった構造体(値型)へのプロパティとなっています。

例：位置の場合
Vector3 P { get; set; }

通常 X のみ値を変更したい場合など、

vme.Bone[0].SetEvent(0, (IES state) =>
{
    IBS s = state as IBS;

    s.P.PV.X = 10;   // Xのみ値設定：エラー！
});

と記述してしまいますが、これらはエラーとなります（仮にエラー表記にならない場合でも正常に動作しません）

C#での構造体は値型なので上記は、

vme.Bone[0].SetEvent(0, (IES state) =>
{
    IBS s = state as IBS;

    s.P.PV = new Vector3(1, 0, 0);    // X,Y,Z合わせて設定する必要がある

    // Xのみ変更したい場合
    Vector3 v = s.P.PV;  // 一旦変数を経由
    v.X = 10;            // 変数化すれば個別に設定可能
    s.P.PV = v;
});

というように値として設定する必要があります。
C++経験者など言語仕様の違いから混乱する可能性もあるのでご注意ください。

余談ですがプラグイン側の IPEVector3 や 相互運用の V3 などはクラス実装(参照型)となっているので、
上記のようなXのみの変更なども正常に機能します。



●ボーンと変形構造

VME管理下のボーンは元になる変形構造によって異なる動作を行います。

まずVMEの初期化処理である、

　void Init(IPEPmd pmd = null);

関数の呼び出し時にpmdをnullにした場合(省略でも同様)、非PMDモードとして機能します。

この状態では、

　IPEVmeBone AddBone(string name, Vector3 pos);  // ボーンの追加
　void RemoveBone(IPEVmeBone bone);              // 追加したボーンの削除

といったボーンの追加系処理が可能になり、それらのボーンは独立した移動／回転可能なボーンとして取り扱われます。
ボーンの親子関係やIKを含む特定の構造変形などを行うことはできません。

対して、初期化関数のpmdパラメータに任意のPMDを指定した場合、
VME内のボーンは元のPMDに従った変形構造を持つようになります。

※この状態でボーンの追加系処理を呼び出すと例外終了
※モーフも同様にPMD初期化状態により追加可能／不可能が変化


変形構造を持つ場合、ボーンパラメータ内の

位置(P:IPEVmePosition)／方向(D:IPEVmeDirection)／グローバルスケール(S.GS:IPEVmeScale)

の各値の取得／設定時に変形状態との整合を行います。具体的には現在のボーン状態で変形状態を更新します。
モデル形状にもよりますが変形処理は比較的重い処理なので、上記グローバル系の値を使う場合は注意が必要になります。

※現在の仕様ではスケール値が等倍になっていない場合、方向やスケールなどが正しく機能しない模様(調査中)

※移動不可ボーンに移動量を設定した場合、変形状態と内部値が整合しなくなると予想されます。



●グローバルとローカル

ボーンの状態変数には、

T : 移動量   | Translation
R : 回転量   | Rotation
P : 位置     | Position
D : 方向     | Direction
S : スケール | Scale (内部にGS:グローバルスケール)

と、いくつかの値が格納されていますが、これらは

ローカル値   : T, R, S
グローバル値 : P, D, (GS)

と分類されます。

ローカル値の場合、そのボーン(オブジェクト)を基準とした座標系で演算が行われます。
X,Y,Zの値設定でも(親オブジェクトの変形などにより)基準となる座標系が回転しているなどで、
グローバルの座標系とは異なった方向で運動することになります。

速度／加速度はオフセット指定になるので、一般的にローカル側にしかありません。
座標系のずれなどにより期待していた運動にならない場合もあります。


注意点として、状態変数内の値は ローカル値 しか保持できないので、グローバル値を扱う場合は
関連したローカル値を一緒に書き換えている、ということを理解する必要があります。

例：位置P
P を書き換える → 逆算してTの値も書き換える

また変形構造を持っているPMDモデルが対象の場合、グローバル値を取得／設定するときに毎回変形状態の更新が行われるので、
多数のオブジェクトを取り扱う場合などは、一旦変数に格納するなどの対応が必要になります。

※グローバル<>ローカル間の変換は自動的に行いますが、変形構造やスケールなどの状況によって正しく機能しないことがあります(調査中)



●カメラ制御の違い

MMD(VMD)のカメラは

Position : 注視点位置
Angle    : カメラ方向
Distance : 視点までの距離

という1点の位置と向き、及び距離で表記されますが、
それに対してVMEのカメラは

Position : カメラ位置
Target   : カメラ注視点
Up       : カメラの上方向

という2点と上方向で表されます。それぞれ移動量と位置(初期位置=原点)を設定することが可能です。

※3DCGでのView行列(LookAtL(R)H)制御ではよく使われる形式です。

基本的に位置情報だけで取り扱いが可能なので、ボーン構造からの写像なども比較的容易に行うことができると思われます。
当然のことながら、ボーンと同じく移動量から速度／加速度制御を行うことも可能です。



●プレビュー機能と結果データ(IPEVmeResult)について

VMEはVMDViewを使ってそのままプレビューすることが可能です。

　bool Preview(IPEPmd pmd, PEVmePreviewOption option = null);  // 任意のPMDを使ってプレビュー
　bool Preview(PEVmePreviewOption option = null);              // PMDから初期化した場合はこちらでOK

この場合、VMDViewは専用のイベント実行モードとして動作します。

まず最初に登録されたイベントを進めながら各部の変形状態を記録していきます。
最後まで完了すると記録された値から再生が可能になります。

※これにより数万単位のオブジェクト数でもリアルタイムプレビューが可能になることがあります(負荷については実行環境によります)

このモードは30FPS固定、物理演算OFFになります。通常のVMDViewとして再生したい場合は一旦VMDへ変換する必要があります。

尚、Run()実行後の IPEVmeResult からプレビューを行うことも可能ですが、こちらもVMEのプレビューと同じ形式になります。

VMEの実行結果は(プレビューをしない場合)一旦結果データ(IPEVmeResult)に格納されます。
冗長な仕様にも見えますが、これにより出力VMDの振り分けや、一旦実行したVME結果の再利用などが可能になります。

※結果データの編集関連機能は未実装。現状の仕様は一括してデータ取得できないので、加工は効率が悪い方法になるかと思います。



●VMDとVMEの相互運用

VMD : VMDオブジェクトの作成とvmdファイルへの入出力が可能です。

VME : 以下の順路でそれぞれ変換することが可能です。

  Vmd → Vme       : void SetVmd(IPEVmd vmd);    // 全フレームにVMDデータがイベント登録されます

  Vme → VmeResult : IPEVmeResult Run();

  VmeResult → Vmd : IPEVmd ToMotionVmd(bool preSkip = false); 
                     IPEVmd ToCameraVmd(bool preSkip = false, bool margin = false);
                     IPEVmd ToLightVmd(bool preSkip = false);

  VmeResult → Vme : void SetVmeResult(IPEVmeResult result);  // 全フレーム登録


※一般的に

VMD → VME → VmeResult → (VMD → ...)

という流れになります。



●VMD出力時の制限について

VMEとVMDViewで独自に拡張されたパラメータ値はVMDへ出力できません。具体的にはボーンのスケール操作などになります。

VMEの実行結果は全フレームの登録キーとなります。
そのため vmdファイル側の制限キー数を超えたデータを出力してしまう可能性があります。

※制限数一覧
ボーン : 30万(10万ほどでも数分以上返ってこなくなるので、数万程度が実用限界)
表情   : 2万
カメラ : 1万
照明   : 1万


VMD出力時はMMDでの"不要キー削除"と同等の処理を行いますが、
例えば等速移動などでも全フレームにキーが配置されてしまうので(MMDの仕様では2点で十分)、
制限数を超えてしまわないよう注意する必要があります。

※制限以上のキー数となっても警告表示などはありません。ちなみにこれらの制限数以上のVMDでもVMDViewでは読み込み可能です。

余談ですが、MMD側でこれらの制限数以上のモーションを利用するには、

1. 制限範囲内に収まるようにVMDを分割出力
2. 同一モデルを複数読み込みVMDをそれぞれに配置
3. 不要な部分のモデルを非表示化

といった方策が必要になります。MMDでは数万キー単位のVMDの読み込みは非常に時間がかかることもあるのでご注意ください。



●グループ

ボーンやモーフなど多量のオブジェクトを一度に扱う場合や、一定の対象範囲を同一的に制御したい場合などに
グループ化して取り扱うことが可能です。

※IPEVmeEventElement 対象なので、カメラや照明なども一応追加可能ですが、用途としてはあまり有用ではないと思われます。

尚、VMEは初期状態でボーン及びモーフについて全ての対象をグループとして所持しています。
→ vme.GroupBone 及び vme.GroupMorph

グループ化したオブジェクトは

vme.GroupBone.ForEach((IPEVmeBone bone) =>
{
    bone.P.Set(0, V3.Rand(-50, 50));  // xyz:-50〜50範囲にランダム配置
});

と、グループ内を一度に設定可能です (Index付きでイベント設定することも可能)


ちなみに上記は以下と同義でコード量もあまり変わりませんが、
特定の対象範囲などをグループとして取り扱う場合などには有用な方法になります。

// 一般的な複数対象への登録方法
for (int i = 0; i < vme.Bone.Count; i++) {
    vme.Bone[i].P.Set(0, V3.Rand(-50, 50));
}



●空間パス

パス＝経路を作成する機能です。IPEVmePath という専用のインターフェイスから作成可能です。

※インターフェイスは PEStaticBuilder.SC.VmePath() などから作成可能。

空間経路は指定の節点を複数追加していくことで作ることができます。
PMDモデルのボーンや頂点などを利用することも可能です。

作成された経路から、任意の速度／速度変化／フレームに対する経路位置で、
その経路を通るフレーム毎の座標列を取得可能、
さらに位置イベント変数からイベントへ登録可能です(ボーン／カメラ共通)

経路は、直線または曲線(現在はスプラインのみ)を選択することができます。

位置の差から向きを算出し、それを方向イベント変数へイベント登録する機能も有しています。

これらの機能をうまく組み合わせることで簡易な空間軌道を手軽に作成することができます。
特にPMXエディタのボーン列作成機能と併用すれば視覚的な作成も可能になるかと思われます。




●諸問題など

○状態変数の変換が一々面倒

イベント登録では大体において

IBS s = state as IBS;

などの変換の記述が一々必要になり、非常に冗長だと思われます。

ジェネリクス機能をいくつか応用するなどしてみましたが、
デリゲート・インターフェイス・クラスと全てを跨ぐ設定に難があるのか？うまく動作できなかったので、
基底インターフェイスである IPEVmeEventState を経由する仕様としています。

少々面倒ですが、そのようなものとして現状ご理解頂きたく思います(うまく解決できる場合は仕様変更するかもしれません)


○物理演算

現状のVMEは物理演算に対応していません。プレビュー時も強制的に物理演算はOFFとなります。

物理演算を使ったモーションを作成したい場合は、一旦VMDへ変換する必要があります。

※VMDViewではVMDキー数などの制限も比較的緩やかですが、MMDではボーンキー30万／表情キー2万が上限となるので
多くのオブジェクトの取り扱いは難しいと思われます（特にVMEから変換したVMDは全フレーム登録系となるので）

将来的に対応する可能性はあるかもしれませんが、あまり期待されない方がよいかと思われます。


○開発環境と実行環境

規模が比較的大きくなってしまったので、開発環境はインテリセンス他支援機能が使える VisualStudio(VS2010以降) などが推奨ですが、
一々プラグイン化して実行するのは非常に面倒なので、実行についてはCSScript上で行った方が楽になるかもしれません。

本来はVS上からも呼び出し可能な外部実行機能が持たせてあったんですが、
.NETアセンブリの仕様からアンロードがうまくいかないようなので(問題解決には仕様の大幅な変更が必要になる模様)、

少々歪な使い方ですが、VSとCSScriptの併用が比較的望ましいようです。

※CSScriptだけでも作れないことはありませんが、コードの記述やリファレンスの参照などにかなり難があるかと思われます。


○サンプルについて

CSScriptプラグイン内に多少のサンプルスクリプトが添付されています。
正直サンプルになっていないかもしれませんが・・・

※プロトタイプを利用して作成したものは以下のようになりました。
http://www.nicovideo.jp/watch/sm11853319
http://www.nicovideo.jp/watch/sm11569284

ちなみに開発環境(Core2-E6600／GF9800GT)では、3万程度のBOXオブジェクト(72万頂点)でも、
VMDView上のプレビューは30FPSで可能でした(メモリ制限により100〜200F程度が活動限界でしたが)








