Quaternion lerp с различными скоростями рыскания / тангажа / крена - PullRequest
0 голосов
/ 07 октября 2018

Я хочу выполнить рывок между двумя вращениями с разными скоростями на трех разных осях (рыскание / тангаж / крен) в unity3d, и попытался добиться этого с помощью Quaternion.LookRotation().

Quaternion.LookRotation() принимает направление Векторв качестве первого параметра, поэтому я подумал, что сначала смогу перенаправить направление, а затем посмотреть на него с вектором вверх-вверх.

Это не должно быть проблемой с Vector3.lerp(), но в этом случае мне нужно выполнить рывокнаправление с разными скоростями по двум осям (X и Y) относительно начального направления.

Так, например, у меня есть камера, обращенная к цели, затем цель немного перемещается вверх и вправо, и теперь я хочукамера тоже должна медленно наклоняться вправо, но немного быстрее до позиции цели (сохраняя свою собственную позицию).

Как получить вектор направления с разными скоростями на обеих осях, чтобы использовать его в Quaternion.LookRotation()?

РЕДАКТИРОВАТЬ: Изменено название с "Lerp ​​между Vector3 с разными скоростями по X / Y" * на "Quaternion lerp с разными скоростямиities for yaw / pitch / roll " и изменил вопрос в соответствии с темой.

Ответы [ 3 ]

0 голосов
/ 11 октября 2018

... другой подход:

Теперь я попытался расширить концепцию разложения свинга / поворота на разложение рыскания / тангажа / крена.

Это работаетхорошо (?), если цель не переворачивается на 180 °, и ей по-прежнему требуется некоторый вклад / обратная связь от человека, который действительно знает, как обращаться с кватернионными вращениями.

public Quaternion QuaternionLerpYawPitchRoll(
    Quaternion rot1,
    Quaternion rot2,
    Vector3 lerpSpeed
) {
    if (rot1 != rot2) {
        float lerpSpeedPitch = lerpSpeed.x * Time.deltaTime;
        float lerpSpeedYaw = lerpSpeed.y * Time.deltaTime;
        float lerpSpeedRoll = lerpSpeed.z * Time.deltaTime;
        // Decompose quaternion into yaw/pitch/roll
        Quaternion rotYaw;
        Quaternion rotPitch;
        Quaternion rotRoll;
        DecomposeYawPitchRoll(rot1, rot2, out rotYaw, out rotPitch, out rotRoll);
        // Lerp swing & twist
        rotYaw = Quaternion.Slerp(Quaternion.identity, rotYaw, lerpSpeedYaw);
        rotPitch = Quaternion.Slerp(Quaternion.identity, rotPitch, lerpSpeedPitch);
        rotRoll = Quaternion.Slerp(Quaternion.identity, rotRoll, lerpSpeedRoll);
        // Combine yaw/pitch/roll with current rotation
        return Quaternion.LookRotation(
            rotPitch * rotYaw * rot1 * Vector3.forward,
            rotRoll * rot1 * Vector3.up
        );
    } else {
        return rot1;
    }
}
public static void DecomposeYawPitchRoll(
    Quaternion rot1,
    Quaternion rot2,
    out Quaternion yaw,
    out Quaternion pitch,
    out Quaternion roll
) {
    Vector3 pitchAxis = rot1 * Vector3.right;
    Vector3 rollAxis = rot1 * Vector3.forward;
    Vector3 yawAxis = rot1 * Vector3.up;
    // Get difference between two rotations
    Quaternion diffQ = rot2 * Quaternion.Inverse(rot1);

    Vector3 r = new Vector3(diffQ.x, diffQ.y, diffQ.z); // (rotation axis) * cos(angle / 2)
    float Epsilon = 1.0e-16f;

    // Singularity: rotation by 180 degree
    if (r.sqrMagnitude < Epsilon) {
        Vector3 rotatedPitchAxis = diffQ * pitchAxis;
        Vector3 rotatedYawAxis = Vector3.Cross(pitchAxis, rotatedPitchAxis);
        Vector3 rotatedRollAxis = diffQ * rollAxis;

        if (rotatedYawAxis.sqrMagnitude > Epsilon) {
            float yawAngle = Vector3.Angle(pitchAxis, rotatedPitchAxis);
            yaw = Quaternion.AngleAxis(yawAngle, rotatedYawAxis);
        } else {
            // More singularity: yaw axis parallel to pitch axis
            yaw = Quaternion.identity; // No yaw
        }
        if (rotatedRollAxis.sqrMagnitude > Epsilon) {
            float rollAngle = Vector3.Angle(yawAxis, rotatedYawAxis);
            roll = Quaternion.AngleAxis(rollAngle, rotatedRollAxis);
        } else {
            // More singularity: roll axis parallel to yaw axis
            roll = Quaternion.identity; // No roll
        }

        // Always twist 180 degree on singularity
        pitch = Quaternion.AngleAxis(180.0f, pitchAxis);
    } else {
        // Formula & proof: 
        // http://www.euclideanspace.com/maths/geometry/rotations/for/decomposition/
        pitch = GetProjectedRotation(diffQ, pitchAxis);
        roll = GetProjectedRotation(diffQ, rollAxis);
        yaw = diffQ * Quaternion.Inverse(pitch);
    }
}
public static Quaternion GetProjectedRotation(Quaternion rotation, Vector3 direction) {
    Vector3 r = new Vector3(rotation.x, rotation.y, rotation.z);
    Vector3 proj = Vector3.Project(r, direction);
    rotation = new Quaternion(proj.x, proj.y, proj.z, rotation.w);
    return Normalize(rotation);
}
public static Quaternion Normalize(Quaternion q) {
    float magInv = 1.0f / Magnitude(q);
    return new Quaternion(magInv * q.x, magInv * q.y, magInv * q.z, magInv * q.w);
}
public static float Magnitude(Quaternion q) {
    return Mathf.Sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w);
}
0 голосов
/ 19 октября 2018

