Add ultimate xr
This commit is contained in:
@@ -0,0 +1,369 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="UxrAutoSlideInObject.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UltimateXR.Animation.Interpolation;
|
||||
using UltimateXR.Avatar;
|
||||
using UltimateXR.Core;
|
||||
using UltimateXR.Core.Components.Composite;
|
||||
using UltimateXR.Core.Math;
|
||||
using UltimateXR.Core.Settings;
|
||||
using UltimateXR.Extensions.Unity;
|
||||
using UltimateXR.Haptics.Helpers;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Manipulation.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that, together with <see cref="UxrAutoSlideInAnchor" /> will add the following behaviour to a
|
||||
/// <see cref="UxrGrabbableObject" />:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// It will slide along the axis given by the grabbable object translation constraint. The constraint should
|
||||
/// be pre-configured along a single axis.
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// It will be smoothly removed from the anchor and made free if dragged beyond the upper translation
|
||||
/// constraint.
|
||||
/// </item>
|
||||
/// <item>It will be smoothly placed automatically on the anchor when moved back close enough.</item>
|
||||
/// <item>It will fall back by itself when released while sliding along the axis.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public partial class UxrAutoSlideInObject : UxrGrabbableObjectComponent<UxrAutoSlideInObject>
|
||||
{
|
||||
[SerializeField] private Vector3 _translationConstraintMin = Vector3.zero;
|
||||
[SerializeField] private Vector3 _translationConstraintMax = Vector3.forward * 0.1f;
|
||||
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Event called right after the object hit the end after sliding in after it was released.
|
||||
/// </summary>
|
||||
public event Action PlacedAfterSlidingIn;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to the avatars updated event so that the manipulation logic is done after all manipulation
|
||||
/// logic has been updated.
|
||||
/// </summary>
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes from the avatars updated event.
|
||||
/// </summary>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize component.
|
||||
/// </summary>
|
||||
protected override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
_placedAfterSlidingIn = GrabbableObject.CurrentAnchor != null;
|
||||
|
||||
// Get slide axis
|
||||
int insertAxis = GrabbableObject.SingleTranslationAxisIndex;
|
||||
|
||||
if (insertAxis == -1 || GrabbableObject.TranslationConstraint != UxrTranslationConstraintMode.RestrictLocalOffset)
|
||||
{
|
||||
if (UxrGlobalSettings.Instance.LogLevelManipulation >= UxrLogLevel.Errors)
|
||||
{
|
||||
Debug.LogError($"{UxrConstants.ManipulationModule} {this}: {nameof(UxrGrabbableObject)} component needs to have a local offset translation constraint along a single axis to work properly");
|
||||
}
|
||||
|
||||
_insertAxis = UxrAxis.Z;
|
||||
}
|
||||
else
|
||||
{
|
||||
_insertAxis = insertAxis;
|
||||
}
|
||||
|
||||
// Store haptic feedback component in case it exists, to disable it while the object is out of the sliding zone
|
||||
_manipulationHapticFeedback = GetComponent<UxrManipulationHapticFeedback>();
|
||||
|
||||
// Compute the slide length
|
||||
_insertOffset = _translationConstraintMax[_insertAxis] > -_translationConstraintMin[_insertAxis] ? _translationConstraintMax[_insertAxis] : _translationConstraintMin[_insertAxis];
|
||||
|
||||
_insertOffsetSign = Mathf.Sign(_insertOffset);
|
||||
_insertOffset = Mathf.Abs(_insertOffset);
|
||||
|
||||
// Fix some object parameters if we need to
|
||||
|
||||
if (GrabbableObject.DropSnapMode != UxrSnapToAnchorMode.DontSnap)
|
||||
{
|
||||
if (UxrGlobalSettings.Instance.LogLevelManipulation >= UxrLogLevel.Warnings)
|
||||
{
|
||||
Debug.LogWarning($"{UxrConstants.ManipulationModule} {this.GetPathUnderScene()}: GrabbableObject needs DropSnapMode to be DontSnap in order to work properly. Overriding.");
|
||||
}
|
||||
|
||||
GrabbableObject.DropSnapMode = UxrSnapToAnchorMode.DontSnap;
|
||||
}
|
||||
|
||||
GrabbableObject.IsPlaceable = false; // We will handle placement ourselves
|
||||
|
||||
// Compute the object size in local coordinates
|
||||
_objectLocalSize = gameObject.GetLocalBounds(true).size;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handling Methods
|
||||
|
||||
/// <summary>
|
||||
/// Called after UltimateXR has done all the frame updating. Does the manipulation logic.
|
||||
/// </summary>
|
||||
private void UxrManager_AvatarsUpdated()
|
||||
{
|
||||
bool grabbedByLocalAvatar = UxrGrabManager.Instance.IsBeingGrabbed(GrabbableObject) && UxrGrabManager.Instance.GetGrabbingHands(GrabbableObject).First().Avatar.AvatarMode == UxrAvatarMode.Local;
|
||||
|
||||
if (GrabbableObject.CurrentAnchor == null && grabbedByLocalAvatar)
|
||||
{
|
||||
// The object is being grabbed and is detached. Check if we need to place it on an anchor again by proximity.
|
||||
|
||||
foreach (UxrAutoSlideInAnchor anchor in UxrAutoSlideInAnchor.EnabledComponents.Where(a => a.Anchor.enabled))
|
||||
{
|
||||
// If it is inside the valid release "volume", place it in the anchor again and let it slide by re-assigning the constraints
|
||||
|
||||
if (anchor.Anchor.CurrentPlacedObject == null && anchor.Anchor.IsCompatibleObject(GrabbableObject) && IsObjectNearPlacement(anchor.Anchor))
|
||||
{
|
||||
AttachObject(anchor);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (GrabbableObject.CurrentAnchor != null && _insertAxis != null)
|
||||
{
|
||||
// Object can only move in a specific axis but if it is grabbed past this distance it becomes free
|
||||
|
||||
if (transform.parent != null && grabbedByLocalAvatar && Mathf.Abs(GrabbableObject.InitialLocalPosition[_insertAxis] - transform.localPosition[_insertAxis]) > _insertOffset * 0.99f)
|
||||
{
|
||||
DetachObject();
|
||||
return;
|
||||
}
|
||||
|
||||
// If it is not being grabbed it will slide in
|
||||
|
||||
if (!GrabbableObject.IsBeingGrabbed)
|
||||
{
|
||||
// Use simple gravity to slide in. Gravity will be mapped to z axis to slide in our local coordinate system.
|
||||
|
||||
Vector3 speed = Physics.gravity * _slideInTimer;
|
||||
Vector3 pos = GrabbableObject.transform.localPosition;
|
||||
pos[_insertAxis] += Time.deltaTime * speed.y * _insertOffsetSign;
|
||||
|
||||
if ((_insertOffsetSign > 0.0f && pos[_insertAxis] < GrabbableObject.InitialLocalPosition[_insertAxis]) ||
|
||||
(_insertOffsetSign < 0.0f && pos[_insertAxis] > GrabbableObject.InitialLocalPosition[_insertAxis]))
|
||||
{
|
||||
pos[_insertAxis] = GrabbableObject.InitialLocalPosition[_insertAxis];
|
||||
|
||||
if (_placedAfterSlidingIn == false)
|
||||
{
|
||||
_placedAfterSlidingIn = true;
|
||||
OnPlacedAfterSlidingIn();
|
||||
}
|
||||
}
|
||||
|
||||
// Interpolate other rotation/translation in case the object was released before the transition
|
||||
|
||||
float smooth = 0.1f;
|
||||
|
||||
pos[_insertAxis.Perpendicular] = UxrInterpolator.SmoothDamp(pos[_insertAxis.Perpendicular], GrabbableObject.InitialLocalPosition[_insertAxis.Perpendicular], smooth);
|
||||
pos[_insertAxis.OtherPerpendicular] = UxrInterpolator.SmoothDamp(pos[_insertAxis.OtherPerpendicular], GrabbableObject.InitialLocalPosition[_insertAxis.OtherPerpendicular], smooth);
|
||||
GrabbableObject.transform.localRotation = UxrInterpolator.SmoothDampRotation(GrabbableObject.transform.localRotation, GrabbableObject.InitialLocalRotation, smooth);
|
||||
|
||||
// Update
|
||||
|
||||
GrabbableObject.transform.localPosition = pos;
|
||||
|
||||
_slideInTimer += Time.deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
_slideInTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Trigger Methods
|
||||
|
||||
/// <summary>
|
||||
/// Called when the object was placed at the end sliding in after it was released.
|
||||
/// Use in child classes to
|
||||
/// </summary>
|
||||
protected virtual void OnPlacedAfterSlidingIn()
|
||||
{
|
||||
PlacedAfterSlidingIn?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called right after the object was grabbed.
|
||||
/// </summary>
|
||||
/// <param name="e">Event parameters</param>
|
||||
protected override void OnObjectGrabbed(UxrManipulationEventArgs e)
|
||||
{
|
||||
if (!GrabbableObject.IsLockedInPlace)
|
||||
{
|
||||
_placedAfterSlidingIn = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called right after the object was released.
|
||||
/// </summary>
|
||||
/// <param name="e">Event parameters</param>
|
||||
protected override void OnObjectReleased(UxrManipulationEventArgs e)
|
||||
{
|
||||
if (e.GrabbableObject.CurrentAnchor != null && e.GrabbableObject.RigidBodySource)
|
||||
{
|
||||
// Force kinematic while released, so that we update the position/rotation.
|
||||
e.GrabbableObject.RigidBodySource.isKinematic = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected Overrides UxrGrabbableObjectComponent<UxrAutoSlideInObject>
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool IsGrabbableObjectRequired => true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
/// <summary>
|
||||
/// Attaches the object to the anchor and assigns constraints to let it slide.
|
||||
/// </summary>
|
||||
protected void AttachObject(UxrAutoSlideInAnchor anchor)
|
||||
{
|
||||
// This method will be synchronized through network
|
||||
BeginSync();
|
||||
|
||||
// Set up constraints and place
|
||||
|
||||
GrabbableObject.TranslationConstraint = UxrTranslationConstraintMode.RestrictLocalOffset;
|
||||
GrabbableObject.RotationConstraint = UxrRotationConstraintMode.Locked;
|
||||
|
||||
GrabbableObject.TranslationLimitsMin = _translationConstraintMin;
|
||||
GrabbableObject.TranslationLimitsMax = _translationConstraintMax;
|
||||
|
||||
UxrGrabManager.Instance.PlaceObject(GrabbableObject, anchor.Anchor, UxrPlacementOptions.Smooth | UxrPlacementOptions.DontRelease, true);
|
||||
|
||||
if (_manipulationHapticFeedback)
|
||||
{
|
||||
_manipulationHapticFeedback.MinAmplitude = _minHapticAmplitude;
|
||||
_manipulationHapticFeedback.MaxAmplitude = _maxHapticAmplitude;
|
||||
}
|
||||
|
||||
EndSyncMethod(new object[] { anchor });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detaches the object from the anchor so that it becomes free.
|
||||
/// </summary>
|
||||
protected void DetachObject()
|
||||
{
|
||||
// This method will be synchronized through network
|
||||
BeginSync();
|
||||
|
||||
if (_manipulationHapticFeedback)
|
||||
{
|
||||
_minHapticAmplitude = _manipulationHapticFeedback.MinAmplitude;
|
||||
_maxHapticAmplitude = _manipulationHapticFeedback.MaxAmplitude;
|
||||
_manipulationHapticFeedback.MinAmplitude = 0.0f;
|
||||
_manipulationHapticFeedback.MaxAmplitude = 0.0f;
|
||||
}
|
||||
|
||||
UxrGrabManager.Instance.RemoveObjectFromAnchor(GrabbableObject, true);
|
||||
GrabbableObject.TranslationConstraint = UxrTranslationConstraintMode.Free;
|
||||
GrabbableObject.RotationConstraint = UxrRotationConstraintMode.Free;
|
||||
|
||||
EndSyncMethod();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the object is close enough to the given anchor to be placed.
|
||||
/// </summary>
|
||||
/// <param name="anchor">Object anchor</param>
|
||||
/// <returns>Whether the object is close enough</returns>
|
||||
private bool IsObjectNearPlacement(UxrGrabbableObjectAnchor anchor)
|
||||
{
|
||||
if (anchor.enabled == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is it near enough in the longitudinal axis?
|
||||
|
||||
float threshold = Mathf.Min(0.03f, Mathf.Abs(_insertOffset * 0.1f));
|
||||
|
||||
Vector3 localOffset = anchor.AlignTransform.InverseTransformPoint(GrabbableObject.DropAlignTransform.position) - GrabbableObject.InitialLocalPosition;
|
||||
bool isInLongitudinalAxisRange = (_insertOffsetSign > 0.0f && localOffset[_insertAxis] < +_insertOffset - threshold && localOffset[_insertAxis] > 0.0f) ||
|
||||
(_insertOffsetSign < 0.0f && localOffset[_insertAxis] > -_insertOffset + threshold && localOffset[_insertAxis] < 0.0f);
|
||||
|
||||
// Is it near enough in both other axes?
|
||||
|
||||
float minGrabDistance = float.MaxValue;
|
||||
|
||||
foreach (UxrGrabber grabber in UxrGrabManager.Instance.GetGrabbingHands(GrabbableObject))
|
||||
{
|
||||
UxrGrabPointInfo grabPointInfo = GrabbableObject.GetGrabPoint(UxrGrabManager.Instance.GetGrabbedPoint(grabber));
|
||||
|
||||
if (grabPointInfo.MaxDistanceGrab < minGrabDistance)
|
||||
{
|
||||
minGrabDistance = grabPointInfo.MaxDistanceGrab;
|
||||
}
|
||||
}
|
||||
|
||||
// We use some calculations for the other axes so that it feels good.
|
||||
|
||||
float sizeOneAxis = Mathf.Min(Mathf.Max(_objectLocalSize[_insertAxis.Perpendicular], 0.1f), minGrabDistance);
|
||||
float sizeOtherAxis = Mathf.Min(Mathf.Max(_objectLocalSize[_insertAxis.OtherPerpendicular], 0.1f), minGrabDistance);
|
||||
|
||||
// Return conditions
|
||||
|
||||
return isInLongitudinalAxisRange && Mathf.Abs(localOffset[_insertAxis.Perpendicular]) < sizeOneAxis && Mathf.Abs(localOffset[_insertAxis.OtherPerpendicular]) < sizeOtherAxis;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
private UxrAxis _insertAxis;
|
||||
private float _insertOffset;
|
||||
private float _insertOffsetSign;
|
||||
private Vector3 _objectLocalSize;
|
||||
private float _slideInTimer;
|
||||
private bool _placedAfterSlidingIn;
|
||||
private UxrManipulationHapticFeedback _manipulationHapticFeedback;
|
||||
private float _minHapticAmplitude;
|
||||
private float _maxHapticAmplitude;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user