我正在为一个虚拟现实游戏创建一个交互系统,我正在努力进行双手交互.我使用Quaternion.LookRotation()方法根据手的位置和旋转来生成抓取对象的旋转.前面的部分很简单:

Vector3 fwd = (primaryHand.position - secondaryHand.position).normalized;

"向上"部分是我遇到的困难.最初,我try 使用双手的平均向上方向:

Vector3 avgUp = Quaternion.Slerp(primaryHand.rotation, secondaryHand.rotation, 0.5f) * Vector3.up;

There is an issue with this approach: the hand's up vector might get aligned with the fwd vector, which causes the object to flip over when it goes over it. Here is a simple illustration of the problem: Small direction changes can cause the object to flip over. The light green arrows represent the up direction of the hands, while the dark green is the calculated direction used as an argument for the LookRotation() method.

显而易见的解决方案似乎是 Select 一个不同的固定向量,而不是"up",一个不容易与fwd向量对齐的向量.在本例中,它可以是与手指对齐的向量.但请记住,手的初始旋转没有限制,因此无论 Select 哪个向量,手都可以旋转,以便向量对齐.即使你动态地 Select 一个最优向量(一个垂直于fwd的向量),它与fwd的对齐度最多也只有90度.

为了解决这个问题,我try 将方向限制在不会导致问题的值上,但这会导致另一个问题(我很难确定哪些值是可以的,哪些应该被丢弃).我觉得我做错了什么,有没有更好的解决办法?

推荐答案

你可以计算手的三角形旋转,并将其应用于对象的"基础向上"(如果我们只考虑手的位置变化,新的向上……当然,它将与对象的轴正交).然后确定每只手旋转的Angular 变化.求出这些Angular 的平均值,然后使用四元数将这些Angular 应用于手轴.AngleAxis从之前的"基准向上".然后你就可以开始四元数了.看旋转).

下面是一个如何使用它的示例,包括VR手部噪声模拟.要查看测试,请在unity中创建一个新场景,并将其连接到相机,它将在播放开始时构建场景.有一个夹点/释放gui将显示在播放视图中.可以在场景视图中调整手的旋转

