Как перераспределить ограничительную рамку по оси после перевода/поворота?

Когда я сначала загружаю свой объект, я вычисляю начальный AABB с точками max и min (x, y, z). Но это в пространстве объектов, и объект перемещается по всему миру и, что более важно, вращается.

Как я могу пересчитать новый AABB каждый раз, когда объект переводится/поворачивается? Это происходит в основном в каждом кадре, будет ли очень интенсивная операция пересчета нового AABB в каждый кадр? Если да, то какова будет альтернатива?

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

Вот мой текущий код после некоторого понимания ответов ниже:

typedef struct sAxisAlignedBoundingBox {
    Vector3D bounds[8];
    Vector3D max, min;
} AxisAlignedBoundingBox;

void drawAxisAlignedBoundingBox(AxisAlignedBoundingBox box) {
    glPushAttrib(GL_LIGHTING_BIT | GL_POLYGON_BIT);

    glEnable(GL_COLOR_MATERIAL);
    glDisable(GL_LIGHTING);

    glColor3f(1.0f, 1.0f, 0.0f);

    glBegin(GL_LINE_LOOP);
        glVertex3f(box.bounds[0].x, box.bounds[0].y, box.bounds[0].z);
        glVertex3f(box.bounds[1].x, box.bounds[1].y, box.bounds[1].z);
        glVertex3f(box.bounds[2].x, box.bounds[2].y, box.bounds[2].z);
        glVertex3f(box.bounds[3].x, box.bounds[3].y, box.bounds[3].z);
    glEnd();

    glBegin(GL_LINE_LOOP);
        glVertex3f(box.bounds[4].x, box.bounds[4].y, box.bounds[4].z);
        glVertex3f(box.bounds[5].x, box.bounds[5].y, box.bounds[5].z);
        glVertex3f(box.bounds[6].x, box.bounds[6].y, box.bounds[6].z);
        glVertex3f(box.bounds[7].x, box.bounds[7].y, box.bounds[7].z);
    glEnd();

    glBegin(GL_LINE_LOOP);
        glVertex3f(box.bounds[0].x, box.bounds[0].y, box.bounds[0].z);
        glVertex3f(box.bounds[5].x, box.bounds[5].y, box.bounds[5].z);
        glVertex3f(box.bounds[6].x, box.bounds[6].y, box.bounds[6].z);
        glVertex3f(box.bounds[1].x, box.bounds[1].y, box.bounds[1].z);
    glEnd();

    glBegin(GL_LINE_LOOP);
        glVertex3f(box.bounds[4].x, box.bounds[4].y, box.bounds[4].z);
        glVertex3f(box.bounds[7].x, box.bounds[7].y, box.bounds[7].z);
        glVertex3f(box.bounds[2].x, box.bounds[2].y, box.bounds[2].z);
        glVertex3f(box.bounds[3].x, box.bounds[3].y, box.bounds[3].z);
    glEnd();

    glPopAttrib();
}

