Files
Studio/Assets/Scripts/XED/AssetTool/RenderObjectHandler.cs
2025-02-24 15:54:06 +09:00

453 lines
18 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using XED.Interfaces;
using XED.Manage;
using XED.RuntimeGizmo;
namespace XED.Util
{
public class RenderObjectHandler : MonoBehaviour, IStatusController, IInputHandler
{
List<CustomAssetRenderObject> selectedRenderObjects = new List<CustomAssetRenderObject>();
List<CustomAssetRenderObject> copyRenderObjects = new List<CustomAssetRenderObject>();
List<GameObject> selectedGameObjects = new List<GameObject>();
CustomAssetRenderObject prevSelectedObject;
RTGController rtgController;
ConvexHullCalculator convexHullCalculator = new ConvexHullCalculator();
Vector3 clickBeginPos;
float clickLengthThreshold = 5.0f;
int uiLayer;
int selectIndex;
bool lockHandler = false;
public event System.Action<Vector3> onDragBegin;
public event System.Action<Vector3> onDragEnd;
public event System.Action onDragForceEnd;
public UnityEvent<List<CustomAssetRenderObject>> onSelectObject;
public UnityEvent<List<CustomAssetRenderObject>> onRemoveObject;
public UnityEvent<List<CustomAssetRenderObject>> onCopyObject;
public UnityEvent<List<CustomAssetRenderObject>> onOrderByHierachy;
public event System.Action onDeselectAll;
public event System.Action<List<GameObject>> onTransformChanged;
void Awake()
{
myHandler = GetInputHandler();
}
private void Start()
{
rtgController = new RTGController();
rtgController.onTransformBegin = OnTransformBegin;
rtgController.onTransformChanged = OnTransformChangedFromRTG;
uiLayer = LayerMask.NameToLayer("UI");
//var inputManager = FindSingle<UserInputManager>();
selectIndex = 0;
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
}
}
private bool ApproximatelyEqual(Vector3 a, Vector3 b, float epsilon = 1e-5f)
{
return Mathf.Abs(a.x - b.x) < epsilon &&
Mathf.Abs(a.y - b.y) < epsilon &&
Mathf.Abs(a.z - b.z) < epsilon;
}
private void OnMousePointerDown()
{
clickBeginPos = Input.mousePosition;
//ui를 선택했을 경우에는 오브젝트 선택을 멈춘다.
PointerEventData pointerData = new PointerEventData(EventSystem.current)
{
position = Input.mousePosition
};
List<RaycastResult> raycastResults = new List<RaycastResult>();
EventSystem.current.RaycastAll(pointerData, raycastResults);
if (raycastResults.Any(x => x.gameObject.layer == uiLayer))
{
return;
}
onDragBegin?.Invoke(clickBeginPos);
}
private void OnMousePointerUp()
{
//rtg 컨트롤러가 조작중일때 락을 건다.
//rtg 컨트롤러 조작 후 포커스가 바뀌는 것을 방지하기 위한 용도
if (lockHandler == true)
{
lockHandler = false;
return;
}
//ui를 선택했을 경우에는 오브젝트 선택을 멈춘다.
PointerEventData pointerData = new PointerEventData(EventSystem.current)
{
position = Input.mousePosition
};
List<RaycastResult> raycastResults = new List<RaycastResult>();
EventSystem.current.RaycastAll(pointerData, raycastResults);
if (raycastResults.Any(x => x.gameObject.layer == uiLayer))
{
onDragForceEnd?.Invoke();
return;
}
//포인터 업 위치가 포인터 다운 위치에서 크게 벗어났을 경우는 클릭을 무시한다.
if ((clickBeginPos - Input.mousePosition).magnitude > clickLengthThreshold)
{
DeselectAll();
onDragEnd?.Invoke(Input.mousePosition);
return;
}
List<CustomAssetRenderObject> raycastedTarget = new List<CustomAssetRenderObject>();
CustomAssetRenderObject renderObject = null;
foreach (var result in raycastResults)
{
renderObject = result.gameObject.GetComponent<CustomAssetRenderObject>();
if (renderObject != null)
{
raycastedTarget.Add(renderObject);
}
}
if (raycastedTarget.Count > 0)
{
//오브젝트가 여러개 겹쳐 있을 경우 뒤의 오브젝트를 순차적으로 선택해주기 위한 용도
if (selectIndex < raycastedTarget.Count && prevSelectedObject == raycastedTarget[selectIndex])
{
selectIndex++;
}
if (selectIndex >= raycastedTarget.Count)
{
selectIndex = 0;
}
renderObject = raycastedTarget[selectIndex];
//컨트롤이 눌려 있으면 오브젝트 선택을 토글한다. (유니티 기능과 일치)
if (Input.GetKey(KeyCode.LeftControl))
{
if (renderObject.ToggleSelect())
{
AddSelection(renderObject);
}
else
{
RemoveSelection(renderObject);
}
}
//시프트 키가 눌려 있는 경우 다중 선택 기능으로 작용한다.
else if (Input.GetKey(KeyCode.LeftShift))
{
AddSelection(renderObject);
}
//그 외는 선택된 아이템을 제외하고 다 비선택해준다.
else
{
RemoveAllSelections();
AddSelection(renderObject);
}
prevSelectedObject = renderObject;
OnSelect();
}
else
{
ResetRTG();
DeselectAll();
}
onDragForceEnd?.Invoke();
}
public void DeselectAll()
{
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.LeftShift))
{
return;
}
RemoveAllSelections();
onDeselectAll?.Invoke();
}
public void AddSelection(CustomAssetRenderObject item)
{
if (selectedRenderObjects.Any(x => x == item))
{
return;
}
item.Select();
selectedRenderObjects.Add(item);
selectedGameObjects.Add(item.gameObject);
rtgController.SetGizmoTargetObjects(selectedGameObjects);
}
public void AddSelection(List<CustomAssetRenderObject> items)
{
for (int i = 0; i < items.Count; i++)
{
CustomAssetRenderObject item = items[i];
if (selectedRenderObjects.Any(x => x == item))
{
return;
}
item.Select();
selectedRenderObjects.Add(item);
selectedGameObjects.Add(item.gameObject);
}
rtgController.SetGizmoTargetObjects(selectedGameObjects);
}
public void RemoveAllSelections()
{
for (int i = 0; i < selectedRenderObjects.Count; i++)
{
selectedRenderObjects[i].Deselect();
}
selectedRenderObjects.Clear();
selectedGameObjects.Clear();
rtgController.SetGizmoTargetObjects(selectedGameObjects);
}
public void RemoveSelection(CustomAssetRenderObject item)
{
selectedRenderObjects.Remove(item);
selectedGameObjects.Remove(item.gameObject);
rtgController.SetGizmoTargetObjects(selectedGameObjects);
}
private void OnSelect()
{
onSelectObject?.Invoke(selectedRenderObjects);
rtgController.SetGizmoTargetObjects(selectedGameObjects);
}
public void ResetRTG()
{
rtgController.SetWorkGizmoId(RTGController.GizmoId.None);
}
public void SetRTGMove()
{
rtgController.SetWorkGizmoId(RTGController.GizmoId.Move);
}
public void SetRTGScale()
{
rtgController.SetWorkGizmoId(RTGController.GizmoId.Scale);
}
public void SetRTGRotate()
{
rtgController.SetWorkGizmoId(RTGController.GizmoId.Rotate);
}
public void OnTransformBegin()
{
lockHandler = true;
}
public void OnTransformChangedFromRTG(List<GameObject> transformObjects)
{
OnTransformChanged(transformObjects);
onTransformChanged?.Invoke(selectedGameObjects);
onDragForceEnd?.Invoke();
}
public void OnTransformChanged(List<GameObject> transformObjects)
{
foreach (GameObject gb in transformObjects)
{
CustomAssetRenderObject renderObject = gb.GetComponent<CustomAssetRenderObject>();
if (renderObject != null)
{
renderObject.onTransformChanged?.Invoke();
}
}
}
public void RemoveItem()
{
if (selectedRenderObjects.Count == 0) return;
onRemoveObject?.Invoke(selectedRenderObjects);
}
public void CopyItem()
{
if (selectedRenderObjects.Count == 0) return;
onCopyObject?.Invoke(selectedRenderObjects);
}
public void SaveItemsToCopy()
{
copyRenderObjects.Clear();
copyRenderObjects.AddRange(selectedRenderObjects);
}
public void CopySavedItems()
{
if (copyRenderObjects.Count == 0) return;
onCopyObject?.Invoke(copyRenderObjects);
}
public void ResetGizmoTargetObjects()
{
rtgController.SetGizmoTargetObjects(selectedGameObjects);
}
public void AlignObjects(int row, int col, float spaceX, float spaceZ, int direction)
{
if (selectedRenderObjects.Count <= 1)
{
return;
}
onOrderByHierachy?.Invoke(selectedRenderObjects);
List<CustomAssetRenderObject> objectsToAlign = new List<CustomAssetRenderObject>(selectedRenderObjects);
List<Vector3> originalPos = objectsToAlign.Select(x => x.transform.position).ToList();
direction = ((direction % 8) + 8) % 8;
int primary = direction % 2 == 0 ? col : row;
Vector3 primaryDir = Vector3.zero;
Vector3 secondaryDir = Vector3.zero;
primaryDir = (direction == 0 || direction == 2) ? Vector3.right * spaceX :
(direction == 4 || direction == 6) ? -Vector3.right * spaceX :
(direction == 1 || direction == 7) ? Vector3.forward * spaceZ : -Vector3.forward * spaceZ;
secondaryDir = (direction == 1 || direction == 3) ? Vector3.right * spaceX :
(direction == 5 || direction == 7) ? -Vector3.right * spaceX :
(direction == 0 || direction == 6) ? Vector3.forward * spaceZ : -Vector3.forward * spaceZ;
ActionCommand command = new ActionCommand(
() =>
{
int countP = 0;
int countS = 0;
Vector3 referencePoint = objectsToAlign[0].transform.position;
for (int i = 1; i < objectsToAlign.Count; i++)
{
CustomAssetRenderObject renderObject = objectsToAlign[i];
if (countP < primary - 1 || primary == 0)
{
countP++;
}
else
{
countS++;
countP = 0;
}
renderObject.transform.position = referencePoint + primaryDir * countP + secondaryDir * countS;
renderObject.onTransformChanged?.Invoke();
}
onTransformChanged?.Invoke(objectsToAlign.Select(renderObject => renderObject.gameObject).ToList());
},
() =>
{
for (int i = 1; i < objectsToAlign.Count; i++)
{
CustomAssetRenderObject renderObject = objectsToAlign[i];
renderObject.transform.position = originalPos[i];
renderObject.onTransformChanged?.Invoke();
}
onTransformChanged?.Invoke(objectsToAlign.Select(renderObject => renderObject.gameObject).ToList());
});
CommandManager.I.ExecuteCommand(command);
}
public void VertexSnap()
{
if (selectedRenderObjects.Count != 1)
{
return;
}
//가장 가까운 오브젝트에 붙여준다.
//Transform.position이 가깝다고 오브젝트 사이 기리가 꼭 가장 가까운 것은 아니기 때문에
//오버랩 박스에 충돌되는 모든 오브젝트의 버텍스로 KDTree를 구성해 볼까 하다가 관두었다.
float minDist = float.MaxValue;
CustomAssetRenderObject fromObject = selectedRenderObjects[0];
CustomAssetRenderObject toObject = null;
Bounds fromBounds = fromObject.GetBounds();
Collider[] colliders = Physics.OverlapBox(fromBounds.center, fromBounds.size * 2.0f);
foreach (Collider collider in colliders)
{
if (collider.gameObject == fromObject.gameObject) continue;
CustomAssetRenderObject otherObject = collider.gameObject.GetComponent<CustomAssetRenderObject>();
if (otherObject != null)
{
float dist = Vector3.Magnitude(fromObject.transform.position - otherObject.transform.position);
if (dist < minDist)
{
minDist = dist;
toObject = otherObject;
}
}
}
if (toObject != null)
{
//선택된 오브젝트의 ConvexHull 버텍스 점을 계산해준다.
//이는 iteration하는 버텍스 수를 줄이기 위해 해주며,
//만약 선택된 오브젝트도 TDTree로 Nearest 포인트 탐지할 경우 toObject의 센터에 가장 가까운 포인트만 탐지되기 때문에 가장 가까운 버텍스 점이 골라지지 않는 경우가 있다.
List<Vector3> verts = new List<Vector3>();
List<int> tris = new List<int>();
List<Vector3> normals = new List<Vector3>();
convexHullCalculator.GenerateHull(fromObject.GetRenderObjectVertices(), false, ref verts, ref tris, ref normals);
KDTree toTree = toObject.GetVertexPointKDTree();
Vector3 nearestPoint = Vector3.zero;
Vector3 snapPoint = Vector3.zero;
float minDistance = float.MaxValue;
for (int i = 0; i < verts.Count; i++)
{
Vector3 ptNearest = verts[i];
Vector3 ptSnap = toTree.FindNearest(ptNearest);
float tempDist = Vector3.Magnitude(ptSnap - ptNearest);
if (tempDist < minDistance)
{
minDistance = tempDist;
nearestPoint = ptNearest;
snapPoint = ptSnap;
}
}
//Vector3 direction = snapPoint - nearestPoint;
//fromObject.transform.position += direction;
List<Vector3> dir = new List<Vector3>() { snapPoint - nearestPoint };
if (dir[0].magnitude < 0.5f)
{
fromObject.transform.position += dir[0];
}
else
{
ActionCommand command = new ActionCommand(
() =>
{
fromObject.transform.position += dir[0];
},
() =>
{
fromObject.transform.position -= dir[0];
});
CommandManager.I.ExecuteCommand(command);
}
}
}
InputHandler myHandler;
public void CurrentStatusEvent()
{
FindSingle<UserInputManager>().SetHandler(myHandler);
}
public void PrevStatusEvent()
{
FindSingle<UserInputManager>().RemoveHandler(myHandler);
}
public InputHandler GetInputHandler()
{
var getKeyActions = new Dictionary<KeyCode, Action>();
var downKeyActions = new Dictionary<KeyCode, Action>();
var upKeyActions = new Dictionary<KeyCode, Action>();
downKeyActions.Add(KeyCode.Mouse0, OnMousePointerDown);
upKeyActions.Add(KeyCode.Mouse0, OnMousePointerUp);
getKeyActions.Add(KeyCode.Q, SetRTGMove);
getKeyActions.Add(KeyCode.W, SetRTGRotate);
getKeyActions.Add(KeyCode.E, SetRTGScale);
getKeyActions.Add(KeyCode.R, ResetRTG);
getKeyActions.Add(KeyCode.V, VertexSnap);
var shortcutTable = new Dictionary<KeyCode, Dictionary<KeyCode, Action>>();
#if UNITY_EDITOR
shortcutTable.Add(KeyCode.LeftShift, new Dictionary<KeyCode, Action>());
shortcutTable[KeyCode.LeftShift].Add(KeyCode.C, SaveItemsToCopy);
shortcutTable[KeyCode.LeftShift].Add(KeyCode.V, CopySavedItems);
#else
shortcutTable.Add(KeyCode.LeftControl, new Dictionary<KeyCode, Action>());
shortcutTable[KeyCode.LeftControl].Add(KeyCode.C, SaveItemsToCopy);
shortcutTable[KeyCode.LeftControl].Add(KeyCode.V, CopySavedItems);
#endif
var handler = new InputHandler(getKeyActions, downKeyActions, upKeyActions, shortcutTable);
return handler;
}
}
}