带偏移量的旋转

Look rotation with offset

我正在研究一种机制来控制对象围绕 anchorPoint 相对于 3D 中的 controlPoint 的位置和旋转 space。对象可以有任何初始旋转,需要使用旋转偏移,以便在移动 controlPoint 时保持相对对齐。统一场景层次结构中不存在控件和锚点,因为它们存储为 Vector3。然而,该对象利用 unity 的 Transform 并且可能有一个或多个父对象,每个对象都有自己的旋转。

预期的行为与局部旋转 Gizmo 非常相似,您可以在其中单击 Gizmo 的环来设置对象的旋转。

我尝试使用 FromToRotation、LookRotation 作为身份旋转以及从先前旋转状态的增量旋转来实现此功能,但没有成功。

下面是我得到的最接近的,但是如果对象或其父对象在计算偏移量时有 Z 轴旋转,则该对象会按预期旋转,但也包括围绕观察轴的不需要的滚动。

public Vector3 anchorPoint = Vector3.zero;
public Vector3 controlPoint = Vector3.left;

Quaternion rotationOffset = Quaternion.identity;

//Called when the object is "attached" to the anchor to store the difference between it's rotation and the directional vector from the control to the anchor
public void StoreOffset() {
    //The unit vector pointing from the controlPoint to it's anchorPoint
    Vector3 controlFwd = (anchorPoint - controlPoint).normalized;

    //Generate a rotation using the direction vector while respecting the objects local up.
    Quaternion lookRot = Quaternion.LookRotation(controlFwd, transform.up);

    //Subtract the object's rotation from the lookRotation to determine the rotational offset between the two.
            //Invert the offset rotation to save on calculations during update
            rotationOffset = Quaternion.Inverse(Quaternion.Inverse(transform.rotation) * lookRot);
}

//Called any time the control point is moved to update the objects rotation
public void UpdateRotation() {
    //The unit vector pointing from the controlPoint to it's anchorPoint
    Vector3 controlFwd = (anchorPoint - controlPoint).normalized;

    //Generate a rotation using the above direction vector while respecting the objects local up.
    Quaternion lookRot = Quaternion.LookRotation(controlFwd, transform.up);

    //Assign the new look rotation to the object then apply the offset rotation.
    //ToLocalRotation is a Quaternion extension that converts rotations from world to local space
            transform.localRotation = (lookRot * rotationOffset).ToLocalRotation(transform);
}

示例动画

第一个 .gif 显示正确的功能,只要父对象没有旋转。

如果父对象的 Z 轴旋转为 10°,则第二个 .gif 显示错误旋转

所以在 storeoffset 发生之前,你有一个当前旋转 R。你想要计算一个偏移 X,使得从旋转 R 处的局部轴开始,这将产生外观旋转,其中局部向前 = 控制-> 锚点和局部向上≈ 从 R.

也就是说,

R * X = lookrotation(anchor - control, transform.up)

所以,为了求出X,在等式两边乘以左边的逆(R):

inverse(R) * R * X = inverse(R) * lookrotation(anchor-control, transform.up)
                 X = inverse(R) * lookrotation(anchor-control, transform.up)

所以,StoreOffset 的结尾应该是:

rotationOffset = Quaternion.Inverse(transform.rotation) * lookRot;

然后,在 UpdateRotation 中,您有 X 和 lookRot 以及来自 ~close-enough~ 变换的 up,所以通过在右侧将两边乘以 inverse(X) 来求解 R:

R * X              = lookrotation(anchor-control, transform.up)
R * X * inverse(X) = lookrotation(anchor-control, transform.up) * inverse(X)
                 R = lookrotation(anchor-control, transform.up) * inverse(X)

所以,UpdateRotation 的结尾应该是:

transform.rotation = lookRot * Quaternion.inverse(rotationOffset);

或者,由于 UpdateRotation 可能比 StoreOffset 更频繁地被调用,所以在赋值之前进行反转:

rotationOffsetInverse = Quaternion.inverse(Quaternion.Inverse(transform.rotation) 
                                           * lookRot);
// ...
transform.rotation = lookRot * rotationOffsetInverse;

所以,一共:

public Vector3 anchorPoint = Vector3.zero;
public Vector3 controlPoint = Vector3.left;

Quaternion rotationOffsetInverse = Quaternion.identity;

Quaternion GetControlLookRot()
{
    // The unit vector pointing from the controlPoint to it's anchorPoint
    Vector3 controlFwd = (anchorPoint - controlPoint).normalized;

    // Generate a rotation using the direction vector while respecting the 
    // objects local up.
    return Quaternion.LookRotation(controlFwd, transform.up);
}

// Called when the object is "attached" to the anchor to store the difference between 
// its rotation and the directional vector from the control to the anchor
public void StoreOffset()
{
    rotationOffsetInverse = Quaternion.inverse(Quaternion.Inverse(transform.rotation) 
                                        * GetControlLookRot());
}

// Called any time the control point is moved to update the objects rotation
public void UpdateRotation() 
{
    // Assign the new look rotation to the object then apply the offset rotation.
    transform.rotation = GetControlLookRot() * rotationOffsetInverse;
}