using System;
using System.Collections;
using UnityEngine;
using UnityEngine.XR;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Interactors;
///
/// ÄÁÆ®·Ñ·¯ÀÇ ¼Õ ¹æÇâ(¿Þ¼Õ/¿À¸¥¼Õ)À» ±¸ºÐÇϱâ À§ÇÑ ¿°ÅÇü
///
public enum HandSide { Left, Right }
///
/// VR ÄÁÆ®·Ñ·¯(¿Þ¼Õ/¿À¸¥¼Õ)ÀÇ »óÈ£ÀÛ¿ëÀ» ´ã´çÇÏ´Â ºä Ŭ·¡½º
/// XR Interaction ToolkitÀÇ À̺¥Æ®¸¦ ¼ö½ÅÇÏ¿© ·Îº¿ Àâ±â, Æ÷ÀÎÆ® Ŭ¸¯/µå·¡±×, È£¹ö¸µ µîÀÇ
/// »ç¿ëÀÚ ÀÔ·ÂÀ» Presenter¿¡°Ô Àü´Þ. ¶ÇÇÑ ÇÝÆ½(Áøµ¿) Çǵå¹é ±â´ÉÀ» Á¦°ø
///
public class InteractionView : MonoBehaviour
{
// --- »ç¿ëÀÚ ÀÔ·Â À̺¥Æ® (ProgramPresenter·Î Àü´ÞµÊ) ---
public event Action OnRobotHoverStateChanged; // ·Îº¿ À§¿¡ ·¹À̰¡ ¿Ã¶ó°¬°Å³ª ³ª°¬À» ¶§
public event Action OnRobotGrabStart; // ·Îº¿À» Àâ±â ½ÃÀÛÇßÀ» ¶§
public event Action OnRobotGrabbed; // ·Îº¿À» Àâ°í ¿òÁ÷ÀÌ´Â ÁßÀÏ ¶§ (¸Å ÇÁ·¹ÀÓ)
public event Action OnRobotReleased; // ·Îº¿À» ³õ¾ÒÀ» ¶§
public event Action OnPointClicked; // Æ÷ÀÎÆ®¸¦ ª°Ô Ŭ¸¯ÇßÀ» ¶§
public event Action OnPointDragStart; // Æ÷ÀÎÆ®¸¦ Àâ°í µå·¡±×¸¦ ½ÃÀÛÇßÀ» ¶§
public event Action OnPointDragUpdate; // Æ÷ÀÎÆ® µå·¡±× ÁßÀÏ ¶§
public event Action OnPointDragEnd; // Æ÷ÀÎÆ® µå·¡±×°¡ ³¡³µÀ» ¶§
// --- XR ÄÄÆ÷³ÍÆ® ÂüÁ¶ ---
[SerializeField] private NearFarInteractor nearFarInteractor; // ±Ù°Å¸®/¿ø°Å¸® »óÈ£ÀÛ¿ë ÅëÇÕ ÄÄÆ÷³ÍÆ®
public HandSide handSide; // ÀÌ ½ºÅ©¸³Æ®°¡ ºÙÀº ¼ÕÀÇ ¹æÇâ
private XRBaseInteractor baseInteractor; // ÀÎÅÍ·¢ÅÍ ±âº» Ŭ·¡½º (À̺¥Æ® ±¸µ¶¿ë)
private IXRRayProvider rayProvider; // ·¹ÀÌ(Ray) Á¤º¸ Á¦°øÀÚ (³¡Á¡ ÁÂÇ¥ È®Àοë)
// --- ÇÝÆ½(Áøµ¿) °ü·Ã ---
private InputDevice hapticDevice; // Áøµ¿À» ¹ß»ý½Ãų ÄÁÆ®·Ñ·¯ Çϵå¿þ¾î ÀåÄ¡
private bool isHapticDeviceFound = false;
// --- ½Ã°¢Àû Çǵå¹é ---
[SerializeField]
[Tooltip("µå·¡±× ½Ã À§Ä¡¸¦ Ç¥½ÃÇÒ ¹ÝÅõ¸í ·Îº¿ ¸ðµ¨")]
private GameObject ghostRobot;
[SerializeField]
[Tooltip("µå·¡±×¿ë ¸¶¿ì½º À̹ÌÁö(È»ìÇ¥ µî)")]
private GameObject dragArrow;
// --- ÀÔ·Â ÆÇÁ¤ ÀÓ°è°ª ---
[SerializeField] private float clickTimeThreshold = 0.5f; // ÀÌ ½Ã°£º¸´Ù ªÀ¸¸é Ŭ¸¯, ±æ¸é µå·¡±×·Î ÆÇÁ¤
[SerializeField] private float dragMovementThreshold = 0.05f; // ÀÌ °Å¸® ÀÌ»ó ¿òÁ÷À̸é Áï½Ã µå·¡±×·Î ÆÇÁ¤
private Coroutine clickOrDragCoroutine = null; // Ŭ¸¯/µå·¡±× ÆÇÁ¤ ŸÀÌ¸Ó ÄÚ·çÆ¾
public Vector3 startGrabPosition; // Àâ±â ½ÃÀÛÇÑ À§Ä¡
public float distance; // ÀâÀº ÈÄ À̵¿ÇÑ °Å¸®
// --- »óÅ Ç÷¡±× ---
private bool isInitialized = false;
private bool isGrabbingPoint = false; // Æ÷ÀÎÆ®¸¦ Àâ°í Àִ°¡?
private int currentGrabbedPointIndex = -1; // Àâ°í ÀÖ´Â Æ÷ÀÎÆ®ÀÇ ID
public Vector3 currentTargetPosition = Vector3.zero; // ÇöÀç ·¹À̰¡ ´êÀº À§Ä¡
Quaternion currentTargetRotation = Quaternion.identity;
public bool isGrabbingRobot = false; // ·Îº¿À» Àâ°í Àִ°¡?
void Update()
{
// 1. ÃʱâÈ ·ÎÁ÷ (XR ÄÄÆ÷³ÍÆ®°¡ ·±Å¸ÀÓ¿¡ ÁغñµÉ ¶§±îÁö ´ë±â)
if (!isInitialized || rayProvider == null)
{
if (nearFarInteractor == null)
{
nearFarInteractor = GetComponentInChildren(true);
baseInteractor = nearFarInteractor as XRBaseInteractor;
rayProvider = nearFarInteractor as IXRRayProvider;
}
if (nearFarInteractor != null)
{
InitializeInteraction();
}
}
else
{
// 2. ¸Å ÇÁ·¹ÀÓ ·¹ÀÌÀÇ ³¡Á¡°ú ȸÀü°ª °»½Å
currentTargetPosition = rayProvider.rayEndPoint;
currentTargetRotation = baseInteractor.attachTransform.rotation;
// 3. ·Îº¿À» Àâ°í ÀÖ´Â °æ¿ì -> Grabbed À̺¥Æ® °è¼Ó ¹ß»ý
if (isGrabbingRobot)
{
OnRobotGrabbed?.Invoke(currentTargetPosition, currentTargetRotation);
return;
}
// 4. Æ÷ÀÎÆ®¸¦ Àâ°í ÀÖ´Â °æ¿ì -> DragUpdate À̺¥Æ® °è¼Ó ¹ß»ý
if (isGrabbingPoint)
{
OnPointDragUpdate?.Invoke(currentGrabbedPointIndex, currentTargetPosition, currentTargetRotation);
}
// 5. ¾ÆÁ÷ ÆÇÁ¤ ÁßÀÎ °æ¿ì (Ŭ¸¯ÀÎÁö µå·¡±×ÀÎÁö)
else if (clickOrDragCoroutine != null)
{
// À̵¿ °Å¸® °è»ê
distance = Vector3.Distance(startGrabPosition, currentTargetPosition);
// ÀÓ°è°ª ÀÌ»ó ¿òÁ÷¿´´Ù¸é ½Ã°£°ú °ü°è¾øÀÌ Áï½Ã µå·¡±×·Î °£ÁÖ
if (distance > dragMovementThreshold)
{
StartDragMode();
}
}
}
}
///
/// XR ÀÎÅÍ·¢ÅÍ À̺¥Æ®¸¦ ±¸µ¶Çϰí AppManager¿¡ ºä¸¦ µî·Ï
///
private void InitializeInteraction()
{
// XRI À̺¥Æ® ±¸µ¶
baseInteractor.selectEntered.AddListener(HandleGrabStart); // ¹öư ´©¸§ (Grab)
baseInteractor.selectExited.AddListener(HandleGrabEnd); // ¹öư ¶À (Release)
baseInteractor.hoverEntered.AddListener(OnHoverEntered); // ·¹À̰¡ ´êÀ½
baseInteractor.hoverExited.AddListener(OnHoverExited); // ·¹À̰¡ ³ª°¨
TryInitializeHapticDevice(); // Áøµ¿ ÀåÄ¡ ÃʱâÈ ½Ãµµ
isInitialized = true;
// AppManager¿¡ ÀÚ½Å(View)À» µî·ÏÇÏ¿© Presenter¿Í ¿¬°áµÉ ¼ö ÀÖ°Ô ÇÔ
AppManager.Instance.RegisterView(this);
}
private void OnDestroy()
{
if (baseInteractor != null)
{
baseInteractor.selectEntered.RemoveListener(HandleGrabStart);
baseInteractor.selectExited.RemoveListener(HandleGrabEnd);
}
}
///
/// ÇöÀç ¼Õ¿¡ ÇØ´çÇÏ´Â XR ÀÔ·Â ÀåÄ¡(ÄÁÆ®·Ñ·¯)¸¦ ã¾Æ ÇÝÆ½ ±â´ÉÀ» Ȱ¼ºÈ
///
private void TryInitializeHapticDevice()
{
XRNode node = (this.handSide == HandSide.Left) ? XRNode.LeftHand : XRNode.RightHand;
InputDevice device = InputDevices.GetDeviceAtXRNode(node);
if (device.isValid)
{
hapticDevice = device;
isHapticDeviceFound = true;
}
}
///
/// InputDevice¸¦ »ç¿ëÇÑ ÇÝÆ½
///
public void SendHapticImpulse(float amplitude, float duration)
{
// ÄÁÆ®·Ñ·¯°¡ ¿¬°áµÇÁö ¾Ê¾Ò°Å³ª(isValid) ²¨Á®ÀÖÀ» ¼ö ÀÖÀ¸¹Ç·Î ¸Å¹ø È®ÀÎ
if (!isHapticDeviceFound || !hapticDevice.isValid)
{
TryInitializeHapticDevice(); // ´Ù½Ã ã±â ½Ãµµ
if (!isHapticDeviceFound) return; // ±×·¡µµ ¾øÀ¸¸é Áß´Ü
}
// Áøµ¿ ±â´ÉÀÌ ÀÖ´ÂÁö È®ÀÎ
HapticCapabilities capabilities;
if (hapticDevice.TryGetHapticCapabilities(out capabilities))
{
if (capabilities.supportsImpulse)
{
// Áøµ¿ Àü¼Û
hapticDevice.SendHapticImpulse(0, amplitude, duration);
}
}
}
// --- XRI À̺¥Æ® Çڵ鷯 ---
///
/// ¿ÀºêÁ§Æ®¸¦ Àâ¾ÒÀ» ¶§(Grip/Trigger ¹öư ´©¸§) È£Ãâ
/// ´ë»óÀÌ 'Æ÷ÀÎÆ®'ÀÎÁö '·Îº¿'ÀÎÁö ±¸ºÐÇÏ¿© ó¸®
///
private void HandleGrabStart(SelectEnterEventArgs args)
{
GameObject grabbedGO = args.interactableObject.transform.gameObject;
RobotPoint point = grabbedGO.GetComponent();
if (point != null)
{
isGrabbingPoint = true;
isGrabbingRobot = false;
currentGrabbedPointIndex = point.pointIndex;
// ŸÀÌ¸Ó ½ÃÀÛ
startGrabPosition = rayProvider.rayEndPoint;
if (clickOrDragCoroutine != null)
StopCoroutine(clickOrDragCoroutine);
clickOrDragCoroutine = StartCoroutine(ClickOrDragTimer());
}
else if (grabbedGO.CompareTag("RobotArm"))
{
isGrabbingPoint = false;
isGrabbingRobot = true;
currentGrabbedPointIndex = -1;
OnRobotGrabStart?.Invoke(startGrabPosition);
OnPointDragStart?.Invoke(currentGrabbedPointIndex); // (À妽º -1 Àü´Þ)
}
}
///
/// ¿ÀºêÁ§Æ®¸¦ ³õ¾ÒÀ» ¶§(¹öư ¶À) È£Ãâ
///
private void HandleGrabEnd(SelectExitEventArgs args)
{
if (isGrabbingRobot)
{
OnRobotReleased?.Invoke(new RobotData());
}
else
{
if (clickOrDragCoroutine != null)
{
StopCoroutine(clickOrDragCoroutine);
clickOrDragCoroutine = null;
OnPointClicked?.Invoke(currentGrabbedPointIndex, currentTargetPosition);
}
else if (isGrabbingPoint)
{
OnPointDragEnd?.Invoke(currentGrabbedPointIndex);
}
}
// »óÅ ÃʱâÈ
clickOrDragCoroutine = null;
isGrabbingPoint = false;
isGrabbingRobot = false;
currentGrabbedPointIndex = -1;
}
///
/// ·¹À̰¡ ¹°Ã¼ À§¿¡ ¿Ã¶ó°¬À» ¶§ È£Ãâ
///
private void OnHoverEntered(HoverEnterEventArgs args)
{
// È£¹öÇÑ ¹°Ã¼°¡ '·Îº¿'ÀÎÁö È®ÀÎ
if (args.interactableObject.transform.CompareTag("RobotArm"))
{
OnRobotHoverStateChanged?.Invoke(true);
}
}
// È£¹ö Á¾·á ½Ã È£Ãâ
private void OnHoverExited(HoverExitEventArgs args)
{
if (args.interactableObject.transform.CompareTag("RobotArm"))
{
OnRobotHoverStateChanged?.Invoke(false);
}
}
// Ŭ¸¯/µå·¡±× ±¸ºÐÇϴ ŸÀÌ¸Ó ÄÚ·çÆ¾
private IEnumerator ClickOrDragTimer()
{
yield return new WaitForSeconds(clickTimeThreshold);
if (clickOrDragCoroutine != null)
{
Debug.Log("½Ã°£ Ãʰú·Î µå·¡±× ½ÃÀÛ (OnPointDragStart)");
StartDragMode();
}
}
// ŸÀÌ¸Ó ÁßÁöÇÏ°í µå·¡±× ¸ðµå·Î Àüȯ
private void StartDragMode()
{
if (clickOrDragCoroutine != null)
{
StopCoroutine(clickOrDragCoroutine);
clickOrDragCoroutine = null;
}
isGrabbingPoint = true;
OnPointDragStart?.Invoke(currentGrabbedPointIndex);
}
// °í½ºÆ® ·Îº¿ÀÇ À§Ä¡/ȸÀü ¼³Á¤
public void ShowGhostRobot()
{
if (ghostRobot == null)
{
Debug.LogWarning("Ghost RobotÀÌ Inspector¿¡ ÇÒ´çµÇÁö ¾Ê¾Ò½À´Ï´Ù.");
return;
}
ghostRobot.SetActive(true);
}
// °í½ºÆ® ·Îº¿ ºñȰ¼ºÈ
public void HideGhostRobot()
{
if (ghostRobot != null)
{
ghostRobot.SetActive(false);
}
}
// µå·¡±× È»ìÇ¥ Ȱ¼ºÈ ¹× À§Ä¡ ¼³Á¤
public void ShowDragArrow(Vector3 position)
{
if (dragArrow == null)
{
Debug.LogWarning("Drag Arrow°¡ Inspector¿¡ ÇÒ´çµÇÁö ¾Ê¾Ò½À´Ï´Ù.");
return;
}
dragArrow.SetActive(true);
dragArrow.transform.position = position;
}
// µå·¡±× È»ìÇ¥ ºñȰ¼ºÈ
public void HideDragArrow()
{
if (dragArrow != null)
{
dragArrow.SetActive(false);
}
}
// ±¤¼± ³¡ À§Ä¡ ¹Ýȯ
public Vector3 GetCurrentRayPosition()
{
if (rayProvider != null)
{
return rayProvider.rayEndPoint;
}
if (baseInteractor != null)
{
return baseInteractor.attachTransform.position;
}
return transform.position;
}
}