오브젝트 스냅 기능 #200

Merged
jym merged 4 commits from pgd/20250708 into main 2025-07-15 13:42:03 +09:00
4 changed files with 26 additions and 103 deletions
Showing only changes of commit 244727391f - Show all commits

View File

@@ -37,7 +37,7 @@ namespace Studio.RuntimeGizmo
objectMoveGizmo.Gizmo.SetEnabled(false);
objectMoveGizmo.Gizmo.MoveGizmo.SetSnapEnabled(false);
objectMoveGizmo.Gizmo.MoveGizmo.SetVertexSnapEnabled(false);
objectMoveGizmo.Gizmo.MoveGizmo.SetVertexSnapEnabled(true);
objectRotationGizmo.Gizmo.SetEnabled(false);
objectScaleGizmo.Gizmo.SetEnabled(false);
objectUniversalGizmo.Gizmo.SetEnabled(false);
@@ -109,6 +109,7 @@ namespace Studio.RuntimeGizmo
selectedObjects.Clear();
selectedObjects.AddRange(objects);
objectMoveGizmo.SetTargetObjects(selectedObjects);
objectMoveGizmo.Gizmo.MoveGizmo.SetVertexSnapTargetObjects(selectedObjects);
objectRotationGizmo.SetTargetObjects(selectedObjects);
objectScaleGizmo.SetTargetObjects(selectedObjects);
objectUniversalGizmo.SetTargetObjects(selectedObjects);

View File

@@ -106,14 +106,13 @@ namespace RTG
bool foundPoint = false;
foreach (var srcObject in gameObjects)
{
Mesh mesh = srcObject.GetMesh();
if (mesh != null)
foreach (var (mesh, transform) in srcObject.GetAllMeshWithTransform())
{
MeshVertexChunkCollection meshVChunkCollection = MeshVertexChunkCollectionDb.Get[mesh];
if (meshVChunkCollection == null)
continue;
Matrix4x4 worldMtx = srcObject.transform.localToWorldMatrix;
Matrix4x4 worldMtx = transform.localToWorldMatrix;
List<MeshVertexChunk> testChunks = meshVChunkCollection.GetWorldChunksHoveredByPoint(inputDeviceScreenPt, worldMtx, focusCamera);
if (testChunks.Count == 0)
{
@@ -126,7 +125,9 @@ namespace RTG
{
Vector3 worldVert = chunk.GetWorldVertClosestToScreenPt(inputDeviceScreenPt, worldMtx, focusCamera);
Vector2 screenVert = focusCamera.WorldToScreenPoint(worldVert);
float distSqr = (inputDeviceScreenPt - screenVert).sqrMagnitude;
Vector2 screenVert2D = new Vector2(screenVert.x, screenVert.y);
float distSqr = (inputDeviceScreenPt - screenVert2D).sqrMagnitude;
if (distSqr < minDistSqr)
{
minDistSqr = distSqr;
@@ -135,27 +136,6 @@ namespace RTG
}
}
}
else
{
OBB spriteWorldOBB = ObjectBounds.CalcSpriteWorldOBB(srcObject);
if (spriteWorldOBB.IsValid)
{
List<Vector3> obbPoints = spriteWorldOBB.GetCenterAndCornerPoints();
List<Vector2> screenPoints = focusCamera.ConvertWorldToScreenPoints(obbPoints);
int closestPtIndex = Vector2Ex.GetPointClosestToPoint(screenPoints, inputDeviceScreenPt);
if (closestPtIndex >= 0)
{
Vector2 closestPt = screenPoints[closestPtIndex];
float distSqr = (inputDeviceScreenPt - closestPt).sqrMagnitude;
if (distSqr < minDistSqr)
{
minDistSqr = distSqr;
point = obbPoints[closestPtIndex];
foundPoint = true;
}
}
}
}
}
return foundPoint;

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

@@ -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