319 lines
11 KiB
C#
319 lines
11 KiB
C#
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using TMPro;
|
|
|
|
public class UIManager : MonoBehaviour
|
|
{
|
|
public static UIManager Instance { get; private set; }
|
|
|
|
#region Inspector Variables
|
|
|
|
[Header("Settings of Target")]
|
|
[SerializeField] private Transform targetToRecord; // endNode 역할을 할 Transform
|
|
[SerializeField] private GameObject IKSolver;
|
|
[SerializeField] private TextMeshProUGUI keyframeText;
|
|
[SerializeField] private List<Button> buttonsToDisable; // 시뮬레이션 실행 중 비활성화할 버튼들
|
|
|
|
[Header("System References")]
|
|
[SerializeField] private RobotManager robotManager;
|
|
[SerializeField] private HybridInverseKinematicsNode hybridIKNode;
|
|
|
|
private Transform originalTransform; // 기록 시작 전의 원래 위치 저장
|
|
private List<IKData> keyframePositions = new List<IKData>();
|
|
public IReadOnlyList<IKData> KeyframePositions => keyframePositions; // 외부에서 읽기 전용으로 접근 가능하게 함
|
|
private float lastRecordingTime = 0f;
|
|
private Coroutine simulationCoroutine = null;
|
|
private readonly StringBuilder stringBuilder = new StringBuilder();
|
|
|
|
#endregion
|
|
|
|
private void Awake()
|
|
{
|
|
// 싱글톤 인스턴스 설정
|
|
if (Instance == null)
|
|
{
|
|
Instance = this;
|
|
DontDestroyOnLoad(gameObject);
|
|
}
|
|
else
|
|
{
|
|
Destroy(gameObject);
|
|
}
|
|
}
|
|
|
|
// 이벤트 구독 관리
|
|
private void OnEnable()
|
|
{
|
|
RobotManager.OnRobotSelected += HandleRobotSelected;
|
|
UIController.OnUIControllerInitialized += HandleUIInitialize;
|
|
UIController.OnRecordUIClicked += RecordPosition;
|
|
UIController.OnDeleteUIClicked += DeleteLastRecordedPosition;
|
|
UIController.OnInitUIClicked += InitRecordedPositions;
|
|
UIController.OnRunUIClicked += RunSimulation;
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
RobotManager.OnRobotSelected -= HandleRobotSelected;
|
|
UIController.OnUIControllerInitialized -= HandleUIInitialize;
|
|
UIController.OnRecordUIClicked -= RecordPosition;
|
|
UIController.OnDeleteUIClicked -= DeleteLastRecordedPosition;
|
|
UIController.OnInitUIClicked -= InitRecordedPositions;
|
|
UIController.OnRunUIClicked -= RunSimulation;
|
|
}
|
|
|
|
// RobotManager로부터 로봇이 선택되었다는 소식을 받았을 때 실행될 함수
|
|
private void HandleRobotSelected(RobotController selectedRobot)
|
|
{
|
|
if (selectedRobot != null)
|
|
{
|
|
Transform handleTransform = selectedRobot.transform.Find("Handle");
|
|
IKSolver = selectedRobot.GetComponentInChildren<HybridInverseKinematicsNode>()?.gameObject;
|
|
|
|
if (handleTransform != null)
|
|
{
|
|
this.targetToRecord = handleTransform;
|
|
Debug.Log($"UIManager: 기록 대상이 '{selectedRobot.robotId}'의 Handle로 변경.");
|
|
originalTransform = targetToRecord;
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning($"'{selectedRobot.robotId}'에서 Handle을 찾지 못함.");
|
|
}
|
|
}
|
|
}
|
|
|
|
// UIController가 초기화되었을 때 실행될 함수
|
|
private void HandleUIInitialize(UIController controller)
|
|
{
|
|
Debug.Log("UIManager: UIController 초기화 이벤트 수신됨.");
|
|
if (controller != null && controller.keyframeText != null && controller.buttonsToDisable != null)
|
|
{
|
|
this.keyframeText = controller.keyframeText;
|
|
Debug.Log("UIManager: Keyframe Text가 UIController로부터 설정됨.");
|
|
this.buttonsToDisable = controller.buttonsToDisable;
|
|
Debug.Log("UIManager: Buttons to Disable가 UIController로부터 설정됨.");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("UIController 또는 Keyframe Text/ButtonsToDisable이 null임.");
|
|
}
|
|
}
|
|
|
|
#region Public Methods
|
|
|
|
public void RecordPosition()
|
|
{
|
|
if (targetToRecord == null)
|
|
{
|
|
Debug.LogError("Target Transform이 지정되지 않았습니다!");
|
|
return;
|
|
}
|
|
float currentTime = Time.time;
|
|
float timeSinceLastFrame = (keyframePositions.Count > 0) ? currentTime - lastRecordingTime : 0f;
|
|
|
|
IKData newFrame = new IKData
|
|
{
|
|
position = targetToRecord.localPosition,
|
|
nodesPosition = new List<Vector3>(hybridIKNode.GetCurrentJointPositions()),
|
|
nodesRotation = new List<Quaternion>(hybridIKNode.GetCurrentJointRotations()),
|
|
deltaTime = timeSinceLastFrame,
|
|
};
|
|
|
|
keyframePositions.Add(newFrame);
|
|
lastRecordingTime = currentTime;
|
|
Debug.Log($"키프레임 기록됨: 위치={newFrame.position}, 시간 간격={newFrame.deltaTime}");
|
|
|
|
UpdateKeyframeUI();
|
|
}
|
|
|
|
public void DeleteLastRecordedPosition()
|
|
{
|
|
if (keyframePositions.Count > 0)
|
|
{
|
|
keyframePositions.RemoveAt(keyframePositions.Count - 1);
|
|
UpdateKeyframeUI();
|
|
}
|
|
else
|
|
{
|
|
Debug.Log("No recorded positions to delete.");
|
|
}
|
|
}
|
|
|
|
public void InitRecordedPositions()
|
|
{
|
|
StartCoroutine(ResetPositionRoutine());
|
|
}
|
|
|
|
private IEnumerator ResetPositionRoutine()
|
|
{
|
|
TargetRangeLimiter limiter = targetToRecord.GetComponent<TargetRangeLimiter>();
|
|
|
|
if (limiter != null) limiter.enabled = false;
|
|
|
|
keyframePositions.Clear();
|
|
UpdateKeyframeUI();
|
|
targetToRecord.localPosition = originalTransform.localPosition;
|
|
Debug.Log("Recorded positions initialized.");
|
|
|
|
// 한 프레임만 기다림
|
|
yield return null;
|
|
|
|
// 다시 활성화
|
|
if (limiter != null) limiter.enabled = true;
|
|
}
|
|
|
|
public void RunSimulation()
|
|
{
|
|
if (keyframePositions.Count == 0)
|
|
{
|
|
Debug.Log("No recorded positions to simulate.");
|
|
return;
|
|
}
|
|
if (simulationCoroutine != null)
|
|
{
|
|
StopCoroutine(simulationCoroutine);
|
|
}
|
|
SetButtonsInteractable(false);
|
|
IKSolver.SetActive(false);
|
|
simulationCoroutine = StartCoroutine(SimulateMovement());
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Inner Logic & Coroutines
|
|
|
|
private void SetButtonsInteractable(bool isInteractable)
|
|
{
|
|
foreach (Button button in buttonsToDisable)
|
|
{
|
|
if (button != null)
|
|
{
|
|
button.interactable = isInteractable;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateKeyframeUI()
|
|
{
|
|
if (keyframeText == null)
|
|
{
|
|
Debug.LogError("Keyframe Text가 지정되지 않았습니다!");
|
|
return;
|
|
}
|
|
// StringBuilder를 사용하여 불필요한 메모리 할당을 방지
|
|
stringBuilder.Clear();
|
|
|
|
for (int i = 0; i < keyframePositions.Count; i++)
|
|
{
|
|
// Vector3의 소수점을 2자리까지만 표시
|
|
string positionText = keyframePositions[i].position.ToString("F2");
|
|
string deltaTimeText = keyframePositions[i].deltaTime.ToString("F2");
|
|
stringBuilder.AppendLine($"[{i + 1}] pos:{positionText}, time:{deltaTimeText}");
|
|
}
|
|
|
|
keyframeText.text = stringBuilder.ToString(); // 최종적으로 완성된 문자열을 한 번만 할당
|
|
}
|
|
|
|
private IEnumerator SimulateMovement()
|
|
{
|
|
//Debug.Log("시뮬레이션 시작.");
|
|
//Vector3 startPosition = keyframePositions[0].position;
|
|
|
|
//foreach (IKData target in keyframePositions)
|
|
//{
|
|
// Vector3 targetPosition = target.position;
|
|
// float moveDuration = target.deltaTime; // 수신된 시간 간격
|
|
|
|
// if (moveDuration <= 0f)
|
|
// {
|
|
// targetToRecord.localPosition = targetPosition;
|
|
// startPosition = targetPosition;
|
|
// continue;
|
|
// }
|
|
|
|
// float elapsedTime = 0f;
|
|
|
|
// while (elapsedTime < moveDuration)
|
|
// {
|
|
// elapsedTime += Time.deltaTime;
|
|
// float t = elapsedTime / moveDuration; // 이동 진행도 계산 (0.0 ~ 1.0)
|
|
// targetToRecord.localPosition = Vector3.Lerp(startPosition, targetPosition, t); // Lerp 함수로 시작점과 목표점 사이의 현재 위치 계산
|
|
// yield return null;
|
|
// }
|
|
|
|
// targetToRecord.localPosition = targetPosition; // 오차 없애기 위해 이동이 끝나면 목표 위치로 정확히 설정
|
|
// startPosition = targetPosition;
|
|
//}
|
|
Debug.Log("동기화 시뮬레이션 시작");
|
|
if (hybridIKNode == null || keyframePositions == null)
|
|
{
|
|
Debug.LogWarning("타겟 IK나 키프레임 리스트가 비어있음");
|
|
yield break;
|
|
}
|
|
|
|
Transform[] jointTransforms = new Transform[hybridIKNode.nodes.Count];
|
|
Debug.Log($"관절 개수: {hybridIKNode.nodes.Count}");
|
|
for (int i = 0; i < hybridIKNode.nodes.Count; i++)
|
|
{
|
|
jointTransforms[i] = hybridIKNode.nodes[i].jointTransform;
|
|
}
|
|
|
|
foreach (IKData targetKeyframe in keyframePositions)
|
|
{
|
|
float moveDuration = targetKeyframe.deltaTime; // 수신된 시간 간격
|
|
|
|
Debug.Log($"목표 키프레임 위치: {targetKeyframe.position}, 이동 시간: {moveDuration}");
|
|
|
|
Vector3[] startPositions = new Vector3[jointTransforms.Length];
|
|
Quaternion[] startRotations = new Quaternion[jointTransforms.Length];
|
|
for (int i = 0; i < jointTransforms.Length; i++)
|
|
{
|
|
startPositions[i] = jointTransforms[i].localPosition;
|
|
startRotations[i] = jointTransforms[i].localRotation;
|
|
}
|
|
|
|
List<Vector3> targetPositions = targetKeyframe.nodesPosition;
|
|
List<Quaternion> targetRotations = targetKeyframe.nodesRotation;
|
|
|
|
if (moveDuration > 0f)
|
|
{
|
|
float elapsedTime = 0f;
|
|
while (elapsedTime < moveDuration)
|
|
{
|
|
elapsedTime += Time.deltaTime;
|
|
Debug.Log($"로봇 이동 중... 경과 시간: {elapsedTime:F2}s / {moveDuration:F2}s");
|
|
float t = elapsedTime / moveDuration; // 이동 진행도 계산 (0.0 ~ 1.0)
|
|
|
|
for (int i = 0; i < jointTransforms.Length; i++)
|
|
{
|
|
jointTransforms[i].localPosition = Vector3.Lerp(startPositions[i], targetPositions[i], t);
|
|
jointTransforms[i].localRotation = Quaternion.Slerp(startRotations[i], targetRotations[i], t);
|
|
Debug.Log($"관절 {i} 위치: {jointTransforms[i].localPosition}, 회전: {jointTransforms[i].localRotation.eulerAngles}");
|
|
}
|
|
|
|
yield return null;
|
|
}
|
|
}
|
|
|
|
// 보간이 끝난 뒤 모든 관절을 목표 자세로 정확히 설정
|
|
for (int i = 0; i < jointTransforms.Length; i++)
|
|
{
|
|
jointTransforms[i].localPosition = targetPositions[i];
|
|
jointTransforms[i].localRotation = targetRotations[i];
|
|
Debug.Log($"로봇 관절 {i} 최종 위치: {jointTransforms[i].localPosition}, 회전: {jointTransforms[i].localRotation.eulerAngles}");
|
|
}
|
|
}
|
|
|
|
Debug.Log("시뮬레이션 종료.");
|
|
simulationCoroutine = null;
|
|
SetButtonsInteractable(true);
|
|
IKSolver.SetActive(true);
|
|
}
|
|
|
|
#endregion
|
|
}
|