2025-10-30 09:31:05 +09:00
|
|
|
|
using System;
|
2025-11-07 16:26:43 +09:00
|
|
|
|
using System.Collections;
|
2025-10-30 09:31:05 +09:00
|
|
|
|
using UnityEngine;
|
2025-11-19 18:38:48 +09:00
|
|
|
|
using UnityEngine.XR;
|
2025-11-04 11:36:19 +09:00
|
|
|
|
using UnityEngine.XR.Interaction.Toolkit;
|
|
|
|
|
|
using UnityEngine.XR.Interaction.Toolkit.Interactors;
|
2025-10-30 09:31:05 +09:00
|
|
|
|
|
2025-11-11 13:21:20 +09:00
|
|
|
|
public enum HandSide { Left, Right }
|
|
|
|
|
|
|
2025-11-19 18:38:48 +09:00
|
|
|
|
public class InteractionView : MonoBehaviour
|
2025-10-30 09:31:05 +09:00
|
|
|
|
{
|
2025-11-21 13:18:03 +09:00
|
|
|
|
public event Action<bool> OnRobotHoverStateChanged;
|
2025-11-19 18:38:48 +09:00
|
|
|
|
public event Action OnRobotGrabStart;
|
2025-11-04 11:36:19 +09:00
|
|
|
|
public event Action<Vector3, Quaternion> OnRobotGrabbed;
|
2025-10-30 09:31:05 +09:00
|
|
|
|
public event Action<RobotData> OnRobotReleased;
|
2025-11-07 16:26:43 +09:00
|
|
|
|
public event Action<int, Vector3> OnPointClicked;
|
2025-10-30 09:31:05 +09:00
|
|
|
|
public event Action<int> OnPointDragStart;
|
2025-11-06 15:28:16 +09:00
|
|
|
|
public event Action<int, Vector3, Quaternion> OnPointDragUpdate;
|
2025-10-30 09:31:05 +09:00
|
|
|
|
public event Action<int> OnPointDragEnd;
|
|
|
|
|
|
|
2025-11-06 15:28:16 +09:00
|
|
|
|
[SerializeField]private NearFarInteractor nearFarInteractor;
|
2025-11-11 13:21:20 +09:00
|
|
|
|
public HandSide handSide;
|
2025-11-06 15:28:16 +09:00
|
|
|
|
private XRBaseInteractor baseInteractor;
|
|
|
|
|
|
private IXRRayProvider rayProvider;
|
2025-11-19 18:38:48 +09:00
|
|
|
|
private InputDevice hapticDevice;
|
|
|
|
|
|
private bool isHapticDeviceFound = false;
|
2025-11-04 11:36:19 +09:00
|
|
|
|
|
2025-11-06 20:14:27 +09:00
|
|
|
|
[SerializeField]
|
|
|
|
|
|
[Tooltip("<22>巡<EFBFBD><E5B7A1> <20><> <20><>ġ<EFBFBD><C4A1> ǥ<><C7A5><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>κ<EFBFBD> <20><><EFBFBD><EFBFBD>")]
|
|
|
|
|
|
private GameObject ghostRobot;
|
|
|
|
|
|
[SerializeField]
|
|
|
|
|
|
[Tooltip("<22>巡<EFBFBD><EFBFBD> <20><><EFBFBD>콺 <20>̹<EFBFBD><CCB9><EFBFBD>")]
|
|
|
|
|
|
private GameObject dragArrow;
|
2025-11-07 16:26:43 +09:00
|
|
|
|
[SerializeField] private float clickTimeThreshold = 0.5f;
|
|
|
|
|
|
[SerializeField] private float dragMovementThreshold = 0.05f;
|
|
|
|
|
|
private Coroutine clickOrDragCoroutine = null;
|
|
|
|
|
|
private Vector3 startGrabPosition;
|
2025-11-06 20:14:27 +09:00
|
|
|
|
|
2025-11-04 11:36:19 +09:00
|
|
|
|
private bool isInitialized = false;
|
|
|
|
|
|
private bool isGrabbingPoint = false;
|
|
|
|
|
|
private int currentGrabbedPointIndex = -1;
|
|
|
|
|
|
|
2025-11-07 16:26:43 +09:00
|
|
|
|
Vector3 currentTargetPosition = Vector3.zero;
|
|
|
|
|
|
Quaternion currentTargetRotation = Quaternion.identity;
|
|
|
|
|
|
|
2025-11-04 11:36:19 +09:00
|
|
|
|
public bool isGrabbingRobot = false;
|
|
|
|
|
|
|
2025-10-30 09:31:05 +09:00
|
|
|
|
void Update()
|
|
|
|
|
|
{
|
2025-11-06 15:28:16 +09:00
|
|
|
|
if (!isInitialized || rayProvider == null)
|
2025-11-04 11:36:19 +09:00
|
|
|
|
{
|
2025-11-06 15:28:16 +09:00
|
|
|
|
if (nearFarInteractor == null)
|
2025-11-04 11:36:19 +09:00
|
|
|
|
{
|
2025-11-06 15:28:16 +09:00
|
|
|
|
nearFarInteractor = GetComponentInChildren<NearFarInteractor>(true);
|
|
|
|
|
|
baseInteractor = nearFarInteractor as XRBaseInteractor;
|
|
|
|
|
|
rayProvider = nearFarInteractor as IXRRayProvider;
|
2025-11-04 11:36:19 +09:00
|
|
|
|
}
|
2025-11-10 19:14:16 +09:00
|
|
|
|
if (nearFarInteractor != null)
|
2025-11-04 11:36:19 +09:00
|
|
|
|
{
|
2025-11-06 15:28:16 +09:00
|
|
|
|
InitializeInteraction();
|
2025-11-04 11:36:19 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-11-06 15:28:16 +09:00
|
|
|
|
currentTargetPosition = rayProvider.rayEndPoint;
|
|
|
|
|
|
currentTargetRotation = baseInteractor.attachTransform.rotation;
|
|
|
|
|
|
|
2025-11-07 16:26:43 +09:00
|
|
|
|
if (isGrabbingRobot)
|
|
|
|
|
|
{
|
|
|
|
|
|
OnRobotGrabbed?.Invoke(currentTargetPosition, currentTargetRotation);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-11-06 15:28:16 +09:00
|
|
|
|
|
|
|
|
|
|
if (isGrabbingPoint)
|
2025-11-04 11:36:19 +09:00
|
|
|
|
{
|
2025-11-06 15:28:16 +09:00
|
|
|
|
OnPointDragUpdate?.Invoke(currentGrabbedPointIndex, currentTargetPosition, currentTargetRotation);
|
2025-11-04 11:36:19 +09:00
|
|
|
|
}
|
2025-11-07 16:26:43 +09:00
|
|
|
|
else if (clickOrDragCoroutine != null)
|
2025-11-04 11:36:19 +09:00
|
|
|
|
{
|
2025-11-07 16:26:43 +09:00
|
|
|
|
float distance = Vector3.Distance(startGrabPosition, currentTargetPosition);
|
|
|
|
|
|
|
|
|
|
|
|
if (distance > dragMovementThreshold)
|
|
|
|
|
|
{
|
|
|
|
|
|
StartDragMode();
|
|
|
|
|
|
}
|
2025-11-04 11:36:19 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void InitializeInteraction()
|
|
|
|
|
|
{
|
|
|
|
|
|
// XRI <20>̺<EFBFBD>Ʈ <20><><EFBFBD><EFBFBD>
|
2025-11-06 15:28:16 +09:00
|
|
|
|
baseInteractor.selectEntered.AddListener(HandleGrabStart);
|
|
|
|
|
|
baseInteractor.selectExited.AddListener(HandleGrabEnd);
|
2025-11-21 13:18:03 +09:00
|
|
|
|
baseInteractor.hoverEntered.AddListener(OnHoverEntered);
|
|
|
|
|
|
baseInteractor.hoverExited.AddListener(OnHoverExited);
|
2025-11-04 11:36:19 +09:00
|
|
|
|
|
2025-11-19 18:38:48 +09:00
|
|
|
|
TryInitializeHapticDevice();
|
|
|
|
|
|
|
2025-11-04 11:36:19 +09:00
|
|
|
|
isInitialized = true;
|
|
|
|
|
|
AppManager.Instance.RegisterView(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnDestroy()
|
|
|
|
|
|
{
|
2025-11-06 15:28:16 +09:00
|
|
|
|
if (baseInteractor != null)
|
2025-11-04 11:36:19 +09:00
|
|
|
|
{
|
2025-11-06 15:28:16 +09:00
|
|
|
|
baseInteractor.selectEntered.RemoveListener(HandleGrabStart);
|
|
|
|
|
|
baseInteractor.selectExited.RemoveListener(HandleGrabEnd);
|
2025-11-04 11:36:19 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-19 18:38:48 +09:00
|
|
|
|
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<63><65> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><>ƽ
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void SendHapticImpulse(float amplitude, float duration)
|
|
|
|
|
|
{
|
|
|
|
|
|
// <20><>Ʈ<EFBFBD>ѷ<EFBFBD><D1B7><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ʾҰų<D2B0>(isValid) <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD>Ƿ<EFBFBD> <20>Ź<EFBFBD> Ȯ<><C8AE>
|
|
|
|
|
|
if (!isHapticDeviceFound || !hapticDevice.isValid)
|
|
|
|
|
|
{
|
|
|
|
|
|
TryInitializeHapticDevice(); // <20>ٽ<EFBFBD> ã<><C3A3> <20>õ<EFBFBD>
|
|
|
|
|
|
if (!isHapticDeviceFound) return; // <20><EFBFBD><D7B7><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ߴ<EFBFBD>
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ִ<EFBFBD><D6B4><EFBFBD> Ȯ<><C8AE>
|
|
|
|
|
|
HapticCapabilities capabilities;
|
|
|
|
|
|
if (hapticDevice.TryGetHapticCapabilities(out capabilities))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (capabilities.supportsImpulse)
|
|
|
|
|
|
{
|
|
|
|
|
|
// <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
|
|
|
|
|
hapticDevice.SendHapticImpulse(0, amplitude, duration);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-04 11:36:19 +09:00
|
|
|
|
// --- XRI <20>̺<EFBFBD>Ʈ <20>ڵ鷯 ---
|
|
|
|
|
|
|
|
|
|
|
|
private void HandleGrabStart(SelectEnterEventArgs args)
|
|
|
|
|
|
{
|
|
|
|
|
|
GameObject grabbedGO = args.interactableObject.transform.gameObject;
|
|
|
|
|
|
|
2025-11-06 20:14:27 +09:00
|
|
|
|
RobotPoint point = grabbedGO.GetComponent<RobotPoint>();
|
|
|
|
|
|
if (point != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
isGrabbingPoint = true;
|
|
|
|
|
|
isGrabbingRobot = false;
|
|
|
|
|
|
currentGrabbedPointIndex = point.pointIndex;
|
|
|
|
|
|
|
2025-11-07 16:26:43 +09:00
|
|
|
|
// Ÿ<≯<EFBFBD> <20><><EFBFBD><EFBFBD>
|
|
|
|
|
|
startGrabPosition = rayProvider.rayEndPoint;
|
|
|
|
|
|
if (clickOrDragCoroutine != null)
|
|
|
|
|
|
StopCoroutine(clickOrDragCoroutine);
|
|
|
|
|
|
clickOrDragCoroutine = StartCoroutine(ClickOrDragTimer());
|
|
|
|
|
|
|
|
|
|
|
|
//OnPointDragStart?.Invoke(currentGrabbedPointIndex);
|
2025-11-06 20:14:27 +09:00
|
|
|
|
}
|
|
|
|
|
|
else if (grabbedGO.CompareTag("RobotArm"))
|
2025-11-04 11:36:19 +09:00
|
|
|
|
{
|
2025-11-06 15:28:16 +09:00
|
|
|
|
isGrabbingPoint = false;
|
2025-11-04 11:36:19 +09:00
|
|
|
|
isGrabbingRobot = true;
|
2025-11-06 15:28:16 +09:00
|
|
|
|
currentGrabbedPointIndex = -1;
|
|
|
|
|
|
|
2025-11-19 18:38:48 +09:00
|
|
|
|
OnRobotGrabStart?.Invoke();
|
2025-11-06 15:28:16 +09:00
|
|
|
|
OnPointDragStart?.Invoke(currentGrabbedPointIndex);
|
2025-11-04 11:36:19 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void HandleGrabEnd(SelectExitEventArgs args)
|
|
|
|
|
|
{
|
2025-11-07 16:26:43 +09:00
|
|
|
|
if (isGrabbingRobot)
|
2025-11-06 20:14:27 +09:00
|
|
|
|
{
|
2025-11-07 16:26:43 +09:00
|
|
|
|
OnRobotReleased?.Invoke(new RobotData());
|
2025-11-06 20:14:27 +09:00
|
|
|
|
}
|
2025-11-07 16:26:43 +09:00
|
|
|
|
else
|
2025-11-06 20:14:27 +09:00
|
|
|
|
{
|
2025-11-07 16:26:43 +09:00
|
|
|
|
if (clickOrDragCoroutine != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
StopCoroutine(clickOrDragCoroutine);
|
|
|
|
|
|
clickOrDragCoroutine = null;
|
|
|
|
|
|
|
|
|
|
|
|
OnPointClicked?.Invoke(currentGrabbedPointIndex, currentTargetPosition);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (isGrabbingPoint)
|
|
|
|
|
|
{
|
|
|
|
|
|
OnPointDragEnd?.Invoke(currentGrabbedPointIndex);
|
|
|
|
|
|
}
|
2025-11-06 20:14:27 +09:00
|
|
|
|
}
|
2025-11-04 11:36:19 +09:00
|
|
|
|
|
|
|
|
|
|
// <20><><EFBFBD><EFBFBD> <20>ʱ<EFBFBD>ȭ
|
2025-11-07 16:26:43 +09:00
|
|
|
|
clickOrDragCoroutine = null;
|
2025-11-04 11:36:19 +09:00
|
|
|
|
isGrabbingPoint = false;
|
2025-11-06 15:28:16 +09:00
|
|
|
|
isGrabbingRobot = false;
|
2025-11-04 11:36:19 +09:00
|
|
|
|
currentGrabbedPointIndex = -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-21 13:18:03 +09:00
|
|
|
|
private void OnHoverEntered(HoverEnterEventArgs args)
|
|
|
|
|
|
{
|
|
|
|
|
|
// ȣ<><C8A3><EFBFBD><EFBFBD> <20><>ü<EFBFBD><C3BC> '<27>κ<EFBFBD>'<27><><EFBFBD><EFBFBD> Ȯ<><C8AE>
|
|
|
|
|
|
if (args.interactableObject.transform.CompareTag("RobotArm"))
|
|
|
|
|
|
{
|
|
|
|
|
|
OnRobotHoverStateChanged?.Invoke(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ȣ<><C8A3> <20><><EFBFBD><EFBFBD> <20><> ȣ<><C8A3>
|
|
|
|
|
|
private void OnHoverExited(HoverExitEventArgs args)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (args.interactableObject.transform.CompareTag("RobotArm"))
|
|
|
|
|
|
{
|
|
|
|
|
|
OnRobotHoverStateChanged?.Invoke(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 16:26:43 +09:00
|
|
|
|
// Ŭ<><C5AC>/<2F>巡<EFBFBD><E5B7A1> <20><><EFBFBD><EFBFBD><EFBFBD>ϴ<EFBFBD> Ÿ<≯<EFBFBD> <20>ڷ<EFBFBD>ƾ
|
|
|
|
|
|
private IEnumerator ClickOrDragTimer()
|
|
|
|
|
|
{
|
|
|
|
|
|
yield return new WaitForSeconds(clickTimeThreshold);
|
|
|
|
|
|
|
|
|
|
|
|
if (clickOrDragCoroutine != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.Log("<22>ð<EFBFBD> <20>ʰ<EFBFBD><CAB0><EFBFBD> <20>巡<EFBFBD><E5B7A1> <20><><EFBFBD><EFBFBD> (OnPointDragStart)");
|
|
|
|
|
|
StartDragMode();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Ÿ<≯<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ϰ<EFBFBD> <20>巡<EFBFBD><E5B7A1> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><>ȯ
|
|
|
|
|
|
private void StartDragMode()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (clickOrDragCoroutine != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
StopCoroutine(clickOrDragCoroutine);
|
|
|
|
|
|
clickOrDragCoroutine = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isGrabbingPoint = true;
|
|
|
|
|
|
OnPointDragStart?.Invoke(currentGrabbedPointIndex);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 20:14:27 +09:00
|
|
|
|
// <20><><EFBFBD><EFBFBD>Ʈ <20>κ<EFBFBD><CEBA><EFBFBD> <20><>ġ/ȸ<><C8B8> <20><><EFBFBD><EFBFBD>
|
|
|
|
|
|
public void ShowGhostRobot()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ghostRobot == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning("Ghost Robot<6F><74> Inspector<6F><72> <20>Ҵ<EFBFBD><D2B4><EFBFBD><EFBFBD><EFBFBD> <20>ʾҽ<CABE><D2BD>ϴ<EFBFBD>.");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ghostRobot.SetActive(true);
|
2025-11-06 15:28:16 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 20:14:27 +09:00
|
|
|
|
// <20><><EFBFBD><EFBFBD>Ʈ <20>κ<EFBFBD> <20><>Ȱ<EFBFBD><C8B0>ȭ
|
|
|
|
|
|
public void HideGhostRobot()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ghostRobot != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
ghostRobot.SetActive(false);
|
|
|
|
|
|
}
|
2025-11-06 15:28:16 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 20:14:27 +09:00
|
|
|
|
// <20>巡<EFBFBD><E5B7A1> ȭ<><C8AD>ǥ Ȱ<><C8B0>ȭ <20><> <20><>ġ <20><><EFBFBD><EFBFBD>
|
|
|
|
|
|
public void ShowDragArrow(Vector3 position)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (dragArrow == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogWarning("Drag Arrow<6F><77> Inspector<6F><72> <20>Ҵ<EFBFBD><D2B4><EFBFBD><EFBFBD><EFBFBD> <20>ʾҽ<CABE><D2BD>ϴ<EFBFBD>.");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
dragArrow.SetActive(true);
|
|
|
|
|
|
dragArrow.transform.position = position;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// <20>巡<EFBFBD><E5B7A1> ȭ<><C8AD>ǥ <20><>Ȱ<EFBFBD><C8B0>ȭ
|
|
|
|
|
|
public void HideDragArrow()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (dragArrow != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
dragArrow.SetActive(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// <20><><EFBFBD><EFBFBD> <20><> <20><>ġ <20><>ȯ
|
|
|
|
|
|
public Vector3 GetCurrentRayPosition()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (rayProvider != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return rayProvider.rayEndPoint;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (baseInteractor != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return baseInteractor.attachTransform.position;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return transform.position;
|
2025-11-06 15:28:16 +09:00
|
|
|
|
}
|
2025-10-30 09:31:05 +09:00
|
|
|
|
}
|