curvedraw

annotate src/app.cc @ 2:ce7aa9a0594c

improved curve editing
author John Tsiombikas <nuclear@member.fsf.org>
date Thu, 17 Dec 2015 05:13:25 +0200
parents 7dcd0f6113e5
children bf78387a9925
rev   line source
nuclear@0 1 #include <stdlib.h>
nuclear@0 2 #include <float.h>
nuclear@2 3 #include <assert.h>
nuclear@0 4 #include <vector>
nuclear@0 5 #include <algorithm>
nuclear@0 6 #include "opengl.h"
nuclear@0 7 #include "app.h"
nuclear@0 8 #include "curve.h"
nuclear@0 9 #include "widgets.h"
nuclear@0 10
nuclear@1 11 int win_width, win_height;
nuclear@1 12 float win_aspect;
nuclear@1 13
nuclear@0 14 static void draw_grid(float sz, float sep, float alpha = 1.0f);
nuclear@0 15 static void draw_curve(const Curve *curve);
nuclear@0 16 static void on_click(int bn, float u, float v);
nuclear@0 17
nuclear@2 18 // viewport control
nuclear@2 19 static Vector2 view_pan;
nuclear@0 20 static float view_scale = 1.0f;
nuclear@0 21
nuclear@0 22 static std::vector<Curve*> curves;
nuclear@2 23 static Curve *sel_curve; // selected curve being edited
nuclear@2 24 static Curve *new_curve; // new curve being entered
nuclear@2 25 static Curve *hover_curve; // curve the mouse is hovering over (click to select)
nuclear@2 26 static int sel_pidx = -1; // selected point of the selected or hovered-over curve
nuclear@0 27
nuclear@2 28 static Label *weight_label; // floating label for the cp weight
nuclear@0 29
nuclear@0 30
nuclear@0 31 bool app_init(int argc, char **argv)
nuclear@0 32 {
nuclear@0 33 glEnable(GL_MULTISAMPLE);
nuclear@0 34 glEnable(GL_CULL_FACE);
nuclear@2 35
nuclear@2 36 glEnable(GL_BLEND);
nuclear@2 37 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
nuclear@0 38 return true;
nuclear@0 39 }
nuclear@0 40
nuclear@0 41 void app_cleanup()
nuclear@0 42 {
nuclear@0 43 for(size_t i=0; i<curves.size(); i++) {
nuclear@0 44 delete curves[i];
nuclear@0 45 }
nuclear@0 46 curves.clear();
nuclear@0 47 }
nuclear@0 48
nuclear@0 49 void app_draw()
nuclear@0 50 {
nuclear@1 51 glClearColor(0.1, 0.1, 0.1, 1);
nuclear@0 52 glClear(GL_COLOR_BUFFER_BIT);
nuclear@0 53
nuclear@0 54 glMatrixMode(GL_MODELVIEW);
nuclear@0 55 glLoadIdentity();
nuclear@2 56 glTranslatef(-view_pan.x, -view_pan.y, 0);
nuclear@0 57 glScalef(view_scale, view_scale, view_scale);
nuclear@0 58
nuclear@0 59 draw_grid(std::max(win_aspect, 1.0f / win_aspect), 0.1);
nuclear@0 60
nuclear@0 61 for(size_t i=0; i<curves.size(); i++) {
nuclear@0 62 draw_curve(curves[i]);
nuclear@0 63 }
nuclear@0 64 if(new_curve) {
nuclear@0 65 draw_curve(new_curve);
nuclear@0 66 }
nuclear@0 67 if(weight_label) {
nuclear@0 68 weight_label->draw();
nuclear@0 69 }
nuclear@0 70 }
nuclear@0 71
nuclear@0 72 static void draw_grid(float sz, float sep, float alpha)
nuclear@0 73 {
nuclear@0 74 float x = 0.0f;
nuclear@0 75
nuclear@0 76 glLineWidth(1.0);
nuclear@0 77 glBegin(GL_LINES);
nuclear@0 78 glColor4f(0.6, 0.3, 0.2, alpha);
nuclear@0 79 glVertex2f(-sz, 0);
nuclear@0 80 glVertex2f(sz, 0);
nuclear@0 81 glColor4f(0.2, 0.3, 0.6, alpha);
nuclear@0 82 glVertex2f(0, -sz);
nuclear@0 83 glVertex2f(0, sz);
nuclear@1 84 glColor4f(0.35, 0.35, 0.35, alpha);
nuclear@0 85 while(x < sz) {
nuclear@0 86 x += sep;
nuclear@0 87 glVertex2f(-sz, x);
nuclear@0 88 glVertex2f(sz, x);
nuclear@0 89 glVertex2f(-sz, -x);
nuclear@0 90 glVertex2f(sz, -x);
nuclear@0 91 glVertex2f(x, -sz);
nuclear@0 92 glVertex2f(x, sz);
nuclear@0 93 glVertex2f(-x, -sz);
nuclear@0 94 glVertex2f(-x, sz);
nuclear@0 95 }
nuclear@0 96 glEnd();
nuclear@0 97 }
nuclear@0 98
nuclear@0 99 static void draw_curve(const Curve *curve)
nuclear@0 100 {
nuclear@2 101 int numpt = curve->size();
nuclear@2 102 int segm = numpt * 16;
nuclear@0 103
nuclear@2 104 /*if(curve == hover_curve) {
nuclear@2 105 glLineWidth(3.0);
nuclear@2 106 glColor4f(0.8, 0.8, 0.0, 1.0);
nuclear@2 107
nuclear@2 108 glBegin(GL_LINE_STRIP);
nuclear@2 109 for(int i=0; i<segm; i++) {
nuclear@2 110 float t = (float)i / (float)(segm - 1);
nuclear@2 111 Vector2 v = curve->interpolate(t);
nuclear@2 112 glVertex2f(v.x, v.y);
nuclear@2 113 }
nuclear@2 114 glEnd();
nuclear@2 115 }
nuclear@2 116 */
nuclear@2 117
nuclear@2 118 glLineWidth(curve == hover_curve ? 4.0 : 2.0);
nuclear@1 119 if(curve == sel_curve) {
nuclear@1 120 glColor3f(0.3, 0.4, 1.0);
nuclear@1 121 } else if(curve == new_curve) {
nuclear@1 122 glColor3f(1.0, 0.75, 0.3);
nuclear@1 123 } else {
nuclear@2 124 glColor3f(0.6, 0.6, 0.6);
nuclear@1 125 }
nuclear@0 126 glBegin(GL_LINE_STRIP);
nuclear@0 127 for(int i=0; i<segm; i++) {
nuclear@0 128 float t = (float)i / (float)(segm - 1);
nuclear@0 129 Vector2 v = curve->interpolate(t);
nuclear@0 130 glVertex2f(v.x, v.y);
nuclear@0 131 }
nuclear@0 132 glEnd();
nuclear@0 133 glLineWidth(1.0);
nuclear@0 134
nuclear@2 135 glPointSize(curve == hover_curve ? 10.0 : 7.0);
nuclear@0 136 glBegin(GL_POINTS);
nuclear@1 137 if(curve == new_curve) {
nuclear@1 138 glColor3f(1.0, 0.0, 0.0);
nuclear@1 139 } else {
nuclear@1 140 glColor3f(0.6, 0.3, 0.2);
nuclear@1 141 }
nuclear@0 142 for(int i=0; i<numpt; i++) {
nuclear@1 143 if(curve == sel_curve) {
nuclear@1 144 if(i == sel_pidx) {
nuclear@1 145 glColor3f(1.0, 0.2, 0.1);
nuclear@1 146 } else {
nuclear@1 147 glColor3f(0.2, 1.0, 0.2);
nuclear@1 148 }
nuclear@1 149 }
nuclear@0 150 Vector2 pt = curve->get_point(i);
nuclear@0 151 glVertex2f(pt.x, pt.y);
nuclear@0 152 }
nuclear@0 153 glEnd();
nuclear@0 154 glPointSize(1.0);
nuclear@0 155 }
nuclear@0 156
nuclear@0 157 void app_reshape(int x, int y)
nuclear@0 158 {
nuclear@0 159 win_width = x;
nuclear@0 160 win_height = y;
nuclear@0 161 win_aspect = (float)x / (float)y;
nuclear@0 162
nuclear@0 163 glViewport(0, 0, x, y);
nuclear@0 164 glMatrixMode(GL_PROJECTION);
nuclear@0 165 glLoadIdentity();
nuclear@0 166 glOrtho(-win_aspect, win_aspect, -1, 1, -1, 1);
nuclear@0 167 }
nuclear@0 168
nuclear@0 169 void app_keyboard(int key, bool pressed)
nuclear@0 170 {
nuclear@0 171 if(pressed) {
nuclear@0 172 switch(key) {
nuclear@1 173 case 'q':
nuclear@1 174 case 'Q':
nuclear@1 175 exit(0);
nuclear@1 176
nuclear@0 177 case 27:
nuclear@1 178 if(new_curve) {
nuclear@1 179 delete new_curve;
nuclear@1 180 new_curve = 0;
nuclear@1 181 post_redisplay();
nuclear@1 182 }
nuclear@1 183 break;
nuclear@0 184
nuclear@0 185 case 'l':
nuclear@0 186 case 'L':
nuclear@0 187 if(sel_curve) {
nuclear@0 188 sel_curve->set_type(CURVE_LINEAR);
nuclear@0 189 post_redisplay();
nuclear@0 190 }
nuclear@1 191 if(new_curve) {
nuclear@1 192 new_curve->set_type(CURVE_LINEAR);
nuclear@1 193 post_redisplay();
nuclear@1 194 }
nuclear@0 195 break;
nuclear@0 196
nuclear@0 197 case 'b':
nuclear@0 198 case 'B':
nuclear@0 199 if(sel_curve) {
nuclear@0 200 sel_curve->set_type(CURVE_BSPLINE);
nuclear@0 201 post_redisplay();
nuclear@0 202 }
nuclear@1 203 if(new_curve) {
nuclear@1 204 new_curve->set_type(CURVE_BSPLINE);
nuclear@1 205 post_redisplay();
nuclear@1 206 }
nuclear@0 207 break;
nuclear@0 208
nuclear@0 209 case 'h':
nuclear@0 210 case 'H':
nuclear@0 211 if(sel_curve) {
nuclear@0 212 sel_curve->set_type(CURVE_HERMITE);
nuclear@0 213 post_redisplay();
nuclear@0 214 }
nuclear@1 215 if(new_curve) {
nuclear@1 216 new_curve->set_type(CURVE_HERMITE);
nuclear@1 217 post_redisplay();
nuclear@1 218 }
nuclear@0 219 break;
nuclear@0 220 }
nuclear@0 221 }
nuclear@0 222 }
nuclear@0 223
nuclear@0 224 static Vector2 pixel_to_uv(int x, int y)
nuclear@0 225 {
nuclear@0 226 float u = win_aspect * (2.0 * (float)x / (float)win_width - 1.0);
nuclear@0 227 float v = 1.0 - 2.0 * (float)y / (float)win_height;
nuclear@0 228 return Vector2(u, v);
nuclear@0 229 }
nuclear@0 230
nuclear@0 231 static int prev_x, prev_y;
nuclear@0 232 static int click_pos[8][2];
nuclear@0 233 static unsigned int bnstate;
nuclear@0 234
nuclear@0 235 #define BNBIT(x) (1 << (x))
nuclear@0 236
nuclear@0 237 void app_mouse_button(int bn, bool pressed, int x, int y)
nuclear@0 238 {
nuclear@0 239 prev_x = x;
nuclear@0 240 prev_y = y;
nuclear@0 241 if(pressed) {
nuclear@0 242 bnstate |= BNBIT(bn);
nuclear@0 243 } else {
nuclear@0 244 bnstate &= ~BNBIT(bn);
nuclear@0 245 }
nuclear@0 246
nuclear@0 247 if(pressed) {
nuclear@0 248 click_pos[bn][0] = x;
nuclear@0 249 click_pos[bn][1] = y;
nuclear@0 250 } else {
nuclear@0 251 int dx = x - click_pos[bn][0];
nuclear@0 252 int dy = y - click_pos[bn][1];
nuclear@0 253
nuclear@0 254 if(abs(dx) + abs(dy) < 3) {
nuclear@0 255 Vector2 uv = pixel_to_uv(x, y);
nuclear@0 256 on_click(bn, uv.x, uv.y);
nuclear@0 257 }
nuclear@0 258
nuclear@0 259 if(!(bnstate & BNBIT(2))) {
nuclear@0 260 delete weight_label;
nuclear@0 261 weight_label = 0;
nuclear@0 262 post_redisplay();
nuclear@0 263 }
nuclear@0 264 }
nuclear@0 265 }
nuclear@0 266
nuclear@2 267 static bool point_hit_test(const Vector2 &pos, Curve **curveret, int *pidxret)
nuclear@0 268 {
nuclear@1 269 float thres = 0.02;
nuclear@0 270
nuclear@0 271 for(size_t i=0; i<curves.size(); i++) {
nuclear@2 272 int pidx = curves[i]->nearest_point(pos);
nuclear@0 273 if(pidx == -1) continue;
nuclear@0 274
nuclear@0 275 Vector2 cp = curves[i]->get_point(pidx);
nuclear@2 276 if((cp - pos).length_sq() < thres * thres) {
nuclear@2 277 *curveret = curves[i];
nuclear@2 278 *pidxret = pidx;
nuclear@2 279 return true;
nuclear@0 280 }
nuclear@0 281 }
nuclear@2 282 *curveret = 0;
nuclear@2 283 *pidxret = -1;
nuclear@2 284 return false;
nuclear@0 285 }
nuclear@0 286
nuclear@0 287 void app_mouse_motion(int x, int y)
nuclear@0 288 {
nuclear@0 289 int dx = x - prev_x;
nuclear@0 290 int dy = y - prev_y;
nuclear@0 291 prev_x = x;
nuclear@0 292 prev_y = y;
nuclear@0 293
nuclear@0 294 if(!dx && !dy) return;
nuclear@0 295
nuclear@2 296 Vector2 uv = pixel_to_uv(x, y);
nuclear@2 297
nuclear@2 298 /* when entering a new curve, have the last (extra) point following
nuclear@2 299 * the mouse until it's entered by a click (see on_click).
nuclear@2 300 */
nuclear@2 301 if(new_curve && !new_curve->empty()) {
nuclear@2 302 new_curve->move_point(new_curve->size() - 1, uv);
nuclear@2 303 post_redisplay();
nuclear@2 304 }
nuclear@2 305
nuclear@0 306 if(!new_curve && !bnstate) {
nuclear@2 307 point_hit_test(uv, &hover_curve, &sel_pidx);
nuclear@0 308 post_redisplay();
nuclear@0 309 }
nuclear@0 310
nuclear@0 311 if(sel_curve && sel_pidx != -1) {
nuclear@0 312 if(bnstate & BNBIT(0)) {
nuclear@0 313 float w = sel_curve->get_weight(sel_pidx);
nuclear@2 314 sel_curve->set_point(sel_pidx, uv, w);
nuclear@0 315 post_redisplay();
nuclear@0 316 }
nuclear@0 317
nuclear@0 318 if(bnstate & BNBIT(2)) {
nuclear@0 319 float w = sel_curve->get_weight(sel_pidx);
nuclear@1 320 w -= dy * 0.01;
nuclear@0 321 if(w < FLT_MIN) w = FLT_MIN;
nuclear@0 322 sel_curve->set_weight(sel_pidx, w);
nuclear@0 323
nuclear@0 324 if(!weight_label) {
nuclear@0 325 weight_label = new Label;
nuclear@0 326 }
nuclear@2 327 weight_label->set_position(uv);
nuclear@0 328 weight_label->set_textf("w=%g", w);
nuclear@0 329 post_redisplay();
nuclear@0 330 }
nuclear@0 331 }
nuclear@0 332 }
nuclear@0 333
nuclear@0 334 static void on_click(int bn, float u, float v)
nuclear@0 335 {
nuclear@2 336 Vector2 uv = Vector2(u, v);
nuclear@2 337
nuclear@0 338 switch(bn) {
nuclear@2 339 case 0: // ------- LEFT CLICK ------
nuclear@2 340 if(hover_curve) {
nuclear@2 341 // if we're hovering: click selects
nuclear@2 342 sel_curve = hover_curve;
nuclear@2 343 hover_curve = 0;
nuclear@2 344 } else if(sel_curve) {
nuclear@2 345 // if we have a selected curve: click adds point (enter new_curve mode)
nuclear@2 346 std::vector<Curve*>::iterator it = std::find(curves.begin(), curves.end(), sel_curve);
nuclear@2 347 assert(it != curves.end());
nuclear@2 348 curves.erase(it, it + 1);
nuclear@2 349
nuclear@2 350 new_curve = sel_curve;
nuclear@2 351 sel_curve = 0;
nuclear@2 352 sel_pidx = -1;
nuclear@2 353
nuclear@2 354 new_curve->add_point(uv);
nuclear@2 355 } else {
nuclear@2 356 // otherwise, click starts a new curve
nuclear@2 357 if(!new_curve) {
nuclear@2 358 new_curve = new Curve;
nuclear@2 359 new_curve->add_point(uv);
nuclear@2 360 }
nuclear@2 361 new_curve->add_point(uv);
nuclear@0 362 }
nuclear@0 363 post_redisplay();
nuclear@0 364 break;
nuclear@0 365
nuclear@2 366 case 2: // ------- RIGHT CLICK ------
nuclear@2 367 if(new_curve) {
nuclear@2 368 // in new-curve mode: finish curve (cancels last floating segment)
nuclear@2 369 new_curve->remove_point(new_curve->size() - 1);
nuclear@2 370 if(new_curve->empty()) {
nuclear@2 371 delete new_curve;
nuclear@2 372 } else {
nuclear@2 373 curves.push_back(new_curve);
nuclear@2 374 }
nuclear@2 375 new_curve = 0;
nuclear@2 376
nuclear@2 377 } else if(sel_curve) {
nuclear@2 378 // in selected curve mode: delete control point or unselect
nuclear@2 379 Curve *hit_curve;
nuclear@2 380 int hit_pidx;
nuclear@2 381 if(point_hit_test(uv, &hit_curve, &hit_pidx) && hit_curve == sel_curve) {
nuclear@2 382 hit_curve->remove_point(hit_pidx);
nuclear@2 383 sel_pidx = -1;
nuclear@2 384 } else {
nuclear@2 385 sel_curve = 0;
nuclear@2 386 sel_pidx = -1;
nuclear@2 387 }
nuclear@2 388 }
nuclear@0 389 post_redisplay();
nuclear@0 390 break;
nuclear@0 391
nuclear@0 392 default:
nuclear@0 393 break;
nuclear@0 394 }
nuclear@0 395 }