Для заданной последовательности точек построить кривую Безье. Кривая Безье - это параметрическая кривая n-го порядка, которая задается следующей формулой: Для построения кривой Безье необходимо задать в клиентской области окна множество точек, используя мышь. Затем, после выбора пункта меню "Draw curve" можно изменять форму кривой с помощью мыши. Пункт меню "New curve" предназначен для удаления текущей кривой и выбора точек для построения новой. #include "stdafx.h" // Заголовочный файл проекта, в который включен файл "windows.h" (#include <windows.h>). #include "Bezier curves.h" #include <vector> using namespace std; // Директива, позволяющая обращаться к // средствам пространства имен стандартной библиотеки // без квалификаторов доступа. #define MAX_LOADSTRING 100 HINSTANCE hInst; // Дескриптор экземпляра приложения. TCHAR szTitle[MAX_LOADSTRING]; // Строка, хранящая текст заголовка окна. TCHAR szWindowClass[MAX_LOADSTRING]; // Имя класса окна. ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Функция, вычисляющая значение х и у координат точки на кривой. POINT CalcBezierCurve(vector<POINT>, const double&); // Процедура отрисовки кривой Безье. void DrawBezier(HDC, vector<POINT>); // Функция, возвращающая номер точки массива, // по которой пользователь щелкает мышью. int GetNumberOfPoint (int, int, vector<POINT>); // Главная функция программы, в которой запускается цикл обработки сообщений. int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); MSG msg; LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_BEZIERCURVES, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance); if (!InitInstance (hInstance, nCmdShow)) { return FALSE; } while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int) msg.wParam; } // Регистрация класса окна. ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_BEZIERCURVES)); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = MAKEINTRESOURCE(IDC_BEZIERCURVES); wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); return RegisterClassEx(&wcex); } // Функция, сохраняющая дескриптор экземпляра приложения и // создающая главное окно приложения. BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; hInst = hInstance; hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; } // Функция, вычисляющая значение х и у координат точки на кривой. // Принимает в качестве параметров вектор-массив точек и // параметр t, характеризующий положение на кривой. POINT CalcBezierCurve(vector<POINT> pts, const double& t) { int i, c; double p; POINT np; int n = static_cast<int>(pts.size()) - 1; c = 1; for (i = 0; i <= n; i++) { pts[i].x = pts[i].x * c; pts[i].y = pts[i].y * c; c = (n-i)*c/(i+1); } p = 1; for (i = 0; i <= n; i++) { pts[i].x = pts[i].x * p; pts[i].y = pts[i].y * p; p = p * t; } p = 1; for (i = n; i >= 0; i--) { pts[i].x = pts[i].x * p; pts[i].y = pts[i].y * p; p = p * (1-t); } np.x = 0; np.y = 0; for (i = 0; i <= n; i++) { np.x = np.x + pts[i].x; np.y = np.y + pts[i].y; } return np; } // Процедура отрисовки кривой Безье. // Принимает в качестве параметров дескриптор контекста устройства и // вектор-массив точек, по которым ведется построение кривой. void DrawBezier(HDC hdc, vector<POINT> pts) { double t; // Параметр, по которому будет идти вычисление точек на кривой: t = [0,...,1]. HPEN hPen; // Дескриптор пера, которым будем пользоваться для сохранения // разных стилей рисования линий для построения как отрезков, соединяющих // точки массива, так и самой кривой. POINT np = {0, 0}; // Точка, в которую будет заноситься результат выполнения функции CalcBezierCurve. // Перемещаем текущую позицию пера в первую точку массива. MoveToEx(hdc, pts[0].x, pts[0].y, NULL); // Присваиваем перу характеристику: пунктирная линия, толщина - 1 пиксель, синий цвет. hPen = CreatePen(PS_DASH, 1, RGB(0, 10, 170)); SelectObject(hdc, hPen); // Выбираем объект нашего пера в контекст устройства. // Строим пунктирные линии, соединяющие точки массива. for (int i = 0; i < pts.size(); i++) LineTo(hdc, pts[i].x, pts[i].y); // Присваиваем перу характеристику: сплошная линия, толщина - 2 пикселя, темно-красный цвет. hPen = CreatePen(PS_SOLID, 2, RGB(200, 50, 10)); SelectObject(hdc, hPen); // Выбираем объект нашего пера в контекст устройства. // Перемещаем текущую позицию пера в первую точку массива. MoveToEx(hdc, pts[0].x, pts[0].y, NULL); // Цикл отрисовки кривой Безье. // Для параметра t, пробегающего от 0 до 1 с шагом 0.3 (данный шаг обеспечивает умеренную сглаженость кривой) // вычисляем значение новой точки и строим линию, соединяющую предыдущую точку с новой. for (t = 0.0; t < 1.0; t += 0.03) { np = CalcBezierCurve(pts, t); LineTo(hdc, np.x, np.y); } LineTo(hdc, pts.back().x, pts.back().y); // Возвращаем стандартное перо в контекст устройства. SelectObject(hdc, (HPEN)GetStockObject(BLACK_PEN)); // Рисуем окружности, выделяющие точки массива. for (int i = 0; i < pts.size(); i++) Ellipse(hdc, pts[i].x-5, pts[i].y-5, pts[i].x+5, pts[i].y+5); } // Функция, возвращающая номер точки массива, // по которой пользователь щелкает мышью. // Принимает в качестве параметров х и у координаты точки щелчка // и вектор-массив точек, задающих кривую. int GetNumberOfPoint (int x, int y, vector<POINT> P) { int lim = static_cast<int>(P.size()); // Проходим по массиву точек, for (int i = 0; i < lim; i++) { // Если щелчок произвелся по точке или в непосредственной близости от нее, то if ((x > P[i].x-15 && x < P[i].x+15) && (y > P[i].y-15 && y < P[i].y+15)) // возвращаем номер этой точки (индекс в массиве). return i; } // Если щелчок произвелся не по точке массива, возвращаем отрицательное число. return -1; } // Оконная процедура, обеспечивающая обработку сообщений для основного окна программы. LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { static bool IsSelected = false; // Булевская переменная, служащая для распознания, выбран ли набор точек // для кривой или еще нет. Начальное значение - "ложь", не выбран. static vector<POINT> pts; // Вектор-массив точек, по которым будем строить кривую. PAINTSTRUCT ps; HDC hdc; // Контекст устройства. switch (message) // Обработка текущего сообщения. { case WM_LBUTTONDOWN: // Нажата левая кнопка мыши. // Если точки еще не выбраны, заносим координаты точки в массив и // рисуем окружность для выделения места нажатия. if (!IsSelected) { hdc = GetDC(hWnd); POINT tmp; tmp.x = LOWORD(lParam); tmp.y = HIWORD(lParam); pts.push_back(tmp); Ellipse(hdc, pts.back().x-5, pts.back().y-5, pts.back().x+5, pts.back().y+5); ReleaseDC(hWnd, hdc); } break; case WM_MOUSEMOVE: // Сообщение от перемещения мыши, обрабатывается только тогда, когда выбран набор точек и // после отрисовки кривой (выбран пункт меню "Draw curve"). if (IsSelected) { // Если при перемещении мыши нажата левая кнопка, if (wParam && MK_LBUTTON) { hdc = GetDC (hWnd); // то находим номер точки массива, если щелчок произвелся по одной из его точек. int mnp = GetNumberOfPoint(LOWORD(lParam), HIWORD(lParam), pts); if (mnp < 0) break; // Заносим в точку массива с номером выбранной точки новые координаты. pts[mnp].x = LOWORD (lParam) ; pts[mnp].y = HIWORD (lParam) ; // Посылаем сообщение для перерисовки клиентской обдасти окна, и, следовательно, самой кривой. SendMessage(hWnd, WM_PAINT, NULL, NULL); ReleaseDC (hWnd, hdc); } } break; case WM_COMMAND: // Обработка сообщений, поступающих при выборе пунктов меню. switch (LOWORD(wParam)) // Обработка значения идентификатора пункта меня. { case IDM_EXIT: // Меню File -> Exit // Выход. DestroyWindow(hWnd); break; case ID_CURVE_DRAWCURVE: // Меню Curve -> Draw curve // Если размер массива точек равен нулю, т. е. пользователь не выбрал ни одной точки, то // выводим сообщение об ошибке. if (pts.size() == 0) { MessageBox(hWnd, "There is necessary to put at least one point to client area of window!", "An error occured!", NULL); break; } // Иначе - присваиваем переменной IsSelected значение "истина" (означающая, что пользователь выбрал набор точек) IsSelected = true; // и осуществляем перерисовку. SendMessage(hWnd, WM_PAINT, NULL, NULL); break; case ID_CURVE_NEWCURVE: // Меню Curve -> New curve // Очищаем массив точек, IsSelected присваиваем "ложь" и осуществляем перерисовку. for (int i = pts.size(); i > 0; i--) pts.pop_back(); IsSelected = false; SendMessage(hWnd, WM_PAINT, NULL, NULL); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_PAINT: // Перерисовка клиенской области окна. // Объявление всей клиентской области подлежащей перерисовке. InvalidateRect(hWnd, NULL, TRUE); hdc = BeginPaint(hWnd, &ps); // Если массив точек не пуст, то рисуем кривую по данным точкам. if (pts.size() != 0) DrawBezier(hdc, pts) ; EndPaint(hWnd, &ps); break; case WM_DESTROY: // Выход. PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
Ключевые слова:
кривая Безье, построение кривой Безье, аппроксимация кривой Безье
|
|||||||