Как рассчитать точку взгляда для перемещения камеры с помощью мыши в OpenGL / GLUT?

Мне будет сложно объяснять это, поэтому, пожалуйста, потерпите меня.

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

#define SENSITIVITY 25.0f

void main(void) {
    (...)
    glutPassiveMotionFunc(processPassiveMotion);    
    glutWarpPointer(WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2);
    glutSetCursor(GLUT_CURSOR_NONE);
    (...)
}

void processPassiveMotion(int x, int y) {
    int centerX = WINDOW_WIDTH / 2;
    int centerY = WINDOW_HEIGHT / 2;

    int deltaX = -1 * (x - centerX);
    int deltaY = -1 * (y - centerY);

    if(deltaX != 0 || deltaY != 0) {
        mainCamera.Rotate(deltaX / SENSITIVITY, deltaY / SENSITIVITY);

        glutWarpPointer(centerX, centerY);
    }
}

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

Эта функция поворота выглядит примерно так:

#define DEG2RAD(a) (a * (M_PI / 180.0f))
#define SINDEG(a)  sin(DEG2RAD(a))
#define COSDEG(a)  cos(DEG2RAD(a))

void Camera::Rotate(GLfloat angleX, GLfloat angleY) {
    Reference = NormalizeVector(
        Reference * COSDEG(angleY) + UpVector * SINDEG(angleY)
    );

    Reference = NormalizeVector(
        Reference * COSDEG(angleX) - RightVector * SINDEG(angleX)
    );

    UpVector = CrossProduct(&Reference, &RightVector) * (-1);
    RightVector = CrossProduct(&Reference, &UpVector);
}

Reference - это направление обзора, точка, на которую смотрит камера. А поскольку это нормализованный вектор, он изменяется от -1,0 до 1,0. Этот вектор или точка позже используется вместе с другим вектором (Position, который является местоположением камеры) для вычисления реальной точки обзора для использования в gluLookAt, например:

void Camera::LookAt(void) {
    Vector3D viewPoint = Position + Reference;

    gluLookAt(
        Position.x, Position.y, Position.z,
        viewPoint.x, viewPoint.y, viewPoint.z,
        UpVector.x, UpVector.y, UpVector.z
    );
}

Все векторные операции выше, такие как +, - и *, конечно, перегружены.

Теперь попробую описать свою проблему ...

Вышеупомянутая функция поворота работает отлично в том смысле, что она правильно выполняет наклон и рыскание с помощью мыши. Однако эти повороты не похожи на те, что в играх-шутерах от первого лица. В этих играх, когда человек смотрит на небо, а затем смотрит влево / вправо, он ожидает, что продолжит смотреть на небо. Представьте, что мы внутри сферы, подобное движение должно «нарисовать» круг в верхней части сферы.

Но это не то, что происходит, потому что это не то, что делает рыскание. Движение по рысканью будет вращаться вокруг произвольной оси, которая, я думаю, является вектором вверх в этой ситуации. Итак, проблема в рысканье, потому что шаг, кажется, работает нормально.

Другими словами, мой код выше не может удерживать горизонт выровненным, и это должно произойти, потому что это происходит в играх, когда вы смотрите на небо, а затем смотрите влево / вправо, горизонт всегда выровнен. То же самое не произойдет с моим кодом, я смотрю вверх, а затем влево / вправо, и горизонт будет искажен.

