Move third party assets to ThirdParty folder

This commit is contained in:
2024-08-08 11:26:28 +02:00
parent 386f303057
commit bd91af6f98
10340 changed files with 100 additions and 175 deletions

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 22c162707ece4d419afdc04513017aae
timeCreated: 1643829651

View File

@@ -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
}
}

View File

@@ -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:

View File

@@ -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
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1102b73bd6d9442ba32ffcccff6f9ab0
timeCreated: 1629189942

View File

@@ -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
}
}

View File

@@ -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:

View File

@@ -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
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 881c017d321643a3b052cb19d9ccc32f
timeCreated: 1643734436

View File

@@ -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

View File

@@ -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:

View File

@@ -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
}
}

View File

@@ -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:

View File

@@ -0,0 +1,121 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrHapticClip.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UnityEngine;
namespace UltimateXR.Haptics
{
/// <summary>
/// Describes a haptic clip. It is possible to specify an audio clip whose wave will be used as a primary source for
/// the vibration, but also a secondary clip type that will be used if the device doesn't support audio clips as haptic
/// feedback.
/// If no audio clip is specified, the fallback clip type will always be used.
/// </summary>
[Serializable]
public class UxrHapticClip
{
#region Inspector Properties/Serialized Fields
[SerializeField] private AudioClip _clip;
[SerializeField] [Range(0, 1)] private float _clipAmplitude = 1.0f;
[SerializeField] private UxrHapticMode _hapticMode = UxrHapticMode.Mix;
[SerializeField] private UxrHapticClipType _fallbackClipType = UxrHapticClipType.None;
[SerializeField] [Range(0, 1)] private float _fallbackAmplitude = 1.0f;
[SerializeField] private float _fallbackDurationSeconds = -1.0f;
#endregion
#region Public Types & Data
/// <summary>
/// Gets or sets the primary <see cref="AudioClip" /> to use as source for vibration. If the device does not support
/// audio
/// clips as sources or this value is null, <see cref="FallbackClipType" /> will be used.
/// </summary>
public AudioClip Clip
{
get => _clip;
set => _clip = value;
}
/// <summary>
/// Gets or sets the amplitude to play <see cref="Clip" />. Valid range is [0.0, 1.0].
/// </summary>
public float ClipAmplitude
{
get => _clipAmplitude;
set => _clipAmplitude = value;
}
/// <summary>
/// Gets or sets whether to replace or mix the clip with any current haptic feedback being played.
/// </summary>
public UxrHapticMode HapticMode
{
get => _hapticMode;
set => _hapticMode = value;
}
/// <summary>
/// Gets or sets the fallback clip: A value from a pre-defined set of procedurally generated haptic feedback clips. It
/// will be
/// used if the current device can't play <see cref="AudioClip" /> as haptics or <see cref="Clip" /> is not assigned.
/// </summary>
public UxrHapticClipType FallbackClipType
{
get => _fallbackClipType;
set => _fallbackClipType = value;
}
/// <summary>
/// Gets or sets the amplitude to play the fallback clip (1.0f = use default).
/// </summary>
public float FallbackAmplitude
{
get => _fallbackAmplitude;
set => _fallbackAmplitude = value;
}
/// <summary>
/// Gets or sets the duration in seconds of the fallback clip (negative = use predefined).
/// </summary>
public float FallbackDurationSeconds
{
get => _fallbackDurationSeconds;
set => _fallbackDurationSeconds = value;
}
#endregion
#region Constructors & Finalizer
/// <summary>
/// Public constructor.
/// </summary>
/// <param name="clip">The audio clip</param>
/// <param name="fallbackClipType">The fallback clip if the primary audio clip is null</param>
/// <param name="hapticMode">The haptic mixing mode</param>
/// <param name="clipAmplitude">The amplitude of the audio clip</param>
/// <param name="fallbackAmplitude">The amplitude of the fallback clip</param>
/// <param name="fallbackDurationSeconds">The duration in seconds of the fallback clip (negative = use predefined)</param>
public UxrHapticClip(AudioClip clip = null,
UxrHapticClipType fallbackClipType = UxrHapticClipType.None,
UxrHapticMode hapticMode = UxrHapticMode.Mix,
float clipAmplitude = 1.0f,
float fallbackAmplitude = 1.0f,
float fallbackDurationSeconds = -1.0f)
{
Clip = clip;
FallbackClipType = fallbackClipType;
HapticMode = hapticMode;
ClipAmplitude = clipAmplitude;
FallbackAmplitude = fallbackAmplitude;
FallbackDurationSeconds = fallbackDurationSeconds;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7aa2110dfa31409d80e30dcf35ad5f2b
timeCreated: 1623749520

View File

@@ -0,0 +1,27 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrHapticClipType.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Haptics
{
/// <summary>
/// Enumerates the supported pre-defined set of haptic feedbacks that can be generated procedurally and played using
/// raw haptic mode.
/// </summary>
public enum UxrHapticClipType
{
None,
RumbleFreqVeryLow,
RumbleFreqLow,
RumbleFreqNormal,
RumbleFreqHigh,
RumbleFreqVeryHigh,
Click,
Shot,
ShotBig,
ShotBigger,
Slide,
Explosion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d20a4fab19a74ba59fdbb8debbe98abb
timeCreated: 1623748277

View File

@@ -0,0 +1,28 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrHapticEventType.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Haptics
{
/// <summary>
/// Enumerates the different types of haptic events.
/// </summary>
public enum UxrHapticEventType
{
/// <summary>
/// Haptic clip playback.
/// </summary>
Clip,
/// <summary>
/// Raw haptic playback.
/// </summary>
Raw,
/// <summary>
/// Haptic feedback stop.
/// </summary>
Stop
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 901958630cb844df8a6dd5144da8e0b6
timeCreated: 1642850664

View File

@@ -0,0 +1,23 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrHapticMode.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Haptics
{
/// <summary>
/// Enumerates the different types of supported haptic playback.
/// </summary>
public enum UxrHapticMode
{
/// <summary>
/// Replaces the current haptics on the device.
/// </summary>
Replace,
/// <summary>
/// Mixes the new haptics with the current haptics on the device.
/// </summary>
Mix
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7c1377e68e13494f9ab6fc0a244f73ef
timeCreated: 1623748177