]> git.tdb.fi Git - r2c2.git/blob - source/designer/designer.cpp
Use the Root widget for tooltips
[r2c2.git] / source / designer / designer.cpp
1 /* $Id$
2
3 This file is part of the MSP Märklin suite
4 Copyright © 2006-2010 Mikkosoft Productions, Mikko Rasa
5 Distributed under the GPL
6 */
7
8 #include <signal.h>
9 #include <cmath>
10 #include <GL/gl.h>
11 #include <msp/gl/blend.h>
12 #include <msp/gl/framebuffer.h>
13 #include <msp/gl/matrix.h>
14 #include <msp/gl/misc.h>
15 #include <msp/gl/projection.h>
16 #include <msp/gl/rendermode.h>
17 #include <msp/gl/select.h>
18 #include <msp/gl/tests.h>
19 #include <msp/gl/texture2d.h>
20 #include <msp/input/keys.h>
21 #include <msp/io/print.h>
22 #include <msp/strings/codec.h>
23 #include <msp/strings/lexicalcast.h>
24 #include <msp/strings/utf8.h>
25 #include <msp/strings/utils.h>
26 #include <msp/time/units.h>
27 #include <msp/time/utils.h>
28 #include "libmarklin/tracktype.h"
29 #include "designer.h"
30 #include "input.h"
31 #include "manipulator.h"
32 #include "measure.h"
33 #include "selection.h"
34 #include "toolbar.h"
35
36 using namespace std;
37 using namespace Marklin;
38 using namespace Msp;
39
40 Application::RegApp<Designer> Designer::reg;
41
42 Designer::Designer(int argc, char **argv):
43         window(1280, 960),
44         base_object(0),
45         cur_route(0),
46         mode(SELECT),
47         manipulator(*this, selection),
48         measure(*this),
49         input(0),
50         camera_ctl(window, camera),
51         shift(false)
52 {
53         window.set_title("Railway Designer");
54         window.signal_close.connect(sigc::bind(sigc::mem_fun(this, &Designer::exit), 0));
55         window.signal_key_press.connect(sigc::mem_fun(this, &Designer::key_press));
56         window.signal_key_release.connect(sigc::mem_fun(this, &Designer::key_release));
57         window.signal_button_press.connect(sigc::mem_fun(this, &Designer::button_press));
58         window.signal_pointer_motion.connect(sigc::mem_fun(this, &Designer::pointer_motion));
59
60         manipulator.signal_status.connect(sigc::mem_fun(this, &Designer::manipulation_status));
61         manipulator.signal_done.connect(sigc::mem_fun(this, &Designer::manipulation_done));
62         measure.signal_changed.connect(sigc::mem_fun(this, &Designer::measure_changed));
63         measure.signal_done.connect(sigc::mem_fun(this, &Designer::measure_done));
64
65         // Setup catalogue and layout
66         DataFile::load(catalogue, "tracks.dat");
67
68         cat_layout_3d = new Layout3D(catalogue.get_layout());
69
70         layout = new Layout(catalogue);
71         layout_3d = new Layout3D(*layout);
72
73         if(argc>1)
74         {
75                 filename = argv[1];
76                 DataFile::load(*layout, argv[1]);
77
78                 if(!layout->get_base().empty())
79                 {
80                         base_object = new GL::Object;
81                         DataFile::load(*base_object, layout->get_base());
82                 }
83         }
84
85         // Setup OpenGL
86         GL::enable(GL::DEPTH_TEST);
87         GL::enable(GL::BLEND);
88         GL::blend_func(GL::SRC_ALPHA, GL::ONE_MINUS_SRC_ALPHA);
89         GL::enable(GL_CULL_FACE);
90
91         pipeline = new GL::Pipeline(window.get_width(), window.get_height(), false);
92         pipeline->set_camera(&camera);
93         pipeline->add_renderable(layout_3d->get_scene());
94         if(base_object)
95                 pipeline->add_renderable(*base_object);
96
97         light.set_position(0, -0.259, 0.966, 0);
98         lighting.attach(0, light);
99
100         GL::PipelinePass *pass = &pipeline->add_pass(GL::Tag());
101         pass->lighting = &lighting;
102
103         camera.set_up_direction(GL::Vector3(0, 0, 1));
104         view_all();
105
106         // Setup UI
107         DataFile::load(ui_res, "marklin.res");
108         root = new GLtk::Root(ui_res, window);
109         root->signal_tooltip.connect(sigc::mem_fun(this, &Designer::tooltip));
110
111         toolbar = new Toolbar(*this);
112         root->add(*toolbar);
113         toolbar->set_position(0, window.get_height()-toolbar->get_geometry().h);
114         toolbar->set_visible(true);
115
116         GLtk::Panel *statusbar = new GLtk::Panel(ui_res);
117         root->add(*statusbar);
118         statusbar->set_size(window.get_width(), 20);
119         statusbar->set_visible(true);
120
121         lbl_status = new GLtk::Label(ui_res);
122         statusbar->add(*lbl_status);
123         lbl_status->set_geometry(GLtk::Geometry(20, 2, 300, 16));
124
125         overlay = new Overlay3D(window, camera, ui_res.get_default_font());
126
127         const list<Track3D *> &tracks = layout_3d->get_tracks();
128         for(list<Track3D *>::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
129                 update_track_icon(**i);
130 }
131
132 Designer::~Designer()
133 {
134         delete overlay;
135         delete root;
136         delete pipeline;
137         delete base_object;
138         delete layout_3d;
139         delete layout;
140         delete cat_layout_3d;
141 }
142
143 int Designer::main()
144 {
145         window.show();
146
147         mode = SELECT;
148
149         return Application::main();
150 }
151
152 void Designer::save()
153 {
154         input = new ::Input(*this, "Save layout", filename);
155         input->signal_cancel.connect(sigc::mem_fun(this, &Designer::input_dismiss));
156         input->signal_accept.connect(sigc::mem_fun(this, &Designer::save_accept));
157         mode = INPUT;
158 }
159
160 void Designer::quit()
161 {
162         exit(0);
163 }
164
165 void Designer::edit_route(Route &r)
166 {
167         cur_route = &r;
168 }
169
170 void Designer::add_selection_to_route()
171 {
172         if(!cur_route)
173                 return;
174
175         try
176         {
177                 const set<Track *> &stracks = selection.get_tracks();
178                 set<const Track *> tracks(stracks.begin(), stracks.end());
179                 cur_route->add_tracks(tracks);
180         }
181         catch(const Exception &e)
182         {
183                 IO::print("%s\n", e.what());
184         }
185 }
186
187 Point Designer::map_pointer_coords(int x, int y)
188 {
189         float xf = x*2.0/window.get_width()-1.0;
190         float yf = y*2.0/window.get_height()-1.0;
191         GL::Vector4 vec = camera.unproject(GL::Vector4(xf, yf, 0, 0));
192         const GL::Vector3 &pos = camera.get_position();
193
194         return Point(pos.x-vec.x*pos.z/vec.z, pos.y-vec.y*pos.z/vec.z);
195 }
196
197 void Designer::tick()
198 {
199         const Msp::Time::TimeStamp t = Msp::Time::now();
200         float dt = (t-last_tick)/Msp::Time::sec;
201         last_tick = t;
202
203         window.get_display().tick();
204         root->tick();
205         camera_ctl.tick(dt);
206
207         render();
208
209         window.swap_buffers();
210 }
211
212 void Designer::key_press(unsigned code, unsigned mod, wchar_t)
213 {
214         unsigned key = Msp::Input::key_from_sys(code);
215
216         if(mode==INPUT)
217                 return;
218
219         if(key==Msp::Input::KEY_SHIFT_L || key==Msp::Input::KEY_SHIFT_R)
220                 shift = true;
221
222         if(key==Msp::Input::KEY_N)
223                 mode = CATALOGUE;
224         else if(key==Msp::Input::KEY_G)
225         {
226                 manipulator.start_move();
227                 mode = MANIPULATE;
228         }
229         else if(key==Msp::Input::KEY_R)
230         {
231                 manipulator.start_rotate();
232                 mode = MANIPULATE;
233         }
234         else if(key==Msp::Input::KEY_D)
235         {
236                 manipulator.duplicate();
237                 manipulator.start_move();
238                 mode = MANIPULATE;
239         }
240         else if(key==Msp::Input::KEY_W)
241                 save();
242         else if(key==Msp::Input::KEY_PLUS)
243                 selection.select_more();
244         else if(key==Msp::Input::KEY_L && (mod&1))
245         {
246                 const set<Track *> &tracks = layout->get_tracks();
247                 float len = 0;
248                 for(set<Track *>::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
249                         len += (*i)->get_type().get_total_length();
250                 IO::print("Total length: %.1fm\n", len);
251         }
252         else if(key==Msp::Input::KEY_L)
253                 selection.select_linked();
254         else if(key==Msp::Input::KEY_M)
255         {
256                 measure.start();
257                 mode = MEASURE;
258         }
259         else if(key==Msp::Input::KEY_Z)
260         {
261                 manipulator.start_elevate();
262                 mode = MANIPULATE;
263         }
264         else if(key==Msp::Input::KEY_ESC)
265         {
266                 if(mode==MANIPULATE)
267                         manipulator.cancel();
268                 else if(mode==CATALOGUE)
269                         mode = SELECT;
270                 else
271                         selection.clear();
272         }
273         else if(key==Msp::Input::KEY_X)
274         {
275                 set<Track *> tracks = selection.get_tracks();
276                 selection.clear();
277                 for(set<Track *>::iterator i=tracks.begin(); i!=tracks.end(); ++i)
278                 {
279                         overlay->clear(layout_3d->get_track(**i));
280                         layout->remove_track(**i);
281                         delete *i;
282                 }
283         }
284         else if(key==Msp::Input::KEY_F && (mod&1))
285         {
286                 const set<Track *> &tracks = selection.get_tracks();
287                 const set<Track *> &ltracks = layout->get_tracks();
288                 for(set<Track *>::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
289                 {
290                         (*i)->set_flex(!(*i)->get_flex());
291                         (*i)->break_links();
292                         for(set<Track *>::const_iterator j=ltracks.begin(); j!=ltracks.end(); ++j)
293                                 if(*j!=*i)
294                                         (*i)->snap_to(**j, true);
295
296                         update_track_icon(layout_3d->get_track(**i));
297                 }
298         }
299         else if(key==Msp::Input::KEY_F)
300                 manipulator.flatten();
301         else if(key==Msp::Input::KEY_E && (mod&1))
302                 manipulator.even_slope(true);
303         else if(key==Msp::Input::KEY_E)
304                 manipulator.even_slope();
305         else if(key==Msp::Input::KEY_T)
306         {
307                 Track *track = selection.get_track();
308                 if(selection.size()==1 && track->get_type().get_n_paths()>1)
309                 {
310                         input = new ::Input(*this, "Turnout ID", lexical_cast(track->get_turnout_id()));
311                         input->signal_cancel.connect(sigc::mem_fun(this, &Designer::input_dismiss));
312                         input->signal_accept.connect(sigc::mem_fun(this, &Designer::turnout_id_accept));
313                         mode = INPUT;
314                 }
315         }
316         else if(key==Msp::Input::KEY_S)
317         {
318                 const set<Track *> &tracks = selection.get_tracks();
319                 bool ok = false;
320                 int id = -1;
321                 for(set<Track *>::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
322                 {
323                         if((*i)->get_type().get_n_paths()==1)
324                                 ok = true;
325                         if(static_cast<int>((*i)->get_sensor_id())!=id)
326                         {
327                                 if(id==-1)
328                                         id = (*i)->get_sensor_id();
329                                 else
330                                         id = -2;
331                         }
332                 }
333                 if(ok)
334                 {
335                         input = new ::Input(*this, "Sensor ID", (id>=0 ? lexical_cast(id) : string()));
336                         input->signal_cancel.connect(sigc::mem_fun(this, &Designer::input_dismiss));
337                         input->signal_accept.connect(sigc::mem_fun(this, &Designer::sensor_id_accept));
338                         mode = INPUT;
339                 }
340         }
341         else if(key==Msp::Input::KEY_A)
342                 add_selection_to_route();
343 }
344
345 void Designer::key_release(unsigned code, unsigned)
346 {
347         unsigned key = Msp::Input::key_from_sys(code);
348
349         if(mode==INPUT)
350                 return;
351
352         if(key==Msp::Input::KEY_SHIFT_L || key==Msp::Input::KEY_SHIFT_R)
353                 shift = false;
354 }
355
356 void Designer::button_press(int x, int y, unsigned btn, unsigned)
357 {
358         y = window.get_height()-y-1;
359
360         Point ground = map_pointer_coords(x, y);
361
362         if(mode==CATALOGUE)
363         {
364                 if(btn==1)
365                 {
366                         Track3D *ctrack = pick_track(x, y);
367                         if(ctrack)
368                         {
369                                 Track *track = ctrack->get_track().copy();
370                                 track->set_position(ground);
371                                 layout->add_track(*track);
372
373                                 selection.clear();
374                                 selection.add_track(track);
375
376                                 mode = SELECT;
377                         }
378                 }
379                 else
380                         mode = SELECT;
381         }
382         else if(mode==SELECT)
383         {
384                 if(btn==1)
385                 {
386                         Track3D *track = pick_track(x, y);
387                         if(track)
388                         {
389                                 if(!shift)
390                                         selection.clear();
391                                 selection.toggle_track(&track->get_track());
392                         }
393                 }
394         }
395         else if(mode==MANIPULATE)
396                 manipulator.button_press(x, y, ground.x, ground.y, btn);
397         else if(mode==MEASURE)
398                 measure.button_press(x, y, ground.x, ground.y, btn);
399 }
400
401 void Designer::pointer_motion(int x, int y)
402 {
403         y = window.get_height()-y-1;
404
405         if(mode!=INPUT)
406         {
407                 Point ground = map_pointer_coords(x, y);
408                 manipulator.pointer_motion(x, y, ground.x, ground.y);
409                 measure.pointer_motion(x, y, ground.x, ground.y);
410         }
411 }
412
413 void Designer::apply_camera()
414 {
415         if(mode==CATALOGUE)
416         {
417                 GL::matrix_mode(GL::PROJECTION);
418                 GL::load_identity();
419                 GL::frustum_centered(0.11046, 0.082843, 0.1, 10);
420                 GL::matrix_mode(GL::MODELVIEW);
421                 GL::load_identity();
422                 GL::translate(0, 0, -1);
423         }
424         else
425                 camera.apply();
426 }
427
428 void Designer::render()
429 {
430         GL::clear(GL::COLOR_BUFFER_BIT|GL::DEPTH_BUFFER_BIT);
431         GL::enable(GL::DEPTH_TEST);
432         GL::Texture::unbind();
433
434         if(mode==CATALOGUE)
435         {
436                 apply_camera();
437                 cat_layout_3d->get_scene().render();
438         }
439         else
440         {
441                 pipeline->render_all();
442                 layout_3d->get_endpoint_scene().render();
443                 GL::enable(GL_CULL_FACE);
444                 GL::disable(GL::DEPTH_TEST);
445                 overlay->render(0);
446                 GL::enable(GL::DEPTH_TEST);
447                 /*if(cur_route)
448                 {
449                         glColor4f(0.5, 0.8, 1.0, 1.0);
450                         const set<const Track *> &rtracks = cur_route->get_tracks();
451                         const map<unsigned, int> &turnouts = cur_route->get_turnouts();
452                         for(set<const Track *>::const_iterator i=rtracks.begin(); i!=rtracks.end(); ++i)
453                         {
454                                 unsigned path = 0;
455                                 if(unsigned tid=(*i)->get_turnout_id())
456                                 {
457                                         map<unsigned, int>::const_iterator j = turnouts.find(tid);
458                                         if(j!=turnouts.end())
459                                                 path = j->second;
460                                 }
461                                 layout_3d->get_track(**i).render_path(path);
462                         }
463                 }*/
464
465                 manipulator.render();
466                 if(mode==MEASURE)
467                         measure.render();
468         }
469
470         GL::matrix_mode(GL::PROJECTION);
471         GL::load_identity();
472         GL::ortho_bottomleft(window.get_width(), window.get_height());
473         GL::matrix_mode(GL::MODELVIEW);
474         GL::load_identity();
475
476         GL::disable(GL::DEPTH_TEST);
477
478         root->render();
479 }
480
481 Track3D *Designer::pick_track(int x, int y)
482 {
483         Layout3D *l = layout_3d;
484         if(mode==CATALOGUE)
485                 l = cat_layout_3d;
486
487         float xx = ((float(x)-window.get_width()/2)/window.get_height())*0.82843;
488         float yy = (float(y)/window.get_height()-0.5)*0.82843;
489         float size = 4.0/window.get_height()*0.82843;
490
491         apply_camera();
492
493         return l->pick_track(xx, yy, size);
494 }
495
496 void Designer::update_track_icon(Track3D &track)
497 {
498         overlay->clear(track);
499
500         if(track.get_track().get_flex())
501                 overlay->add_graphic(track, "flex");
502
503         if(unsigned sid = track.get_track().get_sensor_id())
504         {
505                 overlay->add_graphic(track, "sensor");
506                 overlay->set_label(track, lexical_cast(sid));
507         }
508         else if(unsigned tid = track.get_track().get_turnout_id())
509         {
510                 overlay->add_graphic(track, "turnout");
511                 overlay->set_label(track, lexical_cast(tid));
512         }
513 }
514
515 void Designer::manipulation_status(const string &status)
516 {
517         lbl_status->set_text(status);
518 }
519
520 void Designer::manipulation_done(bool)
521 {
522         lbl_status->set_text(string());
523         mode = SELECT;
524 }
525
526 void Designer::measure_changed()
527 {
528         float pard = measure.get_parallel_distance()*1000;
529         float perpd = measure.get_perpendicular_distance()*1000;
530         float d = sqrt(pard*pard+perpd*perpd);
531         float adiff = measure.get_angle_difference()*180/M_PI;
532         string info = format("Par %.1fmm - Perp %.1fmm - Total %.1fmm - Angle %.1f°", pard, perpd, d, adiff);
533         lbl_status->set_text(info);
534 }
535
536 void Designer::measure_done()
537 {
538         lbl_status->set_text(string());
539         mode = SELECT;
540 }
541
542 void Designer::save_accept()
543 {
544         layout->save(input->get_text());
545
546         input_dismiss();
547 }
548
549 void Designer::turnout_id_accept()
550 {
551         Track *track = selection.get_track();
552         unsigned id = lexical_cast<unsigned>(input->get_text());
553         track->set_turnout_id(id);
554
555         update_track_icon(layout_3d->get_track(*track));
556
557         input_dismiss();
558 }
559
560 void Designer::sensor_id_accept()
561 {
562         const set<Track *> &tracks = selection.get_tracks();
563         unsigned id = lexical_cast<unsigned>(input->get_text());
564         for(set<Track *>::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
565         {
566                 (*i)->set_sensor_id(id);
567
568                 update_track_icon(layout_3d->get_track(**i));
569         }
570
571         input_dismiss();
572 }
573
574 void Designer::input_dismiss()
575 {
576         delete input;
577         input = 0;
578         mode = SELECT;
579 }
580
581 void Designer::view_all()
582 {
583         Point minp;
584         Point maxp;
585
586         const list<Track3D *> &tracks = layout_3d->get_tracks();
587         for(list<Track3D *>::const_iterator i=tracks.begin(); i!=tracks.end(); ++i)
588         {
589                 Point tmin;
590                 Point tmax;
591                 (*i)->get_bounds(0, tmin, tmax);
592                 minp.x = min(minp.x, tmin.x);
593                 minp.y = min(minp.y, tmin.y);
594                 maxp.x = max(maxp.x, tmax.x);
595                 maxp.y = max(maxp.y, tmax.y);
596         }
597
598         float t = tan(camera.get_field_of_view()/2)*2;
599         float size = max((maxp.y-minp.y+0.1), (maxp.x-minp.x+0.1)/camera.get_aspect());
600         float cam_dist = size/t+size*0.25;
601         Point center((minp.x+maxp.x)/2, (minp.y+maxp.y)/2);
602         camera.set_position(GL::Vector3(center.x, center.y-cam_dist*0.5, cam_dist*0.866));
603         camera.set_look_direction(GL::Vector3(0, 0.5, -0.866));
604 }
605
606 string Designer::tooltip(int x, int y)
607 {
608         if(Track3D *t3d = pick_track(x, y))
609         {
610                 const Track &track = t3d->get_track();
611                 const TrackType &ttype = track.get_type();
612                 string info = format("%d %s", ttype.get_article_number(), ttype.get_description());
613                 if(mode!=CATALOGUE && abs(track.get_slope())>1e-4)
614                         info += format(" (slope %.1f%%)", abs(track.get_slope()/ttype.get_total_length()*100));
615                 if(track.get_turnout_id())
616                         info += format(" (turnout %d)", track.get_turnout_id());
617                 else if(track.get_sensor_id())
618                         info += format(" (sensor %d)", track.get_sensor_id());
619
620                 return info;
621         }
622
623         return string();
624 }