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

Устройство ввода

Назначение устройства ввода состоит в том, чтобы получить данные от аппаратуры и сгенерировать по ним шесть величин: x, y, z, r, u и v. Значения x, y и z представляют собой линейные смещения, а r, u и v — угловые скорости. Оси устройства ввода не связаны с осями макета или конкретного объекта; это не более чем необработанные входные данные, которые используются контроллером для смены положения объекта. На рис. 6.2 показаны общие зависимости между осями и угловыми скоростями.


Рис. 6.2. Взаимосвязь между значениями r, u, v и x, y, z

Рис. 6.2. Взаимосвязь между значениями r, u, v и x, y, z


Разумеется, при работе с таким устройством, как SpaceBall, значения x, y, z, r, u и v генерируются просто — достаточно опросить аппаратуру, применить некоторый масштабный множитель и вернуть результат. Для физических устройств, не способных генерировать данные по шести осям, устройство ввода должно получить аппаратные данные и обработать их так, чтобы создать выходные значения для всех шести параметров. Например, устройство ввода из библиотеки 3dPlus, работающее с мышью, получает координаты x и y курсора мыши и в зависимости от состояния клавиш Shift и Ctrl определяет, какие выходные значения следует изменить. Таким образом, если во время перемещения мыши удерживать левую кнопку и не нажимать никаких клавиш, изменяются координаты x и y. При нажатой клавише Shift входное значение координаты x переходит в угловую скорость v, а координата y — в координату z.

Библиотека 3dPlus включает поддержку трех различных устройств ввода: клавиатуры, мыши и джойстика. Каждое устройство реализовано в виде класса C++, производного от C3dInputDevice.

Устройство ввода с клавиатуры

Устройство ввода с клавиатуры обрабатывает сообщения WM_KEYDOWN, посылаемые ему контроллером. Сообщения клавиатуры используются для увеличения или уменьшения текущих значений параметров x, y, z, r, u и v. В таблице 6.1 показано, как различные комбинации клавиш влияют на значения выходных параметров.


Таблица 6.1. Управляющие функции клавиатуры

Клавиша Normal Shift Ctrl
Стрелка влево X-- V-- U--
Стрелка вправо X++ V++ U++
Стрелка вверх Y++ Z++ R++
Стрелка вниз Y-- Z-- R--
Плюс на цифровой клавиатуре Z--    
Минус на цифровой клавиатуре Z++    
Page Up V++    
Page Down V--    
Home U++    
End U--    
Insert R++    
Delete R--    


Не забывайте, что перед тем, как изменять состояние объекта, все выходные значения должны быть обработаны контроллером, поэтому оси с обозначениями x, y, z, r, u и v вовсе не обязаны соответствовать одноименным осям объекта или макета.

