Array ( )
Вход:




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

OpenGL: Выбор 3D объекта






Этот урок был написан, чтобы показать способ выбирать мышью 3D обьекты.
Коротко: сначала мы получаем координаты (экранные) курсора мыши в момент клика,
затем проверяем, нет ли в этом регионе проекции на экран какого-либо 3д обьекта.
Эта технология может быть очень полезна для всяческих редакторов уровней и/или
интерфейсов. Оснавная идея - перед отрисовкой потенциально выбираемого обьекта
присваиваем ему ID.

Исходные коды взяты из урока "Загрузка текстур".
Редактируем файл main.cpp:

// Обьявим глобальные ID для планет:
#define SUN 100     // ID обьекта для Солнца
#define EARTH   101     // ID обьекта для Земли
#define PLUTO   102     // ID обьекта для Плутона

// Изменим количество загружаемых текстур до трех:
TextureImage textures[3];

// Добавляем глобальные переменные вращения планет:
float SunRotation   = 90;
float EarthRotation = 90;
float PlutoRotation = 90;


///////////////////////////////////

// В функции Init добавим загрузку трёх текстур:
Texture->LoadTexture(IL_BMP, "Sun.bmp", &textures[0]);
Texture->LoadTexture(IL_BMP, "Earth.bmp", &textures[1]);
Texture->LoadTexture(IL_BMP, "Pluto.bmp", &textures[2]);

///////////////////////////////////

// Изменяем функцию RenderScene(), в которой будет происходить вся отрисовка:

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

    gluLookAt(0, 3, 6,     0, 0, 0,     0, 1, 0);

    glInitNames();  // Эта функция очищает массив имён, так что перед рендером мы не имеем ни одного ID

    GLUquadricObj *pObj = gluNewQuadric();      // Создадим новый quadric-обьект

    gluQuadricTexture(pObj, true);          // Включим использование текст. координат в quadric-обьекте

    // Наложим текстуру на сферу солнца
    glBindTexture(GL_TEXTURE_2D, textures[0].texID);

    // Сейчас мы вызовем функцию glPushName(). Нам нужно передать в неё ID, чтобы позже его проварять.
    // Вот как это работает. Мы вызываем glPushName() и передаём в него ID обьекта. Функция заносит
    // ID в стек имён. Потом мы отрисовываем любые примитивы и вызываем ф-ю glPopName, которая остановит
    // привязку обьектов к данному ID. Наше ID "SUN" мы привяжем к обьекту, отрисованному ниже:

    glPushName(SUN);        // Привязываем все следующие примитивы к ID "SUN"

    // Теперь переходим в новую матрицу, чтобы изменения не затронули остальные обьекты.
    // Сначала перемещаем сферу в начало координат (0,0,0), потом вращаем вокруг оси Y.
    // Это даст эффект вращения. Потом отрисовываем самую большую нашу сферу с наложенной
    // текстурой солнца.

    glPushMatrix();     // Входим в новую матрицу
        glTranslatef(0, 0, 0);          // Перемещаем сферу
        glRotatef(SunRotation, 0, 1.0, 0);  // Вращаем по оси Y
        gluSphere(pObj, 0.5f, 20, 20);      // Рисуем сферу с радиусом 0.5
    glPopMatrix();      // Заканчиваем работу в этой матрице

    // Теперь, закончив рендер солнца, нужно закрыть пространство имени. Для этого
    // вызываем glPopName(). Теперь ничего не будет ассоциировано с этим ID.

    glPopName();    // Заканчиваем работу с ID "SUN"

    // Далее накладываем текстуру земли
    glBindTexture(GL_TEXTURE_2D, textures[1].texID);

    // Еще раз: нам нужно создать ID обьекта для земли, так что передаем в glPushName ID "EARTH".
    // Теперь, после отрисовки, следующая сфера будет ассоциирована с этим ID.

    glPushName(EARTH);  // Привязываем ID "EARTH"

    // Ещё раз, сначала нам нужно войти в новую матрицу, чтобы не затронуть остальные сферы.
    // Вращаем сферу на её текущее значение вращения ПРЕЖДЕ, чем перемещаем её.
    // Таким образом она будет вращатся вокруг солнца. Затем ещё раз вращаем по
    // оси Y для вращения по собственной оси.

    glPushMatrix();         // Входим в новую систему координат
        glRotatef(EarthRotation / 3, 0, 1.0, 0);    // Вращаем сферу вокруг солнца
        glTranslatef(-2, 0, 0);             // Перемещаем влево
        glRotatef(EarthRotation, 0, 1.0, 0);        // Вращаем по оси Y
        gluSphere(pObj, 0.2f, 20, 20);          // Рисуем сферу с радиусом 0.2
    glPopMatrix();          // Выходим из текущей системы координат


    // Закончили присваивание ID "EARTH", скажем об этом OpenGL:
    glPopName();

    // Накладываем текстуру Плутона:
    glBindTexture(GL_TEXTURE_2D, textures[2].texID);

    // Передаём ID "PLUTO":
    glPushName(PLUTO);

    // Так же, как делали с Землей, сначала вращаем Плутон вокруг солнца, затем
    // перемещаем, и наконец вращаем вокруг своей оси.

    // Передадим пустой код glBegin() и glEnd(), т.к. используем quadric-обьекты.
    // Если этого не сделать, на некоторых видеокартах программа работать не будет.
    //glBegin(GL_LINES);
    //glEnd();

    glPushMatrix();     // Новая матрица
        glRotatef(PlutoRotation / 2, 0, 1.0, 0);        // Вращение вокруг солнца
        glTranslatef(3, 0, 0);              // Перемещение
        glRotatef(PlutoRotation, 0, 1.0, 0);        // Вращение вокруг оси
        gluSphere(pObj, 0.1f, 20, 20);          // Рисуем сферу с радиусом 0.1
    glPopMatrix();      // Выходим из матрицы

    // Выходим из пространства имён:
    glPopName();

    SwapBuffers(g_hDC);

    gluDeleteQuadric(pObj);  // Освободим память, занятую Quadric-обьектом

    // И увеличим значения вращения каждой сферы:

    SunRotation   += 0.2f;
    EarthRotation += 0.5f;
    PlutoRotation += 0.6f;
}


