Array ( )
Вход:




Главная | OpenGL | GLSL | AI | Сеть | Примеры | Библиотека

OpenGL: Коллизия линии и плоскости






Код этого урока взят из урока "Нормали". В уроке мы создадим некий полигон. Этот полигон
и будет плоскостью, которую мы будем проверять на пересечение с линией. Ещё раз, позвольте мне
обьяснить, что такое плоскость. Представьте себе лист бумаги, лежащей на плоской поверхности. Этот
лист бумаги находится на плоскости. Поверхность, на которой лежит бумага - это плоскость.
В 3д-мире плоскость - это горизонтальная невидимая поверхность. А лист бумаги, лежащий
на ней, можно назвать частью плоскости и оперировать его вершинами. В уроке "Нормали" мы научились
находить нормаль плоскости. Нормаль - вектор, перпендикулярный плоскости, что значит, что он
расположен к ней под углом в 90 градусов. Вернитесь к уроку "Нормали" для подробного описания.


Итак, берём код урока "Нормали" и изменяем его. Расширим функционал файлов 3dmath.
3dmath.h:

///////////////////////////////////////////////////////////////////////////////
//
// После имеющихся прототипов функции и перед "#endif" добавим ещё 2 прототипа:
//

// Возвращает расстояние плоскости от начала координат (0,0,0).
// Принимает нормаль к плоскости и ЛЮБУЮ точку, лежащую на этой плоскости:
float PlaneDistance(CVector3 Normal, CVector3 Point);

// Принимает треугольник (плоскость) и линию, и возвращает true при их пересечении.
bool IntersectedPlane(CVector3 vTriangle[], CVector3 vLine[]);
 



Мы добавили две функции в математические файлы: IntersectedPlane() и PlaneDistance().
Это позволит нам проверять, пересекает ли линия плоскость. В следующем уроке мы пойдем
дальше и будем проверять не только пересечение с плоскостью, но и с полигоном.
Самая главная часть этого урока - уравнение плоскости: Ax + By + Cz + D = 0.

Теперь реализуем новые функции в файле 3dmath.cpp:

// Добавляем новые функции:

float PlaneDistance(CVector3 Normal, CVector3 Point)
{
    float distance = 0; // Переменная хранит дистанцию плоскости от начала координат

    // Используем уравнение плоскости для нахождения дистанции (Ax + By + Cz + D = 0).
    // Нам нужно найти D. Больше информации об уравнении плоскости будет ниже (в IntersectedPlane()).
    // Основное: A B C - это значения X Y Z нашей нормали, а x y z - это x y z нашей точки.
    // D - дистанция от начала координат. Итак, нам нужно воспользоваться этим уравнением, чтобы найти D.
    distance = - ((Normal.x * Point.x) + (Normal.y * Point.y) + (Normal.z * Point.z));

    return distance;    // Возвратим дистанцию
}


bool IntersectedPlane(CVector3 vTriangle[], CVector3 vLine[])
{
    float distance1=0, distance2=0; // Дистанция от каждой из 2х точек линии до плоскости

    CVector3 vNormal = Normal(vTriangle);   // Нам нужно получить нормаль плоскости

    // Теперь, имея нормаль, нужно получить дистанцию нашего треугольника от начала координат.
    // Начало координат - это точка (0,0,0), так что нам нужно найти самую короткую дистанцию
    // от плоскости до этой точки. Таким образом мы сможем вычислить пересечения. Очень важно
    // направление плоскости (мы задаем его нормалью), но так же важно, ГДЕ эта плоскость
    // находится в 3д мире.

    // Мы создали функцию, рассчитывающую для нас дистанцию. Всё, что нам нужно - нормаль
    // плоскости, а затем - ЛЮБАЯ точка, расположенная на этой плоскости. Ну, у нас есть
    // 3 точки, т.к. каждая точка треугольника находится на плоскости, так что мы просто передаём
    // одну из них. Не важно какую, мы передадим первую попавшуюся. Мы получим назад некое
    // значение. Это и будет дистанция.

    float originDistance = PlaneDistance(vNormal, vTriangle[0]);


    // Следующий шаг прост, но его тяжело понять с первого раза. Нам нужно получить дистанцию
    // КАЖДОЙ точки от нашей плоскости. Выше мы получили дистанцию плоскости от точки начала
    // координат, теперь нужна дистанция для каждой точки линии. Если дистанция отрицательная,
    // точка находится ЗА плоскостью, если положительная - перед. То есть если линия пересекает
    // плоскость, дистанция будет отрицательной. Понимаете?
    // Но если линия находится с другой стороны плоскости, обе дистанции будут отрицательными.
    // Нам нужно будет проверить и это. Вот для чего нам нужны дистанции обеих точек линии.

    // Теперь нам нужно использовать нечто под названием "формула плоскости" для нахождения
    // дистанции от каждой точки линии. Вот сама формула: (Ax + By + Cz + D = дистанция)
    // Если дистанция == 0, значит точка НА плоскости.
    // A,B,C - координаты X,Y и Z нормали. x,y,z - координаты любой точки плоскости.
    // D - дистанция плоскости от начала координат. Мы только что вычислии это значение
    // и сохранили в "originDistance".
    // Используем формулу плоскости с нашими данными:

    // Получим дистанцию от точки1 до плоскости:
    distance1 = ((vNormal.x * vLine[0].x)  +                // Ax +
                 (vNormal.y * vLine[0].y)  +                // Bx +
                 (vNormal.z * vLine[0].z)) + originDistance;    // Cz + D


    // Мы получили дистанцию от первой точки, теперь получим от второй:
    distance2 = ((vNormal.x * vLine[1].x)  +                // Ax +
                 (vNormal.y * vLine[1].y)  +                // Bx +
                 (vNormal.z * vLine[1].z)) + originDistance;    // Cz + D

    // Ок, теперь у нас есть 2 расстояния до плоскости, от каждой точки линии.
    // Помните, что я говорил о пересечении? Если одна дистанция отрицательна а другая
    // положительна, значит 2 точки находятся по разные стороны плоскости.
    // Итак, всё, что нам надо сделать - умножить дистанци друг на друга, и если результат
    // меньше нуля, есть пересечение. (Надеюсь, вы помните, что минус*плюс = минус? ;))

    if(distance1 * distance2 >= 0)  // Проверим, есть ли пересечение
       return false;        // Вернем fals если нет

    return true;            // и true если да.
}


