290 lines
8.1 KiB
C#
290 lines
8.1 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using UnityEngine;
|
|
using UnityEngine.XR.Interaction.Toolkit;
|
|
using UnityEngine.XR.Interaction.Toolkit.Interactables;
|
|
using UnityEngine.XR.Interaction.Toolkit.Interactors;
|
|
|
|
public interface IInteractionView
|
|
{
|
|
|
|
}
|
|
|
|
public class InteractionView : MonoBehaviour, IInteractionView
|
|
{
|
|
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;
|
|
private XRBaseInteractor baseInteractor;
|
|
private IXRRayProvider rayProvider;
|
|
|
|
[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 Start()
|
|
{
|
|
nearFarInteractor = GetComponent<NearFarInteractor>();
|
|
|
|
if (nearFarInteractor == null)
|
|
{
|
|
Debug.LogError("InteractionView: 'nearFarInteractor'가 할당되지 않았습니다", this);
|
|
return;
|
|
}
|
|
|
|
baseInteractor = nearFarInteractor as XRBaseInteractor;
|
|
rayProvider = nearFarInteractor as IXRRayProvider;
|
|
|
|
if (baseInteractor == null)
|
|
{
|
|
Debug.LogError("NearFarInteractor를 XRBaseInteractor로 변환할 수 없습니다.", this);
|
|
return;
|
|
}
|
|
|
|
InitializeInteraction();
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (!isInitialized || rayProvider == null)
|
|
{
|
|
if (nearFarInteractor == null)
|
|
{
|
|
nearFarInteractor = GetComponentInChildren<NearFarInteractor>(true);
|
|
baseInteractor = nearFarInteractor as XRBaseInteractor;
|
|
rayProvider = nearFarInteractor as IXRRayProvider;
|
|
}
|
|
if (nearFarInteractor != null && nearFarInteractor.gameObject.activeInHierarchy)
|
|
{
|
|
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);
|
|
|
|
isInitialized = true;
|
|
AppManager.Instance.RegisterView(this);
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (baseInteractor != null)
|
|
{
|
|
baseInteractor.selectEntered.RemoveListener(HandleGrabStart);
|
|
baseInteractor.selectExited.RemoveListener(HandleGrabEnd);
|
|
}
|
|
}
|
|
|
|
// --- 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;
|
|
|
|
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 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);
|
|
}
|
|
|
|
// 로봇 좌표계(mm)를 Unity 월드 좌표계(m)로 변환
|
|
private Vector3 ConvertRobotDataToVector3(RobotData pose)
|
|
{
|
|
float x = Convert.ToSingle(pose.x / -1000.0);
|
|
float y = Convert.ToSingle(pose.z / 1000.0);
|
|
float z = Convert.ToSingle(pose.y / -1000.0);
|
|
return new Vector3(x, y, z);
|
|
}
|
|
|
|
// 로봇 오일러 각(rx,ry,rz)을 Unity 쿼터니언으로 변환
|
|
private Quaternion ConvertRobotDataToQuaternion(RobotData pose)
|
|
{
|
|
return Quaternion.Euler(pose.rx, pose.ry, pose.rz);
|
|
}
|
|
|
|
// 고스트 로봇의 위치/회전 설정
|
|
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;
|
|
}
|
|
}
|