main.cpp 20.5 KB
Newer Older
1
2
#include <array>

3
4
#include <imgui/imgui_glfw.h>

5
6
7
8
9
10
#include <util/files.h>
#include <util/console.h>

#include <core/state.h>
#include <core/audio/sound_source.h>
#include <core/objects/skybox.h>
11
#include <core/res/res.h>
12
13
14
15
16

#include <raytrace/tracer/pathtracer.h>

#include <components/FramerateCounter.h>
#include <components/PlayerController.h>
17
#include "core/res/collada.h"
18
#include "components/Rotator.h"
19

Johannes Braun's avatar
Johannes Braun committed
20
21
#include <util/property.h>

22
23
24
25
26
27
28
29
30
31
32
33
using namespace glare;

enum class RenderMode
{
	eLines = 0,
	eRaw = 1,
	eGBuffer = 2,
	ePathtrace = 3
};


// Default paths for easy change access
34
const fs::path engine_settings_path = files::asset("/preferences/default.xml");
35
const fs::path pathtracer_settings_path = files::asset("/preferences/pathtracer_default.xml");
Johannes Braun's avatar
Johannes Braun committed
36
const fs::path skybox_files_path = files::asset("/textures/ryfjallet/");
37
const fs::path env_audio_path = files::asset("/audio/env.wav");
38
const fs::path startup_scene_path = files::asset("/meshes/scenery/TY_Plane.dae");
39
40
41
42
43

// My little all-enclosing pathtracer pointer
std::unique_ptr<raytrace::Pathtracer> pathtracer = nullptr;

// Some Application states
44
45
fs::path current_scene_root = startup_scene_path.parent_path();
RenderMode render_mode = RenderMode::eGBuffer;
46
47
48
49

// All functions used in here
void keyPress(controls::Key key, controls::KeyMods mods);
void loadScene(const fs::path& path, float scale = 1.f);
50
51
void drawSettingsWindow();
void drawSceneWindow();
52
53
54
55

// Implementation
int main(int argc, char* argv[])
{
56
57
	core::Context::createAsCurrent(engine_settings_path);
	core::Context::current().callbacks().addKeyDownCallback("main_application", keyPress);
58
59

	// Create a skybox from cube map.
Johannes Braun's avatar
Johannes Braun committed
60
	core::Context::current().skybox()->reset(skybox_files_path);
61
62
63
64
65
66

	// Load the pre-determined startup scene
	loadScene(startup_scene_path, 1.f);

	// Make some ambient sound source
	al::listenerf(al::ListenerParam::eGain, 0.02f);
67
	auto audio_buffer = core::global_resources::sounds.get(env_audio_path);
68
	auto environment_source = core::Context::current().graph()->makeComponent<core::SoundSource>(audio_buffer->id());
69
70
71
72
73
74
75
76
	environment_source->setLooping(true);
	environment_source->play();

	// Initialize an empty pathtracer
	pathtracer = std::make_unique<raytrace::Pathtracer>();
	pathtracer->loadSettings(pathtracer_settings_path);

	// For debug purposes, add a frame rate counter to the root node, which will never be deleted/removed on runtime if not explicitly told so.
77
	core::Context::current().graph()->makeComponent<component::FramerateCounter>();
78

79
	core::Context::current().loop([&]()
80
81
82
83
	{
		switch (render_mode)
		{
		case RenderMode::eLines:
84
			core::Context::current().draw(core::Context::DrawMode::eWireframe);
Johannes Braun's avatar
Johannes Braun committed
85
			break;
86
		case RenderMode::eRaw:
87
			core::Context::current().draw(core::Context::DrawMode::eColors);
88
			break;
89
		case RenderMode::eGBuffer:
90
			core::Context::current().draw(core::Context::DrawMode::eGBuffer);
91
			break;
92
		case RenderMode::ePathtrace:
Johannes Braun's avatar
Johannes Braun committed
93
94
			pathtracer->draw();
			break;
95
96
		}

97
98
		drawSceneWindow();
		drawSettingsWindow();
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
	});
}

