- •1. Цель работы
- •2. Задание к лабораторной работе
- •3. Основные сведения.
- •Листинг программы
- •1. Движение кривошипно-ползунного механизма. Скорость вращения кривошипа изменяется клавишами PgDn, PgUp.
- •Результат работы программы
- •4. Последовательность выполнения работы
- •5. Требования к содержанию отчета
- •6. Контрольные вопросы
- •7. Методические рекомендации слушателям
- •8. Рекомендуемая литература Основная
3. Основные сведения.
Динамическая графика – это движущиеся или нестационарные графические объекты, реалистично имитирующие природные процессы и реагирующие на внешние события: нажатие клавиши, изменение положения курсора, временные интервалы.
В основе формирования прямолинейного и криволинейного движений лежит стробоскопическая фиксация новых промежуточных положений графического объекта и очистка предыдущих положений с экрана. Фиксация заключается в кратковременной задержке положения объекта, а затем перерисовка его в новом состоянии, с удалением изображения предыдущего состояния. Для задержки времени программно может использоваться пустой цикл с большим количеством итераций или временной интервал фиксации изображения по системному таймеру с обработкой сообщения wm_Timer. Также возможно пользоваться скроллингом экрана.
Рассмотрим все три варианта программной реализации.
Вариант 1.
Реализация прямолинейного движения трех шаров с разными скоростями на темном экране.
Программный код написан в части, ограниченной инструкциями BeginPaint(HWindow,PS) ... EndPaint(HWindow,PS). Сначала зальем экран фоновым цветом:
r.top:=0;r.left:=0;r.bottom:=768;r.right:=1024;
create_brush(bs_solid,rgb(0,0,0),hs_cross,ps.hdc);
fillrect(ps.hdc, r, brush);
delete_brush(ps.hdc);
Затем в цикле по все длине горизонтальной части окна сделаем прорисовку трех шаров разного цвета, затем установим кисть черной, выдержим паузу и закрасим карандашом цветом фона дуговой шлейф, остающийся на экране после последующего изменения положения каждого из шаров:
repeat
x0:=v1+3*i;
create_brush(bs_solid,rgb(200,0,0),hs_bdiagonal,ps.hdc);
ellipse(ps.hdc,x0+20*a,y0+20*a,x0+50*a,y0+50*a);
delete_brush(ps.hdc);
x0:=v3+2*i;
create_brush(bs_solid,rgb(0,0,200),hs_cross,ps.hdc);
ellipse(ps.hdc,x0,y0-80*a,x0+30*a,y0-80*a+30*a);
delete_brush(ps.hdc);
x0:=v2+i;
create_brush(bs_solid,rgb(0,200,0),hs_cross,ps.hdc);
ellipse(ps.hdc,x0,y0-50*a,x0+30*a,y0-50*a+30*a);
delete_brush(ps.hdc);
{создали фоновую кисть}
create_pen(ps_solid,rgb(0,0,0),ps.hdc,3);
{пустой цикл}
for j:=1 to 50000 do begin end;
x0:=v1+3*i;
arc(ps.hdc,x0+20*a,y0+20*a,x0+50*a,y0+50*a,x0+25*a,y0,x0+50*a, y0+50*a);
x0:=v2+i;
arc(ps.hdc,x0-2,y0-2-50*a,x0+2+30*a,y0+2-50*a+30*a,x0+15*a, y0-50*a,x0+30*a,y0-50*a+30*a);
x0:=v3+2*i;
arc(ps.hdc,x0,y0-80*a,x0+30*a,y0-80*a+30*a,x0+15*a,y0-80*a, x0+30*a,y0-80*a+30*a);
delete_pen(ps.hdc);
i:=i+1;
until (i>=1024);
Для аккуратной работы иногда приходится устанавливать инструмент пожирнее, чтобы избежать незакрашенных участков шлейфа. Предпочтительно использовать инструкции циклов без параметров, так как при сворачивании окна заново отрабатывается процедура wmPaint, устанавливающая начальное значение параметра i=0. Параметр масштабирования a обусловливает изменение размеров шаров, а координаты x0, y0 изменяют положение шаров на экране. Параметры v1,v2,v3 определяют стартовую позицию каждого из шаров. Скорость перемещения шаров различна и определяется коэффициентом при i. Максимальную среди шаров скорость перемещения имеет красный шар. Общий темп изменения параметра i задает параметр j цикла задержки. Чем меньше этот параметр, тем быстрее движутся шары.
На рис. 1 показаны некоторые параметры движения шаров.
Рис. 1. Движение трех шаров.
В данном случае ввиду прямолинейности движения легко определить форму остающегося шлейфа. Затирка шлейфа обусловливает отсутствие мерцания на экране и последовательное наложение цветов друг на друга без установки промежуточных цветов. Во многих случаях (например, при вращении или сложном движении) форму шлейфа определить трудно. Тогда прибегают к следующим вариантам действий.
1. Минимизируют площадь элементов, изменяющих свое положение в пространстве и затираемых фоновым цветом.
2. Разбивают последовательность изображений на кадры (прямоугольные или полигональные), каждый из которых отрабатывается целиком и полностью графическими командами.
Например изобразим 3 кадра вращения треугольника внутри круга
Равномерность вращения будет зависеть от количества кадров за один оборот.
Вариант 2.
Создадим движущийся по кругу и мерцающий полигон используя перерисовку отдельной зоны экрана на которой расположен сам объект.
Для данного варианта необходимо воспользоваться сообщением wm_Timer, при отработке которого будет происходит перерисовка не всего экрана, а только той части, которая изменяется. Естественно включается программный таймер, интервал дискрет которого определяется в интервале 1..100 миллисекунд. Параллельно будет написана процедура реакции на нажатие каких-либо клавиш wmKeyDown. Для корректного выхода из программы при нажатии на Esc или применении команды Close (кнопка в правом верхнем углу Windows-окна или нажатие комбинации клавиш Alt+F4) будет использована процедура wmDestroy.
Декларации включают все требуемые процедуры:
type
PointerPaintInWindow=^TPaintInWindow;
TPaintInWindow=object(TWindow)
constructor Init;
destructor Done;
virtual;
procedure wMPaint(var Msg:TMessage);
virtual wm_First + wm_Paint;
procedure WMTimer(var Msg:TMessage);
virtual wm_First + wm_Timer;
procedure wmKeyDown(var Msg:TMessage);
virtual wm_First + wm_KeyDown;
procedure WMDestroy(var Msg:TMessage);
virtual wm_First + wm_Destroy;
procedure SetupWindow; virtual;
end;
TPaintApp=Object(TApplication)
procedure InitMainWindow;
virtual; end;
Раскроем содержание каждой из дополнительных процедур.
procedure TPaintInWindow.SetupWindow;
begin
inherited SetupWindow;
x0 := 200;
y0 := 300;
Rad := 200;
Angle := 0;
a:=3;
SetTimer(PaintApp.MainWindow^.HWindow, 1, 75, nil);
end;
Данная процедура выполняется во время запуска программы (ее первичное появление обозначено словом inherited), В ней определяются переменные изображения (Rad – радиус круга вращения, Angle– начальный угол, x0,y0 – координаты базовой точки, a – параметр масштабирования). Также включается программный таймер с интервалом 1 мс и идентификатором 1. Таким программных таймеров можно включить несколько (с разными интервалами и для разных зон, клипов), например:
SetTimer(HWindow, 2, 100, nil);
SetTimer(HWindow, 3, 200, nil);
SetTimer(HWindow, 4, 100, nil);
Процедура закрытия окна программы включает остановку всех работающих программных таймеров.
procedure TPaintInWindow.WMDestroy(var Msg: TMessage);
begin
KillTimer(HWindow, 1);
KillTimer(HWindow, 2);
KillTimer(HWindow, 3);
KillTimer(HWindow, 4);
PostQuitMessage(0);
end;
Функция PostQuitMessage(0) помещает в очередь сообщение wm_Quit. Когда функция PeekMessage функции Keypr получает это сообщение из очереди, цикл обработки сообщений завершает свою работу и программа завершается с кодом TMsg.wParam (в нашем случае 0 – это успешное завершение).
Реализация процедуры для выхода из программы при нажатии на клавишу Esc будет выглядеть так:
procedure TPaintInWindow.WMKeyDown(var Msg:TMessage);
begin
if Getkeystate(vk_Escape)<0 then begin CloseWindow;
PostQuitMessage(0); end; end;
Инструкция CloseWindow позволяет отобразить курсор после закрытия окна программы. Функция KeyPr немного видоизменилась.
function KeyPr: Boolean;
var
M: TMsg;
begin
while PeekMessage(M, 0, 0, 0, pm_Remove) do
begin
TranslateMessage(M);
DispatchMessage(M);
end;
KeyPr := KeyCount > 0;
end;
Оформим процедуру прорисовки звезды отдельно, используя параметры: контекст устройства, координаты базовой точки и цвет.
procedure DrawStar(cont:hdc; x0,y0,a:integer; color:longint);
var
matrix:array[1..30]of tpoint;
begin
matrix[1].x:=x0; matrix[1].y:=y0;
matrix[2].x:=x0+30*a; matrix[2].y:=y0-5*a;
matrix[3].x:=x0+35*a; matrix[3].y:=y0-35*a;
matrix[4].x:=x0+40*a; matrix[4].y:=y0-5*a;
matrix[5].x:=x0+70*a; matrix[5].y:=y0;
matrix[6].x:=x0+40*a; matrix[6].y:=y0+5*a;
matrix[7].x:=x0+35*a; matrix[7].y:=y0+35*a;
matrix[8].x:=x0+30*a; matrix[8].y:=y0+5*a;
create_brush(bs_solid,color,hs_cross,cont);
polygon(cont,matrix,8);
delete_brush(cont);
end;
Оформим процедуру отработки сообщения wm_Timer. В начале временных дискрет первого таймера вычисляются координаты прямоугольника – зоны клипа, который фиксируется функцией InvalidateRect. Цвет полигона изменяется в начале временных дискрет третьего и четвертого таймеров с использования генератора случайных чисел.
procedure TPaintInWindow.WMTimer(var Msg: TMessage);
var
rect:TRect;
begin
if (Msg.WParam = 1) then
begin
rect.left := trunc(x0+Rad*cos(Angle*pi/180.0)) ;
rect.top := trunc(y0+Rad*sin(Angle*pi/180.0))-35*a;
rect.right := rect.left + 70*a+1;
rect.bottom := rect.top + 70*a+1;
InvalidateRect(HWindow, @rect, true);
Angle := (Angle+1) mod 360;
end;
if (Msg.WParam = 3) then
begin
Randomize;
color := rgb(random(255),random(255),random(255));
end;
if (Msg.WParam = 4) then
begin
Randomize;
color2 := rgb(random(255),random(0),random(0));
end;
end;
Сама процедура отработки сообщения wm_Paint выглядит довольно компактно, по сравнению с первым вариантом.
procedure TPaintInWindow.WMPaint(var Msg:TMessage);
begin
beginPaint(HWindow,PS);
DrawStar(ps.hdc,trunc(x0+Rad*cos(Angle*pi/180.0)), trunc(y0+Rad*sin(Angle*pi/180.0)), a, color);
endPaint(HWindow,PS);
Вариант 3.
Создадим перемещение по экрану закрашенного круга под действием активации клавиш со стрелками используя функцию скроллинга экрана.
В процедуре wmKeyDown для захвата контекста устройства с дальнейшей дорисовкой с помощью графических функций может использоваться getdc. Далее контекст устройства может быть освобожден от использования. Скроллинг выполняется функцией ScrollWindow, которая выполняется в процедуре отработки нажатия клавиш управления wmKeyDown. Аргументами функции является величина прокрутки (в данном случае 5 пикселей)
procedure TPaintinwindow.wmKeyDown;
var dc:hdc;
begin
if GetKeyState(vk_right)<0 then
begin
scrollwindow(Hwindow,5,0,nil,nil);
dc:=getdc(Hwindow);
x0:=x0+5;
y0:=y0;
releaseDC(Hwindow,dc);
end;
if GetKeyState(vk_left)<0 then
begin
scrollwindow(Hwindow,-5,0,nil,nil);
x0:=x0-5;
y0:=y0;
end;
if GetKeyState(vk_down)<0 then
begin
scrollwindow(Hwindow,0,5,nil,nil);
x0:=x0;
y0:=y0+5;
end;
if GetKeyState(vk_up)<0 then
begin
scrollwindow(Hwindow,0,-5,nil,nil);
x0:=x0;
y0:=y0-5;
end;
end;
Процедура отработки сообщения wm_Paint выглядит также компактно, по сравнению с первым вариантом. Параметры b и c определяют коэффициенты сжатия по горизонтали и вертикали (если необходимо отобразить эллипс).
procedure TPaintInWindow.WMPaint(var Msg:TMessage);
begin
beginPaint(HWindow,PS);
x0:=170;y0:=50;a:=10;b:=1;c:=1;
create_brush(bs_solid, rgb(200,0,0),hs_cross,ps.hdc);
create_pen(ps_solid, rgb(200,0,0),ps.hdc,2);
ellipse (ps.hdc,x0+10*a,y0+10*a,x0+20*a*b,y0+20*a*c);
delete_brush(ps.hdc);
delete_pen(ps.hdc);
endPaint(HWindow,PS);
end;
Приведем виртуальные коды некоторых клавиш:
vk_left – клавиша Влево
vk_right – клавиша Вправо
vk_up – клавиша Вверх
vk_down – клавиша Вниз
vk_add – клавиша Серый плюс
vk_subtract – клавиша Серый минус
vk_next – клавиша PageDown
vk_prior – клавиша PageUp
vk_escape – клавиша Esc
vk_lbutton – левая кнопка мыши
vk_rbutton – правая кнопка мыши
vk_return – клавиша Enter
vk_shift – клавиша Shift
vk_control – клавиша Ctrl
vk_space – клавиша Пробел
vk_f1,…,vk_f10 – клавиши F1…F10