Initialer Upload neues Unity-Projekt
This commit is contained in:
8
Assets/Oculus/Interaction/Editor.meta
Normal file
8
Assets/Oculus/Interaction/Editor.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e628d552f8b252d44815ffae12fd3976
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Oculus/Interaction/Editor/AutoWiring.meta
Normal file
8
Assets/Oculus/Interaction/Editor/AutoWiring.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afc952163de48b04dbf0f968256241c0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
134
Assets/Oculus/Interaction/Editor/AutoWiring/AutoWiring.cs
Normal file
134
Assets/Oculus/Interaction/Editor/AutoWiring/AutoWiring.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 87e14328c5481cd458e1749181aaa1f5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 789d3096184f6294e9fd9fe812cfdf19
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cda6aee7f7301f34d88ea873bdf0e310
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6713358c2c4d39e4ba0eae4312ee2580
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Oculus/Interaction/Editor/Body.meta
Normal file
8
Assets/Oculus/Interaction/Editor/Body.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b46659523f404646babada299c34172
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 57db1cbbceaaab34dafc9c1f0a32da20
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
205
Assets/Oculus/Interaction/Editor/Body/BodyPoseRecorder.cs
Normal file
205
Assets/Oculus/Interaction/Editor/Body/BodyPoseRecorder.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 580dbef906f46f1449a123971c210b75
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
399
Assets/Oculus/Interaction/Editor/EditorBase.cs
Normal file
399
Assets/Oculus/Interaction/Editor/EditorBase.cs
Normal 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
|
||||
}
|
||||
}
|
||||
11
Assets/Oculus/Interaction/Editor/EditorBase.cs.meta
Normal file
11
Assets/Oculus/Interaction/Editor/EditorBase.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e06bc013299e1d04a83a12b9cbfd0162
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1349
Assets/Oculus/Interaction/Editor/EditorFallbacks.cs
Normal file
1349
Assets/Oculus/Interaction/Editor/EditorFallbacks.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Assets/Oculus/Interaction/Editor/EditorFallbacks.cs.meta
Normal file
11
Assets/Oculus/Interaction/Editor/EditorFallbacks.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b401f94f5142cf4382bb48278e4f63f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Oculus/Interaction/Editor/Grab.meta
Normal file
8
Assets/Oculus/Interaction/Editor/Grab.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c93e89dd55b481b4aa6ed62d6c8227bd
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
103
Assets/Oculus/Interaction/Editor/Grab/GrabbingRuleEditor.cs
Normal file
103
Assets/Oculus/Interaction/Editor/Grab/GrabbingRuleEditor.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17f32e95fcaa23e45a5ac1297f201be2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Oculus/Interaction/Editor/Grab/HandGrab.meta
Normal file
8
Assets/Oculus/Interaction/Editor/Grab/HandGrab.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0d69d4b88de08343adc54816c3220a1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fbb2e01d7c7a6904eaa71da7a854bfad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62c8971cfb0b66040a9845cfc622ed6a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5ce7770848930447885c9abba8bb99a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92b8a050249b4ea47b9da8fe38ed12a1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1a53d90b3c752f4f84f32e5a94ea8bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
121
Assets/Oculus/Interaction/Editor/Grab/HandGrab/HandPoseEditor.cs
Normal file
121
Assets/Oculus/Interaction/Editor/Grab/HandGrab/HandPoseEditor.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 25b4f8dbe894a92489fe06cc956a42e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 070da65b3b3353149bcd22ecad3b01ef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Oculus/Interaction/Editor/Grab/SnapSurfaces.meta
Normal file
8
Assets/Oculus/Interaction/Editor/Grab/SnapSurfaces.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eef729dcc033d3e419718da8e409c0f3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 718688abdc60caa4984fe98623ff42dd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: baf3d860debef0947b62cdebdd94cb74
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69099ed7427360a4ab3734573c188647
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee7657a153e652d448fa1b7775ca7f8c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Oculus/Interaction/Editor/Grab/Visuals.meta
Normal file
8
Assets/Oculus/Interaction/Editor/Grab/Visuals.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5760a4e866e11cb46a5adff4e32acc44
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb714303644f5c343bd2c80559c02856
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Oculus/Interaction/Editor/Hands.meta
Normal file
8
Assets/Oculus/Interaction/Editor/Hands.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d1754bb0e390e44e906221c5091ed9a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b1127eced26c0748b86cc88310b1711
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
213
Assets/Oculus/Interaction/Editor/Hands/HandVisualEditor.cs
Normal file
213
Assets/Oculus/Interaction/Editor/Hands/HandVisualEditor.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 037bf294d05876e4c8dd96432f39faaa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48af58ae5328ff048acacd924604a804
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Oculus/Interaction/Editor/PackageUtils.meta
Normal file
8
Assets/Oculus/Interaction/Editor/PackageUtils.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e918f3baaf96b84e9e87647d1ffd804
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
189
Assets/Oculus/Interaction/Editor/PackageUtils/AssetListWindow.cs
Normal file
189
Assets/Oculus/Interaction/Editor/PackageUtils/AssetListWindow.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ecccce924260f0a47ae428db9ff1a6ad
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
555
Assets/Oculus/Interaction/Editor/PackageUtils/PackageCleanup.cs
Normal file
555
Assets/Oculus/Interaction/Editor/PackageUtils/PackageCleanup.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9dd3eed68fab7d40a0c5d750d3f478d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Oculus/Interaction/Editor/Poke.meta
Normal file
8
Assets/Oculus/Interaction/Editor/Poke.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0913252150f751d4892fddbe95a753e6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 308350aa983f9894b8b098e2eba08a60
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46c04bf9df613494998209c4f4705544
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Oculus/Interaction/Editor/PoseDetection.meta
Normal file
8
Assets/Oculus/Interaction/Editor/PoseDetection.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 275f3583fdaef334aa875c7f886a086c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39acc9895ae6408898722a714edbe5ce
|
||||
timeCreated: 1631575366
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 536a9cf65c7544ab9e4051eac951baa7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dffceaad44e34343a4e55875976ac097
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a4a7445af8149e0887018ba0cba6abb
|
||||
timeCreated: 1627935350
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 548ff893703b939438b2a96d8c8fcba0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51ada7d46c40f684689664f1b670f7a3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca473a2dca4a42988bd13c46f6ed077f
|
||||
timeCreated: 1631569404
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 195af56ffbd76364e83ff93de249a25a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Oculus/Interaction/Editor/Scripts.meta
Normal file
8
Assets/Oculus/Interaction/Editor/Scripts.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48d2d34c0a202af4a8314dd3ad9f5c39
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f472ecf83ab847938d92dbb8e1025d38
|
||||
timeCreated: 1674865710
|
||||
8
Assets/Oculus/Interaction/Editor/Selection.meta
Normal file
8
Assets/Oculus/Interaction/Editor/Selection.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f157e4f37a940d40ac671a2d9df8e2e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b4138c426cc3b2f46aadd6898a87f6e9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65515f2ba0e41684cbc782bcd3460a0a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Oculus/Interaction/Editor/Selection/Hands.meta
Normal file
8
Assets/Oculus/Interaction/Editor/Selection/Hands.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 288b243a05b123a4c80d776930345735
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f7a5cb795adad5546a86a929a3211ab3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44f030f288c72ff40a4b63d93aa1bd67
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
124
Assets/Oculus/Interaction/Editor/SimplifiedEditor.cs
Normal file
124
Assets/Oculus/Interaction/Editor/SimplifiedEditor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Oculus/Interaction/Editor/SimplifiedEditor.cs.meta
Normal file
11
Assets/Oculus/Interaction/Editor/SimplifiedEditor.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3cbc28bd3229aa4da900e706b38280e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Oculus/Interaction/Editor/Surfaces.meta
Normal file
8
Assets/Oculus/Interaction/Editor/Surfaces.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a58b6e344f5cf448be59c0fd30beed3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
Reference in New Issue
Block a user