curvedraw

view src/app.cc @ 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
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"
10 #include "curvefile.h"
12 enum SnapMode {
13 SNAP_NONE,
14 SNAP_GRID,
15 SNAP_POINT
16 };
18 int win_width, win_height;
19 float win_aspect;
21 static void draw_grid(float sz, float sep, float alpha = 1.0f);
22 static void draw_curve(const Curve *curve);
23 static void on_click(int bn, float u, float v);
25 // viewport control
26 static Vector2 view_pan;
27 static float view_scale = 0.2f;
28 static Matrix4x4 view_matrix;
30 static float grid_size = 1.0;
31 static SnapMode snap_mode;
33 static bool show_bounds;
35 static std::vector<Curve*> curves;
36 static Curve *sel_curve; // selected curve being edited
37 static Curve *new_curve; // new curve being entered
38 static Curve *hover_curve; // curve the mouse is hovering over (click to select)
39 static int sel_pidx = -1; // selected point of the selected curve
40 static int hover_pidx = -1; // hovered over point
42 static Label *weight_label; // floating label for the cp weight
44 static Vector2 mouse_pointer;
47 bool app_init(int argc, char **argv)
48 {
49 glewInit();
51 glEnable(GL_MULTISAMPLE);
52 glEnable(GL_CULL_FACE);
54 glEnable(GL_BLEND);
55 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
56 return true;
57 }
59 void app_cleanup()
60 {
61 for(size_t i=0; i<curves.size(); i++) {
62 delete curves[i];
63 }
64 curves.clear();
65 }
67 void app_draw()
68 {
69 glClearColor(0.1, 0.1, 0.1, 1);
70 glClear(GL_COLOR_BUFFER_BIT);
72 glMatrixMode(GL_MODELVIEW);
73 glLoadIdentity();
74 glTranslatef(view_pan.x * view_scale, view_pan.y * view_scale, 0);
75 glScalef(view_scale, view_scale, view_scale);
77 float max_aspect = std::max(win_aspect, 1.0f / win_aspect);
78 draw_grid(max_aspect, grid_size);
80 for(size_t i=0; i<curves.size(); i++) {
81 draw_curve(curves[i]);
82 }
83 if(new_curve) {
84 draw_curve(new_curve);
85 }
87 #ifdef DRAW_MOUSE_POINTER
88 glPointSize(6.0);
89 glBegin(GL_POINTS);
90 glColor3f(0, 0, 1);
91 glVertex2f(mouse_pointer.x, mouse_pointer.y);
92 glEnd();
93 #endif
95 glMatrixMode(GL_MODELVIEW);
96 glLoadIdentity();
98 if(weight_label) {
99 weight_label->draw();
100 }
101 }
103 static void draw_grid(float sz, float sep, float alpha)
104 {
105 float x = 0.0f;
106 float s = 1.0 / view_scale;
108 sz *= s;
109 sz += sep; // one more step for when we have non-zero fractional pan
110 float end = std::min(sz, 100.0f * sep);
112 // fractional pan
113 Vector2 pan = view_pan;
114 Vector2 fpan = Vector2(fmod(pan.x, sep), fmod(pan.y, sep));
115 Vector2 offset = fpan - pan;
117 glMatrixMode(GL_MODELVIEW);
118 glPushMatrix();
119 glTranslatef(offset.x, offset.y, 0);
121 glBegin(GL_LINES);
122 glColor4f(0.35, 0.35, 0.35, alpha);
123 while(x <= end) {
124 glVertex2f(-end, x);
125 glVertex2f(end, x);
126 glVertex2f(-end, -x);
127 glVertex2f(end, -x);
128 glVertex2f(x, -end);
129 glVertex2f(x, end);
130 glVertex2f(-x, -end);
131 glVertex2f(-x, end);
132 x += sep;
133 }
134 glEnd();
135 glPopMatrix();
138 glLineWidth(1.0);
139 glBegin(GL_LINES);
140 glColor4f(0.6, 0.3, 0.2, alpha);
141 glVertex2f(-sz + offset.x, 0);
142 glVertex2f(sz + offset.x, 0);
143 glColor4f(0.2, 0.3, 0.6, alpha);
144 glVertex2f(0, -sz + offset.y);
145 glVertex2f(0, sz + offset.y);
146 glEnd();
148 }
150 static void draw_curve(const Curve *curve)
151 {
152 int numpt = curve->size();
153 int segm = numpt * 16;
155 if(show_bounds) {
156 Vector3 bmin, bmax;
157 curve->get_bbox(&bmin, &bmax);
159 glLineWidth(1.0);
160 glColor3f(0, 1, 0);
161 glBegin(GL_LINE_LOOP);
162 glVertex2f(bmin.x, bmin.y);
163 glVertex2f(bmax.x, bmin.y);
164 glVertex2f(bmax.x, bmax.y);
165 glVertex2f(bmin.x, bmax.y);
166 glEnd();
167 }
169 glLineWidth(curve == hover_curve ? 4.0 : 2.0);
170 if(curve == sel_curve) {
171 glColor3f(0.3, 0.4, 1.0);
172 } else if(curve == new_curve) {
173 glColor3f(1.0, 0.75, 0.3);
174 } else {
175 glColor3f(0.6, 0.6, 0.6);
176 }
177 glBegin(GL_LINE_STRIP);
178 for(int i=0; i<segm; i++) {
179 float t = (float)i / (float)(segm - 1);
180 Vector3 v = curve->interpolate(t);
181 glVertex2f(v.x, v.y);
182 }
183 glEnd();
184 glLineWidth(1.0);
186 glPointSize(curve == hover_curve ? 10.0 : 7.0);
187 glBegin(GL_POINTS);
188 if(curve == new_curve) {
189 glColor3f(1.0, 0.0, 0.0);
190 } else {
191 glColor3f(0.6, 0.3, 0.2);
192 }
193 for(int i=0; i<numpt; i++) {
194 if(curve == sel_curve) {
195 if(i == sel_pidx) {
196 glColor3f(1.0, 0.2, 0.1);
197 } else {
198 glColor3f(0.2, 1.0, 0.2);
199 }
200 }
201 Vector2 pt = curve->get_point2(i);
202 glVertex2f(pt.x, pt.y);
203 }
204 glEnd();
206 // draw the projected mouse point on the selected curve
207 /*
208 if(curve == sel_curve) {
209 Vector3 pp = curve->proj_point(Vector3(mouse_pointer.x, mouse_pointer.y, 0.0));
211 glPointSize(5.0);
212 glBegin(GL_POINTS);
213 glColor3f(1, 0.8, 0.2);
214 glVertex2f(pp.x, pp.y);
215 glEnd();
216 }
217 */
218 glPointSize(1.0);
219 }
221 void app_reshape(int x, int y)
222 {
223 win_width = x;
224 win_height = y;
225 win_aspect = (float)x / (float)y;
227 glViewport(0, 0, x, y);
228 glMatrixMode(GL_PROJECTION);
229 glLoadIdentity();
230 glOrtho(-win_aspect, win_aspect, -1, 1, -1, 1);
231 }
233 void app_keyboard(int key, bool pressed)
234 {
235 if(pressed) {
236 switch(key) {
237 case 'q':
238 case 'Q':
239 exit(0);
241 case 27:
242 if(new_curve) {
243 delete new_curve;
244 new_curve = 0;
245 post_redisplay();
246 }
247 break;
249 case '1':
250 case '2':
251 case '3':
252 if(sel_curve) {
253 sel_curve->set_type((CurveType)((int)CURVE_LINEAR + key - '1'));
254 post_redisplay();
255 }
256 if(new_curve) {
257 new_curve->set_type((CurveType)((int)CURVE_LINEAR + key - '1'));
258 post_redisplay();
259 }
260 break;
262 case 'b':
263 case 'B':
264 show_bounds = !show_bounds;
265 post_redisplay();
266 break;
268 case 'n':
269 case 'N':
270 if(sel_curve) {
271 sel_curve->normalize();
272 post_redisplay();
273 }
274 break;
276 case 'e':
277 case 'E':
278 // TODO: GUI for filename at least
279 if(!save_curves("test.curves", &curves[0], (int)curves.size())) {
280 fprintf(stderr, "failed to export curves\n");
281 }
282 printf("exported %d curves\n", (int)curves.size());
283 break;
285 case 'l':
286 case 'L':
287 {
288 std::list<Curve*> clist = load_curves("test.curves");
289 if(clist.empty()) {
290 fprintf(stderr, "failed to import curves\n");
291 }
293 for(size_t i=0; i<curves.size(); i++) {
294 delete curves[i];
295 }
296 curves.clear();
298 int num = 0;
299 std::list<Curve*>::iterator it = clist.begin();
300 while(it != clist.end()) {
301 curves.push_back(*it++);
302 ++num;
303 }
304 printf("imported %d curves\n", num);
305 }
306 post_redisplay();
307 break;
308 }
309 }
312 switch(key) {
313 case 's':
314 snap_mode = pressed ? SNAP_GRID : SNAP_NONE;
315 break;
317 case 'S':
318 snap_mode = pressed ? SNAP_POINT : SNAP_NONE;
319 break;
321 default:
322 break;
323 }
324 }
326 static void calc_view_matrix()
327 {
328 view_matrix.reset_identity();
329 view_matrix.scale(Vector3(view_scale, view_scale, view_scale));
330 view_matrix.translate(Vector3(view_pan.x, view_pan.y, 0.0));
331 }
333 static Vector2 pixel_to_uv(int x, int y)
334 {
335 float u = win_aspect * (2.0 * (float)x / (float)win_width - 1.0);
336 float v = 1.0 - 2.0 * (float)y / (float)win_height;
338 u = u / view_scale - view_pan.x;
339 v = v / view_scale - view_pan.y;
340 return Vector2(u, v);
341 /*
342 Matrix4x4 inv_view_matrix = view_matrix.inverse();
343 Vector4 res = Vector4(u, v, 0.0, 1.0).transformed(inv_view_matrix);
345 return Vector2(res.x, res.y);
346 */
347 }
349 static int prev_x, prev_y;
350 static int click_pos[8][2];
351 static unsigned int bnstate;
353 #define BNBIT(x) (1 << (x))
355 void app_mouse_button(int bn, bool pressed, int x, int y)
356 {
357 prev_x = x;
358 prev_y = y;
359 if(pressed) {
360 bnstate |= BNBIT(bn);
361 } else {
362 bnstate &= ~BNBIT(bn);
363 }
365 if(pressed) {
366 click_pos[bn][0] = x;
367 click_pos[bn][1] = y;
368 } else {
369 int dx = x - click_pos[bn][0];
370 int dy = y - click_pos[bn][1];
372 if(abs(dx) + abs(dy) < 3) {
373 Vector2 uv = pixel_to_uv(x, y);
374 on_click(bn, uv.x, uv.y);
375 }
377 if(!(bnstate & BNBIT(2))) {
378 delete weight_label;
379 weight_label = 0;
380 post_redisplay();
381 }
382 }
383 }
385 static bool point_hit_test(const Vector2 &pos, Curve **curveret, int *pidxret)
386 {
387 float thres = 0.02 / view_scale;
389 for(size_t i=0; i<curves.size(); i++) {
390 int pidx = curves[i]->nearest_point(pos);
391 if(pidx == -1) continue;
393 Vector2 cp = curves[i]->get_point2(pidx);
394 if((cp - pos).length_sq() < thres * thres) {
395 *curveret = curves[i];
396 *pidxret = pidx;
397 return true;
398 }
399 }
400 *curveret = 0;
401 *pidxret = -1;
402 return false;
403 }
405 static bool hit_test(const Vector2 &pos, Curve **curveret, int *pidxret)
406 {
407 float thres = 0.02 / view_scale;
409 if(point_hit_test(pos, curveret, pidxret)) {
410 return true;
411 }
413 Vector3 pos3 = Vector3(pos.x, pos.y, 0.0f);
414 for(size_t i=0; i<curves.size(); i++) {
415 float x;
416 if((x = curves[i]->distance_sq(pos3)) < thres * thres) {
417 *curveret = curves[i];
418 *pidxret = -1;
419 return true;
420 }
421 }
422 *curveret = 0;
423 *pidxret = -1;
424 return false;
425 }
427 static Vector2 snap(const Vector2 &p)
428 {
429 switch(snap_mode) {
430 case SNAP_GRID:
431 return Vector2(round(p.x / grid_size) * grid_size, round(p.y / grid_size) * grid_size);
432 case SNAP_POINT:
433 // TODO
434 default:
435 break;
436 }
437 return p;
438 }
440 void app_mouse_motion(int x, int y)
441 {
442 Vector2 prev_uv = pixel_to_uv(prev_x, prev_y);
444 int dx = x - prev_x;
445 int dy = y - prev_y;
446 prev_x = x;
447 prev_y = y;
449 if(!dx && !dy) return;
451 Vector2 uv = pixel_to_uv(x, y);
452 mouse_pointer = uv;
453 //post_redisplay();
455 /* when entering a new curve, have the last (extra) point following
456 * the mouse until it's entered by a click (see on_click).
457 */
458 if(new_curve) {
459 new_curve->move_point(new_curve->size() - 1, snap(uv));
460 post_redisplay();
461 }
463 if(!new_curve && !bnstate) {
464 // not dragging, highlight curve under mouse
465 hit_test(uv, &hover_curve, &hover_pidx);
466 if(hover_curve == sel_curve) {
467 sel_pidx = hover_pidx;
468 }
469 post_redisplay();
471 } else {
472 // we're dragging with one or more buttons held down
474 if(sel_curve && sel_pidx != -1) {
475 // we have a curve and a point of the curve selected
477 if(bnstate & BNBIT(0)) {
478 // dragging point with left button: move it
479 sel_curve->move_point(sel_pidx, snap(uv));
480 post_redisplay();
481 }
483 if(bnstate & BNBIT(2)) {
484 // dragging point with right button: change weight
485 float w = sel_curve->get_weight(sel_pidx);
486 w -= dy * 0.01;
487 if(w < FLT_MIN) w = FLT_MIN;
488 sel_curve->set_weight(sel_pidx, w);
490 // popup floating weight label if not already there
491 if(!weight_label) {
492 weight_label = new Label;
493 }
494 weight_label->set_position(uv);
495 weight_label->set_textf("w=%g", w);
496 post_redisplay();
497 }
498 } else {
499 // no selection, we're dragging in empty space: manipulate viewport
500 Vector2 dir = uv - prev_uv;
502 if(bnstate & (BNBIT(0) | BNBIT(1))) {
503 // panning
504 view_pan += dir;
505 calc_view_matrix();
506 post_redisplay();
507 }
508 if(bnstate & BNBIT(2)) {
509 // zooming
510 view_scale -= ((float)dy / (float)win_height) * view_scale * 5.0;
511 if(view_scale < 1e-4) view_scale = 1e-4;
512 calc_view_matrix();
513 post_redisplay();
514 }
515 }
516 }
517 }
519 static void on_click(int bn, float u, float v)
520 {
521 Vector2 uv = Vector2(u, v);
523 switch(bn) {
524 case 0: // ------- LEFT CLICK ------
525 if(hover_curve) {
526 // if we're hovering: click selects
527 sel_curve = hover_curve;
528 sel_pidx = hover_pidx;
529 hover_curve = 0;
530 } else if(sel_curve) {
531 // if we have a selected curve: click adds point (enter new_curve mode)
532 std::vector<Curve*>::iterator it = std::find(curves.begin(), curves.end(), sel_curve);
533 assert(it != curves.end());
534 curves.erase(it, it + 1);
536 new_curve = sel_curve;
537 sel_curve = 0;
538 sel_pidx = -1;
540 new_curve->add_point(uv);
541 } else {
542 // otherwise, click starts a new curve
543 if(!new_curve) {
544 new_curve = new Curve;
545 new_curve->add_point(uv);
546 }
547 new_curve->add_point(uv);
548 }
549 post_redisplay();
550 break;
552 case 2: // ------- RIGHT CLICK ------
553 if(new_curve) {
554 // in new-curve mode: finish curve (cancels last floating segment)
555 new_curve->remove_point(new_curve->size() - 1);
556 if(new_curve->empty()) {
557 delete new_curve;
558 } else {
559 curves.push_back(new_curve);
560 }
561 new_curve = 0;
563 } else if(sel_curve) {
564 // in selected curve mode: delete control point or unselect
565 Curve *hit_curve;
566 int hit_pidx;
567 if(hit_test(uv, &hit_curve, &hit_pidx) && hit_curve == sel_curve) {
568 if(hit_pidx != -1) {
569 hit_curve->remove_point(hit_pidx);
570 sel_pidx = -1;
571 }
572 } else {
573 sel_curve = 0;
574 sel_pidx = -1;
575 }
576 }
577 post_redisplay();
578 break;
580 default:
581 break;
582 }
583 }