example of use

    Vector3 leftHandPosCurrent;
    Vector3 rightHandPosCurrent;

    Vector3 worldAxisPrev;

    Quaternion leftHandRotPrev;
    Quaternion leftHandRotCurrent;

    Quaternion rightHandRotPrev;
    Quaternion rightHandRotCurrent;

    bool isGripping;
    bool firstFrameGripping;

    Rigidbody grippedRB;

    Transform leftHand;
    Transform rightHand;

    Quaternion targetRot;

    /* 
     * On subsequent frames of gripping, calculate deltas in positions and
     * rotations, average out the hand's effects, then apply them to the gripped
     * object
     */
    void HandleGrippedRot()
    {
        Vector3 worldAxisCurrent = rightHandPosCurrent - leftHandPosCurrent;

        if (!firstFrameGripping)
        {
            Vector3 prevUp = targetRot * Vector3.up;
            // we haven't moved the transform based on the hands yet, so 
            // find the new up would be ignoring hand rotations
            Vector3 newUp = Quaternion.FromToRotation(worldAxisPrev,
                    worldAxisCurrent) * prevUp;

            float leftHandAngle = GetDegRot(newUp, leftHandRotPrev,
                    leftHandRotCurrent, worldAxisCurrent);
            float rightHandAngle = GetDegRot(newUp, rightHandRotPrev,
                    rightHandRotCurrent, worldAxisCurrent);
            float avgAngle = (leftHandAngle + rightHandAngle) * 0.5f;

            newUp = Quaternion.AngleAxis(avgAngle, worldAxisCurrent) * prevUp;

            targetRot = Quaternion.LookRotation(worldAxisCurrent,
                    newUp);
        }
        else
        {
            firstFrameGripping = false;
        }

        leftHandRotPrev = leftHandRotCurrent;
        rightHandRotPrev = rightHandRotCurrent;

        worldAxisPrev = worldAxisCurrent;
    }

    /*
     * Given the "up" of the object without taking hand rotations into account
     * and the axis, determine how a hand's delta rotation affects that up 
     * around the axis and return the angle of that rotation 
     */
    float GetDegRot(Vector3 baseUp, Quaternion prevHandRot, Quaternion curHandRot,
            Vector3 axis)
    {
        Vector3 adjUp = (curHandRot * Quaternion.Inverse(prevHandRot)) * baseUp;
        adjUp = Vector3.ProjectOnPlane(adjUp, axis);

        return Vector3.SignedAngle(baseUp, adjUp, axis);
    }

    void Update()
    {
        AddVRNoise(leftHand);
        AddVRNoise(rightHand);

        leftHandPosCurrent = leftHand.position;
        rightHandPosCurrent = rightHand.position;

        leftHandRotCurrent = leftHand.rotation;
        rightHandRotCurrent = rightHand.rotation;


        if (isGripping)
        {
            HandleGrippedRot();
        }
    }

    void StartGrip()
    {
        if (isGripping) return;
        isGripping = true;
        firstFrameGripping = true;
        // grippedTransform is set accordingly at some point
    }

    void EndGrip()
    {
        if (!isGripping) return;
        isGripping = false;
    }

    /*
     * example of using targetRot to move rb
     */
    private void FixedUpdate()
    {
        if (!isGripping) return;

        Quaternion delta = targetRot
                * Quaternion.Inverse(grippedRB.transform.rotation);

        delta.ToAngleAxis(out float angle, out Vector3 axis);

        // convert to shortest angle form
        if (angle > 180f)
        {
            axis = -axis; angle = 360f - angle;
        }

        grippedRB.angularVelocity = angle * 0.25f * axis;
    }

    /*
     * just for testing purposes
     */
    void Start()
    {
        leftHand = CreateHand(true);
        leftHand.position = Vector3.left;

        rightHand = CreateHand(false);
        rightHand.position = Vector3.right;

        CreateArrow();
    }

    /*
     * just for testing purposes
     */
    void AddVRNoise(Transform hand)
    {
        Quaternion noise = Random.rotation;
        noise.ToAngleAxis(out float angle, out Vector3 axis);
        angle = 100f * Time.deltaTime;
        noise = Quaternion.AngleAxis(angle, axis);

        Quaternion noisyRot = hand.rotation * noise;
        hand.rotation = noisyRot;
    }

    /*
     * just for testing purposes
     */
    void OnGUI()
    {
        if (GUI.Button(new Rect(0, 0, 100, 50), "Grip"))
        {
            StartGrip();
        }

        if (GUI.Button(new Rect(100, 0, 100, 50), "Release"))
        {
            EndGrip();
        }
    }


    /*
     * just for testing purposes
     */
    Transform CreateHand(bool isLeft)
    {
        string handName = isLeft ? "Left" : "Right";
        GameObject hand = new GameObject($"{handName}hand");
        GameObject palm = GameObject.CreatePrimitive(PrimitiveType.Cube);
        palm.transform.localScale = new Vector3(0.5f, 0.2f, 1f);
        palm.transform.SetParent(hand.transform);
        GameObject thumb = GameObject.CreatePrimitive(PrimitiveType.Cube);
        thumb.transform.localScale = new Vector3(0.2f, 0.1f, 0.1f);
        thumb.transform.SetParent(hand.transform);
        thumb.transform.localPosition = new Vector3(isLeft ? 0.32f : -0.32f,
                0f, -.31f);

        return hand.transform;
    }

    /*
     * just for testing purposes
     */
    void CreateArrow()
    {
        GameObject arrow = new GameObject();
        GameObject body = GameObject.CreatePrimitive(PrimitiveType.Cube);
        body.transform.localScale = new Vector3(1f, 1f, 5f);
        body.transform.SetParent(arrow.transform);
        GameObject head = GameObject.CreatePrimitive(PrimitiveType.Cube);
        head.transform.SetParent(arrow.transform);
        head.transform.localEulerAngles = Vector3.up * 45f;
        head.transform.localPosition = new Vector3(0f, 0f, 2.5f);

        grippedRB = arrow.AddComponent<Rigidbody>();
        grippedRB.useGravity = false;

        arrow.transform.position = 2f * Vector3.up;
    }

Csharp相关问答推荐

安装附加的. exe与Visual Studio

Unity 2D自顶向下弓旋转

Polly v8—使用PredicateBuilder重试特定的状态代码

如何在页面重新加载后保持菜单切换状态

使用两个不同的枚举作为Switch语句中的CASE生成唯一值

C#-从基类更新子类

共享暂存/生产环境中Azure事件中心的建议配置

将类移动到新命名空间后更新RavenDB Raven-Clr-Type

HelperText属性不支持复杂内容(混合C#和标记)

VS代码扩展无法在新版本扩展C#中运行从v2.10.28开始

Xamarin.Forms-如何创建可 Select 的显示alert 或弹出窗口?

如果图表S批注包含使用LINQ的具有特定名称的批注,我如何签入C#

Avalonia MVVM数据模板

如何在C#中用Serilog记录类路径、方法名和行编号

如果使用LINQ的值为空,则C#不使用GroupBy进行聚合

为什么调试版本中的C#JIT汇编代码中的每个方法都有cmp+je

仅返回容器中特定类型的控件

";如何在C#中基于多个属性有效地对自定义对象列表进行排序?

在给定条件下,是否可以将某些变量expose 给Unity判断员?

最小API BindAsync没有';不工作.Net 6