Files
HDRobotics/Assets/Scripts/View/InteractionView.cs

311 lines
8.9 KiB
C#

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 }
public class InteractionView : MonoBehaviour
{
public event Action<bool> OnRobotHoverStateChanged;
public event Action<Vector3> OnRobotGrabStart;
public event Action<Vector3, Quaternion> OnRobotGrabbed;
public event Action<RobotData> OnRobotReleased;
public event Action<int, Vector3> OnPointClicked;
public event Action<int> OnPointDragStart;
public event Action<int, Vector3, Quaternion> OnPointDragUpdate;
public event Action<int> OnPointDragEnd;
[SerializeField]private NearFarInteractor nearFarInteractor;
public HandSide handSide;
private XRBaseInteractor baseInteractor;
private IXRRayProvider rayProvider;
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;
private Vector3 startGrabPosition;
private bool isInitialized = false;
private bool isGrabbingPoint = false;
private int currentGrabbedPointIndex = -1;
Vector3 currentTargetPosition = Vector3.zero;
Quaternion currentTargetRotation = Quaternion.identity;
public bool isGrabbingRobot = false;
void Update()
{
if (!isInitialized || rayProvider == null)
{
if (nearFarInteractor == null)
{
nearFarInteractor = GetComponentInChildren<NearFarInteractor>(true);
baseInteractor = nearFarInteractor as XRBaseInteractor;
rayProvider = nearFarInteractor as IXRRayProvider;
}
if (nearFarInteractor != null)
{
InitializeInteraction();
}
}
else
{
currentTargetPosition = rayProvider.rayEndPoint;
currentTargetRotation = baseInteractor.attachTransform.rotation;
if (isGrabbingRobot)
{
OnRobotGrabbed?.Invoke(currentTargetPosition, currentTargetRotation);
return;
}
if (isGrabbingPoint)
{
OnPointDragUpdate?.Invoke(currentGrabbedPointIndex, currentTargetPosition, currentTargetRotation);
}
else if (clickOrDragCoroutine != null)
{
float distance = Vector3.Distance(startGrabPosition, currentTargetPosition);
if (distance > dragMovementThreshold)
{
StartDragMode();
}
}
}
}
private void InitializeInteraction()
{
// XRI 이벤트 구독
baseInteractor.selectEntered.AddListener(HandleGrabStart);
baseInteractor.selectExited.AddListener(HandleGrabEnd);
baseInteractor.hoverEntered.AddListener(OnHoverEntered);
baseInteractor.hoverExited.AddListener(OnHoverExited);
TryInitializeHapticDevice();
isInitialized = true;
AppManager.Instance.RegisterView(this);
}
private void OnDestroy()
{
if (baseInteractor != null)
{
baseInteractor.selectEntered.RemoveListener(HandleGrabStart);
baseInteractor.selectExited.RemoveListener(HandleGrabEnd);
}
}
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;
}
}
/// <summary>
/// InputDevice를 사용한 햅틱
/// </summary>
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 이벤트 핸들러 ---
private void HandleGrabStart(SelectEnterEventArgs args)
{
GameObject grabbedGO = args.interactableObject.transform.gameObject;
RobotPoint point = grabbedGO.GetComponent<RobotPoint>();
if (point != null)
{
isGrabbingPoint = true;
isGrabbingRobot = false;
currentGrabbedPointIndex = point.pointIndex;
// 타이머 시작
startGrabPosition = rayProvider.rayEndPoint;
if (clickOrDragCoroutine != null)
StopCoroutine(clickOrDragCoroutine);
clickOrDragCoroutine = StartCoroutine(ClickOrDragTimer());
//OnPointDragStart?.Invoke(currentGrabbedPointIndex);
}
else if (grabbedGO.CompareTag("RobotArm"))
{
isGrabbingPoint = false;
isGrabbingRobot = true;
currentGrabbedPointIndex = -1;
OnRobotGrabStart?.Invoke(startGrabPosition);
OnPointDragStart?.Invoke(currentGrabbedPointIndex);
}
}
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;
}
}