curvedraw

changeset 12:84a647283237

added all the extra functionality to the curve class first pass at a project-to-curve function (needs more work)
author John Tsiombikas <nuclear@member.fsf.org>
date Sun, 20 Dec 2015 07:21:32 +0200
parents 099fd7adb900
children 4da693339d99
files src/app.cc src/curve.cc src/curve.h src/curvefile.cc
diffstat 4 files changed, 378 insertions(+), 98 deletions(-) [+]
line diff
     1.1 --- a/src/app.cc	Sat Dec 19 22:39:18 2015 +0200
     1.2 +++ b/src/app.cc	Sun Dec 20 07:21:32 2015 +0200
     1.3 @@ -30,6 +30,8 @@
     1.4  static float grid_size = 1.0;
     1.5  static SnapMode snap_mode;
     1.6  
     1.7 +static bool show_bounds;
     1.8 +
     1.9  static std::vector<Curve*> curves;
    1.10  static Curve *sel_curve;	// selected curve being edited
    1.11  static Curve *new_curve;	// new curve being entered
    1.12 @@ -38,9 +40,7 @@
    1.13  
    1.14  static Label *weight_label;	// floating label for the cp weight
    1.15  
    1.16 -#ifdef DRAW_MOUSE_POINTER
    1.17  static Vector2 mouse_pointer;
    1.18 -#endif
    1.19  
    1.20  
    1.21  bool app_init(int argc, char **argv)
    1.22 @@ -151,6 +151,20 @@
    1.23  	int numpt = curve->size();
    1.24  	int segm = numpt * 16;
    1.25  
    1.26 +	if(show_bounds) {
    1.27 +		Vector3 bmin, bmax;
    1.28 +		curve->get_bbox(&bmin, &bmax);
    1.29 +
    1.30 +		glLineWidth(1.0);
    1.31 +		glColor3f(0, 1, 0);
    1.32 +		glBegin(GL_LINE_LOOP);
    1.33 +		glVertex2f(bmin.x, bmin.y);
    1.34 +		glVertex2f(bmax.x, bmin.y);
    1.35 +		glVertex2f(bmax.x, bmax.y);
    1.36 +		glVertex2f(bmin.x, bmax.y);
    1.37 +		glEnd();
    1.38 +	}
    1.39 +
    1.40  	glLineWidth(curve == hover_curve ? 4.0 : 2.0);
    1.41  	if(curve == sel_curve) {
    1.42  		glColor3f(0.3, 0.4, 1.0);
    1.43 @@ -162,7 +176,7 @@
    1.44  	glBegin(GL_LINE_STRIP);
    1.45  	for(int i=0; i<segm; i++) {
    1.46  		float t = (float)i / (float)(segm - 1);
    1.47 -		Vector2 v = curve->interpolate(t);
    1.48 +		Vector3 v = curve->interpolate(t);
    1.49  		glVertex2f(v.x, v.y);
    1.50  	}
    1.51  	glEnd();
    1.52 @@ -183,10 +197,23 @@
    1.53  				glColor3f(0.2, 1.0, 0.2);
    1.54  			}
    1.55  		}
    1.56 -		Vector2 pt = curve->get_point(i);
    1.57 +		Vector2 pt = curve->get_point2(i);
    1.58  		glVertex2f(pt.x, pt.y);
    1.59  	}
    1.60  	glEnd();
    1.61 +
    1.62 +	// draw the projected mouse point on the selected curve
    1.63 +	/*
    1.64 +	if(curve == sel_curve) {
    1.65 +		Vector3 pp = curve->proj_point(Vector3(mouse_pointer.x, mouse_pointer.y, 0.0));
    1.66 +
    1.67 +		glPointSize(5.0);
    1.68 +		glBegin(GL_POINTS);
    1.69 +		glColor3f(1, 0.8, 0.2);
    1.70 +		glVertex2f(pp.x, pp.y);
    1.71 +		glEnd();
    1.72 +	}
    1.73 +	*/
    1.74  	glPointSize(1.0);
    1.75  }
    1.76  
    1.77 @@ -218,38 +245,29 @@
    1.78  			}
    1.79  			break;
    1.80  
    1.81 -		case 'l':
    1.82 -		case 'L':
    1.83 +		case '1':
    1.84 +		case '2':
    1.85 +		case '3':
    1.86  			if(sel_curve) {
    1.87 -				sel_curve->set_type(CURVE_LINEAR);
    1.88 +				sel_curve->set_type((CurveType)((int)CURVE_LINEAR + key - '1'));
    1.89  				post_redisplay();
    1.90  			}
    1.91  			if(new_curve) {
    1.92 -				new_curve->set_type(CURVE_LINEAR);
    1.93 +				new_curve->set_type((CurveType)((int)CURVE_LINEAR + key - '1'));
    1.94  				post_redisplay();
    1.95  			}
    1.96  			break;
    1.97  
    1.98  		case 'b':
    1.99  		case 'B':
   1.100 -			if(sel_curve) {
   1.101 -				sel_curve->set_type(CURVE_BSPLINE);
   1.102 -				post_redisplay();
   1.103 -			}
   1.104 -			if(new_curve) {
   1.105 -				new_curve->set_type(CURVE_BSPLINE);
   1.106 -				post_redisplay();
   1.107 -			}
   1.108 +			show_bounds = !show_bounds;
   1.109 +			post_redisplay();
   1.110  			break;
   1.111  
   1.112 -		case 'h':
   1.113 -		case 'H':
   1.114 +		case 'n':
   1.115 +		case 'N':
   1.116  			if(sel_curve) {
   1.117 -				sel_curve->set_type(CURVE_HERMITE);
   1.118 -				post_redisplay();
   1.119 -			}
   1.120 -			if(new_curve) {
   1.121 -				new_curve->set_type(CURVE_HERMITE);
   1.122 +				sel_curve->normalize();
   1.123  				post_redisplay();
   1.124  			}
   1.125  			break;
   1.126 @@ -263,14 +281,19 @@
   1.127  			printf("exported %d curves\n", (int)curves.size());
   1.128  			break;
   1.129  
   1.130 -		case 'i':
   1.131 -		case 'I':
   1.132 +		case 'l':
   1.133 +		case 'L':
   1.134  			{
   1.135  				std::list<Curve*> clist = load_curves("test.curves");
   1.136  				if(clist.empty()) {
   1.137  					fprintf(stderr, "failed to import curves\n");
   1.138  				}
   1.139  
   1.140 +				for(size_t i=0; i<curves.size(); i++) {
   1.141 +					delete curves[i];
   1.142 +				}
   1.143 +				curves.clear();
   1.144 +
   1.145  				int num = 0;
   1.146  				std::list<Curve*>::iterator it = clist.begin();
   1.147  				while(it != clist.end()) {
   1.148 @@ -279,6 +302,7 @@
   1.149  				}
   1.150  				printf("imported %d curves\n", num);
   1.151  			}
   1.152 +			post_redisplay();
   1.153  			break;
   1.154  		}
   1.155  	}
   1.156 @@ -402,10 +426,8 @@
   1.157  	if(!dx && !dy) return;
   1.158  
   1.159  	Vector2 uv = pixel_to_uv(x, y);
   1.160 -#ifdef DRAW_MOUSE_POINTER
   1.161  	mouse_pointer = uv;
   1.162  	post_redisplay();
   1.163 -#endif
   1.164  
   1.165  	/* when entering a new curve, have the last (extra) point following
   1.166  	 * the mouse until it's entered by a click (see on_click).
     2.1 --- a/src/curve.cc	Sat Dec 19 22:39:18 2015 +0200
     2.2 +++ b/src/curve.cc	Sun Dec 20 07:21:32 2015 +0200
     2.3 @@ -1,10 +1,39 @@
     2.4  #include <float.h>
     2.5 +#include <assert.h>
     2.6  #include <algorithm>
     2.7  #include "curve.h"
     2.8  
     2.9  Curve::Curve(CurveType type)
    2.10  {
    2.11  	this->type = type;
    2.12 +	bbvalid = true;
    2.13 +}
    2.14 +
    2.15 +Curve::Curve(const Vector4 *cp, int numcp, CurveType type)
    2.16 +	: Curve(type)
    2.17 +{
    2.18 +	this->cp.resize(numcp);
    2.19 +	for(int i=0; i<numcp; i++) {
    2.20 +		this->cp[i] = cp[i];
    2.21 +	}
    2.22 +}
    2.23 +
    2.24 +Curve::Curve(const Vector3 *cp, int numcp, CurveType type)
    2.25 +	: Curve(type)
    2.26 +{
    2.27 +	this->cp.resize(numcp);
    2.28 +	for(int i=0; i<numcp; i++) {
    2.29 +		this->cp[i] = Vector4(cp[i].x, cp[i].y, cp[i].z, 1.0f);
    2.30 +	}
    2.31 +}
    2.32 +
    2.33 +Curve::Curve(const Vector2 *cp, int numcp, CurveType type)
    2.34 +	: Curve(type)
    2.35 +{
    2.36 +	this->cp.resize(numcp);
    2.37 +	for(int i=0; i<numcp; i++) {
    2.38 +		this->cp[i] = Vector4(cp[i].x, cp[i].y, 0.0f, 1.0f);
    2.39 +	}
    2.40  }
    2.41  
    2.42  void Curve::set_type(CurveType type)
    2.43 @@ -17,9 +46,20 @@
    2.44  	return type;
    2.45  }
    2.46  
    2.47 +void Curve::add_point(const Vector4 &p)
    2.48 +{
    2.49 +	cp.push_back(p);
    2.50 +	inval_bounds();
    2.51 +}
    2.52 +
    2.53 +void Curve::add_point(const Vector3 &p, float weight)
    2.54 +{
    2.55 +	add_point(Vector4(p.x, p.y, p.z, weight));
    2.56 +}
    2.57 +
    2.58  void Curve::add_point(const Vector2 &p, float weight)
    2.59  {
    2.60 -	cp.push_back(Vector3(p.x, p.y, weight));
    2.61 +	add_point(Vector4(p.x, p.y, 0.0f, weight));
    2.62  }
    2.63  
    2.64  bool Curve::remove_point(int idx)
    2.65 @@ -28,23 +68,14 @@
    2.66  		return false;
    2.67  	}
    2.68  	cp.erase(cp.begin() + idx);
    2.69 +	inval_bounds();
    2.70  	return true;
    2.71  }
    2.72  
    2.73 -int Curve::nearest_point(const Vector2 &p)
    2.74 +void Curve::clear()
    2.75  {
    2.76 -	int res = -1;
    2.77 -	float bestsq = FLT_MAX;
    2.78 -
    2.79 -	for(size_t i=0; i<cp.size(); i++) {
    2.80 -		float d = (get_point(i) - p).length_sq();
    2.81 -		if(d < bestsq) {
    2.82 -			bestsq = d;
    2.83 -			res = i;
    2.84 -		}
    2.85 -	}
    2.86 -
    2.87 -	return res;
    2.88 +	cp.clear();
    2.89 +	inval_bounds();
    2.90  }
    2.91  
    2.92  bool Curve::empty() const
    2.93 @@ -57,29 +88,45 @@
    2.94  	return (int)cp.size();
    2.95  }
    2.96  
    2.97 -Vector3 &Curve::operator [](int idx)
    2.98 +Vector4 &Curve::operator [](int idx)
    2.99 +{
   2.100 +	inval_bounds();
   2.101 +	return cp[idx];
   2.102 +}
   2.103 +
   2.104 +const Vector4 &Curve::operator [](int idx) const
   2.105  {
   2.106  	return cp[idx];
   2.107  }
   2.108  
   2.109 -const Vector3 &Curve::operator [](int idx) const
   2.110 +const Vector4 &Curve::get_point(int idx) const
   2.111  {
   2.112  	return cp[idx];
   2.113  }
   2.114  
   2.115 -const Vector3 &Curve::get_homo_point(int idx) const
   2.116 +Vector3 Curve::get_point3(int idx) const
   2.117  {
   2.118 -	return cp[idx];
   2.119 +	return Vector3(cp[idx].x, cp[idx].y, cp[idx].z);
   2.120  }
   2.121  
   2.122 -Vector2 Curve::get_point(int idx) const
   2.123 +Vector2 Curve::get_point2(int idx) const
   2.124  {
   2.125  	return Vector2(cp[idx].x, cp[idx].y);
   2.126  }
   2.127  
   2.128  float Curve::get_weight(int idx) const
   2.129  {
   2.130 -	return cp[idx].z;
   2.131 +	return cp[idx].w;
   2.132 +}
   2.133 +
   2.134 +bool Curve::set_point(int idx, const Vector3 &p, float weight)
   2.135 +{
   2.136 +	if(idx < 0 || idx >= (int)cp.size()) {
   2.137 +		return false;
   2.138 +	}
   2.139 +	cp[idx] = Vector4(p.x, p.y, p.z, weight);
   2.140 +	inval_bounds();
   2.141 +	return true;
   2.142  }
   2.143  
   2.144  bool Curve::set_point(int idx, const Vector2 &p, float weight)
   2.145 @@ -87,7 +134,8 @@
   2.146  	if(idx < 0 || idx >= (int)cp.size()) {
   2.147  		return false;
   2.148  	}
   2.149 -	cp[idx] = Vector3(p.x, p.y, weight);
   2.150 +	cp[idx] = Vector4(p.x, p.y, 0.0, weight);
   2.151 +	inval_bounds();
   2.152  	return true;
   2.153  }
   2.154  
   2.155 @@ -96,7 +144,17 @@
   2.156  	if(idx < 0 || idx >= (int)cp.size()) {
   2.157  		return false;
   2.158  	}
   2.159 -	cp[idx].z = weight;
   2.160 +	cp[idx].w = weight;
   2.161 +	return true;
   2.162 +}
   2.163 +
   2.164 +bool Curve::move_point(int idx, const Vector3 &p)
   2.165 +{
   2.166 +	if(idx < 0 || idx >= (int)cp.size()) {
   2.167 +		return false;
   2.168 +	}
   2.169 +	cp[idx] = Vector4(p.x, p.y, p.z, cp[idx].w);
   2.170 +	inval_bounds();
   2.171  	return true;
   2.172  }
   2.173  
   2.174 @@ -105,22 +163,207 @@
   2.175  	if(idx < 0 || idx >= (int)cp.size()) {
   2.176  		return false;
   2.177  	}
   2.178 -	cp[idx] = Vector3(p.x, p.y, cp[idx].z);
   2.179 +	cp[idx] = Vector4(p.x, p.y, 0.0f, cp[idx].w);
   2.180 +	inval_bounds();
   2.181  	return true;
   2.182  }
   2.183  
   2.184 -Vector2 Curve::interpolate(float t, CurveType type) const
   2.185 +
   2.186 +int Curve::nearest_point(const Vector3 &p) const
   2.187  {
   2.188 -	if(cp.empty()) {
   2.189 -		return Vector2(0, 0);
   2.190 +	int res = -1;
   2.191 +	float bestsq = FLT_MAX;
   2.192 +
   2.193 +	for(size_t i=0; i<cp.size(); i++) {
   2.194 +		float d = (get_point3(i) - p).length_sq();
   2.195 +		if(d < bestsq) {
   2.196 +			bestsq = d;
   2.197 +			res = i;
   2.198 +		}
   2.199 +	}
   2.200 +	return res;
   2.201 +}
   2.202 +
   2.203 +int Curve::nearest_point(const Vector2 &p) const
   2.204 +{
   2.205 +	int res = -1;
   2.206 +	float bestsq = FLT_MAX;
   2.207 +
   2.208 +	for(size_t i=0; i<cp.size(); i++) {
   2.209 +		float d = (get_point2(i) - p).length_sq();
   2.210 +		if(d < bestsq) {
   2.211 +			bestsq = d;
   2.212 +			res = i;
   2.213 +		}
   2.214 +	}
   2.215 +	return res;
   2.216 +}
   2.217 +
   2.218 +
   2.219 +void Curve::inval_bounds() const
   2.220 +{
   2.221 +	bbvalid = false;
   2.222 +}
   2.223 +
   2.224 +void Curve::calc_bounds() const
   2.225 +{
   2.226 +	calc_bbox(&bbmin, &bbmax);
   2.227 +	bbvalid = true;
   2.228 +}
   2.229 +
   2.230 +void Curve::get_bbox(Vector3 *bbmin, Vector3 *bbmax) const
   2.231 +{
   2.232 +	if(!bbvalid) {
   2.233 +		calc_bounds();
   2.234 +	}
   2.235 +	*bbmin = this->bbmin;
   2.236 +	*bbmax = this->bbmax;
   2.237 +}
   2.238 +
   2.239 +void Curve::calc_bbox(Vector3 *bbmin, Vector3 *bbmax) const
   2.240 +{
   2.241 +	if(empty()) {
   2.242 +		*bbmin = *bbmax = Vector3(0, 0, 0);
   2.243 +		return;
   2.244 +	}
   2.245 +
   2.246 +	Vector3 bmin = cp[0];
   2.247 +	Vector3 bmax = bmin;
   2.248 +	for(size_t i=1; i<cp.size(); i++) {
   2.249 +		const Vector4 &v = cp[i];
   2.250 +		for(int j=0; j<3; j++) {
   2.251 +			if(v[j] < bmin[j]) bmin[j] = v[j];
   2.252 +			if(v[j] > bmax[j]) bmax[j] = v[j];
   2.253 +		}
   2.254 +	}
   2.255 +	*bbmin = bmin;
   2.256 +	*bbmax = bmax;
   2.257 +}
   2.258 +
   2.259 +void Curve::normalize()
   2.260 +{
   2.261 +	if(!bbvalid) {
   2.262 +		calc_bounds();
   2.263 +	}
   2.264 +
   2.265 +	Vector3 bsize = bbmax - bbmin;
   2.266 +	Vector3 boffs = (bbmin + bbmax) * 0.5;
   2.267 +
   2.268 +	Vector3 bscale;
   2.269 +	bscale.x = bsize.x == 0.0f ? 1.0f : 1.0f / bsize.x;
   2.270 +	bscale.y = bsize.y == 0.0f ? 1.0f : 1.0f / bsize.y;
   2.271 +	bscale.z = bsize.z == 0.0f ? 1.0f : 1.0f / bsize.z;
   2.272 +
   2.273 +	for(size_t i=0; i<cp.size(); i++) {
   2.274 +		cp[i].x = (cp[i].x - boffs.x) * bscale.x;
   2.275 +		cp[i].y = (cp[i].y - boffs.y) * bscale.y;
   2.276 +		cp[i].z = (cp[i].z - boffs.z) * bscale.z;
   2.277 +	}
   2.278 +	inval_bounds();
   2.279 +}
   2.280 +
   2.281 +/* Projection to the curve is not correct, but should be good enough for
   2.282 + * most purposes.
   2.283 + *
   2.284 + * First we find the nearest segment (pair of control points), and then
   2.285 + * we subdivide between them to find the nearest interpolated point in that
   2.286 + * segment.
   2.287 + * The incorrect assumption here is that the nearest segment as defined by
   2.288 + * the distance of its control points to p, will contain the nearest point
   2.289 + * on the curve. This only holds for polylines, and *possibly* bsplines, but
   2.290 + * certainly not hermite splines.
   2.291 + */
   2.292 +Vector3 Curve::proj_point(const Vector3 &p) const
   2.293 +{
   2.294 +	// TODO fix: select nearest segment based on point-line distance, not distance from the CPs
   2.295 +	int num_cp = size();
   2.296 +	if(num_cp <= 0) return p;
   2.297 +	if(num_cp == 1) return cp[0];
   2.298 +
   2.299 +	int idx0 = nearest_point(p);
   2.300 +	int next_idx = idx0 + 1;
   2.301 +	int prev_idx = idx0 - 1;
   2.302 +
   2.303 +	float next_distsq = next_idx >= num_cp ? FLT_MAX : (get_point3(next_idx) - p).length_sq();
   2.304 +	float prev_distsq = prev_idx < 0 ? FLT_MAX : (get_point3(prev_idx) - p).length_sq();
   2.305 +	int idx1 = next_distsq < prev_distsq ? next_idx : prev_idx;
   2.306 +	assert(idx1 >= 0 && idx1 < num_cp - 1);
   2.307 +	if(idx0 > idx1) std::swap(idx0, idx1);
   2.308 +
   2.309 +	float t0 = 0.0f, t1 = 1.0f;
   2.310 +	Vector3 pp0 = interpolate_segment(idx0, idx1, 0.0f);
   2.311 +	Vector3 pp1 = interpolate_segment(idx0, idx1, 1.0f);
   2.312 +	float dist0 = (pp0 - p).length_sq();
   2.313 +	float dist1 = (pp1 - p).length_sq();
   2.314 +	Vector3 pp;
   2.315 +
   2.316 +	for(int i=0; i<32; i++) {	// max iterations
   2.317 +		float t = (t0 + t1) / 2.0;
   2.318 +		pp = interpolate_segment(idx0, idx1, t);
   2.319 +		float dist = (pp - p).length_sq();
   2.320 +
   2.321 +		// mid point more distant than both control points, nearest cp is closest
   2.322 +		if(dist > dist0 && dist > dist1) {
   2.323 +			pp = dist0 < dist1 ? pp0 : pp1;
   2.324 +			break;
   2.325 +		}
   2.326 +
   2.327 +		if(dist0 < dist1) {
   2.328 +			t1 = t;
   2.329 +			dist1 = dist;
   2.330 +			pp1 = pp;
   2.331 +		} else {
   2.332 +			t0 = t;
   2.333 +			dist0 = dist;
   2.334 +			pp0 = pp;
   2.335 +		}
   2.336 +
   2.337 +		if(fabs(dist0 - dist1) < 1e-4) {
   2.338 +			break;
   2.339 +		}
   2.340 +	}
   2.341 +	return pp;
   2.342 +}
   2.343 +
   2.344 +Vector3 Curve::interpolate_segment(int a, int b, float t) const
   2.345 +{
   2.346 +	int num_cp = size();
   2.347 +
   2.348 +	if(t < 0.0) t = 0.0;
   2.349 +	if(t > 1.0) t = 1.0;
   2.350 +
   2.351 +	Vector4 res;
   2.352 +	if(type == CURVE_LINEAR || num_cp == 2) {
   2.353 +		res = lerp(cp[a], cp[b], t);
   2.354 +	} else {
   2.355 +		int prev = a <= 0 ? a : a - 1;
   2.356 +		int next = b >= num_cp - 1 ? b : b + 1;
   2.357 +
   2.358 +		if(type == CURVE_HERMITE) {
   2.359 +			res = catmull_rom_spline(cp[prev], cp[a], cp[b], cp[next], t);
   2.360 +		} else {
   2.361 +			res = bspline(cp[prev], cp[a], cp[b], cp[next], t);
   2.362 +			if(res.w != 0.0f) {
   2.363 +				res.x /= res.w;
   2.364 +				res.y /= res.w;
   2.365 +			}
   2.366 +		}
   2.367 +	}
   2.368 +
   2.369 +	return Vector3(res.x, res.y, res.z);
   2.370 +}
   2.371 +
   2.372 +Vector3 Curve::interpolate(float t) const
   2.373 +{
   2.374 +	if(empty()) {
   2.375 +		return Vector3(0, 0, 0);
   2.376  	}
   2.377  
   2.378  	int num_cp = (int)cp.size();
   2.379  	if(num_cp == 1) {
   2.380 -		return Vector2(cp[0].x, cp[0].y);
   2.381 +		return Vector3(cp[0].x, cp[0].y, cp[0].z);
   2.382  	}
   2.383  
   2.384 -	Vector3 res;
   2.385  	int idx0 = std::min((int)floor(t * (num_cp - 1)), num_cp - 2);
   2.386  	int idx1 = idx0 + 1;
   2.387  
   2.388 @@ -129,35 +372,17 @@
   2.389  	float t1 = (float)idx1 * dt;
   2.390  
   2.391  	t = (t - t0) / (t1 - t0);
   2.392 -	if(t < 0.0) t = 0.0;
   2.393 -	if(t > 1.0) t = 1.0;
   2.394  
   2.395 -	if(type == CURVE_LINEAR || num_cp <= 2) {
   2.396 -		res = lerp(cp[idx0], cp[idx1], t);
   2.397 -	} else {
   2.398 -		int idx_prev = idx0 <= 0 ? idx0 : idx0 - 1;
   2.399 -		int idx_next = idx1 >= num_cp - 1 ? idx1 : idx1 + 1;
   2.400 +	return interpolate_segment(idx0, idx1, t);
   2.401 +}
   2.402  
   2.403 -		if(type == CURVE_HERMITE) {
   2.404 -			res = catmull_rom_spline(cp[idx_prev], cp[idx0], cp[idx1], cp[idx_next], t);
   2.405 -		} else {
   2.406 -			res = bspline(cp[idx_prev], cp[idx0], cp[idx1], cp[idx_next], t);
   2.407 -			if(res.z != 0.0f) {
   2.408 -				res.x /= res.z;
   2.409 -				res.y /= res.z;
   2.410 -			}
   2.411 -		}
   2.412 -	}
   2.413 -
   2.414 +Vector2 Curve::interpolate2(float t) const
   2.415 +{
   2.416 +	Vector3 res = interpolate(t);
   2.417  	return Vector2(res.x, res.y);
   2.418  }
   2.419  
   2.420 -Vector2 Curve::interpolate(float t) const
   2.421 -{
   2.422 -	return interpolate(t, type);
   2.423 -}
   2.424 -
   2.425 -Vector2 Curve::operator ()(float t) const
   2.426 +Vector3 Curve::operator ()(float t) const
   2.427  {
   2.428  	return interpolate(t);
   2.429  }
     3.1 --- a/src/curve.h	Sat Dec 19 22:39:18 2015 +0200
     3.2 +++ b/src/curve.h	Sun Dec 20 07:21:32 2015 +0200
     3.3 @@ -12,37 +12,72 @@
     3.4  
     3.5  class Curve {
     3.6  private:
     3.7 -	std::vector<Vector3> cp;
     3.8 +	std::vector<Vector4> cp;
     3.9  	CurveType type;
    3.10  
    3.11 +	// bounding box
    3.12 +	mutable Vector3 bbmin, bbmax;
    3.13 +	mutable bool bbvalid;
    3.14 +
    3.15 +	void calc_bounds() const;
    3.16 +	void inval_bounds() const;
    3.17 +
    3.18  public:
    3.19  	Curve(CurveType type = CURVE_HERMITE);
    3.20 +	Curve(const Vector4 *cp, int numcp, CurveType type = CURVE_HERMITE); // homogenous
    3.21 +	Curve(const Vector3 *cp, int numcp, CurveType type = CURVE_HERMITE); // 3D points, w=1
    3.22 +	Curve(const Vector2 *cp, int numcp, CurveType type = CURVE_HERMITE); // 2D points, z=0, w=1
    3.23  
    3.24  	void set_type(CurveType type);
    3.25  	CurveType get_type() const;
    3.26  
    3.27 +	void add_point(const Vector4 &p);
    3.28 +	void add_point(const Vector3 &p, float weight = 1.0f);
    3.29  	void add_point(const Vector2 &p, float weight = 1.0f);
    3.30  	bool remove_point(int idx);
    3.31  
    3.32 -	int nearest_point(const Vector2 &p);
    3.33 +	void clear();		// remove all control points
    3.34 +	bool empty() const;	// true if 0 control points
    3.35 +	int size() const;	// returns number of control points
    3.36 +	// access operators for control points
    3.37 +	Vector4 &operator [](int idx);
    3.38 +	const Vector4 &operator [](int idx) const;
    3.39 +	const Vector4 &get_point(int idx) const;
    3.40  
    3.41 -	bool empty() const;
    3.42 -	int size() const;
    3.43 -	Vector3 &operator [](int idx);
    3.44 -	const Vector3 &operator [](int idx) const;
    3.45 -
    3.46 -	const Vector3 &get_homo_point(int idx) const;	// homogeneous point
    3.47 -	Vector2 get_point(int idx) const;
    3.48 +	Vector3 get_point3(int idx) const;
    3.49 +	Vector2 get_point2(int idx) const;
    3.50  	float get_weight(int idx) const;
    3.51  
    3.52 +	bool set_point(int idx, const Vector3 &p, float weight = 1.0f);
    3.53  	bool set_point(int idx, const Vector2 &p, float weight = 1.0f);
    3.54  	bool set_weight(int idx, float weight);
    3.55  	// move point without changing its weight
    3.56 +	bool move_point(int idx, const Vector3 &p);
    3.57  	bool move_point(int idx, const Vector2 &p);
    3.58 + 
    3.59 +	int nearest_point(const Vector3 &p) const;
    3.60 +	// nearest control point on the 2D plane z=0
    3.61 +	int nearest_point(const Vector2 &p) const;
    3.62  
    3.63 -	Vector2 interpolate(float t, CurveType type) const;
    3.64 -	Vector2 interpolate(float t) const;
    3.65 -	Vector2 operator ()(float t) const;
    3.66 +	/* get_bbox returns the axis-aligned bounding box of the curve's
    3.67 +	 * control points.
    3.68 +	 * NOTE: hermite curves can go outside of the bounding box of their control points
    3.69 +	 * NOTE: lazy calculation of bounds is performed, use calc_bbox in multithreaded programs
    3.70 +	 */
    3.71 +	void get_bbox(Vector3 *bbmin, Vector3 *bbmax) const;
    3.72 +	void calc_bbox(Vector3 *bbmin, Vector3 *bbmax) const;
    3.73 +	// normalize the curve's bounds to coincide with the unit cube
    3.74 +	void normalize();
    3.75 +
    3.76 +	// project a point to the curve (nearest point on the curve)
    3.77 +	Vector3 proj_point(const Vector3 &p) const;
    3.78 +	// equivalent to (proj_point(p) - p).length();
    3.79 +	float distance(const Vector3 &p) const;
    3.80 +
    3.81 +	Vector3 interpolate_segment(int a, int b, float t) const;
    3.82 +	Vector3 interpolate(float t) const;
    3.83 +	Vector2 interpolate2(float t) const;
    3.84 +	Vector3 operator ()(float t) const;
    3.85  };
    3.86  
    3.87  #endif	// CURVE_H_
     4.1 --- a/src/curvefile.cc	Sat Dec 19 22:39:18 2015 +0200
     4.2 +++ b/src/curvefile.cc	Sun Dec 20 07:21:32 2015 +0200
     4.3 @@ -46,8 +46,8 @@
     4.4  	fprintf(fp, "    type %s\n", curve_type_str(curve->get_type()));
     4.5  	fprintf(fp, "    cpcount %d\n", curve->size());
     4.6  	for(int i=0; i<curve->size(); i++) {
     4.7 -		Vector3 cp = curve->get_homo_point(i);
     4.8 -		fprintf(fp, "    cp %g %g %g\n", cp.x, cp.y, cp.z);
     4.9 +		Vector4 cp = curve->get_point(i);
    4.10 +		fprintf(fp, "    cp %g %g %g %g\n", cp.x, cp.y, cp.z, cp.w);
    4.11  	}
    4.12  	fprintf(fp, "}\n");
    4.13  	return true;
    4.14 @@ -83,8 +83,6 @@
    4.15  		}
    4.16  		s.push_back(c);
    4.17  	}
    4.18 -
    4.19 -	printf("TOKEN: \"%s\"\n", s.c_str());
    4.20  	return s;
    4.21  }
    4.22  
    4.23 @@ -159,13 +157,13 @@
    4.24  			if(tok != "cp") {
    4.25  				goto err;
    4.26  			}
    4.27 -			Vector3 cp;
    4.28 -			for(int i=0; i<3; i++) {
    4.29 +			Vector4 cp;
    4.30 +			for(int i=0; i<4; i++) {
    4.31  				if(!expect_float(fp, &cp[i])) {
    4.32  					goto err;
    4.33  				}
    4.34  			}
    4.35 -			curve->add_point(Vector2(cp.x, cp.y), cp.z);
    4.36 +			curve->add_point(Vector3(cp.x, cp.y, cp.z), cp.w);
    4.37  		}
    4.38  	}
    4.39