306 lines
11 KiB
C#
306 lines
11 KiB
C#
using UnityEngine;
|
|
|
|
namespace GraphicsCat
|
|
{
|
|
public class SlideRotationController : MonoBehaviour, IMGUIDockable
|
|
{
|
|
private const float INERTIA_THRESHOLD = 0.1f; // Threshold to stop inertia rotation
|
|
|
|
[Header("Slide Rotation")]
|
|
[InspectorDisplayName("Enable")]
|
|
public bool slideRotationEnabled = false;
|
|
[InspectorDisplayName("Sensitivity")]
|
|
public Vector3 slideRotationSensitivity = new(0, 20, 0);
|
|
[InspectorDisplayName("Inertia Damping")]
|
|
[Range(0f, 1f)]
|
|
public float inertiaDamping = 0.8f; // 0 = no inertia, 1 = maximum inertia
|
|
|
|
[Header("Auto Rotation")]
|
|
[InspectorDisplayName("Enable")]
|
|
public bool autoRotationEnabled = true;
|
|
[InspectorDisplayName("Speed")]
|
|
public Vector3 autoRotationSpeed = new(0, 5, 0);
|
|
|
|
[Header("Other")]
|
|
public bool displayInGameGUI = false;
|
|
|
|
// Input state
|
|
private bool m_IsDragging = false;
|
|
private Vector3 m_LastPointerPosition = Vector3.zero;
|
|
private Vector3 m_PointerDelta = Vector3.zero;
|
|
private Transform m_Target = null;
|
|
|
|
// Touch input
|
|
private Touch m_MainTouch;
|
|
private bool m_IsTouchActive = false;
|
|
|
|
// Auto rotation
|
|
private bool m_WasAutoRotationEnabled;
|
|
private bool m_IsAutoRotationPaused = false;
|
|
|
|
// Inertia
|
|
private Vector3 m_CurrentInertia = Vector3.zero;
|
|
private Vector3 m_LastAngularVelocity = Vector3.zero;
|
|
private bool m_HasAngularMovement = false; // Track if there was actual angular movement
|
|
|
|
private void Start()
|
|
{
|
|
m_Target = transform;
|
|
IMGUIDock.topRight.DockGUI(this);
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!slideRotationEnabled)
|
|
return;
|
|
|
|
m_PointerDelta = Vector3.zero;
|
|
|
|
HandleMouseInput();
|
|
|
|
if (Application.isMobilePlatform)
|
|
HandleTouchInput();
|
|
}
|
|
|
|
private void FixedUpdate()
|
|
{
|
|
Vector3 slideRotationDelta = Vector3.zero;
|
|
|
|
if (slideRotationEnabled)
|
|
{
|
|
|
|
// Handle manual rotation
|
|
if (m_PointerDelta != Vector3.zero)
|
|
{
|
|
slideRotationDelta = ComputeRotationDelta(m_PointerDelta.x, m_PointerDelta.y);
|
|
}
|
|
// Apply inertia when not actively rotating
|
|
else if (m_CurrentInertia.magnitude > INERTIA_THRESHOLD)
|
|
{
|
|
slideRotationDelta = m_CurrentInertia;
|
|
m_CurrentInertia *= inertiaDamping;
|
|
|
|
if (m_CurrentInertia.magnitude < INERTIA_THRESHOLD)
|
|
m_CurrentInertia = Vector3.zero;
|
|
}
|
|
|
|
// Apply manual rotation or inertia
|
|
if (slideRotationDelta != Vector3.zero)
|
|
{
|
|
Quaternion deltaRotation = Quaternion.Euler(slideRotationDelta * Time.fixedDeltaTime);
|
|
m_Target.localRotation = deltaRotation * m_Target.localRotation;
|
|
m_LastAngularVelocity = slideRotationDelta;
|
|
m_HasAngularMovement = true; // There was actual rotation this frame
|
|
}
|
|
else
|
|
{
|
|
// No rotation this frame
|
|
m_HasAngularMovement = false;
|
|
}
|
|
}
|
|
|
|
// Apply auto rotation
|
|
if (slideRotationDelta == Vector3.zero && autoRotationEnabled && !m_IsAutoRotationPaused)
|
|
{
|
|
ApplyAutoRotation();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply auto rotation according to configured speed.
|
|
/// </summary>
|
|
private void ApplyAutoRotation()
|
|
{
|
|
Quaternion rotation = Quaternion.Euler(autoRotationSpeed * Time.fixedDeltaTime);
|
|
m_Target.localRotation = rotation * m_Target.localRotation;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compute rotation delta based on pointer movement.
|
|
/// </summary>
|
|
private Vector3 ComputeRotationDelta(float x, float y)
|
|
{
|
|
return Vector3.Scale(new Vector3(y, x, 0), -slideRotationSensitivity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle mouse input for slide rotation.
|
|
/// </summary>
|
|
private void HandleMouseInput()
|
|
{
|
|
if (Input.GetMouseButtonDown(0))
|
|
{
|
|
if (IsPointerOverTarget(Input.mousePosition))
|
|
{
|
|
m_IsDragging = true;
|
|
m_LastPointerPosition = Input.mousePosition;
|
|
m_CurrentInertia = Vector3.zero; // Reset inertia when starting drag
|
|
m_HasAngularMovement = false;
|
|
|
|
PauseAutoRotation();
|
|
}
|
|
}
|
|
else if (Input.GetMouseButtonUp(0))
|
|
{
|
|
if (m_IsDragging)
|
|
{
|
|
m_IsDragging = false;
|
|
// Only apply inertia if there was actual angular movement
|
|
if (m_HasAngularMovement)
|
|
ApplyInertia();
|
|
else
|
|
m_CurrentInertia = Vector3.zero; // No movement, no inertia
|
|
|
|
ResumeAutoRotation();
|
|
}
|
|
}
|
|
else if (Input.GetMouseButton(0) && m_IsDragging)
|
|
{
|
|
Vector3 currentPosition = Input.mousePosition;
|
|
m_PointerDelta = currentPosition - m_LastPointerPosition;
|
|
m_LastPointerPosition = currentPosition;
|
|
}
|
|
else
|
|
{
|
|
m_IsDragging = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle touch input for slide rotation.
|
|
/// </summary>
|
|
private void HandleTouchInput()
|
|
{
|
|
int touchCount = Input.touchCount;
|
|
if (touchCount <= 0)
|
|
{
|
|
m_IsTouchActive = false;
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < touchCount; i++)
|
|
{
|
|
Touch touch = Input.GetTouch(i);
|
|
|
|
switch (touch.phase)
|
|
{
|
|
case TouchPhase.Began:
|
|
if (IsPointerOverTarget(touch.position))
|
|
{
|
|
m_MainTouch = touch;
|
|
m_IsTouchActive = true;
|
|
m_LastPointerPosition = touch.position;
|
|
m_CurrentInertia = Vector3.zero; // Reset inertia when starting touch
|
|
m_HasAngularMovement = false;
|
|
PauseAutoRotation();
|
|
}
|
|
break;
|
|
|
|
case TouchPhase.Moved:
|
|
if (m_IsTouchActive && touch.fingerId == m_MainTouch.fingerId)
|
|
{
|
|
Vector3 currentTouchPosition = touch.position;
|
|
m_PointerDelta = currentTouchPosition - m_LastPointerPosition;
|
|
m_LastPointerPosition = currentTouchPosition;
|
|
}
|
|
break;
|
|
|
|
case TouchPhase.Ended:
|
|
case TouchPhase.Canceled:
|
|
if (m_IsTouchActive && touch.fingerId == m_MainTouch.fingerId)
|
|
{
|
|
m_IsTouchActive = false;
|
|
m_PointerDelta = Vector3.zero;
|
|
// Only apply inertia if there was actual angular movement
|
|
if (m_HasAngularMovement)
|
|
ApplyInertia();
|
|
else
|
|
m_CurrentInertia = Vector3.zero; // No movement, no inertia
|
|
|
|
ResumeAutoRotation();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply inertia based on the last angular velocity.
|
|
/// Inertia is directly proportional to the angular velocity at the moment of release.
|
|
/// </summary>
|
|
private void ApplyInertia()
|
|
{
|
|
// Only apply inertia if there was significant angular velocity
|
|
// This ensures inertia magnitude correlates with rotation speed at release
|
|
if (inertiaDamping > 0 && m_LastAngularVelocity.magnitude > INERTIA_THRESHOLD)
|
|
m_CurrentInertia = m_LastAngularVelocity * inertiaDamping;
|
|
else
|
|
m_CurrentInertia = Vector3.zero;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Temporarily pause auto rotation during user interaction.
|
|
/// </summary>
|
|
private void PauseAutoRotation()
|
|
{
|
|
if (autoRotationEnabled)
|
|
{
|
|
m_WasAutoRotationEnabled = autoRotationEnabled;
|
|
autoRotationEnabled = false;
|
|
m_IsAutoRotationPaused = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resume auto rotation after user interaction ends.
|
|
/// </summary>
|
|
private void ResumeAutoRotation()
|
|
{
|
|
if (m_IsAutoRotationPaused)
|
|
{
|
|
autoRotationEnabled = m_WasAutoRotationEnabled;
|
|
m_IsAutoRotationPaused = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if the given screen point is over this object using renderer bounds.
|
|
/// </summary>
|
|
private bool IsPointerOverTarget(Vector3 screenPoint)
|
|
{
|
|
Camera camera = Camera.main;
|
|
if (camera == null)
|
|
return false;
|
|
|
|
Renderer[] renderers = m_Target.GetComponentsInChildren<Renderer>();
|
|
if (renderers.Length == 0)
|
|
return false;
|
|
|
|
Ray ray = camera.ScreenPointToRay(screenPoint);
|
|
foreach (Renderer renderer in renderers)
|
|
{
|
|
if (renderer.enabled && renderer.bounds.IntersectRay(ray))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void OnDockGUI()
|
|
{
|
|
if (!displayInGameGUI)
|
|
return;
|
|
|
|
GUILayout.BeginHorizontal();
|
|
if (GUILayout.Button("Slide Rotation " + (slideRotationEnabled ? "O" : "X")))
|
|
slideRotationEnabled = !slideRotationEnabled;
|
|
GUILayout.EndHorizontal();
|
|
|
|
GUILayout.BeginHorizontal();
|
|
if (GUILayout.Button("Auto Rotation " + (autoRotationEnabled ? "O" : "X")))
|
|
autoRotationEnabled = !autoRotationEnabled;
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
}
|
|
}
|