오브젝트 스냅 기능 #200

Merged
jym merged 4 commits from pgd/20250708 into main 2025-07-15 13:42:03 +09:00
7 changed files with 74 additions and 118 deletions

View File

@@ -4991,12 +4991,12 @@ MonoBehaviour:
_scaleSensitivity: 1
_moveGizmoSettings3D:
_canBeDisplayed: 1
_isExpanded: 0
_isExpanded: 1
_vertexSnapSettings:
_canBeDisplayed: 1
_isExpanded: 1
_snapDestinationLayers: -1
_canSnapToGrid: 1
_snapDestinationLayers: -4095
_canSnapToGrid: 0
_canSnapToObjectVerts: 1
_sglSliderSettings:
- _lineHoverEps: 0.7
@@ -5663,7 +5663,7 @@ MonoBehaviour:
_staticData:
CanHaveMouseButtons: 0
_enableVertexSnapping:
_isEnabled: 0
_isEnabled: 1
_key: 118
_lCtrl: 0
_lCmd: 0
@@ -9801,7 +9801,7 @@ GameObject:
- component: {fileID: 1833346087}
- component: {fileID: 1833346086}
- component: {fileID: 1833346090}
m_Layer: 0
m_Layer: 6
m_Name: Plane
m_TagString: Untagged
m_Icon: {fileID: 0}
@@ -10136,7 +10136,7 @@ MonoBehaviour:
_settings:
_canBeDisplayed: 1
_isExpanded: 1
_physicsMode: 1
_physicsMode: 2
--- !u!224 &1959729457 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 5526574206638406261, guid: ea83f56b6eed48b4d98d8ee2b8f4b6e9, type: 3}

View File

