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;