Автор CjLib здесь.

Звучит так, будто на самом деле вам не нужна декомпозиция свинг-твист.Я бы сказал, просто получить разложенные рыскание / тангаж / ряд для текущего кватерниона и желаемого кватерниона.Затем обновите значения yaw / pitch / row в зависимости от того, как быстро вы хотите, чтобы они индивидуально отслеживали целевое значение, и сгенерируйте обновленный кватернион из этого набора значений yaw / pitch / row.

Лерпинг с максимумомограничение скорости (которое я называю «поиском») может быть хорошим, но оно не будет выглядеть гладким.Я рекомендую использовать критически демпфированные числовые пружины.А вот бесстыдное размещение из трех частей, которые я написал по этой теме.

0 голосов
/ 09 октября 2018

Благодаря minorlogic и CjLib , я попробовал следующее:

public Quaternion QuaternionLerpOn3Axis(
    Quaternion rot1,
    Quaternion rot2,
    Vector3 lerpSpeed
) {
    if (rot1 != rot2) {
        float lerpSpeedPitch = lerpSpeed.x * Time.deltaTime;
        float lerpSpeedYaw = lerpSpeed.y * Time.deltaTime;
        float lerpSpeedRoll = lerpSpeed.z * Time.deltaTime;
        // Lerp up direction
        Vector3 vecUp = Vector3.Slerp(
            rot1 * Vector3.up,
            rot2 * Vector3.up,
            lerpSpeedRoll
        );
        // Get new rotation with lerped yaw/pitch
        Quaternion rotation = QuaternionUtil.Sterp(
            rot1,
            rot2,
            rot1 * Vector3.right,
            lerpSpeedYaw,
            lerpSpeedPitch,
            QuaternionUtil.SterpMode.Slerp
        );
        // Look at new direction and return rotation
        return Quaternion.LookRotation(
            rotation * rot1 * Vector3.forward,
            vecUp
        );
    } else {
        return rot1;
    }
}

