diff --git a/Assets/AlwaysLookCameraScript.cs b/Assets/AlwaysLookCameraScript.cs new file mode 100644 index 00000000..b95308e9 --- /dev/null +++ b/Assets/AlwaysLookCameraScript.cs @@ -0,0 +1,385 @@ +//gpsnmeajp +using System.Collections.Generic; +using UnityEngine; +using VMC; +using VRM; + +public class AlwaysLookCameraScript : MonoBehaviour +{ + //固定視線制御モード + public enum FixedGaze + { + Off, //視線を正面に戻し、以後制御しない + Camera, //視線をカメラ目線に固定する + Ahead, //視線をアバターの前方に固定する(顔仰角に依存しない) + Front, //視線を顔の正面に固定する(顔仰角に依存する) + }; + + // --- 制御モード入力 --- + + public FixedGaze gaze = FixedGaze.Off; //固定視線制御モード(手動カメラ目線・正面・前方・オフ) + public bool fluctuationEnable = false; //目の揺れ制御 + public bool autoLookCameraEnable = false; //自動カメラ目線 + + // --- 内部パラメータ --- + const float limitterAngle = 60f; //角度リミッター + bool isAppliedLastFrame = false; //前Frameで制御適用中か + + // --- 連携変数 --- + FaceController faceController = null; //VMC表情制御ミキサー + Camera currentCamera = null; //現在選択中のカメラ(VMCEventsで更新) + + GameObject currentModel = null; //現在読み込み済みのモデル(VMCEventsで更新) + VRMLookAtHead vrmLookAtHead = null; //現在のモデルに割り当たっているVRM視線制御器 + Transform headTransform = null; //現在のモデルの頭部位置 + + System.Action beforeFaceApply; //VMC表情制御ミキサーに登録する表情制御前処理用Action + bool isSetFaceApplyAction = false; //VMC表情制御ミキサーにActionを登録しているかどうか + + // --- 制御用GameObject --- + GameObject lookTargetALC; //視線制御ターゲット(注意: モデルの子にしてはならない) + GameObject modelHeadBaseALC; //HeadのY軸回転と位置のみを反映する参照用オブジェクト (注意: モデルの子にしてはならない) + + // ---------------- + + //ショートカットキーによる固定視線制御 + public void ManualOperation(FixedGaze lookCamera) + { + gaze = lookCamera; + Debug.Log("ManualOperation:" + gaze); + } + + //グローバル設定の反映 + public void Apply(Settings settings) + { + autoLookCameraEnable = settings.AutoLookCameraEnable; + fluctuationEnable = settings.FluctuationEnable; + + Debug.Log("Apply:" + fluctuationEnable + "/" + autoLookCameraEnable); + } + + //初期化 + void Start() + { + //VMC表情制御ミキサーを取得(必ず取れる) + faceController = GameObject.Find("AnimationController").GetComponent(); + + //視線制御ターゲットを作成 + lookTargetALC = new GameObject(); + lookTargetALC.transform.parent = null; + lookTargetALC.name = "lookTargetALC"; + + //モデル前方参照用オブジェクトを作成(HeadのY軸回転と位置のみを反映する参照用オブジェクト) + modelHeadBaseALC = new GameObject(); + modelHeadBaseALC.transform.parent = null; + modelHeadBaseALC.name = "modelHeadBaseALC"; + + //モデル更新イベントが発生した + VMCEvents.OnModelLoaded += (GameObject CurrentModel) => + { + //設定済みの視線制御Actionがある場合、解除する + unsetFaceApplyAction(); + + //頭部位置を無効化 + headTransform = null; + + //モデルを更新(nullの可能性がある) + currentModel = CurrentModel; + + //有効なモデルが読み込まれていなければ処理しない + if (currentModel == null) { + return; + } + + //VRM視線制御器を取得(nullの可能性がある) + vrmLookAtHead = currentModel.GetComponent(); + + //有効なボーン情報があれば、頭部位置を取得(nullの可能性がある) + if (currentModel.TryGetComponent(out var animator)) + { + headTransform = animator.GetBoneTransform(HumanBodyBones.Head); + } + }; + + //選択中のカメラが変更された + VMCEvents.OnCameraChanged += (Camera currentCamera) => + { + //現在のカメラを更新 + this.currentCamera = currentCamera; + }; + + //表情制御前処理 + beforeFaceApply = () => + { + if (vrmLookAtHead != null) + { + //表情制御前にターゲットを設定し、ワールド座標に視線を向ける + vrmLookAtHead.Target = lookTargetALC.transform; + vrmLookAtHead.LookWorldPosition(); + + //ターゲット設定を解除する + vrmLookAtHead.Target = null; + } + }; + } + + //毎フレーム処理 + void Update() + { + //カメラが無効, モデルが無効, VRM視線制御器が無効, 頭部位置が無効のいずれかを満たす場合は処理しない + if (currentCamera == null || currentModel == null || vrmLookAtHead == null || headTransform == null) + { + return; + } + + + // --- 常に必要な値の計算(単純合成不可) -- + //手動カメラ目線算出(Target: 絶対位置) + Vector3 manualCameraTarget = getManualCameraTarget(); + + //自動カメラ目線算出(Target: 絶対位置, 非制御状態ではZeroを返す) + Vector3 autoCameraTarget = Vector3.zero; + if (autoLookCameraEnable) + { + autoCameraTarget = calcAutoCameraTarget(manualCameraTarget); + } + + //顔正面算出(Target: 絶対位置, 絶対にZeroにはならない) + Vector3 frontTarget = calcFrontTarget(); + + //前方算出(Target: 絶対位置, 絶対にZeroにはならない) + Vector3 aheadTarget = calcAheadTarget(); + + //意図的な視線制御合成 + Vector3 controlledEyeTarget = Vector3.zero; + switch (gaze) + { + case FixedGaze.Camera: + controlledEyeTarget = manualCameraTarget; + break; + case FixedGaze.Ahead: + controlledEyeTarget = aheadTarget; + break; + case FixedGaze.Front: + controlledEyeTarget = frontTarget; + break; + case FixedGaze.Off: + controlledEyeTarget = Vector3.zero; //追従オフ + break; + default: + Debug.LogError("gaze error!"); + break; + } + + //自動カメラが視線向き状態になっている場合は、絶対位置を上書きする + if (autoCameraTarget != Vector3.zero) + { + //顔とターゲットを結ぶベクトルと、顔の正面ベクトルの間の角度を算出 + float HeadForwardToAutoCameraTargetAngle = Vector3.Angle(headTransform.forward, (autoCameraTarget - headTransform.position).normalized); + + //可視範囲(+-60 deg)の範囲にある場合のみ追従する + //この判定がないと、後段の安全装置で正面を向いてしまうため、角度オーバー時はそもそもカメラを向かないようにする + if (HeadForwardToAutoCameraTargetAngle <= 60f) + { + controlledEyeTarget = autoCameraTarget; + } + } + + //顔とターゲットを結ぶベクトルと、顔の正面ベクトルの間の角度を算出 + float HeadForwardToTargetAngle = Vector3.Angle(headTransform.forward, (controlledEyeTarget - headTransform.position).normalized); + + //可視範囲(+-60 deg)の範囲にある場合のみ追従する(ギョロ目防止安全装置) + if (HeadForwardToTargetAngle > 60f) + { + controlledEyeTarget = Vector3.zero; //追従オフ + } + + //視線がZeroの場合は、追従不能なので絶対位置を顔正面にする(Zeroのまま流出すると目が異常になるので注意) + if (controlledEyeTarget == Vector3.zero) { + controlledEyeTarget = frontTarget; + } + + //頭からターゲットの距離を計算(これが微動や揺れの大きさに影響する) + float targetDistance = Vector3.Distance(headTransform.position, controlledEyeTarget); + + // --- 有効無効が分かれる値の計算(単純合成可能) --- + //固視微動算出(EyeMovements: 相対位置) + Vector3 fixational = Vector3.zero; + if (fluctuationEnable) + { + fixational = calcFixationalEyeMovements(targetDistance); + } + + //視線揺れ算出(EyeMovements: 相対位置) + Vector3 saccade = Vector3.zero; + if (fluctuationEnable) + { + saccade = calcSaccadeEyeMovements(targetDistance); + } + + //ランダムな視線制御合成(相対位置) + Vector3 randomEyeMove = fixational + saccade; + + //顔とランダム視線適用済みターゲットを結ぶベクトルと、顔の正面ベクトルの間の角度を算出 + float HeadForwardToTargetWithRandomEyeMoveAngle = Vector3.Angle(headTransform.forward, ((controlledEyeTarget + randomEyeMove) - headTransform.position).normalized); + + //可視範囲(+-60 deg)の範囲にある場合のみ加算する(ギョロ目防止安全装置) + if (HeadForwardToTargetWithRandomEyeMoveAngle > 60f) + { + randomEyeMove = Vector3.zero; //加算オフ + } + + // --- 視線制御に反映 --- + //最終的な視線ターゲット(絶対位置) + Vector3 target = controlledEyeTarget + randomEyeMove; //(絶対位置) + (相対位置) + + //今のフレームで視線制御が行われているかどうかの判定を更新する(正面戻しに使用する) + if (randomEyeMove != Vector3.zero || gaze != FixedGaze.Off) + { + //視線制御は実施中 + isAppliedLastFrame = true; + + //ターゲットオブジェクトに反映 + lookTargetALC.transform.position = target; + + //視線反映設定 + setFaceApplyAction(); + } + else { + //視線制御が行われなくなった & 前フレームまで行われていた場合 + if (isAppliedLastFrame) + { + //視線を正面に戻すため、1フレームだけ正面を設定する + target = frontTarget; + + //ターゲットオブジェクトに反映 + lookTargetALC.transform.position = target; + + //視線反映設定 + setFaceApplyAction(); + + Debug.Log("Turn off gaze control..."); + } + else { + //視線反映解除 + unsetFaceApplyAction(); + } + + //視線制御は実施されていない + isAppliedLastFrame = false; + } + } + + //手動カメラ目線取得 + Vector3 getManualCameraTarget() + { + return currentCamera.transform.position; + } + + //顔正面算出 + Vector3 calcFrontTarget() + { + //顔の正面3m先を視点ターゲットとする + return headTransform.position + (headTransform.forward * 3f); + } + + //前方算出 + Vector3 calcAheadTarget() + { + //角度ピッチを無視した頭位置を生成 + modelHeadBaseALC.transform.position = headTransform.position; + modelHeadBaseALC.transform.rotation = Quaternion.Euler(0,headTransform.rotation.eulerAngles.y, 0); + + //そこから3m先の視点をターゲットとする + return modelHeadBaseALC.transform.position + (modelHeadBaseALC.transform.forward * 3f); + } + + //固視微動算出 + const float fixationalFactor = 0.003f; //移動の大きさ + const float fixationalPeriod = 0.02f; //微動の周期 + + float fixationalTime = 0; //時間カウンタ + Vector3 fixationalEyeMovement = Vector3.zero; //位置 + Vector3 calcFixationalEyeMovements(float distance) { + if (fixationalTime > fixationalPeriod) + { + fixationalEyeMovement = new Vector3(UnityEngine.Random.Range(-fixationalFactor, fixationalFactor), UnityEngine.Random.Range(-fixationalFactor, fixationalFactor), UnityEngine.Random.Range(-fixationalFactor, fixationalFactor)) * distance; + fixationalTime = UnityEngine.Random.Range(-fixationalPeriod, 0f); //最大2倍の時間の間でランダム + } + fixationalTime += Time.deltaTime; + return fixationalEyeMovement; + } + + //視線揺れ算出 + const float saccadeFactor = 0.04f; //移動の大きさ + const float saccadePeriod = 1.4f; //微動の周期 + const float saccadeLength = 0.6f; //継続時間 + + float saccadeTime = 0; //時間カウンタ + bool saccadeFlag = false; //移動制御用フラグ + Vector3 saccadeEyeMovement = Vector3.zero; //位置 + Vector3 calcSaccadeEyeMovements(float distance) { + + if (saccadeTime > (saccadePeriod + saccadeLength)) + { + saccadeTime = UnityEngine.Random.Range(-saccadePeriod, 0f); //最大2倍の時間の間でランダム + saccadeEyeMovement = Vector3.zero; //視線戻す + saccadeFlag = false; + } + else if (saccadeTime > saccadePeriod && saccadeFlag == false) + { + saccadeEyeMovement = new Vector3(UnityEngine.Random.Range(-saccadeFactor, saccadeFactor), UnityEngine.Random.Range(-saccadeFactor, saccadeFactor), UnityEngine.Random.Range(-saccadeFactor, saccadeFactor)) * distance; + saccadeFlag = true; + } + saccadeTime += Time.deltaTime; + return saccadeEyeMovement; + } + + //自動カメラ目線算出 + const float autoCameraPeriod = 15.16f; //移動周期 + const float autoCameraLength = 4.3f;//継続時間 + + bool autoCameraFlag = false; //移動制御用フラグ + float autoCameraTime = 0;//時間カウンタ + Vector3 autoCameraTarget = Vector3.zero; + Vector3 calcAutoCameraTarget(Vector3 manualCameraTarget) + { + if (autoCameraTime > (autoCameraPeriod + autoCameraLength)) + { + autoCameraTime = UnityEngine.Random.Range(-autoCameraPeriod, 0f); //最大2倍の時間の間でランダム + autoCameraTarget = Vector3.zero; //視線戻す + autoCameraFlag = false; + } + else if (autoCameraTime > autoCameraPeriod && autoCameraFlag == false) + { + autoCameraTarget = manualCameraTarget; + autoCameraFlag = true; + } + autoCameraTime += Time.deltaTime; + return autoCameraTarget; + } + + //VMC表情制御ミキサーに視線制御Actionを設定する + void setFaceApplyAction() + { + //設定済みの場合は処理しない + if (isSetFaceApplyAction == true) + { + return; + } + faceController.BeforeApply += beforeFaceApply; + isSetFaceApplyAction = true; + } + + //VMC表情制御ミキサーに設定済みの視線制御Actionを解除する + void unsetFaceApplyAction() + { + //解除済みの場合は処理しない + if (isSetFaceApplyAction == false) + { + return; + } + faceController.BeforeApply -= beforeFaceApply; + isSetFaceApplyAction = false; + } +} diff --git a/Assets/Scripts/ControlWPFWindow.cs b/Assets/Scripts/ControlWPFWindow.cs index 58fcc0ab..85c28712 100644 --- a/Assets/Scripts/ControlWPFWindow.cs +++ b/Assets/Scripts/ControlWPFWindow.cs @@ -62,6 +62,8 @@ public class ControlWPFWindow : MonoBehaviour public PostProcessingManager postProcessingManager; + public AlwaysLookCameraScript alwaysLookCameraScript; + private uint defaultWindowStyle; private uint defaultExWindowStyle; @@ -358,6 +360,19 @@ private void Server_Received(object sender, DataReceivedEventArgs e) var d = (PipeCommands.SetDefaultFace)e.Data; SetDefaultFace(d.face); } + else if (e.CommandType == typeof(PipeCommands.SetAutoEyeMovementConfig)) + { + var d = (PipeCommands.SetAutoEyeMovementConfig)e.Data; + Settings.Current.FluctuationEnable = d.FluctuationEnable; + Settings.Current.AutoLookCameraEnable = d.AutoLookCameraEnable; + + SetAlwaysLookCamera(); + } + else if (e.CommandType == typeof(PipeCommands.GetAutoEyeMovementConfig)) + { + var d = (PipeCommands.GetAutoEyeMovementConfig)e.Data; + LoadAlwaysLookCamera(); + } else if (e.CommandType == typeof(PipeCommands.LoadSettings)) { var d = (PipeCommands.LoadSettings)e.Data; @@ -987,6 +1002,20 @@ private void SetAdvancedGraphicsOption() postProcessingManager.Apply(Settings.Current); } + private async void LoadAlwaysLookCamera() + { + SetAlwaysLookCamera(); + await server.SendCommandAsync(new PipeCommands.SetAutoEyeMovementConfig + { + FluctuationEnable = Settings.Current.FluctuationEnable, + AutoLookCameraEnable = Settings.Current.AutoLookCameraEnable, + }); + } + private void SetAlwaysLookCamera() + { + alwaysLookCameraScript.Apply(Settings.Current); + } + private bool isFirstTimeExecute = true; #region VRM @@ -1399,6 +1428,29 @@ public void DoKeyAction(KeyAction action) case Functions.ShowPhotoWindow: server?.SendCommandAsync(new PipeCommands.ShowPhotoWindow { }); break; + case Functions.FixedGazeControlCamera: + alwaysLookCameraScript.ManualOperation(AlwaysLookCameraScript.FixedGaze.Camera); + break; + case Functions.FixedGazeControlAhead: + alwaysLookCameraScript.ManualOperation(AlwaysLookCameraScript.FixedGaze.Ahead); + break; + case Functions.FixedGazeControlFront: + alwaysLookCameraScript.ManualOperation(AlwaysLookCameraScript.FixedGaze.Front); + break; + case Functions.FixedGazeControlOff: + alwaysLookCameraScript.ManualOperation(AlwaysLookCameraScript.FixedGaze.Off); + break; + case Functions.AutoLookCamerOn: + Settings.Current.AutoLookCameraEnable = true; + LoadAlwaysLookCamera(); + break; + case Functions.AutoLookCamerOff: + Settings.Current.AutoLookCameraEnable = false; + LoadAlwaysLookCamera(); + break; + default: + Debug.LogError("Unimplemented functions!"); + break; } } } @@ -1974,6 +2026,7 @@ await server.SendCommandAsync(new PipeCommands.SetHandFreeOffset SetLipTracking_ViveEnable(Settings.Current.LipTracking_ViveEnable); LoadAdvancedGraphicsOption(); + LoadAlwaysLookCamera(); AdditionalSettingAction?.Invoke(null); diff --git a/Assets/Scripts/ExternalSender/ExternalReceiverForVMC.cs b/Assets/Scripts/ExternalSender/ExternalReceiverForVMC.cs index f3e48691..cdde6f54 100644 --- a/Assets/Scripts/ExternalSender/ExternalReceiverForVMC.cs +++ b/Assets/Scripts/ExternalSender/ExternalReceiverForVMC.cs @@ -1,922 +1,939 @@ -//gpsnmeajp -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using UnityEngine; -using UnityMemoryMappedFile; -using VRM; - -namespace VMC -{ - [DefaultExecutionOrder(15002)] - - [RequireComponent(typeof(uOSC.uOscServer))] - public class ExternalReceiverForVMC : MonoBehaviour - { - public ExternalSender externalSender; - public MidiCCWrapper MIDICCWrapper; - - //仮想コントローラソート済み辞書 - public SortedDictionary virtualHmd = new SortedDictionary(); - public SortedDictionary virtualController = new SortedDictionary(); - public SortedDictionary virtualTracker = new SortedDictionary(); - - public SortedDictionary virtualHmdFiltered = new SortedDictionary(); - public SortedDictionary virtualControllerFiltered = new SortedDictionary(); - public SortedDictionary virtualTrackerFiltered = new SortedDictionary(); - - public int receivePort = 39540; - public string statusString = ""; - private string statusStringOld = ""; - - public bool CorrectRotationWhenCalibration = true; - private int lastCalibrationState = 0; - private bool doCalibration = false; - private Quaternion calibrateRotationOffset = Quaternion.identity; - - public EasyDeviceDiscoveryProtocolManager eddp; - - static public Action StatusStringUpdated = null; - - ControlWPFWindow window = null; - public GameObject CurrentModel = null; - Camera currentCamera = null; - FaceController faceController = null; - VRMLookAtHead vrmLookAtHead = null; - Transform headTransform = null; - - //仮想視線操作用 - GameObject lookTargetOSC; - Action beforeFaceApply; - bool setFaceApplyAction = false; - - //バッファ - Vector3 pos; - Quaternion rot; - - private Queue<(float timestamp, uOSC.Message message)> MessageBuffer = new Queue<(float timestamp, uOSC.Message message)>(); - - public int packets = 0; - - //ボーン情報取得 - Animator animator = null; - //VRMのブレンドシェーププロキシ - VRMBlendShapeProxy blendShapeProxy = null; - - //ボーンENUM情報テーブル - Dictionary HumanBodyBonesTable = new Dictionary(); - - //ボーン情報テーブル - Dictionary HumanBodyBonesPositionTable = new Dictionary(); - Dictionary HumanBodyBonesRotationTable = new Dictionary(); - private VirtualAvatar virtualAvatar; - - private Dictionary blendShapeBuffer = new Dictionary(); - - public bool DisableBlendShapeReception { get; set; } - - private bool enableLocalHandFix = true; - private float lastBoneReceivedTime = 0; - - private VMCProtocolReceiverSettings receiverSetting; - - private bool ApplyBlendShape; - private bool ApplyLookAt; - private bool ApplyTracker; - private bool ApplyCamera; - private bool ApplyLight; - private bool ApplyMidi; - private bool ApplyStatus; - private bool ApplyControl; - private bool ApplySetting; - private bool ApplyControllerInput; - private bool ApplyKeyboardInput; - - public void SetSetting(VMCProtocolReceiverSettings setting) - { - receiverSetting = setting; - - if (virtualAvatar != null) - { - virtualAvatar.Enable = setting.Enable; - virtualAvatar.ApplyRootRotation = setting.ApplyRootRotation; - virtualAvatar.ApplyRootPosition = setting.ApplyRootPosition; - virtualAvatar.ApplySpine = setting.ApplySpine; - virtualAvatar.ApplyChest = setting.ApplyChest; - virtualAvatar.ApplyHead = setting.ApplyHead; - virtualAvatar.ApplyLeftArm = setting.ApplyLeftArm; - virtualAvatar.ApplyRightArm = setting.ApplyRightArm; - virtualAvatar.ApplyLeftHand = setting.ApplyLeftHand; - virtualAvatar.ApplyRightHand = setting.ApplyRightHand; - virtualAvatar.ApplyLeftLeg = setting.ApplyLeftLeg; - virtualAvatar.ApplyRightLeg = setting.ApplyRightLeg; - virtualAvatar.ApplyLeftFoot = setting.ApplyLeftFoot; - virtualAvatar.ApplyRightFoot = setting.ApplyRightFoot; - virtualAvatar.ApplyEye = setting.ApplyEye; - virtualAvatar.ApplyLeftFinger = setting.ApplyLeftFinger; - virtualAvatar.ApplyRightFinger = setting.ApplyRightFinger; - virtualAvatar.CorrectHipBone = setting.CorrectHipBone; - virtualAvatar.IgnoreDefaultBone = setting.IgnoreDefaultBone; - } - - ApplyBlendShape = setting.ApplyBlendShape; - ApplyLookAt = setting.ApplyLookAt; - ApplyTracker = setting.ApplyTracker; - ApplyCamera = setting.ApplyCamera; - ApplyLight = setting.ApplyLight; - ApplyMidi = setting.ApplyMidi; - ApplyStatus = setting.ApplyStatus; - ApplyControl = setting.ApplyControl; - ApplySetting = setting.ApplySetting; - ApplyControllerInput = setting.ApplyControllerInput; - ApplyKeyboardInput = setting.ApplyKeyboardInput; - } - - public void Recenter() - { - virtualAvatar.Recenter(); - } - - public void Initialize() - { - var server = GetComponent(); - server.onDataReceived.AddListener(OnDataReceived); - - window = GameObject.Find("ControlWPFWindow").GetComponent(); - faceController = GameObject.Find("AnimationController").GetComponent(); - VMCEvents.OnModelLoaded += (GameObject CurrentModel) => - { - this.CurrentModel = CurrentModel; - OnModelChanged(); - }; - VMCEvents.OnCameraChanged += (Camera currentCamera) => - { - this.currentCamera = currentCamera; - }; - - beforeFaceApply = () => - { - if (vrmLookAtHead == null || lookTargetOSC == null) return; - vrmLookAtHead.Target = lookTargetOSC.transform; - vrmLookAtHead.LookWorldPosition(); - vrmLookAtHead.Target = null; - }; - - var modelRoot = new GameObject("ModelRoot").transform; - modelRoot.SetParent(transform, false); - virtualAvatar = new VirtualAvatar(modelRoot, MotionSource.VMCProtocol); - virtualAvatar.Enable = false; - MotionManager.Instance.AddVirtualAvatar(virtualAvatar); - if (receiverSetting != null) - { - SetSetting(receiverSetting); - } - - OnModelChanged(); - - this.gameObject.SetActive(false); - server.enabled = true; - } - - private void OnDestroy() - { - if (virtualAvatar != null) - { - MotionManager.Instance.RemoveVirtualAvatar(virtualAvatar); - } - } - - private void OnModelChanged() - { - if (CurrentModel != null) - { - vrmLookAtHead = CurrentModel.GetComponent(); - animator = CurrentModel.GetComponent(); - headTransform = null; - if (animator != null) - { - headTransform = animator.GetBoneTransform(HumanBodyBones.Head); - } - } - } - - private object LockObject = new object(); - - void OnDataReceived(uOSC.Message message) - { - //有効なとき以外処理しない - if (this.isActiveAndEnabled && receiverSetting != null) - { - //生存チェックのためのパケットカウンタ - packets++; - if (packets > int.MaxValue / 2) - { - packets = 0; - } - - if (receiverSetting.DelayMs == 0) - { - ProcessMessage(message); - } - else - { - MessageBuffer.Enqueue((Time.realtimeSinceStartup, message)); - } - } - } - void ProcessMessage(uOSC.Message message) - { - //有効なとき以外処理しない - if (this.isActiveAndEnabled) - { - - //仮想Hmd V2.3 - if (message.address == "/VMC/Ext/Hmd/Pos" && ApplyTracker - && (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 serial = (string)message.values[0]; - var rigidTransform = SetTransform(ref pos, ref rot, ref message); - - lock (LockObject) - { - if (virtualHmd.ContainsKey(serial)) - { - virtualHmd[serial] = rigidTransform; - } - else - { - virtualHmd.Add(serial, rigidTransform); - virtualHmdFiltered.Add(serial, rigidTransform); - } - } - } - //仮想コントローラー V2.3 - else if (message.address == "/VMC/Ext/Con/Pos" && ApplyTracker - && (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 serial = (string)message.values[0]; - var rigidTransform = SetTransform(ref pos, ref rot, ref message); - - lock (LockObject) - { - if (virtualController.ContainsKey(serial)) - { - virtualController[serial] = rigidTransform; - } - else - { - virtualController.Add(serial, rigidTransform); - virtualControllerFiltered.Add(serial, rigidTransform); - } - } - } - //仮想トラッカー V2.3 - else if (message.address == "/VMC/Ext/Tra/Pos" && ApplyTracker - && (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 serial = (string)message.values[0]; - var rigidTransform = SetTransform(ref pos, ref rot, ref message); - - lock (LockObject) - { - if (virtualTracker.ContainsKey(serial)) - { - virtualTracker[serial] = rigidTransform; - } - else - { - virtualTracker.Add(serial, rigidTransform); - virtualTrackerFiltered.Add(serial, rigidTransform); - } - } - } - //フレーム設定 V2.3 - else if (message.address == "/VMC/Ext/Set/Period" && ApplySetting - && (message.values[0] is int) - && (message.values[1] is int) - && (message.values[2] is int) - && (message.values[3] is int) - && (message.values[4] is int) - && (message.values[5] is int) - ) - { - externalSender.periodStatus = (int)message.values[0]; - externalSender.periodRoot = (int)message.values[1]; - externalSender.periodBone = (int)message.values[2]; - externalSender.periodBlendShape = (int)message.values[3]; - externalSender.periodCamera = (int)message.values[4]; - externalSender.periodDevices = (int)message.values[5]; - } - - //コントローラ操作情報 v2.1 - if (message.address == "/VMC/Ext/Con" && ApplyControllerInput - && (message.values[0] is int) - && (message.values[1] is string) - && (message.values[2] is int) - && (message.values[3] is int) - && (message.values[4] is int) - && (message.values[5] is float) - && (message.values[6] is float) - && (message.values[7] is float) - ) - { - var active = (int)message.values[0]; - var name = (string)message.values[1]; - var isLeft = (int)message.values[2] == 1; - var isTouch = (int)message.values[3] == 1; - var isAxis = (int)message.values[4] == 1; - var axis = new Vector3((float)message.values[5], (float)message.values[6], (float)message.values[7]); - - var keyArgs = new OVRKeyEventArgs(name, axis, isLeft, isAxis, isTouch); - if (active == 1) - { - SteamVR2Input.Instance.KeyDownEvent?.Invoke(this, keyArgs); - } - else if (active == 0) - { - SteamVR2Input.Instance.KeyUpEvent?.Invoke(this, keyArgs); - } - else if (active == 2) - { - SteamVR2Input.Instance.AxisChangedEvent?.Invoke(this, keyArgs); - } - } - //キーボード操作情報 v2.1 - else if (message.address == "/VMC/Ext/Key" && ApplyKeyboardInput - && (message.values[0] is int) - && (message.values[1] is string) - && (message.values[2] is int) - ) - { - var active = (int)message.values[0] == 1; - var name = (string)message.values[1]; - var keycode = (int)message.values[2]; - - var keyArgs = new KeyboardEventArgs(keycode); - - if (active) - { - KeyboardAction.KeyDownEvent?.Invoke(this, keyArgs); - } - else - { - KeyboardAction.KeyUpEvent?.Invoke(this, keyArgs); - } - } - - //Virtual MIDI CC V2.3 - else if (message.address == "/VMC/Ext/Midi/CC/Val" && ApplyMidi - && (message.values[0] is int) - && (message.values[1] is float) - ) - { - MIDICCWrapper.KnobUpdated(0, (int)message.values[0], (float)message.values[1]); - } - //Camera Control V2.3 - else if (message.address == "/VMC/Ext/Cam" && ApplyCamera - && (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) - && (message.values[8] is float) - ) - { - 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]; - float fov = (float)message.values[8]; - - //FreeCameraじゃなかったらFreeCameraにする - if (Settings.Current.CameraType != UnityMemoryMappedFile.CameraTypes.Free) - { - CameraManager.Current.ChangeCamera(UnityMemoryMappedFile.CameraTypes.Free); - } - - //カメラ制御を切る - CameraManager.Current.FreeCamera.GetComponent().enabled = false; - - //座標とFOVを適用 - CameraManager.Current.FreeCamera.transform.localPosition = pos; - CameraManager.Current.FreeCamera.transform.localRotation = rot; - CameraManager.Current.ControlCamera.fieldOfView = fov; - } //ブレンドシェープ同期 - else if (message.address == "/VMC/Ext/Blend/Val" && ApplyBlendShape - && (message.values[0] is string) - && (message.values[1] is float) - ) - { - blendShapeBuffer[(string)message.values[0]] = (float)message.values[1]; - } - //ブレンドシェープ適用 - else if (message.address == "/VMC/Ext/Blend/Apply" && ApplyBlendShape) - { - if (DisableBlendShapeReception == true) - { - blendShapeBuffer.Clear(); - } - - faceController.MixPresets(nameof(ExternalReceiverForVMC), blendShapeBuffer.Keys.ToArray(), blendShapeBuffer.Values.ToArray()); - blendShapeBuffer.Clear(); - - }//外部アイトラ V2.3 - else if (message.address == "/VMC/Ext/Set/Eye" && ApplyLookAt - && (message.values[0] is int) - && (message.values[1] is float) - && (message.values[2] is float) - && (message.values[3] is float) - ) - { - bool enable = ((int)message.values[0]) != 0; - pos.x = (float)message.values[1]; - pos.y = (float)message.values[2]; - pos.z = (float)message.values[3]; - - if (enable) - { - //ターゲットが存在しなければ作る - if (lookTargetOSC == null) - { - lookTargetOSC = new GameObject(); - lookTargetOSC.name = "lookTargetOSC"; - } - //位置を書き込む - if (lookTargetOSC.transform != null) - { - lookTargetOSC.transform.parent = headTransform; - lookTargetOSC.transform.localPosition = pos; - } - - //視線に書き込む - if (vrmLookAtHead != null && setFaceApplyAction == false) - { - faceController.BeforeApply += beforeFaceApply; - setFaceApplyAction = true; - } - } - else - { - //視線を止める - if (vrmLookAtHead != null && setFaceApplyAction == true) - { - faceController.BeforeApply -= beforeFaceApply; - setFaceApplyAction = false; - } - } - } - //情報要求 V2.4 - else if (message.address == "/VMC/Ext/Set/Req" && ApplyControl) - { - if (externalSender.isActiveAndEnabled) - { - externalSender.SendPerLowRate(); //即時送信 - } - } - //情報表示 V2.4 - else if (message.address == "/VMC/Ext/Set/Res" && (message.values[0] is string) && ApplyStatus) - { - statusString = (string)message.values[0]; - } - //キャリブレーション準備 V2.5 - else if (message.address == "/VMC/Ext/Set/Calib/Ready" && ApplyControl) - { - if (File.Exists(Settings.Current.VRMPath)) - { - IKManager.Instance.ModelCalibrationInitialize(); - } - } - //キャリブレーション実行 V2.5 - else if (message.address == "/VMC/Ext/Set/Calib/Exec" && (message.values[0] is int) && ApplyControl) - { - PipeCommands.CalibrateType calibrateType = PipeCommands.CalibrateType.Ipose; - - switch ((int)message.values[0]) - { - case 0: - calibrateType = PipeCommands.CalibrateType.Ipose; - break; - case 1: - calibrateType = PipeCommands.CalibrateType.Tpose; - break; - case 2: - calibrateType = PipeCommands.CalibrateType.FixedHandWithGround; - break; - case 3: - calibrateType = PipeCommands.CalibrateType.FixedHand; - break; - default: return; //無視 - } - StartCoroutine(IKManager.Instance.Calibrate(calibrateType)); - Invoke("EndCalibrate", 2f); - } - //設定読み込み V2.5 - else if (message.address == "/VMC/Ext/Set/Config" && (message.values[0] is string && ApplySetting)) - { - string path = (string)message.values[0]; - if (File.Exists(path)) - { - //なぜか時間がかかる - window.LoadSettings(path); - } - } - //スルー情報 V2.6 - else if (message.address != null && message.address.StartsWith("/VMC/Thru/") && ApplyControl) - { - //転送する - if (externalSender.isActiveAndEnabled) - { - externalSender.Send(message.address, message.values); - } - } - //Directional Light V2.9 - else if (message.address == "/VMC/Ext/Light" && ApplyLight - && (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) - && (message.values[8] is float) - && (message.values[9] is float) - && (message.values[10] is float) - && (message.values[11] is float) - ) - { - 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]; - float r = (float)message.values[8]; - float g = (float)message.values[9]; - float b = (float)message.values[10]; - float a = (float)message.values[11]; - - window.MainDirectionalLight.color = new Color(r, g, b, a); - window.MainDirectionalLightTransform.position = pos; - window.MainDirectionalLightTransform.rotation = rot; - } - - //ルートボーン - else 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) - ) - { - 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]; - - HumanBodyBonesTable[boneName] = VirtualAvatar.HumanBodyBonesRoot; - HumanBodyBonesPositionTable[VirtualAvatar.HumanBodyBonesRoot] = pos; - HumanBodyBonesRotationTable[VirtualAvatar.HumanBodyBonesRoot] = rot; - } - //ボーン姿勢 - 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)) - { - //あれば位置と回転をキャッシュする - HumanBodyBonesPositionTable[bone] = pos; - HumanBodyBonesRotationTable[bone] = rot; - - // 手以外を受信したとき - if (!(bone == HumanBodyBones.LeftHand || - bone == HumanBodyBones.RightHand || - (bone >= HumanBodyBones.LeftThumbProximal && - bone <= HumanBodyBones.RightLittleDistal))) - { - enableLocalHandFix = false; - lastBoneReceivedTime = Time.realtimeSinceStartup; - } - } - - //受信と更新のタイミングは切り離した - } - - //ボーン姿勢 - else if (message.address == "/VMC/Ext/OK" - && (message.values[0] is int) - ) - { - int loaded = (int)message.values[0]; - if (message.values.Length > 2) - { - int calibrationState = (int)message.values[1]; - int calibrationMode = (int)message.values[2]; - - if (calibrationState != lastCalibrationState && calibrationState == 3) - { - doCalibration = true; - } - lastCalibrationState = calibrationState; - } - - } - } - } - - SteamVR_Utils.RigidTransform SetTransform(ref Vector3 pos, ref Quaternion rot, ref uOSC.Message message) - { - 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]; - return new SteamVR_Utils.RigidTransform(pos, rot); - } - - public static float filterStrength = 10.0f; - - private void Update() - { - if (receiverSetting == null) return; - - while (MessageBuffer.Count > 0 && MessageBuffer.Peek().timestamp + (float)receiverSetting.DelayMs / 1000f < Time.realtimeSinceStartup) - { - ProcessMessage(MessageBuffer.Dequeue().message); - } - - lock (LockObject) - { - foreach (var pair in virtualHmd) - { - var newpos = Vector3.Lerp(virtualHmdFiltered[pair.Key].pos, pair.Value.pos, filterStrength * Time.deltaTime); - var newrot = Quaternion.Lerp(virtualHmdFiltered[pair.Key].rot, pair.Value.rot, filterStrength * Time.deltaTime); - virtualHmdFiltered[pair.Key] = new SteamVR_Utils.RigidTransform(newpos, newrot); - } - foreach (var pair in virtualController) - { - var newpos = Vector3.Lerp(virtualControllerFiltered[pair.Key].pos, pair.Value.pos, filterStrength * Time.deltaTime); - var newrot = Quaternion.Lerp(virtualControllerFiltered[pair.Key].rot, pair.Value.rot, filterStrength * Time.deltaTime); - virtualControllerFiltered[pair.Key] = new SteamVR_Utils.RigidTransform(newpos, newrot); - } - foreach (var pair in virtualTracker) - { - var newpos = Vector3.Lerp(virtualTrackerFiltered[pair.Key].pos, pair.Value.pos, filterStrength * Time.deltaTime); - var newrot = Quaternion.Lerp(virtualTrackerFiltered[pair.Key].rot, pair.Value.rot, filterStrength * Time.deltaTime); - virtualTrackerFiltered[pair.Key] = new SteamVR_Utils.RigidTransform(newpos, newrot); - } - } - - //更新を検出(あまりに高速な変化に追従しないように) - if (statusString != statusStringOld) - { - statusStringOld = statusString; - - if (StatusStringUpdated != null) - { - StatusStringUpdated.Invoke(statusString); - } - } - } - - // VRIKのボーン情報を取得するためにLateUpdateを使う - private void LateUpdate() - { - if (CorrectRotationWhenCalibration && doCalibration) - { - // 現在のアバターの正面方向回転オフセットを取得 - if (animator != null) - { - var hipBone = animator.GetBoneTransform(HumanBodyBones.Hips); - calibrateRotationOffset = Quaternion.Euler(0, hipBone.rotation.eulerAngles.y, 0); - } - } - doCalibration = false; - - BoneSynchronizeByTable(); - if (lastBoneReceivedTime + 5f < Time.realtimeSinceStartup) - { - enableLocalHandFix = true; - } - } - - - private bool internalActive = false; - - public void SetObjectActive(bool enable) - { - internalActive = enable; - if (enable) - { - var uServer = GetComponent(); - if (uServer.enabled == true) uServer.enabled = false; - if (isPortFree(receivePort)) - { - uServer.enabled = true; - gameObject.SetActive(enable); - } - else - { - Debug.LogError("受信ポートが他のアプリと被っています。変更してください"); - } - } - else - { - var uServer = GetComponent(); - uServer.enabled = false; - gameObject.SetActive(enable); - } - } - - private bool isPortFree(int port) - { - var ipGlobalProp = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties(); - var usedPorts = ipGlobalProp.GetActiveUdpListeners(); - return usedPorts.Any(d => d.Port == port) == false; - } - - public void ChangeOSCPort(int port) - { - receivePort = port; - if (eddp != null) eddp.found = false; - - var uServer = GetComponent(); - uServer.enabled = false; - var type = typeof(uOSC.uOscServer); - var portfield = type.GetField("port", BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Instance); - portfield.SetValue(uServer, port); - if (internalActive == true) - { - SetObjectActive(true); - } - } - - //ボーン位置をキャッシュテーブルに基づいて更新 - 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 (virtualAvatar != null && animator != null && bone != HumanBodyBones.LastBone) - { - Transform targetTransform = virtualAvatar.GetCloneBoneTransform(bone); - Transform tempTransform; - //ボーンによって操作を分ける - if (targetTransform != null) - { - //手首ボーンから先のみ受信した際は - if (receiverSetting.FixHandBone && enableLocalHandFix == true && (bone == HumanBodyBones.LeftHand || bone == HumanBodyBones.RightHand)) - { - //ローカル座標系の回転打ち消し - Quaternion allLocalRotation = Quaternion.identity; - var setRotation = rot; - tempTransform = animator.GetBoneTransform(bone); - var rootTransform = animator.transform; - while (tempTransform != rootTransform) - { - tempTransform = tempTransform.parent; - //後から逆回転をかけて打ち消し - allLocalRotation = allLocalRotation * Quaternion.Inverse(tempTransform.localRotation); - } - allLocalRotation = allLocalRotation * calibrateRotationOffset; - Quaternion receivedRotation = allLocalRotation * rot; - //外部からのボーンへの反映 - BoneSynchronizeSingle(targetTransform, ref bone, ref pos, ref receivedRotation); - } - else - { - BoneSynchronizeSingle(targetTransform, ref bone, ref pos, ref rot); - } - } - } - } - //1本のボーンの同期 - private void BoneSynchronizeSingle(Transform t, ref HumanBodyBones bone, ref Vector3 pos, ref Quaternion rot) - { - if (receiverSetting.UseBonePosition) t.localPosition = pos; - t.localRotation = rot; - virtualAvatar.SetPoseChanged(bone); - } - //ボーン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 - } - } +//gpsnmeajp +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityMemoryMappedFile; +using VRM; + +namespace VMC +{ + [DefaultExecutionOrder(15002)] + + [RequireComponent(typeof(uOSC.uOscServer))] + public class ExternalReceiverForVMC : MonoBehaviour + { + public ExternalSender externalSender; + public MidiCCWrapper MIDICCWrapper; + + //仮想コントローラソート済み辞書 + public SortedDictionary virtualHmd = new SortedDictionary(); + public SortedDictionary virtualController = new SortedDictionary(); + public SortedDictionary virtualTracker = new SortedDictionary(); + + public SortedDictionary virtualHmdFiltered = new SortedDictionary(); + public SortedDictionary virtualControllerFiltered = new SortedDictionary(); + public SortedDictionary virtualTrackerFiltered = new SortedDictionary(); + + public int receivePort = 39540; + public string statusString = ""; + private string statusStringOld = ""; + + public bool CorrectRotationWhenCalibration = true; + private int lastCalibrationState = 0; + private bool doCalibration = false; + private Quaternion calibrateRotationOffset = Quaternion.identity; + + public EasyDeviceDiscoveryProtocolManager eddp; + + static public Action StatusStringUpdated = null; + + ControlWPFWindow window = null; + public GameObject CurrentModel = null; + Camera currentCamera = null; + FaceController faceController = null; + VRMLookAtHead vrmLookAtHead = null; + Transform headTransform = null; + + //仮想視線操作用 + GameObject lookTargetOSC; + Action beforeFaceApply; + bool setFaceApplyAction = false; + + //バッファ + Vector3 pos; + Quaternion rot; + + private Queue<(float timestamp, uOSC.Message message)> MessageBuffer = new Queue<(float timestamp, uOSC.Message message)>(); + + public int packets = 0; + + //ボーン情報取得 + Animator animator = null; + //VRMのブレンドシェーププロキシ + VRMBlendShapeProxy blendShapeProxy = null; + + //ボーンENUM情報テーブル + Dictionary HumanBodyBonesTable = new Dictionary(); + + //ボーン情報テーブル + Dictionary HumanBodyBonesPositionTable = new Dictionary(); + Dictionary HumanBodyBonesRotationTable = new Dictionary(); + private VirtualAvatar virtualAvatar; + + private Dictionary blendShapeBuffer = new Dictionary(); + + public bool DisableBlendShapeReception { get; set; } + + private bool enableLocalHandFix = true; + private float lastBoneReceivedTime = 0; + + private VMCProtocolReceiverSettings receiverSetting; + + private bool ApplyBlendShape; + private bool ApplyLookAt; + private bool ApplyTracker; + private bool ApplyCamera; + private bool ApplyLight; + private bool ApplyMidi; + private bool ApplyStatus; + private bool ApplyControl; + private bool ApplySetting; + private bool ApplyControllerInput; + private bool ApplyKeyboardInput; + + public void SetSetting(VMCProtocolReceiverSettings setting) + { + receiverSetting = setting; + + if (virtualAvatar != null) + { + virtualAvatar.Enable = setting.Enable; + virtualAvatar.ApplyRootRotation = setting.ApplyRootRotation; + virtualAvatar.ApplyRootPosition = setting.ApplyRootPosition; + virtualAvatar.ApplySpine = setting.ApplySpine; + virtualAvatar.ApplyChest = setting.ApplyChest; + virtualAvatar.ApplyHead = setting.ApplyHead; + virtualAvatar.ApplyLeftArm = setting.ApplyLeftArm; + virtualAvatar.ApplyRightArm = setting.ApplyRightArm; + virtualAvatar.ApplyLeftHand = setting.ApplyLeftHand; + virtualAvatar.ApplyRightHand = setting.ApplyRightHand; + virtualAvatar.ApplyLeftLeg = setting.ApplyLeftLeg; + virtualAvatar.ApplyRightLeg = setting.ApplyRightLeg; + virtualAvatar.ApplyLeftFoot = setting.ApplyLeftFoot; + virtualAvatar.ApplyRightFoot = setting.ApplyRightFoot; + virtualAvatar.ApplyEye = setting.ApplyEye; + virtualAvatar.ApplyLeftFinger = setting.ApplyLeftFinger; + virtualAvatar.ApplyRightFinger = setting.ApplyRightFinger; + virtualAvatar.CorrectHipBone = setting.CorrectHipBone; + virtualAvatar.IgnoreDefaultBone = setting.IgnoreDefaultBone; + } + + ApplyBlendShape = setting.ApplyBlendShape; + ApplyLookAt = setting.ApplyLookAt; + ApplyTracker = setting.ApplyTracker; + ApplyCamera = setting.ApplyCamera; + ApplyLight = setting.ApplyLight; + ApplyMidi = setting.ApplyMidi; + ApplyStatus = setting.ApplyStatus; + ApplyControl = setting.ApplyControl; + ApplySetting = setting.ApplySetting; + ApplyControllerInput = setting.ApplyControllerInput; + ApplyKeyboardInput = setting.ApplyKeyboardInput; + } + + public void Recenter() + { + virtualAvatar.Recenter(); + } + + public void Initialize() + { + var server = GetComponent(); + server.onDataReceived.AddListener(OnDataReceived); + + window = GameObject.Find("ControlWPFWindow").GetComponent(); + faceController = GameObject.Find("AnimationController").GetComponent(); + VMCEvents.OnModelLoaded += (GameObject CurrentModel) => + { + this.CurrentModel = CurrentModel; + OnModelChanged(); + }; + VMCEvents.OnCameraChanged += (Camera currentCamera) => + { + this.currentCamera = currentCamera; + }; + + beforeFaceApply = () => + { + if (vrmLookAtHead == null || lookTargetOSC == null) return; + vrmLookAtHead.Target = lookTargetOSC.transform; + vrmLookAtHead.LookWorldPosition(); + vrmLookAtHead.Target = null; + }; + + var modelRoot = new GameObject("ModelRoot").transform; + modelRoot.SetParent(transform, false); + virtualAvatar = new VirtualAvatar(modelRoot, MotionSource.VMCProtocol); + virtualAvatar.Enable = false; + MotionManager.Instance.AddVirtualAvatar(virtualAvatar); + if (receiverSetting != null) + { + SetSetting(receiverSetting); + } + + OnModelChanged(); + + this.gameObject.SetActive(false); + server.enabled = true; + } + + private void OnDestroy() + { + if (virtualAvatar != null) + { + MotionManager.Instance.RemoveVirtualAvatar(virtualAvatar); + } + } + + private void OnModelChanged() + { + if (CurrentModel != null) + { + vrmLookAtHead = CurrentModel.GetComponent(); + animator = CurrentModel.GetComponent(); + headTransform = null; + if (animator != null) + { + headTransform = animator.GetBoneTransform(HumanBodyBones.Head); + } + } + } + + private object LockObject = new object(); + + void OnDataReceived(uOSC.Message message) + { + //有効なとき以外処理しない + if (this.isActiveAndEnabled && receiverSetting != null) + { + //生存チェックのためのパケットカウンタ + packets++; + if (packets > int.MaxValue / 2) + { + packets = 0; + } + + if (receiverSetting.DelayMs == 0) + { + ProcessMessage(message); + } + else + { + MessageBuffer.Enqueue((Time.realtimeSinceStartup, message)); + } + } + } + void ProcessMessage(uOSC.Message message) + { + //有効なとき以外処理しない + if (this.isActiveAndEnabled) + { + + //仮想Hmd V2.3 + if (message.address == "/VMC/Ext/Hmd/Pos" && ApplyTracker + && (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 serial = (string)message.values[0]; + var rigidTransform = SetTransform(ref pos, ref rot, ref message); + + lock (LockObject) + { + if (virtualHmd.ContainsKey(serial)) + { + virtualHmd[serial] = rigidTransform; + } + else + { + virtualHmd.Add(serial, rigidTransform); + virtualHmdFiltered.Add(serial, rigidTransform); + } + } + } + //仮想コントローラー V2.3 + else if (message.address == "/VMC/Ext/Con/Pos" && ApplyTracker + && (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 serial = (string)message.values[0]; + var rigidTransform = SetTransform(ref pos, ref rot, ref message); + + lock (LockObject) + { + if (virtualController.ContainsKey(serial)) + { + virtualController[serial] = rigidTransform; + } + else + { + virtualController.Add(serial, rigidTransform); + virtualControllerFiltered.Add(serial, rigidTransform); + } + } + } + //仮想トラッカー V2.3 + else if (message.address == "/VMC/Ext/Tra/Pos" && ApplyTracker + && (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 serial = (string)message.values[0]; + var rigidTransform = SetTransform(ref pos, ref rot, ref message); + + lock (LockObject) + { + if (virtualTracker.ContainsKey(serial)) + { + virtualTracker[serial] = rigidTransform; + } + else + { + virtualTracker.Add(serial, rigidTransform); + virtualTrackerFiltered.Add(serial, rigidTransform); + } + } + } + //フレーム設定 V2.3 + else if (message.address == "/VMC/Ext/Set/Period" && ApplySetting + && (message.values[0] is int) + && (message.values[1] is int) + && (message.values[2] is int) + && (message.values[3] is int) + && (message.values[4] is int) + && (message.values[5] is int) + ) + { + externalSender.periodStatus = (int)message.values[0]; + externalSender.periodRoot = (int)message.values[1]; + externalSender.periodBone = (int)message.values[2]; + externalSender.periodBlendShape = (int)message.values[3]; + externalSender.periodCamera = (int)message.values[4]; + externalSender.periodDevices = (int)message.values[5]; + } + + //コントローラ操作情報 v2.1 + if (message.address == "/VMC/Ext/Con" && ApplyControllerInput + && (message.values[0] is int) + && (message.values[1] is string) + && (message.values[2] is int) + && (message.values[3] is int) + && (message.values[4] is int) + && (message.values[5] is float) + && (message.values[6] is float) + && (message.values[7] is float) + ) + { + var active = (int)message.values[0]; + var name = (string)message.values[1]; + var isLeft = (int)message.values[2] == 1; + var isTouch = (int)message.values[3] == 1; + var isAxis = (int)message.values[4] == 1; + var axis = new Vector3((float)message.values[5], (float)message.values[6], (float)message.values[7]); + + var keyArgs = new OVRKeyEventArgs(name, axis, isLeft, isAxis, isTouch); + if (active == 1) + { + SteamVR2Input.Instance.KeyDownEvent?.Invoke(this, keyArgs); + } + else if (active == 0) + { + SteamVR2Input.Instance.KeyUpEvent?.Invoke(this, keyArgs); + } + else if (active == 2) + { + SteamVR2Input.Instance.AxisChangedEvent?.Invoke(this, keyArgs); + } + } + //キーボード操作情報 v2.1 + else if (message.address == "/VMC/Ext/Key" && ApplyKeyboardInput + && (message.values[0] is int) + && (message.values[1] is string) + && (message.values[2] is int) + ) + { + var active = (int)message.values[0] == 1; + var name = (string)message.values[1]; + var keycode = (int)message.values[2]; + + var keyArgs = new KeyboardEventArgs(keycode); + + if (active) + { + KeyboardAction.KeyDownEvent?.Invoke(this, keyArgs); + } + else + { + KeyboardAction.KeyUpEvent?.Invoke(this, keyArgs); + } + } + + //Virtual MIDI CC V2.3 + else if (message.address == "/VMC/Ext/Midi/CC/Val" && ApplyMidi + && (message.values[0] is int) + && (message.values[1] is float) + ) + { + MIDICCWrapper.KnobUpdated(0, (int)message.values[0], (float)message.values[1]); + } + //Camera Control V2.3 + else if (message.address == "/VMC/Ext/Cam" && ApplyCamera + && (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) + && (message.values[8] is float) + ) + { + 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]; + float fov = (float)message.values[8]; + + //FreeCameraじゃなかったらFreeCameraにする + if (Settings.Current.CameraType != UnityMemoryMappedFile.CameraTypes.Free) + { + CameraManager.Current.ChangeCamera(UnityMemoryMappedFile.CameraTypes.Free); + } + + //カメラ制御を切る + CameraManager.Current.FreeCamera.GetComponent().enabled = false; + + //座標とFOVを適用 + CameraManager.Current.FreeCamera.transform.localPosition = pos; + CameraManager.Current.FreeCamera.transform.localRotation = rot; + CameraManager.Current.ControlCamera.fieldOfView = fov; + } //ブレンドシェープ同期 + else if (message.address == "/VMC/Ext/Blend/Val" && ApplyBlendShape + && (message.values[0] is string) + && (message.values[1] is float) + ) + { + blendShapeBuffer[(string)message.values[0]] = (float)message.values[1]; + } + //ブレンドシェープ適用 + else if (message.address == "/VMC/Ext/Blend/Apply" && ApplyBlendShape) + { + if (DisableBlendShapeReception == true) + { + blendShapeBuffer.Clear(); + } + + faceController.MixPresets(nameof(ExternalReceiverForVMC), blendShapeBuffer.Keys.ToArray(), blendShapeBuffer.Values.ToArray()); + blendShapeBuffer.Clear(); + + }//外部アイトラ V2.3 + else if (message.address == "/VMC/Ext/Set/Eye" && ApplyLookAt + && (message.values[0] is int) + && (message.values[1] is float) + && (message.values[2] is float) + && (message.values[3] is float) + ) + { + bool enable = ((int)message.values[0]) != 0; + pos.x = (float)message.values[1]; + pos.y = (float)message.values[2]; + pos.z = (float)message.values[3]; + + if (enable) + { + //ターゲットが存在しなければ作る + if (lookTargetOSC == null) + { + lookTargetOSC = new GameObject(); + lookTargetOSC.name = "lookTargetOSC"; + } + //位置を書き込む + if (lookTargetOSC.transform != null) + { + lookTargetOSC.transform.parent = headTransform; + lookTargetOSC.transform.localPosition = pos; + } + + //視線に書き込む + if (vrmLookAtHead != null && setFaceApplyAction == false) + { + faceController.BeforeApply += beforeFaceApply; + setFaceApplyAction = true; + } + } + else + { + //視線を止める + if (vrmLookAtHead != null && setFaceApplyAction == true) + { + faceController.BeforeApply -= beforeFaceApply; + setFaceApplyAction = false; + } + } + } + //情報要求 V2.4 + else if (message.address == "/VMC/Ext/Set/Req" && ApplyControl) + { + if (externalSender.isActiveAndEnabled) + { + externalSender.SendPerLowRate(); //即時送信 + } + } + //情報表示 V2.4 + else if (message.address == "/VMC/Ext/Set/Res" && (message.values[0] is string) && ApplyStatus) + { + statusString = (string)message.values[0]; + } + //キャリブレーション準備 V2.5 + else if (message.address == "/VMC/Ext/Set/Calib/Ready" && ApplyControl) + { + if (File.Exists(Settings.Current.VRMPath)) + { + IKManager.Instance.ModelCalibrationInitialize(); + } + } + //キャリブレーション実行 V2.5 + else if (message.address == "/VMC/Ext/Set/Calib/Exec" && (message.values[0] is int) && ApplyControl) + { + PipeCommands.CalibrateType calibrateType = PipeCommands.CalibrateType.Ipose; + + switch ((int)message.values[0]) + { + case 0: + calibrateType = PipeCommands.CalibrateType.Ipose; + break; + case 1: + calibrateType = PipeCommands.CalibrateType.Tpose; + break; + case 2: + calibrateType = PipeCommands.CalibrateType.FixedHandWithGround; + break; + case 3: + calibrateType = PipeCommands.CalibrateType.FixedHand; + break; + default: return; //無視 + } + StartCoroutine(IKManager.Instance.Calibrate(calibrateType)); + Invoke("EndCalibrate", 2f); + } + //設定読み込み V2.5 + else if (message.address == "/VMC/Ext/Set/Config" && (message.values[0] is string && ApplySetting)) + { + string path = (string)message.values[0]; + if (File.Exists(path)) + { + //なぜか時間がかかる + window.LoadSettings(path); + } + } + //スルー情報 V2.6 + else if (message.address != null && message.address.StartsWith("/VMC/Thru/") && ApplyControl) + { + //転送する + if (externalSender.isActiveAndEnabled) + { + externalSender.Send(message.address, message.values); + } + } + //Directional Light V2.9 + else if (message.address == "/VMC/Ext/Light" && ApplyLight + && (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) + && (message.values[8] is float) + && (message.values[9] is float) + && (message.values[10] is float) + && (message.values[11] is float) + ) + { + 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]; + float r = (float)message.values[8]; + float g = (float)message.values[9]; + float b = (float)message.values[10]; + float a = (float)message.values[11]; + + window.MainDirectionalLight.color = new Color(r, g, b, a); + window.MainDirectionalLightTransform.position = pos; + window.MainDirectionalLightTransform.rotation = rot; + } + + //ルートボーン + else 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) + ) + { + 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]; + + HumanBodyBonesTable[boneName] = VirtualAvatar.HumanBodyBonesRoot; + HumanBodyBonesPositionTable[VirtualAvatar.HumanBodyBonesRoot] = pos; + HumanBodyBonesRotationTable[VirtualAvatar.HumanBodyBonesRoot] = rot; + } + //ボーン姿勢 + 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)) + { + //あれば位置と回転をキャッシュする + HumanBodyBonesPositionTable[bone] = pos; + HumanBodyBonesRotationTable[bone] = rot; + + // 手以外を受信したとき + if (!(bone == HumanBodyBones.LeftHand || + bone == HumanBodyBones.RightHand || + (bone >= HumanBodyBones.LeftThumbProximal && + bone <= HumanBodyBones.RightLittleDistal))) + { + enableLocalHandFix = false; + lastBoneReceivedTime = Time.realtimeSinceStartup; + } + } + + //受信と更新のタイミングは切り離した + } + + //ボーン姿勢 + else if (message.address == "/VMC/Ext/OK" + && (message.values[0] is int) + ) + { + int loaded = (int)message.values[0]; + if (message.values.Length > 2) + { + int calibrationState = (int)message.values[1]; + int calibrationMode = (int)message.values[2]; + + if (calibrationState != lastCalibrationState && calibrationState == 3) + { + doCalibration = true; + } + lastCalibrationState = calibrationState; + } + + } + //ショートカット操作 V3.1 + else if (message.address == "/VMC/Ext/Set/Shortcut" && (message.values[0] is string)) + { + string shortcut = (string)message.values[0]; + + if (shortcut.StartsWith("Functions.")) { + Functions func; + if (Enum.TryParse(shortcut.Replace("Functions.", ""), out func)) { + KeyAction action = new KeyAction + { + FunctionAction = true, + Function = func, + }; + window.DoKeyAction(action); + } + } + } + } + } + + SteamVR_Utils.RigidTransform SetTransform(ref Vector3 pos, ref Quaternion rot, ref uOSC.Message message) + { + 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]; + return new SteamVR_Utils.RigidTransform(pos, rot); + } + + public static float filterStrength = 10.0f; + + private void Update() + { + if (receiverSetting == null) return; + + while (MessageBuffer.Count > 0 && MessageBuffer.Peek().timestamp + (float)receiverSetting.DelayMs / 1000f < Time.realtimeSinceStartup) + { + ProcessMessage(MessageBuffer.Dequeue().message); + } + + lock (LockObject) + { + foreach (var pair in virtualHmd) + { + var newpos = Vector3.Lerp(virtualHmdFiltered[pair.Key].pos, pair.Value.pos, filterStrength * Time.deltaTime); + var newrot = Quaternion.Lerp(virtualHmdFiltered[pair.Key].rot, pair.Value.rot, filterStrength * Time.deltaTime); + virtualHmdFiltered[pair.Key] = new SteamVR_Utils.RigidTransform(newpos, newrot); + } + foreach (var pair in virtualController) + { + var newpos = Vector3.Lerp(virtualControllerFiltered[pair.Key].pos, pair.Value.pos, filterStrength * Time.deltaTime); + var newrot = Quaternion.Lerp(virtualControllerFiltered[pair.Key].rot, pair.Value.rot, filterStrength * Time.deltaTime); + virtualControllerFiltered[pair.Key] = new SteamVR_Utils.RigidTransform(newpos, newrot); + } + foreach (var pair in virtualTracker) + { + var newpos = Vector3.Lerp(virtualTrackerFiltered[pair.Key].pos, pair.Value.pos, filterStrength * Time.deltaTime); + var newrot = Quaternion.Lerp(virtualTrackerFiltered[pair.Key].rot, pair.Value.rot, filterStrength * Time.deltaTime); + virtualTrackerFiltered[pair.Key] = new SteamVR_Utils.RigidTransform(newpos, newrot); + } + } + + //更新を検出(あまりに高速な変化に追従しないように) + if (statusString != statusStringOld) + { + statusStringOld = statusString; + + if (StatusStringUpdated != null) + { + StatusStringUpdated.Invoke(statusString); + } + } + } + + // VRIKのボーン情報を取得するためにLateUpdateを使う + private void LateUpdate() + { + if (CorrectRotationWhenCalibration && doCalibration) + { + // 現在のアバターの正面方向回転オフセットを取得 + if (animator != null) + { + var hipBone = animator.GetBoneTransform(HumanBodyBones.Hips); + calibrateRotationOffset = Quaternion.Euler(0, hipBone.rotation.eulerAngles.y, 0); + } + } + doCalibration = false; + + BoneSynchronizeByTable(); + if (lastBoneReceivedTime + 5f < Time.realtimeSinceStartup) + { + enableLocalHandFix = true; + } + } + + + private bool internalActive = false; + + public void SetObjectActive(bool enable) + { + internalActive = enable; + if (enable) + { + var uServer = GetComponent(); + if (uServer.enabled == true) uServer.enabled = false; + if (isPortFree(receivePort)) + { + uServer.enabled = true; + gameObject.SetActive(enable); + } + else + { + Debug.LogError("受信ポートが他のアプリと被っています。変更してください"); + } + } + else + { + var uServer = GetComponent(); + uServer.enabled = false; + gameObject.SetActive(enable); + } + } + + private bool isPortFree(int port) + { + var ipGlobalProp = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties(); + var usedPorts = ipGlobalProp.GetActiveUdpListeners(); + return usedPorts.Any(d => d.Port == port) == false; + } + + public void ChangeOSCPort(int port) + { + receivePort = port; + if (eddp != null) eddp.found = false; + + var uServer = GetComponent(); + uServer.enabled = false; + var type = typeof(uOSC.uOscServer); + var portfield = type.GetField("port", BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Instance); + portfield.SetValue(uServer, port); + if (internalActive == true) + { + SetObjectActive(true); + } + } + + //ボーン位置をキャッシュテーブルに基づいて更新 + 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 (virtualAvatar != null && animator != null && bone != HumanBodyBones.LastBone) + { + Transform targetTransform = virtualAvatar.GetCloneBoneTransform(bone); + Transform tempTransform; + //ボーンによって操作を分ける + if (targetTransform != null) + { + //手首ボーンから先のみ受信した際は + if (receiverSetting.FixHandBone && enableLocalHandFix == true && (bone == HumanBodyBones.LeftHand || bone == HumanBodyBones.RightHand)) + { + //ローカル座標系の回転打ち消し + Quaternion allLocalRotation = Quaternion.identity; + var setRotation = rot; + tempTransform = animator.GetBoneTransform(bone); + var rootTransform = animator.transform; + while (tempTransform != rootTransform) + { + tempTransform = tempTransform.parent; + //後から逆回転をかけて打ち消し + allLocalRotation = allLocalRotation * Quaternion.Inverse(tempTransform.localRotation); + } + allLocalRotation = allLocalRotation * calibrateRotationOffset; + Quaternion receivedRotation = allLocalRotation * rot; + //外部からのボーンへの反映 + BoneSynchronizeSingle(targetTransform, ref bone, ref pos, ref receivedRotation); + } + else + { + BoneSynchronizeSingle(targetTransform, ref bone, ref pos, ref rot); + } + } + } + } + //1本のボーンの同期 + private void BoneSynchronizeSingle(Transform t, ref HumanBodyBones bone, ref Vector3 pos, ref Quaternion rot) + { + if (receiverSetting.UseBonePosition) t.localPosition = pos; + t.localRotation = rot; + virtualAvatar.SetPoseChanged(bone); + } + //ボーン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 + } + } } \ No newline at end of file diff --git a/Assets/Scripts/Setting/Settings.cs b/Assets/Scripts/Setting/Settings.cs index aa983663..d97d5a43 100644 --- a/Assets/Scripts/Setting/Settings.cs +++ b/Assets/Scripts/Setting/Settings.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; @@ -686,6 +686,10 @@ public class Settings [OptionalField] public float PelvisOffsetAdjustZ; + [OptionalField] + public bool FluctuationEnable; + [OptionalField] + public bool AutoLookCameraEnable; //初期値 [OnDeserializing()] @@ -851,6 +855,10 @@ internal void OnDeserializingMethod(StreamingContext context) mocopi_ApplyRightLeg = true; mocopi_ApplyLeftFoot = true; mocopi_ApplyRightFoot = true; + + FluctuationEnable = true; + AutoLookCameraEnable = false; + mocopi_CorrectHipBone = false; EnableOverrideBodyHeight = false; diff --git a/ControlWindowWPF/ControlWindowWPF/FunctionKeyAddWindow.xaml.cs b/ControlWindowWPF/ControlWindowWPF/FunctionKeyAddWindow.xaml.cs index bd38b8e7..2339d8fd 100644 --- a/ControlWindowWPF/ControlWindowWPF/FunctionKeyAddWindow.xaml.cs +++ b/ControlWindowWPF/ControlWindowWPF/FunctionKeyAddWindow.xaml.cs @@ -36,6 +36,12 @@ public partial class FunctionKeyAddWindow : Window LanguageSelector.Get("Functions_PauseTracking"), LanguageSelector.Get("Functions_ShowCalibrationWindow"), LanguageSelector.Get("Functions_ShowPhotoWindow"), + LanguageSelector.Get("Functions_FixedGazeControlCamera"), + LanguageSelector.Get("Functions_FixedGazeControlAhead"), + LanguageSelector.Get("Functions_FixedGazeControlFront"), + LanguageSelector.Get("Functions_FixedGazeControlOff"), + LanguageSelector.Get("Functions_AutoLookCamerOn"), + LanguageSelector.Get("Functions_AutoLookCamerOff"), }; public FunctionKeyAddWindow() { diff --git a/ControlWindowWPF/ControlWindowWPF/MainWindow.xaml b/ControlWindowWPF/ControlWindowWPF/MainWindow.xaml index f60aa00d..20c3d4b4 100644 --- a/ControlWindowWPF/ControlWindowWPF/MainWindow.xaml +++ b/ControlWindowWPF/ControlWindowWPF/MainWindow.xaml @@ -147,6 +147,7 @@ + @@ -174,6 +175,9 @@ + + + diff --git a/ControlWindowWPF/ControlWindowWPF/MainWindow.xaml.cs b/ControlWindowWPF/ControlWindowWPF/MainWindow.xaml.cs index 3bc9c058..612623d1 100644 --- a/ControlWindowWPF/ControlWindowWPF/MainWindow.xaml.cs +++ b/ControlWindowWPF/ControlWindowWPF/MainWindow.xaml.cs @@ -460,6 +460,12 @@ private void Client_Received(object sender, DataReceivedEventArgs e) var d = (PipeCommands.LoadClosingTime)e.Data; LoadSlider(d.time, 100.0f, ClosingTimeSlider, ClosingTimeSlider_ValueChanged); } + else if (e.CommandType == typeof(PipeCommands.SetAutoEyeMovementConfig)) + { + var d = (PipeCommands.SetAutoEyeMovementConfig)e.Data; + SilentChangeChecked(EyeFluctuationCheckBox, d.FluctuationEnable, EyeFluctuationCheckBox_Checked, EyeFluctuationCheckBox_Checked); + SilentChangeChecked(AutoLookCameraCheckBox, d.AutoLookCameraEnable, EyeFluctuationCheckBox_Checked, EyeFluctuationCheckBox_Checked); + } else if (e.CommandType == typeof(PipeCommands.SetLightAngle)) { var d = (PipeCommands.SetLightAngle)e.Data; @@ -936,6 +942,12 @@ private async void ClosingTimeSlider_ValueChanged(object sender, RoutedPropertyC await SliderValueChanged(sender, ClosingTimeTextBlock, 100.0f, new PipeCommands.SetClosingTime(), IsSliderSetting); } + + private async void EyeFluctuationCheckBox_Checked(object sender, RoutedEventArgs e) + { + await Globals.Client.SendCommandAsync(new PipeCommands.SetAutoEyeMovementConfig { FluctuationEnable = EyeFluctuationCheckBox.IsChecked.Value, AutoLookCameraEnable = AutoLookCameraCheckBox.IsChecked.Value }); + } + #endregion private async void LightSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) diff --git a/ControlWindowWPF/ControlWindowWPF/Resources/Chinese.xaml b/ControlWindowWPF/ControlWindowWPF/Resources/Chinese.xaml index cb8bb07b..572737f4 100644 --- a/ControlWindowWPF/ControlWindowWPF/Resources/Chinese.xaml +++ b/ControlWindowWPF/ControlWindowWPF/Resources/Chinese.xaml @@ -108,6 +108,8 @@ 闭眼 睁眼 闭眼时长(秒) + Eye Fluctuation + Auto Look Camera 面部表情控制 打开自动眨眼功能 更换默认表情为其他表情(默认为 NEUTRAL) @@ -122,6 +124,7 @@ 光线角度 纵轴 横轴 + Advanced Graphics Option 还没有 VRM 模型被加载,请先打开模型。 校准失败。 @@ -439,7 +442,13 @@ 暂停追踪 显示校准窗口 显示拍照窗口 - + Fixed Gaze Control: Camera + Fixed Gaze Control: Ahead + Fixed Gaze Control: Front + Fixed Gaze Control: Off + Auto Look Camera: On + Auto Look Camera: Off + 拍照 diff --git a/ControlWindowWPF/ControlWindowWPF/Resources/English.xaml b/ControlWindowWPF/ControlWindowWPF/Resources/English.xaml index 2e4e7783..cf55e158 100644 --- a/ControlWindowWPF/ControlWindowWPF/Resources/English.xaml +++ b/ControlWindowWPF/ControlWindowWPF/Resources/English.xaml @@ -108,6 +108,8 @@ To close To open Closed time (sec) + Eye Fluctuation + Auto Look Camera Facial expression control Enable automatic blinking function Replace the default expression from NEUTRAL with another expression @@ -122,6 +124,7 @@ Light Angle Vertical Horizontal + Advanced Graphics Option VRM model has not been loaded. Please import first. Calibration failed. @@ -439,7 +442,13 @@ Pause Tracking Show Calibration Window Show Photo Window - + Fixed Gaze Control: Camera + Fixed Gaze Control: Ahead + Fixed Gaze Control: Front + Fixed Gaze Control: Off + Auto Look Camera: On + Auto Look Camera: Off + Take Photo diff --git a/ControlWindowWPF/ControlWindowWPF/Resources/Japanese.xaml b/ControlWindowWPF/ControlWindowWPF/Resources/Japanese.xaml index 0f6c192b..e447c26c 100644 --- a/ControlWindowWPF/ControlWindowWPF/Resources/Japanese.xaml +++ b/ControlWindowWPF/ControlWindowWPF/Resources/Japanese.xaml @@ -107,7 +107,9 @@ 閉じ 開き 閉じたままの時間(秒) - 表情制御 + 目の揺れ + 自動カメラ目線 + 表情制御 自動でまばたきする機能を有効にします デフォルトの表情をNEUTRALから別の表情に置き換えます 次にまばたきが発生するまでの最小秒数を指定します。最短から最長までの間の時間でランダムにまばたきします @@ -121,10 +123,12 @@ 照明角度 上下 回転 + 高度な画質設定(ポストプロセッシング) VRMモデルが読み込まれていません。先に読み込んでください。 キャリブレーションに失敗しました。 + ヘルプ 使い方などはWebサイトをご覧ください 説明書 @@ -439,8 +443,14 @@ トラッキング停止切り替え キャリブレーションウィンドウの表示 写真撮影ウィンドウの表示 - - + 固定視線制御: カメラ + 固定視線制御: 前方 + 固定視線制御: 正面 + 固定視線制御: オフ + 自動カメラ目線: オン + 自動カメラ目線: オフ + + 写真撮影 写真撮影 diff --git a/ControlWindowWPF/ControlWindowWPF/Resources/Korean.xaml b/ControlWindowWPF/ControlWindowWPF/Resources/Korean.xaml index 96f7f8de..564fa53b 100644 --- a/ControlWindowWPF/ControlWindowWPF/Resources/Korean.xaml +++ b/ControlWindowWPF/ControlWindowWPF/Resources/Korean.xaml @@ -107,7 +107,9 @@ 감기 뜨기 감은상태의 시간(초) - 표정제어 + Eye Fluctuation + Auto Look Camera + 표정제어 자동으로 눈을 깜빡이는 기능을 활성화합니다 기본표정을 NEUTRAL에서 다른 표정으로 바꿉니다 다음으로 눈을 깜빡일 때까지의 최단시간(단위:초)을 지정합니다. 최단시간부터 최장시간까지의 시간 사이에서 랜덤으로 깜빡입니다 @@ -121,6 +123,7 @@ 조명각도 상하 회전 + Advanced Graphics Option VRM모델을 로드하지 않았습니다. 모델을 먼저 불러와주세요. Calibration failed. @@ -440,8 +443,14 @@ Pause Tracking Show Calibration Window Show Photo Window - - + Fixed Gaze Control: Camera + Fixed Gaze Control: Ahead + Fixed Gaze Control: Front + Fixed Gaze Control: Off + Auto Look Camera: On + Auto Look Camera: Off + + 사진촬영 사진촬영 diff --git a/UnityMemoryMappedFile/PipeCommands.cs b/UnityMemoryMappedFile/PipeCommands.cs index cbdfa65d..5916004e 100644 --- a/UnityMemoryMappedFile/PipeCommands.cs +++ b/UnityMemoryMappedFile/PipeCommands.cs @@ -697,6 +697,13 @@ public class ModSettingEvent public class ShowCalibrationWindow { } public class ShowPhotoWindow { } + public class GetAutoEyeMovementConfig { } + public class SetAutoEyeMovementConfig + { + public bool FluctuationEnable { get; set; } + public bool AutoLookCameraEnable { get; set; } + } + public class VRMLoadStatus { public bool Valid { get; set; } @@ -711,7 +718,6 @@ public class OpenVRStatus { public bool DashboardOpened { get; set; } } - } public class ModItem @@ -1420,6 +1426,12 @@ public enum Functions PauseTracking = 10, ShowCalibrationWindow = 11, ShowPhotoWindow = 12, + FixedGazeControlCamera = 13, + FixedGazeControlAhead = 14, + FixedGazeControlFront = 15, + FixedGazeControlOff = 16, + AutoLookCamerOn = 17, + AutoLookCamerOff = 18, } public enum Hands