2ffe894fd5d61df54762727004e297bd9e1f6f77
[pmdsim.git] / src / mainwindow.cpp
1 /*
2  * Copyright (C) 2012, 2013, 2014
3  * Computer Graphics Group, University of Siegen, Germany.
4  * http://www.cg.informatik.uni-siegen.de/
5  * All rights reserved.
6  * Written by Martin Lambers <martin.lambers@uni-siegen.de>.
7  */
8
9 #include <stdexcept>
10 #include <system_error>
11 #include <cerrno>
12 #include <cstring>
13 #include <clocale>
14 #include <cmath>
15 #include <cstdio>
16
17 #include <GL/glew.h>
18
19 #include <QMainWindow>
20 #include <QCloseEvent>
21 #include <QSettings>
22 #include <QGridLayout>
23 #include <QMenuBar>
24 #include <QMenu>
25 #include <QAction>
26 #include <QMessageBox>
27 #include <QTimer>
28 #include <QGLFormat>
29 #include <QFileDialog>
30 #include <QFileInfo>
31 #include <QDir>
32 #include <QApplication>
33 #include <QPushButton>
34 #include <QDoubleSpinBox>
35 #include <QSpinBox>
36 #include <QComboBox>
37 #include <QGroupBox>
38 #include <QCheckBox>
39 #include <QRadioButton>
40 #include <QLabel>
41 #include <QElapsedTimer>
42 #include <QProgressDialog>
43 #include <QThread>
44 #include <QLineEdit>
45 #include <QtCore>
46
47 #ifdef HAVE_GTA
48 #  include <gta/gta.hpp>
49 #endif
50
51 #include "mainwindow.h"
52 #include "simwidget.h"
53 #include "osgwidget.h"
54 #include "view2dwidget.h"
55 #include "animwidget.h"
56
57
58 MainWindow::MainWindow(
59             QString script_simulator_file,
60             QString script_background_file,
61             QString script_target_file,
62             QString script_animation_file,
63             QString script_export_dir,
64             bool script_export_animation,
65             double script_export_frame,
66             bool script_minimize_window) :
67     QMainWindow(NULL)
68 {
69     bool script_mode = (!script_simulator_file.isEmpty()
70             || !script_background_file.isEmpty()
71             || !script_target_file.isEmpty()
72             || !script_animation_file.isEmpty()
73             || !script_export_dir.isEmpty()
74             || script_export_animation
75             || std::isfinite(script_export_frame)
76             || script_minimize_window);
77
78     // Set application properties
79     setWindowTitle("PMDSim");
80     setWindowIcon(QIcon(":appicon.png"));
81     _settings = new QSettings();
82
83     // Restore window settings
84     if (script_mode && script_minimize_window) {
85         // XXX: We don't want the window to steal focus from the foreground application,
86         // but none of the following seem to prevent that...
87         setFocusPolicy(Qt::NoFocus);
88         setAttribute(Qt::WA_ShowWithoutActivating);
89         setAttribute(Qt::WA_X11DoNotAcceptFocus);
90         setWindowFlags(Qt::WindowStaysOnBottomHint);
91     } else {
92         _settings->beginGroup("MainWindow");
93         restoreGeometry(_settings->value("geometry").toByteArray());
94         restoreState(_settings->value("window_state").toByteArray());
95         _settings->endGroup();
96     }
97
98     // Create widgets
99     _scene_id = 0;
100     QGLFormat fmt(QGL::DoubleBuffer | QGL::DepthBuffer | QGL::Rgba | QGL::DirectRendering
101             | QGL::NoSampleBuffers | QGL::NoAlphaChannel | QGL::NoAccumBuffer
102             | QGL::NoStencilBuffer | QGL::NoStereoBuffers | QGL::NoOverlay | QGL::NoSampleBuffers);
103     fmt.setSwapInterval(0);
104     QGLFormat::setDefaultFormat(fmt);
105     connect(this, SIGNAL(update_scene(const Target&, const Target&)), this, SLOT(reset_scene()));
106     _sim_widget = new SimWidget();
107     connect(this, SIGNAL(update_simulator(const Simulator&)), _sim_widget, SLOT(update_simulator(const Simulator&)));
108     _osg_widget = new OSGWidget(_sim_widget);
109     connect(this, SIGNAL(update_simulator(const Simulator&)), _osg_widget, SLOT(update_simulator(const Simulator&)));
110     connect(this, SIGNAL(update_scene(const Target&, const Target&)), _osg_widget, SLOT(update_scene(const Target&, const Target&)));
111     _depthmap_widget = new View2DWidget(_sim_widget);
112     connect(this, SIGNAL(update_simulator(const Simulator&)), _depthmap_widget, SLOT(update_simulator(const Simulator&)));
113     _phase_widgets[0] = new View2DWidget(_sim_widget);
114     connect(this, SIGNAL(update_simulator(const Simulator&)), _phase_widgets[0], SLOT(update_simulator(const Simulator&)));
115     _phase_widgets[1] = new View2DWidget(_sim_widget);
116     connect(this, SIGNAL(update_simulator(const Simulator&)), _phase_widgets[1], SLOT(update_simulator(const Simulator&)));
117     _phase_widgets[2] = new View2DWidget(_sim_widget);
118     connect(this, SIGNAL(update_simulator(const Simulator&)), _phase_widgets[2], SLOT(update_simulator(const Simulator&)));
119     _phase_widgets[3] = new View2DWidget(_sim_widget);
120     connect(this, SIGNAL(update_simulator(const Simulator&)), _phase_widgets[3], SLOT(update_simulator(const Simulator&)));
121     _pmd_depth_widget = new View2DWidget(_sim_widget);
122     connect(this, SIGNAL(update_simulator(const Simulator&)), _pmd_depth_widget, SLOT(update_simulator(const Simulator&)));
123     _pmd_amp_widget = new View2DWidget(_sim_widget);
124     connect(this, SIGNAL(update_simulator(const Simulator&)), _pmd_amp_widget, SLOT(update_simulator(const Simulator&)));
125     _pmd_intensity_widget = new View2DWidget(_sim_widget);
126     connect(this, SIGNAL(update_simulator(const Simulator&)), _pmd_intensity_widget, SLOT(update_simulator(const Simulator&)));
127     _anim_widget = new AnimWidget();
128     connect(this, SIGNAL(update_animation(const Animation&)), _anim_widget, SLOT(update_animation(const Animation&)));
129     connect(_anim_widget, SIGNAL(update_state(AnimWidget::state_t)), this, SLOT(animation_state_changed()));
130     connect(_anim_widget, SIGNAL(update_time(long long)), this, SLOT(animation_time_changed(long long)));
131
132     _background = Target(Target::variant_background_planar);
133     if (script_mode) {
134         // Load scripting parameters
135         try {
136             if (!script_simulator_file.isEmpty())
137                 _simulator.load(script_simulator_file.toLocal8Bit().constData());
138             if (!script_background_file.isEmpty())
139                 _background.load(script_background_file.toLocal8Bit().constData());
140             if (!script_target_file.isEmpty())
141                 _target.load(script_target_file.toLocal8Bit().constData());
142             if (!script_animation_file.isEmpty())
143                 _animation.load(script_animation_file.toLocal8Bit().constData());
144         }
145         catch (std::exception& e) {
146             QMessageBox::critical(this, "Error", e.what());
147             std::exit(1);
148         }
149         emit update_simulator(_simulator);
150         emit update_scene(_background, _target);
151         emit update_animation(_animation);
152     } else {
153         // Restore last simulator, background, target, and animation from the last session
154         QString simulator_filename = _settings->value("Session/simulator").toString();
155         if (!simulator_filename.isEmpty()) {
156             try { _simulator.load(simulator_filename.toLocal8Bit().constData()); } catch (...) { }
157         }
158         emit update_simulator(_simulator);
159         QString background_filename = _settings->value("Session/background").toString();
160         if (!background_filename.isEmpty()) {
161             try { _background.load(background_filename.toLocal8Bit().constData()); } catch (...) { }
162         }
163         QString target_filename = _settings->value("Session/target").toString();
164         if (!target_filename.isEmpty()) {
165             try { _target.load(target_filename.toLocal8Bit().constData()); } catch (...) { }
166         }
167         emit update_scene(_background, _target);
168         QString animation_filename = _settings->value("Session/animation").toString();
169         if (!animation_filename.isEmpty()) {
170             try { _animation.load(animation_filename.toLocal8Bit().constData()); } catch (...) { }
171         }
172         emit update_animation(_animation);
173     }
174
175     // Create central widget
176     QWidget* widget = new QWidget;
177     QGridLayout* row0_layout = new QGridLayout;
178     row0_layout->addWidget(_anim_widget, 0, 0);
179     QGridLayout* row1_layout = new QGridLayout;
180     row1_layout->addWidget(_osg_widget, 0, 0, 2, 2);
181     row1_layout->addWidget(_depthmap_widget, 0, 2, 2, 2);
182     row1_layout->addWidget(_phase_widgets[0], 0, 4);
183     row1_layout->addWidget(_phase_widgets[1], 0, 5);
184     row1_layout->addWidget(_phase_widgets[2], 1, 4);
185     row1_layout->addWidget(_phase_widgets[3], 1, 5);
186     QGridLayout* row2_layout = new QGridLayout;
187     row2_layout->addWidget(_pmd_depth_widget, 0, 0);
188     row2_layout->addWidget(_pmd_amp_widget, 0, 1);
189     row2_layout->addWidget(_pmd_intensity_widget, 0, 2);
190     QGridLayout* layout = new QGridLayout;
191     layout->addLayout(row0_layout, 0, 0);
192     layout->addLayout(row1_layout, 1, 0);
193     layout->addLayout(row2_layout, 2, 0);
194     layout->setRowStretch(1, 1);
195     layout->setRowStretch(2, 1);
196     widget->setLayout(layout);
197     setCentralWidget(widget);
198
199     /* Create menus */
200
201     // File menu
202     QMenu* file_menu = menuBar()->addMenu("&File");
203     QAction* file_export_frame_act = new QAction("&Export current frame...", this);
204     file_export_frame_act->setShortcut(tr("Ctrl+E"));
205     connect(file_export_frame_act, SIGNAL(triggered()), this, SLOT(file_export_frame()));
206     file_menu->addAction(file_export_frame_act);
207     QAction* file_export_anim_act = new QAction("Export &all frames...", this);
208     file_export_anim_act->setShortcut(tr("Ctrl+A"));
209     connect(file_export_anim_act, SIGNAL(triggered()), this, SLOT(file_export_anim()));
210     file_menu->addAction(file_export_anim_act);
211     file_menu->addSeparator();
212     QAction* quit_act = new QAction("&Quit...", this);
213     quit_act->setShortcut(QKeySequence::Quit);
214     connect(quit_act, SIGNAL(triggered()), this, SLOT(close()));
215     file_menu->addAction(quit_act);
216     // Simulator menu
217     QMenu* simulator_menu = menuBar()->addMenu("&Simulator");
218     QAction* simulator_load_act = new QAction("&Load...", this);
219     connect(simulator_load_act, SIGNAL(triggered()), this, SLOT(simulator_load()));
220     simulator_menu->addAction(simulator_load_act);
221     QAction* simulator_save_act = new QAction("&Save...", this);
222     connect(simulator_save_act, SIGNAL(triggered()), this, SLOT(simulator_save()));
223     simulator_menu->addAction(simulator_save_act);
224     simulator_menu->addSeparator();
225     QAction* simulator_edit_act = new QAction("&Edit...", this);
226     connect(simulator_edit_act, SIGNAL(triggered()), this, SLOT(simulator_edit()));
227     simulator_menu->addAction(simulator_edit_act);
228     QAction* simulator_export_modelfile_act = new QAction("Export to &model file...", this);
229     connect(simulator_export_modelfile_act, SIGNAL(triggered()), this, SLOT(simulator_export_modelfile()));
230     simulator_menu->addAction(simulator_export_modelfile_act);
231     simulator_menu->addSeparator();
232     QAction* simulator_reset_act = new QAction("&Reset...", this);
233     connect(simulator_reset_act, SIGNAL(triggered()), this, SLOT(simulator_reset()));
234     simulator_menu->addAction(simulator_reset_act);
235     // Background menu
236     QMenu* background_menu = menuBar()->addMenu("&Background");
237     QAction* background_load_act = new QAction("&Load...", this);
238     connect(background_load_act, SIGNAL(triggered()), this, SLOT(background_load()));
239     background_menu->addAction(background_load_act);
240     QAction* background_save_act = new QAction("&Save...", this);
241     connect(background_save_act, SIGNAL(triggered()), this, SLOT(background_save()));
242     background_menu->addAction(background_save_act);
243     background_menu->addSeparator();
244     QAction* background_generate_planar_act = new QAction("Generate &planar background...", this);
245     connect(background_generate_planar_act, SIGNAL(triggered()), this, SLOT(background_generate_planar()));
246     background_menu->addAction(background_generate_planar_act);
247     QAction* background_export_modelfile_act = new QAction("Export to &model file...", this);
248     connect(background_export_modelfile_act, SIGNAL(triggered()), this, SLOT(background_export_modelfile()));
249     background_menu->addAction(background_export_modelfile_act);
250     background_menu->addSeparator();
251     QAction* background_reset_act = new QAction("&Reset...", this);
252     connect(background_reset_act, SIGNAL(triggered()), this, SLOT(background_reset()));
253     background_menu->addAction(background_reset_act);
254     // Target menu
255     QMenu* target_menu = menuBar()->addMenu("&Target");
256     QAction* target_load_act = new QAction("&Load...", this);
257     connect(target_load_act, SIGNAL(triggered()), this, SLOT(target_load()));
258     target_menu->addAction(target_load_act);
259     QAction* target_save_act = new QAction("&Save...", this);
260     connect(target_save_act, SIGNAL(triggered()), this, SLOT(target_save()));
261     target_menu->addAction(target_save_act);
262     target_menu->addSeparator();
263     QAction* target_use_modelfile_act = new QAction("Use &model file...", this);
264     connect(target_use_modelfile_act, SIGNAL(triggered()), this, SLOT(target_use_modelfile()));
265     target_menu->addAction(target_use_modelfile_act);
266     QAction* target_generate_bar_pattern_act = new QAction("Generate &bar pattern...", this);
267     connect(target_generate_bar_pattern_act, SIGNAL(triggered()), this, SLOT(target_generate_bar_pattern()));
268     target_menu->addAction(target_generate_bar_pattern_act);
269     QAction* target_generate_star_pattern_act = new QAction("Generate Siemens s&tar...", this);
270     connect(target_generate_star_pattern_act, SIGNAL(triggered()), this, SLOT(target_generate_star_pattern()));
271     target_menu->addAction(target_generate_star_pattern_act);
272     QAction* target_export_modelfile_act = new QAction("Export to &model file...", this);
273     connect(target_export_modelfile_act, SIGNAL(triggered()), this, SLOT(target_export_modelfile()));
274     target_menu->addAction(target_export_modelfile_act);
275     target_menu->addSeparator();
276     QAction* target_reset_act = new QAction("&Reset...", this);
277     connect(target_reset_act, SIGNAL(triggered()), this, SLOT(target_reset()));
278     target_menu->addAction(target_reset_act);
279     // Animation menu
280     QMenu* animation_menu = menuBar()->addMenu("&Animation");
281     QAction* animation_load_act = new QAction("&Load...", this);
282     connect(animation_load_act, SIGNAL(triggered()), this, SLOT(animation_load()));
283     animation_menu->addAction(animation_load_act);
284     animation_menu->addSeparator();
285     QAction* animation_reset_act = new QAction("&Reset...", this);
286     connect(animation_reset_act, SIGNAL(triggered()), this, SLOT(animation_reset()));
287     animation_menu->addAction(animation_reset_act);
288     // Help menu
289     QMenu* help_menu = menuBar()->addMenu("&Help");
290     QAction* help_about_act = new QAction("&About", this);
291     connect(help_about_act, SIGNAL(triggered()), this, SLOT(help_about()));
292     help_menu->addAction(help_about_act);
293
294     if (script_mode && script_minimize_window) {
295         showMinimized();
296     } else {
297         show();
298     }
299
300     _sim_timer = new QTimer(this);
301     connect(_sim_timer, SIGNAL(timeout()), this, SLOT(simulation_step()));
302     _anim_time_requested = false;
303     _sim_timer->start(0);
304     if (script_mode && (std::isfinite(script_export_frame) || script_export_animation)) {
305         if (!_animation.is_valid()) {
306             QMessageBox::critical(this, "Error", "No valid animation available.");
307             std::exit(1);
308         }
309         _anim_widget->enable();
310         QApplication::processEvents();
311         if (std::isfinite(script_export_frame)) {
312             _anim_widget->start();
313             _anim_widget->pause();
314             _anim_time_requested = true;
315             _anim_time_request = script_export_frame * 1e6;
316             simulation_step();
317             QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
318             try {
319                 export_frame(script_export_dir.toLocal8Bit().constData());
320             }
321             catch (std::exception& e) {
322                 QApplication::restoreOverrideCursor();
323                 QMessageBox::critical(this, "Error", e.what());
324                 std::exit(1);
325             }
326             QApplication::restoreOverrideCursor();
327         } else if (script_export_animation) {
328             try {
329                 export_animation(script_export_dir.toLocal8Bit().constData(), !script_minimize_window);
330             }
331             catch (std::exception& e) {
332                 QMessageBox::critical(this, "Error", e.what());
333                 std::exit(1);
334             }
335         }
336         std::exit(0);
337     }
338 }
339
340 MainWindow::~MainWindow()
341 {
342     delete _settings;
343 }
344
345 class SleepThread : public QThread
346 {
347 public:
348     static void usleep(unsigned long usecs) { QThread::usleep(usecs); }
349 };
350
351 static void active_wait(QElapsedTimer& timer, long long until_usecs)
352 {
353     // Allow Qt to process events while waiting, so that e.g. the OSG interaction
354     // can move on. Otherwise we would always have identical phase images.
355     long long usecs;
356     bool waited = false;
357     while ((usecs = timer.nsecsElapsed() / 1000) < until_usecs) {
358         QApplication::processEvents(QEventLoop::AllEvents, (until_usecs - usecs) / 1000);
359         SleepThread::usleep(10); // prevent busy looping
360         waited = true;
361     }
362     if (!waited) {
363         //fprintf(stderr, "Warning: simulation not fast enough for accurate timing\n");
364         QApplication::processEvents();
365     }
366 }
367
368 void MainWindow::simulation_step()
369 {
370     float ambiguity_range = static_cast<double>(Simulator::c) / static_cast<double>(_simulator.modulation_frequency) * 0.5;
371     float max_energy = _simulator.lightsource_simple_power * 1e4f;
372     float max_pmd_amp = max_energy * static_cast<float>(M_PI) / static_cast<float>(M_SQRT1_2);
373     float max_pmd_intensity = 2.0f * max_energy;
374
375     long long anim_time = 0;
376     QElapsedTimer timer;
377     AnimWidget::state_t anim_state = _anim_widget->state();
378     if (anim_state == AnimWidget::state_stopped) {
379         anim_time = _animation.start_time();
380     } else if (anim_state == AnimWidget::state_active || anim_state == AnimWidget::state_paused) {
381         long long total_frame_duration = 4 * (_simulator.exposure_time + _simulator.readout_time);
382         if (_anim_time_requested) {
383             anim_time = ((_anim_time_request - _animation.start_time()) / total_frame_duration)
384                 * total_frame_duration + _animation.start_time();
385             _anim_time_requested = false;
386         } else {
387             anim_time = _last_anim_time;
388             if (anim_state != AnimWidget::state_paused)
389                 anim_time += total_frame_duration;
390         }
391         if (anim_time > _animation.end_time()) {
392             if (_anim_widget->loop())
393                 anim_time = _animation.start_time();
394             else
395                 anim_time = ((_animation.end_time() - _animation.start_time()) / total_frame_duration)
396                     * total_frame_duration + _animation.start_time();
397         }
398         _last_anim_time = anim_time;
399         _anim_widget->update(anim_time);
400     } else {
401         timer.start();
402     }
403
404     // Simulate the four phase images
405     for (int i = 0; i < 4; i++) {
406         long long phase_start_time = anim_time + i * (_simulator.exposure_time + _simulator.readout_time);
407         for (int j = 0; j < _simulator.exposure_time_samples; j++) {
408             long long phase_step_time = phase_start_time + j * _simulator.exposure_time / _simulator.exposure_time_samples;
409             if (anim_state != AnimWidget::state_disabled) {
410                 float pos[3], rot[4];
411                 _animation.interpolate(phase_step_time, pos, rot);
412                 _osg_widget->set_fixed_target_transformation(pos, rot);
413             }
414             // Draw target in OSG for navigation and visual control
415             _osg_widget->draw_frame();
416             // Render the energy map
417             if (_scene.size() == 0)
418                 _osg_widget->capture_scene(&_scene);
419             else
420                 _osg_widget->update_scene(&_scene);
421             _sim_widget->render_map(_scene_id, _scene, i);
422             // Compute a phase image time step from the reduced map
423             _sim_widget->simulate_phase_img(i, j);
424             // Let time pass in free interaction mode.
425             if (anim_state == AnimWidget::state_disabled) {
426                 long long wait_until;
427                 if (j < _simulator.exposure_time_samples - 1) // wait until next phase time step
428                     wait_until = phase_start_time + (j + 1) * _simulator.exposure_time / _simulator.exposure_time_samples;
429                 else // wait until next phase start time
430                     wait_until = anim_time + (i + 1) * (_simulator.exposure_time + _simulator.readout_time);
431                 active_wait(timer, wait_until);
432             }
433         }
434         // Show the ideal depth from the last time sample
435         _depthmap_widget->view(_sim_widget->get_map(), _simulator.map_aspect_ratio(),
436                 2, 0.0f, std::min(ambiguity_range, _simulator.far_plane));
437         // Show the phase image
438         _phase_widgets[i]->view(_sim_widget->get_phase(i), _simulator.aspect_ratio(), 0, -max_energy, +max_energy, false);
439         // Let time pass in free interaction mode.
440         if (anim_state == AnimWidget::state_disabled)
441             active_wait(timer, (i + 1) * (_simulator.exposure_time + _simulator.readout_time));
442     }
443     // Compute the results from the four phase images
444     _sim_widget->simulate_result();
445     // Show the results
446     _pmd_depth_widget->view(_sim_widget->get_result(), _simulator.aspect_ratio(),
447             0, 0.0f, std::min(ambiguity_range, _simulator.far_plane));
448     _pmd_amp_widget->view(_sim_widget->get_result(), _simulator.aspect_ratio(),
449             1, 0.0f, max_pmd_amp);
450     _pmd_intensity_widget->view(_sim_widget->get_result(), _simulator.aspect_ratio(),
451             2, 0.0f, max_pmd_intensity);
452     // Let time pass in free interaction mode.
453     if (anim_state == AnimWidget::state_disabled)
454         active_wait(timer, (3 + 1) * (_simulator.exposure_time + _simulator.readout_time));
455 }
456
457 void MainWindow::reset_scene()
458 {
459     _scene.clear();
460     _scene_id++;
461 }
462
463 void MainWindow::animation_state_changed()
464 {
465     AnimWidget::state_t state = _anim_widget->state();
466     if (state == AnimWidget::state_disabled)
467         _osg_widget->set_mode(OSGWidget::mode_free_interaction);
468     else
469         _osg_widget->set_mode(OSGWidget::mode_fixed_target);
470 }
471
472 void MainWindow::animation_time_changed(long long t)
473 {
474     _anim_time_requested = true;
475     _anim_time_request = t;
476 }
477
478 void MainWindow::closeEvent(QCloseEvent *event)
479 {
480     // Save settings
481     _settings->beginGroup("MainWindow");
482     _settings->setValue("geometry", saveGeometry());
483     _settings->setValue("window_state", saveState());
484     _settings->endGroup();
485
486     event->accept();
487 }
488
489 void MainWindow::get_sim_data(int w, int h)
490 {
491     _sim_widget->makeCurrent();
492
493     GLint tex_bak;
494     glGetIntegerv(GL_TEXTURE_BINDING_2D, &tex_bak);
495
496     _export_phase0.resize(4 * w * h);
497     _export_phase1.resize(4 * w * h);
498     _export_phase2.resize(4 * w * h);
499     _export_phase3.resize(4 * w * h);
500     _export_result.resize(3 * w * h);
501
502     glBindTexture(GL_TEXTURE_2D, _sim_widget->get_phase(0));
503     glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, &_export_phase0[0]);
504     glBindTexture(GL_TEXTURE_2D, _sim_widget->get_phase(1));
505     glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, &_export_phase1[0]);
506     glBindTexture(GL_TEXTURE_2D, _sim_widget->get_phase(2));
507     glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, &_export_phase2[0]);
508     glBindTexture(GL_TEXTURE_2D, _sim_widget->get_phase(3));
509     glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, &_export_phase3[0]);
510     glBindTexture(GL_TEXTURE_2D, _sim_widget->get_result());
511     glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_FLOAT, &_export_result[0]);
512
513     glBindTexture(GL_TEXTURE_2D, tex_bak);
514 }
515
516 static std::string export_worker(const std::string& filename, const Simulator& sim, bool compute_coords, int stride, const float* data)
517 {
518     int w = sim.sensor_width;
519     int h = sim.sensor_height;
520     float aa = sim.aperture_angle * static_cast<float>(M_PI) / 180.0f;
521     float ar = sim.aspect_ratio();
522     float top = std::tan(aa / 2.0f);    // top border of near plane at z==-1
523     float right = ar * top;             // right border of near plane at z==-1
524
525     std::string exc_what;
526     try {
527         FILE* f = fopen(filename.c_str(), "wb");
528         if (!f) {
529             throw std::system_error(errno, std::system_category(),
530                     std::string("Cannot open ").append(filename));
531         }
532 #ifdef HAVE_GTA
533         gta::header hdr;
534         hdr.set_dimensions(w, h);
535         if (compute_coords) {
536             hdr.set_components(gta::float32, gta::float32, gta::float32);
537             hdr.component_taglist(0).set("INTERPRETATION", "X");
538             hdr.component_taglist(1).set("INTERPRETATION", "Y");
539             hdr.component_taglist(2).set("INTERPRETATION", "Z");
540         } else {
541             hdr.set_components(gta::float32);
542         }
543         hdr.set_compression(gta::zlib);
544         hdr.write_to(f);
545         gta::io_state ios;
546 #endif
547         for (int y = h - 1; y >= 0; y--) {
548             for (int x = 0; x < w; x++) {
549                 if (compute_coords) {
550                     float depth = data[(y * w + x) * stride];
551                     float c[3] = {
552                         (2.0f * (x + 0.5f) / w - 1.0f) * right,
553                         (2.0f * (y + 0.5f) / h - 1.0f) * top,
554                         -1.0f
555                     };
556                     float cl = std::sqrt(c[0] * c[0] + c[1] * c[1] + c[2] * c[2]);
557                     for (int i = 0; i < 3; i++)
558                         c[i] *= depth / cl;
559 #ifdef HAVE_GTA
560                     hdr.write_elements(ios, f, 1, c);
561 #else
562                     std::fprintf(f, "%.9g,%.9g,%.9g%s", c[0], c[1], c[2], x < w - 1 ? "," : "\r\n");
563 #endif
564                 } else {
565 #ifdef HAVE_GTA
566                     hdr.write_elements(ios, f, 1, &data[(y * w + x) * stride]);
567 #else
568                     std::fprintf(f, "%.9g%s", data[(y * w + x) * stride], x < w - 1 ? "," : "\r\n");
569 #endif
570                 }
571             }
572         }
573         if (fflush(f) != 0 || ferror(f)) {
574             fclose(f);
575             throw std::system_error(errno, std::system_category(),
576                     std::string("Cannot write ").append(filename));
577         }
578         fclose(f);
579     }
580     catch (std::exception& e) {
581         exc_what = e.what();
582     }
583     return exc_what;
584 }
585
586 void MainWindow::export_frame(const std::string& dirname, int frameno)
587 {
588     int w = _simulator.sensor_width;
589     int h = _simulator.sensor_height;
590     get_sim_data(w, h);
591     std::string framestr;
592     if (frameno >= 0)
593         framestr = QString("%1").arg(QString::number(frameno), 5, QChar('0')).toStdString() + "-";
594     std::string base = (dirname.empty() ? std::string(".") : dirname) + "/" + framestr;
595 #ifdef HAVE_GTA
596     std::string ext = ".gta";
597 #else
598     std::string ext = ".csv";
599     // Force the C locale so that we get the decimal point '.'
600     const char* locbak = setlocale(LC_NUMERIC, "C");
601 #endif
602     QFuture<std::string> f_rd0 = QtConcurrent::run(export_worker, base + "raw-depth-0" + ext, _simulator, false, 4, &_export_phase0[2]);
603     QFuture<std::string> f_rd1 = QtConcurrent::run(export_worker, base + "raw-depth-1" + ext, _simulator, false, 4, &_export_phase1[2]);
604     QFuture<std::string> f_rd2 = QtConcurrent::run(export_worker, base + "raw-depth-2" + ext, _simulator, false, 4, &_export_phase2[2]);
605     QFuture<std::string> f_rd3 = QtConcurrent::run(export_worker, base + "raw-depth-3" + ext, _simulator, false, 4, &_export_phase3[2]);
606     QFuture<std::string> f_re0 = QtConcurrent::run(export_worker, base + "raw-energy-0" + ext, _simulator, false, 4, &_export_phase0[3]);
607     QFuture<std::string> f_re1 = QtConcurrent::run(export_worker, base + "raw-energy-1" + ext, _simulator, false, 4, &_export_phase1[3]);
608     QFuture<std::string> f_re2 = QtConcurrent::run(export_worker, base + "raw-energy-2" + ext, _simulator, false, 4, &_export_phase2[3]);
609     QFuture<std::string> f_re3 = QtConcurrent::run(export_worker, base + "raw-energy-3" + ext, _simulator, false, 4, &_export_phase3[3]);
610     QFuture<std::string> f_pa0 = QtConcurrent::run(export_worker, base + "sim-phase-a-0" + ext, _simulator, false, 4, &_export_phase0[0]);
611     QFuture<std::string> f_pa1 = QtConcurrent::run(export_worker, base + "sim-phase-a-1" + ext, _simulator, false, 4, &_export_phase1[0]);
612     QFuture<std::string> f_pa2 = QtConcurrent::run(export_worker, base + "sim-phase-a-2" + ext, _simulator, false, 4, &_export_phase2[0]);
613     QFuture<std::string> f_pa3 = QtConcurrent::run(export_worker, base + "sim-phase-a-3" + ext, _simulator, false, 4, &_export_phase3[0]);
614     QFuture<std::string> f_pb0 = QtConcurrent::run(export_worker, base + "sim-phase-b-0" + ext, _simulator, false, 4, &_export_phase0[1]);
615     QFuture<std::string> f_pb1 = QtConcurrent::run(export_worker, base + "sim-phase-b-1" + ext, _simulator, false, 4, &_export_phase1[1]);
616     QFuture<std::string> f_pb2 = QtConcurrent::run(export_worker, base + "sim-phase-b-2" + ext, _simulator, false, 4, &_export_phase2[1]);
617     QFuture<std::string> f_pb3 = QtConcurrent::run(export_worker, base + "sim-phase-b-3" + ext, _simulator, false, 4, &_export_phase3[1]);
618     QFuture<std::string> f_sd = QtConcurrent::run(export_worker, base + "sim-depth" + ext, _simulator, false, 3, &_export_result[0]);
619     QFuture<std::string> f_sa = QtConcurrent::run(export_worker, base + "sim-amplitude" + ext, _simulator, false, 3, &_export_result[1]);
620     QFuture<std::string> f_si = QtConcurrent::run(export_worker, base + "sim-intensity" + ext, _simulator, false, 3, &_export_result[2]);
621     QFuture<std::string> f_sc = QtConcurrent::run(export_worker, base + "sim-coords" + ext, _simulator, true, 4, &_export_phase0[2]);
622 #ifdef HAVE_GTA
623 #else
624     // Restore original locale
625     setlocale(LC_NUMERIC, locbak);
626 #endif
627     std::string result;
628     if (result.empty()) result = f_rd0.result();
629     if (result.empty()) result = f_rd1.result();
630     if (result.empty()) result = f_rd2.result();
631     if (result.empty()) result = f_rd3.result();
632     if (result.empty()) result = f_re0.result();
633     if (result.empty()) result = f_re1.result();
634     if (result.empty()) result = f_re2.result();
635     if (result.empty()) result = f_re3.result();
636     if (result.empty()) result = f_pa0.result();
637     if (result.empty()) result = f_pa1.result();
638     if (result.empty()) result = f_pa2.result();
639     if (result.empty()) result = f_pa3.result();
640     if (result.empty()) result = f_pb0.result();
641     if (result.empty()) result = f_pb1.result();
642     if (result.empty()) result = f_pb2.result();
643     if (result.empty()) result = f_pb3.result();
644     if (result.empty()) result = f_sd.result();
645     if (result.empty()) result = f_sa.result();
646     if (result.empty()) result = f_si.result();
647     if (result.empty()) result = f_sc.result();
648     if (!result.empty())
649         throw std::runtime_error(result);
650 }
651
652 void MainWindow::export_animation(const std::string& dirname, bool show_progress)
653 {
654     QProgressDialog progress("Exporting all animation frames...", "Cancel", 0, 1000, this);
655     progress.setWindowModality(Qt::WindowModal);
656     progress.setMinimumDuration(0);
657     _sim_timer->stop();
658     _anim_widget->stop();
659     _anim_widget->start();
660     try {
661         int frame = 0;
662         long long last_anim_time;
663         do {
664             last_anim_time = _last_anim_time;
665             simulation_step();
666             if (frame == 0 || _last_anim_time > last_anim_time)
667                 export_frame(dirname, frame);
668             frame++;
669             if (show_progress)
670                 progress.setValue((_last_anim_time - _animation.start_time())
671                         / ((_animation.end_time() - _animation.start_time()) / 1000));
672             QApplication::processEvents();
673         }
674         while ((frame == 1 || _last_anim_time > last_anim_time) && !progress.wasCanceled());
675     }
676     catch (std::exception& e) {
677         if (show_progress)
678             progress.setValue(1000);
679         throw;
680     }
681     if (show_progress)
682         progress.setValue(1000);
683     _anim_widget->stop();
684     _sim_timer->start(0);
685 }
686
687 void MainWindow::file_export_frame()
688 {
689     QString dirname = QFileDialog::getExistingDirectory(this, "Export directory",
690             _settings->value("Session/directory", QDir::currentPath()).toString());
691     if (dirname.isEmpty())
692         return;
693     _settings->setValue("Session/directory", QFileInfo(dirname).path());
694     QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
695     try {
696         export_frame(dirname.toLocal8Bit().constData());
697     }
698     catch (std::exception& e) {
699         QApplication::restoreOverrideCursor();
700         QMessageBox::critical(this, "Error", e.what());
701     }
702     QApplication::restoreOverrideCursor();
703 }
704
705 void MainWindow::file_export_anim()
706 {
707     if (!_animation.is_valid() || _anim_widget->state() == AnimWidget::state_disabled) {
708         QMessageBox::critical(this, "Error", "Animation is not activated.");
709         return;
710     }
711     QString dirname = QFileDialog::getExistingDirectory(this, "Export directory",
712             _settings->value("Session/directory", QDir::currentPath()).toString());
713     if (dirname.isEmpty())
714         return;
715     _settings->setValue("Session/directory", QFileInfo(dirname).path());
716     try {
717         export_animation(dirname.toLocal8Bit().constData());
718     }
719     catch (std::exception& e) {
720         QMessageBox::critical(this, "Error", e.what());
721     }
722 }
723
724 void MainWindow::simulator_load()
725 {
726     QString filename = QFileDialog::getOpenFileName(this, "Load simulator",
727             _settings->value("Session/directory", QDir::currentPath()).toString(),
728             tr("Simulator descriptions (*.txt)"));
729     if (filename.isEmpty())
730         return;
731     _settings->setValue("Session/directory", QFileInfo(filename).path());
732     try {
733         _simulator.load(filename.toLocal8Bit().constData());
734     }
735     catch (std::exception& e) {
736         QMessageBox::critical(this, "Error", e.what());
737         return;
738     }
739     emit update_simulator(_simulator);
740     _settings->setValue("Session/simulator", filename);
741 }
742
743 void MainWindow::simulator_save()
744 {
745     QString filename = QFileDialog::getSaveFileName(this, "Save simulator",
746             _settings->value("Session/directory", QDir::currentPath()).toString(),
747             tr("Simulator descriptions (*.txt)"));
748     if (filename.isEmpty())
749         return;
750     _settings->setValue("Session/directory", QFileInfo(filename).path());
751     try {
752         _simulator.save(filename.toLocal8Bit().constData());
753     }
754     catch (std::exception& e) {
755         QMessageBox::critical(this, "Error", e.what());
756         return;
757     }
758     _settings->setValue("Session/simulator", filename);
759 }
760
761 class OddSpinBox : public QSpinBox
762 {
763 public:
764     OddSpinBox(QWidget* parent = NULL) : QSpinBox(parent)
765     {
766         setSingleStep(2);
767     }
768
769     QValidator::State validate(QString& input, int &) const
770     {
771         int number;
772         bool number_valid;
773         number = input.toInt(&number_valid);
774         if (number_valid && number >= minimum() && number <= maximum() && number % 2 == 1)
775             return QValidator::Acceptable;
776         else
777             return QValidator::Invalid;
778     }
779 };
780
781 void MainWindow::simulator_edit()
782 {
783     QDialog* dlg = new QDialog(this);
784     dlg->setWindowTitle("Edit simulator");
785
786     int row = 0;
787     QGridLayout* l0 = new QGridLayout;
788
789     l0->addWidget(new QLabel("<b>Rasterization</b>"), row++, 0);
790
791     l0->addWidget(new QLabel("Aperture angle [deg]:"), row, 0);
792     QDoubleSpinBox* aperture_angle_spinbox = new QDoubleSpinBox;
793     aperture_angle_spinbox->setDecimals(4);
794     aperture_angle_spinbox->setRange(1.0, 179.0);
795     aperture_angle_spinbox->setValue(_simulator.aperture_angle);
796     l0->addWidget(aperture_angle_spinbox, row++, 1);
797     l0->addWidget(new QLabel("Near plane [m]:"), row, 0);
798     QDoubleSpinBox* near_plane_spinbox = new QDoubleSpinBox;
799     near_plane_spinbox->setDecimals(4);
800     near_plane_spinbox->setRange(0.01, 100.0);
801     near_plane_spinbox->setValue(_simulator.near_plane);
802     l0->addWidget(near_plane_spinbox, row++, 1);
803     l0->addWidget(new QLabel("Far plane [m]:"), row, 0);
804     QDoubleSpinBox* far_plane_spinbox = new QDoubleSpinBox;
805     far_plane_spinbox->setDecimals(4);
806     far_plane_spinbox->setRange(0.01, 100.0);
807     far_plane_spinbox->setValue(_simulator.far_plane);
808     l0->addWidget(far_plane_spinbox, row++, 1);
809     l0->addWidget(new QLabel("Exposure time samples:"), row, 0);
810     QSpinBox* exposure_time_samples_spinbox = new QSpinBox;
811     exposure_time_samples_spinbox->setRange(1, 512);
812     exposure_time_samples_spinbox->setValue(_simulator.exposure_time_samples);
813     l0->addWidget(exposure_time_samples_spinbox, row++, 1);
814     l0->addWidget(new QLabel("Rendering method:"), row, 0);
815     QComboBox* rendering_box = new QComboBox;
816     rendering_box->addItem("Default");
817     rendering_box->setCurrentIndex(_simulator.rendering_method);
818     l0->addWidget(rendering_box, row++, 1);
819
820     l0->addWidget(new QLabel("<b>Material</b>"), row++, 0);
821
822     l0->addWidget(new QLabel("Model:"), row, 0);
823     QComboBox* material_model_box = new QComboBox;
824     material_model_box->addItem("Lambertian");
825     material_model_box->setCurrentIndex(_simulator.material_model);
826     l0->addWidget(material_model_box, row++, 1);
827     l0->addWidget(new QLabel("Lambertian material: reflectivity [0,1]:"), row, 0);
828     QDoubleSpinBox* material_lambertian_reflectivity_spinbox = new QDoubleSpinBox;
829     material_lambertian_reflectivity_spinbox->setDecimals(4);
830     material_lambertian_reflectivity_spinbox->setRange(0.0, 1.0);
831     material_lambertian_reflectivity_spinbox->setValue(_simulator.material_lambertian_reflectivity);
832     l0->addWidget(material_lambertian_reflectivity_spinbox, row++, 1);
833
834     l0->addWidget(new QLabel("<b>Light Source</b>"), row++, 0);
835
836     l0->addWidget(new QLabel("Model:"), row, 0);
837     QComboBox* lightsource_model_box = new QComboBox;
838     lightsource_model_box->addItem("Simple");
839     lightsource_model_box->addItem("Measured");
840     lightsource_model_box->setCurrentIndex(_simulator.lightsource_model);
841     l0->addWidget(lightsource_model_box, row++, 1);
842     l0->addWidget(new QLabel("Simple model: power [mW]:"), row, 0);
843     QDoubleSpinBox* lightsource_simple_power_spinbox = new QDoubleSpinBox;
844     lightsource_simple_power_spinbox->setRange(1, 1000);
845     lightsource_simple_power_spinbox->setValue(_simulator.lightsource_simple_power);
846     l0->addWidget(lightsource_simple_power_spinbox, row++, 1);
847     l0->addWidget(new QLabel("Simple model: aperture angle [deg]:"), row, 0);
848     QDoubleSpinBox* lightsource_simple_aperture_angle_spinbox = new QDoubleSpinBox;
849     lightsource_simple_aperture_angle_spinbox->setRange(1, 1000);
850     lightsource_simple_aperture_angle_spinbox->setValue(_simulator.lightsource_simple_aperture_angle);
851     l0->addWidget(lightsource_simple_aperture_angle_spinbox, row++, 1);
852     l0->addWidget(new QLabel("Measured model: table file [.gta]:"), row, 0);
853     QGridLayout* l2 = new QGridLayout;
854     QLineEdit* lightsource_measured_intensities = new QLineEdit;
855     lightsource_measured_intensities->setText(_simulator.lightsource_measured_intensities.filename.c_str());
856     l2->addWidget(lightsource_measured_intensities, 0, 0);
857     l2->addWidget(new QLabel(" "), 0, 1);
858     QFileDialog lmidlg(this, "Open lightsource measured intensities",
859             _settings->value("Session/directory", QDir::currentPath()).toString(),
860             tr("Generic Tagged Array files (*.gta)"));
861     QPushButton* lightsource_measured_intensities_btn = new QPushButton("Choose...");
862     connect(lightsource_measured_intensities_btn, SIGNAL(clicked()), &lmidlg, SLOT(exec()));
863     connect(&lmidlg, SIGNAL(fileSelected(const QString&)), lightsource_measured_intensities, SLOT(setText(const QString&)));
864     l2->addWidget(lightsource_measured_intensities_btn, 0, 2);
865     l0->addItem(l2, row++, 1);
866
867     l0->addWidget(new QLabel("<b>Lens</b>"), row++, 0);
868
869     l0->addWidget(new QLabel("Aperture diameter [mm]:"), row, 0);
870     QDoubleSpinBox* lens_aperture_diameter_spinbox = new QDoubleSpinBox;
871     lens_aperture_diameter_spinbox->setRange(1, 1000);
872     lens_aperture_diameter_spinbox->setValue(_simulator.lens_aperture_diameter);
873     l0->addWidget(lens_aperture_diameter_spinbox, row++, 1);
874     l0->addWidget(new QLabel("Focal length [mm]:"), row, 0);
875     QDoubleSpinBox* lens_focal_length_spinbox = new QDoubleSpinBox;
876     lens_focal_length_spinbox->setRange(1, 1000);
877     lens_focal_length_spinbox->setValue(_simulator.lens_focal_length);
878     l0->addWidget(lens_focal_length_spinbox, row++, 1);
879
880     l0->addWidget(new QLabel("<b>Pixels</b>"), row++, 0);
881
882     l0->addWidget(new QLabel("Sensor width [pixels]:"), row, 0);
883     QSpinBox* sensor_width_spinbox = new QSpinBox;
884     sensor_width_spinbox->setRange(2, 1024);
885     sensor_width_spinbox->setValue(_simulator.sensor_width);
886     l0->addWidget(sensor_width_spinbox, row++, 1);
887     l0->addWidget(new QLabel("Sensor height [pixels]:"), row, 0);
888     QSpinBox* sensor_height_spinbox = new QSpinBox;
889     sensor_height_spinbox->setRange(2, 1024);
890     sensor_height_spinbox->setValue(_simulator.sensor_height);
891     l0->addWidget(sensor_height_spinbox, row++, 1);
892     l0->addWidget(new QLabel("Pixel mask x [0-1]:"), row, 0);
893     QDoubleSpinBox* pixel_mask_x_spinbox = new QDoubleSpinBox;
894     pixel_mask_x_spinbox->setDecimals(4);
895     pixel_mask_x_spinbox->setRange(0.0, 1.0);
896     pixel_mask_x_spinbox->setValue(_simulator.pixel_mask_x);
897     l0->addWidget(pixel_mask_x_spinbox, row++, 1);
898     l0->addWidget(new QLabel("Pixel mask y [0-1]:"), row, 0);
899     QDoubleSpinBox* pixel_mask_y_spinbox = new QDoubleSpinBox;
900     pixel_mask_y_spinbox->setDecimals(4);
901     pixel_mask_y_spinbox->setRange(0.0, 1.0);
902     pixel_mask_y_spinbox->setValue(_simulator.pixel_mask_y);
903     l0->addWidget(pixel_mask_y_spinbox, row++, 1);
904     l0->addWidget(new QLabel("Pixel mask width [0-1]:"), row, 0);
905     QDoubleSpinBox* pixel_mask_width_spinbox = new QDoubleSpinBox;
906     pixel_mask_width_spinbox->setDecimals(4);
907     pixel_mask_width_spinbox->setRange(0.0, 1.0);
908     pixel_mask_width_spinbox->setValue(_simulator.pixel_mask_width);
909     l0->addWidget(pixel_mask_width_spinbox, row++, 1);
910     l0->addWidget(new QLabel("Pixel mask height [0-1]:"), row, 0);
911     QDoubleSpinBox* pixel_mask_height_spinbox = new QDoubleSpinBox;
912     pixel_mask_height_spinbox->setDecimals(4);
913     pixel_mask_height_spinbox->setRange(0.0, 1.0);
914     pixel_mask_height_spinbox->setValue(_simulator.pixel_mask_height);
915     l0->addWidget(pixel_mask_height_spinbox, row++, 1);
916     l0->addWidget(new QLabel("Pixel width [subpixels, odd]:"), row, 0);
917     OddSpinBox* pixel_width_spinbox = new OddSpinBox;
918     pixel_width_spinbox->setRange(1, 31);
919     pixel_width_spinbox->setValue(_simulator.pixel_width);
920     l0->addWidget(pixel_width_spinbox, row++, 1);
921     l0->addWidget(new QLabel("Pixel height [subpixels, odd]:"), row, 0);
922     OddSpinBox* pixel_height_spinbox = new OddSpinBox;
923     pixel_height_spinbox->setRange(1, 31);
924     pixel_height_spinbox->setValue(_simulator.pixel_height);
925     l0->addWidget(pixel_height_spinbox, row++, 1);
926     l0->addWidget(new QLabel("Pitch [micrometer]:"), row, 0);
927     QDoubleSpinBox* pixel_pitch_spinbox = new QDoubleSpinBox;
928     pixel_pitch_spinbox->setRange(1, 1000);
929     pixel_pitch_spinbox->setValue(_simulator.pixel_pitch);
930     l0->addWidget(pixel_pitch_spinbox, row++, 1);
931     l0->addWidget(new QLabel("Read-Out time [microseconds]:"), row, 0);
932     QSpinBox* readout_time_spinbox = new QSpinBox;
933     readout_time_spinbox->setRange(1, 50000);
934     readout_time_spinbox->setValue(_simulator.readout_time);
935     l0->addWidget(readout_time_spinbox, row++, 1);
936     l0->addWidget(new QLabel("Contrast (0-1):"), row, 0);
937     QDoubleSpinBox* contrast_spinbox = new QDoubleSpinBox;
938     contrast_spinbox->setDecimals(4);
939     contrast_spinbox->setRange(0.0, 1.0);
940     contrast_spinbox->setValue(_simulator.contrast);
941     l0->addWidget(contrast_spinbox, row++, 1);
942
943     l0->addWidget(new QLabel("<b>User-modifiable parameters</b>"), row++, 0);
944
945     l0->addWidget(new QLabel("Modulation frequency (MHz):"), row, 0);
946     QSpinBox* modulation_frequency_spinbox = new QSpinBox;
947     modulation_frequency_spinbox->setRange(1, 200);
948     modulation_frequency_spinbox->setValue(_simulator.modulation_frequency / (1000 * 1000));
949     l0->addWidget(modulation_frequency_spinbox, row++, 1);
950     l0->addWidget(new QLabel("Exposure time (microseconds):"), row, 0);
951     QSpinBox* exposure_time_spinbox = new QSpinBox;
952     exposure_time_spinbox->setRange(1, 50000);
953     exposure_time_spinbox->setValue(_simulator.exposure_time);
954     l0->addWidget(exposure_time_spinbox, row++, 1);
955
956     QGridLayout* l1 = new QGridLayout;
957     QPushButton* ok_btn = new QPushButton("OK");
958     QPushButton* cancel_btn = new QPushButton("Cancel");
959     connect(ok_btn, SIGNAL(pressed()), dlg, SLOT(accept()));
960     connect(cancel_btn, SIGNAL(pressed()), dlg, SLOT(reject()));
961     l1->addWidget(ok_btn, 0, 0);
962     l1->addWidget(cancel_btn, 0, 1);
963     QGridLayout* layout = new QGridLayout;
964     layout->addLayout(l0, 0, 0);
965     layout->addLayout(l1, 1, 0);
966     dlg->setLayout(layout);
967
968     dlg->exec();
969     if (dlg->result() == QDialog::Accepted) {
970         if (lightsource_model_box->currentIndex() == 1) {
971             try {
972                 _simulator.lightsource_measured_intensities.load(
973                         qPrintable(lightsource_measured_intensities->text()));
974             }
975             catch (std::exception& e) {
976                 QMessageBox::critical(this, "Error", e.what());
977                 return;
978             }
979         }
980         _simulator.aperture_angle = aperture_angle_spinbox->value();
981         _simulator.near_plane = near_plane_spinbox->value();
982         _simulator.far_plane = far_plane_spinbox->value();
983         _simulator.exposure_time_samples = exposure_time_samples_spinbox->value();
984         _simulator.rendering_method = rendering_box->currentIndex();
985         _simulator.material_model = material_model_box->currentIndex();
986         _simulator.material_lambertian_reflectivity = material_lambertian_reflectivity_spinbox->value();
987         _simulator.lightsource_model = lightsource_model_box->currentIndex();
988         _simulator.lightsource_simple_power = lightsource_simple_power_spinbox->value();
989         _simulator.lightsource_simple_aperture_angle = lightsource_simple_aperture_angle_spinbox->value();
990         _simulator.lens_aperture_diameter = lens_aperture_diameter_spinbox->value();
991         _simulator.lens_focal_length = lens_focal_length_spinbox->value();
992         _simulator.sensor_width = sensor_width_spinbox->value();
993         _simulator.sensor_height = sensor_height_spinbox->value();
994         _simulator.pixel_mask_x = pixel_mask_x_spinbox->value();
995         _simulator.pixel_mask_y = pixel_mask_y_spinbox->value();
996         _simulator.pixel_mask_width = pixel_mask_width_spinbox->value();
997         _simulator.pixel_mask_height = pixel_mask_height_spinbox->value();
998         _simulator.pixel_width = pixel_width_spinbox->value();
999         _simulator.pixel_height = pixel_height_spinbox->value();
1000         _simulator.pixel_pitch = pixel_pitch_spinbox->value();
1001         _simulator.readout_time = readout_time_spinbox->value();
1002         _simulator.contrast = contrast_spinbox->value();
1003         _simulator.modulation_frequency = modulation_frequency_spinbox->value() * 1000 * 1000;
1004         _simulator.exposure_time = exposure_time_spinbox->value();
1005         emit update_simulator(_simulator);
1006     }
1007     delete dlg;
1008 }
1009
1010 void MainWindow::simulator_export_modelfile()
1011 {
1012     QString filename = QFileDialog::getSaveFileName(this, "Save model file",
1013             _settings->value("Session/directory", QDir::currentPath()).toString(),
1014             tr("Model files (*.obj)"));
1015     if (filename.isEmpty())
1016         return;
1017     _settings->setValue("Session/directory", QFileInfo(filename).path());
1018     try {
1019         _osg_widget->export_frustum(filename.toLocal8Bit().constData());
1020     }
1021     catch (std::exception& e) {
1022         QMessageBox::critical(this, "Error", e.what());
1023         return;
1024     }
1025 }
1026
1027 void MainWindow::simulator_reset()
1028 {
1029     _simulator = Simulator();
1030     _settings->setValue("Session/simulator", QString());
1031     emit update_simulator(_simulator);
1032 }
1033
1034 void MainWindow::background_load()
1035 {
1036     QString filename = QFileDialog::getOpenFileName(this, "Load background",
1037             _settings->value("Session/directory", QDir::currentPath()).toString(),
1038             tr("Target descriptions (*.txt)"));
1039     if (filename.isEmpty())
1040         return;
1041     _settings->setValue("Session/directory", QFileInfo(filename).path());
1042     try {
1043         _background.load(filename.toLocal8Bit().constData());
1044     }
1045     catch (std::exception& e) {
1046         QMessageBox::critical(this, "Error", e.what());
1047         return;
1048     }
1049     emit update_scene(_background, _target);
1050     _settings->setValue("Session/background", filename);
1051 }
1052
1053 void MainWindow::background_save()
1054 {
1055     QString filename = QFileDialog::getSaveFileName(this, "Save background",
1056             _settings->value("Session/directory", QDir::currentPath()).toString(),
1057             tr("Target descriptions (*.txt)"));
1058     if (filename.isEmpty())
1059         return;
1060     _settings->setValue("Session/directory", QFileInfo(filename).path());
1061     try {
1062         _background.save(filename.toLocal8Bit().constData());
1063     }
1064     catch (std::exception& e) {
1065         QMessageBox::critical(this, "Error", e.what());
1066         return;
1067     }
1068     _settings->setValue("Session/background", filename);
1069 }
1070
1071 void MainWindow::background_generate_planar()
1072 {
1073     QDialog* dlg = new QDialog(this);
1074     dlg->setWindowTitle("Generate planar background");
1075     QGridLayout* l0 = new QGridLayout;
1076
1077     l0->addWidget(new QLabel("Width (m):"), 1, 0);
1078     QDoubleSpinBox* width_spinbox = new QDoubleSpinBox;
1079     width_spinbox->setDecimals(4);
1080     width_spinbox->setRange(0.001, 10.0);
1081     width_spinbox->setValue(_background.background_planar_width);
1082     l0->addWidget(width_spinbox, 1, 1);
1083     l0->addWidget(new QLabel("Height (m):"), 2, 0);
1084     QDoubleSpinBox* height_spinbox = new QDoubleSpinBox;
1085     height_spinbox->setDecimals(4);
1086     height_spinbox->setRange(0.001, 10.0);
1087     height_spinbox->setValue(_background.background_planar_height);
1088     l0->addWidget(height_spinbox, 2, 1);
1089     l0->addWidget(new QLabel("Distance (m; 0=disabled):"), 3, 0);
1090     QDoubleSpinBox* dist_spinbox = new QDoubleSpinBox;
1091     dist_spinbox->setDecimals(4);
1092     dist_spinbox->setRange(0.0, 10.0);
1093     dist_spinbox->setValue(_background.background_planar_dist);
1094     l0->addWidget(dist_spinbox, 3, 1);
1095
1096     QGridLayout* l1 = new QGridLayout;
1097     QPushButton* ok_btn = new QPushButton("OK");
1098     QPushButton* cancel_btn = new QPushButton("Cancel");
1099     connect(ok_btn, SIGNAL(pressed()), dlg, SLOT(accept()));
1100     connect(cancel_btn, SIGNAL(pressed()), dlg, SLOT(reject()));
1101     l1->addWidget(ok_btn, 0, 0);
1102     l1->addWidget(cancel_btn, 0, 1);
1103     QGridLayout* layout = new QGridLayout;
1104     layout->addLayout(l0, 0, 0);
1105     layout->addLayout(l1, 1, 0);
1106     dlg->setLayout(layout);
1107
1108     dlg->exec();
1109     if (dlg->result() == QDialog::Accepted) {
1110         _background.variant = Target::variant_background_planar;
1111         _background.background_planar_width = width_spinbox->value();
1112         _background.background_planar_height = height_spinbox->value();
1113         _background.background_planar_dist = dist_spinbox->value();
1114         emit update_scene(_background, _target);
1115     }
1116     delete dlg;
1117 }
1118
1119 void MainWindow::background_export_modelfile()
1120 {
1121     QString filename = QFileDialog::getSaveFileName(this, "Save model file",
1122             _settings->value("Session/directory", QDir::currentPath()).toString(),
1123             tr("Model files (*.obj)"));
1124     if (filename.isEmpty())
1125         return;
1126     _settings->setValue("Session/directory", QFileInfo(filename).path());
1127     try {
1128         _osg_widget->export_background(filename.toLocal8Bit().constData());
1129     }
1130     catch (std::exception& e) {
1131         QMessageBox::critical(this, "Error", e.what());
1132         return;
1133     }
1134 }
1135
1136 void MainWindow::background_reset()
1137 {
1138     _background = Target(Target::variant_background_planar);
1139     _settings->setValue("Session/background", QString());
1140     emit update_scene(_background, _target);
1141 }
1142
1143 void MainWindow::target_load()
1144 {
1145     QString filename = QFileDialog::getOpenFileName(this, "Load target",
1146             _settings->value("Session/directory", QDir::currentPath()).toString(),
1147             tr("Target descriptions (*.txt)"));
1148     if (filename.isEmpty())
1149         return;
1150     _settings->setValue("Session/directory", QFileInfo(filename).path());
1151     try {
1152         _target.load(filename.toLocal8Bit().constData());
1153     }
1154     catch (std::exception& e) {
1155         QMessageBox::critical(this, "Error", e.what());
1156         return;
1157     }
1158     emit update_scene(_background, _target);
1159     _settings->setValue("Session/target", filename);
1160 }
1161
1162 void MainWindow::target_save()
1163 {
1164     QString filename = QFileDialog::getSaveFileName(this, "Save target",
1165             _settings->value("Session/directory", QDir::currentPath()).toString(),
1166             tr("Target descriptions (*.txt)"));
1167     if (filename.isEmpty())
1168         return;
1169     _settings->setValue("Session/directory", QFileInfo(filename).path());
1170     try {
1171         _target.save(filename.toLocal8Bit().constData());
1172     }
1173     catch (std::exception& e) {
1174         QMessageBox::critical(this, "Error", e.what());
1175         return;
1176     }
1177     _settings->setValue("Session/target", filename);
1178 }
1179
1180 void MainWindow::target_use_modelfile()
1181 {
1182     QString filename = QFileDialog::getOpenFileName(this, "Read model file",
1183             _settings->value("Session/directory", QDir::currentPath()).toString(),
1184             tr("Target models (*.obj *.ply)"));
1185     if (filename.isEmpty())
1186         return;
1187     _settings->setValue("Session/directory", QFileInfo(filename).path());
1188     _target.variant = Target::variant_model;
1189     _target.model_filename = filename.toLocal8Bit().constData();
1190     emit update_scene(_background, _target);
1191 }
1192
1193 void MainWindow::target_generate_bar_pattern()
1194 {
1195     QDialog* dlg = new QDialog(this);
1196     dlg->setWindowTitle("Generate bar pattern");
1197
1198     QGridLayout* l0 = new QGridLayout;
1199     l0->addWidget(new QLabel("Number of bars:"), 0, 0);
1200     QSpinBox* number_of_bars_spinbox = new QSpinBox;
1201     number_of_bars_spinbox->setRange(1, 99);
1202     number_of_bars_spinbox->setValue(_target.number_of_bars);
1203     l0->addWidget(number_of_bars_spinbox, 0, 1);
1204     l0->addWidget(new QLabel("First bar width (m):"), 1, 0);
1205     QDoubleSpinBox* first_bar_width_spinbox = new QDoubleSpinBox;
1206     first_bar_width_spinbox->setDecimals(4);
1207     first_bar_width_spinbox->setRange(0.001, 10.0);
1208     first_bar_width_spinbox->setValue(_target.first_bar_width);
1209     l0->addWidget(first_bar_width_spinbox, 1, 1);
1210     l0->addWidget(new QLabel("First bar height (m):"), 2, 0);
1211     QDoubleSpinBox* first_bar_height_spinbox = new QDoubleSpinBox;
1212     first_bar_height_spinbox->setDecimals(4);
1213     first_bar_height_spinbox->setRange(0.001, 10.0);
1214     first_bar_height_spinbox->setValue(_target.first_bar_height);
1215     l0->addWidget(first_bar_height_spinbox, 2, 1);
1216     l0->addWidget(new QLabel("First offset x (m):"), 3, 0);
1217     QDoubleSpinBox* first_offset_x_spinbox = new QDoubleSpinBox;
1218     first_offset_x_spinbox->setDecimals(4);
1219     first_offset_x_spinbox->setRange(-10, 10);
1220     first_offset_x_spinbox->setValue(_target.first_offset_x);
1221     l0->addWidget(first_offset_x_spinbox, 3, 1);
1222     l0->addWidget(new QLabel("First offset y (m):"), 4, 0);
1223     QDoubleSpinBox* first_offset_y_spinbox = new QDoubleSpinBox;
1224     first_offset_y_spinbox->setDecimals(4);
1225     first_offset_y_spinbox->setRange(-10, 10);
1226     first_offset_y_spinbox->setValue(_target.first_offset_y);
1227     l0->addWidget(first_offset_y_spinbox, 4, 1);
1228     l0->addWidget(new QLabel("First offset z (m):"), 5, 0);
1229     QDoubleSpinBox* first_offset_z_spinbox = new QDoubleSpinBox;
1230     first_offset_z_spinbox->setDecimals(4);
1231     first_offset_z_spinbox->setRange(-10, 10);
1232     first_offset_z_spinbox->setValue(_target.first_offset_z);
1233     l0->addWidget(first_offset_z_spinbox, 5, 1);
1234     l0->addWidget(new QLabel("Next bar width factor:"), 6, 0);
1235     QDoubleSpinBox* next_bar_width_factor_spinbox = new QDoubleSpinBox;
1236     next_bar_width_factor_spinbox->setDecimals(4);
1237     next_bar_width_factor_spinbox->setRange(0.1, 10.0);
1238     next_bar_width_factor_spinbox->setValue(_target.next_bar_width_factor);
1239     l0->addWidget(next_bar_width_factor_spinbox, 6, 1);
1240     l0->addWidget(new QLabel("Next bar width offset:"), 7, 0);
1241     QDoubleSpinBox* next_bar_width_offset_spinbox = new QDoubleSpinBox;
1242     next_bar_width_offset_spinbox->setDecimals(4);
1243     next_bar_width_offset_spinbox->setRange(-10, 10);
1244     next_bar_width_offset_spinbox->setValue(_target.next_bar_width_offset);
1245     l0->addWidget(next_bar_width_offset_spinbox, 7, 1);
1246     l0->addWidget(new QLabel("Next bar height factor:"), 8, 0);
1247     QDoubleSpinBox* next_bar_height_factor_spinbox = new QDoubleSpinBox;
1248     next_bar_height_factor_spinbox->setDecimals(4);
1249     next_bar_height_factor_spinbox->setRange(0.1, 10.0);
1250     next_bar_height_factor_spinbox->setValue(_target.next_bar_height_factor);
1251     l0->addWidget(next_bar_height_factor_spinbox, 8, 1);
1252     l0->addWidget(new QLabel("Next bar height offset:"), 9, 0);
1253     QDoubleSpinBox* next_bar_height_offset_spinbox = new QDoubleSpinBox;
1254     next_bar_height_offset_spinbox->setDecimals(4);
1255     next_bar_height_offset_spinbox->setRange(-10, 10);
1256     next_bar_height_offset_spinbox->setValue(_target.next_bar_height_offset);
1257     l0->addWidget(next_bar_height_offset_spinbox, 9, 1);
1258     l0->addWidget(new QLabel("Next offset x factor:"), 10, 0);
1259     QDoubleSpinBox* next_offset_x_factor_spinbox = new QDoubleSpinBox;
1260     next_offset_x_factor_spinbox->setDecimals(4);
1261     next_offset_x_factor_spinbox->setRange(-10, 10);
1262     next_offset_x_factor_spinbox->setValue(_target.next_offset_x_factor);
1263     l0->addWidget(next_offset_x_factor_spinbox, 10, 1);
1264     l0->addWidget(new QLabel("Next offset x offset:"), 11, 0);
1265     QDoubleSpinBox* next_offset_x_offset_spinbox = new QDoubleSpinBox;
1266     next_offset_x_offset_spinbox->setDecimals(4);
1267     next_offset_x_offset_spinbox->setRange(-10, 10);
1268     next_offset_x_offset_spinbox->setValue(_target.next_offset_x_offset);
1269     l0->addWidget(next_offset_x_offset_spinbox, 11, 1);
1270     l0->addWidget(new QLabel("Next offset y factor:"), 12, 0);
1271     QDoubleSpinBox* next_offset_y_factor_spinbox = new QDoubleSpinBox;
1272     next_offset_y_factor_spinbox->setDecimals(4);
1273     next_offset_y_factor_spinbox->setRange(-10, 10);
1274     next_offset_y_factor_spinbox->setValue(_target.next_offset_y_factor);
1275     l0->addWidget(next_offset_y_factor_spinbox, 12, 1);
1276     l0->addWidget(new QLabel("Next offset y offset:"), 13, 0);
1277     QDoubleSpinBox* next_offset_y_offset_spinbox = new QDoubleSpinBox;
1278     next_offset_y_offset_spinbox->setDecimals(4);
1279     next_offset_y_offset_spinbox->setRange(-10, 10);
1280     next_offset_y_offset_spinbox->setValue(_target.next_offset_y_offset);
1281     l0->addWidget(next_offset_y_offset_spinbox, 13, 1);
1282     l0->addWidget(new QLabel("Next offset z factor:"), 14, 0);
1283     QDoubleSpinBox* next_offset_z_factor_spinbox = new QDoubleSpinBox;
1284     next_offset_z_factor_spinbox->setDecimals(4);
1285     next_offset_z_factor_spinbox->setRange(-10, 10);
1286     next_offset_z_factor_spinbox->setValue(_target.next_offset_z_factor);
1287     l0->addWidget(next_offset_z_factor_spinbox, 14, 1);
1288     l0->addWidget(new QLabel("Next offset z offset:"), 15, 0);
1289     QDoubleSpinBox* next_offset_z_offset_spinbox = new QDoubleSpinBox;
1290     next_offset_z_offset_spinbox->setDecimals(4);
1291     next_offset_z_offset_spinbox->setRange(-10, 10);
1292     next_offset_z_offset_spinbox->setValue(_target.next_offset_z_offset);
1293     l0->addWidget(next_offset_z_offset_spinbox, 15, 1);
1294     l0->addWidget(new QLabel("Background orientation:"), 16, 0);
1295     QComboBox* bar_background_near_side_combobox = new QComboBox;
1296     bar_background_near_side_combobox->addItem("Disable background");
1297     bar_background_near_side_combobox->addItem("Left side near, right side far");
1298     bar_background_near_side_combobox->addItem("Top side near, bottom side far");
1299     bar_background_near_side_combobox->addItem("Right side near, left side far");
1300     bar_background_near_side_combobox->addItem("Bottom side near, top side far");
1301     bar_background_near_side_combobox->setCurrentIndex(_target.bar_background_near_side + 1);
1302     l0->addWidget(bar_background_near_side_combobox, 16, 1);
1303     l0->addWidget(new QLabel("Background near side distance to bars (m):"), 17, 0);
1304     QDoubleSpinBox* bar_background_dist_near_spinbox = new QDoubleSpinBox;
1305     bar_background_dist_near_spinbox->setDecimals(4);
1306     bar_background_dist_near_spinbox->setRange(-1, 1);
1307     bar_background_dist_near_spinbox->setValue(_target.bar_background_dist_near);
1308     l0->addWidget(bar_background_dist_near_spinbox, 17, 1);
1309     l0->addWidget(new QLabel("Background far side distance to bars (m):"), 18, 0);
1310     QDoubleSpinBox* bar_background_dist_far_spinbox = new QDoubleSpinBox;
1311     bar_background_dist_far_spinbox->setDecimals(4);
1312     bar_background_dist_far_spinbox->setRange(-1, 1);
1313     bar_background_dist_far_spinbox->setValue(_target.bar_background_dist_far);
1314     l0->addWidget(bar_background_dist_far_spinbox, 18, 1);
1315     l0->addWidget(new QLabel("Rotation around view direction (degrees):"), 19, 0);
1316     QDoubleSpinBox* bar_rotation_spinbox = new QDoubleSpinBox;
1317     bar_rotation_spinbox->setDecimals(4);
1318     bar_rotation_spinbox->setRange(-180.0, +180.0);
1319     bar_rotation_spinbox->setValue(_target.bar_rotation / M_PI * 180.0);
1320     l0->addWidget(bar_rotation_spinbox, 19, 1);
1321
1322     QGridLayout* l1 = new QGridLayout;
1323     QPushButton* ok_btn = new QPushButton("OK");
1324     QPushButton* cancel_btn = new QPushButton("Cancel");
1325     connect(ok_btn, SIGNAL(pressed()), dlg, SLOT(accept()));
1326     connect(cancel_btn, SIGNAL(pressed()), dlg, SLOT(reject()));
1327     l1->addWidget(ok_btn, 0, 0);
1328     l1->addWidget(cancel_btn, 0, 1);
1329     QGridLayout* layout = new QGridLayout;
1330     layout->addLayout(l0, 0, 0);
1331     layout->addLayout(l1, 1, 0);
1332     dlg->setLayout(layout);
1333
1334     dlg->exec();
1335     if (dlg->result() == QDialog::Accepted) {
1336         _target.variant = Target::variant_bars;
1337         _target.number_of_bars = number_of_bars_spinbox->value();
1338         _target.first_bar_width = first_bar_width_spinbox->value();
1339         _target.first_bar_height = first_bar_height_spinbox->value();
1340         _target.first_offset_x = first_offset_x_spinbox->value();
1341         _target.first_offset_y = first_offset_y_spinbox->value();
1342         _target.first_offset_z = first_offset_z_spinbox->value();
1343         _target.next_bar_width_factor = next_bar_width_factor_spinbox->value();
1344         _target.next_bar_width_offset = next_bar_width_offset_spinbox->value();
1345         _target.next_bar_height_factor = next_bar_height_factor_spinbox->value();
1346         _target.next_bar_height_offset = next_bar_height_offset_spinbox->value();
1347         _target.next_offset_x_factor = next_offset_x_factor_spinbox->value();
1348         _target.next_offset_x_offset = next_offset_x_offset_spinbox->value();
1349         _target.next_offset_y_factor = next_offset_y_factor_spinbox->value();
1350         _target.next_offset_y_offset = next_offset_y_offset_spinbox->value();
1351         _target.next_offset_z_factor = next_offset_z_factor_spinbox->value();
1352         _target.next_offset_z_offset = next_offset_z_offset_spinbox->value();
1353         _target.bar_background_near_side = bar_background_near_side_combobox->currentIndex() - 1;
1354         _target.bar_background_dist_near = bar_background_dist_near_spinbox->value();
1355         _target.bar_background_dist_far = bar_background_dist_far_spinbox->value();
1356         _target.bar_rotation = bar_rotation_spinbox->value() / 180.0 * M_PI;
1357         emit update_scene(_background, _target);
1358     }
1359     delete dlg;
1360 }
1361
1362 void MainWindow::target_generate_star_pattern()
1363 {
1364     QDialog* dlg = new QDialog(this);
1365     dlg->setWindowTitle("Generate Siemens star pattern");
1366
1367     QGridLayout* l0 = new QGridLayout;
1368     l0->addWidget(new QLabel("Star spokes:"), 0, 0);
1369     QSpinBox* star_spokes_spinbox = new QSpinBox;
1370     star_spokes_spinbox->setRange(2, 50);
1371     star_spokes_spinbox->setValue(_target.star_spokes);
1372     l0->addWidget(star_spokes_spinbox, 0, 1);
1373     l0->addWidget(new QLabel("Star radius (m):"), 1, 0);
1374     QDoubleSpinBox* star_radius_spinbox = new QDoubleSpinBox;
1375     star_radius_spinbox->setDecimals(4);
1376     star_radius_spinbox->setRange(0.1, 10.0);
1377     star_radius_spinbox->setValue(_target.star_radius);
1378     l0->addWidget(star_radius_spinbox, 1, 1);
1379     l0->addWidget(new QLabel("Background distance at center (m):"), 2, 0);
1380     QDoubleSpinBox* star_background_dist_center_spinbox = new QDoubleSpinBox;
1381     star_background_dist_center_spinbox->setDecimals(4);
1382     star_background_dist_center_spinbox->setRange(0, 10);
1383     star_background_dist_center_spinbox->setValue(_target.star_background_dist_center);
1384     l0->addWidget(star_background_dist_center_spinbox, 2, 1);
1385     l0->addWidget(new QLabel("Background distance at rim (m):"), 3, 0);
1386     QDoubleSpinBox* star_background_dist_rim_spinbox = new QDoubleSpinBox;
1387     star_background_dist_rim_spinbox->setDecimals(4);
1388     star_background_dist_rim_spinbox->setRange(0, 10);
1389     star_background_dist_rim_spinbox->setValue(_target.star_background_dist_rim);
1390     l0->addWidget(star_background_dist_rim_spinbox, 3, 1);
1391
1392     QGridLayout* l1 = new QGridLayout;
1393     QPushButton* ok_btn = new QPushButton("OK");
1394     QPushButton* cancel_btn = new QPushButton("Cancel");
1395     connect(ok_btn, SIGNAL(pressed()), dlg, SLOT(accept()));
1396     connect(cancel_btn, SIGNAL(pressed()), dlg, SLOT(reject()));
1397     l1->addWidget(ok_btn, 0, 0);
1398     l1->addWidget(cancel_btn, 0, 1);
1399     QGridLayout* layout = new QGridLayout;
1400     layout->addLayout(l0, 0, 0);
1401     layout->addLayout(l1, 1, 0);
1402     dlg->setLayout(layout);
1403
1404     dlg->exec();
1405     if (dlg->result() == QDialog::Accepted) {
1406         _target.variant = Target::variant_star;
1407         _target.star_spokes = star_spokes_spinbox->value();
1408         _target.star_radius = star_radius_spinbox->value();
1409         _target.star_background_dist_center = star_background_dist_center_spinbox->value();
1410         _target.star_background_dist_rim = star_background_dist_rim_spinbox->value();
1411         emit update_scene(_background, _target);
1412     }
1413     delete dlg;
1414 }
1415
1416 void MainWindow::target_export_modelfile()
1417 {
1418     QString filename = QFileDialog::getSaveFileName(this, "Save model file",
1419             _settings->value("Session/directory", QDir::currentPath()).toString(),
1420             tr("Model files (*.obj)"));
1421     if (filename.isEmpty())
1422         return;
1423     _settings->setValue("Session/directory", QFileInfo(filename).path());
1424     try {
1425         _osg_widget->export_target(filename.toLocal8Bit().constData());
1426     }
1427     catch (std::exception& e) {
1428         QMessageBox::critical(this, "Error", e.what());
1429         return;
1430     }
1431 }
1432
1433 void MainWindow::target_reset()
1434 {
1435     _target = Target();
1436     _settings->setValue("Session/target", QString());
1437     emit update_scene(_background, _target);
1438 }
1439
1440 void MainWindow::animation_load()
1441 {
1442     QString filename = QFileDialog::getOpenFileName(this, "Load animation",
1443             _settings->value("Session/directory", QDir::currentPath()).toString(),
1444             tr("Animation descriptions (*.txt)"));
1445     if (filename.isEmpty())
1446         return;
1447     _settings->setValue("Session/directory", QFileInfo(filename).path());
1448     try {
1449         _animation.load(filename.toLocal8Bit().constData());
1450     }
1451     catch (std::exception& e) {
1452         QMessageBox::critical(this, "Error", e.what());
1453         return;
1454     }
1455     emit update_animation(_animation);
1456     _settings->setValue("Session/animation", filename);
1457 }
1458
1459 void MainWindow::animation_reset()
1460 {
1461     _animation = Animation();
1462     _settings->setValue("Session/animation", QString());
1463     emit update_animation(_animation);
1464 }
1465
1466 void MainWindow::help_about()
1467 {
1468     QMessageBox::about(this, tr("About PMDSim"), tr(
1469                 "<p>PMDSim version %1</p>"
1470                 "<p>Copyright (C) 2014<br>"
1471                 "   <a href=\"http://www.cg.informatik.uni-siegen.de/\">"
1472                 "   Computer Graphics Group, University of Siegen</a>.<br>"
1473                 "   All rights reserved.<br>"
1474                 "</p>").arg(PROJECT_VERSION));
1475 }