curvedraw
view src/app.cc @ 5:2b7ae76c173f
windows port
author | John Tsiombikas <nuclear@member.fsf.org> |
---|---|
date | Fri, 18 Dec 2015 03:47:10 +0200 |
parents | 9f75208b81cd |
children | 6e980fddbf3b |
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 glewInit();
49 glEnable(GL_MULTISAMPLE);
50 glEnable(GL_CULL_FACE);
52 glEnable(GL_BLEND);
53 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
54 return true;
55 }
57 void app_cleanup()
58 {
59 for(size_t i=0; i<curves.size(); i++) {
60 delete curves[i];
61 }
62 curves.clear();
63 }
65 void app_draw()
66 {
67 glClearColor(0.1, 0.1, 0.1, 1);
68 glClear(GL_COLOR_BUFFER_BIT);
70 glMatrixMode(GL_MODELVIEW);
71 glLoadIdentity();
72 glTranslatef(view_pan.x * view_scale, view_pan.y * view_scale, 0);
73 glScalef(view_scale, view_scale, view_scale);
75 float max_aspect = std::max(win_aspect, 1.0f / win_aspect);
76 draw_grid(max_aspect, grid_size);
78 for(size_t i=0; i<curves.size(); i++) {
79 draw_curve(curves[i]);
80 }
81 if(new_curve) {
82 draw_curve(new_curve);
83 }
85 #ifdef DRAW_MOUSE_POINTER
86 glPointSize(6.0);
87 glBegin(GL_POINTS);
88 glColor3f(0, 0, 1);
89 glVertex2f(mouse_pointer.x, mouse_pointer.y);
90 glEnd();
91 #endif
93 glMatrixMode(GL_MODELVIEW);
94 glLoadIdentity();
96 if(weight_label) {
97 weight_label->draw();
98 }
99 }
101 static void draw_grid(float sz, float sep, float alpha)
102 {
103 float x = 0.0f;
104 float s = 1.0 / view_scale;
106 sz *= s;
107 sz += sep; // one more step for when we have non-zero fractional pan
108 float end = std::min(sz, 100.0f * sep);
110 // fractional pan
111 Vector2 pan = view_pan;
112 Vector2 fpan = Vector2(fmod(pan.x, sep), fmod(pan.y, sep));
113 Vector2 offset = fpan - pan;
115 glMatrixMode(GL_MODELVIEW);
116 glPushMatrix();
117 glTranslatef(offset.x, offset.y, 0);
119 glBegin(GL_LINES);
120 glColor4f(0.35, 0.35, 0.35, alpha);
121 while(x <= end) {
122 glVertex2f(-end, x);
123 glVertex2f(end, x);
124 glVertex2f(-end, -x);
125 glVertex2f(end, -x);
126 glVertex2f(x, -end);
127 glVertex2f(x, end);
128 glVertex2f(-x, -end);
129 glVertex2f(-x, end);
130 x += sep;
131 }
132 glEnd();
133 glPopMatrix();
136 glLineWidth(1.0);
137 glBegin(GL_LINES);
138 glColor4f(0.6, 0.3, 0.2, alpha);
139 glVertex2f(-sz + offset.x, 0);
140 glVertex2f(sz + offset.x, 0);
141 glColor4f(0.2, 0.3, 0.6, alpha);
142 glVertex2f(0, -sz + offset.y);
143 glVertex2f(0, sz + offset.y);
144 glEnd();
146 }
148 static void draw_curve(const Curve *curve)
149 {
150 int numpt = curve->size();
151 int segm = numpt * 16;
153 glLineWidth(curve == hover_curve ? 4.0 : 2.0);
154 if(curve == sel_curve) {
155 glColor3f(0.3, 0.4, 1.0);
156 } else if(curve == new_curve) {
157 glColor3f(1.0, 0.75, 0.3);
158 } else {
159 glColor3f(0.6, 0.6, 0.6);
160 }
161 glBegin(GL_LINE_STRIP);
162 for(int i=0; i<segm; i++) {
163 float t = (float)i / (float)(segm - 1);
164 Vector2 v = curve->interpolate(t);
165 glVertex2f(v.x, v.y);
166 }
167 glEnd();
168 glLineWidth(1.0);
170 glPointSize(curve == hover_curve ? 10.0 : 7.0);
171 glBegin(GL_POINTS);
172 if(curve == new_curve) {
173 glColor3f(1.0, 0.0, 0.0);
174 } else {
175 glColor3f(0.6, 0.3, 0.2);
176 }
177 for(int i=0; i<numpt; i++) {
178 if(curve == sel_curve) {
179 if(i == sel_pidx) {
180 glColor3f(1.0, 0.2, 0.1);
181 } else {
182 glColor3f(0.2, 1.0, 0.2);
183 }
184 }
185 Vector2 pt = curve->get_point(i);
186 glVertex2f(pt.x, pt.y);
187 }
188 glEnd();
189 glPointSize(1.0);
190 }
192 void app_reshape(int x, int y)
193 {
194 win_width = x;
195 win_height = y;
196 win_aspect = (float)x / (float)y;
198 glViewport(0, 0, x, y);
199 glMatrixMode(GL_PROJECTION);
200 glLoadIdentity();
201 glOrtho(-win_aspect, win_aspect, -1, 1, -1, 1);
202 }
204 void app_keyboard(int key, bool pressed)
205 {
206 if(pressed) {
207 switch(key) {
208 case 'q':
209 case 'Q':
210 exit(0);
212 case 27:
213 if(new_curve) {
214 delete new_curve;
215 new_curve = 0;
216 post_redisplay();
217 }
218 break;
220 case 'l':
221 case 'L':
222 if(sel_curve) {
223 sel_curve->set_type(CURVE_LINEAR);
224 post_redisplay();
225 }
226 if(new_curve) {
227 new_curve->set_type(CURVE_LINEAR);
228 post_redisplay();
229 }
230 break;
232 case 'b':
233 case 'B':
234 if(sel_curve) {
235 sel_curve->set_type(CURVE_BSPLINE);
236 post_redisplay();
237 }
238 if(new_curve) {
239 new_curve->set_type(CURVE_BSPLINE);
240 post_redisplay();
241 }
242 break;
244 case 'h':
245 case 'H':
246 if(sel_curve) {
247 sel_curve->set_type(CURVE_HERMITE);
248 post_redisplay();
249 }
250 if(new_curve) {
251 new_curve->set_type(CURVE_HERMITE);
252 post_redisplay();
253 }
254 break;
255 }
256 }
259 switch(key) {
260 case 's':
261 snap_mode = pressed ? SNAP_GRID : SNAP_NONE;
262 break;
264 case 'S':
265 snap_mode = pressed ? SNAP_POINT : SNAP_NONE;
266 break;
268 default:
269 break;
270 }
271 }
273 static void calc_view_matrix()
274 {
275 view_matrix.reset_identity();
276 view_matrix.scale(Vector3(view_scale, view_scale, view_scale));
277 view_matrix.translate(Vector3(view_pan.x, view_pan.y, 0.0));
278 }
280 static Vector2 pixel_to_uv(int x, int y)
281 {
282 float u = win_aspect * (2.0 * (float)x / (float)win_width - 1.0);
283 float v = 1.0 - 2.0 * (float)y / (float)win_height;
285 u = u / view_scale - view_pan.x;
286 v = v / view_scale - view_pan.y;
287 return Vector2(u, v);
288 /*
289 Matrix4x4 inv_view_matrix = view_matrix.inverse();
290 Vector4 res = Vector4(u, v, 0.0, 1.0).transformed(inv_view_matrix);
292 return Vector2(res.x, res.y);
293 */
294 }
296 static int prev_x, prev_y;
297 static int click_pos[8][2];
298 static unsigned int bnstate;
300 #define BNBIT(x) (1 << (x))
302 void app_mouse_button(int bn, bool pressed, int x, int y)
303 {
304 prev_x = x;
305 prev_y = y;
306 if(pressed) {
307 bnstate |= BNBIT(bn);
308 } else {
309 bnstate &= ~BNBIT(bn);
310 }
312 if(pressed) {
313 click_pos[bn][0] = x;
314 click_pos[bn][1] = y;
315 } else {
316 int dx = x - click_pos[bn][0];
317 int dy = y - click_pos[bn][1];
319 if(abs(dx) + abs(dy) < 3) {
320 Vector2 uv = pixel_to_uv(x, y);
321 on_click(bn, uv.x, uv.y);
322 }
324 if(!(bnstate & BNBIT(2))) {
325 delete weight_label;
326 weight_label = 0;
327 post_redisplay();
328 }
329 }
330 }
332 static bool point_hit_test(const Vector2 &pos, Curve **curveret, int *pidxret)
333 {
334 float thres = 0.02 / view_scale;
336 for(size_t i=0; i<curves.size(); i++) {
337 int pidx = curves[i]->nearest_point(pos);
338 if(pidx == -1) continue;
340 Vector2 cp = curves[i]->get_point(pidx);
341 if((cp - pos).length_sq() < thres * thres) {
342 *curveret = curves[i];
343 *pidxret = pidx;
344 return true;
345 }
346 }
347 *curveret = 0;
348 *pidxret = -1;
349 return false;
350 }
352 static Vector2 snap(const Vector2 &p)
353 {
354 switch(snap_mode) {
355 case SNAP_GRID:
356 return Vector2(round(p.x / grid_size) * grid_size, round(p.y / grid_size) * grid_size);
357 case SNAP_POINT:
358 // TODO
359 default:
360 break;
361 }
362 return p;
363 }
365 void app_mouse_motion(int x, int y)
366 {
367 Vector2 prev_uv = pixel_to_uv(prev_x, prev_y);
369 int dx = x - prev_x;
370 int dy = y - prev_y;
371 prev_x = x;
372 prev_y = y;
374 if(!dx && !dy) return;
376 Vector2 uv = pixel_to_uv(x, y);
377 #ifdef DRAW_MOUSE_POINTER
378 mouse_pointer = uv;
379 post_redisplay();
380 #endif
382 /* when entering a new curve, have the last (extra) point following
383 * the mouse until it's entered by a click (see on_click).
384 */
385 if(new_curve) {
386 new_curve->move_point(new_curve->size() - 1, snap(uv));
387 post_redisplay();
388 }
390 if(!new_curve && !bnstate) {
391 // not dragging, highlight curve under mouse
392 point_hit_test(uv, &hover_curve, &sel_pidx);
393 post_redisplay();
395 } else {
396 // we're dragging with one or more buttons held down
398 if(sel_curve && sel_pidx != -1) {
399 // we have a curve and a point of the curve selected
401 if(bnstate & BNBIT(0)) {
402 // dragging point with left button: move it
403 sel_curve->move_point(sel_pidx, snap(uv));
404 post_redisplay();
405 }
407 if(bnstate & BNBIT(2)) {
408 // dragging point with right button: change weight
409 float w = sel_curve->get_weight(sel_pidx);
410 w -= dy * 0.01;
411 if(w < FLT_MIN) w = FLT_MIN;
412 sel_curve->set_weight(sel_pidx, w);
414 // popup floating weight label if not already there
415 if(!weight_label) {
416 weight_label = new Label;
417 }
418 weight_label->set_position(uv);
419 weight_label->set_textf("w=%g", w);
420 post_redisplay();
421 }
422 } else {
423 // no selection, we're dragging in empty space: manipulate viewport
424 Vector2 dir = uv - prev_uv;
426 if(bnstate & (BNBIT(0) | BNBIT(1))) {
427 // panning
428 view_pan += dir;
429 calc_view_matrix();
430 post_redisplay();
431 }
432 if(bnstate & BNBIT(2)) {
433 // zooming
434 view_scale -= ((float)dy / (float)win_height) * view_scale * 5.0;
435 if(view_scale < 1e-4) view_scale = 1e-4;
436 calc_view_matrix();
437 post_redisplay();
438 }
439 }
440 }
441 }
443 static void on_click(int bn, float u, float v)
444 {
445 Vector2 uv = Vector2(u, v);
447 switch(bn) {
448 case 0: // ------- LEFT CLICK ------
449 if(hover_curve) {
450 // if we're hovering: click selects
451 sel_curve = hover_curve;
452 hover_curve = 0;
453 } else if(sel_curve) {
454 // if we have a selected curve: click adds point (enter new_curve mode)
455 std::vector<Curve*>::iterator it = std::find(curves.begin(), curves.end(), sel_curve);
456 assert(it != curves.end());
457 curves.erase(it, it + 1);
459 new_curve = sel_curve;
460 sel_curve = 0;
461 sel_pidx = -1;
463 new_curve->add_point(uv);
464 } else {
465 // otherwise, click starts a new curve
466 if(!new_curve) {
467 new_curve = new Curve;
468 new_curve->add_point(uv);
469 }
470 new_curve->add_point(uv);
471 }
472 post_redisplay();
473 break;
475 case 2: // ------- RIGHT CLICK ------
476 if(new_curve) {
477 // in new-curve mode: finish curve (cancels last floating segment)
478 new_curve->remove_point(new_curve->size() - 1);
479 if(new_curve->empty()) {
480 delete new_curve;
481 } else {
482 curves.push_back(new_curve);
483 }
484 new_curve = 0;
486 } else if(sel_curve) {
487 // in selected curve mode: delete control point or unselect
488 Curve *hit_curve;
489 int hit_pidx;
490 if(point_hit_test(uv, &hit_curve, &hit_pidx) && hit_curve == sel_curve) {
491 hit_curve->remove_point(hit_pidx);
492 sel_pidx = -1;
493 } else {
494 sel_curve = 0;
495 sel_pidx = -1;
496 }
497 }
498 post_redisplay();
499 break;
501 default:
502 break;
503 }
504 }