Files
HDRobotics/Assets/HybridIK/Scripts/Main/UIManager.cs

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
}