- •Московский институт электронной техники
- •Введение.
- •Пространственна кривая в параметрическом виде.
- •Интерполяционные кривые.
- •Сплайновые кривые.
- •Сплайновые кривые Эрмита.
- •Сплайновые кривые Безье.
- •Интерактивное изменение положения опорных точек сплайновых кривых с помощью мышки.
- •Интерактивное изменение числа опорных точек сплайновых кривых с помощью диалоговых окон.
- •Составные b-сплайновые кривые с кратными опорными точками
- •Нормализованные базисные функции b-сплайна.
Интерактивное изменение положения опорных точек сплайновых кривых с помощью мышки.
Современные графические системы предполагают наличие интерактивного взаимодействия с пользователем. Другими словами пользователь в диалоговом режиме может изменять создаваемое им графическое изображение.
Покажем на примере как можно в диалоговом режиме менять число и положение опорных точек для сплайновой кривой Безье порядка N. Кривую Безье будем строить по формулам (27). Кроме того, для разнообразия, будем строить плоскую кривую Безье.
Пусть код программы находится в файле line9.cpp. Покажем, как можно создать эту программу. Возьмем за основу программу из первой лабораторной работыmenuwin.cpp. Будем использовать меню, созданное в этой программе.
Создаем пустой проект line9 в каталогеline9. Перебрасываем в этот каталог файлыmenuwin.cpp,menuwin.rc,resource.rc. Переименовываем файл исходного кода и файл ресурсов в файлыline9.cpp,line9.rc. Подключаем эти два файла к проекту. Компилируем файл кода, компилируем файл ресурсов, создаем исполняемый файл. Запускаем программу и убеждаемся в правильной работе.
Теперь начнем вносить изменения в новую программу. Изменим имя класса окна и заголовок окна «Управление в интерактивном режиме сплайновыми кривыми Безье».
char cname[] = "Spline";
char title[] = " Interactive Control to Bezie Spline Curves";
Внесем изменения в названия пунктов меню. Откроем редактор меню. Выберем пункт Ellipse, названиеEllipseзаменим на General Bezier Spline, идентификаторID_ELLIPSEзаменим наID_GENERAL. Выберем пунктPolygon, названиеPolygonзаменим на Cubic Bezier Spline, идентификаторID_POLYGONзаменим наID_CUBIC.
В функции окна WndProcзаменим идентификаторыID_ELLIPSEиID_POLYGONна идентификаторыID_GENERALиID_CUBIC.
В файле resource.hнадо убрать строчки определения идентификаторовID_ELLIPSEиID_POLYGON.
При выборе пункта меню Cubic Bezier Splineдолжна быть нарисована кубическая кривая Безье. Поэтому в начале переименуем функциюPolygon_OnDCв функциюSplineDC. В новой функцииSplineDCдолжны рисоваться опорные точки, ломаная линия, связывающая эти точки и сплайновая кривая Безье произвольного порядкаN.
Порядок Nбудет глобальной переменной, которую обозначимNorder. Сделаем глобальной переменнойNpointsчисло опорных точек. Поэтому в начале программы появятся строчки кода.
//порядок кривой Безье
int Norder;
//число опорных точек
int Npoints;
В функции окна WndProcстрочки кода связанные с выбором пункта меню рисования кубической кривой Безье будут выглядеть следующим образом:
case ID_CUBIC:
Norder = 3;
SplineDC(hWnd);
break;
Введем массив опорных точек, сделав его глобальной переменной.
//массив опорных точек
POINT2 V[21];
Для вычисления функций Безье bN,i(t) , определенных формулами (27) создадим следующие три функции.
//вычисляется факториал
double Fact(int N)
{
double p=1;
if(N != 0)
for(int n=1; n<=N; n++)
p *= n;
return p;
}
//вычисляется коэффициенты для функций Безье
inline double Cb(int N, int i)
{
return Fact(N)/Fact(i)/Fact(N-i);
}
// функции Безье
double Bezie(int N, int i, double t)
{
double b;
double p1 = 1, p2 = 1;
if(i != 0)
for(int in=0; in<i; in++)
p1 *= t;
if((N-i) != 0)
for(int in=0; in<(N-i); in++)
p2 *= 1-t;
b = Cb(N,i)*p1*p2;
return b;
}
Теперь используя функции Безье и опорные векторы, создадим функцию для вычисления точек на кривой Безье в соответствии с формулой (31).
//координаты точки сплайновой кривой
POINT2 splineBezie(int N, double t)
{
POINT2 S;
S.x = 0; S.y = 0;
for(int i=0; i<=N; i++)
{
double Bez = Bezie(N,i,t);
S.x += V[i].x*Bez;
S.y += V[i].y*Bez;
}
return S;
}
Обратимся к функции SplineDCи уберем в ней строчки кода связанные с рисованием полигона. Оставим только рисование координатных осей. Уберем из файла о функции Ellipse_OnDC. В функции окнаWndProcв строчки кода, связанные с выбором пункта меню рисования общей кривой Безье добавим вызов функцииSplineDC.
case ID_GENERAL:
SplineDC(hWnd);
break;
Теперь если откомпилировать программу и запустить на выполнение, то при выборе пунктов меню или будут рисоваться только координатные оси.
Заполним функцию SplineDCстрочками кода для рисования опорных точек, ломаной линии, связывающая эти точки и сплайновой кривой Безье произвольного порядкаN.
Во-первых, задавать опорные точки будем с помощью генератора случайных чисел, как показано в строчках следующего кода.
Npoints = Norder + 1;
//задаем опорные точки с помощью генератора случайных чисел
double kr = 0.75;
for(int i=0; i<Npoints; i++)
{
V[i].x = kr*(xLeft + rand()*(xRight - xLeft)/RAND_MAX);
V[i].y = kr*(yBottom + rand()*(yTop - yBottom)/RAND_MAX);
}
Во-вторых, рисуем ломаную линию по опорным точкам. Имеем следующий код.
//рисуем ломаную линию по опорным точкам
MoveToEx(hdc,xn(V[0].x),ym(V[0].y),0);
for( i=1; i<Npoints; i++)
LineTo(hdc,xn(V[i].x),ym(V[i].y));
В-третьих, рисуем кривую Безье. Имеем следующий код.
//рисуем кривую Безье
int Nt = 10*Norder;
double t, dt = 1.0/(Nt-1);
MoveToEx(hdc,xn(splineBezie(Norder,0).x),ym(splineBezie(Norder,0).y),0);
for(int p=1; p<Nt; p++)
{
t = dt*p;
LineTo(hdc,xn(splineBezie(Norder,t).x),ym(splineBezie(Norder,t).y));
}
В-четвертых, рисуем опорные точки. Имеем следующий код.
//рисуем опорные точки
for(i=0; i<Npoints; i++)
Ellipse(hdc, xn(V[i].x)-4, ym(V[i].y)-4, xn(V[i].x)+4, ym(V[i].y)+4);
Теперь поставим задачу написать код, реализующий следующие действия. Подводим указатель мышки к заданной опорной точке, нажимаем левую клавишу и тащим опорную точку в другое место. Отпускаем левую клавишу и опорная точка отцепляется от указателя мышки. Затем эту операцию можно проделать с любой другой клавишей.
В функции окна WndProcдобавим строчки кода, связанные с обработкой сообщений: левая клавиша нажата -WM_LBUTTONDOWN, левая клавиша опущена -WM_LBUTTONUP.
case WM_LBUTTONDOWN:
SplineLButtonDown();
break;
case WM_LBUTTONUP:
SplineLButtonUp();
break;
Сообщения при нажатии и отпускании левой клавиши мышки обрабатываются функциями SplineLButtonDown()иSplineLButtonUp(). Поэтому в начале программы надо поставить прототипы этих функций.
void SplineLButtonDown();
void SplineLButtonUp();
Затем создадим эти функции.
//обрабатывает сообщение WM_LBUTTONDOWN
void SplineLButtonDown()
{
flagUp = 0;
flagDown = 1;
}
//обрабатывает сообщение WM_LBUTTONUP
void SplineLButtonUp()
{
flagUp = 1;
flagDown = 0;
flagLock = 0;
}
В этих простых функциях устанавливаются соответствующие флаги. Флаг flagDown=0,1обозначает состояние - нажата или нет левая клавиша. Флаг flagUp=0,1обозначает состояние - отпущена или нет левая клавиша. ФлагflagLock=0,1обозначает состояние - захвачена или нет опорная точка.
Флаги сделаем глобальными переменными, и в начале программы напишем строчку.
//флаги
int flagDown, flagUp, flagLock;
Начальные значения флагов удобно установить в ответ на сообщение WM_CREATE, которое приходит в начале создания окна приложения. Поэтому в функции окнаWndProcдобавим строчки кода:
//сообщение при открытии окна
case WM_CREATE:
SplineCreate (hWnd); //выход из цикла сообщений
break;
Создадим также функцию SplineCreate(), где зададим начальные значения флагов.
//обрабатывает сообщение WM_CREATE
void SplineCreate(HWND hWnd)
{
flagDown = 0; flagUp = 0; flagLock = 0;
}
Теперь надо обработать сообщение о движении мышки и обработать координаты указателя мышки (x,y). Для этого в функции окнаWndProcдобавим строчки кода, связанные с обработкой сообщения о движении мышки -WM_MOUSEMOVE.
case WM_MOUSEMOVE:
x = LOWORD(lParam);
y = HIWORD(lParam);
SplineMouseMove(hWnd, x, y);
break;
Здесь функция SplineMouseMove()обрабатывает сообщение о движении мыши, и в качестве параметров принимает координаты указателя мыши. Алгоритм должен быть следующим. Если мышка двигается и нажата левая клавиша, если не зацеплена ни какая опорная точка, и если координаты указателя мышки (x,y) оказались в окрестности опорной точкиV[i], то эта точка должна быть зацепленной. Этой точке присваиваются координаты указателя мышки. Затем картина должна быть перерисована с новым расположением опорной точкиV[i]. Перерисовывание будет выполняться функциейLineDC().
Ниже приводим код функции SplineMouseMove().
void SplineMouseMove(HWND hwnd, int x, int y)
{
if(flagDown)
{
if(!flagLock)
for(int i=0; i<Npoints; i++)
if(abs(x-xn(Pt[i].x))+abs(y-ym(Pt[i].y))<10)
{
Nlock = i;
flagLock = 1;
}
if(flagLock)
{
V[Nlock].x = nx(x); V[Nlock].y = my(y);
LineDC(hwnd);
}
}
}
В этой функции встречается глобальная переменная Nlock, которая равна номеру зацепленной опорной точки. Поэтому добавим в начале программы строчку кода.
//номер зацепленной опорной точки
int Nlock;
Кроме того, в функции SplineMouseMove()вызываются новые функцииnx()иmy(), которые являются обратными к функциямxn()иym(). Функцииxn()иym()переводя мировые координаты в экранные координаты, а функцииnx()иmy()экранные координаты в мировые.
Ниже приводим код этих функций.
//переход от пикселя n к x
inline double nx(int n)
{
return (n - nLeft)*(xRight - xLeft)/(nRight - nLeft) + xLeft;
}
//переход от пикселя m к y
inline double my(int m)
{
return (m - mBottom)*(yTop - yBottom)/(mTop - mBottom) + yBottom;
}
В тот момент, когда зацепляется опорная точка V[i], и ей передаются координаты указателя мышки, вызывается функцияLineDC()для перерисовывания всей картины с новым положением опорной точкиV[i]. Рисовать новую картину будем в контексте памяти, и затем мгновенно выбрасывать на экран с помощью функции BitBlt().
Функция LineDC()и функцияSplineDC()имеют одинаковую структуру, отличие в том, что функцияSplineDC()рисует в контексте экран, а функцияLineDC()рисует в контексте памяти. Ниже приводим код функцииLineDC().
void LineDC(HWND hwnd)
{
//получаем контекст устройства <hdc> для окна <hwnd>
HDC hdc = GetDC(hwnd);
//создаем контекст памяти hdcMem, совместимый с контекстом экрана hdc
HDC hdcMem = CreateCompatibleDC(hdc);
//создаем (пустую) битовую карту совместимую с контекстом экрана
HBITMAP hBmp = CreateCompatibleBitmap(hdc, nRight, mBottom);
//выбираем битовую карту hBmp в контекст памяти hdcMem
HBITMAP hBmpOld = (HBITMAP)SelectObject(hdcMem, hBmp);
HPEN hpen0 = CreatePen(PS_SOLID,2,RGB(0xC0,0xC0,0xC0));
HPEN hpenOld = (HPEN)SelectObject(hdcMem,hpen0);
HBRUSH hbrush0 = CreateSolidBrush(RGB(0xC0,0xC0,0xC0));
HBRUSH hbrushOld = (HBRUSH)SelectObject(hdcMem,hbrush0);
Rectangle(hdcMem,0,0,nRight,mBottom);
HPEN hpen1 = CreatePen(PS_SOLID,2,RGB(0,255,255));
SelectObject(hdcMem,hpen1);
int nb, mb, ne, me;
//рисуем ось OX
nb = xn(xLeft); mb = ym(0);
MoveToEx(hdcMem, nb, mb, NULL);
ne = xn(xRight); me = ym(0);
LineTo(hdcMem,ne,me);
//рисуем ось OY
nb = xn(0); mb = ym(yBottom);
MoveToEx(hdcMem, nb, mb, NULL);
ne = xn(0); me = ym(yTop);
LineTo(hdcMem,ne,me);
HPEN hpen2 = CreatePen(PS_SOLID,2,RGB(255,255,255));
SelectObject(hdcMem,hpen2);
//рисуем ломаную линию по опорным точкам
MoveToEx(hdcMem,xn(V[0].x),ym(V[0].y),0);
for( int i=1; i<Npoints; i++)
LineTo(hdcMem,xn(V[i].x),ym(V[i].y));
HPEN hpen3 = CreatePen(PS_SOLID,2,RGB(0,255,0));
SelectObject(hdcMem,hpen3);
//рисуем кривую Безье
int Nt = 10*Norder;
double t, dt = 1.0/(Nt-1);
MoveToEx(hdcMem,xn(splineBezie(Norder,0).x),ym(splineBezie(Norder,0).y),0);
for(int p=1; p<Nt; p++)
{
t = dt*p;
LineTo(hdcMem,xn(splineBezie(Norder,t).x),ym(splineBezie(Norder,t).y));
}
HPEN hpen4 = CreatePen(PS_SOLID,1,RGB(0,255,255));
SelectObject(hdcMem,hpen4);
HBRUSH hbrush = CreateSolidBrush(RGB(255,0,0));
SelectObject(hdcMem,hbrush);
//рисуем опорные точки в виде кружков
for(i=0; i<Npoints; i++)
Ellipse(hdcMem, xn(V[i].x)-4, ym(V[i].y)-4, xn(V[i].x)+4, ym(V[i].y)+4);
//копируем контекст памяти в контекст экрана
BitBlt(hdc, 0, 0, nRight, mBottom, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem,hbrushOld);
DeleteObject(hbrush0);
DeleteObject(hbrush);
SelectObject(hdcMem,hpenOld);
DeleteObject(hpen0);
DeleteObject(hpen1);
DeleteObject(hpen2);
DeleteObject(hpen3);
DeleteObject(hpen4);
DeleteObject(hBmp);
DeleteDC(hdcMem);
ReleaseDC(hwnd, hdc);
}
Если теперь откомпилировать программу и запустить на выполнение файл line9.exe, то с помощью мышки можно перемещать опорные точки кубической сплайновой кривой Безье.