void keyPress(controls::Key key, controls::KeyMods mods)
{
	switch (key) {
	case controls::Key::e1: render_mode = RenderMode::eLines; break;
	case controls::Key::e2: render_mode = RenderMode::eRaw; break;
	case controls::Key::e3: render_mode = RenderMode::eGBuffer; break;
	case controls::Key::e4: render_mode = RenderMode::ePathtrace; break;
	default: break;
	}
}

void loadScene(const fs::path& path, float scale)
{
	// Ain't no need for the last scene anymore.
116
117
	bool had_player_controller = core::Context::current().graph()->find<component::PlayerController>() != nullptr;
	core::Context::current().graph()->clearChildren();
118
119

	//Initialize some scene graph from an obj file
120
121
	// Don't use resources here, because of duplicating lights.
	if (auto scene = core::Collada().load(path, scale))
122
	{
123
		core::Context::current().graph()->attach(scene);
124
125
126
	}
	else
	{
127
		core::Context::current().destroy();
128
129
130
		console::prompt("Scene could not be loaded: " + path.string() + ". Press [ENTER] to exit.", 1);
	}

131
	std::shared_ptr<core::SceneNode> cam_node = core::Context::current().graph()->find<core::Camera>();
132
133
134
	if (cam_node)
	{
		//If there are any cameras in the collada file, take the first one.
135
		core::Context::current().switchCamera(cam_node->getComponent<core::Camera>());
136
137
138
139
	}
	else
	{
		//Add a camera to render from
Johannes Braun's avatar
Johannes Braun committed
140
		cam_node = std::make_shared<core::SceneNode>("custom_camera");
141
142
		cam_node->addComponent(core::Context::current().camera());
		core::Context::current().graph()->attach(cam_node);
143
144
	}

Johannes Braun's avatar
Johannes Braun committed
145
146
147
	if(!had_player_controller)
		cam_node->makeComponent<component::PlayerController>();
	
148
149
	// Reload Pathtracer if used.
	if (pathtracer && pathtracer->collector())
150
		pathtracer->reload(core::Context::current().graph());
151
152
}

