// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) VRMADA, All rights reserved.
//
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UltimateXR.Manipulation;
using UltimateXR.Manipulation.Helpers;
using UnityEngine;
namespace UltimateXR.Examples.FullScene.Lab
{
///
/// Component that handles the generator door, which has battery locks to lock the battery in place.
///
public class GeneratorDoor : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private UxrAutoSlideInAnchor _batteryAnchor;
[SerializeField] private UxrGrabbableObject _grabbableLock;
[SerializeField] private Transform[] _locks;
[SerializeField] private float _lockHandleAngleClosed;
[SerializeField] private float _lockHandleAngleOpen;
[SerializeField] private bool _startLockOpen = true;
#endregion
#region Public Types & Data
///
/// Gets whether the battery lock is open.
///
public bool IsLockOpen
{
get => LockHandleOpenValue > 0.5f;
private set
{
// Set rotation using the correct property to avoid interference between grabbable object constraint calculation and manually setting its transform.
_grabbableLock.SingleRotationAxisDegrees = value ? _lockHandleAngleOpen : _lockHandleAngleClosed;
for (int i = 0; i < _locks.Length; ++i)
{
_locks[i].transform.localRotation = _lockInitialRotation[i] * Quaternion.AngleAxis((value ? 1.0f : 0.0f) * (_lockHandleAngleOpen - _lockHandleAngleClosed), Vector3.right);
}
}
}
///
/// Gets whether there is a battery placed inside the generator and it is currently in contact with the bottom. This
/// allows to switch things on when the battery is actually in contact instead of being placed, because there is a
/// slide-in animation after the battery has been placed.
///
public bool IsBatteryInContact { get; set; }
#endregion
#region Unity
///
/// Initializes the component.
///
protected override void Awake()
{
base.Awake();
_lockInitialRotation = new Quaternion[_locks.Length];
for (int i = 0; i < _locks.Length; ++i)
{
_lockInitialRotation[i] = _locks[i].localRotation;
}
}
///
/// Subscribes to the events that help model the behavior.
///
protected override void OnEnable()
{
base.OnEnable();
_batteryAnchor.Anchor.Placed += Battery_Placed;
_batteryAnchor.Anchor.Removed += Battery_Removed;
_grabbableLock.ConstraintsApplied += Lock_ConstraintsApplied;
}
///
/// Unsubscribes from the events.
///
protected override void OnDisable()
{
base.OnDisable();
_batteryAnchor.Anchor.Placed -= Battery_Placed;
_batteryAnchor.Anchor.Removed -= Battery_Removed;
_grabbableLock.ConstraintsApplied -= Lock_ConstraintsApplied;
}
///
/// Initializes the lock open state.
///
protected override void Start()
{
base.Start();
IsBatteryInContact = _batteryAnchor.Anchor.CurrentPlacedObject != null;
IsLockOpen = _startLockOpen;
}
///
/// Updates the ability of the door to let a battery be placed inside.
///
private void Update()
{
// The battery door anchor is disabled if the lock is closed and there is no battery inside
if (_batteryAnchor.Anchor.CurrentPlacedObject == null && !IsLockOpen)
{
_batteryAnchor.enabled = false;
}
else
{
_batteryAnchor.enabled = true;
}
}
#endregion
#region Event Handling Methods
///
/// Called after the lock has finished being manipulated so that additional constraints can be applied to it.
///
/// Event sender
/// Event parameters
private void Lock_ConstraintsApplied(object sender, UxrApplyConstraintsEventArgs e)
{
float lockHandleOpenValue = LockHandleOpenValue;
float locksOpenValue = 1.0f - (1.0f - lockHandleOpenValue) * (1.0f - lockHandleOpenValue);
// Update small locks based on the main lock open value
for (int i = 0; i < _locks.Length; ++i)
{
_locks[i].transform.localRotation = _lockInitialRotation[i] * Quaternion.AngleAxis(locksOpenValue * (_lockHandleAngleOpen - _lockHandleAngleClosed), Vector3.right);
}
// Main lock can be manipulated only while the battery is completely inside or there is no battery
if (_batteryAnchor.Anchor.CurrentPlacedObject != null && _batteryAnchor.Anchor.CurrentPlacedObject.transform.localPosition.z > 0.01f)
{
_grabbableLock.IsLockedInPlace = true;
}
else
{
_grabbableLock.IsLockedInPlace = false;
}
}
///
/// Called right after a battery was placed inside.
///
/// Event sender
/// Event parameters
private void Battery_Placed(object sender, UxrManipulationEventArgs e)
{
// In order to make the lights turn on only when the battery reached the bottom, we control this from the Battery component.
}
///
/// Called right after the battery was removed.
///
/// Event sender
/// Event parameters
private void Battery_Removed(object sender, UxrManipulationEventArgs e)
{
IsBatteryInContact = false;
}
#endregion
#region Private Types & Data
///
/// Returns a value between 0.0 and 1.0 telling how open the lock is.
///
private float LockHandleOpenValue
{
get
{
float lockHandleOpenValue = Mathf.Clamp01((_grabbableLock.transform.localRotation.eulerAngles.z - _lockHandleAngleClosed) / (_lockHandleAngleOpen - _lockHandleAngleClosed));
return lockHandleOpenValue;
}
}
private bool _isBatteryInContact;
private Quaternion[] _lockInitialRotation;
#endregion
}
}