Моделирование движения нескольких объектов, приемы анимации

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

Каждому объекту сцены следует поставить в соответствие свою функцию, которая будет рисовать этот объект. Тогда добавление объекта или изменение свойств объекта сведется к добавлению еще одной функции или изменению свойств существующей функции.

Рассмотрим пример, где сцена содержит пять объектов — статические и динамические объекты. Создадим программу Move_0 5, в которой будет моделироваться движение нескольких динамических объектов. Программу создадим на основе программы Move_04.

Создаются пять объектов сцены: статический объект — эллипс и координатные оси; первый динамический объект — движущаяся стрелка; второй — след траектории; третий — движущийся самолет; четвертый — след траектории самолета.

Каждому объекту сцены соответствует своя функция: Land () используется для рисования эллипса с координатными осями; TrajA () — для расчета траектории стрелки; ТгасА () — для рисования траектории стрелки; Arrow () — для вычисления нового положения стрелки и рисования стрелки; Tra j Р () — для расчета траектории самолета; ТгасР () — для рисования траектории самолета; Plain () — для вычисления нового положения самолета и рисования самолета.

В начале программы надо описать геометрию стрелки и самолета. Пусть стрелка определяется семью вершинами. Аналогично положение самолета в пространстве будем определять семью вершинами. Описание стрелки и самолета лучше всего провести в функции Move_OnCreate () , которая вызывается в ответ на сообщение WM^CREATE. Ниже приводятся код этой функции и описание некоторых глобальных переменных, необходимых для работы программы.

//координаты вершин стрелки текущие и начальные Point PtA[7], PtAO[7];

//координаты вершин самолета текущие и начальные Point PtP[7], PtPO[7];

//время движения по траектории центра стрелки double tA, dtA, dtAO;

//время движения по траектории центра самолета double tP, dtP, dtPO;

//задаем угол поворота и коэффициенты растяжения стрелки

double fiA, kxA, kyA;

11 задаем

угол поворота и коэффициенты растяжения

само

лета

double fiPl, fiP2, kxP, kyP;

//создаем б матриц аффинных преобразований Matrix2D R, Rl, Т, D, A, Tl, Т2;

//начальные и текущие точки центра стрелки double хАО, уАО, хА2, уА2, хА1, уА1;

//начальные и текущие точки центра самолета double хРО, уРО, хР2, уР2, хР1, уР1;

//флаг остановки и запуска таймера int MoveStop;

//время задержки таймера

UINT uElapse;

//функция обрабатывает сообщение WM_CREATE

void Move_OnCreate()

{

//размеры поля вывода в мировых и экранных координатах xLeft = -10; xRight = 10; yBottom = -6.0; уТор = 6.0; nLeft = 30; nRight = 570; mTop = 20; mBottom = 350;

//начальные координаты вершин стрелки

PtAO[ 0 ] . х = 1.0; PtAO[ 0 ] . у = 0.0;

PtAO[1].х = 2.0; PtAO[1].у = 0.0;

PtAO[2].х = 0.0; PtAO[2].у = 2.0;

PtA0[3].x = -2.0; PtAO[3].у = 0.0;

PtA0[4].x = -1.0; PtAO[4].у = 0.0;

PtA0[5].x = -1.0; PtA0[5].y = -2.0;

PtA0[6].x = 1.0; PtAO[6].у = -2.0;

//начальные координаты вершин самолета

PtPO[O].х = 6.0; PtPO[O].у = 0.0;

PtPO[l].х = 4.0; PtPO[l].у = 2.0;

PtP0[2].x = -3.0; PtP0[2].y = 0.0;

PtP0[3].x = -4.0; PtP0[3].y = 2.0;

PtP0[4].x = -6.0; PtP0[4].y = 2.0;

PtP0[5].x = -6.0; PtP0[5].y = -1.0;

PtP0[6].x = 4.0; PtP0[6].y = -2.0;

//задаем угол поворота и коэффициенты растяжения стрелки

НА = 3.14/70; kxA = 0.997; kyA = 0.997;

//коэффициенты растяжения самолета кхР = 0.997; куР = 0.997;

//шаг по времени стрелки

dtAO = 0.04;

//шаг по времени самолета dtPO = 0.11;

//время задержки таймера uElapse = 50;

//флаг запуска таймера MoveStop = 1;

}

Аналогичный фрагмент кода уже рассматривался в программе Move Ol, где моделировалось движение стрелки. Здесь, во-первых, добавляется информация о геометрии самолета и, во-вторых, для каждого движущегося объекта определяются свое время и интервалы движения.

Многие функции, которые применяются в программе Move_04, остались без изменения, например функция Traj А (), которая используется для расчета траектории стрелки. Зато появилась функция Tra j Р () для расчета траектории самолета.

//траектория самолета

Point TrajP(double t)

{

Point P;

P.x = -5 + 2*(t-2.5) — 4*sin(t-2.5);

P.y = 1 - 4*cos(t-2.5);

return P;

}

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

Для вычисления нового положения самолета и рисования самолета служит функция Plain (). Код этой функции приводится ниже.

//рисуем стрелку по координатам вершин Pt

в точке х2, у 2

void Plain(HDC hdc, Point *Pt, int N, double xl, double yl,

double x2, double y2, double fil, double fi2, double

kx,double ky)

