/* * ExternalReceiver * https://sabowl.sakura.ne.jp/gpsnmeajp/ * * MIT License * * Copyright (c) 2019 gpsnmeajp * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #pragma warning disable 0414,0219 using System; using System.Reflection; using System.IO; using System.Threading; using System.Threading.Tasks; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using UnityEngine.Profiling; using UniGLTF; using UniVRM10; namespace EVMC4U { //[RequireComponent(typeof(uOSC.uOscServer))] public class ExternalReceiver : MonoBehaviour, IExternalReceiver { [Header("ExternalReceiver v5.0a")] [SerializeField, Label("VRMモデルのGameObject")] public GameObject Model = null; [SerializeField, Label("一時停止")] public bool Freeze = false; //すべての同期を止める(撮影向け) [SerializeField, Label("パケットリミッター")] public bool PacktLimiter = true; //パケットフレーム数が一定値を超えるとき、パケットを捨てる #if EVMC4U_JA [Header("ルート姿勢同期オプション")] #else [Header("Root Synchronize Option")] #endif [SerializeField, Label("ルート位置反映対象(VR向け)")] public Transform RootPositionTransform = null; //VR向けroot位置同期オブジェクト指定 [SerializeField, Label("ルート回転反映対象(VR向け)")] public Transform RootRotationTransform = null; //VR向けroot回転同期オブジェクト指定 [SerializeField, Label("ルート位置反映")] public bool RootPositionSynchronize = true; //ルート座標同期(ルームスケール移動) [SerializeField, Label("ルート回転反映")] public bool RootRotationSynchronize = true; //ルート回転同期 [SerializeField, Label("ルートスケール反映")] public bool RootScaleOffsetSynchronize = false; //MRスケール適用 #if EVMC4U_JA [Header("その他の同期オプション")] #else [Header("Other Synchronize Option")] #endif [SerializeField, Label("表情の同期")] public bool BlendShapeSynchronize = true; //表情等同期 [SerializeField, Label("ボーン位置の厳密な同期")] public bool BonePositionSynchronize = true; //ボーン位置適用(回転は強制) #if EVMC4U_JA [Header("同期遮断オプション")] #else [Header("Synchronize Cutoff Option")] #endif [SerializeField, Label("指ボーンを同期しない")] public bool HandPoseSynchronizeCutoff = false; //指状態反映オフ [SerializeField, Label("目ボーンを同期しない")] public bool EyeBoneSynchronizeCutoff = false; //目ボーン反映オフ #if EVMC4U_JA [Header("なめらかフィルター")] #else [Header("Lowpass Filter Option")] #endif [SerializeField, Label("ボーン位置なめらか")] public bool BonePositionFilterEnable = false; //ボーン位置フィルタ [SerializeField, Label("ボーン回転なめらか")] public bool BoneRotationFilterEnable = false; //ボーン回転フィルタ [SerializeField, Label("なめらか係数(0.500~0.999)")] public float BoneFilter = 0.7f; //ボーンフィルタ係数 [SerializeField, Label("表情なめらか")] public bool BlendShapeFilterEnable = false; //BlendShapeフィルタ [SerializeField, Label("表情なめらか係数(0.500~0.999)")] public float BlendShapeFilter = 0.7f; //BlendShapeフィルタ係数 #if EVMC4U_JA [Header("VRMファイル自動読み込み")] #else [Header("VRM Loader Option")] #endif [SerializeField, Label("VRMファイル自動読み込み(対応アプリのみ)")] public bool enableAutoLoadVRM = true; //VRMの自動読み込みの有効可否 #if EVMC4U_JA [Header("その他")] #else [Header("Other Option")] #endif [SerializeField, Label("更新タイミングをLateUpdateにする(Animationの後に実行して上書きする)")] public bool EnableLateUpdateForOverwriteAnimationResult = false; //LateUpdateにするかどうか(Animationの後に実行して上書きする) [SerializeField, Label("未キャリブレーション時にモデルを隠す")] public bool HideInUncalibrated = false; //キャリブレーション出来ていないときは隠す [SerializeField, Label("MRモード連動")] public bool SyncCalibrationModeWithScaleOffsetSynchronize = true; //キャリブレーションモードとスケール設定を連動させる public Action BeforeModelDestroyAction = null; //破壊時Action public Action AfterAutoLoadAction = null; //自動ロード時Action [SerializeField, Label("VRM1正規化ボーン選択(ロード時)")] public UniVRM10.ControlRigGenerationOption controlRigGenerationOptionOnLoad = UniVRM10.ControlRigGenerationOption.None; //VRM1正規化ボーン(非推奨) #if EVMC4U_JA [Header("現在の状態(表示用)")] #else [Header("Status (Read only)")] #endif [SerializeField, Label("動作状況")] private string StatusMessage = ""; //状態メッセージ(Inspector表示用) [SerializeField, Label("オプション文字列")] public string OptionString = ""; //VMCから送信されるオプション文字列 [SerializeField, Label("自動読み込みVRMパス")] public string loadedVRMPath = ""; //読み込み済みVRMパス [SerializeField, Label("自動読み込みVRM名前")] public string loadedVRMName = ""; //読み込み済みVRM名前 [SerializeField, Label("読み込んだモデルの親GameObject")] public GameObject LoadedModelParent = null; //読み込んだモデルの親 [SerializeField, Label("読み込んだVRMの種別")] public string loadedVRMType = ""; //読み込んだ種別 [SerializeField, Label("1フレームあたりのパケットフレーム数")] public int LastPacketframeCounterInFrame = 0; //1フレーム中に受信したパケットフレーム数 [SerializeField, Label("廃棄されたパケット数")] public int DropPackets = 0; //廃棄されたパケット(not パケットフレーム) public Vector3 HeadPosition = Vector3.zero; #if EVMC4U_JA [Header("デイジーチェーン")] #else [Header("Daisy Chain")] #endif public GameObject[] NextReceivers = new GameObject[6]; //デイジーチェーン #if EVMC4U_JA [Header("ボーンの個別遮断(下半身、腕のみなど)")] #else [Header("Cut bones")] #endif [SerializeField, Label("有効")] public bool CutBonesEnable = false; #if EVMC4U_JA [Header("遮断ボーン (Head)")] #else [Header("Cut bones(Head)")] #endif [SerializeField, Label("Neck遮断")] public bool CutBoneNeck = false; [SerializeField, Label("Head遮断")] public bool CutBoneHead = false; [SerializeField, Label("LeftEye遮断")] public bool CutBoneLeftEye = false; [SerializeField, Label("RightEye遮断")] public bool CutBoneRightEye = false; [SerializeField, Label("Jaw遮断")] public bool CutBoneJaw = false; #if EVMC4U_JA [Header("遮断ボーン (Body)")] #else [Header("Cut bones(Body)")] #endif [SerializeField, Label("Hips遮断")] public bool CutBoneHips = true; [SerializeField, Label("Spine遮断")] public bool CutBoneSpine = true; [SerializeField, Label("Chest遮断")] public bool CutBoneChest = true; [SerializeField, Label("UpperChest遮断")] public bool CutBoneUpperChest = true; #if EVMC4U_JA [Header("遮断ボーン (Left Arm)")] #else [Header("Cut bones(Left Arm)")] #endif [SerializeField, Label("LeftShoulder遮断")] public bool CutBoneLeftShoulder = false; [SerializeField, Label("LeftUpperArm遮断")] public bool CutBoneLeftUpperArm = false; [SerializeField, Label("LeftLowerArm遮断")] public bool CutBoneLeftLowerArm = false; [SerializeField, Label("LeftHand遮断")] public bool CutBoneLeftHand = false; #if EVMC4U_JA [Header("遮断ボーン (Right Arm)")] #else [Header("Cut bones(Right Arm)")] #endif [SerializeField, Label("RightShoulder遮断")] public bool CutBoneRightShoulder = false; [SerializeField, Label("RightUpperArm遮断")] public bool CutBoneRightUpperArm = false; [SerializeField, Label("RightLowerArm遮断")] public bool CutBoneRightLowerArm = false; [SerializeField, Label("RightHand遮断")] public bool CutBoneRightHand = false; #if EVMC4U_JA [Header("遮断ボーン (Left Leg)")] #else [Header("Cut bones(Left Leg)")] #endif [SerializeField, Label("LeftUpperLeg遮断")] public bool CutBoneLeftUpperLeg = true; [SerializeField, Label("LeftLowerLeg遮断")] public bool CutBoneLeftLowerLeg = true; [SerializeField, Label("LeftFoot遮断")] public bool CutBoneLeftFoot = true; [SerializeField, Label("LeftToes遮断")] public bool CutBoneLeftToes = true; #if EVMC4U_JA [Header("遮断ボーン (Right Leg)")] #else [Header("Cut bones(Right Leg)")] #endif [SerializeField, Label("RightUpperLeg遮断")] public bool CutBoneRightUpperLeg = true; [SerializeField, Label("RightLowerLeg遮断")] public bool CutBoneRightLowerLeg = true; [SerializeField, Label("RightFoot遮断")] public bool CutBoneRightFoot = true; [SerializeField, Label("RightToes遮断")] public bool CutBoneRightToes = true; #if EVMC4U_JA [Header("遮断ボーン (Left Hand)")] #else [Header("Cut bones(Left Hand)")] #endif [SerializeField, Label("LeftThumbProximal遮断")] public bool CutBoneLeftThumbProximal = false; [SerializeField, Label("LeftThumbIntermediate遮断")] public bool CutBoneLeftThumbIntermediate = false; [SerializeField, Label("LeftThumbDistal遮断")] public bool CutBoneLeftThumbDistal = false; [SerializeField, Label("LeftIndexProximal遮断")] public bool CutBoneLeftIndexProximal = false; [SerializeField, Label("LeftIndexIntermediate遮断")] public bool CutBoneLeftIndexIntermediate = false; [SerializeField, Label("LeftIndexDistal遮断")] public bool CutBoneLeftIndexDistal = false; [SerializeField, Label("LeftMiddleProximal遮断")] public bool CutBoneLeftMiddleProximal = false; [SerializeField, Label("LeftMiddleIntermediate遮断")] public bool CutBoneLeftMiddleIntermediate = false; [SerializeField, Label("LeftMiddleDistal遮断")] public bool CutBoneLeftMiddleDistal = false; [SerializeField, Label("LeftRingProximal遮断")] public bool CutBoneLeftRingProximal = false; [SerializeField, Label("LeftRingIntermediate遮断")] public bool CutBoneLeftRingIntermediate = false; [SerializeField, Label("LeftRingDistal遮断")] public bool CutBoneLeftRingDistal = false; [SerializeField, Label("LeftLittleProximal遮断")] public bool CutBoneLeftLittleProximal = false; [SerializeField, Label("LeftLittleIntermediate遮断")] public bool CutBoneLeftLittleIntermediate = false; [SerializeField, Label("LeftLittleDistal遮断")] public bool CutBoneLeftLittleDistal = false; #if EVMC4U_JA [Header("遮断ボーン (Right Hand)")] #else [Header("Cut bones(Right Hand)")] #endif [SerializeField, Label("RightThumbProximal遮断")] public bool CutBoneRightThumbProximal = false; [SerializeField, Label("RightThumbIntermediate遮断")] public bool CutBoneRightThumbIntermediate = false; [SerializeField, Label("RightThumbDistal遮断")] public bool CutBoneRightThumbDistal = false; [SerializeField, Label("RightIndexProximal遮断")] public bool CutBoneRightIndexProximal = false; [SerializeField, Label("RightIndexIntermediate遮断")] public bool CutBoneRightIndexIntermediate = false; [SerializeField, Label("RightIndexDistal遮断")] public bool CutBoneRightIndexDistal = false; [SerializeField, Label("RightMiddleProximal遮断")] public bool CutBoneRightMiddleProximal = false; [SerializeField, Label("RightMiddleIntermediate遮断")] public bool CutBoneRightMiddleIntermediate = false; [SerializeField, Label("RightMiddleDistal遮断")] public bool CutBoneRightMiddleDistal = false; [SerializeField, Label("RightRingProximal遮断")] public bool CutBoneRightRingProximal = false; [SerializeField, Label("RightRingIntermediate遮断")] public bool CutBoneRightRingIntermediate = false; [SerializeField, Label("RightRingDistal遮断")] public bool CutBoneRightRingDistal = false; [SerializeField, Label("RightLittleProximal遮断")] public bool CutBoneRightLittleProximal = false; [SerializeField, Label("RightLittleIntermediate遮断")] public bool CutBoneRightLittleIntermediate = false; [SerializeField, Label("RightLittleDistal遮断")] public bool CutBoneRightLittleDistal = false; [Header("API")] public string api1 = "void UpdateDaisyChain()"; public string api2 = "int GetAvailable()"; public string api3 = "float GetRemoteTime()"; public string api4 = "void SetBlend(string key, float value)"; public string api5 = "void ApplyBlend()"; public string api6 = "void DestroyModel()"; public string api7 = "void LoadVRM(string path)"; public string api8 = "void LoadVRMFromData(byte[] VRMdata)"; public string api9 = "void ApplicationQuit()"; //---Const--- //rootパケット長定数(拡張判別) const int RootPacketLengthOfScaleAndOffset = 8; //---Private--- private ExternalReceiverManager externalReceiverManager = null; //フィルタ用データ保持変数 private Vector3[] bonePosFilter = new Vector3[Enum.GetNames(typeof(HumanBodyBones)).Length]; private Quaternion[] boneRotFilter = new Quaternion[Enum.GetNames(typeof(HumanBodyBones)).Length]; private Dictionary blendShapeFilterDictionaly = new Dictionary(); //通信状態保持変数 private int Available = 0; //データ送信可能な状態か private float time = 0; //送信時の時刻 //モデル切替検出用reference保持変数 private GameObject OldModel = null; //ボーン情報取得 Animator animator = null; //VRMのブレンドシェイププロキシ(VRM0) VRM.VRMBlendShapeProxy blendShapeProxyV0 = null; //VRM10のルート(VRM1) UniVRM10.Vrm10Instance VrmRootV1 = null; //ボーンENUM情報テーブル Dictionary HumanBodyBonesTable = new Dictionary(); //ボーン情報テーブル Dictionary HumanBodyBonesPositionTable = new Dictionary(); Dictionary HumanBodyBonesRotationTable = new Dictionary(); //ブレンドシェイプ変換テーブル(VRM0) Dictionary StringToBlendShapeKeyDictionaryV0 = new Dictionary(); Dictionary BlendShapeToValueDictionaryV0 = new Dictionary(); //ブレンドシェイプ変換テーブル(VRM1) Dictionary StringToExpressionKeyDictionaryV1 = new Dictionary(); Dictionary ExpressionToValueDictionaryV1 = new Dictionary(); //uOSCサーバー uOSC.uOscServer server = null; //エラー・無限ループ検出フラグ(trueで一切の受信を停止する) bool shutdown = false; //フレーム間パケットフレーム数測定 int PacketCounterInFrame = 0; //1フレームに30パケットフレーム来たら、同一フレーム内でそれ以上は受け取らない。 const int PACKET_LIMIT_MAX = 30; //読込中は読み込まない bool isLoading = false; //メッセージ処理一時変数struct(負荷対策) Vector3 pos; Quaternion rot; Vector3 scale; Vector3 offset; //同期コンテキスト SynchronizationContext synchronizationContext; public void Start() { synchronizationContext = SynchronizationContext.Current; //nullチェック if (NextReceivers == null) { NextReceivers = new GameObject[0]; } //NextReciverのインターフェースを取得する externalReceiverManager = new ExternalReceiverManager(NextReceivers); //サーバーを取得 server = GetComponent(); if (server) { //サーバーを初期化 StatusMessage = "Waiting for VMC..."; server.onDataReceived.AddListener(OnDataReceived); } else { //デイジーチェーンスレーブモード StatusMessage = "Waiting for Master..."; } //初期状態で読み込み済みのモデルが有る場合はVRMの自動読み込みは禁止する if (Model != null) { enableAutoLoadVRM = false; } } //デイジーチェーンを更新 public void UpdateDaisyChain() { //nullチェック if (NextReceivers == null) { NextReceivers = new GameObject[0]; } externalReceiverManager.GetIExternalReceiver(NextReceivers); } //外部から通信状態を取得するための公開関数 public int GetAvailable() { return Available; } //外部から通信時刻を取得するための公開関数 public float GetRemoteTime() { return time; } public void Update() { // 毎フレーム更新処理(前) if (!EnableLateUpdateForOverwriteAnimationResult) { Process(); } } public void LateUpdate() { // 毎フレーム更新処理(後) if (EnableLateUpdateForOverwriteAnimationResult) { Process(); } } public void Process() { //エラー・無限ループ時は処理をしない if (shutdown) { return; } //Freeze有効時は動きを一切止める if (Freeze) { return; } LastPacketframeCounterInFrame = PacketCounterInFrame; PacketCounterInFrame = 0; //5.6.3p1などRunInBackgroundが既定で無効な場合Unityが極めて重くなるため対処 Application.runInBackground = true; //VRMモデルからBlendShapeProxyを取得(タイミングの問題) (VRM0) if (blendShapeProxyV0 == null && Model != null) { blendShapeProxyV0 = Model.GetComponent(); //以降、blendShapeProxyV0 is not nullでVRM0と扱うことができる } //VRMモデルからVrmRootを取得(タイミングの問題) (VRM1) if (VrmRootV1 == null && Model != null) { VrmRootV1 = Model.GetComponent(); //以降、VrmRootV1 is not nullでVRM1と扱うことができる } //判定 if (blendShapeProxyV0 != null && VrmRootV1 != null) { loadedVRMType = "VRM0&1"; } else if (blendShapeProxyV0 != null) { loadedVRMType = "VRM0"; } else if (VrmRootV1 != null) { loadedVRMType = "VRM1"; } else { loadedVRMType = "Unknown"; } //ルート位置がない場合 if (RootPositionTransform == null && Model != null) { //モデル姿勢をルート姿勢にする RootPositionTransform = Model.transform; } //ルート回転がない場合 if (RootRotationTransform == null && Model != null) { //モデル姿勢をルート姿勢にする RootRotationTransform = Model.transform; } //モデルがない場合はエラー表示をしておく(親切心) if (Model == null) { blendShapeProxyV0 = null; VrmRootV1 = null; StatusMessage = "Model not found."; return; } //モデルが更新されたときに関連情報を更新する if (OldModel != Model && Model != null) { blendShapeProxyV0 = Model.GetComponent(); VrmRootV1 = Model.GetComponent(); OldModel = Model; animator = Model?.GetComponent(); Debug.Log("[ExternalReceiver] New model detected"); //v0.56 BlendShape仕様変更対応 //Debug.Log("-- Make BlendShapeProxy BSKey Table --"); //BSキー値辞書の初期化(SetValueで無駄なキーが適用されるのを防止する) BlendShapeToValueDictionaryV0.Clear(); ExpressionToValueDictionaryV1.Clear(); //文字-BSキー辞書の初期化(キー情報の初期化) StringToBlendShapeKeyDictionaryV0.Clear(); StringToExpressionKeyDictionaryV1.Clear(); //全Clipsを取り出す(VRM0) if (blendShapeProxyV0) { foreach (var c in blendShapeProxyV0.BlendShapeAvatar.Clips) { string key = ""; bool unknown = false; //プリセットかどうかを調べる if (c.Preset == VRM.BlendShapePreset.Unknown) { //非プリセット(Unknown)であれば、Unknown用の名前変数を参照する key = c.BlendShapeName; unknown = true; } else { //プリセットであればENUM値をToStringした値を利用する key = c.Preset.ToString(); unknown = false; } //非ケース化するために小文字変換する string lowerKey = key.ToLower(); //Debug.Log("Add: [key]->" + key + " [lowerKey]->" + lowerKey + " [clip]->" + c.ToString() + " [bskey]->"+c.Key.ToString() + " [unknown]->"+ unknown); //小文字名-BSKeyで登録する StringToBlendShapeKeyDictionaryV0.Add(lowerKey, c.Key); } } //全Clipsを取り出す(VRM1) if (VrmRootV1) { foreach (var c in VrmRootV1.Runtime.Expression.ExpressionKeys) { string key = ""; bool unknown = false; //プリセットかどうかを調べる if (c.Preset == UniVRM10.ExpressionPreset.custom) { //非プリセット(Unknown)であれば、Unknown用の名前変数を参照する key = c.Name; unknown = true; } else { //プリセットであればENUM値をToStringした値を利用する key = c.Preset.ToString(); unknown = false; } //非ケース化するために小文字変換する string lowerKey = key.ToLower(); //Debug.Log("Add: [key]->" + key + " [lowerKey]->" + lowerKey + " [clip]->" + c.ToString() + " [bskey]->"+c.Key.ToString() + " [unknown]->"+ unknown); //小文字名-BSKeyで登録する StringToExpressionKeyDictionaryV1.Add(lowerKey, c); } } //メモ: プリセット同名の独自キー、独自キーのケース違いの重複は、共に区別しないと割り切る /* Debug.Log("-- Registered List --"); foreach (var k in StringToBlendShapeKeyDictionaryV0) { Debug.Log("B [k.Key]" + k.Key + " -> [k.Value.Name]" + k.Value.Name); } foreach (var k in StringToExpressionKeyDictionaryV1) { Debug.Log("E [k.Key]" + k.Key + " -> [k.Value.Name]" + k.Value.Name); } */ //Debug.Log("-- End BlendShapeProxy BSKey Table --"); } // VRM1において、VRM1によるLateUpdateでの更新はUpdateにずらして(Constraints) // EVMC4UはLateUpdateにする(視線動かない問題への暫定対処) if (VrmRootV1?.UpdateType == Vrm10Instance.UpdateTypes.LateUpdate) { VrmRootV1.UpdateType = Vrm10Instance.UpdateTypes.Update; Debug.Log("[ExternalReceiver] [Temporary Fix] Forcefully change the UpdateType of Vrm10Instance to \"Update\"."); } if (VrmRootV1?.UpdateType == Vrm10Instance.UpdateTypes.Update && EnableLateUpdateForOverwriteAnimationResult == false) { EnableLateUpdateForOverwriteAnimationResult = true; Debug.Log("[ExternalReceiver] [Temporary Fix] Forcefully change the Update timing of ExternalReceiver to LateUpdate."); } BoneSynchronizeByTable(); } //データ受信イベント private void OnDataReceived(uOSC.Message message) { //チェーン数0としてデイジーチェーンを発生させる MessageDaisyChain(ref message, 0); } //デイジーチェーン処理 public void MessageDaisyChain(ref uOSC.Message message, int callCount) { //Startされていない場合無視 if (externalReceiverManager == null || enabled == false || gameObject.activeInHierarchy == false) { return; } //エラー・無限ループ時は処理をしない if (shutdown) { return; } //パケットリミッターが有効な場合、一定以上のパケットフレーム/フレーム数を観測した場合、次のフレームまでパケットを捨てる if (PacktLimiter && (LastPacketframeCounterInFrame > PACKET_LIMIT_MAX)) { DropPackets++; return; } //メッセージを処理 if (!Freeze) { //異常を検出して動作停止 try { ProcessMessage(ref message); } catch (Exception e) { StatusMessage = "Error: Exception"; Debug.LogError(" --- Communication Error ---"); Debug.LogError(e.ToString()); shutdown = true; return; } } //次のデイジーチェーンへ伝える if (!externalReceiverManager.SendNextReceivers(message, callCount)) { //無限ループ対策 StatusMessage = "Infinite loop detected!"; //以降の処理を全部停止 shutdown = true; } } //メッセージ処理本体 private void ProcessMessage(ref uOSC.Message message) { //メッセージアドレスがない、あるいはメッセージがない不正な形式の場合は処理しない if (message.address == null || message.values == null) { StatusMessage = "Bad message."; return; } //ルート位置がない場合 if (RootPositionTransform == null && Model != null) { //モデル姿勢をルート姿勢にする RootPositionTransform = Model.transform; } //ルート回転がない場合 if (RootRotationTransform == null && Model != null) { //モデル姿勢をルート姿勢にする RootRotationTransform = Model.transform; } //モーションデータ送信可否 if (message.address == "/VMC/Ext/OK" && (message.values[0] is int)) { Available = (int)message.values[0]; if (Available == 0) { StatusMessage = "Waiting for [Load VRM]"; } //V2.5 キャリブレーション状態(長さ3以上) if (message.values.Length >= 3) { if ((message.values[1] is int) && (message.values[2] is int)) { int calibrationState = (int)message.values[1]; int calibrationMode = (int)message.values[2]; //キャリブレーション出来ていないときは隠す if (HideInUncalibrated && Model != null) { Model.SetActive(calibrationState == 3); } //スケール同期をキャリブレーションと連動させる if (SyncCalibrationModeWithScaleOffsetSynchronize) { RootScaleOffsetSynchronize = !(calibrationMode == 0); //通常モードならオフ、MR系ならオン } } } return; } //データ送信時刻 else if (message.address == "/VMC/Ext/T" && (message.values[0] is float)) { time = (float)message.values[0]; PacketCounterInFrame++; //フレーム中のパケットフレーム数を測定 return; } //VRM自動読み込み else if (message.address == "/VMC/Ext/VRM" && (message.values[0] is string) && (message.values[1] is string) ) { string path = (string)message.values[0]; string title = (string)message.values[1]; //前回読み込んだパスと違う場合かつ、読み込みが許可されている場合 if (path != loadedVRMPath && enableAutoLoadVRM == true) { loadedVRMPath = path; loadedVRMName = title; LoadVRM(path); } return; } //オプション文字列 else if (message.address == "/VMC/Ext/Opt" && (message.values[0] is string)) { OptionString = (string)message.values[0]; return; } //モデルがないか、モデル姿勢、ルート姿勢が取得できないなら以降何もしない if (Model == null || Model.transform == null || RootPositionTransform == null || RootRotationTransform == null) { return; } //Root姿勢 if (message.address == "/VMC/Ext/Root/Pos" && (message.values[0] is string) && (message.values[1] is float) && (message.values[2] is float) && (message.values[3] is float) && (message.values[4] is float) && (message.values[5] is float) && (message.values[6] is float) && (message.values[7] is float) ) { StatusMessage = "OK"; pos.x = (float)message.values[1]; pos.y = (float)message.values[2]; pos.z = (float)message.values[3]; rot.x = (float)message.values[4]; rot.y = (float)message.values[5]; rot.z = (float)message.values[6]; rot.w = (float)message.values[7]; //位置同期 if (RootPositionSynchronize) { RootPositionTransform.localPosition = pos; } //回転同期 if (RootRotationSynchronize) { RootRotationTransform.localRotation = rot; } //スケール同期とオフセット補正(v2.1拡張プロトコルの場合のみ) if (RootScaleOffsetSynchronize && message.values.Length > RootPacketLengthOfScaleAndOffset && (message.values[8] is float) && (message.values[9] is float) && (message.values[10] is float) && (message.values[11] is float) && (message.values[12] is float) && (message.values[13] is float) ) { scale.x = 1.0f / (float)message.values[8]; scale.y = 1.0f / (float)message.values[9]; scale.z = 1.0f / (float)message.values[10]; offset.x = (float)message.values[11]; offset.y = (float)message.values[12]; offset.z = (float)message.values[13]; Model.transform.localScale = scale; RootPositionTransform.localPosition = Vector3.Scale(RootPositionTransform.localPosition, scale); //位置同期が有効な場合のみオフセットを反映する if (RootPositionSynchronize) { offset = Vector3.Scale(offset, scale); RootPositionTransform.localPosition -= offset; } } else { Model.transform.localScale = Vector3.one; } } //ボーン姿勢 else if (message.address == "/VMC/Ext/Bone/Pos" && (message.values[0] is string) && (message.values[1] is float) && (message.values[2] is float) && (message.values[3] is float) && (message.values[4] is float) && (message.values[5] is float) && (message.values[6] is float) && (message.values[7] is float) ) { string boneName = (string)message.values[0]; pos.x = (float)message.values[1]; pos.y = (float)message.values[2]; pos.z = (float)message.values[3]; rot.x = (float)message.values[4]; rot.y = (float)message.values[5]; rot.z = (float)message.values[6]; rot.w = (float)message.values[7]; //Humanoidボーンに該当するボーンがあるか調べる HumanBodyBones bone; if (HumanBodyBonesTryParse(ref boneName, out bone)) { //あれば位置と回転をキャッシュする if (HumanBodyBonesPositionTable.ContainsKey(bone)) { HumanBodyBonesPositionTable[bone] = pos; } else { HumanBodyBonesPositionTable.Add(bone, pos); } if (HumanBodyBonesRotationTable.ContainsKey(bone)) { HumanBodyBonesRotationTable[bone] = rot; } else { HumanBodyBonesRotationTable.Add(bone, rot); } } //受信と更新のタイミングは切り離した } //ブレンドシェイプ同期 else if (message.address == "/VMC/Ext/Blend/Val" && (message.values[0] is string) && (message.values[1] is float) ) { //一旦変数に格納する string key = (string)message.values[0]; float value = (float)message.values[1]; if (BlendShapeSynchronize) { SetBlend(key, value); } } //ブレンドシェープ適用 else if (message.address == "/VMC/Ext/Blend/Apply") { if (BlendShapeSynchronize) { ApplyBlend(); } } } //表情の設定(VRM0,VRM1共通) public void SetBlend(string key, float value) { //BlendShapeフィルタが有効なら if (BlendShapeFilterEnable) { //フィルタテーブルに存在するか確認する if (blendShapeFilterDictionaly.ContainsKey(key)) { //存在する場合はフィルタ更新して値として反映する blendShapeFilterDictionaly[key] = (blendShapeFilterDictionaly[key] * BlendShapeFilter) + value * (1.0f - BlendShapeFilter); value = blendShapeFilterDictionaly[key]; } else { //存在しない場合はフィルタに登録する。値はそのまま blendShapeFilterDictionaly.Add(key, value); } } // VRM0 if (blendShapeProxyV0 != null) { //v0.56 BlendShape仕様変更対応 //辞書からKeyに変換し、Key値辞書に値を入れる //通信で受信したキーを小文字に変換して非ケース化 string lowerKey = key.ToLower(); //キーに該当するBSKeyが存在するかチェックする VRM.BlendShapeKey bskey; if (StringToBlendShapeKeyDictionaryV0.TryGetValue(lowerKey, out bskey)) { //キーに対して値を登録する BlendShapeToValueDictionaryV0[bskey] = value; //Debug.Log("[lowerKey]->"+ lowerKey+" [bskey]->"+bskey.ToString()+" [value]->"+value); } else { //VRM1.x -> VRM0.x逆変換テーブル(前方互換性) switch (lowerKey) { case "happy": lowerKey = "joy"; break; case "sad": lowerKey = "sorrow"; break; case "relaxed": lowerKey = "fun"; break; case "aa": lowerKey = "a"; break; case "ih": lowerKey = "i"; break; case "ou": lowerKey = "u"; break; case "ee": lowerKey = "e"; break; case "oh": lowerKey = "o"; break; case "blinkleft": lowerKey = "blink_l"; break; case "blinkright": lowerKey = "blink_r"; break; } if (StringToBlendShapeKeyDictionaryV0.TryGetValue(lowerKey, out bskey)) { //キーに対して値を登録する BlendShapeToValueDictionaryV0[bskey] = value; //Debug.Log("[lowerKey]->"+ lowerKey+" [bskey]->"+bskey.ToString()+" [value]->"+value); } else { //そんなキーは無い //Debug.LogError("[lowerKey]->" + lowerKey + " is not found"); } } } // VRM1 if (VrmRootV1 != null) { //v0.56 BlendShape仕様変更対応 //辞書からKeyに変換し、Key値辞書に値を入れる //通信で受信したキーを小文字に変換して非ケース化 string lowerKey = key.ToLower(); //キーに該当するBSKeyが存在するかチェックする UniVRM10.ExpressionKey exkey; if (StringToExpressionKeyDictionaryV1.TryGetValue(lowerKey, out exkey)) { //キーに対して値を登録する ExpressionToValueDictionaryV1[exkey] = value; //Debug.Log("[lowerKey]->"+ lowerKey+" [exkey]->"+exkey.ToString()+" [value]->"+value); } else { //VRM0.x -> VRM1.x変換テーブル(後方互換性) switch (lowerKey) { case "joy": lowerKey = "happy"; break; case "sorrow": lowerKey = "sad"; break; case "fun": lowerKey = "relaxed"; break; case "a": lowerKey = "aa"; break; case "i": lowerKey = "ih"; break; case "u": lowerKey = "ou"; break; case "e": lowerKey = "ee"; break; case "o": lowerKey = "oh"; break; case "blink_l": lowerKey = "blinkleft"; break; case "blink_r": lowerKey = "blinkright"; break; } if (StringToExpressionKeyDictionaryV1.TryGetValue(lowerKey, out exkey)) { //キーに対して値を登録する ExpressionToValueDictionaryV1[exkey] = value; //Debug.Log("[lowerKey]->"+ lowerKey+" [exkey]->"+exkey.ToString()+" [value]->"+value); } else { //そんなキーは無い //Debug.LogError("[lowerKey]->" + lowerKey + " is not found"); } } } } //表情の確定(VRM0,VRM1共通) public void ApplyBlend() { if (blendShapeProxyV0 != null) { blendShapeProxyV0.SetValues(BlendShapeToValueDictionaryV0); } if (VrmRootV1 != null) { VrmRootV1.Runtime.Expression.SetWeights(ExpressionToValueDictionaryV1); } } //モデル破棄 public void DestroyModel() { //存在すれば即破壊(異常顔防止) if (Model != null) { BeforeModelDestroyAction?.Invoke(Model); Destroy(Model); Model = null; } if (LoadedModelParent != null) { Destroy(LoadedModelParent); LoadedModelParent = null; } } //ファイルからモデルを読み込む public void LoadVRM(string path) { DestroyModel(); //バイナリの読み込み if (File.Exists(path)) { byte[] VRMdata = File.ReadAllBytes(path); LoadVRMFromData(VRMdata); } else { Debug.LogError("VRM load failed."); } } //ファイルからモデルを読み込む public void LoadVRMFromData(byte[] VRMdata) { if (isLoading) { Debug.LogError("Now Loading! load request is rejected."); return; } DestroyModel(); //読み込み GlbLowLevelParser glbLowLevelParser = new GlbLowLevelParser(null, VRMdata); GltfData gltfData = glbLowLevelParser.Parse(); // VRM0 読み込み処理 try { VRM.VRMData vrm = new VRM.VRMData(gltfData); VRM.VRMImporterContext vrmImporter = new VRM.VRMImporterContext(vrm); isLoading = true; synchronizationContext.Post(async (arg) => { RuntimeGltfInstance instance = await vrmImporter.LoadAsync(new VRMShaders.ImmediateCaller()); isLoading = false; Model = instance.Root; //ExternalReceiverの下にぶら下げる LoadedModelParent = new GameObject(); LoadedModelParent.transform.SetParent(transform, false); LoadedModelParent.name = "LoadedModelParent"; //その下にモデルをぶら下げる Model.transform.SetParent(LoadedModelParent.transform, false); instance.EnableUpdateWhenOffscreen(); instance.ShowMeshes(); //カメラなどの移動補助のため、頭の位置を格納する animator = Model.GetComponent(); HeadPosition = animator.GetBoneTransform(HumanBodyBones.Head).position; //開放 vrmImporter.Dispose(); gltfData.Dispose(); //読み込み後アクションを実行 AfterAutoLoadAction?.Invoke(Model); }, null); return; //VRM0 Loaded } catch (VRM.NotVrm0Exception) { //continue loading } // VRM1 読み込み処理 synchronizationContext.Post(async (_) => { var instance = await UniVRM10.Vrm10.LoadBytesAsync(VRMdata, canLoadVrm0X: false, showMeshes: true, controlRigGenerationOption: controlRigGenerationOptionOnLoad); await Task.Delay(100); //VRM1不具合対策 Model = instance.gameObject; Model.transform.parent = this.transform; //ExternalReceiverの下にぶら下げる LoadedModelParent = new GameObject(); LoadedModelParent.transform.SetParent(transform, false); LoadedModelParent.name = "LoadedModelParent"; //その下にモデルをぶら下げる Model.transform.SetParent(LoadedModelParent.transform, false); //カメラなどの移動補助のため、頭の位置を格納する animator = Model.GetComponent(); HeadPosition = animator.GetBoneTransform(HumanBodyBones.Head).position; var meshes = Model.GetComponentsInChildren(); foreach (var m in meshes) { m.updateWhenOffscreen = true; } //開放 gltfData?.Dispose(); //読み込み後アクションを実行 AfterAutoLoadAction?.Invoke(Model); }, null); // Debug.LogError("Failed to load VRM1 and VRM0."); } //ボーン位置をキャッシュテーブルに基づいて更新 private void BoneSynchronizeByTable() { //キャッシュテーブルを参照 foreach (var bone in HumanBodyBonesTable) { //キャッシュされた位置・回転を適用 if (HumanBodyBonesPositionTable.ContainsKey(bone.Value) && HumanBodyBonesRotationTable.ContainsKey(bone.Value)) { BoneSynchronize(bone.Value, HumanBodyBonesPositionTable[bone.Value], HumanBodyBonesRotationTable[bone.Value]); } } } //ボーン位置同期 private void BoneSynchronize(HumanBodyBones bone, Vector3 pos, Quaternion rot) { //操作可能な状態かチェック if (animator != null && bone != HumanBodyBones.LastBone) { //ボーンによって操作を分ける Transform t; t = animator.GetBoneTransform(bone); if (t != null) { //個別ボーン遮断 if (CutBonesEnable) { if (bone == HumanBodyBones.Hips && CutBoneHips) { return; } if (bone == HumanBodyBones.LeftUpperLeg && CutBoneLeftUpperLeg) { return; } if (bone == HumanBodyBones.RightUpperLeg && CutBoneRightUpperLeg) { return; } if (bone == HumanBodyBones.LeftLowerLeg && CutBoneLeftLowerLeg) { return; } if (bone == HumanBodyBones.RightLowerLeg && CutBoneRightLowerLeg) { return; } if (bone == HumanBodyBones.LeftFoot && CutBoneLeftFoot) { return; } if (bone == HumanBodyBones.RightFoot && CutBoneRightFoot) { return; } if (bone == HumanBodyBones.Spine && CutBoneSpine) { return; } if (bone == HumanBodyBones.Chest && CutBoneChest) { return; } if (bone == HumanBodyBones.Neck && CutBoneNeck) { return; } if (bone == HumanBodyBones.Head && CutBoneHead) { return; } if (bone == HumanBodyBones.LeftShoulder && CutBoneLeftShoulder) { return; } if (bone == HumanBodyBones.RightShoulder && CutBoneRightShoulder) { return; } if (bone == HumanBodyBones.LeftUpperArm && CutBoneLeftUpperArm) { return; } if (bone == HumanBodyBones.RightUpperArm && CutBoneRightUpperArm) { return; } if (bone == HumanBodyBones.LeftLowerArm && CutBoneLeftLowerArm) { return; } if (bone == HumanBodyBones.RightLowerArm && CutBoneRightLowerArm) { return; } if (bone == HumanBodyBones.LeftHand && CutBoneLeftHand) { return; } if (bone == HumanBodyBones.RightHand && CutBoneRightHand) { return; } if (bone == HumanBodyBones.LeftToes && CutBoneLeftToes) { return; } if (bone == HumanBodyBones.RightToes && CutBoneRightToes) { return; } if (bone == HumanBodyBones.LeftEye && CutBoneLeftEye) { return; } if (bone == HumanBodyBones.RightEye && CutBoneRightEye) { return; } if (bone == HumanBodyBones.Jaw && CutBoneJaw) { return; } if (bone == HumanBodyBones.LeftThumbProximal && CutBoneLeftThumbProximal) { return; } if (bone == HumanBodyBones.LeftThumbIntermediate && CutBoneLeftThumbIntermediate) { return; } if (bone == HumanBodyBones.LeftThumbDistal && CutBoneLeftThumbDistal) { return; } if (bone == HumanBodyBones.LeftIndexProximal && CutBoneLeftIndexProximal) { return; } if (bone == HumanBodyBones.LeftIndexIntermediate && CutBoneLeftIndexIntermediate) { return; } if (bone == HumanBodyBones.LeftIndexDistal && CutBoneLeftIndexDistal) { return; } if (bone == HumanBodyBones.LeftMiddleProximal && CutBoneLeftMiddleProximal) { return; } if (bone == HumanBodyBones.LeftMiddleIntermediate && CutBoneLeftMiddleIntermediate) { return; } if (bone == HumanBodyBones.LeftMiddleDistal && CutBoneLeftMiddleDistal) { return; } if (bone == HumanBodyBones.LeftRingProximal && CutBoneLeftRingProximal) { return; } if (bone == HumanBodyBones.LeftRingIntermediate && CutBoneLeftRingIntermediate) { return; } if (bone == HumanBodyBones.LeftRingDistal && CutBoneLeftRingDistal) { return; } if (bone == HumanBodyBones.LeftLittleProximal && CutBoneLeftLittleProximal) { return; } if (bone == HumanBodyBones.LeftLittleIntermediate && CutBoneLeftLittleIntermediate) { return; } if (bone == HumanBodyBones.LeftLittleDistal && CutBoneLeftLittleDistal) { return; } if (bone == HumanBodyBones.RightThumbProximal && CutBoneRightThumbProximal) { return; } if (bone == HumanBodyBones.RightThumbIntermediate && CutBoneRightThumbIntermediate) { return; } if (bone == HumanBodyBones.RightThumbDistal && CutBoneRightThumbDistal) { return; } if (bone == HumanBodyBones.RightIndexProximal && CutBoneRightIndexProximal) { return; } if (bone == HumanBodyBones.RightIndexIntermediate && CutBoneRightIndexIntermediate) { return; } if (bone == HumanBodyBones.RightIndexDistal && CutBoneRightIndexDistal) { return; } if (bone == HumanBodyBones.RightMiddleProximal && CutBoneRightMiddleProximal) { return; } if (bone == HumanBodyBones.RightMiddleIntermediate && CutBoneRightMiddleIntermediate) { return; } if (bone == HumanBodyBones.RightMiddleDistal && CutBoneRightMiddleDistal) { return; } if (bone == HumanBodyBones.RightRingProximal && CutBoneRightRingProximal) { return; } if (bone == HumanBodyBones.RightRingIntermediate && CutBoneRightRingIntermediate) { return; } if (bone == HumanBodyBones.RightRingDistal && CutBoneRightRingDistal) { return; } if (bone == HumanBodyBones.RightLittleProximal && CutBoneRightLittleProximal) { return; } if (bone == HumanBodyBones.RightLittleIntermediate && CutBoneRightLittleIntermediate) { return; } if (bone == HumanBodyBones.RightLittleDistal && CutBoneRightLittleDistal) { return; } if (bone == HumanBodyBones.UpperChest && CutBoneUpperChest) { return; } } //指ボーン if (bone == HumanBodyBones.LeftIndexDistal || bone == HumanBodyBones.LeftIndexIntermediate || bone == HumanBodyBones.LeftIndexProximal || bone == HumanBodyBones.LeftLittleDistal || bone == HumanBodyBones.LeftLittleIntermediate || bone == HumanBodyBones.LeftLittleProximal || bone == HumanBodyBones.LeftMiddleDistal || bone == HumanBodyBones.LeftMiddleIntermediate || bone == HumanBodyBones.LeftMiddleProximal || bone == HumanBodyBones.LeftRingDistal || bone == HumanBodyBones.LeftRingIntermediate || bone == HumanBodyBones.LeftRingProximal || bone == HumanBodyBones.LeftThumbDistal || bone == HumanBodyBones.LeftThumbIntermediate || bone == HumanBodyBones.LeftThumbProximal || bone == HumanBodyBones.RightIndexDistal || bone == HumanBodyBones.RightIndexIntermediate || bone == HumanBodyBones.RightIndexProximal || bone == HumanBodyBones.RightLittleDistal || bone == HumanBodyBones.RightLittleIntermediate || bone == HumanBodyBones.RightLittleProximal || bone == HumanBodyBones.RightMiddleDistal || bone == HumanBodyBones.RightMiddleIntermediate || bone == HumanBodyBones.RightMiddleProximal || bone == HumanBodyBones.RightRingDistal || bone == HumanBodyBones.RightRingIntermediate || bone == HumanBodyBones.RightRingProximal || bone == HumanBodyBones.RightThumbDistal || bone == HumanBodyBones.RightThumbIntermediate || bone == HumanBodyBones.RightThumbProximal) { //指ボーンカットオフが有効でなければ if (!HandPoseSynchronizeCutoff) { //ボーン同期する。ただしフィルタはかけない BoneSynchronizeSingle(t, ref bone, ref pos, ref rot, false, false); } } //目ボーン else if (bone == HumanBodyBones.LeftEye || bone == HumanBodyBones.RightEye) { //目ボーンカットオフが有効でなければ if (!EyeBoneSynchronizeCutoff) { //ボーン同期する。ただしフィルタはかけない BoneSynchronizeSingle(t, ref bone, ref pos, ref rot, false, false); } } else { //ボーン同期する。フィルタは設定依存 BoneSynchronizeSingle(t, ref bone, ref pos, ref rot, BonePositionFilterEnable, BoneRotationFilterEnable); } } } } //1本のボーンの同期 private void BoneSynchronizeSingle(Transform t, ref HumanBodyBones bone, ref Vector3 pos, ref Quaternion rot, bool posFilter, bool rotFilter) { BoneFilter = Mathf.Clamp(BoneFilter, 0f, 1f); //ボーン位置同期が有効か if (BonePositionSynchronize) { //ボーン位置フィルタが有効か if (posFilter) { bonePosFilter[(int)bone] = (bonePosFilter[(int)bone] * BoneFilter) + pos * (1.0f - BoneFilter); t.localPosition = bonePosFilter[(int)bone]; } else { t.localPosition = pos; } } //ボーン回転フィルタが有効か if (rotFilter) { boneRotFilter[(int)bone] = Quaternion.Slerp(boneRotFilter[(int)bone], rot, 1.0f - BoneFilter); t.localRotation = boneRotFilter[(int)bone]; } else { t.localRotation = rot; } } //ボーンENUM情報をキャッシュして高速化 private bool HumanBodyBonesTryParse(ref string boneName, out HumanBodyBones bone) { //ボーンキャッシュテーブルに存在するなら if (HumanBodyBonesTable.ContainsKey(boneName)) { //キャッシュテーブルから返す bone = HumanBodyBonesTable[boneName]; //ただしLastBoneは発見しなかったことにする(無効値として扱う) if (bone == HumanBodyBones.LastBone) { return false; } return true; } else { //キャッシュテーブルにない場合、検索する var res = EnumTryParse(boneName, out bone); if (!res) { //見つからなかった場合はLastBoneとして登録する(無効値として扱う)ことにより次回から検索しない bone = HumanBodyBones.LastBone; } //キャシュテーブルに登録する HumanBodyBonesTable.Add(boneName, bone); return res; } } //互換性を持ったTryParse private static bool EnumTryParse(string value, out T result) where T : struct { #if NET_4_6 return Enum.TryParse(value, out result); #else try { result = (T)Enum.Parse(typeof(T), value, true); return true; } catch { result = default(T); return false; } #endif } //アプリケーションを終了させる public void ApplicationQuit() { #if UNITY_EDITOR UnityEditor.EditorApplication.isPlaying = false; #else Application.Quit(); #endif } } }