clean project
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe9c2ebcb43026146ba2e7335e2e86c9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,113 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public class ActiveStateGroup : MonoBehaviour, IActiveState
|
||||
{
|
||||
public enum ActiveStateGroupLogicOperator
|
||||
{
|
||||
AND = 0,
|
||||
OR = 1,
|
||||
XOR = 2
|
||||
}
|
||||
|
||||
[SerializeField, Interface(typeof(IActiveState))]
|
||||
private List<MonoBehaviour> _activeStates;
|
||||
private List<IActiveState> ActiveStates;
|
||||
|
||||
[SerializeField]
|
||||
private ActiveStateGroupLogicOperator _logicOperator = ActiveStateGroupLogicOperator.AND;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
ActiveStates = _activeStates.ConvertAll(mono => mono as IActiveState);
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
foreach (IActiveState activeState in ActiveStates)
|
||||
{
|
||||
Assert.IsNotNull(activeState);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Active
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ActiveStates == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch(_logicOperator)
|
||||
{
|
||||
case ActiveStateGroupLogicOperator.AND:
|
||||
foreach(IActiveState activeState in ActiveStates)
|
||||
{
|
||||
if(!activeState.Active) return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
case ActiveStateGroupLogicOperator.OR:
|
||||
foreach(IActiveState activeState in ActiveStates)
|
||||
{
|
||||
if(activeState.Active) return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
case ActiveStateGroupLogicOperator.XOR:
|
||||
bool foundActive = false;
|
||||
foreach(IActiveState activeState in ActiveStates)
|
||||
{
|
||||
if(activeState.Active)
|
||||
{
|
||||
if(foundActive) return false;
|
||||
foundActive = true;
|
||||
}
|
||||
}
|
||||
return foundActive;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllActiveStateGroup(List<IActiveState> activeStates)
|
||||
{
|
||||
InjectActiveStates(activeStates);
|
||||
}
|
||||
|
||||
public void InjectActiveStates(List<IActiveState> activeStates)
|
||||
{
|
||||
ActiveStates = activeStates;
|
||||
_activeStates = activeStates.ConvertAll(activeState => activeState as MonoBehaviour);
|
||||
}
|
||||
|
||||
public void InjectOptionalLogicOperator(ActiveStateGroupLogicOperator logicOperator)
|
||||
{
|
||||
_logicOperator = logicOperator;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aebd5da9a8bd22243b407f1927f4965b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public class ActiveStateNot : MonoBehaviour, IActiveState
|
||||
{
|
||||
[SerializeField, Interface(typeof(IActiveState))]
|
||||
private MonoBehaviour _activeState;
|
||||
|
||||
private IActiveState ActiveState;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
ActiveState = _activeState as IActiveState;;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
Assert.IsNotNull(ActiveState);
|
||||
}
|
||||
|
||||
public bool Active => !ActiveState.Active;
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllActiveStateNot(IActiveState activeState)
|
||||
{
|
||||
InjectActiveState(activeState);
|
||||
}
|
||||
|
||||
public void InjectActiveState(IActiveState activeState)
|
||||
{
|
||||
_activeState = activeState as MonoBehaviour;
|
||||
ActiveState = activeState;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67fb2ae006d618c4180efcabc2481fcf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,71 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public class ActiveStateSelector : MonoBehaviour, ISelector
|
||||
{
|
||||
[SerializeField, Interface(typeof(IActiveState))]
|
||||
private MonoBehaviour _activeState;
|
||||
|
||||
private IActiveState ActiveState;
|
||||
private bool _selecting = false;
|
||||
|
||||
public event Action WhenSelected = delegate { };
|
||||
public event Action WhenUnselected = delegate { };
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
ActiveState = _activeState as IActiveState;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
Assert.IsNotNull(ActiveState);
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
if (_selecting != ActiveState.Active)
|
||||
{
|
||||
_selecting = ActiveState.Active;
|
||||
if (_selecting)
|
||||
{
|
||||
WhenSelected();
|
||||
}
|
||||
else
|
||||
{
|
||||
WhenUnselected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllActiveStateSelector(IActiveState activeState)
|
||||
{
|
||||
InjectActiveState(activeState);
|
||||
}
|
||||
|
||||
public void InjectActiveState(IActiveState activeState)
|
||||
{
|
||||
_activeState = activeState as MonoBehaviour;
|
||||
ActiveState = activeState;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1cd9780be7e512049b4d33d5c9d0ac92
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,120 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Modifies the `active` state of a list of GameObjects, as well as the `enabled` state of a
|
||||
/// list of components, from the `Active` field of the given IActiveState.
|
||||
/// The component will only activate/enable dependants that were active/enabled during Start()
|
||||
/// lifecycle phase.
|
||||
/// </summary>
|
||||
/// These need to be updated in batch or else we could get inconsistent behaviour when
|
||||
/// multiple of these are in a scene.
|
||||
[DefaultExecutionOrder(1)]
|
||||
public class ActiveStateTracker : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Interface(typeof(IActiveState))]
|
||||
private MonoBehaviour _activeState;
|
||||
|
||||
private IActiveState ActiveState;
|
||||
|
||||
[Header("Active state dependents")]
|
||||
[SerializeField]
|
||||
private bool _includeChildrenAsDependents = false;
|
||||
|
||||
[SerializeField, Optional]
|
||||
[Tooltip("Sets the `active` field on whole GameObjects")]
|
||||
private List<GameObject> _gameObjects;
|
||||
|
||||
[SerializeField, Optional]
|
||||
[Tooltip("Sets the `enabled` field on individual components")]
|
||||
private List<MonoBehaviour> _monoBehaviours;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
ActiveState = _activeState as IActiveState;
|
||||
}
|
||||
|
||||
private bool _active = false;
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
Assert.IsNotNull(ActiveState);
|
||||
|
||||
if (_includeChildrenAsDependents)
|
||||
{
|
||||
for(int i = 0; i < transform.childCount; i ++)
|
||||
{
|
||||
_gameObjects.Add(transform.GetChild(i).gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
SetDependentsActive(false);
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
if (_active == ActiveState.Active) return;
|
||||
|
||||
_active = ActiveState.Active;
|
||||
SetDependentsActive(ActiveState.Active);
|
||||
}
|
||||
|
||||
private void SetDependentsActive(bool active)
|
||||
{
|
||||
for (int i = 0; i < _gameObjects.Count; ++i)
|
||||
{
|
||||
_gameObjects[i].SetActive(active);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _monoBehaviours.Count; ++i)
|
||||
{
|
||||
_monoBehaviours[i].enabled = active;
|
||||
}
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllActiveStateTracker(IActiveState activeState)
|
||||
{
|
||||
InjectActiveState(activeState);
|
||||
}
|
||||
|
||||
public void InjectActiveState(IActiveState activeState)
|
||||
{
|
||||
_activeState = activeState as MonoBehaviour;
|
||||
ActiveState = activeState;
|
||||
}
|
||||
|
||||
public void InjectOptionalIncludeChildrenAsDependents(bool includeChildrenAsDependents)
|
||||
{
|
||||
_includeChildrenAsDependents = includeChildrenAsDependents;
|
||||
}
|
||||
|
||||
public void InjectOptionalGameObjects(List<GameObject> gameObjects)
|
||||
{
|
||||
_gameObjects = gameObjects;
|
||||
}
|
||||
|
||||
public void InjectOptionalMonoBehaviours(List<MonoBehaviour> monoBehaviours)
|
||||
{
|
||||
_monoBehaviours = monoBehaviours;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 194b7d1054b8fa64b9bdcea46d06c3a1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,105 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// The CollisionsInteractableRegistry maintains a collision map for any Rigidbody-Interactables
|
||||
/// pair that utilizes Unity Colliders for overlap checks
|
||||
/// </summary>
|
||||
public class CollisionInteractionRegistry<TInteractor, TInteractable> :
|
||||
InteractableRegistry<TInteractor, TInteractable>
|
||||
where TInteractor : class, IInteractor<TInteractable>, IRigidbodyRef
|
||||
where TInteractable : class, IInteractable<TInteractor>, IRigidbodyRef
|
||||
{
|
||||
private Dictionary<Rigidbody, HashSet<TInteractable>> _rigidbodyCollisionMap;
|
||||
private Dictionary<TInteractable, InteractableTriggerBroadcaster> _broadcasters;
|
||||
|
||||
public CollisionInteractionRegistry() : base()
|
||||
{
|
||||
_rigidbodyCollisionMap = new Dictionary<Rigidbody, HashSet<TInteractable>>();
|
||||
_broadcasters = new Dictionary<TInteractable, InteractableTriggerBroadcaster>();
|
||||
}
|
||||
|
||||
public override void Register(TInteractable interactable)
|
||||
{
|
||||
base.Register(interactable);
|
||||
|
||||
GameObject triggerGameObject = interactable.Rigidbody.gameObject;
|
||||
InteractableTriggerBroadcaster broadcaster;
|
||||
if (!_broadcasters.TryGetValue(interactable, out broadcaster))
|
||||
{
|
||||
broadcaster = triggerGameObject.AddComponent<InteractableTriggerBroadcaster>();
|
||||
broadcaster.InjectAllInteractableTriggerBroadcaster(interactable);
|
||||
_broadcasters.Add(interactable, broadcaster);
|
||||
broadcaster.OnTriggerEntered += MarkCollision;
|
||||
broadcaster.OnTriggerExited += UnmarkCollision;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Unregister(TInteractable interactable)
|
||||
{
|
||||
base.Unregister(interactable);
|
||||
|
||||
InteractableTriggerBroadcaster broadcaster;
|
||||
if (_broadcasters.TryGetValue(interactable, out broadcaster))
|
||||
{
|
||||
broadcaster.enabled = false;
|
||||
broadcaster.OnTriggerEntered -= MarkCollision;
|
||||
broadcaster.OnTriggerExited -= UnmarkCollision;
|
||||
_broadcasters.Remove(interactable);
|
||||
Object.Destroy(broadcaster);
|
||||
}
|
||||
}
|
||||
|
||||
private void MarkCollision(IInteractable interactable, Rigidbody rigidbody)
|
||||
{
|
||||
TInteractable typedInteractable = interactable as TInteractable;
|
||||
if (!_rigidbodyCollisionMap.ContainsKey(rigidbody))
|
||||
{
|
||||
_rigidbodyCollisionMap.Add(rigidbody, new HashSet<TInteractable>());
|
||||
}
|
||||
|
||||
HashSet<TInteractable> interactables = _rigidbodyCollisionMap[rigidbody];
|
||||
interactables.Add(typedInteractable);
|
||||
}
|
||||
|
||||
private void UnmarkCollision(IInteractable interactable, Rigidbody rigidbody)
|
||||
{
|
||||
TInteractable typedInteractable = interactable as TInteractable;
|
||||
HashSet<TInteractable> interactables = _rigidbodyCollisionMap[rigidbody];
|
||||
interactables.Remove(typedInteractable);
|
||||
|
||||
if (interactables.Count == 0)
|
||||
{
|
||||
_rigidbodyCollisionMap.Remove(rigidbody);
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<TInteractable> List(TInteractor interactor)
|
||||
{
|
||||
HashSet<TInteractable> colliding;
|
||||
if (_rigidbodyCollisionMap.TryGetValue(interactor.Rigidbody, out colliding))
|
||||
{
|
||||
return PruneInteractables(colliding, interactor);
|
||||
}
|
||||
return _empty;
|
||||
}
|
||||
|
||||
private static readonly List<TInteractable> _empty = new List<TInteractable>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb2a1991dcdc8df40849843bb47ca6e4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,54 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using Oculus.Interaction.Input;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public class ControllerActiveState : MonoBehaviour, IActiveState
|
||||
{
|
||||
[SerializeField, Interface(typeof(IController))]
|
||||
MonoBehaviour _controller;
|
||||
|
||||
private IController Controller;
|
||||
|
||||
public bool Active => Controller.IsConnected;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
Controller = _controller as IController;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
Assert.IsNotNull(Controller);
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllControllerActiveState(IController controller)
|
||||
{
|
||||
InjectController(controller);
|
||||
}
|
||||
|
||||
public void InjectController(IController controller)
|
||||
{
|
||||
_controller = controller as MonoBehaviour;
|
||||
Controller = controller;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e2cb428b733e56478338c736189a0d5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,63 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public class GameObjectActiveState : MonoBehaviour, IActiveState
|
||||
{
|
||||
[SerializeField]
|
||||
private GameObject _sourceGameObject;
|
||||
|
||||
[SerializeField]
|
||||
private bool _sourceActiveSelf;
|
||||
|
||||
public bool SourceActiveSelf
|
||||
{
|
||||
get
|
||||
{
|
||||
return _sourceActiveSelf;
|
||||
}
|
||||
set
|
||||
{
|
||||
_sourceActiveSelf = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
Assert.IsNotNull(_sourceGameObject);
|
||||
}
|
||||
|
||||
public bool Active => _sourceActiveSelf
|
||||
? _sourceGameObject.activeSelf
|
||||
: _sourceGameObject.activeInHierarchy;
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllGameObjectActiveState(GameObject sourceGameObject)
|
||||
{
|
||||
InjectSourceGameObject(sourceGameObject);
|
||||
}
|
||||
|
||||
public void InjectSourceGameObject(GameObject sourceGameObject)
|
||||
{
|
||||
_sourceGameObject = sourceGameObject;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9f83bf7e62dcb742ae460b2520f245e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using Oculus.Interaction.Input;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public class HandActiveState : MonoBehaviour, IActiveState
|
||||
{
|
||||
[SerializeField, Interface(typeof(IHand))]
|
||||
MonoBehaviour _hand;
|
||||
private IHand Hand;
|
||||
|
||||
public bool Active => Hand.IsConnected;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
Hand = _hand as IHand;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
Assert.IsNotNull(Hand);
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllHandActiveState(IHand hand)
|
||||
{
|
||||
InjectHand(hand);
|
||||
}
|
||||
|
||||
public void InjectHand(IHand hand)
|
||||
{
|
||||
_hand = hand as MonoBehaviour;
|
||||
Hand = hand;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bebfb74b97b000d4899abbd61f49aa40
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public interface IActiveState
|
||||
{
|
||||
bool Active { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1aebdea39235ca249ab63bdcacd9b45b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,91 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public struct InteractableStateChangeArgs
|
||||
{
|
||||
public InteractableState PreviousState;
|
||||
public InteractableState NewState;
|
||||
}
|
||||
|
||||
public interface IComponent
|
||||
{
|
||||
GameObject gameObject { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An IInteractableView defines the view for an object that can be
|
||||
/// interacted with.
|
||||
/// </summary>
|
||||
public interface IInteractableView : IComponent
|
||||
{
|
||||
InteractableState State { get; }
|
||||
event Action<InteractableStateChangeArgs> WhenStateChanged;
|
||||
|
||||
int MaxInteractors { get; }
|
||||
int MaxSelectingInteractors { get; }
|
||||
|
||||
int InteractorsCount { get; }
|
||||
int SelectingInteractorsCount { get; }
|
||||
|
||||
event Action WhenInteractorsCountUpdated;
|
||||
event Action WhenSelectingInteractorsCountUpdated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An object that can be interacted with, an IInteractable can, in addition to
|
||||
/// an IInteractableView, be enabled or disabled.
|
||||
/// </summary>
|
||||
public interface IInteractable : IInteractableView
|
||||
{
|
||||
void Enable();
|
||||
void Disable();
|
||||
void RemoveInteractorById(int id);
|
||||
new int MaxInteractors { get; set; }
|
||||
new int MaxSelectingInteractors { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An IInteractableView{out TInteractor} defines additional members for IInteractableView
|
||||
/// that expose the concrete types interacting with this object.
|
||||
/// </summary>
|
||||
public interface IInteractableView<out TInteractor> : IInteractableView
|
||||
{
|
||||
IEnumerable<TInteractor> Interactors { get; }
|
||||
IEnumerable<TInteractor> SelectingInteractors { get; }
|
||||
MAction<TInteractor> WhenInteractorAdded { get; }
|
||||
MAction<TInteractor> WhenInteractorRemoved { get; }
|
||||
MAction<TInteractor> WhenSelectingInteractorAdded { get; }
|
||||
MAction<TInteractor> WhenSelectingInteractorRemoved { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An Interactable{TInteractor} can have its set of Concrete Interactors
|
||||
/// modified by an external party.
|
||||
/// </summary>
|
||||
public interface IInteractable<TInteractor> : IInteractable, IInteractableView<TInteractor>
|
||||
{
|
||||
bool IsPotentialCandidateFor(TInteractor interactor);
|
||||
bool HasInteractor(TInteractor interactor);
|
||||
void AddInteractor(TInteractor interactor);
|
||||
void RemoveInteractor(TInteractor interactor);
|
||||
|
||||
bool HasSelectingInteractor(TInteractor interactor);
|
||||
void AddSelectingInteractor(TInteractor interactor);
|
||||
void RemoveSelectingInteractor(TInteractor interactor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4196df5f1c041fa4380575befde652ea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public interface IInteractableFilter
|
||||
{
|
||||
bool FilterInteractable(IInteractable interactable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 943f4030051d4ae4eb8e1d146817e1ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// A interface for a registry that houses a set of concrete Interactables.
|
||||
/// </summary>
|
||||
public interface IInteractableRegistry<TInteractor, TInteractable>
|
||||
where TInteractable : IInteractable<TInteractor>
|
||||
{
|
||||
void Register(TInteractable interactable);
|
||||
void Unregister(TInteractable interactable);
|
||||
IEnumerable<TInteractable> List();
|
||||
IEnumerable<TInteractable> List(TInteractor interactor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 55c241c4bfa9de14999ef5e8fdd59dd4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,79 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public struct InteractorStateChangeArgs
|
||||
{
|
||||
public InteractorState PreviousState;
|
||||
public InteractorState NewState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IInteractorView defines the view for an object that can interact with other objects.
|
||||
/// </summary>
|
||||
public interface IInteractorView
|
||||
{
|
||||
int Identifier { get; }
|
||||
bool HasInteractable { get; }
|
||||
bool HasSelectedInteractable { get; }
|
||||
|
||||
InteractorState State { get; }
|
||||
event Action<InteractorStateChangeArgs> WhenStateChanged;
|
||||
event Action WhenInteractorUpdated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IInteractor defines an object that can interact with other objects
|
||||
/// and can handle selection events to change its state.
|
||||
/// </summary>
|
||||
public interface IInteractor : IInteractorView
|
||||
{
|
||||
void Enable();
|
||||
void Disable();
|
||||
|
||||
void UpdateInteractor();
|
||||
void Hover();
|
||||
void Select();
|
||||
void Unselect();
|
||||
|
||||
bool HasCandidate { get; }
|
||||
bool ShouldSelect { get; }
|
||||
bool ShouldUnselect { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IInteractorView{out TInteractable} defines an InteractorView with concretely typed
|
||||
/// Interactable members.
|
||||
/// </summary>
|
||||
public interface IInteractorView<out TInteractable> : IInteractorView
|
||||
{
|
||||
MAction<TInteractable> WhenInteractableSet { get; }
|
||||
MAction<TInteractable> WhenInteractableUnset { get; }
|
||||
MAction<TInteractable> WhenInteractableSelected { get; }
|
||||
MAction<TInteractable> WhenInteractableUnselected { get; }
|
||||
TInteractable Candidate { get; }
|
||||
TInteractable Interactable { get; }
|
||||
TInteractable SelectedInteractable { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IInteractor{out TInteractable} defines an IInteractor with concretely typed
|
||||
/// Interactable members.
|
||||
/// </summary>
|
||||
public interface IInteractor<TInteractable> : IInteractor, IInteractorView<TInteractable>
|
||||
{
|
||||
bool IsFilterPassedBy(TInteractable interactable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d49df07314eee54ba3a1aea25bd337f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// IInteractionDriver drives the update loop of an Interaction.
|
||||
/// </summary>
|
||||
public interface IInteractorDriver
|
||||
{
|
||||
// This flag determines whether this driver should controls its own update loop
|
||||
bool IsRootInteractorDriver { get; set; }
|
||||
|
||||
bool HasCandidate { get; }
|
||||
bool ShouldSelect { get; }
|
||||
|
||||
bool IsHovering { get; }
|
||||
bool IsSelecting { get; }
|
||||
bool IsSelectingInteractable { get; }
|
||||
|
||||
void UpdateInteraction();
|
||||
void UpdateHover();
|
||||
void UpdateSelection(bool selectionCanBeEmpty);
|
||||
void Enable();
|
||||
void Disable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 261537a780dd3e0439affc81c097db0f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public interface IRigidbodyRef
|
||||
{
|
||||
Rigidbody Rigidbody { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 901de7ebb9bd1624bbfb64a09629a730
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,26 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// ISelector defines an input abstraction that can broadcast
|
||||
/// select and release events
|
||||
/// </summary>
|
||||
public interface ISelector
|
||||
{
|
||||
event Action WhenSelected;
|
||||
event Action WhenUnselected;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 943b088ec5d454f408757c12bc50bba9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,314 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Interactable provides a base template for any kind of interactable object.
|
||||
/// An Interactable can have Hover and HandleSelected Interactor(s) acting on it.
|
||||
/// Concrete Interactables can define whether they have a One-to-One or
|
||||
/// One-to-Many relationship with their associated concrete Interactors.
|
||||
/// </summary>
|
||||
public abstract class Interactable<TInteractor, TInteractable> : MonoBehaviour, IInteractable<TInteractor>
|
||||
where TInteractor : class, IInteractor<TInteractable>
|
||||
where TInteractable : Interactable<TInteractor, TInteractable>
|
||||
{
|
||||
/// <summary>
|
||||
/// The max interactors and max selecting Interactors that this Interactable can
|
||||
/// have acting on it.
|
||||
/// -1 signifies NO limit (can have infinite interactors)
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
private int _maxInteractors = -1;
|
||||
|
||||
[SerializeField]
|
||||
private int _maxSelectingInteractors = -1;
|
||||
|
||||
public int MaxInteractors
|
||||
{
|
||||
get
|
||||
{
|
||||
return _maxInteractors;
|
||||
}
|
||||
set
|
||||
{
|
||||
_maxInteractors = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int MaxSelectingInteractors
|
||||
{
|
||||
get
|
||||
{
|
||||
return _maxSelectingInteractors;
|
||||
}
|
||||
set
|
||||
{
|
||||
_maxSelectingInteractors = value;
|
||||
}
|
||||
}
|
||||
|
||||
private HashSet<TInteractor> _interactors = new HashSet<TInteractor>();
|
||||
private HashSet<TInteractor> _selectingInteractors = new HashSet<TInteractor>();
|
||||
|
||||
public event Action WhenInteractorsCountUpdated = delegate { };
|
||||
public event Action WhenSelectingInteractorsCountUpdated = delegate { };
|
||||
|
||||
private InteractableState _state = InteractableState.Disabled;
|
||||
public event Action<InteractableStateChangeArgs> WhenStateChanged = delegate { };
|
||||
|
||||
private MultiAction<TInteractor> _whenInteractorAdded = new MultiAction<TInteractor>();
|
||||
private MultiAction<TInteractor> _whenInteractorRemoved = new MultiAction<TInteractor>();
|
||||
private MultiAction<TInteractor> _whenSelectingInteractorAdded = new MultiAction<TInteractor>();
|
||||
private MultiAction<TInteractor> _whenSelectingInteractorRemoved = new MultiAction<TInteractor>();
|
||||
public MAction<TInteractor> WhenInteractorAdded => _whenInteractorAdded;
|
||||
public MAction<TInteractor> WhenInteractorRemoved => _whenInteractorRemoved;
|
||||
public MAction<TInteractor> WhenSelectingInteractorAdded => _whenSelectingInteractorAdded;
|
||||
public MAction<TInteractor> WhenSelectingInteractorRemoved => _whenSelectingInteractorRemoved;
|
||||
|
||||
public InteractableState State
|
||||
{
|
||||
get
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
private set
|
||||
{
|
||||
if (_state == value) return;
|
||||
InteractableState previousState = _state;
|
||||
_state = value;
|
||||
WhenStateChanged(new InteractableStateChangeArgs
|
||||
{
|
||||
PreviousState = previousState,
|
||||
NewState = _state
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static IInteractableRegistry<TInteractor, TInteractable> _registry =
|
||||
new InteractableRegistry<TInteractor, TInteractable>();
|
||||
|
||||
public static IInteractableRegistry<TInteractor, TInteractable> Registry => _registry;
|
||||
|
||||
protected virtual void InteractorAdded(TInteractor interactor)
|
||||
{
|
||||
_whenInteractorAdded.Invoke(interactor);
|
||||
}
|
||||
protected virtual void InteractorRemoved(TInteractor interactor)
|
||||
{
|
||||
_whenInteractorRemoved.Invoke(interactor);
|
||||
}
|
||||
|
||||
protected virtual void SelectingInteractorAdded(TInteractor interactor)
|
||||
{
|
||||
_whenSelectingInteractorAdded.Invoke(interactor);
|
||||
}
|
||||
protected virtual void SelectingInteractorRemoved(TInteractor interactor)
|
||||
{
|
||||
_whenSelectingInteractorRemoved.Invoke(interactor);
|
||||
}
|
||||
|
||||
public int InteractorsCount => _interactors.Count;
|
||||
|
||||
public int SelectingInteractorsCount => _selectingInteractors.Count;
|
||||
|
||||
public IEnumerable<TInteractor> Interactors => _interactors;
|
||||
|
||||
public IEnumerable<TInteractor> SelectingInteractors => _selectingInteractors;
|
||||
|
||||
public void AddInteractor(TInteractor interactor)
|
||||
{
|
||||
_interactors.Add(interactor);
|
||||
WhenInteractorsCountUpdated();
|
||||
InteractorAdded(interactor);
|
||||
UpdateInteractableState();
|
||||
}
|
||||
|
||||
public void RemoveInteractor(TInteractor interactor)
|
||||
{
|
||||
if (!_interactors.Remove(interactor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
WhenInteractorsCountUpdated();
|
||||
InteractorRemoved(interactor);
|
||||
UpdateInteractableState();
|
||||
}
|
||||
|
||||
public void AddSelectingInteractor(TInteractor interactor)
|
||||
{
|
||||
_selectingInteractors.Add(interactor);
|
||||
WhenSelectingInteractorsCountUpdated();
|
||||
SelectingInteractorAdded(interactor);
|
||||
UpdateInteractableState();
|
||||
}
|
||||
|
||||
public void RemoveSelectingInteractor(TInteractor interactor)
|
||||
{
|
||||
if (!_selectingInteractors.Remove(interactor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
WhenSelectingInteractorsCountUpdated();
|
||||
SelectingInteractorRemoved(interactor);
|
||||
UpdateInteractableState();
|
||||
}
|
||||
|
||||
private void UpdateInteractableState()
|
||||
{
|
||||
if (State == InteractableState.Disabled) return;
|
||||
if (SelectingInteractorsCount > 0)
|
||||
{
|
||||
State = InteractableState.Select;
|
||||
}
|
||||
else if (InteractorsCount > 0)
|
||||
{
|
||||
State = InteractableState.Hover;
|
||||
}
|
||||
else
|
||||
{
|
||||
State = InteractableState.Normal;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPotentialCandidateFor(TInteractor interactor)
|
||||
{
|
||||
if (State == InteractableState.Disabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (MaxSelectingInteractors >= 0 &&
|
||||
SelectingInteractorsCount == MaxSelectingInteractors)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (MaxInteractors >= 0 &&
|
||||
InteractorsCount == MaxInteractors &&
|
||||
!_interactors.Contains(interactor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool HasInteractor(TInteractor interactor)
|
||||
{
|
||||
return _interactors.Contains(interactor);
|
||||
}
|
||||
|
||||
public bool HasSelectingInteractor(TInteractor interactor)
|
||||
{
|
||||
return _selectingInteractors.Contains(interactor);
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
if (State != InteractableState.Disabled) return;
|
||||
_registry.Register((TInteractable)this);
|
||||
State = InteractableState.Normal;
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
if (State == InteractableState.Disabled) return;
|
||||
|
||||
List<TInteractor> selectingInteractorsCopy = new List<TInteractor>(_selectingInteractors);
|
||||
foreach (TInteractor selectingInteractor in selectingInteractorsCopy)
|
||||
{
|
||||
RemoveSelectingInteractor(selectingInteractor);
|
||||
}
|
||||
|
||||
List<TInteractor> interactorsCopy = new List<TInteractor>(_interactors);
|
||||
foreach (TInteractor interactor in interactorsCopy)
|
||||
{
|
||||
RemoveInteractor(interactor);
|
||||
}
|
||||
|
||||
State = InteractableState.Disabled;
|
||||
_registry.Unregister((TInteractable)this);
|
||||
}
|
||||
|
||||
public void RemoveInteractorById(int id)
|
||||
{
|
||||
if (State != InteractableState.Select)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TInteractor foundInteractor = null;
|
||||
foreach (TInteractor selectingInteractor in _selectingInteractors)
|
||||
{
|
||||
if (selectingInteractor.Identifier == id)
|
||||
{
|
||||
foundInteractor = selectingInteractor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundInteractor == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveSelectingInteractor(foundInteractor);
|
||||
|
||||
foundInteractor = null;
|
||||
foreach (TInteractor interactor in _interactors)
|
||||
{
|
||||
if (interactor.Identifier == id)
|
||||
{
|
||||
foundInteractor = interactor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundInteractor == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveInteractor(foundInteractor);
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
Enable();
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
Disable();
|
||||
}
|
||||
|
||||
protected virtual void SetRegistry(IInteractableRegistry<TInteractor, TInteractable> registry)
|
||||
{
|
||||
if (registry == _registry) return;
|
||||
|
||||
IEnumerable<TInteractable> interactables = _registry.List();
|
||||
foreach (TInteractable interactable in interactables)
|
||||
{
|
||||
registry.Register(interactable);
|
||||
_registry.Unregister(interactable);
|
||||
}
|
||||
_registry = registry;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d7d49e36357b824db786507810ed922
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,170 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// This class implements higher level logic to share the number of max
|
||||
/// max interactors acting on this group of interactors at a time.
|
||||
/// </summary>
|
||||
public class InteractableGroup : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Interface(typeof(IInteractable))]
|
||||
private List<MonoBehaviour> _interactables;
|
||||
private List<IInteractable> Interactables;
|
||||
private List<InteractableLimits> _limits;
|
||||
|
||||
private struct InteractableLimits
|
||||
{
|
||||
public int MaxInteractors;
|
||||
public int MaxSelectingInteractors;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private int _maxInteractors;
|
||||
|
||||
[SerializeField]
|
||||
private int _maxSelectingInteractors;
|
||||
|
||||
private int _interactors;
|
||||
private int _selectInteractors;
|
||||
|
||||
protected bool _started = false;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
Interactables = _interactables.ConvertAll(mono => mono as IInteractable);
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
this.BeginStart(ref _started);
|
||||
foreach (IInteractable interactable in Interactables)
|
||||
{
|
||||
Assert.IsNotNull(interactable);
|
||||
}
|
||||
|
||||
Assert.IsTrue(_interactables != null && _interactables.Count > 0);
|
||||
|
||||
_limits = new List<InteractableLimits>();
|
||||
foreach (IInteractable interactable in Interactables)
|
||||
{
|
||||
_limits.Add(new InteractableLimits()
|
||||
{
|
||||
MaxInteractors = interactable.MaxInteractors,
|
||||
MaxSelectingInteractors = interactable.MaxSelectingInteractors
|
||||
});
|
||||
}
|
||||
this.EndStart(ref _started);
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
foreach (IInteractable interactable in Interactables)
|
||||
{
|
||||
interactable.WhenInteractorsCountUpdated += CountWhenInteractors;
|
||||
interactable.WhenSelectingInteractorsCountUpdated += CountWhenSelectingInteractors;
|
||||
}
|
||||
|
||||
CountWhenInteractors();
|
||||
CountWhenSelectingInteractors();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
foreach (IInteractable interactable in Interactables)
|
||||
{
|
||||
interactable.WhenInteractorsCountUpdated -= CountWhenInteractors;
|
||||
interactable.WhenSelectingInteractorsCountUpdated -= CountWhenSelectingInteractors;
|
||||
}
|
||||
|
||||
CountWhenInteractors();
|
||||
CountWhenSelectingInteractors();
|
||||
}
|
||||
}
|
||||
|
||||
private void CountWhenInteractors()
|
||||
{
|
||||
_interactors = 0;
|
||||
foreach (IInteractable interactable in Interactables)
|
||||
{
|
||||
_interactors += interactable.InteractorsCount;
|
||||
}
|
||||
|
||||
UpdateMaxInteractors();
|
||||
}
|
||||
|
||||
private void UpdateMaxInteractors()
|
||||
{
|
||||
if (_maxInteractors == -1) return;
|
||||
int remainingInteractors = Mathf.Max(0, _maxInteractors - _interactors);
|
||||
for (int i = 0; i < Interactables.Count; i++)
|
||||
{
|
||||
Interactables[i].MaxInteractors = (_limits[i].MaxInteractors == -1
|
||||
? remainingInteractors
|
||||
: Mathf.Max(0, _limits[i].MaxInteractors - _interactors)) +
|
||||
Interactables[i].InteractorsCount;
|
||||
}
|
||||
}
|
||||
|
||||
private void CountWhenSelectingInteractors()
|
||||
{
|
||||
_selectInteractors = 0;
|
||||
foreach (IInteractable interactable in Interactables)
|
||||
{
|
||||
_selectInteractors += interactable.SelectingInteractorsCount;
|
||||
}
|
||||
|
||||
UpdateMaxActive();
|
||||
}
|
||||
|
||||
private void UpdateMaxActive()
|
||||
{
|
||||
if (_maxSelectingInteractors == -1) return;
|
||||
int remainingActive = Mathf.Max(0, _maxSelectingInteractors - _selectInteractors);
|
||||
for (int i = 0; i < Interactables.Count; i++)
|
||||
{
|
||||
Interactables[i].MaxSelectingInteractors = (_limits[i].MaxSelectingInteractors == -1
|
||||
? remainingActive
|
||||
: Mathf.Max(0, _limits[i].MaxSelectingInteractors - _selectInteractors)) +
|
||||
Interactables[i].SelectingInteractorsCount;
|
||||
}
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllInteractableGroup(List<IInteractable> interactables)
|
||||
{
|
||||
InjectInteractables(interactables);
|
||||
}
|
||||
|
||||
public void InjectInteractables(List<IInteractable> interactables)
|
||||
{
|
||||
Interactables = interactables;
|
||||
_interactables =
|
||||
interactables.ConvertAll(interactable => interactable as MonoBehaviour);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8514192a85a2a7e4a8ddac63837c54dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,183 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using JetBrains.Annotations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// This class implements higher level logic to forward the highest IInteractable
|
||||
/// state of any of the interactables in its list
|
||||
/// </summary>
|
||||
public class InteractableGroupView : MonoBehaviour, IInteractableView
|
||||
{
|
||||
[SerializeField, Interface(typeof(IInteractable))]
|
||||
private List<MonoBehaviour> _interactables;
|
||||
private List<IInteractable> Interactables;
|
||||
|
||||
public int InteractorsCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = 0;
|
||||
foreach (IInteractable interactable in Interactables)
|
||||
{
|
||||
count += interactable.InteractorsCount;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public int SelectingInteractorsCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = 0;
|
||||
foreach (IInteractable interactable in Interactables)
|
||||
{
|
||||
count += interactable.SelectingInteractorsCount;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public int MaxInteractors
|
||||
{
|
||||
get
|
||||
{
|
||||
int max = 0;
|
||||
foreach (IInteractable interactable in Interactables)
|
||||
{
|
||||
max = Mathf.Max(interactable.MaxInteractors, max);
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
}
|
||||
|
||||
public int MaxSelectingInteractors
|
||||
{
|
||||
get
|
||||
{
|
||||
int max = 0;
|
||||
foreach (IInteractable interactable in Interactables)
|
||||
{
|
||||
max = Mathf.Max(interactable.MaxSelectingInteractors, max);
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
}
|
||||
|
||||
public event Action WhenInteractorsCountUpdated = delegate { };
|
||||
public event Action WhenSelectingInteractorsCountUpdated = delegate { };
|
||||
|
||||
public event Action<InteractableStateChangeArgs> WhenStateChanged = delegate { };
|
||||
|
||||
private InteractableState _state = InteractableState.Normal;
|
||||
public InteractableState State
|
||||
{
|
||||
get
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_state == value) return;
|
||||
InteractableState previousState = _state;
|
||||
_state = value;
|
||||
WhenStateChanged(new InteractableStateChangeArgs { PreviousState = previousState, NewState = _state });
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateState()
|
||||
{
|
||||
if (SelectingInteractorsCount > 0)
|
||||
{
|
||||
State = InteractableState.Select;
|
||||
return;
|
||||
}
|
||||
if (InteractorsCount > 0)
|
||||
{
|
||||
State = InteractableState.Hover;
|
||||
return;
|
||||
}
|
||||
State = InteractableState.Normal;
|
||||
}
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
Interactables = _interactables.ConvertAll(mono => mono as IInteractable);
|
||||
}
|
||||
|
||||
protected bool _started = false;
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
this.BeginStart(ref _started);
|
||||
foreach (IInteractable interactable in Interactables)
|
||||
{
|
||||
Assert.IsNotNull(interactable);
|
||||
}
|
||||
this.EndStart(ref _started);
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
foreach (IInteractable interactable in Interactables)
|
||||
{
|
||||
interactable.WhenStateChanged += HandleStateChange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
foreach (IInteractable interactable in Interactables)
|
||||
{
|
||||
interactable.WhenStateChanged -= HandleStateChange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleStateChange(InteractableStateChangeArgs args)
|
||||
{
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllInteractableGroupView(List<IInteractable> interactables)
|
||||
{
|
||||
InjectInteractables(interactables);
|
||||
}
|
||||
|
||||
public void InjectInteractables(List<IInteractable> interactables)
|
||||
{
|
||||
Interactables = interactables;
|
||||
_interactables =
|
||||
Interactables.ConvertAll(interactable => interactable as MonoBehaviour);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bff16bb2b8cae9409e88ec4d3527c9b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,91 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// A registry that houses a set of concrete Interactables.
|
||||
/// </summary>
|
||||
public class InteractableRegistry<TInteractor, TInteractable> :
|
||||
IInteractableRegistry<TInteractor, TInteractable>
|
||||
where TInteractor:IInteractor<TInteractable>
|
||||
where TInteractable : IInteractable<TInteractor>
|
||||
{
|
||||
private static List<TInteractable> _interactables;
|
||||
private List<TInteractable> _interactableEnumeratorList;
|
||||
|
||||
public InteractableRegistry()
|
||||
{
|
||||
_interactables = new List<TInteractable>();
|
||||
_interactableEnumeratorList = new List<TInteractable>();
|
||||
}
|
||||
|
||||
public virtual void Register(TInteractable interactable) => _interactables.Add(interactable);
|
||||
public virtual void Unregister(TInteractable interactable) => _interactables.Remove(interactable);
|
||||
|
||||
protected IEnumerable<TInteractable> PruneInteractables(IEnumerable<TInteractable> interactables,
|
||||
TInteractor interactor)
|
||||
{
|
||||
int interactableCount = 0;
|
||||
foreach (TInteractable interactable in interactables)
|
||||
{
|
||||
if (!interactor.IsFilterPassedBy(interactable))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!interactable.IsPotentialCandidateFor(interactor))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (interactableCount == _interactableEnumeratorList.Count)
|
||||
{
|
||||
_interactableEnumeratorList.Add(interactable);
|
||||
}
|
||||
else
|
||||
{
|
||||
_interactableEnumeratorList[interactableCount] = interactable;
|
||||
}
|
||||
interactableCount++;
|
||||
}
|
||||
|
||||
return GetRange(_interactableEnumeratorList, 0, interactableCount);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<TInteractable> List(TInteractor interactor)
|
||||
{
|
||||
return PruneInteractables(_interactables, interactor);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<TInteractable> List()
|
||||
{
|
||||
return _interactables;
|
||||
}
|
||||
|
||||
private IEnumerable<T> GetRange<T>(IEnumerable<T> source, int start, int end)
|
||||
{
|
||||
using (IEnumerator<T> e = source.GetEnumerator())
|
||||
{
|
||||
int i = 0;
|
||||
while (i < start && e.MoveNext()) { i++; }
|
||||
while (i < end && e.MoveNext())
|
||||
{
|
||||
yield return e.Current;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f443729221394248953476eb8039ddb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public enum InteractableState
|
||||
{
|
||||
Normal,
|
||||
Hover,
|
||||
Select,
|
||||
Disabled
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3ce0573c277c4440a698a8063a6150c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,128 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Acts as a forwarder of Trigger events for Rigidbody overlaps. Used in conjunction with
|
||||
/// CollisionInteractionRegistry.
|
||||
///
|
||||
/// Note: If Physics.autoSimulation is false, ForceGlobalUpdateTrigger should be called
|
||||
/// after every call to Physics.Simulate
|
||||
/// </summary>
|
||||
public class InteractableTriggerBroadcaster : MonoBehaviour
|
||||
{
|
||||
public Action<IInteractable, Rigidbody> OnTriggerEntered = delegate { };
|
||||
public Action<IInteractable, Rigidbody> OnTriggerExited = delegate { };
|
||||
|
||||
private IInteractable _interactable;
|
||||
private Dictionary<Rigidbody, bool> _rigidbodyTriggers;
|
||||
|
||||
private static HashSet<InteractableTriggerBroadcaster> _broadcasters =
|
||||
new HashSet<InteractableTriggerBroadcaster>();
|
||||
|
||||
protected bool _started = false;
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
this.BeginStart(ref _started);
|
||||
_rigidbodyTriggers = new Dictionary<Rigidbody, bool>();
|
||||
this.EndStart(ref _started);
|
||||
}
|
||||
|
||||
protected virtual void OnTriggerStay(Collider collider)
|
||||
{
|
||||
Rigidbody rigidbody = collider.attachedRigidbody;
|
||||
if (rigidbody == null) return;
|
||||
if (!_rigidbodyTriggers.ContainsKey(rigidbody))
|
||||
{
|
||||
OnTriggerEntered(_interactable, rigidbody);
|
||||
_rigidbodyTriggers.Add(rigidbody, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_rigidbodyTriggers[rigidbody] = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
_broadcasters.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void FixedUpdate()
|
||||
{
|
||||
if (!Physics.autoSimulation)
|
||||
{
|
||||
return;
|
||||
}
|
||||
UpdateTriggers();
|
||||
}
|
||||
|
||||
private void UpdateTriggers()
|
||||
{
|
||||
List<Rigidbody> rigidbodys = new List<Rigidbody>(_rigidbodyTriggers.Keys);
|
||||
foreach (Rigidbody rigidbody in rigidbodys)
|
||||
{
|
||||
if (_rigidbodyTriggers[rigidbody] == false)
|
||||
{
|
||||
_rigidbodyTriggers.Remove(rigidbody);
|
||||
OnTriggerExited(_interactable, rigidbody);
|
||||
}
|
||||
else
|
||||
{
|
||||
_rigidbodyTriggers[rigidbody] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
// Clean up any remaining active triggers
|
||||
foreach (Rigidbody rigidbody in _rigidbodyTriggers.Keys)
|
||||
{
|
||||
OnTriggerExited(_interactable, rigidbody);
|
||||
}
|
||||
_broadcasters.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ForceGlobalUpdateTriggers()
|
||||
{
|
||||
foreach (InteractableTriggerBroadcaster broadcaster in _broadcasters)
|
||||
{
|
||||
broadcaster.UpdateTriggers();
|
||||
}
|
||||
}
|
||||
|
||||
#region Inject
|
||||
public void InjectAllInteractableTriggerBroadcaster(IInteractable interactable)
|
||||
{
|
||||
InjectInteractable(interactable);
|
||||
}
|
||||
|
||||
public void InjectInteractable(IInteractable interactable)
|
||||
{
|
||||
_interactable = interactable;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26cb0a0cb13965347b765a3a9356dd69
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,361 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Interactor provides a base template for any kind of interaction.
|
||||
/// Interactions can be wholly defined by three things: the concrete Interactor,
|
||||
/// the concrete Interactable, and the logic governing their coordination.
|
||||
///
|
||||
/// Subclasses are responsible for implementing that coordination logic via template
|
||||
/// methods that operate on the concrete interactor and interactable classes.
|
||||
/// </summary>
|
||||
public abstract class Interactor<TInteractor, TInteractable> : MonoBehaviour, IInteractor<TInteractable>
|
||||
where TInteractor : class, IInteractor<TInteractable>
|
||||
where TInteractable : class, IInteractable<TInteractor>
|
||||
{
|
||||
[SerializeField, Interface(typeof(IInteractableFilter)), Optional]
|
||||
private List<MonoBehaviour> _interactableFilters;
|
||||
private List<IInteractableFilter> InteractableFilters = null;
|
||||
|
||||
protected virtual void DoEveryUpdate() { }
|
||||
protected virtual void DoNormalUpdate() { }
|
||||
protected virtual void DoHoverUpdate() { }
|
||||
protected virtual void DoSelectUpdate(TInteractable interactable = null) { }
|
||||
|
||||
public virtual bool ShouldSelect { get; protected set; }
|
||||
public virtual bool ShouldUnselect { get; protected set; }
|
||||
|
||||
private InteractorState _state = InteractorState.Normal;
|
||||
public event Action<InteractorStateChangeArgs> WhenStateChanged = delegate { };
|
||||
public event Action WhenInteractorUpdated = delegate { };
|
||||
|
||||
public InteractorState State
|
||||
{
|
||||
get
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
private set
|
||||
{
|
||||
if (_state == value) return;
|
||||
InteractorState previousState = _state;
|
||||
_state = value;
|
||||
|
||||
WhenStateChanged(new InteractorStateChangeArgs
|
||||
{
|
||||
PreviousState = previousState,
|
||||
NewState = _state
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected TInteractable _candidate;
|
||||
protected TInteractable _interactable;
|
||||
protected TInteractable _selectedInteractable;
|
||||
|
||||
public TInteractable Candidate => _candidate;
|
||||
public TInteractable Interactable => _interactable;
|
||||
public TInteractable SelectedInteractable => _selectedInteractable;
|
||||
|
||||
public bool HasCandidate => _candidate != null;
|
||||
public bool HasInteractable => _interactable != null;
|
||||
public bool HasSelectedInteractable => _selectedInteractable != null;
|
||||
|
||||
private MultiAction<TInteractable> _whenInteractableSet = new MultiAction<TInteractable>();
|
||||
private MultiAction<TInteractable> _whenInteractableUnset = new MultiAction<TInteractable>();
|
||||
private MultiAction<TInteractable> _whenInteractableSelected = new MultiAction<TInteractable>();
|
||||
private MultiAction<TInteractable> _whenInteractableUnselected = new MultiAction<TInteractable>();
|
||||
public MAction<TInteractable> WhenInteractableSet => _whenInteractableSet;
|
||||
public MAction<TInteractable> WhenInteractableUnset => _whenInteractableUnset;
|
||||
public MAction<TInteractable> WhenInteractableSelected => _whenInteractableSelected;
|
||||
public MAction<TInteractable> WhenInteractableUnselected => _whenInteractableUnselected;
|
||||
|
||||
protected virtual void InteractableSet(TInteractable interactable)
|
||||
{
|
||||
_whenInteractableSet.Invoke(interactable);
|
||||
}
|
||||
|
||||
protected virtual void InteractableUnset(TInteractable interactable)
|
||||
{
|
||||
_whenInteractableUnset.Invoke(interactable);
|
||||
}
|
||||
|
||||
protected virtual void InteractableSelected(TInteractable interactable)
|
||||
{
|
||||
_whenInteractableSelected.Invoke(interactable);
|
||||
}
|
||||
|
||||
protected virtual void InteractableUnselected(TInteractable interactable)
|
||||
{
|
||||
_whenInteractableUnselected.Invoke(interactable);
|
||||
}
|
||||
|
||||
private UniqueIdentifier _identifier;
|
||||
public int Identifier => _identifier.ID;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
_identifier = UniqueIdentifier.Generate();
|
||||
InteractableFilters =
|
||||
_interactableFilters.ConvertAll(mono => mono as IInteractableFilter);
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
foreach (IInteractableFilter filter in InteractableFilters)
|
||||
{
|
||||
Assert.IsNotNull(filter);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
Enable();
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
Disable();
|
||||
}
|
||||
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
UniqueIdentifier.Release(_identifier);
|
||||
}
|
||||
|
||||
private void CandidateUpdate()
|
||||
{
|
||||
if (State == InteractorState.Select) return;
|
||||
if (State == InteractorState.Disabled)
|
||||
{
|
||||
UnsetInteractable();
|
||||
return;
|
||||
}
|
||||
|
||||
_candidate = ComputeCandidate();
|
||||
}
|
||||
|
||||
public void UpdateInteractor()
|
||||
{
|
||||
if (State == InteractorState.Disabled) return;
|
||||
InteractableChangesUpdate();
|
||||
DoEveryUpdate();
|
||||
NormalUpdate();
|
||||
CandidateUpdate();
|
||||
HoverUpdate();
|
||||
SelectUpdate();
|
||||
WhenInteractorUpdated();
|
||||
}
|
||||
|
||||
private void InteractableChangesUpdate()
|
||||
{
|
||||
if (State == InteractorState.Select)
|
||||
{
|
||||
if (_selectedInteractable != null &&
|
||||
!_selectedInteractable.HasSelectingInteractor(this as TInteractor))
|
||||
{
|
||||
TInteractable interactable = _selectedInteractable;
|
||||
_selectedInteractable = null;
|
||||
InteractableUnselected(interactable);
|
||||
}
|
||||
|
||||
if (_interactable != null &&
|
||||
!_interactable.HasInteractor(this as TInteractor))
|
||||
{
|
||||
TInteractable interactable = _interactable;
|
||||
_interactable = null;
|
||||
InteractableUnset(interactable);
|
||||
}
|
||||
}
|
||||
|
||||
if(State == InteractorState.Hover &&
|
||||
_interactable != null &&
|
||||
!_interactable.HasInteractor(this as TInteractor))
|
||||
{
|
||||
TInteractable interactable = _interactable;
|
||||
_interactable = null;
|
||||
InteractableUnset(interactable);
|
||||
State = InteractorState.Normal;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Select()
|
||||
{
|
||||
ShouldSelect = false;
|
||||
|
||||
if (State == InteractorState.Select) return;
|
||||
|
||||
TInteractable interactable = _interactable;
|
||||
if (interactable != null)
|
||||
{
|
||||
if (interactable.IsPotentialCandidateFor(this as TInteractor))
|
||||
{
|
||||
SelectInteractable(interactable);
|
||||
}
|
||||
else
|
||||
{
|
||||
State = InteractorState.Normal;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Selected with no interactable
|
||||
State = InteractorState.Select;
|
||||
}
|
||||
|
||||
SelectUpdate();
|
||||
}
|
||||
|
||||
public virtual void Unselect()
|
||||
{
|
||||
ShouldUnselect = false;
|
||||
|
||||
if (State != InteractorState.Select) return;
|
||||
UnselectInteractable();
|
||||
UpdateInteractor();
|
||||
}
|
||||
|
||||
// Returns the best interactable for selection or null
|
||||
protected abstract TInteractable ComputeCandidate();
|
||||
|
||||
public virtual bool IsFilterPassedBy(TInteractable interactable)
|
||||
{
|
||||
if (InteractableFilters == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (IInteractableFilter interactableFilter in InteractableFilters)
|
||||
{
|
||||
if (!interactableFilter.FilterInteractable(interactable))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Hover()
|
||||
{
|
||||
if (_candidate != null)
|
||||
{
|
||||
SetInteractable(_candidate);
|
||||
HoverUpdate();
|
||||
}
|
||||
else
|
||||
{
|
||||
UnsetInteractable();
|
||||
}
|
||||
}
|
||||
|
||||
private void NormalUpdate()
|
||||
{
|
||||
if (State != InteractorState.Normal)
|
||||
{
|
||||
return;
|
||||
}
|
||||
DoNormalUpdate();
|
||||
}
|
||||
|
||||
private void HoverUpdate()
|
||||
{
|
||||
if (State != InteractorState.Hover)
|
||||
{
|
||||
return;
|
||||
}
|
||||
DoHoverUpdate();
|
||||
}
|
||||
|
||||
private void SelectUpdate()
|
||||
{
|
||||
if (State != InteractorState.Select)
|
||||
{
|
||||
return;
|
||||
}
|
||||
DoSelectUpdate(_selectedInteractable);
|
||||
}
|
||||
|
||||
private void SetInteractable(TInteractable interactable)
|
||||
{
|
||||
if (_interactable == interactable) return;
|
||||
UnsetInteractable();
|
||||
_interactable = interactable;
|
||||
interactable.AddInteractor(this as TInteractor);
|
||||
InteractableSet(interactable);
|
||||
State = InteractorState.Hover;
|
||||
}
|
||||
|
||||
private void UnsetInteractable()
|
||||
{
|
||||
TInteractable interactable = _interactable;
|
||||
if (interactable == null) return;
|
||||
_interactable = null;
|
||||
interactable.RemoveInteractor(this as TInteractor);
|
||||
InteractableUnset(interactable);
|
||||
State = InteractorState.Normal;
|
||||
}
|
||||
|
||||
private void SelectInteractable(TInteractable interactable)
|
||||
{
|
||||
UnselectInteractable();
|
||||
_selectedInteractable = interactable;
|
||||
interactable.AddSelectingInteractor(this as TInteractor);
|
||||
InteractableSelected(interactable);
|
||||
State = InteractorState.Select;
|
||||
}
|
||||
|
||||
private void UnselectInteractable()
|
||||
{
|
||||
TInteractable interactable = _selectedInteractable;
|
||||
if (interactable == null)
|
||||
{
|
||||
State = InteractorState.Normal;
|
||||
return;
|
||||
}
|
||||
interactable.RemoveSelectingInteractor(this as TInteractor);
|
||||
_selectedInteractable = null;
|
||||
InteractableUnselected(interactable);
|
||||
State = InteractorState.Hover;
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
if (State != InteractorState.Disabled) return;
|
||||
State = InteractorState.Normal;
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
if (State == InteractorState.Disabled) return;
|
||||
UnselectInteractable();
|
||||
UnsetInteractable();
|
||||
State = InteractorState.Disabled;
|
||||
}
|
||||
|
||||
#region Inject
|
||||
public void InjectOptionalInteractableFilters(List<IInteractableFilter> interactableFilters)
|
||||
{
|
||||
InteractableFilters = interactableFilters;
|
||||
_interactableFilters = interactableFilters.ConvertAll(interactableFilter =>
|
||||
interactableFilter as MonoBehaviour);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 569aada7077c43349ad26c2b000ff744
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,101 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public class InteractorActiveState : MonoBehaviour, IActiveState
|
||||
{
|
||||
[System.Flags]
|
||||
public enum InteractorProperty
|
||||
{
|
||||
HasCandidate = 1 << 0,
|
||||
HasInteractable = 1 << 1,
|
||||
IsSelecting = 1 << 2,
|
||||
HasSelectedInteractable = 1 << 3,
|
||||
}
|
||||
|
||||
[SerializeField, Interface(typeof(IInteractor))]
|
||||
private MonoBehaviour _interactor;
|
||||
private IInteractor Interactor;
|
||||
|
||||
[SerializeField]
|
||||
private InteractorProperty _property;
|
||||
|
||||
public InteractorProperty Property
|
||||
{
|
||||
get
|
||||
{
|
||||
return _property;
|
||||
}
|
||||
set
|
||||
{
|
||||
_property = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Active
|
||||
{
|
||||
get
|
||||
{
|
||||
if((_property & InteractorProperty.HasCandidate) != 0
|
||||
&& Interactor.HasCandidate)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if((_property & InteractorProperty.HasInteractable) != 0
|
||||
&& Interactor.HasInteractable)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if((_property & InteractorProperty.IsSelecting) != 0
|
||||
&& Interactor.State == InteractorState.Select)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if((_property & InteractorProperty.HasSelectedInteractable) != 0
|
||||
&& Interactor.HasSelectedInteractable)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
Interactor = _interactor as IInteractor;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
Assert.IsNotNull(Interactor);
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllInteractorActiveState(IInteractor interactor)
|
||||
{
|
||||
InjectInteractor(interactor);
|
||||
}
|
||||
|
||||
public void InjectInteractor(IInteractor interactor)
|
||||
{
|
||||
_interactor = interactor as MonoBehaviour;
|
||||
Interactor = interactor;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a56054f6e3838f4e85dce4e71979800
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,225 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// InteractorDriver provides a means to drive the update loop of an Interactor.
|
||||
/// Optionally can be provided a Selector to drive selection of the Interactor.
|
||||
/// Optionally can be provided an ActiveState to enable or disable the Interactor.
|
||||
/// </summary>
|
||||
public class InteractorDriver : MonoBehaviour, IInteractorDriver
|
||||
{
|
||||
[SerializeField, Interface(typeof(IInteractor))]
|
||||
private MonoBehaviour _interactor;
|
||||
|
||||
public IInteractor Interactor;
|
||||
|
||||
[SerializeField, Interface(typeof(ISelector)), Optional]
|
||||
private MonoBehaviour _selector;
|
||||
private ISelector Selector = null;
|
||||
|
||||
[SerializeField, Interface(typeof(IActiveState)), Optional]
|
||||
private MonoBehaviour _activeState;
|
||||
private IActiveState ActiveState = null;
|
||||
|
||||
public bool IsRootInteractorDriver { get; set; } = true;
|
||||
|
||||
public bool IsSelectingInteractable => Interactor.HasSelectedInteractable;
|
||||
|
||||
public bool IsHovering => Interactor.State == InteractorState.Hover;
|
||||
public bool IsSelecting => Interactor.State == InteractorState.Select;
|
||||
public bool HasCandidate => Interactor.HasCandidate;
|
||||
public bool ShouldSelect => _performSelect || Interactor.ShouldSelect;
|
||||
|
||||
private bool _performSelect = false;
|
||||
private bool _performUnselect = false;
|
||||
private bool _selected = false;
|
||||
|
||||
protected bool _started = false;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
Interactor = _interactor as IInteractor;
|
||||
Selector = _selector as ISelector;
|
||||
ActiveState = _activeState as IActiveState;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
this.BeginStart(ref _started);
|
||||
Assert.IsNotNull(Interactor);
|
||||
|
||||
if (_selector != null)
|
||||
{
|
||||
Assert.IsNotNull(Selector);
|
||||
}
|
||||
|
||||
if (_activeState != null)
|
||||
{
|
||||
Assert.IsNotNull(ActiveState);
|
||||
}
|
||||
this.EndStart(ref _started);
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
if (Selector != null)
|
||||
{
|
||||
Selector.WhenSelected += HandleSelected;
|
||||
Selector.WhenUnselected += HandleUnselected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
if (Selector != null)
|
||||
{
|
||||
Selector.WhenSelected -= HandleSelected;
|
||||
Selector.WhenUnselected -= HandleUnselected;
|
||||
|
||||
_performSelect = _performUnselect = false;
|
||||
|
||||
if (_selected)
|
||||
{
|
||||
_selected = false;
|
||||
Interactor.Unselect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
if (!IsRootInteractorDriver) return;
|
||||
UpdateInteraction();
|
||||
UpdateHover();
|
||||
UpdateSelection(true);
|
||||
}
|
||||
|
||||
private bool UpdateActiveState()
|
||||
{
|
||||
if (ActiveState == null || ActiveState.Active)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
_performSelect = _performUnselect = false;
|
||||
Interactor.Disable();
|
||||
return false;
|
||||
}
|
||||
|
||||
public void UpdateInteraction()
|
||||
{
|
||||
if (!UpdateActiveState())
|
||||
{
|
||||
return;
|
||||
}
|
||||
Interactor.Enable();
|
||||
Interactor.UpdateInteractor();
|
||||
}
|
||||
|
||||
public void UpdateHover()
|
||||
{
|
||||
if (!UpdateActiveState())
|
||||
{
|
||||
return;
|
||||
}
|
||||
Interactor.Enable();
|
||||
Interactor.Hover();
|
||||
}
|
||||
|
||||
public void UpdateSelection(bool selectionCanBeEmpty)
|
||||
{
|
||||
if (!UpdateActiveState())
|
||||
{
|
||||
return;
|
||||
}
|
||||
Interactor.Enable();
|
||||
|
||||
_performSelect |= Interactor.ShouldSelect;
|
||||
if (_performSelect)
|
||||
{
|
||||
_selected = true;
|
||||
if (selectionCanBeEmpty || Interactor.HasInteractable)
|
||||
{
|
||||
Interactor.Select();
|
||||
}
|
||||
}
|
||||
|
||||
_performUnselect |= Interactor.ShouldUnselect;
|
||||
if (_performUnselect)
|
||||
{
|
||||
_selected = false;
|
||||
Interactor.Unselect();
|
||||
}
|
||||
|
||||
_performSelect = _performUnselect = false;
|
||||
}
|
||||
|
||||
public void HandleSelected()
|
||||
{
|
||||
_performSelect = true;
|
||||
}
|
||||
|
||||
public void HandleUnselected()
|
||||
{
|
||||
_performUnselect = true;
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
if (ActiveState != null && !ActiveState.Active) return;
|
||||
Interactor.Enable();
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
_performSelect = _performUnselect = false;
|
||||
Interactor.Disable();
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllInteractorDriver(IInteractor interactor)
|
||||
{
|
||||
InjectInteractor(interactor);
|
||||
}
|
||||
|
||||
public void InjectInteractor(IInteractor interactor)
|
||||
{
|
||||
_interactor = interactor as MonoBehaviour;
|
||||
Interactor = interactor;
|
||||
}
|
||||
|
||||
public void InjectOptionalSelector(ISelector selector)
|
||||
{
|
||||
_selector = selector as MonoBehaviour;
|
||||
Selector = selector;
|
||||
}
|
||||
|
||||
public void InjectOptionalActiveState(IActiveState activeState)
|
||||
{
|
||||
_activeState = activeState as MonoBehaviour;
|
||||
ActiveState = activeState;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba7282e6cbb44d94b9399c80f4c2c7e4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,335 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// The InteractorDriverGroup coordinates between multiple InteractorDrivers to determine
|
||||
/// which InteractorDriver(s) should be enabled at a time.
|
||||
/// Passed in InteractorDrivers are prioritized in list order (first = highest priority).
|
||||
public class InteractorDriverGroup : MonoBehaviour, IInteractorDriver
|
||||
{
|
||||
[SerializeField, Interface(typeof(IInteractorDriver))]
|
||||
private List<MonoBehaviour> _interactorDrivers;
|
||||
|
||||
private List<IInteractorDriver> InteractorDrivers;
|
||||
|
||||
// The HoverStrategy defines how this InteractorDriverGroup coordinates hovering
|
||||
// between InteractorDrivers when no InteractorDriver is yet selected.
|
||||
// Upon selection, all other drivers are disabled.
|
||||
public enum InteractorDriverGroupStrategy
|
||||
{
|
||||
// Keep checking all drivers for hover in priority order, disabling all others
|
||||
PRIORITY = 0,
|
||||
// When any driver is hovered, disable all other drivers until this driver is no longer hovering
|
||||
FIRST = 1,
|
||||
// Allow for multiple hovered drivers at a time
|
||||
MULTIPLE = 2
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private InteractorDriverGroupStrategy _interactorDriverGroupStrategy =
|
||||
InteractorDriverGroupStrategy.PRIORITY;
|
||||
|
||||
public bool IsRootInteractorDriver { get; set; } = true;
|
||||
|
||||
private IInteractorDriver _currentDriver = null;
|
||||
private IInteractorDriver _selectingDriver = null;
|
||||
|
||||
[SerializeField]
|
||||
private bool _selectionCanBeEmpty = true;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
InteractorDrivers = _interactorDrivers.ConvertAll(mono => mono as IInteractorDriver);
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
foreach (IInteractorDriver interactorDriver in InteractorDrivers)
|
||||
{
|
||||
Assert.IsNotNull(interactorDriver);
|
||||
}
|
||||
|
||||
foreach (IInteractorDriver interactorDriver in InteractorDrivers)
|
||||
{
|
||||
interactorDriver.IsRootInteractorDriver = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
if (!IsRootInteractorDriver) return;
|
||||
UpdateInteraction();
|
||||
UpdateHover();
|
||||
UpdateSelection(_selectionCanBeEmpty);
|
||||
}
|
||||
|
||||
public void UpdateInteraction()
|
||||
{
|
||||
if (_selectingDriver != null)
|
||||
{
|
||||
_selectingDriver.UpdateInteraction();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_interactorDriverGroupStrategy)
|
||||
{
|
||||
case InteractorDriverGroupStrategy.PRIORITY:
|
||||
PriorityHoverStrategy();
|
||||
break;
|
||||
|
||||
case InteractorDriverGroupStrategy.FIRST:
|
||||
FirstHoverStrategy();
|
||||
break;
|
||||
|
||||
case InteractorDriverGroupStrategy.MULTIPLE:
|
||||
MultiHoverStrategy();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateHover()
|
||||
{
|
||||
if (_selectingDriver != null || _currentDriver == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_currentDriver.UpdateHover();
|
||||
switch (_interactorDriverGroupStrategy)
|
||||
{
|
||||
case InteractorDriverGroupStrategy.PRIORITY:
|
||||
case InteractorDriverGroupStrategy.FIRST:
|
||||
if (_currentDriver != null)
|
||||
{
|
||||
_currentDriver.UpdateHover();
|
||||
}
|
||||
break;
|
||||
case InteractorDriverGroupStrategy.MULTIPLE:
|
||||
foreach(IInteractorDriver driver in InteractorDrivers)
|
||||
{
|
||||
if (!driver.HasCandidate)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
driver.UpdateHover();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSelection(bool selectionCanBeEmpty)
|
||||
{
|
||||
if (_selectingDriver != null)
|
||||
{
|
||||
_selectingDriver.UpdateSelection(selectionCanBeEmpty);
|
||||
if (_selectingDriver.IsSelecting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_selectingDriver = null;
|
||||
UpdateInteraction();
|
||||
UpdateHover();
|
||||
}
|
||||
|
||||
switch (_interactorDriverGroupStrategy)
|
||||
{
|
||||
case InteractorDriverGroupStrategy.PRIORITY:
|
||||
case InteractorDriverGroupStrategy.FIRST:
|
||||
if (_currentDriver != null)
|
||||
{
|
||||
_currentDriver.UpdateSelection(selectionCanBeEmpty);
|
||||
if (_currentDriver.IsSelecting)
|
||||
{
|
||||
SelectDriver(_currentDriver);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case InteractorDriverGroupStrategy.MULTIPLE:
|
||||
foreach(IInteractorDriver driver in InteractorDrivers)
|
||||
{
|
||||
driver.UpdateSelection(selectionCanBeEmpty);
|
||||
if (driver.IsSelecting)
|
||||
{
|
||||
_currentDriver = driver;
|
||||
SelectDriver(_currentDriver);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void PriorityHoverStrategy()
|
||||
{
|
||||
_currentDriver = null;
|
||||
for (int i = 0; i < InteractorDrivers.Count; i++)
|
||||
{
|
||||
IInteractorDriver driver = InteractorDrivers[i];
|
||||
|
||||
driver.Enable();
|
||||
driver.UpdateInteraction();
|
||||
|
||||
// If we are in a hover state, we want to make the hover interactor visible
|
||||
if (driver.HasCandidate)
|
||||
{
|
||||
_currentDriver = driver;
|
||||
DisableAllDriversExcept(_currentDriver);
|
||||
return;
|
||||
}
|
||||
else if (i != InteractorDrivers.Count - 1)
|
||||
{
|
||||
// when we only allow only one hovering interactor,
|
||||
// all but the least prioritized interactor
|
||||
// should be hidden
|
||||
driver.Disable();
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentDriver == null)
|
||||
{
|
||||
_currentDriver = InteractorDrivers[InteractorDrivers.Count - 1];
|
||||
}
|
||||
}
|
||||
|
||||
private void FirstHoverStrategy()
|
||||
{
|
||||
if (_currentDriver != null)
|
||||
{
|
||||
_currentDriver.UpdateInteraction();
|
||||
if (_currentDriver.HasCandidate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_currentDriver = null;
|
||||
for (int i = 0; i < InteractorDrivers.Count; i++)
|
||||
{
|
||||
IInteractorDriver driver = InteractorDrivers[i];
|
||||
driver.Enable();
|
||||
driver.UpdateInteraction();
|
||||
|
||||
// If we are in a hover state, we want to make the hover interactor visible
|
||||
if (driver.HasCandidate)
|
||||
{
|
||||
_currentDriver = driver;
|
||||
DisableAllDriversExcept(_currentDriver);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MultiHoverStrategy()
|
||||
{
|
||||
_currentDriver = null;
|
||||
foreach (IInteractorDriver driver in InteractorDrivers)
|
||||
{
|
||||
driver.Enable();
|
||||
driver.UpdateInteraction();
|
||||
|
||||
// If we are in a hover state, we want to make the hover interactor visible
|
||||
if (driver.HasCandidate)
|
||||
{
|
||||
_currentDriver = driver;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectDriver(IInteractorDriver selectedDriver)
|
||||
{
|
||||
// Mark this as the selected driver
|
||||
_currentDriver = selectedDriver;
|
||||
_selectingDriver = selectedDriver;
|
||||
|
||||
DisableAllDriversExcept(_selectingDriver);
|
||||
}
|
||||
|
||||
private void DisableAllDriversExcept(IInteractorDriver enabledDriver)
|
||||
{
|
||||
foreach (IInteractorDriver driver in InteractorDrivers)
|
||||
{
|
||||
if (driver == enabledDriver) continue;
|
||||
driver.Disable();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSelectingInteractable => _selectingDriver != null && _selectingDriver.IsSelectingInteractable;
|
||||
public bool IsSelecting => _selectingDriver != null;
|
||||
public bool HasCandidate => _currentDriver != null && _currentDriver.HasCandidate;
|
||||
public bool ShouldSelect =>
|
||||
_currentDriver != null && _currentDriver.ShouldSelect;
|
||||
public bool IsHovering => _currentDriver != null && _currentDriver.IsHovering;
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
foreach (IInteractorDriver interactorDriver in InteractorDrivers)
|
||||
{
|
||||
interactorDriver.Enable();
|
||||
}
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
foreach (IInteractorDriver interactorDriver in InteractorDrivers)
|
||||
{
|
||||
interactorDriver.Disable();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddInteractorDriver(IInteractorDriver interactorDriver)
|
||||
{
|
||||
InteractorDrivers.Add(interactorDriver);
|
||||
_interactorDrivers.Add(interactorDriver as MonoBehaviour);
|
||||
interactorDriver.IsRootInteractorDriver = false;
|
||||
}
|
||||
|
||||
public void RemoveInteractorDriver(IInteractorDriver interactorDriver)
|
||||
{
|
||||
InteractorDrivers.Remove(interactorDriver);
|
||||
_interactorDrivers.Remove(interactorDriver as MonoBehaviour);
|
||||
interactorDriver.IsRootInteractorDriver = true;
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllInteractorDriverGroup(List<IInteractorDriver> interactorDrivers)
|
||||
{
|
||||
InjectInteractorDrivers(interactorDrivers);
|
||||
}
|
||||
|
||||
public void InjectInteractorDrivers(List<IInteractorDriver> interactorDrivers)
|
||||
{
|
||||
InteractorDrivers = interactorDrivers;
|
||||
_interactorDrivers = interactorDrivers.ConvertAll(interactorDriver =>
|
||||
interactorDriver as MonoBehaviour);
|
||||
}
|
||||
|
||||
public void InjectOptionalInteractorDriverGroupStrategy(
|
||||
InteractorDriverGroupStrategy interactorDriverGroupStrategy)
|
||||
{
|
||||
_interactorDriverGroupStrategy = interactorDriverGroupStrategy;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e06fe30fd4efa954b8f53ccc8da8f46b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public enum InteractorState
|
||||
{
|
||||
Normal,
|
||||
Hover,
|
||||
Select,
|
||||
Disabled
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31bb3bcc725d37d46bfbc93aec2ebc37
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,58 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// MAction can be used in place of Action. This allows
|
||||
/// for interfaces with Actions of generic covariant types
|
||||
/// to be subscribed to by multiple types of delegates.
|
||||
/// </summary>
|
||||
public interface MAction<out T>
|
||||
{
|
||||
event Action<T> Action;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Classes that implement an interface that has MActions
|
||||
/// can use MultiAction as their MAction implementation to
|
||||
/// allow for multiple types of delegates to subscribe to the
|
||||
/// generic type.
|
||||
/// </summary>
|
||||
public class MultiAction<T> : MAction<T>
|
||||
{
|
||||
protected HashSet<Action<T>> actions = new HashSet<Action<T>>();
|
||||
|
||||
public event Action<T> Action
|
||||
{
|
||||
add
|
||||
{
|
||||
actions.Add(value);
|
||||
}
|
||||
remove
|
||||
{
|
||||
actions.Remove(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Invoke(T t)
|
||||
{
|
||||
foreach (Action<T> action in actions)
|
||||
{
|
||||
action(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4479693b9d773544faad110735e769de
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,96 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// An Interactable Filter that uses tags to filter Interactables
|
||||
/// </summary>
|
||||
public class TagInteractableFilter : MonoBehaviour, IInteractableFilter
|
||||
{
|
||||
/// If there is at least one required tag, an interactable must meet one of them
|
||||
[SerializeField, Optional]
|
||||
private string[] _requireTags;
|
||||
|
||||
/// An interactable must not meet any of the avoid tags
|
||||
[SerializeField, Optional]
|
||||
private string[] _avoidTags;
|
||||
|
||||
private HashSet<string> _requireTagSet;
|
||||
private HashSet<string> _avoidTagSet;
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
_requireTagSet = new HashSet<string>();
|
||||
_avoidTagSet = new HashSet<string>();
|
||||
|
||||
foreach (string requireTag in _requireTags)
|
||||
{
|
||||
_requireTagSet.Add(requireTag);
|
||||
}
|
||||
|
||||
foreach (string avoidTag in _avoidTags)
|
||||
{
|
||||
_avoidTagSet.Add(avoidTag);
|
||||
}
|
||||
}
|
||||
|
||||
public bool FilterInteractable(IInteractable interactable)
|
||||
{
|
||||
GameObject gameObject = interactable.gameObject;
|
||||
TagSet tagSet = gameObject.GetComponent<TagSet>();
|
||||
if (tagSet == null && _requireTagSet.Count > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (string tag in _requireTagSet)
|
||||
{
|
||||
if (!tagSet.ContainsTag(tag))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (tagSet == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (string tag in _avoidTagSet)
|
||||
{
|
||||
if (tagSet.ContainsTag(tag))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectOptionalRequireTags(string[] requireTags)
|
||||
{
|
||||
_requireTags = requireTags;
|
||||
}
|
||||
public void InjectOptionalAvoidTags(string[] avoidTags)
|
||||
{
|
||||
_avoidTags = avoidTags;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75d3a1facc5f29d4ea65d3af68af5c9e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// A Tag Set that can be added to a GameObject with an Interactable to be filtered against.
|
||||
/// </summary>
|
||||
public class TagSet : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private List<string> _tags;
|
||||
|
||||
private HashSet<string> _tagSet;
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
_tagSet = new HashSet<string>();
|
||||
foreach (string tag in _tags)
|
||||
{
|
||||
_tagSet.Add(tag);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsTag(string tag) => _tagSet.Contains(tag);
|
||||
|
||||
public void AddTag(string tag) => _tagSet.Add(tag);
|
||||
public void RemoveTag(string tag) => _tagSet.Remove(tag);
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectOptionalTags(List<string> tags)
|
||||
{
|
||||
_tags = tags;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fdeaf26e845dc5841bcd31de97d26003
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,47 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public class UniqueIdentifier
|
||||
{
|
||||
public int ID { get; private set; }
|
||||
|
||||
|
||||
private UniqueIdentifier(int identifier)
|
||||
{
|
||||
ID = identifier;
|
||||
}
|
||||
|
||||
private static System.Random Random = new System.Random();
|
||||
private static HashSet<int> _identifierSet = new HashSet<int>();
|
||||
|
||||
public static UniqueIdentifier Generate()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
int identifier = Random.Next(Int32.MaxValue);
|
||||
if (_identifierSet.Contains(identifier)) continue;
|
||||
_identifierSet.Add(identifier);
|
||||
return new UniqueIdentifier(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Release(UniqueIdentifier identifier)
|
||||
{
|
||||
_identifierSet.Remove(identifier.ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44a265649958ca641ab37f99689bf65f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79ba4df999bea4549a74a386e2071ff4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a07d0f6d9353f724684ee12d33139844
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,135 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public class GrabInteractable : Interactable<GrabInteractor, GrabInteractable>,
|
||||
IPointable, IRigidbodyRef
|
||||
{
|
||||
private Collider[] _colliders;
|
||||
public Collider[] Colliders => _colliders;
|
||||
|
||||
[SerializeField]
|
||||
Rigidbody _rigidbody;
|
||||
public Rigidbody Rigidbody => _rigidbody;
|
||||
|
||||
[SerializeField]
|
||||
private float _releaseDistance = 0f;
|
||||
public float ReleaseDistance => _releaseDistance;
|
||||
|
||||
[SerializeField, Optional]
|
||||
private PhysicsTransformable _physicsObject = null;
|
||||
|
||||
private static CollisionInteractionRegistry<GrabInteractor, GrabInteractable> _grabRegistry = null;
|
||||
|
||||
public event Action<PointerArgs> OnPointerEvent = delegate { };
|
||||
private PointableDelegate<GrabInteractor> _pointableDelegate;
|
||||
|
||||
protected bool _started = false;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
if (_grabRegistry == null)
|
||||
{
|
||||
_grabRegistry = new CollisionInteractionRegistry<GrabInteractor, GrabInteractable>();
|
||||
SetRegistry(_grabRegistry);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
this.BeginStart(ref _started);
|
||||
|
||||
Assert.IsNotNull(Rigidbody);
|
||||
_colliders = Rigidbody.GetComponentsInChildren<Collider>();
|
||||
Assert.IsTrue(Colliders.Length > 0,
|
||||
"The associated Rigidbody must have at least one Collider.");
|
||||
|
||||
_pointableDelegate =
|
||||
new PointableDelegate<GrabInteractor>(this, ComputePointer);
|
||||
|
||||
this.EndStart(ref _started);
|
||||
}
|
||||
|
||||
public void ApplyVelocities(Vector3 linearVelocity, Vector3 angularVelocity)
|
||||
{
|
||||
if (_physicsObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_physicsObject.ApplyVelocities(linearVelocity, angularVelocity);
|
||||
}
|
||||
|
||||
private void ComputePointer(GrabInteractor interactor, out Vector3 position, out Quaternion rotation)
|
||||
{
|
||||
position = interactor.GrabPosition;
|
||||
rotation = interactor.GrabRotation;
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
if (_started)
|
||||
{
|
||||
_pointableDelegate.OnPointerEvent += InvokeOnPointerEvent;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
_pointableDelegate.OnPointerEvent -= InvokeOnPointerEvent;
|
||||
}
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
private void InvokeOnPointerEvent(PointerArgs args)
|
||||
{
|
||||
OnPointerEvent(args);
|
||||
}
|
||||
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
_pointableDelegate = null;
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllGrabInteractable(Rigidbody rigidbody)
|
||||
{
|
||||
InjectRigidbody(rigidbody);
|
||||
}
|
||||
|
||||
public void InjectRigidbody(Rigidbody rigidbody)
|
||||
{
|
||||
_rigidbody = rigidbody;
|
||||
}
|
||||
|
||||
public void InjectOptionalReleaseDistance(float releaseDistance)
|
||||
{
|
||||
_releaseDistance = releaseDistance;
|
||||
}
|
||||
|
||||
public void InjectOptionalPhysicsObject(PhysicsTransformable physicsObject)
|
||||
{
|
||||
_physicsObject = physicsObject;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a1bc992571301d4a9602ac95ef4c71a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,196 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using Oculus.Interaction.Throw;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public class GrabInteractor : Interactor<GrabInteractor, GrabInteractable>, IRigidbodyRef
|
||||
{
|
||||
[SerializeField]
|
||||
private Transform _targetTransform;
|
||||
|
||||
[SerializeField]
|
||||
private Rigidbody _rigidbody;
|
||||
public Rigidbody Rigidbody => _rigidbody;
|
||||
|
||||
[SerializeField, Optional]
|
||||
private Transform _grabAnchorTransform;
|
||||
|
||||
[SerializeField, Interface(typeof(IVelocityCalculator)), Optional]
|
||||
private MonoBehaviour _velocityCalculator;
|
||||
public IVelocityCalculator VelocityCalculator { get; set; }
|
||||
|
||||
private Collider[] _colliders;
|
||||
|
||||
public float BestInteractableWeight { get; private set; } = float.MaxValue;
|
||||
|
||||
public Vector3 GrabPosition => _grabAnchorTransform.position;
|
||||
public Quaternion GrabRotation => _grabAnchorTransform.rotation;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
VelocityCalculator = _velocityCalculator as IVelocityCalculator;
|
||||
}
|
||||
|
||||
protected override void Start()
|
||||
{
|
||||
Assert.IsNotNull(_targetTransform);
|
||||
Assert.IsNotNull(Rigidbody);
|
||||
|
||||
if (_grabAnchorTransform == null)
|
||||
{
|
||||
_grabAnchorTransform = _targetTransform;
|
||||
}
|
||||
|
||||
Assert.IsNotNull(Rigidbody);
|
||||
|
||||
_colliders = Rigidbody.GetComponentsInChildren<Collider>();
|
||||
Assert.IsTrue(_colliders.Length > 0,
|
||||
"The associated Rigidbody must have at least one Collider.");
|
||||
foreach (Collider collider in _colliders)
|
||||
{
|
||||
Assert.IsTrue(collider.isTrigger,
|
||||
"Associated Colliders must be marked as Triggers.");
|
||||
}
|
||||
|
||||
if (_velocityCalculator != null)
|
||||
{
|
||||
Assert.IsNotNull(VelocityCalculator);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DoEveryUpdate()
|
||||
{
|
||||
base.DoEveryUpdate();
|
||||
|
||||
transform.position = _targetTransform.position;
|
||||
transform.rotation = _targetTransform.rotation;
|
||||
// Any collider on the Interactor may need updating for
|
||||
// future collision checks this frame
|
||||
Physics.SyncTransforms();
|
||||
}
|
||||
|
||||
protected override GrabInteractable ComputeCandidate()
|
||||
{
|
||||
GrabInteractable closestInteractable = null;
|
||||
float bestWeight = float.MinValue;
|
||||
float weight = bestWeight;
|
||||
|
||||
IEnumerable<GrabInteractable> interactables = GrabInteractable.Registry.List(this);
|
||||
foreach (GrabInteractable interactable in interactables)
|
||||
{
|
||||
Collider[] colliders = interactable.Colliders;
|
||||
foreach (Collider collider in colliders)
|
||||
{
|
||||
if (Collisions.IsPointWithinCollider(Rigidbody.transform.position, collider))
|
||||
{
|
||||
// Points within a collider are always weighted better than those outside
|
||||
float sqrDistanceFromCenter =
|
||||
(Rigidbody.transform.position - collider.bounds.center).magnitude;
|
||||
weight = float.MaxValue - sqrDistanceFromCenter;
|
||||
}
|
||||
else
|
||||
{
|
||||
var position = Rigidbody.transform.position;
|
||||
Vector3 closestPointOnInteractable = collider.ClosestPoint(position);
|
||||
weight = -1f * (position - closestPointOnInteractable).magnitude;
|
||||
}
|
||||
|
||||
if (weight > bestWeight)
|
||||
{
|
||||
bestWeight = weight;
|
||||
closestInteractable = interactable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BestInteractableWeight = bestWeight;
|
||||
return closestInteractable;
|
||||
}
|
||||
|
||||
protected override void InteractableUnselected(GrabInteractable interactable)
|
||||
{
|
||||
base.InteractableUnselected(interactable);
|
||||
|
||||
ReleaseVelocityInformation throwVelocity = VelocityCalculator != null ?
|
||||
VelocityCalculator.CalculateThrowVelocity(interactable.transform) :
|
||||
new ReleaseVelocityInformation(Vector3.zero, Vector3.zero, Vector3.zero);
|
||||
interactable.ApplyVelocities(throwVelocity.LinearVelocity, throwVelocity.AngularVelocity);
|
||||
}
|
||||
|
||||
protected override void DoSelectUpdate(GrabInteractable interactable)
|
||||
{
|
||||
base.DoSelectUpdate();
|
||||
|
||||
if (interactable == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (interactable.ReleaseDistance > 0.0f)
|
||||
{
|
||||
float closestSqrDist = float.MaxValue;
|
||||
Collider[] colliders = interactable.Colliders;
|
||||
foreach (Collider collider in colliders)
|
||||
{
|
||||
float sqrDistanceFromCenter =
|
||||
(collider.bounds.center - Rigidbody.transform.position).sqrMagnitude;
|
||||
closestSqrDist = Mathf.Min(closestSqrDist, sqrDistanceFromCenter);
|
||||
}
|
||||
|
||||
float sqrReleaseDistance = interactable.ReleaseDistance * interactable.ReleaseDistance;
|
||||
|
||||
if (closestSqrDist > sqrReleaseDistance)
|
||||
{
|
||||
ShouldUnselect = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllGrabInteractor(Transform targetTransform, Rigidbody rigidbody)
|
||||
{
|
||||
InjectTargetTransform(targetTransform);
|
||||
InjectRigidbodyRef(rigidbody);
|
||||
}
|
||||
|
||||
public void InjectTargetTransform(Transform targetTransform)
|
||||
{
|
||||
_targetTransform = targetTransform;
|
||||
}
|
||||
|
||||
public void InjectRigidbodyRef(Rigidbody rigidbody)
|
||||
{
|
||||
_rigidbody = rigidbody;
|
||||
}
|
||||
|
||||
public void InjectOptionalGrabAnchorTransform(Transform grabAnchorTransform)
|
||||
{
|
||||
_grabAnchorTransform = grabAnchorTransform;
|
||||
}
|
||||
|
||||
public void InjectOptionalVelocityCalculator(IVelocityCalculator velocityCalculator)
|
||||
{
|
||||
_velocityCalculator = velocityCalculator as MonoBehaviour;
|
||||
VelocityCalculator = velocityCalculator;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: acb743c666c1ab34e928a0b62c2fa862
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd6a8178aa1fa19458ed3921d62c7d8d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,180 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using Oculus.Interaction.Input;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.HandPosing
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class used by the grab interactors to find the best matching
|
||||
/// pose from a provided list of HandGrabPoints in an object.
|
||||
/// </summary>
|
||||
public class GrabPointsPoseFinder
|
||||
{
|
||||
private List<HandGrabPoint> _handGrabPoints;
|
||||
private Transform _fallbackPoint;
|
||||
|
||||
private InterpolationCache _interpolationCache = new InterpolationCache();
|
||||
|
||||
public GrabPointsPoseFinder(List<HandGrabPoint> handGrabPoints, Transform fallbackPoint = null)
|
||||
{
|
||||
_handGrabPoints = handGrabPoints;
|
||||
_fallbackPoint = fallbackPoint;
|
||||
}
|
||||
|
||||
public bool UsesHandPose()
|
||||
{
|
||||
return _handGrabPoints.Count > 0 && _handGrabPoints[0].HandPose != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the best valid hand-pose at this HandGrabInteractable.
|
||||
/// Remember that a HandGrabPoint can actually have a whole surface the user can snap to.
|
||||
/// </summary>
|
||||
/// <param name="userPose">Pose to compare to the snap point in world coordinates.</param>
|
||||
/// <param name="handScale">The scale of the tracked hand.</param>
|
||||
/// <param name="handedness">The handedness of the tracked hand.</param>
|
||||
/// <param name="bestHandPose">The most similar valid HandPose at this HandGrabInteractable.</param>
|
||||
/// <param name="bestSnapPoint">The point of the valid snap.</param>
|
||||
/// <param name="scoringModifier">Parameters indicating how to score the different poses.</param>
|
||||
/// <param name="usesHandPose">True if the resultHandPose was populated.</param>
|
||||
/// <param name="score">The score of the best pose found.</param>
|
||||
/// <returns>True if a good pose was found</returns>
|
||||
public bool FindBestPose(Pose userPose, float handScale, Handedness handedness,
|
||||
ref HandPose bestHandPose, ref Pose bestSnapPoint, in PoseMeasureParameters scoringModifier, out bool usesHandPose, out float score)
|
||||
{
|
||||
if (_handGrabPoints.Count == 1)
|
||||
{
|
||||
return _handGrabPoints[0].CalculateBestPose(userPose, handedness,
|
||||
ref bestHandPose, ref bestSnapPoint, scoringModifier, out usesHandPose, out score);
|
||||
}
|
||||
else if (_handGrabPoints.Count > 1)
|
||||
{
|
||||
return CalculateBestScaleInterpolatedPose(userPose, handedness, handScale,
|
||||
ref bestHandPose, ref bestSnapPoint, scoringModifier, out usesHandPose, out score);
|
||||
}
|
||||
else if (_fallbackPoint != null)
|
||||
{
|
||||
usesHandPose = false;
|
||||
score = PoseUtils.Similarity(userPose, _fallbackPoint.GetPose(), scoringModifier);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
usesHandPose = false;
|
||||
score = float.NaN;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CalculateBestScaleInterpolatedPose(Pose userPose, Handedness handedness, float handScale,
|
||||
ref HandPose result, ref Pose snapPoint, in PoseMeasureParameters scoringModifier, out bool usesHandPose, out float score)
|
||||
{
|
||||
usesHandPose = false;
|
||||
score = float.NaN;
|
||||
|
||||
FindInterpolationRange(handScale, _handGrabPoints, out HandGrabPoint under, out HandGrabPoint over, out float t);
|
||||
|
||||
bool underFound = under.CalculateBestPose(userPose, handedness,
|
||||
ref _interpolationCache.underHandPose, ref _interpolationCache.underSnapPoint, scoringModifier,
|
||||
out bool underPoseWritten, out float underScore);
|
||||
|
||||
bool overFound = over.CalculateBestPose(userPose, handedness,
|
||||
ref _interpolationCache.overHandPose, ref _interpolationCache.overSnapPoint, scoringModifier,
|
||||
out bool overPoseWritten, out float overScore);
|
||||
|
||||
if (underPoseWritten && overPoseWritten)
|
||||
{
|
||||
usesHandPose = true;
|
||||
result.CopyFrom(_interpolationCache.underHandPose);
|
||||
HandPose.Lerp(_interpolationCache.underHandPose, _interpolationCache.overHandPose, t, ref result);
|
||||
PoseUtils.Lerp(_interpolationCache.underSnapPoint, _interpolationCache.overSnapPoint, t, ref snapPoint);
|
||||
}
|
||||
else if (underPoseWritten)
|
||||
{
|
||||
usesHandPose = true;
|
||||
result.CopyFrom(_interpolationCache.underHandPose);
|
||||
snapPoint.CopyFrom(_interpolationCache.underSnapPoint);
|
||||
}
|
||||
else if (overPoseWritten)
|
||||
{
|
||||
usesHandPose = true;
|
||||
result.CopyFrom(_interpolationCache.overHandPose);
|
||||
snapPoint.CopyFrom(_interpolationCache.overSnapPoint);
|
||||
}
|
||||
|
||||
if (underFound && overFound)
|
||||
{
|
||||
score = Mathf.Lerp(underScore, overScore, t);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (underFound)
|
||||
{
|
||||
score = underScore;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (overFound)
|
||||
{
|
||||
score = overScore;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the two nearest HandGrabPoints to interpolate from given a scale.
|
||||
/// The result can require an unclamped interpolation (t can be bigger than 1 or smaller than 0).
|
||||
/// </summary>
|
||||
/// <param name="scale">The user scale</param>
|
||||
/// <param name="grabPoints">The list of grabpoints to interpolate from</param>
|
||||
/// <param name="from">The HandGrabInteractable with nearest scale recorder that is smaller than the provided one</param>
|
||||
/// <param name="to">The HandGrabInteractable with nearest scale recorder that is bigger than the provided one</param>
|
||||
/// <param name="t">The progress between from and to variables at which the desired scale resides</param>
|
||||
/// <returns>The HandGrabPoint near under and over the scale, and the interpolation factor between them.</returns>
|
||||
public static void FindInterpolationRange(float scale, List<HandGrabPoint> grabPoints, out HandGrabPoint from, out HandGrabPoint to, out float t)
|
||||
{
|
||||
from = grabPoints[0];
|
||||
to = grabPoints[1];
|
||||
|
||||
for (int i = 2; i < grabPoints.Count; i++)
|
||||
{
|
||||
HandGrabPoint point = grabPoints[i];
|
||||
|
||||
if (point.Scale <= scale
|
||||
&& point.Scale > from.Scale)
|
||||
{
|
||||
from = point;
|
||||
}
|
||||
else if (point.Scale >= scale
|
||||
&& point.Scale < to.Scale)
|
||||
{
|
||||
to = point;
|
||||
}
|
||||
}
|
||||
|
||||
t = (scale - from.Scale) / (to.Scale - from.Scale);
|
||||
}
|
||||
|
||||
private class InterpolationCache
|
||||
{
|
||||
public HandPose underHandPose = new HandPose();
|
||||
public HandPose overHandPose = new HandPose();
|
||||
public Pose underSnapPoint = new Pose();
|
||||
public Pose overSnapPoint = new Pose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 534b7c998bd48b040ac365ad13ff6ab8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,358 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using Oculus.Interaction.Grab;
|
||||
using Oculus.Interaction.GrabAPI;
|
||||
using Oculus.Interaction.Input;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction.HandPosing
|
||||
{
|
||||
/// <summary>
|
||||
/// Serializable data-only version of the HandGrabInteractable so it can be stored when they
|
||||
/// are generated at Play-Mode (where Hand-tracking works).
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct HandGrabInteractableData
|
||||
{
|
||||
public List<HandGrabPointData> points;
|
||||
public SnapType snapType;
|
||||
public GrabTypeFlags grabType;
|
||||
public float travelSpeed;
|
||||
public PoseMeasureParameters scoringModifier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A HandGrabInteractable indicates the properties about how a hand can snap to an object.
|
||||
/// The most important is the position/rotation and finger rotations for the hand,
|
||||
/// but it can also contain extra information like a valid holding surface (instead of just
|
||||
/// a single point) or a visual representation (using a hand-ghost)
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class HandGrabInteractable : Interactable<HandGrabInteractor, HandGrabInteractable>,
|
||||
IPointable, ISnappable, IRigidbodyRef, IHandGrabInteractable
|
||||
{
|
||||
[Header("Grab")]
|
||||
/// <summary>
|
||||
/// The transform of the object this HandGrabInteractable refers to.
|
||||
/// Typically the parent.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
private Transform _relativeTo;
|
||||
|
||||
[SerializeField]
|
||||
private Rigidbody _rigidbody;
|
||||
public Rigidbody Rigidbody => _rigidbody;
|
||||
|
||||
[SerializeField, Optional]
|
||||
private float _releaseDistance = 0f;
|
||||
|
||||
[SerializeField, Optional]
|
||||
private PhysicsTransformable _physicsObject = null;
|
||||
|
||||
[SerializeField]
|
||||
private PoseMeasureParameters _scoringModifier = new PoseMeasureParameters(0.1f, 0f);
|
||||
|
||||
[Space]
|
||||
[SerializeField]
|
||||
private GrabTypeFlags _supportedGrabTypes = GrabTypeFlags.All;
|
||||
[SerializeField]
|
||||
private GrabbingRule _pinchGrabRules = GrabbingRule.DefaultPinchRule;
|
||||
[SerializeField]
|
||||
private GrabbingRule _palmGrabRules = GrabbingRule.DefaultPalmRule;
|
||||
|
||||
[Header("Snap")]
|
||||
/// <summary>
|
||||
/// How the snap will occur.
|
||||
/// For example the hand can artificially move to perfectly wrap the object, or the object can move to align with the hand.
|
||||
/// </summary>
|
||||
[Tooltip("How the snap will occur, for example the hand can artificially move to perfectly wrap the object, or the object can move to align with the hand")]
|
||||
[SerializeField]
|
||||
private SnapType _snapType;
|
||||
|
||||
/// <summary>
|
||||
/// When attracting the object, indicates how many seconds it will take for the object to realign with the hand after a grab
|
||||
/// </summary>
|
||||
[Tooltip("When attracting the object, indicates the speed (in m/s) for the object to realign with the hand after a grab.")]
|
||||
[SerializeField]
|
||||
private float _travelSpeed = 1f;
|
||||
|
||||
[SerializeField, Optional]
|
||||
private List<HandGrabPoint> _handGrabPoints = new List<HandGrabPoint>();
|
||||
|
||||
/// <summary>
|
||||
/// General getter for the transform of the object this interactable refers to.
|
||||
/// </summary>
|
||||
public Transform RelativeTo => _relativeTo != null ? _relativeTo : this.transform.parent;
|
||||
|
||||
/// <summary>
|
||||
/// General getter indicating how the hand and object will align for the grab.
|
||||
/// </summary>
|
||||
public SnapType SnapType => _snapType;
|
||||
|
||||
public GrabTypeFlags SupportedGrabTypes => _supportedGrabTypes;
|
||||
public GrabbingRule PinchGrabRules => _pinchGrabRules;
|
||||
public GrabbingRule PalmGrabRules => _palmGrabRules;
|
||||
|
||||
public List<HandGrabPoint> GrabPoints => _handGrabPoints;
|
||||
public Collider[] Colliders { get; private set; }
|
||||
public float ReleaseDistance => _releaseDistance;
|
||||
|
||||
public event Action<PointerArgs> OnPointerEvent = delegate { };
|
||||
private GrabPointsPoseFinder _grabPointsPoseFinder;
|
||||
private PointableDelegate<HandGrabInteractor> _pointableDelegate;
|
||||
|
||||
private static CollisionInteractionRegistry<HandGrabInteractor, HandGrabInteractable> _registry = null;
|
||||
|
||||
protected bool _started = false;
|
||||
|
||||
#region editor events
|
||||
protected virtual void Reset()
|
||||
{
|
||||
_relativeTo = this.transform.parent;
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
if (_registry == null)
|
||||
{
|
||||
_registry = new CollisionInteractionRegistry<HandGrabInteractor, HandGrabInteractable>();
|
||||
SetRegistry(_registry);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
this.BeginStart(ref _started);
|
||||
Assert.IsNotNull(Rigidbody);
|
||||
Colliders = Rigidbody.GetComponentsInChildren<Collider>();
|
||||
Assert.IsTrue(Colliders.Length > 0,
|
||||
"The associated Rigidbody must have at least one Collider.");
|
||||
|
||||
_grabPointsPoseFinder = new GrabPointsPoseFinder(_handGrabPoints, this.transform);
|
||||
_pointableDelegate = new PointableDelegate<HandGrabInteractor>(this, ComputePointer);
|
||||
this.EndStart(ref _started);
|
||||
}
|
||||
|
||||
private void ComputePointer(HandGrabInteractor interactor, out Vector3 position, out Quaternion rotation)
|
||||
{
|
||||
position = interactor.GrabPose.position;
|
||||
rotation = interactor.GrabPose.rotation;
|
||||
}
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
if (_started)
|
||||
{
|
||||
_pointableDelegate.OnPointerEvent += InvokeOnPointerEvent;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
_pointableDelegate.OnPointerEvent -= InvokeOnPointerEvent;
|
||||
}
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
private void InvokeOnPointerEvent(PointerArgs args)
|
||||
{
|
||||
OnPointerEvent.Invoke(args);
|
||||
}
|
||||
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
_pointableDelegate = null;
|
||||
}
|
||||
|
||||
#region pose snapping
|
||||
|
||||
public bool CalculateBestPose(Pose userPose, float handScale, Handedness handedness,
|
||||
ref HandPose result, ref Pose snapPoint, out bool usesHandPose, out float score)
|
||||
{
|
||||
return _grabPointsPoseFinder.FindBestPose(userPose, handScale, handedness,
|
||||
ref result, ref snapPoint, _scoringModifier, out usesHandPose, out score);
|
||||
}
|
||||
|
||||
public bool UsesHandPose()
|
||||
{
|
||||
return _grabPointsPoseFinder.UsesHandPose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region generation
|
||||
/// <summary>
|
||||
/// Creates a new HandGrabInteractable under the given object
|
||||
/// </summary>
|
||||
/// <param name="parent">The relative object for the interactable</param>
|
||||
/// <returns>An non-populated HandGrabInteractable</returns>
|
||||
public static HandGrabInteractable Create(Transform parent)
|
||||
{
|
||||
GameObject go = new GameObject("HandGrabInteractable");
|
||||
go.transform.SetParent(parent, false);
|
||||
HandGrabInteractable record = go.AddComponent<HandGrabInteractable>();
|
||||
record._relativeTo = parent;
|
||||
return record;
|
||||
}
|
||||
|
||||
public HandGrabPoint CreatePoint()
|
||||
{
|
||||
GameObject go = this.gameObject;
|
||||
if (this.TryGetComponent(out HandGrabPoint point))
|
||||
{
|
||||
go = new GameObject("HandGrab Point");
|
||||
go.transform.SetParent(this.transform, false);
|
||||
}
|
||||
HandGrabPoint record = go.AddComponent<HandGrabPoint>();
|
||||
return record;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region dataSave
|
||||
/// <summary>
|
||||
/// Serializes the data of the HandGrabInteractable so it can be stored
|
||||
/// </summary>
|
||||
/// <returns>The struct data to recreate the interactable</returns>
|
||||
public HandGrabInteractableData SaveData()
|
||||
{
|
||||
return new HandGrabInteractableData()
|
||||
{
|
||||
snapType = _snapType,
|
||||
travelSpeed = _travelSpeed,
|
||||
points = _handGrabPoints.Select(p => p.SaveData()).ToList(),
|
||||
scoringModifier = _scoringModifier
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the HandGrabInteractable with the serialized data version
|
||||
/// </summary>
|
||||
/// <param name="data">The serialized data for the HandGrabInteractable.</param>
|
||||
public void LoadData(HandGrabInteractableData data)
|
||||
{
|
||||
_snapType = data.snapType;
|
||||
_travelSpeed = data.travelSpeed;
|
||||
_scoringModifier = data.scoringModifier;
|
||||
foreach (HandGrabPointData pointData in data.points)
|
||||
{
|
||||
LoadPoint(pointData);
|
||||
}
|
||||
}
|
||||
|
||||
public HandGrabPoint LoadPoint(HandGrabPointData pointData)
|
||||
{
|
||||
HandGrabPoint point = CreatePoint();
|
||||
point.LoadData(pointData, this.RelativeTo);
|
||||
_handGrabPoints.Add(point);
|
||||
return point;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void ApplyVelocities(Vector3 linearVelocity, Vector3 angularVelocity)
|
||||
{
|
||||
if (_physicsObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_physicsObject.ApplyVelocities(linearVelocity, angularVelocity);
|
||||
}
|
||||
|
||||
public PoseTravelData CreateTravelData(in Pose from, in Pose to)
|
||||
{
|
||||
return new PoseTravelData(from, to, _travelSpeed);
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllHandGrabInteractable(Transform relativeTo, Rigidbody rigidbody,
|
||||
GrabTypeFlags supportedGrabTypes, GrabbingRule pinchGrabRules, GrabbingRule palmGrabRules,
|
||||
float travelSpeed, SnapType snapType)
|
||||
{
|
||||
InjectRelativeTo(relativeTo);
|
||||
InjectRigidbody(rigidbody);
|
||||
InjectTravelSpeed(travelSpeed);
|
||||
InjectSnapType(snapType);
|
||||
InjectSupportedGrabTypes(supportedGrabTypes);
|
||||
InjectPinchGrabRules(pinchGrabRules);
|
||||
InjectPalmGrabRules(palmGrabRules);
|
||||
}
|
||||
|
||||
public void InjectRelativeTo(Transform relativeTo)
|
||||
{
|
||||
_relativeTo = relativeTo;
|
||||
}
|
||||
|
||||
public void InjectRigidbody(Rigidbody rigidbody)
|
||||
{
|
||||
_rigidbody = rigidbody;
|
||||
}
|
||||
|
||||
public void InjectOptionalReleaseDistance(float releaseDistance)
|
||||
{
|
||||
_releaseDistance = releaseDistance;
|
||||
}
|
||||
|
||||
public void InjectSupportedGrabTypes(GrabTypeFlags supportedGrabTypes)
|
||||
{
|
||||
_supportedGrabTypes = supportedGrabTypes;
|
||||
}
|
||||
|
||||
public void InjectPinchGrabRules(GrabbingRule pinchGrabRules)
|
||||
{
|
||||
_pinchGrabRules = pinchGrabRules;
|
||||
}
|
||||
|
||||
public void InjectPalmGrabRules(GrabbingRule palmGrabRules)
|
||||
{
|
||||
_palmGrabRules = palmGrabRules;
|
||||
}
|
||||
|
||||
public void InjectOptionalPhysicsObject(PhysicsTransformable physicsObject)
|
||||
{
|
||||
_physicsObject = physicsObject;
|
||||
}
|
||||
|
||||
public void InjectSnapType(SnapType snapType)
|
||||
{
|
||||
_snapType = snapType;
|
||||
}
|
||||
|
||||
public void InjectTravelSpeed(float travelSpeed)
|
||||
{
|
||||
_travelSpeed = travelSpeed;
|
||||
}
|
||||
|
||||
public void InjectOptionalHandGrabPoints(List<HandGrabPoint> handGrabPoints)
|
||||
{
|
||||
_handGrabPoints = handGrabPoints;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region editor
|
||||
|
||||
protected virtual void OnDrawGizmos()
|
||||
{
|
||||
Gizmos.DrawIcon(this.transform.position, "sv_icon_dot10_pix16_gizmo");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9a7676b01585ce43908639a27765dfc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,56 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.HandPosing
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of HandGrabInteractable Data, to be used to store the information of several HandGrabInteractable
|
||||
/// so it survives Play-Mode Edit-Mode cycles.
|
||||
///
|
||||
/// Use this to store information once in Play-Mode (where Hand-tracking can be used)
|
||||
/// and then restore it forever at Edit-time.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "Oculus/Interaction/SDK/Pose Authoring/HandGrabInteractable Data Collection")]
|
||||
public class HandGrabInteractableDataCollection : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// The data-only version of the HandGrabInteractable to be restored.
|
||||
/// Do not modify this manually here unless you are sure of what you are doing, instead
|
||||
/// reload it at Edit-Mode and use the provided tools at the HandGrabInteractable.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
[Tooltip("Do not modify this manually unless you are sure! Instead load the HandGrabInteractable and use the tools provided.")]
|
||||
private List<HandGrabInteractableData> _interactablesData;
|
||||
|
||||
/// <summary>
|
||||
/// General getter for the data-only version of the HandGrabInteractable to be restored.
|
||||
/// </summary>
|
||||
public List<HandGrabInteractableData> InteractablesData => _interactablesData;
|
||||
|
||||
/// <summary>
|
||||
/// Register all the data into the Asset Database so it survives the Play-Mode shutdown.
|
||||
/// </summary>
|
||||
/// <param name="interactablesData"></param>
|
||||
public void StoreInteractables(List<HandGrabInteractableData> interactablesData)
|
||||
{
|
||||
_interactablesData = interactablesData;
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.AssetDatabase.Refresh();
|
||||
UnityEditor.EditorUtility.SetDirty(this);
|
||||
UnityEditor.AssetDatabase.SaveAssets();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c252863d3264814b8c34a1c4a2e762d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,61 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.HandPosing
|
||||
{
|
||||
/// <summary>
|
||||
/// Utilities class for HandGrab and DistanceHandGrab interaction common methods.
|
||||
/// </summary>
|
||||
public static class HandGrabInteractionUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Forces the release of an interactable when
|
||||
/// the distance to the grabber is big enough.
|
||||
/// </summary>
|
||||
/// <param name="snappable">The object to check for release</param>
|
||||
public static bool CheckReleaseDistance(ISnappable snappable, Vector3 centerPoint, float scaleModifier)
|
||||
{
|
||||
if (snappable == null
|
||||
|| snappable.ReleaseDistance <= 0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float closestSqrDist = float.MaxValue;
|
||||
Collider[] colliders = snappable.Colliders;
|
||||
foreach (Collider collider in colliders)
|
||||
{
|
||||
float sqrDistanceFromCenter = (collider.bounds.center - centerPoint).sqrMagnitude;
|
||||
closestSqrDist = Mathf.Min(closestSqrDist, sqrDistanceFromCenter);
|
||||
}
|
||||
|
||||
float scaledDistance = snappable.ReleaseDistance * scaleModifier;
|
||||
float sqrReleaseDistance = scaledDistance * scaledDistance;
|
||||
|
||||
return (closestSqrDist > sqrReleaseDistance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the normalized snapback time for a given grab event.
|
||||
/// It indicates how close in time we are from the given event.
|
||||
/// </summary>
|
||||
/// <param name="grabEventTime">Time at which the grab event initiated.</param>
|
||||
/// <param name="maxTime">Total time desired for the event. Used for normalization.</param>
|
||||
/// <returns>1 at the grab moment, 0 after _snapbackTime seconds or more</returns>
|
||||
public static float GetNormalizedEventTime(float grabEventTime, float maxTime)
|
||||
{
|
||||
return Mathf.Clamp01((Time.realtimeSinceStartup - grabEventTime) / maxTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3d6fe0ff3970eb4187eb5c7c6fbb1b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,455 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using Oculus.Interaction.Grab;
|
||||
using Oculus.Interaction.GrabAPI;
|
||||
using Oculus.Interaction.Input;
|
||||
using Oculus.Interaction.Throw;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction.HandPosing
|
||||
{
|
||||
using SnapAddress = SnapAddress<HandGrabInteractable>;
|
||||
|
||||
/// <summary>
|
||||
/// The HandGrabInteractor allows grabbing objects while having the hands snap to them
|
||||
/// adopting a previously authored HandPose.
|
||||
/// There are different snapping techniques available, and when None is selected it will
|
||||
/// behave as a normal GrabInteractor.
|
||||
/// </summary>
|
||||
public class HandGrabInteractor : Interactor<HandGrabInteractor, HandGrabInteractable>
|
||||
, ISnapper, IRigidbodyRef, IHandGrabInteractor
|
||||
{
|
||||
[SerializeField, Interface(typeof(IHand))]
|
||||
private MonoBehaviour _hand;
|
||||
|
||||
private IHand Hand { get; set; }
|
||||
|
||||
[SerializeField]
|
||||
private Rigidbody _rigidbody;
|
||||
|
||||
[SerializeField]
|
||||
private HandGrabAPI _handGrabApi;
|
||||
|
||||
[SerializeField]
|
||||
private GrabTypeFlags _supportedGrabTypes = GrabTypeFlags.All;
|
||||
|
||||
[SerializeField]
|
||||
private HandWristOffset _gripPoint;
|
||||
[SerializeField, Optional]
|
||||
private Transform _pinchPoint;
|
||||
|
||||
[FormerlySerializedAs("_optionalVelocityProviderMono")]
|
||||
[SerializeField, Interface(typeof(IVelocityCalculator)), Optional]
|
||||
private MonoBehaviour _velocityCalculator;
|
||||
public IVelocityCalculator VelocityCalculator { get; set; }
|
||||
|
||||
private SnapAddress _currentSnap = new SnapAddress();
|
||||
private HandPose _cachedBestHandPose = new HandPose();
|
||||
private Pose _cachedBestSnapPoint = new Pose();
|
||||
|
||||
protected bool _isTravelling;
|
||||
private PoseTravelData _travelInformation;
|
||||
private Pose _grabPose;
|
||||
|
||||
private Pose _wristToGripOffset;
|
||||
private Pose _gripToPinchOffset;
|
||||
private Pose _trackedGripPose;
|
||||
private Grab.HandGrabInteractableData _lastInteractableData = new Grab.HandGrabInteractableData();
|
||||
|
||||
public Pose GrabPose => _grabPose;
|
||||
|
||||
#region IHandGrabInteractor
|
||||
public HandGrabAPI HandGrabApi => _handGrabApi;
|
||||
public GrabTypeFlags SupportedGrabTypes => _supportedGrabTypes;
|
||||
public IHandGrabInteractable TargetInteractable => Interactable;
|
||||
#endregion
|
||||
|
||||
#region ISnapper
|
||||
|
||||
public virtual bool IsSnapping => HasSelectedInteractable && !_isTravelling;
|
||||
public float SnapStrength { get; private set; }
|
||||
|
||||
public HandFingerFlags SnappingFingers()
|
||||
{
|
||||
return HandGrab.GrabbingFingers(this, SelectedInteractable);
|
||||
}
|
||||
|
||||
public Pose WristToGripOffset => _wristToGripOffset;
|
||||
|
||||
public ISnapData SnapData { get; private set; }
|
||||
public System.Action<ISnapper> WhenSnapStarted { get; set; } = delegate { };
|
||||
public System.Action<ISnapper> WhenSnapEnded { get; set; } = delegate { };
|
||||
#endregion
|
||||
|
||||
#region IRigidbodyRef
|
||||
public Rigidbody Rigidbody => _rigidbody;
|
||||
#endregion
|
||||
|
||||
protected bool _started = false;
|
||||
|
||||
#region editor events
|
||||
protected virtual void Reset()
|
||||
{
|
||||
_hand = this.GetComponentInParent<IHand>() as MonoBehaviour;
|
||||
_handGrabApi = this.GetComponentInParent<HandGrabAPI>();
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
Hand = _hand as IHand;
|
||||
VelocityCalculator = _velocityCalculator as IVelocityCalculator;
|
||||
}
|
||||
|
||||
protected override void Start()
|
||||
{
|
||||
this.BeginStart(ref _started, base.Start);
|
||||
|
||||
Assert.IsNotNull(Rigidbody);
|
||||
Collider[] colliders = Rigidbody.GetComponentsInChildren<Collider>();
|
||||
Assert.IsTrue(colliders.Length > 0,
|
||||
"The associated Rigidbody must have at least one Collider.");
|
||||
foreach (Collider collider in colliders)
|
||||
{
|
||||
Assert.IsTrue(collider.isTrigger,
|
||||
"Associated Colliders must be marked as Triggers.");
|
||||
}
|
||||
Assert.IsNotNull(_handGrabApi);
|
||||
Assert.IsNotNull(Hand);
|
||||
if (_velocityCalculator != null)
|
||||
{
|
||||
Assert.IsNotNull(VelocityCalculator);
|
||||
}
|
||||
this.EndStart(ref _started);
|
||||
}
|
||||
|
||||
#region life cycle
|
||||
|
||||
/// <summary>
|
||||
/// During the update event, move the current interactor (containing also the
|
||||
/// trigger for detecting nearby interactableS) to the tracked position of the grip.
|
||||
///
|
||||
/// That is the tracked wrist plus a pregenerated position and rotation offset.
|
||||
/// </summary>
|
||||
protected override void DoEveryUpdate()
|
||||
{
|
||||
base.DoEveryUpdate();
|
||||
|
||||
_gripPoint.GetWorldPose(ref _trackedGripPose);
|
||||
_gripPoint.GetOffset(ref _wristToGripOffset);
|
||||
|
||||
this.transform.SetPose(_trackedGripPose);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Each call while the hand is selecting/grabbing an interactable, it moves the item to the
|
||||
/// new position while also attracting it towards the hand if the snapping mode requires it.
|
||||
///
|
||||
/// In some cases the parameter can be null, for example if the selection was interrupted
|
||||
/// by another hand grabbing the object. In those cases it will come out of the release
|
||||
/// state once the grabbing gesture properly finishes.
|
||||
/// </summary>
|
||||
/// <param name="interactable">The selected item</param>
|
||||
protected override void DoSelectUpdate(HandGrabInteractable interactable)
|
||||
{
|
||||
base.DoSelectUpdate(interactable);
|
||||
_isTravelling = false;
|
||||
|
||||
if (interactable == null)
|
||||
{
|
||||
_currentSnap.Clear();
|
||||
}
|
||||
|
||||
Pose grabbingPoint = GrabbingPoint(_currentSnap);
|
||||
if (CanTravel(_currentSnap))
|
||||
{
|
||||
_travelInformation.DestinationPose = grabbingPoint;
|
||||
bool travelFinished = _travelInformation.CurrentTravelPose(ref _grabPose);
|
||||
_isTravelling = !travelFinished;
|
||||
}
|
||||
else
|
||||
{
|
||||
_grabPose = grabbingPoint;
|
||||
}
|
||||
|
||||
if (!_isTravelling
|
||||
&& HandGrabInteractionUtilities.CheckReleaseDistance(interactable,
|
||||
grabbingPoint.position, Hand.Scale))
|
||||
{
|
||||
ShouldUnselect = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (interactable != null)
|
||||
{
|
||||
HandGrab.StoreGrabData(this, interactable, ref _lastInteractableData);
|
||||
ShouldUnselect = HandGrab.ComputeShouldUnselect(this, interactable);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShouldUnselect = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool CanTravel(SnapAddress snap)
|
||||
{
|
||||
return !SnapAddress.IsNullOrInvalid(snap)
|
||||
&& (snap.SnapType == SnapType.HandToObjectAndReturn
|
||||
|| snap.SnapType == SnapType.ObjectToHand);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Each call while the interactor is hovering, it checks whether there is an interaction
|
||||
/// being hovered and sets the target snapping address to it. In the HandToObject snapping
|
||||
/// behaviors this is relevant as the hand can approach the object progressively even before
|
||||
/// a true grab starts.
|
||||
/// </summary>
|
||||
protected override void DoHoverUpdate()
|
||||
{
|
||||
base.DoHoverUpdate();
|
||||
if (_currentSnap.IsValidAddress)
|
||||
{
|
||||
SnapStrength = HandGrab.ComputeHoverStrength(this, Interactable,
|
||||
out GrabTypeFlags hoverGrabTypes);
|
||||
SnapData = _currentSnap;
|
||||
}
|
||||
else
|
||||
{
|
||||
SnapStrength = 0f;
|
||||
SnapData = null;
|
||||
}
|
||||
|
||||
if (Interactable != null)
|
||||
{
|
||||
ShouldSelect = HandGrab.ComputeShouldSelect(this, Interactable,
|
||||
out GrabTypeFlags selectingGrabTypes);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Select()
|
||||
{
|
||||
PrepareStartGrab(_currentSnap);
|
||||
base.Select();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a new interactable is selected, start the grab at the ideal point. When snapping is
|
||||
/// involved that can be a point in the interactable offset from the hand
|
||||
/// which will be stored to progressively reduced it in the next updates,
|
||||
/// effectively attracting the object towards the hand.
|
||||
/// When no snapping is involved the point will be the grip point of the hand directly.
|
||||
/// Note: ideally this code would be in InteractableSelected but it needs
|
||||
/// to be called before the object is marked as active.
|
||||
/// </summary>
|
||||
/// <param name="snap">The selected Snap Data </param>
|
||||
private void PrepareStartGrab(SnapAddress snap)
|
||||
{
|
||||
if (SnapAddress.IsNullOrInvalid(snap))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Pose grabbingPoint = _trackedGripPose;
|
||||
Pose pinchPose = _pinchPoint.GetPose();
|
||||
_gripToPinchOffset = PoseUtils.RelativeOffset(pinchPose, _trackedGripPose);
|
||||
if (snap.SnappedToPinch)
|
||||
{
|
||||
grabbingPoint = pinchPose;
|
||||
}
|
||||
|
||||
Pose originalGrabPose = snap.Interactable.RelativeTo.GlobalPose(snap.SnapPoint);
|
||||
_travelInformation = snap.Interactable.CreateTravelData(originalGrabPose, grabbingPoint);
|
||||
|
||||
if (CanTravel(snap))
|
||||
{
|
||||
_grabPose.CopyFrom(originalGrabPose);
|
||||
}
|
||||
else
|
||||
{
|
||||
_grabPose = grabbingPoint;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When releasing an active interactable, calculate the releasing point in similar
|
||||
/// fashion to InteractableSelected
|
||||
/// </summary>
|
||||
/// <param name="interactable">The released interactable</param>
|
||||
protected override void InteractableUnselected(HandGrabInteractable interactable)
|
||||
{
|
||||
base.InteractableUnselected(interactable);
|
||||
if (interactable == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReleaseVelocityInformation throwVelocity = VelocityCalculator != null ?
|
||||
VelocityCalculator.CalculateThrowVelocity(interactable.transform) :
|
||||
new ReleaseVelocityInformation(Vector3.zero, Vector3.zero, Vector3.zero);
|
||||
interactable.ApplyVelocities(throwVelocity.LinearVelocity, throwVelocity.AngularVelocity);
|
||||
}
|
||||
|
||||
protected override void InteractableSet(HandGrabInteractable interactable)
|
||||
{
|
||||
base.InteractableSet(interactable);
|
||||
WhenSnapStarted.Invoke(this);
|
||||
}
|
||||
|
||||
protected override void InteractableUnset(HandGrabInteractable interactable)
|
||||
{
|
||||
base.InteractableUnset(interactable);
|
||||
WhenSnapEnded.Invoke(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region grab detection
|
||||
|
||||
private bool CanSnapToPinchPoint(HandGrabInteractable interactable, GrabTypeFlags grabTypes)
|
||||
{
|
||||
return _pinchPoint != null
|
||||
&& !interactable.UsesHandPose()
|
||||
&& (grabTypes & GrabTypeFlags.Pinch) != 0;
|
||||
}
|
||||
|
||||
private Pose GrabbingPoint(SnapAddress snapAddress)
|
||||
{
|
||||
if (snapAddress.SnappedToPinch)
|
||||
{
|
||||
return PoseUtils.Multiply(_trackedGripPose, _gripToPinchOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _trackedGripPose;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Compute the best interactable to snap to. In order to do it the method measures
|
||||
/// the score from the current grip pose to the closes pose in the surfaces
|
||||
/// of each one of the interactables in the registry.
|
||||
/// Even though it returns the best interactable, it also saves the entire SnapAddress to
|
||||
/// it in which the exact pose within the surface is already recorded to avoid recalculations
|
||||
/// within the same frame.
|
||||
/// </summary>
|
||||
/// <returns>The best interactable to snap the hand to.</returns>
|
||||
protected override HandGrabInteractable ComputeCandidate()
|
||||
{
|
||||
ComputeBestSnapAddress(ref _currentSnap);
|
||||
return _currentSnap.Interactable;
|
||||
}
|
||||
|
||||
protected virtual void ComputeBestSnapAddress(ref SnapAddress snapAddress)
|
||||
{
|
||||
IEnumerable<HandGrabInteractable> interactables = HandGrabInteractable.Registry.List(this);
|
||||
float bestFingerScore = -1f;
|
||||
float bestPoseScore = -1f;
|
||||
|
||||
foreach (HandGrabInteractable interactable in interactables)
|
||||
{
|
||||
float fingerScore = 1.0f;
|
||||
if (!HandGrab.ComputeShouldSelect(this, interactable, out GrabTypeFlags selectingGrabTypes))
|
||||
{
|
||||
fingerScore = HandGrab.ComputeHoverStrength(this, interactable, out selectingGrabTypes);
|
||||
}
|
||||
if (fingerScore < bestFingerScore)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool usePinchPoint = CanSnapToPinchPoint(interactable, selectingGrabTypes);
|
||||
Pose grabPoint = usePinchPoint ? _pinchPoint.GetPose() : _trackedGripPose;
|
||||
bool poseFound = interactable.CalculateBestPose(grabPoint, Hand.Scale, Hand.Handedness,
|
||||
ref _cachedBestHandPose, ref _cachedBestSnapPoint,
|
||||
out bool usesHandPose, out float poseScore);
|
||||
|
||||
if (!poseFound)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fingerScore > bestFingerScore
|
||||
|| poseScore > bestPoseScore)
|
||||
{
|
||||
bestFingerScore = fingerScore;
|
||||
bestPoseScore = poseScore;
|
||||
HandPose handPose = usesHandPose ? _cachedBestHandPose : null;
|
||||
snapAddress.Set(interactable, handPose, _cachedBestSnapPoint, usePinchPoint);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (bestFingerScore < 0)
|
||||
{
|
||||
snapAddress.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllHandGrabInteractor(HandGrabAPI handGrabApi,
|
||||
IHand hand, Rigidbody rigidbody, GrabTypeFlags supportedGrabTypes, HandWristOffset gripPoint)
|
||||
{
|
||||
InjectHandGrabApi(handGrabApi);
|
||||
InjectHand(hand);
|
||||
InjectRigidbody(rigidbody);
|
||||
InjectSupportedGrabTypes(supportedGrabTypes);
|
||||
InjectGripPoint(gripPoint);
|
||||
}
|
||||
|
||||
public void InjectHandGrabApi(HandGrabAPI handGrabAPI)
|
||||
{
|
||||
_handGrabApi = handGrabAPI;
|
||||
}
|
||||
|
||||
public void InjectHand(IHand hand)
|
||||
{
|
||||
_hand = hand as MonoBehaviour;
|
||||
Hand = hand;
|
||||
}
|
||||
|
||||
public void InjectRigidbody(Rigidbody rigidbody)
|
||||
{
|
||||
_rigidbody = rigidbody;
|
||||
}
|
||||
|
||||
public void InjectSupportedGrabTypes(GrabTypeFlags supportedGrabTypes)
|
||||
{
|
||||
_supportedGrabTypes = supportedGrabTypes;
|
||||
}
|
||||
|
||||
public void InjectGripPoint(HandWristOffset gripPoint)
|
||||
{
|
||||
_gripPoint = gripPoint;
|
||||
}
|
||||
|
||||
public void InjectOptionalPinchPoint(Transform pinchPoint)
|
||||
{
|
||||
_pinchPoint = pinchPoint;
|
||||
}
|
||||
|
||||
public void InjectOptionalVelocityCalculator(IVelocityCalculator velocityCalculator)
|
||||
{
|
||||
_velocityCalculator = velocityCalculator as MonoBehaviour;
|
||||
VelocityCalculator = velocityCalculator;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9067480af55f5874d8f613b16812f968
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,197 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using Oculus.Interaction.HandPosing.SnapSurfaces;
|
||||
using Oculus.Interaction.Input;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
using UnityEngine.Assertions;
|
||||
|
||||
namespace Oculus.Interaction.HandPosing
|
||||
{
|
||||
[System.Serializable]
|
||||
public struct HandGrabPointData
|
||||
{
|
||||
public Pose gripPose;
|
||||
public HandPose handPose;
|
||||
public float scale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used in conjunction with HandGrabInteractable or DistanceHandGrabInteractable,
|
||||
/// the HandGrabPoint defines the local point in an object to which the grip point
|
||||
/// of the hand should align. It can also contain information about the final pose
|
||||
/// of the hand for perfect alignment as well as a surface that indicates the valid
|
||||
/// positions for the point.
|
||||
/// </summary>
|
||||
public class HandGrabPoint : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private Transform _relativeTo;
|
||||
|
||||
[SerializeField, Optional, Interface(typeof(ISnapSurface))]
|
||||
private MonoBehaviour _surface = null;
|
||||
private ISnapSurface _snapSurface;
|
||||
public ISnapSurface SnapSurface
|
||||
{
|
||||
get => _snapSurface ?? _surface as ISnapSurface;
|
||||
private set
|
||||
{
|
||||
_snapSurface = value;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private bool _usesHandPose = true;
|
||||
|
||||
[SerializeField, Optional]
|
||||
[HideInInspector]
|
||||
private HandPose _handPose = new HandPose();
|
||||
|
||||
public HandPose HandPose => _usesHandPose ? _handPose : null;
|
||||
public float Scale => this.transform.lossyScale.x;
|
||||
public Transform RelativeTo { get => _relativeTo; set => _relativeTo = value; }
|
||||
public Pose RelativeGrip => RelativeTo.RelativeOffset(this.transform);
|
||||
|
||||
#region editor events
|
||||
|
||||
protected virtual void Reset()
|
||||
{
|
||||
_relativeTo = this.GetComponentInParent<HandGrabInteractable>()?.RelativeTo;
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
Assert.IsNotNull(_relativeTo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the given position/rotation to the HandGrabPoint
|
||||
/// </summary>
|
||||
/// <param name="handPose">Relative hand position/rotation.</param>
|
||||
/// <param name="relativeTo">Reference coordinates for the pose.</param>
|
||||
public void SetPose(HandPose handPose, in Pose gripPoint, Transform relativeTo)
|
||||
{
|
||||
_handPose = new HandPose(handPose);
|
||||
_relativeTo = relativeTo;
|
||||
this.transform.SetPose(relativeTo.GlobalPose(gripPoint));
|
||||
}
|
||||
|
||||
public HandGrabPointData SaveData()
|
||||
{
|
||||
HandGrabPointData data = new HandGrabPointData()
|
||||
{
|
||||
handPose = new HandPose(_handPose),
|
||||
scale = Scale,
|
||||
gripPose = _relativeTo.RelativeOffset(this.transform)
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public void LoadData(HandGrabPointData data, Transform relativeTo)
|
||||
{
|
||||
_relativeTo = relativeTo;
|
||||
this.transform.localScale = Vector3.one * data.scale;
|
||||
this.transform.SetPose(_relativeTo.GlobalPose(data.gripPose));
|
||||
if (data.handPose != null)
|
||||
{
|
||||
_handPose.CopyFrom(data.handPose);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool CalculateBestPose(Pose userPose, Handedness handedness,
|
||||
ref HandPose bestHandPose, ref Pose bestSnapPoint, in PoseMeasureParameters scoringModifier,
|
||||
out bool usesHandPose, out float score)
|
||||
{
|
||||
usesHandPose = false;
|
||||
if (HandPose != null && HandPose.Handedness != handedness)
|
||||
{
|
||||
score = float.NaN;
|
||||
return false;
|
||||
}
|
||||
|
||||
score = CompareNearPoses(userPose, ref bestSnapPoint, scoringModifier);
|
||||
if (HandPose != null)
|
||||
{
|
||||
usesHandPose = true;
|
||||
bestHandPose.CopyFrom(HandPose);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the most similar pose at this HandGrabInteractable to the user hand pose
|
||||
/// </summary>
|
||||
/// <param name="worldPoint">The user current hand pose.</param>
|
||||
/// <param name="bestSnapPoint">The snap point hand pose within the surface (if any).</param>
|
||||
/// <returns>The adjusted best pose at the surface.</returns>
|
||||
private float CompareNearPoses(in Pose worldPoint, ref Pose bestSnapPoint, in PoseMeasureParameters scoringModifier)
|
||||
{
|
||||
Pose desired = worldPoint;
|
||||
Pose snap = this.transform.GetPose();
|
||||
|
||||
float bestScore;
|
||||
Pose bestPlace;
|
||||
if (SnapSurface != null)
|
||||
{
|
||||
bestScore = SnapSurface.CalculateBestPoseAtSurface(desired, snap, out bestPlace, scoringModifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
bestPlace = snap;
|
||||
bestScore = PoseUtils.Similarity(desired, snap, scoringModifier);
|
||||
}
|
||||
|
||||
_relativeTo.RelativeOffset(bestPlace, ref bestSnapPoint);
|
||||
|
||||
return bestScore;
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectRelativeTo(Transform relativeTo)
|
||||
{
|
||||
_relativeTo = relativeTo;
|
||||
}
|
||||
|
||||
public void InjectOptionalSurface(ISnapSurface surface)
|
||||
{
|
||||
_surface = surface as MonoBehaviour;
|
||||
SnapSurface = surface;
|
||||
}
|
||||
|
||||
public void InjectOptionalHandPose(HandPose handPose)
|
||||
{
|
||||
_handPose = handPose;
|
||||
_usesHandPose = _handPose != null;
|
||||
}
|
||||
|
||||
public void InjectAllHandGrabPoint(Transform relativeTo)
|
||||
{
|
||||
InjectRelativeTo(relativeTo);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region editor
|
||||
|
||||
protected virtual void OnDrawGizmos()
|
||||
{
|
||||
Gizmos.DrawIcon(this.transform.position, "sv_icon_dot2_sml");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 622db80eec0bd344f920b1aa056ae8d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,144 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using Oculus.Interaction.Input;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction.HandPosing
|
||||
{
|
||||
/// <summary>
|
||||
/// Data for the pose of a hand for grabbing an object.
|
||||
/// It contains not only the position of the hand and the fingers but
|
||||
/// other relevant data to the pose like the scale or which
|
||||
/// fingers are locked.
|
||||
///
|
||||
/// Even though this class is Serializable it is not a Component.
|
||||
/// The HandPoseEditor class should be used
|
||||
/// (in conjunction with the HandGrabInteractableEditor class)
|
||||
/// to edit the values in the inspector.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class HandPose
|
||||
{
|
||||
[SerializeField]
|
||||
private Handedness _handedness;
|
||||
|
||||
[SerializeField]
|
||||
private JointFreedom[] _fingersFreedom = FingersMetadata.DefaultFingersFreedom();
|
||||
|
||||
[SerializeField]
|
||||
private Quaternion[] _jointRotations = new Quaternion[FingersMetadata.HAND_JOINT_IDS.Length];
|
||||
|
||||
/// <summary>
|
||||
/// Handedness of the hand.
|
||||
/// </summary>
|
||||
public Handedness Handedness
|
||||
{
|
||||
get => _handedness;
|
||||
set => _handedness = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collection of joints and their rotations in this hand.
|
||||
/// It follows the FingersMetadata.HAND_JOINT_IDS convention
|
||||
/// </summary>
|
||||
public Quaternion[] JointRotations
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_jointRotations == null
|
||||
|| _jointRotations.Length == 0)
|
||||
{
|
||||
_jointRotations = new Quaternion[FingersMetadata.HAND_JOINT_IDS.Length];
|
||||
}
|
||||
return _jointRotations;
|
||||
}
|
||||
set
|
||||
{
|
||||
_jointRotations = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates which fingers can be locked, constrained or free in this
|
||||
/// hand pose.
|
||||
/// It follows the Hand.HandFinger order for the collection.
|
||||
/// </summary>
|
||||
public JointFreedom[] FingersFreedom
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_fingersFreedom == null
|
||||
|| _fingersFreedom.Length == 0)
|
||||
{
|
||||
_fingersFreedom = FingersMetadata.DefaultFingersFreedom();
|
||||
}
|
||||
return _fingersFreedom;
|
||||
}
|
||||
}
|
||||
|
||||
public HandPose()
|
||||
{
|
||||
}
|
||||
|
||||
public HandPose(HandPose other)
|
||||
{
|
||||
this.CopyFrom(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the values over to the hand pose
|
||||
/// without requiring any new allocations.
|
||||
///
|
||||
/// This is thanks to the fact of the fingers freedom
|
||||
/// and joints rotations collections being always
|
||||
/// fixed size and order.
|
||||
/// </summary>
|
||||
/// <param name="from">The hand pose to copy the values from</param>
|
||||
public void CopyFrom(HandPose from)
|
||||
{
|
||||
this.Handedness = from.Handedness;
|
||||
for (int i = 0; i < Constants.NUM_FINGERS; i++)
|
||||
{
|
||||
this.FingersFreedom[i] = from.FingersFreedom[i];
|
||||
}
|
||||
for (int i = 0; i < FingersMetadata.HAND_JOINT_IDS.Length; i++)
|
||||
{
|
||||
this.JointRotations[i] = from.JointRotations[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interpolates between two HandPoses, if they have the same handedness and joints.
|
||||
/// </summary>
|
||||
/// <param name="from">Base HandPose to interpolate from.</param>
|
||||
/// <param name="to">Target HandPose to interpolate to.</param>
|
||||
/// <param name="t">Interpolation factor, 0 for the base, 1 for the target.</param>
|
||||
/// <param name="result">A HandPose positioned/rotated between the base and target.</param>
|
||||
public static void Lerp(in HandPose from, in HandPose to, float t, ref HandPose result)
|
||||
{
|
||||
for (int i = 0; i < FingersMetadata.HAND_JOINT_IDS.Length; i++)
|
||||
{
|
||||
result.JointRotations[i] = Quaternion.SlerpUnclamped(from.JointRotations[i], to.JointRotations[i], t);
|
||||
}
|
||||
|
||||
HandPose dominantPose = t <= 0.5f? from : to;
|
||||
result.Handedness = dominantPose.Handedness;
|
||||
for (int i = 0; i < Constants.NUM_FINGERS; i++)
|
||||
{
|
||||
result.FingersFreedom[i] = dominantPose.FingersFreedom[i];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eed3cf2e4531a9b469816dbddc576517
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,84 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using Oculus.Interaction.Input;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.HandPosing
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Defines the strategy for aligning the object to the hand.
|
||||
/// The hand can go towards the object or vice-versa.
|
||||
/// </summary>
|
||||
public enum SnapType
|
||||
{
|
||||
/// <summary>
|
||||
/// Move the object towards the hand after the user starts a grab
|
||||
/// </summary>
|
||||
ObjectToHand,
|
||||
/// <summary>
|
||||
/// Move the hand towards the object and stay
|
||||
/// </summary>
|
||||
HandToObject,
|
||||
/// <summary>
|
||||
/// Move the hand towards the object and return to the original position
|
||||
/// </summary>
|
||||
HandToObjectAndReturn,
|
||||
/// <summary>
|
||||
/// Keeps both object and hand at the same distance while grabbed
|
||||
/// </summary>
|
||||
None
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for interactors that allow aligning to an object.
|
||||
/// Contains information to drive the HandGrabVisual moving
|
||||
/// the fingers and wrist.
|
||||
/// </summary>
|
||||
public interface ISnapper
|
||||
{
|
||||
bool IsSnapping { get; }
|
||||
float SnapStrength { get; }
|
||||
Pose WristToGripOffset { get; }
|
||||
HandFingerFlags SnappingFingers();
|
||||
ISnapData SnapData { get; }
|
||||
System.Action<ISnapper> WhenSnapStarted { get; set; }
|
||||
System.Action<ISnapper> WhenSnapEnded { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for interactables that allow
|
||||
/// being visually aligned to by an interactor.
|
||||
/// </summary>
|
||||
public interface ISnappable
|
||||
{
|
||||
Transform RelativeTo { get; }
|
||||
SnapType SnapType { get; }
|
||||
|
||||
Collider[] Colliders { get; }
|
||||
float ReleaseDistance { get; }
|
||||
|
||||
bool UsesHandPose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface containing specific data regarding
|
||||
/// the pose of the hand as it aligns to an ISnappable
|
||||
/// </summary>
|
||||
public interface ISnapData
|
||||
{
|
||||
SnapType SnapType { get; }
|
||||
HandPose HandPose { get; }
|
||||
Pose WorldSnapPose { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b03f0442bba98f4d99df1f95e1488a5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,38 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.HandPosing
|
||||
{
|
||||
[Serializable]
|
||||
public struct PoseMeasureParameters
|
||||
{
|
||||
[SerializeField]
|
||||
[Min(0f)]
|
||||
private float _maxDistance;
|
||||
|
||||
[SerializeField]
|
||||
[Range(0f, 1f)]
|
||||
private float _positionRotationWeight;
|
||||
|
||||
public float MaxDistance => _maxDistance;
|
||||
public float PositionRotationWeight => _positionRotationWeight;
|
||||
|
||||
public PoseMeasureParameters(float maxDistance, float positionRotationWeight)
|
||||
{
|
||||
_maxDistance = maxDistance;
|
||||
_positionRotationWeight = positionRotationWeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6cf5fb44c93da2d4387ee22e7d4e93f4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,59 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.HandPosing
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility struct containing the information and calculations
|
||||
/// to move a pose in space using an AnimationCurve at a given speed.
|
||||
/// Unifies code in the HandGrab and DistanceHandGrab interactors.
|
||||
/// </summary>
|
||||
public struct PoseTravelData
|
||||
{
|
||||
private const float DEGREES_TO_PERCEIVED_METERS = 0.1f / 360f;
|
||||
|
||||
private float _startTime;
|
||||
private float _totalTime;
|
||||
private Pose _sourcePose;
|
||||
private AnimationCurve _easingCurve;
|
||||
|
||||
public Pose DestinationPose { private get; set; }
|
||||
|
||||
public PoseTravelData(in Pose from, in Pose to, float speed, AnimationCurve curve = null)
|
||||
{
|
||||
_startTime = Time.realtimeSinceStartup;
|
||||
_sourcePose = from;
|
||||
_easingCurve = curve;
|
||||
|
||||
DestinationPose = to;
|
||||
|
||||
Pose grabOffset = PoseUtils.RelativeOffset(from, to);
|
||||
float travelDistance = Mathf.Max(grabOffset.position.magnitude,
|
||||
(grabOffset.rotation.eulerAngles * DEGREES_TO_PERCEIVED_METERS).magnitude);
|
||||
_totalTime = travelDistance / speed;
|
||||
}
|
||||
|
||||
public bool CurrentTravelPose(ref Pose currentTravelPose)
|
||||
{
|
||||
float animationProgress = HandGrabInteractionUtilities.GetNormalizedEventTime(_startTime, _totalTime);
|
||||
bool isCompleted = animationProgress >= 1f;
|
||||
if (_easingCurve != null)
|
||||
{
|
||||
animationProgress = _easingCurve.Evaluate(animationProgress);
|
||||
}
|
||||
PoseUtils.Lerp(_sourcePose, DestinationPose, animationProgress, ref currentTravelPose);
|
||||
return isCompleted;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f94400e421365684090f0a34f9fe5514
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,64 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Oculus.Interaction.HandPosing
|
||||
{
|
||||
/// <summary>
|
||||
/// All the relevant data needed for a snapping position.
|
||||
/// This includes the HandGrabInteractor to (which can have a volume)
|
||||
/// and the best Pose within that volume if any available
|
||||
/// </summary>
|
||||
public class SnapAddress<TSnappable> : ISnapData
|
||||
where TSnappable : class, ISnappable
|
||||
{
|
||||
public TSnappable Interactable { get; set; }
|
||||
public ref Pose SnapPoint => ref _snapPoint;
|
||||
public bool IsValidAddress => Interactable != null;
|
||||
|
||||
public HandPose HandPose => _isHandPoseValid ? _handPose : null;
|
||||
|
||||
public Pose WorldSnapPose => Interactable.RelativeTo.GlobalPose(SnapPoint);
|
||||
public SnapType SnapType => Interactable != null ? Interactable.SnapType : SnapType.None;
|
||||
public bool SnappedToPinch { get; private set; }
|
||||
|
||||
private bool _isHandPoseValid = false;
|
||||
private HandPose _handPose = new HandPose();
|
||||
private Pose _snapPoint;
|
||||
|
||||
public void Set(TSnappable snapInteractable, HandPose pose, in Pose snapPoint, bool usedPinchPoint)
|
||||
{
|
||||
Interactable = snapInteractable;
|
||||
SnappedToPinch = usedPinchPoint;
|
||||
|
||||
_snapPoint.CopyFrom(snapPoint);
|
||||
_isHandPoseValid = pose != null;
|
||||
if (_isHandPoseValid)
|
||||
{
|
||||
_handPose.CopyFrom(pose);
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Interactable = null;
|
||||
SnappedToPinch = false;
|
||||
_isHandPoseValid = false;
|
||||
}
|
||||
|
||||
public static bool IsNullOrInvalid(SnapAddress<TSnappable> snapAddress)
|
||||
{
|
||||
return snapAddress == null || !snapAddress.IsValidAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3abe3ff74adb27a44a07a06780b1edb2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b895f4f5c93bb84293290ad3dad231f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,158 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
public class PokeInteractable : Interactable<PokeInteractor, PokeInteractable>, IPointable
|
||||
{
|
||||
[SerializeField, Interface(typeof(IProximityField))]
|
||||
private MonoBehaviour _proximityField;
|
||||
public IProximityField ProximityField;
|
||||
|
||||
[SerializeField]
|
||||
// The plane "forward" direction should be towards negative Z (to maintain parity with Canvas)
|
||||
private Transform _triggerPlaneTransform;
|
||||
public Transform TriggerPlaneTransform => _triggerPlaneTransform;
|
||||
|
||||
[SerializeField]
|
||||
private float _maxDistance = 0.1f;
|
||||
public float MaxDistance => _maxDistance;
|
||||
|
||||
[SerializeField]
|
||||
private float _releaseDistance = 0.25f;
|
||||
public float ReleaseDistance => _releaseDistance;
|
||||
|
||||
[SerializeField]
|
||||
private float _horizontalDragThreshold = 0.0f;
|
||||
public float HorizontalDragThreshold => _horizontalDragThreshold;
|
||||
|
||||
[SerializeField]
|
||||
private float _verticalDragThreshold = 0.0f;
|
||||
public float VerticalDragThreshold => _verticalDragThreshold;
|
||||
|
||||
[SerializeField, Optional]
|
||||
private Collider _volumeMask = null;
|
||||
public Collider VolumeMask { get => _volumeMask; }
|
||||
|
||||
public event Action<PointerArgs> OnPointerEvent = delegate { };
|
||||
private PointableDelegate<PokeInteractor> _pointableDelegate;
|
||||
|
||||
protected bool _started = false;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
ProximityField = _proximityField as IProximityField;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
this.BeginStart(ref _started);
|
||||
Assert.IsNotNull(_triggerPlaneTransform);
|
||||
Assert.IsNotNull(ProximityField);
|
||||
|
||||
_pointableDelegate = new PointableDelegate<PokeInteractor>(this, ComputePointer);
|
||||
this.EndStart(ref _started);
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
if (_started)
|
||||
{
|
||||
_pointableDelegate.OnPointerEvent += InvokePointerEvent;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
_pointableDelegate.OnPointerEvent -= InvokePointerEvent;
|
||||
}
|
||||
|
||||
base.OnDisable();
|
||||
}
|
||||
|
||||
public Vector3 ComputeClosestPoint(Vector3 point)
|
||||
{
|
||||
return ProximityField.ComputeClosestPoint(point);
|
||||
}
|
||||
|
||||
private void ComputePointer(PokeInteractor pokeInteractor, out Vector3 position, out Quaternion rotation)
|
||||
{
|
||||
position = pokeInteractor.TouchPoint;
|
||||
rotation = _triggerPlaneTransform.rotation;
|
||||
}
|
||||
|
||||
private void InvokePointerEvent(PointerArgs args)
|
||||
{
|
||||
OnPointerEvent(args);
|
||||
}
|
||||
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
_pointableDelegate = null;
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllPokeInteractable(Transform triggerPlaneTransform,
|
||||
IProximityField proximityField)
|
||||
{
|
||||
InjectTriggerPlaneTransform(triggerPlaneTransform);
|
||||
InjectProximityField(proximityField);
|
||||
}
|
||||
|
||||
public void InjectTriggerPlaneTransform(Transform triggerPlaneTransform)
|
||||
{
|
||||
_triggerPlaneTransform = triggerPlaneTransform;
|
||||
}
|
||||
|
||||
public void InjectProximityField(IProximityField proximityField)
|
||||
{
|
||||
_proximityField = proximityField as MonoBehaviour;
|
||||
ProximityField = proximityField;
|
||||
}
|
||||
|
||||
public void InjectOptionalMaxDistance(float maxDistance)
|
||||
{
|
||||
_maxDistance = maxDistance;
|
||||
}
|
||||
|
||||
public void InjectOptionalReleaseDistance(float releaseDistance)
|
||||
{
|
||||
_releaseDistance = releaseDistance;
|
||||
}
|
||||
|
||||
public void InjectHorizontalDragThreshold(float horizontalDragThreshold)
|
||||
{
|
||||
_horizontalDragThreshold = horizontalDragThreshold;
|
||||
}
|
||||
|
||||
public void InjectVerticalDragThreshold(float verticalDragThreshold)
|
||||
{
|
||||
_verticalDragThreshold = verticalDragThreshold;
|
||||
}
|
||||
|
||||
public void InjectOptionalVolumeMask(Collider volumeMask)
|
||||
{
|
||||
_volumeMask = volumeMask;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 317e663e2bb60ea408fe22b908b59295
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,360 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a near-poke interaction that is driven by a near-distance
|
||||
/// proximity computation and a raycast between the position
|
||||
/// recorded across two frames against a target plane.
|
||||
/// </summary>
|
||||
public class PokeInteractor : Interactor<PokeInteractor, PokeInteractable>
|
||||
{
|
||||
[SerializeField]
|
||||
private Transform _pointTransform;
|
||||
|
||||
[SerializeField]
|
||||
private float _touchReleaseThreshold = 0.002f;
|
||||
|
||||
[SerializeField]
|
||||
private ProgressCurve _dragStartCurve;
|
||||
|
||||
private Vector3 _prevPosition = new Vector3();
|
||||
private PokeInteractable _hitInteractable = null;
|
||||
public Vector3 ClosestPoint { get; private set; }
|
||||
|
||||
private Vector3 _currentPosition;
|
||||
public Vector3 Origin => _currentPosition;
|
||||
|
||||
private Vector3 _touchPoint;
|
||||
public Vector3 TouchPoint => _touchPoint;
|
||||
|
||||
private Vector3 _previousTouchPoint;
|
||||
private Vector3 _capturedTouchPoint;
|
||||
|
||||
private bool _dragging;
|
||||
private Vector3 _lateralDelta;
|
||||
private Vector3 _startDragOffset;
|
||||
|
||||
protected override void Start()
|
||||
{
|
||||
base.Start();
|
||||
Assert.IsNotNull(_pointTransform);
|
||||
}
|
||||
|
||||
protected override void DoEveryUpdate()
|
||||
{
|
||||
_prevPosition = _currentPosition;
|
||||
_currentPosition = _pointTransform.position;
|
||||
}
|
||||
|
||||
protected override void DoHoverUpdate()
|
||||
{
|
||||
if (_interactable != null)
|
||||
{
|
||||
_touchPoint = _interactable.ComputeClosestPoint(Origin);
|
||||
}
|
||||
|
||||
if (_hitInteractable != null)
|
||||
{
|
||||
_hitInteractable = null;
|
||||
ShouldSelect = true;
|
||||
_dragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override PokeInteractable ComputeCandidate()
|
||||
{
|
||||
// First, see if we trigger a press on any interactable
|
||||
PokeInteractable closestInteractable = ComputeSelectCandidate();
|
||||
if (closestInteractable != null)
|
||||
{
|
||||
// We have found an active hit target, so we return it
|
||||
_hitInteractable = closestInteractable;
|
||||
return _hitInteractable;
|
||||
}
|
||||
|
||||
// Otherwise we have no active interactable, so we do a proximity-only check for
|
||||
// closest hovered interactable (above the trigger plane)
|
||||
closestInteractable = ComputeBestHoverInteractable();
|
||||
|
||||
return closestInteractable;
|
||||
}
|
||||
|
||||
private PokeInteractable ComputeSelectCandidate()
|
||||
{
|
||||
PokeInteractable closestInteractable = null;
|
||||
float closestSqrDist = float.MaxValue;
|
||||
|
||||
IEnumerable<PokeInteractable> interactables = PokeInteractable.Registry.List(this);
|
||||
|
||||
Vector3 direction = _currentPosition - _prevPosition;
|
||||
float magnitude = direction.magnitude;
|
||||
if (magnitude == 0f)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
direction /= magnitude;
|
||||
Ray ray = new Ray(_prevPosition, direction);
|
||||
|
||||
// Check the trigger plane first as a movement through this will
|
||||
// automatically put us in a "active" state. We expect the raycast
|
||||
// to happen only in one direction
|
||||
foreach (PokeInteractable interactable in interactables)
|
||||
{
|
||||
Transform triggerPlaneTransform = interactable.TriggerPlaneTransform;
|
||||
|
||||
// First check that we are moving towards the trigger plane by checking
|
||||
// the direction of our position delta with the forward direction of the trigger transform.
|
||||
// This is to not allow presses from "behind" the trigger plane.
|
||||
|
||||
// We consider the plane "forward" normal to be z = -1 (to maintain parity with Canvas)
|
||||
if (Vector3.Dot(direction, triggerPlaneTransform.transform.forward) > 0f)
|
||||
{
|
||||
// Then do a raycast against the trigger plane defined by the trigger transform
|
||||
Plane triggerPlane = new Plane(-1f * triggerPlaneTransform.forward,
|
||||
triggerPlaneTransform.position);
|
||||
|
||||
float hitDistance;
|
||||
bool hit = triggerPlane.Raycast(ray, out hitDistance);
|
||||
if (hit && hitDistance <= magnitude)
|
||||
{
|
||||
// We collided against the trigger plane and now we must rank this
|
||||
// interactable versus others that also pass this test this frame
|
||||
// but may be at a closer proximity. For this we use the closest
|
||||
// point compute against the plane intersection point
|
||||
Vector3 planePoint = ray.GetPoint(hitDistance);
|
||||
|
||||
// Check if our collision lies outside of the optional volume mask
|
||||
if (interactable.VolumeMask != null &&
|
||||
!Collisions.IsPointWithinCollider(planePoint, interactable.VolumeMask))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector3 closestPointToHitPoint = interactable.ComputeClosestPoint(planePoint);
|
||||
|
||||
float sqrDistanceFromPoint = (closestPointToHitPoint - planePoint).sqrMagnitude;
|
||||
|
||||
if (sqrDistanceFromPoint > interactable.MaxDistance * interactable.MaxDistance) continue;
|
||||
|
||||
if (sqrDistanceFromPoint < closestSqrDist)
|
||||
{
|
||||
closestSqrDist = sqrDistanceFromPoint;
|
||||
closestInteractable = interactable;
|
||||
ClosestPoint = closestPointToHitPoint;
|
||||
_touchPoint = ClosestPoint;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return closestInteractable;
|
||||
}
|
||||
|
||||
private PokeInteractable ComputeBestHoverInteractable()
|
||||
{
|
||||
PokeInteractable closestInteractable = null;
|
||||
float closestSqrDist = float.MaxValue;
|
||||
|
||||
IEnumerable<PokeInteractable> interactables = PokeInteractable.Registry.List(this);
|
||||
|
||||
// We check that we're above the trigger plane first as we don't
|
||||
// care about hovers that originate below the trigger plane
|
||||
foreach (PokeInteractable interactable in interactables)
|
||||
{
|
||||
Transform triggerPlaneTransform = interactable.TriggerPlaneTransform;
|
||||
|
||||
Vector3 planeToPoint = _currentPosition - triggerPlaneTransform.position;
|
||||
float magnitude = planeToPoint.magnitude;
|
||||
if (magnitude != 0f)
|
||||
{
|
||||
// We consider the plane "forward" normal to be z = -1 (to maintain parity with Canvas)
|
||||
if (Vector3.Dot(planeToPoint, triggerPlaneTransform.transform.forward) < 0f)
|
||||
{
|
||||
// Check if our position lies outside of the optional volume mask
|
||||
if (interactable.VolumeMask != null &&
|
||||
!Collisions.IsPointWithinCollider(_currentPosition, interactable.VolumeMask))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// We're above the plane so now we must rank this
|
||||
// interactable versus others that also pass this test this frame
|
||||
// but may be at a closer proximity.
|
||||
Vector3 closestPoint = interactable.ComputeClosestPoint(_currentPosition);
|
||||
|
||||
float sqrDistanceFromPoint = (closestPoint - _currentPosition).sqrMagnitude;
|
||||
|
||||
if (sqrDistanceFromPoint > interactable.MaxDistance * interactable.MaxDistance) continue;
|
||||
|
||||
if (sqrDistanceFromPoint < closestSqrDist)
|
||||
{
|
||||
closestSqrDist = sqrDistanceFromPoint;
|
||||
closestInteractable = interactable;
|
||||
ClosestPoint = closestPoint;
|
||||
_touchPoint = ClosestPoint;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return closestInteractable;
|
||||
}
|
||||
|
||||
protected override void InteractableSelected(PokeInteractable interactable)
|
||||
{
|
||||
if (interactable != null)
|
||||
{
|
||||
Vector3 worldPosition = ComputePlanePosition(interactable);
|
||||
_previousTouchPoint = worldPosition;
|
||||
_capturedTouchPoint = worldPosition;
|
||||
_lateralDelta = Vector3.zero;
|
||||
}
|
||||
|
||||
base.InteractableSelected(interactable);
|
||||
}
|
||||
|
||||
private Vector3 ComputePlanePosition(PokeInteractable interactable)
|
||||
{
|
||||
Vector3 originRelativeToPlane = Origin - interactable.TriggerPlaneTransform.position;
|
||||
Vector3 planePosition = Vector3.ProjectOnPlane(originRelativeToPlane, -1f * interactable.TriggerPlaneTransform.forward);
|
||||
return planePosition + interactable.TriggerPlaneTransform.position;
|
||||
}
|
||||
|
||||
private float ComputeDepth(PokeInteractable interactable, Vector3 loc)
|
||||
{
|
||||
Vector3 originRelativeToPlane = loc - interactable.TriggerPlaneTransform.position;
|
||||
return Vector3.Project(originRelativeToPlane,
|
||||
interactable.TriggerPlaneTransform.forward).magnitude *
|
||||
(Vector3.Dot(originRelativeToPlane, interactable.TriggerPlaneTransform.forward) > 0
|
||||
? 1.0f
|
||||
: 0.0f);
|
||||
}
|
||||
|
||||
protected override void DoSelectUpdate(PokeInteractable interactable)
|
||||
{
|
||||
if (interactable == null)
|
||||
{
|
||||
ShouldUnselect = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Transform triggerPlaneTransform = interactable.TriggerPlaneTransform;
|
||||
Vector3 triggerToInteractor = _currentPosition - triggerPlaneTransform.position;
|
||||
|
||||
// Unselect our interactor if it is above the plane of the trigger collider by at least releaseDistancePadding
|
||||
if (Vector3.Dot(triggerToInteractor, triggerPlaneTransform.forward) < -1f * _touchReleaseThreshold)
|
||||
{
|
||||
ShouldUnselect = true;
|
||||
}
|
||||
|
||||
Vector3 worldPosition = ComputePlanePosition(interactable);
|
||||
|
||||
float planarDelta = (worldPosition - _previousTouchPoint).magnitude;
|
||||
float depthDelta = Mathf.Abs(ComputeDepth(interactable, Origin) -
|
||||
ComputeDepth(interactable, _prevPosition));
|
||||
|
||||
Vector3 frameDelta = worldPosition - _previousTouchPoint;
|
||||
bool outsideDelta = false;
|
||||
|
||||
if (!_dragging && planarDelta > depthDelta)
|
||||
{
|
||||
_lateralDelta += frameDelta;
|
||||
|
||||
while (!outsideDelta)
|
||||
{
|
||||
float horizontalDelta =
|
||||
Vector3.Project(_lateralDelta, interactable.TriggerPlaneTransform.right).magnitude;
|
||||
|
||||
if (horizontalDelta > _selectedInteractable.HorizontalDragThreshold)
|
||||
{
|
||||
outsideDelta = true;
|
||||
break;
|
||||
}
|
||||
|
||||
float verticalDelta =
|
||||
Vector3.Project(_lateralDelta, interactable.TriggerPlaneTransform.up).magnitude;
|
||||
|
||||
if (verticalDelta > _selectedInteractable.VerticalDragThreshold)
|
||||
{
|
||||
outsideDelta = true;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (outsideDelta)
|
||||
{
|
||||
_dragStartCurve.Start();
|
||||
_startDragOffset = _capturedTouchPoint - worldPosition;
|
||||
_dragging = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!_dragging)
|
||||
{
|
||||
_touchPoint = _capturedTouchPoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
float deltaEase = _dragStartCurve.Progress();
|
||||
Vector3 offset = Vector3.Lerp(_startDragOffset, Vector3.zero, deltaEase);
|
||||
|
||||
_touchPoint = worldPosition + offset;
|
||||
}
|
||||
|
||||
_previousTouchPoint = worldPosition;
|
||||
|
||||
Vector3 closestPoint = interactable.ComputeClosestPoint(_currentPosition);
|
||||
float distanceFromPoint = (closestPoint - _currentPosition).magnitude;
|
||||
if (interactable.ReleaseDistance > 0.0f)
|
||||
{
|
||||
if (distanceFromPoint > interactable.ReleaseDistance)
|
||||
{
|
||||
ShouldUnselect = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllPokeInteractor(Transform pointTransform)
|
||||
{
|
||||
InjectPointTransform(pointTransform);
|
||||
}
|
||||
|
||||
public void InjectPointTransform(Transform pointTransform)
|
||||
{
|
||||
_pointTransform = pointTransform;
|
||||
}
|
||||
|
||||
public void InjectOptionalTouchReleaseThreshold(float touchReleaseThreshold)
|
||||
{
|
||||
_touchReleaseThreshold = touchReleaseThreshold;
|
||||
}
|
||||
|
||||
public void InjectOptionDragStartCurve(ProgressCurve dragStartCurve)
|
||||
{
|
||||
_dragStartCurve = dragStartCurve;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d952c175ec3c6554199fd744548ce50a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9921bc203a167347bb874ede719ff0e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,163 @@
|
||||
/************************************************************************************
|
||||
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
||||
|
||||
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
|
||||
https://developer.oculus.com/licenses/oculussdk/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
||||
ANY KIND, either express or implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
************************************************************************************/
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using Oculus.Interaction.Input;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Oculus.Interaction
|
||||
{
|
||||
/// <summary>
|
||||
/// HandPokeInteractorVisual forwards the finger state of an associated
|
||||
/// HandPokeInteractor to a HandGrabModifier to lock and unlock
|
||||
/// finger joints in the modifier's target hand data.
|
||||
/// </summary>
|
||||
public class HandPokeLimiterVisual : MonoBehaviour
|
||||
{
|
||||
[SerializeField, Interface(typeof(IHand))]
|
||||
private MonoBehaviour _hand;
|
||||
private IHand Hand;
|
||||
|
||||
[FormerlySerializedAs("_interactor")]
|
||||
[SerializeField]
|
||||
private PokeInteractor _pokeInteractor;
|
||||
|
||||
[FormerlySerializedAs("_modifier")]
|
||||
[SerializeField]
|
||||
private SyntheticHandModifier _syntheticHand;
|
||||
|
||||
[SerializeField]
|
||||
private float _maxDistanceFromTouchPoint = 0.1f;
|
||||
|
||||
private bool _isTouching;
|
||||
private Vector3 _initialTouchPoint;
|
||||
private float _maxDeltaFromTouchPoint;
|
||||
|
||||
protected bool _started = false;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
Hand = _hand as IHand;
|
||||
}
|
||||
|
||||
protected virtual void Start()
|
||||
{
|
||||
this.BeginStart(ref _started);
|
||||
Assert.IsNotNull(Hand);
|
||||
Assert.IsNotNull(_pokeInteractor);
|
||||
Assert.IsNotNull(_syntheticHand);
|
||||
this.EndStart(ref _started);
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
_pokeInteractor.WhenInteractableSelected.Action += HandleLock;
|
||||
_pokeInteractor.WhenInteractableUnselected.Action += HandleUnlock;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
if (_isTouching)
|
||||
{
|
||||
HandleUnlock(_pokeInteractor.SelectedInteractable);
|
||||
}
|
||||
|
||||
_pokeInteractor.WhenInteractableSelected.Action -= HandleLock;
|
||||
_pokeInteractor.WhenInteractableUnselected.Action -= HandleUnlock;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void LateUpdate()
|
||||
{
|
||||
UpdateWrist();
|
||||
}
|
||||
|
||||
private void HandleLock(PokeInteractable pokeInteractable)
|
||||
{
|
||||
_isTouching = true;
|
||||
_initialTouchPoint = _pokeInteractor.TouchPoint;
|
||||
}
|
||||
|
||||
private void HandleUnlock(PokeInteractable pokeInteractable)
|
||||
{
|
||||
_syntheticHand.FreeWrist();
|
||||
_isTouching = false;
|
||||
}
|
||||
|
||||
private Vector3 ComputePlanePosition(Vector3 point, PokeInteractable interactable)
|
||||
{
|
||||
Vector3 planeToPoint = point - interactable.TriggerPlaneTransform.position;
|
||||
Vector3 projectOnNormal = Vector3.Project(planeToPoint, -1f * interactable.TriggerPlaneTransform.forward);
|
||||
return point - projectOnNormal;
|
||||
}
|
||||
|
||||
private void UpdateWrist()
|
||||
{
|
||||
if (!_isTouching) return;
|
||||
|
||||
if (!Hand.GetRootPose(out Pose rootPose))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 planePosition = ComputePlanePosition(_pokeInteractor.Origin, _pokeInteractor.SelectedInteractable);
|
||||
_maxDeltaFromTouchPoint = Mathf.Max((planePosition - _initialTouchPoint).magnitude, _maxDeltaFromTouchPoint);
|
||||
|
||||
float deltaAsPercent =
|
||||
Mathf.Clamp01(_maxDeltaFromTouchPoint / _maxDistanceFromTouchPoint);
|
||||
|
||||
Vector3 fullDelta = planePosition - _initialTouchPoint;
|
||||
Vector3 easedPosition = _initialTouchPoint + fullDelta * deltaAsPercent;
|
||||
|
||||
Vector3 positionDelta = rootPose.position - _pokeInteractor.Origin;
|
||||
Vector3 targetPosePosition = easedPosition + positionDelta;
|
||||
Pose wristPoseOverride = new Pose(targetPosePosition, rootPose.rotation);
|
||||
|
||||
_syntheticHand.LockWristPose(wristPoseOverride, 1.0f, SyntheticHandModifier.WristLockMode.Full, true, true);
|
||||
_syntheticHand.MarkInputDataRequiresUpdate();
|
||||
}
|
||||
|
||||
#region Inject
|
||||
|
||||
public void InjectAllHandPokeLimiterVisual(IHand hand, PokeInteractor pokeInteractor,
|
||||
SyntheticHandModifier syntheticHand)
|
||||
{
|
||||
InjectHand(hand);
|
||||
InjectPokeInteractor(pokeInteractor);
|
||||
InjectSyntheticHand(syntheticHand);
|
||||
}
|
||||
|
||||
public void InjectHand(IHand hand)
|
||||
{
|
||||
_hand = hand as MonoBehaviour;
|
||||
Hand = hand;
|
||||
}
|
||||
|
||||
public void InjectPokeInteractor(PokeInteractor pokeInteractor)
|
||||
{
|
||||
_pokeInteractor = pokeInteractor;
|
||||
}
|
||||
|
||||
public void InjectSyntheticHand(SyntheticHandModifier syntheticHand)
|
||||
{
|
||||
_syntheticHand = syntheticHand;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb028ce99f9700e4197a49bfdcd525dd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user