153
void drawSceneWindow()
154
{
155
156
157
158
159
160
161
162
163
	static int current_tab = 0;
	ImGui::BeginTabbed("Scene", std::array<std::string, 2>{"Scene Graph", "Open File"}, current_tab);
	switch (current_tab)
	{
	default:
	case 0:
	{
		ImGui::BeginChild("Graph", ImVec2(0, ImGui::GetContentRegionAvail().y * 0.5f));
		ImGui::BeginSelectableTree("tree_scene_graph");
164
		core::Context::current().graph()->debugGui();
165
166
167
		ImGui::EndSelectableTree();
		ImGui::EndChild();
		ImGui::Separator();
Johannes Braun's avatar
Johannes Braun committed
168
		core::SceneNode* selected_node = reinterpret_cast<core::SceneNode*>(ImGui::GetSelectableTreeData("tree_scene_graph"));
169
170
171
172
		if(selected_node)
		{
			ImGui::BeginChild("Settings For Tree Node");
			static int curr_settings_tab = 0;
173
			ImGui::Tabs(std::array<std::string, 3>{"Settings", "Transformation", "Components"}, curr_settings_tab);
174
175
176
177

			switch (curr_settings_tab)
			{
			default:
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
			case 0: // Settings
				{
					if(ImGui::AntiFeatureButton("Delete", ImVec2(ImGui::GetContentRegionAvailWidth() / 2, 32)))
					{
						ImGui::OpenPopup("del_curr_node");
					}
					ImGui::SameLine();
					if (ImGui::FeatureButton("Add Child Node", ImVec2(ImGui::GetContentRegionAvailWidth(), 32)))
					{
						ImGui::OpenPopup("add_to_curr_node");
					}
					if (ImGui::FeatureButton("Attach Scene", ImVec2(ImGui::GetContentRegionAvailWidth(), 32)))
					{
						ImGui::OpenPopup("attach_scene");
					}
193
194
195
196
					if (ImGui::FeatureButton("Rename Node", ImVec2(ImGui::GetContentRegionAvailWidth(), 32)))
					{
						ImGui::OpenPopup("rn_curr_node");
					}
Johannes Braun's avatar
Johannes Braun committed
197
198
199
200
201
202
203
204
205
206
207
					if (ImGui::AntiFeatureButton("Dissolve", ImVec2(ImGui::GetContentRegionAvailWidth(), 32)) && selected_node)
					{
						auto parent = selected_node->parent();
						while (selected_node->childCount() != 0)
						{
							for (auto&& child : selected_node->iterateChildren())
							{
								parent->attach(child->detachSelf());
							}
						}
						selected_node->destroy();
208
						ImGui::ClearSelectableTreeData("tree_scene_graph");
Johannes Braun's avatar
Johannes Braun committed
209
					}
210
211
212
213
214
215
216
217
					
					if(ImGui::BeginPopup("del_curr_node"))
					{
						ImGui::Text("Do you really want to delete this node?");

						if(ImGui::AntiFeatureButton("Yes"))
						{
							if(selected_node)
Johannes Braun's avatar
Johannes Braun committed
218
								selected_node->destroy();
Johannes Braun's avatar
Johannes Braun committed
219
							selected_node = nullptr;
220
							ImGui::ClearSelectableTreeData("tree_scene_graph");
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
							ImGui::CloseCurrentPopup();
						}
						ImGui::SameLine();
						if(ImGui::Button("No"))
						{
							ImGui::CloseCurrentPopup();
						}
						ImGui::EndPopup();
					}

					if (ImGui::BeginPopup("add_to_curr_node"))
					{
						ImGui::Text("What shall be the name of the new node?");

						static char buf[64] = {};
						ImGui::InputText("Node Name", buf, 128);

						if (ImGui::AntiFeatureButton("Attach!"))
						{
							if (selected_node)
Johannes Braun's avatar
Johannes Braun committed
241
								selected_node->attach(std::make_shared<core::SceneNode>(buf));
242
243
244
245
246
247
248
249
250
251
							ImGui::CloseCurrentPopup();
						}
						ImGui::SameLine();
						if (ImGui::Button("Cancel"))
						{
							ImGui::CloseCurrentPopup();
						}
						ImGui::EndPopup();
					}

252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
					if (ImGui::BeginPopup("rn_curr_node"))
					{
						ImGui::Text("What shall be the name of the new node?");

						static char buf[64] = {};
						ImGui::InputText("Node Name", buf, 128);

						if (ImGui::AntiFeatureButton("Rename!"))
						{
							if (selected_node)
								selected_node->rename(buf);
							ImGui::CloseCurrentPopup();
						}
						ImGui::SameLine();
						if (ImGui::Button("Cancel"))
						{
							ImGui::CloseCurrentPopup();
						}
						ImGui::EndPopup();
					}

273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
					if (ImGui::BeginPopup("attach_scene"))
					{
						static auto root = current_scene_root;
						ImGui::Title(root.string().c_str());
						static float import_scale = 1.f;
						ImGui::DragFloat("Scale", &import_scale, 0.01f, 0.01f, 100.0f);
						ImGui::BeginChild("file_browser", ImVec2(0, 200));

						ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));

						// Go to parent directory
						if (ImGui::Button("..", ImVec2(ImGui::GetContentRegionAvailWidth(), 32)))
							root = root.parent_path();

						for (auto& p : fs::directory_iterator(current_scene_root)) {
							fs::path path = p;

							// Either draw directory button or...
							if (is_directory(path) && ImGui::Button(path.filename().string().c_str(), ImVec2(ImGui::GetContentRegionAvailWidth(), 40)))
								root = path;
							// ... draw a button which loads a dae file.
							else if (path.extension() == ".dae" && ImGui::FeatureButton(path.filename().string().c_str(), ImVec2(ImGui::GetContentRegionAvailWidth(), 32)))
							{
								if (selected_node)
297
								{
298
									selected_node->attach(core::Collada().load(path, import_scale));
299
								}
300
301
302
303
304
305
306
307
308
309
							}
						}
						ImGui::PopStyleVar();
						ImGui::EndChild();

						ImGui::EndPopup();
					}
				}
				break;
			case 1: // Transform
310
311
312
313
314
315
316
				// Pos
				ImGui::DragFloat3("Position", reinterpret_cast<float*>(&(selected_node->transform.position)), 0.01f);
				// Rotation
				ImGui::QuaternionGizmo("Rotation", *reinterpret_cast<ImQuat*>(&selected_node->transform.rotation));
				// Scale
				ImGui::DragFloat3("Scale", reinterpret_cast<float*>(&(selected_node->transform.scale)), 0.01f);
				break;
317
			case 2: // Components (if available)
Johannes Braun's avatar
Johannes Braun committed
318
				if (selected_node)
319
				{
Johannes Braun's avatar
Johannes Braun committed
320
					selected_node->iterateComponentsStable([](core::SceneComponent& component)
321
					{
Johannes Braun's avatar
Johannes Braun committed
322
323
						ImGui::PushID(("component-" + std::to_string(component.id())).c_str());
						if (ImGui::CollapsingHeader(component.typeName().c_str()))
324
						{
Johannes Braun's avatar
Johannes Braun committed
325
326
							component.debugGui();
							if (ImGui::AntiFeatureButton("Delete", ImVec2(ImGui::GetContentRegionAvailWidth(), 24)))
327
							{
Johannes Braun's avatar
Johannes Braun committed
328
								ImGui::OpenPopup("del_comp");
329
							}
Johannes Braun's avatar
Johannes Braun committed
330
331

							if (ImGui::BeginPopup("del_comp"))
332
							{
Johannes Braun's avatar
Johannes Braun committed
333
334
335
336
337
338
339
340
341
342
343
344
345
								ImGui::Text("Do you really want to delete this node?");

								if (ImGui::AntiFeatureButton("Yes"))
								{
									component.destroy();
									ImGui::CloseCurrentPopup();
								}
								ImGui::SameLine();
								if (ImGui::Button("No"))
								{
									ImGui::CloseCurrentPopup();
								}
								ImGui::EndPopup();
346
							}
347
						}
Johannes Braun's avatar
Johannes Braun committed
348
						ImGui::PopID();
349

Johannes Braun's avatar
Johannes Braun committed
350
351
352
						ImGui::Spacing();
					});
				}
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400

				ImGui::Spacing();
				if (ImGui::FeatureButton("Add...", ImVec2(ImGui::GetContentRegionAvailWidth(), 32)))
				{
					ImGui::OpenPopup("add_comp");
				}

				if (ImGui::BeginPopup("add_comp"))
				{
					ImGui::Text("New Component Type?");
					if(ImGui::Button("Light", ImVec2(180, 24)))
					{
						if (selected_node)
							selected_node->makeComponent<core::LightComponent>(color::rgba32f(3), core::LightParameters::makePointLight({1, 0.1f, 0.01f}, 1.f));
					}

					if (ImGui::Button("Camera", ImVec2(180, 24)))
					{
						if (selected_node)
							selected_node->makeComponent<core::Camera>();
					}

					ImGui::Separator();

					if (ImGui::Button("Player Controller", ImVec2(180, 24)))
					{
						if (selected_node)
							selected_node->makeComponent<component::PlayerController>();
					}

					if (ImGui::Button("Rotator", ImVec2(180, 24)))
					{
						if (selected_node)
							selected_node->makeComponent<component::Rotator>();
					}

					if (ImGui::Button("Framerate Counter", ImVec2(180, 24)))
					{
						if (selected_node)
							selected_node->makeComponent<component::FramerateCounter>();
					}

					if (ImGui::Button("Cancel"))
					{
						ImGui::CloseCurrentPopup();
					}
					ImGui::EndPopup();
				}
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
				break;
			}
			ImGui::EndTabs();
			ImGui::EndChild();
		}
	}	break;

	case 1:
	{
		//Scene selection Window
		ImGui::Title(current_scene_root.string().c_str());
		static float import_scale = 1.f;
		ImGui::DragFloat("Scale", &import_scale, 0.01f, 0.01f, 100.0f);
		ImGui::BeginChild("file_browser");

		ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));

		// Go to parent directory
		if (ImGui::Button("..", ImVec2(ImGui::GetContentRegionAvailWidth(), 32)))
			current_scene_root = current_scene_root.parent_path();

		for (auto& p : fs::directory_iterator(current_scene_root)) {
			fs::path path = p;

			// Either draw directory button or...
			if (is_directory(path) && ImGui::Button(path.filename().string().c_str(), ImVec2(ImGui::GetContentRegionAvailWidth(), 40)))
				current_scene_root = path;
			// ... draw a button which loads a dae file.
			else if (path.extension() == ".dae" && ImGui::FeatureButton(path.filename().string().c_str(), ImVec2(ImGui::GetContentRegionAvailWidth(), 32)))
				loadScene(path, import_scale);
		}
		ImGui::PopStyleVar();
		ImGui::EndChild();
	} break;
	}