Чтобы попробовать это без загрузки CjLib, вот весь код, включая соответствующие части для декодирования свинга/ twist:

public Quaternion QuaternionLerpOn3Axis(
    Quaternion rot1,
    Quaternion rot2,
    Vector3 lerpSpeed
) {
    if (rot1 != rot2) {
        float lerpSpeedPitch = lerpSpeed.x * Time.deltaTime;
        float lerpSpeedYaw = lerpSpeed.y * Time.deltaTime;
        float lerpSpeedRoll = lerpSpeed.z * Time.deltaTime;
        // Lerp up direction
        Vector3 vecUp = Vector3.Slerp(
            rot1 * Vector3.up,
            rot2 * Vector3.up,
            lerpSpeedRoll
        );
        // Get difference between two rotations
        Quaternion q = rot2 * Quaternion.Inverse(rot1);
        // Decompose quaternion into two axis
        Quaternion rotYaw;
        Quaternion rotPitch;
        DecomposeSwingTwist(
            q,
            rot1 * Vector3.right,
            out rotYaw,
            out rotPitch
        );
        // Lerp yaw & pitch
        rotYaw = Quaternion.Slerp(Quaternion.identity, rotYaw, lerpSpeedYaw);
        rotPitch = Quaternion.Slerp(Quaternion.identity, rotPitch, lerpSpeedPitch);
        // Look at new direction and return rotation
        return Quaternion.LookRotation(
            rotPitch * rotYaw * rot1 * Vector3.forward,
            vecUp
        );
    } else {
        return rot1;
    }
}
public static void DecomposeSwingTwist(
    Quaternion q,
    Vector3 twistAxis,
    out Quaternion swing,
    out Quaternion twist
) {
    Vector3 r = new Vector3(q.x, q.y, q.z); // (rotation axis) * cos(angle / 2)
    float Epsilon = 1.0e-16f;

    // Singularity: rotation by 180 degree
    if (r.sqrMagnitude < Epsilon) {
        Vector3 rotatedTwistAxis = q * twistAxis;
        Vector3 swingAxis = Vector3.Cross(twistAxis, rotatedTwistAxis);

        if (swingAxis.sqrMagnitude > Epsilon) {
            float swingAngle = Vector3.Angle(twistAxis, rotatedTwistAxis);
            swing = Quaternion.AngleAxis(swingAngle, swingAxis);
        } else {
            // More singularity: rotation axis parallel to twist axis
            swing = Quaternion.identity; // no swing
        }

        // Always twist 180 degree on singularity
        twist = Quaternion.AngleAxis(180.0f, twistAxis);
        return;
    }

    // Formula & proof: 
    // http://www.euclideanspace.com/maths/geometry/rotations/for/decomposition/
    Vector3 p = Vector3.Project(r, twistAxis);
    twist = new Quaternion(p.x, p.y, p.z, q.w);
    twist = Normalize(twist);
    swing = q * Quaternion.Inverse(twist);
}
public static Quaternion Normalize(Quaternion q) {
    float magInv = 1.0f / Magnitude(q);
    return new Quaternion(magInv * q.x, magInv * q.y, magInv * q.z, magInv * q.w);
}
public static float Magnitude(Quaternion q) {
    return Mathf.Sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w);
}

К настоящему времени это единственный способ, с помощью которого я мог бы достичь скачка кватерниона (ов) с разными скоростями на трех разных осях с достаточно приемлемым результатом.

Но в моеммнение, что это не настоящее математическое решение, оно не очень хорошо работает, если значения lerp ниже ~ 1.5f (особенно по оси Z / Roll), и у него много накладных расходов.

Есть идеи, какрешить эту головоломку с меньшим / лучшим кодом?

...