@@ -4,6 +4,7 @@ using UnityEngine;
using Studio.Command;
using Studio.Core;
using Studio.Manage;
using Studio.AssetTool;
namespace Studio.RuntimeGizmo
{
@@ -114,6 +115,21 @@ namespace Studio.RuntimeGizmo
objectUniversalGizmo.SetTargetObjects(selectedObjects);
workGizmo.Gizmo.SetEnabled(selectedObjects.Count != 0);
List<GameObject> meshObjects = new List<GameObject>();
foreach (GameObject go in selectedObjects)
{
CustomAssetRenderObject assetObj = go.GetComponent<CustomAssetRenderObject>();
if (assetObj == null) continue;
MeshRenderer[] renderer = assetObj.RenderObject.GetComponentsInChildren<MeshRenderer>(true);
foreach (var r in renderer)
{
meshObjects.Add(r.gameObject);
}
}
objectMoveGizmo.Gizmo.MoveGizmo.SetVertexSnapTargetObjects(meshObjects);
}
public void ResetGizmo()
{

View File

@@ -1,6 +1,7 @@
using UnityEngine;
using System.Collections.Generic;
using System;
using System.Linq;
namespace RTG
{
@@ -57,33 +58,37 @@ namespace RTG
_destinationObjects.Clear();
}
List<Vector3> centersAndCorners = new List<Vector3>();
protected override void CalculateDragValues()
{
Camera focusCamera = RTFocusCamera.Get.TargetCamera;
_relativeDragOffset = Vector3.zero;
if (_destinationObjects.Count != 0 && _settings.CanSnapToObjectVerts)
{
Vector3 worldDestPos;
if (GetWorldPointClosestToInputDevice(focusCamera, _destinationObjects, out worldDestPos))
_relativeDragOffset = worldDestPos - _snapPivot;
return;
if (GetWorldPointClosestToInputDevice(focusCamera, _destinationObjects, out worldDestPos)) _relativeDragOffset = worldDestPos - _snapPivot;
}
if (_settings.CanSnapToGrid)
else
if(_settings.CanSnapToGrid)
{
Ray ray = RTInputDevice.Get.Device.GetRay(focusCamera);
XZGridRayHit gridHit = RTScene.Get.RaycastSceneGridIfVisible(ray);
if (gridHit != null)
{
XZGridCell gridCell = RTSceneGrid.Get.CellFromWorldPoint(gridHit.HitPoint);
gridCell.GetCenterAndCorners(ref centersAndCorners);
List<Vector3> centersAndCorners = gridCell.GetCenterAndCorners();
int closestPtIndex = Vector3Ex.GetPointClosestToPoint(centersAndCorners, gridHit.HitPoint);
if (closestPtIndex >= 0)
_relativeDragOffset = centersAndCorners[closestPtIndex] - _snapPivot;
if (closestPtIndex >= 0) _relativeDragOffset = centersAndCorners[closestPtIndex] - _snapPivot;
}
}
else
{
Ray ray = RTInputDevice.Get.Device.GetRay(focusCamera);
Plane dragPlane = new Plane(Vector3.up, _snapPivot);
if (dragPlane.Raycast(ray, out float enter))
{
Vector3 worldPoint = ray.GetPoint(enter);
_relativeDragOffset = worldPoint - _snapPivot;
}
}
@@ -94,11 +99,8 @@ namespace RTG
protected bool GetWorldPointClosestToInputDevice(Camera focusCamera, IEnumerable<GameObject> gameObjects, out Vector3 point)
{
point = Vector3.zero;
if (gameObjects == null)
return false;
if (!RTInputDevice.Get.Device.HasPointer())
return false;
if (gameObjects == null) return false;
if (!RTInputDevice.Get.Device.HasPointer()) return false;
Vector2 inputDeviceScreenPt = RTInputDevice.Get.Device.GetPositionYAxisUp();
float minDistSqr = float.MaxValue;
@@ -110,16 +112,14 @@ namespace RTG
if (mesh != null)
{
MeshVertexChunkCollection meshVChunkCollection = MeshVertexChunkCollectionDb.Get[mesh];
if (meshVChunkCollection == null)
continue;
if (meshVChunkCollection == null) continue;
Matrix4x4 worldMtx = srcObject.transform.localToWorldMatrix;
List<MeshVertexChunk> testChunks = meshVChunkCollection.GetWorldChunksHoveredByPoint(inputDeviceScreenPt, worldMtx, focusCamera);
if (testChunks.Count == 0)
{
MeshVertexChunk closestChunk = meshVChunkCollection.GetWorldVertChunkClosestToScreenPt(inputDeviceScreenPt, worldMtx, focusCamera);
if (closestChunk != null && closestChunk.VertexCount != 0)
testChunks.Add(closestChunk);
if (closestChunk != null && closestChunk.VertexCount != 0) testChunks.Add(closestChunk);
}
foreach (var chunk in testChunks)
@@ -172,9 +172,7 @@ namespace RTG
_destinationObjects.Clear();
IInputDevice inputDevice = RTInputDevice.Get.Device;
if (!inputDevice.HasPointer())
return;
if (!inputDevice.HasPointer()) return;
Vector2 inputDevicePos = inputDevice.GetPositionYAxisUp();
var boundsQConfig = new ObjectBounds.QueryConfig();
@@ -183,20 +181,17 @@ namespace RTG
RTFocusCamera.Get.GetVisibleObjects(_visibleObjectBuffer);
List<GameObject> targetObjects = new List<GameObject>(_targetObjects);
_visibleObjectBuffer
.RemoveAll(a =>
targetObjects.Contains(a) ||
!ObjectBounds.CalcScreenRect(a, focusCamera, boundsQConfig).Contains(inputDevicePos) ||
targetObjects.FindAll(b => a.transform.IsChildOf(b.transform)).Count != 0);
_visibleObjectBuffer.RemoveAll(a => targetObjects.Contains(a) ||
!ObjectBounds.CalcScreenRect(a, focusCamera, boundsQConfig).Contains(inputDevicePos) ||
targetObjects.FindAll(b => a.transform.IsChildOf(b.transform)).Count != 0 ||
a.GetComponent<MeshRenderer>()?.enabled == false);
foreach (var visibleObject in _visibleObjectBuffer)
{
if (!CanUseObjectAsSnapDestination(visibleObject))
continue;
if (!CanUseObjectAsSnapDestination(visibleObject)) continue;
GameObjectType objectType = visibleObject.GetGameObjectType();
if (objectType == GameObjectType.Mesh || objectType == GameObjectType.Sprite)
_destinationObjects.Add(visibleObject);
if (objectType == GameObjectType.Mesh || objectType == GameObjectType.Sprite) _destinationObjects.Add(visibleObject);
}
}
}

View File

@@ -215,6 +215,25 @@ namespace RTG
return null;
}
public static List<(Mesh mesh, Transform transform)> GetAllMeshWithTransform(this GameObject root)
{
var result = new List<(Mesh, Transform)>();
foreach (var mf in root.GetComponentsInChildren<MeshFilter>(true))
{
if (mf.sharedMesh != null)
result.Add((mf.sharedMesh, mf.transform));
}
foreach (var smr in root.GetComponentsInChildren<SkinnedMeshRenderer>(true))
{
if (smr.sharedMesh != null)
result.Add((smr.sharedMesh, smr.transform));
}
return result;
}
public static Renderer GetMeshRenderer(this GameObject gameObject)
{
MeshRenderer meshRenderer = gameObject.GetComponent<MeshRenderer>();

View File

@@ -44,9 +44,9 @@ namespace RTG
return new XZGridCell(cellIndexX, cellIndexZ, cellMin, cellMax, parentGrid);
}
public void GetCenterAndCorners(ref List<Vector3> centerAndCorners)
public List<Vector3> GetCenterAndCorners()
{
centerAndCorners.Clear();
var centerAndCorners = new List<Vector3>();
centerAndCorners.Add(Center);
Vector3 minToMax = _max - _min;
@@ -54,6 +54,8 @@ namespace RTG
centerAndCorners.Add(_min + Vector3.forward * minToMax.z);
centerAndCorners.Add(_max);
centerAndCorners.Add(_min + Vector3.right * minToMax.x);
return centerAndCorners;
}
}
}

View File

@@ -52,6 +52,7 @@ namespace Studio.AssetTool
matCubeCollided = Resources.Load<Material>("Materials/Mat_CubeCollided");
boxCollider.gameObject.layer = LayerMask.NameToLayer("TwinObject");
objectRenderer.gameObject.layer = LayerMask.NameToLayer("TwinObject");
twinObjectMask = LayerMask.GetMask("TwinObject");
}
// Start is called once before the first execution of Update after the MonoBehaviour is created

View File

@@ -318,82 +318,6 @@ namespace Studio.AssetTool
}
CanvasManager.instance.GetCanvas<Canvas_Popup>().panel_dynamicobjectinfo.OnTransformChanged(objectsToAlign.Select(renderObject => renderObject.gameObject).ToList());
}
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];
});
CommandInvoker.instance.Invoke(command);
}
}
}
InputHandler myHandler;
public void StatusEnterEvent()
@@ -422,7 +346,6 @@ namespace Studio.AssetTool
downKeyActions.Add(KeyCode.E, () => SetGizmoChangedCommand(new ActivateScaleGizmoCommand()));
downKeyActions.Add(KeyCode.R, () => SetGizmoChangedCommand(new ResetGizmoCommand()));
downKeyActions.Add(KeyCode.Delete, () => CommandInvoker.instance.Invoke(new RemoveSelectObjectCommand()));
getKeyActions.Add(KeyCode.V, VertexSnap);
var shortcutTable = new Dictionary<KeyCode, Dictionary<KeyCode, Action>>();
#if UNITY_EDITOR