332 lines
11 KiB
C#
332 lines
11 KiB
C#
#nullable enable
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UVC.UI.Commands;
|
|
using UVC.Core;
|
|
using UVC.Object3d.Manager;
|
|
|
|
namespace EnglewoodLAB.Command
|
|
{
|
|
/// <summary>
|
|
/// Transform 변경 커맨드 (Undo/Redo 지원)
|
|
/// RTGizmo 드래그 완료 시 사용
|
|
/// </summary>
|
|
public class TransformChangeCommand : IUndoableCommand
|
|
{
|
|
public string Description { get; }
|
|
|
|
/// <summary>
|
|
/// 단일 객체의 Transform 상태
|
|
/// </summary>
|
|
private class TransformState
|
|
{
|
|
public Transform Transform { get; set; } = null!;
|
|
public string? StageObjectId { get; set; }
|
|
public Vector3 LocalPosition { get; set; }
|
|
public Quaternion LocalRotation { get; set; }
|
|
public Vector3 LocalScale { get; set; }
|
|
|
|
public TransformState() { }
|
|
|
|
public TransformState(Transform transform)
|
|
{
|
|
Transform = transform;
|
|
CaptureStageObjectId();
|
|
Capture();
|
|
}
|
|
|
|
/// <summary>
|
|
/// StageObjectManager에서 StageObject ID를 캡처합니다.
|
|
/// </summary>
|
|
private void CaptureStageObjectId()
|
|
{
|
|
if (Transform == null) return;
|
|
|
|
var stageObjectManager = InjectorAppContext.Instance?.Get<StageObjectManager>();
|
|
if (stageObjectManager != null)
|
|
{
|
|
var stageObject = stageObjectManager.GetByGameObject(Transform.gameObject);
|
|
if (stageObject != null)
|
|
{
|
|
StageObjectId = stageObject.Id;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Transform이 유효한지 확인 (파괴되지 않았는지)
|
|
/// </summary>
|
|
public bool IsValid()
|
|
{
|
|
// Unity에서 파괴된 객체는 == null로 체크
|
|
// ReferenceEquals는 실제 null만 체크하므로 Unity 객체에는 부적합
|
|
return Transform != null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Transform이 null이면 StageObject ID로 복구를 시도합니다.
|
|
/// </summary>
|
|
/// <returns>복구 성공 여부</returns>
|
|
public bool TryResolveTransform()
|
|
{
|
|
if (IsValid()) return true;
|
|
if (string.IsNullOrEmpty(StageObjectId)) return false;
|
|
|
|
var stageObjectManager = InjectorAppContext.Instance?.Get<StageObjectManager>();
|
|
if (stageObjectManager == null) return false;
|
|
|
|
var stageObject = stageObjectManager.GetById(StageObjectId);
|
|
if (stageObject?.gameObject != null)
|
|
{
|
|
Transform = stageObject.gameObject.transform;
|
|
Debug.Log($"[TransformState] Transform resolved by StageObject ID: {StageObjectId}");
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void Capture()
|
|
{
|
|
if (!IsValid()) return;
|
|
LocalPosition = Transform.localPosition;
|
|
LocalRotation = Transform.localRotation;
|
|
LocalScale = Transform.localScale;
|
|
}
|
|
|
|
public void Apply()
|
|
{
|
|
// Transform이 null이면 StageObject ID로 복구 시도
|
|
if (!IsValid())
|
|
{
|
|
TryResolveTransform();
|
|
}
|
|
|
|
if (!IsValid()) return;
|
|
Transform.localPosition = LocalPosition;
|
|
Transform.localRotation = LocalRotation;
|
|
Transform.localScale = LocalScale;
|
|
}
|
|
|
|
public TransformState Clone()
|
|
{
|
|
return new TransformState
|
|
{
|
|
Transform = Transform,
|
|
StageObjectId = StageObjectId,
|
|
LocalPosition = LocalPosition,
|
|
LocalRotation = LocalRotation,
|
|
LocalScale = LocalScale
|
|
};
|
|
}
|
|
|
|
public bool HasChanged(TransformState other)
|
|
{
|
|
return LocalPosition != other.LocalPosition ||
|
|
LocalRotation != other.LocalRotation ||
|
|
LocalScale != other.LocalScale;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 이 커맨드가 유효한지 확인 (대상 Transform이 파괴되지 않았는지)
|
|
/// StageObject ID가 있으면 복구를 시도합니다.
|
|
/// </summary>
|
|
public bool IsValid()
|
|
{
|
|
// 모든 Transform이 유효해야 함 (복구 시도 포함)
|
|
foreach (var state in _beforeStates)
|
|
{
|
|
if (!state.IsValid() && !state.TryResolveTransform())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return _beforeStates.Count > 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Transform 복구를 시도하고 결과를 반환합니다.
|
|
/// </summary>
|
|
/// <returns>모든 Transform이 복구되었으면 true</returns>
|
|
public bool TryResolveAllTransforms()
|
|
{
|
|
bool allResolved = true;
|
|
foreach (var state in _beforeStates)
|
|
{
|
|
if (!state.IsValid() && !state.TryResolveTransform())
|
|
{
|
|
allResolved = false;
|
|
}
|
|
}
|
|
foreach (var state in _afterStates)
|
|
{
|
|
if (!state.IsValid() && !state.TryResolveTransform())
|
|
{
|
|
allResolved = false;
|
|
}
|
|
}
|
|
return allResolved;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 지정된 Transform 중 하나라도 이 커맨드에 포함되어 있는지 확인합니다.
|
|
/// </summary>
|
|
/// <param name="transforms">확인할 Transform 집합</param>
|
|
/// <returns>포함 여부</returns>
|
|
public bool ContainsAnyTransform(HashSet<Transform> transforms)
|
|
{
|
|
foreach (var state in _beforeStates)
|
|
{
|
|
if (transforms.Contains(state.Transform))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// 변경 전/후 상태 저장 (다중 객체 지원)
|
|
private readonly List<TransformState> _beforeStates = new();
|
|
private readonly List<TransformState> _afterStates = new();
|
|
|
|
/// <summary>
|
|
/// 단일 Transform에 대한 생성자
|
|
/// </summary>
|
|
/// <param name="transform">대상 Transform</param>
|
|
/// <param name="description">작업 설명</param>
|
|
public TransformChangeCommand(Transform transform, string description = "객체 이동")
|
|
{
|
|
Description = description;
|
|
_beforeStates.Add(new TransformState(transform));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 다중 Transform에 대한 생성자
|
|
/// </summary>
|
|
/// <param name="transforms">대상 Transform 목록</param>
|
|
/// <param name="description">작업 설명</param>
|
|
public TransformChangeCommand(IEnumerable<Transform> transforms, string description = "객체 이동")
|
|
{
|
|
Description = description;
|
|
foreach (var transform in transforms)
|
|
{
|
|
_beforeStates.Add(new TransformState(transform));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// GameObject 목록에 대한 생성자
|
|
/// </summary>
|
|
/// <param name="gameObjects">대상 GameObject 목록</param>
|
|
/// <param name="description">작업 설명</param>
|
|
public TransformChangeCommand(IEnumerable<GameObject> gameObjects, string description = "객체 이동")
|
|
{
|
|
Description = description;
|
|
foreach (var go in gameObjects)
|
|
{
|
|
if (go != null)
|
|
{
|
|
_beforeStates.Add(new TransformState(go.transform));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드래그 완료 후 현재 상태를 After로 캡처
|
|
/// </summary>
|
|
public void CaptureAfterState()
|
|
{
|
|
_afterStates.Clear();
|
|
foreach (var beforeState in _beforeStates)
|
|
{
|
|
var afterState = new TransformState
|
|
{
|
|
Transform = beforeState.Transform,
|
|
StageObjectId = beforeState.StageObjectId
|
|
};
|
|
afterState.Capture();
|
|
_afterStates.Add(afterState);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 변경사항이 있는지 확인
|
|
/// </summary>
|
|
public bool HasChanges()
|
|
{
|
|
if (_beforeStates.Count != _afterStates.Count) return false;
|
|
|
|
for (int i = 0; i < _beforeStates.Count; i++)
|
|
{
|
|
if (_beforeStates[i].HasChanged(_afterStates[i]))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void Execute(object? parameter = null)
|
|
{
|
|
// RTGizmo가 이미 Transform을 변경했으므로
|
|
// After 상태만 캡처
|
|
CaptureAfterState();
|
|
}
|
|
|
|
public void Undo(object? parameter = null)
|
|
{
|
|
int appliedCount = 0;
|
|
foreach (var state in _beforeStates)
|
|
{
|
|
if (state.IsValid())
|
|
{
|
|
state.Apply();
|
|
appliedCount++;
|
|
}
|
|
}
|
|
Debug.Log($"[TransformChangeCommand] Undo: {appliedCount}/{_beforeStates.Count}개 객체 Transform 복원");
|
|
}
|
|
|
|
public void Redo()
|
|
{
|
|
// Transform 복구 시도 (StageObject ID 기반)
|
|
TryResolveAllTransforms();
|
|
|
|
// 복구 후에도 유효하지 않으면 스킵
|
|
if (!IsValid())
|
|
{
|
|
Debug.LogWarning($"[TransformChangeCommand] Redo skipped: 대상 객체가 파괴됨");
|
|
return;
|
|
}
|
|
|
|
int appliedCount = 0;
|
|
foreach (var state in _afterStates)
|
|
{
|
|
state.Apply(); // Apply 내부에서 TryResolveTransform 호출
|
|
if (state.IsValid())
|
|
{
|
|
appliedCount++;
|
|
}
|
|
}
|
|
Debug.Log($"[TransformChangeCommand] Redo: {appliedCount}/{_afterStates.Count}개 객체 Transform 재적용");
|
|
}
|
|
|
|
public bool CanMerge(IUndoableCommand other)
|
|
{
|
|
// 기즈모 조절 시 각 드래그 작업이 별도의 Undo 단계가 되도록 병합하지 않음
|
|
return false;
|
|
}
|
|
|
|
public void Merge(IUndoableCommand other)
|
|
{
|
|
if (other is TransformChangeCommand otherCmd)
|
|
{
|
|
// After 상태만 최신 값으로 업데이트
|
|
_afterStates.Clear();
|
|
_afterStates.AddRange(otherCmd._afterStates);
|
|
}
|
|
}
|
|
}
|
|
}
|