netlib.narod.ru< Назад | Оглавление | Далее >

Выбор объектов

Порты просмотра Direct3D предоставляют поддержку выбора объектов (picking). Выбор объекта — это указание интересующего объекта путем указания его местоположения в порте просмотра. Обычно для указания местоположения объектов используется мышь. Порт просмотра использует местоположение указателя мыши, чтобы определить, какой из объектов был выбран. Выбор объектов полезен в приложениях, которые требуют точного и инитуитивно понятного выделения объектов.

Выбор объектов сильно влияет на быстродействие. Он требует, чтобы Direct3D выполнил сортировку внутренних структур и данных буферизации. Это нетривиальная задача. Фактически, часто в момент выполнения операции выбора объекта наблюдается видимая задержка анимации.

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

Приложение MeshPick

Приложение MeshPick отображает девять сферических сеток и позволяет использовать мышь для их выбора и перетаскивания. Сетки могут быть размещены одна поверх другой, что позволяет проверить точность работы механизма выбора объектов. Внешний вид окна приложения MeshPick показан на рис. 9.2.


Рис. 9.2. Приложение MeshPick

Рис. 9.2. Приложение MeshPick


Приложение MeshPick демонстрирует использование следующих техник:

Мы подробно обсудим каждую из этих техник при изучении кода приложения MeshPick.

Класс MeshPickWin

Функциональность приложения MeshPick предоставляется классом MeshPickWin:

class MeshPickWin : public RMWin
{
public:
    MeshPickWin();
    BOOL CreateScene();
protected:
    //{{AFX_MSG(MeshPickWin)
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
private:
    static void UpdateDrag(LPDIRECT3DRMFRAME frame, void*, D3DVALUE);
    BOOL PickMesh(const CPoint& point);
private:
    static DragData drag;
};

В классе объявлены две открытые функции: конструктор и функция CreateScene(). Конструктор инициализирует единственный член данных класса, а функция CreateScene() создает сцену для приложения.

Кроме того, в классе объявлены два защищенных обработчика сообщений: OnLButtonDown() и OnLButtonUp(). MFC вызывает эти функции чтобы уведомить приложение об изменении состояния левой кнопки мыши. Мы будем использовать функцию OnLButtonDown() для выполнения процедуры выбора объекта и для инициализации операции перетаскивания, позволяющей с помощью мыши перемещать выбранный объект. Функция OnLButtonUp() используется для выключения режима перетаскивания.

Затем размещено объявление двух закрытых функций: UpdateDrag() и PickMesh(). UpdateDrag() — это функция обратного вызова, используемая для опроса текущего состояния приложения. Если включен режим перетаскивания, функция UpdateDrag() изменяет местоположение выбранной в данный момент сетки, основываясть на положении указателя мыши. Функция PickMesh() используется для выполнения самой операции выбора объекта.

В классе объявлен только один член даных — структура DragData. Ее объявление выглядит следующим образом:

struct DragData
{
    LPDIRECT3DRMFRAME frame;
    POINT mousedown;
    D3DVALUE origx,origy;
};

Структура содержит данные, имеющие отношение к выполняемой операции перетаскивания. Член структуры frame указывает на фрейм, к которому присоединена перетаскиваемая сетка. Поле mousedown используется для сохранения координат указателя мыши в момент инициализации операции перетаскивания. Члены данных origx и origy применяются для сохранения координат фрейма в момент начала перетаскивания.

Функция MeshPickWin::CreateScene()

Функция CreateScene() приложения MeshPick привелена в листинге 9.2.

Листинг 9.2. Функция MeshPickWin::CreateScene()

