/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * Licensed under the Oculus SDK License Agreement (the "License"); * you may not use the Oculus SDK except in compliance with the License, * which is provided at the time of installation or download, or which * otherwise accompanies this software in either electronic or hard copy form. * * You may obtain a copy of the License at * * https://developer.oculus.com/licenses/oculussdk/ * * Unless required by applicable law or agreed to in writing, the Oculus SDK * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System.Collections.Generic; using System; using System.ComponentModel; using System.Linq; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.Assertions; using Component = UnityEngine.Component; /// /// Custom Editor for /// /// /// Custom Editor for that supports: /// - attempting to find an in the parent hierarchy to match to if none is chosen /// - supporting string matching to attempt to automatically match to blend shapes on the shared mesh /// The string matching algorithm will tokenize the blend shape names and the names and for each /// blend shape find the with the most total characters in matching tokens. /// To match tokens it currently uses case invariant matching. /// The tokenization is based on some common string seperation characters and seperation by camel case. /// [CustomEditor(typeof(OVRCustomFace))] public class OVRCustomFaceEditor : Editor { private SerializedProperty _expressionsProp; private SerializedProperty _mappings; private SerializedProperty _strengthMultiplier; private SerializedProperty _allowDuplicateMapping; private bool _showBlendshapes = true; protected virtual void OnEnable() { _expressionsProp = serializedObject.FindProperty(nameof(OVRCustomFace._faceExpressions)); _mappings = serializedObject.FindProperty(nameof(OVRCustomFace._mappings)); _strengthMultiplier = serializedObject.FindProperty(nameof(OVRCustomFace._blendShapeStrengthMultiplier)); _allowDuplicateMapping = serializedObject.FindProperty(nameof(OVRCustomFace._allowDuplicateMapping)); } public override void OnInspectorGUI() { var face = (OVRCustomFace)target; serializedObject.Update(); if (_expressionsProp.objectReferenceValue == null) { _expressionsProp.objectReferenceValue = face.SearchFaceExpressions(); } if (!IsFaceExpressionsConfigured(face)) { if (OVREditorUIElements.RenderWarningWithButton( "OVRFaceExpressions is required.", "Configure OVRFaceExpressions")) { FixFaceExpressions(face); } } EditorGUILayout.PropertyField(_expressionsProp, new GUIContent(nameof(OVRFaceExpressions))); EditorGUILayout.PropertyField(_strengthMultiplier, new GUIContent("Blend Shape Strength Multiplier")); //need to pass out some property to find the component from SkinnedMeshRenderer renderer = GetSkinnedMeshRenderer(_expressionsProp); if (renderer == null || renderer.sharedMesh == null) { if (_mappings.arraySize > 0) { _mappings.ClearArray(); } serializedObject.ApplyModifiedProperties(); return; } if (_mappings.arraySize != renderer.sharedMesh.blendShapeCount) { _mappings.ClearArray(); _mappings.arraySize = renderer.sharedMesh.blendShapeCount; for (int i = 0; i < renderer.sharedMesh.blendShapeCount; ++i) { _mappings.GetArrayElementAtIndex(i).intValue = (int)OVRFaceExpressions.FaceExpression.Invalid; } } EditorGUILayout.Space(); var enumValues = Enum.GetNames(typeof(OVRCustomFace.RetargetingType)); face.retargetingType = (OVRCustomFace.RetargetingType) EditorGUILayout.Popup("Custom face structure", (int)face.retargetingType, enumValues); if (face.retargetingType == OVRCustomFace.RetargetingType.OculusFace || face.retargetingType == OVRCustomFace.RetargetingType.Custom ) { _showBlendshapes = EditorGUILayout.BeginFoldoutHeaderGroup(_showBlendshapes, "Blendshapes"); if (_showBlendshapes) { if (GUILayout.Button("Auto Generate Mapping")) { face.AutoMapBlendshapes(); Refresh(face); } if (GUILayout.Button("Clear Mapping")) { face.ClearBlendshapes(); Refresh(face); } EditorGUILayout.Space(); for (int i = 0; i < renderer.sharedMesh.blendShapeCount; ++i) { EditorGUILayout.PropertyField(_mappings.GetArrayElementAtIndex(i), new GUIContent(renderer.sharedMesh.GetBlendShapeName(i))); } } } EditorGUILayout.EndFoldoutHeaderGroup(); EditorGUILayout.PropertyField(_allowDuplicateMapping, new GUIContent("Allow duplicate mapping")); serializedObject.ApplyModifiedProperties(); static void Refresh(OVRCustomFace face) { EditorUtility.SetDirty(face); EditorSceneManager.MarkSceneDirty(face.gameObject.scene); } } internal static bool IsFaceExpressionsConfigured(OVRFace face) { return face._faceExpressions != null; } internal static void FixFaceExpressions(OVRFace face) { Undo.IncrementCurrentGroup(); var gameObject = face.gameObject; var faceExpressions = face.SearchFaceExpressions(); if (!faceExpressions) { faceExpressions = gameObject.AddComponent(); Undo.RegisterCreatedObjectUndo(faceExpressions, "Create OVRFaceExpressions component"); } Undo.RecordObject(face, "Linked OVRFaceExpression"); face._faceExpressions = faceExpressions; EditorUtility.SetDirty(face); EditorSceneManager.MarkSceneDirty(gameObject.scene); Undo.SetCurrentGroupName("Configure OVRFaceExpressions for OVRCustomFace"); } private static SkinnedMeshRenderer GetSkinnedMeshRenderer(SerializedProperty property) { GameObject targetObject = GetGameObject(property); if (!targetObject) return null; return targetObject.GetComponent(); } private static GameObject GetGameObject(SerializedProperty property) { Component targetComponent = property.serializedObject.targetObject as Component; if (targetComponent && targetComponent.gameObject) { return targetComponent.gameObject; } return null; } }