Files
dungeons/Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Doors/HandScanner.cs

347 lines
15 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="HandScanner.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Animation.Interpolation;
using UltimateXR.Animation.Materials;
using UltimateXR.Audio;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Extensions.Unity.Math;
using UltimateXR.Extensions.Unity.Render;
using UnityEngine;
namespace UltimateXR.Examples.FullScene.Doors
{
/// <summary>
/// Component that handles the hand scanning required to open an <see cref="ArmoredDoor" />.
/// </summary>
public class HandScanner : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private ArmoredDoor _armoredDoor;
[SerializeField] private Renderer _validLight;
[SerializeField] private Renderer _invalidLight;
[SerializeField] private Renderer _scannerBeam;
[SerializeField] private Vector3 _scannerBeamTopLocalPos;
[SerializeField] private Vector3 _scannerBeamBottomLocalPos;
[SerializeField] private int _beamCount = 5;
[SerializeField] [Range(0.0f, 1.0f)] private float _beamTrailDelay = 0.1f;
[SerializeField] private UxrEasing _beamEeasing = UxrEasing.EaseInOutQuint;
[SerializeField] private Vector3 _beamMaxScale = Vector3.one;
[SerializeField] private Renderer _handRendererLeft;
[SerializeField] private Renderer _handRendererRight;
[SerializeField] private UxrHandSide _defaultHandSide = UxrHandSide.Right;
[SerializeField] private BoxCollider _handBoxValidPos;
[SerializeField] private float _scanSeconds = 1.5f;
[SerializeField] private float _resultSeconds = 2.0f;
[SerializeField] private UxrAudioSample _audioScan;
[SerializeField] private UxrAudioSample _audioError;
[SerializeField] private UxrAudioSample _audioOk;
#endregion
#region Public Types & Data
/// <summary>
/// Event called right after a hand was scanned. Parameters are the avatar that was scanned and if the scan granted
/// access.
/// </summary>
public event Action<UxrAvatar, UxrHandSide, bool> HandScanned;
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
_colorValid = _validLight.sharedMaterial.GetColor(UxrConstants.Shaders.StandardColorVarName);
_colorInvalid = _invalidLight.sharedMaterial.GetColor(UxrConstants.Shaders.StandardColorVarName);
_handSide = _defaultHandSide;
_beamScale = _scannerBeam.transform.localScale;
_beams.Add(_scannerBeam);
for (int i = 0; i < _beamCount - 1; ++i)
{
_beams.Add(Instantiate(_scannerBeam.gameObject, _scannerBeam.transform.position, _scannerBeam.transform.rotation, _scannerBeam.transform.parent).GetComponent<Renderer>());
}
}
/// <summary>
/// Sets the default scanning state.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
_scanReady = true;
_validLight.enabled = false;
_invalidLight.enabled = false;
_handRendererLeft.enabled = _defaultHandSide == UxrHandSide.Left;
_handRendererRight.enabled = _defaultHandSide == UxrHandSide.Right;
_handRendererLeft.material.color = ColorExt.ColorAlpha(_handRendererLeft.material.color, _defaultHandSide == UxrHandSide.Left ? 1.0f : 0.0f);
_handRendererRight.material.color = ColorExt.ColorAlpha(_handRendererRight.material.color, _defaultHandSide == UxrHandSide.Right ? 1.0f : 0.0f);
EnableBeams(false);
}
/// <summary>
/// Disables the component.
/// </summary>
protected override void OnDisable()
{
base.OnEnable();
_scanReady = true;
_validLight.enabled = false;
_invalidLight.enabled = false;
EnableBeams(false);
}
/// <summary>
/// Updates the component. Performs the scanning process.
/// </summary>
private void Update()
{
if (UxrAvatar.LocalAvatar == null || !_scanReady)
{
return;
}
// Update scanning & beam
if (_scanTimer < 0.0f)
{
if (_armoredDoor != null && _armoredDoor.IsOpen)
{
// If we are controlling an armored door and it is already open, ignore the hands.
}
else
{
// Waiting for hand to be scanned. Look for hand:
if (UxrAvatar.LocalAvatar.GetHandBone(UxrHandSide.Left).position.IsInsideBox(_handBoxValidPos))
{
StartScan(UxrAvatar.LocalAvatar, UxrHandSide.Left);
}
else if (UxrAvatar.LocalAvatar.GetHandBone(UxrHandSide.Right).position.IsInsideBox(_handBoxValidPos))
{
StartScan(UxrAvatar.LocalAvatar, UxrHandSide.Right);
}
}
}
else
{
// Hand is scanning
_scanTimer += Time.deltaTime;
EnableBeams(true);
for (int i = 0; i < _beams.Count; ++i)
{
float beamStartTime = i / (_beams.Count == 1 ? 1.0f : _beams.Count - 1.0f) * _beamTrailDelay * _scanSeconds;
float beamDuration = _scanSeconds - _beamTrailDelay;
float t = Mathf.Clamp01((_scanTimer - beamStartTime) / beamDuration);
float tScale = 1.0f - Mathf.Abs(t - 0.5f) * 2.0f;
_beams[i].transform.localPosition = Vector3.Lerp(_scannerBeamTopLocalPos, _scannerBeamBottomLocalPos, UxrInterpolator.GetInterpolationFactor(t, _beamEeasing));
_beams[i].transform.localScale = Vector3.Lerp(_beamScale, _beamMaxScale, Mathf.Pow(tScale, 8.0f));
}
// Check for conditions
if (_avatarScanning == UxrAvatar.LocalAvatar)
{
if (UxrAvatar.LocalAvatar.GetHandBone(_handSide).position.IsInsideBox(_handBoxValidPos))
{
if (!UxrAvatar.LocalAvatar.GetHandBone(UxrUtils.GetOppositeSide(_handSide)).position.IsInsideBox(_handBoxValidPos))
{
// Keep scanning
if (_scanTimer > _scanSeconds)
{
ProcessScanResult(UxrAvatar.LocalAvatar, _handSide, true);
if (_armoredDoor != null)
{
_armoredDoor.OpenDoor(false);
}
}
}
else
{
// Opposite hand got in. Aborting.
ProcessScanResult(UxrAvatar.LocalAvatar, _handSide, false);
}
}
else
{
// Scanning hand got out. Aborting.
ProcessScanResult(UxrAvatar.LocalAvatar, _handSide, false);
}
}
}
// Update scan side
if (_handSide == UxrHandSide.Left)
{
if (_handRendererRight.enabled)
{
float rightAlpha = _handRendererRight.material.color.a - Time.deltaTime * HandAlphaSwitchSpeed;
_handRendererRight.material.color = ColorExt.ColorAlpha(_handRendererRight.material.color, Mathf.Clamp01(rightAlpha));
if (rightAlpha < 0.0f)
{
_handRendererRight.enabled = false;
}
}
if (!_handRendererLeft.enabled)
{
_handRendererLeft.enabled = true;
}
_handRendererLeft.material.color = ColorExt.ColorAlpha(_handRendererLeft.material.color, Mathf.Clamp01(_handRendererLeft.material.color.a + Time.deltaTime * HandAlphaSwitchSpeed));
}
else
{
if (_handRendererLeft.enabled)
{
float leftAlpha = _handRendererLeft.material.color.a - Time.deltaTime * HandAlphaSwitchSpeed;
_handRendererLeft.material.color = ColorExt.ColorAlpha(_handRendererLeft.material.color, Mathf.Clamp01(leftAlpha));
if (leftAlpha < 0.0f)
{
_handRendererLeft.enabled = false;
}
}
if (!_handRendererRight.enabled)
{
_handRendererRight.enabled = true;
}
_handRendererRight.material.color = ColorExt.ColorAlpha(_handRendererRight.material.color, Mathf.Clamp01(_handRendererRight.material.color.a + Time.deltaTime * HandAlphaSwitchSpeed));
}
}
#endregion
#region Private Methods
/// <summary>
/// Starts scanning a hand.
/// </summary>
/// <param name="avatarScanning">The avatar that started scanning</param>
/// <param name="handSide">The side of the hand being scanned</param>
private void StartScan(UxrAvatar avatarScanning, UxrHandSide handSide)
{
BeginSync();
_scanTimer = 0.0f;
_handSide = handSide;
_avatarScanning = avatarScanning;
_audioScan.Play(transform.position);
EndSyncMethod(new object[] { avatarScanning, handSide });
}
/// <summary>
/// Enables/disables the scanning beams.
/// </summary>
/// <param name="enable">Whether the beams should be enabled</param>
private void EnableBeams(bool enable)
{
_beams.ForEach(r => r.enabled = enable);
}
/// <summary>
/// Processes a scan result.
/// </summary>
/// <param name="avatar">Avatar that was scanned</param>
/// <param name="handSide">Which hand was scanned</param>
/// <param name="isValid">Whether the access was granted</param>
private void ProcessScanResult(UxrAvatar avatar, UxrHandSide handSide, bool isValid)
{
BeginSync();
EnableBeams(false);
_scanReady = false;
_scanTimer = -1.0f;
if (isValid)
{
_audioOk.Play(transform.position);
_validLight.enabled = true;
UxrAnimatedMaterial.AnimateBlinkColor(_validLight.gameObject,
UxrConstants.Shaders.StandardColorVarName,
_colorValid.WithAlpha(0.0f),
_colorValid,
UxrAnimatedMaterial.DefaultBlinkFrequency,
_resultSeconds,
UxrMaterialMode.InstanceOnly,
() =>
{
_scanReady = true;
_validLight.enabled = false;
});
}
else
{
_audioError.Play(transform.position);
_invalidLight.enabled = true;
UxrAnimatedMaterial.AnimateBlinkColor(_invalidLight.gameObject,
UxrConstants.Shaders.StandardColorVarName,
_colorInvalid.WithAlpha(0.0f),
_colorInvalid,
UxrAnimatedMaterial.DefaultBlinkFrequency,
_resultSeconds,
UxrMaterialMode.InstanceOnly,
() =>
{
_scanReady = true;
_invalidLight.enabled = false;
});
}
HandScanned?.Invoke(avatar, handSide, isValid);
EndSyncMethod(new object[] { avatar, handSide, isValid });
}
#endregion
#region Private Types & Data
/// <summary>
/// Controls how fast the hand image will switch from one side to the other when a different hand than the currently
/// shown is placed on the scanner.
/// </summary>
private const float HandAlphaSwitchSpeed = 4.0f;
private readonly List<Renderer> _beams = new List<Renderer>();
private bool _scanReady = true;
private float _scanTimer = -1.0f;
private Vector3 _beamScale;
private UxrAvatar _avatarScanning;
private UxrHandSide _handSide;
private Color _colorValid;
private Color _colorInvalid;
#endregion
}
}