BOOL MeshPickWin::CreateScene()
{
    // ------- КОНСТРУКТОР СЕТОК --------
    D3DRMLOADRESOURCE resinfo;
    resinfo.hModule = NULL;
    resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH);
    resinfo.lpType = "MESH";
    LPDIRECT3DRMMESHBUILDER meshbuilder;
    d3drm->CreateMeshBuilder(&meshbuilder);
    meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE,
            NULL, NULL);
    meshbuilder->SetQuality(D3DRMRENDER_FLAT);

    //------ ДЕВЯТЬ СЕТОК ----------
    for (int x = 0; x < 3; x++)
    {
        for (int y = 0; y < 3; y++)
        {
            LPDIRECT3DRMMESH mesh;
            meshbuilder->CreateMesh(&mesh);
            mesh->SetGroupColorRGB(0,
                    D3DVALUE(x % 2), D3DVALUE(y % 2), D3DVALUE(1));

            LPDIRECT3DRMFRAME meshframe;
            d3drm->CreateFrame(scene, &meshframe);
            meshframe->AddVisual(mesh);
            int xoffset = (rand() % 3) - 1;
            int yoffset = (rand() % 3) - 1;
            meshframe->SetPosition(scene,
                    D3DVALUE((x - 1) * 10 + xoffset),
                    D3DVALUE((y - 1) * 10 + yoffset),
                    D3DVALUE(0));
            meshframe->SetRotation(scene,
                    D3DVALUE(0), D3DVALUE(1), D3DVALUE(0),
                    D3DVALUE(.1));

            meshframe->Release();
            meshframe = 0;
            mesh->Release();
            mesh = 0;
        }
    }

    meshbuilder->Release();
    meshbuilder = 0;

    //------- ФУНКЦИЯ ОБРАТНОГО ВЫЗОВА --------
    scene->AddMoveCallback(UpdateDrag, NULL);

    // -------- НАПРАВЛЕННЫЙ СВЕТ --------
    LPDIRECT3DRMLIGHT dlight;
    d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL,
            D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00),
            &dlight);

    LPDIRECT3DRMFRAME dlightframe;
    d3drm->CreateFrame(scene, &dlightframe);
    dlightframe->AddLight(dlight);
    dlightframe->SetOrientation(scene,
            D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1),
            D3DVALUE(0), D3DVALUE(1), D3DVALUE(0));
    dlight->Release();
    dlight = 0;
    dlightframe->Release();
    dlightframe = 0;

    //------ КАМЕРА ----------
    d3drm->CreateFrame(scene, &camera);
    camera->SetPosition(scene,
            D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50));

    d3drm->CreateViewport(device, camera, 0, 0,
            device->GetWidth(), device->GetHeight(),
            &viewport);

    return TRUE;
}

Функция CreateScene() выполняет следующие пять действий:

  1. Использует интерфейс Direct3DRMMeshBuilder для загрузки сферической сетки.
  2. Создает девять сеток и фреймы для каждой из них.
  3. Устанавливает функцию обратного вызова UpdateDrag().
  4. Создает источник света.
  5. Создает порт просмотра.

На первом этапе осуществляется создание конструктора сеток:

D3DRMLOADRESOURCE resinfo;
resinfo.hModule = NULL
resinfo.lpName = MAKEINTRESOURCE(IDR_SPHEREMESH);
resinfo.lpType = "MESH";
LPDIRECT3DRMMESHBUILDER meshbuilder;
d3drm->CreateMeshBuilder(&meshbuilder);
meshbuilder->Load(&resinfo, NULL, D3DRMLOAD_FROMRESOURCE,
           NULL, NULL);
meshbuilder->SetQuality(D3DRMRENDER_FLAT);

Сетка загружается из ресурсов приложения функцией Load() интерфейса Direct3DRMMeshBuilder. Затем вызывается функция SetQuality(), чтобы изменить используемый по умолчанию для конструктора сеток метод визуализации Гуро на плоский метод визуализации.

На втором этапе для создания девяти сеток используется цикл. В цикле используется инициализированный ранее указатель meshbuilder:

for (int x = 0; x < 3; x++)
{
    for (int y = 0; y < 3; y++)
    {

        LPDIRECT3DRMMESH mesh;
        meshbuilder->CreateMesh(&mesh);
        mesh->SetGroupColorRGB(0,
                D3DVALUE(x % 2), D3DVALUE(y % 2), D3DVALUE(1));
        LPDIRECT3DRMFRAME meshframe;
        d3drm->CreateFrame(scene, &meshframe);
        meshframe->AddVisual(mesh);
        int xoffset = (rand() % 3) - 1;
        int yoffset = (rand() % 3) - 1;
        meshframe->SetPosition(scene,
                D3DVALUE((x - 1) * 10 + xoffset),
                D3DVALUE((y - 1) * 10 + yoffset),
                D3DVALUE(0));
        meshframe->SetRotation(scene,
                D3DVALUE(0), D3DVALUE(1), D3DVALUE(0),
                D3DVALUE(.1));

        meshframe->Release();
        meshframe = 0;
        mesh->Release();
        mesh = 0;
    }
}

meshbuilder->Release();
meshbuilder = 0;

