【问题标题】:Clamping rotation/quaternion夹紧旋转/四元数
【发布时间】:2020-04-11 01:14:48
【问题描述】:

仍在研究 VR 交互,我希望能够旋转对象,但我遇到了问题。

例如,我想在 VR 中用手打开/关闭笔记本电脑的上部。我正在做的是实现这一点,就是这样放置前锋:

我正在使用 position, forward, up 创建一个平面。然后获取平面上与我的 VR 控制器对应的最近点,然后使用 transform.LookAt。

这工作正常,但我希望能够限制旋转,所以我不能旋转太多(见视频结尾)。

我一直在尝试一切,使用 eulersAngle 和四元数,但我无法做到。

我制作了一些助手(显示 localEulerAngles 的文本,以及对 LookAt 的转换,因此我不必使用 VR 头戴设备,因为它变得非常乏味)

这是一个展示正在发生的事情的视频:https://www.youtube.com/watch?v=UfN97OpYElk

这是我的代码:

using UnityEngine;

public class JVRLookAtRotation : MonoBehaviour, IJVRControllerInteract
{

    [SerializeField] private Transform toRotate;

    [SerializeField] private Vector3 minRotation;
    [SerializeField] private Vector3 maxRotation;

    [Header("Rotation contraints")]
    [SerializeField] private bool lockX;
    [SerializeField] private bool lockY;
    [SerializeField] private bool lockZ;

    private JVRController _jvrController;
    private bool _isGrabbed;
    private Vector3 _targetPosition;
    private Vector3 _tmp;

    public Transform followTransform;

    private void LateUpdate()
    {

        /*
        if (!_isGrabbed) return;

        if (_jvrController.Grip + _jvrController.Trigger < Rules.GrabbingThreshold)
        {
            _isGrabbed = false;
            _jvrController.StopGrabbing();
            _jvrController = null;
            return;
        }

        */

        Vector3 up = toRotate.up;
        Vector3 forward = toRotate.forward;
        Vector3 pos0 = toRotate.position;
        Vector3 pos1 = pos0 + up;
        Vector3 pos2 = pos0 + forward;
        Plane p = new Plane(pos0, pos1, pos2);

        // Using followTransform just to no have to use VR, otherwise it's the controller pos
        _targetPosition = p.ClosestPointOnPlane(followTransform.position);

        toRotate.LookAt(_targetPosition, up);

        /*
        _tmp = toRotate.localEulerAngles;
        _tmp.x = Mathf.Clamp(WrapAngle(_tmp.x), minRotation.x, maxRotation.x);
        _tmp.y = WrapAngle(_tmp.y);
        _tmp.z = WrapAngle(_tmp.z);
        toRotate.localRotation = Quaternion.Euler(_tmp);
        */

    }

    public void JVRControllerInteract(JVRController jvrController)
    {
        if (_isGrabbed) return;
        if (!(jvrController.Grip + jvrController.Trigger > Rules.GrabbingThreshold)) return;

        _jvrController = jvrController;
        _jvrController.SetGrabbedObject(this);
        _isGrabbed = true;
    }

    private static float WrapAngle(float angle)
    {
        angle%=360;
        if(angle >180)
            return angle - 360;

        return angle;
    }

    private static float UnwrapAngle(float angle)
    {
        if(angle >=0)
            return angle;

        angle = -angle%360;

        return 360-angle;
    }
}

【问题讨论】:

  • 是的,我尝试了一些看起来很像两个答案的东西。最大的问题是(你可以在我的视频中看到),当我到达“顶部”时(当前向指向 Vector3.up 时),Y 和 Z 都从 0 变为 180,而 X 以相反的顺序变为它只是他妈的我的夹紧。四元数不是那么容易理解:x
  • 也许我应该只做一个 Rotate() 或其他事情,而不是 LookAt + 试图在之后进行夹紧。但我不知道如何实现这一点,所以我可以按照我的需要去做。 (理想情况下,我可以使用 3 个布尔值“锁定”,这样我就可以选择我可以/不能在哪个轴上旋转,但是嘿,如果我找到一个单轴的解决方案,它仍然会很棒

标签: c# unity3d math quaternions


【解决方案1】:

假设监视器的父变换是笔记本电脑的主体/键盘。父级的局部坐标轴如下所示:

要描述运动范围,您可以定义父级本地的“旋转中心”向量(例如,标记为 C 的灰色向量)和每个紫色向量和灰色向量之间的角度(例如,110 度) )。例如:

[SerializeField] private Vector3 LocalRotationRangeCenter = new Vector3(0f, 0.94f, 0.342f);
[SerializeField] private float RotationRangeExtent = 110f;

然后,你可以取它“想要”去的前向向量,找到RotationRangeCenter的世界方向和那个点之间的符号角度,然后将它钳制到±RotationRangeExtent

Vector3 worldRotationRangeCenter = toRotate.parent.TransformDirection(RotationRangeCenter);

Vector3 targetForward = _targetPosition - toRotate.position;

float targetAngle = Vector3.SignedAngle(worldRotationRangeCenter, targetForward, 
        toRotate.right);

float clampedAngle = Mathf.Clamp(targetAngle, -RotationRangeExtent, RotationRangeExtent);

