Построение кривой Безье

bezie.jpg

Для заданной последовательности точек построить кривую Безье.

Кривая Безье - это параметрическая кривая n-го порядка, которая задается следующей формулой:
x(t) = Cn0 t0(1-t) nx0 + Cn1 t 1(1-t)n-1x1 + Cn2 t 2(1-t)n-2x2 + ... + Cnn t n(1-t)0xn
В этой формуле xi - абсцисса i-ой точки, параметр t лежит в интервале от 0 до 1. Подобным образом задаются другие координаты.
Если задана последовательность, состоящая из (n-1) точек, то такая последовательность точек, называемая ломаной Безье, однозначно определяет форму кривой Безье. Изменяя положения вершин ломаной, можно управлять формой соот­ветствующей кривой Безье.

Для построения кривой Безье необходимо задать в клиентской области окна множество точек, используя мышь. Затем, после выбора пункта меню "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;
}

Ключевые слова: 
кривая Безье, построение кривой Безье, аппроксимация кривой Безье
ВложениеРазмер
opita.net_Bcurves.rar1.1 Мб