Move third party assets to ThirdParty folder
This commit is contained in:
111
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/Battery.cs
vendored
Normal file
111
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/Battery.cs
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="Battery.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using UltimateXR.Core;
|
||||
using UltimateXR.Manipulation;
|
||||
using UltimateXR.Manipulation.Helpers;
|
||||
|
||||
namespace UltimateXR.Examples.FullScene.Lab
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that adds the smooth slide-in/slide-out effects to the grabbable battery.
|
||||
/// </summary>
|
||||
public class Battery : UxrAutoSlideInObject
|
||||
{
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to the avatars updated event so that the manipulation logic is done after UltimateXR's 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;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handling Methods
|
||||
|
||||
/// <summary>
|
||||
/// Called after UltimateXR has done all the frame updating. Does the manipulation logic.
|
||||
/// </summary>
|
||||
private void UxrManager_AvatarsUpdated()
|
||||
{
|
||||
if (GrabbableObject.CurrentAnchor != null)
|
||||
{
|
||||
// Constrain transform when the battery is inside a door
|
||||
|
||||
GeneratorDoor generatorDoor = GrabbableObject.CurrentAnchor.GetComponentInParent<GeneratorDoor>();
|
||||
|
||||
if (generatorDoor != null && !generatorDoor.IsLockOpen)
|
||||
{
|
||||
// Lock is closed? Battery can't move
|
||||
GrabbableObject.IsLockedInPlace = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Battery can move
|
||||
GrabbableObject.IsLockedInPlace = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Trigger Methods
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnPlacedAfterSlidingIn()
|
||||
{
|
||||
base.OnPlacedAfterSlidingIn();
|
||||
|
||||
if (GrabbableObject.CurrentAnchor != null)
|
||||
{
|
||||
GeneratorDoor generatorDoor = GrabbableObject.CurrentAnchor.GetComponentInParent<GeneratorDoor>();
|
||||
|
||||
if (generatorDoor != null)
|
||||
{
|
||||
// Turn lights on when the battery finished sliding in
|
||||
generatorDoor.IsBatteryInContact = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called right after the battery was grabbed.
|
||||
/// </summary>
|
||||
/// <param name="e">Event parameters</param>
|
||||
protected override void OnObjectGrabbed(UxrManipulationEventArgs e)
|
||||
{
|
||||
base.OnObjectGrabbed(e);
|
||||
|
||||
if (!GrabbableObject.IsLockedInPlace && e.GrabbableAnchor != null)
|
||||
{
|
||||
GeneratorDoor generatorDoor = e.GrabbableAnchor.GetComponentInParent<GeneratorDoor>();
|
||||
|
||||
if (generatorDoor != null)
|
||||
{
|
||||
// Turn lights off when the battery is moved away
|
||||
generatorDoor.IsBatteryInContact = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
13
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/Battery.cs.meta
vendored
Normal file
13
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/Battery.cs.meta
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9d66e1c4a827a345a4c670b5cbf2d14
|
||||
timeCreated: 1530605952
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
30
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/BatteryAnchor.cs
vendored
Normal file
30
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/BatteryAnchor.cs
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="BatteryAnchor.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using UltimateXR.Core.Components;
|
||||
using UltimateXR.Manipulation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Examples.FullScene.Lab
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that helps enumerate the potential battery anchors in a scene using LabBatteryAnchor.EnabledComponents.
|
||||
/// Battery placing has some additional special mechanics for the sliding-in/sliding-out behavior. This component
|
||||
/// helps identifying where those places are.
|
||||
/// </summary>
|
||||
public class BatteryAnchor : UxrComponent<BatteryAnchor>
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private UxrGrabbableObjectAnchor _batteryAnchor;
|
||||
|
||||
/// <summary>
|
||||
/// Actual anchor component where a battery can be placed.
|
||||
/// </summary>
|
||||
public UxrGrabbableObjectAnchor Anchor => _batteryAnchor;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
3
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/BatteryAnchor.cs.meta
vendored
Normal file
3
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/BatteryAnchor.cs.meta
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d25f62cc89144e0811a69ceae042e06
|
||||
timeCreated: 1655561113
|
||||
201
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/GeneratorDoor.cs
vendored
Normal file
201
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/GeneratorDoor.cs
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="GeneratorDoor.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using UltimateXR.Core.Components;
|
||||
using UltimateXR.Manipulation;
|
||||
using UltimateXR.Manipulation.Helpers;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Examples.FullScene.Lab
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that handles the generator door, which has battery locks to lock the battery in place.
|
||||
/// </summary>
|
||||
public class GeneratorDoor : UxrComponent<GeneratorDoor>
|
||||
{
|
||||
#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
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the battery lock is open.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public bool IsBatteryInContact { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the component.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
_lockInitialRotation = new Quaternion[_locks.Length];
|
||||
|
||||
for (int i = 0; i < _locks.Length; ++i)
|
||||
{
|
||||
_lockInitialRotation[i] = _locks[i].localRotation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to the events that help model the behavior.
|
||||
/// </summary>
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
_batteryAnchor.Anchor.Placed += Battery_Placed;
|
||||
_batteryAnchor.Anchor.Removed += Battery_Removed;
|
||||
_grabbableLock.ConstraintsApplied += Lock_ConstraintsApplied;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribes from the events.
|
||||
/// </summary>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
_batteryAnchor.Anchor.Placed -= Battery_Placed;
|
||||
_batteryAnchor.Anchor.Removed -= Battery_Removed;
|
||||
_grabbableLock.ConstraintsApplied -= Lock_ConstraintsApplied;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the lock open state.
|
||||
/// </summary>
|
||||
protected override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
IsBatteryInContact = _batteryAnchor.Anchor.CurrentPlacedObject != null;
|
||||
IsLockOpen = _startLockOpen;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the ability of the door to let a battery be placed inside.
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Called after the lock has finished being manipulated so that additional constraints can be applied to it.
|
||||
/// </summary>
|
||||
/// <param name="sender">Event sender</param>
|
||||
/// <param name="e">Event parameters</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called right after a battery was placed inside.
|
||||
/// </summary>
|
||||
/// <param name="sender">Event sender</param>
|
||||
/// <param name="e">Event parameters</param>
|
||||
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.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called right after the battery was removed.
|
||||
/// </summary>
|
||||
/// <param name="sender">Event sender</param>
|
||||
/// <param name="e">Event parameters</param>
|
||||
private void Battery_Removed(object sender, UxrManipulationEventArgs e)
|
||||
{
|
||||
IsBatteryInContact = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value between 0.0 and 1.0 telling how open the lock is.
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
||||
13
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/GeneratorDoor.cs.meta
vendored
Normal file
13
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/GeneratorDoor.cs.meta
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 241d54f6114ee524895079db62d245cf
|
||||
timeCreated: 1530536555
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
51
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/Lamp.cs
vendored
Normal file
51
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/Lamp.cs
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="Lamp.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using UltimateXR.Core.Components;
|
||||
using UltimateXR.Manipulation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Examples.FullScene.Lab
|
||||
{
|
||||
/// <summary>
|
||||
/// Binds the light intensity to the state of the currently placed light bulbs.
|
||||
/// </summary>
|
||||
public class Lamp : UxrComponent
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private UxrGrabbableObjectAnchor[] _sockets;
|
||||
[SerializeField] private Light _light;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Updates the light intensity based on the currently placed light bulbs.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
float lightBulbIntensity = 0.0f;
|
||||
|
||||
foreach (UxrGrabbableObjectAnchor socket in _sockets)
|
||||
{
|
||||
if (socket.CurrentPlacedObject != null)
|
||||
{
|
||||
LightBulb lightBulb = socket.CurrentPlacedObject.GetComponentInChildren<LightBulb>();
|
||||
|
||||
if (lightBulb != null)
|
||||
{
|
||||
lightBulbIntensity += lightBulb.Intensity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_light.intensity = lightBulbIntensity;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
13
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/Lamp.cs.meta
vendored
Normal file
13
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/Lamp.cs.meta
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1672ad7753c9a75438933016a4b0e54d
|
||||
timeCreated: 1530530970
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
82
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/Laser.LaserBurn.cs
vendored
Normal file
82
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/Laser.LaserBurn.cs
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="Laser.LaserBurn.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Examples.FullScene.Lab
|
||||
{
|
||||
public partial class Laser
|
||||
{
|
||||
#region Private Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Stores all information for a burn result of pointing the enabled laser to an object.
|
||||
/// </summary>
|
||||
private class LaserBurn
|
||||
{
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets the transform component of the GameObject that is used to represent the burn.
|
||||
/// </summary>
|
||||
public Transform Transform => GameObject.transform;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last normal of the laser impact that caused the laser burn.
|
||||
/// </summary>
|
||||
public Vector3 LastWorldNormal => Transform.TransformVector(LastNormal);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last world-space position in the burn path.
|
||||
/// </summary>
|
||||
public Vector3 LastWorldPathPosition => Transform.TransformPoint(PathPositions[PathPositions.Count - 1]);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dynamically created object to represent the burn.
|
||||
/// </summary>
|
||||
public GameObject GameObject { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dynamically created object that represents the incandescent part in the burn.
|
||||
/// </summary>
|
||||
public GameObject GameObjectIncandescent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collider that was hit.
|
||||
/// </summary>
|
||||
public Collider Collider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the burn path line renderer.
|
||||
/// </summary>
|
||||
public LineRenderer LineRenderer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the incandescent path line renderer.
|
||||
/// </summary>
|
||||
public LineRenderer IncandescentLineRenderer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the positions in the burn path.
|
||||
/// </summary>
|
||||
public List<Vector3> PathPositions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the creation times of each path position so that we can fade them out based on age.
|
||||
/// </summary>
|
||||
public List<float> PathCreationTimes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Last hit normal in local coordinates of the burn object.
|
||||
/// </summary>
|
||||
public Vector3 LastNormal { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
3
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/Laser.LaserBurn.cs.meta
vendored
Normal file
3
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/Laser.LaserBurn.cs.meta
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8164104655d2449c88e83840fb643de0
|
||||
timeCreated: 1655550081
|
||||
586
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/Laser.cs
vendored
Normal file
586
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/Laser.cs
vendored
Normal file
@@ -0,0 +1,586 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="Laser.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using System.Collections.Generic;
|
||||
using UltimateXR.Avatar;
|
||||
using UltimateXR.Core;
|
||||
using UltimateXR.Core.Components;
|
||||
using UltimateXR.Devices;
|
||||
using UltimateXR.Extensions.Unity.Math;
|
||||
using UltimateXR.Extensions.Unity.Render;
|
||||
using UltimateXR.Haptics.Helpers;
|
||||
using UltimateXR.Manipulation;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace UltimateXR.Examples.FullScene.Lab
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that handles the laser in the lab room.
|
||||
/// </summary>
|
||||
public partial class Laser : UxrComponent
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private Transform _laserSource;
|
||||
[SerializeField] private LayerMask _collisionMask = -1;
|
||||
[SerializeField] private float _laserRayWidth = 0.003f;
|
||||
[SerializeField] private float _laserRayLength = 5.0f;
|
||||
[SerializeField] private Color _laserColor = Color.red;
|
||||
[SerializeField] private float _laserBurnDelaySeconds = 0.2f; // Time the laser needs to be travelling at a speed less than LaserBurnParticlesMaxSpeed to create burn FX
|
||||
[SerializeField] private float _laserBurnSpeedThreshold = 0.4f; // Maximum speed the laser can travel before stopping particles emission
|
||||
[SerializeField] private Color _laserBurnColor = new Color(0.0f, 0.0f, 0.0f, 0.6f);
|
||||
[SerializeField] private float _laserBurnVertexDistance = 0.03f;
|
||||
[SerializeField] private float _laserBurnHeightOffset = 0.001f;
|
||||
[SerializeField] private float _laserBurnDurationFadeStart = 3.0f;
|
||||
[SerializeField] private float _laserBurnDurationFadeEnd = 6.0f;
|
||||
[SerializeField] private Color _laserBurnIncandescentColor = new Color(0.7f, 0.7f, 0.1f, 1.0f);
|
||||
[SerializeField] private float _laserBurnIncandescentDurationFadeEnd = 1.0f;
|
||||
[SerializeField] private ParticleSystem _laserBurnParticles;
|
||||
[SerializeField] private float _laserBurnParticlesHeightOffset;
|
||||
[SerializeField] private bool _laserBurnReflectParticles;
|
||||
[SerializeField] private GameObject _enableWhenLaserActive;
|
||||
[SerializeField] private UxrGrabbableObject _triggerGrabbable;
|
||||
[SerializeField] private Transform _trigger;
|
||||
[SerializeField] private Vector3 _triggerOffset;
|
||||
[SerializeField] private UxrFixedHapticFeedback _laserHaptics;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Sets up internal data.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
_triggerInitialOffset = _trigger.localPosition;
|
||||
|
||||
// Line renderer setup
|
||||
|
||||
_laserLineRenderer = gameObject.AddComponent<LineRenderer>();
|
||||
_laserLineRenderer.useWorldSpace = false;
|
||||
|
||||
SetLaserLineRendererMesh(_laserRayLength);
|
||||
|
||||
_laserLineRenderer.material = new Material(ShaderExt.UnlitTransparentColor);
|
||||
_laserLineRenderer.material.renderQueue = (int)RenderQueue.Overlay + 1;
|
||||
_laserLineRenderer.material.color = _laserColor;
|
||||
_laserLineRenderer.loop = true;
|
||||
_laserLineRenderer.enabled = false;
|
||||
|
||||
_laserBurns = new List<LaserBurn>();
|
||||
|
||||
ParticleSystem.EmissionModule emission = _laserBurnParticles.emission;
|
||||
emission.enabled = false;
|
||||
|
||||
_laserHaptics.enabled = false;
|
||||
|
||||
_createBurnTimer = _laserBurnDelaySeconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribe to avatar updated event.
|
||||
/// </summary>
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unsubscribe from avatar updated event.
|
||||
/// </summary>
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
|
||||
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handling Methods
|
||||
|
||||
/// <summary>
|
||||
/// We update the laser after all VR avatars have been updated to make sure it's processed after all manipulation.
|
||||
/// </summary>
|
||||
private void UxrManager_AvatarsUpdated()
|
||||
{
|
||||
// Check if there is a hand grabbing the laser
|
||||
|
||||
if (UxrGrabManager.Instance.GetGrabbingHand(_triggerGrabbable, 0, out UxrGrabber grabber))
|
||||
{
|
||||
// Check if it's the local avatar, because the local avatar will drive the state changes (IsLaserEnabled is network synchronized).
|
||||
|
||||
if (grabber.Avatar.AvatarMode == UxrAvatarMode.Local)
|
||||
{
|
||||
// It is! see which hand to check for a trigger squeeze
|
||||
|
||||
float trigger = UxrAvatar.LocalAvatarInput.GetInput1D(grabber.Side, UxrInput1D.Trigger);
|
||||
|
||||
_trigger.localPosition = _triggerInitialOffset + _triggerOffset * trigger;
|
||||
|
||||
_triggerGrabbable.GetGrabPoint(0).GetGripPoseInfo(grabber.Avatar).PoseBlendValue = trigger;
|
||||
|
||||
if (UxrAvatar.LocalAvatarInput.GetButtonsPress(grabber.Side, UxrInputButtons.Trigger))
|
||||
{
|
||||
// Trigger is squeezed
|
||||
|
||||
if (IsLaserEnabled == false)
|
||||
{
|
||||
IsLaserEnabled = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IsLaserEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there are no grabs, we don't need to sync using IsLaserEnabled because it can be solved locally.
|
||||
_laserLineRenderer.enabled = false;
|
||||
}
|
||||
|
||||
// Check laser raycast
|
||||
|
||||
if (IsLaserEnabled)
|
||||
{
|
||||
float currentRayLength = _laserRayLength;
|
||||
|
||||
if (Physics.Raycast(_laserSource.position, _laserSource.forward, out RaycastHit hitInfo, currentRayLength, _collisionMask, QueryTriggerInteraction.Ignore))
|
||||
{
|
||||
if (CurrentLaserBurn == null)
|
||||
{
|
||||
// This is a new burn -> initialize
|
||||
_laserBurns[_laserBurns.Count - 1] = CreateNewLaserBurn(hitInfo.collider);
|
||||
_createBurnTimer = _laserBurnDelaySeconds;
|
||||
}
|
||||
else if (CurrentLaserBurn.Collider != hitInfo.collider)
|
||||
{
|
||||
// If we hit another object we create a new laser burn entry
|
||||
_laserBurns.Add(CreateNewLaserBurn(hitInfo.collider));
|
||||
_createBurnTimer = _laserBurnDelaySeconds;
|
||||
}
|
||||
|
||||
// Check if laser travel speed is below threshold to create a burn. If so decrement timer which will start a burn if it reaches 0.
|
||||
|
||||
if (Vector3.Distance(_lastLaserHitPosition, hitInfo.point) / Time.deltaTime < _laserBurnSpeedThreshold)
|
||||
{
|
||||
_createBurnTimer -= Time.deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not enough speed -> new burn
|
||||
_laserBurns.Add(CreateNewLaserBurn(hitInfo.collider));
|
||||
_createBurnTimer = _laserBurnDelaySeconds;
|
||||
}
|
||||
|
||||
_lastLaserHitPosition = hitInfo.point;
|
||||
|
||||
// Needs to start burn FX?
|
||||
|
||||
ParticleSystem.EmissionModule emission = _laserBurnParticles.emission;
|
||||
emission.enabled = _createBurnTimer < 0.0f;
|
||||
_laserBurnParticles.transform.position = hitInfo.point + hitInfo.normal * _laserBurnParticlesHeightOffset;
|
||||
_laserBurnParticles.transform.rotation = _laserBurnReflectParticles ? Quaternion.LookRotation(Vector3.Reflect(_laserSource.forward, hitInfo.normal)) : Quaternion.Slerp(Quaternion.LookRotation(hitInfo.normal, Vector3.right), Quaternion.LookRotation(Vector3.up, Vector3.right), 0.9f);
|
||||
|
||||
if (_createBurnTimer < 0.0f)
|
||||
{
|
||||
// Burn trail
|
||||
|
||||
float segmentDistance = CurrentLaserBurn.PathPositions.Count == 0 ? 0.0f : Vector3.Distance(hitInfo.point, CurrentLaserBurn.LastWorldPathPosition);
|
||||
|
||||
if (CurrentLaserBurn.PathPositions.Count == 0 || segmentDistance > _laserBurnVertexDistance)
|
||||
{
|
||||
// Here we should create a new segment since the burn has travelled enough distance to create a new one, but first we are going to check if we somehow
|
||||
// went over a hole, bump or depression in the geometry from the last point to this one. We do not want a burn strip to appear over gaps on the geometry so in that case
|
||||
// what we will do is just create a new laser burn and skip this segment
|
||||
|
||||
if (CurrentLaserBurn.PathPositions.Count > 0 && segmentDistance > BurnGapCheckMinDistance)
|
||||
{
|
||||
bool startNewBurn = false;
|
||||
|
||||
Vector3 vectorAB = hitInfo.point - CurrentLaserBurn.LastWorldPathPosition;
|
||||
|
||||
for (int checkStep = 0; checkStep < BurnGapCheckSteps; ++checkStep)
|
||||
{
|
||||
// Perform a series of steps casting rays from the laser source to intermediate points between the last burn segment and the current one looking for changes in depth
|
||||
|
||||
float t = (checkStep + 1.0f) / (BurnGapCheckSteps + 1.0f);
|
||||
Vector3 pointCheck = hitInfo.point + vectorAB * t;
|
||||
Vector3 perpendicular = Vector3.Cross(Vector3.Cross(vectorAB.normalized, ((CurrentLaserBurn.LastWorldNormal + hitInfo.normal) * 0.5f).normalized), vectorAB.normalized);
|
||||
Vector3 raySource = CurrentLaserBurn.LastWorldPathPosition + vectorAB * 0.5f + perpendicular * BurnGapCheckRaySourceDistance;
|
||||
|
||||
if (Physics.Raycast(raySource, (pointCheck - raySource).normalized, out RaycastHit hitInfoBurnGapCheck, _laserRayLength, _collisionMask, QueryTriggerInteraction.Ignore))
|
||||
{
|
||||
float distanceToLine = hitInfoBurnGapCheck.point.DistanceToLine(CurrentLaserBurn.LastWorldPathPosition, hitInfo.point);
|
||||
|
||||
if (distanceToLine > BurnGapCheckLineDistanceThreshold)
|
||||
{
|
||||
// Depth change too big -> create new laser burn
|
||||
startNewBurn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Raycast did not find intersection -> create new laser burn
|
||||
startNewBurn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (startNewBurn)
|
||||
{
|
||||
_laserBurns.Add(CreateNewLaserBurn(hitInfo.collider));
|
||||
}
|
||||
}
|
||||
|
||||
// Add last point and offset a little bit from the geometry to draw correctly
|
||||
|
||||
CurrentLaserBurn.PathPositions.Add(CurrentLaserBurn.Transform.InverseTransformPoint(hitInfo.point + hitInfo.normal * _laserBurnHeightOffset));
|
||||
CurrentLaserBurn.PathCreationTimes.Add(Time.time);
|
||||
CurrentLaserBurn.LastNormal = CurrentLaserBurn.Transform.InverseTransformVector(hitInfo.normal);
|
||||
UpdateLaserBurnLineRenderer(CurrentLaserBurn);
|
||||
UpdateLaserBurns(0, _laserBurns.Count - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateLaserBurns(0, _laserBurns.Count);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateLaserBurns(0, _laserBurns.Count);
|
||||
}
|
||||
|
||||
currentRayLength = hitInfo.distance;
|
||||
}
|
||||
else
|
||||
{
|
||||
ParticleSystem.EmissionModule emission = _laserBurnParticles.emission;
|
||||
emission.enabled = false;
|
||||
_createBurnTimer = _laserBurnDelaySeconds;
|
||||
|
||||
UpdateLaserBurns(0, _laserBurns.Count);
|
||||
}
|
||||
|
||||
SetLaserLineRendererMesh(currentRayLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
ParticleSystem.EmissionModule emission = _laserBurnParticles.emission;
|
||||
emission.enabled = false;
|
||||
_createBurnTimer = _laserBurnDelaySeconds;
|
||||
|
||||
UpdateLaserBurns(0, _laserBurns.Count);
|
||||
}
|
||||
|
||||
if (_laserHaptics && grabber != null && grabber.Avatar.AvatarMode == UxrAvatarMode.Local)
|
||||
{
|
||||
_laserHaptics.enabled = IsLaserEnabled;
|
||||
_laserHaptics.HandSide = grabber.Side;
|
||||
}
|
||||
else
|
||||
{
|
||||
_laserHaptics.enabled = false;
|
||||
}
|
||||
|
||||
if (_enableWhenLaserActive && !_enableWhenLaserActive.activeSelf && IsLaserEnabled)
|
||||
{
|
||||
_enableWhenLaserActive.SetActive(true);
|
||||
}
|
||||
else if (_enableWhenLaserActive && _enableWhenLaserActive.activeSelf && !IsLaserEnabled)
|
||||
{
|
||||
_enableWhenLaserActive.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Updates the laser line renderer
|
||||
/// </summary>
|
||||
/// <param name="rayLength">Current laser ray length</param>
|
||||
private void SetLaserLineRendererMesh(float rayLength)
|
||||
{
|
||||
_laserLineRenderer.startWidth = _laserRayWidth;
|
||||
_laserLineRenderer.endWidth = _laserRayWidth;
|
||||
|
||||
Vector3[] positions =
|
||||
{
|
||||
new Vector3(0.0f, 0.0f, 0.0f),
|
||||
new Vector3(0.0f, 0.0f, rayLength > LaserGradientLength ? LaserGradientLength : rayLength * 0.33f),
|
||||
new Vector3(0.0f, 0.0f, rayLength < LaserGradientLength * 2.0f ? rayLength * 0.66f : rayLength - LaserGradientLength),
|
||||
new Vector3(0.0f, 0.0f, rayLength)
|
||||
};
|
||||
|
||||
for (int i = 0; i < positions.Length; ++i)
|
||||
{
|
||||
positions[i] = _laserLineRenderer.transform.InverseTransformPoint(_laserSource.TransformPoint(positions[i]));
|
||||
}
|
||||
|
||||
_laserLineRenderer.positionCount = 4;
|
||||
_laserLineRenderer.SetPositions(positions);
|
||||
|
||||
Gradient colorGradient = new Gradient();
|
||||
|
||||
colorGradient.colorKeys = new[]
|
||||
{
|
||||
new GradientColorKey(Color.white, 0.0f),
|
||||
new GradientColorKey(Color.white, rayLength > LaserGradientLength ? LaserGradientLength / rayLength : 0.33f),
|
||||
new GradientColorKey(Color.white, rayLength < LaserGradientLength * 2.0f ? 0.66f : 1.0f - LaserGradientLength / rayLength),
|
||||
new GradientColorKey(Color.white, 1.0f)
|
||||
};
|
||||
|
||||
colorGradient.alphaKeys = new[]
|
||||
{
|
||||
new GradientAlphaKey(0.0f, 0.0f),
|
||||
new GradientAlphaKey(_laserColor.a, rayLength > LaserGradientLength ? LaserGradientLength / rayLength : 0.3f),
|
||||
new GradientAlphaKey(_laserColor.a, rayLength < LaserGradientLength * 2.0f ? 0.66f : 1.0f - LaserGradientLength / rayLength),
|
||||
new GradientAlphaKey(0.0f, 1.0f)
|
||||
};
|
||||
|
||||
_laserLineRenderer.colorGradient = colorGradient;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new laser burn entry
|
||||
/// </summary>
|
||||
/// <param name="collider">Collider the laser burn is attached to</param>
|
||||
/// <returns>New laser burn object</returns>
|
||||
private LaserBurn CreateNewLaserBurn(Collider collider)
|
||||
{
|
||||
LaserBurn newLaserBurn = new LaserBurn();
|
||||
|
||||
newLaserBurn.Collider = collider;
|
||||
|
||||
newLaserBurn.GameObject = new GameObject("LaserBurn");
|
||||
newLaserBurn.GameObject.transform.parent = collider.transform;
|
||||
newLaserBurn.GameObject.transform.localPosition = Vector3.zero;
|
||||
newLaserBurn.GameObject.transform.localRotation = Quaternion.identity;
|
||||
newLaserBurn.LineRenderer = newLaserBurn.GameObject.AddComponent<LineRenderer>();
|
||||
newLaserBurn.LineRenderer.receiveShadows = true;
|
||||
newLaserBurn.LineRenderer.shadowCastingMode = ShadowCastingMode.Off;
|
||||
newLaserBurn.LineRenderer.useWorldSpace = false;
|
||||
newLaserBurn.LineRenderer.material = new Material(ShaderExt.UnlitTransparentColor);
|
||||
newLaserBurn.LineRenderer.material.renderQueue = (int)RenderQueue.Overlay + 1;
|
||||
newLaserBurn.LineRenderer.material.color = _laserBurnColor;
|
||||
newLaserBurn.LineRenderer.loop = false;
|
||||
newLaserBurn.LineRenderer.positionCount = 0;
|
||||
|
||||
newLaserBurn.GameObjectIncandescent = new GameObject("LaserBurnIncandescent");
|
||||
newLaserBurn.GameObjectIncandescent.transform.parent = collider.transform;
|
||||
newLaserBurn.GameObjectIncandescent.transform.localPosition = Vector3.zero;
|
||||
newLaserBurn.GameObjectIncandescent.transform.localRotation = Quaternion.identity;
|
||||
newLaserBurn.IncandescentLineRenderer = newLaserBurn.GameObjectIncandescent.AddComponent<LineRenderer>();
|
||||
newLaserBurn.IncandescentLineRenderer.receiveShadows = false;
|
||||
newLaserBurn.IncandescentLineRenderer.shadowCastingMode = ShadowCastingMode.Off;
|
||||
newLaserBurn.IncandescentLineRenderer.useWorldSpace = false;
|
||||
newLaserBurn.IncandescentLineRenderer.material = new Material(ShaderExt.UnlitAdditiveColor);
|
||||
newLaserBurn.IncandescentLineRenderer.material.renderQueue = (int)RenderQueue.Overlay + 2;
|
||||
newLaserBurn.IncandescentLineRenderer.material.color = _laserBurnIncandescentColor;
|
||||
newLaserBurn.IncandescentLineRenderer.loop = false;
|
||||
newLaserBurn.IncandescentLineRenderer.positionCount = 0;
|
||||
|
||||
newLaserBurn.PathPositions = new List<Vector3>();
|
||||
newLaserBurn.PathCreationTimes = new List<float>();
|
||||
|
||||
_createBurnTimer = _laserBurnDelaySeconds;
|
||||
|
||||
return newLaserBurn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the given range of laser burns. Unused laser burns will be deleted
|
||||
/// </summary>
|
||||
/// <param name="startIndex">The start index</param>
|
||||
/// <param name="count">The number of items to update</param>
|
||||
private void UpdateLaserBurns(int startIndex, int count)
|
||||
{
|
||||
for (int i = startIndex; i < startIndex + count && i < _laserBurns.Count; ++i)
|
||||
{
|
||||
UpdateLaserBurnLineRenderer(_laserBurns[i]);
|
||||
|
||||
if (_laserBurns[i].PathPositions.Count <= 1 && _laserBurns[i] != CurrentLaserBurn)
|
||||
{
|
||||
Destroy(_laserBurns[i].GameObject);
|
||||
Destroy(_laserBurns[i].GameObjectIncandescent);
|
||||
_laserBurns.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the laser burn line renderer
|
||||
/// </summary>
|
||||
/// <param name="laserBurn">LaserBurn object whose LineRenderer to update</param>
|
||||
private void UpdateLaserBurnLineRenderer(LaserBurn laserBurn)
|
||||
{
|
||||
if (laserBurn == null || laserBurn.GameObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
laserBurn.LineRenderer.startWidth = _laserRayWidth * 2.5f;
|
||||
laserBurn.LineRenderer.endWidth = _laserRayWidth * 2.5f;
|
||||
laserBurn.IncandescentLineRenderer.startWidth = _laserRayWidth * 1.5f;
|
||||
laserBurn.IncandescentLineRenderer.endWidth = _laserRayWidth * 1.5f;
|
||||
|
||||
// Remove segment positions that have already faded out. Keep just 1 if all need to be deleted to have LastPathPosition accessible.
|
||||
|
||||
int lastIndexToDelete = -1;
|
||||
|
||||
for (int i = 0; i < laserBurn.PathCreationTimes.Count; ++i)
|
||||
{
|
||||
if (Time.time - laserBurn.PathCreationTimes[i] >= _laserBurnDurationFadeEnd)
|
||||
{
|
||||
lastIndexToDelete = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastIndexToDelete >= 0 && laserBurn.PathPositions.Count > 1)
|
||||
{
|
||||
laserBurn.PathCreationTimes.RemoveRange(0, lastIndexToDelete + 1);
|
||||
laserBurn.PathPositions.RemoveRange(0, lastIndexToDelete + 1);
|
||||
}
|
||||
|
||||
// Inside the burn range, compute the start index for the incandescent part
|
||||
|
||||
int incandescentIndexStart = 0;
|
||||
|
||||
for (int i = 0; i < laserBurn.PathCreationTimes.Count; ++i)
|
||||
{
|
||||
if (Time.time - laserBurn.PathCreationTimes[i] < _laserBurnIncandescentDurationFadeEnd)
|
||||
{
|
||||
incandescentIndexStart = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create color and alpha gradients. Maximum number of entries for Unity's LineRenderer are 8.
|
||||
|
||||
if (laserBurn.PathCreationTimes.Count > 0)
|
||||
{
|
||||
Gradient colorGradient = new Gradient();
|
||||
Gradient colorGradientIncandescent = new Gradient();
|
||||
|
||||
colorGradient.colorKeys = new[] { new GradientColorKey(Color.white, 0.0f), new GradientColorKey(Color.white, 1.0f) };
|
||||
colorGradientIncandescent.colorKeys = new[] { new GradientColorKey(Color.white, 0.0f), new GradientColorKey(Color.white, 1.0f) };
|
||||
|
||||
GradientAlphaKey[] alphaKeys = new GradientAlphaKey[8];
|
||||
GradientAlphaKey[] alphaKeysIncandescent = new GradientAlphaKey[8];
|
||||
|
||||
for (int i = 0; i < alphaKeys.Length; ++i)
|
||||
{
|
||||
float t = i / (alphaKeys.Length - 1.0f);
|
||||
int timeIndex = Mathf.Clamp(Mathf.RoundToInt(t * (laserBurn.PathCreationTimes.Count - 1.0f)), 0, laserBurn.PathCreationTimes.Count - 1);
|
||||
float life = Time.time - laserBurn.PathCreationTimes[timeIndex];
|
||||
alphaKeys[i].alpha = life < _laserBurnDurationFadeStart ? 1.0f : 1.0f - Mathf.Clamp01((life - _laserBurnDurationFadeStart) / (_laserBurnDurationFadeEnd - _laserBurnDurationFadeStart));
|
||||
alphaKeys[i].time = t;
|
||||
}
|
||||
|
||||
for (int i = 0; i < alphaKeysIncandescent.Length; ++i)
|
||||
{
|
||||
float t = i / (alphaKeysIncandescent.Length - 1.0f);
|
||||
int numIncandescentEntries = laserBurn.PathCreationTimes.Count - incandescentIndexStart;
|
||||
int timeIndex = Mathf.Clamp(Mathf.RoundToInt(t * (numIncandescentEntries - 1.0f)), 0, numIncandescentEntries - 1);
|
||||
float life = Time.time - laserBurn.PathCreationTimes[incandescentIndexStart + timeIndex];
|
||||
alphaKeysIncandescent[i].alpha = 1.0f - Mathf.Clamp01(life / _laserBurnIncandescentDurationFadeEnd);
|
||||
alphaKeysIncandescent[i].time = t;
|
||||
}
|
||||
|
||||
colorGradient.alphaKeys = alphaKeys;
|
||||
colorGradientIncandescent.alphaKeys = alphaKeysIncandescent;
|
||||
|
||||
laserBurn.LineRenderer.colorGradient = colorGradient;
|
||||
laserBurn.IncandescentLineRenderer.colorGradient = colorGradientIncandescent;
|
||||
}
|
||||
|
||||
// Update positions
|
||||
|
||||
if (laserBurn.PathPositions.Count > 1)
|
||||
{
|
||||
laserBurn.LineRenderer.positionCount = laserBurn.PathPositions.Count;
|
||||
laserBurn.LineRenderer.SetPositions(laserBurn.PathPositions.ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
laserBurn.LineRenderer.positionCount = 0;
|
||||
}
|
||||
|
||||
if (incandescentIndexStart < laserBurn.PathPositions.Count - 1)
|
||||
{
|
||||
int count = laserBurn.PathPositions.Count - incandescentIndexStart;
|
||||
laserBurn.IncandescentLineRenderer.positionCount = count;
|
||||
laserBurn.IncandescentLineRenderer.SetPositions(laserBurn.PathPositions.GetRange(incandescentIndexStart, count).ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
laserBurn.IncandescentLineRenderer.positionCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the laser is enabled.
|
||||
/// </summary>
|
||||
private bool IsLaserEnabled
|
||||
{
|
||||
get => _laserLineRenderer.enabled;
|
||||
set
|
||||
{
|
||||
if (_laserLineRenderer.enabled != value)
|
||||
{
|
||||
BeginSync();
|
||||
|
||||
if (value)
|
||||
{
|
||||
// Start a new empty laser burn (it will be setup later, we use null to force to set up a new one)
|
||||
_laserBurns.Add(null);
|
||||
}
|
||||
|
||||
_laserLineRenderer.enabled = value;
|
||||
EndSyncProperty(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current laser burn being generated.
|
||||
/// </summary>
|
||||
private LaserBurn CurrentLaserBurn => _laserBurns[_laserBurns.Count - 1];
|
||||
|
||||
private const float LaserGradientLength = 0.4f; // Laser is rendered with a gradient of this length to make it a little less dull
|
||||
private const float BurnGapCheckSteps = 4; // Number of steps to check for gaps in the geometry when computing laser burns
|
||||
private const float BurnGapCheckMinDistance = 0.02f; // Only perform gap checks if the separation between two consecutive laser burn segments is greater than this value
|
||||
private const float BurnGapCheckRaySourceDistance = 0.1f; // The raycasts performed on each gap check step will be casted from this distance to the geometry
|
||||
private const float BurnGapCheckLineDistanceThreshold = 0.002f; // Allow this amount of depth variation when checking for gaps or bumps in the geometry
|
||||
|
||||
private Vector3 _triggerInitialOffset;
|
||||
private LineRenderer _laserLineRenderer;
|
||||
private List<LaserBurn> _laserBurns;
|
||||
private Vector3 _lastLaserHitPosition;
|
||||
private float _createBurnTimer;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
13
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/Laser.cs.meta
vendored
Normal file
13
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/Laser.cs.meta
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82bab9d2f1f530649af83fd846d679e9
|
||||
timeCreated: 1530622409
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
112
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/LightBulb.cs
vendored
Normal file
112
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/LightBulb.cs
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="LightBulb.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using UltimateXR.Core;
|
||||
using UltimateXR.Core.Components;
|
||||
using UltimateXR.Manipulation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Examples.FullScene.Lab
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows to model light bulbs that will affect the light attached to the <see cref="Lamp" /> they are placed on.
|
||||
/// </summary>
|
||||
public class LightBulb : UxrComponent
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private UxrGrabbableObject _grabbableObject;
|
||||
[SerializeField] private float _lightIntensity;
|
||||
[SerializeField] private bool _isFaulty;
|
||||
[SerializeField] private Color _emissiveDisabled = Color.black;
|
||||
[SerializeField] private Color _emissiveEnabled = Color.white;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Types & Data
|
||||
|
||||
/// <summary>
|
||||
/// Gets the light intensity contributed by the light bulb, which may flicker if it's faulty or be zero if it's not
|
||||
/// connected to the lamp.
|
||||
/// </summary>
|
||||
public float Intensity
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_grabbableObject.CurrentAnchor == null)
|
||||
{
|
||||
// Not attached to anything
|
||||
return 0.0f;
|
||||
}
|
||||
if (_grabbableObject.CurrentAnchor.GetComponentInParent<Lamp>())
|
||||
{
|
||||
// Attached to a lamp. See if it is faulty or works correctly.
|
||||
|
||||
if (_isFaulty)
|
||||
{
|
||||
float noise = Mathf.PerlinNoise(_randX + Time.time * 20.0f, _randY * 10.0f);
|
||||
|
||||
if (noise > 0.66f)
|
||||
{
|
||||
return _lightIntensity;
|
||||
}
|
||||
if (noise > 0.16f)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
return _lightIntensity * 0.5f;
|
||||
}
|
||||
return _lightIntensity;
|
||||
}
|
||||
// Not attached to a lamp
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the component.
|
||||
/// </summary>
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
Renderer renderer = GetComponent<Renderer>();
|
||||
|
||||
if (renderer)
|
||||
{
|
||||
_material = renderer.material;
|
||||
}
|
||||
|
||||
_randX = Random.value;
|
||||
_randY = Random.value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the emissive based on the light intensity.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
if (_material && _grabbableObject && _lightIntensity > 0.0f)
|
||||
{
|
||||
Color emissiveColor = Color.Lerp(_emissiveDisabled, _emissiveEnabled, Intensity / _lightIntensity);
|
||||
_material.SetColor(UxrConstants.Shaders.EmissionColorVarName, emissiveColor);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
private Material _material;
|
||||
private float _randX;
|
||||
private float _randY;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
13
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/LightBulb.cs.meta
vendored
Normal file
13
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/LightBulb.cs.meta
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70e977db6e159b54f80cba9f6dc6f6a3
|
||||
timeCreated: 1530531429
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
122
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/SpringOnRelease.cs
vendored
Normal file
122
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/SpringOnRelease.cs
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
// <copyright file="SpringOnRelease.cs" company="VRMADA">
|
||||
// Copyright (c) VRMADA, All rights reserved.
|
||||
// </copyright>
|
||||
// --------------------------------------------------------------------------------------------------------------------
|
||||
using UltimateXR.Core.Components.Composite;
|
||||
using UltimateXR.Manipulation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UltimateXR.Examples.FullScene.Lab
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a spring behavior to an object whenever it is released.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(UxrGrabbableObject))]
|
||||
public class SpringOnRelease : UxrGrabbableObjectComponent<SpringOnRelease>
|
||||
{
|
||||
#region Inspector Properties/Serialized Fields
|
||||
|
||||
[SerializeField] private float _springAmplitudeMultiplier;
|
||||
[SerializeField] private float _springMaxAmplitude;
|
||||
[SerializeField] private float _springRotAmplitudeMultiplier;
|
||||
[SerializeField] private float _springMaxRotAmplitude;
|
||||
[SerializeField] private float _springDuration;
|
||||
[SerializeField] private float _springFrequency;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity
|
||||
|
||||
/// <summary>
|
||||
/// Updates the spring if it is currently active.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
if (_timer > 0.0f)
|
||||
{
|
||||
_timer -= Time.deltaTime;
|
||||
|
||||
if (_timer < 0.0f)
|
||||
{
|
||||
transform.position = _releasePosition;
|
||||
transform.eulerAngles = _releaseEuler;
|
||||
}
|
||||
else
|
||||
{
|
||||
float t = (_springDuration - _timer) / _springDuration;
|
||||
Vector3 delta = _filteredVelocity * (Mathf.Sin(-(_springDuration - _timer) * Mathf.PI * 2.0f * _springFrequency) * (1.0f - t));
|
||||
transform.position = _releasePosition + delta;
|
||||
|
||||
Vector3 deltaEuler = _filteredAngularVelocity * (Mathf.Sin(-(_springDuration - _timer) * Mathf.PI * 2.0f * _springFrequency) * (1.0f - t));
|
||||
transform.eulerAngles = _releaseEuler + deltaEuler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Trigger Methods
|
||||
|
||||
/// <summary>
|
||||
/// Called right after the object was grabbed.
|
||||
/// </summary>
|
||||
/// <param name="e">Event parameters</param>
|
||||
protected override void OnObjectGrabbed(UxrManipulationEventArgs e)
|
||||
{
|
||||
_timer = -1.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called right after the object was released.
|
||||
/// </summary>
|
||||
/// <param name="e">Event parameters</param>
|
||||
protected override void OnObjectReleased(UxrManipulationEventArgs e)
|
||||
{
|
||||
if (e.IsGrabbedStateChanged)
|
||||
{
|
||||
_releasePosition = transform.position;
|
||||
_releaseEuler = transform.eulerAngles;
|
||||
_releaseVelocity = e.ReleaseVelocity;
|
||||
_releaseAngularVelocity = e.ReleaseAngularVelocity;
|
||||
_timer = _springDuration;
|
||||
|
||||
_filteredVelocity = _releaseVelocity * _springAmplitudeMultiplier;
|
||||
|
||||
if (_filteredVelocity.magnitude > _springMaxAmplitude)
|
||||
{
|
||||
_filteredVelocity = _filteredVelocity.normalized * _springMaxAmplitude;
|
||||
}
|
||||
|
||||
_filteredAngularVelocity = _releaseAngularVelocity * _springRotAmplitudeMultiplier;
|
||||
|
||||
if (Mathf.Abs(_filteredAngularVelocity.x) > _springMaxRotAmplitude)
|
||||
{
|
||||
_filteredAngularVelocity.x = _filteredAngularVelocity.x > 0.0f ? _springMaxRotAmplitude : -_springMaxRotAmplitude;
|
||||
}
|
||||
if (Mathf.Abs(_filteredAngularVelocity.y) > _springMaxRotAmplitude)
|
||||
{
|
||||
_filteredAngularVelocity.y = _filteredAngularVelocity.y > 0.0f ? _springMaxRotAmplitude : -_springMaxRotAmplitude;
|
||||
}
|
||||
if (Mathf.Abs(_filteredAngularVelocity.z) > _springMaxRotAmplitude)
|
||||
{
|
||||
_filteredAngularVelocity.z = _filteredAngularVelocity.z > 0.0f ? _springMaxRotAmplitude : -_springMaxRotAmplitude;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Types & Data
|
||||
|
||||
private Vector3 _releasePosition;
|
||||
private Vector3 _releaseEuler;
|
||||
private Vector3 _releaseVelocity;
|
||||
private Vector3 _releaseAngularVelocity;
|
||||
private Vector3 _filteredVelocity;
|
||||
private Vector3 _filteredAngularVelocity;
|
||||
private float _timer;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
13
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/SpringOnRelease.cs.meta
vendored
Normal file
13
Assets/ThirdParty/UltimateXR/Samples/FullScene/Scripts/Lab/SpringOnRelease.cs.meta
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e9ebb4e3c01c8c4287ce10c7e1f4edd
|
||||
timeCreated: 1530876385
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user