curvedraw

view src/app.cc @ 14:b625f0575d66

point snapping
author John Tsiombikas <nuclear@member.fsf.org>
date Thu, 17 Dec 2015 16:41:42 +0200
parents 9f75208b81cd
children 37ab3a4c02f8
line source
1 #include <stdlib.h>
2 #include <float.h>
3 #include <assert.h>
4 #include <vector>
5 #include <algorithm>
6 #include "opengl.h"
7 #include "app.h"
8 #include "curve.h"
9 #include "widgets.h"
11 enum SnapMode {
12 SNAP_NONE,
13 SNAP_GRID,
14 SNAP_POINT
15 };
17 int win_width, win_height;
18 float win_aspect;
20 static void draw_grid(float sz, float sep, float alpha = 1.0f);
21 static void draw_curve(const Curve *curve);
22 static void on_click(int bn, float u, float v);
24 // viewport control
25 static Vector2 view_pan;
26 static float view_scale = 0.2f;
27 static Matrix4x4 view_matrix;
29 static float grid_size = 1.0;
30 static SnapMode snap_mode;
32 static std::vector<Curve*> curves;
33 static Curve *sel_curve; // selected curve being edited
34 static Curve *new_curve; // new curve being entered
35 static Curve *hover_curve; // curve the mouse is hovering over (click to select)
36 static int sel_pidx = -1; // selected point of the selected or hovered-over curve
38 static Label *weight_label; // floating label for the cp weight
40 #ifdef DRAW_MOUSE_POINTER
41 static Vector2 mouse_pointer;
42 #endif
45 bool app_init(int argc, char **argv)
46 {
47 glEnable(GL_MULTISAMPLE);
48 glEnable(GL_CULL_FACE);
50 glEnable(GL_BLEND);
51 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
52 return true;
53 }
55 void app_cleanup()
56 {
57 for(size_t i=0; i<curves.size(); i++) {
58 delete curves[i];
59 }
60 curves.clear();
61 }
63 void app_draw()
64 {
65 glClearColor(0.1, 0.1, 0.1, 1);
66 glClear(GL_COLOR_BUFFER_BIT);
68 glMatrixMode(GL_MODELVIEW);
69 glLoadIdentity();
70 glTranslatef(view_pan.x * view_scale, view_pan.y * view_scale, 0);
71 glScalef(view_scale, view_scale, view_scale);
73 float max_aspect = std::max(win_aspect, 1.0f / win_aspect);
74 draw_grid(max_aspect, grid_size);
76 for(size_t i=0; i<curves.size(); i++) {
77 draw_curve(curves[i]);
78 }
79 if(new_curve) {
80 draw_curve(new_curve);
81 }
83 #ifdef DRAW_MOUSE_POINTER
84 glPointSize(6.0);
85 glBegin(GL_POINTS);
86 glColor3f(0, 0, 1);
87 glVertex2f(mouse_pointer.x, mouse_pointer.y);
88 glEnd();
89 #endif
91 glMatrixMode(GL_MODELVIEW);
92 glLoadIdentity();
94 if(weight_label) {
95 weight_label->draw();
96 }
97 }
99 static void draw_grid(float sz, float sep, float alpha)
100 {
101 float x = 0.0f;
102 float s = 1.0 / view_scale;
104 sz *= s;
105 sz += sep; // one more step for when we have non-zero fractional pan
106 float end = std::min(sz, 100.0f * sep);
108 // fractional pan
109 Vector2 pan = view_pan;
110 Vector2 fpan = Vector2(fmod(pan.x, sep), fmod(pan.y, sep));
111 Vector2 offset = fpan - pan;
113 glMatrixMode(GL_MODELVIEW);
114 glPushMatrix();
115 glTranslatef(offset.x, offset.y, 0);
117 glBegin(GL_LINES);
118 glColor4f(0.35, 0.35, 0.35, alpha);
119 while(x <= end) {
120 glVertex2f(-end, x);
121 glVertex2f(end, x);
122 glVertex2f(-end, -x);
123 glVertex2f(end, -x);
124 glVertex2f(x, -end);
125 glVertex2f(x, end);
126 glVertex2f(-x, -end);
127 glVertex2f(-x, end);
128 x += sep;
129 }
130 glEnd();
131 glPopMatrix();
134 glLineWidth(1.0);
135 glBegin(GL_LINES);
136 glColor4f(0.6, 0.3, 0.2, alpha);
137 glVertex2f(-sz + offset.x, 0);
138 glVertex2f(sz + offset.x, 0);
139 glColor4f(0.2, 0.3, 0.6, alpha);
140 glVertex2f(0, -sz + offset.y);
141 glVertex2f(0, sz + offset.y);
142 glEnd();
144 }
146 static void draw_curve(const Curve *curve)
147 {
148 int numpt = curve->size();
149 int segm = numpt * 16;
151 glLineWidth(curve == hover_curve ? 4.0 : 2.0);
152 if(curve == sel_curve) {
153 glColor3f(0.3, 0.4, 1.0);
154 } else if(curve == new_curve) {
155 glColor3f(1.0, 0.75, 0.3);
156 } else {
157 glColor3f(0.6, 0.6, 0.6);
158 }
159 glBegin(GL_LINE_STRIP);
160 for(int i=0; i<segm; i++) {
161 float t = (float)i / (float)(segm - 1);
162 Vector2 v = curve->interpolate(t);
163 glVertex2f(v.x, v.y);
164 }
165 glEnd();
166 glLineWidth(1.0);
168 glPointSize(curve == hover_curve ? 10.0 : 7.0);
169 glBegin(GL_POINTS);
170 if(curve == new_curve) {
171 glColor3f(1.0, 0.0, 0.0);
172 } else {
173 glColor3f(0.6, 0.3, 0.2);
174 }
175 for(int i=0; i<numpt; i++) {
176 if(curve == sel_curve) {
177 if(i == sel_pidx) {
178 glColor3f(1.0, 0.2, 0.1);
179 } else {
180 glColor3f(0.2, 1.0, 0.2);
181 }
182 }
183 Vector2 pt = curve->get_point(i);
184 glVertex2f(pt.x, pt.y);
185 }
186 glEnd();
187 glPointSize(1.0);
188 }
190 void app_reshape(int x, int y)
191 {
192 win_width = x;
193 win_height = y;
194 win_aspect = (float)x / (float)y;
196 glViewport(0, 0, x, y);
197 glMatrixMode(GL_PROJECTION);
198 glLoadIdentity();
199 glOrtho(-win_aspect, win_aspect, -1, 1, -1, 1);
200 }
202 void app_keyboard(int key, bool pressed)
203 {
204 if(pressed) {
205 switch(key) {
206 case 'q':
207 case 'Q':
208 exit(0);
210 case 27:
211 if(new_curve) {
212 delete new_curve;
213 new_curve = 0;
214 post_redisplay();
215 }
216 break;
218 case 'l':
219 case 'L':
220 if(sel_curve) {
221 sel_curve->set_type(CURVE_LINEAR);
222 post_redisplay();
223 }
224 if(new_curve) {
225 new_curve->set_type(CURVE_LINEAR);
226 post_redisplay();
227 }
228 break;
230 case 'b':
231 case 'B':
232 if(sel_curve) {
233 sel_curve->set_type(CURVE_BSPLINE);
234 post_redisplay();
235 }
236 if(new_curve) {
237 new_curve->set_type(CURVE_BSPLINE);
238 post_redisplay();
239 }
240 break;
242 case 'h':
243 case 'H':
244 if(sel_curve) {
245 sel_curve->set_type(CURVE_HERMITE);
246 post_redisplay();
247 }
248 if(new_curve) {
249 new_curve->set_type(CURVE_HERMITE);
250 post_redisplay();
251 }
252 break;
253 }
254 }
257 switch(key) {
258 case 's':
259 snap_mode = pressed ? SNAP_GRID : SNAP_NONE;
260 break;
262 case 'S':
263 snap_mode = pressed ? SNAP_POINT : SNAP_NONE;
264 break;
266 default:
267 break;
268 }
269 }
271 static void calc_view_matrix()
272 {
273 view_matrix.reset_identity();
274 view_matrix.scale(Vector3(view_scale, view_scale, view_scale));
275 view_matrix.translate(Vector3(view_pan.x, view_pan.y, 0.0));
276 }
278 static Vector2 pixel_to_uv(int x, int y)
279 {
280 float u = win_aspect * (2.0 * (float)x / (float)win_width - 1.0);
281 float v = 1.0 - 2.0 * (float)y / (float)win_height;
283 u = u / view_scale - view_pan.x;
284 v = v / view_scale - view_pan.y;
285 return Vector2(u, v);
286 /*
287 Matrix4x4 inv_view_matrix = view_matrix.inverse();
288 Vector4 res = Vector4(u, v, 0.0, 1.0).transformed(inv_view_matrix);
290 return Vector2(res.x, res.y);
291 */
292 }
294 static int prev_x, prev_y;
295 static int click_pos[8][2];
296 static unsigned int bnstate;
298 #define BNBIT(x) (1 << (x))
300 void app_mouse_button(int bn, bool pressed, int x, int y)
301 {
302 prev_x = x;
303 prev_y = y;
304 if(pressed) {
305 bnstate |= BNBIT(bn);
306 } else {
307 bnstate &= ~BNBIT(bn);
308 }
310 if(pressed) {
311 click_pos[bn][0] = x;
312 click_pos[bn][1] = y;
313 } else {
314 int dx = x - click_pos[bn][0];
315 int dy = y - click_pos[bn][1];
317 if(abs(dx) + abs(dy) < 3) {
318 Vector2 uv = pixel_to_uv(x, y);
319 on_click(bn, uv.x, uv.y);
320 }
322 if(!(bnstate & BNBIT(2))) {
323 delete weight_label;
324 weight_label = 0;
325 post_redisplay();
326 }
327 }
328 }
330 static bool point_hit_test(const Vector2 &pos, Curve **curveret, int *pidxret)
331 {
332 float thres = 0.02 / view_scale;
334 for(size_t i=0; i<curves.size(); i++) {
335 int pidx = curves[i]->nearest_point(pos);
336 if(pidx == -1) continue;
338 Vector2 cp = curves[i]->get_point(pidx);
339 if((cp - pos).length_sq() < thres * thres) {
340 *curveret = curves[i];
341 *pidxret = pidx;
342 return true;
343 }
344 }
345 *curveret = 0;
346 *pidxret = -1;
347 return false;
348 }
350 static Vector2 snap(const Vector2 &p)
351 {
352 switch(snap_mode) {
353 case SNAP_GRID:
354 return Vector2(round(p.x / grid_size) * grid_size, round(p.y / grid_size) * grid_size);
355 case SNAP_POINT:
356 {
357 Curve *nearest_curve = 0;
358 int nearest_curve_pidx = -1;
359 float nearest_dist_sq = FLT_MAX;
361 if(new_curve) {
362 // find the closest point, ignoring the last
363 for(int i=0; i<new_curve->size() - 1; i++) {
364 Vector2 cp = new_curve->get_point(i);
365 float distsq = (cp - p).length_sq();
366 if(distsq < nearest_dist_sq) {
367 nearest_curve = new_curve;
368 nearest_dist_sq = distsq;
369 nearest_curve_pidx = i;
370 }
371 }
372 }
375 for(size_t i=0; i<curves.size(); i++) {
376 int pidx = curves[i]->nearest_point(p);
377 Vector2 cp = curves[i]->get_point(pidx);
378 float dist_sq = (cp - p).length_sq();
379 if(dist_sq < nearest_dist_sq) {
380 nearest_curve = curves[i];
381 nearest_curve_pidx = pidx;
382 nearest_dist_sq = dist_sq;
383 }
384 }
386 if(nearest_curve) {
387 return nearest_curve->get_point(nearest_curve_pidx);
388 }
389 }
390 break;
392 default:
393 break;
394 }
395 return p;
396 }
398 void app_mouse_motion(int x, int y)
399 {
400 Vector2 prev_uv = pixel_to_uv(prev_x, prev_y);
402 int dx = x - prev_x;
403 int dy = y - prev_y;
404 prev_x = x;
405 prev_y = y;
407 if(!dx && !dy) return;
409 Vector2 uv = pixel_to_uv(x, y);
410 #ifdef DRAW_MOUSE_POINTER
411 mouse_pointer = uv;
412 post_redisplay();
413 #endif
415 /* when entering a new curve, have the last (extra) point following
416 * the mouse until it's entered by a click (see on_click).
417 */
418 if(new_curve) {
419 new_curve->move_point(new_curve->size() - 1, snap(uv));
420 post_redisplay();
421 }
423 if(!new_curve && !bnstate) {
424 // not dragging, highlight curve under mouse
425 point_hit_test(uv, &hover_curve, &sel_pidx);
426 post_redisplay();
428 } else {
429 // we're dragging with one or more buttons held down
431 if(sel_curve && sel_pidx != -1) {
432 // we have a curve and a point of the curve selected
434 if(bnstate & BNBIT(0)) {
435 // dragging point with left button: move it
436 sel_curve->move_point(sel_pidx, snap(uv));
437 post_redisplay();
438 }
440 if(bnstate & BNBIT(2)) {
441 // dragging point with right button: change weight
442 float w = sel_curve->get_weight(sel_pidx);
443 w -= dy * 0.01;
444 if(w < FLT_MIN) w = FLT_MIN;
445 sel_curve->set_weight(sel_pidx, w);
447 // popup floating weight label if not already there
448 if(!weight_label) {
449 weight_label = new Label;
450 }
451 weight_label->set_position(uv);
452 weight_label->set_textf("w=%g", w);
453 post_redisplay();
454 }
455 } else {
456 // no selection, we're dragging in empty space: manipulate viewport
457 Vector2 dir = uv - prev_uv;
459 if(bnstate & (BNBIT(0) | BNBIT(1))) {
460 // panning
461 view_pan += dir;
462 calc_view_matrix();
463 post_redisplay();
464 }
465 if(bnstate & BNBIT(2)) {
466 // zooming
467 view_scale -= ((float)dy / (float)win_height) * view_scale * 5.0;
468 if(view_scale < 1e-4) view_scale = 1e-4;
469 calc_view_matrix();
470 post_redisplay();
471 }
472 }
473 }
474 }
476 static void on_click(int bn, float u, float v)
477 {
478 Vector2 uv = Vector2(u, v);
480 switch(bn) {
481 case 0: // ------- LEFT CLICK ------
482 if(hover_curve) {
483 // if we're hovering: click selects
484 sel_curve = hover_curve;
485 hover_curve = 0;
486 } else if(sel_curve) {
487 // if we have a selected curve: click adds point (enter new_curve mode)
488 std::vector<Curve*>::iterator it = std::find(curves.begin(), curves.end(), sel_curve);
489 assert(it != curves.end());
490 curves.erase(it, it + 1);
492 new_curve = sel_curve;
493 sel_curve = 0;
494 sel_pidx = -1;
496 new_curve->add_point(uv);
497 } else {
498 // otherwise, click starts a new curve
499 if(!new_curve) {
500 new_curve = new Curve;
501 new_curve->add_point(uv);
502 }
503 new_curve->add_point(uv);
504 }
505 post_redisplay();
506 break;
508 case 2: // ------- RIGHT CLICK ------
509 if(new_curve) {
510 // in new-curve mode: finish curve (cancels last floating segment)
511 new_curve->remove_point(new_curve->size() - 1);
512 if(new_curve->empty()) {
513 delete new_curve;
514 } else {
515 curves.push_back(new_curve);
516 }
517 new_curve = 0;
519 } else if(sel_curve) {
520 // in selected curve mode: delete control point or unselect
521 Curve *hit_curve;
522 int hit_pidx;
523 if(point_hit_test(uv, &hit_curve, &hit_pidx) && hit_curve == sel_curve) {
524 hit_curve->remove_point(hit_pidx);
525 sel_pidx = -1;
526 } else {
527 sel_curve = 0;
528 sel_pidx = -1;
529 }
530 }
531 post_redisplay();
532 break;
534 default:
535 break;
536 }
537 }