OpenGL в Delphi

       

Структура программы


Программа реализована в виде нескольких модулей Во-первых, это выполняемый модуль, ARM exe, самый главный файл комплекса, хранящий весь основной код Для работы он нуждается в файле InitRC.dll, динамической библиотеке, хранящей процедуры и данные, связанные с источником света Кроме этого, используются еще две библиотеки, About.dll и ParForm.dll В принципе, основной модуль комплекса может работать и без этих двух библиотек Первая из них предназначена для вывода окна "Об авторах", а вторая - для вывода диалогового окна по заданию параметров системы
В этом разделе мы разберем основные модули программного комплекса Подкаталог Ex01 содержит проект основного модуля и обязательную библиотеку InitRC.dll
Проект основного модуля не использует библиотеку классов Delphi, поэтому размер откомпилированного модуля занимает всего 34 Кбайта
Для экономии ресурсов запретим пользователю запускать несколько копий приложения При запуске приложения определяем, зарегистрированы ли в системе окна такого же класса, и если это так, то прекращаем программу:

If FindWindow (AppName, AppName) <> 0 then
Exit;

Это необходимо сделать самым первым действием программы Следующий шаг - задание высшего приоритета для процесса:

SetPriorityClass (GetCurrentProcess, HIGH_PRIORITY_CIASS);

Теперь все остальные приложения, выполняемые параллельно, станут работать значительно медленнее, испытывая острую нехватку в процессорном времени, но мы тем самым обеспечим наивысшую производительность нашего приложения

Замечание
Возможно, более тактичным по отношению к пользователю будет предоставление ему возможности самостоятельно решать, стоит ли повышать приоритет процесса Тогда это действие можно оформить так If MessageBox (0,'Для более быстрой работы приложения все остальные процессы будут замедлены ', 'Внимание'1, MB_OKCANCEL) = idOK then SetPriorityClass (GetCurrentProcess, HIGH_PRIORITY_CLASS)

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

hcDHMaterials : THandle;

Значение этой величины - указатель на библиотеку Если он не может быть получен, информируем пользователя и прекращаем работу:

hcDHMaterials := LoadLibrary('InitRC');
If hcDHMaterials <= HINSTANCE_ERROR then begin
MessageBox (0, 'Невозможно загрузить файл библиотеки InitRC.dll',
'Ошибка инициализации программы', mb_OK);
Exit
end,

Ответ на вопрос, зачем потребовалось выносить в отдельную библиотеку процедуры инициализации источника света, мы дадим чуть позже. Посмотрим, что происходит дальше в нашем приложении Откройте модуль WinMain.pas, содержащий описание головной процедуры, и модуль WinProc.pas, хранящий описание оконной функции. В предыдущих примерах, построенных полностью на использовании функций API, нам не приходилось использовать всплывающее меню, а сейчас разберем, как обеспечивается его функционирование.
В программе должны быть введены целочисленные идентификаторы пунктов создаваемого меню, обязательно в блоке констант:

const // идентификаторы пунктов меню
id_param = 101; // пункт "Параметры"
id about = 102; // пункт "Об авторах"
id_close = 103; // пункт "Выход"
id_help = 104; // пункт "Помощь"

Для хранения ссылки на меню должна присутствовать переменная типа HMenu. В процедуре, соответствующей точке входа в программу, ссылка принимает ненулевое значение при вызове функции createPopupMenu, заполнение меню осуществляется вызовом функций AppendMenu:



