curvedraw
view src/app.cc @ 6:6e980fddbf3b
curve export
author | John Tsiombikas <nuclear@member.fsf.org> |
---|---|
date | Fri, 18 Dec 2015 07:07:19 +0200 |
parents | 2b7ae76c173f |
children | 95fada20c638 |
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 std::vector<Curve*> curves;
34 static Curve *sel_curve; // selected curve being edited
35 static Curve *new_curve; // new curve being entered
36 static Curve *hover_curve; // curve the mouse is hovering over (click to select)
37 static int sel_pidx = -1; // selected point of the selected or hovered-over curve
39 static Label *weight_label; // floating label for the cp weight
41 #ifdef DRAW_MOUSE_POINTER
42 static Vector2 mouse_pointer;
43 #endif
46 bool app_init(int argc, char **argv)
47 {
48 glewInit();
50 glEnable(GL_MULTISAMPLE);
51 glEnable(GL_CULL_FACE);
53 glEnable(GL_BLEND);
54 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
55 return true;
56 }
58 void app_cleanup()
59 {
60 for(size_t i=0; i<curves.size(); i++) {
61 delete curves[i];
62 }
63 curves.clear();
64 }
66 void app_draw()
67 {
68 glClearColor(0.1, 0.1, 0.1, 1);
69 glClear(GL_COLOR_BUFFER_BIT);
71 glMatrixMode(GL_MODELVIEW);
72 glLoadIdentity();
73 glTranslatef(view_pan.x * view_scale, view_pan.y * view_scale, 0);
74 glScalef(view_scale, view_scale, view_scale);
76 float max_aspect = std::max(win_aspect, 1.0f / win_aspect);
77 draw_grid(max_aspect, grid_size);
79 for(size_t i=0; i<curves.size(); i++) {
80 draw_curve(curves[i]);
81 }
82 if(new_curve) {
83 draw_curve(new_curve);
84 }
86 #ifdef DRAW_MOUSE_POINTER
87 glPointSize(6.0);
88 glBegin(GL_POINTS);
89 glColor3f(0, 0, 1);
90 glVertex2f(mouse_pointer.x, mouse_pointer.y);
91 glEnd();
92 #endif
94 glMatrixMode(GL_MODELVIEW);
95 glLoadIdentity();
97 if(weight_label) {
98 weight_label->draw();
99 }
100 }
102 static void draw_grid(float sz, float sep, float alpha)
103 {
104 float x = 0.0f;
105 float s = 1.0 / view_scale;
107 sz *= s;
108 sz += sep; // one more step for when we have non-zero fractional pan
109 float end = std::min(sz, 100.0f * sep);
111 // fractional pan
112 Vector2 pan = view_pan;
113 Vector2 fpan = Vector2(fmod(pan.x, sep), fmod(pan.y, sep));
114 Vector2 offset = fpan - pan;
116 glMatrixMode(GL_MODELVIEW);
117 glPushMatrix();
118 glTranslatef(offset.x, offset.y, 0);
120 glBegin(GL_LINES);
121 glColor4f(0.35, 0.35, 0.35, alpha);
122 while(x <= end) {
123 glVertex2f(-end, x);
124 glVertex2f(end, x);
125 glVertex2f(-end, -x);
126 glVertex2f(end, -x);
127 glVertex2f(x, -end);
128 glVertex2f(x, end);
129 glVertex2f(-x, -end);
130 glVertex2f(-x, end);
131 x += sep;
132 }
133 glEnd();
134 glPopMatrix();
137 glLineWidth(1.0);
138 glBegin(GL_LINES);
139 glColor4f(0.6, 0.3, 0.2, alpha);
140 glVertex2f(-sz + offset.x, 0);
141 glVertex2f(sz + offset.x, 0);
142 glColor4f(0.2, 0.3, 0.6, alpha);
143 glVertex2f(0, -sz + offset.y);
144 glVertex2f(0, sz + offset.y);
145 glEnd();
147 }
149 static void draw_curve(const Curve *curve)
150 {
151 int numpt = curve->size();
152 int segm = numpt * 16;
154 glLineWidth(curve == hover_curve ? 4.0 : 2.0);
155 if(curve == sel_curve) {
156 glColor3f(0.3, 0.4, 1.0);
157 } else if(curve == new_curve) {
158 glColor3f(1.0, 0.75, 0.3);
159 } else {
160 glColor3f(0.6, 0.6, 0.6);
161 }
162 glBegin(GL_LINE_STRIP);
163 for(int i=0; i<segm; i++) {
164 float t = (float)i / (float)(segm - 1);
165 Vector2 v = curve->interpolate(t);
166 glVertex2f(v.x, v.y);
167 }
168 glEnd();
169 glLineWidth(1.0);
171 glPointSize(curve == hover_curve ? 10.0 : 7.0);
172 glBegin(GL_POINTS);
173 if(curve == new_curve) {
174 glColor3f(1.0, 0.0, 0.0);
175 } else {
176 glColor3f(0.6, 0.3, 0.2);
177 }
178 for(int i=0; i<numpt; i++) {
179 if(curve == sel_curve) {
180 if(i == sel_pidx) {
181 glColor3f(1.0, 0.2, 0.1);
182 } else {
183 glColor3f(0.2, 1.0, 0.2);
184 }
185 }
186 Vector2 pt = curve->get_point(i);
187 glVertex2f(pt.x, pt.y);
188 }
189 glEnd();
190 glPointSize(1.0);
191 }
193 void app_reshape(int x, int y)
194 {
195 win_width = x;
196 win_height = y;
197 win_aspect = (float)x / (float)y;
199 glViewport(0, 0, x, y);
200 glMatrixMode(GL_PROJECTION);
201 glLoadIdentity();
202 glOrtho(-win_aspect, win_aspect, -1, 1, -1, 1);
203 }
205 void app_keyboard(int key, bool pressed)
206 {
207 if(pressed) {
208 switch(key) {
209 case 'q':
210 case 'Q':
211 exit(0);
213 case 27:
214 if(new_curve) {
215 delete new_curve;
216 new_curve = 0;
217 post_redisplay();
218 }
219 break;
221 case 'l':
222 case 'L':
223 if(sel_curve) {
224 sel_curve->set_type(CURVE_LINEAR);
225 post_redisplay();
226 }
227 if(new_curve) {
228 new_curve->set_type(CURVE_LINEAR);
229 post_redisplay();
230 }
231 break;
233 case 'b':
234 case 'B':
235 if(sel_curve) {
236 sel_curve->set_type(CURVE_BSPLINE);
237 post_redisplay();
238 }
239 if(new_curve) {
240 new_curve->set_type(CURVE_BSPLINE);
241 post_redisplay();
242 }
243 break;
245 case 'h':
246 case 'H':
247 if(sel_curve) {
248 sel_curve->set_type(CURVE_HERMITE);
249 post_redisplay();
250 }
251 if(new_curve) {
252 new_curve->set_type(CURVE_HERMITE);
253 post_redisplay();
254 }
255 break;
257 case 'e':
258 case 'E':
259 // TODO: GUI for filename at least
260 if(!save_curves(stdout, &curves[0], (int)curves.size())) {
261 fprintf(stderr, "failed to export curves\n");
262 }
263 printf("exported %d curves\n", (int)curves.size());
264 break;
265 }
266 }
269 switch(key) {
270 case 's':
271 snap_mode = pressed ? SNAP_GRID : SNAP_NONE;
272 break;
274 case 'S':
275 snap_mode = pressed ? SNAP_POINT : SNAP_NONE;
276 break;
278 default:
279 break;
280 }
281 }
283 static void calc_view_matrix()
284 {
285 view_matrix.reset_identity();
286 view_matrix.scale(Vector3(view_scale, view_scale, view_scale));
287 view_matrix.translate(Vector3(view_pan.x, view_pan.y, 0.0));
288 }
290 static Vector2 pixel_to_uv(int x, int y)
291 {
292 float u = win_aspect * (2.0 * (float)x / (float)win_width - 1.0);
293 float v = 1.0 - 2.0 * (float)y / (float)win_height;
295 u = u / view_scale - view_pan.x;
296 v = v / view_scale - view_pan.y;
297 return Vector2(u, v);
298 /*
299 Matrix4x4 inv_view_matrix = view_matrix.inverse();
300 Vector4 res = Vector4(u, v, 0.0, 1.0).transformed(inv_view_matrix);
302 return Vector2(res.x, res.y);
303 */
304 }
306 static int prev_x, prev_y;
307 static int click_pos[8][2];
308 static unsigned int bnstate;
310 #define BNBIT(x) (1 << (x))
312 void app_mouse_button(int bn, bool pressed, int x, int y)
313 {
314 prev_x = x;
315 prev_y = y;
316 if(pressed) {
317 bnstate |= BNBIT(bn);
318 } else {
319 bnstate &= ~BNBIT(bn);
320 }
322 if(pressed) {
323 click_pos[bn][0] = x;
324 click_pos[bn][1] = y;
325 } else {
326 int dx = x - click_pos[bn][0];
327 int dy = y - click_pos[bn][1];
329 if(abs(dx) + abs(dy) < 3) {
330 Vector2 uv = pixel_to_uv(x, y);
331 on_click(bn, uv.x, uv.y);
332 }
334 if(!(bnstate & BNBIT(2))) {
335 delete weight_label;
336 weight_label = 0;
337 post_redisplay();
338 }
339 }
340 }
342 static bool point_hit_test(const Vector2 &pos, Curve **curveret, int *pidxret)
343 {
344 float thres = 0.02 / view_scale;
346 for(size_t i=0; i<curves.size(); i++) {
347 int pidx = curves[i]->nearest_point(pos);
348 if(pidx == -1) continue;
350 Vector2 cp = curves[i]->get_point(pidx);
351 if((cp - pos).length_sq() < thres * thres) {
352 *curveret = curves[i];
353 *pidxret = pidx;
354 return true;
355 }
356 }
357 *curveret = 0;
358 *pidxret = -1;
359 return false;
360 }
362 static Vector2 snap(const Vector2 &p)
363 {
364 switch(snap_mode) {
365 case SNAP_GRID:
366 return Vector2(round(p.x / grid_size) * grid_size, round(p.y / grid_size) * grid_size);
367 case SNAP_POINT:
368 // TODO
369 default:
370 break;
371 }
372 return p;
373 }
375 void app_mouse_motion(int x, int y)
376 {
377 Vector2 prev_uv = pixel_to_uv(prev_x, prev_y);
379 int dx = x - prev_x;
380 int dy = y - prev_y;
381 prev_x = x;
382 prev_y = y;
384 if(!dx && !dy) return;
386 Vector2 uv = pixel_to_uv(x, y);
387 #ifdef DRAW_MOUSE_POINTER
388 mouse_pointer = uv;
389 post_redisplay();
390 #endif
392 /* when entering a new curve, have the last (extra) point following
393 * the mouse until it's entered by a click (see on_click).
394 */
395 if(new_curve) {
396 new_curve->move_point(new_curve->size() - 1, snap(uv));
397 post_redisplay();
398 }
400 if(!new_curve && !bnstate) {
401 // not dragging, highlight curve under mouse
402 point_hit_test(uv, &hover_curve, &sel_pidx);
403 post_redisplay();
405 } else {
406 // we're dragging with one or more buttons held down
408 if(sel_curve && sel_pidx != -1) {
409 // we have a curve and a point of the curve selected
411 if(bnstate & BNBIT(0)) {
412 // dragging point with left button: move it
413 sel_curve->move_point(sel_pidx, snap(uv));
414 post_redisplay();
415 }
417 if(bnstate & BNBIT(2)) {
418 // dragging point with right button: change weight
419 float w = sel_curve->get_weight(sel_pidx);
420 w -= dy * 0.01;
421 if(w < FLT_MIN) w = FLT_MIN;
422 sel_curve->set_weight(sel_pidx, w);
424 // popup floating weight label if not already there
425 if(!weight_label) {
426 weight_label = new Label;
427 }
428 weight_label->set_position(uv);
429 weight_label->set_textf("w=%g", w);
430 post_redisplay();
431 }
432 } else {
433 // no selection, we're dragging in empty space: manipulate viewport
434 Vector2 dir = uv - prev_uv;
436 if(bnstate & (BNBIT(0) | BNBIT(1))) {
437 // panning
438 view_pan += dir;
439 calc_view_matrix();
440 post_redisplay();
441 }
442 if(bnstate & BNBIT(2)) {
443 // zooming
444 view_scale -= ((float)dy / (float)win_height) * view_scale * 5.0;
445 if(view_scale < 1e-4) view_scale = 1e-4;
446 calc_view_matrix();
447 post_redisplay();
448 }
449 }
450 }
451 }
453 static void on_click(int bn, float u, float v)
454 {
455 Vector2 uv = Vector2(u, v);
457 switch(bn) {
458 case 0: // ------- LEFT CLICK ------
459 if(hover_curve) {
460 // if we're hovering: click selects
461 sel_curve = hover_curve;
462 hover_curve = 0;
463 } else if(sel_curve) {
464 // if we have a selected curve: click adds point (enter new_curve mode)
465 std::vector<Curve*>::iterator it = std::find(curves.begin(), curves.end(), sel_curve);
466 assert(it != curves.end());
467 curves.erase(it, it + 1);
469 new_curve = sel_curve;
470 sel_curve = 0;
471 sel_pidx = -1;
473 new_curve->add_point(uv);
474 } else {
475 // otherwise, click starts a new curve
476 if(!new_curve) {
477 new_curve = new Curve;
478 new_curve->add_point(uv);
479 }
480 new_curve->add_point(uv);
481 }
482 post_redisplay();
483 break;
485 case 2: // ------- RIGHT CLICK ------
486 if(new_curve) {
487 // in new-curve mode: finish curve (cancels last floating segment)
488 new_curve->remove_point(new_curve->size() - 1);
489 if(new_curve->empty()) {
490 delete new_curve;
491 } else {
492 curves.push_back(new_curve);
493 }
494 new_curve = 0;
496 } else if(sel_curve) {
497 // in selected curve mode: delete control point or unselect
498 Curve *hit_curve;
499 int hit_pidx;
500 if(point_hit_test(uv, &hit_curve, &hit_pidx) && hit_curve == sel_curve) {
501 hit_curve->remove_point(hit_pidx);
502 sel_pidx = -1;
503 } else {
504 sel_curve = 0;
505 sel_pidx = -1;
506 }
507 }
508 post_redisplay();
509 break;
511 default:
512 break;
513 }
514 }