如何使用 LookRotation 根据手部位置旋转对象?

发布于 2025-01-19 10:03:53 字数 1143 浏览 1 评论 0原文

我正在为VR游戏创建一个交互系统,并且正在努力进行两种手相互作用。我正在使用 quaternion.quaternion.lookrotation.lookrotation.lookrotation() 方法是根据手的位置和旋转生成抓取对象的旋转的方法。前部很简单:

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

“向上”部分是我遇到的困难。最初,我尝试使用双手的平均向上方向:

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

这种方法存在问题:手的向量向量可能与fwd向量对齐,这会导致对象在过去时翻转它。这是问题的简单说明: 浅绿色箭头表示手的向上方向,而深绿色是用作lookrotation()方法的参数的计算方向。

显而易见的解决方案似乎是选择另一个固定向量而不是“向上”,该向量与fwd向量不容易对齐。在示例中,它可能是与手指对齐的矢量。但是请记住,初始旋转没有限制,因此无论您选择哪种矢量,始终都会旋转旋转,以使矢量对齐。即使您动态选择最佳矢量(垂直于fwd>),与fwd对齐相比,它仍然充其量仍然是90度。

为了解决这个问题,我尝试将方向限制为不会引起问题的值,但这引起了另一个问题(我很难确定哪些值可以,哪些值应丢弃)。我觉得我在这里做错了什么,对这个问题有更好的解决方案吗?

I'm creating an interaction system for a VR game and I'm struggling with two hand interactions. I'm using the Quaternion.LookRotation() method to generate the rotation of the grabbed object based on the positions and rotations of the hands. The forward part is pretty simple:

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

The "up" part is what I have difficulty with. Initially I tried using the average up direction of both hands:

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.

The obvious solution seems to be to pick a different fixed vector instead of "up", one which won't be so easily aligned with the fwd vector. In the example it could be a vector aligned with the fingers. But keep in mind that there are no restrictions on initial hand rotation so no matter which vector you choose the hands can always happen to be rotated so that the vectors align. And even if you pick the an optimal vector dynamically (one that is perpendicular to fwd), it's still at best 90 degrees from aligning with fwd.

To solve this I tried restricting the direction to the values which don't cause problems but this caused another issues (I had difficulties with determining which values are ok and which should be discarded). I feel like I'm doing something wrong here, is there any better solution to this problem?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

想挽留 2025-01-26 10:03:53

您可以计算手的三角洲旋转并将其应用于对象的“基础”(如果我们只考虑到手的位置的变化,则新的是……当然,这与轴是正交的对象)。然后确定角度变化,从而导致每只手旋转的角度。平均这些角度,然后使用QUATERNION.ANGLEAXIS将这些角度施加到较早的“基础”上。然后,您可以向前及以进行quaternion.lookrotation)。

以下是如何使用此功能的示例,包括VR手噪声模拟。要查看测试,请在Unity中创建一个新场景,然后将其附加到相机上,它将在游戏开始时构建场景。有一个抓地力/发行GUI将出现在Play View中。您可以调整场景中的手旋转

“使用示例”

    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;
    }

You can calculate the delta rotations of the hands and apply it to the "base up" of the object (the new up if we only take into account the change in position of hands...which will of course be orthogonal to the axis of the object). Then determine the change in angle that results in that up being rotated with each hand. Average those angles out, then apply those angles with the hand-hand axis using Quaternion.AngleAxis to the "base up" from earlier. Then you have your forward and up for Quaternion.LookRotation).

Below is an example of how you can use this, including VR hand noise simulation. To see the test, create a new scene in unity and attach this to the camera and it will build the scene on play start. There is a grip/release gui that will appear in Play view. You can adjust the hand rotation in Scene view

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(
quot;{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;
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文