Это всё, что нужно для нахождения пересечения плоскости и линии. Совсем несложно, да?
Мы пойдем ещё дальше в следующих уроках пересечений, а пока рассмотрим шаги, требующиеся нам
для рассчета:

1) Для начала нам нужен полигон (хотя бы 3 точки) и линия. Далее находим нормаль полигона.

2) Получив нормаль, находим дистанцию плоскости от начала координат. Используем для этого
формулу плоскости (Ax + By + Cz + D = 0). Нам нужно найти D, так что чуть переделываем:
D = -(Ax + By + Cz)

3) Теперь мы можем найти расстояние до обоих точек линии. Используем для этого ту же формулу.

4) Умножив дистанции двух точек друг на друга, получим положительное или отрицательное значение.
Если оно отрицательное - мы нашли пересечение!


Вот и всё!
Теперь испробуем всё это в файле main.cpp:

// В самом начале включаем хидер математических файлов:
#include "3dmath.h"

// Затем добавляем точки для двух фигур: линии и треугольника
CVector3 vTriangle[3] = { {-1,  0,     0},  {1,    0,   0}, {0,   1,    0} }; // Треугольник
CVector3 vLine[2]     = { {0,   0.5f, -0.5f},  {0,   0.5f, 0.5f} }; // Линия


////////////////////////////////////////////////////////////////////////////////////
//
// Вносим изменения в ф-ю WinProc, чтобы иметь возможность двигать плоскость.
// Блок WM_KEYDOWN теперь выглядит так:

    case WM_KEYDOWN:
        switch(wParam)
        {
            case VK_ESCAPE:
                PostQuitMessage(0);
                break;

            case VK_UP:         // Если нажата ВВЕРХ
                vTriangle[0].x += 0.01f;    // Передвигаем левую точку влево
                vTriangle[1].x += 0.01f;    // И правую
                vTriangle[2].x += 0.01f;    // И верхнюю
                break;

            case VK_DOWN:           // Если нажата ВНИЗ
                vTriangle[0].x -= 0.01f;    // Передвигаем левую точку влево
                vTriangle[1].x -= 0.01f;    // И правую
                vTriangle[2].x -= 0.01f;    // И верхнюю
                break;


            case VK_LEFT:
                vTriangle[0].z -= 0.01f;
                vTriangle[1].z -= 0.01f;
                vTriangle[2].z -= 0.01f;
                break;

            case VK_RIGHT:
                vTriangle[0].z += 0.01f;
                vTriangle[1].z += 0.01f;
                vTriangle[2].z += 0.01f;
                break;
        }

        break;

//////////////////////////////////////////////////////////////////////////////
//
// И наконец изменяем функцию RenderScene():

void RenderScene()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    gluLookAt(-2.5f, 0.5, 0.5f,  0, 0.5f, 0,   0, 1, 0);

    // Передаем OpenGL 3 вершины треугольника.

    glBegin (GL_TRIANGLES);

        glColor3ub(255, 0, 0);
        glVertex3f(vTriangle[0].x, vTriangle[0].y, vTriangle[0].z);

        glColor3ub(0, 255, 0);
        glVertex3f(vTriangle[1].x, vTriangle[1].y, vTriangle[1].z);

        glColor3ub(0, 0, 255);
        glVertex3f(vTriangle[2].x, vTriangle[2].y, vTriangle[2].z);
    glEnd();


    // Искользуем только что написанную функцию для проверки пересечения.
    bool bCollided = IntersectedPlane(vTriangle, vLine);


    // Рисуем линию, которая и будет пересекатся с треугольником.
    // Её цвет будет зависеть от факта пересечения.
    glBegin (GL_LINES);

        if(bCollided)
            glColor3ub(0, 255, 0);
        else
            glColor3ub(255, 255, 0);

        glVertex3f(vLine[0].x, vLine[0].y, vLine[0].z); // Нарисуем нормаль треугольника
        glVertex3f(vLine[1].x, vLine[1].y, vLine[1].z);
    glEnd();

    // Вот и все, используйте ВЛЕВО и ВПРАВО чтобы увидеть процесс в действии.

    SwapBuffers(g_hDC);
}






Исходные коды к уроку




Комментарии:

Войдите, чтобы оставить комментарий:












Яндекс.Метрика
 Яндекс цитирования.