void calculateAxisAlignedBoundingBox(GLMmodel *model, float matrix[16]) {
    AxisAlignedBoundingBox box;
    float dimensions[3];

    // This will give me the absolute dimensions of the object
    glmDimensions(model, dimensions);

    // This calculates the max and min points in object space
    box.max.x = dimensions[0] / 2.0f, box.min.x = -1.0f * box.max.x;
    box.max.y = dimensions[1] / 2.0f, box.min.y = -1.0f * box.max.y;
    box.max.z = dimensions[2] / 2.0f, box.min.z = -1.0f * box.max.z;

    // These calculations are probably the culprit but I don't know what I'm doing wrong
    box.max.x = matrix[0] * box.max.x + matrix[4] * box.max.y + matrix[8] * box.max.z + matrix[12];
    box.max.y = matrix[1] * box.max.x + matrix[5] * box.max.y + matrix[9] * box.max.z + matrix[13];
    box.max.z = matrix[2] * box.max.x + matrix[6] * box.max.y + matrix[10] * box.max.z + matrix[14];
    box.min.x = matrix[0] * box.min.x + matrix[4] * box.min.y + matrix[8] * box.min.z + matrix[12];
    box.min.y = matrix[1] * box.min.x + matrix[5] * box.min.y + matrix[9] * box.min.z + matrix[13];
    box.min.z = matrix[2] * box.min.x + matrix[6] * box.min.y + matrix[10] * box.min.z + matrix[14];

    /* NOTE: If I remove the above calculations and do something like this:

             box.max = box.max + objPlayer.position;
             box.min = box.min + objPlayer.position;

             The bounding box will move correctly when I move the player, the same does not
             happen with the calculations above. It makes sense and it very simple to move
             the box like this. The only problem is when I rotate the player, the box should
             be adapted and increased/decreased in size to properly fit the object as a AABB.
    */

    box.bounds[0] = Vector3D(box.max.x, box.max.y, box.min.z);
    box.bounds[1] = Vector3D(box.min.x, box.max.y, box.min.z);
    box.bounds[2] = Vector3D(box.min.x, box.min.y, box.min.z);
    box.bounds[3] = Vector3D(box.max.x, box.min.y, box.min.z);
    box.bounds[4] = Vector3D(box.max.x, box.min.y, box.max.z);
    box.bounds[5] = Vector3D(box.max.x, box.max.y, box.max.z);
    box.bounds[6] = Vector3D(box.min.x, box.max.y, box.max.z);
    box.bounds[7] = Vector3D(box.min.x, box.min.y, box.max.z);

    // This draw call is for testing porpuses only
    drawAxisAlignedBoundingBox(box);
}

void drawObjectPlayer(void) {
    static float mvMatrix[16];

    if(SceneCamera.GetActiveCameraMode() == CAMERA_MODE_THIRD_PERSON) {
        objPlayer.position = SceneCamera.GetPlayerPosition();
        objPlayer.rotation = SceneCamera.GetRotationAngles();

        objPlayer.position.y += -PLAYER_EYE_HEIGHT + 0.875f;

        /* Only one of the two code blocks below should be active at the same time
           Neither of them is working as expected. The bounding box doesn't is all
           messed up with either code. */

        // Attempt #1
        glPushMatrix();
            glTranslatef(objPlayer.position.x, objPlayer.position.y, objPlayer.position.z);
            glRotatef(objPlayer.rotation.y + 180.0f, 0.0f, 1.0f, 0.0f);
            glCallList(gameDisplayLists.player);
            glGetFloatv(GL_MODELVIEW_MATRIX, mvMatrix);
        glPopMatrix();

        // Attempt #2
        glPushMatrix();
            glLoadIdentity();
            glTranslatef(objPlayer.position.x, objPlayer.position.y, objPlayer.position.z);
            glRotatef(objPlayer.rotation.y + 180.0f, 0.0f, 1.0f, 0.0f);
            glGetFloatv(GL_MODELVIEW_MATRIX, mvMatrix);
        glPopMatrix();

        calculateAxisAlignedBoundingBox(objPlayer.model, mvMatrix);
    }
}

Но это не работает, как должно... Что я делаю неправильно?

Ответы

Ответ 1

Просто пересчитайте AABB преобразованного AABB. Это означает преобразование 8 вершин (8 вершинно-матричных умножений) и 8 вершинно-вершинных сравнений.

Итак, при инициализации вы вычисляете свой AABB в пространстве модели: для каждого x, y, z каждой вершины модели вы проверяете xmin, xmax, ymin, ymax,...

Каждый кадр, вы создаете новую матрицу преобразования. В OpenGL это делается с помощью glLoadIdentity, за которым следует glTransform/Rotate/Scale (если используется старый API). Это модельная матрица, как сказал Ильмилевский.

Вы вычисляете эту матрицу преобразования второй раз (вне Opengl, например, используя glm). Вы также можете получить полученную матрицу OpenGL, используя glGet.

Вы умножаете каждую из восьми вершин AABB на эту матрицу. Используйте glm для умножения матричных векторов. Вы получите преобразованный AABB (в мировом пространстве). Это он скорее всего повернут (не больше оси)

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