{

//создаем матрицы аффинного преобразования

Т1.transl(-xl,-yl);

R1. rotate (-fil) ;

R. rotate (fi2) ;

D.displa(kx, ky);

T.transl(x2-xl,y2-yl);

T2.transl(xl,yl);

A = T2*T*D*R*R1*T1;

//совершаем аффинное преобразование точек фигуры for(int п=0; п<7; п++)

Pt[n] = affine (A, Pt [n]);

POINT pt[7];

for(int i = 0; i < N; i++)

{

pt[i].x = xn(Pt[i].x); pt[i].y = ym(Pt[i].y);

}

HBRUSH hbrush, hbrushOld;

HPEN hpen, hpenOld;

hbrush = CreateSolidBrush(RGB(0,170,170)); hbrushOld = (HBRUSH)SelectObject(hdc,hbrush) ; hpen = CreatePen(PS_SOLID,1,RGB(0,0,200)) ; hpenOld = (HPEN)SelectObject(hdc,hpen);

Polygon(hdc,pt,N);

SelectObject(hdc,hpenOld) ;

DeleteObject(hpen);

SelectObject(hdc,hbrushOld);

DeleteObject(hbrush);

}

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

В программе Move O 5 предусмотрены аффинные преобразования над самолетом, при которых направление движения самолета (вектор скорости самолета) совпадает с направлением касательной к траектории самолета. На рис. 2.22 С'Ь показана схема расчета угла поворота самолета относительно горизонтали.

