Initialer Upload neues Unity-Projekt

This commit is contained in:
Daniel Ocks
2025-07-21 09:11:14 +02:00
commit eeca72985b
14558 changed files with 1508140 additions and 0 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e628d552f8b252d44815ffae12fd3976
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: afc952163de48b04dbf0f968256241c0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,134 @@
/*
* 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;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace Oculus.Interaction.Editor
{
[InitializeOnLoad]
public static class AutoWiring
{
static AutoWiring()
{
UnityObjectAddedBroadcaster.WhenComponentAdded += (component) =>
{
MonoBehaviour monoBehaviour = component as MonoBehaviour;
if (monoBehaviour == null) return;
if (!_configs.TryGetValue(component.GetType(), out ComponentWiringStrategyConfig[] configs))
{
return;
}
foreach (ComponentWiringStrategyConfig config in configs)
{
AutoWireField(monoBehaviour, config.FieldName, config.Methods);
}
};
}
private static readonly Dictionary<Type, ComponentWiringStrategyConfig[]> _configs = new
Dictionary<Type, ComponentWiringStrategyConfig[]>();
public static void Register(Type type, ComponentWiringStrategyConfig[] fieldConfigs)
{
_configs.Add(type, fieldConfigs);
}
public static void Unregister(Type type)
{
_configs.Remove(type);
}
public static bool AutoWireField(MonoBehaviour monoBehaviour,
string fieldName,
FieldWiringStrategy[] wiringMethods)
{
FieldInfo field = FindField(fieldName, monoBehaviour.GetType());
if (field == null)
{
return false;
}
UnityEngine.Object value = field.GetValue(monoBehaviour) as UnityEngine.Object;
if (value != null)
{
return false;
}
Undo.RecordObject(monoBehaviour, "Autowiring");
var interfaceAttribute = field.GetCustomAttribute<InterfaceAttribute>();
var wirableTypes = interfaceAttribute != null ?
interfaceAttribute.Types :
new[] { field.FieldType };
if (wirableTypes != null)
{
foreach (var method in wiringMethods)
{
foreach (Type type in wirableTypes)
{
if (method.Invoke(monoBehaviour, field, type))
{
Component component = field.GetValue(monoBehaviour) as Component;
Debug.Log("Auto-wiring succeeded: " + monoBehaviour.gameObject.name + "::" +
monoBehaviour.GetType().Name + "." + field.Name +
" was linked to " +
component.gameObject.name + "::" + component.GetType().Name,
monoBehaviour);
return true;
}
}
}
}
if (field.GetCustomAttribute<OptionalAttribute>() == null)
{
Debug.LogWarning("Auto-wiring failed: no suitable targets for " +
monoBehaviour.gameObject.name + "::" + monoBehaviour.GetType().Name +
"." + field.Name + " could be found.",
monoBehaviour);
}
return false;
}
private static FieldInfo FindField(string fieldName, Type type)
{
if (type == null)
{
return null;
}
FieldInfo field = type.GetField(fieldName,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field == null)
{
return FindField(fieldName, type.BaseType);
}
return field;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 87e14328c5481cd458e1749181aaa1f5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,79 @@
/*
* 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;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Oculus.Interaction.Editor
{
public delegate Boolean FieldWiringStrategy(MonoBehaviour monoBehaviour, FieldInfo fieldInfo, Type type);
public struct ComponentWiringStrategyConfig
{
public string FieldName { get; }
public FieldWiringStrategy[] Methods { get; }
public ComponentWiringStrategyConfig(string fieldName, FieldWiringStrategy[] methods)
{
FieldName = fieldName;
Methods = methods;
}
}
public class FieldWiringStrategies
{
public static bool WireFieldToAncestors(MonoBehaviour monoBehaviour, FieldInfo field, Type targetType)
{
for (var transform = monoBehaviour.transform.parent; transform != null; transform = transform.parent)
{
var component = transform.gameObject.GetComponent(targetType);
if (component)
{
field.SetValue(monoBehaviour, component);
EditorUtility.SetDirty(monoBehaviour);
return true;
}
}
return false;
}
public static bool WireFieldToSceneComponent(MonoBehaviour monoBehaviour, FieldInfo field, Type targetType)
{
var rootObjs = SceneManager.GetActiveScene().GetRootGameObjects();
foreach (var rootGameObject in rootObjs)
{
var component = rootGameObject.GetComponentInChildren(targetType, true);
if (component != null)
{
field.SetValue(monoBehaviour, component);
EditorUtility.SetDirty(monoBehaviour);
return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 789d3096184f6294e9fd9fe812cfdf19
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,114 @@
/*
* 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 Oculus.Interaction.Input;
using Oculus.Interaction.PoseDetection;
using UnityEditor;
namespace Oculus.Interaction.Editor
{
[InitializeOnLoad]
public static class InteractionAutoWiring
{
static InteractionAutoWiring()
{
AutoWiring.Register(
typeof(HandRef),
new[] {
new ComponentWiringStrategyConfig("_hand", new FieldWiringStrategy[]
{
FieldWiringStrategies.WireFieldToAncestors
})
}
);
AutoWiring.Register(
typeof(ControllerRef),
new[]
{
new ComponentWiringStrategyConfig("_controller", new FieldWiringStrategy[]
{
FieldWiringStrategies.WireFieldToAncestors
})
}
);
AutoWiring.Register(
typeof(FingerFeatureStateProvider),
new[] {
new ComponentWiringStrategyConfig("_hand", new FieldWiringStrategy[]
{
FieldWiringStrategies.WireFieldToAncestors
}),
}
);
AutoWiring.Register(
typeof(TransformFeatureStateProvider),
new[] {
new ComponentWiringStrategyConfig("_hand", new FieldWiringStrategy[]
{
FieldWiringStrategies.WireFieldToAncestors
}),
new ComponentWiringStrategyConfig("_trackingToWorldTransformer", new FieldWiringStrategy[]
{
FieldWiringStrategies.WireFieldToAncestors
}),
new ComponentWiringStrategyConfig("_hmd", new FieldWiringStrategy[]
{
FieldWiringStrategies.WireFieldToAncestors,
FieldWiringStrategies.WireFieldToSceneComponent
})
}
);
AutoWiring.Register(
typeof(JointDeltaProvider),
new[] {
new ComponentWiringStrategyConfig("_hand", new FieldWiringStrategy[]
{
FieldWiringStrategies.WireFieldToAncestors
}),
}
);
#region HandGrab
AutoWiring.Register(
typeof(HandGrab.HandGrabInteractable),
new[] {
new ComponentWiringStrategyConfig("_rigidbody", new FieldWiringStrategy[]
{
FieldWiringStrategies.WireFieldToAncestors
}),
new ComponentWiringStrategyConfig("_pointableElement", new FieldWiringStrategy[]
{
FieldWiringStrategies.WireFieldToAncestors
}),
new ComponentWiringStrategyConfig("_physicsGrabbable", new FieldWiringStrategy[]
{
FieldWiringStrategies.WireFieldToAncestors
})
}
);
#endregion
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cda6aee7f7301f34d88ea873bdf0e310
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,193 @@
/*
* 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;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Oculus.Interaction.Editor
{
[InitializeOnLoad]
public static class UnityObjectAddedBroadcaster
{
public static event Action<GameObject> WhenGameObjectHierarchyAdded = (_) => {};
public static event Action<Component> WhenComponentAdded = (_) => {};
private static int _objectAddedUndoNestingCounter = 0;
private static int _objectAddedUndoGroupId = -1;
static UnityObjectAddedBroadcaster()
{
HashSet<int> knownIds = new HashSet<int>();
EditorSceneManager.SceneOpenedCallback handleSceneOpened = (scene, mode) =>
{
UnityObjectAddedBroadcaster.HandleSceneOpened(scene, mode, knownIds);
};
Action handleHierarchyChanged = () =>
{
UnityObjectAddedBroadcaster.HandleHierarchyChanged(knownIds);
};
Action<Component> handleComponentWasAdded = (component) =>
{
UnityObjectAddedBroadcaster.HandleComponentWasAdded(component);
};
AssemblyReloadEvents.AssemblyReloadCallback handleBeforeAssemblyReload = null;
handleBeforeAssemblyReload = () =>
{
UnityObjectAddedBroadcaster.WhenGameObjectHierarchyAdded = (_) => { };
UnityObjectAddedBroadcaster.WhenComponentAdded = (_) => { };
EditorSceneManager.sceneOpened -= handleSceneOpened;
EditorApplication.hierarchyChanged -= handleHierarchyChanged;
ObjectFactory.componentWasAdded -= handleComponentWasAdded;
AssemblyReloadEvents.beforeAssemblyReload -= handleBeforeAssemblyReload;
};
EditorSceneManager.sceneOpened += handleSceneOpened;
EditorApplication.hierarchyChanged += handleHierarchyChanged;
ObjectFactory.componentWasAdded += handleComponentWasAdded;
AssemblyReloadEvents.beforeAssemblyReload += handleBeforeAssemblyReload;
for (int idx = 0; idx < SceneManager.loadedSceneCount; ++idx)
{
handleSceneOpened(EditorSceneManager.GetSceneAt(idx), OpenSceneMode.Additive);
}
}
private static void HandleSceneOpened(Scene scene, OpenSceneMode mode, HashSet<int> knownIds)
{
if (mode == OpenSceneMode.Single)
{
knownIds.Clear();
}
AddInstanceIdsFromSubHierarchyToCache(knownIds, scene.GetRootGameObjects());
}
/// <summary>
/// Fires signals for GameObjects and Components added through the addition of a prefab,
/// checking whether the selected GameObject at the moment of a hierarchy change is
/// unfamiliar (i.e., has an instance ID which is not already in known IDs) and signaling
/// appropriately. Note that this will NOT signal GameObjects or Components added to the
/// scene through prefab updating, which are added without modifying the Editor's
/// selection variable, upon which this handler relies.
/// </summary>
/// <param name="knownIds">Cache of known GameObject instance IDs</param>
private static void HandleHierarchyChanged(HashSet<int> knownIds)
{
if (EditorApplication.isPlaying)
{
return;
}
var selection = Selection.activeGameObject;
if (selection == null)
{
return;
}
if (!knownIds.Contains(selection.GetInstanceID()))
{
AddInstanceIdsFromSubHierarchyToCache(knownIds, selection);
StartUndoGroup();
UnityObjectAddedBroadcaster.WhenGameObjectHierarchyAdded(selection);
// ObjectFactory.componentWasAdded is not called for components added to the scene
// as part of a prefab, so we manually iterate them here so that
// Signaler.WhenComponentAdded presents a more complete picture of activity in
// the scene.
var addedComponents = selection.GetComponentsInChildren<Component>(true);
foreach (var component in addedComponents)
{
UnityObjectAddedBroadcaster.WhenComponentAdded(component);
}
EndUndoGroup();
}
}
/// <summary>
/// Fires signals for Components added to existing GameObjects. Note that this will
/// NOT signal Components added to the scene through prefab updating, which are added
/// without triggering the ObjectFactory, upon which this handler relies.
/// </summary>
/// <param name="component">The component added to the scene</param>
private static void HandleComponentWasAdded(Component component)
{
if (EditorApplication.isPlaying)
{
return;
}
StartUndoGroup();
UnityObjectAddedBroadcaster.WhenComponentAdded(component);
EndUndoGroup();
}
public static void HandleObjectWasAdded(GameObject gameObject)
{
StartUndoGroup();
var addedComponents = gameObject.GetComponentsInChildren<Component>(true);
foreach (var component in addedComponents)
{
UnityObjectAddedBroadcaster.WhenComponentAdded(component);
}
EndUndoGroup();
}
private static void AddInstanceIdsFromSubHierarchyToCache(HashSet<int> cache, params GameObject[] subHierarchyRoots)
{
foreach (var gameObject in subHierarchyRoots)
{
cache.Add(gameObject.GetInstanceID());
for (int idx = 0; idx < gameObject.transform.childCount; ++idx)
{
AddInstanceIdsFromSubHierarchyToCache(cache, gameObject.transform.GetChild(idx).gameObject);
}
}
}
private static void StartUndoGroup()
{
if (_objectAddedUndoNestingCounter == 0)
{
_objectAddedUndoGroupId = Undo.GetCurrentGroup() - 1;
}
_objectAddedUndoNestingCounter++;
}
private static void EndUndoGroup()
{
_objectAddedUndoNestingCounter--;
if (_objectAddedUndoNestingCounter == 0)
{
Undo.FlushUndoRecordObjects();
Undo.CollapseUndoOperations(_objectAddedUndoGroupId);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6713358c2c4d39e4ba0eae4312ee2580
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7b46659523f404646babada299c34172
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,132 @@
/*
* 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 UnityEngine;
using UnityEditor;
using System.Linq;
using System.Collections.Generic;
using Oculus.Interaction.Body.PoseDetection;
using Oculus.Interaction.Body.Input;
using System.Reflection;
namespace Oculus.Interaction.Body.Editor
{
using JointComparerConfig = BodyPoseComparerActiveState.JointComparerConfig;
[CustomPropertyDrawer(typeof(JointComparerConfig))]
public class BodyPoseComparerConfigPropertyDrawer : PropertyDrawer
{
private static GUIContent[] _jointNames;
private static BodyJointId[] _joints;
private int _controlCount = 0;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return GetLineHeight() * _controlCount;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (_jointNames == null || _joints == null)
{
UpdateJointLists();
}
_controlCount = 0;
Rect pos = new Rect(position.x, position.y, position.width,
EditorGUIUtility.singleLineHeight);
EditorGUI.BeginProperty(position, label, property);
var joint = property.FindPropertyRelative(nameof(JointComparerConfig.Joint));
var maxAngle = property.FindPropertyRelative(nameof(JointComparerConfig.MaxDelta));
var width = property.FindPropertyRelative(nameof(JointComparerConfig.Width));
DrawJointPopup(joint, "Body Joint", ref pos);
DrawControl(maxAngle, "Max Angle Delta", ref pos);
DrawControl(width, "Threshold Width", ref pos);
EditorGUI.EndProperty();
}
private void DrawJointPopup(SerializedProperty jointProp, string name, ref Rect position)
{
int currentJoint = GetDisplayIndexFromJoint((BodyJointId)jointProp.intValue);
int selectedJoint = EditorGUI.Popup(position,
new GUIContent(name, jointProp.tooltip),
currentJoint, _jointNames);
position.y += GetLineHeight();
++_controlCount;
jointProp.intValue = (int)_joints[selectedJoint];
}
private void DrawControl(SerializedProperty property, string name, ref Rect position)
{
EditorGUI.PropertyField(position, property, new GUIContent(name, property.tooltip));
position.y += GetLineHeight();
++_controlCount;
}
private float GetLineHeight()
{
return EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
}
private static int GetDisplayIndexFromJoint(BodyJointId jointId)
{
for (int i = 0; i < _joints.Length; ++i)
{
if (jointId == _joints[i])
{
return i;
}
}
return 0;
}
private static void UpdateJointLists()
{
List<string> jointNames = new List<string>();
List<BodyJointId> joints = new List<BodyJointId>();
for (int i = (int)BodyJointId.Body_Hips; i < (int)BodyJointId.Body_End; ++i)
{
BodyJointId jointId = (BodyJointId)i;
InspectorNameAttribute inspectorNameAttribute =
typeof(BodyJointId)
.GetMember(jointId.ToString())
.First()
.GetCustomAttribute<InspectorNameAttribute>();
joints.Add(jointId);
jointNames.Add(inspectorNameAttribute != null ?
inspectorNameAttribute.displayName :
jointId.ToString());
}
_joints = joints.ToArray();
_jointNames = jointNames
.Select(s => new GUIContent(s))
.ToArray();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 57db1cbbceaaab34dafc9c1f0a32da20
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,205 @@
/*
* 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 UnityEngine;
using UnityEditor;
using Oculus.Interaction.Body.PoseDetection;
using Oculus.Interaction.Body.Input;
using System.IO;
namespace Oculus.Interaction.Body.Editor
{
public class BodyPoseRecorder : EditorWindow
{
private const string POSE_DIRECTORY = "BodyPoses";
[Tooltip("The " + nameof(IBody) + " that provides joint " +
"data for pose creation.")]
[SerializeField, Interface(typeof(IBody))]
private UnityEngine.Object _body;
[Tooltip("The captured body pose will be written into this " +
nameof(BodyPoseData) + " asset. If this field is left " +
"unassigned, each capture will create a new asset in the " +
POSE_DIRECTORY + " directory.")]
[SerializeField, Optional]
private BodyPoseData _targetAsset;
private SerializedObject _serializedEditor;
private float _captureDelay = 5f;
private float _nextCaptureTime;
private bool _willCapture = false;
private bool _beepOnCapture = true;
private GUIStyle _richTextStyle;
private IBody Body => _body as IBody;
private float TimeUntilCapture =>
Mathf.Max(_nextCaptureTime - Time.unscaledTime, 0);
[MenuItem("Oculus/Interaction/Body Pose Recorder")]
private static void ShowWindow()
{
BodyPoseRecorder window = GetWindow<BodyPoseRecorder>();
window.titleContent = new GUIContent("Body Pose Recorder");
window._serializedEditor = null;
window.Show();
if (Application.isPlaying)
{
window.Initialize();
}
}
[InitializeOnEnterPlayMode]
private static void OnPlayModeEnter()
{
EditorApplication.delayCall += () =>
{
if (HasOpenInstances<BodyPoseRecorder>())
{
BodyPoseRecorder window = GetWindow<BodyPoseRecorder>();
window.Initialize();
}
};
}
private void Initialize()
{
_targetAsset = null;
_body = FindObjectOfType<Input.Body>();
_serializedEditor = new SerializedObject(this);
_richTextStyle = EditorGUIUtility.GetBuiltinSkin(
EditorGUIUtility.isProSkin ?
EditorSkin.Scene :
EditorSkin.Inspector).label;
_richTextStyle.richText = true;
_richTextStyle.wordWrap = true;
}
private void OnGUI()
{
if (!Application.isPlaying)
{
EditorGUILayout.LabelField("Body Pose Recorder only works in Play mode");
return;
}
if (_serializedEditor == null)
{
return;
}
HandleCapture();
DrawUI();
if (_willCapture)
{
Repaint();
}
}
private void DrawUI()
{
GUI.enabled = !_willCapture;
SerializedProperty bodyProp =
_serializedEditor.FindProperty(nameof(_body));
SerializedProperty targetAssetProp =
_serializedEditor.FindProperty(nameof(_targetAsset));
GUILayout.Label("<size=16>Source</size>\n" + bodyProp.tooltip, _richTextStyle);
EditorGUILayout.PropertyField(
bodyProp, new GUIContent("IBody", bodyProp.tooltip), true);
GUILayout.Space(20);
GUILayout.Label("<size=16>Target</size>\n" + targetAssetProp.tooltip, _richTextStyle);
EditorGUILayout.PropertyField(
targetAssetProp, new GUIContent("Target Asset", targetAssetProp.tooltip), true);
GUILayout.Space(20);
GUILayout.Label("<size=16>Capture Settings</size>", _richTextStyle);
_captureDelay = EditorGUILayout.FloatField("Capture Delay (Seconds)", _captureDelay);
_captureDelay = Mathf.Max(_captureDelay, 0);
_beepOnCapture = EditorGUILayout.Toggle("Play Sound on Capture", _beepOnCapture);
_serializedEditor.ApplyModifiedProperties();
GUILayout.Space(20);
GUI.enabled = Body != null;
string buttonLabel = _willCapture ?
$"Capturing in {TimeUntilCapture.ToString("#.#")} " +
$"seconds\n(Click To Cancel)" : "Capture Body Pose";
if (GUILayout.Button(buttonLabel, GUILayout.Height(36)))
{
_willCapture = !_willCapture;
_nextCaptureTime = Time.unscaledTime + _captureDelay;
}
GUI.enabled = true;
}
private void HandleCapture()
{
if (!_willCapture)
{
return;
}
if (TimeUntilCapture <= 0f)
{
_willCapture = false;
if (_beepOnCapture)
{
EditorApplication.Beep();
}
BodyPoseData assetToWrite = _targetAsset;
if (assetToWrite == null)
{
assetToWrite = GeneratePoseAsset();
}
assetToWrite.SetBodyPose(Body);
Debug.Log($"Captured Body Pose into " +
$"{AssetDatabase.GetAssetPath(assetToWrite)}");
EditorUtility.SetDirty(assetToWrite);
AssetDatabase.SaveAssetIfDirty(assetToWrite);
AssetDatabase.Refresh();
}
}
public BodyPoseData GeneratePoseAsset()
{
var poseDataAsset = ScriptableObject.CreateInstance<BodyPoseData>();
string parentDir = Path.Combine("Assets", POSE_DIRECTORY);
if (!Directory.Exists(parentDir))
{
Directory.CreateDirectory(parentDir);
}
string name = "BodyPose-" + $"{System.DateTime.Now.ToString("yyyyMMdd-HHmmss")}";
AssetDatabase.CreateAsset(poseDataAsset, Path.Combine(parentDir, $"{name}.asset"));
return poseDataAsset;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 580dbef906f46f1449a123971c210b75
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,399 @@
/*
* 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;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.Editor
{
/// <summary>
/// A utility class for building custom editors with less work required.
/// </summary>
public class EditorBase
{
private SerializedObject _serializedObject;
private HashSet<string> _hiddenProperties = new HashSet<string>();
private HashSet<string> _skipProperties = new HashSet<string>();
private Dictionary<string, Action<SerializedProperty>> _customDrawers =
new Dictionary<string, Action<SerializedProperty>>();
private Dictionary<string, Section> _sections =
new Dictionary<string, Section>();
private List<string> _orderedSections = new List<string>();
public class Section
{
public string title;
public bool isFoldout;
public bool foldout;
public List<string> properties = new List<string>();
public bool HasSomethingToDraw()
{
return properties != null && properties.Count > 0;
}
}
public EditorBase(SerializedObject serializedObject)
{
_serializedObject = serializedObject;
}
#region Sections
private Section GetOrCreateSection(string sectionName)
{
if (_sections.TryGetValue(sectionName, out Section existingSection))
{
return existingSection;
}
Section section = CreateSection(sectionName, false);
return section;
}
public Section CreateSection(string sectionName, bool isFoldout)
{
if (_sections.TryGetValue(sectionName, out Section existingSection))
{
Debug.LogError($"Section {sectionName} already exists");
return null;
}
Section section = new Section() { title = sectionName, isFoldout = isFoldout };
_sections.Add(sectionName, section);
_orderedSections.Add(sectionName);
return section;
}
public void CreateSections(Dictionary<string, string[]> sections, bool isFoldout)
{
foreach (var sectionData in sections)
{
CreateSection(sectionData.Key, isFoldout);
AddToSection(sectionData.Key, sectionData.Value);
}
}
public void AddToSection(string sectionName, params string[] properties)
{
if (properties.Length == 0
|| !ValidateProperties(properties))
{
return;
}
Section section = GetOrCreateSection(sectionName);
foreach (var property in properties)
{
section.properties.Add(property);
_skipProperties.Add(property);
}
}
#endregion
#region API
/// <summary>
/// Call in OnEnable with one or more property names to hide them from the inspector.
///
/// This is preferable to using [HideInInspector] because it still allows the property to
/// be viewed when using the Inspector debug mode.
/// </summary>
public void Hide(params string[] properties)
{
Assert.IsTrue(properties.Length > 0, "Should always hide at least one property.");
if (!ValidateProperties(properties))
{
return;
}
_hiddenProperties.UnionWith(properties);
}
/// <summary>
/// Call in OnInit to specify a custom drawer for a single property. Whenever the property is drawn,
/// it will use the provided property drawer instead of the default one.
/// </summary>
public void Draw(string property, Action<SerializedProperty> drawer)
{
if (!ValidateProperties(property))
{
return;
}
_customDrawers.Add(property, drawer);
}
/// <summary>
/// Call in OnInit to specify a custom drawer for a single property. Include an extra property that gets
/// lumped in with the primary property. The extra property is not drawn normally, and is instead grouped in
/// with the primary property. Can be used in situations where a collection of properties need to be drawn together.
/// </summary>
public void Draw(string property,
string withExtra0,
Action<SerializedProperty, SerializedProperty> drawer)
{
if (!ValidateProperties(property, withExtra0))
{
return;
}
Hide(withExtra0);
Draw(property, p =>
{
drawer(p,
_serializedObject.FindProperty(withExtra0));
});
}
public void Draw(string property,
string withExtra0,
string withExtra1,
Action<SerializedProperty, SerializedProperty, SerializedProperty> drawer)
{
if (!ValidateProperties(property, withExtra0, withExtra1))
{
return;
}
Hide(withExtra0);
Hide(withExtra1);
Draw(property, p =>
{
drawer(p,
_serializedObject.FindProperty(withExtra0),
_serializedObject.FindProperty(withExtra1));
});
}
public void Draw(string property,
string withExtra0,
string withExtra1,
string withExtra2,
Action<SerializedProperty, SerializedProperty, SerializedProperty, SerializedProperty>
drawer)
{
if (!ValidateProperties(property, withExtra0, withExtra1, withExtra2))
{
return;
}
Hide(withExtra0);
Hide(withExtra1);
Hide(withExtra2);
Draw(property, p =>
{
drawer(p,
_serializedObject.FindProperty(withExtra0),
_serializedObject.FindProperty(withExtra1),
_serializedObject.FindProperty(withExtra2));
});
}
public void Draw(string property,
string withExtra0,
string withExtra1,
string withExtra2,
string withExtra3,
Action<SerializedProperty, SerializedProperty, SerializedProperty, SerializedProperty,
SerializedProperty> drawer)
{
if (!ValidateProperties(property, withExtra0, withExtra1, withExtra2, withExtra3))
{
return;
}
Hide(withExtra0);
Hide(withExtra1);
Hide(withExtra2);
Hide(withExtra3);
Draw(property, p =>
{
drawer(p,
_serializedObject.FindProperty(withExtra0),
_serializedObject.FindProperty(withExtra1),
_serializedObject.FindProperty(withExtra2),
_serializedObject.FindProperty(withExtra3));
});
}
#endregion
#region IMPLEMENTATION
/// <summary>
/// Indicates if the property in the serializedObject
/// has been assigned to a section
/// </summary>
/// <param name="property">The name of the property in the serialized object</param>
/// <returns>True if the property has been added to a section</returns>
public bool IsInSection(string property)
{
return _skipProperties.Contains(property);
}
/// <summary>
/// Indicates if the property in the serializedObject
/// needs to be hidden.
/// Hidden properties are typically drawn in a custom way
/// so they don't need to be drawn with the default methods.
/// </summary>
/// <param name="property">The name of the property in the serialized object</param>
/// <returns>True if the property has been hidden</returns>
public bool IsHidden(string property)
{
return _hiddenProperties.Contains(property);
}
/// <summary>
/// Draws all the visible (non hidden)properties in the serialized
/// object following this order:
/// First all properties that has not been added to sections,
/// in the order they appear in the Component.
/// Then all the sections (indented and in foldouts) in the order
/// they were created, with the internal properties ordered in the
/// order the properties were added to the section.
///
/// If a special property drawer was specified it will use it when
/// drawing said property.
///
/// If a property was hidden, it will not present it in any section.
/// </summary>
public void DrawFullInspector()
{
SerializedProperty it = _serializedObject.GetIterator();
it.NextVisible(enterChildren: true);
//Draw script header
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.PropertyField(it);
EditorGUI.EndDisabledGroup();
EditorGUI.BeginChangeCheck();
while (it.NextVisible(enterChildren: false))
{
//Don't draw skip properties in this pass, we will draw them after everything else
if (IsInSection(it.name))
{
continue;
}
DrawProperty(it);
}
foreach (string sectionKey in _orderedSections)
{
DrawSection(sectionKey);
}
_serializedObject.ApplyModifiedProperties();
}
/// <summary>
/// Draws all the properties in the section in the order
/// they were added.
/// </summary>
/// <param name="sectionName">The name of the section</param>
public void DrawSection(string sectionName)
{
Section section = _sections[sectionName];
DrawSection(section);
}
private void DrawSection(Section section)
{
if (!section.HasSomethingToDraw())
{
return;
}
if (section.isFoldout)
{
section.foldout = EditorGUILayout.Foldout(section.foldout, section.title);
if (!section.foldout)
{
return;
}
EditorGUI.indentLevel++;
}
foreach (string prop in section.properties)
{
DrawProperty(_serializedObject.FindProperty(prop));
}
if (section.isFoldout)
{
EditorGUI.indentLevel--;
}
}
private void DrawProperty(SerializedProperty property)
{
try
{
//Don't draw hidden properties
if (IsHidden(property.name))
{
return;
}
//Then draw the property itself, using a custom drawer if needed
Action<SerializedProperty> customDrawer;
if (_customDrawers.TryGetValue(property.name, out customDrawer))
{
customDrawer(property);
}
else
{
EditorGUILayout.PropertyField(property, includeChildren: true);
}
}
catch (Exception e)
{
Debug.LogError($"Error drawing property {e.Message}");
}
}
private bool ValidateProperties(params string[] properties)
{
foreach (var property in properties)
{
if (_serializedObject.FindProperty(property) == null)
{
Debug.LogWarning(
$"Could not find property {property}, maybe it was deleted or renamed?");
return false;
}
}
return true;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e06bc013299e1d04a83a12b9cbfd0162
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6b401f94f5142cf4382bb48278e4f63f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c93e89dd55b481b4aa6ed62d6c8227bd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,103 @@
/*
* 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 Oculus.Interaction.Editor;
using Oculus.Interaction.Input;
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Oculus.Interaction.GrabAPI
{
[CustomPropertyDrawer(typeof(GrabbingRule))]
public class GrabbingRuleEditor : PropertyDrawer
{
private static Dictionary<string, bool> _unfolds = new Dictionary<string, bool>();
private static readonly string[] FINGER_PROPERTY_NAMES = new string[]
{
"_thumbRequirement",
"_indexRequirement",
"_middleRequirement",
"_ringRequirement",
"_pinkyRequirement",
};
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
InitializeUnfold(property);
if (_unfolds[property.propertyPath])
{
return EditorConstants.ROW_HEIGHT * (Constants.NUM_FINGERS + 2);
}
else
{
return EditorConstants.ROW_HEIGHT * 1;
}
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
InitializeUnfold(property);
Rect rowRect = new Rect(position.x, position.y, position.width, EditorConstants.ROW_HEIGHT);
_unfolds[property.propertyPath] = EditorGUI.Foldout(rowRect, _unfolds[property.propertyPath], label, true);
if (_unfolds[property.propertyPath])
{
EditorGUI.indentLevel++;
for (int i = 0; i < Constants.NUM_FINGERS; i++)
{
rowRect.y += EditorConstants.ROW_HEIGHT;
SerializedProperty finger = property.FindPropertyRelative(FINGER_PROPERTY_NAMES[i]);
HandFinger fingerID = (HandFinger)i;
FingerRequirement current = (FingerRequirement)finger.intValue;
FingerRequirement selected = (FingerRequirement)EditorGUI.EnumPopup(rowRect, $"{fingerID}: ", current);
finger.intValue = (int)selected;
}
rowRect.y += EditorConstants.ROW_HEIGHT;
DrawFlagProperty<FingerUnselectMode>(property, rowRect, "Unselect Mode", "_unselectMode", false);
EditorGUI.indentLevel--;
}
EditorGUI.EndProperty();
}
private void InitializeUnfold(SerializedProperty property)
{
if (!_unfolds.ContainsKey(property.propertyPath))
{
_unfolds.Add(property.propertyPath, false);
}
}
private void DrawFlagProperty<TEnum>(SerializedProperty parentProperty, Rect position, string title, string fieldName, bool isFlags) where TEnum : Enum
{
SerializedProperty fieldProperty = parentProperty.FindPropertyRelative(fieldName);
TEnum value = (TEnum)Enum.ToObject(typeof(TEnum), fieldProperty.intValue);
Enum selectedValue = isFlags ?
EditorGUI.EnumFlagsField(position, title, value)
: EditorGUI.EnumPopup(position, title, value);
fieldProperty.intValue = (int)Enum.ToObject(typeof(TEnum), selectedValue);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 17f32e95fcaa23e45a5ac1297f201be2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f0d69d4b88de08343adc54816c3220a1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,56 @@
/*
* 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 Oculus.Interaction.Editor;
using UnityEditor;
namespace Oculus.Interaction.HandGrab.Editor
{
[CanEditMultipleObjects]
[CustomEditor(typeof(DistanceHandGrabInteractable))]
public partial class DistanceHandGrabInteractableEditor : SimplifiedEditor
{
private DistanceHandGrabInteractable _target;
private HandGrabScaleKeysEditor<DistanceHandGrabInteractable> _listDrawer;
private void Awake()
{
_target = target as DistanceHandGrabInteractable;
}
protected override void OnEnable()
{
base.OnEnable();
_listDrawer = new HandGrabScaleKeysEditor<DistanceHandGrabInteractable>(serializedObject,
_target.HandGrabPoses, "_handGrabPoses", true);
_editorDrawer.Draw("_handGrabPoses", (modeProp) =>
{
_listDrawer.DrawInspector();
});
}
protected override void OnDisable()
{
base.OnDisable();
_listDrawer.TearDown();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fbb2e01d7c7a6904eaa71da7a854bfad
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,50 @@
/*
* 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 UnityEditor;
using UnityEngine;
using Oculus.Interaction.HandGrab.Visuals;
namespace Oculus.Interaction.HandGrab.Editor
{
public class HandGhostProviderUtils
{
public static bool TryGetDefaultProvider(out HandGhostProvider provider)
{
provider = null;
HandGhostProvider[] providers = Resources.FindObjectsOfTypeAll<HandGhostProvider>();
if (providers != null && providers.Length > 0)
{
provider = providers[0];
return true;
}
string[] assets = AssetDatabase.FindAssets($"t:{nameof(HandGhostProvider)}");
if (assets != null && assets.Length > 0)
{
string pathPath = AssetDatabase.GUIDToAssetPath(assets[0]);
provider = AssetDatabase.LoadAssetAtPath<HandGhostProvider>(pathPath);
}
return provider != null;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 62c8971cfb0b66040a9845cfc622ed6a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,85 @@
/*
* 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 Oculus.Interaction.Editor;
using UnityEditor;
using UnityEngine;
namespace Oculus.Interaction.HandGrab.Editor
{
[CanEditMultipleObjects]
[CustomEditor(typeof(HandGrabInteractable))]
public partial class HandGrabInteractableEditor : SimplifiedEditor
{
private HandGrabInteractable _target;
private HandGrabScaleKeysEditor<HandGrabInteractable> _listDrawer;
private void Awake()
{
_target = target as HandGrabInteractable;
}
protected override void OnEnable()
{
base.OnEnable();
_listDrawer = new HandGrabScaleKeysEditor<HandGrabInteractable>(serializedObject,
_target.HandGrabPoses, "_handGrabPoses", true);
_editorDrawer.Draw("_handGrabPoses", (modeProp) =>
{
_listDrawer.DrawInspector();
});
}
protected override void OnDisable()
{
base.OnDisable();
_listDrawer.TearDown();
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
if (GUILayout.Button("Create Mirrored HandGrabInteractable"))
{
Mirror();
}
}
private void Mirror()
{
HandGrabInteractable mirrorInteractable =
HandGrabUtils.CreateHandGrabInteractable(_target.RelativeTo,
$"{_target.gameObject.name}_mirror");
var data = HandGrabUtils.SaveData(_target);
data.poses = null;
HandGrabUtils.LoadData(mirrorInteractable, data);
foreach (HandGrabPose point in _target.HandGrabPoses)
{
HandGrabPose mirrorPose = HandGrabUtils.CreateHandGrabPose(mirrorInteractable.transform,
mirrorInteractable.RelativeTo);
HandGrabUtils.MirrorHandGrabPose(point, mirrorPose, _target.RelativeTo);
mirrorPose.transform.SetParent(mirrorInteractable.transform);
mirrorInteractable.HandGrabPoses.Add(mirrorPose);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e5ce7770848930447885c9abba8bb99a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,273 @@
/*
* 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 Oculus.Interaction.Editor;
using Oculus.Interaction.HandGrab.Visuals;
using Oculus.Interaction.Input;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Oculus.Interaction.HandGrab.Editor
{
[CustomEditor(typeof(HandGrabPose))]
public class HandGrabPoseEditor : UnityEditor.Editor
{
private HandGrabPose _handGrabPose;
private HandGhostProvider _ghostVisualsProvider;
private HandGhost _handGhost;
private Handedness _lastHandedness;
private Transform _relativeTo;
private int _editMode = 0;
private SerializedProperty _handPoseProperty;
private SerializedProperty _relativeToProperty;
private const float GIZMO_SCALE = 0.005f;
private static readonly string[] EDIT_MODES = new string[] { "Edit fingers", "Follow Surface" };
private void Awake()
{
_handGrabPose = target as HandGrabPose;
}
private void OnEnable()
{
_handPoseProperty = serializedObject.FindProperty("_handPose");
_relativeToProperty = serializedObject.FindProperty("_relativeTo");
AssignMissingGhostProvider();
}
private void OnDisable()
{
DestroyGhost();
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
_relativeTo = _relativeToProperty.objectReferenceValue as Transform;
if (_handGrabPose.UsesHandPose())
{
EditorGUILayout.PropertyField(_handPoseProperty);
}
GUIStyle boldStyle = new GUIStyle(GUI.skin.label) { fontStyle = FontStyle.Bold };
EditorGUILayout.LabelField("Interactive Edition (Editor only)", boldStyle);
if (_handGrabPose.UsesHandPose())
{
DrawGhostMenu(_handGrabPose.HandPose);
}
else
{
DestroyGhost();
}
serializedObject.ApplyModifiedProperties();
}
private void DrawGhostMenu(HandPose handPose)
{
HandGhostProvider provider = EditorGUILayout.ObjectField("Ghost Provider", _ghostVisualsProvider, typeof(HandGhostProvider), false) as HandGhostProvider;
if (_handGhost == null
|| _ghostVisualsProvider != provider
|| _lastHandedness != handPose.Handedness)
{
RegenerateGhost(provider);
}
_ghostVisualsProvider = provider;
_lastHandedness = handPose.Handedness;
if (_handGrabPose.SnapSurface == null)
{
_editMode = 0;
}
else
{
_editMode = GUILayout.Toolbar(_editMode, EDIT_MODES);
}
}
public void OnSceneGUI()
{
if (SceneView.currentDrawingSceneView == null)
{
return;
}
if (_handGhost == null)
{
return;
}
if (_editMode == 0)
{
GhostEditFingers();
}
else if (_editMode == 1)
{
GhostFollowSurface();
}
}
#region ghost
private void AssignMissingGhostProvider()
{
if (_ghostVisualsProvider != null)
{
return;
}
HandGhostProviderUtils.TryGetDefaultProvider(out _ghostVisualsProvider);
}
private void RegenerateGhost(HandGhostProvider provider)
{
DestroyGhost();
CreateGhost();
}
private void CreateGhost()
{
if (_ghostVisualsProvider == null)
{
return;
}
Transform relativeTo = _handGrabPose.RelativeTo;
HandGhost ghostPrototype = _ghostVisualsProvider.GetHand(_handGrabPose.HandPose.Handedness);
_handGhost = GameObject.Instantiate(ghostPrototype, _handGrabPose.transform);
_handGhost.gameObject.hideFlags = HideFlags.HideAndDontSave;
Pose relativePose = _handGrabPose.RelativePose;
Pose pose = PoseUtils.GlobalPoseScaled(relativeTo, relativePose);
_handGhost.SetPose(_handGrabPose.HandPose, pose);
}
private void DestroyGhost()
{
if (_handGhost == null)
{
return;
}
GameObject.DestroyImmediate(_handGhost.gameObject);
}
private void GhostFollowSurface()
{
if (_handGhost == null)
{
return;
}
Pose ghostTargetPose = _handGrabPose.RelativePose;
if (_handGrabPose.SnapSurface != null)
{
Vector3 mousePosition = Event.current.mousePosition;
Ray ray = HandleUtility.GUIPointToWorldRay(mousePosition);
if (_handGrabPose.SnapSurface.CalculateBestPoseAtSurface(ray, out Pose poseInSurface, _relativeTo))
{
ghostTargetPose = PoseUtils.DeltaScaled(_relativeTo, poseInSurface);
}
}
_handGhost.SetRootPose(ghostTargetPose, _relativeTo);
Handles.color = EditorConstants.PRIMARY_COLOR_DISABLED;
Handles.DrawSolidDisc(_handGhost.transform.position, _handGhost.transform.right, 0.01f);
}
private void GhostEditFingers()
{
HandPuppet puppet = _handGhost.GetComponent<HandPuppet>();
if (puppet != null && puppet.JointMaps != null)
{
DrawBonesRotator(puppet.JointMaps);
}
}
private void DrawBonesRotator(List<HandJointMap> bones)
{
bool anyChanged = false;
for (int i = 0; i < FingersMetadata.HAND_JOINT_IDS.Length; i++)
{
bool changed = false;
HandJointId joint = FingersMetadata.HAND_JOINT_IDS[i];
HandFinger finger = FingersMetadata.JOINT_TO_FINGER[(int)joint];
if (_handGrabPose.HandPose.FingersFreedom[(int)finger] == JointFreedom.Free)
{
continue;
}
HandJointMap jointMap = bones.Find(b => b.id == joint);
if (jointMap == null)
{
continue;
}
Transform transform = jointMap.transform;
transform.localRotation = jointMap.RotationOffset * _handGrabPose.HandPose.JointRotations[i];
float scale = GIZMO_SCALE * _handGrabPose.transform.lossyScale.x;
Handles.color = EditorConstants.PRIMARY_COLOR;
Quaternion entryRotation = transform.rotation;
Quaternion rotation = Handles.Disc(entryRotation, transform.position,
transform.forward, scale, false, 0);
if (rotation != entryRotation)
{
changed = true;
}
if (FingersMetadata.HAND_JOINT_CAN_SPREAD[i])
{
Handles.color = EditorConstants.SECONDARY_COLOR;
Quaternion curlRotation = rotation;
rotation = Handles.Disc(curlRotation, transform.position,
transform.up, scale, false, 0);
if (rotation != curlRotation)
{
changed = true;
}
}
if (!changed)
{
continue;
}
transform.rotation = rotation;
Undo.RecordObject(_handGrabPose, "Bone Rotation");
_handGrabPose.HandPose.JointRotations[i] = jointMap.TrackedRotation;
anyChanged = true;
}
if (anyChanged)
{
EditorUtility.SetDirty(_handGrabPose);
}
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 92b8a050249b4ea47b9da8fe38ed12a1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,324 @@
/*
* 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 Oculus.Interaction.HandGrab.Visuals;
using Oculus.Interaction.Input;
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace Oculus.Interaction.HandGrab.Editor
{
public class HandGrabScaleKeysEditor<TInteractable>
where TInteractable : MonoBehaviour, IRelativeToRef
{
private MonoBehaviour _target;
private SerializedObject _serializedObject;
private List<HandGrabPose> _handGrabPoses;
private bool _markAsOptional;
private SerializedProperty _posesProperty;
private ReorderableList _list;
private HashSet<float> _scalesSet = new HashSet<float>();
private IRelativeToRef _relativeToRef;
private HandGhostProvider _ghostVisualsProvider;
private HandGhost _handGhost;
private HandPose _ghostHandPose = new HandPose();
private Handedness _lastHandedness;
private float _handScale = 1f;
private const float POSE_RECT_WIDTH = 40f;
private const float LEFT_MARGIN = 15;
private const float RIGHT_MARGIN = 65f;
private const float MIN_SCALE = 0.5f;
private const float MAX_SCALE = 2f;
public HandGrabScaleKeysEditor(SerializedObject serializedObject,
List<HandGrabPose> handGrabPoses, string collectionName, bool markAsOptional)
{
_target = serializedObject.targetObject as MonoBehaviour;
_relativeToRef = serializedObject.targetObject as IRelativeToRef;
_posesProperty = serializedObject.FindProperty(collectionName);
_handGrabPoses = handGrabPoses;
_markAsOptional = markAsOptional;
HandGhostProviderUtils.TryGetDefaultProvider(out _ghostVisualsProvider);
_list = new ReorderableList(serializedObject, _posesProperty,
draggable: true, displayHeader: false,
displayAddButton: true, displayRemoveButton: true);
InitializeListDrawers(_list);
}
public void TearDown()
{
DestroyGhost();
}
public void DrawInspector()
{
EditorGUILayout.LabelField($"{(_markAsOptional ? "[Optional]" : "")} Scaled Hand Grab Poses");
Rect startRect = GUILayoutUtility.GetLastRect();
_list.DoLayoutList();
ScaledHandPoseSlider();
CheckUniqueScales();
CheckUniqueHandedness();
DrawGenerationMenu();
Rect endRect = GUILayoutUtility.GetLastRect();
DrawBox(
new Vector2(startRect.position.x, startRect.position.y + startRect.size.y),
endRect.position + endRect.size);
UpdateGhost();
}
private void DrawBox(Vector2 start, Vector2 end)
{
float margin = 5f;
float height = end.y - start.y;
Rect topBar = new Rect(start.x - margin, start.y,
margin, 1f);
Rect bottomBar = new Rect(start.x- margin, end.y,
margin, 1f);
Rect leftBar = new Rect(start.x - margin, start.y,
1f, height);
EditorGUI.DrawRect(topBar, Color.gray);
EditorGUI.DrawRect(bottomBar, Color.gray);
EditorGUI.DrawRect(leftBar, Color.gray);
}
private void InitializeListDrawers(ReorderableList list)
{
list.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
{
rect.height = EditorGUIUtility.singleLineHeight;
SerializedProperty itemProperty = list.serializedProperty.GetArrayElementAtIndex(index);
HandGrabPose grabPose = itemProperty.objectReferenceValue as HandGrabPose;
float scale = float.NaN;
if (grabPose != null)
{
scale = grabPose.transform.lossyScale.x /
_relativeToRef.RelativeTo.lossyScale.x;
}
GUIContent objectLabel = new GUIContent($"Scale x{scale.ToString("F2")}");
EditorGUI.PropertyField(rect, itemProperty, objectLabel);
};
}
private void ScaledHandPoseSlider()
{
float minScale = MIN_SCALE;
float maxScale = MAX_SCALE;
for (int i = 0; i < _handGrabPoses.Count; i++)
{
HandGrabPose grabPose = _handGrabPoses[i];
float scale = grabPose.transform.lossyScale.x;
if (scale < minScale)
{
minScale = scale;
}
if (scale > maxScale)
{
maxScale = scale;
}
}
_handScale = EditorGUILayout.Slider(_handScale, minScale, maxScale);
Rect backRect = GUILayoutUtility.GetLastRect();
backRect.x += LEFT_MARGIN;
backRect.width -= RIGHT_MARGIN;
for (int i = 0; i < _handGrabPoses.Count; i++)
{
HandGrabPose grabPose = _handGrabPoses[i];
if (grabPose == null)
{
continue;
}
float x = backRect.x + Mathf.InverseLerp(minScale, maxScale, grabPose.transform.lossyScale.x) * backRect.width;
Rect poseRect = new Rect(x - POSE_RECT_WIDTH * 0.5f, backRect.y, POSE_RECT_WIDTH, backRect.height);
EditorGUI.LabelField(poseRect, EditorGUIUtility.IconContent("curvekeyframeselected"));
}
}
private void CheckUniqueScales()
{
_scalesSet.Clear();
for (int i = 0; i < _handGrabPoses.Count; i++)
{
HandGrabPose grabPose = _handGrabPoses[i];
if (grabPose == null)
{
continue;
}
float scale = grabPose.transform.lossyScale.x /
_relativeToRef.RelativeTo.lossyScale.x;
if (_scalesSet.Contains(scale))
{
EditorGUILayout.HelpBox(
$"Duplicated {nameof(HandGrabPose)} of scale {scale} at index {i}.",
MessageType.Warning);
}
_scalesSet.Add(scale);
}
}
private void CheckUniqueHandedness()
{
bool handednessSet = false;
Handedness validHandedness = Handedness.Left;
for (int i = 0; i < _handGrabPoses.Count; i++)
{
HandGrabPose grabPose = _handGrabPoses[i];
if (grabPose == null || grabPose.HandPose == null)
{
continue;
}
Handedness grabPoseHandedness = grabPose.HandPose.Handedness;
if (!handednessSet)
{
handednessSet = true;
validHandedness = grabPoseHandedness;
}
else if (grabPoseHandedness != validHandedness)
{
EditorGUILayout.HelpBox($"Different Handedness at index {i}. " +
$"Ensure all HandGrabPoses have the same Handedness", MessageType.Warning);
}
}
}
private void DrawGenerationMenu()
{
if (GUILayout.Button($"Add HandGrabPose Key with Scale {_handScale.ToString("F2")}"))
{
AddHandGrabPose(_handScale);
}
if (GUILayout.Button("Refresh HandGrab Poses"))
{
RefreshHandPoses();
}
}
private void AddHandGrabPose(float scale)
{
HandGrabPose handGrabPose = HandGrabUtils.CreateHandGrabPose(_target.transform,
_relativeToRef.RelativeTo);
float relativeScale = scale / _relativeToRef.RelativeTo.lossyScale.x;
bool rangeFound = GrabPoseFinder.FindInterpolationRange(relativeScale, _handGrabPoses,
out HandGrabPose from, out HandGrabPose to, out float t);
handGrabPose.transform.localScale = Vector3.one * relativeScale;
_handGrabPoses.Add(handGrabPose);
EditorUtility.SetDirty(_target);
if (!rangeFound)
{
return;
}
Pose relativePose = Pose.identity;
PoseUtils.Lerp(from.RelativePose, to.RelativePose, t, ref relativePose);
Pose rootPose = PoseUtils.GlobalPoseScaled(_relativeToRef.RelativeTo, relativePose);
HandPose resultHandPose = new HandPose(from.HandPose);
HandPose.Lerp(from.HandPose, to.HandPose, t, ref resultHandPose);
Grab.GrabSurfaces.IGrabSurface surface = from.SnapSurface?.CreateDuplicatedSurface(handGrabPose.gameObject);
handGrabPose.InjectAllHandGrabPose(_relativeToRef.RelativeTo);
handGrabPose.InjectOptionalHandPose(resultHandPose);
handGrabPose.InjectOptionalSurface(surface);
handGrabPose.transform.SetPose(rootPose);
}
private void RefreshHandPoses()
{
_handGrabPoses.Clear();
HandGrabPose[] handGrabPoses = _target.GetComponentsInChildren<HandGrabPose>();
_handGrabPoses.AddRange(handGrabPoses);
EditorUtility.SetDirty(_target);
}
#region Ghost
private void UpdateGhost()
{
if (_handGrabPoses.Count == 0
|| _handGrabPoses[0].HandPose == null)
{
DestroyGhost();
return;
}
Transform relativeTo = _relativeToRef.RelativeTo;
Pose rootPose = Pose.identity;
float relativeScale = _handScale / relativeTo.lossyScale.x;
bool rangeFound = GrabPoseFinder.FindInterpolationRange(relativeScale, _handGrabPoses,
out HandGrabPose from, out HandGrabPose to, out float t);
if (!rangeFound)
{
DestroyGhost();
return;
}
HandPose.Lerp(from.HandPose, to.HandPose, t, ref _ghostHandPose);
PoseUtils.Lerp(from.RelativePose, to.RelativePose, t, ref rootPose);
rootPose = PoseUtils.GlobalPoseScaled(relativeTo, rootPose);
DisplayGhost(_ghostHandPose, rootPose, relativeScale);
}
private void DisplayGhost(HandPose handPose, Pose rootPose, float scale)
{
if (_handGhost != null
&& _lastHandedness != handPose.Handedness)
{
DestroyGhost();
}
_lastHandedness = handPose.Handedness;
if (_handGhost == null)
{
HandGhost ghostPrototype = _ghostVisualsProvider.GetHand(_lastHandedness);
_handGhost = GameObject.Instantiate(ghostPrototype, _target.transform);
_handGhost.gameObject.hideFlags = HideFlags.HideAndDontSave;
}
_handGhost.transform.localScale = Vector3.one * scale;
_handGhost.SetPose(handPose, rootPose);
}
private void DestroyGhost()
{
if (_handGhost == null)
{
return;
}
GameObject.DestroyImmediate(_handGhost.gameObject);
_handGhost = null;
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c1a53d90b3c752f4f84f32e5a94ea8bc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,121 @@
/*
* 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 Oculus.Interaction.Editor;
using Oculus.Interaction.Input;
using System;
using UnityEditor;
using UnityEngine;
namespace Oculus.Interaction.HandGrab.Editor
{
[CustomPropertyDrawer(typeof(HandPose))]
public class HandPoseEditor : PropertyDrawer
{
private bool _foldedFreedom = true;
private bool _foldedRotations = false;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
float multiplier = 4;
if (_foldedFreedom)
{
multiplier += Constants.NUM_FINGERS;
}
if (_foldedRotations)
{
multiplier += FingersMetadata.HAND_JOINT_IDS.Length;
}
return EditorConstants.ROW_HEIGHT * multiplier;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
Rect labelPos = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
EditorGUI.indentLevel++;
Rect rowRect = new Rect(position.x, labelPos.y + EditorConstants.ROW_HEIGHT, position.width, EditorConstants.ROW_HEIGHT);
DrawFlagProperty<Handedness>(property, rowRect, "Handedness:", "_handedness", false);
rowRect.y += EditorConstants.ROW_HEIGHT;
rowRect = DrawFingersFreedomMenu(property, rowRect);
rowRect = DrawJointAngles(property, rowRect);
EditorGUI.indentLevel--;
EditorGUI.EndProperty();
}
private Rect DrawFingersFreedomMenu(SerializedProperty property, Rect position)
{
_foldedFreedom = EditorGUI.Foldout(position, _foldedFreedom, "Fingers Freedom", true);
position.y += EditorConstants.ROW_HEIGHT;
if (_foldedFreedom)
{
SerializedProperty fingersFreedom = property.FindPropertyRelative("_fingersFreedom");
EditorGUI.indentLevel++;
for (int i = 0; i < Constants.NUM_FINGERS; i++)
{
SerializedProperty finger = fingersFreedom.GetArrayElementAtIndex(i);
HandFinger fingerID = (HandFinger)i;
JointFreedom current = (JointFreedom)finger.intValue;
JointFreedom selected = (JointFreedom)EditorGUI.EnumPopup(position, $"{fingerID}", current);
finger.intValue = (int)selected;
position.y += EditorConstants.ROW_HEIGHT;
}
EditorGUI.indentLevel--;
}
return position;
}
private Rect DrawJointAngles(SerializedProperty property, Rect position)
{
_foldedRotations = EditorGUI.Foldout(position, _foldedRotations, "Joint Angles", true);
position.y += EditorConstants.ROW_HEIGHT;
if (_foldedRotations)
{
SerializedProperty jointRotations = property.FindPropertyRelative("_jointRotations");
EditorGUI.indentLevel++;
for (int i = 0; i < FingersMetadata.HAND_JOINT_IDS.Length; i++)
{
SerializedProperty finger = jointRotations.GetArrayElementAtIndex(i);
HandJointId jointID = FingersMetadata.HAND_JOINT_IDS[i];
Vector3 current = finger.quaternionValue.eulerAngles;
Vector3 rotation = EditorGUI.Vector3Field(position, $"{jointID}", current);
finger.quaternionValue = Quaternion.Euler(rotation);
position.y += EditorConstants.ROW_HEIGHT;
}
EditorGUI.indentLevel--;
}
return position;
}
private void DrawFlagProperty<TEnum>(SerializedProperty parentProperty, Rect position, string title, string fieldName, bool isFlags) where TEnum : Enum
{
SerializedProperty fieldProperty = parentProperty.FindPropertyRelative(fieldName);
TEnum value = (TEnum)Enum.ToObject(typeof(TEnum), fieldProperty.intValue);
Enum selectedValue = isFlags ?
EditorGUI.EnumFlagsField(position, title, value)
: EditorGUI.EnumPopup(position, title, value);
fieldProperty.intValue = (int)Enum.ToObject(typeof(TEnum), selectedValue);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 25b4f8dbe894a92489fe06cc956a42e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,83 @@
/*
* 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 UnityEditor;
using UnityEngine;
namespace Oculus.Interaction.HandGrab.Editor
{
public class HandWristOffsetUndoWizard : ScriptableWizard
{
[SerializeField]
private HandWristOffset _wristOffset;
[SerializeField]
private HandGrabPose _grabPose;
[MenuItem("Oculus/Interaction/HandWristOffset Undo Wizard")]
private static void CreateWizard()
{
ScriptableWizard.DisplayWizard<HandWristOffsetUndoWizard>("HandWristOffset Undo Wizard", "Close", "Undo Offset");
}
private void OnWizardCreate()
{
}
private void OnWizardOtherButton()
{
List<HandGrabPose> children = new List<HandGrabPose>(_grabPose.GetComponentsInChildren<HandGrabPose>());
children.Remove(_grabPose);
foreach (HandGrabPose childPoint in children)
{
if (childPoint == _grabPose)
{
continue;
}
childPoint.transform.SetParent(_grabPose.transform.parent, true);
UndoOffset(childPoint);
}
UndoOffset(_grabPose);
foreach (HandGrabPose childPoint in children)
{
childPoint.transform.SetParent(_grabPose.transform, true);
}
}
private void UndoOffset(HandGrabPose grabPose)
{
Pose offset = Pose.identity;
_wristOffset.GetOffset(ref offset, grabPose.HandPose.Handedness, grabPose.transform.localScale.x);
offset.Invert();
Undo.RecordObject(grabPose.transform, "Transform Changed");
Pose pose = grabPose.transform.GetPose(Space.Self);
pose.Premultiply(offset);
grabPose.transform.SetPose(pose, Space.Self);
EditorUtility.SetDirty(grabPose.transform);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 070da65b3b3353149bcd22ecad3b01ef
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: eef729dcc033d3e419718da8e409c0f3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,321 @@
/*
* 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 Oculus.Interaction.Editor;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Oculus.Interaction.Grab.GrabSurfaces.Editor
{
[CustomEditor(typeof(BezierGrabSurface))]
[CanEditMultipleObjects]
public class BezierGrabSurfaceEditor : UnityEditor.Editor
{
private BezierGrabSurface _surface;
private SerializedProperty _relativeToProperty;
private Transform _relativeTo;
private bool IsSelectedIndexValid => _selectedIndex >= 0
&& _selectedIndex < _surface.ControlPoints.Count;
private int _selectedIndex = -1;
private const float PICK_SIZE = 0.1f;
private const float AXIS_SIZE = 0.5f;
private const int CURVE_STEPS = 50;
private const float SEPARATION = 0.1f;
private void OnEnable()
{
_surface = (target as BezierGrabSurface);
_relativeToProperty = serializedObject.FindProperty("_relativeTo");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
_relativeTo = _relativeToProperty.objectReferenceValue as Transform;
if (_relativeTo == null)
{
return;
}
if (GUILayout.Button("Add ControlPoint At Start"))
{
AddControlPoint(true, _relativeTo);
}
if (GUILayout.Button("Add ControlPoint At End"))
{
AddControlPoint(false, _relativeTo);
}
if (!IsSelectedIndexValid)
{
_selectedIndex = -1;
GUILayout.Label($"No Selected Point");
}
else
{
GUILayout.Label($"Selected Point: {_selectedIndex}");
if (GUILayout.Button("Align Selected Tangent"))
{
AlignTangent(_selectedIndex, _relativeTo);
}
if (GUILayout.Button("Smooth Selected Tangent"))
{
SmoothTangent(_selectedIndex, _relativeTo);
}
}
serializedObject.ApplyModifiedProperties();
}
public void OnSceneGUI()
{
if (_relativeTo == null)
{
return;
}
Handles.color = EditorConstants.PRIMARY_COLOR;
DrawEndsCaps(_surface.ControlPoints, _relativeTo);
if (Event.current.type == EventType.Repaint)
{
DrawCurve(_surface.ControlPoints, _relativeTo);
}
}
private void AddControlPoint(bool addFirst, Transform relativeTo)
{
BezierControlPoint controlPoint = new BezierControlPoint();
if (_surface.ControlPoints.Count == 0)
{
Pose pose = _surface.transform.GetPose();
controlPoint.SetPose(pose, relativeTo);
_surface.ControlPoints.Add(controlPoint);
_selectedIndex = 0;
return;
}
else if (_surface.ControlPoints.Count == 1)
{
controlPoint = _surface.ControlPoints[0];
Pose pose = controlPoint.GetPose(relativeTo);
pose.position += relativeTo.forward * SEPARATION;
controlPoint.SetPose(pose, relativeTo);
}
else if (_surface.ControlPoints.Count > 1)
{
BezierControlPoint firstControlPoint;
BezierControlPoint secondControlPoint;
if (addFirst)
{
firstControlPoint = _surface.ControlPoints[1];
secondControlPoint = _surface.ControlPoints[0];
}
else
{
firstControlPoint = _surface.ControlPoints[_surface.ControlPoints.Count - 2];
secondControlPoint = _surface.ControlPoints[_surface.ControlPoints.Count - 1];
}
Pose firstPose = firstControlPoint.GetPose(relativeTo);
Pose secondPose = secondControlPoint.GetPose(relativeTo);
Pose controlPointPose;
controlPointPose.position = 2 * secondPose.position - firstPose.position;
controlPointPose.rotation = secondPose.rotation;
controlPoint.SetPose(controlPointPose, relativeTo);
}
if (addFirst)
{
_surface.ControlPoints.Insert(0, controlPoint);
_selectedIndex = 0;
AlignTangent(0, relativeTo);
}
else
{
_surface.ControlPoints.Add(controlPoint);
_selectedIndex = _surface.ControlPoints.Count - 1;
AlignTangent(_selectedIndex - 1, relativeTo);
}
}
private void AlignTangent(int index, Transform relativeTo)
{
BezierControlPoint controlPoint = _surface.ControlPoints[index];
BezierControlPoint nextControlPoint = _surface.ControlPoints[(index + 1) % _surface.ControlPoints.Count];
Vector3 tangent = (nextControlPoint.GetPose(relativeTo).position + controlPoint.GetPose(relativeTo).position) * 0.5f;
controlPoint.SetTangent(tangent, relativeTo);
_surface.ControlPoints[index] = controlPoint;
}
private void SmoothTangent(int index, Transform relativeTo)
{
BezierControlPoint controlPoint = _surface.ControlPoints[index];
BezierControlPoint prevControlPoint = _surface.ControlPoints[(index + _surface.ControlPoints.Count - 1) % _surface.ControlPoints.Count];
Vector3 tangent = prevControlPoint.GetTangent(relativeTo);
tangent = (controlPoint.GetPose(relativeTo).position - tangent) * 0.5f;
controlPoint.SetTangent(tangent, relativeTo);
_surface.ControlPoints[index] = controlPoint;
}
private void DrawEndsCaps(List<BezierControlPoint> controlPoints, Transform relativeTo)
{
Handles.color = EditorConstants.PRIMARY_COLOR;
for (int i = 0; i < controlPoints.Count; i++)
{
DrawControlPoint(i, relativeTo);
}
Handles.color = EditorConstants.PRIMARY_COLOR_DISABLED;
if (IsSelectedIndexValid)
{
DrawControlPointHandles(_selectedIndex, relativeTo);
DrawTangentLine(_selectedIndex, relativeTo);
}
}
private void DrawCurve(List<BezierControlPoint> controlPoints, Transform relativeTo)
{
Handles.color = EditorConstants.PRIMARY_COLOR;
for (int i = 0; i < controlPoints.Count && controlPoints.Count > 1; i++)
{
BezierControlPoint fromControlPoint = _surface.ControlPoints[i];
Pose from = fromControlPoint.GetPose(relativeTo);
BezierControlPoint toControlPoint = _surface.ControlPoints[(i + 1) % controlPoints.Count];
if (toControlPoint.Disconnected)
{
continue;
}
Pose to = toControlPoint.GetPose(relativeTo);
Vector3 tangent = fromControlPoint.GetTangent(relativeTo);
DrawBezier(from.position, tangent, to.position, CURVE_STEPS);
}
}
private void DrawBezier(Vector3 start, Vector3 middle, Vector3 end, int steps)
{
Vector3 from = start;
Vector3 to;
float t;
for (int i = 1; i < steps; i++)
{
t = i / (steps - 1f);
to = BezierGrabSurface.EvaluateBezier(start, middle, end, t);
#if UNITY_2020_2_OR_NEWER
Handles.DrawLine(from, to, EditorConstants.LINE_THICKNESS);
#else
Handles.DrawLine(from, to);
#endif
from = to;
}
}
private void DrawTangentLine(int index, Transform relativeTo)
{
BezierControlPoint controlPoint = _surface.ControlPoints[index];
Pose pose = controlPoint.GetPose(relativeTo);
Vector3 center = pose.position;
Vector3 tangent = controlPoint.GetTangent(relativeTo);
#if UNITY_2020_2_OR_NEWER
Handles.DrawLine(center, tangent, EditorConstants.LINE_THICKNESS);
#else
Handles.DrawLine(center, tangent);
#endif
}
private void DrawControlPoint(int index, Transform relativeTo)
{
BezierControlPoint controlPoint = _surface.ControlPoints[index];
Pose pose = controlPoint.GetPose(relativeTo);
float handleSize = HandleUtility.GetHandleSize(pose.position);
Handles.color = EditorConstants.PRIMARY_COLOR;
if (Handles.Button(pose.position, pose.rotation, handleSize * PICK_SIZE, handleSize * PICK_SIZE, Handles.DotHandleCap))
{
_selectedIndex = index;
}
Handles.color = Color.red;
Handles.DrawLine(pose.position, pose.position + pose.right * handleSize * AXIS_SIZE);
Handles.color = Color.green;
Handles.DrawLine(pose.position, pose.position + pose.up * handleSize * AXIS_SIZE);
Handles.color = Color.blue;
Handles.DrawLine(pose.position, pose.position + pose.forward * handleSize * AXIS_SIZE);
}
private void DrawControlPointHandles(int index, Transform relativeTo)
{
BezierControlPoint controlPoint = _surface.ControlPoints[index];
Pose pose = controlPoint.GetPose(relativeTo);
if (Tools.current == Tool.Move)
{
EditorGUI.BeginChangeCheck();
Quaternion pointRotation = Tools.pivotRotation == PivotRotation.Global ? Quaternion.identity : pose.rotation;
pose.position = Handles.PositionHandle(pose.position, pointRotation);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(_surface, "Change ControlPoint Position");
controlPoint.SetPose(pose, relativeTo);
_surface.ControlPoints[index] = controlPoint;
}
}
else if (Tools.current == Tool.Rotate)
{
Quaternion originalRotation = pose.rotation;
if (Tools.pivotRotation == PivotRotation.Global)
{
Quaternion offset = Handles.RotationHandle(Quaternion.identity, pose.position);
pose.rotation = offset * pose.rotation;
}
else
{
pose.rotation = Handles.RotationHandle(pose.rotation, pose.position);
}
pose.rotation.Normalize();
if (originalRotation != pose.rotation)
{
Undo.RecordObject(_surface, "Change ControlPoint Rotation");
controlPoint.SetPose(pose, relativeTo);
_surface.ControlPoints[index] = controlPoint;
}
}
Vector3 tangent = controlPoint.GetTangent(relativeTo);
Quaternion tangentRotation = Tools.pivotRotation == PivotRotation.Global ? Quaternion.identity : pose.rotation;
EditorGUI.BeginChangeCheck();
tangent = Handles.PositionHandle(tangent, tangentRotation);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(_surface, "Change ControlPoint Tangent");
controlPoint.SetTangent(tangent, relativeTo);
_surface.ControlPoints[index] = controlPoint;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 718688abdc60caa4984fe98623ff42dd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,193 @@
/*
* 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 Oculus.Interaction.Editor;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Oculus.Interaction.Grab.GrabSurfaces.Editor
{
[CustomEditor(typeof(BoxGrabSurface))]
[CanEditMultipleObjects]
public class BoxGrabSurfaceEditor : UnityEditor.Editor
{
private BoxBoundsHandle _boxHandle = new BoxBoundsHandle();
private BoxGrabSurface _surface;
private Transform _relativeTo;
private SerializedProperty _relativeToProperty;
private void OnEnable()
{
_boxHandle.handleColor = EditorConstants.PRIMARY_COLOR;
_boxHandle.wireframeColor = EditorConstants.PRIMARY_COLOR_DISABLED;
_boxHandle.axes = PrimitiveBoundsHandle.Axes.X | PrimitiveBoundsHandle.Axes.Z;
_surface = (target as BoxGrabSurface);
_relativeToProperty = serializedObject.FindProperty("_relativeTo");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
_relativeTo = _relativeToProperty.objectReferenceValue as Transform;
}
public void OnSceneGUI()
{
if (_relativeTo == null)
{
return;
}
DrawRotator(_surface, _relativeTo);
DrawBoxEditor(_surface, _relativeTo);
DrawSlider(_surface, _relativeTo);
if (Event.current.type == EventType.Repaint)
{
DrawSnapLines(_surface, _relativeTo);
}
}
private void DrawSnapLines(BoxGrabSurface surface, Transform relativeTo)
{
Handles.color = EditorConstants.PRIMARY_COLOR;
Vector3 size = surface.GetSize(relativeTo);
Quaternion rotation = surface.GetRotation(relativeTo);
Vector3 surfacePosition = surface.GetReferencePose(relativeTo).position;
float widthOffset = surface.GetWidthOffset(relativeTo);
Vector4 snapOffset = surface.GetSnapOffset(relativeTo);
Vector3 rightAxis = rotation * Vector3.right;
Vector3 forwardAxis = rotation * Vector3.forward;
Vector3 forwardOffset = forwardAxis * size.z;
Vector3 bottomLeft = surfacePosition - rightAxis * size.x * (1f - widthOffset);
Vector3 bottomRight = surfacePosition + rightAxis * size.x * (widthOffset);
Vector3 topLeft = bottomLeft + forwardOffset;
Vector3 topRight = bottomRight + forwardOffset;
Handles.DrawLine(bottomLeft + rightAxis * snapOffset.y, bottomRight + rightAxis * snapOffset.x);
Handles.DrawLine(topLeft - rightAxis * snapOffset.x, topRight - rightAxis * snapOffset.y);
Handles.DrawLine(bottomLeft - forwardAxis * snapOffset.z, topLeft - forwardAxis * snapOffset.w);
Handles.DrawLine(bottomRight + forwardAxis * snapOffset.w, topRight + forwardAxis * snapOffset.z);
}
private void DrawSlider(BoxGrabSurface surface, Transform relativeTo)
{
Handles.color = EditorConstants.PRIMARY_COLOR;
Vector3 size = surface.GetSize(relativeTo);
Quaternion rotation = surface.GetRotation(relativeTo);
Vector3 surfacePosition = surface.GetReferencePose(relativeTo).position;
float widthOffset = surface.GetWidthOffset(relativeTo);
Vector4 snapOffset = surface.GetSnapOffset(relativeTo);
EditorGUI.BeginChangeCheck();
Vector3 rightDir = rotation * Vector3.right;
Vector3 forwardDir = rotation * Vector3.forward;
Vector3 bottomRight = surfacePosition
+ rightDir * size.x * (widthOffset);
Vector3 bottomLeft = surfacePosition
- rightDir * size.x * (1f - widthOffset);
Vector3 topRight = bottomRight + forwardDir * size.z;
Vector3 rightHandle = DrawOffsetHandle(bottomRight + rightDir * snapOffset.x, rightDir);
Vector3 leftHandle = DrawOffsetHandle(bottomLeft + rightDir * snapOffset.y, -rightDir);
Vector3 topHandle = DrawOffsetHandle(topRight + forwardDir * snapOffset.z, forwardDir);
Vector3 bottomHandle = DrawOffsetHandle(bottomRight + forwardDir * snapOffset.w, -forwardDir);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(surface, "Change Offset Box");
Vector4 offset = snapOffset;
offset.x = DistanceToHandle(bottomRight, rightHandle, rightDir);
offset.y = DistanceToHandle(bottomLeft, leftHandle, rightDir);
offset.z = DistanceToHandle(topRight, topHandle, forwardDir);
offset.w = DistanceToHandle(bottomRight, bottomHandle, forwardDir);
surface.SetSnapOffset(offset, relativeTo);
}
}
private Vector3 DrawOffsetHandle(Vector3 point, Vector3 dir)
{
float size = HandleUtility.GetHandleSize(point) * 0.2f;
return Handles.Slider(point, dir, size, Handles.ConeHandleCap, 0f);
}
private float DistanceToHandle(Vector3 origin, Vector3 handlePoint, Vector3 dir)
{
float distance = Vector3.Distance(origin, handlePoint);
if (Vector3.Dot(handlePoint - origin, dir) < 0f)
{
distance = -distance;
}
return distance;
}
private void DrawRotator(BoxGrabSurface surface, Transform relativeTo)
{
EditorGUI.BeginChangeCheck();
Quaternion rotation = Handles.RotationHandle(
surface.GetRotation(relativeTo),
surface.GetReferencePose(relativeTo).position);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(surface, "Change Rotation Box");
surface.SetRotation(rotation, relativeTo);
}
}
private void DrawBoxEditor(BoxGrabSurface surface, Transform relativeTo)
{
Quaternion rot = surface.GetRotation(relativeTo);
Vector3 size = surface.GetSize(relativeTo);
float widthOffset = surface.GetWidthOffset(relativeTo);
Vector3 snapP = surface.GetReferencePose(relativeTo).position;
_boxHandle.size = size;
float widthPos = Mathf.Lerp(-size.x * 0.5f, size.x * 0.5f, widthOffset);
_boxHandle.center = new Vector3(widthPos, 0f, size.z * 0.5f);
Matrix4x4 handleMatrix = Matrix4x4.TRS(
snapP,
rot,
Vector3.one
);
using (new Handles.DrawingScope(handleMatrix))
{
EditorGUI.BeginChangeCheck();
_boxHandle.DrawHandle();
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(surface, "Change Box Properties");
surface.SetSize(_boxHandle.size, relativeTo);
float width = _boxHandle.size.x;
if (width != 0f)
{
width = (_boxHandle.center.x + width * 0.5f) / width;
}
surface.SetWidthOffset(width, relativeTo);
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: baf3d860debef0947b62cdebdd94cb74
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,172 @@
/*
* 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 Oculus.Interaction.Editor;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Oculus.Interaction.Grab.GrabSurfaces.Editor
{
[CustomEditor(typeof(CylinderGrabSurface))]
[CanEditMultipleObjects]
public class CylinderGrabSurfaceEditor : UnityEditor.Editor
{
private const float DRAW_SURFACE_ANGULAR_RESOLUTION = 5f;
private ArcHandle _arcEndHandle = new ArcHandle();
private ArcHandle _arcStartHandle = new ArcHandle();
private Vector3[] _surfaceEdges;
private CylinderGrabSurface _surface;
private SerializedProperty _relativeToProperty;
private Transform _relativeTo;
private void OnEnable()
{
_arcStartHandle.SetColorWithRadiusHandle(EditorConstants.PRIMARY_COLOR_DISABLED, 0f);
_arcEndHandle.SetColorWithRadiusHandle(EditorConstants.PRIMARY_COLOR, 0f);
_surface = target as CylinderGrabSurface;
_relativeToProperty = serializedObject.FindProperty("_relativeTo");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
_relativeTo = _relativeToProperty.objectReferenceValue as Transform;
}
public void OnSceneGUI()
{
if (_relativeTo == null)
{
return;
}
DrawEndsCaps(_surface, _relativeTo);
float oldArcStart = _surface.ArcOffset;
Quaternion look = Quaternion.LookRotation(_surface.GetPerpendicularDir(_relativeTo), _surface.GetDirection(_relativeTo));
float newArcStart = DrawArcEditor(_surface, _arcStartHandle, _relativeTo,
oldArcStart,_surface.GetStartPoint(_relativeTo),
look);
_surface.ArcOffset = newArcStart;
_surface.ArcLength -= newArcStart - oldArcStart;
_surface.ArcLength = DrawArcEditor(_surface, _arcEndHandle, _relativeTo,
_surface.ArcLength,
_surface.GetStartPoint(_relativeTo),
Quaternion.LookRotation(_surface.GetStartArcDir(_relativeTo), _surface.GetDirection(_relativeTo)));
if (Event.current.type == EventType.Repaint)
{
DrawSurfaceVolume(_surface, _relativeTo);
}
}
private void DrawEndsCaps(CylinderGrabSurface surface, Transform relativeTo)
{
EditorGUI.BeginChangeCheck();
Quaternion handleRotation = relativeTo.rotation;
Vector3 startPosition = Handles.PositionHandle(surface.GetStartPoint(relativeTo), handleRotation);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(surface, "Change Start Cylinder Position");
surface.SetStartPoint(startPosition, relativeTo);
}
EditorGUI.BeginChangeCheck();
Vector3 endPosition = Handles.PositionHandle(surface.GetEndPoint(relativeTo), handleRotation);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(surface, "Change Start Cylinder Position");
surface.SetEndPoint(endPosition, relativeTo);
}
}
private void DrawSurfaceVolume(CylinderGrabSurface surface, Transform relativeTo)
{
Vector3 start = surface.GetStartPoint(relativeTo);
Vector3 end = surface.GetEndPoint(relativeTo);
Vector3 startArc = surface.GetStartArcDir(relativeTo);
Vector3 endArc = surface.GetEndArcDir(relativeTo);
Vector3 direction = surface.GetDirection(relativeTo);
float radius = surface.GetRadius(relativeTo);
Handles.color = EditorConstants.PRIMARY_COLOR;
Handles.DrawWireArc(end,
direction,
startArc,
surface.ArcLength,
radius);
Handles.DrawLine(start, end);
Handles.DrawLine(start, start + startArc * radius);
Handles.DrawLine(start, start + endArc * radius);
Handles.DrawLine(end, end + startArc * radius);
Handles.DrawLine(end, end + endArc * radius);
int edgePoints = Mathf.CeilToInt((2 * surface.ArcLength) / DRAW_SURFACE_ANGULAR_RESOLUTION) + 3;
if (_surfaceEdges == null
|| _surfaceEdges.Length != edgePoints)
{
_surfaceEdges = new Vector3[edgePoints];
}
Handles.color = EditorConstants.PRIMARY_COLOR_DISABLED;
int i = 0;
for (float angle = 0f; angle < surface.ArcLength; angle += DRAW_SURFACE_ANGULAR_RESOLUTION)
{
Vector3 dir = Quaternion.AngleAxis(angle, direction) * startArc;
_surfaceEdges[i++] = start + dir * radius;
_surfaceEdges[i++] = end + dir * radius;
}
_surfaceEdges[i++] = start + endArc * radius;
_surfaceEdges[i++] = end + endArc * radius;
Handles.DrawPolyLine(_surfaceEdges);
}
private float DrawArcEditor(CylinderGrabSurface surface, ArcHandle handle, Transform relativeTo,
float inputAngle, Vector3 position, Quaternion rotation)
{
handle.radius = surface.GetRadius(relativeTo);
handle.angle = inputAngle;
Matrix4x4 handleMatrix = Matrix4x4.TRS(
position,
rotation,
Vector3.one
);
using (new Handles.DrawingScope(handleMatrix))
{
EditorGUI.BeginChangeCheck();
handle.DrawHandle();
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(surface, "Change Cylinder Properties");
return handle.angle;
}
}
return inputAngle;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 69099ed7427360a4ab3734573c188647
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,97 @@
/*
* 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 Oculus.Interaction.Editor;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace Oculus.Interaction.Grab.GrabSurfaces.Editor
{
[CustomEditor(typeof(SphereGrabSurface))]
[CanEditMultipleObjects]
public class SphereGrabSurfaceEditor : UnityEditor.Editor
{
private SphereBoundsHandle _sphereHandle = new SphereBoundsHandle();
private SphereGrabSurface _surface;
private SerializedProperty _relativeToProperty;
private Transform _relativeTo;
private void OnEnable()
{
_sphereHandle.SetColor(EditorConstants.PRIMARY_COLOR);
_sphereHandle.midpointHandleDrawFunction = null;
_surface = target as SphereGrabSurface;
_relativeToProperty = serializedObject.FindProperty("_relativeTo");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
_relativeTo = _relativeToProperty.objectReferenceValue as Transform;
}
public void OnSceneGUI()
{
if (_relativeTo == null)
{
return;
}
DrawCentre(_surface, _relativeTo);
Handles.color = Color.white;
DrawSphereEditor(_surface, _relativeTo);
if (Event.current.type == EventType.Repaint)
{
DrawSurfaceVolume(_surface, _relativeTo);
}
}
private void DrawCentre(SphereGrabSurface surface, Transform relativeTo)
{
EditorGUI.BeginChangeCheck();
Quaternion handleRotation = relativeTo.rotation;
Vector3 centrePosition = Handles.PositionHandle(surface.GetCentre(relativeTo), handleRotation);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(surface, "Change Centre Sphere Position");
surface.SetCentre(centrePosition, relativeTo);
}
}
private void DrawSurfaceVolume(SphereGrabSurface surface, Transform relativeTo)
{
Handles.color = EditorConstants.PRIMARY_COLOR;
Vector3 startLine = surface.GetCentre(relativeTo);
Vector3 endLine = startLine + surface.GetDirection(relativeTo) * surface.GetRadius(relativeTo);
Handles.DrawDottedLine(startLine, endLine, 5);
}
private void DrawSphereEditor(SphereGrabSurface surface, Transform relativeTo)
{
_sphereHandle.radius = surface.GetRadius(relativeTo);
_sphereHandle.center = surface.GetCentre(relativeTo);
EditorGUI.BeginChangeCheck();
_sphereHandle.DrawHandle();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ee7657a153e652d448fa1b7775ca7f8c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5760a4e866e11cb46a5adff4e32acc44
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,122 @@
/*
* 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 Oculus.Interaction.Input;
using UnityEditor;
using UnityEngine;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System;
namespace Oculus.Interaction.HandGrab.Visuals.Editor
{
[CustomEditor(typeof(HandPuppet))]
public class HandPuppetEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
HandPuppet puppet = target as HandPuppet;
if (GUILayout.Button("Auto-Assign Bones"))
{
SkinnedMeshRenderer skinnedHand = puppet.GetComponentInChildren<SkinnedMeshRenderer>();
if (skinnedHand != null)
{
SetPrivateValue(puppet, "_jointMaps", AutoAsignBones(skinnedHand));
}
}
}
private List<HandJointMap> AutoAsignBones(SkinnedMeshRenderer skinnedHand)
{
List<HandJointMap> maps = new List<HandJointMap>();
Transform root = skinnedHand.rootBone;
Regex regEx = new Regex(@"Hand(\w*)(\d)");
foreach (var bone in FingersMetadata.HAND_JOINT_IDS)
{
Match match = regEx.Match(bone.ToString());
if (match != Match.Empty)
{
string boneName = match.Groups[1].Value.ToLower();
string boneNumber = match.Groups[2].Value;
Transform skinnedBone = RecursiveSearchForChildrenContainingPattern(root, "col", boneName, boneNumber);
if (skinnedBone != null)
{
maps.Add(new HandJointMap()
{
id = bone,
transform = skinnedBone,
rotationOffset = Vector3.zero
});
}
}
}
return maps;
}
private Transform RecursiveSearchForChildrenContainingPattern(Transform root, string ignorePattern, params string[] args)
{
if (root == null)
{
return null;
}
for (int i = 0; i < root.childCount; i++)
{
Transform child = root.GetChild(i);
string childName = child.name.ToLower();
bool shouldCheck = string.IsNullOrEmpty(ignorePattern)|| !childName.Contains(ignorePattern);
if (shouldCheck)
{
bool containsAllArgs = args.All(a => childName.Contains(a));
Transform result = containsAllArgs ? child
: RecursiveSearchForChildrenContainingPattern(child, ignorePattern, args);
if (result != null)
{
return result;
}
}
}
return null;
}
private static void SetPrivateValue(object instance, string fieldName, object value)
{
FieldInfo fieldData = GetPrivateField(instance, fieldName);
fieldData.SetValue(instance, value);
}
private static FieldInfo GetPrivateField(object instance, string fieldName)
{
Type type = instance.GetType();
FieldInfo fieldData = null;
while (type != null && fieldData == null)
{
fieldData = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
type = type.BaseType;
}
return fieldData;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fb714303644f5c343bd2c80559c02856
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6d1754bb0e390e44e906221c5091ed9a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,196 @@
/*
* 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 Oculus.Interaction.Input;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
namespace Oculus.Interaction.Hands.Editor
{
[CustomEditor(typeof(FromHandPrefabDataSource))]
public class FromHandPrefabDataSourceEditor : UnityEditor.Editor
{
private SerializedProperty _handednessProperty;
private int HandednessIdx => _handednessProperty.enumValueIndex;
private void OnEnable()
{
_handednessProperty = serializedObject.FindProperty("_handedness");
}
public override void OnInspectorGUI()
{
DrawPropertiesExcluding(serializedObject);
serializedObject.ApplyModifiedProperties();
FromHandPrefabDataSource source = (FromHandPrefabDataSource)target;
InitializeSkeleton(source);
if (GUILayout.Button("Auto Map Joints"))
{
AutoMapJoints(source);
EditorUtility.SetDirty(source);
EditorSceneManager.MarkSceneDirty(source.gameObject.scene);
}
EditorGUILayout.LabelField("Joints", EditorStyles.boldLabel);
HandJointId start = HandJointId.HandStart;
HandJointId end = HandJointId.HandEnd;
for (int i = (int)start; i < (int)end; ++i)
{
string jointName = HandJointLabelFromJointId((HandJointId)i);
source.JointTransforms[i] = (Transform)EditorGUILayout.ObjectField(jointName,
source.JointTransforms[i], typeof(Transform), true);
}
}
private static readonly string[] _fbxHandSidePrefix = { "l_", "r_" };
private static readonly string _fbxHandBonePrefix = "b_";
private static readonly string[] _fbxHandBoneNames =
{
"wrist",
"forearm_stub",
"thumb0",
"thumb1",
"thumb2",
"thumb3",
"index1",
"index2",
"index3",
"middle1",
"middle2",
"middle3",
"ring1",
"ring2",
"ring3",
"pinky0",
"pinky1",
"pinky2",
"pinky3"
};
private static readonly string[] _fbxHandFingerNames =
{
"thumb",
"index",
"middle",
"ring",
"pinky"
};
private void InitializeSkeleton(FromHandPrefabDataSource source)
{
if (source.JointTransforms.Count == 0)
{
for (int i = (int)HandJointId.HandStart; i < (int)HandJointId.HandEnd; ++i)
{
source.JointTransforms.Add(null);
}
}
}
private void AutoMapJoints(FromHandPrefabDataSource source)
{
Transform rootTransform = source.transform;
for (int i = (int)HandJointId.HandStart; i < (int)HandJointId.HandEnd; ++i)
{
string fbxBoneName = FbxBoneNameFromHandJointId((HandJointId)i);
Transform t = rootTransform.FindChildRecursive(fbxBoneName);
source.JointTransforms[i] = t;
}
}
private string FbxBoneNameFromHandJointId(HandJointId handJointId)
{
if (handJointId >= HandJointId.HandThumbTip && handJointId <= HandJointId.HandPinkyTip)
{
return _fbxHandSidePrefix[(int)HandednessIdx] + _fbxHandFingerNames[(int)handJointId - (int)HandJointId.HandThumbTip] + "_finger_tip_marker";
}
else
{
return _fbxHandBonePrefix + _fbxHandSidePrefix[(int)HandednessIdx] + _fbxHandBoneNames[(int)handJointId];
}
}
// force aliased enum values to the more appropriate value
private static string HandJointLabelFromJointId(HandJointId handJointId)
{
switch (handJointId)
{
case HandJointId.HandWristRoot:
return "HandWristRoot";
case HandJointId.HandForearmStub:
return "HandForearmStub";
case HandJointId.HandThumb0:
return "HandThumb0";
case HandJointId.HandThumb1:
return "HandThumb1";
case HandJointId.HandThumb2:
return "HandThumb2";
case HandJointId.HandThumb3:
return "HandThumb3";
case HandJointId.HandIndex1:
return "HandIndex1";
case HandJointId.HandIndex2:
return "HandIndex2";
case HandJointId.HandIndex3:
return "HandIndex3";
case HandJointId.HandMiddle1:
return "HandMiddle1";
case HandJointId.HandMiddle2:
return "HandMiddle2";
case HandJointId.HandMiddle3:
return "HandMiddle3";
case HandJointId.HandRing1:
return "HandRing1";
case HandJointId.HandRing2:
return "HandRing2";
case HandJointId.HandRing3:
return "HandRing3";
case HandJointId.HandPinky0:
return "HandPinky0";
case HandJointId.HandPinky1:
return "HandPinky1";
case HandJointId.HandPinky2:
return "HandPinky2";
case HandJointId.HandPinky3:
return "HandPinky3";
case HandJointId.HandThumbTip:
return "HandThumbTip";
case HandJointId.HandIndexTip:
return "HandIndexTip";
case HandJointId.HandMiddleTip:
return "HandMiddleTip";
case HandJointId.HandRingTip:
return "HandRingTip";
case HandJointId.HandPinkyTip:
return "HandPinkyTip";
default:
return "HandUnknown";
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7b1127eced26c0748b86cc88310b1711
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,213 @@
/*
* 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 Oculus.Interaction.Input;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
namespace Oculus.Interaction.Hands.Editor
{
[CustomEditor(typeof(HandVisual))]
public class HandVisualEditor : UnityEditor.Editor
{
private SerializedProperty _handProperty;
private SerializedProperty _rootProperty;
private IHand Hand => _handProperty.objectReferenceValue as IHand;
private void OnEnable()
{
_handProperty = serializedObject.FindProperty("_hand");
_rootProperty = serializedObject.FindProperty("_root");
}
public override void OnInspectorGUI()
{
DrawPropertiesExcluding(serializedObject);
serializedObject.ApplyModifiedProperties();
HandVisual visual = (HandVisual)target;
InitializeSkeleton(visual);
if (Hand == null)
{
return;
}
if (GUILayout.Button("Auto Map Joints"))
{
AutoMapJoints(visual);
EditorUtility.SetDirty(visual);
EditorSceneManager.MarkSceneDirty(visual.gameObject.scene);
}
EditorGUILayout.LabelField("Joints", EditorStyles.boldLabel);
HandJointId start = HandJointId.HandStart;
HandJointId end = HandJointId.HandEnd;
for (int i = (int)start; i < (int)end; ++i)
{
string jointName = HandJointLabelFromJointId((HandJointId)i);
visual.Joints[i] = (Transform)EditorGUILayout.ObjectField(jointName,
visual.Joints[i], typeof(Transform), true);
}
}
private static readonly string[] _fbxHandSidePrefix = { "l_", "r_" };
private static readonly string _fbxHandBonePrefix = "b_";
private static readonly string[] _fbxHandBoneNames =
{
"wrist",
"forearm_stub",
"thumb0",
"thumb1",
"thumb2",
"thumb3",
"index1",
"index2",
"index3",
"middle1",
"middle2",
"middle3",
"ring1",
"ring2",
"ring3",
"pinky0",
"pinky1",
"pinky2",
"pinky3"
};
private static readonly string[] _fbxHandFingerNames =
{
"thumb",
"index",
"middle",
"ring",
"pinky"
};
private void InitializeSkeleton(HandVisual visual)
{
if (visual.Joints.Count == 0)
{
for (int i = (int)HandJointId.HandStart; i < (int)HandJointId.HandEnd; ++i)
{
visual.Joints.Add(null);
}
}
}
private void AutoMapJoints(HandVisual visual)
{
if (Hand == null)
{
InitializeSkeleton(visual);
return;
}
Transform rootTransform = visual.transform;
if (_rootProperty.objectReferenceValue != null)
{
rootTransform = _rootProperty.objectReferenceValue as Transform;
}
for (int i = (int)HandJointId.HandStart; i < (int)HandJointId.HandEnd; ++i)
{
string fbxBoneName = FbxBoneNameFromHandJointId(visual, (HandJointId)i);
Transform t = rootTransform.FindChildRecursive(fbxBoneName);
visual.Joints[i] = t;
}
}
private string FbxBoneNameFromHandJointId(HandVisual visual, HandJointId handJointId)
{
if (handJointId >= HandJointId.HandThumbTip && handJointId <= HandJointId.HandPinkyTip)
{
return _fbxHandSidePrefix[(int)Hand.Handedness] + _fbxHandFingerNames[(int)handJointId - (int)HandJointId.HandThumbTip] + "_finger_tip_marker";
}
else
{
return _fbxHandBonePrefix + _fbxHandSidePrefix[(int)Hand.Handedness] + _fbxHandBoneNames[(int)handJointId];
}
}
// force aliased enum values to the more appropriate value
private static string HandJointLabelFromJointId(HandJointId handJointId)
{
switch (handJointId)
{
case HandJointId.HandWristRoot:
return "HandWristRoot";
case HandJointId.HandForearmStub:
return "HandForearmStub";
case HandJointId.HandThumb0:
return "HandThumb0";
case HandJointId.HandThumb1:
return "HandThumb1";
case HandJointId.HandThumb2:
return "HandThumb2";
case HandJointId.HandThumb3:
return "HandThumb3";
case HandJointId.HandIndex1:
return "HandIndex1";
case HandJointId.HandIndex2:
return "HandIndex2";
case HandJointId.HandIndex3:
return "HandIndex3";
case HandJointId.HandMiddle1:
return "HandMiddle1";
case HandJointId.HandMiddle2:
return "HandMiddle2";
case HandJointId.HandMiddle3:
return "HandMiddle3";
case HandJointId.HandRing1:
return "HandRing1";
case HandJointId.HandRing2:
return "HandRing2";
case HandJointId.HandRing3:
return "HandRing3";
case HandJointId.HandPinky0:
return "HandPinky0";
case HandJointId.HandPinky1:
return "HandPinky1";
case HandJointId.HandPinky2:
return "HandPinky2";
case HandJointId.HandPinky3:
return "HandPinky3";
case HandJointId.HandThumbTip:
return "HandThumbTip";
case HandJointId.HandIndexTip:
return "HandIndexTip";
case HandJointId.HandMiddleTip:
return "HandMiddleTip";
case HandJointId.HandRingTip:
return "HandRingTip";
case HandJointId.HandPinkyTip:
return "HandPinkyTip";
default:
return "HandUnknown";
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 037bf294d05876e4c8dd96432f39faaa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
{
"name": "Oculus.Interaction.Editor",
"rootNamespace": "",
"references": [
"GUID:2a230cb87a1d3ba4a98bdc0ddae76e6c"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 48af58ae5328ff048acacd924604a804
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9e918f3baaf96b84e9e87647d1ffd804
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,189 @@
/*
* 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 UnityEngine;
using UnityEditor;
using System;
using Object = UnityEngine.Object;
namespace Oculus.Interaction.Editor
{
public class AssetListWindow : EditorWindow
{
public class AssetInfo
{
public readonly string AssetPath;
public readonly string DisplayName;
public readonly Action ClickAction;
public AssetInfo(string assetPath) : this(assetPath, assetPath) { }
public AssetInfo(string assetPath, string displayName, Action clickAction) :
this(assetPath, displayName)
{
ClickAction = clickAction;
}
public AssetInfo(string assetPath, string displayName)
{
AssetPath = assetPath;
DisplayName = displayName;
ClickAction = () => PingObject(assetPath);
}
}
private List<AssetInfo> _assetInfos;
private Vector2 _scrollPos;
private Action<AssetListWindow> _headerDrawer;
private Action<AssetListWindow> _footerDrawer;
public IReadOnlyList<AssetInfo> AssetInfos => _assetInfos;
public static AssetListWindow Show(
string title,
IEnumerable<string> assetPaths,
bool modal = false,
Action<AssetListWindow> headerDrawer = null,
Action<AssetListWindow> footerDrawer = null)
{
List<AssetInfo> assetInfos = new List<AssetInfo>();
foreach (var path in assetPaths)
{
assetInfos.Add(new AssetInfo(path));
}
return Show(title, assetInfos, modal, headerDrawer, footerDrawer);
}
public static AssetListWindow Show(
string title,
IEnumerable<AssetInfo> assetInfos,
bool modal = false,
Action<AssetListWindow> headerDrawer = null,
Action<AssetListWindow> footerDrawer = null)
{
AssetListWindow window = GetWindow<AssetListWindow>(true);
window._assetInfos = new List<AssetInfo>(assetInfos);
window.SetTitle(title);
window.SetHeader(headerDrawer);
window.SetFooter(footerDrawer);
if (modal)
{
window.ShowModalUtility();
}
else
{
window.ShowUtility();
}
return window;
}
public static void CloseAll()
{
if (HasOpenInstances<AssetListWindow>())
{
AssetListWindow window = GetWindow<AssetListWindow>(true);
window.Close();
}
}
public void SetTitle(string title)
{
titleContent = new GUIContent(title);
}
public void SetHeader(Action<AssetListWindow> headerDrawer)
{
_headerDrawer = headerDrawer;
}
public void SetFooter(Action<AssetListWindow> footerDrawer)
{
_footerDrawer = footerDrawer;
}
private void OnGUI()
{
DrawHeader();
DrawContent();
DrawFooter();
}
private void DrawHeader()
{
if (_headerDrawer == null)
{
return;
}
EditorGUILayout.BeginVertical();
_headerDrawer.Invoke(this);
EditorGUILayout.EndVertical();
}
private void DrawFooter()
{
if (_footerDrawer == null)
{
return;
}
EditorGUILayout.BeginVertical();
_footerDrawer.Invoke(this);
EditorGUILayout.EndVertical();
}
private void DrawContent()
{
EditorGUILayout.BeginVertical();
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos);
foreach (var assetInfo in _assetInfos)
{
var rect = EditorGUILayout.BeginHorizontal();
if (GUI.Button(rect, "", GUIStyle.none))
{
assetInfo.ClickAction.Invoke();
}
GUIStyle style = new GUIStyle(GUI.skin.label);
style.richText = true;
EditorGUILayout.LabelField(assetInfo.DisplayName, style);
EditorGUILayout.EndHorizontal();
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndScrollView();
EditorGUILayout.EndVertical();
}
private static void PingObject(string assetPath)
{
Object obj = AssetDatabase.LoadAssetAtPath(
assetPath, typeof(Object));
if (obj != null)
{
EditorGUIUtility.PingObject(obj);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ecccce924260f0a47ae428db9ff1a6ad
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,555 @@
/*
* 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 UnityEngine;
using UnityEditor;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using UnityEditor.PackageManager;
using PackageInfo = UnityEditor.PackageManager.PackageInfo;
namespace Oculus.Interaction.Editor
{
[InitializeOnLoad]
public static class PackageCleanup
{
private enum CleanupOperation
{
None,
Delete,
Move,
StripTags,
}
private class CleanupInfo
{
public CleanupOperation Operation;
public GUID AssetGuid;
public GUID MoveToPathGuid;
}
private enum CleanupResult
{
None,
Success,
Cancel,
Incomplete,
}
public const string PACKAGE_VERSION = "0.57.0";
public const string DEPRECATED_TAG = "oculus_interaction_deprecated";
public const string MOVED_TAG = "oculus_interaction_moved_";
private const string MENU_NAME = "Oculus/Interaction/Clean Up Package";
private const string AUTO_CLEANUP_KEY = "Oculus_Interaction_AutoCleanUp_" + PACKAGE_VERSION;
private static bool AutoCleanup
{
get => PlayerPrefs.GetInt(AUTO_CLEANUP_KEY, 1) == 1;
set => PlayerPrefs.SetInt(AUTO_CLEANUP_KEY, value ? 1 : 0);
}
static PackageCleanup()
{
EditorApplication.delayCall += HandleDelayCall;
}
[MenuItem(MENU_NAME)]
private static void AssetRemovalMenuCommand()
{
AutoCleanup = true;
StartRemovalUserFlow(true);
}
private static void HandleDelayCall()
{
bool startAutoDeprecation = !Application.isBatchMode &&
AutoCleanup &&
!Application.isPlaying;
if (startAutoDeprecation)
{
StartRemovalUserFlow(false);
}
}
/// <summary>
/// Check if there are any assets in the project that require
/// cleanup operations.
/// </summary>
/// <returns>True if package needs cleanup</returns>
public static bool CheckPackageNeedsCleanup()
{
return GetAssetInfos().Count > 0;
}
/// <summary>
/// Start the removal flow for removing deprecated assets.
/// </summary>
/// <param name="userTriggered">If true, the window will
/// be non-modal, and a dialog will be shown if no assets found</param>
public static void StartRemovalUserFlow(bool userTriggered)
{
var assetInfos = GetAssetInfos();
if (assetInfos.Count == 0)
{
if (userTriggered)
{
EditorUtility.DisplayDialog("Interaction SDK",
"No clean up needed in package.", "Close");
}
else
{
return;
}
}
else
{
int deletionPromptResult = EditorUtility.DisplayDialogComplex(
"Interaction SDK",
"This utility performs a cleanup operation which relocates " +
"Interaction SDK files and folders, and removes asset stubs provided " +
"for backwards compatibility during package upgrade." +
"\n\n" +
"Click 'Show Assets' to view a list of the assets to be modified. " +
"You will then be given the option to run the cleanup operation on them.",
"Show Assets (Recommended)", "No, Don't Ask Again", "No");
switch (deletionPromptResult)
{
case 0: // "Yes"
bool modalWindow = !userTriggered;
ShowAssetCleanupWindow(assetInfos, modalWindow);
break;
case 1: // "No, Don't Ask Again"
AutoCleanup = false;
ShowCancelDialog();
break;
default:
case 2: // "No"
AutoCleanup = true;
break;
}
}
}
private static IReadOnlyList<CleanupInfo> GetAssetInfos()
{
List<CleanupInfo> result = new List<CleanupInfo>();
var deprecatedGUIDs = AssetDatabase.FindAssets($"l:{DEPRECATED_TAG}", null)
.Select((guidStr) => new GUID(guidStr));
var movedGUIDs = AssetDatabase.FindAssets($"l:{MOVED_TAG}", null)
.Select((guidStr) => new GUID(guidStr));
foreach (var GUID in deprecatedGUIDs)
{
result.Add(new CleanupInfo()
{
Operation = CleanupOperation.Delete,
AssetGuid = GUID,
});
}
foreach (var GUID in movedGUIDs)
{
if (GetDestFolderForMovedAsset(GUID, out GUID newPathGUID))
{
result.Add(new CleanupInfo()
{
Operation = CleanupOperation.Move,
AssetGuid = GUID,
MoveToPathGuid = newPathGUID,
});
}
else
{
result.Add(new CleanupInfo()
{
Operation = CleanupOperation.StripTags,
AssetGuid = GUID,
});
}
}
result.RemoveAll((info) =>
{
// Ignore assets in read-only packages
var pSource = PackageInfo.FindForAssetPath(
AssetDatabase.GUIDToAssetPath(info.AssetGuid))?.source;
return pSource != null && // In Assets folder
pSource != PackageSource.Embedded &&
pSource != PackageSource.Local;
});
return result;
}
private static void ShowAssetCleanupWindow(
IEnumerable<CleanupInfo> cleanupInfos, bool modal)
{
void DrawHeader(AssetListWindow window)
{
EditorGUILayout.HelpBox(
"Assets marked Delete will be permanently deleted",
MessageType.Warning);
}
void DrawFooter(AssetListWindow window)
{
GUILayoutOption buttonHeight = GUILayout.Height(36);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Clean Up (Recommended)", buttonHeight))
{
var result = CleanUpAssets(cleanupInfos);
switch (result)
{
default:
case CleanupResult.None:
case CleanupResult.Cancel:
AutoCleanup = true;
break;
case CleanupResult.Success:
case CleanupResult.Incomplete:
AutoCleanup = false;
window.Close();
break;
}
}
if (GUILayout.Button("Cancel", buttonHeight))
{
ShowCancelDialog();
}
EditorGUILayout.EndHorizontal();
}
List<AssetListWindow.AssetInfo> windowInfos =
new List<AssetListWindow.AssetInfo>();
foreach (var info in cleanupInfos)
{
switch (info.Operation)
{
default:
case CleanupOperation.None:
break;
case CleanupOperation.Delete:
windowInfos.Add(new AssetListWindow.AssetInfo(
GUIDToAssetPath(info.AssetGuid),
$"<color=orange>Delete:</color> " +
$"{GUIDToAssetPath(info.AssetGuid)}"));
break;
case CleanupOperation.Move:
windowInfos.Add(new AssetListWindow.AssetInfo(
GUIDToAssetPath(info.AssetGuid),
$"<color=yellow>Move:</color> " +
$"{GUIDToAssetPath(info.AssetGuid)} -> " +
$"{GUIDToAssetPath(info.MoveToPathGuid)}"));
break;
case CleanupOperation.StripTags:
windowInfos.Add(new AssetListWindow.AssetInfo(
GUIDToAssetPath(info.AssetGuid),
$"<color=lime>Unlabel:</color> " +
$"{GUIDToAssetPath(info.AssetGuid)}"));
break;
}
}
AssetListWindow assetListWindow = AssetListWindow.Show(
"Interaction SDK - All Assets to be Modified",
windowInfos, modal, DrawHeader, DrawFooter);
}
private static void ShowCancelDialog()
{
AssetListWindow.CloseAll();
EditorUtility.DisplayDialog("Interaction SDK",
$"Package cleanup was not run. " +
$"You can run this utility at any time " +
$"using the '{MENU_NAME}' menu.",
"Close");
}
private static bool GetDestFolderForMovedAsset(GUID assetGUID, out GUID destFolderGUID)
{
destFolderGUID = new GUID();
Object assetObject = AssetDatabase.LoadMainAssetAtPath(GUIDToAssetPath(assetGUID));
List<string> labels = new List<string>(AssetDatabase.GetLabels(assetObject));
int index = labels.FindIndex((l) => l.Contains(MOVED_TAG));
if (index >= 0)
{
destFolderGUID = new GUID(labels[index].Remove(0, MOVED_TAG.Length));
// Verify that paths exist, and new path is not the same as old path
string curPath = Path.GetFullPath(GUIDToAssetPath(assetGUID));
string newFolder = Path.GetFullPath(GUIDToAssetPath(destFolderGUID));
string targetFilePath = Path.Combine(newFolder, Path.GetFileName(curPath));
if (!curPath.Equals(targetFilePath) &&
(Directory.Exists(curPath) || File.Exists(curPath)) &&
Directory.Exists(newFolder))
{
return true;
}
}
return false;
}
private static CleanupResult CleanUpAssets(IEnumerable<CleanupInfo> cleanupInfos)
{
if (EditorUtility.DisplayDialog("Are you sure?",
"Any assets marked for deletion will be permanently deleted." +
"\n\n" +
"It is strongly recommended that you back up your project before proceeding.",
"Clean Up Package", "Cancel"))
{
var deletions = new List<GUID>();
var moves = new Dictionary<GUID, GUID>();
var stripTags = new List<GUID>();
foreach (var info in cleanupInfos)
{
switch (info.Operation)
{
default:
case CleanupOperation.None:
break;
case CleanupOperation.Delete:
deletions.Add(info.AssetGuid);
break;
case CleanupOperation.Move:
moves.Add(info.AssetGuid, info.MoveToPathGuid);
break;
case CleanupOperation.StripTags:
stripTags.Add(info.AssetGuid);
break;
}
}
bool result = true;
result &= MoveAssets(moves);
result &= DeleteAssets(deletions);
result &= StripTags(stripTags);
return result ? CleanupResult.Success : CleanupResult.Incomplete;
}
else
{
return CleanupResult.Cancel;
}
}
private static bool MoveAssets(IDictionary<GUID, GUID> curToNewPathGUID)
{
Dictionary<string, string> moves = new Dictionary<string, string>();
Dictionary<string, string> failures = new Dictionary<string, string>();
foreach (var assetGUID in curToNewPathGUID.Keys)
{
if (!curToNewPathGUID.TryGetValue(assetGUID, out GUID newPathGUID))
{
string failedPath = GUIDToAssetPath(assetGUID);
failures.Add(failedPath, $"No new path provided for asset {failedPath}");
continue;
}
string curPath = GUIDToAssetPath(assetGUID);
string newPath = Path.Combine(GUIDToAssetPath(newPathGUID),
Path.GetFileName(curPath));
if (Path.GetFullPath(curPath).Equals(Path.GetFullPath(newPath)))
{
// Source and destination paths already match
continue;
}
string result = AssetDatabase.MoveAsset(curPath, newPath);
if (!string.IsNullOrEmpty(result))
{
failures.Add(curPath, result);
}
else
{
// Strip labels after successful move
StripTag(assetGUID, MOVED_TAG);
moves.Add(curPath, newPath);
}
}
string logMessage;
if (BuildLogMessage("Assets moved:",
moves.Keys.Select((key) => $"{key} -> {moves[key]}"),
out logMessage))
{
Debug.Log(logMessage);
}
if (BuildLogMessage("Could not move assets:",
failures.Keys.Select((key) => $"{key}:{failures[key]}"),
out logMessage))
{
Debug.LogError(logMessage);
}
return failures.Count == 0;
}
private static bool DeleteAssets(IEnumerable<GUID> assetGUIDs)
{
var assetPaths = assetGUIDs
.Select((guid) => GUIDToAssetPath(guid));
HashSet<string> filesToDelete = new HashSet<string>();
HashSet<string> foldersToDelete = new HashSet<string>();
HashSet<string> skippedFolders = new HashSet<string>();
HashSet<string> failedPaths = new HashSet<string>();
foreach (var path in assetPaths)
{
if (File.Exists(path))
{
filesToDelete.Add(path);
}
else if (Directory.Exists(path))
{
foldersToDelete.Add(path);
}
else
{
failedPaths.Add(path);
}
}
#if UNITY_2020_1_OR_NEWER
List<string> failed = new List<string>();
// Delete files
AssetDatabase.DeleteAssets(filesToDelete.ToArray(), failed);
failedPaths.UnionWith(failed);
// Remove non-empty folders from delete list
skippedFolders.UnionWith(foldersToDelete
.Where((path) => AssetDatabase.FindAssets("", new[] { path })
.Select((guid) => AssetDatabase.GUIDToAssetPath(guid))
.Any((path) => !AssetDatabase.IsValidFolder(path))));
foldersToDelete.ExceptWith(skippedFolders);
// Delete folders, removing longest paths (subfolders) first
List<string> sortedFolders = new List<string>(foldersToDelete);
sortedFolders.Sort((a, b) => b.Length.CompareTo(a.Length));
AssetDatabase.DeleteAssets(sortedFolders.ToArray(), failed);
failedPaths.UnionWith(failed);
#else
// Delete files
foreach (var path in filesToDelete)
{
if (!AssetDatabase.DeleteAsset(path))
{
failedPaths.Add(path);
}
}
// Remove non-empty folders from delete list
skippedFolders.UnionWith(foldersToDelete
.Where((path) => Directory.EnumerateFiles(path).Any()));
foldersToDelete.ExceptWith(skippedFolders);
// Delete folders
foreach (var path in foldersToDelete)
{
if (!AssetDatabase.DeleteAsset(path))
{
failedPaths.Add(path);
}
}
#endif
string logMessage;
if (BuildLogMessage("Deprecated assets deleted:",
filesToDelete.Union(foldersToDelete), out logMessage))
{
Debug.Log(logMessage);
}
if (BuildLogMessage("Skipped non-empty folders:",
skippedFolders, out logMessage))
{
Debug.LogWarning(logMessage);
}
if (BuildLogMessage("Failed to delete assets:",
failedPaths, out logMessage))
{
Debug.LogError(logMessage);
}
return failedPaths.Count == 0;
}
private static bool StripTags(IEnumerable<GUID> assetGUIDs)
{
foreach (var GUID in assetGUIDs)
{
StripTag(GUID, DEPRECATED_TAG);
StripTag(GUID, MOVED_TAG);
}
return true;
}
private static void StripTag(in GUID assetGUID, string tag)
{
string assetPath = GUIDToAssetPath(assetGUID);
Object assetObject = AssetDatabase.LoadMainAssetAtPath(assetPath);
List<string> labels = new List<string>(AssetDatabase.GetLabels(assetObject));
labels.RemoveAll((l) => l.Contains(tag));
AssetDatabase.SetLabels(assetObject, labels.ToArray());
}
private static bool BuildLogMessage(
string title,
IEnumerable<string> messages,
out string message)
{
int count = 0;
StringBuilder sb = new StringBuilder();
sb.Append(title);
foreach (var msg in messages)
{
sb.Append(System.Environment.NewLine);
sb.Append(msg);
++count;
}
message = sb.ToString();
return count > 0;
}
private static string GUIDToAssetPath(GUID guid)
{
#if UNITY_2020_3_OR_NEWER
return AssetDatabase.GUIDToAssetPath(guid);
#else
return AssetDatabase.GUIDToAssetPath(guid.ToString());
#endif
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e9dd3eed68fab7d40a0c5d750d3f478d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0913252150f751d4892fddbe95a753e6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,84 @@
/*
* 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 UnityEditor;
using UnityEngine;
using Oculus.Interaction.Surfaces;
namespace Oculus.Interaction.Editor
{
[CustomEditor(typeof(PokeInteractable))]
public class PokeInteractableEditor : SimplifiedEditor
{
private PokeInteractable _interactable;
private SerializedProperty _surfaceProperty;
private static readonly float DRAW_RADIUS = 0.02f;
private void Awake()
{
_interactable = target as PokeInteractable;
_surfaceProperty = serializedObject.FindProperty("_surfacePatch");
}
public void OnSceneGUI()
{
Handles.color = EditorConstants.PRIMARY_COLOR;
ISurfacePatch surfacePatch = _surfaceProperty.objectReferenceValue as ISurfacePatch;
if (surfacePatch == null)
{
// Currently only supports visualizing planar surfaces
return;
}
Transform triggerPlaneTransform = surfacePatch.Transform;
if (triggerPlaneTransform == null)
{
return;
}
Vector3 touchPoint = triggerPlaneTransform.position - triggerPlaneTransform.forward * _interactable.EnterHoverNormal;
surfacePatch.ClosestSurfacePoint(touchPoint, out SurfaceHit hit);
Vector3 proximalPoint = hit.Point;
Handles.DrawSolidDisc(touchPoint, triggerPlaneTransform.forward, DRAW_RADIUS);
#if UNITY_2020_2_OR_NEWER
Handles.DrawLine(touchPoint, proximalPoint, EditorConstants.LINE_THICKNESS);
Handles.DrawLine(proximalPoint - triggerPlaneTransform.right * DRAW_RADIUS,
proximalPoint + triggerPlaneTransform.right * DRAW_RADIUS, EditorConstants.LINE_THICKNESS);
Handles.DrawLine(proximalPoint - triggerPlaneTransform.up * DRAW_RADIUS,
proximalPoint + triggerPlaneTransform.up * DRAW_RADIUS, EditorConstants.LINE_THICKNESS);
#else
Handles.DrawLine(touchPoint, proximalPoint);
Handles.DrawLine(proximalPoint - triggerPlaneTransform.right * DRAW_RADIUS,
proximalPoint + triggerPlaneTransform.right * DRAW_RADIUS);
Handles.DrawLine(proximalPoint - triggerPlaneTransform.up * DRAW_RADIUS,
proximalPoint + triggerPlaneTransform.up * DRAW_RADIUS);
#endif
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 308350aa983f9894b8b098e2eba08a60
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,61 @@
/*
* 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 UnityEditor;
using UnityEngine;
using UnityEditor.IMGUI.Controls;
namespace Oculus.Interaction.Editor
{
[CustomEditor(typeof(PokeInteractor))]
public class PokeInteractorEditor : SimplifiedEditor
{
private SphereBoundsHandle _sphereHandle = new SphereBoundsHandle();
private PokeInteractor _interactor;
private SerializedProperty _pointProperty;
private void Awake()
{
_interactor = target as PokeInteractor;
_sphereHandle.SetColor(EditorConstants.PRIMARY_COLOR);
_sphereHandle.midpointHandleDrawFunction = null;
_pointProperty = serializedObject.FindProperty("_pointTransform");
}
private void OnSceneGUI()
{
DrawSphereEditor();
}
private void DrawSphereEditor()
{
Transform transform = _pointProperty.objectReferenceValue as Transform;
if (transform == null)
{
return;
}
_sphereHandle.radius = _interactor.Radius;
_sphereHandle.center = transform.position;
_sphereHandle.DrawHandle();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 46c04bf9df613494998209c4f4705544
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 275f3583fdaef334aa875c7f886a086c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,330 @@
/*
* 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 Oculus.Interaction.PoseDetection.Editor.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection.Editor
{
namespace Model
{
public class FeatureConfigList
{
private readonly SerializedProperty _root;
private uint _flags;
private readonly IReadOnlyDictionary<int, FeatureDescription> _featureDescriptions;
public FeatureConfigList(SerializedProperty root,
IReadOnlyDictionary<int, FeatureDescription> featureDescriptions)
{
Assert.IsNotNull(root);
_root = root;
_flags = GetFlags(_root);
_featureDescriptions = featureDescriptions;
}
private uint GetFlags(SerializedProperty root)
{
if (!root.isArray)
{
return 0U;
}
uint flags = 0U;
for (int i = 0; i < root.arraySize; i++)
{
var elemProp = root.GetArrayElementAtIndex(i);
var featureProp = elemProp.FindPropertyRelative("_feature");
flags |= 1U << featureProp.enumValueIndex;
}
return flags;
}
public uint Flags
{
get => _flags;
set
{
uint flagsToCreate = value;
for (int i = 0; i < _root.arraySize;)
{
var elemProp = _root.GetArrayElementAtIndex(i);
var featureProp = elemProp.FindPropertyRelative("_feature");
uint propFlags = (1U << featureProp.enumValueIndex);
if ((flagsToCreate & propFlags) == 0U)
{
// Feature is in list, but not flags... delete list entry.
_root.DeleteArrayElementAtIndex(i);
}
else
{
// Feature is in list, and in flags; remove from list of things we need to create.
flagsToCreate &= ~propFlags;
i++;
}
}
// Create missing elements.
foreach (var feature in _featureDescriptions.Keys)
{
uint flags = 1U << feature;
if ((flagsToCreate & flags) == 0U)
{
continue;
}
var lastIndex = _root.arraySize;
_root.InsertArrayElementAtIndex(lastIndex);
var model = new FeatureConfig(_root.GetArrayElementAtIndex(lastIndex));
model.Feature = feature;
model.FeatureState = _featureDescriptions[feature].FeatureStates[0].Id;
}
_flags = value;
}
}
public IEnumerable<FeatureConfig> ConfigModels
{
get
{
List<FeatureConfig> models = new List<FeatureConfig>(_root.arraySize);
for (int i = 0; i < _root.arraySize; i++)
{
models.Add(new FeatureConfig(_root.GetArrayElementAtIndex(i)));
}
return models;
}
}
}
public class FeatureConfig
{
SerializedProperty _modeProp;
SerializedProperty _featureProp;
SerializedProperty _stateProp;
public FeatureConfig(SerializedProperty root)
{
_modeProp = root.FindPropertyRelative("_mode");
_featureProp = root.FindPropertyRelative("_feature");
_stateProp = root.FindPropertyRelative("_state");
}
public FeatureStateActiveMode Mode
{
get => (FeatureStateActiveMode)_modeProp.enumValueIndex;
set
{
if (value != (FeatureStateActiveMode)_modeProp.enumValueIndex)
{
_modeProp.enumValueIndex = (int)value;
}
}
}
public int Feature
{
get => _featureProp.enumValueIndex;
set
{
if (value != _featureProp.enumValueIndex)
{
_featureProp.enumValueIndex = value;
}
}
}
public string FeatureState
{
get => _stateProp.stringValue;
set
{
if (value != _stateProp.stringValue)
{
_stateProp.stringValue = value;
}
}
}
}
}
public abstract class FeatureListPropertyDrawer : PropertyDrawer
{
public float ControlLineHeight => EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; // = 18 + 2
public float BottomMargin => EditorGUIUtility.standardVerticalSpacing * 2;
private const float IndentSize = 16;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
var height = base.GetPropertyHeight(property, label); // includes height of first line
var model = CreateModel(property);
if (property.isExpanded)
{
var controlCount = model.ConfigModels.Count();
height += controlCount * ControlLineHeight;
height += BottomMargin;
}
return height;
}
public override bool CanCacheInspectorGUI(SerializedProperty property)
{
return true;
}
public override void OnGUI(Rect drawerPos, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(drawerPos, label, property);
var oldIndent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
var labelPos = new Rect(drawerPos)
{
width = EditorGUIUtility.labelWidth - drawerPos.x,
height = EditorGUIUtility.singleLineHeight
};
var model = CreateModel(property);
property.isExpanded = EditorGUI.Foldout(labelPos, property.isExpanded, label, true);
if (property.isExpanded)
{
RenderExpanded(drawerPos, model);
}
else
{
RenderCollapsed(drawerPos, model);
}
EditorGUI.indentLevel = oldIndent;
EditorGUI.EndProperty();
}
private void RenderExpanded(Rect drawerPos, FeatureConfigList model) {
Rect controlPos = new Rect(drawerPos.x, drawerPos.y, drawerPos.width,
EditorGUIUtility.singleLineHeight);
var flagsPos = Indent(controlPos, EditorGUIUtility.labelWidth);
var newFlags = EnumToFlags(EditorGUI.EnumFlagsField(flagsPos, FlagsToEnum(model.Flags)));
if (newFlags != model.Flags)
{
model.Flags = newFlags;
}
controlPos = Indent(controlPos, IndentSize);
foreach (var configModel in model.ConfigModels)
{
controlPos.y += ControlLineHeight;
// Render the label
float indent = 0f;
var labelPos = Indent(controlPos, indent);
labelPos.width = EditorGUIUtility.labelWidth - IndentSize;
var featureName = FeatureToString(configModel.Feature);
featureName = ObjectNames.NicifyVariableName(featureName);
EditorGUI.PrefixLabel(labelPos, new GUIContent(featureName));
// Render the mode dropdown
indent += labelPos.width;
var modePos = Indent(controlPos, indent);
var allowedModes = GetAllowedModes(configModel);
if (allowedModes.Length > 1)
{
modePos.width = 70;
configModel.Mode =
(FeatureStateActiveMode)EditorGUI.EnumPopup(modePos, configModel.Mode);
}
else if (allowedModes.Length == 1)
{
configModel.Mode = allowedModes[0];
modePos.width = 15;
EditorGUI.SelectableLabel(modePos,
ObjectNames.NicifyVariableName(allowedModes[0] + ": "));
}
else
{
modePos.width = -2;
}
// Render the state dropdown
indent += modePos.width + 2;
var statePos = Indent(controlPos, indent);
var featureStates = GetStatesForFeature(configModel.Feature);
string[] options = featureStates.Select(fs => fs.Name).ToArray();
int selectedIndex = Array.FindIndex(featureStates, fs => fs.Id == configModel.FeatureState);
int newSelectedIndex = EditorGUI.Popup(statePos, selectedIndex, options);
if (newSelectedIndex != selectedIndex) {
configModel.FeatureState = featureStates[newSelectedIndex].Id;
}
}
}
private void RenderCollapsed(Rect drawerPos, FeatureConfigList model)
{
Rect controlPos = drawerPos;
controlPos.height = EditorGUIUtility.singleLineHeight;
var valuePos = Indent(controlPos, EditorGUIUtility.labelWidth + 2);
valuePos.width = drawerPos.width - valuePos.x;
var flagsEnum = FlagsToEnum(model.Flags);
var values = Enum.GetValues(flagsEnum.GetType())
.Cast<Enum>()
.Where(e => flagsEnum.HasFlag(e))
.Select(e => e.ToString());
EditorGUI.SelectableLabel(valuePos, String.Join(", ", values));
}
private static Rect Indent(Rect position, float indentWidth)
{
return new Rect(position)
{
x = position.x + indentWidth,
width = position.width - indentWidth
};
}
protected virtual FeatureStateActiveMode[] GetAllowedModes(FeatureConfig model)
{
var statesForFeature = GetStatesForFeature(model.Feature);
if (statesForFeature.Length > 2)
return (FeatureStateActiveMode[])Enum.GetValues(typeof(FeatureStateActiveMode));
else
return new[] { FeatureStateActiveMode.Is };
}
protected abstract Enum FlagsToEnum(uint flags);
protected abstract uint EnumToFlags(Enum flags);
protected abstract string FeatureToString(int featureIdx);
protected abstract FeatureStateDescription[] GetStatesForFeature(int featureIdx);
protected abstract Model.FeatureConfigList CreateModel(SerializedProperty property);
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 39acc9895ae6408898722a714edbe5ce
timeCreated: 1631575366

View File

@ -0,0 +1,498 @@
/*
* 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;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.PoseDetection.Editor
{
public abstract class FeatureStateThresholdsEditor<TFeature> : UnityEditor.Editor
where TFeature : unmanaged, Enum
{
#region static helpers
public static readonly TFeature[] FeatureEnumValues = (TFeature[])Enum.GetValues(typeof(TFeature));
public static TFeature IntToFeature(int value)
{
return FeatureEnumValues[value];
}
public static int FeatureToInt(TFeature feature)
{
for (int i = 0; i < FeatureEnumValues.Length; i++)
{
TFeature enumVal = FeatureEnumValues[i];
if (enumVal.Equals(feature))
{
return i;
}
}
throw new ArgumentOutOfRangeException();
}
#endregion
#region Model Classes
public class FeatureStateThresholdsModel
{
private readonly SerializedProperty _thresholdsProp;
private readonly SerializedProperty _featureProp;
public FeatureStateThresholdsModel(SerializedProperty self)
{
_featureProp = self.FindPropertyRelative("_feature");
_thresholdsProp = self.FindPropertyRelative("_thresholds");
Assert.IsNotNull(_featureProp);
Assert.IsNotNull(_thresholdsProp);
}
public TFeature Feature
{
get => IntToFeature(_featureProp.enumValueIndex);
set => _featureProp.enumValueIndex = FeatureToInt(value);
}
public SerializedProperty ThresholdsProp => _thresholdsProp;
}
public class FeatureStateThresholdModel
{
private readonly SerializedProperty _thresholdMidpointProp;
private readonly SerializedProperty _thresholdWidthProp;
private readonly SerializedProperty _firstStateProp;
private readonly SerializedProperty _secondStateProp;
public FeatureStateThresholdModel(SerializedProperty self)
{
_thresholdMidpointProp = self.FindPropertyRelative("_thresholdMidpoint");
_thresholdWidthProp = self.FindPropertyRelative("_thresholdWidth");
_firstStateProp = self.FindPropertyRelative("_firstState");
_secondStateProp = self.FindPropertyRelative("_secondState");
Assert.IsNotNull(_thresholdMidpointProp);
Assert.IsNotNull(_thresholdWidthProp);
Assert.IsNotNull(_firstStateProp);
Assert.IsNotNull(_secondStateProp);
}
public float ThresholdMidpoint
{
get => _thresholdMidpointProp.floatValue;
set { _thresholdMidpointProp.floatValue = value; }
}
public float ThresholdWidth
{
get => _thresholdWidthProp.floatValue;
set { _thresholdWidthProp.floatValue = value; }
}
public string FirstStateId
{
get => _firstStateProp.stringValue;
set => _firstStateProp.stringValue = value;
}
public string SecondStateId
{
get => _secondStateProp.stringValue;
set => _secondStateProp.stringValue = value;
}
public float ToFirstWhenBelow => ThresholdMidpoint - ThresholdWidth * 0.5f;
public float ToSecondWhenAbove => ThresholdMidpoint + ThresholdWidth * 0.5f;
}
#endregion
private SerializedProperty _rootProperty;
private SerializedProperty _minTimeInStateProp;
private readonly bool[] _featureVisible = new bool[FeatureEnumValues.Length];
private readonly Color _visStateColorPro = new Color32(194, 194, 194, 255);
private readonly Color _visStateColorLight = new Color32(56, 56, 56, 255);
private readonly Color _visTransitionColorPro = new Color32(80, 80, 80, 255);
private readonly Color _visTransitionColorLight = new Color32(160, 160, 160, 255);
private readonly Color _visDragColor = new Color32(0, 0, 128, 255);
private readonly Color _visBorderColor = new Color32(0, 0, 0, 255);
private const float _visHeight = 20.0f;
private const float _visMargin = 10.0f;
private const float _dragMargin = 10f;
private IReadOnlyDictionary<TFeature, FeatureDescription> _featureDescriptions;
protected abstract IReadOnlyDictionary<TFeature, FeatureDescription> CreateFeatureDescriptions();
protected abstract string FeatureMidpointTooltip { get; }
protected abstract string FeatureWidthTooltip { get; }
void OnEnable()
{
if (_featureDescriptions == null)
{
_featureDescriptions = CreateFeatureDescriptions();
}
if (_featureDescriptions.Count != FeatureEnumValues.Length)
{
throw new InvalidOperationException(
"CreateFeatureDescriptions() must return one key for each enum value.");
}
_rootProperty = serializedObject.FindProperty("_featureThresholds");
_minTimeInStateProp = serializedObject.FindProperty("_minTimeInState");
for (var index = 0; index < _featureVisible.Length; index++)
{
_featureVisible[index] = true;
}
}
public override void OnInspectorGUI()
{
if (_rootProperty == null || !_rootProperty.isArray || _minTimeInStateProp == null)
{
return;
}
EditorGUILayout.LabelField("All Features", EditorStyles.whiteLargeLabel);
EditorGUILayout.PropertyField(_minTimeInStateProp);
GUILayout.Space(10);
EditorGUILayout.LabelField("Per Feature", EditorStyles.whiteLargeLabel);
foreach (TFeature feature in FeatureEnumValues)
{
FeatureStateThresholdsModel foundFeatureProp = null;
for (int i = 0; i < _rootProperty.arraySize; ++i)
{
var featureThresholdsProp =
new FeatureStateThresholdsModel(
_rootProperty.GetArrayElementAtIndex(i));
if (featureThresholdsProp.Feature.Equals(feature))
{
foundFeatureProp = featureThresholdsProp;
break;
}
}
ref bool isVisible = ref _featureVisible[FeatureToInt(feature)];
isVisible = EditorGUILayout.BeginFoldoutHeaderGroup(isVisible, $"{feature} Thresholds");
if (!isVisible)
{
EditorGUILayout.EndFoldoutHeaderGroup();
continue;
}
if (!IsFeatureThresholdsValid(foundFeatureProp))
{
if (GUILayout.Button("Create Config"))
{
foundFeatureProp = CreateFeatureStateConfig(feature);
}
else
{
foundFeatureProp = null;
}
}
if (foundFeatureProp != null)
{
RenderFeatureStates(feature, foundFeatureProp);
}
EditorGUILayout.EndFoldoutHeaderGroup();
}
serializedObject.ApplyModifiedProperties();
}
private FeatureStateThresholdsModel CreateFeatureStateConfig(
TFeature feature)
{
// Delete any old invalid configs for this feature.
for (int i = 0; i < _rootProperty.arraySize;)
{
var model =
new FeatureStateThresholdsModel(
_rootProperty.GetArrayElementAtIndex(i));
if (model.Feature.Equals(feature))
{
_rootProperty.DeleteArrayElementAtIndex(i);
}
else
{
i++;
}
}
// Create a new config
int insertIndex = _rootProperty.arraySize;
_rootProperty.InsertArrayElementAtIndex(insertIndex);
var featureStateThresholds = new FeatureStateThresholdsModel(
_rootProperty.GetArrayElementAtIndex(insertIndex))
{
Feature = feature
};
// Set initial state
ResetFeatureStates(featureStateThresholds);
return featureStateThresholds;
}
private void ResetFeatureStates(FeatureStateThresholdsModel foundFeatureProp)
{
var states = _featureDescriptions[foundFeatureProp.Feature].FeatureStates;
var thresholdsArrayProp = foundFeatureProp.ThresholdsProp;
foundFeatureProp.ThresholdsProp.arraySize = states.Length - 1;
var featureDescription = _featureDescriptions[foundFeatureProp.Feature];
float minExpectedValue = featureDescription.MinValueHint;
float maxExpectedValue = featureDescription.MaxValueHint;
float range = maxExpectedValue - minExpectedValue;
float initialWidth = range * 0.075f;
float numStatesMultiplier = range / (states.Length);
for (int stateIdx = 0; stateIdx < states.Length - 1; ++stateIdx)
{
var featureState = states[stateIdx];
FeatureStateThresholdModel model = new FeatureStateThresholdModel(
thresholdsArrayProp.GetArrayElementAtIndex(stateIdx));
model.ThresholdMidpoint = minExpectedValue + (stateIdx + 1) * numStatesMultiplier;
model.ThresholdWidth = initialWidth;
model.FirstStateId = featureState.Id;
model.SecondStateId = states[stateIdx + 1].Id;
}
}
private bool IsFeatureThresholdsValid(FeatureStateThresholdsModel foundFeatureModel)
{
if (foundFeatureModel == null)
{
return false;
}
var states = _featureDescriptions[foundFeatureModel.Feature].FeatureStates;
if (foundFeatureModel.ThresholdsProp.arraySize != states.Length - 1)
{
return false;
}
for (var firstStateIdx = 0; firstStateIdx < states.Length - 1; firstStateIdx++)
{
var model = new FeatureStateThresholdModel(
foundFeatureModel.ThresholdsProp.GetArrayElementAtIndex(firstStateIdx));
if (states[firstStateIdx].Id != model.FirstStateId ||
states[firstStateIdx + 1].Id != model.SecondStateId)
{
return false;
}
}
return true;
}
private void RenderFeatureStates(TFeature feature, FeatureStateThresholdsModel featureStateThresholdsModel)
{
FeatureDescription featureDescription = _featureDescriptions[feature];
// Indent block
using (new EditorGUI.IndentLevelScope())
{
RenderFeatureDescription(featureDescription);
var states = _featureDescriptions[feature].FeatureStates;
float minVal = float.MaxValue;
float maxVal = float.MinValue;
bool overlappingValues = false;
float thresholdMaxWidth =
featureDescription.MaxValueHint - featureDescription.MinValueHint;
for (var firstStateIdx = 0; firstStateIdx < states.Length - 1; firstStateIdx++)
{
var firstState = states[firstStateIdx];
var secondState = states[firstStateIdx + 1];
EditorGUILayout.LabelField($"{firstState.Name} ⟷ {secondState.Name}", EditorStyles.label);
// Indent block
using (new EditorGUI.IndentLevelScope())
{
var model = new FeatureStateThresholdModel(
featureStateThresholdsModel.ThresholdsProp.GetArrayElementAtIndex(firstStateIdx));
if (model.ToFirstWhenBelow <= maxVal || model.ToSecondWhenAbove <= maxVal)
{
overlappingValues = true;
}
float thresholdMidpoint = model.ThresholdMidpoint;
float thresholdWidth = model.ThresholdWidth;
float newMidpoint = EditorGUILayout.FloatField(new GUIContent("Midpoint", FeatureMidpointTooltip), thresholdMidpoint);
float newWidth = EditorGUILayout.Slider(new GUIContent("Width", FeatureWidthTooltip), thresholdWidth, 0.0f,
thresholdMaxWidth);
if (Math.Abs(newMidpoint - thresholdMidpoint) > float.Epsilon ||
Math.Abs(newWidth - thresholdWidth) > float.Epsilon)
{
model.ThresholdMidpoint = newMidpoint;
model.ThresholdWidth = newWidth;
}
minVal = Mathf.Min(minVal, model.ToFirstWhenBelow);
maxVal = Mathf.Max(maxVal, model.ToSecondWhenAbove);
}
}
float range = maxVal - minVal;
if (range <= 0.0f)
{
EditorGUILayout.HelpBox("Invalid threshold values", MessageType.Warning);
}
else
{
if (overlappingValues)
{
EditorGUILayout.HelpBox("Overlapping threshold values",
MessageType.Warning);
}
RenderFeatureStateGraphic(featureStateThresholdsModel,
states,
Mathf.Min(featureDescription.MinValueHint, minVal),
Mathf.Max(featureDescription.MaxValueHint, maxVal));
}
}
}
private void RenderFeatureDescription(FeatureDescription featureDescription)
{
if (!String.IsNullOrWhiteSpace(featureDescription.ShortDescription))
{
EditorGUILayout.HelpBox(featureDescription.ShortDescription, MessageType.Info);
}
EditorGUILayout.LabelField(
new GUIContent("Expected value range", featureDescription.Description),
new GUIContent($"[{featureDescription.MinValueHint}, {featureDescription.MaxValueHint}]"));
}
private void RenderFeatureStateGraphic(FeatureStateThresholdsModel prop,
FeatureStateDescription[] stateDescriptions,
float minVal, float maxVal)
{
Rect lastRect = GUILayoutUtility.GetLastRect();
float xOffset = lastRect.xMin + _visMargin;
float widgetWidth = lastRect.width - _visMargin;
GUILayout.Space(_visHeight + _visMargin * 2);
EditorGUI.DrawRect(
new Rect(xOffset - 1, lastRect.yMax + _visMargin - 1, widgetWidth + 2.0f,
_visHeight + 2.0f), _visBorderColor);
float range = maxVal - minVal;
Color stateColor = EditorGUIUtility.isProSkin
? _visStateColorPro
: _visStateColorLight;
Color transitionColor = EditorGUIUtility.isProSkin
? _visTransitionColorPro
: _visTransitionColorLight;
GUIStyle richTextStyle = new GUIStyle();
richTextStyle.alignment = TextAnchor.MiddleCenter;
richTextStyle.normal.textColor = transitionColor;
for (var firstStateIdx = 0;
firstStateIdx < prop.ThresholdsProp.arraySize;
firstStateIdx++)
{
var model = new FeatureStateThresholdModel(
prop.ThresholdsProp.GetArrayElementAtIndex(firstStateIdx));
float firstPc = ((model.ToFirstWhenBelow - minVal)) / range;
DrawStateRect(firstPc, firstStateIdx);
xOffset += widgetWidth * firstPc;
minVal = model.ToFirstWhenBelow;
float secondPc = ((model.ToSecondWhenAbove - minVal)) / range;
Rect rect = DrawTransitionRect(secondPc);
UpdateDrag(rect, model, range / widgetWidth);
xOffset += widgetWidth * secondPc;
minVal = model.ToSecondWhenAbove;
}
float lastPc = ((maxVal - minVal)) / range;
DrawStateRect(lastPc, prop.ThresholdsProp.arraySize);
Rect DrawStateRect(float pc, int stateIndex)
{
Rect rect = new Rect(xOffset, lastRect.yMax + _visMargin,
widgetWidth * pc, _visHeight);
EditorGUI.DrawRect(rect, stateColor);
EditorGUI.LabelField(rect,
$"{stateDescriptions[stateIndex].Name}", richTextStyle);
return rect;
}
Rect DrawTransitionRect(float pc)
{
Rect rect = new Rect(xOffset, lastRect.yMax + _visMargin,
widgetWidth * pc, _visHeight);
EditorGUI.DrawRect(rect, transitionColor);
Rect leftRect = new Rect(rect);
leftRect.width = _dragMargin;
Rect rightRect = new Rect(rect);
rightRect.width = _dragMargin;
rightRect.x = rect.x + rect.width - _dragMargin;
EditorGUI.DrawRect(leftRect, _visDragColor);
EditorGUI.DrawRect(rightRect, _visDragColor);
return rect;
}
}
private void UpdateDrag(Rect rect, FeatureStateThresholdModel model, float factor)
{
Event currentEvent = Event.current;
Vector2 prevPos = currentEvent.mousePosition - currentEvent.delta;
if (currentEvent.type != EventType.MouseDrag
|| !rect.Contains(prevPos))
{
return;
}
float delta = currentEvent.delta.x * factor;
if (prevPos.x < rect.x + _dragMargin)
{
model.ThresholdMidpoint += delta * 0.5f;
model.ThresholdWidth -= delta;
}
else if (prevPos.x > rect.x + rect.width - _dragMargin)
{
model.ThresholdMidpoint += delta * 0.5f;
model.ThresholdWidth += delta;
}
else
{
model.ThresholdMidpoint += delta;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 536a9cf65c7544ab9e4051eac951baa7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,69 @@
/*
* 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 Oculus.Interaction.PoseDetection.Editor.Model;
using System;
using System.Linq;
using UnityEditor;
namespace Oculus.Interaction.PoseDetection.Editor
{
[CustomPropertyDrawer(typeof(ShapeRecognizer.FingerFeatureConfigList))]
public class FingerFeatureListPropertyDrawer : FeatureListPropertyDrawer
{
[Flags]
enum FingerFeatureFlags
{
Curl = 1 << 0,
Flexion = 1 << 1,
Abduction = 1 << 2,
Opposition = 1 << 3
}
protected override Enum FlagsToEnum(uint flags)
{
return (FingerFeatureFlags)flags;
}
protected override uint EnumToFlags(Enum flags)
{
return (uint)(FingerFeatureFlags)flags;
}
protected override string FeatureToString(int featureIdx)
{
return ((FingerFeature)featureIdx).ToString();
}
protected override FeatureStateDescription[] GetStatesForFeature(int featureIdx)
{
return FingerFeatureProperties.FeatureDescriptions[(FingerFeature)featureIdx].FeatureStates;
}
protected override FeatureConfigList CreateModel(SerializedProperty property)
{
var descriptions = FingerFeatureProperties.FeatureDescriptions
.ToDictionary(p => (int)p.Key, p => p.Value);
return new FeatureConfigList(property.FindPropertyRelative("_value"),
descriptions);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dffceaad44e34343a4e55875976ac097
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,39 @@
/*
* 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 UnityEditor;
namespace Oculus.Interaction.PoseDetection.Editor
{
[CustomEditor(typeof(FingerFeatureStateThresholds))]
public class FingerFeatureStateThresholdsEditor
: FeatureStateThresholdsEditor<FingerFeature>
{
protected override IReadOnlyDictionary<FingerFeature, FeatureDescription> CreateFeatureDescriptions()
{
return FingerFeatureProperties.FeatureDescriptions;
}
protected override string FeatureMidpointTooltip => FingerFeatureProperties.FeatureStateThresholdMidpointHelpText;
protected override string FeatureWidthTooltip => FingerFeatureProperties.FeatureStateThresholdWidthHelpText;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7a4a7445af8149e0887018ba0cba6abb
timeCreated: 1627935350

View File

@ -0,0 +1,88 @@
/*
* 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 UnityEditor;
using UnityEngine;
namespace Oculus.Interaction.PoseDetection.Editor
{
[CustomPropertyDrawer(typeof(JointRotationActiveState.JointRotationFeatureConfigList))]
public class JointRotationConfigListEditor : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUI.GetPropertyHeight(property.FindPropertyRelative("_values"));
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
EditorGUI.PropertyField(position, property.FindPropertyRelative("_values"), new GUIContent("Joints"), true);
EditorGUI.EndProperty();
}
}
[CustomPropertyDrawer(typeof(JointRotationActiveState.JointRotationFeatureConfig))]
public class JointRotationFeatureConfigEditor : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return GetLineHeight() * 3;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
Rect pos = new Rect(position.x, position.y, position.width,
EditorGUIUtility.singleLineHeight);
EditorGUI.BeginProperty(position, label, property);
var joint = property.FindPropertyRelative("_feature");
var relativeTo = property.FindPropertyRelative("_relativeTo");
var handAxis = property.FindPropertyRelative("_handAxis");
var worldAxis = property.FindPropertyRelative("_worldAxis");
DrawControl(joint, "Joint", ref pos);
DrawControl(relativeTo, "Relative To", ref pos);
if ((JointRotationActiveState.RelativeTo)relativeTo.enumValueIndex ==
JointRotationActiveState.RelativeTo.Hand)
{
DrawControl(handAxis, "Hand Axis", ref pos);
}
else
{
DrawControl(worldAxis, "World Axis", ref pos);
}
EditorGUI.EndProperty();
}
private void DrawControl(SerializedProperty property, string name, ref Rect position)
{
EditorGUI.PropertyField(position, property, new GUIContent(name));
position.y += GetLineHeight();
}
private float GetLineHeight()
{
return EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 548ff893703b939438b2a96d8c8fcba0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,95 @@
/*
* 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 UnityEditor;
using UnityEngine;
namespace Oculus.Interaction.PoseDetection.Editor
{
[CustomPropertyDrawer(typeof(JointVelocityActiveState.JointVelocityFeatureConfigList))]
public class JointVelocityConfigListEditor : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUI.GetPropertyHeight(property.FindPropertyRelative("_values"));
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
EditorGUI.PropertyField(position, property.FindPropertyRelative("_values"), new GUIContent("Joints"), true);
EditorGUI.EndProperty();
}
}
[CustomPropertyDrawer(typeof(JointVelocityActiveState.JointVelocityFeatureConfig))]
public class JointVelocityFeatureConfigEditor : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return GetLineHeight() * 3;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
Rect pos = new Rect(position.x, position.y, position.width,
EditorGUIUtility.singleLineHeight);
EditorGUI.BeginProperty(position, label, property);
var joint = property.FindPropertyRelative("_feature");
var relativeTo = property.FindPropertyRelative("_relativeTo");
var handAxis = property.FindPropertyRelative("_handAxis");
var worldAxis = property.FindPropertyRelative("_worldAxis");
var headAxis = property.FindPropertyRelative("_headAxis");
DrawControl(joint, "Joint", ref pos);
DrawControl(relativeTo, "Relative To", ref pos);
if ((JointVelocityActiveState.RelativeTo)relativeTo.enumValueIndex ==
JointVelocityActiveState.RelativeTo.Hand)
{
DrawControl(handAxis, "Hand Axis", ref pos);
}
else if ((JointVelocityActiveState.RelativeTo)relativeTo.enumValueIndex ==
JointVelocityActiveState.RelativeTo.World)
{
DrawControl(worldAxis, "World Axis", ref pos);
}
else if ((JointVelocityActiveState.RelativeTo)relativeTo.enumValueIndex ==
JointVelocityActiveState.RelativeTo.Head)
{
DrawControl(headAxis, "Head Axis", ref pos);
}
EditorGUI.EndProperty();
}
private void DrawControl(SerializedProperty property, string name, ref Rect position)
{
EditorGUI.PropertyField(position, property, new GUIContent(name));
position.y += GetLineHeight();
}
private float GetLineHeight()
{
return EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 51ada7d46c40f684689664f1b670f7a3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,74 @@
/*
* 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 Oculus.Interaction.PoseDetection.Editor.Model;
using System;
using System.Linq;
using UnityEditor;
namespace Oculus.Interaction.PoseDetection.Editor
{
[Flags]
public enum TransformFeatureFlags
{
WristUp = 1 << 0,
WristDown = 1 << 1,
PalmDown = 1 << 2,
PalmUp = 1 << 3,
PalmTowardsFace = 1 << 4,
PalmAwayFromFace = 1 << 5,
FingersUp = 1 << 6,
FingersDown = 1 << 7,
PinchClear = 1 << 8
}
[CustomPropertyDrawer(typeof(TransformFeatureConfigList))]
public class TransformConfigEditor : FeatureListPropertyDrawer
{
protected override Enum FlagsToEnum(uint flags)
{
return (TransformFeatureFlags)flags;
}
protected override uint EnumToFlags(Enum flags)
{
return (uint)(TransformFeatureFlags)flags;
}
protected override string FeatureToString(int featureIdx)
{
return ((TransformFeature)featureIdx).ToString();
}
protected override FeatureStateDescription[] GetStatesForFeature(int featureIdx)
{
return TransformFeatureProperties.FeatureDescriptions[(TransformFeature)featureIdx].FeatureStates;
}
protected override FeatureConfigList CreateModel(SerializedProperty property)
{
var descriptions = TransformFeatureProperties.FeatureDescriptions
.ToDictionary(p => (int)p.Key, p => p.Value);
return new FeatureConfigList(property.FindPropertyRelative("_values"),
descriptions);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ca473a2dca4a42988bd13c46f6ed077f
timeCreated: 1631569404

View File

@ -0,0 +1,40 @@
/*
* 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 UnityEditor;
namespace Oculus.Interaction.PoseDetection.Editor
{
[CustomEditor(typeof(TransformFeatureStateThresholds))]
class TransformFeatureStateThresholdModel
: FeatureStateThresholdsEditor<TransformFeature>
{
protected override IReadOnlyDictionary<TransformFeature, FeatureDescription> CreateFeatureDescriptions()
{
return TransformFeatureProperties.FeatureDescriptions;
}
protected override string FeatureMidpointTooltip => TransformFeatureProperties.FeatureStateThresholdMidpointHelpText;
protected override string FeatureWidthTooltip => TransformFeatureProperties.FeatureStateThresholdWidthHelpText;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 195af56ffbd76364e83ff93de249a25a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 48d2d34c0a202af4a8314dd3ad9f5c39
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,255 @@
/*
* 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.
*/
#if UNITY_2020_2_OR_NEWER
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEditor.AssetImporters; // AssetImporters namespace became non-experimental in 2020.2.0f1
/**
* Asset processor that will read custom PBR properties from a Phong material description as an asset file is
* imported. These custom properties are applied to standard lit shader properties on the material that is being
* pre processed.
* This is required since the Phong shader is the only one which the unity built-in FBXMaterialDescriptionPreprocessor
* will convert to a "Standard Lit" shader when parsing an FBX file.
* The advantage of performing this processing here, rather than leveraging custom materials in the unity package, is
* that the Asset Import step will create a suitable material for either the URP or Builtin render pipeline.
*/
public class PhongPbrMaterialDescriptionPostprocessor : AssetPostprocessor
{
private const uint Version = 1;
// Required to be executed after every builtin unity processor to ensure the smoothness property is set properly
private const int Order = 11;
// Smoothness source (Albedo or Metallic map alpha)
private static readonly int SmoothnessTextureChannelPropId = Shader.PropertyToID("_SmoothnessTextureChannel");
private static readonly string SmoothnessTextureChannelCustomProp = "OVR_SmoothnessTextureChannel";
// Smoothness map and scalar properties:
private static readonly int SmoothnessValueBrpPropId = Shader.PropertyToID("_Glossiness");
private static readonly int SmoothnessValueUrpPropId = Shader.PropertyToID("_Smoothness");
private static readonly int SmoothnessScaleValuePropId = Shader.PropertyToID("_GlossMapScale");
private static readonly string SmoothnessFactorCustomProp = "OVR_SmoothnessFactor";
// Metallic map
private static readonly int MetallicTexturePropId = Shader.PropertyToID("_MetallicGlossMap");
private static readonly int MetallicValuePropId = Shader.PropertyToID("_Metallic");
private static readonly string MetallicFactorPhongProp = "ReflectionFactor";
// Parallax
private static readonly int ParallaxTexturePropId = Shader.PropertyToID("_ParallaxMap");
private static readonly int ParallaxStrengthPropId = Shader.PropertyToID("_Parallax");
private static readonly string ParallaxCustomProp = "OVR_HeightMap";
private static readonly string ParallaxStrengthCustomProp = "OVR_HeightFactor";
// Occlusion
private static readonly int OcclusionTexturePropId = Shader.PropertyToID("_OcclusionMap");
private static readonly int OcclusionStrengthPropId = Shader.PropertyToID("_OcclusionStrength");
private static readonly string OcclusionMapCustomProp = "OVR_OcclusionMap";
private static readonly string OcclusionStrengthCustomProp = "OVR_OcclusionFactor";
// Detail
private static readonly int DetailTexturePropId = Shader.PropertyToID("_DetailMask");
private static readonly string DetailMapCustomProp = "OVR_DetailMap";
public override uint GetVersion()
{
return Version;
}
public override int GetPostprocessOrder()
{
return Order;
}
public void OnPreprocessMaterialDescription(MaterialDescription description, Material material,
AnimationClip[] clips)
{
// Asset Processor supports only URP and Builtin standard shaders.
bool isBirpStandardLit = material.shader.name == "Standard";
bool isUrpStandardLit = material.shader.name == "Universal Render Pipeline/Lit";
if (isBirpStandardLit || isUrpStandardLit)
{
var lowerCaseExtension = Path.GetExtension(assetPath).ToLower();
if (lowerCaseExtension == ".fbx" || lowerCaseExtension == ".dae" || lowerCaseExtension == ".obj" ||
lowerCaseExtension == ".blend" || lowerCaseExtension == ".mb" || lowerCaseExtension == ".ma" ||
lowerCaseExtension == ".max")
{
ReadOvrPbrProperties(description, material, isUrpStandardLit);
}
}
}
private void ReadOvrPbrProperties(MaterialDescription description, Material material, bool isUrp)
{
if (!HasOvrProperty(description))
{
// Only overwrite values if an OVR property was detected. This
// should prevent accidental conflicts with any other user defined AssetPostprocessor's
// since we are reading from the Phong Reflection property, not a custom user defined
// property.
return;
}
Vector4 vectorProperty;
float floatProperty;
TexturePropertyDescription textureProperty;
if (description.TryGetProperty(SmoothnessTextureChannelCustomProp, out floatProperty))
{
var isAlbedoAlphaChannel = floatProperty > float.Epsilon;
material.SetFloat(SmoothnessTextureChannelPropId, isAlbedoAlphaChannel ? 1.0f : 0.0f);
var isOpaque = (material.HasProperty("_Mode") && material.GetFloat("_Mode") == 0.0) ||
(material.HasProperty("_Surface") && material.GetFloat("_Surface") == 0.0);
if (isAlbedoAlphaChannel && isOpaque)
{
material.EnableKeyword("_SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A");
}
}
if (description.TryGetProperty(SmoothnessFactorCustomProp, out floatProperty))
{
// Note: setting both value and scale value does not seem to cause any problems.
// For smoothness texture channel = metallic alpha
material.SetFloat(SmoothnessValueBrpPropId, floatProperty);
// For smoothness texture channel = albedo alpha
material.SetFloat(SmoothnessScaleValuePropId, floatProperty);
// For smoothness on URP metallic and albedo alpha
material.SetFloat(SmoothnessValueUrpPropId, floatProperty);
}
if (description.TryGetProperty(ParallaxStrengthCustomProp, out floatProperty))
{
material.SetFloat(ParallaxStrengthPropId, floatProperty);
}
if (description.TryGetProperty(OcclusionStrengthCustomProp, out floatProperty))
{
material.SetFloat(OcclusionStrengthPropId, floatProperty);
}
if (description.TryGetProperty("DiffuseColor", out vectorProperty))
{
// URP uses _BaseColor; BIRP uses _Color
var materialColorProp = isUrp ? "_BaseColor" : "_Color";
var diffuseColor = material.GetColor(materialColorProp);
if (!description.TryGetProperty("DiffuseFactor", out float diffuseFactorProperty))
{
diffuseFactorProperty = 1.0f;
}
diffuseColor.r = vectorProperty.x * diffuseFactorProperty;
diffuseColor.g = vectorProperty.y * diffuseFactorProperty;
diffuseColor.b = vectorProperty.z * diffuseFactorProperty;
// Re-assign the diffuse color to work around incorrect color space behavior in the regular FBX importer:
// in gamma mode, it seems to double-apply the gamma correction, so counteract one of the conversions.
if (PlayerSettings.colorSpace == ColorSpace.Gamma)
{
diffuseColor = new Color(
Mathf.GammaToLinearSpace(diffuseColor.r),
Mathf.GammaToLinearSpace(diffuseColor.g),
Mathf.GammaToLinearSpace(diffuseColor.b));
}
material.SetColor(materialColorProp, diffuseColor);
}
if (description.TryGetProperty(MetallicFactorPhongProp, out float metallic))
{
material.SetFloat(MetallicValuePropId, metallic);
}
if (description.TryGetProperty(MetallicFactorPhongProp, out textureProperty))
{
material.SetTexture(MetallicTexturePropId, textureProperty.texture);
material.SetTextureOffset(MetallicTexturePropId, textureProperty.offset);
material.SetTextureScale(MetallicTexturePropId, textureProperty.scale);
// BIRP uses _METALLICGLOSSMAP ; URP uses _METALLICSPECGLOSSMAP
material.EnableKeyword(isUrp ? "_METALLICSPECGLOSSMAP" : "_METALLICGLOSSMAP");
}
if (description.TryGetProperty(ParallaxCustomProp, out textureProperty))
{
material.SetTexture(ParallaxTexturePropId, textureProperty.texture);
material.SetTextureOffset(ParallaxTexturePropId, textureProperty.offset);
material.SetTextureScale(ParallaxTexturePropId, textureProperty.scale);
material.EnableKeyword("_PARALLAXMAP");
}
if (description.TryGetProperty(OcclusionMapCustomProp, out textureProperty))
{
material.SetTexture(OcclusionTexturePropId, textureProperty.texture);
material.SetTextureOffset(OcclusionTexturePropId, textureProperty.offset);
material.SetTextureScale(OcclusionTexturePropId, textureProperty.scale);
}
if (description.TryGetProperty(DetailMapCustomProp, out textureProperty))
{
material.SetTexture(DetailTexturePropId, textureProperty.texture);
material.SetTextureOffset(DetailTexturePropId, textureProperty.offset);
material.SetTextureScale(DetailTexturePropId, textureProperty.scale);
}
// Apply emissive color as well as texture map, which standard doesn't do.
if (
description.TryGetProperty("EmissiveColor", out vectorProperty) &&
vectorProperty.magnitude > vectorProperty.w
|| description.HasAnimationCurve("EmissiveColor.x"))
{
if (description.TryGetProperty("EmissiveFactor", out floatProperty))
vectorProperty *= floatProperty;
Color emissiveColor = vectorProperty;
if (PlayerSettings.colorSpace == ColorSpace.Gamma)
{
// Re-assign the color to work around incorrect color space behavior in the regular FBX importer:
// in gamma mode, it seems to double-apply the gamma correction, so counteract one of the conversions.
emissiveColor = new Color(
Mathf.GammaToLinearSpace(emissiveColor.r),
Mathf.GammaToLinearSpace(emissiveColor.g),
Mathf.GammaToLinearSpace(emissiveColor.b));
}
material.SetColor("_EmissionColor", emissiveColor);
if (floatProperty > 0.0f)
{
material.EnableKeyword("_EMISSION");
material.globalIlluminationFlags |= MaterialGlobalIlluminationFlags.RealtimeEmissive;
}
}
}
private static bool HasOvrProperty(MaterialDescription description)
{
var propertyNames = new List<string>();
description.GetFloatPropertyNames(propertyNames);
foreach (var propertyName in propertyNames)
{
if (propertyName.StartsWith("OVR_"))
{
return true;
}
}
return false;
}
}
#endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f472ecf83ab847938d92dbb8e1025d38
timeCreated: 1674865710

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8f157e4f37a940d40ac671a2d9df8e2e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b4138c426cc3b2f46aadd6898a87f6e9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,101 @@
/*
* 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 UnityEditor;
using UnityEngine;
namespace Oculus.Interaction.Editor
{
[CanEditMultipleObjects]
[CustomEditor(typeof(ControllerOffset))]
public class ControllerOffsetEditor : UnityEditor.Editor
{
private Transform _gripPoint;
private ControllerOffset _controllerOffset;
private SerializedProperty _offsetPositionProperty;
private SerializedProperty _rotationProperty;
private Pose _cachedPose;
private const float THICKNESS = 2f;
private void OnEnable()
{
_controllerOffset = target as ControllerOffset;
_offsetPositionProperty = serializedObject.FindProperty("_offset");
_rotationProperty = serializedObject.FindProperty("_rotation");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
Transform point = EditorGUILayout.ObjectField("Optional Calculate Offset To", _gripPoint, typeof(Transform), true) as Transform;
if (point != _gripPoint)
{
_gripPoint = point;
if (_gripPoint != null)
{
Pose offset = _controllerOffset.transform.Delta(_gripPoint);
_rotationProperty.quaternionValue = offset.rotation;
_offsetPositionProperty.vector3Value = offset.position;
serializedObject.ApplyModifiedProperties();
}
}
}
private void OnSceneGUI()
{
GetEditorOffset(ref _cachedPose);
Pose wristPose = _controllerOffset.transform.GetPose();
_cachedPose.Postmultiply(wristPose);
DrawAxis(_cachedPose);
}
private void DrawAxis(in Pose pose)
{
float scale = HandleUtility.GetHandleSize(pose.position);
#if UNITY_2020_2_OR_NEWER
Handles.color = Color.red;
Handles.DrawLine(pose.position, pose.position + pose.right * scale, THICKNESS);
Handles.color = Color.green;
Handles.DrawLine(pose.position, pose.position + pose.up * scale, THICKNESS);
Handles.color = Color.blue;
Handles.DrawLine(pose.position, pose.position + pose.forward * scale, THICKNESS);
#else
Handles.color = Color.red;
Handles.DrawLine(pose.position, pose.position + pose.right * scale);
Handles.color = Color.green;
Handles.DrawLine(pose.position, pose.position + pose.up * scale);
Handles.color = Color.blue;
Handles.DrawLine(pose.position, pose.position + pose.forward * scale);
#endif
}
private void GetEditorOffset(ref Pose pose)
{
pose.position = _offsetPositionProperty.vector3Value;
pose.rotation = _rotationProperty.quaternionValue;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 65515f2ba0e41684cbc782bcd3460a0a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 288b243a05b123a4c80d776930345735
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,82 @@
/*
* 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 UnityEditor;
using UnityEngine;
namespace Oculus.Interaction.Editor
{
[CanEditMultipleObjects]
[CustomEditor(typeof(ConicalFrustum))]
public class ConicalFrustumEditor : UnityEditor.Editor
{
private ConicalFrustum _frustum;
private SerializedProperty _minLengthProperty;
private SerializedProperty _maxLengthProperty;
private const float SURFACE_SPACING = 10f;
private void Awake()
{
_frustum = target as ConicalFrustum;
_minLengthProperty = serializedObject.FindProperty("_minLength");
_maxLengthProperty = serializedObject.FindProperty("_maxLength");
}
private void OnSceneGUI()
{
if (Event.current.type == EventType.Repaint)
{
Handles.color = EditorConstants.PRIMARY_COLOR;
DrawConeFrustrum();
}
}
private void DrawConeFrustrum()
{
Vector3 origin = _frustum.Pose.position;
Vector3 direction = _frustum.Pose.forward;
Vector3 tangent = _frustum.Pose.up;
float minLength = _minLengthProperty.floatValue;
float maxLength = _maxLengthProperty.floatValue;
Vector3 start = origin + direction * minLength;
Vector3 end = origin + direction * maxLength;
float minRadius = _frustum.ConeFrustumRadiusAtLength(minLength);
float maxRadius = _frustum.ConeFrustumRadiusAtLength(maxLength);
Handles.DrawLine(start, end);
for (float i = 0; i < 360; i += SURFACE_SPACING)
{
Vector3 rotatedTangent = Quaternion.AngleAxis(i, direction) * tangent;
Handles.DrawLine(
start + rotatedTangent * minRadius,
end + rotatedTangent * maxRadius);
}
Handles.DrawWireDisc(start, direction, minRadius);
Handles.DrawWireDisc(end, direction, maxRadius);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f7a5cb795adad5546a86a929a3211ab3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,87 @@
/*
* 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 UnityEditor;
using UnityEngine;
namespace Oculus.Interaction.Editor
{
[CanEditMultipleObjects]
[CustomEditor(typeof(HandWristOffset))]
public class HandWristOffsetEditor : UnityEditor.Editor
{
private HandWristOffset _wristOffset;
private SerializedProperty _offsetPositionProperty;
private SerializedProperty _rotationProperty;
private Pose _cachedPose;
private void Awake()
{
_wristOffset = target as HandWristOffset;
_offsetPositionProperty = serializedObject.FindProperty("_offset");
_rotationProperty = serializedObject.FindProperty("_rotation");
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
_offsetPositionProperty.vector3Value = EditorGUILayout.Vector3Field("Offset", _offsetPositionProperty.vector3Value);
Vector3 euler = EditorGUILayout.Vector3Field("Rotation", _rotationProperty.quaternionValue.eulerAngles);
_rotationProperty.quaternionValue = Quaternion.Euler(euler);
serializedObject.ApplyModifiedProperties();
}
private void OnSceneGUI()
{
_cachedPose.position = _wristOffset.Offset;
_cachedPose.rotation = _wristOffset.Rotation;
Pose wristPose = _wristOffset.transform.GetPose();
_cachedPose.Postmultiply(wristPose);
DrawAxis(_cachedPose);
}
private void DrawAxis(in Pose pose)
{
float scale = HandleUtility.GetHandleSize(pose.position);
#if UNITY_2020_2_OR_NEWER
Handles.color = Color.red;
Handles.DrawLine(pose.position, pose.position + pose.right * scale, EditorConstants.LINE_THICKNESS);
Handles.color = Color.green;
Handles.DrawLine(pose.position, pose.position + pose.up * scale, EditorConstants.LINE_THICKNESS);
Handles.color = Color.blue;
Handles.DrawLine(pose.position, pose.position + pose.forward * scale, EditorConstants.LINE_THICKNESS);
#else
Handles.color = Color.red;
Handles.DrawLine(pose.position, pose.position + pose.right * scale);
Handles.color = Color.green;
Handles.DrawLine(pose.position, pose.position + pose.up * scale);
Handles.color = Color.blue;
Handles.DrawLine(pose.position, pose.position + pose.forward * scale);
#endif
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 44f030f288c72ff40a4b63d93aa1bd67
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,124 @@
/*
* 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;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace Oculus.Interaction.Editor
{
public class SimplifiedEditor : UnityEditor.Editor
{
protected EditorBase _editorDrawer;
private const string OptionalSection = "Optionals";
protected virtual void OnEnable()
{
_editorDrawer = new EditorBase(serializedObject);
_editorDrawer.CreateSections(FindCustomSections(serializedObject), true);
_editorDrawer.CreateSection(OptionalSection, true);
_editorDrawer.AddToSection(OptionalSection, FindOptionals(serializedObject));
}
protected virtual void OnDisable()
{
}
public override void OnInspectorGUI()
{
_editorDrawer.DrawFullInspector();
}
private static string[] FindOptionals(SerializedObject serializedObject)
{
List<AttributedProperty<OptionalAttribute>> props = new List<AttributedProperty<OptionalAttribute>>();
UnityEngine.Object obj = serializedObject.targetObject;
if (obj != null)
{
FindAttributedSerializedFields(obj.GetType(), props);
}
return props.Where(p => (p.attribute.Flags & OptionalAttribute.Flag.DontHide) == 0)
.Select(p => p.propertyName)
.ToArray();
}
private static Dictionary<string, string[]> FindCustomSections(SerializedObject serializedObject)
{
List<AttributedProperty<SectionAttribute>> props = new List<AttributedProperty<SectionAttribute>>();
UnityEngine.Object obj = serializedObject.targetObject;
if (obj != null)
{
FindAttributedSerializedFields(obj.GetType(), props);
}
Dictionary<string, string[]> sections = new Dictionary<string, string[]>();
var namedSections = props.GroupBy(p => p.attribute.SectionName);
foreach (var namedSection in namedSections)
{
string[] values = namedSection.Select(p => p.propertyName).ToArray();
sections.Add(namedSection.Key, values);
}
return sections;
}
private static void FindAttributedSerializedFields<TAttribute>(Type type,
List<AttributedProperty<TAttribute>> props)
where TAttribute : PropertyAttribute
{
FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
foreach (FieldInfo field in fields)
{
TAttribute attribute = field.GetCustomAttribute<TAttribute>();
if (attribute == null)
{
continue;
}
if (field.GetCustomAttribute<SerializeField>() == null)
{
continue;
}
props.Add(new AttributedProperty<TAttribute>(field.Name, attribute));
}
if (typeof(Component).IsAssignableFrom(type.BaseType))
{
FindAttributedSerializedFields<TAttribute>(type.BaseType, props);
}
}
private struct AttributedProperty<TAttribute>
where TAttribute : PropertyAttribute
{
public string propertyName;
public TAttribute attribute;
public AttributedProperty(string propertyName, TAttribute attribute)
{
this.propertyName = propertyName;
this.attribute = attribute;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e3cbc28bd3229aa4da900e706b38280e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7a58b6e344f5cf448be59c0fd30beed3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,91 @@
/*
* 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 UnityEngine;
using UnityEditor;
using Oculus.Interaction.Surfaces;
using UnityEngine.SceneManagement;
using System.Linq;
namespace Oculus.Interaction.Editor
{
[CustomEditor(typeof(BoundsClipper))]
public class BoundsClipperEditor : UnityEditor.Editor
{
private bool _visualize = false;
private IEnumerable<IRemoteDrawable> _drawables;
private BoundsClipper Clipper => target as BoundsClipper;
public override void OnInspectorGUI()
{
if (GUILayout.Button(_visualize ? "Hide Surface Visuals" : "Show Surface Visuals"))
{
_visualize = !_visualize;
_drawables = null;
SceneView.RepaintAll();
}
EditorGUILayout.Space();
base.OnInspectorGUI();
}
private void UpdateDrawables()
{
_drawables = SceneManager.GetActiveScene()
.GetRootGameObjects()
.Union(new[] { Clipper.transform.root.gameObject })
.SelectMany(root => root
.GetComponentsInChildren<IClippedSurface<IBoundsClipper>>(false))
.Where((s) => s.GetClippers().Contains(Clipper))
.Select(s => CreateEditor(s as Object) as IRemoteDrawable);
}
private void OnSceneGUI()
{
if (!Clipper.GetLocalBounds(Clipper.transform, out Bounds localBounds))
{
return;
}
var prevColor = Handles.color;
var prevMatrix = Handles.matrix;
Handles.color = _visualize ? Color.white : Color.white;
Handles.matrix = Clipper.transform.localToWorldMatrix;
Handles.DrawWireCube(localBounds.center, localBounds.size);
Handles.color = prevColor;
Handles.matrix = prevMatrix;
if (_visualize)
{
if (_drawables == null)
{
UpdateDrawables();
}
foreach (var drawer in _drawables)
{
drawer.DrawRemote();
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ac62e2f2d66aeef43b16ba05a35880fa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More