--- a
+++ b/distortion.cpp
@@ -0,0 +1,283 @@
+#include <windows.h>
+#include <direct.h>
+#include <newutil.h>
+#include <winutil.h>
+#include <distortion.h>
+
+#pragma warning(disable: 4018)
+
+//#include <linear_fit.h>
+
+const double mouse_gain = 0.04;
+const double mouse_accel = 0.005;
+const double cursor_radius = 0.2;
+const color grid_color(1);
+const color cursor_color(0.5, 0.25, 0.25);
+const color drag_color(1, 0, 0);
+const color text_color(0.5);
+const color fit_color(0.25, 0.25, 0.5);
+const string data_directory = "C:\\Programming\\graphics\\calibrate_screen\\data";
+const string default_calib_file = data_directory + "\\calib.dat";
+
+distortion::distortion()
+{
+	distortion_options opt;
+	init(opt);
+}
+
+distortion::distortion(const distortion_options &opt)
+{
+	init(opt);
+}
+
+void distortion::init(const distortion_options &opt)
+{
+	_opt = opt;
+	_grid0 = _grid = 0;
+	make_grid(_grid0);
+	make_grid(_grid);
+	//_fit = false;
+}
+
+distortion::~distortion()
+{
+	free_grid(_grid0);
+	free_grid(_grid);
+}
+
+void distortion::make_grid(v2 **&g)
+{
+	free_grid(g);
+	g = new v2*[_opt.n_grid_x + 1];
+	const double i0 = double(_opt.n_grid_x)/2, j0 = double(_opt.n_grid_y)/2;
+	for(int i = 0; i <= _opt.n_grid_x; i++) {
+		g[i] = new v2[_opt.n_grid_y + 1];
+		for(int j = 0; j <= _opt.n_grid_y; j++)
+			g[i][j] = _opt.grid_center +
+				v2(_opt.cell_size(0)*(i - i0), _opt.cell_size(1)*(j - j0));
+	}
+}
+
+void distortion::free_grid(v2 **&g)
+{
+	if(g) {
+		for(int i = 0; i <= _opt.n_grid_x; i++)
+			delete [] g[i];
+		delete [] g;
+		g = 0;
+	}
+}
+
+void distortion::show_grid(ideo *pid)
+{
+	pid->set_color(grid_color);
+	for(int i = 0; i <= _opt.n_grid_x; i++)
+		for(int j = 0; j <= _opt.n_grid_y - 1; j++)
+			pid->line(_grid[i][j], _grid[i][j + 1]);
+	for(int j = 0; j <= _opt.n_grid_y; j++)
+		for(int i = 0; i <= _opt.n_grid_x - 1; i++)
+			pid->line(_grid[i][j], _grid[i + 1][j]);
+}
+
+void distortion::move_node(int i, int j, const v2 &disp)
+{
+	_grid[i][j] += disp;
+}
+
+void distortion::set_node(int i, int j, const v2 &pos)
+{
+	_grid[i][j] = pos;
+}
+
+/*
+void distortion::fit()
+{
+	for(int d = 0; d < 2; d++) {
+		vector<fit_data_t> data;
+		for(int i = 0; i <= _opt.n_grid_x; i++)
+			for(int j = 0; j <= _opt.n_grid_y; j++) {
+				data.push_back(fit_data_t(Vd(_grid0[i][j](0), _grid0[i][j](1)),
+					_grid[i][j](d)));
+			}
+		multidimensional_polynomial_fit(data, _opt.degree, _fit_coeff[d]);
+	}
+	_fit = true;
+}
+
+void distortion::show_fit(ideo *pid)
+{
+	if(!_fit) return;
+	pid->set_color(fit_color);
+	for(int i = 0; i <= _opt.n_grid_x; i++)
+		for(int j = 0; j <= _opt.n_grid_y - 1; j++) {
+			const v2 fr(
+				multidimensional_polynomial(Vd(_grid0[i][j](0), _grid0[i][j](1)), _fit_coeff[0]),
+				multidimensional_polynomial(Vd(_grid0[i][j](0), _grid0[i][j](1)), _fit_coeff[1]));
+			const v2 to(
+				multidimensional_polynomial(Vd(_grid0[i][j + 1](0), _grid0[i][j + 1](1)), _fit_coeff[0]),
+				multidimensional_polynomial(Vd(_grid0[i][j + 1](0), _grid0[i][j + 1](1)), _fit_coeff[1]));
+			pid->line(fr, to);
+		}
+	for(int j = 0; j <= _opt.n_grid_y; j++)
+		for(int i = 0; i <= _opt.n_grid_x - 1; i++) {
+			const v2 fr(
+				multidimensional_polynomial(Vd(_grid0[i][j](0), _grid0[i][j](1)), _fit_coeff[0]),
+				multidimensional_polynomial(Vd(_grid0[i][j](0), _grid0[i][j](1)), _fit_coeff[1]));
+			const v2 to(
+				multidimensional_polynomial(Vd(_grid0[i + 1][j](0), _grid0[i + 1][j](1)), _fit_coeff[0]),
+				multidimensional_polynomial(Vd(_grid0[i + 1][j](0), _grid0[i + 1][j](1)), _fit_coeff[1]));
+			pid->line(fr, to);
+		}
+}
+*/
+
+// returns true if successful
+bool distortion::calc(const v2 &phys, v2 &log)
+{
+	v2 disp(0);
+	double sum_weights = 0;
+	for(int i = 0; i <= _opt.n_grid_x; i++)
+		for(int j = 0; j <= _opt.n_grid_y; j++) {
+			double w;
+			const double r = (phys - _grid0[i][j]).length2();
+			if(r > 0) {
+				w = pow(r, -_opt.calc_power/2);
+				if(w > _opt.max_weight)
+					w = _opt.max_weight;
+			}
+			else
+				w = _opt.max_weight;
+			disp += w*(_grid[i][j] - _grid0[i][j]);
+			sum_weights += w;
+		}
+	if(sum_weights == 0) return false;
+	log = phys + disp/sum_weights;
+	return true;
+}
+
+void distortion::save(const string &fn, const string &comment, bool append)
+{
+	ensure_dir(data_directory);
+	char old_dir[_MAX_PATH];
+	getcwd(old_dir, _MAX_PATH);
+	ensure_dir(data_directory, true);
+
+	try {
+		string my_comment = string("screen calibration ") + date_time();
+		if(comment != "") my_comment += "\n" + comment;
+		ofstream out((fn != "" ? fn.c_str() : default_calib_file.c_str()), append ? ios::app : ios::out);
+		out << "# ";
+		for(string::const_iterator it = my_comment.begin(); it != my_comment.end(); it++) {
+			if(*it != '\n')
+				out << *it;
+			else
+				out << endl << "# ";
+		}
+		out << endl;
+		out << "grid " << _opt.n_grid_x << " " << _opt.n_grid_y << endl;
+		for(int i = 0; i <= _opt.n_grid_x; i++)
+			for(int j = 0; j <= _opt.n_grid_y; j++)
+				out << _grid0[i][j](0) << '\t' << _grid0[i][j](1) << '\t'
+					<< _grid[i][j](0) << '\t' << _grid[i][j](1) << endl;
+		out.close();
+	}
+	catch(error err) {
+		chdir(old_dir);
+		throw(err);
+	}
+	chdir(old_dir);
+}
+
+void distortion::load(const string &fn)
+{
+	free_grid(_grid0);
+	free_grid(_grid);
+	_comment = "";
+
+	ifstream in(fn != "" ? fn.c_str() : default_calib_file.c_str());
+	string line;
+	while(true) {
+		getline(in, line);
+		if(!in) throw(error("distortion", "unexpected end-of-file"));
+		trim_space(line);
+		if(line == "") continue;
+		if(line[0] != '#') break;
+		line.erase(0, 1);
+		trim_space(line);
+		if(_comment != "") _comment += "\n";
+		_comment += line;
+	}
+	int n_grid_x, n_grid_y;
+	if(sscanf(line.c_str(), "grid %d %d", &n_grid_x, &n_grid_y) != 2)
+		throw(error("distortion", "format error"));
+	_opt.n_grid_x = n_grid_x;
+	_opt.n_grid_y = n_grid_y;
+	make_grid(_grid0);
+	make_grid(_grid);
+	for(int i = 0; i <= _opt.n_grid_x; i++)
+		for(int j = 0; j <= _opt.n_grid_y; j++) {
+			if(!in) throw(error("distortion", "format error"));
+			in >> _grid0[i][j](0) >> _grid0[i][j](1)
+				>> _grid[i][j](0) >> _grid[i][j](1);
+		}
+	in.close();
+}
+
+void distortion::calibrate(ideo *pid)
+{
+	ideo_state state;
+	pid->get_state(state);
+	v2 cursor(_opt.grid_center);
+	bool dragging = false, drag_all;
+	int drag_i, drag_j;
+
+	while(true) {
+		if(pid->mouse_right_button()) break;
+
+		const bool button = pid->mouse_button();
+		if(button && !dragging) {
+			dragging = true;
+			drag_all = key_down(VK_SHIFT);
+			double min_dist2 = DBL_MAX;
+			for(int i = 0; i <= _opt.n_grid_x; i++)
+				for(int j = 0; j <= _opt.n_grid_y; j++) {
+					const double dist2 = (_grid[i][j] - cursor).length2();
+					if(dist2 < min_dist2) {
+						min_dist2 = dist2;
+						drag_i = i;
+						drag_j = j;
+					}
+				}
+			cursor = _grid[drag_i][drag_j];
+		}
+		if(!button && dragging) {
+			dragging = false;
+		}
+
+		const v2 disp = pid->mouse();
+		const double dist = disp.length();
+		if(dist > 0) {
+			const v2 dir = disp.normalize();
+			cursor += (mouse_gain*dist + mouse_accel*dist*dist)*dir;
+			if(cursor(0) < state.rf.minx) cursor(0) = state.rf.minx;
+			if(cursor(0) > state.rf.maxx) cursor(0) = state.rf.maxx;
+			if(cursor(1) < state.rf.miny) cursor(1) = state.rf.miny;
+			if(cursor(1) > state.rf.maxy) cursor(1) = state.rf.maxy;
+		}
+		if(dragging) set_node(drag_i, drag_j, cursor);
+		pid->set_color(dragging ? drag_color : cursor_color);
+		pid->disk(cursor, cursor_radius);
+
+		v2 log;
+		calc(cursor, log);
+		pid->line(cursor, log);
+
+		pid->set_color(text_color);
+		pid->text_pixel(state.width, state.height, DT_RIGHT | DT_BOTTOM, 
+			"right-click when done");
+
+		show_grid(pid);
+
+		pid->show();
+	}
+}