curvedraw

changeset 13:4da693339d99

- distance from curve - hover/selection of curves directly on the curve
author John Tsiombikas <nuclear@member.fsf.org>
date Sun, 20 Dec 2015 08:22:24 +0200
parents 84a647283237
children 37ab3a4c02f8
files src/app.cc src/curve.cc src/curve.h
diffstat 3 files changed, 92 insertions(+), 57 deletions(-) [+]
line diff
     1.1 --- a/src/app.cc	Sun Dec 20 07:21:32 2015 +0200
     1.2 +++ b/src/app.cc	Sun Dec 20 08:22:24 2015 +0200
     1.3 @@ -36,7 +36,8 @@
     1.4  static Curve *sel_curve;	// selected curve being edited
     1.5  static Curve *new_curve;	// new curve being entered
     1.6  static Curve *hover_curve;	// curve the mouse is hovering over (click to select)
     1.7 -static int sel_pidx = -1;	// selected point of the selected or hovered-over curve
     1.8 +static int sel_pidx = -1;	// selected point of the selected curve
     1.9 +static int hover_pidx = -1;	// hovered over point
    1.10  
    1.11  static Label *weight_label;	// floating label for the cp weight
    1.12  
    1.13 @@ -389,7 +390,7 @@
    1.14  		int pidx = curves[i]->nearest_point(pos);
    1.15  		if(pidx == -1) continue;
    1.16  
    1.17 -		Vector2 cp = curves[i]->get_point(pidx);
    1.18 +		Vector2 cp = curves[i]->get_point2(pidx);
    1.19  		if((cp - pos).length_sq() < thres * thres) {
    1.20  			*curveret = curves[i];
    1.21  			*pidxret = pidx;
    1.22 @@ -401,6 +402,28 @@
    1.23  	return false;
    1.24  }
    1.25  
    1.26 +static bool hit_test(const Vector2 &pos, Curve **curveret, int *pidxret)
    1.27 +{
    1.28 +	float thres = 0.02 / view_scale;
    1.29 +
    1.30 +	if(point_hit_test(pos, curveret, pidxret)) {
    1.31 +		return true;
    1.32 +	}
    1.33 +
    1.34 +	Vector3 pos3 = Vector3(pos.x, pos.y, 0.0f);
    1.35 +	for(size_t i=0; i<curves.size(); i++) {
    1.36 +		float x;
    1.37 +		if((x = curves[i]->distance_sq(pos3)) < thres * thres) {
    1.38 +			*curveret = curves[i];
    1.39 +			*pidxret = -1;
    1.40 +			return true;
    1.41 +		}
    1.42 +	}
    1.43 +	*curveret = 0;
    1.44 +	*pidxret = -1;
    1.45 +	return false;
    1.46 +}
    1.47 +
    1.48  static Vector2 snap(const Vector2 &p)
    1.49  {
    1.50  	switch(snap_mode) {
    1.51 @@ -427,7 +450,7 @@
    1.52  
    1.53  	Vector2 uv = pixel_to_uv(x, y);
    1.54  	mouse_pointer = uv;
    1.55 -	post_redisplay();
    1.56 +	//post_redisplay();
    1.57  
    1.58  	/* when entering a new curve, have the last (extra) point following
    1.59  	 * the mouse until it's entered by a click (see on_click).
    1.60 @@ -439,7 +462,10 @@
    1.61  
    1.62  	if(!new_curve && !bnstate) {
    1.63  		// not dragging, highlight curve under mouse
    1.64 -		point_hit_test(uv, &hover_curve, &sel_pidx);
    1.65 +		hit_test(uv, &hover_curve, &hover_pidx);
    1.66 +		if(hover_curve == sel_curve) {
    1.67 +			sel_pidx = hover_pidx;
    1.68 +		}
    1.69  		post_redisplay();
    1.70  
    1.71  	} else {
    1.72 @@ -499,6 +525,7 @@
    1.73  		if(hover_curve) {
    1.74  			// if we're hovering: click selects
    1.75  			sel_curve = hover_curve;
    1.76 +			sel_pidx = hover_pidx;
    1.77  			hover_curve = 0;
    1.78  		} else if(sel_curve) {
    1.79  			// if we have a selected curve: click adds point (enter new_curve mode)
    1.80 @@ -537,9 +564,11 @@
    1.81  			// in selected curve mode: delete control point or unselect
    1.82  			Curve *hit_curve;
    1.83  			int hit_pidx;
    1.84 -			if(point_hit_test(uv, &hit_curve, &hit_pidx) && hit_curve == sel_curve) {
    1.85 -				hit_curve->remove_point(hit_pidx);
    1.86 -				sel_pidx = -1;
    1.87 +			if(hit_test(uv, &hit_curve, &hit_pidx) && hit_curve == sel_curve) {
    1.88 +				if(hit_pidx != -1) {
    1.89 +					hit_curve->remove_point(hit_pidx);
    1.90 +					sel_pidx = -1;
    1.91 +				}
    1.92  			} else {
    1.93  				sel_curve = 0;
    1.94  				sel_pidx = -1;
     2.1 --- a/src/curve.cc	Sun Dec 20 07:21:32 2015 +0200
     2.2 +++ b/src/curve.cc	Sun Dec 20 08:22:24 2015 +0200
     2.3 @@ -262,69 +262,69 @@
     2.4  	inval_bounds();
     2.5  }
     2.6  
     2.7 -/* Projection to the curve is not correct, but should be good enough for
     2.8 - * most purposes.
     2.9 - *
    2.10 - * First we find the nearest segment (pair of control points), and then
    2.11 - * we subdivide between them to find the nearest interpolated point in that
    2.12 - * segment.
    2.13 - * The incorrect assumption here is that the nearest segment as defined by
    2.14 - * the distance of its control points to p, will contain the nearest point
    2.15 - * on the curve. This only holds for polylines, and *possibly* bsplines, but
    2.16 - * certainly not hermite splines.
    2.17 - */
    2.18 -Vector3 Curve::proj_point(const Vector3 &p) const
    2.19 +Vector3 Curve::proj_point(const Vector3 &p, float refine_thres) const
    2.20  {
    2.21 -	// TODO fix: select nearest segment based on point-line distance, not distance from the CPs
    2.22 +	// first step through the curve a few times and find the nearest of them
    2.23  	int num_cp = size();
    2.24 -	if(num_cp <= 0) return p;
    2.25 -	if(num_cp == 1) return cp[0];
    2.26 +	int num_steps = num_cp * 5;	// arbitrary number; sounds ok
    2.27 +	float dt = 1.0f / (float)(num_steps - 1);
    2.28  
    2.29 -	int idx0 = nearest_point(p);
    2.30 -	int next_idx = idx0 + 1;
    2.31 -	int prev_idx = idx0 - 1;
    2.32 +	float best_distsq = FLT_MAX;
    2.33 +	float best_t = 0.0f;
    2.34 +	Vector3 best_pp;
    2.35  
    2.36 -	float next_distsq = next_idx >= num_cp ? FLT_MAX : (get_point3(next_idx) - p).length_sq();
    2.37 -	float prev_distsq = prev_idx < 0 ? FLT_MAX : (get_point3(prev_idx) - p).length_sq();
    2.38 -	int idx1 = next_distsq < prev_distsq ? next_idx : prev_idx;
    2.39 -	assert(idx1 >= 0 && idx1 < num_cp - 1);
    2.40 -	if(idx0 > idx1) std::swap(idx0, idx1);
    2.41 +	float t = 0.0f;
    2.42 +	for(int i=0; i<num_steps; i++) {
    2.43 +		Vector3 pp = interpolate(t);
    2.44 +		float distsq = (pp - p).length_sq();
    2.45 +		if(distsq < best_distsq) {
    2.46 +			best_distsq = distsq;
    2.47 +			best_pp = pp;
    2.48 +			best_t = t;
    2.49 +		}
    2.50 +		t += dt;
    2.51 +	}
    2.52  
    2.53 -	float t0 = 0.0f, t1 = 1.0f;
    2.54 -	Vector3 pp0 = interpolate_segment(idx0, idx1, 0.0f);
    2.55 -	Vector3 pp1 = interpolate_segment(idx0, idx1, 1.0f);
    2.56 -	float dist0 = (pp0 - p).length_sq();
    2.57 -	float dist1 = (pp1 - p).length_sq();
    2.58 -	Vector3 pp;
    2.59 +	// refine by gradient descent
    2.60 +	float dist = best_distsq;
    2.61 +	t = best_t;
    2.62 +	dt *= 0.05;
    2.63 +	for(;;) {
    2.64 +		float tn = t + dt;
    2.65 +		float tp = t - dt;
    2.66  
    2.67 -	for(int i=0; i<32; i++) {	// max iterations
    2.68 -		float t = (t0 + t1) / 2.0;
    2.69 -		pp = interpolate_segment(idx0, idx1, t);
    2.70 -		float dist = (pp - p).length_sq();
    2.71 +		float dn = (interpolate(tn) - p).length_sq();
    2.72 +		float dp = (interpolate(tp) - p).length_sq();
    2.73  
    2.74 -		// mid point more distant than both control points, nearest cp is closest
    2.75 -		if(dist > dist0 && dist > dist1) {
    2.76 -			pp = dist0 < dist1 ? pp0 : pp1;
    2.77 +		if(fabs(dn - dp) < refine_thres * refine_thres) {
    2.78  			break;
    2.79  		}
    2.80  
    2.81 -		if(dist0 < dist1) {
    2.82 -			t1 = t;
    2.83 -			dist1 = dist;
    2.84 -			pp1 = pp;
    2.85 +		if(dn < dist) {
    2.86 +			t = tn;
    2.87 +			dist = dn;
    2.88 +		} else if(dp < dist) {
    2.89 +			t = tp;
    2.90 +			dist = dp;
    2.91  		} else {
    2.92 -			t0 = t;
    2.93 -			dist0 = dist;
    2.94 -			pp0 = pp;
    2.95 -		}
    2.96 -
    2.97 -		if(fabs(dist0 - dist1) < 1e-4) {
    2.98 -			break;
    2.99 +			break;	// found the minimum
   2.100  		}
   2.101  	}
   2.102 -	return pp;
   2.103 +
   2.104 +	return interpolate(t);
   2.105  }
   2.106  
   2.107 +float Curve::distance(const Vector3 &p) const
   2.108 +{
   2.109 +	return (proj_point(p) - p).length();
   2.110 +}
   2.111 +
   2.112 +float Curve::distance_sq(const Vector3 &p) const
   2.113 +{
   2.114 +	return fabs((proj_point(p) - p).length_sq());
   2.115 +}
   2.116 +
   2.117 +
   2.118  Vector3 Curve::interpolate_segment(int a, int b, float t) const
   2.119  {
   2.120  	int num_cp = size();
   2.121 @@ -346,6 +346,7 @@
   2.122  			if(res.w != 0.0f) {
   2.123  				res.x /= res.w;
   2.124  				res.y /= res.w;
   2.125 +				res.z /= res.w;
   2.126  			}
   2.127  		}
   2.128  	}
   2.129 @@ -364,6 +365,9 @@
   2.130  		return Vector3(cp[0].x, cp[0].y, cp[0].z);
   2.131  	}
   2.132  
   2.133 +	if(t < 0.0) t = 0.0;
   2.134 +	if(t > 1.0) t = 1.0;
   2.135 +
   2.136  	int idx0 = std::min((int)floor(t * (num_cp - 1)), num_cp - 2);
   2.137  	int idx1 = idx0 + 1;
   2.138  
     3.1 --- a/src/curve.h	Sun Dec 20 07:21:32 2015 +0200
     3.2 +++ b/src/curve.h	Sun Dec 20 08:22:24 2015 +0200
     3.3 @@ -70,9 +70,11 @@
     3.4  	void normalize();
     3.5  
     3.6  	// project a point to the curve (nearest point on the curve)
     3.7 -	Vector3 proj_point(const Vector3 &p) const;
     3.8 -	// equivalent to (proj_point(p) - p).length();
     3.9 +	Vector3 proj_point(const Vector3 &p, float refine_thres = 0.01) const;
    3.10 +	// equivalent to (proj_point(p) - p).length()
    3.11  	float distance(const Vector3 &p) const;
    3.12 +	// equivalent to fabs((proj_point(p) - p).length_sq())
    3.13 +	float distance_sq(const Vector3 &p) const;
    3.14  
    3.15  	Vector3 interpolate_segment(int a, int b, float t) const;
    3.16  	Vector3 interpolate(float t) const;