diff --git a/Editor/AnimationRigging/CaptureAnimationConstraintEditor.cs b/Editor/AnimationRigging/CaptureAnimationConstraintEditor.cs
new file mode 100644
index 00000000..30932f63
--- /dev/null
+++ b/Editor/AnimationRigging/CaptureAnimationConstraintEditor.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+using UnityEditor;
+using UnityEngine;
+
+namespace Oculus.Movement.AnimationRigging
+{
+ ///
+ /// Custom editor for the capture animation constraint.
+ ///
+ [CustomEditor(typeof(CaptureAnimationConstraint)), CanEditMultipleObjects]
+ public class CaptureAnimationConstraintEditor : Editor
+ {
+ ///
+ public override void OnInspectorGUI()
+ {
+ var constraint = (CaptureAnimationConstraint)target;
+ ICaptureAnimationData constraintData = constraint.data;
+ if (constraintData.ConstraintAnimator == null)
+ {
+ if (GUILayout.Button("Find Animator"))
+ {
+ Undo.RecordObject(constraint, "Find Animator");
+ var animator = constraint.GetComponentInParent();
+ constraint.data.AssignAnimator(animator);
+ }
+ }
+
+ if (constraintData.CurrentPose == null ||
+ constraintData.ReferencePose == null ||
+ constraintData.CurrentPose.Length < (int)HumanBodyBones.LastBone ||
+ constraintData.ReferencePose.Length < (int)HumanBodyBones.LastBone)
+ {
+ if (GUILayout.Button("Setup pose arrays"))
+ {
+ Undo.RecordObject(constraint, "Setup pose arrays");
+ constraint.data.SetupPoseArrays();
+ }
+ }
+
+ GUILayout.Space(EditorGUIUtility.standardVerticalSpacing);
+
+ DrawDefaultInspector();
+ }
+ }
+}
diff --git a/Editor/AnimationRigging/CaptureAnimationConstraintEditor.cs.meta b/Editor/AnimationRigging/CaptureAnimationConstraintEditor.cs.meta
new file mode 100644
index 00000000..0d92470f
--- /dev/null
+++ b/Editor/AnimationRigging/CaptureAnimationConstraintEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: bd79122f455b2774d96a37b354227f9e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/AnimationRigging/DeformationConstraintEditor.cs b/Editor/AnimationRigging/DeformationConstraintEditor.cs
index 5cc75607..3e6481fd 100644
--- a/Editor/AnimationRigging/DeformationConstraintEditor.cs
+++ b/Editor/AnimationRigging/DeformationConstraintEditor.cs
@@ -41,7 +41,17 @@ public override void OnInspectorGUI()
if (GUILayout.Button("Find Hips To Head Bones"))
{
Undo.RecordObject(constraint, "Find Hips To Head Bones");
- constraint.data.SetUpHipsAndHeadBones();
+ constraint.data.SetUpHipsToHeadBones();
+ EditorUtility.SetDirty(target);
+ }
+ }
+ if (constraintData.HipsToHeadBoneTargets == null ||
+ constraintData.HipsToHeadBoneTargets.Length == 0)
+ {
+ if (GUILayout.Button("Find Hips To Head Bone Targets"))
+ {
+ Undo.RecordObject(constraint, "Find Hips To Head Bone Targets");
+ constraint.data.SetUpHipsToHeadBoneTargets(constraint.transform);
EditorUtility.SetDirty(target);
}
}
diff --git a/Editor/AnimationRigging/GroundingConstraintEditor.cs b/Editor/AnimationRigging/GroundingConstraintEditor.cs
index 356fa2f3..5a441c32 100644
--- a/Editor/AnimationRigging/GroundingConstraintEditor.cs
+++ b/Editor/AnimationRigging/GroundingConstraintEditor.cs
@@ -57,17 +57,20 @@ private void FindHips(GroundingConstraint constraint)
{
IGroundingData groundingData = constraint.data;
Transform hipsTransform = null;
+ bool isFullSkeleton = groundingData.ConstraintSkeleton.GetSkeletonType() == OVRSkeleton.SkeletonType.FullBody;
+
if (groundingData.ConstraintSkeleton != null)
{
hipsTransform = RiggingUtilities.FindBoneTransformFromCustomSkeleton(
groundingData.ConstraintSkeleton,
- OVRSkeleton.BoneId.Body_Hips);
+ isFullSkeleton ? OVRSkeleton.BoneId.FullBody_Hips : OVRSkeleton.BoneId.Body_Hips);
}
else
{
hipsTransform = RiggingUtilities.FindBoneTransformAnimator(
groundingData.ConstraintAnimator,
- OVRSkeleton.BoneId.Body_Hips);
+ isFullSkeleton ? OVRSkeleton.BoneId.FullBody_Hips : OVRSkeleton.BoneId.Body_Hips,
+ groundingData.ConstraintAnimator.GetComponent().GetSkeletonType() == OVRSkeleton.SkeletonType.FullBody);
}
if (hipsTransform == null)
{
diff --git a/Editor/AnimationRigging/HipPinningConstraintEditor.cs b/Editor/AnimationRigging/HipPinningConstraintEditor.cs
index 381096a1..58f4eb3e 100644
--- a/Editor/AnimationRigging/HipPinningConstraintEditor.cs
+++ b/Editor/AnimationRigging/HipPinningConstraintEditor.cs
@@ -16,16 +16,44 @@ public override void OnInspectorGUI()
{
var constraint = (HipPinningConstraint)target;
IHipPinningData constraintData = constraint.data;
- if (constraintData.ConstraintSkeleton == null)
+ if (constraintData.ConstraintSkeleton == null &&
+ constraintData.AnimatorComponent == null)
{
if (GUILayout.Button("Find OVR Skeleton"))
{
Undo.RecordObject(constraint, "Find OVR Skeleton");
var skeleton = constraint.GetComponentInParent();
constraint.data.AssignOVRSkeleton(skeleton);
+ EditorUtility.SetDirty(target);
+ }
+ if (GUILayout.Button("Find Animator"))
+ {
+ Undo.RecordObject(constraint, "Find Animator");
+ var animatorComp = constraint.GetComponentInParent();
+ constraint.data.AssignAnimator(animatorComp);
+ EditorUtility.SetDirty(target);
+ }
+ }
+ else if (!constraintData.ObtainedProperReferences)
+ {
+ if (GUILayout.Button("Set up data"))
+ {
+ Undo.RecordObject(constraint, "Set up data");
+ constraint.data.SetUpBoneReferences();
+ EditorUtility.SetDirty(target);
}
- GUILayout.Space(EditorGUIUtility.standardVerticalSpacing);
}
+ else if (constraintData.ObtainedProperReferences)
+ {
+ if (GUILayout.Button("Clear data"))
+ {
+ Undo.RecordObject(constraint, "Clear data");
+ constraint.data.ClearSetupReferences();
+ EditorUtility.SetDirty(target);
+ }
+ }
+
+ GUILayout.Space(EditorGUIUtility.standardVerticalSpacing);
DrawDefaultInspector();
}
}
diff --git a/Editor/Meta.Movement.Editor.asmdef b/Editor/Meta.Movement.Editor.asmdef
index c670b7d1..98afd220 100644
--- a/Editor/Meta.Movement.Editor.asmdef
+++ b/Editor/Meta.Movement.Editor.asmdef
@@ -3,6 +3,7 @@
"rootNamespace": "",
"references": [
"GUID:502e28d01b3ebaf4689df96d8655467c",
+ "GUID:2a230cb87a1d3ba4a98bdc0ddae76e6c",
"GUID:f64c9ebcd7899c3448a08dc9f9ddbe30",
"GUID:d2761a0af0f567748a72629d4bb18a26",
"GUID:7305c54a43f3814439df347c7519653e",
diff --git a/Editor/ThirdParty.meta b/Editor/ThirdParty.meta
new file mode 100644
index 00000000..6fd5d01c
--- /dev/null
+++ b/Editor/ThirdParty.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: fe9323a1eb39ae74dba006263149a27d
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/ThirdParty/LICENSE.txt b/Editor/ThirdParty/LICENSE.txt
new file mode 100644
index 00000000..eccd949f
--- /dev/null
+++ b/Editor/ThirdParty/LICENSE.txt
@@ -0,0 +1,4 @@
+// Developed by Tom Kail at Inkle
+// Released under the MIT Licence as held at https://opensource.org/licenses/MIT
+//
+// From https://gist.github.com/tomkail/ba4136e6aa990f4dc94e0d39ec6a058c
diff --git a/Editor/ThirdParty/LICENSE.txt.meta b/Editor/ThirdParty/LICENSE.txt.meta
new file mode 100644
index 00000000..b45c8390
--- /dev/null
+++ b/Editor/ThirdParty/LICENSE.txt.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: d3302a9841dc2ea4bbef8422ef32827c
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/ThirdParty/RetargetingProcessorScriptableObjectDrawer.cs b/Editor/ThirdParty/RetargetingProcessorScriptableObjectDrawer.cs
new file mode 100644
index 00000000..7ec462e4
--- /dev/null
+++ b/Editor/ThirdParty/RetargetingProcessorScriptableObjectDrawer.cs
@@ -0,0 +1,453 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+// Developed by Tom Kail at Inkle
+// Released under the MIT Licence as held at https://opensource.org/licenses/MIT
+
+// Must be placed within a folder named "Editor"
+// From https://gist.github.com/tomkail/ba4136e6aa990f4dc94e0d39ec6a058c
+
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEditor;
+
+namespace Oculus.Movement.AnimationRigging
+{
+ ///
+ /// Extends how RetargetingProcessorScriptableObject object references are displayed in the inspector
+ /// Shows you all values under the object reference
+ /// Also provides a button to create a new ScriptableObject if property is null.
+ ///
+ [CustomPropertyDrawer(typeof(RetargetingProcessor), true)]
+ public class RetargetingProcessorScriptableObjectDrawer : PropertyDrawer
+ {
+ private const int buttonWidth = 66;
+ private static readonly List ignoreClassFullNames = new List { "TMPro.TMP_FontAsset" };
+
+ ///
+ public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
+ {
+ float totalHeight = EditorGUIUtility.singleLineHeight;
+ if (property.objectReferenceValue == null || !AreAnySubPropertiesVisible(property))
+ {
+ return totalHeight;
+ }
+
+ if (property.isExpanded)
+ {
+ var data = property.objectReferenceValue as ScriptableObject;
+ if (data == null) return EditorGUIUtility.singleLineHeight;
+ SerializedObject serializedObject = new SerializedObject(data);
+ SerializedProperty prop = serializedObject.GetIterator();
+ if (prop.NextVisible(true))
+ {
+ do
+ {
+ if (prop.name == "m_Script")
+ {
+ continue;
+ }
+ var subProp = serializedObject.FindProperty(prop.name);
+ float height = EditorGUI.GetPropertyHeight(subProp, null, true) +
+ EditorGUIUtility.standardVerticalSpacing;
+ totalHeight += height;
+ } while (prop.NextVisible(false));
+ }
+
+ // Add a tiny bit of height if open for the background
+ totalHeight += EditorGUIUtility.standardVerticalSpacing;
+ serializedObject.Dispose();
+ }
+
+ return totalHeight;
+ }
+
+ ///
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
+ EditorGUI.BeginProperty(position, label, property);
+ var type = GetFieldType();
+
+ if (type == null || ignoreClassFullNames.Contains(type.FullName))
+ {
+ EditorGUI.PropertyField(position, property, label);
+ EditorGUI.EndProperty();
+ return;
+ }
+
+ ScriptableObject propertySO = null;
+ if (!property.hasMultipleDifferentValues && property.serializedObject.targetObject != null &&
+ property.serializedObject.targetObject is ScriptableObject)
+ {
+ propertySO = (ScriptableObject)property.serializedObject.targetObject;
+ }
+
+ var propertyRect = Rect.zero;
+ var guiContent = new GUIContent(property.displayName);
+ var foldoutRect = new Rect(position.x, position.y, EditorGUIUtility.labelWidth,
+ EditorGUIUtility.singleLineHeight);
+ if (property.objectReferenceValue != null && AreAnySubPropertiesVisible(property))
+ {
+ property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, guiContent, true);
+ }
+ else
+ {
+ // So yeah having a foldout look like a label is a weird hack
+ // but both code paths seem to need to be a foldout or
+ // the object field control goes weird when the codepath changes.
+ // I guess because foldout is an interactable control of its own and throws off the controlID?
+ foldoutRect.x += 12;
+ EditorGUI.Foldout(foldoutRect, property.isExpanded, guiContent, true, EditorStyles.label);
+ }
+
+ var indentedPosition = EditorGUI.IndentedRect(position);
+ var indentOffset = indentedPosition.x - position.x;
+ var saveToAssets = false;
+ var propertyWidth = buttonWidth;
+ propertyRect = new Rect(position.x + (EditorGUIUtility.labelWidth - indentOffset), position.y,
+ position.width - (EditorGUIUtility.labelWidth - indentOffset), EditorGUIUtility.singleLineHeight);
+
+ if (property.propertyType == SerializedPropertyType.ObjectReference &&
+ property.objectReferenceValue != null)
+ {
+ var data = (ScriptableObject)property.objectReferenceValue;
+ if (!AssetDatabase.Contains(data))
+ {
+ saveToAssets = true;
+ propertyWidth = Mathf.RoundToInt(buttonWidth * 1.5f);
+ }
+ }
+
+ if (propertySO != null || property.objectReferenceValue == null || saveToAssets)
+ {
+ propertyRect.width -= propertyWidth;
+ }
+
+ EditorGUI.ObjectField(propertyRect, property, type, GUIContent.none);
+ if (GUI.changed)
+ {
+ property.serializedObject.ApplyModifiedProperties();
+ }
+
+ var buttonRect = new Rect(position.x + position.width - propertyWidth, position.y, propertyWidth,
+ EditorGUIUtility.singleLineHeight);
+
+ if (property.propertyType == SerializedPropertyType.ObjectReference && property.objectReferenceValue != null)
+ {
+ var data = (ScriptableObject)property.objectReferenceValue;
+
+ if (saveToAssets)
+ {
+ if (GUI.Button(buttonRect, "Save to Assets"))
+ {
+ string selectedAssetPath = "Assets";
+ if (property.serializedObject.targetObject is MonoBehaviour)
+ {
+ MonoScript ms = MonoScript.FromMonoBehaviour((MonoBehaviour)property.serializedObject.targetObject);
+ selectedAssetPath = System.IO.Path.GetDirectoryName(AssetDatabase.GetAssetPath(ms));
+ }
+
+ var replacementObject = ReplaceAssetWithSavePrompt(data.GetType(), selectedAssetPath, data as RetargetingProcessor);
+ if (replacementObject != null)
+ {
+ Undo.DestroyObjectImmediate(data);
+ property.objectReferenceValue = replacementObject;
+ }
+ property.serializedObject.ApplyModifiedProperties();
+ GUIUtility.ExitGUI();
+ return;
+ }
+ }
+
+ if (property.isExpanded)
+ {
+ // Draw a background that shows us clearly which fields are part of the ScriptableObject
+ GUI.Box(
+ new Rect(0,
+ position.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing - 1,
+ Screen.width,
+ position.height - EditorGUIUtility.singleLineHeight - EditorGUIUtility.standardVerticalSpacing),
+ "");
+
+ EditorGUI.indentLevel++;
+ SerializedObject serializedObject = new SerializedObject(data);
+
+ // Iterate over all the values and draw them
+ SerializedProperty prop = serializedObject.GetIterator();
+ float y = position.y + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
+ if (prop.NextVisible(true))
+ {
+ do
+ {
+ // Don't bother drawing the class file
+ if (prop.name == "m_Script")
+ {
+ continue;
+ }
+ float height = EditorGUI.GetPropertyHeight(prop, new GUIContent(prop.displayName), true);
+ EditorGUI.PropertyField(new Rect(position.x, y, position.width - buttonWidth, height), prop,
+ true);
+ y += height + EditorGUIUtility.standardVerticalSpacing;
+ } while (prop.NextVisible(false));
+ }
+
+ if (GUI.changed)
+ {
+ serializedObject.ApplyModifiedProperties();
+ }
+ serializedObject.Dispose();
+ EditorGUI.indentLevel--;
+ }
+ }
+ else
+ {
+ if (GUI.Button(buttonRect, "Create"))
+ {
+ string selectedAssetPath = "Assets";
+ if (property.serializedObject.targetObject is MonoBehaviour)
+ {
+ MonoScript ms = MonoScript.FromMonoBehaviour((MonoBehaviour)property.serializedObject.targetObject);
+ selectedAssetPath = System.IO.Path.GetDirectoryName(AssetDatabase.GetAssetPath(ms));
+ }
+
+ property.objectReferenceValue = CreateAssetWithSavePrompt(type, selectedAssetPath);
+ property.serializedObject.ApplyModifiedProperties();
+ GUIUtility.ExitGUI();
+ return;
+ }
+ }
+
+ property.serializedObject.ApplyModifiedProperties();
+ EditorGUI.EndProperty();
+ }
+
+ private static T _GUILayout(string label, T objectReferenceValue, ref bool isExpanded) where T : ScriptableObject
+ {
+ return _GUILayout(new GUIContent(label), objectReferenceValue, ref isExpanded);
+ }
+
+ private static T _GUILayout(GUIContent label, T objectReferenceValue, ref bool isExpanded)
+ where T : ScriptableObject
+ {
+ Rect position = EditorGUILayout.BeginVertical();
+
+ var propertyRect = Rect.zero;
+ var guiContent = label;
+ var foldoutRect = new Rect(position.x, position.y, EditorGUIUtility.labelWidth,
+ EditorGUIUtility.singleLineHeight);
+ if (objectReferenceValue != null)
+ {
+ isExpanded = EditorGUI.Foldout(foldoutRect, isExpanded, guiContent, true);
+
+ var indentedPosition = EditorGUI.IndentedRect(position);
+ var indentOffset = indentedPosition.x - position.x;
+ propertyRect = new Rect(position.x + EditorGUIUtility.labelWidth - indentOffset, position.y,
+ position.width - EditorGUIUtility.labelWidth - indentOffset, EditorGUIUtility.singleLineHeight);
+ }
+ else
+ {
+ // So yeah having a foldout look like a label is a weird hack
+ // but both code paths seem to need to be a foldout or
+ // the object field control goes weird when the codepath changes.
+ // I guess because foldout is an interactable control of its own and throws off the controlID?
+ foldoutRect.x += 12;
+ EditorGUI.Foldout(foldoutRect, isExpanded, guiContent, true, EditorStyles.label);
+
+ var indentedPosition = EditorGUI.IndentedRect(position);
+ var indentOffset = indentedPosition.x - position.x;
+ propertyRect = new Rect(position.x + EditorGUIUtility.labelWidth - indentOffset, position.y,
+ position.width - EditorGUIUtility.labelWidth - indentOffset - 60, EditorGUIUtility.singleLineHeight);
+ }
+
+ EditorGUILayout.BeginHorizontal();
+ objectReferenceValue =
+ EditorGUILayout.ObjectField(new GUIContent(" "), objectReferenceValue, typeof(T), false) as T;
+
+ if (objectReferenceValue != null)
+ {
+ EditorGUILayout.EndHorizontal();
+ if (isExpanded)
+ {
+ DrawScriptableObjectChildFields(objectReferenceValue);
+ }
+ }
+ else
+ {
+ if (GUILayout.Button("Create", GUILayout.Width(buttonWidth)))
+ {
+ string selectedAssetPath = "Assets";
+ var newAsset = CreateAssetWithSavePrompt(typeof(T), selectedAssetPath);
+ if (newAsset != null)
+ {
+ objectReferenceValue = (T)newAsset;
+ }
+ }
+
+ EditorGUILayout.EndHorizontal();
+ }
+
+ EditorGUILayout.EndVertical();
+ return objectReferenceValue;
+ }
+
+ private static void DrawScriptableObjectChildFields(T objectReferenceValue) where T : ScriptableObject
+ {
+ // Draw a background that shows us clearly which fields are part of the ScriptableObject
+ EditorGUI.indentLevel++;
+ EditorGUILayout.BeginVertical(GUI.skin.box);
+
+ var serializedObject = new SerializedObject(objectReferenceValue);
+ // Iterate over all the values and draw them
+ SerializedProperty prop = serializedObject.GetIterator();
+ if (prop.NextVisible(true))
+ {
+ do
+ {
+ // Don't bother drawing the class file
+ if (prop.name == "m_Script") continue;
+ EditorGUILayout.PropertyField(prop, true);
+ } while (prop.NextVisible(false));
+ }
+
+ if (GUI.changed)
+ {
+ serializedObject.ApplyModifiedProperties();
+ }
+ serializedObject.Dispose();
+ EditorGUILayout.EndVertical();
+ EditorGUI.indentLevel--;
+ }
+
+ private static T DrawScriptableObjectField(GUIContent label, T objectReferenceValue, ref bool isExpanded)
+ where T : ScriptableObject
+ {
+ Rect position = EditorGUILayout.BeginVertical();
+
+ var propertyRect = Rect.zero;
+ var guiContent = label;
+ var foldoutRect = new Rect(position.x, position.y, EditorGUIUtility.labelWidth,
+ EditorGUIUtility.singleLineHeight);
+ if (objectReferenceValue != null)
+ {
+ isExpanded = EditorGUI.Foldout(foldoutRect, isExpanded, guiContent, true);
+
+ var indentedPosition = EditorGUI.IndentedRect(position);
+ var indentOffset = indentedPosition.x - position.x;
+ propertyRect = new Rect(position.x + EditorGUIUtility.labelWidth - indentOffset, position.y,
+ position.width - EditorGUIUtility.labelWidth - indentOffset, EditorGUIUtility.singleLineHeight);
+ }
+ else
+ {
+ // So yeah having a foldout look like a label is a weird hack
+ // but both code paths seem to need to be a foldout or
+ // the object field control goes weird when the codepath changes.
+ // I guess because foldout is an interactable control of its own and throws off the controlID?
+ foldoutRect.x += 12;
+ EditorGUI.Foldout(foldoutRect, isExpanded, guiContent, true, EditorStyles.label);
+
+ var indentedPosition = EditorGUI.IndentedRect(position);
+ var indentOffset = indentedPosition.x - position.x;
+ propertyRect = new Rect(position.x + EditorGUIUtility.labelWidth - indentOffset, position.y,
+ position.width - EditorGUIUtility.labelWidth - indentOffset - 60, EditorGUIUtility.singleLineHeight);
+ }
+
+ EditorGUILayout.BeginHorizontal();
+ objectReferenceValue =
+ EditorGUILayout.ObjectField(new GUIContent(" "), objectReferenceValue, typeof(T), false) as T;
+
+ if (objectReferenceValue != null)
+ {
+ EditorGUILayout.EndHorizontal();
+ if (isExpanded)
+ {
+ }
+ }
+ else
+ {
+ if (GUILayout.Button("Create", GUILayout.Width(buttonWidth)))
+ {
+ string selectedAssetPath = "Assets";
+ var newAsset = CreateAssetWithSavePrompt(typeof(T), selectedAssetPath);
+ if (newAsset != null)
+ {
+ objectReferenceValue = (T)newAsset;
+ }
+ }
+
+ EditorGUILayout.EndHorizontal();
+ }
+
+ EditorGUILayout.EndVertical();
+ return objectReferenceValue;
+ }
+
+ // Creates a new ScriptableObject via the default Save File panel
+ private static ScriptableObject CreateAssetWithSavePrompt(Type type, string path)
+ {
+ path = EditorUtility.SaveFilePanelInProject("Save ScriptableObject", type.Name + ".asset", "asset",
+ "Enter a file name for the ScriptableObject.", path);
+ if (path == "")
+ {
+ return null;
+ }
+ ScriptableObject asset = ScriptableObject.CreateInstance(type);
+ AssetDatabase.CreateAsset(asset, path);
+ AssetDatabase.SaveAssets();
+ AssetDatabase.Refresh();
+ AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
+ EditorGUIUtility.PingObject(asset);
+ return asset;
+ }
+
+ private static ScriptableObject ReplaceAssetWithSavePrompt(Type type, string path, RetargetingProcessor processor)
+ {
+ path = EditorUtility.SaveFilePanelInProject("Save ScriptableObject", processor.name + ".asset", "asset",
+ "Enter a file name for the ScriptableObject.", path);
+ if (path == "")
+ {
+ return null;
+ }
+ ScriptableObject asset = ScriptableObject.CreateInstance(type);
+ var newProcessor = asset as RetargetingProcessor;
+ if (newProcessor != null)
+ {
+ newProcessor.CopyData(processor);
+ }
+ AssetDatabase.CreateAsset(asset, path);
+ AssetDatabase.SaveAssets();
+ AssetDatabase.Refresh();
+ AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
+ EditorGUIUtility.PingObject(asset);
+ return asset;
+ }
+
+ private Type GetFieldType()
+ {
+ Type type = fieldInfo.FieldType;
+ if (type.IsArray)
+ {
+ type = type.GetElementType();
+ }
+ else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
+ {
+ type = type.GetGenericArguments()[0];
+ }
+ return type;
+ }
+
+ private static bool AreAnySubPropertiesVisible(SerializedProperty property)
+ {
+ var data = (ScriptableObject)property.objectReferenceValue;
+ SerializedObject serializedObject = new SerializedObject(data);
+ SerializedProperty prop = serializedObject.GetIterator();
+ while (prop.NextVisible(true))
+ {
+ if (prop.name == "m_Script") continue;
+ return true; //if theres any visible property other than m_script
+ }
+
+ serializedObject.Dispose();
+ return false;
+ }
+ }
+}
diff --git a/Editor/ThirdParty/RetargetingProcessorScriptableObjectDrawer.cs.meta b/Editor/ThirdParty/RetargetingProcessorScriptableObjectDrawer.cs.meta
new file mode 100644
index 00000000..a31ff463
--- /dev/null
+++ b/Editor/ThirdParty/RetargetingProcessorScriptableObjectDrawer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2ca1d7cc685c52247a9138b6cf9e1540
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Tracking/CorrectivesFaceEditor.cs b/Editor/Tracking/CorrectivesFaceEditor.cs
index fd82ff04..b1e1602e 100644
--- a/Editor/Tracking/CorrectivesFaceEditor.cs
+++ b/Editor/Tracking/CorrectivesFaceEditor.cs
@@ -13,12 +13,18 @@ public class CorrectivesFaceEditor : OVRCustomFaceEditor
{
private SerializedProperty _blendshapeModifier;
private SerializedProperty _combinationShapesTextAsset;
+ private SerializedProperty _forceJawDropForTongue;
+ private SerializedProperty _tongueOutThreshold;
+ private SerializedProperty _minJawDrop;
protected override void OnEnable()
{
base.OnEnable();
_blendshapeModifier = serializedObject.FindProperty("_blendshapeModifier");
_combinationShapesTextAsset = serializedObject.FindProperty("_combinationShapesTextAsset");
+ _forceJawDropForTongue = serializedObject.FindProperty("_forceJawDropForTongue");
+ _tongueOutThreshold = serializedObject.FindProperty("_tongueOutThreshold");
+ _minJawDrop = serializedObject.FindProperty("_minJawDrop");
}
///
@@ -29,9 +35,16 @@ public override void OnInspectorGUI()
serializedObject.Update();
EditorGUILayout.ObjectField(_blendshapeModifier,
- new GUIContent("Blend shape modifiers (optional)"));
+ new GUIContent("Blendshape Modifiers (Optional)"));
EditorGUILayout.ObjectField(_combinationShapesTextAsset,
- new GUIContent("Combination shapes text (Optional)"));
+ new GUIContent("Combination Shapes Text (Optional)"));
+
+ EditorGUILayout.PropertyField(_forceJawDropForTongue);
+ if (_forceJawDropForTongue.boolValue)
+ {
+ EditorGUILayout.PropertyField(_tongueOutThreshold);
+ EditorGUILayout.PropertyField(_minJawDrop);
+ }
serializedObject.ApplyModifiedProperties();
}
diff --git a/Editor/Utils/FullBodyHelperMenus.cs b/Editor/Utils/FullBodyHelperMenus.cs
new file mode 100644
index 00000000..ddb1bd01
--- /dev/null
+++ b/Editor/Utils/FullBodyHelperMenus.cs
@@ -0,0 +1,562 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+using Oculus.Interaction.Input;
+using Oculus.Movement.AnimationRigging;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using UnityEditor;
+using UnityEngine;
+using UnityEngine.Animations.Rigging;
+using static Oculus.Movement.AnimationRigging.RetargetedBoneTargets;
+using static OVRUnityHumanoidSkeletonRetargeter;
+
+namespace Oculus.Movement.Utils
+{
+ ///
+ /// Provides useful menus to help one set up tracking technologies
+ /// on characters.
+ ///
+ internal static class FullBodyHelperMenus
+ {
+ private const string _MOVEMENT_SAMPLES_MENU =
+ "GameObject/Movement Samples/";
+ private const string _MOVEMENT_SAMPLES_BT_MENU =
+ "Body Tracking/";
+
+ private const string _ANIM_RIGGING_RETARGETING_FULL_BODY_MENU_CONSTRAINTS =
+ "Animation Rigging Retargeting (full body) (constraints)";
+
+ [MenuItem(_MOVEMENT_SAMPLES_MENU + _MOVEMENT_SAMPLES_BT_MENU + _ANIM_RIGGING_RETARGETING_FULL_BODY_MENU_CONSTRAINTS)]
+ private static void SetupCharacterForAnimationRiggingRetargetingConstraints()
+ {
+ var activeGameObject = Selection.activeGameObject;
+
+ try
+ {
+ ValidGameObjectForAnimationRigging(activeGameObject);
+ }
+ catch (InvalidOperationException e)
+ {
+ EditorUtility.DisplayDialog("Retargeting setup error.", e.Message, "Ok");
+ return;
+ }
+
+ Undo.IncrementCurrentGroup();
+
+ // Add the retargeting and body tracking components at root first.
+ RetargetingLayer retargetingLayer = AddMainRetargetingComponents(activeGameObject);
+
+ GameObject rigObject;
+ RigBuilder rigBuilder;
+ (rigBuilder, rigObject) = AddBasicAnimationRiggingComponents(activeGameObject);
+
+ List constraintMonos = new List();
+ RetargetingAnimationConstraint retargetConstraint =
+ AddRetargetingConstraint(rigObject, retargetingLayer);
+ retargetingLayer.RetargetingConstraint = retargetConstraint;
+ constraintMonos.Add(retargetConstraint);
+
+ Animator animatorComp = activeGameObject.GetComponent();
+
+ // Destroy old components
+ DestroyBoneTarget(rigObject, "LeftHandTarget");
+ DestroyBoneTarget(rigObject, "RightHandTarget");
+ DestroyBoneTarget(rigObject, "LeftElbowTarget");
+ DestroyBoneTarget(rigObject, "RightElbowTarget");
+ DestroyTwoBoneIKConstraint(rigObject, "LeftArmIK");
+ DestroyTwoBoneIKConstraint(rigObject, "RightArmIK");
+
+ // Full body deformation.
+ RetargetedBoneTarget[] spineBoneTargets = AddSpineBoneTargets(rigObject, animatorComp);
+ FullBodyDeformationConstraint deformationConstraint =
+ AddDeformationConstraint(rigObject, animatorComp, spineBoneTargets);
+ constraintMonos.Add(deformationConstraint);
+
+ // Setup retargeted bone targets.
+ AddRetargetedBoneTargetComponent(activeGameObject, spineBoneTargets);
+
+ // Hand blend constraints.
+ AddHandBlendConstraint(activeGameObject, null,
+ retargetingLayer, OVRHumanBodyBonesMappings.FullBodyTrackingBoneId.FullBody_LeftHandWrist,
+ animatorComp.GetBoneTransform(HumanBodyBones.Head));
+
+ AddHandBlendConstraint(activeGameObject, null,
+ retargetingLayer, OVRHumanBodyBonesMappings.FullBodyTrackingBoneId.FullBody_RightHandWrist,
+ animatorComp.GetBoneTransform(HumanBodyBones.Head));
+
+ // Add final components to tie everything together.
+ AddAnimationRiggingLayer(activeGameObject, retargetingLayer, rigBuilder,
+ constraintMonos.ToArray(), retargetingLayer);
+
+ // Add retargeting processors to the retargeting layer.
+ AddCorrectBonesRetargetingProcessor(retargetingLayer);
+ AddCorrectHandRetargetingProcessor(retargetingLayer, Handedness.Left);
+ AddCorrectHandRetargetingProcessor(retargetingLayer, Handedness.Right);
+
+ // Add full body hand deformation.
+ AddFullBodyHandDeformation(activeGameObject, animatorComp, retargetingLayer);
+
+ Undo.SetCurrentGroupName("Setup Animation Rigging Retargeting");
+ }
+ private static void AddCorrectBonesRetargetingProcessor(RetargetingLayer retargetingLayer)
+ {
+ bool needCorrectBones = true;
+ foreach (var processor in retargetingLayer.RetargetingProcessors)
+ {
+ if (processor as RetargetingProcessorCorrectBones != null)
+ {
+ needCorrectBones = false;
+ }
+ }
+
+ if (needCorrectBones)
+ {
+ var retargetingProcessorCorrectBones = ScriptableObject.CreateInstance();
+ Undo.RegisterCreatedObjectUndo(retargetingProcessorCorrectBones, "Create correct bones retargeting processor.");
+ Undo.RecordObject(retargetingLayer, "Add retargeting processor to retargeting layer.");
+ retargetingProcessorCorrectBones.name = "CorrectBones";
+ retargetingLayer.AddRetargetingProcessor(retargetingProcessorCorrectBones);
+ }
+ }
+
+ private static void AddCorrectHandRetargetingProcessor(RetargetingLayer retargetingLayer, Handedness handedness)
+ {
+ bool needCorrectHand = true;
+ foreach (var processor in retargetingLayer.RetargetingProcessors)
+ {
+ var correctHand = processor as RetargetingProcessorCorrectHand;
+ if (correctHand != null)
+ {
+ if (correctHand.Handedness == handedness)
+ {
+ needCorrectHand = false;
+ }
+ }
+ }
+
+ if (needCorrectHand)
+ {
+ var retargetingProcessorCorrectHand = ScriptableObject.CreateInstance();
+ var handednessString = handedness == Handedness.Left ? "Left" : "Right";
+ Undo.RegisterCreatedObjectUndo(retargetingProcessorCorrectHand, $"Create correct hand ({handednessString}) retargeting processor.");
+ Undo.RecordObject(retargetingLayer, "Add retargeting processor to retargeting layer.");
+ retargetingProcessorCorrectHand.Handedness = handedness;
+ retargetingProcessorCorrectHand.HandIKType = RetargetingProcessorCorrectHand.IKType.CCDIK;
+ retargetingProcessorCorrectHand.name = $"Correct{handednessString}Hand";
+ retargetingLayer.AddRetargetingProcessor(retargetingProcessorCorrectHand);
+ }
+ }
+
+ private static RetargetedBoneTarget[] AddSpineBoneTargets(GameObject rigObject, Animator animator)
+ {
+ var boneTargets = new List();
+ Transform hipsTarget = AddSpineTarget(rigObject, "HipsTarget",
+ animator.GetBoneTransform(HumanBodyBones.Hips));
+ Transform spineLowerTarget = AddSpineTarget(rigObject, "SpineLowerTarget",
+ animator.GetBoneTransform(HumanBodyBones.Spine));
+ Transform spineUpperTarget = AddSpineTarget(rigObject, "SpineUpperTarget",
+ animator.GetBoneTransform(HumanBodyBones.Chest));
+ Transform chestTarget = AddSpineTarget(rigObject, "ChestTarget",
+ animator.GetBoneTransform(HumanBodyBones.UpperChest));
+ Transform neckTarget = AddSpineTarget(rigObject, "NeckTarget",
+ animator.GetBoneTransform(HumanBodyBones.Neck));
+ Transform headTarget = AddSpineTarget(rigObject, "HeadTarget",
+ animator.GetBoneTransform(HumanBodyBones.Head));
+
+ Tuple[] bonesToRetarget =
+ {
+ new(OVRSkeleton.BoneId.FullBody_Hips, hipsTarget),
+ new(OVRSkeleton.BoneId.FullBody_SpineLower, spineLowerTarget),
+ new(OVRSkeleton.BoneId.FullBody_SpineUpper, spineUpperTarget),
+ new(OVRSkeleton.BoneId.FullBody_Chest, chestTarget),
+ new(OVRSkeleton.BoneId.FullBody_Neck, neckTarget),
+ new(OVRSkeleton.BoneId.FullBody_Head, headTarget),
+ };
+
+ foreach (var boneToRetarget in bonesToRetarget)
+ {
+ RetargetedBoneTarget boneRTTarget = new RetargetedBoneTarget();
+ boneRTTarget.BoneId = boneToRetarget.Item1;
+ boneRTTarget.Target = boneToRetarget.Item2;
+ boneRTTarget.HumanBodyBone = OVRHumanBodyBonesMappings.FullBodyBoneIdToHumanBodyBone[boneRTTarget.BoneId];
+ boneTargets.Add(boneRTTarget);
+ }
+ return boneTargets.ToArray();
+ }
+
+ private static RetargetingLayer AddMainRetargetingComponents(GameObject mainParent)
+ {
+ RetargetingLayer retargetingLayer = mainParent.GetComponent();
+
+ // Check for old retargeting layer first.
+ RetargetingLayer baseRetargetingLayer = mainParent.GetComponent();
+ if (retargetingLayer == null && baseRetargetingLayer != null)
+ {
+ Undo.DestroyObjectImmediate(baseRetargetingLayer);
+ }
+
+ if (!retargetingLayer)
+ {
+ retargetingLayer = mainParent.AddComponent();
+ Undo.RegisterCreatedObjectUndo(retargetingLayer, "Add Retargeting Layer");
+ }
+
+ var bodySectionToPosition =
+ typeof(OVRUnityHumanoidSkeletonRetargeter).GetField(
+ "_fullBodySectionToPosition",
+ BindingFlags.Instance | BindingFlags.NonPublic);
+
+ if (bodySectionToPosition != null)
+ {
+ bodySectionToPosition.SetValue(retargetingLayer, new[]
+ {
+ OVRHumanBodyBonesMappings.BodySection.LeftArm,
+ OVRHumanBodyBonesMappings.BodySection.RightArm,
+ OVRHumanBodyBonesMappings.BodySection.LeftHand,
+ OVRHumanBodyBonesMappings.BodySection.RightHand,
+ OVRHumanBodyBonesMappings.BodySection.Hips,
+ OVRHumanBodyBonesMappings.BodySection.Back,
+ OVRHumanBodyBonesMappings.BodySection.Neck,
+ OVRHumanBodyBonesMappings.BodySection.Head,
+ OVRHumanBodyBonesMappings.BodySection.LeftLeg,
+ OVRHumanBodyBonesMappings.BodySection.LeftFoot,
+ OVRHumanBodyBonesMappings.BodySection.RightLeg,
+ OVRHumanBodyBonesMappings.BodySection.RightFoot
+ });
+ }
+
+ OVRBody bodyComp = mainParent.GetComponent();
+ if (!bodyComp)
+ {
+ bodyComp = mainParent.AddComponent();
+ Undo.RegisterCreatedObjectUndo(bodyComp, "Add OVRBody component");
+ }
+
+ typeof(RetargetingLayer).GetField(
+ "_skeletonType", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(
+ retargetingLayer, OVRSkeleton.SkeletonType.FullBody);
+ typeof(OVRBody).GetField(
+ "_providedSkeletonType", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(
+ bodyComp, OVRPlugin.BodyJointSet.FullBody);
+
+ retargetingLayer.EnableTrackingByProxy = true;
+ PrefabUtility.RecordPrefabInstancePropertyModifications(retargetingLayer);
+ PrefabUtility.RecordPrefabInstancePropertyModifications(bodyComp);
+
+ return retargetingLayer;
+ }
+
+ private static (RigBuilder, GameObject) AddBasicAnimationRiggingComponents(GameObject mainParent)
+ {
+ Rig rigComponent = mainParent.GetComponentInChildren();
+ if (!rigComponent)
+ {
+ // Create rig for constraints.
+ GameObject rigObject = new GameObject("Rig");
+ rigComponent = rigObject.AddComponent();
+ rigComponent.weight = 1.0f;
+ Undo.RegisterCreatedObjectUndo(rigObject, "Create Rig");
+ }
+
+ RigBuilder rigBuilder = mainParent.GetComponent();
+ if (!rigBuilder)
+ {
+ rigBuilder = mainParent.AddComponent();
+ rigBuilder.layers = new System.Collections.Generic.List
+ {
+ new RigLayer(rigComponent, true)
+ };
+ Undo.RegisterCreatedObjectUndo(rigBuilder, "Create RigBuilder");
+ }
+
+ Undo.SetTransformParent(rigComponent.transform, mainParent.transform, "Add Rig to Main Parent");
+ Undo.RegisterCompleteObjectUndo(rigComponent, "Rig Component Transform init");
+ rigComponent.transform.localPosition = Vector3.zero;
+ rigComponent.transform.localRotation = Quaternion.identity;
+ rigComponent.transform.localScale = Vector3.one;
+
+ return (rigBuilder, rigComponent.gameObject);
+ }
+
+ private static RetargetingAnimationConstraint AddRetargetingConstraint(
+ GameObject rigObject, RetargetingLayer retargetingLayer)
+ {
+ RetargetingAnimationConstraint retargetConstraint =
+ rigObject.GetComponentInChildren(true);
+ if (retargetConstraint == null)
+ {
+ GameObject retargetingAnimConstraintObj =
+ new GameObject("RetargetingConstraint");
+ retargetConstraint =
+ retargetingAnimConstraintObj.AddComponent();
+ Undo.RegisterCreatedObjectUndo(retargetingAnimConstraintObj, "Create Retargeting Constraint");
+
+ Undo.SetTransformParent(retargetingAnimConstraintObj.transform, rigObject.transform, "Add Retarget Constraint to Rig");
+ Undo.RegisterCompleteObjectUndo(retargetingAnimConstraintObj, "Retarget Constraint Transform Init");
+ retargetConstraint.transform.localPosition = Vector3.zero;
+ retargetConstraint.transform.localRotation = Quaternion.identity;
+ retargetConstraint.transform.localScale = Vector3.one;
+
+ // keep retargeter disabled until it initializes properly
+ retargetConstraint.gameObject.SetActive(false);
+ }
+ retargetConstraint.RetargetingLayerComp = retargetingLayer;
+ PrefabUtility.RecordPrefabInstancePropertyModifications(retargetConstraint);
+ return retargetConstraint;
+ }
+
+ private static void AddAnimationRiggingLayer(GameObject mainParent,
+ OVRSkeleton skeletalComponent, RigBuilder rigBuilder,
+ MonoBehaviour[] constraintComponents,
+ RetargetingLayer retargetingLayer)
+ {
+ AnimationRigSetup rigSetup = mainParent.GetComponent();
+ if (rigSetup)
+ {
+ return;
+ }
+ rigSetup = mainParent.AddComponent();
+ rigSetup.Skeleton = skeletalComponent;
+ var animatorComponent = mainParent.GetComponent();
+ rigSetup.AnimatorComp = animatorComponent;
+ rigSetup.RigbuilderComp = rigBuilder;
+ if (constraintComponents != null)
+ {
+ foreach (var constraintComponent in constraintComponents)
+ {
+ rigSetup.AddSkeletalConstraint(constraintComponent);
+ }
+ }
+ rigSetup.RebindAnimator = true;
+ rigSetup.ReEnableRig = true;
+ rigSetup.RetargetingLayerComp = retargetingLayer;
+ rigSetup.CheckSkeletalUpdatesByProxy = true;
+
+ Undo.RegisterCreatedObjectUndo(rigSetup, "Create Anim Rig Setup");
+ }
+
+ private static FullBodyDeformationConstraint AddDeformationConstraint(
+ GameObject rigObject, Animator animator, RetargetedBoneTarget[] spineBoneTargets)
+ {
+ FullBodyDeformationConstraint deformationConstraint =
+ rigObject.GetComponentInChildren();
+ DeformationConstraint baseDeformationConstraint =
+ rigObject.GetComponentInChildren();
+ if (deformationConstraint == null && baseDeformationConstraint != null)
+ {
+ Undo.DestroyObjectImmediate(baseDeformationConstraint.gameObject);
+ }
+ if (deformationConstraint == null)
+ {
+ GameObject deformationConstraintObject =
+ new GameObject("Deformation");
+ deformationConstraint =
+ deformationConstraintObject.AddComponent();
+
+ Undo.RegisterCreatedObjectUndo(deformationConstraintObject, "Create Deformation");
+
+ Undo.SetTransformParent(deformationConstraintObject.transform, rigObject.transform,
+ $"Add Deformation Constraint to Rig");
+ Undo.RegisterCompleteObjectUndo(deformationConstraintObject,
+ $"Deformation Constraint Transform Init");
+ deformationConstraintObject.transform.localPosition = Vector3.zero;
+ deformationConstraintObject.transform.localRotation = Quaternion.identity;
+ deformationConstraintObject.transform.localScale = Vector3.one;
+
+ foreach (var spineBoneTarget in spineBoneTargets)
+ {
+ Undo.SetTransformParent(spineBoneTarget.Target, deformationConstraintObject.transform,
+ $"Parent Spine Bone Target {spineBoneTarget.Target.name} to Deformation.");
+ Undo.RegisterCompleteObjectUndo(spineBoneTarget.Target,
+ $"Spine Bone Target {spineBoneTarget.Target.name} Init");
+ }
+ }
+
+ deformationConstraint.data.SpineTranslationCorrectionTypeField
+ = FullBodyDeformationData.SpineTranslationCorrectionType.AccurateHipsAndHead;
+ deformationConstraint.data.SpineLowerAlignmentWeight = 1.0f;
+ deformationConstraint.data.SpineUpperAlignmentWeight = 0.5f;
+ deformationConstraint.data.ChestAlignmentWeight = 0.0f;
+ deformationConstraint.data.LeftShoulderWeight = 0.75f;
+ deformationConstraint.data.RightShoulderWeight = 0.75f;
+ deformationConstraint.data.LeftArmWeight = 1.0f;
+ deformationConstraint.data.RightArmWeight = 1.0f;
+ deformationConstraint.data.LeftHandWeight = 1.0f;
+ deformationConstraint.data.RightHandWeight = 1.0f;
+ deformationConstraint.data.LeftLegWeight = 1.0f;
+ deformationConstraint.data.RightLegWeight = 1.0f;
+ deformationConstraint.data.LeftToesWeight = 1.0f;
+ deformationConstraint.data.RightToesWeight = 1.0f;
+ deformationConstraint.data.AlignFeetWeight = 0.75f;
+
+ deformationConstraint.data.AssignAnimator(animator);
+ deformationConstraint.data.SetUpLeftArmData();
+ deformationConstraint.data.SetUpRightArmData();
+ deformationConstraint.data.SetUpLeftLegData();
+ deformationConstraint.data.SetUpRightLegData();
+ deformationConstraint.data.SetUpHipsAndHeadBones();
+ deformationConstraint.data.SetUpBonePairs();
+ deformationConstraint.data.SetUpBoneTargets(deformationConstraint.transform);
+ deformationConstraint.data.InitializeStartingScale();
+
+ PrefabUtility.RecordPrefabInstancePropertyModifications(deformationConstraint);
+
+ return deformationConstraint;
+ }
+
+ private static Transform AddSpineTarget(GameObject mainParent,
+ string nameOfTarget, Transform targetTransform = null)
+ {
+ Transform spineTarget =
+ mainParent.transform.FindChildRecursive(nameOfTarget);
+ if (spineTarget == null)
+ {
+ GameObject spineTargetObject =
+ new GameObject(nameOfTarget);
+ Undo.RegisterCreatedObjectUndo(spineTargetObject, "Create Spine Target " + nameOfTarget);
+
+ Undo.SetTransformParent(spineTargetObject.transform, mainParent.transform,
+ $"Add Spine Target {nameOfTarget} To Main Parent");
+ Undo.RegisterCompleteObjectUndo(spineTargetObject,
+ $"Spine Target {nameOfTarget} Transform Init");
+ spineTarget = spineTargetObject.transform;
+ }
+
+ if (targetTransform != null)
+ {
+ spineTarget.position = targetTransform.position;
+ spineTarget.rotation = targetTransform.rotation;
+ spineTarget.localScale = targetTransform.localScale;
+ }
+ else
+ {
+ spineTarget.localPosition = Vector3.zero;
+ spineTarget.localRotation = Quaternion.identity;
+ spineTarget.localScale = Vector3.one;
+ }
+ return spineTarget;
+ }
+
+ private static FullBodyRetargetedBoneTargets AddRetargetedBoneTargetComponent(GameObject mainParent,
+ RetargetedBoneTarget[] boneTargetsArray)
+ {
+ var boneTargets = mainParent.GetComponent();
+ var baseBoneTargets = mainParent.GetComponent();
+ if (boneTargets == null && baseBoneTargets != null)
+ {
+ Undo.DestroyObjectImmediate(baseBoneTargets);
+ }
+ if (boneTargets != null)
+ {
+ boneTargets.AutoAdd = mainParent.GetComponent();
+ boneTargets.RetargetedBoneTargets = boneTargetsArray;
+ PrefabUtility.RecordPrefabInstancePropertyModifications(boneTargets);
+ return boneTargets;
+ }
+ FullBodyRetargetedBoneTargets retargetedBoneTargets =
+ mainParent.AddComponent();
+ Undo.RegisterCreatedObjectUndo(retargetedBoneTargets, "Add RT bone targets");
+
+ retargetedBoneTargets.AutoAdd = mainParent.GetComponent();
+ retargetedBoneTargets.RetargetedBoneTargets = boneTargetsArray;
+
+ return retargetedBoneTargets;
+ }
+
+ private static bool DestroyBoneTarget(GameObject mainParent, string nameOfTarget)
+ {
+ Transform handTarget =
+ mainParent.transform.FindChildRecursive(nameOfTarget);
+ if (handTarget != null)
+ {
+ Undo.DestroyObjectImmediate(handTarget);
+ return true;
+ }
+ return false;
+ }
+
+ private static bool DestroyTwoBoneIKConstraint(GameObject rigObject, string name)
+ {
+ Transform twoBoneIKConstraintObjTransform = rigObject.transform.Find(name);
+ if (twoBoneIKConstraintObjTransform != null)
+ {
+ Undo.DestroyObjectImmediate(twoBoneIKConstraintObjTransform);
+ return true;
+ }
+ return false;
+ }
+
+ private static BlendHandConstraintsFullBody AddHandBlendConstraint(
+ GameObject mainParent, MonoBehaviour[] constraints, RetargetingLayer retargetingLayer,
+ OVRHumanBodyBonesMappings.FullBodyTrackingBoneId boneIdToTest, Transform headTransform)
+ {
+ var blendHandConstraints = mainParent.GetComponents();
+ var baseBlendHandConstraints = mainParent.GetComponents();
+ if (blendHandConstraints.Length == 0 && baseBlendHandConstraints.Length > 0)
+ {
+ for (int i = 0; i < baseBlendHandConstraints.Length; i++)
+ {
+ Undo.DestroyObjectImmediate(baseBlendHandConstraints[i]);
+ }
+ }
+ foreach (var blendHandConstraint in blendHandConstraints)
+ {
+ if (blendHandConstraint.BoneIdToTest == boneIdToTest)
+ {
+ blendHandConstraint.Constraints = null;
+ blendHandConstraint.RetargetingLayerComp = retargetingLayer;
+ blendHandConstraint.BoneIdToTest = boneIdToTest;
+ blendHandConstraint.HeadTransform = headTransform;
+ blendHandConstraint.AutoAddTo = mainParent.GetComponent();
+ blendHandConstraint.BlendCurve = new AnimationCurve(new Keyframe(0, 0), new Keyframe(1, 1));
+ PrefabUtility.RecordPrefabInstancePropertyModifications(blendHandConstraint);
+ return blendHandConstraint;
+ }
+ }
+ BlendHandConstraintsFullBody blendConstraint =
+ mainParent.AddComponent();
+ Undo.RegisterCreatedObjectUndo(blendConstraint, "Add blend constraint");
+
+ blendConstraint.Constraints = null;
+ blendConstraint.RetargetingLayerComp = retargetingLayer;
+ blendConstraint.BoneIdToTest = boneIdToTest;
+ blendConstraint.HeadTransform = headTransform;
+ blendConstraint.AutoAddTo = mainParent.GetComponent();
+ blendConstraint.BlendCurve = new AnimationCurve(new Keyframe(0, 0), new Keyframe(1, 1));
+
+ return blendConstraint;
+ }
+
+ private static void AddFullBodyHandDeformation(GameObject mainParent,
+ Animator animatorComp, OVRSkeleton skeletalComponent)
+ {
+ FullBodyHandDeformation fullBodyHandDeformation =
+ mainParent.GetComponent();
+ if (fullBodyHandDeformation == null)
+ {
+ fullBodyHandDeformation = mainParent.AddComponent();
+ Undo.RegisterCreatedObjectUndo(fullBodyHandDeformation, "Add full body hand deformation");
+ }
+
+ fullBodyHandDeformation.AnimatorComp = animatorComp;
+ fullBodyHandDeformation.Skeleton = skeletalComponent;
+ fullBodyHandDeformation.LeftHand = animatorComp.GetBoneTransform(HumanBodyBones.LeftHand);
+ fullBodyHandDeformation.RightHand = animatorComp.GetBoneTransform(HumanBodyBones.RightHand);
+ fullBodyHandDeformation.FingerOffsets = new FullBodyHandDeformation.FingerOffset[0];
+ fullBodyHandDeformation.CalculateFingerData();
+ }
+
+ private static void ValidGameObjectForAnimationRigging(GameObject go)
+ {
+ var animatorComp = go.GetComponent();
+ if (animatorComp == null || animatorComp.avatar == null
+ || !animatorComp.avatar.isHuman)
+ {
+ throw new InvalidOperationException(
+ $"Animation Rigging requires an {nameof(Animator)} " +
+ $"component with a Humanoid avatar.");
+ }
+ }
+ }
+}
diff --git a/Editor/Utils/FullBodyHelperMenus.cs.meta b/Editor/Utils/FullBodyHelperMenus.cs.meta
new file mode 100644
index 00000000..d09c4cc5
--- /dev/null
+++ b/Editor/Utils/FullBodyHelperMenus.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f8cc61b2ce4347047a85631ddf43bd43
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Utils/HelperMenus.cs b/Editor/Utils/HelperMenus.cs
index 692bf2bf..d6c6498e 100644
--- a/Editor/Utils/HelperMenus.cs
+++ b/Editor/Utils/HelperMenus.cs
@@ -1,14 +1,17 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.
+using Oculus.Interaction.Input;
using Oculus.Movement.AnimationRigging;
using Oculus.Movement.Tracking;
using System;
using System.Collections.Generic;
+using System.Reflection;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Animations.Rigging;
using static Oculus.Movement.AnimationRigging.RetargetedBoneTargets;
+using static OVRUnityHumanoidSkeletonRetargeter;
namespace Oculus.Movement.Utils
{
@@ -37,41 +40,6 @@ internal static class HelperMenus
private const string _NO_DUPLICATES_SUFFIX =
" (duplicate mapping off)";
- [MenuItem(_MOVEMENT_SAMPLES_MENU + _MOVEMENT_SAMPLES_BT_MENU + _ANIM_RIGGING_RETARGETING_MENU)]
- private static void SetupCharacterForAnimationRiggingRetargeting()
- {
- var activeGameObject = Selection.activeGameObject;
-
- try
- {
- ValidGameObjectForAnimationRigging(activeGameObject);
- }
- catch (InvalidOperationException e)
- {
- EditorUtility.DisplayDialog("Retargeting setup error.", e.Message, "Ok");
- return;
- }
-
- Undo.IncrementCurrentGroup();
-
- // Add the retargeting and body tracking components at root first.
- RetargetingLayer retargetingLayer = AddMainRetargetingComponents(activeGameObject);
-
- GameObject rigObject;
- RigBuilder rigBuilder;
- (rigBuilder, rigObject) = AddBasicAnimationRiggingComponents(activeGameObject);
-
- RetargetingAnimationConstraint retargetConstraint =
- AddRetargetingConstraint(rigObject, retargetingLayer);
- retargetingLayer.RetargetingConstraint = retargetConstraint;
-
- // Add final components to tie everything together.
- AddAnimationRiggingLayer(activeGameObject, retargetingLayer, rigBuilder,
- new RetargetingAnimationConstraint[] { retargetConstraint }, retargetingLayer);
-
- Undo.SetCurrentGroupName("Setup Animation Rigging Retargeting");
- }
-
[MenuItem(_MOVEMENT_SAMPLES_MENU + _MOVEMENT_SAMPLES_BT_MENU + _ANIM_RIGGING_RETARGETING_MENU + _CONSTRAINTS_SUFFIX)]
private static void SetupCharacterForAnimationRiggingRetargetingConstraints()
{
@@ -104,52 +72,39 @@ private static void SetupCharacterForAnimationRiggingRetargetingConstraints()
Animator animatorComp = activeGameObject.GetComponent();
+ // Destroy old components
+ DestroyBoneTarget(rigObject, "LeftHandTarget");
+ DestroyBoneTarget(rigObject, "RightHandTarget");
+ DestroyBoneTarget(rigObject, "LeftElbowTarget");
+ DestroyBoneTarget(rigObject, "RightElbowTarget");
+ DestroyTwoBoneIKConstraint(rigObject, "LeftArmIK");
+ DestroyTwoBoneIKConstraint(rigObject, "RightArmIK");
+
+ // Body deformation.
+ RetargetedBoneTarget[] spineBoneTargets = AddSpineBoneTargets(rigObject, animatorComp);
DeformationConstraint deformationConstraint =
- AddDeformationConstraint(rigObject, animatorComp);
+ AddDeformationConstraint(rigObject, animatorComp, spineBoneTargets);
constraintMonos.Add(deformationConstraint);
- Transform leftHandTarget = AddHandTarget(rigObject, "LeftHandTarget");
- TwoBoneIKConstraint leftTwoBoneIKConstraint =
- AddTwoBoneIKConstraint(rigObject, "LeftArmIK",
- animatorComp.GetBoneTransform(HumanBodyBones.LeftUpperArm),
- animatorComp.GetBoneTransform(HumanBodyBones.LeftLowerArm),
- animatorComp.GetBoneTransform(HumanBodyBones.LeftHand),
- leftHandTarget);
-
- Transform rightHandTarget = AddHandTarget(rigObject, "RightHandTarget");
- TwoBoneIKConstraint rightTwoBoneIKConstraint =
- AddTwoBoneIKConstraint(rigObject, "RightArmIK",
- animatorComp.GetBoneTransform(HumanBodyBones.RightUpperArm),
- animatorComp.GetBoneTransform(HumanBodyBones.RightLowerArm),
- animatorComp.GetBoneTransform(HumanBodyBones.RightHand),
- rightHandTarget);
-
- RetargetedBoneTarget leftHandRTTarget = new RetargetedBoneTarget();
- leftHandRTTarget.BoneId = OVRSkeleton.BoneId.Body_LeftHandWrist;
- leftHandRTTarget.Target = leftHandTarget;
- leftHandRTTarget.HumanBodyBone = HumanBodyBones.LeftHand;
- RetargetedBoneTarget rightHandRTTarget = new RetargetedBoneTarget();
- rightHandRTTarget.BoneId = OVRSkeleton.BoneId.Body_RightHandWrist;
- rightHandRTTarget.Target = rightHandTarget;
- rightHandRTTarget.HumanBodyBone = HumanBodyBones.RightHand;
- AddRetargetedBoneTargetComponent(activeGameObject,
- new RetargetedBoneTarget[]
- {
- leftHandRTTarget, rightHandRTTarget
- });
+ AddRetargetedBoneTargetComponent(activeGameObject, spineBoneTargets);
- AddHandBlendConstraint(activeGameObject, new MonoBehaviour[] { leftTwoBoneIKConstraint },
- retargetingLayer, CustomMappings.BodyTrackingBoneId.Body_LeftHandWrist,
+ AddHandBlendConstraint(activeGameObject,
+ retargetingLayer, OVRHumanBodyBonesMappings.BodyTrackingBoneId.Body_LeftHandWrist,
animatorComp.GetBoneTransform(HumanBodyBones.Head));
- AddHandBlendConstraint(activeGameObject, new MonoBehaviour[] { rightTwoBoneIKConstraint },
- retargetingLayer, CustomMappings.BodyTrackingBoneId.Body_RightHandWrist,
+ AddHandBlendConstraint(activeGameObject,
+ retargetingLayer, OVRHumanBodyBonesMappings.BodyTrackingBoneId.Body_RightHandWrist,
animatorComp.GetBoneTransform(HumanBodyBones.Head));
// Add final components to tie everything together.
AddAnimationRiggingLayer(activeGameObject, retargetingLayer, rigBuilder,
constraintMonos.ToArray(), retargetingLayer);
+ // Add retargeting processors to the retargeting layer.
+ AddCorrectBonesRetargetingProcessor(retargetingLayer);
+ AddCorrectHandRetargetingProcessor(retargetingLayer, Handedness.Left);
+ AddCorrectHandRetargetingProcessor(retargetingLayer, Handedness.Right);
+
Undo.SetCurrentGroupName("Setup Animation Rigging Retargeting");
}
@@ -235,6 +190,26 @@ private static RetargetingLayer AddMainRetargetingComponents(GameObject mainPare
retargetingLayer = mainParent.AddComponent();
Undo.RegisterCreatedObjectUndo(retargetingLayer, "Add Retargeting Layer");
}
+ retargetingLayer.EnableTrackingByProxy = true;
+
+ var bodySectionToPosition =
+ typeof(OVRUnityHumanoidSkeletonRetargeter).GetField(
+ "_bodySectionToPosition", BindingFlags.Instance | BindingFlags.NonPublic);
+ if (bodySectionToPosition != null)
+ {
+ bodySectionToPosition.SetValue(retargetingLayer, new[]
+ {
+ OVRUnityHumanoidSkeletonRetargeter.OVRHumanBodyBonesMappings.BodySection.LeftArm,
+ OVRUnityHumanoidSkeletonRetargeter.OVRHumanBodyBonesMappings.BodySection.RightArm,
+ OVRUnityHumanoidSkeletonRetargeter.OVRHumanBodyBonesMappings.BodySection.LeftHand,
+ OVRUnityHumanoidSkeletonRetargeter.OVRHumanBodyBonesMappings.BodySection.RightHand,
+ OVRUnityHumanoidSkeletonRetargeter.OVRHumanBodyBonesMappings.BodySection.Hips,
+ OVRUnityHumanoidSkeletonRetargeter.OVRHumanBodyBonesMappings.BodySection.Back,
+ OVRUnityHumanoidSkeletonRetargeter.OVRHumanBodyBonesMappings.BodySection.Neck,
+ OVRUnityHumanoidSkeletonRetargeter.OVRHumanBodyBonesMappings.BodySection.Head
+ });
+ }
+ EditorUtility.SetDirty(retargetingLayer);
OVRBody bodyComp = mainParent.GetComponent();
if (!bodyComp)
@@ -262,7 +237,7 @@ private static (RigBuilder, GameObject) AddBasicAnimationRiggingComponents(GameO
if (!rigBuilder)
{
rigBuilder = mainParent.AddComponent();
- rigBuilder.layers = new System.Collections.Generic.List
+ rigBuilder.layers = new List
{
new RigLayer(rigComponent, true)
};
@@ -282,7 +257,7 @@ private static RetargetingAnimationConstraint AddRetargetingConstraint(
GameObject rigObject, RetargetingLayer retargetingLayer)
{
RetargetingAnimationConstraint retargetConstraint =
- rigObject.GetComponentInChildren();
+ rigObject.GetComponentInChildren(true);
if (retargetConstraint == null)
{
GameObject retargetingAnimConstraintObj =
@@ -298,10 +273,10 @@ private static RetargetingAnimationConstraint AddRetargetingConstraint(
retargetConstraint.transform.localPosition = Vector3.zero;
retargetConstraint.transform.localRotation = Quaternion.identity;
retargetConstraint.transform.localScale = Vector3.one;
-
- // keep retargeter disabled until it initializes properly
- retargetConstraint.gameObject.SetActive(false);
}
+
+ // Keep retargeter disabled until it initializes properly.
+ retargetConstraint.gameObject.SetActive(false);
return retargetConstraint;
}
@@ -315,9 +290,9 @@ private static void AddAnimationRiggingLayer(GameObject mainParent,
{
return;
}
+ var animatorComponent = mainParent.GetComponent();
rigSetup = mainParent.AddComponent();
rigSetup.Skeleton = skeletalComponent;
- var animatorComponent = mainParent.GetComponent();
rigSetup.AnimatorComp = animatorComponent;
rigSetup.RigbuilderComp = rigBuilder;
if (constraintComponents != null)
@@ -330,11 +305,132 @@ private static void AddAnimationRiggingLayer(GameObject mainParent,
rigSetup.RebindAnimator = true;
rigSetup.ReEnableRig = true;
rigSetup.RetargetingLayerComp = retargetingLayer;
+ rigSetup.CheckSkeletalUpdatesByProxy = true;
Undo.RegisterCreatedObjectUndo(rigSetup, "Create Anim Rig Setup");
}
+ private static void AddCorrectBonesRetargetingProcessor(RetargetingLayer retargetingLayer)
+ {
+ bool needCorrectBones = true;
+ foreach (var processor in retargetingLayer.RetargetingProcessors)
+ {
+ if (processor as RetargetingProcessorCorrectBones != null)
+ {
+ needCorrectBones = false;
+ }
+ }
+
+ if (needCorrectBones)
+ {
+ var retargetingProcessorCorrectBones = ScriptableObject.CreateInstance();
+ Undo.RegisterCreatedObjectUndo(retargetingProcessorCorrectBones, "Create correct bones retargeting processor.");
+ Undo.RecordObject (retargetingLayer, "Add retargeting processor to retargeting layer.");
+ retargetingProcessorCorrectBones.name = "CorrectBones";
+ retargetingLayer.AddRetargetingProcessor(retargetingProcessorCorrectBones);
+ }
+ }
+
+ private static void AddCorrectHandRetargetingProcessor(RetargetingLayer retargetingLayer, Handedness handedness)
+ {
+ bool needCorrectHand = true;
+ foreach (var processor in retargetingLayer.RetargetingProcessors)
+ {
+ var correctHand = processor as RetargetingProcessorCorrectHand;
+ if (correctHand != null)
+ {
+ if (correctHand.Handedness == handedness)
+ {
+ needCorrectHand = false;
+ }
+ }
+ }
+
+ if (needCorrectHand)
+ {
+ var retargetingProcessorCorrectHand = ScriptableObject.CreateInstance();
+ var handednessString = handedness == Handedness.Left ? "Left" : "Right";
+ Undo.RegisterCreatedObjectUndo(retargetingProcessorCorrectHand, $"Create correct hand ({handednessString}) retargeting processor.");
+ Undo.RecordObject (retargetingLayer, "Add retargeting processor to retargeting layer.");
+ retargetingProcessorCorrectHand.Handedness = handedness;
+ retargetingProcessorCorrectHand.HandIKType = RetargetingProcessorCorrectHand.IKType.CCDIK;
+ retargetingProcessorCorrectHand.name = $"Correct{handednessString}Hand";
+ retargetingLayer.AddRetargetingProcessor(retargetingProcessorCorrectHand);
+ }
+ }
+
+ private static RetargetedBoneTarget[] AddSpineBoneTargets(GameObject rigObject,
+ Animator animator)
+ {
+ var boneTargets = new List();
+ Transform hipsTarget = AddSpineTarget(rigObject, "HipsTarget",
+ animator.GetBoneTransform(HumanBodyBones.Hips));
+ Transform spineLowerTarget = AddSpineTarget(rigObject, "SpineLowerTarget",
+ animator.GetBoneTransform(HumanBodyBones.Spine));
+ Transform spineUpperTarget = AddSpineTarget(rigObject, "SpineUpperTarget",
+ animator.GetBoneTransform(HumanBodyBones.Chest));
+ Transform chestTarget = AddSpineTarget(rigObject, "ChestTarget",
+ animator.GetBoneTransform(HumanBodyBones.UpperChest));
+ Transform neckTarget = AddSpineTarget(rigObject, "NeckTarget",
+ animator.GetBoneTransform(HumanBodyBones.Neck));
+ Transform headTarget = AddSpineTarget(rigObject, "HeadTarget",
+ animator.GetBoneTransform(HumanBodyBones.Head));
+
+ Tuple[] bonesToRetarget =
+ {
+ new(OVRSkeleton.BoneId.Body_Hips, hipsTarget),
+ new(OVRSkeleton.BoneId.Body_SpineLower, spineLowerTarget),
+ new(OVRSkeleton.BoneId.Body_SpineUpper, spineUpperTarget),
+ new(OVRSkeleton.BoneId.Body_Chest, chestTarget),
+ new(OVRSkeleton.BoneId.Body_Neck, neckTarget),
+ new(OVRSkeleton.BoneId.Body_Head, headTarget),
+ };
+
+ foreach (var boneToRetarget in bonesToRetarget)
+ {
+ RetargetedBoneTarget boneRTTarget = new RetargetedBoneTarget();
+ boneRTTarget.BoneId = boneToRetarget.Item1;
+ boneRTTarget.Target = boneToRetarget.Item2;
+ boneRTTarget.HumanBodyBone = OVRHumanBodyBonesMappings.BoneIdToHumanBodyBone[boneRTTarget.BoneId];
+ boneTargets.Add(boneRTTarget);
+ }
+ return boneTargets.ToArray();
+ }
+
+ private static Transform AddSpineTarget(GameObject mainParent,
+ string nameOfTarget, Transform targetTransform = null)
+ {
+ Transform spineTarget =
+ mainParent.transform.FindChildRecursive(nameOfTarget);
+ if (spineTarget == null)
+ {
+ GameObject spineTargetObject =
+ new GameObject(nameOfTarget);
+ Undo.RegisterCreatedObjectUndo(spineTargetObject,
+ $"Create Spine Target {nameOfTarget}");
+ Undo.SetTransformParent(spineTargetObject.transform, mainParent.transform,
+ $"Add Spine Target {nameOfTarget} To Main Parent");
+ Undo.RegisterCompleteObjectUndo(spineTargetObject,
+ $"Spine Target {nameOfTarget} Transform Init");
+ spineTarget = spineTargetObject.transform;
+ }
+
+ if (targetTransform != null)
+ {
+ spineTarget.position = targetTransform.position;
+ spineTarget.rotation = targetTransform.rotation;
+ spineTarget.localScale = targetTransform.localScale;
+ }
+ else
+ {
+ spineTarget.localPosition = Vector3.zero;
+ spineTarget.localRotation = Quaternion.identity;
+ spineTarget.localScale = Vector3.one;
+ }
+ return spineTarget;
+ }
+
private static DeformationConstraint AddDeformationConstraint(
- GameObject rigObject, Animator animator)
+ GameObject rigObject, Animator animator, RetargetedBoneTarget[] spineBoneTargets)
{
DeformationConstraint deformationConstraint =
rigObject.GetComponentInChildren();
@@ -346,7 +442,6 @@ private static DeformationConstraint AddDeformationConstraint(
deformationConstraintObject.AddComponent();
Undo.RegisterCreatedObjectUndo(deformationConstraintObject, "Create Deformation");
-
Undo.SetTransformParent(deformationConstraintObject.transform, rigObject.transform,
$"Add Deformation Constraint to Rig");
Undo.RegisterCompleteObjectUndo(deformationConstraintObject,
@@ -354,105 +449,106 @@ private static DeformationConstraint AddDeformationConstraint(
deformationConstraintObject.transform.localPosition = Vector3.zero;
deformationConstraintObject.transform.localRotation = Quaternion.identity;
deformationConstraintObject.transform.localScale = Vector3.one;
+ }
- deformationConstraint.data.SpineTranslationCorrectionTypeField
- = DeformationData.SpineTranslationCorrectionType.SkipHips;
- deformationConstraint.data.ApplyToArms = true;
- deformationConstraint.data.ApplyToHands = true;
- deformationConstraint.data.ArmWeight = 1.0f;
- deformationConstraint.data.HandWeight = 1;
-
- deformationConstraint.data.AssignAnimator(animator);
- deformationConstraint.data.SetUpHipsAndHeadBones();
- deformationConstraint.data.SetUpLeftArmData();
- deformationConstraint.data.SetUpRightArmData();
- deformationConstraint.data.SetUpBonePairs();
- deformationConstraint.data.InitializeStartingScale();
+ foreach (var spineBoneTarget in spineBoneTargets)
+ {
+ Undo.SetTransformParent(spineBoneTarget.Target, deformationConstraint.transform,
+ $"Parent Spine Bone Target {spineBoneTarget.Target.name} to Deformation.");
+ Undo.RegisterCompleteObjectUndo(spineBoneTarget.Target,
+ $"Spine Bone Target {spineBoneTarget.Target.name} Init");
}
+
+ deformationConstraint.data.SpineTranslationCorrectionTypeField
+ = DeformationData.SpineTranslationCorrectionType.AccurateHipsAndHead;
+ deformationConstraint.data.SpineLowerAlignmentWeight = 1.0f;
+ deformationConstraint.data.SpineUpperAlignmentWeight = 0.5f;
+ deformationConstraint.data.ChestAlignmentWeight = 0.0f;
+ deformationConstraint.data.LeftShoulderWeight = 0.75f;
+ deformationConstraint.data.RightShoulderWeight = 0.75f;
+ deformationConstraint.data.LeftArmWeight = 1.0f;
+ deformationConstraint.data.RightArmWeight = 1.0f;
+ deformationConstraint.data.LeftHandWeight = 1.0f;
+ deformationConstraint.data.RightHandWeight = 1.0f;
+
+ deformationConstraint.data.AssignAnimator(animator);
+ deformationConstraint.data.SetUpLeftArmData();
+ deformationConstraint.data.SetUpRightArmData();
+ deformationConstraint.data.SetUpHipsToHeadBones();
+ deformationConstraint.data.SetUpHipsToHeadBoneTargets(deformationConstraint.transform);
+ deformationConstraint.data.SetUpBonePairs();
+ deformationConstraint.data.InitializeStartingScale();
+
+ PrefabUtility.RecordPrefabInstancePropertyModifications(deformationConstraint);
return deformationConstraint;
}
- private static Transform AddHandTarget(GameObject mainParent, string nameOfTarget)
+ private static bool DestroyBoneTarget(GameObject mainParent, string nameOfTarget)
{
Transform handTarget =
- mainParent.transform.Find(nameOfTarget);
- if (handTarget == null)
+ mainParent.transform.FindChildRecursive(nameOfTarget);
+ if (handTarget != null)
{
- GameObject handTargetObject =
- new GameObject(nameOfTarget);
- Undo.RegisterCreatedObjectUndo(handTargetObject, "Create Hand Target " + nameOfTarget);
-
- Undo.SetTransformParent(handTargetObject.transform, mainParent.transform,
- $"Add Hand Target {nameOfTarget} To Main Parent");
- Undo.RegisterCompleteObjectUndo(handTargetObject,
- $"Hand Target {nameOfTarget} Transform Init");
- handTarget = handTargetObject.transform;
- handTarget.transform.localPosition = Vector3.zero;
- handTarget.transform.localRotation = Quaternion.identity;
- handTarget.transform.localScale = Vector3.one;
- }
- return handTarget;
+ Undo.DestroyObjectImmediate(handTarget);
+ return true;
+ }
+ return false;
}
private static RetargetedBoneTargets AddRetargetedBoneTargetComponent(GameObject mainParent,
RetargetedBoneTarget[] boneTargetsArray)
{
- RetargetedBoneTargets retargetedBoneTargets =
- mainParent.AddComponent();
- Undo.RegisterCreatedObjectUndo(retargetedBoneTargets, "Add RT bone targets");
+ RetargetedBoneTargets retargetedBoneTargets = mainParent.GetComponent();
+ if (retargetedBoneTargets == null)
+ {
+ retargetedBoneTargets =
+ mainParent.AddComponent();
+ Undo.RegisterCreatedObjectUndo(retargetedBoneTargets, "Add RT bone targets");
+ }
- retargetedBoneTargets.AutoAddTo = mainParent.GetComponent();
+ retargetedBoneTargets.AutoAddTo = mainParent.GetComponent();
retargetedBoneTargets.RetargetedBoneTargetsArray = boneTargetsArray;
+ PrefabUtility.RecordPrefabInstancePropertyModifications(retargetedBoneTargets);
return retargetedBoneTargets;
}
- private static TwoBoneIKConstraint AddTwoBoneIKConstraint(
- GameObject rigObject, string name, Transform root,
- Transform mid, Transform tip, Transform target)
+ private static bool DestroyTwoBoneIKConstraint(GameObject rigObject, string name)
{
- TwoBoneIKConstraint twoBoneIKConstraint = null;
Transform twoBoneIKConstraintObjTransform = rigObject.transform.Find(name);
- if (twoBoneIKConstraintObjTransform == null)
- {
- GameObject twoBoneIKConstraintObj =
- new GameObject(name);
- twoBoneIKConstraint =
- twoBoneIKConstraintObj.AddComponent();
- twoBoneIKConstraint.data.root = root;
- twoBoneIKConstraint.data.mid = mid;
- twoBoneIKConstraint.data.tip = tip;
- twoBoneIKConstraint.data.target = target;
- twoBoneIKConstraint.data.maintainTargetPositionOffset = false;
- twoBoneIKConstraint.data.maintainTargetRotationOffset = false;
- twoBoneIKConstraint.data.targetRotationWeight = 0.0f;
- twoBoneIKConstraint.data.targetPositionWeight = 1.0f;
- twoBoneIKConstraint.data.hintWeight = 1.0f;
- Undo.RegisterCreatedObjectUndo(twoBoneIKConstraintObj, "Create Two Bone IK " + name);
-
- Undo.SetTransformParent(twoBoneIKConstraintObj.transform, rigObject.transform,
- $"Add TwoBone IK {name} Constraint to Rig");
- Undo.RegisterCompleteObjectUndo(twoBoneIKConstraintObj,
- $"TwoBone IK Constraint {name} Transform Init");
- twoBoneIKConstraintObj.transform.localPosition = Vector3.zero;
- twoBoneIKConstraintObj.transform.localRotation = Quaternion.identity;
- twoBoneIKConstraintObj.transform.localScale = Vector3.one;
- }
- return twoBoneIKConstraint;
+ if (twoBoneIKConstraintObjTransform != null)
+ {
+ Undo.DestroyObjectImmediate(twoBoneIKConstraintObjTransform);
+ return true;
+ }
+ return false;
}
private static BlendHandConstraints AddHandBlendConstraint(
- GameObject mainParent, MonoBehaviour[] constraints, RetargetingLayer retargetingLayer,
- CustomMappings.BodyTrackingBoneId boneIdToTest, Transform headTransform)
+ GameObject mainParent, RetargetingLayer retargetingLayer,
+ OVRHumanBodyBonesMappings.BodyTrackingBoneId boneIdToTest, Transform headTransform)
{
+ var blendHandConstraints = mainParent.GetComponentsInChildren();
+ foreach (var blendHandConstraint in blendHandConstraints)
+ {
+ if (blendHandConstraint.BoneIdToTest == boneIdToTest)
+ {
+ blendHandConstraint.Constraints = null;
+ blendHandConstraint.RetargetingLayerComp = retargetingLayer;
+ blendHandConstraint.BoneIdToTest = boneIdToTest;
+ blendHandConstraint.HeadTransform = headTransform;
+ blendHandConstraint.AutoAddTo = mainParent.GetComponent();
+ blendHandConstraint.BlendCurve = new AnimationCurve(new Keyframe(0, 0), new Keyframe(1, 1));
+ PrefabUtility.RecordPrefabInstancePropertyModifications(blendHandConstraint);
+ return blendHandConstraint;
+ }
+ }
+
BlendHandConstraints blendConstraint =
mainParent.AddComponent();
Undo.RegisterCreatedObjectUndo(blendConstraint, "Add blend constraint");
- foreach (MonoBehaviour constraint in constraints)
- {
- blendConstraint.AddSkeletalConstraint(constraint);
- }
+ blendConstraint.Constraints = null;
blendConstraint.RetargetingLayerComp = retargetingLayer;
blendConstraint.BoneIdToTest = boneIdToTest;
blendConstraint.HeadTransform = headTransform;
@@ -474,6 +570,11 @@ private static void ValidGameObjectForAnimationRigging(GameObject go)
}
}
+ ///
+ /// Validates GameObject for face mapping.
+ ///
+ /// GameObject to check.
+ /// Exception thrown if GameObject fails check.
public static void ValidateGameObjectForFaceMapping(GameObject go)
{
var renderer = go.GetComponent();
diff --git a/Editor/Utils/IntAsEnumDrawer.cs b/Editor/Utils/IntAsEnumDrawer.cs
new file mode 100644
index 00000000..9346cfd9
--- /dev/null
+++ b/Editor/Utils/IntAsEnumDrawer.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+using UnityEditor;
+using UnityEngine;
+
+namespace Oculus.Movement.Utils
+{
+ ///
+ /// Int drawer that allows displaying an int like an enum. This is used for displaying ints that are bound
+ /// to an AnimationStream but are functionally used as an enum.
+ ///
+ [CustomPropertyDrawer(typeof(IntAsEnumAttribute))]
+ public class DisplayIntAsEnumDrawer : PropertyDrawer
+ {
+ ///
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
+ IntAsEnumAttribute intAsEnumAttribute = (IntAsEnumAttribute)attribute;
+
+ if (property.propertyType == SerializedPropertyType.Integer)
+ {
+ var names = System.Enum.GetNames(intAsEnumAttribute.Type);
+ var values = new int[names.Length];
+ for (int i = 0; i < values.Length; i++)
+ {
+ values[i] = i;
+ names[i] = FormatName(names[i]);
+ }
+ EditorGUI.BeginProperty(position, GUIContent.none, property);
+ property.intValue = EditorGUI.IntPopup(position,
+ FormatName(intAsEnumAttribute.Type.Name), property.intValue, names, values);
+ EditorGUI.EndProperty();
+ }
+ }
+
+ private string FormatName(string name)
+ {
+ return System.Text.RegularExpressions.Regex.Replace(name, @"([a-z])([A-Z])", "$1 $2");;
+ }
+ }
+}
diff --git a/Editor/Utils/IntAsEnumDrawer.cs.meta b/Editor/Utils/IntAsEnumDrawer.cs.meta
new file mode 100644
index 00000000..95c98152
--- /dev/null
+++ b/Editor/Utils/IntAsEnumDrawer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fbf36ff54320f2842907781f6a06eb34
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/Utils/OVRProjectSetupMovementSDKSamples.cs b/Editor/Utils/OVRProjectSetupMovementSDKSamples.cs
new file mode 100644
index 00000000..ba66ddf9
--- /dev/null
+++ b/Editor/Utils/OVRProjectSetupMovementSDKSamples.cs
@@ -0,0 +1,141 @@
+// Copyright (c) Meta Platforms, Inc. and affiliates.
+
+using UnityEditor;
+using UnityEngine;
+
+namespace Oculus.Movement.Utils
+{
+ ///
+ /// Add project setup validation tasks for MovementSDK Samples.
+ ///
+ [InitializeOnLoad]
+ internal static class OVRProjectSetupMovementSDKSamplesTasks
+ {
+ ///
+ /// All character and mirrored character layers should exist based on their indices.
+ /// That is because the scene assets are assigned based on layer index.
+ ///
+ private static readonly int[] _expectedLayersIndices =
+ {
+ 10, 11
+ };
+
+ ///
+ /// The HiddenMesh layer is required for RecalculateNormals to function correctly.
+ ///
+ private static readonly string _hiddenMeshLayerName = "HiddenMesh";
+
+ private const OVRProjectSetup.TaskGroup _group = OVRProjectSetup.TaskGroup.Features;
+
+ static OVRProjectSetupMovementSDKSamplesTasks()
+ {
+ // Body tracking settings.
+ OVRProjectSetup.AddTask(
+ level: OVRProjectSetup.TaskLevel.Required,
+ group: _group,
+ platform: BuildTargetGroup.Android,
+ isDone: group => OVRRuntimeSettings.Instance.BodyTrackingFidelity == OVRPlugin.BodyTrackingFidelity2.High,
+ message: "Body Tracking Fidelity should be set to High.",
+ fix: group =>
+ {
+ OVRRuntimeSettings.Instance.BodyTrackingFidelity = OVRPlugin.BodyTrackingFidelity2.High;
+ OVRRuntimeSettings.CommitRuntimeSettings(OVRRuntimeSettings.Instance);
+ },
+ fixMessage: "Set OVRRuntimeSettings.BodyTrackingFidelity = High"
+ );
+
+ OVRProjectSetup.AddTask(
+ level: OVRProjectSetup.TaskLevel.Required,
+ group: _group,
+ platform: BuildTargetGroup.Android,
+ isDone: group => OVRRuntimeSettings.GetRuntimeSettings().BodyTrackingJointSet == OVRPlugin.BodyJointSet.FullBody,
+ message: "Body Tracking Joint Set should be set to Full Body.",
+ fix: group =>
+ {
+ OVRRuntimeSettings.Instance.BodyTrackingJointSet = OVRPlugin.BodyJointSet.FullBody;
+ OVRRuntimeSettings.CommitRuntimeSettings(OVRRuntimeSettings.Instance);
+ },
+ fixMessage: "Set OVRRuntimeSettings.BodyTrackingJointSet = FullBody"
+ );
+
+ // Test layers.
+ OVRProjectSetup.AddTask(
+ level: OVRProjectSetup.TaskLevel.Recommended,
+ group: _group,
+ platform: BuildTargetGroup.Android,
+ isDone: group => LayerMask.NameToLayer(_hiddenMeshLayerName) != -1,
+ message: $"The layer '{_hiddenMeshLayerName}' needs to exist for Recalculate Normals to function properly.",
+ fix: group =>
+ {
+ int unusedLayer = -1;
+ for (int i = 0; i < 32; i++)
+ {
+ if (string.IsNullOrEmpty(LayerMask.LayerToName(i)))
+ {
+ unusedLayer = i;
+ break;
+ }
+ }
+ if (LayerMask.NameToLayer(_hiddenMeshLayerName) == -1)
+ {
+ if (unusedLayer == -1)
+ {
+ Debug.LogError("All Layers are Used!");
+ }
+ else
+ {
+ SetLayerName(unusedLayer, "HiddenMesh");
+ }
+ }
+ },
+ fixMessage: $"Set an unused layer to '{_hiddenMeshLayerName}'."
+ );
+ OVRProjectSetup.AddTask(
+ level: OVRProjectSetup.TaskLevel.Recommended,
+ group: _group,
+ platform: BuildTargetGroup.Android,
+ isDone: group =>
+ {
+ foreach (var layerIndex in _expectedLayersIndices)
+ {
+ if (string.IsNullOrEmpty(LayerMask.LayerToName(layerIndex)))
+ {
+ return false;
+ }
+ }
+ return true;
+ },
+ message: "Layers 10 and 11 must be indexed for the samples to display correctly.",
+ fix: group =>
+ {
+ foreach (var expectedLayerIndex in _expectedLayersIndices)
+ {
+ if (string.IsNullOrEmpty(LayerMask.LayerToName(expectedLayerIndex)))
+ {
+ // Default layer names.
+ string newLayerName = expectedLayerIndex == 10 ? "Character" : "MirroredCharacter";
+ SetLayerName(expectedLayerIndex, newLayerName);
+ }
+ }
+ },
+ fixMessage: "Set layers 10 and 11 to be valid layers."
+ );
+ }
+
+ private static void SetLayerName(int layer, string name)
+ {
+ SerializedObject tagManager = new SerializedObject(AssetDatabase.LoadAssetAtPath