夹紧 rotation/quaternion
Clamping rotation/quaternion
仍在研究 VR 交互,我希望能够旋转对象,但我遇到了问题。
例如,我想 open/close 在 VR 中用我的手来 open/close 笔记本电脑的上半部分。为了实现这一目标,我正在做的是,我把前锋放在这样的位置:
我正在使用 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;
}
}
假设显示器的父转换是笔记本电脑的 body/keyboard。父级的局部轴如下所示:
为了描述运动范围,您可以定义一个 "center of rotation" 向量(例如,标记为 C 的灰色向量),该向量是父对象的局部变量,以及一个角度(例如,110 度,在每个紫色向量和灰色矢量)。例如:
[SerializeField] private Vector3 LocalRotationRangeCenter = new Vector3(0f, 0.94f, 0.342f);
[SerializeField] private float RotationRangeExtent = 110f;
然后,就可以把它"wants"的正向向量带过去,求出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));
如果有人试图将监视器拖到 "boundaries" 之外太远,它将从一个限制传送到另一个限制。如果这不是您想要的行为,您可以考虑从 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));
@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;
}
}
仍在研究 VR 交互,我希望能够旋转对象,但我遇到了问题。
例如,我想 open/close 在 VR 中用我的手来 open/close 笔记本电脑的上半部分。为了实现这一目标,我正在做的是,我把前锋放在这样的位置:
我正在使用 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;
}
}
假设显示器的父转换是笔记本电脑的 body/keyboard。父级的局部轴如下所示:
为了描述运动范围,您可以定义一个 "center of rotation" 向量(例如,标记为 C 的灰色向量),该向量是父对象的局部变量,以及一个角度(例如,110 度,在每个紫色向量和灰色矢量)。例如:
[SerializeField] private Vector3 LocalRotationRangeCenter = new Vector3(0f, 0.94f, 0.342f);
[SerializeField] private float RotationRangeExtent = 110f;
然后,就可以把它"wants"的正向向量带过去,求出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));
如果有人试图将监视器拖到 "boundaries" 之外太远,它将从一个限制传送到另一个限制。如果这不是您想要的行为,您可以考虑从 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));
@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;
}
}