MenuPopup := CreatePopupMenu;
If MenuPopup <> 0 then begin
AppendMenu (MenuPopup, MF_Enabled, id_help, '&Помощь');
AppendMenu (MenuPopup, MF_Enabled, id_param, '&Параметры');
AppendMenu (MenuPopup, MF_Enabled, id_about, ' &0б авторах');
AppendMenu (MenuPopup, MF_Enabled, id_close, &Выход');
end;

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

wm_LButtonDown :
begin
ShowCursor (True);
TrackPopupMenu (MenuPopup, TPM_LEFTBUTTON,10,10,0,Window, nil) ;
end;

На время функционирования меню включается отображение курсора, чтобы пользователю не пришлось осуществлять выбор пунктов вслепую. Обработка выбора, произведенного пользователем, связана с сообщением WM_COMMAND, параметр wParam такого сообщения содержит значение, указывающее на сделанный выбор:

wm_Command : // всплывающее меню
begin
case wParam of // выбранный пункт меню
id_param : CreateParWindow; // "Параметры"
id_help : WinHelp(Window, 'ARM', HELP_CONTENTS, 0); // "Помощь"
// "Выход"
id_close : SendMessage (Window, wm_Destroy, wParam, IParam);
id_about : About; // "Об авторах"
end; // case
// рисовать ли курсор
If flgCursor = False then ShowCursor (False)
else ShowCursor (True);
end; // wm_coramand

При завершении работы приложения память, ассоциированную с меню, необходимо освободить:

DestroyMenu (MenuPopup);

Из этого же фрагмента вы можете увидеть, что вывод справки осуществляется вызовом функции WinHelp.
Поскольку справка может вызываться по выбору пункта меня и по нажатию клавиши <F1>, обратите внимание, что нажатию этой клавиши в операционной системе соответствует отдельное сообщение WM_HELP.
Приложение работает в полноэкранном режиме, что обеспечивается заданием стиля окна как

ws_Visible or ws_PopUp or ws_EX_TopMost,

что соответствует окну без рамки и без границ, располагающемуся поверх остальных окон Обработчик сообщения WM_CREATE начинается с того, что окно развертывается на весь экран - приложение посылает себе сообщение "развернуть окно"-

SendMessage (Window, WM_SYSCOMMAND, SC_MAXIMIZE, 0) ;

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

После этого происходит обращение к процедуре, считывающей значения Установок и загружающей процедуры из библиотеки InitRC.dll. Поясним, как это делается, на примере процедуры инициализации источника света Введен пользовательский тип:

TlnitializeRC = procedure stdcall;

Затем необходимо установить адрес этой процедуры в динамической библиотеке:

InitializeRC := GetProcAddress (hcDHMaterials, 'InitializeRC1);

Первый аргумент используемой функции API - ссылка на библиотеку, второй - имя экспортируемой функции. Далее в программе происходит считывание массива конфигурации, хранящего значения записанных установок. Если это окажется невозможным, например, из-за отсутствия файла, переменные инициализируются некоторыми предопределенными значениями. Затем создаются quadnc-объекты и подготавливаются дисплейные списки, после чего остается только включить таймер. В коде подготовки списков я опираюсь на константу, задающую уровень детализации рисования объектов. Варьируя значение этой константы, можно получать приложения, имеющие приемлемые скоростные характеристики на маломощных компьютерах, конечно за счет качества изображения.
Код воспроизведения кадра при использовании дисплейных списков становится сравнительно коротким и ясным.
Для максимального сокращения промежуточных действий я не стал создавать отдельной процедуры воспроизведения, чтобы сэкономить хотя бы десяток тактов:

begin // используется в case
// очистка буфера цвета и буфера глубины
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER__BIT);

glPushMatrix; // запомнили текущую систему координат
glRotatef (AngleXYZ [I], 1, О, 0);
glRotatef (AngleXYZ [2], 0, 1, 0);
glRotatef (AngleXYZ [3], 0, 0, 1);

glPushMatrix; // запомнили текущую систему координат - 0,0
If flgSquare then glCallList (4); // рисуем площадку
If flgOc then OcXYZ; // рисуем оси
If flgLight -then begin // рисуем источник света
glPushMatrix;
glTranslatef (PLPosition^ [1], PLPosition^[2], PLPosition^[3]);
gluSphere (ObjSphere, 0.01, 5, 5) ;
glPopMatnx;
end;

glCallList (11); // список - основание накопителя
glCallList (1) ; // штыри накопителя
// стопка прокладок
glTranslatef (0.1, -0.1, 0.0);
glEnable (GL_TEXTURE_1D); //на цилиндр накладывается текстура
gluCylinder (ObjCylinder, 0.125, 0.125, hStopki, 50, 50);
// последний уплотнитель в стопке
glTranslatef (0.0, 0.0, hStopki);
glCallList (5);
glDisable (GL_TEXTURE_1D);
// рисуем крышку накопителя
glTranslatef (0.0, 0.0, 1.5 - hStopki);
glCallList (10);
// рисуем пневмоцилиндр
glTranslatef (0.15, 0.0, -1.725);
glRotatef (90.0, 0.0, 1.0, 0.0);
glCallList (6);
glRotatef (-90.0, 0.0, 1.0, 0.0);
glTranslatef (-1.4, 0.0, 0.0);
// рисуем штырь пневмоцилиндра
If not (flgRotation) then begin // флаг, вращать ли стол
If wrkl = 0 then begin
hStopki := hStopki - 0.025; // уменьшить стопку
If hStopki < 0 then hStopki := 1; // стопка закончилась
end;
glPushMatrix;
glTranslatef (0.9, 0.0, 0.0);
glRotatef (90.0, 0.0, 1.0, 0.0);
glCallList (8); // список - штырь пневмоцилиндра
glPopMatrix;
end;
// рисуем шибер
If flgRotation // флаг, вращать ли стол
then glTranslatef (1.25, 0.0, 0.0)
else begin
glTranslatef (0.75, 0.0, 0.0);
Inc (wrkl); end;
glRotatef (90.0, 0.0, 1.0, 0.0); // шибер - кубик
glCallList (9);
If (not flgRotation) and (wrkl = 4) then begin // пауза закончилась
flgRotation := True;
Angle := 0;
wrkl := 0;
end;
glPopMatrix; // текущая точка - 0, 0
glCallList (7); // ось рабочего стола
glRotatef (90.0, 0.0, 1.0, 0.0);
If flgRotation then // флаг, вращать ли стол
glRotatef ( Angle, 1.0, 0.0, 0.0);
glCallList (2); // шесть цилиндров
glRotatef (-90.0, 0.0, 1.0, 0.0); // систему координат - назад
glRotatef (-30.0, 0.0, 0.0, 1.0); // для соответствия с кубиками
// список - шесть кубиков - деталь
glCallList (3) ;
glPopMatrix; // конец работы
SwapBuffers(DC);
end;

Стоит обратить внимание, что стопка уплотнителей симулируется наложением текстуры на цилиндр, поскольку прорисовка двух десятков цилиндров приведет к сильной потере производительности В примере используется одномерная текстура:

const // параметры текстуры
TexImageWidth = 64;
TexParams : Array [0..3] of GLfloat = (0.0, 0.0, 1.0, 0.0);
var
Texlmage : Array [1 .. 3 * TexImageWidth] of GLuByte;
procedure MakeTexImage;
begin 3 := 1;
While з < TexImageWidth * 3 do begin
Texlmage [3] := 248; // красный
Texlmage [3 + I] := 150; // зеленый
Texlmage [3 + 2] := 41; // синий
Texlmage [3 + 3] := 205; // красный
Texlmage [3 + 4] := 52; // зеленый
Texlmage [3 + 5] := 24; // синий
Inc (3, 6);
end;
glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL__REPEAT);
glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER7 GL NEAREST);
glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE~MIN_FILTER, GL_NEAREST);
glTexImagelD (GL_TEXTURE_1D, 0, 3, TexImageWidth, 0, GL_RGB,
GL_UNSIGNED_BYTE, @Texlmage);
glEnable (GL_TEXTURE_GEN_S);
// чтобы полоски не размывались
glTexGeni (GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
// поворачиваем полоски поперек цилиндра
glTexGenfv (GL_S, GL_OBJECT_PLANE, @TexParams);
end;

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

procedure OcXYZ; // Оси координат
begin
glColorSf (О, 1, 0);
glBegin (GL_LINES);
glVertex3f (0, 0, 0);
glVertexSf (3, 0, 0);
glVertex3f (0, 0, 0);
glVertexSf (0, 2, 0);
glVertexSf (0, 0, 0);
glVertex3f (0, 0, 3);
glEnd;
// буква X
glBegin (GL_LINES);
glVertexSf (3.1, -0.2, 0.5);
glVertexSf (3.1, 0.2, 0.1);
glVertexSf (3.1, -0.2, 0.1);
glVertexSf (3.1, 0.2, 0.5);
glEnd;
// буква Y
glBegin (GL_LINES);
glVertexSf (0.0, 2.1, 0.0);
glVertexSf (0.0, 2.1, -0.1);
glVertexSf (0.0, 2.1, 0.0);
glVertexSf (0.1, 2.1, 0.1);
glVertexSf (0.0, 2.1, 0.0);
glVertexSf (-0.1, 2.1, 0.1);
glEnd;
// буква Z
glBegin (GL_LINES);
glVertexSf (0.1, -0.1, 3.1);
glVertexSf (-0.1, -0.1, 3.1);
glVertex3f (0.1, 0.1, 3.1);
glVertexSf (-0.1, 0.1, 3.1);
glVertexSf (-0.1, -0.1, 3.1);
glVertexSf (0.1, 0.1, 3.1);
glEnd;
// Восстанавливаем значение текущего цвета
glColor3f (Colors [1], Colors [2], Colors [3]);
end;



Содержание раздела