436

437
438
	ImGui::EndTabbed();
}
439

440
441
442
443
444
void drawSettingsWindow()
{
	// Debug Window
	static int current_tab = static_cast<int>(render_mode);
	if(ImGui::BeginTabbed("Settings", std::array<std::string, 5> { "Wireframe", "Raw", "GBuffer", "Pathtracing", "General" }, current_tab) && current_tab != 4)
445
	{
446
		render_mode = static_cast<RenderMode>(current_tab);
447
448
	}

449
	if (current_tab == 3)
450
451
452
453
454
	{
		if (pathtracer->collector()) {
			ImGui::Title("Save Render as...");
			if (ImGui::FeatureButton("32bit HDR", ImVec2(ImGui::GetContentRegionAvailWidth() / 2, 0)))
				pathtracer->saveRender(files::asset("/screenshots/render.hdr"));
455

456
			ImGui::Columns();
457

458
			ImGui::SameLine();
459

460
461
			if (ImGui::FeatureButton("8bit PNG", ImVec2(ImGui::GetContentRegionAvailWidth(), 0)))
				pathtracer->saveRender(files::asset("/screenshots/render.png"));
462
463
464

			ImGui::Spacing();

465
466
			std::array<std::string, 3> names{ "Scene Settings", "Sampling and Performance" , "Linespace Thresholds" };
			static int curr = 0;
467
			ImGui::Tabs(names, curr);
468
469
470
471
472
473

			switch (curr)
			{
			case 0:
			{
				ImGui::TextWrapped("By default, a transformation or mesh update will trigger a pathtracer sample count reset. If you want to limit this to explicit settings changes and camera movements, uncheck the following option.");
Johannes Braun's avatar
Johannes Braun committed
474

475
476
477
				bool collector_active = pathtracer->collector()->isActive();
				if (ImGui::Checkbox("Reset on scene update", &collector_active))
					pathtracer->collector()->setActive(collector_active);
478
479
480

				ImGui::Spacing();
				auto format = static_cast<int>(pathtracer->collector()->getSceneFormat());
Johannes Braun's avatar
Johannes Braun committed
481
				if (ImGui::Combo("", &format, { "Default", "Global BVH" }) && format != static_cast<int>(pathtracer->collector()->getSceneFormat()))
482
483
484
				{
					pathtracer->collector()->setSceneFormat(static_cast<raytrace::SceneFormat>(format));
				}
485
486
487
			}
			break;
			case 1:
488
			{
489
490
				ImGui::BeginNamedGroup("Ray Generator");
				{
491
					int raygenstate = static_cast<int>(pathtracer->getRayGenerator());
492
493
					ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth());
					if (ImGui::Combo("", &raygenstate, { "Trace [default]", "OpenGL GBuffer" })) {
494
						pathtracer->setRayGenerator(static_cast<raytrace::RayGenType>(raygenstate));
495
496
497
498
					}
					ImGui::PopItemWidth();
				}
				ImGui::EndNamedGroup();
499

500
				ImGui::Spacing();
501

502
503
504
				ImGui::BeginNamedGroup("Samples");
				{
					ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth());
505
506
					ImGui::ProgressBar(pathtracer->getSamplesCurrent() / static_cast<float>(pathtracer->getSamplesMax()), ImVec2(-1, 0),
						("Current: " + std::to_string(pathtracer->getSamplesCurrent()) + " of " + std::to_string(pathtracer->getSamplesMax())).c_str());
507
508
509
					ImGui::PopItemWidth();

					ImGui::Spacing();
510
					int max_samples = pathtracer->getSamplesMax();
Johannes Braun's avatar
Johannes Braun committed
511
					if (ImGui::DragInt("Maximum", &max_samples, 10, 1, 100000))
512
						pathtracer->setSamplesMax(max_samples);
513
514
515
516
517
518

					int spfr = pathtracer->getSamplesPerFrame();
					if (ImGui::DragInt("per frame", &spfr, 0.1f, 1, 10))
						pathtracer->setSamplesPerFrame(spfr);
				}
				ImGui::EndNamedGroup();
519

520
				ImGui::Spacing();
521

522
523
				ImGui::BeginNamedGroup("Bounces");
				ImGui::TextWrapped("The global bounce limit clamps down all effect-dependant bounce limits to a unified maximum value.");
524
				{
525
					int bounces = pathtracer->getBounceCount();
Johannes Braun's avatar
Johannes Braun committed
526
					if (ImGui::DragInt("Global Limit", &bounces, 0.1f, 0, 255))
527
						pathtracer->setBounceCount(bounces);
528

529
530
531
532
533
					ImGui::Spacing();

					if (ImGui::TreeNode("Bounce Thresholds"))
					{
						// Let's keep this function locally near to the only place it's being used.
534
						static auto effectBounces = [](const std::string &label, raytrace::Effect effect)
535
						{
536
							int val = pathtracer->getBounceThreshold(effect);
537
							if (ImGui::DragInt(label.c_str(), &val, 0.1f, 0, 255))
538
								pathtracer->setBounceThreshold(effect, static_cast<uint8_t>(val));
539
540
						};

541
542
543
544
						effectBounces("Diffusion", raytrace::Effect::eDiffusion);
						effectBounces("Reflection", raytrace::Effect::eReflection);
						effectBounces("Transmission", raytrace::Effect::eTransmission);
						effectBounces("Emission", raytrace::Effect::eEmission);
545
546
						ImGui::TreePop();
					}
547
				}
548
				ImGui::EndNamedGroup();
549

550
551
552
553
554
				ImGui::TextWrapped("The direct clamp setting will lead to the primary ray result color being clamped down to a set maximum. That means each color component will be clamped. The same applies to the \"clamp indirect\" setting for non-primary bounces.");
				{
					float clamp_direct = pathtracer->getClampDirect();
					if (ImGui::DragFloat("Clamp Direct", &clamp_direct, 0.1f, 0.0f, 100.f))
						pathtracer->setClampDirect(clamp_direct);
555

556
557
558
559
					float clamp_indirect = pathtracer->getClampIndirect();
					if (ImGui::DragFloat("Clamp Indirect", &clamp_indirect, 0.1f, 0.0f, 100.f))
						pathtracer->setClampIndirect(clamp_indirect);
				}
560
			}
561
562
			break;
			case 2:
563
			{
564
565
566
				ImGui::Title("PDF Thresholds");
				ImGui::TextWrapped("The following threshold values will determine as of which accumulated PDF value the Line Space should be used instead of the BVH.");
				{
567
					float acc = pathtracer->getLinespaceAccuracy();
568
					if (ImGui::DragFloat("Direct", &acc, 0.001f, 0.0f, 1.f))
569
						pathtracer->setLinespaceAccuracy(acc);
570

571
					float shd = pathtracer->getLinespaceShadow();
572
					if (ImGui::DragFloat("Shadow", &shd, 0.001f, 0.0f, 1.f))
573
						pathtracer->setLinespaceShadow(shd);
574
				}
575

576
577
578
				ImGui::Title("Distance Threshold");
				ImGui::TextWrapped("Works like a level of detail slider. This is the maximum path length with which the BVH should be used.");
				{
579
					float dt = pathtracer->getLinespaceDistance();
580
					if (ImGui::DragFloat("Distance", &dt, 0.1f, 0.0f, 10000.f))
581
						pathtracer->setLinespaceDistance(dt);
582
				}
583

584
585
				ImGui::Title("Bounce Thresholds");
				ImGui::TextWrapped("The following threshold values will determine as of which bounce the Line Space should be used instead of the BVH. This can vary for every kind of effect.");
586
				{
587
					// Let's keep this function locally near to the only place it's being used.
588
					static auto bounceControl = [](const std::string &label, raytrace::Effect effect)
589
					{
590
591
592
						int val = pathtracer->getLinespaceBounces(effect);
						if (ImGui::DragInt(label.c_str(), &val, 0.1f, 0, pathtracer->getLinespaceBounces(effect)))
							pathtracer->setLinespaceBounces(effect, static_cast<uint8_t>(val));
593
594
					};

595
596
597
598
					bounceControl("Diffusion", raytrace::Effect::eDiffusion);
					bounceControl("Reflection", raytrace::Effect::eReflection);
					bounceControl("Transmission", raytrace::Effect::eTransmission);
					bounceControl("Emission", raytrace::Effect::eEmission);
599
				}
600
			}
601
602
603
604
605
606
607
608
			break;
			default: break;
			}
			
			ImGui::EndTabs();
		}
		else if (ImGui::FeatureButton("Initialize Pathtracer", ImVec2(ImGui::GetContentRegionAvailWidth(), 0)))
		{
609
			pathtracer->reload(core::Context::current().graph());
610
611
		}
	}
612
	else if (current_tab == 4)
613
	{
614
615
		ImGui::Title("General Settings");

616
		auto vsync = static_cast<int>(core::Context::current().window().getVSync());
617
		if (ImGui::Combo("VSync", &vsync, { "None", "60 FPS", "30 FPS", "20 FPS" }))
618
			core::Context::current().window().setVSync(static_cast<VSync>(vsync));
619
620
621

		ImGui::Spacing();

622
623
624
625
626
627
628
629
630
631
632
		ImGui::Title("Controls");
		ImGui::TitledDescription("Right Click", "Grab cursor, enables looking around. Right click again to release it.");
		ImGui::TitledDescription("W/S", "Fly along the looking direction.");
		ImGui::TitledDescription("A/D", "Fly along the horizontal perpendiculat to the looking direction.");
		ImGui::TitledDescription("E", "Fly towards the relative up-direction.");
		ImGui::TitledDescription("Q", "Fly towards the relative down-direction.");
		ImGui::TitledDescription("1, 2, 3", "Toggle rendering mode.");
	}
	else
	{
		ImGui::Text("Nothing to set up...");
633
634
	}

635
	ImGui::EndTabbed();
636
}