Unity物体缩放旋转拖拽控制框(支持VR)

很久之前某 VR 项目需要用手柄控制物体旋转、缩放等功能,并且要求有 MRTK 一样的控制框。简单说说我显示这个框以及计算缩放、旋转的思路。
画边框和控制点
生成的控制框分为两部分,一部分是线条,另一部分是供拖拽使用的控制点。
当物体发生缩放、旋转操作时,线条与控制点实际尺寸不变(即边框的物理大小不变),姿态跟随物体旋转。
1. 获取八个顶点坐标
这是对 Bounds 类添加的扩展实例方法,使用方法为bound.Get8CornerPositions
。
/// <summary>
/// 按顺时针,先上后下的顺序返回角落坐标
/// </summary>
public static void Get8CornerPositions(this Bounds bounds,Transform transform,ref Vector3[] allPoints)
{
if(allPoints == null || allPoints.Length < 8)
{
allPoints = new Vector3[8];
}
Vector3 center = bounds.center;
Vector3 extents = bounds.extents;
float cxx = center.x;
float cyy = center.y;
float czz = center.z;
float xx = extents.x;
float yy = extents.y;
float zz = extents.z;
float ledge = cxx - xx;//左边线条的 X 坐标
float redge = cxx + xx;//右边线条的 X 坐标
float backedge = czz + zz;
float frontedge = czz - zz;
float topedge = cyy + yy;
float bottomedge = cyy - yy;
allPoints[0] = transform.TransformPoint(ledge, topedge, backedge);
allPoints[1] = transform.TransformPoint(redge, topedge, backedge);
allPoints[2] = transform.TransformPoint(redge, topedge, frontedge);
allPoints[3] = transform.TransformPoint(ledge, topedge, frontedge);
allPoints[4] = transform.TransformPoint(ledge, bottomedge, backedge);
allPoints[5] = transform.TransformPoint(redge, bottomedge, backedge);
allPoints[6] = transform.TransformPoint(redge, bottomedge, frontedge);
allPoints[7] = transform.TransformPoint(ledge, bottomedge, frontedge);
}
我们通过 BoxCollider 数据创建 Bound,之后调用该扩展方法即可得到八个顶点坐标:
Bounds tt = new Bounds(m_collider.center, m_collider.size);
tt.Get8CornerPositions(this.transform, ref allCornerPoints);
2.获取旋转控制点坐标
按照点与边的对应关系,两个点坐标相加除 2 即可得到某条边的中心点。
3.获取边坐标
本条边需要两个点,从计算的 8 个顶点中按对应关系取出 12 条边即可。
4.生成控制点与线
在物体下创建一个子物体 ctrlRoot 作为控制框中心,之后在其下创建线或控制点。
线使用 Cube 物体或 GPU 直接绘制 Cube 即可。8 个顶点生成 Cube,带碰撞体,8 个线中心的控制点使用 Sphere,带碰撞体。
控制旋转
旋转原理就是旋转控制点
在指定的旋转平面上“追”手柄的位置,直到手柄在旋转平面上的投影与旋转控制点在一条直线上。
当某个旋转控制点
被拖拽时(通过碰撞体事件监听),假设控制框原点为Pos(o)
,控制点坐标为Pos(k)
,手柄位置为Pos(a)
,坐标均为世界坐标系下坐标。
根据旋转控制点
的Index
,我们可以得到旋转平面的法向量Normal(plane)
,该法向量为本地坐标系下法向量。
// 手柄到控制框中心点的方向向量
Vector3 direct = Pos(a) - Pos(o);
// direct 在旋转平面上的投影
Vector3 worldCtrlNormal = this.transform.TransformDirection(Normal(plane));
Vector3 k = Vector3.ProjectOnPlane(direct,worldCtrlNormal);
// 求在此旋转平面上两个向量的角度(带方向)
float angle = Vector3.SignedAngle(Pos(k) - Pos(o), k,worldCtrlNormal);
//旋转指定角度
if (Mathf.Abs(angle) > 0.01)
{
this.transform.rotation *= Quaternion.Euler(angle * Normal(plane));
}
旋转时,由于我们的 rootCtrl 为子物体,因此无需考虑重置或调整控制框位置。
控制缩放
缩放时,缩放的方向为对角两个点 A B 计算而来的方向向量 P(A.pos – B.pos)。
Vector3 scaleDirect = A.position -B.position;
拖拽 A 点进行缩放,修改物体 Scale,使得在 P(A – B) 方向上,A 点坐标与手柄位置在此方向上的投影重合。
float v2 = Vector3.Dot(Pos(a) - A.position, Vector3.Normalize(scaleDirect));//手柄在该轴上的投影
float scaleNumber =v2 / Vector3.Distance(A.pos,B.pos);//在当前基础上需要再次缩放的倍数
应用缩放:
this.transform.localScale *= scaleNumber;
为了防止控制点大小发生变化,修改 rootCtrl 缩放为反比:
this.rigRoot.transform.localScale /= scaleNumber;
调整缩放后,需要调整控制点与线的位置,按照画边框与控制点内的方法获取新坐标,调整控制点坐标与线缩放即可,不再描述。
兼容 VR 或鼠标
根据需要,自行实现控制点的监听事件即可。
博主真是高产如…
母猪?
请问有源代码嘛,这个不是很懂 😥