using SC.XR.Unity.Module_InputSystem;
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
namespace SC.XR.Unity.Module_Sphere
{
[RequireComponent(typeof(ManipulationHandler))]
[RequireComponent(typeof(Rigidbody))]
public class VelocityHandler : MonoBehaviour
{
///
/// Whether to enable rigidbody's velocity.
///
[SerializeField]
private bool useVelocity = true;
///
/// Whether to enable rigidbody's angular velocity.
///
[SerializeField]
private bool useAngularVelocity = true;
///
/// The type of rigidbody's origin.
///
[SerializeField]
protected OriginType originType = OriginType.Pointer;
// The number of frames to estimating velocity.
public int velocityAverageFrames = 5;
// The number of frames to estimating angular velocity.
public int angularVelocityAverageFrames = 10;
public float velocityIntensity = 1.0f;
public float angularVelocityIntensity = 1.0f;
///
/// Enable velocity handling.
///
protected void OnEnable()
{
InitCaches();
ManipulationHandler.PointerDown.AddListener(OnPointerDown);
ManipulationHandler.PointerUp.AddListener(OnPointerUp);
}
///
/// Disable velocity handling.
///
protected void OnDisable()
{
ManipulationHandler.PointerDown.RemoveListener(OnPointerDown);
ManipulationHandler.PointerUp.RemoveListener(OnPointerUp);
}
private ManipulationHandler _manipulationHandler;
public ManipulationHandler ManipulationHandler
{
get
{
if (_manipulationHandler == null)
_manipulationHandler = GetComponent();
return _manipulationHandler;
}
}
private Rigidbody _rigidbody;
public Rigidbody RigidBody
{
get
{
if (_rigidbody == null)
_rigidbody = GetComponent();
return _rigidbody;
}
}
public Transform Origin
{
get
{
switch (originType)
{
case OriginType.Pointer:
return Pointer;
case OriginType.Cursor:
return Cursor;
case OriginType.Transform:
return transform;
}
return null;
}
}
private DetectorBase _detectorBase;
public DetectorBase DetectorBase { get => _detectorBase;set => _detectorBase = value; }
public Transform Pointer {get => DetectorBase.currentPointer.transform;}
public Transform Cursor { get => DetectorBase.currentPointer.cursorBase.transform; }
public void OnPointerDown(PointerEventData eventData)
{
DetectorBase = (eventData as SCPointEventData).inputDevicePartBase.detectorBase;
HandleVelocityStarted();
}
public void OnPointerUp(PointerEventData eventData)
{
HandleVelocityEnded();
}
///
/// Start handle rigidbody's velocity and angular velocity.
///
private void HandleVelocityStarted()
{
estimateVelocity = StartCoroutine(EstimateVelocity());
}
///
/// End handle rigidbody's velocity and angular velocity.
///
private void HandleVelocityEnded()
{
if (useVelocity) RigidBody.velocity = GetVelocityEstimate();
if (useAngularVelocity) RigidBody.angularVelocity = GetAngularVelocityEstimate();
StopCoroutine(estimateVelocity);
estimateVelocity = null;
DetectorBase = null;
}
private Vector3[] velocityCaches;
private Vector3[] angularVelocityCaches;
private int currentCacheCount;
// The coroutine to estimate velocity and angular velocity.
private Coroutine estimateVelocity;
//Initialize the velocity caches and angular velocity caches.
public void InitCaches()
{
velocityCaches = new Vector3[velocityAverageFrames];
angularVelocityCaches = new Vector3[angularVelocityAverageFrames];
}
///
/// Estimate the average velocity based on the velocities in the cache.
///
public Vector3 GetVelocityEstimate()
{
Vector3 averageVelocity = Vector3.zero;
int velocityCacheCount = Mathf.Min(currentCacheCount, velocityCaches.Length);
if (velocityCacheCount != 0)
{
for (int i = 0; i < velocityCacheCount; i++)
{
averageVelocity += velocityCaches[i];
}
averageVelocity *= (1.0f / velocityCacheCount);
}
return averageVelocity * velocityIntensity;
}
///
/// Estimate the average angular velocity based on the angular velocities in the cache.
///
public Vector3 GetAngularVelocityEstimate()
{
Vector3 angularVelocity = Vector3.zero;
int angularVelocitySampleCount = Mathf.Min(currentCacheCount, angularVelocityCaches.Length);
if (angularVelocitySampleCount != 0)
{
for (int i = 0; i < angularVelocitySampleCount; i++)
{
angularVelocity += angularVelocityCaches[i];
}
angularVelocity *= (1.0f / angularVelocitySampleCount);
}
return angularVelocity * angularVelocityIntensity;
}
///
/// Estimate the velocity and angular velocity of the object in this frame,
/// based on the position and rotation of the previous frame and this frame, and store it in cache.
///
public IEnumerator EstimateVelocity()
{
currentCacheCount = 0;
Vector3 previousPosition = Origin.position;
Quaternion previousRotation = Origin.rotation;
while (true)
{
yield return new WaitForEndOfFrame();
int v = currentCacheCount % velocityCaches.Length;
int w = currentCacheCount % angularVelocityCaches.Length;
currentCacheCount++;
float velocityFactor = 1.0f / Time.deltaTime;
velocityCaches[v] = CalculateVelocity(Origin.position, previousPosition, velocityFactor);
angularVelocityCaches[w] = CalculateAngularVelocity(Origin.rotation, previousRotation, velocityFactor);
previousPosition = Origin.position;
previousRotation = Origin.rotation;
}
}
///
/// Calculate the rigidbody's velocity.
///
/// Rigidbody's position in present frame.
/// Rigidbody's position in previous frame.
///
public Vector3 CalculateVelocity(Vector3 presentRotation, Vector3 previousPosition, float velocityFactor)
{
Vector3 velocity = (presentRotation - previousPosition) * velocityFactor;
return velocity;
}
///
/// Using Quaternion to Calculate the rigidbody's angular velocity.
///
/// Rigidbody's rotation(Quaternion) in present frame.
/// Rigidbody's rotation(Quaternion) in previous frame.
///
///
public Vector3 CalculateAngularVelocity(Quaternion presentRotation, Quaternion previousRotation, float velocityFactor)
{
Quaternion deltaRotation = presentRotation * Quaternion.Inverse(previousRotation);
float theta = 2.0f * Mathf.Acos(Mathf.Clamp(deltaRotation.w, -1.0f, 1.0f));
if (theta > Mathf.PI)
{
theta -= 2.0f * Mathf.PI;
}
Vector3 angularAxis = new Vector3(deltaRotation.x, deltaRotation.y, deltaRotation.z).normalized;
Vector3 angularVelocity = Vector3.zero;
if (angularAxis.sqrMagnitude > 0.0f)
{
angularVelocity = theta * angularAxis * velocityFactor;
}
return angularVelocity;
}
}
}