453 lines
18 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|