UP-Viagg-io/Viagg-io/Assets/afca/ViaggioAI/Scripts/Helpers/ThresholdedBillboard.cs

327 lines
8.9 KiB
C#
Executable File

// 04.10.2021 11:00
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
public enum PivotAxis
{
Free = 10,
X = 20,
Y = 30,
Z = 40,
XY = 50,
XZ = 60,
YZ = 70,
}
public enum EEnablingBehaviour
{
Transition = 0,
Hold = 10,
Jump = 20,
}
public class ThresholdedBillboard : MonoBehaviour
{
#region Inspector Properties
[Header("Config Values")]
[Tooltip("Specifies the axis about which the object will rotate.")]
[SerializeField]
private PivotAxis pivotAxis = PivotAxis.XY;
[Tooltip("Specifies the desired OnEnable behaviour.")]
[SerializeField]
private EEnablingBehaviour enablingBehaviour;
[Tooltip("Specifies the delay in seconds before initializing.")]
[SerializeField]
private float initDelay = 0.03f;
[Tooltip("Specifies how fast the object will rotate.")]
[SerializeField]
private float rotationSpeed = 2f;
[Tooltip("Specifies the deviation at which the object will start rotating.")]
[SerializeField]
private float startBillboardingAngle = 20f;
[Tooltip("Specifies the deviation at which the object will stop rotating.")]
[SerializeField]
private float stopBillboardingAngle = 2f;
[Tooltip("Specifies if the rotation should happen after a delay.")]
[SerializeField]
private bool isDelayedModeActive = false;
[Tooltip("Only for delayed Mode: Specifies the delay before the rotation should happen.")]
[SerializeField]
private float delayedModeDelay = 0.3f;
[Header("Scene Objects")]
[Tooltip("Specifies the target we will orient to. If no target is specified, the main camera will be used.")]
[SerializeField]
private Transform targetTransform;
[Tooltip("Specifies transforms which will be set to the same rotation.")]
[SerializeField]
private List<Transform> linkedTransforms;
#endregion
#region Public Properties
public PivotAxis PivotAxis
{
get { return pivotAxis; }
set { pivotAxis = value; }
}
/// <summary>
/// The target we will orient to. If no target is specified, the main camera will be used.
/// </summary>
public Transform TargetTransform => targetTransform;
#endregion
#region Private Properties
private bool isInitialized = false;
private Camera cam;
private Quaternion rotationFrom;
private Quaternion rotationTo;
private bool delayedRotationIsPending = false;
private float lastRotationRequiredTime = 0;
#endregion
#region Framework Functions
void Awake()
{
this.cam = Camera.main;
}
void OnEnable()
{
this.initBillboardWithDelay();
}
void Update()
{
if (this.isInitialized)
{
this.rotateToTarget(false, false);
}
}
void OnDisable()
{
this.isInitialized = false;
}
#endregion
#region Events
#endregion
#region Public Functions
public void OverrideEnablingBehaviour(EEnablingBehaviour newBehaviour)
{
this.enablingBehaviour = newBehaviour;
}
public void OverrideRotationSpeed(float newRotationSpeed)
{
this.rotationSpeed = newRotationSpeed;
}
public void OverrideStartBillboardingAngle(float newStartBillboardingAngle)
{
this.startBillboardingAngle = newStartBillboardingAngle;
}
public void OverrideStopBillboardingAngle(float newStopBillboardingAngle)
{
this.stopBillboardingAngle = newStopBillboardingAngle;
}
public void JumpToDesiredRotation()
{
this.rotateToTarget(false, true);
}
#endregion
#region Private Functions
private async void initBillboardWithDelay()
{
await Task.Delay(TimeSpan.FromSeconds(this.initDelay));
if (this == null)
{
// Has been destroyed -> cancel
return;
}
this.rotationFrom = Quaternion.identity;
this.rotationTo = Quaternion.identity;
if (this.enablingBehaviour == EEnablingBehaviour.Transition)
{
this.rotateToTarget(true, false);
}
if (this.enablingBehaviour == EEnablingBehaviour.Jump)
{
this.rotateToTarget(false, true);
}
this.isInitialized = true;
}
private void rotateToTarget(bool forceRotation, bool jumpToDesiredRotation)
{
if (this.targetTransform == null)
{
this.targetTransform = this.cam.transform;
}
// Get a Vector that points from the target to the main camera.
Vector3 directionToTarget = targetTransform.position - transform.position;
bool useCameraAsUpVector = true;
// Adjust for the pivot axis.
switch (pivotAxis)
{
case PivotAxis.X:
directionToTarget.x = 0.0f;
useCameraAsUpVector = false;
break;
case PivotAxis.Y:
directionToTarget.y = 0.0f;
useCameraAsUpVector = false;
break;
case PivotAxis.Z:
directionToTarget.x = 0.0f;
directionToTarget.y = 0.0f;
break;
case PivotAxis.XY:
useCameraAsUpVector = false;
break;
case PivotAxis.XZ:
directionToTarget.x = 0.0f;
break;
case PivotAxis.YZ:
directionToTarget.y = 0.0f;
break;
case PivotAxis.Free:
default:
// No changes needed.
break;
}
// If we are right next to the camera the rotation is undefined.
if (directionToTarget.sqrMagnitude < 0.001f)
{
return;
}
Quaternion rotationCurrent = Quaternion.identity;
// Calculate and apply the rotation required to reorient the object
if (useCameraAsUpVector)
{
rotationCurrent = Quaternion.LookRotation(-directionToTarget, this.cam.transform.up);
}
else
{
rotationCurrent = Quaternion.LookRotation(-directionToTarget);
}
if (jumpToDesiredRotation)
{
this.transform.rotation = rotationCurrent;
return;
}
float deviationToDesiredRotation = Mathf.Abs(Quaternion.Angle(rotationCurrent, transform.rotation));
// Check if rotation target needs to be set
if (deviationToDesiredRotation > this.startBillboardingAngle || forceRotation)
{
if (this.isDelayedModeActive)
{
// Delayed mode
if (!this.delayedRotationIsPending)
{
// Start "timer"
this.delayedRotationIsPending = true;
this.lastRotationRequiredTime = Time.time;
}
else
{
// "Timer" running
if (Time.time > this.lastRotationRequiredTime + this.delayedModeDelay)
{
// Delaytime has passed -> start rotating
this.delayedRotationIsPending = false;
this.rotationFrom = transform.rotation;
this.rotationTo = rotationCurrent;
}
}
}
else
{
// Non delayed mode -> start rotating
this.rotationFrom = transform.rotation;
this.rotationTo = rotationCurrent;
}
}
// If delayed mode -> check if delayedRotationIsPending needs to be reset
if (deviationToDesiredRotation < this.startBillboardingAngle && this.isDelayedModeActive)
{
this.delayedRotationIsPending = false;
}
if (this.rotationTo != Quaternion.identity)
{
// Currently rotating
transform.rotation = Quaternion.Lerp(this.transform.rotation, this.rotationTo, Time.deltaTime * rotationSpeed);
for (int i = 0; i < this.linkedTransforms.Count; i++)
{
this.linkedTransforms[i].rotation = this.transform.rotation;
}
// Check if should stop
float deviationToTargetRotation = Mathf.Abs(Quaternion.Angle(this.transform.rotation, this.rotationTo));
if (deviationToTargetRotation < this.stopBillboardingAngle)
{
this.rotationFrom = Quaternion.identity;
this.rotationTo = Quaternion.identity;
}
}
}
#endregion
}