Я достаточно ясно выразился? Я не уверен, как лучше это объяснить. :( Надеюсь, этого хватит, чтобы кто-нибудь понял.

Я не уверен, как мне решить эту проблему ... Как мне правильно смотреть влево / вправо после того, как я посмотрел вверх / вниз, сохраняя горизонт выровненным?

РЕДАКТИРОВАТЬ:

Мой код функции поворота взят из функций Yaw и Pitch, которые также существуют, поэтому я могу вызывать эти вращения независимо. Для справки я добавлю их ниже вместе с функцией Roll (которую я, вероятно, никогда не буду использовать, но, если она мне понадобится, она есть):

void Camera::Pitch(GLfloat angle) {
    Reference = NormalizeVector(
        Reference * COSDEG(angle) + UpVector * SINDEG(angle)
    );

    UpVector = CrossProduct(&Reference, &RightVector) * (-1);
}

void Camera::Yaw(GLfloat angle) {
    Reference = NormalizeVector(
        Reference * COSDEG(angle) - RightVector * SINDEG(angle)
    );

    RightVector = CrossProduct(&Reference, &UpVector);
}

void Camera::Roll(GLfloat angle) {
    RightVector = NormalizeVector(
        RightVector * COSDEG(angle) - UpVector * SINDEG(angle)
    );

    UpVector = CrossProduct(&Reference, &RightVector) * (-1);
}

person rfgamaral    schedule 13.03.2011    source источник


Ответы (1)


Ваша проблема, похоже, заключается в заявлении:

UpVector = CrossProduct(&Reference, &RightVector) * (-1);

После того, как вы повернули Reference к RightVector в предыдущем операторе, их перекрестное произведение больше не будет приводить к UpVector, дающему горизонтальный горизонт. Попробуйте это руками. Кроме того, Reference и RightVector не разделены на 90 градусов, поэтому UpVector даже не будет единичным вектором. (Наконец, вам действительно стоит просто поменять порядок перекрестного произведения для ясности, а не умножать на (-1).)

Честно говоря, если бы я этим занимался, я бы применил другой подход. Я не вижу логической причины, по которой два поворота должны выполняться в одной функции. Я также избегаю явных синусов и косинусов любой ценой при работе с векторами. Я думаю, что вам действительно нужна функция для вращения вокруг произвольной оси. По крайней мере, это очень полезно. К счастью, мистер Мюррей позаботился обо всех деталях! Если реализовать эту функцию, то становится очень просто. Определите константу SkyVector, которая всегда направлена ​​вверх. Затем в псевдокоде

AxisRotation( Vector vec, Vector axis, float angle ) {
    Vector result;

    // The axis is assumed to be normalized:  
    //    (just make sure you're not modifying the original)
    axis = NormalizeVector( &axis );

    // expanded for clarity:
    float u = axis.x;
    float v = axis.y;
    float w = axis.z;
    float x = vec.x;
    float y = vec.y;
    float z = vec.z;
    float c = cos(angle);
    float s = sin(angle);

    // Apply the formula verbatim from the linked page:
    result.x = u*(u*x + v*y + w*z)*(1.-c) + x*c + (-w*y + v*z)*s;
    result.y = v*(u*x + v*y + w*z)*(1.-c) + y*c + ( w*x - u*z)*s;
    result.z = w*(u*x + v*y + w*z)*(1.-c) + z*c + (-v*x + u*y)*s;

    return result;
}

Yaw(angleX) {
    Reference = AxisRotation( &Reference, &SkyVector, angleX );
    RightVector = NormalizeVector( CrossProduct( &Reference, &SkyVector ) );
    UpVector = CrossProduct( &RightVector, &Reference );
}

Pitch(angleY) {
    Reference = AxisRotation( &Reference, &RightVector, angleY );
    //RightVector doesn't change!
    UpVector = CrossProduct( &RightVector, &Reference );
}

Если вы проделаете эту операцию за операцией, мы надеемся, что это будет иметь какой-то смысл. Наконец, я добавлю, что кватернионы действительно «правильный» способ делать это и избегать gimbal lock, но обычно я делаю почти то же самое, что и вы. Возможно, вам придется время от времени проверять, чтобы ваши векторы оставались красивыми и перпендикулярными. Кватернионы более стабильны.

Изменить: если функция вращения оси избыточна, вы все равно можете реализовать это с помощью простых векторов и матриц вращения. Единственное, что вам нужно будет начать проецировать объекты на горизонтальную плоскость, чтобы вы могли делать два поворота независимо друг от друга, и это все равно потребует некоторых синусов и косинусов. Ваше время, вероятно, лучше потратить на реализацию функции вращения оси!

person Community    schedule 13.03.2011
comment
Пара моментов: 1) Я буду честен и скажу вам, что, хотя я понимаю несколько концепций, связанных со всем этим, я сам не реализовал приведенный выше код (codecolony.de/docs/camera2.htm), что означает, что я не уверен, что смогу реализовать эту функцию самостоятельно. 2) Оба поворота находятся в одной функции только для пояснения, у меня есть 3 отдельные функции (для тангажа, рыскания и крена). Я добавил их к вопросу, как вы думаете, их тоже нужно переделать? - person rfgamaral; 13.03.2011
comment
3) Да, я продолжаю читать о кватернионах, но мне это слишком сложно реализовать, и я действительно не хочу отказываться от всего, что у меня есть, и идти другим путем. По крайней мере, сейчас, в будущем, если этот вопрос станет для меня действительно важным, я займусь этим. 4) Если я точно знаю, что такое Gimbal Lock, я думаю, что приведенный выше код предотвращает это. Возможно, это не «правильный» способ сделать это, но он решает проблему. 4) Что вы имеете в виду, время от времени проверяя, чтобы ваши векторы оставались красивыми и перпендикулярными? Что, где и когда я должен это проверить? - person rfgamaral; 13.03.2011
comment
Ваш код предотвращает это. Когда вы смотрите прямо вверх, вы все равно можете изящно поворачивать по курсу, но горизонт не остается ровным. То или другое. И я согласен. Я реализовал кватернионы один раз, но так и не закончил его отладку. - person ; 13.03.2011
comment
Когда я говорю, что они должны оставаться перпендикулярными, я имею в виду только то, что если вы вращаете векторы независимо с помощью одного и того же преобразования снова и снова, могут закрасться ошибки с плавающей запятой, и они могут отклониться от перпендикулярности. Точка (x, y), точка (y, z) и точка (x, z) должны быть очень близки к нулю. Если нет, вам нужно сделать несколько перекрестных произведений и перпендикулярно их перпендикулярно. Это так же просто, как перезаписать z = cross (x, y), затем y = cross (z, x). - person ; 13.03.2011
comment
Да, это работает, большое вам спасибо. Но у меня все еще есть несколько вопросов / небольших проблем. Это не форум, поэтому сейчас сложно это обсуждать. Если вы не возражаете, я отправлю ссылку на текстовый файл кода со всем соответствующим кодом и комментарий, где у меня есть вопросы, я обещаю, что это всего несколько. Я перечислил свои вопросы, чтобы было легче ответить. Извините за использование заглавных букв, это просто для того, чтобы отделить мои вопросы от кода. Вот ссылка: pastebin.com/UqjA6WU5 (Pastebin отредактировал №1) - person rfgamaral; 13.03.2011
comment
Блин, я еще раз редактировал pastebin, но вы слишком быстро ... Вот новая версия с соответствующими правками, все, что вы уже ответили, было удалено. Хотя я, наверное, уже знаю ответы, вот они: pastebin.com/ZWHKKc8t - person rfgamaral; 13.03.2011
comment
Да, да и да. Прыгает из-за особенности. Вы все равно должны быть осторожны, поскольку «рыскание» не является истинным рысканием, а ограничение, которое камера должна направлять вверх, конфликтует с «креном». Опять же, я могу только посоветовать вам подумать о том, какое поведение вы хотите, и попытаться реализовать его. Нет правильного ответа. Требования к 3D-моделисту отличаются от требований к симулятору полета, от требований к шутеру от первого лица. - person ; 13.03.2011
comment
Да, теперь я это понимаю. Я просто хочу иметь там все возможности на случай, если они мне понадобятся. На данный момент мне вообще не нужен крен, но если бы я это сделал, я бы просто переключился с SkyVector на UpVector, но тогда горизонт не выровнялся бы. Ничего страшного, я понимаю ограничения на выбранный мной тип камеры. Я не могу отблагодарить вас за всю вашу помощь. Большое спасибо. - person rfgamaral; 13.03.2011
comment
И последнее, не связанное с вопросом, что вы подразумеваете под ссылкой на стиль С ++? Есть ли разница только с C? На самом деле это мой первый раз, когда я работаю с C ++, и на очень базовом уровне я не вижу никаких различий в том, как работают структуры, указатели, передача val / ref. Я уже некоторое время кодирую на C, и C ++ кажется почти таким же синтаксисом (игнорируя классы, пространства имен, ATL / MFC и тому подобное). Я упустил что-нибудь важное? - person rfgamaral; 13.03.2011
comment
передача указателя! = передача по ссылке. stackoverflow.com/questions/410593/pass- по ссылке-значение-в-c - person ; 13.03.2011
comment
Вот почему я люблю stackoverflow… Я не думаю, что когда-либо нашел бы это самостоятельно. Я пытался сделать то же самое, спасибо @Ricky! - person mk12; 10.12.2011
comment
@ Mk12 - Я в равной степени шокирован тем, что то, что я сказал, было полезным! - person ; 01.03.2012