然后,找到与该角度对应的方向。最后,旋转显示器,使其前部与夹紧的前部对齐,并且其右侧不变。您可以使用叉积来查找显示器的向上情况,然后使用Quaternion.LookRotation 查找相应的旋转:

Vector3 clampedForward = Quaternion.AngleAxis(clampedAngle, toRotate.right)
        * worldRotationRangeCenter;

toRotate.rotation = Quaternion.LookRotation(clampedForward, 
        Vector3.Cross(clampedForward, toRotate.right));

如果有人试图将监视器拖到“边界”之外太远,它将从一个限制传送到另一个限制。如果这不是所需的行为,您可以考虑从SignedAngle(worldRotationRangecenter, targetForward, toRotate.right) 插值到clampedAngle,以便在限制之间移动:

private float angleChangeLimit = 90f; // max angular speed

// ...

Vector3 worldRotationRangeCenter = toRotate.parent.TransformDirection(RotationRangeCenter);

Vector3 targetForward = _targetPosition - toRotate.position;

float targetAngle = Vector3.SignedAngle(worldRotationRangeCenter, targetForward, 
        toRotate.right);

float clampedAngle = Mathf.Clamp(targetAngle, -RotationRangeExtent, RotationRangeExtent);

float currentAngle = Vector3.SignedAngle(worldRotationRangeCenter, toRotate.forward, 
        toRotate.right);

clampedAngle = Mathf.MoveTowards(currentAngle, clampedAngle, 
        angleChangeLimit * Time.deltaTime);

Vector3 clampedForward = Quaternion.AngleAxis(clampedAngle, toRotate.right)
        * worldRotationRangeCenter;

toRotate.rotation = Quaternion.LookRotation(clampedForward, 
        Vector3.Cross(clampedForward, toRotate.right));

【讨论】:

  • 非常感谢您的回答!尝试实施它,它根本不起作用,我将继续在这里调查和报告。只是一个问题:这些来自哪里: private Vector3 LocalRotationRangeCenter = new Vector3(0f, 0.999f, 0.044f);私人浮动 RotationRangeExtent = 110f; ?当你使用 TransformDirection 时,它只是一个错字,应该使用 LocalRotationRangeCenter 吗?
  • 好的,现在可以使用了!仍然不知道 LocalRotationRangeCenter 来自哪里
  • 你必须找到一个适合你的目的。它应该是父变换局部运动范围的中间方向。
  • @jichael 我的示例中的值来自错误。它应该是LocalRotationRangeCenter = new Vector3(0f, 0.94f, 0.342f);,它是 (0, cos(20), sin(20)) 的近似值,因为我希望中途方向位于 +y,+z 象限并且与 y 轴成 20 度角。我已经编辑了答案以反映这种变化。
  • @Jichael 如果它被卡在桌子上,一个好的值可能是Vector3.upRotationRangeExtent = 90f;
【解决方案2】:

@Ruzihm 的回答只需稍作调整即可!老实说,我自己无法做到这一点。

如果有人感兴趣,这里是为 VR 更新的完整代码:

using UnityEngine;

public class JVRLookAtRotation : MonoBehaviour, IJVRControllerInteract
{

    [SerializeField] private Transform toRotate;

    [SerializeField] private Vector3 minRotationDelta;
    [SerializeField] private Vector3 maxRotationDelta;

    private JVRController _jvrController;
    private bool _isGrabbed;
    private Vector3 _targetPosition;

    // No clue where does this come from
    private Vector3 _localRotationRangeCenter = new Vector3(0, 0.999f, 0.044f);

    private void LateUpdate()
    {

        if (!_isGrabbed) return;

        if (_jvrController.Grip + _jvrController.Trigger < Rules.GrabbingThreshold)
        {
            _isGrabbed = false;
            _jvrController.StopGrabbing();
            _jvrController = null;
            return;
        }

        Vector3 up = toRotate.up;
        Vector3 forward = toRotate.forward;
        Vector3 right = toRotate.right;
        Vector3 rotatePosition = toRotate.position;
        Vector3 pos1 = rotatePosition + up;
        Vector3 pos2 = rotatePosition + forward;
        Plane p = new Plane(rotatePosition, pos1, pos2);
        _targetPosition = p.ClosestPointOnPlane(_jvrController.CurrentPositionWorld);

        Vector3 worldRotationRangeCenter = toRotate.parent.TransformDirection(_localRotationRangeCenter);
        Vector3 targetForward = _targetPosition - rotatePosition;

        float targetAngle = Vector3.SignedAngle(worldRotationRangeCenter, targetForward, right);

        float clampedAngle = Mathf.Clamp(targetAngle, minRotationDelta.x, maxRotationDelta.x);

        Vector3 clampedForward = Quaternion.AngleAxis(clampedAngle, right) * worldRotationRangeCenter;

        toRotate.rotation = Quaternion.LookRotation(clampedForward, Vector3.Cross(clampedForward, right));

    }

    public void JVRControllerInteract(JVRController jvrController)
    {
        if (_isGrabbed) return;
        if (!(jvrController.Grip + jvrController.Trigger > Rules.GrabbingThreshold)) return;

        _jvrController = jvrController;
        _jvrController.SetGrabbedObject(this);
        _isGrabbed = true;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-14
    • 1970-01-01
    • 1970-01-01
    • 2013-03-13
    相关资源
    最近更新 更多