Невозможно ограничить угол наклона камеры между [-90º, 90º] в OpenGL, используя векторы! - PullRequest
2 голосов
/ 22 марта 2011

У меня большая проблема с ограничением угла наклона камеры (от -90º до 90º) с помощью приведенного ниже кода. Это в некоторой степени продолжение этого вопроса .

Проблема, похоже, заключается в том, что камера поворачивается более чем на -90º или более + 90º, и когда это произойдет, я буду смотреть вниз (или вверх), но вид просто поворачивается на 180º вокруг оси Y.

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

void Camera::Rotate(Vector3D angle) {
    angle = angle * CAMERA_ROTATION_SPEED;

    accumPitchAngle += angle.x;

    if(accumPitchAngle > 90.0f) {
        angle.x = 90.0f - (accumPitchAngle - angle.x);
        accumPitchAngle = 90.0f;
    }

    if(accumPitchAngle < -90.0f) {
        angle.x = -90.0f - (accumPitchAngle - angle.x);
        accumPitchAngle = -90.0f;
    }

    // Rotate along the WORLD_SKY_VECTOR axis (yaw/heading rotation)
    // WORLD_SKY_VECTOR = (0.0f, 1.0f, 0.0f)
    if(angle.y != 0.0f) {
        Reference = RotateArbitraryAxis(Reference, WORLD_SKY_VECTOR, angle.y);
        RightVector = Vector3D::CrossProduct(Reference, WORLD_SKY_VECTOR);
        UpVector = Vector3D::CrossProduct(RightVector, Reference);
    }

    // Rotate along the x axis (pitch rotation)
    if(angle.x != 0.0f) {
        Reference = RotateArbitraryAxis(Reference, RightVector, angle.x);
        UpVector = Vector3D::CrossProduct(RightVector, Reference);
    }

    // Makes sure all vectors are perpendicular all the time
    Reference.Normalize();
    RightVector = Vector3D::CrossProduct(Reference, UpVector);
    RightVector.Normalize();
    UpVector = Vector3D::CrossProduct(RightVector, Reference);
    UpVector.Normalize();
}

Vector3D Camera::RotateArbitraryAxis(const Vector3D v, Vector3D u, double angle) {
    Vector3D result;

    u.Normalize();

    double scalar = Vector3D::DotProduct(v, u);
    double c = cos(Math::DegreesToRadians(angle));
    double s = sin(Math::DegreesToRadians(angle));
    double a = 1.0f - c;

    result.x = u.x * scalar * a + (v.x * c) + ((-u.z * v.y) + (u.y * v.z)) * s;
    result.y = u.y * scalar * a + (v.y * c) + (( u.z * v.x) - (u.x * v.z)) * s;
    result.z = u.z * scalar * a + (v.z * c) + ((-u.y * v.x) + (u.x * v.y)) * s;

    return result;
}

Возможно, проблема в выражении if(angle.y != 0.0f), если я прокомментирую этот блок кода, проблема вообще не будет существовать. Это как-то связано с WORLD_SKY_VECTOR, но этот код так, чтобы позволить мне вращать курс и держать камеру вровень. Если бы я использовал UpVector, проблема решена. Но это хорошо только для симулятора полета, мне нужно держать горизонт в горизонтальном положении, вот причина WORLD_SKY_VECTOR. Но кажется, что это причина «бокового переключения», когда я указываю камеру прямо вниз.

Согласно запросу в комментарии ниже ... Это для камеры от первого лица (и от третьего лица, но я еще не начал реализовывать эту часть), и когда я смотрю прямо вниз, -90º (или прямо вверх, + 90º) и когда угол изменяется от -89º до -91º (или от + 89º до + 91º), я хочу, чтобы камера это предотвращала и не выходила за пределы -90º, + 90º. Когда он достигнет этого предела, мне нужно, чтобы камера смогла вернуться (либо вверх, если я на -90º, либо вниз, если я на + 90º). Прямо сейчас это работает только иногда, в других случаях я буду смотреть в другую сторону вместо того, как я первоначально смотрел.

Ответы [ 3 ]

2 голосов
/ 22 марта 2011

