43#include <ufo/glfw_webgpu/glfw_surface.h>
45#include <ufo/viz/viz.hpp>
54#include <GLFW/glfw3.h>
56#include <GLFW/glfw3native.h>
60#include <backends/imgui_impl_glfw.h>
61#include <backends/imgui_impl_wgpu.h>
66#include <emscripten.h>
67#include <emscripten/html5.h>
68#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU)
69#include <emscripten/html5_webgpu.h>
71#include "../libs/emscripten/emscripten_mainloop_stub.h"
78Viz::~Viz() { close(); }
80bool Viz::running()
const
82 return nullptr != window_ && !glfwWindowShouldClose(window_);
87#if defined(__EMSCRIPTEN__)
91 io.IniFilename =
nullptr;
93 auto callback = [](
void* arg) {
94 Viz* v =
static_cast<Viz*
>(arg);
97 emscripten_set_main_loop_arg(callback,
this, 0,
true);
105void Viz::runAsync() { render_thread_ = std::thread(&Viz::run,
this); }
107bool Viz::open(
int width,
int height,
bool resizable, std::string
const& window_name,
108 WGPUPowerPreference power_preference, WGPUBackendType backend_type)
114 if (!initWindow(width, height, resizable, window_name)) {
115 std::println(stderr,
"[UFO | Viz] Failed to initialize window");
119 if (!initWGPU(power_preference, backend_type)) {
120 std::println(stderr,
"[UFO | Viz] Failed to initialize WebGPU");
121 glfwDestroyWindow(window_);
126 glfwShowWindow(window_);
129 std::println(stderr,
"[UFO | Viz] Failed to initialize GUI");
135 std::println(stderr,
"[UFO | Viz] Failed to initialize camera");
149 if (render_thread_.joinable()) {
150 render_thread_.join();
153 for (
auto& renderable : renderables_) {
154 renderable->release();
159 ImGui_ImplWGPU_Shutdown();
160 ImGui_ImplGlfw_Shutdown();
161 ImGui::DestroyContext();
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_);
171 compute::release(surface_);
172 glfwDestroyWindow(window_);
173 compute::release(instance_);
178 glfwDestroyWindow(window_);
188 depth_texture_ =
nullptr;
189 depth_view_ =
nullptr;
192void Viz::resizeSurface(
int width,
int height)
196 surface_width_ = width * scale_;
197 surface_height_ = height * scale_;
199 surface_config_.width = surface_width_;
200 surface_config_.height = surface_height_;
202 wgpuSurfaceConfigure(surface_, &surface_config_);
204 if (
nullptr != depth_view_) {
205 compute::release(depth_view_);
206 depth_view_ =
nullptr;
208 if (
nullptr != depth_texture_) {
209 compute::release(depth_texture_);
210 depth_texture_ =
nullptr;
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_;
222 depth_texture_ = wgpuDeviceCreateTexture(device_, &depth_texture_desc);
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;
235 depth_view_ = wgpuTextureCreateView(depth_texture_, &depth_texture_view_desc);
241 float cur_time = glfwGetTime();
242 float dt = cur_time - prev_time_;
243 prev_time_ = cur_time;
247 wgpuInstanceProcessEvents(instance_);
249 if (0 != glfwGetWindowAttrib(window_, GLFW_ICONIFIED)) {
250 ImGui_ImplGlfw_Sleep(10);
256 glfwGetFramebufferSize(window_, &width, &height);
257 if (width != surface_width_ || height != surface_height_) {
259 resizeSurface(width, height);
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");
272 if (ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(surface_texture.status)) {
273 if (
nullptr != surface_texture.texture) {
274 wgpuTextureRelease(surface_texture.texture);
277 if (0 < width && 0 < height) {
279 resizeSurface(width, height);
291 ImGuiIO& io = ImGui::GetIO();
298 if (width != io.DisplaySize.x || height != io.DisplaySize.y) {
299 wgpuTextureRelease(surface_texture.texture);
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;
314 WGPUTextureView texture_view =
315 wgpuTextureCreateView(surface_texture.texture, &view_desc);
316 assert(
nullptr != texture_view);
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;
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_;
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;
342 WGPUCommandEncoderDescriptor encoder_desc = WGPU_COMMAND_ENCODER_DESCRIPTOR_INIT;
343 encoder_desc.label = {
"[UFO | Viz] Command Encoder", WGPU_STRLEN};
345 WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device_, &encoder_desc);
347 WGPURenderPassEncoder pass =
348 wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc);
350 ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass);
352 for (
auto& renderable : renderables_) {
353 renderable->update(device_, encoder, pass, camera_);
356 wgpuRenderPassEncoderEnd(pass);
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);
363 wgpuQueueSubmit(queue_, 1, &command_buffer);
365#ifndef __EMSCRIPTEN__
366 wgpuSurfacePresent(surface_);
369 compute::release(command_buffer);
370 compute::release(pass);
371 compute::release(encoder);
372 compute::release(texture_view);
373 compute::release(surface_texture.texture);
376WGPUDevice Viz::device()
const {
return device_; }
378void Viz::addRenderable(std::shared_ptr<Renderable>
const& renderable)
382 renderables_.push_back(renderable);
384 if (
nullptr == device_) {
388 renderable->init(device_, surface_preferred_format_);
391void Viz::eraseRenderable(std::shared_ptr<Renderable>
const& renderable)
395 renderables_.erase(std::remove(renderables_.begin(), renderables_.end(), renderable),
399void Viz::clearRenderables()
403 renderables_.clear();
406void Viz::loadConfig(std::filesystem::path
const& config)
411void Viz::saveConfig(std::filesystem::path
const& file)
const
416WGPULimits Viz::requiredLimits(WGPUAdapter adapter)
const
418 WGPULimits required = WGPU_LIMITS_INIT;
419 WGPULimits supported = WGPU_LIMITS_INIT;
421 wgpuAdapterGetLimits(adapter, &supported);
425 required.minUniformBufferOffsetAlignment = supported.minUniformBufferOffsetAlignment;
426 required.minStorageBufferOffsetAlignment = supported.minStorageBufferOffsetAlignment;
430 required.maxBindGroups = 2;
432 required.maxBufferSize = 2'147'483'648;
433 required.maxStorageBufferBindingSize = 2'147'483'648;
435 required.maxBufferSize = std::min(required.maxBufferSize, supported.maxBufferSize);
436 required.maxStorageBufferBindingSize = std::min(required.maxStorageBufferBindingSize,
437 supported.maxStorageBufferBindingSize);
439 required.maxComputeWorkgroupStorageSize = 16352;
440 required.maxComputeInvocationsPerWorkgroup = 256;
441 required.maxComputeWorkgroupSizeX = 256;
442 required.maxComputeWorkgroupSizeY = 256;
443 required.maxComputeWorkgroupSizeZ = 64;
444 required.maxComputeWorkgroupsPerDimension = 65535;
446 required.maxUniformBuffersPerShaderStage = 12;
447 required.maxUniformBufferBindingSize = 65536;
452WGPUTextureFormat Viz::surfaceFormat(WGPUSurfaceCapabilities capabilities)
const
454 assert(0 < capabilities.formatCount);
455 return capabilities.formats[0];
458void Viz::updateCamera(
float dt)
463 float speed_multiplier = 2.0f;
465 if (GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_W) ||
466 GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_UP)) {
467 speed.
x += translation_speed_;
470 if (GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_S) ||
471 GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_DOWN)) {
472 speed.x -= translation_speed_;
475 if (GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_A) ||
476 GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_LEFT)) {
477 speed.y += translation_speed_;
480 if (GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_D) ||
481 GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_RIGHT)) {
482 speed.y -= translation_speed_;
485 if (GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_SPACE)) {
486 speed.z += translation_speed_;
489 if (GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_LEFT_CONTROL)) {
490 speed.z -= translation_speed_;
493 if (GLFW_PRESS == glfwGetKey(window_, GLFW_KEY_LEFT_SHIFT)) {
494 speed *= speed_multiplier;
508bool Viz::initWindow(
int width,
int height,
bool resizable, std::string
const& title)
510 std::println(
"[UFO | Viz] Initializing GLFW window...");
512 glfwSetErrorCallback([](
int error,
char const* description) {
513 std::println(stderr,
"[UFO | Viz] GLFW Error ({}): {}", error, description);
517 throw std::runtime_error(
"[UFO | Viz] Failed to initialize GLFW");
520 glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
521 glfwWindowHint(GLFW_RESIZABLE, resizable ? GLFW_TRUE : GLFW_FALSE);
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_);
528 glfwCreateWindow(surface_width_, surface_height_, title.c_str(),
nullptr,
nullptr);
530 if (
nullptr == window_) {
534 glfwSetWindowUserPointer(window_,
static_cast<void*
>(
this));
536 glfwSetKeyCallback(window_,
537 [](GLFWwindow* window,
int key,
int scancode,
int action,
int mods) {
538 Viz* v =
static_cast<Viz*
>(glfwGetWindowUserPointer(window));
542 v->onKey(key, scancode, action, mods);
545 glfwSetCursorPosCallback(window_, [](GLFWwindow* window,
double x_pos,
double y_pos) {
546 Viz* v =
static_cast<Viz*
>(glfwGetWindowUserPointer(window));
550 v->onMouseMove(x_pos, y_pos);
553 glfwSetMouseButtonCallback(
554 window_, [](GLFWwindow* window,
int button,
int action,
int mods) {
555 Viz* v =
static_cast<Viz*
>(glfwGetWindowUserPointer(window));
559 v->onMouseButton(button, action, mods);
562 glfwSetScrollCallback(window_,
563 [](GLFWwindow* window,
double x_offset,
double y_offset) {
564 Viz* v =
static_cast<Viz*
>(glfwGetWindowUserPointer(window));
568 v->onScroll(x_offset, y_offset);
574bool Viz::initWGPU(WGPUPowerPreference power_preference, WGPUBackendType backend_type)
576 std::println(
"[UFO | Viz] Initializing WebGPU...");
578 std::println(
"[UFO | Viz] Creating WGPU instance...");
579 instance_ = compute::createInstance();
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));
591 wgpuSetLogLevel(WGPULogLevel_Warn);
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");
600 std::println(
"[UFO | Viz] Requesting WGPU adapter...");
601 adapter_ = compute::createAdapter(instance_, surface_, power_preference, backend_type);
603 ImGui_ImplWGPU_DebugPrintAdapterInfo(adapter_);
605 auto limits = requiredLimits(adapter_);
606 std::println(
"[UFO | Viz] Creating WGPU device...");
607 device_ = compute::createDevice(adapter_, &limits);
609 auto surface_capabilities = compute::surfaceCapabilities(surface_, adapter_);
610 surface_preferred_format_ = surfaceFormat(surface_capabilities);
611 wgpuSurfaceCapabilitiesFreeMembers(surface_capabilities);
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_;
621 resizeSurface(surface_width_, surface_height_);
623 queue_ = compute::queue(device_);
630 std::println(
"[UFO | Viz] Initializing ImGui...");
632 IMGUI_CHECKVERSION();
633 ImGui::CreateContext();
634 ImGuiIO& io = ImGui::GetIO();
635 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
636 io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
638 ImGui::StyleColorsDark();
641 ImGuiStyle& style = ImGui::GetStyle();
642 style.ScaleAllSizes(scale_);
643 style.FontScaleDpi = scale_;
645 ImGui_ImplGlfw_InitForOther(window_,
true);
647 ImGui_ImplGlfw_InstallEmscriptenCallbacks(window_,
"#canvas");
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);
674#ifndef IMGUI_DISABLE_FILE_FUNCTIONS
687bool Viz::initCamera()
689 std::println(
"[UFO | Viz] Initializing camera...");
728 ImGui_ImplWGPU_NewFrame();
729 ImGui_ImplGlfw_NewFrame();
732 ImGuiIO& io = ImGui::GetIO();
736 ImVec2 top_size = ImVec2(0, 0);
737 ImVec2 bottom_size = ImVec2(0, 0);
740 ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always, ImVec2(0.0, 0.0));
743 ImGui::SetNextWindowSizeConstraints(ImVec2(io.DisplaySize.x, 0),
744 ImVec2(io.DisplaySize.x, io.DisplaySize.y));
745 ImGui::SetNextWindowBgAlpha(1.0);
747 ImGui::Begin(
"Top",
nullptr,
748 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse |
749 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar |
750 ImGuiWindowFlags_AlwaysAutoResize);
752 top_size = ImGui::GetWindowSize();
754 if (ImGui::Button(
"[")) {
755 show_left_panel_ = !show_left_panel_;
758 if (ImGui::Button(
"_")) {
759 show_bottom_panel_ = !show_bottom_panel_;
762 if (ImGui::Button(
"]")) {
763 show_right_panel_ = !show_right_panel_;
769 if (show_bottom_panel_) {
770 ImGui::SetNextWindowPos(ImVec2(0, io.DisplaySize.y), ImGuiCond_Always,
774 ImGui::SetNextWindowSizeConstraints(ImVec2(io.DisplaySize.x, 100),
775 ImVec2(io.DisplaySize.x, 500));
776 ImGui::SetNextWindowBgAlpha(1.0);
778 ImGui::Begin(
"Info",
nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
780 bottom_size = ImGui::GetWindowSize();
782 ImGui::Text(
"Framerate %.1f FPS (%.3f ms/frame)", io.Framerate,
783 1000.0f / io.Framerate);
785 frame_times_.push_back(1000.0f / io.Framerate);
786 if (frame_times_.size() > 1000) {
787 frame_times_.erase(frame_times_.begin());
791 float min = std::numeric_limits<float>::max();
792 float max = std::numeric_limits<float>::lowest();
793 for (
auto const& ft : frame_times_) {
798 float average = total / frame_times_.size();
800 std::string overlay =
801 std::format(
"avg {:.3f} ms, min {:.3f} ms, max {:.3f}",
average,
min,
max);
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));
810 if (show_left_panel_) {
811 ImGui::SetNextWindowPos(ImVec2(0, top_size.y), ImGuiCond_Always, ImVec2(0.0, 0.0));
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);
819 ImGui::Begin(
"Left",
nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
821 ImGui::ColorEdit3(
"clear color", (
float*)&clear_color_);
826 if (show_right_panel_) {
827 ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x, top_size.y), 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);
836 ImGui::Begin(
"View",
nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
838 ImGui::SeparatorText(
"Camera");
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")) {
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);
863 if (ImGui::BeginTabItem(
"Orbit")) {
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));
882 ImGui::TableNextColumn();
883 ImGui::Text(
"Mouse sense");
884 ImGui::TableNextColumn();
885 ImGui::SliderFloat(
"##mouse_sense", &rotation_speed_, 0.01f, 10.0f,
"%.3f");
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));
894 ImGui::TableNextColumn();
895 ImGui::Text(
"Position");
896 ImGui::TableNextColumn();
897 ImGui::DragFloat3(
"##position", &camera_.pose.
translation.x, 0.01f, 0.0f, 0.0f,
900 ImGui::TableNextColumn();
901 ImGui::Text(
"Orientation");
902 ImGui::TableNextColumn();
903 if (ImGui::BeginTabBar(
"OrientationTabs")) {
905 if (ImGui::BeginTabItem(
"Quat")) {
906 if (ImGui::DragFloat4(
"##orientation_quat", &rot.w, 0.01f, -1.0f, 1.0f,
908 camera_.pose.
rotation =
static_cast<Mat3x3f
>(rot);
910 ImGui::SetItemTooltip(
"W, X, Y, Z");
913 if (ImGui::BeginTabItem(
"RPY")) {
916 if (ImGui::DragFloat3(
"##orientation_rpy", &rpy.x, 0.1f, -360.0f, 360.0f,
921 camera_.pose.
rotation =
static_cast<Mat3x3f
>(Quatf(rpy));
923 ImGui::SetItemTooltip(
"Roll, Pitch, Yaw (deg)");
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);
937 ImGui::TableNextColumn();
939 ImGui::TableNextColumn();
940 ImGui::SliderFloat(
"##zoom", &zoom_, -100.0f, 100.0f,
"%.3f");
950void Viz::onMouseMove(
double x_pos,
double y_pos)
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_;
981void Viz::onMouseButton(
int button,
int action,
int modifiers)
983 ImGuiIO& io = ImGui::GetIO();
984 if (io.WantCaptureMouse) {
990 if (GLFW_MOUSE_BUTTON_LEFT == button) {
995 glfwGetCursorPos(window_, &xpos, &ypos);
996 start_mouse_pos_ = Vec2f(
static_cast<float>(xpos),
static_cast<float>(ypos));
997 start_camera_state_ = camera_;
999 case GLFW_RELEASE: mouse_drag_ =
false;
break;
1006void Viz::onScroll(
double x_offset,
double y_offset)
1009 zoom_ += scroll_sensitivity_ *
static_cast<float>(y_offset);
1013void Viz::onKey(
int key,
int scancode,
int action,
int mods)
1018void Viz::updateViewMatrix()
constexpr T radians(T deg) noexcept
Converts degrees to radians.
constexpr T degrees(T rad) noexcept
Converts radians to degrees.
T roll(Quat< T > const &q) noexcept
Extracts the roll angle (rotation about the X-axis) in radians.
constexpr C average(C a, C const &b)
Computes the average of two colors.
All vision-related classes and functions.
T pitch(Quat< T > const &q) noexcept
Extracts the pitch angle (rotation about the Y-axis) in radians.
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.
T yaw(Quat< T > const &q) noexcept
Extracts the yaw angle (rotation about the Z-axis) in radians.
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.
constexpr auto & x(this auto &self) noexcept
Accesses the first component (x).