UFO 1.0.0
An Efficient Probabilistic 3D Mapping Framework That Embraces the Unknown
Loading...
Searching...
No Matches
viz.cpp
1
42// UFO
43#include <ufo/glfw_webgpu/glfw_surface.h>
44
45#include <ufo/viz/viz.hpp>
46
47// STL
48#include <algorithm>
49#include <cassert>
50#include <print>
51#include <stdexcept>
52
53// GLFW
54#include <GLFW/glfw3.h>
55#ifndef __EMSCRIPTEN__
56#include <GLFW/glfw3native.h>
57#endif
58
59// ImGui
60#include <backends/imgui_impl_glfw.h>
61#include <backends/imgui_impl_wgpu.h>
62#include <imgui.h>
63
64// EMSCRIPTEN
65#ifdef __EMSCRIPTEN__
66#include <emscripten.h>
67#include <emscripten/html5.h>
68#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU)
69#include <emscripten/html5_webgpu.h>
70#endif
71#include "../libs/emscripten/emscripten_mainloop_stub.h"
72#endif
73
74namespace ufo
75{
76Viz::Viz() {}
77
78Viz::~Viz() { close(); }
79
80bool Viz::running() const
81{
82 return nullptr != window_ && !glfwWindowShouldClose(window_);
83}
84
85void Viz::run()
86{
87#if defined(__EMSCRIPTEN__)
88 // For an Emscripten build we are disabling file-system access, so let's not attempt to
89 // do a fopen() of the imgui.ini file. You may manually call LoadIniSettingsFromMemory()
90 // to load settings from your own storage.
91 io.IniFilename = nullptr;
92
93 auto callback = [](void* arg) {
94 Viz* v = static_cast<Viz*>(arg);
95 v->update();
96 };
97 emscripten_set_main_loop_arg(callback, this, 0, true);
98#else
99 while (running()) {
100 update();
101 }
102#endif
103}
104
105void Viz::runAsync() { render_thread_ = std::thread(&Viz::run, this); }
106
107bool Viz::open(int width, int height, bool resizable, std::string const& window_name,
108 WGPUPowerPreference power_preference, WGPUBackendType backend_type)
109{
110 if (running()) {
111 return false;
112 }
113
114 if (!initWindow(width, height, resizable, window_name)) {
115 std::println(stderr, "[UFO | Viz] Failed to initialize window");
116 return false;
117 }
118
119 if (!initWGPU(power_preference, backend_type)) {
120 std::println(stderr, "[UFO | Viz] Failed to initialize WebGPU");
121 glfwDestroyWindow(window_);
122 glfwTerminate();
123 return false;
124 }
125
126 glfwShowWindow(window_);
127
128 if (!initGUI()) {
129 std::println(stderr, "[UFO | Viz] Failed to initialize GUI");
130 // TODO: Clean up
131 return false;
132 }
133
134 if (!initCamera()) {
135 std::println(stderr, "[UFO | Viz] Failed to initialize camera");
136 // TODO: Clean up
137 return false;
138 }
139
140 return true;
141}
142
143void Viz::close()
144{
145 if (!running()) {
146 return;
147 }
148
149 if (render_thread_.joinable()) {
150 render_thread_.join();
151 }
152
153 for (auto& renderable : renderables_) {
154 renderable->release();
155 }
156
157 // ImGUI
158
159 ImGui_ImplWGPU_Shutdown();
160 ImGui_ImplGlfw_Shutdown();
161 ImGui::DestroyContext();
162
163 // WGPU & GLFW
164
165 compute::release(depth_view_);
166 compute::release(depth_texture_);
167 compute::release(queue_);
168 compute::release(device_);
169 compute::release(adapter_);
170 wgpuSurfaceUnconfigure(surface_); // FIXME: Needed?
171 compute::release(surface_);
172 glfwDestroyWindow(window_);
173 compute::release(instance_);
174 glfwTerminate();
175
176 // GLFW
177
178 glfwDestroyWindow(window_);
179 glfwTerminate();
180
181 window_ = nullptr;
182
183 instance_ = nullptr;
184 surface_ = nullptr;
185 adapter_ = nullptr;
186 device_ = nullptr;
187 queue_ = nullptr;
188 depth_texture_ = nullptr;
189 depth_view_ = nullptr;
190}
191
192void Viz::resizeSurface(int width, int height)
193{
194 // scale_ = ImGui_ImplGlfw_GetContentScaleForWindow(window_);
195
196 surface_width_ = width * scale_;
197 surface_height_ = height * scale_;
198
199 surface_config_.width = surface_width_;
200 surface_config_.height = surface_height_;
201
202 wgpuSurfaceConfigure(surface_, &surface_config_);
203
204 if (nullptr != depth_view_) {
205 compute::release(depth_view_);
206 depth_view_ = nullptr;
207 }
208 if (nullptr != depth_texture_) {
209 compute::release(depth_texture_);
210 depth_texture_ = nullptr;
211 }
212
213 if (WGPUTextureFormat_Undefined != depth_texture_format_) {
214 WGPUTextureDescriptor depth_texture_desc = WGPU_TEXTURE_DESCRIPTOR_INIT;
215 depth_texture_desc.label = {"[UFO | Viz] Z Buffer", WGPU_STRLEN};
216 depth_texture_desc.usage = WGPUTextureUsage_RenderAttachment;
217 depth_texture_desc.size.width = surface_width_;
218 depth_texture_desc.size.height = surface_height_;
219 depth_texture_desc.size.depthOrArrayLayers = 1;
220 depth_texture_desc.format = depth_texture_format_;
221
222 depth_texture_ = wgpuDeviceCreateTexture(device_, &depth_texture_desc);
223
224 WGPUTextureViewDescriptor depth_texture_view_desc = WGPU_TEXTURE_VIEW_DESCRIPTOR_INIT;
225 depth_texture_view_desc.label = {"[UFO | Viz] Z Buffer View", WGPU_STRLEN};
226 depth_texture_view_desc.format = wgpuTextureGetFormat(depth_texture_);
227 depth_texture_view_desc.dimension = WGPUTextureViewDimension_2D;
228 depth_texture_view_desc.baseMipLevel = 0;
229 depth_texture_view_desc.mipLevelCount = 1;
230 depth_texture_view_desc.baseArrayLayer = 0;
231 depth_texture_view_desc.arrayLayerCount = 1;
232 depth_texture_view_desc.aspect = WGPUTextureAspect_All;
233 depth_texture_view_desc.usage = WGPUTextureUsage_RenderAttachment;
234
235 depth_view_ = wgpuTextureCreateView(depth_texture_, &depth_texture_view_desc);
236 }
237}
238
239void Viz::update()
240{
241 float cur_time = glfwGetTime();
242 float dt = cur_time - prev_time_;
243 prev_time_ = cur_time;
244
245 glfwPollEvents();
246
247 wgpuInstanceProcessEvents(instance_);
248
249 if (0 != glfwGetWindowAttrib(window_, GLFW_ICONIFIED)) {
250 ImGui_ImplGlfw_Sleep(10);
251 return;
252 }
253
254 // React to changes in screen size
255 int width, height;
256 glfwGetFramebufferSize(window_, &width, &height);
257 if (width != surface_width_ || height != surface_height_) {
258 // ImGui_ImplWGPU_InvalidateDeviceObjects();
259 resizeSurface(width, height);
260 // ImGui_ImplWGPU_CreateDeviceObjects();
261 }
262
263 WGPUSurfaceTexture surface_texture;
264 wgpuSurfaceGetCurrentTexture(surface_, &surface_texture);
265 if (ImGui_ImplWGPU_IsSurfaceStatusError(surface_texture.status)) {
266 std::println(stderr, "[UFO | Viz] Unrecoverable Surface Texture status");
267 // TODO: Implement
268 // std::println(stderr, "[UFO | Viz] Unrecoverable Surface Texture status={}",
269 // surface_texture.status);
270 abort(); // TODO: What to do here?
271 }
272 if (ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(surface_texture.status)) {
273 if (nullptr != surface_texture.texture) {
274 wgpuTextureRelease(surface_texture.texture);
275 }
276
277 if (0 < width && 0 < height) {
278 // ImGui_ImplWGPU_InvalidateDeviceObjects();
279 resizeSurface(width, height);
280 // ImGui_ImplWGPU_CreateDeviceObjects();
281 }
282
283 return;
284 }
285
286 updateGui();
287
288 // TODO: For some reason the surface texture size can be different from the imgui
289 // display size
290
291 ImGuiIO& io = ImGui::GetIO();
292 // std::println("Framebuffer size: {}x{}", width, height);
293 // std::println("Display size: {}x{}", io.DisplaySize.x, io.DisplaySize.y);
294 // std::println("Display framebuffer scale: {}x{}", io.DisplayFramebufferScale.x,
295 // io.DisplayFramebufferScale.y);
296 // std::print("\n");
297
298 if (width != io.DisplaySize.x || height != io.DisplaySize.y) {
299 wgpuTextureRelease(surface_texture.texture);
300 return;
301 }
302
303 updateCamera(dt);
304
305 WGPUTextureViewDescriptor view_desc = WGPU_TEXTURE_VIEW_DESCRIPTOR_INIT;
306 view_desc.label = {"[UFO | Viz] Surface Texture View", WGPU_STRLEN};
307 view_desc.format = surface_config_.format;
308 view_desc.dimension = WGPUTextureViewDimension_2D;
309 view_desc.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED;
310 view_desc.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED;
311 view_desc.aspect = WGPUTextureAspect_All;
312 view_desc.usage = WGPUTextureUsage_RenderAttachment;
313
314 WGPUTextureView texture_view =
315 wgpuTextureCreateView(surface_texture.texture, &view_desc);
316 assert(nullptr != texture_view);
317
318 WGPURenderPassColorAttachment color_attachments =
319 WGPU_RENDER_PASS_COLOR_ATTACHMENT_INIT;
320 color_attachments.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
321 color_attachments.loadOp = WGPULoadOp_Clear;
322 color_attachments.storeOp = WGPUStoreOp_Store;
323 color_attachments.clearValue = WGPUColor{
324 clear_color_.red * clear_color_.alpha, clear_color_.green * clear_color_.alpha,
325 clear_color_.blue * clear_color_.alpha, clear_color_.alpha};
326 color_attachments.view = texture_view;
327
328 WGPURenderPassDepthStencilAttachment depth_attachment =
329 WGPU_RENDER_PASS_DEPTH_STENCIL_ATTACHMENT_INIT;
330 depth_attachment.depthLoadOp = WGPULoadOp_Clear;
331 depth_attachment.depthStoreOp = WGPUStoreOp_Store;
332 depth_attachment.depthClearValue = 1.0;
333 depth_attachment.depthReadOnly = false;
334 depth_attachment.view = depth_view_;
335
336 WGPURenderPassDescriptor render_pass_desc = WGPU_RENDER_PASS_DESCRIPTOR_INIT;
337 render_pass_desc.label = {"[UFO | Viz] Render Pass", WGPU_STRLEN};
338 render_pass_desc.colorAttachmentCount = 1;
339 render_pass_desc.colorAttachments = &color_attachments;
340 render_pass_desc.depthStencilAttachment = &depth_attachment;
341
342 WGPUCommandEncoderDescriptor encoder_desc = WGPU_COMMAND_ENCODER_DESCRIPTOR_INIT;
343 encoder_desc.label = {"[UFO | Viz] Command Encoder", WGPU_STRLEN};
344
345 WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device_, &encoder_desc);
346
347 WGPURenderPassEncoder pass =
348 wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc);
349
350 ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass);
351
352 for (auto& renderable : renderables_) {
353 renderable->update(device_, encoder, pass, camera_);
354 }
355
356 wgpuRenderPassEncoderEnd(pass);
357
358 WGPUCommandBufferDescriptor command_buffer_desc = WGPU_COMMAND_BUFFER_DESCRIPTOR_INIT;
359 command_buffer_desc.label = {"[UFO | Viz] Command Buffer", WGPU_STRLEN};
360 WGPUCommandBuffer command_buffer =
361 wgpuCommandEncoderFinish(encoder, &command_buffer_desc);
362
363 wgpuQueueSubmit(queue_, 1, &command_buffer);
364
365#ifndef __EMSCRIPTEN__
366 wgpuSurfacePresent(surface_);
367#endif
368
369 compute::release(command_buffer);
370 compute::release(pass);
371 compute::release(encoder);
372 compute::release(texture_view);
373 compute::release(surface_texture.texture);
374}
375
376WGPUDevice Viz::device() const { return device_; }
377
378void Viz::addRenderable(std::shared_ptr<Renderable> const& renderable)
379{
380 // NOTE: Not thread safe, call before open() or ensure device_ is valid
381
382 renderables_.push_back(renderable);
383
384 if (nullptr == device_) {
385 return;
386 }
387
388 renderable->init(device_, surface_preferred_format_);
389}
390
391void Viz::eraseRenderable(std::shared_ptr<Renderable> const& renderable)
392{
393 // NOTE: Not thread safe, call before open() or ensure device_ is valid
394
395 renderables_.erase(std::remove(renderables_.begin(), renderables_.end(), renderable),
396 renderables_.end());
397}
398
399void Viz::clearRenderables()
400{
401 // NOTE: Not thread safe, call before open() or ensure device_ is valid
402
403 renderables_.clear();
404}
405
406void Viz::loadConfig(std::filesystem::path const& config)
407{
408 // TODO: Implement
409}
410
411void Viz::saveConfig(std::filesystem::path const& file) const
412{
413 // TODO: Implement
414}
415
416WGPULimits Viz::requiredLimits(WGPUAdapter adapter) const
417{
418 WGPULimits required = WGPU_LIMITS_INIT;
419 WGPULimits supported = WGPU_LIMITS_INIT;
420
421 wgpuAdapterGetLimits(adapter, &supported);
422
423 // These two limits are different because they are "minimum" limits,
424 // they are the only ones we may forward from the adapter's supported limits.
425 required.minUniformBufferOffsetAlignment = supported.minUniformBufferOffsetAlignment;
426 required.minStorageBufferOffsetAlignment = supported.minStorageBufferOffsetAlignment;
427
428 // TODO: Update to what is needed
429
430 required.maxBindGroups = 2;
431
432 required.maxBufferSize = 2'147'483'648;
433 required.maxStorageBufferBindingSize = 2'147'483'648;
434
435 required.maxBufferSize = std::min(required.maxBufferSize, supported.maxBufferSize);
436 required.maxStorageBufferBindingSize = std::min(required.maxStorageBufferBindingSize,
437 supported.maxStorageBufferBindingSize);
438
439 required.maxComputeWorkgroupStorageSize = 16352;
440 required.maxComputeInvocationsPerWorkgroup = 256;
441 required.maxComputeWorkgroupSizeX = 256;
442 required.maxComputeWorkgroupSizeY = 256;
443 required.maxComputeWorkgroupSizeZ = 64;
444 required.maxComputeWorkgroupsPerDimension = 65535;
445
446 required.maxUniformBuffersPerShaderStage = 12;
447 required.maxUniformBufferBindingSize = 65536;
448
449 return required;
450}
451
452WGPUTextureFormat Viz::surfaceFormat(WGPUSurfaceCapabilities capabilities) const
453{
454 assert(0 < capabilities.formatCount);
455 return capabilities.formats[0];
456}
457
458void Viz::updateCamera(float dt)
459{
460 // TODO: Implement
461
462 Vec3f speed;
463 float speed_multiplier = 2.0f;
464
465 if (GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_W) ||
466 GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_UP)) {
467 speed.x += translation_speed_;
468 }
469
470 if (GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_S) ||
471 GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_DOWN)) {
472 speed.x -= translation_speed_;
473 }
474
475 if (GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_A) ||
476 GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_LEFT)) {
477 speed.y += translation_speed_;
478 }
479
480 if (GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_D) ||
481 GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_RIGHT)) {
482 speed.y -= translation_speed_;
483 }
484
485 if (GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_SPACE)) {
486 speed.z += translation_speed_;
487 }
488
489 if (GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_LEFT_CONTROL)) {
490 speed.z -= translation_speed_;
491 }
492
493 if (GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_LEFT_SHIFT)) {
494 speed *= speed_multiplier;
495 }
496
497 camera_.pose.translation += camera_.pose.rotation * speed * dt;
498
499 // float cx = std::cos(angles_.x);
500 // float cy = std::cos(angles_.y);
501 // float sx = std::sin(angles_.x);
502 // float sy = std::sin(angles_.y);
503 // ufo::Vec3f offset = ufo::Vec3f(cx * cy, sx * cy, sy) * std::exp(-zoom_);
504 // camera_.pose = static_cast<ufo::Transform3f>(
505 // ufo::lookAt<float, true>(center_ + offset, center_, camera_.up));
506}
507
508bool Viz::initWindow(int width, int height, bool resizable, std::string const& title)
509{
510 std::println("[UFO | Viz] Initializing GLFW window...");
511
512 glfwSetErrorCallback([](int error, char const* description) {
513 std::println(stderr, "[UFO | Viz] GLFW Error ({}): {}", error, description);
514 });
515
516 if (!glfwInit()) {
517 throw std::runtime_error("[UFO | Viz] Failed to initialize GLFW");
518 }
519
520 glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
521 glfwWindowHint(GLFW_RESIZABLE, resizable ? GLFW_TRUE : GLFW_FALSE);
522
523 scale_ = ImGui_ImplGlfw_GetContentScaleForMonitor(glfwGetPrimaryMonitor());
524 surface_width_ = static_cast<std::uint32_t>(width * scale_);
525 surface_height_ = static_cast<std::uint32_t>(height * scale_);
526
527 window_ =
528 glfwCreateWindow(surface_width_, surface_height_, title.c_str(), nullptr, nullptr);
529
530 if (nullptr == window_) {
531 return false;
532 }
533
534 glfwSetWindowUserPointer(window_, static_cast<void*>(this));
535
536 glfwSetKeyCallback(window_,
537 [](GLFWwindow* window, int key, int scancode, int action, int mods) {
538 Viz* v = static_cast<Viz*>(glfwGetWindowUserPointer(window));
539 if (nullptr == v) {
540 return;
541 }
542 v->onKey(key, scancode, action, mods);
543 });
544
545 glfwSetCursorPosCallback(window_, [](GLFWwindow* window, double x_pos, double y_pos) {
546 Viz* v = static_cast<Viz*>(glfwGetWindowUserPointer(window));
547 if (nullptr == v) {
548 return;
549 }
550 v->onMouseMove(x_pos, y_pos);
551 });
552
553 glfwSetMouseButtonCallback(
554 window_, [](GLFWwindow* window, int button, int action, int mods) {
555 Viz* v = static_cast<Viz*>(glfwGetWindowUserPointer(window));
556 if (nullptr == v) {
557 return;
558 }
559 v->onMouseButton(button, action, mods);
560 });
561
562 glfwSetScrollCallback(window_,
563 [](GLFWwindow* window, double x_offset, double y_offset) {
564 Viz* v = static_cast<Viz*>(glfwGetWindowUserPointer(window));
565 if (nullptr == v) {
566 return;
567 }
568 v->onScroll(x_offset, y_offset);
569 });
570
571 return true;
572}
573
574bool Viz::initWGPU(WGPUPowerPreference power_preference, WGPUBackendType backend_type)
575{
576 std::println("[UFO | Viz] Initializing WebGPU...");
577
578 std::println("[UFO | Viz] Creating WGPU instance...");
579 instance_ = compute::createInstance();
580
581#ifdef __EMSCRIPTEN__
582// TODO: Implement
583#else
584 wgpuSetLogCallback(
585 [](WGPULogLevel level, WGPUStringView msg, void* userdata) {
586 std::println(stderr, "[UFO | Viz] WebGPU Log (level={}): {}",
587 ImGui_ImplWGPU_GetLogLevelName(level),
588 std::string(msg.data, msg.length));
589 },
590 nullptr);
591 wgpuSetLogLevel(WGPULogLevel_Warn);
592
593 std::println("[UFO | Viz] Creating WGPU surface...");
594 surface_ = glfwSurface(instance_, window_);
595 if (nullptr == surface_) {
596 std::println(stderr, "[UFO | Viz] Failed to create WGPU surface");
597 return false;
598 }
599
600 std::println("[UFO | Viz] Requesting WGPU adapter...");
601 adapter_ = compute::createAdapter(instance_, surface_, power_preference, backend_type);
602
603 ImGui_ImplWGPU_DebugPrintAdapterInfo(adapter_);
604
605 auto limits = requiredLimits(adapter_);
606 std::println("[UFO | Viz] Creating WGPU device...");
607 device_ = compute::createDevice(adapter_, &limits);
608
609 auto surface_capabilities = compute::surfaceCapabilities(surface_, adapter_);
610 surface_preferred_format_ = surfaceFormat(surface_capabilities);
611 wgpuSurfaceCapabilitiesFreeMembers(surface_capabilities);
612#endif
613
614 surface_config_ = WGPU_SURFACE_CONFIGURATION_INIT;
615 surface_config_.presentMode = WGPUPresentMode_Fifo;
616 surface_config_.alphaMode = WGPUCompositeAlphaMode_Auto;
617 surface_config_.usage = WGPUTextureUsage_RenderAttachment;
618 surface_config_.device = device_;
619 surface_config_.format = surface_preferred_format_;
620
621 resizeSurface(surface_width_, surface_height_);
622
623 queue_ = compute::queue(device_);
624
625 return true;
626}
627
628bool Viz::initGUI()
629{
630 std::println("[UFO | Viz] Initializing ImGui...");
631
632 IMGUI_CHECKVERSION();
633 ImGui::CreateContext();
634 ImGuiIO& io = ImGui::GetIO();
635 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
636 io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
637
638 ImGui::StyleColorsDark();
639 // ImGui::StyleColorsLight();
640
641 ImGuiStyle& style = ImGui::GetStyle();
642 style.ScaleAllSizes(scale_);
643 style.FontScaleDpi = scale_;
644
645 ImGui_ImplGlfw_InitForOther(window_, true);
646#ifdef __EMSCRIPTEN__
647 ImGui_ImplGlfw_InstallEmscriptenCallbacks(window_, "#canvas");
648#endif
649
650 ImGui_ImplWGPU_InitInfo init_info;
651 init_info.Device = device_;
652 init_info.NumFramesInFlight = 3;
653 init_info.RenderTargetFormat = surface_preferred_format_;
654 init_info.DepthStencilFormat = depth_texture_format_;
655 ImGui_ImplWGPU_Init(&init_info);
656
657 // Load Fonts
658 // - If no fonts are loaded, dear imgui will use the default font. You can also load
659 // multiple fonts and use ImGui::PushFont()/PopFont() to select them.
660 // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to
661 // select the font among multiple.
662 // - If the file cannot be loaded, the function will return a nullptr. Please handle
663 // those errors in your application (e.g. use an assertion, or display an error and
664 // quit).
665 // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for
666 // higher quality font rendering.
667 // - Read 'docs/FONTS.md' for more instructions and details.
668 // - Remember that in C/C++ if you want to include a backslash \ in a string literal you
669 // need to write a double backslash \\ !
670 // - Emscripten allows preloading a file or folder to be accessible at runtime. See
671 // Makefile for details.
672 // io.Fonts->AddFontDefault();
673 // style.FontSizeBase = 20.0f;
674#ifndef IMGUI_DISABLE_FILE_FUNCTIONS
675 // io.Fonts->AddFontFromFileTTF("fonts/segoeui.ttf");
676 // io.Fonts->AddFontFromFileTTF("fonts/DroidSans.ttf");
677 // io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf");
678 // io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf");
679 // io.Fonts->AddFontFromFileTTF("fonts/ProggyTiny.ttf");
680 // ImFont* font = io.Fonts->AddFontFromFileTTF("fonts/ArialUni.ttf");
681 // IM_ASSERT(font != nullptr);
682#endif
683
684 return true;
685}
686
687bool Viz::initCamera()
688{
689 std::println("[UFO | Viz] Initializing camera...");
690
691 // TODO: Implement
692
693 // camera_.vertical_fov = ufo::radians(60.0f);
694 // camera_.near_clip = 0.01;
695 // camera_.far_clip = 10000.0;
696 // camera_.rows = surface_config_.height;
697 // camera_.cols = surface_config_.width;
698
699 return true;
700}
701
702void Viz::updateGui()
703{
704 // // Remove resize grips by making them the same color as the window bg
705 // ImGui::PushStyleColor(ImGuiCol_ResizeGrip, ImGui::GetColorU32(ImGuiCol_WindowBg));
706 // ImGui::PushStyleColor(ImGuiCol_ResizeGripActive,
707 // ImGui::GetColorU32(ImGuiCol_WindowBg));
708 // ImGui::PushStyleColor(ImGuiCol_ResizeGripHovered,
709 // ImGui::GetColorU32(ImGuiCol_WindowBg));
710
711 // if (ImGuiMouseCursor_ResizeNWSE == ImGui::GetMouseCursor()) {
712 // ImGui::SetMouseCursor(ImGuiMouseCursor_Arrow);
713 // }
714
715 // // Title bars the same color as the window bg
716 // ImGui::PushStyleColor(ImGuiCol_TitleBg, ImGui::GetColorU32(ImGuiCol_WindowBg));
717 // ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImGui::GetColorU32(ImGuiCol_WindowBg));
718 // ImGui::PushStyleColor(ImGuiCol_TitleBgCollapsed,
719 // ImGui::GetColorU32(ImGuiCol_WindowBg));
720
721 // // Separator
722 // ImGui::PushStyleColor(ImGuiCol_Separator, ImGui::GetColorU32(ImGuiCol_WindowBg));
723 // ImGui::PushStyleColor(ImGuiCol_SeparatorActive,
724 // ImGui::GetColorU32(ImGuiCol_WindowBg));
725 // ImGui::PushStyleColor(ImGuiCol_SeparatorHovered,
726 // ImGui::GetColorU32(ImGuiCol_WindowBg));
727
728 ImGui_ImplWGPU_NewFrame();
729 ImGui_ImplGlfw_NewFrame();
730 ImGui::NewFrame();
731
732 ImGuiIO& io = ImGui::GetIO();
733
734 // TODO: Implement
735
736 ImVec2 top_size = ImVec2(0, 0);
737 ImVec2 bottom_size = ImVec2(0, 0);
738
739 {
740 ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always, ImVec2(0.0, 0.0));
741 // ImGui::SetNextWindowSize(ImVec2(surface_width_ / 4, surface_height_),
742 // ImGuiCond_Always);
743 ImGui::SetNextWindowSizeConstraints(ImVec2(io.DisplaySize.x, 0),
744 ImVec2(io.DisplaySize.x, io.DisplaySize.y));
745 ImGui::SetNextWindowBgAlpha(1.0);
746
747 ImGui::Begin("Top", nullptr,
748 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse |
749 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar |
750 ImGuiWindowFlags_AlwaysAutoResize);
751
752 top_size = ImGui::GetWindowSize();
753
754 if (ImGui::Button("[")) {
755 show_left_panel_ = !show_left_panel_;
756 }
757 ImGui::SameLine();
758 if (ImGui::Button("_")) {
759 show_bottom_panel_ = !show_bottom_panel_;
760 }
761 ImGui::SameLine();
762 if (ImGui::Button("]")) {
763 show_right_panel_ = !show_right_panel_;
764 }
765
766 ImGui::End();
767 }
768
769 if (show_bottom_panel_) {
770 ImGui::SetNextWindowPos(ImVec2(0, io.DisplaySize.y), ImGuiCond_Always,
771 ImVec2(0.0, 1.0));
772 // ImGui::SetNextWindowSize(ImVec2(surface_width_ / 4, surface_height_),
773 // ImGuiCond_Always);
774 ImGui::SetNextWindowSizeConstraints(ImVec2(io.DisplaySize.x, 100),
775 ImVec2(io.DisplaySize.x, 500));
776 ImGui::SetNextWindowBgAlpha(1.0);
777
778 ImGui::Begin("Info", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
779
780 bottom_size = ImGui::GetWindowSize();
781
782 ImGui::Text("Framerate %.1f FPS (%.3f ms/frame)", io.Framerate,
783 1000.0f / io.Framerate);
784
785 frame_times_.push_back(1000.0f / io.Framerate);
786 if (frame_times_.size() > 1000) {
787 frame_times_.erase(frame_times_.begin());
788 }
789
790 float total = 0.0f;
791 float min = std::numeric_limits<float>::max();
792 float max = std::numeric_limits<float>::lowest();
793 for (auto const& ft : frame_times_) {
794 total += ft;
795 min = std::min(min, ft);
796 max = std::max(max, ft);
797 }
798 float average = total / frame_times_.size();
799
800 std::string overlay =
801 std::format("avg {:.3f} ms, min {:.3f} ms, max {:.3f}", average, min, max);
802 ImGui::PlotLines(
803 "Frame Times", frame_times_.data(), frame_times_.size(), 0, overlay.c_str(),
804 *std::min_element(frame_times_.begin(), frame_times_.end()),
805 *std::max_element(frame_times_.begin(), frame_times_.end()), ImVec2(0, 100));
806
807 ImGui::End();
808 }
809
810 if (show_left_panel_) {
811 ImGui::SetNextWindowPos(ImVec2(0, top_size.y), ImGuiCond_Always, ImVec2(0.0, 0.0));
812 // ImGui::SetNextWindowSize(ImVec2(surface_width_ / 4, surface_height_),
813 // ImGuiCond_Always);
814 ImGui::SetNextWindowSizeConstraints(
815 ImVec2(100, io.DisplaySize.y - top_size.y - bottom_size.y),
816 ImVec2(io.DisplaySize.x / 2, io.DisplaySize.y - top_size.y - bottom_size.y));
817 ImGui::SetNextWindowBgAlpha(1.0);
818
819 ImGui::Begin("Left", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
820
821 ImGui::ColorEdit3("clear color", (float*)&clear_color_);
822
823 ImGui::End();
824 }
825
826 if (show_right_panel_) {
827 ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x, top_size.y), ImGuiCond_Always,
828 ImVec2(1.0, 0.0));
829 // ImGui::SetNextWindowSize(ImVec2(surface_width_ / 4, surface_height_),
830 // ImGuiCond_Always);
831 ImGui::SetNextWindowSizeConstraints(
832 ImVec2(100, io.DisplaySize.y - top_size.y - bottom_size.y),
833 ImVec2(io.DisplaySize.x / 2, io.DisplaySize.y - top_size.y - bottom_size.y));
834 ImGui::SetNextWindowBgAlpha(1.0);
835
836 ImGui::Begin("View", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
837
838 ImGui::SeparatorText("Camera");
839
840 if (ImGui::BeginTable("CameraTable", 2, ImGuiTableFlags_SizingStretchProp)) {
841 ImGui::TableNextColumn();
842 ImGui::Text("Control");
843 ImGui::SetItemTooltip("I am a tooltip");
844 ImGui::TableNextColumn();
845 ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
846 if (ImGui::BeginTabBar("ControlTabs")) {
847 if (ImGui::BeginTabItem("FPS")) {
848 control_type_ = 0;
849
850 if (ImGui::BeginTable("FPSTable", 2, ImGuiTableFlags_SizingStretchProp)) {
851 ImGui::TableNextColumn();
852 ImGui::Text("Move speed");
853 ImGui::TableNextColumn();
854 ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
855 ImGui::SliderFloat("##t_speed", &translation_speed_, 0.01f, 10000.0f,
856 "%.3f (m/s)", ImGuiSliderFlags_Logarithmic);
857
858 ImGui::EndTable();
859 }
860
861 ImGui::EndTabItem();
862 }
863 if (ImGui::BeginTabItem("Orbit")) {
864 control_type_ = 1;
865
866 if (ImGui::BeginTable("OrbitTable", 2, ImGuiTableFlags_SizingStretchProp)) {
867 ImGui::TableNextColumn();
868 ImGui::Text("Fixed axis");
869 ImGui::TableNextColumn();
870 ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
871 char const* items[] = {"None", "X-axis", "Y-axis", "Z-axis"};
872 ImGui::Combo("##fixed_axis", &projection_type_, items, IM_ARRAYSIZE(items));
873
874 ImGui::EndTable();
875 }
876
877 ImGui::EndTabItem();
878 }
879 ImGui::EndTabBar();
880 }
881
882 ImGui::TableNextColumn();
883 ImGui::Text("Mouse sense");
884 ImGui::TableNextColumn();
885 ImGui::SliderFloat("##mouse_sense", &rotation_speed_, 0.01f, 10.0f, "%.3f");
886
887 ImGui::TableNextColumn();
888 ImGui::Text("Projection");
889 ImGui::TableNextColumn();
890 char const* projection_items[] = {"Perspective", "Orthogonal"};
891 ImGui::Combo("##perspective", &projection_type_, projection_items,
892 IM_ARRAYSIZE(projection_items));
893
894 ImGui::TableNextColumn();
895 ImGui::Text("Position");
896 ImGui::TableNextColumn();
897 ImGui::DragFloat3("##position", &camera_.pose.translation.x, 0.01f, 0.0f, 0.0f,
898 "%.3f");
899
900 ImGui::TableNextColumn();
901 ImGui::Text("Orientation");
902 ImGui::TableNextColumn();
903 if (ImGui::BeginTabBar("OrientationTabs")) {
904 Quatf rot(camera_.pose.rotation);
905 if (ImGui::BeginTabItem("Quat")) {
906 if (ImGui::DragFloat4("##orientation_quat", &rot.w, 0.01f, -1.0f, 1.0f,
907 "%.3f")) {
908 camera_.pose.rotation = static_cast<Mat3x3f>(rot);
909 }
910 ImGui::SetItemTooltip("W, X, Y, Z");
911 ImGui::EndTabItem();
912 }
913 if (ImGui::BeginTabItem("RPY")) {
914 Vec3f rpy{degrees(pitch(rot)), degrees(yaw(rot)), degrees(roll(rot))};
915
916 if (ImGui::DragFloat3("##orientation_rpy", &rpy.x, 0.1f, -360.0f, 360.0f,
917 "%.2f")) {
918 rpy.x = radians(rpy.x);
919 rpy.y = radians(rpy.y);
920 rpy.z = radians(rpy.z);
921 camera_.pose.rotation = static_cast<Mat3x3f>(Quatf(rpy));
922 }
923 ImGui::SetItemTooltip("Roll, Pitch, Yaw (deg)");
924 ImGui::EndTabItem();
925 }
926 ImGui::EndTabBar();
927 }
928
929 ImGui::TableNextColumn();
930 ImGui::Text("Field of view");
931 ImGui::TableNextColumn();
932 float fov_deg = degrees(camera_.vertical_fov);
933 if (ImGui::SliderFloat("##fov", &fov_deg, 1.0f, 179.0f, "%.1f (deg)")) {
934 camera_.vertical_fov = radians(fov_deg);
935 }
936
937 ImGui::TableNextColumn();
938 ImGui::Text("Zoom");
939 ImGui::TableNextColumn();
940 ImGui::SliderFloat("##zoom", &zoom_, -100.0f, 100.0f, "%.3f");
941 ImGui::EndTable();
942 }
943
944 ImGui::End();
945 }
946
947 ImGui::Render();
948}
949
950void Viz::onMouseMove(double x_pos, double y_pos)
951{
952 if (mouse_drag_) {
953 Vec2f cur_mouse_pos(static_cast<float>(x_pos), static_cast<float>(y_pos));
954 Vec2f delta = (cur_mouse_pos - start_mouse_pos_) * mouse_sense_;
955
956 // TODO: Implement
957
958 // m_cameraState.angles = m_drag.startCameraState.angles + delta;
959 // // Clamp to avoid going too far when orbitting up/down
960 // m_cameraState.angles.y =
961 // glm::clamp(m_cameraState.angles.y, -PI / 2 + 1e-5f, PI / 2 - 1e-5f);
962
963 // Vec3f euler_angles = toEulerAngles(start_camera_state_.pose.rotation);
964
965 // euler_angles.y -= rotation_speed_ * delta.x * 0.01f;
966 // euler_angles.x -= rotation_speed_ * delta.y * 0.01f;
967
968 // // Clamp the pitch to avoid gimball lock
969 // euler_angles.x = std::clamp(euler_angles.x, -ufo::half_pi<float>() + 0.01f,
970 // ufo::half_pi<float>() - 0.01f);
971
972 // start_camera_state_.pose.rotation = static_cast<Mat3x3f>(Quatf(euler_angles));
973 // camera_.pose.rotation = start_camera_state_.pose.rotation;
974
975 updateViewMatrix();
976 }
977
978 // TODO: Implement
979}
980
981void Viz::onMouseButton(int button, int action, int modifiers)
982{
983 ImGuiIO& io = ImGui::GetIO();
984 if (io.WantCaptureMouse) {
985 // Don't rotate the camera if the mouse is already captured by an ImGui
986 // interaction at this frame.
987 return;
988 }
989
990 if (GLFW_MOUSE_BUTTON_LEFT == button) {
991 switch (action) {
992 case GLFW_PRESS:
993 mouse_drag_ = true;
994 double xpos, ypos;
995 glfwGetCursorPos(window_, &xpos, &ypos);
996 start_mouse_pos_ = Vec2f(static_cast<float>(xpos), static_cast<float>(ypos));
997 start_camera_state_ = camera_;
998 break;
999 case GLFW_RELEASE: mouse_drag_ = false; break;
1000 }
1001 }
1002
1003 // TODO: Implement
1004}
1005
1006void Viz::onScroll(double x_offset, double y_offset)
1007{
1008 // TODO: Implement
1009 zoom_ += scroll_sensitivity_ * static_cast<float>(y_offset);
1010 updateViewMatrix();
1011}
1012
1013void Viz::onKey(int key, int scancode, int action, int mods)
1014{
1015 // TODO: Implement
1016}
1017
1018void Viz::updateViewMatrix()
1019{
1020 // TODO: Implement
1021 // float cx = cos(m_cameraState.angles.x);
1022 // float sx = sin(m_cameraState.angles.x);
1023 // float cy = cos(m_cameraState.angles.y);
1024 // float sy = sin(m_cameraState.angles.y);
1025 // vec3 position = vec3(cx * cy, sx * cy, sy) * std::exp(-m_cameraState.zoom);
1026 // m_uniforms.viewMatrix = glm::lookAt(position, vec3(0.0f), vec3(0, 0, 1));
1027 // m_queue.writeBuffer(m_uniformBuffer, offsetof(MyUniforms, viewMatrix),
1028 // &m_uniforms.viewMatrix, sizeof(MyUniforms::viewMatrix));
1029}
1030} // namespace ufo
constexpr T radians(T deg) noexcept
Converts degrees to radians.
Definition math.hpp:86
constexpr T degrees(T rad) noexcept
Converts radians to degrees.
Definition math.hpp:98
T roll(Quat< T > const &q) noexcept
Extracts the roll angle (rotation about the X-axis) in radians.
Definition quat.hpp:905
constexpr C average(C a, C const &b)
Computes the average of two colors.
Definition average.hpp:77
All vision-related classes and functions.
Definition cloud.hpp:49
T pitch(Quat< T > const &q) noexcept
Extracts the pitch angle (rotation about the Y-axis) in radians.
Definition quat.hpp:918
constexpr Vec< Geometry::dimension(), typename Geometry::value_type > max(Geometry const &g)
Returns the maximum coordinate of the minimum spanning axis-aligned bounding box of a geometry.
Definition fun.hpp:72
T yaw(Quat< T > const &q) noexcept
Extracts the yaw angle (rotation about the Z-axis) in radians.
Definition quat.hpp:931
constexpr Vec< Geometry::dimension(), typename Geometry::value_type > min(Geometry const &g)
Returns the minimum coordinate of the minimum spanning axis-aligned bounding box of a geometry.
Definition fun.hpp:58
Mat< Dim, Dim, T > rotation
Rotation component of the transform, represented as a Dim × Dim matrix.
Definition transform.hpp:88
Vec< Dim, T > translation
Translation component of the transform.
Definition transform.hpp:92
constexpr auto & x(this auto &self) noexcept
Accesses the first component (x).
Definition vec.hpp:136