Add ultimate xr
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrFixedHapticFeedback.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections;
|
||||
using UltimateXR.Avatar;
|
||||
using UltimateXR.Core;
|
||||
using UltimateXR.Core.Components;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Haptics.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that will send haptic feedback while enabled.
|
||||
/// </summary>
|
||||
public class UxrFixedHapticFeedback : UxrComponent
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private UxrHandSide _handSide = UxrHandSide.Left;
|
||||
[SerializeField] private UxrHapticMode _hapticMixMode = UxrHapticMode.Mix;
|
||||
[SerializeField] [Range(0, 1)] private float _amplitude = 0.5f;
|
||||
[SerializeField] private float _frequency = 100.0f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target hand.
|
||||
/// </summary>
|
||||
public UxrHandSide HandSide
|
||||
{
|
||||
get => _handSide;
|
||||
set => _handSide = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the haptic playback mix mode.
|
||||
/// </summary>
|
||||
public UxrHapticMode HapticMixMode
|
||||
{
|
||||
get => _hapticMixMode;
|
||||
set => _hapticMixMode = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the haptic signal amplitude.
|
||||
/// </summary>
|
||||
public float Amplitude
|
||||
{
|
||||
get => _amplitude;
|
||||
set => _amplitude = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the haptic signal frequency.
|
||||
/// </summary>
|
||||
public float Frequency
|
||||
{
|
||||
get => _frequency;
|
||||
set => _frequency = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Starts the haptic coroutine.
|
||||
/// </summary>
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
_hapticsCoroutine = StartCoroutine(HapticsCoroutine());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the haptic coroutine.
|
||||
/// </summary>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
StopCoroutine(_hapticsCoroutine);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Coroutines
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine that sends continuous, fixed, haptic feedback to the target controller.
|
||||
/// </summary>
|
||||
/// <returns>Coroutine enumerator</returns>
|
||||
private IEnumerator HapticsCoroutine()
|
||||
{
|
||||
yield return null;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (isActiveAndEnabled && UxrAvatar.LocalAvatar)
|
||||
{
|
||||
SendHapticClip(_handSide);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(UxrConstants.InputControllers.HapticSampleDurationSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Sends the haptic feedback.
|
||||
/// </summary>
|
||||
/// <param name="handSide">Target hand</param>
|
||||
private void SendHapticClip(UxrHandSide handSide)
|
||||
{
|
||||
UxrAvatar.LocalAvatarInput.SendHapticFeedback(handSide, _frequency, _amplitude, UxrConstants.InputControllers.HapticSampleDurationSeconds);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
private Coroutine _hapticsCoroutine;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1eeb41b3529195c4a807e933a7a6bebc
|
||||
timeCreated: 1537261748
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,66 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrHapticImpactEventArgs.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Haptics.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// EventArgs for events generated by a <see cref="UxrHapticOnImpact" /> component.
|
||||
/// </summary>
|
||||
public class UxrHapticImpactEventArgs : EventArgs
|
||||
{
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raycast hit information.
|
||||
/// </summary>
|
||||
public RaycastHit HitInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the normalized hit force (0 = min, 1 = max).
|
||||
/// </summary>
|
||||
public float ForceT { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tip velocity.
|
||||
/// </summary>
|
||||
public Vector3 Velocity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the angle between hit transform world forward and hit transform world velocity.
|
||||
/// </summary>
|
||||
public float AngleForwardVelocity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the angle between hit transform world forward and -normal.
|
||||
/// </summary>
|
||||
public float AnglePenetration { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors & Finalizer
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="hitInfo">Hit information</param>
|
||||
/// <param name="forceT">Hit force</param>
|
||||
/// <param name="velocity">Hit velocity</param>
|
||||
/// <param name="angleForwardVelocity">Angle between hit transform world forward and hit transform world velocity</param>
|
||||
/// <param name="anglePenetration">Angle between hit transform world and -normal</param>
|
||||
public UxrHapticImpactEventArgs(RaycastHit hitInfo, float forceT, Vector3 velocity, float angleForwardVelocity, float anglePenetration)
|
||||
{
|
||||
HitInfo = hitInfo;
|
||||
ForceT = forceT;
|
||||
Velocity = velocity;
|
||||
AngleForwardVelocity = angleForwardVelocity;
|
||||
AnglePenetration = anglePenetration;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1102b73bd6d9442ba32ffcccff6f9ab0
|
||||
timeCreated: 1629189942
|
||||
@@ -0,0 +1,30 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrHapticImpactReceiver.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using UltimateXR.Core.Components;
|
||||
|
||||
namespace UltimateXR.Haptics.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for components that, added to an object, can receive notifications when a collider on the same object or
|
||||
/// any of its children gets hit with a <see cref="UxrHapticOnImpact" /> component.
|
||||
/// </summary>
|
||||
public abstract class UxrHapticImpactReceiver : UxrComponent
|
||||
{
|
||||
#region Event Trigger Methods
|
||||
|
||||
/// <summary>
|
||||
/// Overridable method that gets called whenever an object with a <see cref="UxrHapticOnImpact" /> hits the collider
|
||||
/// with the <see cref="UxrHapticImpactReceiver" /> or any of its children.
|
||||
/// </summary>
|
||||
/// <param name="sender">Source component</param>
|
||||
/// <param name="eventArgs">Event parameters</param>
|
||||
public virtual void OnHit(UxrHapticOnImpact sender, UxrHapticImpactEventArgs eventArgs)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a73cd4555d34de241895efe71ddce703
|
||||
timeCreated: 1537720867
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,66 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrHapticOnImpact.HitPointInfo.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Haptics.Helpers
|
||||
{
|
||||
public partial class UxrHapticOnImpact
|
||||
{
|
||||
#region Private Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Keeps track of runtime information of all <see cref="Transform" />s that can generate hit events by a
|
||||
/// <see cref="UxrHapticOnImpact" /> component.
|
||||
/// </summary>
|
||||
private class HitPointInfo
|
||||
{
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Transform" /> component whose position will be checked for contacts.
|
||||
/// </summary>
|
||||
public Transform HitPoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the different velocity samples that are used to average the velocity of the last frames.
|
||||
/// </summary>
|
||||
public List<Vector3> VelocitySamples { get; } = new List<Vector3>(VelocityAverageSamples);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last frame position.
|
||||
/// </summary>
|
||||
public Vector3 LastPos { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current velocity.
|
||||
/// </summary>
|
||||
public Vector3 Velocity { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors & Finalizer
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="hitPoint">Transform of the hit point</param>
|
||||
public HitPointInfo(Transform hitPoint)
|
||||
{
|
||||
HitPoint = hitPoint;
|
||||
|
||||
for (int i = 0; i < VelocityAverageSamples; ++i)
|
||||
{
|
||||
VelocitySamples.Add(Vector3.zero);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 881c017d321643a3b052cb19d9ccc32f
|
||||
timeCreated: 1643734436
|
||||
@@ -0,0 +1,296 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrHapticOnImpact.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UltimateXR.Avatar;
|
||||
using UltimateXR.Core;
|
||||
using UltimateXR.Core.Components.Composite;
|
||||
using UltimateXR.Extensions.Unity;
|
||||
using UltimateXR.Manipulation;
|
||||
using UnityEngine;
|
||||
|
||||
#pragma warning disable 414 // Disable warnings due to unused values
|
||||
|
||||
namespace UltimateXR.Haptics.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that plays haptic clips on the VR controllers whenever certain points hit colliders.
|
||||
/// This enables to model haptic functionality like hitting walls with a hammer and similar.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(UxrGrabbableObject))]
|
||||
public partial class UxrHapticOnImpact : UxrGrabbableObjectComponent<UxrHapticOnImpact>
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[Header("General")] [SerializeField] private List<Transform> _hitPoints;
|
||||
[SerializeField] private LayerMask _collisionLayers = ~0;
|
||||
[SerializeField] [Range(0, 180)] private float _forwardAngleThreshold = 30.0f;
|
||||
[SerializeField] [Range(0, 180)] private float _surfaceAngleThreshold = 30.0f;
|
||||
[SerializeField] private float _minSpeed = 0.01f;
|
||||
[SerializeField] private float _maxSpeed = 1.0f;
|
||||
|
||||
[Header("Haptics")] [SerializeField] private UxrHapticClipType _hapticClip = UxrHapticClipType.Shot;
|
||||
[SerializeField] private UxrHapticMode _hapticMode = UxrHapticMode.Mix;
|
||||
[SerializeField] private float _hapticPulseDurationMin = 0.05f;
|
||||
[SerializeField] private float _hapticPulseDurationMax = 0.05f;
|
||||
[SerializeField] [Range(0, 1)] private float _hapticPulseAmplitudeMin = 0.2f;
|
||||
[SerializeField] [Range(0, 1)] private float _hapticPulseAmplitudeMax = 1.0f;
|
||||
|
||||
[Header("Physics")] [SerializeField] private float _minHitForce = 1.0f;
|
||||
[SerializeField] private float _maxHitForce = 100.0f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Event triggered when the component detects a collision between any hit point and a collider.
|
||||
/// </summary>
|
||||
public event EventHandler<UxrHapticImpactEventArgs> Hit;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hit point transforms.
|
||||
/// </summary>
|
||||
public IEnumerable<Transform> HitPoints => _hitPoints;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of times something was hit.
|
||||
/// </summary>
|
||||
public int TotalHitCount { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Applies an explosive force to a rigidbody as a result of a hit.
|
||||
/// </summary>
|
||||
/// <param name="rigidbody">The rigidbody to apply a force to</param>
|
||||
/// <param name="eventArgs">Event parameters</param>
|
||||
/// <param name="force">Explosive force applied to the rigidbody</param>
|
||||
public static void ApplyBreakExplosionForce(Rigidbody rigidbody, UxrHapticImpactEventArgs eventArgs, float force)
|
||||
{
|
||||
if (rigidbody != null)
|
||||
{
|
||||
rigidbody.AddForceAtPosition(eventArgs.Velocity.normalized * force, eventArgs.HitInfo.point);
|
||||
rigidbody.AddTorque(eventArgs.Velocity.normalized * force, ForceMode.Impulse);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Initializes internal data.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
CreateHitPointInfo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to events and re-initializes data.
|
||||
/// </summary>
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
|
||||
|
||||
_hitPointInfos.ForEach(p =>
|
||||
{
|
||||
p.LastPos = p.HitPoint.position;
|
||||
|
||||
for (int i = 0; i < VelocityAverageSamples; ++i)
|
||||
{
|
||||
p.VelocitySamples[i] = Vector3.zero;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes from events.
|
||||
/// </summary>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handling Methods
|
||||
|
||||
/// <summary>
|
||||
/// Called after avatars are updated. Tries to find objects that were hit.
|
||||
/// </summary>
|
||||
private void UxrManager_AvatarsUpdated()
|
||||
{
|
||||
if (isActiveAndEnabled == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Time.deltaTime > 0.0f)
|
||||
{
|
||||
foreach (HitPointInfo hitPointInfo in _hitPointInfos)
|
||||
{
|
||||
// Update velocity frame history
|
||||
|
||||
Vector3 currentFrameVelocity = (hitPointInfo.HitPoint.position - hitPointInfo.LastPos) / Time.deltaTime;
|
||||
|
||||
for (int i = 0; i < hitPointInfo.VelocitySamples.Count - 1; ++i)
|
||||
{
|
||||
hitPointInfo.VelocitySamples[i] = hitPointInfo.VelocitySamples[i + 1];
|
||||
}
|
||||
|
||||
hitPointInfo.VelocitySamples[hitPointInfo.VelocitySamples.Count - 1] = currentFrameVelocity;
|
||||
|
||||
// Average history to compute current velocity
|
||||
|
||||
hitPointInfo.Velocity = Vector3.zero;
|
||||
|
||||
for (int i = 0; i < hitPointInfo.VelocitySamples.Count - 1; ++i)
|
||||
{
|
||||
hitPointInfo.Velocity += hitPointInfo.VelocitySamples[i];
|
||||
}
|
||||
|
||||
hitPointInfo.Velocity = hitPointInfo.Velocity / hitPointInfo.VelocitySamples.Count;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (HitPointInfo hitPointInfo in _hitPointInfos)
|
||||
{
|
||||
float speed = hitPointInfo.Velocity.magnitude;
|
||||
|
||||
// Check if we are grabbing the object and moving it with enough speed
|
||||
if (UxrGrabManager.Instance && UxrGrabManager.Instance.GetGrabbingHand(GrabbableObject, out bool isLeft, out bool isRight) && speed > _minSpeed && speed > 0.0f)
|
||||
{
|
||||
Vector3 direction = hitPointInfo.HitPoint.position - hitPointInfo.LastPos;
|
||||
|
||||
// Raycast between the previous and current frame positions
|
||||
|
||||
RaycastHit[] hits = Physics.RaycastAll(hitPointInfo.LastPos, direction, direction.magnitude, _collisionLayers, QueryTriggerInteraction.Ignore);
|
||||
|
||||
foreach (RaycastHit hitInfo in hits)
|
||||
{
|
||||
// Avoid self collision first
|
||||
if (hitInfo.collider.transform.HasParent(transform))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// We hit something! get the normalized force 0 = min, 1 = max
|
||||
float forceT = Mathf.Clamp01((speed - _minSpeed) / (_maxSpeed - _minSpeed));
|
||||
|
||||
// Compute angles (forward motion angle and surface angle)
|
||||
float forwardVelocityAngle = Vector3.Angle(hitPointInfo.Velocity, hitPointInfo.HitPoint.forward);
|
||||
float surfaceAngle = Vector3.Angle(direction, -hitInfo.normal);
|
||||
|
||||
// Below thresholds to trigger event?
|
||||
if (forwardVelocityAngle <= _forwardAngleThreshold && surfaceAngle <= _surfaceAngleThreshold)
|
||||
{
|
||||
// Yes
|
||||
UxrHapticImpactEventArgs eventArgs = new UxrHapticImpactEventArgs(hitInfo,
|
||||
forceT,
|
||||
hitPointInfo.Velocity,
|
||||
forwardVelocityAngle,
|
||||
Vector3.Angle(hitPointInfo.HitPoint.forward, -hitInfo.normal));
|
||||
|
||||
// Apply physics to the other object if it is dynamic
|
||||
Rigidbody otherRigidbody = hitInfo.collider.GetComponent<Rigidbody>();
|
||||
|
||||
if (otherRigidbody && !otherRigidbody.isKinematic)
|
||||
{
|
||||
ApplyBreakExplosionForce(otherRigidbody, eventArgs, Mathf.Lerp(_minHitForce, _maxHitForce, eventArgs.ForceT));
|
||||
}
|
||||
|
||||
// Send haptic feedback
|
||||
if (UxrAvatar.LocalAvatarInput)
|
||||
{
|
||||
float amplitude = Mathf.Lerp(_hapticPulseAmplitudeMin, _hapticPulseAmplitudeMax, forceT);
|
||||
float duration = Mathf.Lerp(_hapticPulseDurationMin, _hapticPulseDurationMax, forceT);
|
||||
|
||||
if (isLeft)
|
||||
{
|
||||
UxrAvatar.LocalAvatarInput.SendHapticFeedback(UxrHandSide.Left, _hapticClip, amplitude, duration, _hapticMode);
|
||||
}
|
||||
|
||||
if (isRight)
|
||||
{
|
||||
UxrAvatar.LocalAvatarInput.SendHapticFeedback(UxrHandSide.Right, _hapticClip, amplitude, duration, _hapticMode);
|
||||
}
|
||||
|
||||
OnHit(eventArgs);
|
||||
}
|
||||
|
||||
// Check if there is a receiver component to send the event
|
||||
UxrHapticImpactReceiver receiver = hitInfo.collider.GetComponentInParent<UxrHapticImpactReceiver>();
|
||||
|
||||
if (receiver)
|
||||
{
|
||||
receiver.OnHit(this, eventArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (HitPointInfo hitPointInfo in _hitPointInfos)
|
||||
{
|
||||
hitPointInfo.LastPos = hitPointInfo.HitPoint.position;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Trigger Methods
|
||||
|
||||
/// <summary>
|
||||
/// Event trigger for the <see cref="Hit" /> event.
|
||||
/// </summary>
|
||||
/// <param name="e">Event parameters</param>
|
||||
private void OnHit(UxrHapticImpactEventArgs e)
|
||||
{
|
||||
TotalHitCount++;
|
||||
Hit?.Invoke(this, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Creates the hit point information list.
|
||||
/// </summary>
|
||||
private void CreateHitPointInfo()
|
||||
{
|
||||
foreach (Transform hitPoint in _hitPoints)
|
||||
{
|
||||
_hitPointInfos.Add(new HitPointInfo(hitPoint));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// The number of frames to sample velocity to average.
|
||||
/// </summary>
|
||||
private const int VelocityAverageSamples = 3;
|
||||
|
||||
private readonly List<HitPointInfo> _hitPointInfos = new List<HitPointInfo>();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore 414
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 48ac8cb834cf2394fb2ade9556cfc659
|
||||
timeCreated: 1537720867
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,421 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrManipulationHapticFeedback.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections;
|
||||
using UltimateXR.Avatar;
|
||||
using UltimateXR.Core;
|
||||
using UltimateXR.Core.Components.Composite;
|
||||
using UltimateXR.Manipulation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Haptics.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that, added to a grabbable object (<see cref="UxrGrabbableObject" />), sends haptic feedback to any
|
||||
/// controller that manipulates it.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(UxrGrabbableObject))]
|
||||
public class UxrManipulationHapticFeedback : UxrGrabbableObjectComponent<UxrManipulationHapticFeedback>
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[Header("Continuous Manipulation:")] [SerializeField] private bool _continuousManipulationHaptics;
|
||||
[SerializeField] private UxrHapticMode _hapticMixMode = UxrHapticMode.Mix;
|
||||
[SerializeField] [Range(0, 1)] private float _minAmplitude = 0.3f;
|
||||
[SerializeField] [Range(0, 1)] private float _maxAmplitude = 1.0f;
|
||||
[SerializeField] private float _minFrequency = 10.0f;
|
||||
[SerializeField] private float _maxFrequency = 100.0f;
|
||||
[SerializeField] private float _minSpeed = 0.01f;
|
||||
[SerializeField] private float _maxSpeed = 1.0f;
|
||||
[SerializeField] private float _minAngularSpeed = 1.0f;
|
||||
[SerializeField] private float _maxAngularSpeed = 1800.0f;
|
||||
[SerializeField] private bool _useExternalRigidbody;
|
||||
[SerializeField] private Rigidbody _externalRigidbody;
|
||||
|
||||
[Header("Events Haptics:")] [SerializeField] private UxrHapticClip _hapticClipOnGrab = new UxrHapticClip();
|
||||
[SerializeField] private UxrHapticClip _hapticClipOnPlace = new UxrHapticClip();
|
||||
[SerializeField] private UxrHapticClip _hapticClipOnRelease = new UxrHapticClip();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the component will send haptic feedback continuously while the object is being grabbed.
|
||||
/// </summary>
|
||||
public bool ContinuousManipulationHaptics
|
||||
{
|
||||
get => _continuousManipulationHaptics;
|
||||
set => _continuousManipulationHaptics = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the haptic feedback mix mode.
|
||||
/// </summary>
|
||||
public UxrHapticMode HapticMixMode
|
||||
{
|
||||
get => _hapticMixMode;
|
||||
set => _hapticMixMode = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets continuous manipulation haptic feedback's minimum amplitude, which is the haptic amplitude sent when
|
||||
/// the object is moving/rotating at or below <see cref="MinSpeed" />/<see cref="MinAngularSpeed" />.
|
||||
/// </summary>
|
||||
public float MinAmplitude
|
||||
{
|
||||
get => _minAmplitude;
|
||||
set => _minAmplitude = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets continuous manipulation haptic feedback's maximum amplitude, which is the haptic amplitude sent when
|
||||
/// the object is moving/rotating at or over <see cref="MaxSpeed" />/<see cref="MaxAngularSpeed" />.
|
||||
/// </summary>
|
||||
public float MaxAmplitude
|
||||
{
|
||||
get => _maxAmplitude;
|
||||
set => _maxAmplitude = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets continuous manipulation haptic feedback's minimum frequency, which is the haptic frequency sent when
|
||||
/// the object is moving/rotating at or below <see cref="MinSpeed" />/<see cref="MinAngularSpeed" />.
|
||||
/// </summary>
|
||||
public float MinFrequency
|
||||
{
|
||||
get => _minFrequency;
|
||||
set => _minFrequency = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets continuous manipulation haptic feedback's maximum frequency, which is the haptic frequency sent when
|
||||
/// the object is moving/rotating at or over <see cref="MaxSpeed" />/<see cref="MaxAngularSpeed" />.
|
||||
/// </summary>
|
||||
public float MaxFrequency
|
||||
{
|
||||
get => _maxFrequency;
|
||||
set => _maxFrequency = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum manipulation speed, which is the object travel speed while being manipulated below which
|
||||
/// the haptics will be sent with <see cref="MinFrequency" /> and <see cref="MinAmplitude" />.
|
||||
/// Speeds up to <see cref="MaxSpeed" /> will send haptic feedback with frequency and amplitude values linearly
|
||||
/// increasing up to <see cref="MaxFrequency" /> and <see cref="MaxAmplitude" />. This allows to send haptic feedback
|
||||
/// with an intensity/frequency depending on how fast the object is being moved.
|
||||
/// </summary>
|
||||
public float MinSpeed
|
||||
{
|
||||
get => _minSpeed;
|
||||
set => _minSpeed = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum manipulation speed, which is the object travel speed while being manipulated above which
|
||||
/// the haptics will be sent with <see cref="MaxFrequency" /> and <see cref="MaxAmplitude" />.
|
||||
/// Speeds down to <see cref="MinSpeed" /> will send haptic feedback with frequency and amplitude values linearly
|
||||
/// decreasing down to <see cref="MinFrequency" /> and <see cref="MinAmplitude" />. This allows to send haptic feedback
|
||||
/// with an intensity/frequency depending on how fast the object is being moved.
|
||||
/// </summary>
|
||||
public float MaxSpeed
|
||||
{
|
||||
get => _maxSpeed;
|
||||
set => _maxSpeed = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum manipulation angular speed. This is the same as <see cref="MinSpeed" /> but when rotating an
|
||||
/// object.
|
||||
/// </summary>
|
||||
public float MinAngularSpeed
|
||||
{
|
||||
get => _minAngularSpeed;
|
||||
set => _minAngularSpeed = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum manipulation angular speed. This is the same as <see cref="MaxSpeed" /> but when rotating an
|
||||
/// object.
|
||||
/// </summary>
|
||||
public float MaxAngularSpeed
|
||||
{
|
||||
get => _maxAngularSpeed;
|
||||
set => _maxAngularSpeed = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="ExternalRigidbody" />.
|
||||
/// </summary>
|
||||
public bool UseExternalRigidbody
|
||||
{
|
||||
get => _useExternalRigidbody;
|
||||
set => _useExternalRigidbody = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In continuous manipulation mode, allows to get the linear/rotational speed from an external rigidbody instead of
|
||||
/// the object being grabbed. This is useful to emulate the tension propagated by a connected physics-driven object.
|
||||
/// For example, in a flail weapon, the grabbable object is the handle which also has the
|
||||
/// <see cref="UxrManipulationHapticFeedback" /> component, but the physics-driven head is the object that should be
|
||||
/// monitored for haptics to generate better results.
|
||||
/// </summary>
|
||||
public Rigidbody ExternalRigidbody
|
||||
{
|
||||
get => _externalRigidbody;
|
||||
set => _externalRigidbody = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the haptic clip played when the object is grabbed.
|
||||
/// </summary>
|
||||
public UxrHapticClip HapticClipOnGrab
|
||||
{
|
||||
get => _hapticClipOnGrab;
|
||||
set => _hapticClipOnGrab = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the haptic clip played when the object is placed.
|
||||
/// </summary>
|
||||
public UxrHapticClip HapticClipOnPlace
|
||||
{
|
||||
get => _hapticClipOnPlace;
|
||||
set => _hapticClipOnPlace = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the haptic clip played when the object is released.
|
||||
/// </summary>
|
||||
public UxrHapticClip HapticClipOnRelease
|
||||
{
|
||||
get => _hapticClipOnRelease;
|
||||
set => _hapticClipOnRelease = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Stops the haptic coroutines.
|
||||
/// </summary>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
if (_leftHapticsCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_leftHapticsCoroutine);
|
||||
_leftHapticsCoroutine = null;
|
||||
}
|
||||
|
||||
if (_rightHapticsCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_rightHapticsCoroutine);
|
||||
_rightHapticsCoroutine = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Coroutines
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine that sends haptic clip to the left controller if the object is being grabbed and continuous manipulation
|
||||
/// haptics are enabled.
|
||||
/// </summary>
|
||||
/// <param name="grabber">Grabber component that is currently grabbing the object</param>
|
||||
/// <returns>Coroutine enumerator</returns>
|
||||
private IEnumerator LeftHapticsCoroutine(UxrGrabber grabber)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (isActiveAndEnabled && grabber && _continuousManipulationHaptics)
|
||||
{
|
||||
SendHapticClip(UxrHandSide.Left);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(UxrConstants.InputControllers.HapticSampleDurationSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Coroutine that sends haptic clip to the right controller if the object is being grabbed and continuous manipulation
|
||||
/// haptics are enabled.
|
||||
/// </summary>
|
||||
/// <param name="grabber">Grabber component that is currently grabbing the object</param>
|
||||
/// <returns>Coroutine enumerator</returns>
|
||||
private IEnumerator RightHapticsCoroutine(UxrGrabber grabber)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (isActiveAndEnabled && grabber && _continuousManipulationHaptics)
|
||||
{
|
||||
SendHapticClip(UxrHandSide.Right);
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(UxrConstants.InputControllers.HapticSampleDurationSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Trigger Methods
|
||||
|
||||
/// <summary>
|
||||
/// Called when the object was grabbed. Sends haptic feedback if it's required.
|
||||
/// </summary>
|
||||
/// <param name="e">Grab event parameters</param>
|
||||
protected override void OnObjectGrabbed(UxrManipulationEventArgs e)
|
||||
{
|
||||
base.OnObjectGrabbed(e);
|
||||
|
||||
if (!isActiveAndEnabled || !UxrAvatar.LocalAvatar)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Grabber.Avatar == UxrAvatar.LocalAvatar)
|
||||
{
|
||||
if (e.Grabber.Side == UxrHandSide.Left)
|
||||
{
|
||||
_leftHapticsCoroutine = StartCoroutine(LeftHapticsCoroutine(e.Grabber));
|
||||
}
|
||||
else
|
||||
{
|
||||
_rightHapticsCoroutine = StartCoroutine(RightHapticsCoroutine(e.Grabber));
|
||||
}
|
||||
|
||||
UxrAvatar.LocalAvatarInput.SendHapticFeedback(e.Grabber.Side, _hapticClipOnGrab);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the object was placed. Sends haptic feedback if it's required.
|
||||
/// </summary>
|
||||
/// <param name="e">Grab event parameters</param>
|
||||
protected override void OnObjectPlaced(UxrManipulationEventArgs e)
|
||||
{
|
||||
base.OnObjectPlaced(e);
|
||||
|
||||
if (e.Grabber != null && e.Grabber.Avatar == UxrAvatar.LocalAvatar)
|
||||
{
|
||||
if (e.Grabber.Side == UxrHandSide.Left && _leftHapticsCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_leftHapticsCoroutine);
|
||||
}
|
||||
else if (e.Grabber.Side == UxrHandSide.Right && _rightHapticsCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_rightHapticsCoroutine);
|
||||
}
|
||||
|
||||
if (isActiveAndEnabled)
|
||||
{
|
||||
UxrAvatar.LocalAvatarInput.SendHapticFeedback(e.Grabber.Side, _hapticClipOnPlace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the object was released. Sends haptic feedback if it's required.
|
||||
/// </summary>
|
||||
/// <param name="e">Grab event parameters</param>
|
||||
protected override void OnObjectReleased(UxrManipulationEventArgs e)
|
||||
{
|
||||
base.OnObjectReleased(e);
|
||||
|
||||
if (e.Grabber != null && e.Grabber.Avatar == UxrAvatar.LocalAvatar)
|
||||
{
|
||||
// Set speed to 0 in case we go from two-handed grab to single grab and object has NeedsTwoHandsToRotate set.
|
||||
// In this case the object will stop sending constrain events and we need a way to set the speed to 0.
|
||||
_linearSpeed = 0.0f;
|
||||
_angularSpeed = 0.0f;
|
||||
|
||||
if (e.Grabber.Side == UxrHandSide.Left && _leftHapticsCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_leftHapticsCoroutine);
|
||||
}
|
||||
else if (e.Grabber.Side == UxrHandSide.Right && _rightHapticsCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_rightHapticsCoroutine);
|
||||
}
|
||||
|
||||
if (isActiveAndEnabled)
|
||||
{
|
||||
UxrAvatar.LocalAvatarInput.SendHapticFeedback(e.Grabber.Side, _hapticClipOnRelease);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after all object manipulation has been processed and potential constraints have been applied.
|
||||
/// It is used to update the speed information.
|
||||
/// </summary>
|
||||
/// <param name="e">Event parameters</param>
|
||||
protected override void OnObjectConstraintsFinished(UxrApplyConstraintsEventArgs e)
|
||||
{
|
||||
if (UxrAvatar.LocalAvatar == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 localPosition = UxrAvatar.LocalAvatar.transform.InverseTransformPoint(e.GrabbableObject.transform.position);
|
||||
Quaternion localRotation = Quaternion.Inverse(UxrAvatar.LocalAvatar.transform.rotation) * e.GrabbableObject.transform.rotation;
|
||||
|
||||
_linearSpeed = Vector3.Distance(_previousLocalPosition, localPosition) / Time.deltaTime;
|
||||
_angularSpeed = Quaternion.Angle(_previousLocalRotation, localRotation) / Time.deltaTime;
|
||||
|
||||
_previousLocalPosition = localPosition;
|
||||
_previousLocalRotation = localRotation;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Sends the continuous haptic feedback clip for a short amount of time defined by
|
||||
/// <see cref="UxrConstants.InputControllers.HapticSampleDurationSeconds" />.
|
||||
/// </summary>
|
||||
/// <param name="handSide">Target hand</param>
|
||||
private void SendHapticClip(UxrHandSide handSide)
|
||||
{
|
||||
if (!UxrAvatar.LocalAvatar)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float speed = _useExternalRigidbody && _externalRigidbody ? _externalRigidbody.velocity.magnitude : _linearSpeed;
|
||||
float angularSpeed = _useExternalRigidbody && _externalRigidbody ? _externalRigidbody.angularVelocity.magnitude : _angularSpeed;
|
||||
|
||||
float quantityPos = _maxSpeed - _minSpeed <= 0.0f ? 0.0f : (speed - _minSpeed) / (_maxSpeed - _minSpeed);
|
||||
float quantityRot = _maxAngularSpeed - _minAngularSpeed <= 0.0f ? 0.0f : (angularSpeed - _minAngularSpeed) / (_maxAngularSpeed - _minAngularSpeed);
|
||||
|
||||
if (quantityPos > 0.0f || quantityRot > 0.0f)
|
||||
{
|
||||
float frequencyPos = Mathf.Lerp(_minFrequency, _maxFrequency, Mathf.Clamp01(quantityPos));
|
||||
float amplitudePos = Mathf.Lerp(_minAmplitude, _maxAmplitude, Mathf.Clamp01(quantityPos));
|
||||
float frequencyRot = Mathf.Lerp(_minFrequency, _maxFrequency, Mathf.Clamp01(quantityRot));
|
||||
float amplitudeRot = Mathf.Lerp(_minAmplitude, _maxAmplitude, Mathf.Clamp01(quantityRot));
|
||||
|
||||
UxrAvatar.LocalAvatarInput.SendHapticFeedback(handSide, Mathf.Max(frequencyPos, frequencyRot), Mathf.Max(amplitudePos, amplitudeRot), UxrConstants.InputControllers.HapticSampleDurationSeconds, _hapticMixMode);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
private Coroutine _leftHapticsCoroutine;
|
||||
private Coroutine _rightHapticsCoroutine;
|
||||
private Vector3 _previousLocalPosition;
|
||||
private Quaternion _previousLocalRotation;
|
||||
private float _linearSpeed;
|
||||
private float _angularSpeed;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d17182e9c78c594d96529eb7adbcbe0
|
||||
timeCreated: 1537261748
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user