Для создания девяти сеток применяются вложенные циклы. В теле внутреннего цикла для создания сетки используется функция CreateMesh() интерфейса Direct3DRMMeshBuilder. Назначаемый сетке цвет зависит от текущей итерации цикла. Затем создается фрейм и сетка присоединяется к нему с помощью функции AddVisual(). Местоположение сетки зависит от текущей итерации цикла, но слегка изменяется на случайную величину (это сделано для того, чтобы пользователь понял, что сетки можно перемещать). Каждому фрейму назначаются атрибуты вращения, после чего указатели meshframe и mesh освобождаются.

Затем устанавливается функция обратного вызова:

scene->AddMoveCallback(UpdateDrag, NULL);

При установке функции обратного вызова используется фрейм scene (корневой фрейм). Этот фрейм выбран произвольно, можно использовать любой другой фрейм сцены.

На четвертом этапе выполняется создание источников света:

LPDIRECT3DRMLIGHT dlight;
d3drm->CreateLightRGB(D3DRMLIGHT_DIRECTIONAL,
        D3DVALUE(1.00), D3DVALUE(1.00), D3DVALUE(1.00),
        &dlight);
LPDIRECT3DRMFRAME dlightframe;
d3drm->CreateFrame(scene, &dlightframe);
dlightframe->AddLight(dlight);
dlightframe->SetOrientation(scene,
        D3DVALUE(0), D3DVALUE(-1), D3DVALUE(1),
        D3DVALUE(0), D3DVALUE(1), D3DVALUE(0));

dlight->Release();
dlight = 0;
dlightframe->Release();
dlightframe = 0;

Приведенный выше код создает направленный источник света и позиционирует его таким образом, чтобы свет распространялся между положительным направлением оси Z и отрицательным направлением оси Y. Источник света присоединаяется к фрейму с помощью функции AddLight() интерфейса Direct3DRMFrame.

На последнем этапе осуществляется создание порта просмотра:

d3drm->CreateFrame(scene, &camera);
camera->SetPosition(scene,
        D3DVALUE(0), D3DVALUE(0), D3DVALUE(-50));
d3drm->CreateViewport(device, camera, 0, 0,
        device->GetWidth(), device->GetHeight(),
        &viewport);

Указатель на фрейм camera инициализируется функцией CreateFrame() интерфейса Direct3DRM, после чего фрейм позиционируется с помощью функции SetPosition() интерфейса Direct3DRMFrame. Указатель viewport инициализируется функцией CreateViewport() интерфейса Direct3DRM.

Функции класса MeshPickWin для работы с мышью

Приложение MeshPick использует две функции обработки сообщений, чтобы реагировать на изменение состояния кнопок мыши. Функция OnLButtonDown() вызывается MFC каждый раз, когда пользователь нажимает левую кнопку мыши. Код этой функции выглядит так:

void MeshPickWin::OnLButtonDown(UINT nFlags, CPoint point)
{
    if (PickMesh(point))
    {
        ShowCursor(FALSE);
        SetCapture();
    }
    RMWin::OnLButtonDown(nFlags, point);
}

MFC передает в функцию OnLButtonDown() два аргумента. Первый представляет собой набор флагов, указывающих на состояние некоторых клавиш (CTRL, SHIFT и т.д.) в момент нажатия на кнопку мыши. Второй параметр, point, содержит координаты указателя мыши в момент нажатия кнопки. В функции OnLButtonDown() параметр point используется в качестве аргумента для функции PickMesh(), которая определяет, существует ли в указанной точке какой-либо объект, и, если да, то инициализирует операцию перетаскивания. Если операция перетаскивания инициализирована, функция PickMesh() возвращает TRUE, и вызывается функция ShowCursor() чтобы скрыть указатель мыши на время операции перетаскивания. Кроме того, вызывается функция SetCapture(), чтобы уведомить Windows, что наше приложение хочет получать все сообщения от мыши, даже когда указатель мыши находится вне окна приложения. Перед выходом выполняется вызов функции OnLButtonDown() базового класса.

Функция OnLButtonUp() вызывается, когда пользователь отпускает левую кнопку мыши. Код функции приведен ниже:

void MeshPickWin::OnLButtonUp(UINT nFlags, CPoint point)
{
    if (drag.frame)
    {
        drag.frame = 0;
        ShowCursor(TRUE);
        ReleaseCapture();
    }
    RMWin::OnLButtonUp(nFlags, point);
}

Функция проверяет значение члена данных drag.frame. Эта переменная указывает, во-первых, есть ли начатая операция перетаскивания, и, во-вторых, какой фрейм перетаскивается. Член данных drag.frame инициализируется в функции PickMesh() если в точке, указанной курсором мыши, обнаружен объект.

