Initialer Upload neues Unity-Projekt

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

View File

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

View File

@ -0,0 +1,36 @@
/*
* 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;
namespace Oculus.Interaction
{
public class ConditionalHideAttribute : PropertyAttribute
{
public string ConditionalFieldPath { get; private set; }
public object HideValue { get; private set; }
public ConditionalHideAttribute(string fieldName, object hideValue)
{
ConditionalFieldPath = fieldName;
HideValue = hideValue;
}
}
}

View File

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

View File

@ -0,0 +1,85 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
using System.Reflection;
using System;
using UnityEditor;
namespace Oculus.Interaction
{
[AttributeUsage(AttributeTargets.Field)]
public class InspectorButtonAttribute : PropertyAttribute
{
private const float BUTTON_WIDTH = 80;
private const float BUTTON_HEIGHT = 20;
public float ButtonWidth { get; set; } = BUTTON_WIDTH;
public readonly string methodName;
public readonly float buttonHeight;
public InspectorButtonAttribute(string methodName)
{
this.methodName = methodName;
this.buttonHeight = BUTTON_HEIGHT;
}
public InspectorButtonAttribute(string methodName, float buttonHeight)
{
this.methodName = methodName;
this.buttonHeight = buttonHeight;
}
}
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(InspectorButtonAttribute))]
public class InspectorButtonPropertyDrawer : PropertyDrawer
{
private MethodInfo _method = null;
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
InspectorButtonAttribute inspectorButtonAttribute = (InspectorButtonAttribute)attribute;
return inspectorButtonAttribute.buttonHeight;
}
public override void OnGUI(Rect positionRect, SerializedProperty prop, GUIContent label)
{
InspectorButtonAttribute inspectorButtonAttribute = (InspectorButtonAttribute)attribute;
Rect rect = positionRect;
rect.height = inspectorButtonAttribute.buttonHeight;
if (GUI.Button(rect, label.text))
{
Type eventType = prop.serializedObject.targetObject.GetType();
string eventName = inspectorButtonAttribute.methodName;
if (_method == null)
{
_method = eventType.GetMethod(eventName,
BindingFlags.Public
| BindingFlags.NonPublic
| BindingFlags.Instance
| BindingFlags.Static);
}
_method?.Invoke(prop.serializedObject.targetObject, null);
}
}
}
#endif
}

View File

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

View File

@ -0,0 +1,58 @@
/*
* 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;
namespace Oculus.Interaction
{
/// <summary>
/// Used on a SerializedField surfaces the expectation that this field can remain empty.
/// </summary>
public class OptionalAttribute : PropertyAttribute
{
[System.Flags]
public enum Flag
{
/// <summary>
/// Presents the Optional tag and moves it into the Optional Section
/// </summary>
None = 0,
/// <summary>
/// Indicates that if the reference is missing, a new object will be
/// created and linked to it during runtime.
/// </summary>
AutoGenerated = 1 << 0,
/// <summary>
/// Indicates that even though the reference is Optional, it is important
/// for the component to work as expected.
/// </summary>
DontHide = 1 << 1
}
public Flag Flags { get; private set; } = Flag.None;
public OptionalAttribute() { }
public OptionalAttribute(Flag flags)
{
Flags = flags;
}
}
}

View File

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

View File

@ -0,0 +1,37 @@
/*
* 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;
namespace Oculus.Interaction
{
/// <summary>
/// Used on a SerializedField tags the field to be added to a custom section
/// </summary>
public class SectionAttribute : PropertyAttribute
{
public string SectionName { get; private set; } = string.Empty;
public SectionAttribute(string sectionName)
{
SectionName = sectionName;
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,137 @@
/*
* 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 Oculus.Interaction.Body.Input;
using UnityEngine.Assertions;
namespace Oculus.Interaction.Body
{
public class BodyDebugGizmos : SkeletonDebugGizmos
{
public enum CoordSpace
{
World,
Local,
}
[SerializeField, Interface(typeof(IBody))]
private UnityEngine.Object _body;
private IBody Body;
[Tooltip("The coordinate space in which to draw the skeleton. " +
"World space draws the skeleton at the world Body location. " +
"Local draws the skeleton relative to this transform.")]
[SerializeField]
private CoordSpace _space = CoordSpace.World;
public CoordSpace Space
{
get => _space;
set => _space = value;
}
protected bool _started = false;
protected virtual void Awake()
{
Body = _body as IBody;
}
protected virtual void Start()
{
this.BeginStart(ref _started);
Assert.IsNotNull(Body);
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
Body.WhenBodyUpdated += HandleBodyUpdated;
}
}
protected virtual void OnDisable()
{
if (_started)
{
Body.WhenBodyUpdated -= HandleBodyUpdated;
}
}
protected override bool TryGetWorldJointPose(BodyJointId jointId, out Pose pose)
{
bool result;
switch (_space)
{
default:
case CoordSpace.World:
result = Body.GetJointPose(jointId, out pose);
break;
case CoordSpace.Local:
result = Body.GetJointPoseFromRoot(jointId, out pose);
pose.position = transform.TransformPoint(pose.position);
pose.rotation = transform.rotation * pose.rotation;
break;
}
return result;
}
protected override bool TryGetParentJointId(BodyJointId jointId, out BodyJointId parent)
{
return Body.SkeletonMapping.TryGetParentJointId(jointId, out parent);
}
private VisibilityFlags GetModifiedDrawFlags()
{
VisibilityFlags modifiedFlags = Visibility;
if (HasNegativeScale && Space == CoordSpace.Local)
{
modifiedFlags &= ~VisibilityFlags.Axes;
}
return modifiedFlags;
}
private void HandleBodyUpdated()
{
foreach (BodyJointId joint in Body.SkeletonMapping.Joints)
{
Draw(joint, GetModifiedDrawFlags());
}
}
#region Inject
public void InjectAllBodyJointDebugGizmos(IBody body)
{
InjectBody(body);
}
public void InjectBody(IBody body)
{
_body = body as UnityEngine.Object;
Body = body;
}
#endregion
}
}

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 3f90df98d80579a4eb0b25852e0f6758
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences:
- _body: {instanceID: 0}
- _debugPrefab: {fileID: 1332877395996002986, guid: d1ed0fc8d54eb1e4283a76baf1e42b1d,
type: 3}
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@ -0,0 +1,133 @@
/*
* 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 System;
using Oculus.Interaction.Input;
namespace Oculus.Interaction.Body.Input
{
public class Body : DataModifier<BodyDataAsset>, IBody
{
[Tooltip("If assigned, joint pose translations into world " +
"space will be performed via this transform. If unassigned, " +
"world joint poses will be returned in tracking space.")]
[SerializeField, Optional]
private Transform _trackingSpace;
public bool IsConnected => GetData().IsDataValid;
public bool IsHighConfidence => GetData().IsDataHighConfidence;
public float Scale => GetData().RootScale;
public ISkeletonMapping SkeletonMapping => GetData().SkeletonMapping;
public bool IsTrackedDataValid => GetData().IsDataValid;
public event Action WhenBodyUpdated = delegate { };
private BodyJointsCache _jointPosesCache;
public bool GetJointPose(BodyJointId bodyJointId, out Pose pose)
{
pose = Pose.identity;
if (!IsTrackedDataValid || !SkeletonMapping.Joints.Contains(bodyJointId))
{
return false;
}
CheckJointPosesCacheUpdate();
pose = _jointPosesCache.GetWorldJointPose(bodyJointId);
return true;
}
public bool GetJointPoseLocal(BodyJointId bodyJointId, out Pose pose)
{
pose = Pose.identity;
if (!IsTrackedDataValid || !SkeletonMapping.Joints.Contains(bodyJointId))
{
return false;
}
CheckJointPosesCacheUpdate();
pose = _jointPosesCache.GetLocalJointPose(bodyJointId);
return true;
}
public bool GetJointPoseFromRoot(BodyJointId bodyJointId, out Pose pose)
{
pose = Pose.identity;
if (!IsTrackedDataValid || !SkeletonMapping.Joints.Contains(bodyJointId))
{
return false;
}
CheckJointPosesCacheUpdate();
pose = _jointPosesCache.GetJointPoseFromRoot(bodyJointId);
return true;
}
public bool GetRootPose(out Pose pose)
{
pose = Pose.identity;
if (!IsTrackedDataValid)
{
return false;
}
CheckJointPosesCacheUpdate();
pose = _jointPosesCache.GetWorldRootPose();
return true;
}
private void InitializeJointPosesCache()
{
if (_jointPosesCache == null)
{
_jointPosesCache = new BodyJointsCache();
}
}
private void CheckJointPosesCacheUpdate()
{
if (_jointPosesCache != null
&& CurrentDataVersion != _jointPosesCache.LocalDataVersion)
{
_jointPosesCache.Update(GetData(), CurrentDataVersion, _trackingSpace);
}
}
protected override void Apply(BodyDataAsset data)
{
// Default implementation does nothing, to allow instantiation of this modifier directly
}
public override void MarkInputDataRequiresUpdate()
{
base.MarkInputDataRequiresUpdate();
if (Started)
{
InitializeJointPosesCache();
WhenBodyUpdated.Invoke();
}
}
}
}

View File

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

View File

@ -0,0 +1,52 @@
/*
* 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 System;
using Oculus.Interaction.Input;
namespace Oculus.Interaction.Body.Input
{
public class BodyDataAsset : ICopyFrom<BodyDataAsset>
{
public ISkeletonMapping SkeletonMapping { get; set; }
public Pose Root { get; set; } = Pose.identity;
public float RootScale { get; set; } = 1f;
public bool IsDataValid { get; set; } = false;
public bool IsDataHighConfidence { get; set; } = false;
public Pose[] JointPoses { get; set; } = new Pose[Constants.NUM_BODY_JOINTS];
public int SkeletonChangedCount { get; set; } = 0;
public void CopyFrom(BodyDataAsset source)
{
SkeletonMapping = source.SkeletonMapping;
Root = source.Root;
RootScale = source.RootScale;
IsDataValid = source.IsDataValid;
IsDataHighConfidence = source.IsDataHighConfidence;
SkeletonChangedCount = source.SkeletonChangedCount;
for (int i = 0; i < source.JointPoses.Length; ++i)
{
JointPoses[i] = source.JointPoses[i];
}
}
}
}

View File

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

View File

@ -0,0 +1,236 @@
/*
* 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;
namespace Oculus.Interaction.Body.Input
{
public class BodyJointsCache
{
private const int DIRTY_ARRAY_SIZE = 1 + (Constants.NUM_BODY_JOINTS / 64);
public int LocalDataVersion { get; private set; } = -1;
private Pose[] _originalPoses = new Pose[Constants.NUM_BODY_JOINTS];
private Pose[] _posesFromRoot = new Pose[Constants.NUM_BODY_JOINTS];
private Pose[] _localPoses = new Pose[Constants.NUM_BODY_JOINTS];
private Pose[] _worldPoses = new Pose[Constants.NUM_BODY_JOINTS];
private ReadOnlyBodyJointPoses _posesFromRootCollection;
private ReadOnlyBodyJointPoses _worldPosesCollection;
private ReadOnlyBodyJointPoses _localPosesCollection;
private ulong[] _dirtyJointsFromRoot;
private ulong[] _dirtyLocalJoints;
private ulong[] _dirtyWorldJoints;
private Matrix4x4 _scale;
private Pose _rootPose;
private Pose _worldRoot;
private ISkeletonMapping _mapping;
public BodyJointsCache()
{
LocalDataVersion = -1;
_dirtyJointsFromRoot = new ulong[DIRTY_ARRAY_SIZE];
_dirtyLocalJoints = new ulong[DIRTY_ARRAY_SIZE];
_dirtyWorldJoints = new ulong[DIRTY_ARRAY_SIZE];
_localPosesCollection = new ReadOnlyBodyJointPoses(_localPoses);
_worldPosesCollection = new ReadOnlyBodyJointPoses(_worldPoses);
_posesFromRootCollection = new ReadOnlyBodyJointPoses(_posesFromRoot);
}
public void Update(BodyDataAsset data, int dataVersion,
Transform trackingSpace = null)
{
LocalDataVersion = dataVersion;
_mapping = data.SkeletonMapping;
for (int i = 0; i < DIRTY_ARRAY_SIZE; ++i)
{
_dirtyJointsFromRoot[i] = ulong.MaxValue;
_dirtyLocalJoints[i] = ulong.MaxValue;
_dirtyWorldJoints[i] = ulong.MaxValue;
}
if (!data.IsDataValid)
{
return;
}
_scale = Matrix4x4.Scale(Vector3.one * data.RootScale);
_rootPose = data.Root;
_worldRoot = _rootPose;
if (trackingSpace != null)
{
_scale *= Matrix4x4.Scale(trackingSpace.lossyScale);
_worldRoot.position = trackingSpace.TransformPoint(_rootPose.position);
_worldRoot.rotation = trackingSpace.rotation * _rootPose.rotation;
}
for (int i = 0; i < Constants.NUM_BODY_JOINTS; ++i)
{
_originalPoses[i] = data.JointPoses[i];
}
}
public bool GetAllLocalPoses(out ReadOnlyBodyJointPoses localJointPoses)
{
UpdateAllLocalPoses();
localJointPoses = _localPosesCollection;
return _localPosesCollection.Count > 0;
}
public bool GetAllPosesFromRoot(out ReadOnlyBodyJointPoses posesFromRoot)
{
UpdateAllPosesFromRoot();
posesFromRoot = _posesFromRootCollection;
return _posesFromRootCollection.Count > 0;
}
public bool GetAllWorldPoses(out ReadOnlyBodyJointPoses worldJointPoses)
{
UpdateAllWorldPoses();
worldJointPoses = _worldPosesCollection;
return _worldPosesCollection.Count > 0;
}
public Pose GetLocalJointPose(BodyJointId jointId)
{
UpdateLocalJointPose(jointId);
return _localPoses[(int)jointId];
}
public Pose GetJointPoseFromRoot(BodyJointId jointId)
{
UpdateJointPoseFromRoot(jointId);
return _posesFromRoot[(int)jointId];
}
public Pose GetWorldJointPose(BodyJointId jointId)
{
UpdateWorldJointPose(jointId);
return _worldPoses[(int)jointId];
}
public Pose GetWorldRootPose()
{
return _worldRoot;
}
private void UpdateJointPoseFromRoot(BodyJointId jointId)
{
if (!CheckJointDirty(jointId, _dirtyJointsFromRoot))
{
return;
}
Pose originalPose = _originalPoses[(int)jointId];
Vector3 posFromRoot = Quaternion.Inverse(_rootPose.rotation) *
(originalPose.position - _rootPose.position);
Quaternion rotFromRoot = Quaternion.Inverse(_rootPose.rotation) *
originalPose.rotation;
_posesFromRoot[(int)jointId] = new Pose(posFromRoot, rotFromRoot);
SetJointClean(jointId, _dirtyJointsFromRoot);
}
private void UpdateLocalJointPose(BodyJointId jointId)
{
if (!CheckJointDirty(jointId, _dirtyLocalJoints))
{
return;
}
if (_mapping.TryGetParentJointId(jointId, out BodyJointId parentId) &&
parentId >= BodyJointId.Body_Root)
{
Pose originalPose = _originalPoses[(int)jointId];
Pose parentPose = _originalPoses[(int)parentId];
Vector3 localPos = Quaternion.Inverse(parentPose.rotation) *
(originalPose.position - parentPose.position);
Quaternion localRot = Quaternion.Inverse(parentPose.rotation) *
originalPose.rotation;
_localPoses[(int)jointId] = new Pose(localPos, localRot);
}
else
{
_localPoses[(int)jointId] = Pose.identity;
}
SetJointClean(jointId, _dirtyLocalJoints);
}
private void UpdateWorldJointPose(BodyJointId jointId)
{
if (!CheckJointDirty(jointId, _dirtyWorldJoints))
{
return;
}
Pose fromRoot = GetJointPoseFromRoot(jointId);
fromRoot.position = _scale * fromRoot.position;
fromRoot.Postmultiply(GetWorldRootPose());
_worldPoses[(int)jointId] = fromRoot;
SetJointClean(jointId, _dirtyWorldJoints);
}
private void UpdateAllWorldPoses()
{
foreach (BodyJointId joint in _mapping.Joints)
{
UpdateWorldJointPose(joint);
}
}
private void UpdateAllLocalPoses()
{
foreach (BodyJointId joint in _mapping.Joints)
{
UpdateLocalJointPose(joint);
}
}
private void UpdateAllPosesFromRoot()
{
foreach (BodyJointId joint in _mapping.Joints)
{
UpdateJointPoseFromRoot(joint);
}
}
private bool CheckJointDirty(BodyJointId jointId, ulong[] dirtyFlags)
{
int outerIdx = (int)jointId / 64;
int innerIdx = (int)jointId % 64;
return (dirtyFlags[outerIdx] & (1UL << innerIdx)) != 0;
}
private void SetJointClean(BodyJointId jointId, ulong[] dirtyFlags)
{
int outerIdx = (int)jointId / 64;
int innerIdx = (int)jointId % 64;
dirtyFlags[outerIdx] = dirtyFlags[outerIdx] & ~(1UL << innerIdx);
}
}
}

View File

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

View File

@ -0,0 +1,218 @@
/*
* 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;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Primitive type serialization
/// </summary>
namespace Oculus.Interaction.Body.Input
{
public static class Constants
{
public const int NUM_BODY_JOINTS = (int)BodyJointId.Body_End;
}
public enum BodyJointId
{
Invalid = -1,
[InspectorName("Body Start")]
Body_Start = 0,
[InspectorName("Root")]
Body_Root = Body_Start + 0,
[InspectorName("Hips")]
Body_Hips = Body_Start + 1,
[InspectorName("Spine Lower")]
Body_SpineLower = Body_Start + 2,
[InspectorName("Spine Middle")]
Body_SpineMiddle = Body_Start + 3,
[InspectorName("Spine Upper")]
Body_SpineUpper = Body_Start + 4,
[InspectorName("Chest")]
Body_Chest = Body_Start + 5,
[InspectorName("Neck")]
Body_Neck = Body_Start + 6,
[InspectorName("Head")]
Body_Head = Body_Start + 7,
[InspectorName("Left Arm/Left Shoulder")]
Body_LeftShoulder = Body_Start + 8,
[InspectorName("Left Arm/Left Scapula")]
Body_LeftScapula = Body_Start + 9,
[InspectorName("Left Arm/Left Arm Upper")]
Body_LeftArmUpper = Body_Start + 10,
[InspectorName("Left Arm/Left Arm Lower")]
Body_LeftArmLower = Body_Start + 11,
[InspectorName("Left Arm/Left Hand Wrist Twist")]
Body_LeftHandWristTwist = Body_Start + 12,
[InspectorName("Right Arm/Right Shoulder")]
Body_RightShoulder = Body_Start + 13,
[InspectorName("Right Arm/Right Scapula")]
Body_RightScapula = Body_Start + 14,
[InspectorName("Right Arm/Right Arm Upper")]
Body_RightArmUpper = Body_Start + 15,
[InspectorName("Right Arm/Right Arm Lower")]
Body_RightArmLower = Body_Start + 16,
[InspectorName("Right Arm/Right Hand Wrist Twist")]
Body_RightHandWristTwist = Body_Start + 17,
[InspectorName("Left Hand/Left Hand Wrist")]
Body_LeftHandWrist = Body_Start + 18,
[InspectorName("Left Hand/Left Hand Palm")]
Body_LeftHandPalm = Body_Start + 19,
[InspectorName("Left Hand/Left Hand Thumb Metacarpal")]
Body_LeftHandThumbMetacarpal = Body_Start + 20,
[InspectorName("Left Hand/Left Hand Thumb Proximal")]
Body_LeftHandThumbProximal = Body_Start + 21,
[InspectorName("Left Hand/Left Hand Thumb Distal")]
Body_LeftHandThumbDistal = Body_Start + 22,
[InspectorName("Left Hand/Left Hand Thumb Tip")]
Body_LeftHandThumbTip = Body_Start + 23,
[InspectorName("Left Hand/Left Hand Index Metacarpal")]
Body_LeftHandIndexMetacarpal = Body_Start + 24,
[InspectorName("Left Hand/Left Hand Index Proximal")]
Body_LeftHandIndexProximal = Body_Start + 25,
[InspectorName("Left Hand/Left Hand Index Intermediate")]
Body_LeftHandIndexIntermediate = Body_Start + 26,
[InspectorName("Left Hand/Left Hand Index Distal")]
Body_LeftHandIndexDistal = Body_Start + 27,
[InspectorName("Left Hand/Left Hand Index Tip")]
Body_LeftHandIndexTip = Body_Start + 28,
[InspectorName("Left Hand/Left Hand Middle Metacarpal")]
Body_LeftHandMiddleMetacarpal = Body_Start + 29,
[InspectorName("Left Hand/Left Hand Middle Proximal")]
Body_LeftHandMiddleProximal = Body_Start + 30,
[InspectorName("Left Hand/Left Hand Middle Intermediate")]
Body_LeftHandMiddleIntermediate = Body_Start + 31,
[InspectorName("Left Hand/Left Hand Middle Distal")]
Body_LeftHandMiddleDistal = Body_Start + 32,
[InspectorName("Left Hand/Left Hand Middle Tip")]
Body_LeftHandMiddleTip = Body_Start + 33,
[InspectorName("Left Hand/Left Hand Ring Metacarpal")]
Body_LeftHandRingMetacarpal = Body_Start + 34,
[InspectorName("Left Hand/Left Hand Ring Proximal")]
Body_LeftHandRingProximal = Body_Start + 35,
[InspectorName("Left Hand/Left Hand Ring Intermediate")]
Body_LeftHandRingIntermediate = Body_Start + 36,
[InspectorName("Left Hand/Left Hand Ring Distal")]
Body_LeftHandRingDistal = Body_Start + 37,
[InspectorName("Left Hand/Left Hand Ring Tip")]
Body_LeftHandRingTip = Body_Start + 38,
[InspectorName("Left Hand/Left Hand Little Metacarpal")]
Body_LeftHandLittleMetacarpal = Body_Start + 39,
[InspectorName("Left Hand/Left Hand Little Proximal")]
Body_LeftHandLittleProximal = Body_Start + 40,
[InspectorName("Left Hand/Left Hand Little Intermediate")]
Body_LeftHandLittleIntermediate = Body_Start + 41,
[InspectorName("Left Hand/Left Hand Little Distal")]
Body_LeftHandLittleDistal = Body_Start + 42,
[InspectorName("Left Hand/Left Hand Little Tip")]
Body_LeftHandLittleTip = Body_Start + 43,
[InspectorName("Right Hand/Right Hand Wrist")]
Body_RightHandWrist = Body_Start + 44,
[InspectorName("Right Hand/Right Hand Palm")]
Body_RightHandPalm = Body_Start + 45,
[InspectorName("Right Hand/Right Hand Thumb Metacarpal")]
Body_RightHandThumbMetacarpal = Body_Start + 46,
[InspectorName("Right Hand/Right Hand Thumb Proximal")]
Body_RightHandThumbProximal = Body_Start + 47,
[InspectorName("Right Hand/Right Hand Thumb Distal")]
Body_RightHandThumbDistal = Body_Start + 48,
[InspectorName("Right Hand/Right Hand Thumb Tip")]
Body_RightHandThumbTip = Body_Start + 49,
[InspectorName("Right Hand/Right Hand Index Metacarpal")]
Body_RightHandIndexMetacarpal = Body_Start + 50,
[InspectorName("Right Hand/Right Hand Index Proximal")]
Body_RightHandIndexProximal = Body_Start + 51,
[InspectorName("Right Hand/Right Hand Index Intermediate")]
Body_RightHandIndexIntermediate = Body_Start + 52,
[InspectorName("Right Hand/Right Hand Index Distal")]
Body_RightHandIndexDistal = Body_Start + 53,
[InspectorName("Right Hand/Right Hand Index Tip")]
Body_RightHandIndexTip = Body_Start + 54,
[InspectorName("Right Hand/Right Hand Middle Metacarpal")]
Body_RightHandMiddleMetacarpal = Body_Start + 55,
[InspectorName("Right Hand/Right Hand Middle Proximal")]
Body_RightHandMiddleProximal = Body_Start + 56,
[InspectorName("Right Hand/Right Hand Middle Intermediate")]
Body_RightHandMiddleIntermediate = Body_Start + 57,
[InspectorName("Right Hand/Right Hand Middle Distal")]
Body_RightHandMiddleDistal = Body_Start + 58,
[InspectorName("Right Hand/Right Hand Middle Tip")]
Body_RightHandMiddleTip = Body_Start + 59,
[InspectorName("Right Hand/Right Hand Ring Metacarpal")]
Body_RightHandRingMetacarpal = Body_Start + 60,
[InspectorName("Right Hand/Right Hand Ring Proximal")]
Body_RightHandRingProximal = Body_Start + 61,
[InspectorName("Right Hand/Right Hand Ring Intermediate")]
Body_RightHandRingIntermediate = Body_Start + 62,
[InspectorName("Right Hand/Right Hand Ring Distal")]
Body_RightHandRingDistal = Body_Start + 63,
[InspectorName("Right Hand/Right Hand Ring Tip")]
Body_RightHandRingTip = Body_Start + 64,
[InspectorName("Right Hand/Right Hand Little Metacarpal")]
Body_RightHandLittleMetacarpal = Body_Start + 65,
[InspectorName("Right Hand/Right Hand Little Proximal")]
Body_RightHandLittleProximal = Body_Start + 66,
[InspectorName("Right Hand/Right Hand Little Intermediate")]
Body_RightHandLittleIntermediate = Body_Start + 67,
[InspectorName("Right Hand/Right Hand Little Distal")]
Body_RightHandLittleDistal = Body_Start + 68,
[InspectorName("Right Hand/Right Hand Little Tip")]
Body_RightHandLittleTip = Body_Start + 69,
[InspectorName("Body End")]
Body_End = Body_Start + 70,
}
public class ReadOnlyBodyJointPoses : IReadOnlyList<Pose>
{
private Pose[] _poses;
public ReadOnlyBodyJointPoses(Pose[] poses)
{
_poses = poses;
}
public IEnumerator<Pose> GetEnumerator()
{
foreach (var pose in _poses)
{
yield return pose;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public static ReadOnlyBodyJointPoses Empty { get; } = new ReadOnlyBodyJointPoses(Array.Empty<Pose>());
public int Count => _poses.Length;
public Pose this[int index] => _poses[index];
public ref readonly Pose this[BodyJointId index] => ref _poses[(int)index];
}
}

View File

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

View File

@ -0,0 +1,139 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Assertions;
using Oculus.Interaction.Collections;
namespace Oculus.Interaction.Body.Input
{
/// <summary>
/// Maps a skeleton with joint id <see cref="TSourceJointId"/> to BodyJointId,
/// and exposes parent/child relationships within the skeleton.
/// </summary>
/// <typeparam name="TSourceJointId">The enum joint type of the source skeleton</typeparam>
public abstract class BodySkeletonMapping<TSourceJointId> : ISkeletonMapping
where TSourceJointId : System.Enum
{
public IEnumerableHashSet<BodyJointId> Joints => _joints;
public bool TryGetParentJointId(BodyJointId jointId, out BodyJointId parentJointId)
{
return _jointToParent.TryGetValue(jointId, out parentJointId);
}
public bool TryGetSourceJointId(BodyJointId jointId, out TSourceJointId sourceJointId)
{
return _reverseMap.TryGetValue(jointId, out sourceJointId);
}
public bool TryGetBodyJointId(TSourceJointId jointId, out BodyJointId bodyJointId)
{
return _forwardMap.TryGetValue(jointId, out bodyJointId);
}
protected TSourceJointId GetSourceJointFromBodyJoint(BodyJointId jointId)
{
return _reverseMap[jointId];
}
protected BodyJointId GetBodyJointFromSourceJoint(TSourceJointId sourceJointId)
{
return _forwardMap[sourceJointId];
}
protected abstract TSourceJointId GetRoot();
protected abstract IReadOnlyDictionary<BodyJointId, JointInfo> GetJointMapping();
public BodySkeletonMapping()
{
_tree = new SkeletonTree(GetRoot(), GetJointMapping());
_joints = new EnumerableHashSet<BodyJointId>(_tree.Nodes.Select(n => n.BodyJointId));
_forwardMap = _tree.Nodes.ToDictionary((n) => n.SourceJointId, (n) => n.BodyJointId);
_reverseMap = _tree.Nodes.ToDictionary((n) => n.BodyJointId, (n) => n.SourceJointId);
_jointToParent = _tree.Nodes.ToDictionary((n) => n.BodyJointId, (n) => n.Parent.BodyJointId);
}
private readonly SkeletonTree _tree;
private readonly IEnumerableHashSet<BodyJointId> _joints;
private readonly IReadOnlyDictionary<TSourceJointId, BodyJointId> _forwardMap;
private readonly IReadOnlyDictionary<BodyJointId, TSourceJointId> _reverseMap;
private readonly IReadOnlyDictionary<BodyJointId, BodyJointId> _jointToParent;
private class SkeletonTree
{
public readonly Node Root;
public readonly IReadOnlyList<Node> Nodes;
public SkeletonTree(TSourceJointId root,
IReadOnlyDictionary<BodyJointId, JointInfo> mapping)
{
Dictionary<TSourceJointId, Node> nodes = new Dictionary<TSourceJointId, Node>();
foreach (var map in mapping)
{
BodyJointId jointId = map.Key;
JointInfo jointInfo = map.Value;
Assert.IsFalse(nodes.ContainsKey(jointInfo.SourceJointId),
"Duplicate Joint ID in Mapping");
nodes[jointInfo.SourceJointId] =
new Node(jointInfo.SourceJointId, jointId);
}
foreach (var jointInfo in mapping.Values)
{
Node thisNode = nodes[jointInfo.SourceJointId];
thisNode.Parent = nodes[jointInfo.ParentJointId];
thisNode.Parent.Children.Add(thisNode);
}
Nodes = new List<Node>(nodes.Values);
Root = nodes[root];
}
public class Node
{
public readonly TSourceJointId SourceJointId;
public readonly BodyJointId BodyJointId;
public Node Parent;
public List<Node> Children = new List<Node>();
public Node(TSourceJointId sourceJointId, BodyJointId bodyJointId)
{
SourceJointId = sourceJointId;
BodyJointId = bodyJointId;
}
}
}
protected readonly struct JointInfo
{
public readonly TSourceJointId SourceJointId;
public readonly TSourceJointId ParentJointId;
public JointInfo(
TSourceJointId sourceJointId,
TSourceJointId parentJointId)
{
SourceJointId = sourceJointId;
ParentJointId = parentJointId;
}
}
}
}

View File

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

View File

@ -0,0 +1,96 @@
/*
* 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 System;
namespace Oculus.Interaction.Body.Input
{
public interface IBody
{
/// <summary>
/// The mapping for this body, which allows querying of the supported joint set
/// of the skeleton as well as joint parent/child relationship.
/// </summary>
ISkeletonMapping SkeletonMapping { get; }
/// <summary>
/// Is the body connected and tracked
/// </summary>
bool IsConnected { get; }
/// <summary>
/// The body is connected and tracked, and the root pose's tracking data is marked as
/// high confidence.
/// If this is true, then it implies that <see cref="IsConnected"/> is also true,
/// so they don't need to be checked in addition to this.
/// </summary>
bool IsHighConfidence { get; }
/// <summary>
/// True if the body is currently tracked, thus tracking poses are available for the body
/// root and joints.
/// </summary>
bool IsTrackedDataValid { get; }
/// <summary>
/// The scale of the body skeleton applied to world joint poses.
/// </summary>
float Scale { get; }
/// <summary>
/// Gets the root pose of the body, in world space.
/// Will return true if a pose was available; false otherwise.
/// Confidence level of the pose is exposed via <see cref="IsHighConfidence"/>.
/// </summary>
bool GetRootPose(out Pose pose);
/// <summary>
/// Attempts to calculate the pose of the requested body joint, in world space.
/// Returns false if the skeleton is not yet initialized, or there is no valid
/// tracking data. Scale is applied to this joint position.
/// </summary>
bool GetJointPose(BodyJointId bodyJointId, out Pose pose);
/// <summary>
/// Attempts to calculate the pose of the requested body joint, in local space
/// to its parent joint. Returns false if the skeleton is not yet initialized,
/// or there is no valid tracking data. Scale is not applied.
/// </summary>
bool GetJointPoseLocal(BodyJointId bodyJointId, out Pose pose);
/// <summary>
/// Attempts to calculate the pose of the requested hand joint relative to the root.
/// Returns false if the skeleton is not yet initialized, or there is no valid
/// tracking data. Scale is not applied.
/// </summary>
bool GetJointPoseFromRoot(BodyJointId bodyJointId, out Pose pose);
/// <summary>
/// Incremented every time the source tracking or state data changes.
/// </summary>
int CurrentDataVersion { get; }
/// <summary>
/// Called each time the body is updated with new data
/// </summary>
event Action WhenBodyUpdated;
}
}

View File

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

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Oculus.Interaction.Collections;
namespace Oculus.Interaction.Body.Input
{
public interface ISkeletonMapping
{
/// <summary>
/// The set of <see cref="BodyJointId"/>s supported by this skeleton
/// </summary>
IEnumerableHashSet<BodyJointId> Joints { get; }
/// <summary>
/// Get the parent joint for a given body joint
/// </summary>
/// <param name="jointId">The joint to fetch the parent for</param>
/// <param name="parent">The parent joint</param>
/// <returns>True if parent could be retrieved</returns>
bool TryGetParentJointId(BodyJointId jointId, out BodyJointId parent);
}
}

View File

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

View File

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

View File

@ -0,0 +1,199 @@
/*
* 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 UnityEngine;
using Oculus.Interaction.Body.Input;
namespace Oculus.Interaction.Body.PoseDetection
{
/// <summary>
/// Compares a user-provided set of joints between two Body Poses.
/// </summary>
public class BodyPoseComparerActiveState :
MonoBehaviour, IActiveState
{
public struct BodyPoseComparerFeatureState
{
public readonly float Delta;
public readonly float MaxDelta;
public BodyPoseComparerFeatureState(float delta, float maxDelta)
{
Delta = delta;
MaxDelta = maxDelta;
}
}
[Serializable]
public class JointComparerConfig
{
[Tooltip("The joint to compare from each Body Pose")]
public BodyJointId Joint = BodyJointId.Body_Head;
[Min(0)]
[Tooltip("The maximum angle that two joint rotations " +
"can be from each other to be considered equal.")]
public float MaxDelta = 30f;
[Tooltip("The width of the threshold when transitioning " +
"states. " + nameof(Width) + " / 2 is added to " +
nameof(MaxDelta) + " when leaving Active state, and " +
"subtracted when entering.")]
[Min(0)]
public float Width = 4f;
}
[Tooltip("The first body pose to compare.")]
[SerializeField, Interface(typeof(IBodyPose))]
private UnityEngine.Object _poseA;
private IBodyPose PoseA;
[Tooltip("The second body pose to compare.")]
[SerializeField, Interface(typeof(IBodyPose))]
private UnityEngine.Object _poseB;
private IBodyPose PoseB;
[SerializeField]
private List<JointComparerConfig> _configs =
new List<JointComparerConfig>()
{
new JointComparerConfig()
};
[Tooltip("A new state must be maintaned for at least this " +
"many seconds before the Active property changes.")]
[SerializeField]
private float _minTimeInState = 0.05f;
public float MinTimeInState
{
get => _minTimeInState;
set => _minTimeInState = value;
}
public IReadOnlyDictionary<JointComparerConfig, BodyPoseComparerFeatureState> FeatureStates =>
_featureStates;
private Dictionary<JointComparerConfig, BodyPoseComparerFeatureState> _featureStates =
new Dictionary<JointComparerConfig, BodyPoseComparerFeatureState>();
private Func<float> _timeProvider;
private bool _isActive;
private bool _internalActive;
private float _lastStateChangeTime;
protected virtual void Awake()
{
PoseA = _poseA as IBodyPose;
PoseB = _poseB as IBodyPose;
_timeProvider = () => Time.time;
}
protected virtual void Start()
{
this.AssertField(PoseA, nameof(PoseA));
this.AssertField(PoseB, nameof(PoseB));
}
public bool Active
{
get
{
if (!isActiveAndEnabled)
{
return false;
}
bool wasActive = _internalActive;
_internalActive = true;
foreach (var config in _configs)
{
float maxDelta = wasActive ?
config.MaxDelta + config.Width / 2f :
config.MaxDelta - config.Width / 2f;
bool withinDelta = GetJointDelta(config.Joint, out float delta) &&
Mathf.Abs(delta) <= maxDelta;
_featureStates[config] = new BodyPoseComparerFeatureState(delta, maxDelta);
_internalActive &= withinDelta;
}
float time = _timeProvider();
if (wasActive != _internalActive)
{
_lastStateChangeTime = time;
}
if (time - _lastStateChangeTime >= _minTimeInState)
{
_isActive = _internalActive;
}
return _isActive;
}
}
private bool GetJointDelta(BodyJointId joint, out float delta)
{
if (!PoseA.GetJointPoseLocal(joint, out Pose localA) ||
!PoseB.GetJointPoseLocal(joint, out Pose localB))
{
delta = 0;
return false;
}
delta = Quaternion.Angle(localA.rotation, localB.rotation);
return true;
}
#region Inject
public void InjectAllBodyPoseComparerActiveState(
IBodyPose poseA, IBodyPose poseB,
IEnumerable<JointComparerConfig> configs)
{
InjectPoseA(poseA);
InjectPoseB(poseB);
InjectJoints(configs);
}
public void InjectPoseA(IBodyPose poseA)
{
_poseA = poseA as UnityEngine.Object;
PoseA = poseA;
}
public void InjectPoseB(IBodyPose poseB)
{
_poseB = poseB as UnityEngine.Object;
PoseB = poseB;
}
public void InjectJoints(IEnumerable<JointComparerConfig> configs)
{
_configs = new List<JointComparerConfig>(configs);
}
public void InjectOptionalTimeProvider(Func<float> timeProvider)
{
_timeProvider = timeProvider;
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,131 @@
/*
* 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.Body.Input;
using UnityEngine;
namespace Oculus.Interaction.Body.PoseDetection
{
public class BodyPoseComparerActiveStateDebugVisual : MonoBehaviour
{
[Tooltip("The PoseComparer to debug.")]
[SerializeField]
private BodyPoseComparerActiveState _bodyPoseComparer;
[Tooltip("Gizmos will be drawn at joint positions of this body pose.")]
[SerializeField, Interface(typeof(IBodyPose))]
private UnityEngine.Object _bodyPose;
private IBodyPose BodyPose;
[Tooltip("The root transform of the body skeleton. Debug " +
"gizmos will be drawn in the local space of this transform.")]
[SerializeField]
private Transform _root;
[Tooltip("The radius of the debug spheres.")]
[SerializeField, Delayed]
private float _radius = 0.1f;
public float Radius
{
get => _radius;
set => _radius = value;
}
protected virtual void Awake()
{
BodyPose = _bodyPose as IBodyPose;
}
protected virtual void Start()
{
this.AssertField(_bodyPoseComparer, nameof(_bodyPoseComparer));
this.AssertField(BodyPose, nameof(BodyPose));
this.AssertField(_root, nameof(BodyPose));
}
protected virtual void Update()
{
DrawJointSpheres();
}
private void DrawJointSpheres()
{
var featureStates = _bodyPoseComparer.FeatureStates;
foreach (var kvp in featureStates)
{
BodyJointId joint = kvp.Key.Joint;
var state = kvp.Value;
if (BodyPose.GetJointPoseFromRoot(joint, out Pose pose))
{
Vector3 jointPos = _root.TransformPoint(pose.position);
Color color;
if (state.Delta <= state.MaxDelta)
{
color = Color.green;
}
else if (state.MaxDelta > 0)
{
float amt = (state.Delta / state.MaxDelta) / 2f;
color = Color.Lerp(Color.yellow, Color.red, amt);
}
else
{
color = Color.red;
}
DebugGizmos.LineWidth = _radius / 2f;
DebugGizmos.Color = color;
DebugGizmos.DrawPoint(jointPos);
}
}
}
#region Inject
public void InjectAllBodyPoseComparerActiveStateDebugVisual(
BodyPoseComparerActiveState bodyPoseComparer,
IBodyPose bodyPose, Transform root)
{
InjectBodyPoseComparer(bodyPoseComparer);
InjectBodyPose(bodyPose);
InjectRootTransform(root);
}
public void InjectRootTransform(Transform root)
{
_root = root;
}
public void InjectBodyPoseComparer(BodyPoseComparerActiveState bodyPoseComparer)
{
_bodyPoseComparer = bodyPoseComparer;
}
public void InjectBodyPose(IBodyPose bodyPose)
{
_bodyPose = bodyPose as UnityEngine.Object;
BodyPose = bodyPose;
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,128 @@
/*
* 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 Oculus.Interaction.Body.Input;
using System;
using Oculus.Interaction.Collections;
namespace Oculus.Interaction.Body.PoseDetection
{
[CreateAssetMenu(menuName = "Oculus/Interaction/SDK/Pose Detection/Body Pose")]
public class BodyPoseData : ScriptableObject,
IBodyPose, ISerializationCallbackReceiver
{
[System.Serializable]
private struct JointData
{
public BodyJointId JointId;
public BodyJointId ParentId;
public Pose PoseFromRoot;
public Pose LocalPose;
}
private class Mapping : ISkeletonMapping
{
public EnumerableHashSet<BodyJointId> Joints =
new EnumerableHashSet<BodyJointId>();
public Dictionary<BodyJointId, BodyJointId> JointToParent =
new Dictionary<BodyJointId, BodyJointId>();
IEnumerableHashSet<BodyJointId> ISkeletonMapping.Joints => Joints;
bool ISkeletonMapping.TryGetParentJointId(BodyJointId jointId, out BodyJointId parent) =>
JointToParent.TryGetValue(jointId, out parent);
}
public event Action WhenBodyPoseUpdated = delegate { };
[SerializeField, HideInInspector]
private List<JointData> _jointData = new List<JointData>();
private Dictionary<BodyJointId, Pose> _posesFromRoot =
new Dictionary<BodyJointId, Pose>();
private Dictionary<BodyJointId, Pose> _localPoses =
new Dictionary<BodyJointId, Pose>();
private Mapping _mapping = new Mapping();
public bool GetJointPoseFromRoot(BodyJointId bodyJointId, out Pose pose) =>
_posesFromRoot.TryGetValue(bodyJointId, out pose);
public bool GetJointPoseLocal(BodyJointId bodyJointId, out Pose pose) =>
_localPoses.TryGetValue(bodyJointId, out pose);
public ISkeletonMapping SkeletonMapping => _mapping;
public void SetBodyPose(IBody body)
{
_jointData.Clear();
foreach (var joint in body.SkeletonMapping.Joints)
{
if (body.GetJointPoseLocal(joint, out Pose local) &&
body.GetJointPoseFromRoot(joint, out Pose fromRoot) &&
body.SkeletonMapping.TryGetParentJointId(joint, out BodyJointId parent))
{
_jointData.Add(new JointData()
{
JointId = joint,
ParentId = parent,
PoseFromRoot = fromRoot,
LocalPose = local,
});
}
}
Rebuild();
WhenBodyPoseUpdated.Invoke();
}
public void OnBeforeSerialize()
{
}
public void OnAfterDeserialize()
{
Rebuild();
}
private void Rebuild()
{
_localPoses.Clear();
_posesFromRoot.Clear();
_mapping.Joints.Clear();
_mapping.JointToParent.Clear();
for (int i = 0; i < _jointData.Count; ++i)
{
_localPoses[_jointData[i].JointId] =
_jointData[i].LocalPose;
_posesFromRoot[_jointData[i].JointId] =
_jointData[i].PoseFromRoot;
_mapping.Joints.Add(
_jointData[i].JointId);
_mapping.JointToParent.Add(
_jointData[i].JointId, _jointData[i].ParentId);
}
}
}
}

View File

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

View File

@ -0,0 +1,92 @@
/*
* 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 Oculus.Interaction.Body.Input;
using UnityEngine.Assertions;
namespace Oculus.Interaction.Body.PoseDetection
{
public class BodyPoseDebugGizmos : SkeletonDebugGizmos
{
[SerializeField, Interface(typeof(IBodyPose))]
private UnityEngine.Object _bodyPose;
private IBodyPose BodyPose;
protected virtual void Awake()
{
BodyPose = _bodyPose as IBodyPose;
}
protected virtual void Start()
{
Assert.IsNotNull(BodyPose);
}
protected virtual void Update()
{
foreach (BodyJointId joint in BodyPose.SkeletonMapping.Joints)
{
Draw(joint, GetVisibilityFlags());
}
}
private VisibilityFlags GetVisibilityFlags()
{
VisibilityFlags modifiedFlags = Visibility;
if (HasNegativeScale)
{
modifiedFlags &= ~VisibilityFlags.Axes;
}
return modifiedFlags;
}
protected override bool TryGetWorldJointPose(BodyJointId jointId, out Pose pose)
{
if (BodyPose.GetJointPoseFromRoot(jointId, out pose))
{
pose.position = transform.TransformPoint(pose.position);
pose.rotation = transform.rotation * pose.rotation;
return true;
}
return false;
}
protected override bool TryGetParentJointId(BodyJointId jointId, out BodyJointId parent)
{
return BodyPose.SkeletonMapping.TryGetParentJointId(jointId, out parent);
}
#region Inject
public void InjectAllBodyJointDebugGizmos(IBodyPose bodyPose)
{
InjectBodyPose(bodyPose);
}
public void InjectBodyPose(IBodyPose bodyPose)
{
_bodyPose = bodyPose as UnityEngine.Object;
BodyPose = bodyPose;
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,51 @@
/*
* 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 UnityEngine;
using Oculus.Interaction.Body.Input;
namespace Oculus.Interaction.Body.PoseDetection
{
public interface IBodyPose
{
/// <summary>
/// Called each time the body pose is updated with new data
/// </summary>
event Action WhenBodyPoseUpdated;
/// <summary>
/// The mapping of the skeleton
/// </summary>
ISkeletonMapping SkeletonMapping { get; }
/// <summary>
/// Attempts to return the pose of the requested body joint,
/// in local space relative to its parent joint.
/// </summary>
bool GetJointPoseLocal(BodyJointId bodyJointId, out Pose pose);
/// <summary>
/// Attempts to return the pose of the requested body joint relative
/// to the root joint <see cref="BodyJointId.Body_Root"/>.
/// </summary>
bool GetJointPoseFromRoot(BodyJointId bodyJointId, out Pose pose);
}
}

View File

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

View File

@ -0,0 +1,145 @@
/*
* 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 UnityEngine;
using Oculus.Interaction.Body.Input;
using System.Collections.Generic;
namespace Oculus.Interaction.Body.PoseDetection
{
/// <summary>
/// Exposes an <see cref="IBodyPose"/> from an <see cref="IBody"/>
/// </summary>
public class PoseFromBody : MonoBehaviour, IBodyPose
{
public event Action WhenBodyPoseUpdated = delegate { };
[Tooltip("The IBodyPose will be derived from this IBody.")]
[SerializeField, Interface(typeof(IBody))]
private UnityEngine.Object _body;
private IBody Body;
[Tooltip("If true, this component will track the provided IBody as " +
"its data is updated. If false, you must call " +
nameof(UpdatePose) + " to update joint data.")]
[SerializeField]
private bool _autoUpdate = true;
/// <summary>
/// If true, this component will track the provided IBody as
/// its data is updated. If false, you must call
/// <see cref="UpdatePose"/> to update joint data.
/// </summary>
public bool AutoUpdate
{
get => _autoUpdate;
set => _autoUpdate = value;
}
protected bool _started = false;
private Dictionary<BodyJointId, Pose> _jointPosesLocal;
private Dictionary<BodyJointId, Pose> _jointPosesFromRoot;
public ISkeletonMapping SkeletonMapping => Body.SkeletonMapping;
public bool GetJointPoseLocal(BodyJointId bodyJointId, out Pose pose) =>
_jointPosesLocal.TryGetValue(bodyJointId, out pose);
public bool GetJointPoseFromRoot(BodyJointId bodyJointId, out Pose pose) =>
_jointPosesFromRoot.TryGetValue(bodyJointId, out pose);
protected virtual void Awake()
{
_jointPosesLocal = new Dictionary<BodyJointId, Pose>();
_jointPosesFromRoot = new Dictionary<BodyJointId, Pose>();
Body = _body as IBody;
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(Body, nameof(Body));
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
Body.WhenBodyUpdated += Body_WhenBodyUpdated;
}
}
protected virtual void OnDisable()
{
if (_started)
{
Body.WhenBodyUpdated -= Body_WhenBodyUpdated;
}
}
private void Body_WhenBodyUpdated()
{
if (_autoUpdate)
{
UpdatePose();
}
}
public void UpdatePose()
{
_jointPosesLocal.Clear();
_jointPosesFromRoot.Clear();
foreach (var joint in Body.SkeletonMapping.Joints)
{
if (Body.GetJointPoseLocal(joint,
out Pose localPose))
{
_jointPosesLocal[joint] = localPose;
}
if (Body.GetJointPoseFromRoot(joint,
out Pose poseFromRoot))
{
_jointPosesFromRoot[joint] = poseFromRoot;
}
}
WhenBodyPoseUpdated.Invoke();
}
#region Inject
public void InjectAllPoseFromBody(IBody body)
{
InjectBody(body);
}
public void InjectBody(IBody body)
{
_body = body as UnityEngine.Object;
Body = body;
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,114 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
using Oculus.Interaction.Body.Input;
namespace Oculus.Interaction.Body
{
public abstract class SkeletonDebugGizmos : MonoBehaviour
{
[System.Flags]
public enum VisibilityFlags
{
Joints = 1 << 0,
Axes = 1 << 1,
Bones = 1 << 2,
}
[Tooltip("Which components of the skeleton will be visualized.")]
[SerializeField]
private VisibilityFlags _visibility =
VisibilityFlags.Axes | VisibilityFlags.Joints;
[Tooltip("The joint debug spheres will be drawn with this color.")]
[SerializeField]
private Color _jointColor = Color.white;
[Tooltip("The bone connecting lines will be drawn with this color.")]
[SerializeField]
private Color _boneColor = Color.gray;
[Tooltip("The radius of the joint spheres and the thickness " +
"of the bone and axis lines.")]
[SerializeField]
private float _radius = 0.02f;
public float Radius
{
get => _radius;
set => _radius = value;
}
public VisibilityFlags Visibility
{
get => _visibility;
set => _visibility = value;
}
public Color JointColor
{
get => _jointColor;
set => _jointColor = value;
}
public Color BoneColor
{
get => _boneColor;
set => _boneColor = value;
}
private float LineWidth => _radius / 2f;
protected abstract bool TryGetWorldJointPose(BodyJointId jointId, out Pose pose);
protected abstract bool TryGetParentJointId(BodyJointId jointId, out BodyJointId parent);
protected bool HasNegativeScale => transform.lossyScale.x < 0 ||
transform.lossyScale.y < 0 ||
transform.lossyScale.z < 0;
protected void Draw(BodyJointId joint, VisibilityFlags visibility)
{
if (TryGetWorldJointPose(joint, out Pose pose))
{
if (visibility.HasFlag(VisibilityFlags.Axes))
{
DebugGizmos.LineWidth = LineWidth;
DebugGizmos.DrawAxis(pose, _radius);
}
if (visibility.HasFlag(VisibilityFlags.Joints))
{
DebugGizmos.Color = _jointColor;
DebugGizmos.LineWidth = _radius;
DebugGizmos.DrawPoint(pose.position);
}
if (visibility.HasFlag(VisibilityFlags.Bones) &&
TryGetParentJointId(joint, out BodyJointId parent) &&
TryGetWorldJointPose(parent, out Pose parentPose))
{
DebugGizmos.Color = _boneColor;
DebugGizmos.LineWidth = LineWidth;
DebugGizmos.DrawLine(pose.position, parentPose.position);
}
}
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,52 @@
/*
* 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;
namespace Oculus.Interaction.Collections
{
/// <summary>
/// Exposes a GetEnumerator method with a non-allocating
/// HashSet.Enumerator struct.
/// </summary>
public interface IEnumerableHashSet<T> : IEnumerable<T>
{
int Count { get; }
new HashSet<T>.Enumerator GetEnumerator();
bool Contains(T item);
bool IsProperSubsetOf(IEnumerable<T> other);
bool IsProperSupersetOf(IEnumerable<T> other);
bool IsSubsetOf(IEnumerable<T> other);
bool IsSupersetOf(IEnumerable<T> other);
public bool Overlaps(IEnumerable<T> other);
public bool SetEquals(IEnumerable<T> other);
}
/// <summary>
/// A Hash set that implements the <see cref="IEnumerableHashSet{T}"/>
/// interface, to use for non-allocating iteration of a HashSet
/// </summary>
public class EnumerableHashSet<T> : HashSet<T>, IEnumerableHashSet<T>
{
public EnumerableHashSet() : base() { }
public EnumerableHashSet(IEnumerable<T> values) : base(values) { }
}
}

View File

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

View File

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

View File

@ -0,0 +1,50 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
namespace Oculus.Interaction
{
public static partial class Collisions
{
public static Vector3 ClosestPointToColliders(Vector3 point, Collider[] colliders)
{
Vector3 closestPoint = point;
float closestDistance = float.MaxValue;
foreach (Collider collider in colliders)
{
if (Collisions.IsPointWithinCollider(point, collider))
{
return point;
}
Vector3 closest = collider.ClosestPoint(point);
float distance = (closest - point).magnitude;
if (distance < closestDistance)
{
closestDistance = distance;
closestPoint = closest;
}
}
return closestPoint;
}
}
}

View File

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

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections.Generic;
using UnityEngine;
namespace Oculus.Interaction
{
public class ColliderGroup
{
private Collider _boundsCollider;
private List<Collider> _colliders;
public Collider Bounds => _boundsCollider;
public List<Collider> Colliders => _colliders;
public ColliderGroup(List<Collider> colliders, Collider boundsCollider)
{
_colliders = colliders;
_boundsCollider = boundsCollider;
}
}
}

View File

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

View File

@ -0,0 +1,57 @@
/*
* 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;
namespace Oculus.Interaction
{
public static partial class Collisions
{
/// <summary>
/// Approximate capsule collision by doing sphere collisions down the capsule length
/// </summary>
/// <param name="p0">Capsule Start</param>
/// <param name="p1">Capsule End</param>
/// <param name="radius">Capsule Radius</param>
/// <param name="collider">Collider to check against</param>
/// <returns>Whether or not an approximate collision occured.</returns>
public static bool IsCapsuleWithinColliderApprox(Vector3 p0, Vector3 p1, float radius, Collider collider)
{
int divisions = Mathf.CeilToInt((p1 - p0).magnitude / radius) * 2;
if (divisions == 0)
{
return IsSphereWithinCollider(p0, radius, collider);
}
float tStep = 1f / divisions;
for (int i = 0; i <= divisions; i++)
{
Vector3 point = Vector3.Lerp(p0, p1, tStep * i);
if (IsSphereWithinCollider(point, radius, collider))
{
return true;
}
}
return false;
}
}
}

View File

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

View File

@ -0,0 +1,42 @@
/*
* 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;
namespace Oculus.Interaction
{
public static partial class Collisions
{
public static bool IsPointWithinCollider(Vector3 point, Collider collider)
{
if (!collider.bounds.Contains(point))
{
return false;
}
Vector3 closestPoint = collider.ClosestPoint(point);
if (collider is MeshCollider)
{
return (closestPoint - point).sqrMagnitude < collider.contactOffset * collider.contactOffset;
}
return closestPoint.Equals(point);
}
}
}

View File

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

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
namespace Oculus.Interaction
{
public static partial class Collisions
{
public static bool IsSphereWithinCollider(Vector3 point, float radius, Collider collider)
{
Vector3 boundsPoint = collider.bounds.ClosestPoint(point);
if (Vector3.SqrMagnitude(boundsPoint - point) > radius * radius)
{
return false;
}
Vector3 closestPoint = collider.ClosestPoint(point);
return Vector3.SqrMagnitude(closestPoint - point) <= radius * radius;
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,178 @@
/*
* 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;
namespace Oculus.Interaction
{
[System.Serializable]
public struct DistantPointDetectorFrustums
{
/// <summary>
/// The main frustum, items inside this frustum will be selected.
/// </summary>
[SerializeField]
private ConicalFrustum _selectionFrustum;
/// <summary>
/// Selected items within this optional frustum will keep being selected.
/// It should be wider than the selection frustum.
/// </summary>
[SerializeField, Optional]
private ConicalFrustum _deselectionFrustum;
/// <summary>
/// When provided, items need to be also inside this frustum as well as the
/// selection one to be selected.
/// e.g. Selection frustum coming out from the hand, and aid frustum from the
/// head so items need to be pointed at with the gaze and hand.
/// </summary>
[SerializeField, Optional]
private ConicalFrustum _aidFrustum;
/// <summary>
/// Blends the scores between the Selection and Aid frustums.
/// At 0 items centered in the selection frustum are given preference, at one
/// items centered at the aid frustum are given preference.
/// </summary>
[SerializeField]
[Range(0f, 1f)]
private float _aidBlending;
public ConicalFrustum SelectionFrustum => _selectionFrustum;
public ConicalFrustum DeselectionFrustum => _deselectionFrustum;
public ConicalFrustum AidFrustum => _aidFrustum;
public float AidBlending => _aidBlending;
public DistantPointDetectorFrustums(ConicalFrustum selection,
ConicalFrustum deselection, ConicalFrustum aid, float blend)
{
_selectionFrustum = selection;
_deselectionFrustum = deselection;
_aidFrustum = aid;
_aidBlending = blend;
}
}
/// <summary>
/// This class contains the logic for finding the best candidate among a series of colliders.
/// It uses up to three conical frustums from DistantPointDetectorFrustums for selection and deselection.
/// </summary>
public class DistantPointDetector
{
private DistantPointDetectorFrustums _frustums;
public DistantPointDetector(DistantPointDetectorFrustums frustums)
{
_frustums = frustums;
}
public bool ComputeIsPointing(Collider[] colliders, bool isSelecting, out float bestScore, out Vector3 bestHitPoint)
{
ConicalFrustum searchFrustrum = (isSelecting || _frustums.DeselectionFrustum == null) ?
_frustums.SelectionFrustum : _frustums.DeselectionFrustum;
bestHitPoint = Vector3.zero;
bestScore = float.NegativeInfinity;
bool anyHit = false;
foreach (Collider collider in colliders)
{
float score = 0f;
if (!searchFrustrum.HitsCollider(collider, out score, out Vector3 hitPoint))
{
continue;
}
if (_frustums.AidFrustum != null)
{
if (!_frustums.AidFrustum.HitsCollider(collider, out float headScore, out Vector3 headPosition))
{
continue;
}
score = score * (1f - _frustums.AidBlending) + headScore * _frustums.AidBlending;
}
if (score > bestScore)
{
bestHitPoint = hitPoint;
bestScore = score;
anyHit = true;
}
}
return anyHit;
}
public bool IsPointingWithoutAid(Collider[] colliders, out Vector3 bestHitPoint)
{
if (_frustums.AidFrustum == null)
{
bestHitPoint = Vector3.zero;
return false;
}
return !IsPointingAtColliders(colliders, _frustums.AidFrustum, out bestHitPoint)
&& IsWithinDeselectionRange(colliders);
}
public bool IsWithinDeselectionRange(Collider[] colliders)
{
return IsPointingAtColliders(colliders, _frustums.DeselectionFrustum)
|| IsPointingAtColliders(colliders, _frustums.SelectionFrustum);
}
private bool IsPointingAtColliders(Collider[] colliders, ConicalFrustum frustum)
{
if (frustum == null)
{
return false;
}
foreach (Collider collider in colliders)
{
if (frustum.HitsCollider(collider, out float score, out Vector3 point))
{
return true;
}
}
return false;
}
private bool IsPointingAtColliders(Collider[] colliders, ConicalFrustum frustum,
out Vector3 bestHitPoint)
{
bestHitPoint = Vector3.zero;
float bestScore = float.NegativeInfinity;
bool isPointing = false;
if (frustum == null)
{
return false;
}
foreach (Collider collider in colliders)
{
if (frustum.HitsCollider(collider, out float score, out Vector3 point))
{
isPointing = true;
if (score > bestScore)
{
bestScore = score;
bestHitPoint = point;
}
}
}
return isPointing;
}
}
}

View File

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

View File

@ -0,0 +1,31 @@
/*
* 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;
namespace Oculus.Interaction
{
public interface IDistanceInteractor : IInteractorView
{
Pose Origin { get; }
Vector3 HitPoint { get; }
IRelativeToRef DistanceInteractable { get; }
}
}

View File

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

View File

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

View File

@ -0,0 +1,66 @@
/*
* 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 UnityEngine.Assertions;
namespace Oculus.Interaction.DistanceReticles
{
public class DistantInteractionLineRendererVisual : DistantInteractionLineVisual
{
[SerializeField]
private LineRenderer _lineRenderer;
protected override void Start()
{
base.Start();
this.AssertField(_lineRenderer, nameof(_lineRenderer));
_lineRenderer.positionCount = NumLinePoints;
}
protected override void RenderLine(Vector3[] linePoints)
{
_lineRenderer.SetPositions(linePoints);
_lineRenderer.enabled = true;
}
protected override void HideLine()
{
_lineRenderer.enabled = false;
}
#region Inject
public void InjectAllDistantInteractionLineRendererVisual(IDistanceInteractor interactor,
LineRenderer lineRenderer)
{
InjectDistanceInteractor(interactor);
InjectLineRenderer(lineRenderer);
}
public void InjectLineRenderer(LineRenderer lineRenderer)
{
_lineRenderer = lineRenderer;
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,228 @@
/*
* 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;
namespace Oculus.Interaction.DistanceReticles
{
public abstract class DistantInteractionLineVisual : MonoBehaviour
{
[SerializeField, Interface(typeof(IDistanceInteractor))]
private UnityEngine.Object _distanceInteractor;
public IDistanceInteractor DistanceInteractor { get; protected set; }
[SerializeField]
private float _visualOffset = 0.07f;
public float VisualOffset
{
get
{
return _visualOffset;
}
set
{
_visualOffset = value;
}
}
private Vector3[] _linePoints;
[SerializeField]
private bool _visibleDuringNormal;
private IReticleData _target;
[SerializeField]
private int _numLinePoints = 20;
protected int NumLinePoints => _numLinePoints;
[SerializeField]
private float _targetlessLength = 0.5f;
protected float TargetlessLength => _targetlessLength;
protected bool _started;
private bool _shouldDrawLine;
private DummyPointReticle _dummyTarget = new DummyPointReticle();
private void Awake()
{
DistanceInteractor = _distanceInteractor as IDistanceInteractor;
}
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(DistanceInteractor, nameof(DistanceInteractor));
_linePoints = new Vector3[NumLinePoints];
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
DistanceInteractor.WhenStateChanged += HandleStateChanged;
DistanceInteractor.WhenPostprocessed += HandlePostProcessed;
}
}
protected virtual void OnDisable()
{
if (_started)
{
DistanceInteractor.WhenStateChanged -= HandleStateChanged;
DistanceInteractor.WhenPostprocessed -= HandlePostProcessed;
}
}
private void HandleStateChanged(InteractorStateChangeArgs args)
{
switch (args.NewState)
{
case InteractorState.Normal:
if (args.PreviousState != InteractorState.Disabled)
{
InteractableUnset();
}
break;
case InteractorState.Hover:
if (args.PreviousState == InteractorState.Normal)
{
InteractableSet(DistanceInteractor.DistanceInteractable);
}
break;
}
if (args.NewState == InteractorState.Select
|| args.NewState == InteractorState.Disabled
|| args.PreviousState == InteractorState.Disabled)
{
_shouldDrawLine = false;
}
else if (args.NewState == InteractorState.Hover)
{
_shouldDrawLine = true;
}
else if (args.NewState == InteractorState.Normal)
{
_shouldDrawLine = _visibleDuringNormal;
}
}
private void HandlePostProcessed()
{
if (_shouldDrawLine)
{
UpdateLine();
}
else
{
HideLine();
}
}
protected virtual void InteractableSet(IRelativeToRef interactable)
{
Component component = interactable as Component;
if (component == null)
{
_target = null;
return;
}
if (!component.TryGetComponent(out _target))
{
_dummyTarget.Target = interactable.RelativeTo;
_target = _dummyTarget;
}
}
protected virtual void InteractableUnset()
{
_target = null;
}
private void UpdateLine()
{
Vector3 direction = DistanceInteractor.Origin.forward;
Vector3 origin = DistanceInteractor.Origin.position;
Vector3 start = origin + direction * VisualOffset;
Vector3 end = TargetHit(DistanceInteractor.HitPoint);
Vector3 middle = start + direction * Vector3.Distance(start, end) * 0.5f;
for (int i = 0; i < NumLinePoints; i++)
{
float t = i / (NumLinePoints - 1f);
Vector3 point = EvaluateBezier(start, middle, end, t);
_linePoints[i] = point;
}
RenderLine(_linePoints);
}
protected abstract void RenderLine(Vector3[] linePoints);
protected abstract void HideLine();
protected Vector3 TargetHit(Vector3 hitPoint)
{
if (_target != null)
{
return _target.ProcessHitPoint(hitPoint);
}
return DistanceInteractor.Origin.position
+ DistanceInteractor.Origin.forward * _targetlessLength;
}
protected static Vector3 EvaluateBezier(Vector3 start, Vector3 middle, Vector3 end, float t)
{
t = Mathf.Clamp01(t);
float oneMinusT = 1f - t;
return (oneMinusT * oneMinusT * start)
+ (2f * oneMinusT * t * middle)
+ (t * t * end);
}
private class DummyPointReticle : IReticleData
{
public Transform Target { get; set; }
public Vector3 ProcessHitPoint(Vector3 hitPoint)
{
return Target.position;
}
}
#region Inject
public void InjectAllDistantInteractionLineVisual(IDistanceInteractor interactor)
{
InjectDistanceInteractor(interactor);
}
public void InjectDistanceInteractor(IDistanceInteractor interactor)
{
_distanceInteractor = interactor as UnityEngine.Object;
DistanceInteractor = interactor;
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,114 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.DistanceReticles
{
public class DistantInteractionPolylineVisual : DistantInteractionLineVisual
{
[SerializeField]
private Color _color = Color.white;
public Color Color
{
get
{
return _color;
}
set
{
_color = value;
}
}
[SerializeField]
private float _lineWidth = 0.02f;
public float LineWidth
{
get
{
return _lineWidth;
}
set
{
_lineWidth = value;
}
}
private List<Vector4> _linePointsVec4;
[SerializeField]
private Material _lineMaterial;
private PolylineRenderer _polylineRenderer;
protected override void Start()
{
base.Start();
this.AssertField(_lineMaterial, nameof(_lineMaterial));
_polylineRenderer = new PolylineRenderer(_lineMaterial);
_linePointsVec4 = new List<Vector4>(new Vector4[NumLinePoints]);
}
private void OnDestroy()
{
_polylineRenderer.Cleanup();
}
protected override void RenderLine(Vector3[] linePoints)
{
for (int i = 0; i < linePoints.Length; i++)
{
Vector3 p = linePoints[i];
_linePointsVec4[i] = new Vector4(p.x, p.y, p.z, _lineWidth);
}
_polylineRenderer.SetLines(_linePointsVec4, _color);
_polylineRenderer.RenderLines();
}
protected override void HideLine()
{
}
#region Inject
public void InjectAllDistantInteractionPolylineVisual(IDistanceInteractor interactor,
Color color, Material material)
{
InjectDistanceInteractor(interactor);
InjectLineColor(color);
InjectLineMaterial(material);
}
public void InjectLineColor(Color color)
{
_color = color;
}
public void InjectLineMaterial(Material material)
{
_lineMaterial = material;
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.DistanceReticles
{
public class DistantInteractionTubeVisual : DistantInteractionLineVisual
{
[SerializeField]
private TubeRenderer _tubeRenderer;
private TubePoint[] _tubePoints;
protected override void Start()
{
base.Start();
Assert.IsNotNull(_tubeRenderer);
}
protected override void RenderLine(Vector3[] linePoints)
{
InitializeArcPoints(linePoints);
_tubeRenderer.RenderTube(_tubePoints, Space.World);
}
protected override void HideLine()
{
_tubeRenderer.Hide();
}
private void InitializeArcPoints(Vector3[] linePoints)
{
if (_tubePoints == null
|| _tubePoints.Length < linePoints.Length)
{
_tubePoints = new TubePoint[linePoints.Length];
}
float totalLength = 0f;
for (int i = 1; i < linePoints.Length; i++)
{
totalLength += (linePoints[i] - linePoints[i - 1]).magnitude;
}
for (int i = 0; i < linePoints.Length; i++)
{
Vector3 difference = i == 0 ? linePoints[i + 1] - linePoints[i]
: linePoints[i] - linePoints[i - 1];
_tubePoints[i].position = linePoints[i];
_tubePoints[i].rotation = Quaternion.LookRotation(difference);
_tubePoints[i].relativeLength = i == 0 ? 0f
: _tubePoints[i - 1].relativeLength + (difference.magnitude / totalLength);
}
}
#region Inject
public void InjectAllDistantInteractionPolylineVisual(IDistanceInteractor interactor)
{
InjectDistanceInteractor(interactor);
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,29 @@
/*
* 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;
namespace Oculus.Interaction.DistanceReticles
{
public interface IReticleData
{
Vector3 ProcessHitPoint(Vector3 hitPoint);
}
}

View File

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

View File

@ -0,0 +1,138 @@
/*
* 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;
namespace Oculus.Interaction.DistanceReticles
{
public abstract class InteractorReticle<TReticleData> : MonoBehaviour
where TReticleData : class, IReticleData
{
[SerializeField]
private bool _visibleDuringSelect = false;
private bool VisibleDuringSelect
{
get
{
return _visibleDuringSelect;
}
set
{
_visibleDuringSelect = value;
}
}
protected bool _started;
protected TReticleData _targetData;
private bool _drawn;
protected abstract IInteractorView Interactor { get; set; }
protected abstract Component InteractableComponent { get; }
protected virtual void Start()
{
this.BeginStart(ref _started);
this.AssertField(Interactor, nameof(Interactor));
Hide();
this.EndStart(ref _started);
}
protected virtual void OnEnable()
{
if (_started)
{
Interactor.WhenStateChanged += HandleStateChanged;
Interactor.WhenPostprocessed += HandlePostProcessed;
}
}
protected virtual void OnDisable()
{
if (_started)
{
Interactor.WhenStateChanged -= HandleStateChanged;
Interactor.WhenPostprocessed -= HandlePostProcessed;
}
}
private void HandleStateChanged(InteractorStateChangeArgs args)
{
if (args.NewState == InteractorState.Normal
|| args.NewState == InteractorState.Disabled)
{
InteractableUnset();
}
else if (args.NewState == InteractorState.Hover
&& args.PreviousState != InteractorState.Select)
{
InteractableSet(InteractableComponent);
}
}
private void HandlePostProcessed()
{
if (_targetData != null
&& (Interactor.State == InteractorState.Hover
|| (Interactor.State == InteractorState.Select && _visibleDuringSelect)))
{
if (!_drawn)
{
_drawn = true;
Draw(_targetData);
}
Align(_targetData);
}
else if (_drawn)
{
_drawn = false;
Hide();
}
}
private void InteractableSet(Component interactable)
{
if (interactable != null
&& interactable.TryGetComponent(out _targetData))
{
_drawn = false;
}
else
{
_targetData = null;
}
}
private void InteractableUnset()
{
if (_drawn)
{
_drawn = false;
Hide();
}
_targetData = default(TReticleData);
}
#region Drawing
protected abstract void Draw(TReticleData data);
protected abstract void Align(TReticleData data);
protected abstract void Hide();
#endregion
}
}

View File

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

View File

@ -0,0 +1,36 @@
/*
* 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;
namespace Oculus.Interaction.DistanceReticles
{
public class ReticleDataGhost : MonoBehaviour, IReticleData
{
[SerializeField, Optional]
private Transform _targetPoint;
public Vector3 ProcessHitPoint(Vector3 hitPoint)
{
return _targetPoint != null ? _targetPoint.position
: this.transform.position;
}
}
}

View File

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

View File

@ -0,0 +1,81 @@
/*
* 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;
namespace Oculus.Interaction.DistanceReticles
{
public class ReticleDataIcon : MonoBehaviour, IReticleData
{
[SerializeField, Optional]
private MeshRenderer _renderer;
[SerializeField, Optional]
private Texture _customIcon;
public Texture CustomIcon
{
get
{
return _customIcon;
}
set
{
_customIcon = value;
}
}
[SerializeField]
[Range(0f, 1f)]
private float _snappiness;
public float Snappiness
{
get
{
return _snappiness;
}
set
{
_snappiness = value;
}
}
public Vector3 GetTargetSize()
{
if (_renderer != null)
{
return _renderer.bounds.size;
}
return this.transform.localScale;
}
public Vector3 ProcessHitPoint(Vector3 hitPoint)
{
return Vector3.Lerp(hitPoint, this.transform.position, _snappiness);
}
#region Inject
public void InjectOptionalRenderer(MeshRenderer renderer)
{
_renderer = renderer;
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,48 @@
/*
* 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;
namespace Oculus.Interaction.DistanceReticles
{
public class ReticleDataMesh : MonoBehaviour, IReticleData
{
[SerializeField]
private MeshFilter _filter;
public MeshFilter Filter
{
get
{
return _filter;
}
set
{
_filter = value;
}
}
public Transform Target => _filter.transform;
public Vector3 ProcessHitPoint(Vector3 hitPoint)
{
return _filter.transform.position;
}
}
}

View File

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

View File

@ -0,0 +1,189 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Oculus.Interaction.Grab;
using Oculus.Interaction.HandGrab;
using Oculus.Interaction.Input;
using UnityEngine;
using UnityEngine.Serialization;
namespace Oculus.Interaction.DistanceReticles
{
public class ReticleGhostDrawer : InteractorReticle<ReticleDataGhost>
{
[FormerlySerializedAs("_handGrabber")]
[SerializeField, Interface(typeof(IHandGrabInteractor), typeof(IInteractorView))]
private UnityEngine.Object _handGrabInteractor;
private IHandGrabInteractor HandGrabInteractor { get; set; }
[FormerlySerializedAs("_modifier")]
[SerializeField]
private SyntheticHand _syntheticHand;
[SerializeField, Interface(typeof(IHandVisual))]
[FormerlySerializedAs("_visualHand")]
private UnityEngine.Object _handVisual;
private IHandVisual HandVisual;
private bool _areFingersFree = true;
private bool _isWristFree = true;
protected override IInteractorView Interactor { get; set; }
protected override Component InteractableComponent => HandGrabInteractor.TargetInteractable as Component;
private ITrackingToWorldTransformer Transformer;
protected virtual void Awake()
{
HandVisual = _handVisual as IHandVisual;
HandGrabInteractor = _handGrabInteractor as IHandGrabInteractor;
Interactor = _handGrabInteractor as IInteractorView;
}
protected override void Start()
{
this.BeginStart(ref _started, () => base.Start());
this.AssertField(HandGrabInteractor, nameof(_handGrabInteractor));
this.AssertField(Interactor, nameof(_handGrabInteractor));
this.AssertField(HandVisual, nameof(HandVisual));
this.AssertField(_syntheticHand, nameof(_syntheticHand));
Transformer = _syntheticHand.GetData().Config.TrackingToWorldTransformer;
Hide();
this.EndStart(ref _started);
}
private void UpdateHandPose(IHandGrabState snapper)
{
HandGrabTarget snap = snapper.HandGrabTarget;
if (snap == null)
{
FreeFingers();
FreeWrist();
return;
}
if (snap.HandPose != null)
{
UpdateFingers(snap.HandPose, snapper.GrabbingFingers());
_areFingersFree = false;
}
else
{
FreeFingers();
}
Pose wristLocalPose = snapper.GetVisualWristPose();
Pose wristPose = Transformer != null
? Transformer.ToTrackingPose(wristLocalPose)
: wristLocalPose;
_syntheticHand.LockWristPose(wristPose, 1f);
_isWristFree = false;
}
private void UpdateFingers(HandPose handPose, HandFingerFlags grabbingFingers)
{
Quaternion[] desiredRotations = handPose.JointRotations;
_syntheticHand.OverrideAllJoints(desiredRotations, 1f);
for (int fingerIndex = 0; fingerIndex < Constants.NUM_FINGERS; fingerIndex++)
{
int fingerFlag = 1 << fingerIndex;
JointFreedom fingerFreedom = handPose.FingersFreedom[fingerIndex];
if (fingerFreedom == JointFreedom.Constrained
&& ((int)grabbingFingers & fingerFlag) != 0)
{
fingerFreedom = JointFreedom.Locked;
}
_syntheticHand.SetFingerFreedom((HandFinger)fingerIndex, fingerFreedom);
}
}
private bool FreeFingers()
{
if (!_areFingersFree)
{
_syntheticHand.FreeAllJoints();
_areFingersFree = true;
return true;
}
return false;
}
private bool FreeWrist()
{
if (!_isWristFree)
{
_syntheticHand.FreeWrist();
_isWristFree = true;
return true;
}
return false;
}
protected override void Align(ReticleDataGhost data)
{
UpdateHandPose(HandGrabInteractor);
_syntheticHand.MarkInputDataRequiresUpdate();
}
protected override void Draw(ReticleDataGhost data)
{
HandVisual.ForceOffVisibility = false;
}
protected override void Hide()
{
HandVisual.ForceOffVisibility = true;
_syntheticHand.MarkInputDataRequiresUpdate();
}
#region Inject
public void InjectAllReticleGhostDrawer(IHandGrabInteractor handGrabInteractor,
SyntheticHand syntheticHand, IHandVisual visualHand)
{
InjectHandGrabInteractor(handGrabInteractor);
InjectSyntheticHand(syntheticHand);
InjectVisualHand(visualHand);
}
public void InjectHandGrabInteractor(IHandGrabInteractor handGrabInteractor)
{
_handGrabInteractor = handGrabInteractor as UnityEngine.Object;
HandGrabInteractor = handGrabInteractor;
Interactor = handGrabInteractor as IInteractorView;
}
public void InjectSyntheticHand(SyntheticHand syntheticHand)
{
_syntheticHand = syntheticHand;
}
public void InjectVisualHand(IHandVisual visualHand)
{
_handVisual = visualHand as UnityEngine.Object;
HandVisual = visualHand;
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,164 @@
/*
* 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 UnityEngine.Assertions;
namespace Oculus.Interaction.DistanceReticles
{
public class ReticleIconDrawer : InteractorReticle<ReticleDataIcon>
{
[SerializeField, Interface(typeof(IDistanceInteractor))]
private UnityEngine.Object _distanceInteractor;
private IDistanceInteractor DistanceInteractor { get; set; }
[SerializeField]
private MeshRenderer _renderer;
[SerializeField]
private Transform _centerEye;
[SerializeField]
private Texture _defaultIcon;
public Texture DefaultIcon
{
get
{
return _defaultIcon;
}
set
{
_defaultIcon = value;
}
}
[SerializeField]
private bool _constantScreenSize;
public bool ConstantScreenSize
{
get
{
return _constantScreenSize;
}
set
{
_constantScreenSize = value;
}
}
private Vector3 _originalScale;
protected override IInteractorView Interactor { get; set; }
protected override Component InteractableComponent => DistanceInteractor.DistanceInteractable as Component;
#region Editor events
protected virtual void OnValidate()
{
if (_renderer != null)
{
_renderer.sharedMaterial.mainTexture = _defaultIcon;
}
}
#endregion
protected virtual void Awake()
{
DistanceInteractor = _distanceInteractor as IDistanceInteractor;
Interactor = DistanceInteractor;
}
protected override void Start()
{
this.BeginStart(ref _started, () => base.Start());
this.AssertField(_renderer, nameof(_renderer));
this.AssertField(_centerEye, nameof(_centerEye));
_originalScale = this.transform.localScale;
this.EndStart(ref _started);
}
protected override void Draw(ReticleDataIcon dataIcon)
{
if (dataIcon != null
&& dataIcon.CustomIcon != null)
{
_renderer.material.mainTexture = dataIcon.CustomIcon;
}
else
{
_renderer.material.mainTexture = _defaultIcon;
}
if (!_constantScreenSize)
{
_renderer.transform.localScale = _originalScale * dataIcon.GetTargetSize().magnitude;
}
_renderer.enabled = true;
}
protected override void Align(ReticleDataIcon data)
{
this.transform.position = data.ProcessHitPoint(DistanceInteractor.HitPoint);
if (_renderer.enabled)
{
Vector3 dirToTarget = (_centerEye.position - transform.position).normalized;
transform.LookAt(transform.position - dirToTarget, Vector3.up);
if (_constantScreenSize)
{
float distance = Vector3.Distance(transform.position, _centerEye.position);
_renderer.transform.localScale = _originalScale * distance;
}
}
}
protected override void Hide()
{
_renderer.enabled = false;
}
#region Inject
public void InjectAllReticleIconDrawer(IDistanceInteractor distanceInteractor,
Transform centerEye, MeshRenderer renderer)
{
InjectDistanceInteractor(distanceInteractor);
InjectCenterEye(centerEye);
InjectRenderer(renderer);
}
public void InjectDistanceInteractor(IDistanceInteractor distanceInteractor)
{
_distanceInteractor = distanceInteractor as UnityEngine.Object;
DistanceInteractor = distanceInteractor;
Interactor = distanceInteractor;
}
public void InjectCenterEye(Transform centerEye)
{
_centerEye = centerEye;
}
public void InjectRenderer(MeshRenderer renderer)
{
_renderer = renderer;
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,144 @@
/*
* 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;
using UnityEngine;
using UnityEngine.Serialization;
namespace Oculus.Interaction.DistanceReticles
{
public class ReticleMeshDrawer : InteractorReticle<ReticleDataMesh>
{
[FormerlySerializedAs("_handGrabber")]
[SerializeField, Interface(typeof(IHandGrabInteractor), typeof(IInteractorView))]
private UnityEngine.Object _handGrabInteractor;
private IHandGrabInteractor HandGrabInteractor { get; set; }
[SerializeField]
private MeshFilter _filter;
[SerializeField]
private MeshRenderer _renderer;
[SerializeField]
private PoseTravelData _travelData = PoseTravelData.FAST;
public PoseTravelData TravelData
{
get
{
return _travelData;
}
set
{
_travelData = value;
}
}
protected override IInteractorView Interactor { get; set; }
protected override Component InteractableComponent => HandGrabInteractor.TargetInteractable as Component;
private Tween _tween;
protected virtual void Reset()
{
_filter = this.GetComponent<MeshFilter>();
_renderer = this.GetComponent<MeshRenderer>();
}
protected virtual void Awake()
{
HandGrabInteractor = _handGrabInteractor as IHandGrabInteractor;
Interactor = _handGrabInteractor as IInteractorView;
}
protected override void Start()
{
this.BeginStart(ref _started, () => base.Start());
this.AssertField(Interactor, nameof(_handGrabInteractor));
this.AssertField(HandGrabInteractor, nameof(_handGrabInteractor));
this.AssertField(_filter, nameof(_filter));
this.AssertField(_renderer, nameof(_renderer));
this.EndStart(ref _started);
}
protected override void Draw(ReticleDataMesh dataMesh)
{
_filter.sharedMesh = dataMesh.Filter.sharedMesh;
_filter.transform.localScale = dataMesh.Filter.transform.lossyScale;
_renderer.enabled = true;
Pose target = DestinationPose(dataMesh, HandGrabInteractor.GetTargetGrabPose());
_tween = _travelData.CreateTween(dataMesh.Target.GetPose(), target);
}
protected override void Hide()
{
_tween = null;
_renderer.enabled = false;
}
protected override void Align(ReticleDataMesh data)
{
Pose target = DestinationPose(data, HandGrabInteractor.GetTargetGrabPose());
_tween.UpdateTarget(target);
_tween.Tick();
_filter.transform.SetPose(_tween.Pose);
}
private Pose DestinationPose(ReticleDataMesh data, Pose worldSnapPose)
{
Pose targetOffset = PoseUtils.Delta(worldSnapPose, data.Target.GetPose());
HandGrabInteractor.HandGrabApi.Hand.GetRootPose(out Pose pose);
pose.Premultiply(HandGrabInteractor.WristToGrabPoseOffset);
pose.Premultiply(targetOffset);
return pose;
}
#region Inject
public void InjectAllReticleMeshDrawer(IHandGrabInteractor handGrabInteractor,
MeshFilter filter, MeshRenderer renderer)
{
InjectHandGrabInteractor(handGrabInteractor);
InjectFilter(filter);
InjectRenderer(renderer);
}
public void InjectHandGrabInteractor(IHandGrabInteractor handGrabInteractor)
{
_handGrabInteractor = handGrabInteractor as UnityEngine.Object;
HandGrabInteractor = handGrabInteractor;
Interactor = handGrabInteractor as IInteractorView;
}
public void InjectFilter(MeshFilter filter)
{
_filter = filter;
}
public void InjectRenderer(MeshRenderer renderer)
{
_renderer = renderer;
}
#endregion
}
}

View File

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

View File

@ -0,0 +1,507 @@
/*
* 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.Runtime.InteropServices;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;
namespace Oculus.Interaction
{
public struct TubePoint
{
public Vector3 position;
public Quaternion rotation;
public float relativeLength;
}
/// <summary>
/// Creates and renders a tube mesh from sequence of points.
/// </summary>
public class TubeRenderer : MonoBehaviour
{
[StructLayout(LayoutKind.Sequential)]
private struct VertexLayout
{
public Vector3 pos;
public Color32 color;
public Vector2 uv;
}
[SerializeField]
private MeshFilter _filter;
[SerializeField]
private MeshRenderer _renderer;
[SerializeField]
private int _divisions = 6;
[SerializeField]
private int _bevel = 4;
[SerializeField]
private int _renderQueue = -1;
public int RenderQueue
{
get
{
return _renderQueue;
}
set
{
_renderQueue = value;
}
}
[SerializeField]
private Vector2 _renderOffset = Vector2.zero;
public Vector2 RenderOffset
{
get
{
return _renderOffset;
}
set
{
_renderOffset = value;
}
}
[SerializeField]
private float _radius = 0.005f;
public float Radius
{
get
{
return _radius;
}
set
{
_radius = value;
}
}
[SerializeField]
private Gradient _gradient;
public Gradient Gradient
{
get
{
return _gradient;
}
set
{
_gradient = value;
}
}
[SerializeField]
private Color _tint = Color.white;
public Color Tint
{
get
{
return _tint;
}
set
{
_tint = value;
}
}
[SerializeField, Range(0f, 1f)]
private float _progressFade = 0.2f;
public float ProgressFade
{
get
{
return _progressFade;
}
set
{
_progressFade = value;
}
}
[SerializeField]
private float _startFadeThresold = 0.2f;
public float StartFadeThresold
{
get
{
return _startFadeThresold;
}
set
{
_startFadeThresold = value;
}
}
[SerializeField]
private float _endFadeThresold = 0.2f;
public float EndFadeThresold
{
get
{
return _endFadeThresold;
}
set
{
_endFadeThresold = value;
}
}
[SerializeField]
private bool _invertThreshold = false;
public bool InvertThreshold
{
get
{
return _invertThreshold;
}
set
{
_invertThreshold = value;
}
}
[SerializeField]
private float _feather = 0.2f;
public float Feather
{
get
{
return _feather;
}
set
{
_feather = value;
}
}
[SerializeField]
private bool _mirrorTexture;
public bool MirrorTexture
{
get
{
return _mirrorTexture;
}
set
{
_mirrorTexture = value;
}
}
public float Progress { get; set; } = 0f;
public float TotalLength => _totalLength;
private VertexAttributeDescriptor[] _dataLayout;
private NativeArray<VertexLayout> _vertsData;
private VertexLayout _layout = new VertexLayout();
private Mesh _mesh;
private int[] _tris;
private int _initializedSteps = -1;
private int _vertsCount;
private float _totalLength = 0f;
private static readonly int _fadeLimitsShaderID = Shader.PropertyToID("_FadeLimit");
private static readonly int _fadeSignShaderID = Shader.PropertyToID("_FadeSign");
private static readonly int _offsetFactorShaderPropertyID = Shader.PropertyToID("_OffsetFactor");
private static readonly int _offsetUnitsShaderPropertyID = Shader.PropertyToID("_OffsetUnits");
#region Editor events
protected virtual void Reset()
{
_filter = this.GetComponent<MeshFilter>();
_renderer = this.GetComponent<MeshRenderer>();
}
#endregion
protected virtual void OnEnable()
{
_renderer.enabled = true;
}
protected virtual void OnDisable()
{
_renderer.enabled = false;
}
/// <summary>
/// Updates the mesh data for the tube with the specified points
/// </summary>
/// <param name="points">The points that the tube must follow</param>
/// <param name="space">Indicates if the points are specified in local space or world space</param>
public void RenderTube(TubePoint[] points, Space space = Space.Self)
{
int steps = points.Length;
if (steps != _initializedSteps)
{
InitializeMeshData(steps);
_initializedSteps = steps;
}
_vertsData = new NativeArray<VertexLayout>(_vertsCount, Allocator.Temp);
UpdateMeshData(points, space);
_renderer.enabled = enabled;
}
/// <summary>
/// Hides the renderer of the tube
/// </summary>
public void Hide()
{
_renderer.enabled = false;
}
private void InitializeMeshData(int steps)
{
_dataLayout = new VertexAttributeDescriptor[]
{
new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3),
new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.UNorm8, 4),
new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2),
};
_vertsCount = SetVertexCount(steps, _divisions, _bevel);
SubMeshDescriptor submeshDesc = new SubMeshDescriptor(0, _tris.Length, MeshTopology.Triangles);
_mesh = new Mesh();
_mesh.SetVertexBufferParams(_vertsCount, _dataLayout);
_mesh.SetIndexBufferParams(_tris.Length, IndexFormat.UInt32);
_mesh.SetIndexBufferData(_tris, 0, 0, _tris.Length);
_mesh.subMeshCount = 1;
_mesh.SetSubMesh(0, submeshDesc);
_filter.mesh = _mesh;
}
private void UpdateMeshData(TubePoint[] points, Space space)
{
int steps = points.Length;
float totalLength = 0f;
Vector3 prevPoint = Vector3.zero;
Pose pose = Pose.identity;
Pose start = Pose.identity;
Pose end = Pose.identity;
Pose rootPose = this.transform.GetPose(Space.World);
Quaternion inverseRootRotation = Quaternion.Inverse(rootPose.rotation);
Vector3 rootPositionScaled = new Vector3(
rootPose.position.x / this.transform.lossyScale.x,
rootPose.position.y / this.transform.lossyScale.y,
rootPose.position.z / this.transform.lossyScale.z);
float uniformScale = space == Space.World ? this.transform.lossyScale.x : 1f;
TransformPose(points[0], ref start);
TransformPose(points[points.Length - 1], ref end);
BevelCap(start, false, 0);
for (int i = 0; i < steps; i++)
{
TransformPose(points[i], ref pose);
Vector3 point = pose.position;
Quaternion rotation = pose.rotation;
float progress = points[i].relativeLength;
Color color = Gradient.Evaluate(progress) * _tint;
if (i > 0)
{
totalLength += Vector3.Distance(point, prevPoint);
}
prevPoint = point;
if (i / (steps - 1f) < Progress)
{
color.a *= ProgressFade;
}
_layout.color = color;
WriteCircle(point, rotation, _radius, i + _bevel, progress);
}
BevelCap(end, true, _bevel + steps);
_mesh.bounds = new Bounds(
(start.position + end.position) * 0.5f,
end.position - start.position);
_mesh.SetVertexBufferData(_vertsData, 0, 0, _vertsData.Length, 0, MeshUpdateFlags.DontRecalculateBounds);
_totalLength = totalLength * uniformScale;
RedrawFadeThresholds();
void TransformPose(in TubePoint tubePoint, ref Pose pose)
{
if (space == Space.Self)
{
pose.position = tubePoint.position;
pose.rotation = tubePoint.rotation;
return;
}
pose.position = inverseRootRotation * (tubePoint.position - rootPositionScaled);
pose.rotation = inverseRootRotation * tubePoint.rotation;
}
}
/// <summary>
/// Resubmits the fading thresholds data to the material without re-generating the mesh
/// </summary>
public void RedrawFadeThresholds()
{
float originFadeIn = StartFadeThresold / _totalLength;
float originFadeOut = (StartFadeThresold + Feather) / _totalLength;
float endFadeIn = (_totalLength - EndFadeThresold) / _totalLength;
float endFadeOut = (_totalLength - EndFadeThresold - Feather) / _totalLength;
_renderer.material.SetVector(_fadeLimitsShaderID, new Vector4(
_invertThreshold ? originFadeOut : originFadeIn,
_invertThreshold ? originFadeIn : originFadeOut,
endFadeOut,
endFadeIn));
_renderer.material.SetFloat(_fadeSignShaderID, _invertThreshold ? -1 : 1);
_renderer.material.renderQueue = _renderQueue;
_renderer.material.SetFloat(_offsetFactorShaderPropertyID, _renderOffset.x);
_renderer.material.SetFloat(_offsetUnitsShaderPropertyID, _renderOffset.y);
}
private void BevelCap(in Pose pose, bool end, int indexOffset)
{
Vector3 origin = pose.position;
Quaternion rotation = pose.rotation;
for (int i = 0; i < _bevel; i++)
{
float radiusFactor = Mathf.InverseLerp(-1, _bevel + 1, i);
if (end)
{
radiusFactor = 1 - radiusFactor;
}
float positionFactor = Mathf.Sqrt(1 - radiusFactor * radiusFactor);
Vector3 point = origin + (end ? 1 : -1) * (rotation * Vector3.forward) * _radius * positionFactor;
WriteCircle(point, rotation, _radius * radiusFactor, i + indexOffset, end ? 1 : 0);
}
}
private void WriteCircle(Vector3 point, Quaternion rotation, float width, int index, float progress)
{
Color color = Gradient.Evaluate(progress) * _tint;
if (progress < Progress)
{
color.a *= ProgressFade;
}
_layout.color = color;
for (int j = 0; j <= _divisions; j++)
{
float radius = 2 * Mathf.PI * j / _divisions;
Vector3 circle = new Vector3(Mathf.Sin(radius), Mathf.Cos(radius), 0);
Vector3 normal = rotation * circle;
_layout.pos = point + normal * width;
if (_mirrorTexture)
{
float x = (j / (float)_divisions) * 2f;
if (j >= _divisions * 0.5f)
{
x = 2 - x;
}
_layout.uv = new Vector2(x, progress);
}
else
{
_layout.uv = new Vector2(j / (float)_divisions, progress);
}
int vertIndex = index * (_divisions + 1) + j;
_vertsData[vertIndex] = _layout;
}
}
private int SetVertexCount(int positionCount, int divisions, int bevelCap)
{
bevelCap = bevelCap * 2;
int vertsPerPosition = divisions + 1;
int vertCount = (positionCount + bevelCap) * vertsPerPosition;
int tubeTriangles = (positionCount - 1 + bevelCap) * divisions * 6;
int capTriangles = (divisions - 2) * 3;
int triangleCount = tubeTriangles + capTriangles * 2;
_tris = new int[triangleCount];
// handle triangulation
for (int i = 0; i < positionCount - 1 + bevelCap; i++)
{
// add faces
for (int j = 0; j < divisions; j++)
{
int vert0 = i * vertsPerPosition + j;
int vert1 = (i + 1) * vertsPerPosition + j;
int t = (i * divisions + j) * 6;
_tris[t] = vert0;
_tris[t + 1] = _tris[t + 4] = vert1;
_tris[t + 2] = _tris[t + 3] = vert0 + 1;
_tris[t + 5] = vert1 + 1;
}
}
// triangulate the ends
Cap(tubeTriangles, 0, divisions - 1, true);
Cap(tubeTriangles + capTriangles, vertCount - divisions, vertCount - 1);
void Cap(int t, int firstVert, int lastVert, bool clockwise = false)
{
for (int i = firstVert + 1; i < lastVert; i++)
{
_tris[t++] = firstVert;
_tris[t++] = clockwise ? i : i + 1;
_tris[t++] = clockwise ? i + 1 : i;
}
}
return vertCount;
}
#region Inject
public void InjectAllTubeRenderer(MeshFilter filter,
MeshRenderer renderer, int divisions, int bevel)
{
InjectFilter(filter);
InjectRenderer(renderer);
InjectDivisions(divisions);
InjectBevel(bevel);
}
public void InjectFilter(MeshFilter filter)
{
_filter = filter;
}
public void InjectRenderer(MeshRenderer renderer)
{
_renderer = renderer;
}
public void InjectDivisions(int divisions)
{
_divisions = divisions;
}
public void InjectBevel(int bevel)
{
_bevel = bevel;
}
#endregion
}
}

View File

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

View File

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

View File

@ -0,0 +1,183 @@
/*
* 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;
namespace Oculus.Interaction
{
/// <summary>
/// Non-allocating HashSet extension methods mirroring MS implementation
/// https://referencesource.microsoft.com/#system.core/system/Collections/Generic/HashSet.cs
/// </summary>
public static class HashSetExtensions
{
/// <summary>
/// Take the union of this HashSet with other. Modifies this set.
/// </summary>
/// <param name="other">HashSet with items to add</param>
public static void UnionWithNonAlloc<T>(this HashSet<T> hashSetToModify, HashSet<T> other)
{
if (hashSetToModify == null)
{
throw new ArgumentNullException(nameof(hashSetToModify));
}
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
foreach (T item in other)
{
hashSetToModify.Add(item);
}
}
/// <summary>
/// Take the union of this HashSet with other. Modifies this set.
/// </summary>
/// <param name="other">IList with items to add</param>
public static void UnionWithNonAlloc<T>(this HashSet<T> hashSetToModify, IList<T> other)
{
if (hashSetToModify == null)
{
throw new ArgumentNullException(nameof(hashSetToModify));
}
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
for (int i = 0; i < other.Count; ++i)
{
hashSetToModify.Add(other[i]);
}
}
/// <summary>
/// Remove items in other from this set. Modifies this set
/// </summary>
/// <param name="other">HashSet with items to remove</param>
public static void ExceptWithNonAlloc<T>(this HashSet<T> hashSetToModify, HashSet<T> other)
{
if (hashSetToModify == null)
{
throw new ArgumentNullException(nameof(hashSetToModify));
}
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
if (hashSetToModify.Count == 0)
{
return;
}
if (other == hashSetToModify)
{
hashSetToModify.Clear();
return;
}
foreach (T element in other)
{
hashSetToModify.Remove(element);
}
}
/// <summary>
/// Remove items in other from this set. Modifies this set
/// </summary>
/// <param name="other">IList with items to remove</param>
public static void ExceptWithNonAlloc<T>(this HashSet<T> hashSetToModify, IList<T> other)
{
if (hashSetToModify == null)
{
throw new ArgumentNullException(nameof(hashSetToModify));
}
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
if (hashSetToModify.Count == 0)
{
return;
}
for (int i = 0; i < other.Count; ++i)
{
hashSetToModify.Remove(other[i]);
}
}
/// <summary>
/// Checks if this set overlaps other (i.e. they share at least one item)
/// </summary>
/// <param name="other">HashSet to check overlap against</param>
/// <returns>true if these have at least one common element; false if disjoint</returns>
public static bool OverlapsNonAlloc<T>(this HashSet<T> hashSetToCheck, HashSet<T> other)
{
if (hashSetToCheck == null)
{
throw new ArgumentNullException(nameof(hashSetToCheck));
}
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
if (hashSetToCheck.Count == 0)
{
return false;
}
foreach (T element in other)
{
if (hashSetToCheck.Contains(element))
{
return true;
}
}
return false;
}
/// <summary>
/// Checks if this set overlaps other (i.e. they share at least one item)
/// </summary>
/// <param name="other">IList to check overlap against</param>
/// <returns>true if these have at least one common element; false if disjoint</returns>
public static bool OverlapsNonAlloc<T>(this HashSet<T> hashSetToCheck, IList<T> other)
{
if (hashSetToCheck == null)
{
throw new ArgumentNullException(nameof(hashSetToCheck));
}
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
if (hashSetToCheck.Count == 0)
{
return false;
}
for (int i = 0; i < other.Count; ++i)
{
if (hashSetToCheck.Contains(other[i]))
{
return true;
}
}
return false;
}
}
}

View File

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

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using UnityEngine;
namespace Oculus.Interaction
{
/// <summary>
/// By adding BeginStart and EndStart at the beginning and end of Start, MonoBehaviours with
/// OnEnable and OnDisable logic can wrap their contents within a _started flag and effectively
/// skip over logic in those methods until after Start has been invoked.
///
/// To not bypass the Unity Lifecycle, the enabled property is used to disable the most derived
/// MonoBehaviour, invoke Start up the hierarchy chain, and finally re-enable the MonoBehaviour.
/// </summary>
public static class MonoBehaviourStartExtensions
{
public static void BeginStart(this MonoBehaviour monoBehaviour, ref bool started,
Action baseStart = null)
{
if (!started)
{
monoBehaviour.enabled = false;
started = true;
baseStart?.Invoke();
started = false;
}
else
{
baseStart?.Invoke();
}
}
public static void EndStart(this MonoBehaviour monoBehaviour, ref bool started)
{
if (!started)
{
started = true;
monoBehaviour.enabled = true;
}
}
}
}

View File

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

View File

@ -0,0 +1,91 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
namespace Oculus.Interaction
{
public static class TransformExtensions
{
/// <summary>
/// Transforms position from world space to local space
/// </summary>
public static Vector3 InverseTransformPointUnscaled(this Transform transform, Vector3 position)
{
Matrix4x4 worldToLocal = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one).inverse;
return worldToLocal.MultiplyPoint3x4(position);
}
/// <summary>
/// Transforms position from local space to world space
/// </summary>
public static Vector3 TransformPointUnscaled(this Transform transform, Vector3 position)
{
Matrix4x4 localToWorld = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one);
return localToWorld.MultiplyPoint3x4(position);
}
/// <summary>
/// Transform a bounding box from local to world space.
/// </summary>
/// <param name="transform">Transfrom that
/// <paramref name="bounds"/> is local to</param>
/// <param name="bounds">The bounds to transform, in local space</param>
/// <returns>The bounding box in world space</returns>
public static Bounds TransformBounds(this Transform transform, in Bounds bounds)
{
Bounds worldBounds = new Bounds();
Vector3 boundsMin = bounds.min;
Vector3 boundsMax = bounds.max;
Vector3 min = transform.position;
Vector3 max = transform.position;
Matrix4x4 m = transform.localToWorldMatrix;
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 3; ++j)
{
float e = m[i, j] * boundsMin[j];
float f = m[i, j] * boundsMax[j];
min[i] += (e < f) ? e : f;
max[i] += (e < f) ? f : e;
}
}
worldBounds.SetMinMax(min, max);
return worldBounds;
}
public static Transform FindChildRecursive(this Transform parent, string name)
{
foreach (Transform child in parent)
{
if (child.name.Contains(name))
return child;
var result = child.FindChildRecursive(name);
if (result != null)
return result;
}
return null;
}
}
}

View File

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

View File

@ -0,0 +1,36 @@
/*
* 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;
namespace Oculus.Interaction
{
public static class VectorExtensions
{
/// <summary>
/// Are two points within <see cref="Vector3.kEpsilon"/>
/// distance of each other
/// </summary>
public static bool Approximately(this Vector3 a, Vector3 b, float epsilon = Vector3.kEpsilon)
{
return (a - b).sqrMagnitude <= epsilon * epsilon;
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,171 @@
/*
* 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 System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.Assertions;
namespace Oculus.Interaction.GrabAPI
{
/// <summary>
/// This Finger API uses the curl value of the fingers to detect if they are grabbing
/// </summary>
public class FingerPalmGrabAPI : IFingerAPI
{
// Temporary structure used to pass data to and from native components
[StructLayout(LayoutKind.Sequential)]
public class HandData
{
private const int NumHandJoints = 24;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = NumHandJoints * 7, ArraySubType = UnmanagedType.R4)]
private float[] jointValues;
private float _rootRotX;
private float _rootRotY;
private float _rootRotZ;
private float _rootRotW;
private float _rootPosX;
private float _rootPosY;
private float _rootPosZ;
private int _handedness;
public HandData()
{
jointValues = new float[NumHandJoints * 7];
}
public void SetData(IReadOnlyList<Pose> joints, Pose root, Handedness handedness)
{
Assert.AreEqual(NumHandJoints, joints.Count);
int jointValueIndex = 0;
for (int jointIndex = 0; jointIndex < NumHandJoints; jointIndex++)
{
Pose joint = joints[jointIndex];
jointValues[jointValueIndex++] = joint.rotation.x;
jointValues[jointValueIndex++] = joint.rotation.y;
jointValues[jointValueIndex++] = joint.rotation.z;
jointValues[jointValueIndex++] = joint.rotation.w;
jointValues[jointValueIndex++] = joint.position.x;
jointValues[jointValueIndex++] = joint.position.y;
jointValues[jointValueIndex++] = joint.position.z;
}
this._rootRotX = root.rotation.x;
this._rootRotY = root.rotation.y;
this._rootRotZ = root.rotation.z;
this._rootRotW = root.rotation.w;
this._rootPosX = root.position.x;
this._rootPosY = root.position.y;
this._rootPosZ = root.position.z;
this._handedness = (int)handedness;
}
}
#region DLLImports
enum ReturnValue { Success = 0, Failure = -1 };
[DllImport("InteractionSdk")]
private static extern int isdk_FingerPalmGrabAPI_Create();
[DllImport("InteractionSdk")]
private static extern ReturnValue isdk_FingerPalmGrabAPI_UpdateHandData(int handle, [In] HandData data);
[DllImport("InteractionSdk")]
private static extern ReturnValue isdk_FingerPalmGrabAPI_GetFingerIsGrabbing(int handle, HandFinger finger, out bool grabbing);
[DllImport("InteractionSdk")]
private static extern ReturnValue isdk_FingerPalmGrabAPI_GetFingerIsGrabbingChanged(int handle, HandFinger finger, bool targetGrabState, out bool changed);
[DllImport("InteractionSdk")]
private static extern ReturnValue isdk_FingerPalmGrabAPI_GetFingerGrabScore(int handle, HandFinger finger, out float score);
[DllImport("InteractionSdk")]
private static extern ReturnValue isdk_FingerPalmGrabAPI_GetCenterOffset(int handle, out Vector3 score);
#endregion
private int apiHandle_ = -1;
private HandData handData_;
public FingerPalmGrabAPI()
{
handData_ = new HandData();
}
private int GetHandle()
{
if (apiHandle_ == -1)
{
apiHandle_ = isdk_FingerPalmGrabAPI_Create();
Debug.Assert(apiHandle_ != -1, "FingerPalmGrabAPI: isdk_FingerPalmGrabAPI_Create failed");
}
return apiHandle_;
}
public bool GetFingerIsGrabbing(HandFinger finger)
{
ReturnValue rv = isdk_FingerPalmGrabAPI_GetFingerIsGrabbing(GetHandle(), finger, out bool grabbing);
Debug.Assert(rv != ReturnValue.Failure, "FingerPalmGrabAPI: isdk_FingerPalmGrabAPI_GetFingerIsGrabbing failed");
return grabbing;
}
public bool GetFingerIsGrabbingChanged(HandFinger finger, bool targetGrabState)
{
ReturnValue rv = isdk_FingerPalmGrabAPI_GetFingerIsGrabbingChanged(GetHandle(), finger, targetGrabState, out bool grabbing);
Debug.Assert(rv != ReturnValue.Failure, "FingerPalmGrabAPI: isdk_FingerPalmGrabAPI_GetFingerIsGrabbingChanged failed");
return grabbing;
}
public float GetFingerGrabScore(HandFinger finger)
{
ReturnValue rv = isdk_FingerPalmGrabAPI_GetFingerGrabScore(GetHandle(), finger, out float score);
Debug.Assert(rv != ReturnValue.Failure, "FingerPalmGrabAPI: isdk_FingerPalmGrabAPI_GetFingerGrabScore failed");
return score;
}
public void Update(IHand hand)
{
if (!hand.GetRootPose(out Pose rootPose))
{
return;
}
if (!hand.GetJointPosesFromWrist(out ReadOnlyHandJointPoses poses))
{
return;
}
handData_.SetData(poses, rootPose, hand.Handedness);
ReturnValue rv = isdk_FingerPalmGrabAPI_UpdateHandData(GetHandle(), handData_);
Debug.Assert(rv != ReturnValue.Failure, "FingerPalmGrabAPI: isdk_FingerPalmGrabAPI_UpdateHandData failed");
}
public Vector3 GetWristOffsetLocal()
{
ReturnValue rv = isdk_FingerPalmGrabAPI_GetCenterOffset(GetHandle(), out Vector3 center);
Debug.Assert(rv != ReturnValue.Failure, "FingerPalmGrabAPI: isdk_FingerPalmGrabAPI_GetCenterOffset failed");
return center;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: df4b2f3ffa2d8a04eb0badc7bfd33a41
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