有没有办法使用 Quaternion.FromToRotation 来支持使用一个轴?

Is there a way to use Quaternion.FromToRotation to favor using one axis?

我已经为这个问题苦苦挣扎了太多个小时,是时候寻求您的帮助了。

情况

我有一个移动的角色,我带着它四处走动,并与它行走的表面对齐。我通过向下投射到下一个表面并获得与表面对齐所需的旋转来做到这一点

Quaternion newRotation = Quaternion.FromToRotation(transform.up, foundSurface.normal) * transform.rotation;

角色是一个名为 Modelholder 的变换,可以平滑地旋转到新的旋转

modelHolder.rotation = Quaternion.Slerp(modelHolder.rotation, newRotation, Time.deltaTime * modelRotationSmoothing);

modelHolder 里面是模型。我根据鼠标下巴的移动改变model.localRotation.y。

相机只是一个跟随模型持有者的变换,并且有一个名为 Rotator 的子变换,它也根据鼠标移动(这次是下颚和俯仰)旋转。相机是此旋转器变换的子项。

有了这个,我可以走上人行道和天花板,同时正确对齐所有东西,非常整洁。

问题

我制作了一个抓钩,可以将角色移动到抓钩表面。飞行结束时的重新对准以与步行对准相同的方式完成。这一切都很好,除非你从地面到天花板(或反之亦然)。当我向东或向西看时,该模型似乎会做 "barrel roll" 以重新对齐新表面,但当向北或向南看时会做后空翻或前空翻。

ModelHolder运动: https://streamable.com/qf94k

模型运动: https://streamable.com/4xkl4

滚桶很好,但我想以某种方式找到一种旋转方式,这样玩家在着陆后就不会朝相反的方向看(或者从天花板上跳下来同时朝北或朝南看,因为那样的话重新对准重力也会导致翻转)因为翻转会让人迷失方向。

我尝试过的事情:

你需要计算一个newRotation,它尽可能保持向前的方向,同时让局部向上成为表面的法线。

FromToRotation 仅保证您使用它的方式对齐一个轴。相反,您可以使用叉积和 Quaternion.LookRotation 来进行您需要的计算。

Vector3 newPlayerRight = Vector3.Cross(foundSurface.normal, modelHolder.forward);
Vector3 newPlayerForward = Vector3.Cross(newPlayerRight, foundSurface.normal);

Quaternion newRotation = Quaternion.LookRotation(newPlayerForward, foundSurface.normal);

然后,您可以像以前一样进行:

modelHolder.rotation = Quaternion.Slerp(modelHolder.rotation, newRotation, 
        Time.deltaTime * modelRotationSmoothing);

虽然我不赞同使用 Slerp/Lerp 方法,但 t 不能保证达到或超过 1。我建议使用 Quaternion.RotateTowards:

float modelRotationSpeed = 180f;
modelHolder.rotation = Quaternion.RotateTowards(modelHolder.rotation, newRotation, 
        Time.deltaTime * modelRotationSpeed);

为了保持相对于刚刚遍历的边的前倾角,可以尝试不同的方法:

Quaternion newRotation;

// ..

Vector3 previousSurfaceNormal = modelHolder.up;
Vector3 previousForward = modelHolder.forward;
bool flyingOrFallingToNewSurface;

if (flyingOrFallingToNewSurface)
{
    Vector3 newPlayerRight = Vector3.Cross(foundSurface.normal, modelHolder.forward);
    Vector3 newPlayerForward = Vector3.Cross(newPlayerRight, foundSurface.normal);

    newRotation = Quaternion.LookRotation(newPlayerForward, foundSurface.normal);
} else
{
    // This direction lies in both surfaces. 
    Vector3 edgeTraversed = Vector3.Cross(previousSurfaceNormal, foundSurface.normal);

    // Find the angle from edgeTraversed to previousForward
    float ang = Vector3.SignedAngle(edgeTraversed, previousForward, previousSurfaceNormal);

    // Find newForward in new plane that's the same angle
    Vector3 newPlayerForward = Quaternion.AngleAxis(ang,foundSurface.normal) * edgeTraversed;

    newRotation = Quaternion.LookRotation(newPlayerForward, foundSurface.normal);
}

// ...

float modelRotationSpeed = 180f;
modelHolder.rotation = Quaternion.RotateTowards(modelHolder.rotation, newRotation, 
        Time.deltaTime * modelRotationSpeed);