Проблема 1: код, развернутый "// Обеспечивает, чтобы все векторы были перпендикулярны все время", гарантирует, что вектор вашей камеры UP действительно работает (относительно WORLD_SKY_VECTOR). Итак:

  • Когда вы смотрите полностью вниз, «вверх» не значит много. В вашем случае камера должна быть направлена ​​на север, а не на небо
  • Когда вы продолжаете поворачивать, после этой точки камера поворачивается, чтобы держать свое небо наверху.

Посмотри на свою грудь. Верх вашей головы на самом деле вниз. Если вы хотите посмотреть на то же самое, с той же точки зрения, но с поднятой головой, вам придется повернуть его (к сожалению, это невозможно с человеческой точки зрения)

Проблема 2: То же, что и выше. CrossProduct (Ссылка, WORLD_SKY_VECTOR) дает крест (вверх, вниз), который ничего не значит, просто попробуйте на бумаге.

Если вы хотите выглядеть «вверх ногами»:

  • Вычислите вектор направления вашего взгляда. Вы точно знаете это, потому что у вас есть ориентация и ваш шаг.
  • Рассчитайте ваш правильный вектор. Это то же самое, но с ориентацией более 90 ° и шагом 0 (камера всегда в горизонтальном положении, т.е. вы не наклоняете голову)
  • Вычислить ваш вектор вверх как крест (справа, спереди).

Итак:

yaw += whatever;
pitch += whatever;
FrontVector = SphericalToCartesian(yaw, pitch);
RightVector = SphericalToCartesian(yaw + PI/2.0f, 0.0);
UpVector = cross(RightVector, FrontVector);

с SphericalToCartesian beeing что-то вроде if (pitch = 0, yaw = 0) означает смотреть на юг:

x = cos(pitch) * sin(yaw)
y = sin(pitch)
z = cos(pitch) * cos(yaw)
1 голос
/ 23 марта 2011

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

Моя мысль, лежащая в основе этого решения, заключается в том, что когда я смотрю прямо вниз (или вверх) и хочу повернуть влево или вправо, это на самом деле вращение roll вокруг вектора вперед.Почему бы не выполнить движение крена, когда угол наклона равен -90º или 90º вместо этого?

Собрав это вместе, я решил проблему, просто заменив поворот рыскания / курса на следующий код:

if(angle.y != 0.0f) {
    if(abs(accumPitchAngle) == 90.0f) {
        RightVector = RotateArbitraryAxis(RightVector, Reference, -angle.y);
        UpVector = Vector3D::CrossProduct(RightVector, Reference);
    } else {
        Reference = RotateArbitraryAxis(Reference, WORLD_SKY_VECTOR, angle.y);
        RightVector = Vector3D::CrossProduct(Reference, WORLD_SKY_VECTOR);
        UpVector = Vector3D::CrossProduct(RightVector, Reference);
    }
}

Это кажется довольно простым и понятным рабочим решением, я, вероятно, отмечу свой ответ как принятый, если только кто-то не укажет на какую-либо серьезную проблему с этой реализацией, которую я, возможно, не рассматриваю.Я подожду несколько часов, прежде чем сделать это, чтобы предыдущие ответы пришли к какому-то заключению.

1 голос
/ 22 марта 2011

Нельзя взять перекрестное произведение двух параллельных векторов.Я думаю, что именно там он и терпит неудачу, то есть когда параметр subsPitchAngle равен ± 90 °.

Возможно, вы захотите ограничить его значением -89,999 ° ~ + 89,999 °.

Редактировать: чтобы начать с начала,Вы хотите преобразовать pitch & yaw в вектор вперед и вектор вверх для gluLookAt(), верно?Затем я предлагаю:

1) Используйте yaw (и только yaw), чтобы создать вектор right, параллельный земле.
2) Перекрестные right и WORLD_SKY_VECTORчтобы получить вектор forward, который является правильным для рыскания, но не для шага.
3) Поверните forward вокруг right на pitch градусов, чтобы получить прямой вектор, который будет правильным как для высоты, так и для рыскания.(Я думаю, у вас это далеко.)
4) Пересеките forward и right, чтобы получить вектор up камеры, который будет работать во всех случаях.

...