curvedraw

view src/app.cc @ 3:bf78387a9925

pan/zoom, grid snapping
author John Tsiombikas <nuclear@member.fsf.org>
date Thu, 17 Dec 2015 07:10:10 +0200
parents ce7aa9a0594c
children 9f75208b81cd
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 }
82 if(weight_label) {
83 weight_label->draw();
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
93 }
95 static void draw_grid(float sz, float sep, float alpha)
96 {
97 float x = 0.0f;
98 float s = 1.0 / view_scale;
100 sz *= s;
101 sz += sep; // one more step for when we have non-zero fractional pan
102 float end = std::min(sz, 100.0f * sep);
104 // fractional pan
105 Vector2 pan = view_pan;
106 Vector2 fpan = Vector2(fmod(pan.x, sep), fmod(pan.y, sep));
107 Vector2 offset = fpan - pan;
109 glMatrixMode(GL_MODELVIEW);
110 glPushMatrix();
111 glTranslatef(offset.x, offset.y, 0);
113 glBegin(GL_LINES);
114 glColor4f(0.35, 0.35, 0.35, alpha);
115 while(x <= end) {
116 glVertex2f(-end, x);
117 glVertex2f(end, x);
118 glVertex2f(-end, -x);
119 glVertex2f(end, -x);
120 glVertex2f(x, -end);
121 glVertex2f(x, end);
122 glVertex2f(-x, -end);
123 glVertex2f(-x, end);
124 x += sep;
125 }
126 glEnd();
127 glPopMatrix();
130 glLineWidth(1.0);
131 glBegin(GL_LINES);
132 glColor4f(0.6, 0.3, 0.2, alpha);
133 glVertex2f(-sz + offset.x, 0);
134 glVertex2f(sz + offset.x, 0);
135 glColor4f(0.2, 0.3, 0.6, alpha);
136 glVertex2f(0, -sz + offset.y);
137 glVertex2f(0, sz + offset.y);
138 glEnd();
140 }
142 static void draw_curve(const Curve *curve)
143 {
144 int numpt = curve->size();
145 int segm = numpt * 16;
147 glLineWidth(curve == hover_curve ? 4.0 : 2.0);
148 if(curve == sel_curve) {
149 glColor3f(0.3, 0.4, 1.0);
150 } else if(curve == new_curve) {
151 glColor3f(1.0, 0.75, 0.3);
152 } else {
153 glColor3f(0.6, 0.6, 0.6);
154 }
155 glBegin(GL_LINE_STRIP);
156 for(int i=0; i<segm; i++) {
157 float t = (float)i / (float)(segm - 1);
158 Vector2 v = curve->interpolate(t);
159 glVertex2f(v.x, v.y);
160 }
161 glEnd();
162 glLineWidth(1.0);
164 glPointSize(curve == hover_curve ? 10.0 : 7.0);
165 glBegin(GL_POINTS);
166 if(curve == new_curve) {
167 glColor3f(1.0, 0.0, 0.0);
168 } else {
169 glColor3f(0.6, 0.3, 0.2);
170 }
171 for(int i=0; i<numpt; i++) {
172 if(curve == sel_curve) {
173 if(i == sel_pidx) {
174 glColor3f(1.0, 0.2, 0.1);
175 } else {
176 glColor3f(0.2, 1.0, 0.2);
177 }
178 }
179 Vector2 pt = curve->get_point(i);
180 glVertex2f(pt.x, pt.y);
181 }
182 glEnd();
183 glPointSize(1.0);
184 }
186 void app_reshape(int x, int y)
187 {
188 win_width = x;
189 win_height = y;
190 win_aspect = (float)x / (float)y;
192 glViewport(0, 0, x, y);
193 glMatrixMode(GL_PROJECTION);
194 glLoadIdentity();
195 glOrtho(-win_aspect, win_aspect, -1, 1, -1, 1);
196 }
198 void app_keyboard(int key, bool pressed)
199 {
200 if(pressed) {
201 switch(key) {
202 case 'q':
203 case 'Q':
204 exit(0);
206 case 27:
207 if(new_curve) {
208 delete new_curve;
209 new_curve = 0;
210 post_redisplay();
211 }
212 break;
214 case 'l':
215 case 'L':
216 if(sel_curve) {
217 sel_curve->set_type(CURVE_LINEAR);
218 post_redisplay();
219 }
220 if(new_curve) {
221 new_curve->set_type(CURVE_LINEAR);
222 post_redisplay();
223 }
224 break;
226 case 'b':
227 case 'B':
228 if(sel_curve) {
229 sel_curve->set_type(CURVE_BSPLINE);
230 post_redisplay();
231 }
232 if(new_curve) {
233 new_curve->set_type(CURVE_BSPLINE);
234 post_redisplay();
235 }
236 break;
238 case 'h':
239 case 'H':
240 if(sel_curve) {
241 sel_curve->set_type(CURVE_HERMITE);
242 post_redisplay();
243 }
244 if(new_curve) {
245 new_curve->set_type(CURVE_HERMITE);
246 post_redisplay();
247 }
248 break;
249 }
250 }
253 switch(key) {
254 case 's':
255 snap_mode = pressed ? SNAP_GRID : SNAP_NONE;
256 break;
258 case 'S':
259 snap_mode = pressed ? SNAP_POINT : SNAP_NONE;
260 break;
262 default:
263 break;
264 }
265 }
267 static void calc_view_matrix()
268 {
269 view_matrix.reset_identity();
270 view_matrix.scale(Vector3(view_scale, view_scale, view_scale));
271 view_matrix.translate(Vector3(view_pan.x, view_pan.y, 0.0));
272 }
274 static Vector2 pixel_to_uv(int x, int y)
275 {
276 float u = win_aspect * (2.0 * (float)x / (float)win_width - 1.0);
277 float v = 1.0 - 2.0 * (float)y / (float)win_height;
279 u = u / view_scale - view_pan.x;
280 v = v / view_scale - view_pan.y;
281 return Vector2(u, v);
282 /*
283 Matrix4x4 inv_view_matrix = view_matrix.inverse();
284 Vector4 res = Vector4(u, v, 0.0, 1.0).transformed(inv_view_matrix);
286 return Vector2(res.x, res.y);
287 */
288 }
290 static int prev_x, prev_y;
291 static int click_pos[8][2];
292 static unsigned int bnstate;
294 #define BNBIT(x) (1 << (x))
296 void app_mouse_button(int bn, bool pressed, int x, int y)
297 {
298 prev_x = x;
299 prev_y = y;
300 if(pressed) {
301 bnstate |= BNBIT(bn);
302 } else {
303 bnstate &= ~BNBIT(bn);
304 }
306 if(pressed) {
307 click_pos[bn][0] = x;
308 click_pos[bn][1] = y;
309 } else {
310 int dx = x - click_pos[bn][0];
311 int dy = y - click_pos[bn][1];
313 if(abs(dx) + abs(dy) < 3) {
314 Vector2 uv = pixel_to_uv(x, y);
315 on_click(bn, uv.x, uv.y);
316 }
318 if(!(bnstate & BNBIT(2))) {
319 delete weight_label;
320 weight_label = 0;
321 post_redisplay();
322 }
323 }
324 }
326 static bool point_hit_test(const Vector2 &pos, Curve **curveret, int *pidxret)
327 {
328 float thres = 0.02 / view_scale;
330 for(size_t i=0; i<curves.size(); i++) {
331 int pidx = curves[i]->nearest_point(pos);
332 if(pidx == -1) continue;
334 Vector2 cp = curves[i]->get_point(pidx);
335 if((cp - pos).length_sq() < thres * thres) {
336 *curveret = curves[i];
337 *pidxret = pidx;
338 return true;
339 }
340 }
341 *curveret = 0;
342 *pidxret = -1;
343 return false;
344 }
346 static Vector2 snap(const Vector2 &p)
347 {
348 switch(snap_mode) {
349 case SNAP_GRID:
350 return Vector2(round(p.x / grid_size) * grid_size, round(p.y / grid_size) * grid_size);
351 case SNAP_POINT:
352 // TODO
353 default:
354 break;
355 }
356 return p;
357 }
359 void app_mouse_motion(int x, int y)
360 {
361 Vector2 prev_uv = pixel_to_uv(prev_x, prev_y);
363 int dx = x - prev_x;
364 int dy = y - prev_y;
365 prev_x = x;
366 prev_y = y;
368 if(!dx && !dy) return;
370 Vector2 uv = pixel_to_uv(x, y);
371 #ifdef DRAW_MOUSE_POINTER
372 mouse_pointer = uv;
373 post_redisplay();
374 #endif
376 /* when entering a new curve, have the last (extra) point following
377 * the mouse until it's entered by a click (see on_click).
378 */
379 if(new_curve) {
380 new_curve->move_point(new_curve->size() - 1, snap(uv));
381 post_redisplay();
382 return;
383 }
384 // from this point on we're definitely not in new-curve mode
386 if(!bnstate) {
387 // not dragging, highlight curve under mouse
388 point_hit_test(uv, &hover_curve, &sel_pidx);
389 post_redisplay();
391 } else {
392 // we're dragging with one or more buttons held down
394 if(sel_curve && sel_pidx != -1) {
395 // we have a curve and a point of the curve selected
397 if(bnstate & BNBIT(0)) {
398 // dragging point with left button: move it
399 sel_curve->move_point(sel_pidx, snap(uv));
400 post_redisplay();
401 }
403 if(bnstate & BNBIT(2)) {
404 // dragging point with right button: change weight
405 float w = sel_curve->get_weight(sel_pidx);
406 w -= dy * 0.01;
407 if(w < FLT_MIN) w = FLT_MIN;
408 sel_curve->set_weight(sel_pidx, w);
410 // popup floating weight label if not already there
411 if(!weight_label) {
412 weight_label = new Label;
413 }
414 weight_label->set_position(uv);
415 weight_label->set_textf("w=%g", w);
416 post_redisplay();
417 }
418 } else {
419 // no selection, we're dragging in empty space: manipulate viewport
420 Vector2 dir = uv - prev_uv;
422 if(bnstate & (BNBIT(0) | BNBIT(1))) {
423 // panning
424 view_pan += dir;
425 calc_view_matrix();
426 post_redisplay();
427 }
428 if(bnstate & BNBIT(2)) {
429 // zooming
430 view_scale -= ((float)dy / (float)win_height) * view_scale * 5.0;
431 if(view_scale < 1e-4) view_scale = 1e-4;
432 calc_view_matrix();
433 post_redisplay();
434 }
435 }
436 }
437 }
439 static void on_click(int bn, float u, float v)
440 {
441 Vector2 uv = Vector2(u, v);
443 switch(bn) {
444 case 0: // ------- LEFT CLICK ------
445 if(hover_curve) {
446 // if we're hovering: click selects
447 sel_curve = hover_curve;
448 hover_curve = 0;
449 } else if(sel_curve) {
450 // if we have a selected curve: click adds point (enter new_curve mode)
451 std::vector<Curve*>::iterator it = std::find(curves.begin(), curves.end(), sel_curve);
452 assert(it != curves.end());
453 curves.erase(it, it + 1);
455 new_curve = sel_curve;
456 sel_curve = 0;
457 sel_pidx = -1;
459 new_curve->add_point(uv);
460 } else {
461 // otherwise, click starts a new curve
462 if(!new_curve) {
463 new_curve = new Curve;
464 new_curve->add_point(uv);
465 }
466 new_curve->add_point(uv);
467 }
468 post_redisplay();
469 break;
471 case 2: // ------- RIGHT CLICK ------
472 if(new_curve) {
473 // in new-curve mode: finish curve (cancels last floating segment)
474 new_curve->remove_point(new_curve->size() - 1);
475 if(new_curve->empty()) {
476 delete new_curve;
477 } else {
478 curves.push_back(new_curve);
479 }
480 new_curve = 0;
482 } else if(sel_curve) {
483 // in selected curve mode: delete control point or unselect
484 Curve *hit_curve;
485 int hit_pidx;
486 if(point_hit_test(uv, &hit_curve, &hit_pidx) && hit_curve == sel_curve) {
487 hit_curve->remove_point(hit_pidx);
488 sel_pidx = -1;
489 } else {
490 sel_curve = 0;
491 sel_pidx = -1;
492 }
493 }
494 post_redisplay();
495 break;
497 default:
498 break;
499 }
500 }