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

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

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

#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);
}

1 Ответ

4 голосов
/ 13 марта 2011

Ваша проблема в утверждении:

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

Как только вы повернули ссылку на 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 );
}

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

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

...