По двум ближайшим точкам на траектории (х,, уД и (х2, у2) вычисляется угол ср2, определяющий направление скорости самолета:

tan((p2) = -V2~~V1. (2.35)

х2 -Х[

Схема расчета направления самолета при движении по траектории

Рис. 2.22. Схема расчета направления самолета при движении по траектории

В программе Move_05 этому действию соответствует следующий фрагмент кода.

tP += dtP;

dtP *= 0.99;

хР2 = TrajP (tP).x;

yP2 = TrajP(tP).y;

fiP2 =atan21(yP2-yPl, xP2-xPl);

//рисуем фигуру в точке (х2, у2)

Plain (hdcMem, PtP, 7, хР1, yPl, xP2, yP2, fiPl, fiP2, kxP, kyP) ;

xPl = xP2; yPl = yP2; fiPl = fiP2;

В этом фрагменте кода видно, что в функцию Plain () поступают два значения угла направления вектора скорости cpj и ф2. Здесь cpj — угол, вычисленный в предыдущей точке (х|5У|) траектории самолета. Аффинное преобразование поворота самолета на дополнительный угол Дф - ф2 - Ф1 будет состоять из двух элементарных поворотов. Сначала поворот на угол ф[, а затем — на угол ф2.

Программно этот поворот реализован в функции Plain () в строчках кода.

//создаем матрицы аффинного преобразования

Т1.tгansi(-xl,-yl);

R1. rotate (—fil) ;

R. rotate (fi2) ;

D.displa(kx, ky);

T.transl(x2-xl,y2-yl);

Т2.transl(xl,yl);

А = T2*T*D*R*R1*T1;

В отличие от вращения стрелки, где применялась одна матрица вращения R на заданный постоянный угол, здесь используются две матрицы вращения R1 и R. И только после перемножения этих матриц направление самолета устанавливается в направлении касательной к траектории.

Для рисования следа от стрелки была создана функция ТгасА ().

//след от стрелки

void ТгасА(HDC hdc, double te, double dt)

{

double x, y, t;

t = 0;

x = TrajA(t). x;

у = TrajA(t) .y;

HPEN hpen, hpenOld;

hpen = CreatePen(PS_SOLID,2,RGB(255,255,0) ) ;

hpenOld = (HPEN)Selectobject(hdc,hpen);

MoveToEx(hdc,xn(x),ym(y),0);

do{

t += dt;

x = TrajA(t).x;

у = TrajA(t) .y;

LineTo(hdc,xn(x),ym(y));

}while(t < te) ;

Selectobject(hdc,hpenOld);

DeleteObject(hpen);

}

Алгоритм работы этой функции очень простой. В любой момент времени tc помощью функции Traj А (), можно найти координаты (х, у) точек траектории. Поэтому от момента времени до момента времени t2 легко нарисовать траекторию. Полагаем =0, что соответствует началу траектории. Момент времени t2 = t совпадает с текущим временем стрелки.

Аналогично для рисования следа от самолета была создана функция ТгасР ().

//след от самолета

void ТгасР(HDC hdc, double te, double dt)

{

double x, y, t;

t = 0;

x = Traj P(t) .x;

у = TrajP(t).y;

HPEN hpen, hpenOld;

hpen = CreatePen(PS_SOLID,1,RGB(255,255,255) ) ;

hpenOld = (HPEN)SelectObject(hdc,hpen) ;

MoveToEx(hdc,xn(x),ym(y) , 0) ; do{

t += dt;

x = TrajP(t).x;

у = Traj P (t) .y;

LineTo(hdc,xn(x),ym(y));

}while(t < te) ;

SelectObject(hdc,hpenOld);

DeleteObject(hpen);

}

Алгоритм работы функции ТгасР () такой же, как и функции ТгасА() .

Теперь созданы все функции для рисования объектов сцены в любой момент времени. Все эти функции рисуют свои графические объекты в нужной последовательности в функции Move_ OnTimer (), которая вызывается в ответ на сообщение WM_TIMER от таймера.

//функция обрабатывает сообщение WM_TIMER

void Move_OnTimer(HWND hwnd)

{

HDC hdcMem, hdcWin;

HBITMAP hBitmap;

//получаем контекст экрана hdcWin = GetDC(hwnd);

//создаем контекст памяти, совместимый с контекстом

экрана

hdcMem = CreateCompatibleDC(hdcWin);

//создаем битовую карту, совместимую с контекстом экрана

hBitmap = CreateCompatibleBitmap(hdcWin, nRight, mBottom);

//помещаем битовую карту в контекст памяти SelectObject(hdcMem, hBitmap);

//вычисляем координаты центра стрелки

tA += dtA;

хА2 = TrajA(tA). х;

if(хА2 >= xRight-1)

{

хА2 = xRight-1;

tA -= dtA;

}

yA2 = TrajA(tA).y;

//рисуем стрелку в точке (х2, у2)

Arrow (hdcMem, PtA, 7, хА1, yAl, xA2, yA2, fiA, kxA, kyA) ;

//рисуем след за стрелкой TracA(hdcMem, tA, dtA);

xAl = xA2; yAl = yA2;

//рисуем статический объект

Land(hdcMem);

//вычисляем координаты центра самолета tP += dtP;

dtP *= 0.99;

хР2 = TrajP(tP).x;

yP2 = TrajP(tP),y;

fiP2 =atan21 (yP2-yPl,xP2-xPl) ;

//рисуем самолет в точке (х2, у2)

Plain (hdcMem, PtP, 7, хР1, yPl, xP2, yP2, fiPl, fiP2, kxP, kyP) ;

//рисуем след за самолетом

TracP(hdcMem, tP, dtP);

xPl = xP2; yPl = yP2; fiPl = fiP2;

//копируем контекст памяти в контекст экрана

int xd = nLeft, yd = mTop;

int Wd = nRight-nLeft, Hd = mBottom-mTop;

int xs = nLeft, ys = mTop;

BitBlt(hdcWin,xd,yd,Wd,Hd,hdcMem,xs,ys,SRCCOPY);

DeleteObject(hBitmap); //уничтожаем битовую карту DeleteDC(hdcMem); // уничтожаем контекст памяти ReleaseDC(hwnd,hdcWin); //освобождаем контекст экрана }

Обратим внимание на порядок рисования динамических и статических объектов. Сначала рисуются стрелка и след от стрелки, затем зеленый эллипс, а в конце самолет и след от самолета. Возникает следующий эффект: стрелка движется позади эллипса, а самолет — перед эллипсом (рис. 2.23 2>).

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

Программа Move_0 6 создана на основе предыдущей программы Move_0 5. Общий алгоритм работы программы остался прежним. В ответ на сообщение WM_TIMER от таймера вызывается функция Move_OnTimer (), где в определенной последовательности рисуются графические объекты с помощью соответствующих функций. Рисование производится на битовой карте в контексте памяти. После того как нарисован очередной кадр сцены, изображение копируется из контекста памяти в контекст экрана с помощью функции

Результат работы программы Move_05

Рис. 2.23. Результат работы программы Move_05

BitBlt (). При следующем сообщении от таймера объекты рисуются в новом положении.

В начале программы описывается геометрия лодки и лучей солнца, причем описание лучше всего провести в функции Move_ OnCreate (), которая вызывается в ответ на сообщение WM_CREATE. Ниже приводятся код этой функции и описание некоторых глобальных переменных, необходимых для работы программы.

//координаты лучей солнца текущие и начальные Point PtS[8], PtSO [8];

//координаты точек лодки текущие и начальные Point PtB[11], PtBO [11];

//время движения по траектории лодки double tB, dtBO, dtB;

//время движения по траектории солнца double tS, dtSO, dtS;

//время движения по траектории облака double tC, dtCO, dtC;

//время движения по траектории луны double tM, dtMO, dtM;

//задаем угол поворота и коэффициенты растяжения double fi, kx, ky;

//создаем 6 матриц

Matrix2D R, Т, D, A, Tl, Т2;

//начальная точка центра лодки double хВО, уВО;

//начальная точка центра солнца double xSO, ySO;

//начальная точка центра облака double хСО, уСО;

//начальная точка центра луны double хМО, уМО;

//текущие точки центра лодки double хВ2, уВ2, хВ1, уВ1;

//текущие точки центра солнца double xS2, yS2, xSl, ySl;

//текущие точки центра облака double хС, уС;

//текущие точки центра луны double хМ, уМ;

//точки центра звезд

double xStar[50], yStar[50];

//флаг остановки и запуска таймера int MoveStop;

//время задержки таймера

UINT uElapse;

//функция обрабатывает сообщение WM_CREATE

void Move_OnCreate()

{

//размеры поля вывода в мировых и экранных координатах xLeft = -10; xRight = 10; yBottom = -6.0; уТор = 6.0; nLeft = 50; nRight = 650; mTop = 50; mBottom = 400;

//координаты лучей

PtSO[0].х = 0.0;

PtSO[1].х = 4.5;

PtSO[2].x = 6.0;

PtSO[3].x = 4.5;

PtSO [4] .x = 0.0;

PtSO[5].x = -4.5;

PtSO[6].x = -6.0;

PtSO[7].x = -4.5;

солнца

PtSO[0]. у = 6.0;

PtSO[1]. у = 4.5;

PtSO[2].у = 0.0;

PtSO[3].y = -4.5;

PtSO[4].y = -6.0;

PtSO[5].у = -4.5

PtSO[6].y = 0.0;

PtSO[7].y = 4.5;

//координаты точек лодки

PtB0[0].x = 0.0; PtB0[0].y = 14.0;

PtB0[l].x = 6.0; PtB0[l].y = 2.0;

PtB0[2].x = 0.0; PtB0[2].y = 5.0;

PtB0[3].x = 0.0; PtB0[3].y = 3.0;

PtB0[4].x = 3.0; PtB0[4].y = -0.0;

PtB0[5].x = -4.0; PtB0[5].y = 7.0;

PtB0[6].x = 1.0; PtB0[6].y = -1.0;

PtB0[7].x = 5.0; PtB0[7].y = 1.0;

PtB0[8].x = 4.0; PtB0[8].y = -2.5;

PtB0[9].x = 2.5; PtB0[9].y = -3.5;

PtB0[10].x = -3.0; PtB0[10].y = 4.0;

//задаем случайные координаты звезд на небе for(int k=0; k<50; k++) {

xStar[k] = xLeftt(xRight-xLeft)*(1.0*rand()/RAND MAX) ;

yStar[k] = yTop*((1.0*rand()/RAND_MAX));

}

//задаем угол поворота и коэффициенты растяжения лодки fi = 0; kx = 0.993; ky = 0.993;

//шаг по времени для лодки dtBO = 0.039;

//шаг по времени для солнца dtSO = 0.23;

//шаг по времени для облака dtCO = 0.035;

//шаг по времени для луны

dtMO = 0.2;

//время задержки таймера

uElapse = 70;

//флаг для запуска таймера

MoveStop = 1;

}

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

Обратим внимание на следующие строчки кода.

//задаем случайные координаты звезд на небе

for(int k=0; k<50; k++)

{

xStar[k] = xLeft+(xRight-xLeft)*(1.0*rand()/RAND_ MAX) ;

yStar[k] = yTop*((1.0*rand()/RAND_MAX));

}

Здесь случайным образом разбрасываются по небу 50 звезд. Рассматриваем дальше код программы.

Траектория лодки описывается функцией TrajB ().

//траектория лодки

Point TrajB(double t)

{

Point P;

P.x = -(t — 4.5);

P.у = (t — 4.5);

return P;

}

Лодка рисуется функцией Boat ().

//рисуем лодку по координатам вершин Pt

void Boat(HDC hdc, Point *Pt, int N, double xl,double yh double x2,double y2,double fi, double kx,double ky) { //создаем матрицы аффинного преобразования

Т1.transl(-xl,-yl);

R. rotate (fi) ;

D.displa(kx, ky);

T.transl(x2-xl,y2-yl);

T2.transl(xl,yl);

A = T2*T*D*R*T1;

//совершаем аффинное преобразование точек фигуры

for(int n=0; n

Pt[n] = affine (A, Pt [n]) ;

//рисуем палубу лодки

POINT pt[7];

pt[0].x = xn(Pt[5].x); pt [ 0 ] . у = ym(Pt[5].y);

pt[l].x = xn(Pt[6].x); pt[l].y = ym(Pt[6].y);

pt[2].x = xn(Pt[7].x); pt[2].y = ym(Pt[7].y);

HBRUSH hbrushOld;

HPEN hpenOld;

HBRUSH hbrushl, hbrush2, hbrush3, hbrush4;

HPEN hpenl, hpen2, hpen3, hpen4, hpen5;

hbrushl = CreateSolidBrush(RGB(200,200,200)); hbrushOld = (HBRUSH)SelectObject(hdc,hbrushl);

hpenl = CreatePen(PS_SOLID,2,RGB(170,170,170)); hpenOld = (HPEN)SelectObject(hdc,hpenl);

Polygon(hdc,pt,3);

//рисуем борт лодки

pt[0].x = xn(Pt[5].x); pt[0].y = ym(Pt[5].y);

pt[l].x = xn(Pt[6].x); pt[l].y = ym(Pt[6].y);

pt[2].x = xn(Pt[9].x); pt[2].y = ym(Pt[9].y);

pt[3].x = xn(Pt[10].x); pt[3].y = ym(Pt[10].y);

hbrush2 = CreateSolidBrush(RGB(100,80,80)) ; SelectObject(hdc,hbrush2) ;

hpen2 = CreatePen(PS_SOLID,2,RGB(100,100,100));

Selectobject(hdc,hpen2);

Polygon(hdc,pt,4);

//рисуем корму лодки

pt[0].x = xn(Pt[6].x); pt[0].y = ym(Pt[6].y);

pt[l].x = xn(Pt[7].x); pt[l].y = ym(Pt[7].y);

pt[2].x = xn(Pt[8].x); pt[2].y = ym(Pt[8].y);

pt[3].x = xn(Pt[9].x); pt[3].y = ym(Pt[9].y);

hbrush3 = CreateSolidBrush(RGB(100,130,100));

SelectObject(hdc,hbrush3);

hpen3 = CreatePen(PS_SOLID,2,RGB(120,120,120)) ;

SelectObject(hdc,hpen3);

Polygon(hdc,pt,4);

//рисуем парус лодки

pt[0].x = xn(Pt[0].x); pt[0].y = ym(Pt[0].y);

pt [ 1] . x = xn(Pt[l].x); pt[1].у = ym(Pt[l].y);

pt[2].x = xn(Pt[2].x); pt[2].y = ym(Pt[2].y);

hbrush4 = CreateSolidBrush(RGB(230,230,230));

SelectObject(hdc,hbrush4) ;

hpen4 = CreatePen(PS_SOLID,2,RGB(200,200,0));

SelectObject(hdc,hpen4) ;

Polygon(hdc,pt,3);

//рисуем мачту лодки

pt[0].x = xn(Pt[0].x); pt[0].y = ym(Pt[0].y);

pt[l].x = xn(Pt[3].x); pt[l].y = ym(Pt[3].y);

hpen5 = CreatePen(PS_SOLID,3, RGB(255,100,100) ) ;

SelectObject(hdc,hpen5) ;

MoveToEx(hdc, pt[0].x, pt[0].y, 0);

LineTo(hdc,pt[1].x, pt[l].y);

SelectObject(hdc,hpenOld);

DeleteObject(hpenl);

DeleteObject(hpen2);

DeleteObject(hpen3);

DeleteObject(hpen4);

DeleteObject(hpen5);

SelectObject(hdc,hbrushOld);

DeleteObject(hbrushl);

DeleteObject(hbrush2);

DeleteObject(hbrush3);

DeleteObject(hbrush4);

Отметим большое число деталей, из которых строится лодка. Для каждой детали применяются аффинные преобразования.

След от лодки рисуется с помощью функции ТгасВ ().

//след от лодки

void ТгасВ(HDC hdc, double te, double dt)

{

double x, y, t;

t = 0;

x = TrajB(t) .x;

у = TrajB(t) .y;

HPEN hpen, hpenOld;

hpen = CreatePen(PS_SOLID,2,RGB(255,255,200)) ;

hpenOld = (HPEN)SelectObject(hdc,hpen);

MoveToEx(hdc,xn(x),ym(y),0);

do{

t += dt;

x = TrajB(t).x;

у = TrajB(t).y;

LineTo(hdc,xn(x),ym(y));

}while(t < te);

SelectObject(hdc,hpenOld);

DeleteObject(hpen);

Отметим, что алгоритм рисования следа от лодки такой же, как в программе Move_05 при рисовании следа от стрелки или самолета.

Траектория солнца вычисляется с помощью функции Traj S().

//траектория солнца

Point TrajS(double t)

{

Point P;

P.x = -4.5*sin(0.1*t)+4.5;

P.y = 4.5*cos(0.l*t);

return P;

Функция Sun () рисует круг солнца и солнечные лучи.

//рисуем солнце

void Sun(HDC hdc, Point *Pt, int N, double xl,double yl, double x2, double y2, double fi, double kx, double ky)

//создаем матрицы аффинного преобразования

Т1.transl(-xl,-yl);

R. rotate (fi) ;

D.displa(kx, ky);

T.transl(x2-xl,y2-yl);

T2.transl(xl,yl);

A = T2*T*D*R*T1;

//совершаем аффинное преобразование точек фигуры

for(int n=0; n

Pt[n] = affine (A, Pt [n]) ;

POINT pt[8] ;

for(int i = 0; i < N; i++)

{

pt[i].x = xn(Pt[i].x); pt[i].y = ym(Pt[i].y);

}

HBRUSH hbrush, hbrushOld;

HPEN hpen, hpenOld;

//рисуем круг солнца и лучи

hbrush = CreateSolidBrush(RGB(255,255,0)) ;

hbrushOld = (HBRUSH)SelectObject(hdc,hbrush) ;

hpen = CreatePen(PS_SOLID,2,RGB(255,155,0));

hpenOld = (HPEN)SelectObject(hdc,hpen) ;

//рисуем лучи for(int i = 0; i < 4; i++) {

MoveToEx(hdc,pt[i]. x,pt[i].y,0);

LineTo(hdc,pt[i+4].x,pt[i+4].y) ;

}

//рисуем круг солнца

Ellipse(hdc,pt[7].x,pt[7].y,pt[3].x,pt[3].y) ;

SelectObject(hdc,hpenOld) ;

DeleteObject(hpen) ;

SelectObject(hdc,hbrushOld) ;

DeleteObject(hbrush);

}

Функция Line Sun () рисует солнечную дорожку.

//рисуем солнечную дорожку void LineSun(HDC hdc, double x, double t) {

POINT pt[4];

pt[0].x = xn(x+0.2*(1+t)); pt[0].y = ym(-O.Ol);

pt[l].x = xn(x-0.2*(1+t)); pt[l].y = ym(-O.Ol);

pt[2].x = xn(-0.7* (1+t)); pt[2].y = ym(yBottom+0.1); pt[3].x = xn(0.7*(1+t)); pt[3].y = ym(yBottom+0.1);

HBRUSH hbrush, hbrushOld;

HPEN hpen, hpenOld;

int colR, colG, colB;

colR = (int)(255*exp(-0.l*t));

colG = (int)(155*exp(-0.15*t)+ 100/(1+0.l*t));

colB = (int) ((1-exp(-0.2*t))*230/(1 + 0.l*t) ) ; hbrush = CreateSolidBrush(RGB(colR, colG, colB)); hbrushOld = (HBRUSH)Selectobject(hdc,hbrush);

hpen = CreatePen(PS_SOLID,2,RGB(colR, colG, colB)); hpenOld = (HPEN)Selectobject(hdc,hpen);

Polygon(hdc,pt,4);

Selectobject(hdc,hpenOld) ;

DeleteObject(hpen);

SelectObject(hdc,hbrushOld);

DeleteObject(hbrush);

}

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

Точно так же по заданным законам изменяются со временем компоненты цвета солнечной дорожки.

Функция вычисляет траекторию облака.

//траектория облака

Point TrajC(double t)

{

Point P;

P.x = t-7.5;

P.y = 4.0;

return P;

}

Функция Cloud () рисует облако в разные моменты времени.

//рисуем облако

void Cloud(HDC hdc, double xO,double yO,double t)

{

HBRUSH hbrush, hbrushOld;

HPEN hpen, hpenOld;

int coll = (int)(255*exp(-0.l*t));

hbrush = CreateSolidBrush(RGB(coll,coll, coll)) ;

hbrushOld = (HBRUSH)SelectObject(hdc,hbrush);

hpen = CreatePen(PS_SOLID,2,RGB(coll,coll,coll)) ;

hpenOld = (HPEN)SelectObject(hdc,hpen);

int xl,yl,x2,y2;

xl = xn(x0-2); yl = ym(yO+l);

x2 = xn(x0+2); у 2 = ym(yO-l);

Ellipse(hdc,xl,yl,x2,y2) ;

xl = xn(xO+l-l); yl = ym(y0+0.5+l);

x2 = xn(xO+l+l); y2 = ym(y0+0.5-1);

Ellipse(hdc,xl,yl, x2, y2) ;

SelectObject(hdc,hpenOld);

DeleteObject(hpen);

SelectObject(hdc,hbrushOld) ;

DeleteObject(hbrush);

Отметим, что моделирование движения облака происходит без применения аффинных преобразований.

Следующая функция Tr a j М () вычисляет траекторию луны.

//траектория луны

Point TrajM(double t)

{

Point P;

P.x = -6.5;

P.y = 0.1*t-3;

return P;

Функция Moon () рисует луну.

//рисуем луну

void Moon(HDC hdc, double xO,double yO,double t)

{

HBRUSH hbrush, hbrushOld;

HPEN hpen, hpenOld;

int col = (int)(255*(1-exp(-0.2*t)));

hbrush = CreateSolidBrush(RGB(col,col, col));

hbrushOld = (HBRUSH)SelectObject(hdc,hbrush);

hpen = CreatePen(PS_SOLID,2,RGB(col,col,col) ) ;

hpenOld = (HPEN)SelectObject(hdc,hpen) ;

int xl,yl,x2,y2;

xl = xn(xO-l); yl = ym(y0+0.8);

x2 = xn(x0+l); у 2 = ym(y0-0.8);

Ellipse(hdc,xl,yl,x2,y2);

SelectObject(hdc,hpenOld);

DeleteObject(hpen);

SelectObject(hdc,hbrushOld);

DeleteObject(hbrush) ;

}

Отметим, что моделирование движения луны происходит без применения аффинных преобразований.

Функция LineMoon () рисует лунную дорожку

//рисуем лунную дорожку

void LineMoon(HDC hdc, double x, double t)

{

double dl = 1*(1-exp(-0.1* (t-20))), d2 =

= 4*(l-exp(-0.1*(t-20)));

POINT pt[4];

pt[0].x = xn(x+dl); pt[0].y = ym(-O.Ol);

pt[l].x = xn(x-dl); pt[l].y = ym(-O.Ol);

pt[2].x = xn(-d2); pt[2].y = ym(yBottom+0.1);

pt[3].x = xn(d2); pt[3].y = ym(yBottom+0.1);

HBRUSH hbrush, hbrushOld;

HPEN hpen, hpenOld;

int col = 255;

hbrush = CreateSolidBrush(RGB(col, col, col));

hbrushOld = (HBRUSH)SelectObject(hdc,hbrush) ;

hpen = CreatePen(PS_SOLID,2,RGB(col, col, col)); hpenOld = (HPEN)SelectObject(hdc,hpen) ;

Polygon(hdc,pt,4);

SelectObject(hdc,hpenOld) ;

DeleteObject(hpen);

SelectObject(hdc,hbrushOld);

DeleteObject(hbrush);

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

Точно так же по заданным законам изменяются со временем компоненты цвета лунной дорожки.

Звезды рисуются с помощью функции Star ().

//номер мерцающей звезды

int kLight;

//счетчик звезд

static int kLL = 0;

//рисуем звезды

void Star(HDC hdc, double t)

{

int colR = (int)(255*(1-exp(-0.001*t*t)));

int colG = (int) (255*(1-exp(-0.001*t*t)));

int colB = (int) (255*(1-exp(-0.001*t*t)));

double xO, yO;

int xl,yl;

for(int k=0; k<50; k++)

{

xO = xStar[k]; yO = yStar[k];

xl - xn(xO); yl - ym(yO);

if(k == kLight)

//рисуем мерцающую звезду

SetPixel(hdc,xl,yl, RGB(colR, colG, 0) ) ;

for(int sn=l; sn<=8; sn++)

{

SetPixel(hdc,xl-sn,yl,RGB(colR,colG,0));

SetPixel(hdc,xl+sn,yl,RGB(colR,colG,0)) ;

SetPixel(hdc,xl,yl-sn,RGB(colR,colG,0));

SetPixel(hdc,xl,yl+sn,RGB(colR,colG,0));

for(int sn=l; sn<=3; sn++)

{

SetPixel(hdc,xl-sn,yl-sn,RGB(colR,colG, 0));

SetPixel(hdc,xl-sn,yl+sn,RGB(colR,colG,0));

SetPixel(hdc,xl+sn,yl-sn,RGB(colR,colG, 0));

SetPixel(hdc,xl+sn,yl+sn,RGB(colR,colG,0));

}

}

else

{

SetPixel(hdc,xl,yl,RGB(colR,colG, colB) ) ;

for(int sn=l; sn<=4; sn++)

{

SetPixel(hdc,xl-sn,yl,RGB(colR,colG,colB)) ;

SetPixel(hdc,xl+sn,yl,RGB(colR,colG,colB));

SetPixel(hdc,xl,yl-sn,RGB(colR,colG,colB));

SetPixel(hdc,xl,yl+sn,RGB(colR,colG,colB)) ;

for(int sn=l; sn<=2; sn++)

{

SetPixel(hdc,xl-sn,yl-sn,RGB(colR,colG,colB)) ;

SetPixel(hdc,xl-sn,yl+sn,RGB(colR,colG,colB));

SetPixel(hdc,xl+sn,yl-sn,RGB(colR,colG,colB));

SetPixel(hdc,xl+sn,yl+sn,RGB(colR,colG,colB));

Отметим, что случайным образом создается несколько мерцающих звезд.

Функция Sky () рисует небо.

//рисуем небо

void Sky(HDC hdc, double t)

{

HBRUSH hbrush, hbrushOld;

HPEN hpen, hpenOld;

int colR = (int)(200*exp(-0.004*t*t));

int colG = (int) (255*exp(-0.01*t*t));

int colB = (int) (255*exp(-0.01*t*t));

hbrush = CreateSolidBrush(RGB(colR,colG,colB)) ;

hbrushOld = (HBRUSH)SelectObject(hdc,hbrush);

hpen = CreatePen(PS_SOLID,1,RGB(colR,colG,colB) ) ;

hpenOld = (HPEN)SelectObject(hdc,hpen);

Rectangle(hdc,nLeft,mBottom,nRight,mTop);

SelectObject(hdc,hpenOld) ;

DeleteObject(hpen);

SelectObject(hdc,hbrushOld);

DeleteObject(hbrush);

}

Заметим, что небо — это статический объект, у которого со временем меняется цвет по определенному закону.

Функция See () рисует море.

//рисуем море

void See(HDC hdc, double t)

{

HBRUSH hbrush, hbrushOld;

HPEN hpenl, hpen2, hpenOld;

int colG = (int) (100/ (1+0.l*t));

int colB = (int) (230/ (1+0.l*t));

hbrush = CreateSolidBrush(RGB(0,colG,colB)) ; hbrushOld = (HBRUSH)SelectObject(hdc,hbrush);

hpenl = CreatePen(PS_SOLID,1,RGB(0,colG,colB) ) ; hpenOld = (HPEN)SelectObject(hdc,hpenl);

Rectangle(hdc,nLeft,mBottom,nRight,(mTop+mBottom)/2);

hpen2 = CreatePen(PS_SOLID,1,RGB(0,255,255) ) ;

Selectobject(hdc,hpen2) ;

MoveToEx(hdc, nLeft,(mTop+mBottom)/2, 0);

LineTo(hdc,nRight,(mTop+mBottom)/2);

SelectObject(hdc,hpenOld) ;

DeleteObject(hpenl) ;

DeleteObject(hpen2);

SelectObject(hdc,hbrushOld);

DeleteObject(hbrush);

}

Отметим, что море — это статический объект, у которого со временем меняется цвет по определенному закону.

Теперь описаны все статические и динамические элементы сцены. Их действие происходит в функции Move_OnTimer (), которая вызывается сообщением WM^TIMER от таймера.

//функция обрабатывает сообщение WM_TIMER

void Move_OnTimer(HWND hwnd)

{

HDC hdcMem, hdcWin;

HBITMAP hBitmap;

//получаем контекст экрана

hdcWin = GetDC(hwnd);

//создаем контекст памяти, совместимый с контекстом

экрана

hdcMem = CreateCompatibleDC(hdcWin);

//создаем битовую карту, совместимую с контекстом экрана

hBitmap = CreateCompatibleBitmap(hdcWin, nRight, mBottom);

//помещаем битовую карту в контекст памяти SelectObject(hdcMem, hBitmap);

//координаты солнца

tS += dtS;

dtS *= 0.9905;

xS2 = TrajS(tS).x;

yS2 = TrajS(tS). y;

//рисуем небо

Sky(hdcMem,tS);

//рисуем солнце

Sun (hdcMem, PtS, 8, xSl, ySl, xS2, yS2,fi, 1.01,1.001) ;

xSl = xS2; ySl = yS2;

//номер мерцающей звезды

kLL++;

if(kLL>=5)

{

kLight = (int)(50*((1.0*rand()/RAND_MAX)));

kLL = 0;

}

//координаты луны

tM += dtM;

dtM *= 0.997;

xM = TrajM(tM).x;

yM = TrajM(tM).y;

//рисуем луну

Moon(hdcMem, xM, yM, tM);

//рисуем звезды

if(yM>-l.0)

Star(hdcMem,tM);

//рисуем море

See(hdcMem,tS);

double xx;

xx = xS2;

if(yS2 < -0.0)

xx = 0;

//рисуем солнечную дорожку

if(yS2 > -1.0)

LineSun(hdcMem,xx,tS);

//рисуем лунную дорожку

if(yM > -1.0)

LineMoon(hdcMem,xM,tM);

//координаты облака

tC += dtC;

xC = TrajC(tC).x;

yC = TrajC(tC).y;

//рисуем облако

Cloud(hdcMem, xC, yC, tC);

//координаты лодки

tB += dtB;

dtB *= 0.9913;

xB2 = TrajB(tB).x;

yB2 = TrajB(tB). y;

//рисуем след лодки

TracB(hdcMem, tB, dtB);

//рисуем лодку

Boat (hdcMem, PtB, 11, xBl, yBl, xB2, yB2,fi, kx, ky) ;

xBl = xB2; yBl = yB2;

//копируем контекст памяти в контекст экрана

int xd = nLeft, yd = mTop;

int Wd = nRight-nLeft, Hd = mBottom-mTop;

int xs = nLeft, ys = mTop;

BitBlt(hdcWin,xd,yd,Wd,Hd,hdcMem,xs,ys,SRCCOPY);

DeleteObject(hBitmap); //уничтожаем битовую карту

DeleteDC(hdcMem); // уничтожаем контекст памяти

ReleaseDC(hwnd,hdcWin); //освобождаем контекст экрана }

В начале работы программы приходит сообщение WM_PAINT и рисуется картина, которая показана на рис. 2.24 cS2>. Поскольку сообщение WM PAINT является особым сообщением, то рисование картины происходит вызовом функции Move^OnPaint ().

Результат работы программы Move_0 6. Начальное положение сцены — утро

Рис. 2.24. Результат работы программы Move_0 6. Начальное положение сцены — утро

Картина, показанная на рис. 2.24, создается функцией в начальный момент работы программы.

//функция обрабатывает сообщение WM_PAINT void Move_OnPaint(HWND hwnd) {

RECT rc;

//даем размеры клиентской области окна GetClientRect(hwnd,&rc);

//размеры окна в мировых координатах и в пикселях xLeft = -10; xRight = 10; yBottom = -б; уТор = б; nLeft = rc.left; nRight = rc.right; mBottom = rc.bottom; mTop = rc.top;

HDC hdc;

PAINTSTRUCT ps;

//получаем контекст экрана

hdc = BeginPaint(hwnd,&ps);

HBRUSH hbrush, hbrushOld;

HPEN hpen, hpenOld;

hbrush = CreateSolidBrush(RGB(0,255,255) ) ; hbrushOld = (HBRUSH)SelectObject(hdc,hbrush); hpen = CreatePen(PS_SOLID,3,RGB(255,255,255));

hpenOld = (HPEN)SelectObject(hdc,hpen);

Rectangle(hdc,nLeft,mBottom,nRight,mTop);

SelectObject(hdc,hpenOld);

DeleteObject(hpen) ;

SelectObject(hdc,hbrushOld) ;

DeleteObject(hbrush) ;

Sky(hdc,0);

SetTextColor(hdc,RGB(230,230,220));

SetBkColor(hdc,RGB(0,0,0));

TextOut(hdc, 325,100,TEXT ("Press any key"),27);

//начальный момент времени для лодки tB = 0; dtB = dtBO;

//начальный момент времени для солнца tS = 0; dtS = dtSO;

//начальный момент времени для облака tC = 0; dtC = dtCO;

//начальный момент времени для луны tM = 0; dtM = dtMO;

//начальное положение центра корабля

хВО = TrajB(tB),х;

уВО = TrajB(tB).у;

//начальное положение центра солнца

xSO = TrajS(tS).х;

ySO = TrajS(tS).у;

//начальное положение центра облака

хСО = TrajC(tC).х;

уСО = TrajC(tC).у;

//начальное положение центра луны хМО = TrajM(tM). х;

уМО = TrajM(tM).у;

for(int n=0; n<8; n++) PtS[n] = PtSO[n];

for(int n=0; n

Moon(hdc,хМО,yMO,0);

See(hdc,0);

Sun(hdc,PtS,8,0,0,xS0,yS0,0,0.2,0.2);

LineSun(hdc,xSO,0);

Cloud(hdc,xCO,yCO,0);

Boat(hdc, PtB,11,0,0,xBO,yBO,0,0.3,0.3);

xBl = xBO; yBl = yBO;

xSl = xSO; ySl = ySO;

//освобождаем контекст устройства

EndPaint(hwnd,&ps);

В функции Move_OnPaint () производятся начальные установки программы и рисуются объекты сцены в начальный момент времени. При работе программы сменяется день на вечер, как показано на рис. 2.25 С

Результат работы программы Move_0 6. Следующее положение сцены — вечер

Рис. 2.25. Результат работы программы Move_0 6. Следующее положение сцены — вечер

Затем вечер сменяется ночью, появляются звезды и луна, как это видно на рис. 2.26 С^2>.

Результат работы программы Move_06. Завершающая стадия — ночь

Рис. 2.26. Результат работы программы Move_06. Завершающая стадия — ночь

В работе программы Move_0 6 надо отметить следующие моменты. Во-первых, для моделирования движения некоторых динамических объектов не применялись аффинные преобразования, как это было в программах Move_01 — Move_05. Во-вторых, со статическими объектами, такими как море, небо, звезды, происходили изменения — с течением времени менялись цвета и яркость.

 
Посмотреть оригинал
< Пред   СОДЕРЖАНИЕ   ОРИГИНАЛ   След >