Fix export of simulated coordinates, and add export of raw coordinates.
[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_rc0 = QtConcurrent::run(export_worker, base + "raw-coords-0" + ext, _simulator, true, 4, &_export_phase0[2]);
607     QFuture<std::string> f_rc1 = QtConcurrent::run(export_worker, base + "raw-coords-1" + ext, _simulator, true, 4, &_export_phase1[2]);
608     QFuture<std::string> f_rc2 = QtConcurrent::run(export_worker, base + "raw-coords-2" + ext, _simulator, true, 4, &_export_phase2[2]);
609     QFuture<std::string> f_rc3 = QtConcurrent::run(export_worker, base + "raw-coords-3" + ext, _simulator, true, 4, &_export_phase3[2]);
610     QFuture<std::string> f_re0 = QtConcurrent::run(export_worker, base + "raw-energy-0" + ext, _simulator, false, 4, &_export_phase0[3]);
611     QFuture<std::string> f_re1 = QtConcurrent::run(export_worker, base + "raw-energy-1" + ext, _simulator, false, 4, &_export_phase1[3]);
612     QFuture<std::string> f_re2 = QtConcurrent::run(export_worker, base + "raw-energy-2" + ext, _simulator, false, 4, &_export_phase2[3]);
613     QFuture<std::string> f_re3 = QtConcurrent::run(export_worker, base + "raw-energy-3" + ext, _simulator, false, 4, &_export_phase3[3]);
614     QFuture<std::string> f_pa0 = QtConcurrent::run(export_worker, base + "sim-phase-a-0" + ext, _simulator, false, 4, &_export_phase0[0]);
615     QFuture<std::string> f_pa1 = QtConcurrent::run(export_worker, base + "sim-phase-a-1" + ext, _simulator, false, 4, &_export_phase1[0]);
616     QFuture<std::string> f_pa2 = QtConcurrent::run(export_worker, base + "sim-phase-a-2" + ext, _simulator, false, 4, &_export_phase2[0]);
617     QFuture<std::string> f_pa3 = QtConcurrent::run(export_worker, base + "sim-phase-a-3" + ext, _simulator, false, 4, &_export_phase3[0]);
618     QFuture<std::string> f_pb0 = QtConcurrent::run(export_worker, base + "sim-phase-b-0" + ext, _simulator, false, 4, &_export_phase0[1]);
619     QFuture<std::string> f_pb1 = QtConcurrent::run(export_worker, base + "sim-phase-b-1" + ext, _simulator, false, 4, &_export_phase1[1]);
620     QFuture<std::string> f_pb2 = QtConcurrent::run(export_worker, base + "sim-phase-b-2" + ext, _simulator, false, 4, &_export_phase2[1]);
621     QFuture<std::string> f_pb3 = QtConcurrent::run(export_worker, base + "sim-phase-b-3" + ext, _simulator, false, 4, &_export_phase3[1]);
622     QFuture<std::string> f_sd = QtConcurrent::run(export_worker, base + "sim-depth" + ext, _simulator, false, 3, &_export_result[0]);
623     QFuture<std::string> f_sa = QtConcurrent::run(export_worker, base + "sim-amplitude" + ext, _simulator, false, 3, &_export_result[1]);
624     QFuture<std::string> f_si = QtConcurrent::run(export_worker, base + "sim-intensity" + ext, _simulator, false, 3, &_export_result[2]);
625     QFuture<std::string> f_sc = QtConcurrent::run(export_worker, base + "sim-coords" + ext, _simulator, true, 3, &_export_result[0]);
626 #ifdef HAVE_GTA
627 #else
628     // Restore original locale
629     setlocale(LC_NUMERIC, locbak);
630 #endif
631     std::string result;
632     if (result.empty()) result = f_rd0.result();
633     if (result.empty()) result = f_rd1.result();
634     if (result.empty()) result = f_rd2.result();
635     if (result.empty()) result = f_rd3.result();
636     if (result.empty()) result = f_rc0.result();
637     if (result.empty()) result = f_rc1.result();
638     if (result.empty()) result = f_rc2.result();
639     if (result.empty()) result = f_rc3.result();
640     if (result.empty()) result = f_re0.result();
641     if (result.empty()) result = f_re1.result();
642     if (result.empty()) result = f_re2.result();
643     if (result.empty()) result = f_re3.result();
644     if (result.empty()) result = f_pa0.result();
645     if (result.empty()) result = f_pa1.result();
646     if (result.empty()) result = f_pa2.result();
647     if (result.empty()) result = f_pa3.result();
648     if (result.empty()) result = f_pb0.result();
649     if (result.empty()) result = f_pb1.result();
650     if (result.empty()) result = f_pb2.result();
651     if (result.empty()) result = f_pb3.result();
652     if (result.empty()) result = f_sd.result();
653     if (result.empty()) result = f_sa.result();
654     if (result.empty()) result = f_si.result();
655     if (result.empty()) result = f_sc.result();
656     if (!result.empty())
657         throw std::runtime_error(result);
658 }
659
660 void MainWindow::export_animation(const std::string& dirname, bool show_progress)
661 {
662     QProgressDialog progress("Exporting all animation frames...", "Cancel", 0, 1000, this);
663     progress.setWindowModality(Qt::WindowModal);
664     progress.setMinimumDuration(0);
665     _sim_timer->stop();
666     _anim_widget->stop();
667     _anim_widget->start();
668     try {
669         int frame = 0;
670         long long last_anim_time;
671         do {
672             last_anim_time = _last_anim_time;
673             simulation_step();
674             if (frame == 0 || _last_anim_time > last_anim_time)
675                 export_frame(dirname, frame);
676             frame++;
677             if (show_progress)
678                 progress.setValue((_last_anim_time - _animation.start_time())
679                         / ((_animation.end_time() - _animation.start_time()) / 1000));
680             QApplication::processEvents();
681         }
682         while ((frame == 1 || _last_anim_time > last_anim_time) && !progress.wasCanceled());
683     }
684     catch (std::exception& e) {
685         if (show_progress)
686             progress.setValue(1000);
687         throw;
688     }
689     if (show_progress)
690         progress.setValue(1000);
691     _anim_widget->stop();
692     _sim_timer->start(0);
693 }
694
695 void MainWindow::file_export_frame()
696 {
697     QString dirname = QFileDialog::getExistingDirectory(this, "Export directory",
698             _settings->value("Session/directory", QDir::currentPath()).toString());
699     if (dirname.isEmpty())
700         return;
701     _settings->setValue("Session/directory", QFileInfo(dirname).path());
702     QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
703     try {
704         export_frame(dirname.toLocal8Bit().constData());
705     }
706     catch (std::exception& e) {
707         QApplication::restoreOverrideCursor();
708         QMessageBox::critical(this, "Error", e.what());
709     }
710     QApplication::restoreOverrideCursor();
711 }
712
713 void MainWindow::file_export_anim()
714 {
715     if (!_animation.is_valid() || _anim_widget->state() == AnimWidget::state_disabled) {
716         QMessageBox::critical(this, "Error", "Animation is not activated.");
717         return;
718     }
719     QString dirname = QFileDialog::getExistingDirectory(this, "Export directory",
720             _settings->value("Session/directory", QDir::currentPath()).toString());
721     if (dirname.isEmpty())
722         return;
723     _settings->setValue("Session/directory", QFileInfo(dirname).path());
724     try {
725         export_animation(dirname.toLocal8Bit().constData());
726     }
727     catch (std::exception& e) {
728         QMessageBox::critical(this, "Error", e.what());
729     }
730 }
731
732 void MainWindow::simulator_load()
733 {
734     QString filename = QFileDialog::getOpenFileName(this, "Load simulator",
735             _settings->value("Session/directory", QDir::currentPath()).toString(),
736             tr("Simulator descriptions (*.txt)"));
737     if (filename.isEmpty())
738         return;
739     _settings->setValue("Session/directory", QFileInfo(filename).path());
740     try {
741         _simulator.load(filename.toLocal8Bit().constData());
742     }
743     catch (std::exception& e) {
744         QMessageBox::critical(this, "Error", e.what());
745         return;
746     }
747     emit update_simulator(_simulator);
748     _settings->setValue("Session/simulator", filename);
749 }
750
751 void MainWindow::simulator_save()
752 {
753     QString filename = QFileDialog::getSaveFileName(this, "Save simulator",
754             _settings->value("Session/directory", QDir::currentPath()).toString(),
755             tr("Simulator descriptions (*.txt)"));
756     if (filename.isEmpty())
757         return;
758     _settings->setValue("Session/directory", QFileInfo(filename).path());
759     try {
760         _simulator.save(filename.toLocal8Bit().constData());
761     }
762     catch (std::exception& e) {
763         QMessageBox::critical(this, "Error", e.what());
764         return;
765     }
766     _settings->setValue("Session/simulator", filename);
767 }
768
769 class OddSpinBox : public QSpinBox
770 {
771 public:
772     OddSpinBox(QWidget* parent = NULL) : QSpinBox(parent)
773     {
774         setSingleStep(2);
775     }
776
777     QValidator::State validate(QString& input, int &) const
778     {
779         int number;
780         bool number_valid;
781         number = input.toInt(&number_valid);
782         if (number_valid && number >= minimum() && number <= maximum() && number % 2 == 1)
783             return QValidator::Acceptable;
784         else
785             return QValidator::Invalid;
786     }
787 };
788
789 void MainWindow::simulator_edit()
790 {
791     QDialog* dlg = new QDialog(this);
792     dlg->setWindowTitle("Edit simulator");
793
794     int row = 0;
795     QGridLayout* l0 = new QGridLayout;
796
797     l0->addWidget(new QLabel("<b>Rasterization</b>"), row++, 0);
798
799     l0->addWidget(new QLabel("Aperture angle [deg]:"), row, 0);
800     QDoubleSpinBox* aperture_angle_spinbox = new QDoubleSpinBox;
801     aperture_angle_spinbox->setDecimals(4);
802     aperture_angle_spinbox->setRange(1.0, 179.0);
803     aperture_angle_spinbox->setValue(_simulator.aperture_angle);
804     l0->addWidget(aperture_angle_spinbox, row++, 1);
805     l0->addWidget(new QLabel("Near plane [m]:"), row, 0);
806     QDoubleSpinBox* near_plane_spinbox = new QDoubleSpinBox;
807     near_plane_spinbox->setDecimals(4);
808     near_plane_spinbox->setRange(0.01, 100.0);
809     near_plane_spinbox->setValue(_simulator.near_plane);
810     l0->addWidget(near_plane_spinbox, row++, 1);
811     l0->addWidget(new QLabel("Far plane [m]:"), row, 0);
812     QDoubleSpinBox* far_plane_spinbox = new QDoubleSpinBox;
813     far_plane_spinbox->setDecimals(4);
814     far_plane_spinbox->setRange(0.01, 100.0);
815     far_plane_spinbox->setValue(_simulator.far_plane);
816     l0->addWidget(far_plane_spinbox, row++, 1);
817     l0->addWidget(new QLabel("Exposure time samples:"), row, 0);
818     QSpinBox* exposure_time_samples_spinbox = new QSpinBox;
819     exposure_time_samples_spinbox->setRange(1, 512);
820     exposure_time_samples_spinbox->setValue(_simulator.exposure_time_samples);
821     l0->addWidget(exposure_time_samples_spinbox, row++, 1);
822     l0->addWidget(new QLabel("Rendering method:"), row, 0);
823     QComboBox* rendering_box = new QComboBox;
824     rendering_box->addItem("Default");
825     rendering_box->setCurrentIndex(_simulator.rendering_method);
826     l0->addWidget(rendering_box, row++, 1);
827
828     l0->addWidget(new QLabel("<b>Material</b>"), row++, 0);
829
830     l0->addWidget(new QLabel("Model:"), row, 0);
831     QComboBox* material_model_box = new QComboBox;
832     material_model_box->addItem("Lambertian");
833     material_model_box->setCurrentIndex(_simulator.material_model);
834     l0->addWidget(material_model_box, row++, 1);
835     l0->addWidget(new QLabel("Lambertian material: reflectivity [0,1]:"), row, 0);
836     QDoubleSpinBox* material_lambertian_reflectivity_spinbox = new QDoubleSpinBox;
837     material_lambertian_reflectivity_spinbox->setDecimals(4);
838     material_lambertian_reflectivity_spinbox->setRange(0.0, 1.0);
839     material_lambertian_reflectivity_spinbox->setValue(_simulator.material_lambertian_reflectivity);
840     l0->addWidget(material_lambertian_reflectivity_spinbox, row++, 1);
841
842     l0->addWidget(new QLabel("<b>Light Source</b>"), row++, 0);
843
844     l0->addWidget(new QLabel("Model:"), row, 0);
845     QComboBox* lightsource_model_box = new QComboBox;
846     lightsource_model_box->addItem("Simple");
847     lightsource_model_box->addItem("Measured");
848     lightsource_model_box->setCurrentIndex(_simulator.lightsource_model);
849     l0->addWidget(lightsource_model_box, row++, 1);
850     l0->addWidget(new QLabel("Simple model: power [mW]:"), row, 0);
851     QDoubleSpinBox* lightsource_simple_power_spinbox = new QDoubleSpinBox;
852     lightsource_simple_power_spinbox->setRange(1, 1000);
853     lightsource_simple_power_spinbox->setValue(_simulator.lightsource_simple_power);
854     l0->addWidget(lightsource_simple_power_spinbox, row++, 1);
855     l0->addWidget(new QLabel("Simple model: aperture angle [deg]:"), row, 0);
856     QDoubleSpinBox* lightsource_simple_aperture_angle_spinbox = new QDoubleSpinBox;
857     lightsource_simple_aperture_angle_spinbox->setRange(1, 1000);
858     lightsource_simple_aperture_angle_spinbox->setValue(_simulator.lightsource_simple_aperture_angle);
859     l0->addWidget(lightsource_simple_aperture_angle_spinbox, row++, 1);
860     l0->addWidget(new QLabel("Measured model: table file [.gta]:"), row, 0);
861     QGridLayout* l2 = new QGridLayout;
862     QLineEdit* lightsource_measured_intensities = new QLineEdit;
863     lightsource_measured_intensities->setText(_simulator.lightsource_measured_intensities.filename.c_str());
864     l2->addWidget(lightsource_measured_intensities, 0, 0);
865     l2->addWidget(new QLabel(" "), 0, 1);
866     QFileDialog lmidlg(this, "Open lightsource measured intensities",
867             _settings->value("Session/directory", QDir::currentPath()).toString(),
868             tr("Generic Tagged Array files (*.gta)"));
869     QPushButton* lightsource_measured_intensities_btn = new QPushButton("Choose...");
870     connect(lightsource_measured_intensities_btn, SIGNAL(clicked()), &lmidlg, SLOT(exec()));
871     connect(&lmidlg, SIGNAL(fileSelected(const QString&)), lightsource_measured_intensities, SLOT(setText(const QString&)));
872     l2->addWidget(lightsource_measured_intensities_btn, 0, 2);
873     l0->addItem(l2, row++, 1);
874
875     l0->addWidget(new QLabel("<b>Lens</b>"), row++, 0);
876
877     l0->addWidget(new QLabel("Aperture diameter [mm]:"), row, 0);
878     QDoubleSpinBox* lens_aperture_diameter_spinbox = new QDoubleSpinBox;
879     lens_aperture_diameter_spinbox->setRange(1, 1000);
880     lens_aperture_diameter_spinbox->setValue(_simulator.lens_aperture_diameter);
881     l0->addWidget(lens_aperture_diameter_spinbox, row++, 1);
882     l0->addWidget(new QLabel("Focal length [mm]:"), row, 0);
883     QDoubleSpinBox* lens_focal_length_spinbox = new QDoubleSpinBox;
884     lens_focal_length_spinbox->setRange(1, 1000);
885     lens_focal_length_spinbox->setValue(_simulator.lens_focal_length);
886     l0->addWidget(lens_focal_length_spinbox, row++, 1);
887
888     l0->addWidget(new QLabel("<b>Pixels</b>"), row++, 0);
889
890     l0->addWidget(new QLabel("Sensor width [pixels]:"), row, 0);
891     QSpinBox* sensor_width_spinbox = new QSpinBox;
892     sensor_width_spinbox->setRange(2, 1024);
893     sensor_width_spinbox->setValue(_simulator.sensor_width);
894     l0->addWidget(sensor_width_spinbox, row++, 1);
895     l0->addWidget(new QLabel("Sensor height [pixels]:"), row, 0);
896     QSpinBox* sensor_height_spinbox = new QSpinBox;
897     sensor_height_spinbox->setRange(2, 1024);
898     sensor_height_spinbox->setValue(_simulator.sensor_height);
899     l0->addWidget(sensor_height_spinbox, row++, 1);
900     l0->addWidget(new QLabel("Pixel mask x [0-1]:"), row, 0);
901     QDoubleSpinBox* pixel_mask_x_spinbox = new QDoubleSpinBox;
902     pixel_mask_x_spinbox->setDecimals(4);
903     pixel_mask_x_spinbox->setRange(0.0, 1.0);
904     pixel_mask_x_spinbox->setValue(_simulator.pixel_mask_x);
905     l0->addWidget(pixel_mask_x_spinbox, row++, 1);
906     l0->addWidget(new QLabel("Pixel mask y [0-1]:"), row, 0);
907     QDoubleSpinBox* pixel_mask_y_spinbox = new QDoubleSpinBox;
908     pixel_mask_y_spinbox->setDecimals(4);
909     pixel_mask_y_spinbox->setRange(0.0, 1.0);
910     pixel_mask_y_spinbox->setValue(_simulator.pixel_mask_y);
911     l0->addWidget(pixel_mask_y_spinbox, row++, 1);
912     l0->addWidget(new QLabel("Pixel mask width [0-1]:"), row, 0);
913     QDoubleSpinBox* pixel_mask_width_spinbox = new QDoubleSpinBox;
914     pixel_mask_width_spinbox->setDecimals(4);
915     pixel_mask_width_spinbox->setRange(0.0, 1.0);
916     pixel_mask_width_spinbox->setValue(_simulator.pixel_mask_width);
917     l0->addWidget(pixel_mask_width_spinbox, row++, 1);
918     l0->addWidget(new QLabel("Pixel mask height [0-1]:"), row, 0);
919     QDoubleSpinBox* pixel_mask_height_spinbox = new QDoubleSpinBox;
920     pixel_mask_height_spinbox->setDecimals(4);
921     pixel_mask_height_spinbox->setRange(0.0, 1.0);
922     pixel_mask_height_spinbox->setValue(_simulator.pixel_mask_height);
923     l0->addWidget(pixel_mask_height_spinbox, row++, 1);
924     l0->addWidget(new QLabel("Pixel width [subpixels, odd]:"), row, 0);
925     OddSpinBox* pixel_width_spinbox = new OddSpinBox;
926     pixel_width_spinbox->setRange(1, 31);
927     pixel_width_spinbox->setValue(_simulator.pixel_width);
928     l0->addWidget(pixel_width_spinbox, row++, 1);
929     l0->addWidget(new QLabel("Pixel height [subpixels, odd]:"), row, 0);
930     OddSpinBox* pixel_height_spinbox = new OddSpinBox;
931     pixel_height_spinbox->setRange(1, 31);
932     pixel_height_spinbox->setValue(_simulator.pixel_height);
933     l0->addWidget(pixel_height_spinbox, row++, 1);
934     l0->addWidget(new QLabel("Pitch [micrometer]:"), row, 0);
935     QDoubleSpinBox* pixel_pitch_spinbox = new QDoubleSpinBox;
936     pixel_pitch_spinbox->setRange(1, 1000);
937     pixel_pitch_spinbox->setValue(_simulator.pixel_pitch);
938     l0->addWidget(pixel_pitch_spinbox, row++, 1);
939     l0->addWidget(new QLabel("Read-Out time [microseconds]:"), row, 0);
940     QSpinBox* readout_time_spinbox = new QSpinBox;
941     readout_time_spinbox->setRange(1, 50000);
942     readout_time_spinbox->setValue(_simulator.readout_time);
943     l0->addWidget(readout_time_spinbox, row++, 1);
944     l0->addWidget(new QLabel("Contrast (0-1):"), row, 0);
945     QDoubleSpinBox* contrast_spinbox = new QDoubleSpinBox;
946     contrast_spinbox->setDecimals(4);
947     contrast_spinbox->setRange(0.0, 1.0);
948     contrast_spinbox->setValue(_simulator.contrast);
949     l0->addWidget(contrast_spinbox, row++, 1);
950
951     l0->addWidget(new QLabel("<b>User-modifiable parameters</b>"), row++, 0);
952
953     l0->addWidget(new QLabel("Modulation frequency (MHz):"), row, 0);
954     QSpinBox* modulation_frequency_spinbox = new QSpinBox;
955     modulation_frequency_spinbox->setRange(1, 200);
956     modulation_frequency_spinbox->setValue(_simulator.modulation_frequency / (1000 * 1000));
957     l0->addWidget(modulation_frequency_spinbox, row++, 1);
958     l0->addWidget(new QLabel("Exposure time (microseconds):"), row, 0);
959     QSpinBox* exposure_time_spinbox = new QSpinBox;
960     exposure_time_spinbox->setRange(1, 50000);
961     exposure_time_spinbox->setValue(_simulator.exposure_time);
962     l0->addWidget(exposure_time_spinbox, row++, 1);
963
964     QGridLayout* l1 = new QGridLayout;
965     QPushButton* ok_btn = new QPushButton("OK");
966     QPushButton* cancel_btn = new QPushButton("Cancel");
967     connect(ok_btn, SIGNAL(pressed()), dlg, SLOT(accept()));
968     connect(cancel_btn, SIGNAL(pressed()), dlg, SLOT(reject()));
969     l1->addWidget(ok_btn, 0, 0);
970     l1->addWidget(cancel_btn, 0, 1);
971     QGridLayout* layout = new QGridLayout;
972     layout->addLayout(l0, 0, 0);
973     layout->addLayout(l1, 1, 0);
974     dlg->setLayout(layout);
975
976     dlg->exec();
977     if (dlg->result() == QDialog::Accepted) {
978         if (lightsource_model_box->currentIndex() == 1) {
979             try {
980                 _simulator.lightsource_measured_intensities.load(
981                         qPrintable(lightsource_measured_intensities->text()));
982             }
983             catch (std::exception& e) {
984                 QMessageBox::critical(this, "Error", e.what());
985                 return;
986             }
987         }
988         _simulator.aperture_angle = aperture_angle_spinbox->value();
989         _simulator.near_plane = near_plane_spinbox->value();
990         _simulator.far_plane = far_plane_spinbox->value();
991         _simulator.exposure_time_samples = exposure_time_samples_spinbox->value();
992         _simulator.rendering_method = rendering_box->currentIndex();
993         _simulator.material_model = material_model_box->currentIndex();
994         _simulator.material_lambertian_reflectivity = material_lambertian_reflectivity_spinbox->value();
995         _simulator.lightsource_model = lightsource_model_box->currentIndex();
996         _simulator.lightsource_simple_power = lightsource_simple_power_spinbox->value();
997         _simulator.lightsource_simple_aperture_angle = lightsource_simple_aperture_angle_spinbox->value();
998         _simulator.lens_aperture_diameter = lens_aperture_diameter_spinbox->value();
999         _simulator.lens_focal_length = lens_focal_length_spinbox->value();
1000         _simulator.sensor_width = sensor_width_spinbox->value();
1001         _simulator.sensor_height = sensor_height_spinbox->value();
1002         _simulator.pixel_mask_x = pixel_mask_x_spinbox->value();
1003         _simulator.pixel_mask_y = pixel_mask_y_spinbox->value();
1004         _simulator.pixel_mask_width = pixel_mask_width_spinbox->value();
1005         _simulator.pixel_mask_height = pixel_mask_height_spinbox->value();
1006         _simulator.pixel_width = pixel_width_spinbox->value();
1007         _simulator.pixel_height = pixel_height_spinbox->value();
1008         _simulator.pixel_pitch = pixel_pitch_spinbox->value();
1009         _simulator.readout_time = readout_time_spinbox->value();
1010         _simulator.contrast = contrast_spinbox->value();
1011         _simulator.modulation_frequency = modulation_frequency_spinbox->value() * 1000 * 1000;
1012         _simulator.exposure_time = exposure_time_spinbox->value();
1013         emit update_simulator(_simulator);
1014     }
1015     delete dlg;
1016 }
1017
1018 void MainWindow::simulator_export_modelfile()
1019 {
1020     QString filename = QFileDialog::getSaveFileName(this, "Save model file",
1021             _settings->value("Session/directory", QDir::currentPath()).toString(),
1022             tr("Model files (*.obj)"));
1023     if (filename.isEmpty())
1024         return;
1025     _settings->setValue("Session/directory", QFileInfo(filename).path());
1026     try {
1027         _osg_widget->export_frustum(filename.toLocal8Bit().constData());
1028     }
1029     catch (std::exception& e) {
1030         QMessageBox::critical(this, "Error", e.what());
1031         return;
1032     }
1033 }
1034
1035 void MainWindow::simulator_reset()
1036 {
1037     _simulator = Simulator();
1038     _settings->setValue("Session/simulator", QString());
1039     emit update_simulator(_simulator);
1040 }
1041
1042 void MainWindow::background_load()
1043 {
1044     QString filename = QFileDialog::getOpenFileName(this, "Load background",
1045             _settings->value("Session/directory", QDir::currentPath()).toString(),
1046             tr("Target descriptions (*.txt)"));
1047     if (filename.isEmpty())
1048         return;
1049     _settings->setValue("Session/directory", QFileInfo(filename).path());
1050     try {
1051         _background.load(filename.toLocal8Bit().constData());
1052     }
1053     catch (std::exception& e) {
1054         QMessageBox::critical(this, "Error", e.what());
1055         return;
1056     }
1057     emit update_scene(_background, _target);
1058     _settings->setValue("Session/background", filename);
1059 }
1060
1061 void MainWindow::background_save()
1062 {
1063     QString filename = QFileDialog::getSaveFileName(this, "Save background",
1064             _settings->value("Session/directory", QDir::currentPath()).toString(),
1065             tr("Target descriptions (*.txt)"));
1066     if (filename.isEmpty())
1067         return;
1068     _settings->setValue("Session/directory", QFileInfo(filename).path());
1069     try {
1070         _background.save(filename.toLocal8Bit().constData());
1071     }
1072     catch (std::exception& e) {
1073         QMessageBox::critical(this, "Error", e.what());
1074         return;
1075     }
1076     _settings->setValue("Session/background", filename);
1077 }
1078
1079 void MainWindow::background_generate_planar()
1080 {
1081     QDialog* dlg = new QDialog(this);
1082     dlg->setWindowTitle("Generate planar background");
1083     QGridLayout* l0 = new QGridLayout;
1084
1085     l0->addWidget(new QLabel("Width (m):"), 1, 0);
1086     QDoubleSpinBox* width_spinbox = new QDoubleSpinBox;
1087     width_spinbox->setDecimals(4);
1088     width_spinbox->setRange(0.001, 10.0);
1089     width_spinbox->setValue(_background.background_planar_width);
1090     l0->addWidget(width_spinbox, 1, 1);
1091     l0->addWidget(new QLabel("Height (m):"), 2, 0);
1092     QDoubleSpinBox* height_spinbox = new QDoubleSpinBox;
1093     height_spinbox->setDecimals(4);
1094     height_spinbox->setRange(0.001, 10.0);
1095     height_spinbox->setValue(_background.background_planar_height);
1096     l0->addWidget(height_spinbox, 2, 1);
1097     l0->addWidget(new QLabel("Distance (m; 0=disabled):"), 3, 0);
1098     QDoubleSpinBox* dist_spinbox = new QDoubleSpinBox;
1099     dist_spinbox->setDecimals(4);
1100     dist_spinbox->setRange(0.0, 10.0);
1101     dist_spinbox->setValue(_background.background_planar_dist);
1102     l0->addWidget(dist_spinbox, 3, 1);
1103
1104     QGridLayout* l1 = new QGridLayout;
1105     QPushButton* ok_btn = new QPushButton("OK");
1106     QPushButton* cancel_btn = new QPushButton("Cancel");
1107     connect(ok_btn, SIGNAL(pressed()), dlg, SLOT(accept()));
1108     connect(cancel_btn, SIGNAL(pressed()), dlg, SLOT(reject()));
1109     l1->addWidget(ok_btn, 0, 0);
1110     l1->addWidget(cancel_btn, 0, 1);
1111     QGridLayout* layout = new QGridLayout;
1112     layout->addLayout(l0, 0, 0);
1113     layout->addLayout(l1, 1, 0);
1114     dlg->setLayout(layout);
1115
1116     dlg->exec();
1117     if (dlg->result() == QDialog::Accepted) {
1118         _background.variant = Target::variant_background_planar;
1119         _background.background_planar_width = width_spinbox->value();
1120         _background.background_planar_height = height_spinbox->value();
1121         _background.background_planar_dist = dist_spinbox->value();
1122         emit update_scene(_background, _target);
1123     }
1124     delete dlg;
1125 }
1126
1127 void MainWindow::background_export_modelfile()
1128 {
1129     QString filename = QFileDialog::getSaveFileName(this, "Save model file",
1130             _settings->value("Session/directory", QDir::currentPath()).toString(),
1131             tr("Model files (*.obj)"));
1132     if (filename.isEmpty())
1133         return;
1134     _settings->setValue("Session/directory", QFileInfo(filename).path());
1135     try {
1136         _osg_widget->export_background(filename.toLocal8Bit().constData());
1137     }
1138     catch (std::exception& e) {
1139         QMessageBox::critical(this, "Error", e.what());
1140         return;
1141     }
1142 }
1143
1144 void MainWindow::background_reset()
1145 {
1146     _background = Target(Target::variant_background_planar);
1147     _settings->setValue("Session/background", QString());
1148     emit update_scene(_background, _target);
1149 }
1150
1151 void MainWindow::target_load()
1152 {
1153     QString filename = QFileDialog::getOpenFileName(this, "Load target",
1154             _settings->value("Session/directory", QDir::currentPath()).toString(),
1155             tr("Target descriptions (*.txt)"));
1156     if (filename.isEmpty())
1157         return;
1158     _settings->setValue("Session/directory", QFileInfo(filename).path());
1159     try {
1160         _target.load(filename.toLocal8Bit().constData());
1161     }
1162     catch (std::exception& e) {
1163         QMessageBox::critical(this, "Error", e.what());
1164         return;
1165     }
1166     emit update_scene(_background, _target);
1167     _settings->setValue("Session/target", filename);
1168 }
1169
1170 void MainWindow::target_save()
1171 {
1172     QString filename = QFileDialog::getSaveFileName(this, "Save target",
1173             _settings->value("Session/directory", QDir::currentPath()).toString(),
1174             tr("Target descriptions (*.txt)"));
1175     if (filename.isEmpty())
1176         return;
1177     _settings->setValue("Session/directory", QFileInfo(filename).path());
1178     try {
1179         _target.save(filename.toLocal8Bit().constData());
1180     }
1181     catch (std::exception& e) {
1182         QMessageBox::critical(this, "Error", e.what());
1183         return;
1184     }
1185     _settings->setValue("Session/target", filename);
1186 }
1187
1188 void MainWindow::target_use_modelfile()
1189 {
1190     QString filename = QFileDialog::getOpenFileName(this, "Read model file",
1191             _settings->value("Session/directory", QDir::currentPath()).toString(),
1192             tr("Target models (*.obj *.ply)"));
1193     if (filename.isEmpty())
1194         return;
1195     _settings->setValue("Session/directory", QFileInfo(filename).path());
1196     _target.variant = Target::variant_model;
1197     _target.model_filename = filename.toLocal8Bit().constData();
1198     emit update_scene(_background, _target);
1199 }
1200
1201 void MainWindow::target_generate_bar_pattern()
1202 {
1203     QDialog* dlg = new QDialog(this);
1204     dlg->setWindowTitle("Generate bar pattern");
1205
1206     QGridLayout* l0 = new QGridLayout;
1207     l0->addWidget(new QLabel("Number of bars:"), 0, 0);
1208     QSpinBox* number_of_bars_spinbox = new QSpinBox;
1209     number_of_bars_spinbox->setRange(1, 99);
1210     number_of_bars_spinbox->setValue(_target.number_of_bars);
1211     l0->addWidget(number_of_bars_spinbox, 0, 1);
1212     l0->addWidget(new QLabel("First bar width (m):"), 1, 0);
1213     QDoubleSpinBox* first_bar_width_spinbox = new QDoubleSpinBox;
1214     first_bar_width_spinbox->setDecimals(4);
1215     first_bar_width_spinbox->setRange(0.001, 10.0);
1216     first_bar_width_spinbox->setValue(_target.first_bar_width);
1217     l0->addWidget(first_bar_width_spinbox, 1, 1);
1218     l0->addWidget(new QLabel("First bar height (m):"), 2, 0);
1219     QDoubleSpinBox* first_bar_height_spinbox = new QDoubleSpinBox;
1220     first_bar_height_spinbox->setDecimals(4);
1221     first_bar_height_spinbox->setRange(0.001, 10.0);
1222     first_bar_height_spinbox->setValue(_target.first_bar_height);
1223     l0->addWidget(first_bar_height_spinbox, 2, 1);
1224     l0->addWidget(new QLabel("First offset x (m):"), 3, 0);
1225     QDoubleSpinBox* first_offset_x_spinbox = new QDoubleSpinBox;
1226     first_offset_x_spinbox->setDecimals(4);
1227     first_offset_x_spinbox->setRange(-10, 10);
1228     first_offset_x_spinbox->setValue(_target.first_offset_x);
1229     l0->addWidget(first_offset_x_spinbox, 3, 1);
1230     l0->addWidget(new QLabel("First offset y (m):"), 4, 0);
1231     QDoubleSpinBox* first_offset_y_spinbox = new QDoubleSpinBox;
1232     first_offset_y_spinbox->setDecimals(4);
1233     first_offset_y_spinbox->setRange(-10, 10);
1234     first_offset_y_spinbox->setValue(_target.first_offset_y);
1235     l0->addWidget(first_offset_y_spinbox, 4, 1);
1236     l0->addWidget(new QLabel("First offset z (m):"), 5, 0);
1237     QDoubleSpinBox* first_offset_z_spinbox = new QDoubleSpinBox;
1238     first_offset_z_spinbox->setDecimals(4);
1239     first_offset_z_spinbox->setRange(-10, 10);
1240     first_offset_z_spinbox->setValue(_target.first_offset_z);
1241     l0->addWidget(first_offset_z_spinbox, 5, 1);
1242     l0->addWidget(new QLabel("Next bar width factor:"), 6, 0);
1243     QDoubleSpinBox* next_bar_width_factor_spinbox = new QDoubleSpinBox;
1244     next_bar_width_factor_spinbox->setDecimals(4);
1245     next_bar_width_factor_spinbox->setRange(0.1, 10.0);
1246     next_bar_width_factor_spinbox->setValue(_target.next_bar_width_factor);
1247     l0->addWidget(next_bar_width_factor_spinbox, 6, 1);
1248     l0->addWidget(new QLabel("Next bar width offset:"), 7, 0);
1249     QDoubleSpinBox* next_bar_width_offset_spinbox = new QDoubleSpinBox;
1250     next_bar_width_offset_spinbox->setDecimals(4);
1251     next_bar_width_offset_spinbox->setRange(-10, 10);
1252     next_bar_width_offset_spinbox->setValue(_target.next_bar_width_offset);
1253     l0->addWidget(next_bar_width_offset_spinbox, 7, 1);
1254     l0->addWidget(new QLabel("Next bar height factor:"), 8, 0);
1255     QDoubleSpinBox* next_bar_height_factor_spinbox = new QDoubleSpinBox;
1256     next_bar_height_factor_spinbox->setDecimals(4);
1257     next_bar_height_factor_spinbox->setRange(0.1, 10.0);
1258     next_bar_height_factor_spinbox->setValue(_target.next_bar_height_factor);
1259     l0->addWidget(next_bar_height_factor_spinbox, 8, 1);
1260     l0->addWidget(new QLabel("Next bar height offset:"), 9, 0);
1261     QDoubleSpinBox* next_bar_height_offset_spinbox = new QDoubleSpinBox;
1262     next_bar_height_offset_spinbox->setDecimals(4);
1263     next_bar_height_offset_spinbox->setRange(-10, 10);
1264     next_bar_height_offset_spinbox->setValue(_target.next_bar_height_offset);
1265     l0->addWidget(next_bar_height_offset_spinbox, 9, 1);
1266     l0->addWidget(new QLabel("Next offset x factor:"), 10, 0);
1267     QDoubleSpinBox* next_offset_x_factor_spinbox = new QDoubleSpinBox;
1268     next_offset_x_factor_spinbox->setDecimals(4);
1269     next_offset_x_factor_spinbox->setRange(-10, 10);
1270     next_offset_x_factor_spinbox->setValue(_target.next_offset_x_factor);
1271     l0->addWidget(next_offset_x_factor_spinbox, 10, 1);
1272     l0->addWidget(new QLabel("Next offset x offset:"), 11, 0);
1273     QDoubleSpinBox* next_offset_x_offset_spinbox = new QDoubleSpinBox;
1274     next_offset_x_offset_spinbox->setDecimals(4);
1275     next_offset_x_offset_spinbox->setRange(-10, 10);
1276     next_offset_x_offset_spinbox->setValue(_target.next_offset_x_offset);
1277     l0->addWidget(next_offset_x_offset_spinbox, 11, 1);
1278     l0->addWidget(new QLabel("Next offset y factor:"), 12, 0);
1279     QDoubleSpinBox* next_offset_y_factor_spinbox = new QDoubleSpinBox;
1280     next_offset_y_factor_spinbox->setDecimals(4);
1281     next_offset_y_factor_spinbox->setRange(-10, 10);
1282     next_offset_y_factor_spinbox->setValue(_target.next_offset_y_factor);
1283     l0->addWidget(next_offset_y_factor_spinbox, 12, 1);
1284     l0->addWidget(new QLabel("Next offset y offset:"), 13, 0);
1285     QDoubleSpinBox* next_offset_y_offset_spinbox = new QDoubleSpinBox;
1286     next_offset_y_offset_spinbox->setDecimals(4);
1287     next_offset_y_offset_spinbox->setRange(-10, 10);
1288     next_offset_y_offset_spinbox->setValue(_target.next_offset_y_offset);
1289     l0->addWidget(next_offset_y_offset_spinbox, 13, 1);
1290     l0->addWidget(new QLabel("Next offset z factor:"), 14, 0);
1291     QDoubleSpinBox* next_offset_z_factor_spinbox = new QDoubleSpinBox;
1292     next_offset_z_factor_spinbox->setDecimals(4);
1293     next_offset_z_factor_spinbox->setRange(-10, 10);
1294     next_offset_z_factor_spinbox->setValue(_target.next_offset_z_factor);
1295     l0->addWidget(next_offset_z_factor_spinbox, 14, 1);
1296     l0->addWidget(new QLabel("Next offset z offset:"), 15, 0);
1297     QDoubleSpinBox* next_offset_z_offset_spinbox = new QDoubleSpinBox;
1298     next_offset_z_offset_spinbox->setDecimals(4);
1299     next_offset_z_offset_spinbox->setRange(-10, 10);
1300     next_offset_z_offset_spinbox->setValue(_target.next_offset_z_offset);
1301     l0->addWidget(next_offset_z_offset_spinbox, 15, 1);
1302     l0->addWidget(new QLabel("Background orientation:"), 16, 0);
1303     QComboBox* bar_background_near_side_combobox = new QComboBox;
1304     bar_background_near_side_combobox->addItem("Disable background");
1305     bar_background_near_side_combobox->addItem("Left side near, right side far");
1306     bar_background_near_side_combobox->addItem("Top side near, bottom side far");
1307     bar_background_near_side_combobox->addItem("Right side near, left side far");
1308     bar_background_near_side_combobox->addItem("Bottom side near, top side far");
1309     bar_background_near_side_combobox->setCurrentIndex(_target.bar_background_near_side + 1);
1310     l0->addWidget(bar_background_near_side_combobox, 16, 1);
1311     l0->addWidget(new QLabel("Background near side distance to bars (m):"), 17, 0);
1312     QDoubleSpinBox* bar_background_dist_near_spinbox = new QDoubleSpinBox;
1313     bar_background_dist_near_spinbox->setDecimals(4);
1314     bar_background_dist_near_spinbox->setRange(-1, 1);
1315     bar_background_dist_near_spinbox->setValue(_target.bar_background_dist_near);
1316     l0->addWidget(bar_background_dist_near_spinbox, 17, 1);
1317     l0->addWidget(new QLabel("Background far side distance to bars (m):"), 18, 0);
1318     QDoubleSpinBox* bar_background_dist_far_spinbox = new QDoubleSpinBox;
1319     bar_background_dist_far_spinbox->setDecimals(4);
1320     bar_background_dist_far_spinbox->setRange(-1, 1);
1321     bar_background_dist_far_spinbox->setValue(_target.bar_background_dist_far);
1322     l0->addWidget(bar_background_dist_far_spinbox, 18, 1);
1323     l0->addWidget(new QLabel("Rotation around view direction (degrees):"), 19, 0);
1324     QDoubleSpinBox* bar_rotation_spinbox = new QDoubleSpinBox;
1325     bar_rotation_spinbox->setDecimals(4);
1326     bar_rotation_spinbox->setRange(-180.0, +180.0);
1327     bar_rotation_spinbox->setValue(_target.bar_rotation / M_PI * 180.0);
1328     l0->addWidget(bar_rotation_spinbox, 19, 1);
1329
1330     QGridLayout* l1 = new QGridLayout;
1331     QPushButton* ok_btn = new QPushButton("OK");
1332     QPushButton* cancel_btn = new QPushButton("Cancel");
1333     connect(ok_btn, SIGNAL(pressed()), dlg, SLOT(accept()));
1334     connect(cancel_btn, SIGNAL(pressed()), dlg, SLOT(reject()));
1335     l1->addWidget(ok_btn, 0, 0);
1336     l1->addWidget(cancel_btn, 0, 1);
1337     QGridLayout* layout = new QGridLayout;
1338     layout->addLayout(l0, 0, 0);
1339     layout->addLayout(l1, 1, 0);
1340     dlg->setLayout(layout);
1341
1342     dlg->exec();
1343     if (dlg->result() == QDialog::Accepted) {
1344         _target.variant = Target::variant_bars;
1345         _target.number_of_bars = number_of_bars_spinbox->value();
1346         _target.first_bar_width = first_bar_width_spinbox->value();
1347         _target.first_bar_height = first_bar_height_spinbox->value();
1348         _target.first_offset_x = first_offset_x_spinbox->value();
1349         _target.first_offset_y = first_offset_y_spinbox->value();
1350         _target.first_offset_z = first_offset_z_spinbox->value();
1351         _target.next_bar_width_factor = next_bar_width_factor_spinbox->value();
1352         _target.next_bar_width_offset = next_bar_width_offset_spinbox->value();
1353         _target.next_bar_height_factor = next_bar_height_factor_spinbox->value();
1354         _target.next_bar_height_offset = next_bar_height_offset_spinbox->value();
1355         _target.next_offset_x_factor = next_offset_x_factor_spinbox->value();
1356         _target.next_offset_x_offset = next_offset_x_offset_spinbox->value();
1357         _target.next_offset_y_factor = next_offset_y_factor_spinbox->value();
1358         _target.next_offset_y_offset = next_offset_y_offset_spinbox->value();
1359         _target.next_offset_z_factor = next_offset_z_factor_spinbox->value();
1360         _target.next_offset_z_offset = next_offset_z_offset_spinbox->value();
1361         _target.bar_background_near_side = bar_background_near_side_combobox->currentIndex() - 1;
1362         _target.bar_background_dist_near = bar_background_dist_near_spinbox->value();
1363         _target.bar_background_dist_far = bar_background_dist_far_spinbox->value();
1364         _target.bar_rotation = bar_rotation_spinbox->value() / 180.0 * M_PI;
1365         emit update_scene(_background, _target);
1366     }
1367     delete dlg;
1368 }
1369
1370 void MainWindow::target_generate_star_pattern()
1371 {
1372     QDialog* dlg = new QDialog(this);
1373     dlg->setWindowTitle("Generate Siemens star pattern");
1374
1375     QGridLayout* l0 = new QGridLayout;
1376     l0->addWidget(new QLabel("Star spokes:"), 0, 0);
1377     QSpinBox* star_spokes_spinbox = new QSpinBox;
1378     star_spokes_spinbox->setRange(2, 50);
1379     star_spokes_spinbox->setValue(_target.star_spokes);
1380     l0->addWidget(star_spokes_spinbox, 0, 1);
1381     l0->addWidget(new QLabel("Star radius (m):"), 1, 0);
1382     QDoubleSpinBox* star_radius_spinbox = new QDoubleSpinBox;
1383     star_radius_spinbox->setDecimals(4);
1384     star_radius_spinbox->setRange(0.1, 10.0);
1385     star_radius_spinbox->setValue(_target.star_radius);
1386     l0->addWidget(star_radius_spinbox, 1, 1);
1387     l0->addWidget(new QLabel("Background distance at center (m):"), 2, 0);
1388     QDoubleSpinBox* star_background_dist_center_spinbox = new QDoubleSpinBox;
1389     star_background_dist_center_spinbox->setDecimals(4);
1390     star_background_dist_center_spinbox->setRange(0, 10);
1391     star_background_dist_center_spinbox->setValue(_target.star_background_dist_center);
1392     l0->addWidget(star_background_dist_center_spinbox, 2, 1);
1393     l0->addWidget(new QLabel("Background distance at rim (m):"), 3, 0);
1394     QDoubleSpinBox* star_background_dist_rim_spinbox = new QDoubleSpinBox;
1395     star_background_dist_rim_spinbox->setDecimals(4);
1396     star_background_dist_rim_spinbox->setRange(0, 10);
1397     star_background_dist_rim_spinbox->setValue(_target.star_background_dist_rim);
1398     l0->addWidget(star_background_dist_rim_spinbox, 3, 1);
1399
1400     QGridLayout* l1 = new QGridLayout;
1401     QPushButton* ok_btn = new QPushButton("OK");
1402     QPushButton* cancel_btn = new QPushButton("Cancel");
1403     connect(ok_btn, SIGNAL(pressed()), dlg, SLOT(accept()));
1404     connect(cancel_btn, SIGNAL(pressed()), dlg, SLOT(reject()));
1405     l1->addWidget(ok_btn, 0, 0);
1406     l1->addWidget(cancel_btn, 0, 1);
1407     QGridLayout* layout = new QGridLayout;
1408     layout->addLayout(l0, 0, 0);
1409     layout->addLayout(l1, 1, 0);
1410     dlg->setLayout(layout);
1411
1412     dlg->exec();
1413     if (dlg->result() == QDialog::Accepted) {
1414         _target.variant = Target::variant_star;
1415         _target.star_spokes = star_spokes_spinbox->value();
1416         _target.star_radius = star_radius_spinbox->value();
1417         _target.star_background_dist_center = star_background_dist_center_spinbox->value();
1418         _target.star_background_dist_rim = star_background_dist_rim_spinbox->value();
1419         emit update_scene(_background, _target);
1420     }
1421     delete dlg;
1422 }
1423
1424 void MainWindow::target_export_modelfile()
1425 {
1426     QString filename = QFileDialog::getSaveFileName(this, "Save model file",
1427             _settings->value("Session/directory", QDir::currentPath()).toString(),
1428             tr("Model files (*.obj)"));
1429     if (filename.isEmpty())
1430         return;
1431     _settings->setValue("Session/directory", QFileInfo(filename).path());
1432     try {
1433         _osg_widget->export_target(filename.toLocal8Bit().constData());
1434     }
1435     catch (std::exception& e) {
1436         QMessageBox::critical(this, "Error", e.what());
1437         return;
1438     }
1439 }
1440
1441 void MainWindow::target_reset()
1442 {
1443     _target = Target();
1444     _settings->setValue("Session/target", QString());
1445     emit update_scene(_background, _target);
1446 }
1447
1448 void MainWindow::animation_load()
1449 {
1450     QString filename = QFileDialog::getOpenFileName(this, "Load animation",
1451             _settings->value("Session/directory", QDir::currentPath()).toString(),
1452             tr("Animation descriptions (*.txt)"));
1453     if (filename.isEmpty())
1454         return;
1455     _settings->setValue("Session/directory", QFileInfo(filename).path());
1456     try {
1457         _animation.load(filename.toLocal8Bit().constData());
1458     }
1459     catch (std::exception& e) {
1460         QMessageBox::critical(this, "Error", e.what());
1461         return;
1462     }
1463     emit update_animation(_animation);
1464     _settings->setValue("Session/animation", filename);
1465 }
1466
1467 void MainWindow::animation_reset()
1468 {
1469     _animation = Animation();
1470     _settings->setValue("Session/animation", QString());
1471     emit update_animation(_animation);
1472 }
1473
1474 void MainWindow::help_about()
1475 {
1476     QMessageBox::about(this, tr("About PMDSim"), tr(
1477                 "<p>PMDSim version %1</p>"
1478                 "<p>Copyright (C) 2014<br>"
1479                 "   <a href=\"http://www.cg.informatik.uni-siegen.de/\">"
1480                 "   Computer Graphics Group, University of Siegen</a>.<br>"
1481                 "   All rights reserved.<br>"
1482                 "</p>").arg(PROJECT_VERSION));
1483 }