diff --git a/Editor/AnimationRigging/FullBodyDeformationConstraintEditor.cs b/Editor/AnimationRigging/FullBodyDeformationConstraintEditor.cs
index 601d1abc..5645f076 100644
--- a/Editor/AnimationRigging/FullBodyDeformationConstraintEditor.cs
+++ b/Editor/AnimationRigging/FullBodyDeformationConstraintEditor.cs
@@ -101,6 +101,7 @@ private static class Content
private SerializedProperty _leftShoulderWeightProperty;
private SerializedProperty _rightShoulderWeightProperty;
+ private SerializedProperty _shoulderRollWeightProperty;
private SerializedProperty _leftArmWeightProperty;
private SerializedProperty _rightArmWeightProperty;
private SerializedProperty _leftHandWeightProperty;
@@ -111,6 +112,9 @@ private static class Content
private SerializedProperty _rightToesWeightProperty;
private SerializedProperty _squashProperty;
private SerializedProperty _stretchProperty;
+ private SerializedProperty _originalSpinePositionsWeight;
+ private SerializedProperty _originalSpineBoneCount;
+ private SerializedProperty _originalSpineUseHipsToHeadToScale;
private SerializedProperty _hipsToHeadBonesProperty;
private SerializedProperty _hipsToHeadBoneTargetsProperty;
@@ -148,6 +152,7 @@ private void OnEnable()
_leftShoulderWeightProperty = data.FindPropertyRelative("_leftShoulderWeight");
_rightShoulderWeightProperty = data.FindPropertyRelative("_rightShoulderWeight");
+ _shoulderRollWeightProperty = data.FindPropertyRelative("_shoulderRollWeight");
_leftArmWeightProperty = data.FindPropertyRelative("_leftArmWeight");
_rightArmWeightProperty = data.FindPropertyRelative("_rightArmWeight");
_leftHandWeightProperty = data.FindPropertyRelative("_leftHandWeight");
@@ -159,6 +164,9 @@ private void OnEnable()
_alignFeetWeightProperty = data.FindPropertyRelative("_alignFeetWeight");
_squashProperty = data.FindPropertyRelative("_squashLimit");
_stretchProperty = data.FindPropertyRelative("_stretchLimit");
+ _originalSpinePositionsWeight = data.FindPropertyRelative("_originalSpinePositionsWeight");
+ _originalSpineBoneCount = data.FindPropertyRelative("_originalSpineBoneCount");
+ _originalSpineUseHipsToHeadToScale = data.FindPropertyRelative("_originalSpineUseHipsToHeadToScale");
_hipsToHeadBonesProperty = data.FindPropertyRelative("_hipsToHeadBones");
_hipsToHeadBoneTargetsProperty = data.FindPropertyRelative("_hipsToHeadBoneTargets");
@@ -292,6 +300,7 @@ private void DisplaySettingsFoldoutContent()
GUILayout.Label(new GUIContent("Body"), EditorStyles.boldLabel);
GUILayout.EndHorizontal();
EditorGUILayout.PropertyField(_spineTranslationCorrectionTypeProperty);
+ EditorGUILayout.PropertyField(_originalSpinePositionsWeight);
EditorGUILayout.PropertyField(_spineLowerAlignmentWeightProperty);
EditorGUILayout.PropertyField(_spineUpperAlignmentWeightProperty);
EditorGUILayout.PropertyField(_chestAlignmentWeightProperty);
@@ -303,6 +312,7 @@ private void DisplaySettingsFoldoutContent()
GUILayout.EndHorizontal();
AverageWeightSlider(Content.ShouldersWeight,
new[] { _leftShoulderWeightProperty, _rightShoulderWeightProperty });
+ EditorGUILayout.PropertyField(_shoulderRollWeightProperty);
EditorGUILayout.Space();
GUILayout.BeginHorizontal();
@@ -356,6 +366,8 @@ private void DisplayAdvancedSettingsFoldoutContent()
}
EditorGUILayout.PropertyField(_squashProperty);
EditorGUILayout.PropertyField(_stretchProperty);
+ EditorGUILayout.PropertyField(_originalSpineBoneCount);
+ EditorGUILayout.PropertyField(_originalSpineUseHipsToHeadToScale);
EditorGUILayout.Space();
GUILayout.BeginHorizontal();
diff --git a/Editor/AnimationRigging/RetargetingLayerEditor.cs b/Editor/AnimationRigging/RetargetingLayerEditor.cs
index 9f3b2212..5a49a8fc 100644
--- a/Editor/AnimationRigging/RetargetingLayerEditor.cs
+++ b/Editor/AnimationRigging/RetargetingLayerEditor.cs
@@ -15,11 +15,22 @@ public class RetargetingLayerEditor : Editor
///
public override void OnInspectorGUI()
{
+ var retargetingLayer = serializedObject.targetObject as RetargetingLayer;
+ var animatorComponent = retargetingLayer.GetComponent();
+
+ if (!IsAnimatorProperlyConfigured(animatorComponent))
+ {
+ GUILayout.BeginHorizontal();
+ EditorGUILayout.HelpBox("Requires Animator component with a humanoid " +
+ "avatar, and Translation DoF enabled in avatar's Muscles & Settings.", MessageType.Error);
+ GUILayout.EndVertical();
+ }
+
base.OnInspectorGUI();
if (GUILayout.Button("Calculate Adjustments"))
{
- var retargetingLayer = serializedObject.targetObject as RetargetingLayer;
+
if (retargetingLayer != null)
{
var animator = retargetingLayer.GetComponent();
@@ -32,5 +43,12 @@ public override void OnInspectorGUI()
serializedObject.ApplyModifiedProperties();
}
+
+ internal static bool IsAnimatorProperlyConfigured(Animator animatorComponent)
+ {
+ return animatorComponent != null && animatorComponent.avatar != null &&
+ animatorComponent.avatar.isHuman &&
+ animatorComponent.avatar.humanDescription.hasTranslationDoF;
+ }
}
}
diff --git a/Editor/BodyTrackingForFitness.meta b/Editor/BodyTrackingForFitness.meta
new file mode 100644
index 00000000..4d263c3f
--- /dev/null
+++ b/Editor/BodyTrackingForFitness.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: c149578ad842f1140bebe960e46dfdf7
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/BodyTrackingForFitness/BodyBoneVisualsEditor.cs b/Editor/BodyTrackingForFitness/BodyBoneVisualsEditor.cs
new file mode 100644
index 00000000..b666e1a1
--- /dev/null
+++ b/Editor/BodyTrackingForFitness/BodyBoneVisualsEditor.cs
@@ -0,0 +1,60 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+using System;
+using Oculus.Movement.Utils;
+using UnityEditor;
+
+namespace Oculus.Movement.BodyTrackingForFitness
+{
+ ///
+ /// Adds button features in the inspector:
+ /// * Connect to
+ /// * Generate a default bone prefab
+ /// * Generate and/or refresh positions of bone visuals childed to bone transforms
+ /// * Delete all bone visuals
+ ///
+ [CustomEditor(typeof(BodyPoseBoneVisuals))]
+ public class BodyBoneVisualsEditor : Editor
+ {
+ private InspectorGuiHelper[] helpers;
+ private InspectorGuiHelper[] Helpers => helpers != null ? helpers : helpers = new InspectorGuiHelper[]
+ {
+ new InspectorGuiHelper(IsSkeletonInvalid, PopulateSkeleton, "Missing target skeleton",
+ "Find Target Skeleton", InspectorGuiHelper.OptionalIcon.Warning),
+ new InspectorGuiHelper(IsBoneVisualEmpty, GenerateBoneVisualPrefab, "Needs bone prefab",
+ "Generate Bone Prefab", InspectorGuiHelper.OptionalIcon.Warning),
+ new InspectorGuiHelper(IsShowingRefreshButton, Refresh, null,
+ "Refresh Bone Visuals", InspectorGuiHelper.OptionalIcon.None),
+ new InspectorGuiHelper(IsPopulatedWithBones, Clear, null,
+ "Clear Bone Visuals", InspectorGuiHelper.OptionalIcon.None),
+ };
+
+ private BodyPoseBoneVisuals Target => (BodyPoseBoneVisuals)target;
+
+ ///
+ public override void OnInspectorGUI()
+ {
+ Array.ForEach(Helpers, helper => helper.DrawInInspector());
+ DrawDefaultInspector();
+ }
+
+ private bool IsShowingRefreshButton() => !IsSkeletonInvalid();
+
+ private bool IsBoneVisualEmpty() => Target.BoneVisualPrefab == null;
+
+ private bool IsSkeletonInvalid() => Target.Skeleton == null;
+
+ private bool IsPopulatedWithBones() => Target.BoneVisuals.Count != 0;
+
+ private void Refresh() => Target.RefreshVisualsInEditor();
+
+ private void GenerateBoneVisualPrefab() => Target.CreatePrimitiveCubeVisualPrefab();
+
+ private void PopulateSkeleton() => Target.Skeleton = FindBestSkeletonOwner();
+
+ private void Clear() => Target.ClearBoneVisuals();
+
+ private BodyPoseBoneTransforms FindBestSkeletonOwner() =>
+ FindFirstObjectByType();
+ }
+}
diff --git a/Editor/BodyTrackingForFitness/BodyBoneVisualsEditor.cs.meta b/Editor/BodyTrackingForFitness/BodyBoneVisualsEditor.cs.meta
new file mode 100644
index 00000000..ffc86548
--- /dev/null
+++ b/Editor/BodyTrackingForFitness/BodyBoneVisualsEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9d72a5372704cb34b8ad84f623e70246
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/BodyTrackingForFitness/BodyPoseAlignmentDetectorConfigDrawer.cs b/Editor/BodyTrackingForFitness/BodyPoseAlignmentDetectorConfigDrawer.cs
new file mode 100644
index 00000000..41767bae
--- /dev/null
+++ b/Editor/BodyTrackingForFitness/BodyPoseAlignmentDetectorConfigDrawer.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+using UnityEditor;
+using UnityEngine;
+
+namespace Oculus.Movement.BodyTrackingForFitness
+{
+ ///
+ /// Used to simplify drawing of a
+ /// element.
+ ///
+ [CustomPropertyDrawer(typeof(BodyPoseAlignmentDetector.AlignmentState))]
+ public class BodyPoseAlignmentDetectorStateEditor : PropertyDrawer
+ {
+ ///
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
+ BoneTupleGUI(position, property, label);
+ }
+
+ ///
+ /// Call from OnGUI to draw a .
+ ///
+ ///
+ ///
+ ///
+ private static void BoneTupleGUI(Rect position, SerializedProperty property,
+ GUIContent label) {
+ SerializedProperty angle = property.FindPropertyRelative(
+ nameof(BodyPoseAlignmentDetector.AlignmentState.AngleDelta));
+ EditorGUI.BeginProperty(position, label, property);
+ const string ElementLabel = "Element ";
+ if (!label.text.StartsWith(ElementLabel))
+ {
+ position = EditorGUI.PrefixLabel(position,
+ GUIUtility.GetControlID(FocusType.Passive), label);
+ }
+ int indent = EditorGUI.indentLevel;
+ EditorGUI.indentLevel = 0;
+ Rect rect = new Rect(position.x, position.y, position.width, position.height);
+ EditorGUI.Slider(rect, "", angle.floatValue, 0, 180);
+ EditorGUI.indentLevel = indent;
+ EditorGUI.EndProperty();
+ }
+ }
+}
diff --git a/Editor/BodyTrackingForFitness/BodyPoseAlignmentDetectorConfigDrawer.cs.meta b/Editor/BodyTrackingForFitness/BodyPoseAlignmentDetectorConfigDrawer.cs.meta
new file mode 100644
index 00000000..ee2ffc0a
--- /dev/null
+++ b/Editor/BodyTrackingForFitness/BodyPoseAlignmentDetectorConfigDrawer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0ea509bba11da8d44bdaf5834ac033e3
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/BodyTrackingForFitness/BodyPoseBoneTransformsEditor.cs b/Editor/BodyTrackingForFitness/BodyPoseBoneTransformsEditor.cs
new file mode 100644
index 00000000..fea78782
--- /dev/null
+++ b/Editor/BodyTrackingForFitness/BodyPoseBoneTransformsEditor.cs
@@ -0,0 +1,60 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+using System;
+using Oculus.Movement.Utils;
+using UnityEditor;
+
+namespace Oculus.Movement.BodyTrackingForFitness
+{
+ ///
+ /// Adds button features in the inspector:
+ /// * Generate and/or refresh positions of bone visuals childed to bone transforms
+ ///
+ [CustomEditor(typeof(BodyPoseBoneTransforms))]
+ public class BodyPoseBoneTransformsEditor : Editor
+ {
+ private InspectorGuiHelper[] _helpers;
+ private InspectorGuiHelper[] Helpers =>
+ _helpers != null ? _helpers : _helpers = new InspectorGuiHelper[]
+ {
+ new InspectorGuiHelper(IsShowingRefreshButton, Refresh, null,
+ "Refresh Transforms", InspectorGuiHelper.OptionalIcon.None),
+ new InspectorGuiHelper(IsAbleToTPose, RefreshTPose, null,
+ "Refresh T-Pose", InspectorGuiHelper.OptionalIcon.None),
+ new InspectorGuiHelper(IsAbleToSave, SaveAsset, null,
+ "Export Asset", InspectorGuiHelper.OptionalIcon.None),
+ };
+
+ private BodyPoseBoneTransforms Target => (BodyPoseBoneTransforms)target;
+
+ ///
+ public override void OnInspectorGUI()
+ {
+ Array.ForEach(Helpers, helper => helper.DrawInInspector());
+ DrawDefaultInspector();
+ }
+
+ private bool IsShowingRefreshButton() => Target.BodyPose != null;
+
+ private bool IsAbleToTPose() => true;
+
+ private bool IsAbleToSave() => Target.BoneContainer != null;
+
+ private void Refresh()
+ {
+ Target.RefreshHierarchyDuringEditor();
+ EditorTransformAwareness.RefreshSystem();
+ EditorBodyPoseLineSkeleton.RefreshSystem();
+ }
+
+ private void RefreshTPose()
+ {
+ Target.RefreshTPose();
+ }
+
+ private void SaveAsset()
+ {
+ BodyPoseControllerEditor.SaveAsset(Target);
+ }
+ }
+}
diff --git a/Editor/BodyTrackingForFitness/BodyPoseBoneTransformsEditor.cs.meta b/Editor/BodyTrackingForFitness/BodyPoseBoneTransformsEditor.cs.meta
new file mode 100644
index 00000000..852d188a
--- /dev/null
+++ b/Editor/BodyTrackingForFitness/BodyPoseBoneTransformsEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b0f63ea977eb8a14088a3b67d11d4a7f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/BodyTrackingForFitness/BodyPoseControllerEditor.cs b/Editor/BodyTrackingForFitness/BodyPoseControllerEditor.cs
new file mode 100644
index 00000000..2dfe9fbe
--- /dev/null
+++ b/Editor/BodyTrackingForFitness/BodyPoseControllerEditor.cs
@@ -0,0 +1,140 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+using System;
+using System.IO;
+using Oculus.Interaction.Body.Input;
+using Oculus.Interaction.Body.PoseDetection;
+using Oculus.Movement.Utils;
+using UnityEditor;
+using UnityEngine;
+
+namespace Oculus.Movement.BodyTrackingForFitness
+{
+ ///
+ /// Adds button features in the inspector:
+ /// * Refresh bone pose data from the controller's data source
+ /// * Refresh bone pose data from the static T-Pose
+ /// * Export bone pose data to a new ScriptableObject in the Assets folder
+ ///
+ [CustomEditor(typeof(BodyPoseController))]
+ public class BodyPoseControllerEditor : Editor
+ {
+ ///
+ /// Internal wrapper for treating as , for
+ /// serialization.
+ ///
+ private class IBodyWrapperForIBodyPose : IBody
+ {
+ public IBodyPose _iBodyPose;
+
+ public event Action WhenBodyUpdated = delegate { };
+
+ public ISkeletonMapping SkeletonMapping => new FullBodySkeletonTPose();
+
+ public bool IsConnected => true;
+
+ public bool IsHighConfidence => true;
+
+ public bool IsTrackedDataValid => true;
+
+ public float Scale => 1;
+
+ public int CurrentDataVersion => 1;
+
+ public IBodyWrapperForIBodyPose(IBodyPose iBodyPose) => _iBodyPose = iBodyPose;
+
+ public bool GetRootPose(out Pose pose)
+ {
+ pose = new Pose(Vector3.zero, Quaternion.identity);
+ return true;
+ }
+
+ public bool GetJointPose(BodyJointId bodyJointId, out Pose pose) =>
+ _iBodyPose.GetJointPoseFromRoot(bodyJointId, out pose);
+
+ public bool GetJointPoseLocal(BodyJointId bodyJointId, out Pose pose) =>
+ _iBodyPose.GetJointPoseLocal(bodyJointId, out pose);
+
+ public bool GetJointPoseFromRoot(BodyJointId bodyJointId, out Pose pose) =>
+ _iBodyPose.GetJointPoseFromRoot(bodyJointId, out pose);
+ }
+
+ private const string POSE_DIRECTORY = "BodyPoses";
+
+ private InspectorGuiHelper[] helpers;
+
+ private InspectorGuiHelper[] Helpers => helpers != null
+ ? helpers
+ : helpers = new InspectorGuiHelper[]
+ {
+ new InspectorGuiHelper(IsAbleToRefreshSource, RefreshSource, null,
+ "Refresh Source Data", InspectorGuiHelper.OptionalIcon.None),
+ new InspectorGuiHelper(IsAbleToTPose, RefreshTPose, null,
+ "Refresh T-Pose", InspectorGuiHelper.OptionalIcon.None),
+ new InspectorGuiHelper(IsAbleToSave, SaveAsset, null,
+ "Export Asset", InspectorGuiHelper.OptionalIcon.None),
+ };
+
+ private BodyPoseController Target => (BodyPoseController)target;
+
+ ///
+ public override void OnInspectorGUI()
+ {
+ DrawDefaultInspector();
+ Array.ForEach(Helpers, helper => helper.DrawInInspector());
+ }
+
+ private bool IsAbleToSave() => Target.BonePoses.Length != 0;
+
+ private bool IsAbleToTPose() => true;
+
+ private bool IsAbleToRefreshSource() => Target.BodyPose != null;
+
+ private void SaveAsset()
+ {
+ SaveAsset(Target);
+ }
+
+ internal static void SaveAsset(IBodyPose data)
+ {
+ string name = $"BodyPose-{System.DateTime.Now.ToString("yyyyMMdd-HHmmss")}";
+ SaveAsset(data, name);
+ }
+
+ internal static void SaveAsset(IBodyPose data, string assetName)
+ {
+ BodyPoseData assetToWrite = GeneratePoseAsset(assetName);
+ assetToWrite.SetBodyPose(new IBodyWrapperForIBodyPose(data));
+ string path = AssetDatabase.GetAssetPath(assetToWrite);
+ Debug.Log($"Captured Body Pose into {AssetDatabase.GetAssetPath(assetToWrite)}");
+ EditorUtility.SetDirty(assetToWrite);
+ AssetDatabase.SaveAssetIfDirty(assetToWrite);
+ AssetDatabase.Refresh();
+ EditorGUIUtility.PingObject(assetToWrite);
+ }
+
+ private static BodyPoseData GeneratePoseAsset(string name)
+ {
+ var poseDataAsset = ScriptableObject.CreateInstance();
+ string parentDir = Path.Combine("Assets", POSE_DIRECTORY);
+ if (!Directory.Exists(parentDir))
+ {
+ Directory.CreateDirectory(parentDir);
+ }
+ AssetDatabase.CreateAsset(poseDataAsset, Path.Combine(parentDir, $"{name}.asset"));
+ return poseDataAsset;
+ }
+
+ private void RefreshTPose()
+ {
+ Target.RefreshTPose();
+ }
+
+ private void RefreshSource()
+ {
+ Target.RefreshFromSourceData();
+ EditorTransformAwareness.RefreshSystem();
+ EditorBodyPoseLineSkeleton.RefreshSystem();
+ }
+ }
+}
diff --git a/Editor/BodyTrackingForFitness/BodyPoseControllerEditor.cs.meta b/Editor/BodyTrackingForFitness/BodyPoseControllerEditor.cs.meta
new file mode 100644
index 00000000..e99fcaf4
--- /dev/null
+++ b/Editor/BodyTrackingForFitness/BodyPoseControllerEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8a65d218c6fa98748a30802e76ff2e74
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/BodyTrackingForFitness/EditorBodyPoseLineSkeleton.cs b/Editor/BodyTrackingForFitness/EditorBodyPoseLineSkeleton.cs
new file mode 100644
index 00000000..b0b2b9e0
--- /dev/null
+++ b/Editor/BodyTrackingForFitness/EditorBodyPoseLineSkeleton.cs
@@ -0,0 +1,408 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+using System.Collections.Generic;
+using Oculus.Interaction.Body.Input;
+using Oculus.Interaction.Body.PoseDetection;
+using UnityEditor;
+using UnityEditor.SceneManagement;
+using UnityEngine;
+
+namespace Oculus.Movement.BodyTrackingForFitness
+{
+ ///
+ /// Uses to automatically connect
+ /// &
+ /// scripts at editor time, also drawing a line visualization in the editor
+ /// of the most relevant on a GameObject.
+ ///
+ public class EditorBodyPoseLineSkeleton
+ {
+ ///
+ /// Private singleton so some editor time functionality can be static.
+ ///
+ private static EditorBodyPoseLineSkeleton _instance;
+
+ ///
+ /// Any GameObject with a script could draw a skeleton
+ ///
+ private Dictionary _drawList =
+ new Dictionary();
+
+ /// [InitializeOnLoadMethod]
will trigger the static constructor
+ [InitializeOnLoadMethod]
+ static void OnProjectLoadedInEditor() { }
+
+ ///
+ /// Look for objects with components, add them
+ /// to a list of poses to draw, and update when transforms change
+ /// with
+ /// .
+ ///
+ static EditorBodyPoseLineSkeleton()
+ {
+ RefreshSystem();
+ }
+
+ ///
+ /// Re-initializes the system
+ ///
+ public static void RefreshSystem()
+ {
+ _instance = new EditorBodyPoseLineSkeleton();
+ SceneView.duringSceneGui -= OnGui;
+ SceneView.duringSceneGui += OnGui;
+ EditorSceneManager.sceneOpened -= OnScene;
+ EditorSceneManager.sceneOpened += OnScene;
+ ObjectChangeEvents.changesPublished -= OnChange;
+ ObjectChangeEvents.changesPublished += OnChange;
+ EditorApplication.delayCall += _instance.BecomeAwareOfInitialIBodyPoseObjects;
+ }
+
+ private static void OnGui(SceneView sceneView) => _instance.DrawIBodyPoseObjects(sceneView);
+ private static void OnScene(UnityEngine.SceneManagement.Scene scene, OpenSceneMode mode) =>
+ RefreshSystem();
+ private static void OnChange(ref ObjectChangeEventStream stream) =>
+ _instance.UpdateChangedIBodyPoseObjects(ref stream);
+
+ private void BecomeAwareOfInitialIBodyPoseObjects()
+ {
+ GameObject[] list = Object.FindObjectsByType(FindObjectsSortMode.None);
+ foreach (var item in list)
+ {
+ UpdateIBodyPoseUiOn(item);
+ }
+ }
+
+ private bool UpdateIBodyPoseUiOn(GameObject gameObject)
+ {
+ bool change = UpdateBodyPoseSkeletonToDraw(gameObject);
+ change |= UpdateTransformAwareness(gameObject);
+ return change;
+ }
+
+ private bool UpdateBodyPoseSkeletonToDraw(GameObject gameObject)
+ {
+ IBodyPose pose = GetMostConsequentialBodyPose(gameObject);
+ if (pose != null)
+ {
+ _drawList[gameObject] = pose;
+ return true;
+ }
+ _drawList.Remove(gameObject);
+ return false;
+ }
+
+ private static IBodyPose GetMostConsequentialBodyPose(GameObject gameObject)
+ {
+ IBodyPose[] bodyPoses = gameObject.GetComponents();
+ if (bodyPoses == null || bodyPoses.Length == 0)
+ {
+ return null;
+ }
+ IBodyPose bestCandidate = bodyPoses[0];
+ bool candidateIsTransient = IsTransientBodyPose(bestCandidate);
+ for (int i = 1; i < bodyPoses.Length && candidateIsTransient; ++i)
+ {
+ IBodyPose pose = bodyPoses[i];
+ bool isTransient = IsTransientBodyPose(pose);
+ if (!isTransient)
+ {
+ bestCandidate = pose;
+ candidateIsTransient = false;
+ }
+ }
+ return bestCandidate;
+ }
+
+ ///
+ /// Check if the data source refers to other poses, or to real data accessed at runtime.
+ ///
+ private static bool IsTransientBodyPose(IBodyPose body)
+ {
+ Component reference = null;
+ switch (body)
+ {
+ case OVRBodyPose:
+ return true;
+ case BodyPoseBoneTransforms skeleton:
+ reference = skeleton.BodyPose as Component;
+ break;
+ case BodyPoseController controller:
+ reference = controller.BodyPose as Component;
+ break;
+ }
+ return reference != null && !(reference is IBody);
+ }
+
+ private bool UpdateTransformAwareness(GameObject gameObject)
+ {
+ bool change = false;
+ BodyPoseController[] controllers = gameObject.GetComponents();
+ foreach (var controller in controllers)
+ {
+ BodyPoseBoneTransforms skeleton = controller.SkeletonTransforms;
+ if (skeleton != null)
+ {
+ EditorTransformAwareness.SetBoneListener(controller, skeleton.OwnsBone, Notify);
+ change = true;
+ }
+ }
+ BodyPoseBoneTransforms[] skeletons = gameObject.GetComponents();
+ foreach (var skeleton in skeletons)
+ {
+ EditorTransformAwareness.SetBoneListener(skeleton, skeleton.OwnsBone, Notify);
+ change = true;
+ }
+ return change;
+ }
+
+ private static void Notify(Transform movedBone)
+ {
+ IEnumerable