//////////////////////////////////////////////////////////////
//
// Теперь нам нужно написать функцию, получающую ID кликнутого обьекта:

int RetrieveObjectID(int x, int y)
{
    int objectsFound = 0;   // Общее количество кликнутых обьектов
    int viewportCoords[4] = {0};    // Массив для хранения экранных координат


    // Переменная для хранения ID обьектов, на которые мы кликнули.
    // Мы делаем массив в 32 элемента, т.к. OpenGL также сохраняет другую
    // информацию, которая нам сейчас не нужна. Для каждого обьекта нужно
    // 4 слота.
    unsigned int selectBuffer[32] = {0};

    // glSelectBuffer регистрирует массив как буфер выбора обьектов. Первый параметр - размер
    // массива. Второй - сам массив для хранения информации.

    glSelectBuffer(32, selectBuffer);   // Регистрируем буфер для хранения выбранных обьектов

    // Эта функция возвращает информацию о многих вещах в OpenGL. Мы передаём GL_VIEWPOR,
    // чтобы получить координаты экрана. Функция сохранит их в переданном вторым параметром массиве
    // в виде top,left,bottom,right.
    glGetIntegerv(GL_VIEWPORT, viewportCoords); // Получаем текущие координаты экрана

    // Теперь выходим из матрицы GL_MODELVIEW и переходим в матрицу GL_PROJECTION.
    // Это даёт возможность использовать X и Y координаты вместо 3D.

    glMatrixMode(GL_PROJECTION);    // Переходим в матрицу проекции

    glPushMatrix();         // Переходим в новые экранные координаты

        // Эта функция делает так, что фреймбуфер не изменяется при рендере в него, вместо этого
        // происходит запись имён (ID) примитивов, которые были бы отрисованы при режиме
        // GL_RENDER. Информация помещается в selectBuffer.

        glRenderMode(GL_SELECT);    // Позволяет рендерить обьекты без изменения фреймбуфера

        glLoadIdentity();       // Сбросим матрицу проекции

        // gluPickMatrix позволяет создавать матрицу проекции около нашего курсора. Проще говоря,
        // рендерится только область, которую мы укажем (вокруг курсора). Если обьект рендерится
        // в этой области, его ID сохраняется (Вот он, смысл всей функции).
        // Первые 2 параметра - X и Y координаты начала, следующие 2 - ширина и высота области
        // отрисовки. Последний параметр - экранные координаты. Заметьте, мы вычитаем 'y' из
        // НИЖНЕЙ экранной координаты. Мы сделали это, чтобы перевернуть Y координаты.
        // В 3д-пространстве нулевые y-координаты начинаются внизу, а в экранных координатах
        // 0 по y находится вверху. Также передаём регион 2 на 2 пиксела для поиска в нём обьекта.
        // Это может быть изменено как вам удобнее.

        gluPickMatrix(x, viewportCoords[3] - y, 2, 2, viewportCoords);

        // Далее просто вызываем нашу нормальную функцию gluPerspective, точно так же, как
        // делали при инициализации.

        gluPerspective(45.0f,(float)g_rRect.right/(float)g_rRect.bottom,0.1f,150.0f);

        glMatrixMode(GL_MODELVIEW); // Возвращаемся в матрицу GL_MODELVIEW

        RenderScene();          // Теперь рендерим выбранную зону для выбора обьекта

        // Если мы вернёмся в нормальный режим рендеринга из режима выбора, glRenderMode
        // возвратит число обьектов, найденных в указанном регионе (в gluPickMatrix()).

        objectsFound = glRenderMode(GL_RENDER); // Вернемся в режим отрисовки и получим число обьектов

        glMatrixMode(GL_PROJECTION);    // Вернемся в привычную матрицу проекции
    glPopMatrix();              // Выходим из матрицы

    glMatrixMode(GL_MODELVIEW);     // Вернемся в матрицу GL_MODELVIEW

    // УФФ! Это было немного сложно. Теперь нам нужно выяснить ID выбранных обьектов.
    // Если они есть - objectsFound должно быть как минимум 1.

    if (objectsFound > 0)
    {
        // Если мы нашли более 1 обьекта, нужно проверить значения глубины всех
        // выбоанных обьектов. Обьект с МЕНЬШИМ значением глубины - ближайший
        // к нам обьект, значит и щелкнули мы на него. В зависимости от того, что
        // мы программируем, нам могут понадобится и ВСЕ выбранные обьекты (если
        // некоторые были за ближайшим), но в этом уроке мы позаботимся только о
        // переднем обьекте. Итак, как нам получить значение глубины? Оно сохранено
        // в буфере выбора (selectionBuffer). Для каждого обьекта в нем 4 значения.
        // Первое - "число имен в массиве имен на момент события, далее минимум и
        // максимум значений глубины для всех вершин, которые были выбраны при прошлом
        // событии, далее по содержимое массива имен, нижнее имя - первое;
        // ("the number of names in the name stack at the time of the event, followed
        // by the minimum and maximum depth values of all vertices that hit since the
        // previous event, then followed by the name stack contents, bottom name first.") - MSDN.
        // Единстве, что нам нужно - минимальное значение глубины (второе значение) и
        // ID обьекта, переданного в glLoadName() (четвертое значение).
        // Итак, [0-3] - данные первого обьекта, [4-7] - второго, и т.д...
        // Будте осторожны, так как если вы отображаете на экране 2Д текст, он будет
        // всегда находится как ближайший обьект. Так что убедитесь, что отключили вывод
        // текста при рендеринге в режиме GL_SELECT. Я для этого использую флаг, передаваемый
        // в RenderScene(). Итак, получим обьект с минимальной глубиной!

        // При старте установим ближайшую глубину как глубину первого обьекта.
        // 1 - это минимальное Z-значение первого обьекта.
        unsigned int lowestDepth = selectBuffer[1];

        // Установим выбранный обьект как первый при старте.
        // 3 - ID первого обьекта, переданный в glLoadName().
        int selectedObject = selectBuffer[3];

        // Проходим через все найденные обьекты, начиная со второго (значения первого
        // мы присвоили изначально).
        for(int i = 1; i < objectsFound; i++)
        {
            // Проверяем, не ниже ли значение глубины текущего обьекта, чем предидущего.
            // Заметьте, мы умножаем i на 4 (4 значения на каждый обьект) и прибавляем 1 для глубины.
            if(selectBuffer[(i * 4) + 1] < lowestDepth)
            {
                // Установим новое низшее значение
                lowestDepth = selectBuffer[(i * 4) + 1];

                // Установим текущий ID обьекта
                selectedObject = selectBuffer[(i * 4) + 3];
            }
        }

        // Вернем выбранный обьект
        return selectedObject;
    }

    // Если не щелкнули ни на 1 обьект, вернём 0
    return 0;
}

////////////////////////////////////////////////////////////
//
// И последнее: добавим новый код в обработку событий:

    // Если нажата левая кнопка мыши, нужно обработать X и Y координаты клика
    // и проверить, не найдется ли по этим координатам обьект. Запомните, LOWORD и
    // HIWORD значения lParam - это x и y координаты мыши. Вызываем RetrieveObjectID(),
    // чтобы получить обьект, на который мы кликнули.

    case WM_LBUTTONDOWN:                    // Если нажата ЛКМ

        int objectID;

        // передаём координаты курсора в функцию, отыскивающую обьект
        objectID = RetrieveObjectID(LOWORD(lParam), HIWORD(lParam));

        // Теперь просто делаем switch по нужным обьектам:

        switch(objectID)        // Проверим objectID
        {
        case SUN:           // Мы щелкнули на Солнце!
            MessageBox(NULL, "Солнце!", "Click", MB_OK);
            break;
        case EARTH:         // Мы щелкнули на Землю!
            MessageBox(NULL, "Земля!", "Click", MB_OK);
            break;
        case PLUTO:         // Мы щелкнули на Плутон!
            MessageBox(NULL, "Плутон!", "Click", MB_OK);
            break;
        }
        break;






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




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

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












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