很久之前某 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 或鼠标

根据需要,自行实现控制点的监听事件即可。

梓喵出没博客(azimiao.com)版权所有,转载请注明链接:https://www.azimiao.com/7680.html
欢迎加入梓喵出没博客交流群:313732000

我来吐槽

*

*

0位绅士参与评论

  1. WeiCN03-07 06:37 回复

    博主真是高产如…