для каждого x, y, z каждой вершины нового AABB, вы проверяете xmin, xmax, ymin, ymax,... это дает вам AABB мирового класса, который вы можете использовать в своем алгоритме отсечения.

Это не оптимально (AABB-мудрый), вы получите много пустого пространства, но по производительности, гораздо лучше, чем перепрограммирование AABB всей сетки.


Что касается матрицы преобразования, в drawObjectPlayer:

        gLLoadIdentity();
        glTranslatef(objPlayer.position.x, objPlayer.position.y, objPlayer.position.z);
        glRotatef(objPlayer.rotation.y + 180.0f, 0.0f, 1.0f, 0.0f);
        glGetFloatv(GL_MODELVIEW_MATRIX, mvMatrix);
  // Now you've got your OWN Model Matrix (don't trust the GL_MODELVIEW_MATRIX flag : this is a workaround, and I know what I'm doing ^^ )

        gLLoadIdentity(); // Reset the matrix so that you won't make the transformations twice
        gluLookAt( whatever you wrote here earlier )
        glTranslatef(objPlayer.position.x, objPlayer.position.y, objPlayer.position.z);
        glRotatef(objPlayer.rotation.y + 180.0f, 0.0f, 1.0f, 0.0f);
      // Now OpenGL is happy, he got his MODELVIEW matrix correct ( gluLookAt is the VIEW part; Translate/Rotate is the MODEL part
        glCallList(gameDisplayLists.player); // Transformed correcty

Не могу объяснить дальше, чем это... как сказано в комментариях, вам нужно было сделать это дважды. У вас не было бы этих проблем и уродливых обходных путей в OpenGL 3, кстати, потому что вы несете полную ответственность за свои собственные матрицы. Эквивалент в OpenGL 2:

glm::mat4 ViewMatrix = glm::LookAt(...);
glm::mat4 ModelMatrix = glm::rotate() * glm::translate(...);
// Use ModelMatrix for whatever you want
glm::mat4 ModelViewMatrix = ViewMatrix * ModelMatrix;
glLoadMatrix4fv( &ModelViewMatrix[0][0] ); // In opengl3 you would use an uniform instead

намного чище право

Ответ 2

Чтобы процитировать предыдущий ответ: fooobar.com/questions/368296/...

"К сожалению, если ваш персонаж вращается, вам нужно пересчитать ваш AABB...

Skurmedel


Рекомендация респондента, и моя, заключается в том, чтобы реализовать ориентированные ограничивающие прямоугольники, когда у вас работает AABB, а также отметить, что вы можете сделать абаб частей сетки, чтобы вымыть обнаружение столкновения с большей точностью, чем 1 огромный ящик для каждого объекта.

Ответ 3

Чтобы сделать это, вам нужно перебрать каждую вершину, вычислить ее позицию в мире (умножить на модельное представление) и найти минимальные/максимальные координаты вершин внутри каждого объекта (точно так же, как при его вычислении в первый раз).

Вы можете масштабировать бит AABB так, чтобы вам не приходилось его пересчитывать - достаточно его увеличить с помощью фактора sqrt (2) - ваш повернутый объект всегда всегда вписывается в AABB.

Есть также вопрос, в каком направлении вы вращаетесь? Если всегда в одном, вы можете увеличить AABB только в этом направлении.

При желании вы можете использовать ограничивающие сферы вместо AABB. Тогда вы не заботитесь о ротации, и масштабирование не является проблемой.

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

Ответ 4

Почему бы не использовать свой GPU? Сегодня я применил решение этой проблемы, выполнив пару кадров.

  • Временно поместите камеру над объектом, над ним, указав вниз по объекту.
  • Отправлять только ваш объект, без огней или что-нибудь.
  • Используйте орфографическую проекцию.
  • Затем запустите буфер кадров. Строки и столбцы черных пикселей означает, что модели нет. Ударьте белый пиксель - вы нажмете одну из границ AABB модели.

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

Для снятия экрана см. здесь.