Функция, управляющая работой устройства ввода с клавиатуры, представляет собой инструкцию switch, в которой обрабатываются сообщения от различных клавиш. Ниже приведена первая часть функции из файла 3dInpDev.cpp каталога Source библиотеки 3dPlus, обрабатывающая нажатия клавиш управления курсором:

    void C3dKeyInDev::OnKeyDown(UINT nChar, UINT nRepCnt,
                                UINT nFlags)
    {
        double dInc = 0.02;
        switch (nChar) {
            case VK_SHIFT:
                m_bShift = TRUE;
                break;
        case VK_CONTROL:
                m_bControl = TRUE;
                break;
        case VK_RIGHT:
                if (m_bShift) {
                    Inc(m_st.dV);
                } else if (m_bControl) {
                    Inc(m_st.dU);
                } else {
                    Inc(m_st.dX);
                }
                break;
            case VK_LEFT:
                if (m_bShift) {
                    Dec(m_st.dV);
                } else if (m_bControl) {
                    Dec(m_st.dU);
                } else {
                    Dec(m_st.dX);
                }
                break;
    .
    .
    .

Устройство ввода от мыши

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


Таблица 6.2. Управляющие функции мыши

Входной параметр Normal Shift Ctrl
X X –V –U
Y –Y –Z –R


Обратите внимание на то, что некоторые параметры инвертируются. Я изменил направление осей, чтобы управление стало более логичным. Код устройства ввода от мыши состоит из двух функций: C3dMouseInDev::OnUserEvent и C3dMouseInDev::GetState. Первая функция, исходный текст которой приведен ниже, находится в файле 3dInpDev.cpp. Данная функция обрабатывает перемещение мыши и захватывает ее указатель (то есть ограничивает его перемещение текущим окном) при нажатии левой кнопки:

    void C3dMouseInDev::OnUserEvent(HWND hWnd, UINT uiMsg,
                                    WPARAM wParam,
                                    LPARAM lParam)
    {
        switch (uiMsg) {
            case WM_LBUTTONDOWN:
                ::SetCapture(hWnd);
                m_bCaptured = TRUE;
                break; 

            case WM_LBUTTONUP:
                if (m_bCaptured) {
                    ::ReleaseCapture();
                    m_bCaptured = FALSE;
                }
                break; 

            case WM_MOUSEMOVE:
                if (m_bCaptured) {
                    // Внимание: экранные координаты! (см. C3dWnd)
                    m_ptCur.x = LOWORD(lParam);
                    m_ptCur.y = HIWORD(lParam);
                    m_dwFlags = wParam;
                }
                break; 

            default:
                break;
        }
    }

Положение мыши запоминается в m_ptCur, локальной структуре класса CPoint. Вторая функция, исходный текст которой приведен ниже, вызывается, когда контроллер запрашивает текущее состояние устройства ввода:

    BOOL C3dMouseInDev::GetState(_3DINPUTSTATE& st)
    {
        if (m_ptPrev.x < 0) {
            m_ptPrev = m_ptCur;
        }

        // Установить исходное состояние
        m_st.dX = 0;
        m_st.dY = 0;
        m_st.dZ = 0;
        m_st.dR = 0;
        m_st.dU = 0;
        m_st.dV = 0; 

        // Определить смещение мыши
        int dx = m_ptCur.x — m_ptPrev.x;
        int dy = m_ptCur.y — m_ptPrev.y; 

        // Слишком большое смещение игнорируется
        if ((abs(dx) > 100) || (abs(dy) > 100)) {
            dx = 0;
            dy = 0;
        }

        // Внести поправку "мертвой зоны",
        // чтобы избежать случайных перемещений
        int idb = 3;
        if (dx > idb) {
            dx -= idb;
        } else if (dx < -idb) {
            dx += idb;
        } else {
            dx = 0;
        }
        if (dy > idb) {
            dy -= idb;
        } else if (dy < -idb) {
            dy += idb;
        } else {
            dy = 0;
        } 

        double dScale = 0.1;
        if (dx != 0) {
            double d = dx * dScale;
            if (m_dwFlags & MK_SHIFT) {
                m_st.dV = -d;
            } else if (m_dwFlags & MK_CONTROL) {
                m_st.dU = -d;
            } else {
                m_st.dX = d;
            }
        }
        if (dy != 0) {
            double d = dy * dScale;
            if (m_dwFlags & MK_SHIFT) {
                m_st.dZ = -d;
            } else if (m_dwFlags & MK_CONTROL) {
                m_st.dR = -d;
            } else {
                m_st.dY = -d;
            }
        }

        m_ptPrev = m_ptCur; 

        st = m_st;
        return TRUE;
    } 

Функция обрабатывает полученные значения x и y таким образом, чтобы обеспечить небольшую «мертвую зону» для малых смещений и предотвратить случайное перемещение объекта. Затем смещения умножаются на коэффициент пропорциональности, чтобы перемещение объектов всегда происходило в правильном масштабе. Наконец, в зависимости от текущего состояния клавиш Shift и Ctrl функция определяет, какие выходные параметры следует изменить.

Устройство ввода от джойстика

Устройство ввода от джойстика реализуется несколько сложнее, чем ввод от мыши или клавиатуры. На рис. 6.3 изображено диалоговое окно Joystick Settings.


Рис. 6.3. Диалоговое окно Joystick Settings

Рис. 6.3. Диалоговое окно Joystick Settings


Значение каждого выходного параметра может определяться по любой из входных осей, а кнопка джойстика может выступать в роли модификатора. Например, из рис. 6.3 видно, что значение выходного параметра v определяется значением входного параметра x, но только при нажатой кнопке 4. В столбцах Value изображены текущие значения параметров. Левый столбец показывает текущее входное значение, полученное от джойстика; темно-серая полоса соответствует «мертвой зоне». Если входное значение лежит внутри «мертвой зоны», выходное значение не изменяется. Наличие «мертвой зоны» позволяет предотвратить мелкие смещения объектов в тех случаях, когда отпущенная рукоять джойстика не возвращается точно к нейтральному положению. Правый столбец Value изображает выходное значение параметра.

Кроме того, вы можете изменить масштабы осей. Увеличение числа в столбце Scale соответствует повышению чувствительности джойстика, причем отрицательные значения меняют направление оси на противоположное. Конфигурация, показанная на рисунке, была выбрана мной для джойстика Microsoft SideWinder. При работе со SpaceBall остается лишь задать коэффициент пропорциональности между параметрами (x отображается на x, y — на y и т.д.). На рис. 6.4 изображен типичный график зависимости выходных значений параметров от входных. Плоский участок в центре соответствует «мертвой зоне».


Рис. 6.4. Типичный график зависимости вход/выход для джойстика

Рис. 6.4. Типичный график зависимости вход/выход для джойстика


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

    HKEY_CURRENT_USER\Software\3dPlus\<имя-приложения>\Settings
    \Joystick\<тип-джойстика>

Большой объем кода для работы с джойстиком не позволяет привести его в книге, поэтому я предлагаю вам просмотреть файл 3dJoyDev.cpp в каталоге Source библиотеки 3dPlus.

Контроллер ввода

Задача контроллера ввода заключается в том, чтобы получить от устройства ввода значения параметров x, y, z, r, u и v и определенным образом применить их к объекту C3dFrame. Я создал два различных типа контроллеров ввода: позиционный контроллер (position controller) и контроллер полета (flying controller). Контроллеры обоих типов могут использоваться для манипуляций с объектами макета или с камерой. Контроллеру необходимо указать фрейм, с которым он должен работать, а остальное происходит автоматически. Помимо перемещения объекта, контроллер уведомляет приложение о различных событиях — скажем, об изменении параметра x или о нажатии определенной кнопки, — на которые приложение должно реагировать определенным образом. На рис. 6.5 изображено диалоговое окно, которое вызывается из меню Edit приложения Moving. Здесь можно выбрать разновидность контролируемого объекта, тип контроллера и устройство ввода.


Рис. 6.5. Диалоговое окно Control Device

Рис. 6.5. Диалоговое окно Control Device


Позиционный контроллер используется для перемещения объектов внутри макета. Параметры x, y и z определяют положение объекта по отношению к фрейму макета; следовательно, при смещении джойстика влево объект перемещается вдоль оси X макета. Повороты происходят относительно оси объекта, а не начала координат, и это выглядит достаточно разумно. Например, при повороте джойстика объект вращается, а его центр тяжести остается на месте. Любой пользователь может достаточно быстро научиться работать с таким контроллером и перемещать объекты внутри макета.

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

Контроллер полета используется для имитации «полета» объекта или камеры внутри макета. Параметры x и y служат для определения углов атаки и крена, z определяет скорость, а u — угол тангажа. Идея состоит в том, чтобы привести объект в прямолинейное движение и затем выбирать его траекторию посредством изменения углов атаки, крена и тангажа. В исходном варианте программы углы крена и атаки умножались на скорость, чтобы имитация получалась более реалистичной. Однако вскоре выяснилось, что пилота из меня не выйдет, поэтому я пошел по более простому пути и допустил изменение ориентации даже для неподвижного объекта. Если вам это покажется нелогичным, попробуйте поработать с текущим вариантом и затем модифицировать его так, чтобы учитывать скорость полета. Что же именно модифицировать, спросите вы? Приведенную ниже функцию, которая находится в файле 3dInCtlr:

    void C3dFlyCtlr::OnUpdate(_3DINPUTSTATE& st,
                              C3dFrame* pFrame)
    {
        // Определить скорость (по значению параметра z)
        double v = st.dZ / 10; 

        // Получить углы атаки, крена и тангажа
        // для осей x, y и u
        double pitch = st.dY / 3;
        double roll = -st.dX / 3;
        double yaw = st.dU / 5; 

        // Умножить угол атаки и крена на скорость
        // для повышения реализма
        // pitch *= v;
        // roll *= v; 

        pFrame->AddRotation(1, 0, 0, pitch, D3DRMCOMBINE_BEFORE);
        pFrame->AddRotation(0, 0, 1, roll,  D3DRMCOMBINE_BEFORE);
        pFrame->AddRotation(0, 1, 0, yaw,   D3DRMCOMBINE_BEFORE); 

        // Получить вектор текущего направления
        double x1, y1, z1;
        pFrame->GetDirection(x1, y1, z1); 

        // Умножить вектор направления на скорость
        x1 *= v;
        y1 *= v;
        z1 *= v; 

        // Определить текущее положение
        double x, y, z;
        pFrame->GetPosition(x, y, z); 

        // Обновить текущее положение
        x += x1;
        y += y1;
        z += z1;
        pFrame->SetPosition(x, y, z);
    } 

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

Начнем со структуры для хранения данных, полученных от джойстика:

    typedef struct _3DINPUTSTATE {
        double dX;      // -1 <= значение <= 1
        double dY;      // -1 <= значение <= 1
        double dZ;      // -1 <= значение <= 1
        double dR;      // -1 <= значение <= 1
        double dU;      // -1 <= значение <= 1
        double dV;      // -1 <= значение <= 1
        double dpov;    //  0 <= значение <= 359
                        // (значения <0 являются недопустимыми)
        DWORD dwButtons;// 1 = кнопка активна (нажата)
} _3DINPUTSTATE; 

Как видно из листинга, значения шести основных параметров лежат в интервале от –1.0 до 1.0. Кроме того, в структуре присутствует член dPov, определяющий направление, в котором вы смотрите, — вперед, влево, вправо и т.д. (на некоторых джойстиках имеется специальная кнопка для выбора направления). Значение dPov представляет собой угол в градусах, измеряемый от направления «вперед».

Контроллер задает скорость и углы крена, атаки и тангажа по входным значениям параметров z, x, y и u соответственно. Для повышения чувствительности я применил масштабные коэффициенты, значения которых были определены эмпирическим путем.

Если вы захотите усложнить управление летящим объектом, попробуйте убрать комментарии из строк, в которых углы атаки и крена умножаются на скорость.

После определения текущей скорости и величины смещения объекта, следующим шагом является применение поворотов к фрейму. Для этого мы вызываем функцию AddRotation и указываем, что поворот должен быть выполнен до текущего преобразования. Это необходимо для того, чтобы объект вращался вокруг собственной оси, а не вокруг оси макета.

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

Возможно, вы обратили внимание на то, что в программе используются версии функций GetDirection, GetPosition и SetPosition, в которых значения x, y и z заданы в виде отдельных аргументов, а не в виде объекта C3dVector. Никакой особой причины для этого нет, и вы вполне можете в качестве упражнения переписать данные функции с использованием аргументов-векторов.

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


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

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