clean project
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0d69d4b88de08343adc54816c3220a1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,105 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities 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.HandPosing.Editor
|
||||
{
|
||||
[CanEditMultipleObjects]
|
||||
[CustomEditor(typeof(HandGrabInteractable))]
|
||||
public class HandGrabInteractableEditor : UnityEditor.Editor
|
||||
{
|
||||
private HandGrabInteractable _interactable;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_interactable = target as HandGrabInteractable;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
|
||||
DrawGrabPointsMenu();
|
||||
GUILayout.Space(20f);
|
||||
DrawGenerationMenu();
|
||||
}
|
||||
|
||||
private void DrawGrabPointsMenu()
|
||||
{
|
||||
if (GUILayout.Button("Refresh HandGrab Points"))
|
||||
{
|
||||
_interactable.GrabPoints.Clear();
|
||||
HandGrabPoint[] handGrabPoints = _interactable.GetComponentsInChildren<HandGrabPoint>();
|
||||
_interactable.GrabPoints.AddRange(handGrabPoints);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Add HandGrab Point"))
|
||||
{
|
||||
if (_interactable.GrabPoints.Count > 0)
|
||||
{
|
||||
AddHandGrabPoint(_interactable.GrabPoints[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddHandGrabPoint();
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Replicate Default Scaled HandGrab Points"))
|
||||
{
|
||||
if (_interactable.GrabPoints.Count > 0)
|
||||
{
|
||||
AddHandGrabPoint(_interactable.GrabPoints[0], 0.8f);
|
||||
AddHandGrabPoint(_interactable.GrabPoints[0], 1.2f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("You have to provide a default HandGrabPoint first!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddHandGrabPoint(HandGrabPoint copy = null, float? scale = null)
|
||||
{
|
||||
HandGrabPoint point = _interactable.CreatePoint();
|
||||
if (copy != null)
|
||||
{
|
||||
HandGrabPointEditor.CloneHandGrabPoint(copy, point);
|
||||
if (scale.HasValue)
|
||||
{
|
||||
HandGrabPointData scaledData = point.SaveData();
|
||||
scaledData.scale = scale.Value;
|
||||
point.LoadData(scaledData, copy.RelativeTo);
|
||||
}
|
||||
}
|
||||
_interactable.GrabPoints.Add(point);
|
||||
}
|
||||
|
||||
private void DrawGenerationMenu()
|
||||
{
|
||||
if (GUILayout.Button("Create Mirrored HandGrabInteractable"))
|
||||
{
|
||||
HandGrabInteractable mirrorInteractable = HandGrabInteractable.Create(_interactable.RelativeTo);
|
||||
|
||||
foreach (HandGrabPoint point in _interactable.GrabPoints)
|
||||
{
|
||||
HandGrabPoint mirrorPoint = mirrorInteractable.CreatePoint();
|
||||
HandGrabPointEditor.MirrorHandGrabPoint(point, mirrorPoint);
|
||||
mirrorPoint.transform.SetParent(mirrorInteractable.transform);
|
||||
mirrorInteractable.GrabPoints.Add(mirrorPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5ce7770848930447885c9abba8bb99a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,325 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities 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.HandPosing.Visuals;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.HandPosing.Editor
|
||||
{
|
||||
[CanEditMultipleObjects]
|
||||
[CustomEditor(typeof(HandGrabPoint))]
|
||||
public class HandGrabPointEditor : UnityEditor.Editor
|
||||
{
|
||||
private HandGrabPoint _handGrabPoint;
|
||||
|
||||
private HandGhostProvider _ghostVisualsProvider;
|
||||
private HandGhost _handGhost;
|
||||
private HandPuppet _ghostPuppet;
|
||||
private Handedness _lastHandedness;
|
||||
|
||||
private HandPose _poseTransfer;
|
||||
private bool _editingFingers;
|
||||
|
||||
private SerializedProperty _handPoseProperty;
|
||||
|
||||
private const float GIZMO_SCALE = 0.005f;
|
||||
private static readonly Color FLEX_COLOR = new Color(0f, 1f, 1f, 0.5f);
|
||||
private static readonly Color SPREAD_COLOR = new Color(0.5f, 0.3f, 1f, 0.5f);
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_handGrabPoint = target as HandGrabPoint;
|
||||
_handPoseProperty = serializedObject.FindProperty("_handPose");
|
||||
AssignMissingGhostProvider();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
DestroyGhost();
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
|
||||
if (_handGrabPoint.HandPose != null
|
||||
&& _handPoseProperty != null)
|
||||
{
|
||||
EditorGUILayout.PropertyField(_handPoseProperty);
|
||||
EditorGUILayout.Space();
|
||||
DrawGhostMenu(_handGrabPoint.HandPose, false);
|
||||
}
|
||||
else if (_handGhost != null)
|
||||
{
|
||||
DestroyGhost();
|
||||
}
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void DrawGhostMenu(HandPose handPose, bool forceCreate)
|
||||
{
|
||||
GUIStyle boldStyle = new GUIStyle(GUI.skin.label) { fontStyle = FontStyle.Bold };
|
||||
EditorGUILayout.LabelField("Interactive Edition", boldStyle);
|
||||
|
||||
HandGhostProvider provider = EditorGUILayout.ObjectField("Ghost Provider", _ghostVisualsProvider, typeof(HandGhostProvider), false) as HandGhostProvider;
|
||||
if (forceCreate
|
||||
|| provider != _ghostVisualsProvider
|
||||
|| _handGhost == null
|
||||
|| _lastHandedness != handPose.Handedness)
|
||||
{
|
||||
RegenerateGhost(provider);
|
||||
}
|
||||
_lastHandedness = handPose.Handedness;
|
||||
|
||||
if (_handGrabPoint.SnapSurface == null)
|
||||
{
|
||||
_editingFingers = true;
|
||||
}
|
||||
else if (GUILayout.Button(_editingFingers ? "Follow Surface" : "Edit fingers"))
|
||||
{
|
||||
_editingFingers = !_editingFingers;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSceneGUI()
|
||||
{
|
||||
SceneView view = SceneView.currentDrawingSceneView;
|
||||
if (view == null || _handGhost == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_editingFingers)
|
||||
{
|
||||
GhostEditFingers();
|
||||
if (AnyPuppetBoneChanged())
|
||||
{
|
||||
TransferGhostBoneRotations();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GhostFollowSurface();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void EditSnapPoint(Transform snapPoint)
|
||||
{
|
||||
Vector3 pos = snapPoint.position;
|
||||
Quaternion rot = snapPoint.rotation;
|
||||
Handles.TransformHandle(ref pos, ref rot);
|
||||
|
||||
if (pos != snapPoint.position)
|
||||
{
|
||||
Undo.RecordObject(snapPoint.transform, "SnapPoint Position");
|
||||
snapPoint.position = pos;
|
||||
}
|
||||
|
||||
if (rot != snapPoint.rotation)
|
||||
{
|
||||
Undo.RecordObject(snapPoint.transform, "SnapPoint Rotation");
|
||||
snapPoint.rotation = rot;
|
||||
}
|
||||
}
|
||||
|
||||
#region generation
|
||||
/// <summary>
|
||||
/// Generates a new HandGrabPointData that mirrors the provided one. Left hand becomes right hand and vice-versa.
|
||||
/// The mirror axis is defined by the surface of the snap point, if any, if none a best-guess is provided
|
||||
/// but note that it can then moved manually in the editor.
|
||||
/// </summary>
|
||||
/// <param name="originalPoint">The point to mirror</param>
|
||||
/// <param name="originalPoint">The target HandGrabPoint to set as mirrored of the originalPoint</param>
|
||||
public static void MirrorHandGrabPoint(HandGrabPoint originalPoint, HandGrabPoint mirrorPoint)
|
||||
{
|
||||
HandPose handPose = originalPoint.HandPose;
|
||||
|
||||
Handedness oppositeHandedness = handPose.Handedness == Handedness.Left ? Handedness.Right : Handedness.Left;
|
||||
|
||||
HandGrabPointData mirrorData = originalPoint.SaveData();
|
||||
mirrorData.handPose.Handedness = oppositeHandedness;
|
||||
|
||||
if (originalPoint.SnapSurface != null)
|
||||
{
|
||||
mirrorData.gripPose = originalPoint.SnapSurface.MirrorPose(mirrorData.gripPose);
|
||||
}
|
||||
else
|
||||
{
|
||||
mirrorData.gripPose = mirrorData.gripPose.MirrorPoseRotation(Vector3.forward, Vector3.up);
|
||||
Vector3 translation = Vector3.Project(mirrorData.gripPose.position, Vector3.right);
|
||||
mirrorData.gripPose.position = mirrorData.gripPose.position - 2f * translation;
|
||||
}
|
||||
|
||||
mirrorPoint.LoadData(mirrorData, originalPoint.RelativeTo);
|
||||
if (originalPoint.SnapSurface != null)
|
||||
{
|
||||
SnapSurfaces.ISnapSurface mirroredSurface = originalPoint.SnapSurface.CreateMirroredSurface(mirrorPoint.gameObject);
|
||||
mirrorPoint.InjectOptionalSurface(mirroredSurface);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CloneHandGrabPoint(HandGrabPoint originalPoint, HandGrabPoint targetPoint)
|
||||
{
|
||||
HandGrabPointData mirrorData = originalPoint.SaveData();
|
||||
targetPoint.LoadData(mirrorData, originalPoint.RelativeTo);
|
||||
if (originalPoint.SnapSurface != null)
|
||||
{
|
||||
SnapSurfaces.ISnapSurface mirroredSurface = originalPoint.SnapSurface.CreateDuplicatedSurface(targetPoint.gameObject);
|
||||
targetPoint.InjectOptionalSurface(mirroredSurface);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ghost
|
||||
|
||||
private void AssignMissingGhostProvider()
|
||||
{
|
||||
if (_ghostVisualsProvider != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HandGhostProvider[] providers = Resources.FindObjectsOfTypeAll<HandGhostProvider>();
|
||||
if (providers != null && providers.Length > 0)
|
||||
{
|
||||
_ghostVisualsProvider = providers[0];
|
||||
}
|
||||
}
|
||||
|
||||
private void RegenerateGhost(HandGhostProvider provider)
|
||||
{
|
||||
_ghostVisualsProvider = provider;
|
||||
DestroyGhost();
|
||||
CreateGhost();
|
||||
}
|
||||
|
||||
private void CreateGhost()
|
||||
{
|
||||
if (_ghostVisualsProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HandGhost ghostPrototype = _ghostVisualsProvider.GetHand(_handGrabPoint.HandPose.Handedness);
|
||||
_handGhost = GameObject.Instantiate(ghostPrototype, _handGrabPoint.transform);
|
||||
_handGhost.gameObject.hideFlags = HideFlags.HideAndDontSave;
|
||||
_handGhost.SetPose(_handGrabPoint);
|
||||
_ghostPuppet = _handGhost.GetComponent<HandPuppet>();
|
||||
}
|
||||
|
||||
private void DestroyGhost()
|
||||
{
|
||||
if (_handGhost != null)
|
||||
{
|
||||
GameObject.DestroyImmediate(_handGhost.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private void GhostFollowSurface()
|
||||
{
|
||||
if (_handGhost == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Pose ghostTargetPose = _handGrabPoint.RelativeGrip;
|
||||
|
||||
if (_handGrabPoint.SnapSurface != null)
|
||||
{
|
||||
Vector3 mousePosition = Event.current.mousePosition;
|
||||
Ray ray = HandleUtility.GUIPointToWorldRay(mousePosition);
|
||||
Pose recorderPose = _handGrabPoint.transform.GetPose();
|
||||
if (_handGrabPoint.SnapSurface.CalculateBestPoseAtSurface(ray, recorderPose, out Pose target))
|
||||
{
|
||||
ghostTargetPose.position = _handGrabPoint.RelativeTo.InverseTransformPoint(target.position);
|
||||
ghostTargetPose.rotation = Quaternion.Inverse(_handGrabPoint.RelativeTo.rotation) * target.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
_handGhost.SetGripPose(ghostTargetPose, _handGrabPoint.RelativeTo);
|
||||
}
|
||||
|
||||
private void GhostEditFingers()
|
||||
{
|
||||
HandPuppet puppet = _handGhost.GetComponent<HandPuppet>();
|
||||
if (puppet != null && puppet.JointMaps != null)
|
||||
{
|
||||
DrawBonesRotator(puppet.JointMaps);
|
||||
}
|
||||
}
|
||||
|
||||
private void TransferGhostBoneRotations()
|
||||
{
|
||||
_poseTransfer = _handGrabPoint.HandPose;
|
||||
_ghostPuppet.CopyCachedJoints(ref _poseTransfer);
|
||||
_handGrabPoint.HandPose.CopyFrom(_poseTransfer);
|
||||
}
|
||||
|
||||
private void DrawBonesRotator(List<HandJointMap> bones)
|
||||
{
|
||||
foreach (HandJointMap bone in bones)
|
||||
{
|
||||
int metadataIndex = FingersMetadata.HandJointIdToIndex(bone.id);
|
||||
HandFinger finger = FingersMetadata.HAND_FINGER_ID[metadataIndex];
|
||||
|
||||
if (_handGrabPoint.HandPose.FingersFreedom[(int)finger] == JointFreedom.Free)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Handles.color = FLEX_COLOR;
|
||||
Quaternion rotation = Handles.Disc(bone.transform.rotation, bone.transform.position,
|
||||
bone.transform.forward, GIZMO_SCALE, false, 0);
|
||||
|
||||
if (FingersMetadata.HAND_JOINT_CAN_SPREAD[metadataIndex])
|
||||
{
|
||||
Handles.color = SPREAD_COLOR;
|
||||
rotation = Handles.Disc(rotation, bone.transform.position,
|
||||
bone.transform.up, GIZMO_SCALE, false, 0);
|
||||
}
|
||||
|
||||
if (bone.transform.rotation != rotation)
|
||||
{
|
||||
Undo.RecordObject(bone.transform, "Bone Rotation");
|
||||
bone.transform.rotation = rotation;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects if we have moved the transforms of the fingers so the data can be kept up to date.
|
||||
/// To be used in Edit-mode.
|
||||
/// </summary>
|
||||
/// <returns>True if any of the fingers has moved from the previous frame.</returns>
|
||||
private bool AnyPuppetBoneChanged()
|
||||
{
|
||||
bool hasChanged = false;
|
||||
foreach (HandJointMap bone in _ghostPuppet.JointMaps)
|
||||
{
|
||||
if (bone.transform.hasChanged)
|
||||
{
|
||||
bone.transform.hasChanged = false;
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
return hasChanged;
|
||||
}
|
||||
|
||||
#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,67 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities 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 System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.HandPosing.Editor
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(HandPose))]
|
||||
public class HandPoseEditor : PropertyDrawer
|
||||
{
|
||||
private bool _foldedFreedom = true;
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
EditorGUI.BeginProperty(position, label, property);
|
||||
EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
DrawEnumProperty<Handedness>(property, "Handedness:", "_handedness", false);
|
||||
DrawFingersFreedomMenu(property);
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUI.EndProperty();
|
||||
}
|
||||
|
||||
private void DrawFingersFreedomMenu(SerializedProperty property)
|
||||
{
|
||||
_foldedFreedom = EditorGUILayout.Foldout(_foldedFreedom, "Fingers Freedom", true);
|
||||
if (_foldedFreedom)
|
||||
{
|
||||
SerializedProperty fingersFreedom = property.FindPropertyRelative("_fingersFreedom");
|
||||
EditorGUILayout.BeginVertical();
|
||||
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)EditorGUILayout.EnumPopup($"{fingerID}: ", current);
|
||||
finger.intValue = (int)selected;
|
||||
}
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawEnumProperty<TEnum>(SerializedProperty parentProperty, 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 ?
|
||||
EditorGUILayout.EnumFlagsField(title, value)
|
||||
: EditorGUILayout.EnumPopup(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,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eef729dcc033d3e419718da8e409c0f3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,163 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities 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 UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.HandPosing.SnapSurfaces.Editor
|
||||
{
|
||||
[CustomEditor(typeof(BoxSurface))]
|
||||
[CanEditMultipleObjects]
|
||||
public class BoxEditor : UnityEditor.Editor
|
||||
{
|
||||
private static readonly Color NONINTERACTABLE_COLOR = new Color(0f, 1f, 1f, 0.1f);
|
||||
private static readonly Color INTERACTABLE_COLOR = new Color(0f, 1f, 1f, 0.5f);
|
||||
|
||||
private BoxBoundsHandle _boxHandle = new BoxBoundsHandle();
|
||||
private BoxSurface _surface;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_boxHandle.handleColor = INTERACTABLE_COLOR;
|
||||
_boxHandle.wireframeColor = NONINTERACTABLE_COLOR;
|
||||
_boxHandle.axes = PrimitiveBoundsHandle.Axes.X | PrimitiveBoundsHandle.Axes.Z;
|
||||
|
||||
_surface = (target as BoxSurface);
|
||||
}
|
||||
|
||||
public void OnSceneGUI()
|
||||
{
|
||||
DrawRotator(_surface);
|
||||
DrawBoxEditor(_surface);
|
||||
DrawSlider(_surface);
|
||||
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
DrawSnapLines(_surface);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSnapLines(BoxSurface surface)
|
||||
{
|
||||
Handles.color = INTERACTABLE_COLOR;
|
||||
|
||||
Vector3 rightAxis = surface.Rotation * Vector3.right;
|
||||
Vector3 forwardAxis = surface.Rotation * Vector3.forward;
|
||||
Vector3 forwardOffset = forwardAxis * surface.Size.z;
|
||||
|
||||
Vector3 bottomLeft = surface.transform.position - rightAxis * surface.Size.x * (1f - surface.WidthOffset);
|
||||
Vector3 bottomRight = surface.transform.position + rightAxis * surface.Size.x * (surface.WidthOffset);
|
||||
Vector3 topLeft = bottomLeft + forwardOffset;
|
||||
Vector3 topRight = bottomRight + forwardOffset;
|
||||
|
||||
Handles.DrawLine(bottomLeft + rightAxis * surface.SnapOffset.y, bottomRight + rightAxis * surface.SnapOffset.x);
|
||||
Handles.DrawLine(topLeft - rightAxis * surface.SnapOffset.x, topRight - rightAxis * surface.SnapOffset.y);
|
||||
Handles.DrawLine(bottomLeft - forwardAxis * surface.SnapOffset.z, topLeft - forwardAxis * surface.SnapOffset.w);
|
||||
Handles.DrawLine(bottomRight + forwardAxis * surface.SnapOffset.w, topRight + forwardAxis * surface.SnapOffset.z);
|
||||
}
|
||||
|
||||
private void DrawSlider(BoxSurface surface)
|
||||
{
|
||||
Handles.color = INTERACTABLE_COLOR;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Vector3 rightDir = surface.Rotation * Vector3.right;
|
||||
Vector3 forwardDir = surface.Rotation * Vector3.forward;
|
||||
Vector3 bottomRight = surface.transform.position
|
||||
+ rightDir * surface.Size.x * (surface.WidthOffset);
|
||||
Vector3 bottomLeft = surface.transform.position
|
||||
- rightDir * surface.Size.x * (1f - surface.WidthOffset);
|
||||
Vector3 topRight = bottomRight + forwardDir * surface.Size.z;
|
||||
|
||||
Vector3 rightHandle = DrawOffsetHandle(bottomRight + rightDir * surface.SnapOffset.x, rightDir);
|
||||
Vector3 leftHandle = DrawOffsetHandle(bottomLeft + rightDir * surface.SnapOffset.y, -rightDir);
|
||||
Vector3 topHandle = DrawOffsetHandle(topRight + forwardDir * surface.SnapOffset.z, forwardDir);
|
||||
Vector3 bottomHandle = DrawOffsetHandle(bottomRight + forwardDir * surface.SnapOffset.w, -forwardDir);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(surface, "Change Offset Box");
|
||||
Vector4 offset = surface.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.SnapOffset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
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(BoxSurface surface)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Quaternion rotation = Handles.RotationHandle(surface.Rotation, surface.transform.position);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(surface, "Change Rotation Box");
|
||||
surface.Rotation = rotation;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawBoxEditor(BoxSurface surface)
|
||||
{
|
||||
Quaternion rot = surface.Rotation;
|
||||
Vector3 size = surface.Size;
|
||||
|
||||
Vector3 snapP = surface.transform.position;
|
||||
|
||||
_boxHandle.size = size;
|
||||
float widthPos = Mathf.Lerp(-size.x * 0.5f, size.x * 0.5f, surface.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.Size = _boxHandle.size;
|
||||
float width = _boxHandle.size.x;
|
||||
surface.WidthOffset = width != 0f ? (_boxHandle.center.x + width * 0.5f) / width : 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
private float RemapClamped(float value, (float, float) from, (float, float) to)
|
||||
{
|
||||
value = Mathf.Clamp(value, from.Item1, from.Item2);
|
||||
return to.Item1 + (value - from.Item1) * (to.Item2 - to.Item1) / (from.Item2 - from.Item1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: baf3d860debef0947b62cdebdd94cb74
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,132 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities 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 UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.HandPosing.SnapSurfaces.Editor
|
||||
{
|
||||
[CustomEditor(typeof(CylinderSurface))]
|
||||
[CanEditMultipleObjects]
|
||||
public class CylinderEditor : UnityEditor.Editor
|
||||
{
|
||||
private static readonly Color NONINTERACTABLE_COLOR = new Color(0f, 1f, 1f, 0.1f);
|
||||
private static readonly Color INTERACTABLE_COLOR = new Color(0f, 1f, 1f, 0.5f);
|
||||
private const float DRAW_SURFACE_ANGULAR_RESOLUTION = 5f;
|
||||
|
||||
private ArcHandle _arcHandle = new ArcHandle();
|
||||
private Vector3[] _surfaceEdges;
|
||||
|
||||
CylinderSurface _surface;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_arcHandle.SetColorWithRadiusHandle(INTERACTABLE_COLOR, 0f);
|
||||
_surface = (target as CylinderSurface);
|
||||
}
|
||||
|
||||
public void OnSceneGUI()
|
||||
{
|
||||
DrawEndsCaps(_surface);
|
||||
DrawArcEditor(_surface);
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
DrawSurfaceVolume(_surface);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawEndsCaps(CylinderSurface surface)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
Quaternion handleRotation = (surface.RelativeTo ?? surface.transform).rotation;
|
||||
|
||||
Vector3 startPosition = Handles.PositionHandle(surface.StartPoint, handleRotation);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(surface, "Change Start Cylinder Position");
|
||||
surface.StartPoint = startPosition;
|
||||
}
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Vector3 endPosition = Handles.PositionHandle(surface.EndPoint, handleRotation);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(surface, "Change Start Cylinder Position");
|
||||
surface.EndPoint = endPosition;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSurfaceVolume(CylinderSurface surface)
|
||||
{
|
||||
Vector3 start = surface.StartPoint;
|
||||
Vector3 end = surface.EndPoint;
|
||||
float radius = surface.Radius;
|
||||
|
||||
Handles.color = INTERACTABLE_COLOR;
|
||||
Handles.DrawWireArc(end,
|
||||
surface.Direction,
|
||||
surface.StartAngleDir,
|
||||
surface.Angle,
|
||||
radius);
|
||||
|
||||
Handles.DrawLine(start, end);
|
||||
Handles.DrawLine(start, start + surface.StartAngleDir * radius);
|
||||
Handles.DrawLine(start, start + surface.EndAngleDir * radius);
|
||||
Handles.DrawLine(end, end + surface.StartAngleDir * radius);
|
||||
Handles.DrawLine(end, end + surface.EndAngleDir * radius);
|
||||
|
||||
int edgePoints = Mathf.CeilToInt((2 * surface.Angle) / DRAW_SURFACE_ANGULAR_RESOLUTION) + 3;
|
||||
if (_surfaceEdges == null
|
||||
|| _surfaceEdges.Length != edgePoints)
|
||||
{
|
||||
_surfaceEdges = new Vector3[edgePoints];
|
||||
}
|
||||
|
||||
Handles.color = NONINTERACTABLE_COLOR;
|
||||
int i = 0;
|
||||
for (float angle = 0f; angle < surface.Angle; angle += DRAW_SURFACE_ANGULAR_RESOLUTION)
|
||||
{
|
||||
Vector3 direction = Quaternion.AngleAxis(angle, surface.Direction) * surface.StartAngleDir;
|
||||
_surfaceEdges[i++] = start + direction * radius;
|
||||
_surfaceEdges[i++] = end + direction * radius;
|
||||
}
|
||||
_surfaceEdges[i++] = start + surface.EndAngleDir * radius;
|
||||
_surfaceEdges[i++] = end + surface.EndAngleDir * radius;
|
||||
Handles.DrawPolyLine(_surfaceEdges);
|
||||
}
|
||||
|
||||
private void DrawArcEditor(CylinderSurface surface)
|
||||
{
|
||||
float radius = surface.Radius;
|
||||
_arcHandle.angle = surface.Angle;
|
||||
_arcHandle.radius = radius;
|
||||
|
||||
Matrix4x4 handleMatrix = Matrix4x4.TRS(
|
||||
surface.StartPoint,
|
||||
Quaternion.LookRotation(surface.StartAngleDir, surface.Direction),
|
||||
Vector3.one
|
||||
);
|
||||
using (new Handles.DrawingScope(handleMatrix))
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
_arcHandle.DrawHandle();
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(surface, "Change Cylinder Properties");
|
||||
surface.Angle = _arcHandle.angle;
|
||||
radius = _arcHandle.radius;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69099ed7427360a4ab3734573c188647
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,77 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities 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 UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.HandPosing.SnapSurfaces.Editor
|
||||
{
|
||||
[CustomEditor(typeof(SphereSurface))]
|
||||
[CanEditMultipleObjects]
|
||||
public class SphereEditor : UnityEditor.Editor
|
||||
{
|
||||
private static readonly Color NONINTERACTABLE_COLOR = new Color(0f, 1f, 1f, 0.1f);
|
||||
private static readonly Color INTERACTABLE_COLOR = new Color(0f, 1f, 1f, 0.5f);
|
||||
|
||||
private SphereBoundsHandle _sphereHandle = new SphereBoundsHandle();
|
||||
private SphereSurface _surface;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_sphereHandle.SetColor(INTERACTABLE_COLOR);
|
||||
_sphereHandle.midpointHandleDrawFunction = null;
|
||||
|
||||
_surface = (target as SphereSurface);
|
||||
}
|
||||
|
||||
public void OnSceneGUI()
|
||||
{
|
||||
DrawCentre(_surface);
|
||||
Handles.color = Color.white;
|
||||
DrawSphereEditor(_surface);
|
||||
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
DrawSurfaceVolume(_surface);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCentre(SphereSurface surface)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
Quaternion handleRotation = (surface.RelativeTo ?? surface.transform).rotation;
|
||||
Vector3 centrePosition = Handles.PositionHandle(surface.Centre, handleRotation);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(surface, "Change Centre Sphere Position");
|
||||
surface.Centre = centrePosition;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSurfaceVolume(SphereSurface surface)
|
||||
{
|
||||
Handles.color = INTERACTABLE_COLOR;
|
||||
Vector3 startLine = surface.Centre;
|
||||
Vector3 endLine = startLine + surface.Rotation * Vector3.forward * surface.Radius;
|
||||
Handles.DrawDottedLine(startLine, endLine, 5);
|
||||
}
|
||||
private void DrawSphereEditor(SphereSurface surface)
|
||||
{
|
||||
_sphereHandle.radius = surface.Radius;
|
||||
_sphereHandle.center = surface.Centre;
|
||||
|
||||
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/HandPosing/Visuals.meta
Normal file
8
Assets/Oculus/Interaction/Editor/HandPosing/Visuals.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5760a4e866e11cb46a5adff4e32acc44
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,114 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities 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.HandPosing.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:
|
||||
Reference in New Issue
Block a user