added summerhack
[summerhack] / tools / curve_draw / curve_draw.cc
1 #include <iostream>
2 #include <vector>
3 #include <SDL.h>
4 #include "vmath/vmath.h"
5 #include "curves.h"
6 #include "cursors.h"
7 #include "gfx.h"
8
9 using namespace std;
10
11 enum {
12         TOOL_SELECT,
13         TOOL_INSERT,
14         TOOL_REMOVE
15 };
16
17 #define TOOL_COUNT      3
18
19 SDL_Cursor *tool_cursors[TOOL_COUNT];
20
21 struct EditState {
22         Vector2 view_size;
23         unsigned int tool;
24         std::vector<Curve*> curves;
25         int sel_curve;
26         int sel_pt;
27         int sel_x, sel_y;
28         bool draw_wire;
29         bool draw_pt;
30 };
31
32 void reset_state(EditState *s = 0);
33 void redraw();
34 Vector2 world_to_dev(const Vector2 &pt);
35 Vector2 dev_to_world(const Vector2 &pt);
36 void key_handler(SDLKey key, bool pressed);
37 void motion_handler(int x, int y);
38 void bn_handler(int x, int y, int bn, bool pressed);
39
40
41 int xsz = 800, ysz = 600;
42 SDL_Surface *sdl_fb;
43 bool done = false;
44 EditState state;
45 bool view_valid = false;
46
47 int main(int argc, char **argv) {
48         const char *usage = "curve_draw [-s(--size) WxH] [curve(s) to load]\n";
49
50         reset_state();
51
52         for(int i=1; i<argc; i++) {
53                 if(argv[i][0] == '-') {
54                         if(!strcmp(argv[i], "-s") || !strcmp(argv[i], "--size")) {
55                                 if(!isdigit(argv[++i][0])) {
56                                         cerr << "invalid size argument: " << argv[i] << endl;
57                                         return EXIT_FAILURE;
58                                 }
59                                 xsz = atoi(argv[i]);
60
61                                 char *ptr = strchr(argv[i], 'x');
62                                 if(!ptr || !isdigit(*(ptr + 1))) {
63                                         cerr << "invalid size argument: " << argv[i] << endl;
64                                         return EXIT_FAILURE;
65                                 }
66                                 ysz = atoi(ptr + 1);
67                                 
68                         } else if(!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
69                                 fputs(usage, stdout);
70                                 return 0;
71                         } else {
72                                 fputs(usage, stderr);
73                                 return EXIT_FAILURE;
74                         }
75                 } else {
76                         Curve *curve = load_curve(argv[i]);
77                         if(!curve) {
78                                 fprintf(stderr, "failed to load curve: %s\n", argv[i]);
79                                 return EXIT_FAILURE;
80                         }
81                         state.curves.push_back(curve);
82                         view_valid = false;
83                 }
84         }
85         
86         SDL_Init(SDL_INIT_VIDEO);
87         if(!(sdl_fb = SDL_SetVideoMode(xsz, ysz, 32, SDL_SWSURFACE))) {
88                 cerr << "failed to set initialize graphics\n";
89                 return EXIT_FAILURE;
90         }
91
92         SDL_WM_SetCaption("curvedraw - press h for usage help", 0);
93
94         init_cursors();
95
96         tool_cursors[TOOL_SELECT] = cursor_std;
97         tool_cursors[TOOL_INSERT] = cursor_cross;
98         tool_cursors[TOOL_REMOVE] = cursor_x;
99
100
101         SDL_Event event;
102         while(!done && SDL_WaitEvent(&event)) {
103                 do {
104                         switch(event.type) {
105                         case SDL_QUIT:
106                                 done = true;
107                                 break;
108
109                         case SDL_KEYDOWN:
110                         case SDL_KEYUP:
111                                 key_handler(event.key.keysym.sym, event.type == SDL_KEYDOWN);
112                                 break;
113
114                         case SDL_MOUSEMOTION:
115                                 motion_handler(event.motion.x, event.motion.y);
116                                 break;
117
118                         case SDL_MOUSEBUTTONDOWN:
119                         case SDL_MOUSEBUTTONUP:
120                                 bn_handler(event.button.x, event.button.y, event.button.button, event.button.state == SDL_PRESSED);
121                                 break;
122
123                         default:
124                                 break;
125                         }
126                 } while(SDL_PollEvent(&event));
127
128                 if(!view_valid) redraw();
129         }
130         
131         SDL_Quit();
132         return 0;
133 }
134
135
136 void reset_state(EditState *s) {
137         if(!s) s = &state;
138
139         s->view_size.y = 100;
140         s->view_size.x = s->view_size.y * ((float)xsz / (float)ysz);
141
142         s->tool = TOOL_SELECT;
143         s->sel_curve = -1;
144         s->sel_pt = -1;
145         s->sel_x = s->sel_y = -1;
146
147         s->draw_wire = false;
148         s->draw_pt = true;
149
150         for(size_t i=0; i<s->curves.size(); i++) {
151                 delete s->curves[i];
152         }
153         s->curves.clear();
154 }
155
156 void redraw() {
157
158         SDL_FillRect(sdl_fb, 0, 0);
159         
160         if(SDL_MUSTLOCK(sdl_fb)) {
161                 SDL_LockSurface(sdl_fb);
162         }
163         unsigned int *fb = (unsigned int*)sdl_fb->pixels;
164
165         memset(fb + xsz * ysz / 2, 0x18, xsz * 4);
166
167         unsigned int *ptr = fb + xsz / 2;
168         for(int i=0; i<ysz; i++) {
169                 *ptr = 0x181818;
170                 ptr += xsz;
171         }
172         
173
174         for(size_t i=0; i<state.curves.size(); i++) {
175                 Vector2 prev;
176
177                 // draw a line between control points
178                 if(state.draw_wire) {
179                         for(int j=0; j<state.curves[i]->get_point_count(); j++) {
180                                 Vector3 *ptptr = state.curves[i]->get_control_point(j);
181                                 Vector2 pt = world_to_dev(*ptptr);
182
183                                 if(j > 0) {
184                                         draw_line((int)prev.x, (int)prev.y, (int)pt.x, (int)pt.y, 0x404040, fb, xsz);
185                                 }
186                                 prev = pt;
187                         }
188                 }
189
190
191                 // draw the spline as a polyline
192                 const int draw_seg = 12 * state.curves[i]->get_segment_count();
193                 for(int j=0; j<=draw_seg; j++) {
194                         float t = (float)j / (float)draw_seg;
195                         Vector2 pt = (*state.curves[i])(t);
196
197                         pt = world_to_dev(pt);
198
199                         if(j > 0) {
200                                 unsigned int col = state.sel_curve == (int)i ? 0x00ff00 : 0x0000ff;
201                                 draw_line((int)prev.x, (int)prev.y, (int)pt.x, (int)pt.y, col, fb, xsz);
202                         }
203                         
204                         prev = pt;
205                 }
206
207                 // draw the control points
208                 if(state.draw_pt) {
209                         for(int j=0; j<state.curves[i]->get_point_count(); j++) {
210                                 Vector3 *ptptr = state.curves[i]->get_control_point(j);
211                                 Vector2 pt = world_to_dev(*ptptr);
212
213                                 unsigned int col = 0x808080;
214                                 if(state.sel_curve == (int)i) {
215                                         col = state.sel_pt == j ? 0xffff00 : 0xff0000;
216                                 }
217                         
218                                 draw_point((int)pt.x, (int)pt.y, col, fb, xsz);
219                         }
220                 }
221         }
222
223         if(SDL_MUSTLOCK(sdl_fb)) {
224                 SDL_UnlockSurface(sdl_fb);
225         }
226
227         SDL_Flip(sdl_fb);
228         view_valid = true;
229 }
230
231 Vector2 world_to_dev(const Vector2 &pt) {
232         Vector2 norm = (Vector2(1, -1) * pt + state.view_size / 2.0) / state.view_size;
233         return norm * Vector2(xsz, ysz);
234 }
235
236 Vector2 dev_to_world(const Vector2 &pt) {
237         Vector2 norm = pt / Vector2(xsz, ysz);
238         return Vector2(1, -1) * ((norm * state.view_size) - state.view_size / 2.0);
239 }
240
241
242 void key_handler(SDLKey key, bool pressed) {
243         switch(key) {
244         case 'q':
245                 done = true;
246                 break;
247
248         case SDLK_ESCAPE:
249                 state.sel_curve = -1;
250                 state.sel_pt = -1;
251                 view_valid = false;
252                 break;
253
254         case '1':
255                 redraw();
256                 break;
257
258         case 'w':
259                 if(!pressed) {
260                         state.draw_wire = !state.draw_wire;
261                         view_valid = false;
262                 }
263                 break;
264
265         case 'p':
266                 if(!pressed) {
267                         state.draw_pt = !state.draw_pt;
268                         view_valid = false;
269                 }
270                 break;
271
272         case SDLK_LEFT:
273                 if(!pressed) {
274                         if(state.sel_curve <= 0) {
275                                 state.sel_curve = state.curves.size() - 1;
276                         } else {
277                                 state.sel_curve--;
278                         }
279                         state.sel_pt = -1;
280                         view_valid = false;
281                 }
282                 break;
283
284         case SDLK_SPACE:
285         case SDLK_RIGHT:
286                 if(!pressed) {  
287                         if(state.sel_curve == -1 || state.sel_curve >= (int)state.curves.size() - 1) {
288                                 state.sel_curve = 0;
289                         } else {
290                                 state.sel_curve++;
291                         }
292                         state.sel_pt = -1;
293                         view_valid = false;
294                 }
295                 break;
296
297         case SDLK_RETURN:
298                 if(!pressed && state.sel_curve != -1) {
299                         Curve *current = state.curves[state.sel_curve];
300                         Curve *curve = dynamic_cast<BSplineCurve*>(current) ? (Curve*)new CatmullRomSplineCurve : (Curve*)new BSplineCurve;
301
302                         for(int i=0; i<current->get_point_count(); i++) {
303                                 Vector3 pt = *current->get_control_point(i);
304                                 pt.z = 0;
305                                 curve->add_control_point(pt);
306                         }
307
308                         delete current;
309                         state.curves[state.sel_curve] = curve;
310                         view_valid = false;
311                 }
312                 break;
313
314         case 'h':
315                 if(!pressed) {
316                         cout << "----- controls -----\n";
317                         cout << "[keyboard]\n";
318                         cout << "space - cycle through curves\n";
319                         cout << "right - cycle through curves forward\n";
320                         cout << "left  - cycle through curves backwards\n";
321                         cout << "esc   - deselect selected curve\n";
322                         cout << "enter - toggle curve type (bspline/catmull-rom)\n";
323                         cout << "q     - quit\n";
324                         cout << "s     - save all curves to current directory\n";
325                         cout << "[mouse]\n";
326                         cout << "left-button  - perform action (select tool also drags points around)\n";
327                         cout << "right-button - cycle through tools (select/insert/remove)\n";
328                         cout << "mousewheel   - zoom in/out\n";
329                         cout << endl;
330                 }
331                 break;
332
333         case 's':
334                 if(!pressed) {
335                         char fname[128];
336                         for(size_t i=0; i<state.curves.size(); i++) {
337                                 sprintf(fname, "curve%02d", (int)i);
338                                 save_curve(fname, state.curves[i]);
339                         }
340                         cout << state.curves.size() << " curves saved.\n";
341                 }
342                 break;
343
344         default:
345                 break;
346         }
347 }
348
349 void motion_handler(int x, int y) {
350         Vector2 mouse = dev_to_world(Vector2(x, y));
351         
352         if(state.sel_x != -1) {
353                 if(x != state.sel_x || y != state.sel_y) {
354                         Curve *curve = state.curves[state.sel_curve];
355                         Vector3 *ptptr = curve->get_control_point(state.sel_pt);
356                         *ptptr = mouse;
357
358                         state.sel_x = x;
359                         state.sel_y = y;
360                         view_valid = false;
361                 }
362         }
363 }
364
365 void bn_handler(int x, int y, int bn, bool pressed) {
366         Vector2 mouse = dev_to_world(Vector2(x, y));
367
368         if(bn == 3 && pressed) {
369                 state.tool = (state.tool + 1) % TOOL_COUNT;
370                 SDL_SetCursor(tool_cursors[state.tool]);
371         }
372         
373         if(bn == 1 && state.tool == TOOL_SELECT) {
374                 if(pressed) {
375                         
376                         if(state.sel_curve >= 0) {
377                                 Curve *curve = state.curves[state.sel_curve];
378                                 for(int i=0; i<curve->get_point_count(); i++) {
379                                         Vector3 *ptptr = curve->get_control_point(i);
380                                         Vector2 pt = *ptptr;
381                                 
382                                         if((pt - mouse).length() < state.view_size.y * 0.008) {
383                                                 state.sel_pt = i;
384                                                 state.sel_x = x;
385                                                 state.sel_y = y;
386                                                 view_valid = false;
387                                         }
388                                 }
389                         }
390
391                 } else {        // depressed
392                         state.sel_x = -1;
393                         state.sel_y = -1;
394                 }
395         }
396
397         if(bn == 1 && state.tool == TOOL_REMOVE) {
398                 static int press_x, press_y;
399                 
400                 if(pressed) {
401                         press_x = x;
402                         press_y = y;
403                 } else {
404                         if(press_x == x && press_y == y && state.sel_curve >= 0) {
405                                 Curve *curve = state.curves[state.sel_curve];
406                                 for(int i=0; i<curve->get_point_count(); i++) {
407                                         Vector3 *ptptr = curve->get_control_point(i);
408                                         Vector2 pt = *ptptr;
409                                 
410                                         if((pt - mouse).length() < state.view_size.y * 0.008) {
411                                                 curve->remove_control_point(i);
412                                                 if(state.sel_pt >= i) state.sel_pt--;
413                                                 if(!curve->get_point_count()) {
414                                                         delete curve;
415                                                         state.curves.erase(state.curves.begin() + state.sel_curve);
416                                                 }
417                                                 view_valid = false;
418                                                 break;
419                                         }
420                                 }
421                         }
422                 }
423         }
424
425         if(bn == 1 && state.tool == TOOL_INSERT) {
426                 static int press_x, press_y;
427                 
428                 if(pressed) {
429                         press_x = x;
430                         press_y = y;
431                 } else {
432                         if(press_x == x && press_y == y) {
433                                 if(state.sel_curve == -1) {
434                                         // create a new curve...
435                                         Curve *curve = new CatmullRomSplineCurve;
436                                         curve->add_control_point(Vector3(mouse.x, mouse.y, 0.0));
437
438                                         state.sel_curve = state.curves.size();
439                                         state.sel_pt = 0;
440                                         state.curves.push_back(curve);
441                                 } else {
442                                         // add a control point to the selected curve
443                                         Curve *curve = state.curves[state.sel_curve];
444                                         state.sel_pt = curve->get_point_count();
445                                         curve->add_control_point(Vector3(mouse.x, mouse.y, 0.0));
446                                 }
447                                 view_valid = false;
448                         }
449                 }
450         }
451
452         if(bn == 4 && pressed) {
453                 state.view_size *= 0.95;
454                 view_valid = false;
455         }
456
457         if(bn == 5 && pressed) {
458                 state.view_size *= 1.05;
459                 view_valid = false;
460         }
461 }