Если значение переменной drag.frame не равно нулю, в настоящий момент выполняется операция перетаскивания, и мы должны ее прекратить (поскольку кнопка мыши была отпущена). Переменной присваивается нулевое значение, указатель мыши отображается на экране и отменяется захват мыши приложением.

Функция MeshPickWin::PickMesh()

Как мы отмечали при рассмотрении функции OnLButtonDown(), функция PickMesh() используется для проверки наличия объекта в указанной точке:

BOOL MeshPickWin::PickMesh(const CPoint& point)
{
    HRESULT r;
    LPDIRECT3DRMPICKEDARRAY pickarray;

    viewport->Pick(point.x, point.y, &pickarray);

    BOOL ret = FALSE;
    DWORD numpicks = pickarray->GetSize();
    if (numpicks > 0)
    {
        LPDIRECT3DRMVISUAL visual;
        LPDIRECT3DRMFRAMEARRAY framearray;
        D3DRMPICKDESC pickdesc;

        r = pickarray->GetPick(0, &visual, &framearray, &pickdesc);
        if (r == D3DRM_OK)
        {
            framearray->GetElement(framearray->GetSize() - 1,
                    &drag.frame);
            D3DVECTOR pos;
            drag.frame->GetPosition(0, &pos);
            drag.origx = pos.x;
            drag.origy = pos.y;
            drag.mousedown.x = point.x;
            drag.mousedown.y = point.y;
            visual->Release();
            framearray->Release();
            ret = TRUE;
        }
    }
    pickarray->Release();
    return ret;
}

Сначала функция MeshPick() вызывает функцию Pick() интерфейса Direct3DRMViewport. Функции Pick() передается три аргумента. Первые два аргумента определяют точку порта просмотра, которая должна быть проверена. Третий аргумент — это указатель на интерфейс Direct3DRMPickedArray. Этот указатель инициализируется массивом объектов, которые обнаружены в данной точке порта просмотра (даже если один объект полностью скрыт другими).

Интерфейс Direct3DRMPickedArray поддерживает два метода: GetSize() и GetPick(). Функция GetSize() возвращает количество элементов массива. Функция GetPick() возвращает указатель на видимый объект, который был выбран, и указатель на массив указателей на инетрфейс Direct3DRMFrame.

Массив фреймов, возвращаемый функцией GetPick() является списком всех выбранных фреймов объекта, начиная с корневого фрейма сцены. Последний фрейм в списке — это тот фрейм, к которому присоединен объект. Получив этот массив, мы используем функцию GetElement(), чтобы получить указатель на последний фрейм в массиве.

Как только будет определено, что должна начаться новая операция перетаскивания, будет выполнено присваивание значений членам структуры типа DragData. Член drag.frame используется для хранения указателя на перетаскиваемый фрейм. Местоположение фрейма и координаты указателя мыши в момент нажатия кнопки также сохраняются в структуре. Эти данные потребуются позднее, когда мы будем рассчитывать новую позицию фрейма на основе перемещения мыши.

После того, как структура с необходимыми для операции перетаскивания данными готова, указатели на различные интерфейсы освобождаются. Если был выбран какой-либо объект, функция MeshPick() возвращает TRUE.

Функция MeshPickWin::UpdateDrag()

Функция UpdateDrag() — это функция обратного вызова, устанавливаемая в функции CreateScene(). Она используется для опроса состояния приложения и перемещения сеток, когда существует начатая операция перетаскивания.

void MeshPickWin::UpdateDrag(LPDIRECT3DRMFRAME frame, void*, D3DVALUE)
{
    if (drag.frame)
    {
        int x = GetMouseX();
        int y  =GetMouseY();
        D3DVALUE newx =
                -D3DVALUE(drag.mousedown.x - x) * D3DVALUE(.07) + drag.origx;
        D3DVALUE newy =
                D3DVALUE(drag.mousedown.y - y) * D3DVALUE(.07) + drag.origy;
        drag.frame->SetPosition(0, newx, newy, D3DVALUE(0));
    }
}

Функция UpdateDrag() проверяет значение переменной drag.frame, чтобы определить, существует ли фрейм (и объект, присоединенный к этому фрейму), который в данный момент перетаскивается. Если да, то для получения текущего положения указателя мыши используются функции GetMouseX() и GetMouseY() (эти функции унаследованы от класса RMWin).

На основе полученных координат указателя мыши, координат указателя мыши в момент начала операции перетаскивания и начального местоположения фрейма рассчитывается новое местоположение фрейма. Для изменения местоположения фрейма используется функция SetPosition() интерфейса Direct3DRMFrame.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz