#include "viewer/instance.hpp" #include "viewer/asset_loader.hpp" #include "SFML/Window.hpp" #include "SFML/Graphics/Text.hpp" #include "SFML/Graphics/Shape.hpp" #include "SFML/Graphics/RectangleShape.hpp" #include "SFML/Graphics/Sprite.hpp" #include #include #include "glm/gtx/euler_angles.hpp" namespace viewer { instance::instance() : m_context_settings{ 24, 8, 2, 4, 6 }, m_mesh_renderer(static_cast(rendering::modes::mesh::count)), m_point_cloud_renderer(static_cast(rendering::modes::point_cloud::count)), //m_camera(0.0f, 0.0f, 0.0f) m_camera(0.0f, -std::numbers::pi_v / 2.0f, std::numbers::pi_v) { } std::error_code instance::init(std::string title) { m_title = std::move(title); windowed(512, 344, true); if (glewInit() != GLEW_OK) { return std::make_error_code(std::errc::not_supported); } m_settings.lighting.point_light_direction = glm::normalize(m_settings.lighting.point_light_direction); m_mesh_render_mode = rendering::modes::mesh::lit_faces; m_point_cloud_render_mode = rendering::modes::point_cloud::rainbow; glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glFrontFace(GL_CCW); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glClearDepth(1.f); set_background_color({ 0.0f, 0.0f, 0.0f, 0.0f }); const auto current_path = std::filesystem::current_path(); const auto data_path = current_path / ".." / "data"; const auto font_filename = data_path / "fonts" / "JetBrainsMono_Medium.ttf"; if (not m_font.loadFromFile(font_filename)) { ztu::logger::error("Could not open font file: %", font_filename); } const auto image_dir = data_path / "images"; const auto logo_filename = image_dir/ "logo.png"; if (not m_logo.loadFromFile(logo_filename)) { ztu::logger::error("Could not open image file: %", logo_filename); } const auto spinner_filename = image_dir/ "spinner.png"; if (not m_spinner.loadFromFile(spinner_filename)) { ztu::logger::error("Could not open image file: %", spinner_filename); } return {}; } void instance::set_background_color(const glm::vec4& color) { glClearColor(color.r, color.g, color.b, color.a); } std::optional instance::add_mesh( const zgl::mesh_handle& mesh, const aabb& bounding_box, const components::mesh_vertex::flags mesh_components, const zgl::material_handle& material, const material_component::flags material_components ) { const auto mesh_id = m_mesh_renderer.add( std::make_pair(mesh_components, material_components), mesh, bounding_box, glm::identity(), material, m_mesh_shader_program_lookup ); if (not mesh_id) { return std::nullopt; } return asset_id(std::in_place_index_t{}, *mesh_id); } std::optional instance::add_point_cloud( const zgl::point_cloud_handle& point_cloud, const aabb& bounding_box, const components::point_cloud_vertex::flags point_cloud_components ) { const auto point_cloud_id = m_point_cloud_renderer.add( point_cloud_components, point_cloud, bounding_box, glm::identity(), m_point_cloud_shader_program_lookup ); if (not point_cloud_id) { return std::nullopt; } return asset_id(std::in_place_index_t{}, *point_cloud_id); } void instance::add_shader_program( asset_types type, zgl::shader_program_handle shader_program_handle ) { switch (type) { case asset_types::mesh: m_mesh_shader_program_lookup.add(shader_program_handle); break; case asset_types::point_cloud: m_point_cloud_shader_program_lookup.add(shader_program_handle); break; default: break; } } bool instance::remove(asset_id id) { switch (id.index()) { case id_mesh_index: return m_mesh_renderer.remove(std::get(id)); case id_point_cloud_index: return m_point_cloud_renderer.remove(std::get(id)); default: return false; } } bool instance::look_at(asset_id id) { auto bounding_box_opt = std::optional{ std::nullopt }; switch (id.index()) { case id_mesh_index: bounding_box_opt = m_mesh_renderer.bounding_box(std::get(id)); break; case id_point_cloud_index: bounding_box_opt = m_point_cloud_renderer.bounding_box(std::get(id)); break; default: return false; } if (bounding_box_opt) { const auto& bounding_box = *bounding_box_opt; ztu::logger::debug("aabb: % %", glm::to_string(bounding_box.min), glm::to_string(bounding_box.max)); //m_camera.look_at(bounding_box.max, bounding_box.min, m_view); m_camera.look_at(bounding_box.center(), bounding_box.min, m_view); ztu::logger::debug("pos: % front: % right: % up: % as: % fov: %", glm::to_string(m_view.position), glm::to_string(m_view.front), glm::to_string(m_view.right), glm::to_string(m_view.up), m_view.aspect_ratio, m_view.fov ); } return true; } void instance::run_progress( std::mutex& lock, std::string& title, float& progress, const double fps ) { namespace chr = std::chrono; using namespace std::chrono_literals; using clock = chr::high_resolution_clock; using duration_type = clock::duration; using floating_second = chr::duration; sf::RectangleShape bar_background, bar_foreground; sf::Text title_text, percent_text; sf::Texture logo_texture, spinner_texture; sf::Sprite logo_sprite, spinner_sprite; sf::Color background_color = { 0, 0, 0, 255 }; constexpr auto font_size = 14; constexpr auto padding = 15; constexpr auto spinner_size = 10.0f; constexpr auto spinner_degrees_per_second = 270.0f; title_text.setFont(m_font); title_text.setCharacterSize(font_size); percent_text.setFont(m_font); percent_text.setCharacterSize(font_size); bar_foreground.setOutlineColor(sf::Color::Transparent); bar_foreground.setFillColor(sf::Color{ 107, 203, 119 }); bar_background.setOutlineColor(sf::Color::Transparent); bar_background.setFillColor(sf::Color{ 64, 170, 87 }); const auto target_frame_time = std::chrono::duration_cast(1s) / fps; std::stringstream percent_builder; percent_builder << std::setw(3) << std::setfill(' '); logo_texture.loadFromImage(m_logo); logo_sprite.setTexture(logo_texture); const auto logo_scale = static_cast(m_screen_size.x) / static_cast(logo_texture.getSize().x); logo_sprite.scale({ logo_scale, logo_scale }); logo_sprite.setOrigin(0, 0); logo_sprite.setPosition(0, 0); const auto dim = static_cast(m_screen_size); const auto bar_dim = sf::Vector2f(dim.x - 2 * padding, 10.0f); const auto bar_pos = sf::Vector2f((dim.x - bar_dim.x) / 2.0f, dim.y - bar_dim.y - padding); bar_background.setPosition(bar_pos + sf::Vector2f{ 3, 3 }); bar_background.setSize(bar_dim); bar_foreground.setPosition(bar_pos); spinner_texture.loadFromImage(m_spinner); spinner_sprite.setTexture(spinner_texture); const auto spinner_dim = static_cast(spinner_texture.getSize().y); const auto spinner_scale = spinner_size / spinner_dim; spinner_sprite.scale({ spinner_scale, spinner_scale }); spinner_sprite.setOrigin(spinner_dim / 2.0f, spinner_dim / 2.0f); spinner_sprite.setPosition(padding + spinner_size / 2.0f, bar_pos.y - 0.5f * padding - spinner_size / 2.0f); const auto title_text_pos = sf::Vector2f(1.5f * padding + spinner_size, bar_pos.y - 0.5f * padding - font_size); const auto percent_text_pos = sf::Vector2f(dim.x - padding, bar_pos.y - 0.5f * padding - font_size); title_text.setPosition(title_text_pos); percent_text.setPosition(percent_text_pos); percent_text.setFillColor(sf::Color::White); title_text.setFillColor(sf::Color::White); const auto start_time = clock::now(); while (true) { const auto frame_begin = clock::now(); const auto t = chr::duration_cast(frame_begin - start_time).count(); lock.lock(); auto curr_progress = progress; title_text.setString(title); lock.unlock(); if (curr_progress == std::numeric_limits::max()) { break; } m_window.clear(background_color); m_window.draw(logo_sprite); m_window.draw(bar_background); bar_foreground.setSize(sf::Vector2f(curr_progress * bar_dim.x, bar_dim.y)); m_window.draw(bar_foreground); const auto spinner_angle = t * spinner_degrees_per_second; spinner_sprite.setRotation(spinner_angle); m_window.draw(spinner_sprite); const auto percent = static_cast(std::round(100.0f * curr_progress)); percent_builder.str(""); percent_builder << percent << '%'; percent_text.setString(percent_builder.str()); const auto percent_text_bounds = percent_text.getLocalBounds(); percent_text.setOrigin( percent_text_bounds.left + percent_text_bounds.width, 0 ); m_window.draw(title_text); m_window.draw(percent_text); m_window.display(); const auto time_taken = clock::now() - frame_begin; if (time_taken < target_frame_time) { std::this_thread::sleep_for(target_frame_time - time_taken); } } } void instance::run(std::mutex& gl_resource_lock, const double fps) { namespace chr = std::chrono; using namespace std::chrono_literals; using clock = chr::high_resolution_clock; using duration_type = clock::duration; using floating_second = chr::duration; const auto target_frame_time = std::chrono::duration_cast(1s) / fps; auto frame_begin = clock::now(); m_mouse_locked = true; m_window.setMouseCursorVisible(false); while (true) { auto prev_frame_begin = frame_begin; frame_begin = clock::now(); const auto dt = chr::duration_cast(frame_begin - prev_frame_begin); if (not update(dt.count())) break; gl_resource_lock.lock(); render(); gl_resource_lock.unlock(); /*ztu::logger::debug("pos: % front: % right: % up: % as: % fov: %", glm::to_string(m_view.position), glm::to_string(m_view.front), glm::to_string(m_view.right), glm::to_string(m_view.up), m_view.aspect_ratio, m_view.fov );*/ const auto time_taken = clock::now() - frame_begin; if (time_taken < target_frame_time) { std::this_thread::sleep_for(target_frame_time - time_taken); } } } void instance::windowed(const unsigned int width, const unsigned int height, const bool decorations) { m_window.create( sf::VideoMode(width, height), m_title, decorations ? sf::Style::Default : sf::Style::None, m_context_settings ); m_screen_size = { width, height }; m_view.aspect_ratio = static_cast(width) / static_cast(height); } void instance::fullscreen() { m_window.create( sf::VideoMode(), m_title, sf::Style::Fullscreen, m_context_settings ); } void instance::size(const unsigned int width, const unsigned int height) { m_window.setSize({ width, height }); m_screen_size = { width, height }; m_view.aspect_ratio = static_cast(width) / static_cast(height); } bool instance::update(const double dt) { //ztu::logger::log("pos: % dir: %", glm::to_string(m_view.position), glm::to_string(m_view.front)); auto mouse_pos_delta = glm::vec2{ 0, 0 }; auto mouse_wheel_delta = 0.0f; sf::Event event; while (m_window.pollEvent(event)) { switch (event.type) { case sf::Event::Closed: [[unlikely]] { return false; } case sf::Event::Resized: { const auto& [ width, height ] = event.size; glViewport(0, 0, width, height); m_screen_size = { width, height }; break; } case sf::Event::MouseWheelMoved: { mouse_wheel_delta = m_settings.scroll_speed * event.mouseWheel.delta; break; } case sf::Event::KeyPressed: switch (event.key.code) { case sf::Keyboard::Escape: return false; case sf::Keyboard::Tab: m_window.setMouseCursorVisible(m_mouse_locked); m_mouse_locked = not m_mouse_locked; break; case sf::Keyboard::T: //renderIndex = (renderIndex + 1) % renderers.size(); break; case sf::Keyboard::C: //renderIndex = (renderIndex + 1) % renderers.size(); break; default: break; } break; default: break; } } m_view.aspect_ratio = static_cast(m_screen_size.x) / static_cast(m_screen_size.y); if (m_mouse_locked) [[likely]] { const auto screen_center = m_screen_size / 2; const auto [ mouse_x, mouse_y ] = sf::Mouse::getPosition(m_window); sf::Mouse::setPosition({ screen_center.x, screen_center.y }, m_window); const auto mouse_pixel_delta = glm::ivec2{ mouse_x, mouse_y } - screen_center; mouse_pos_delta = m_settings.mouse_sensitivity * static_cast(mouse_pixel_delta); m_camera.update( static_cast(dt), mouse_pos_delta, mouse_wheel_delta, m_view ); } return true; } void instance::render() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); const auto view_matrix = m_view.create_view_matrix(); const auto projection_matrix = m_view.create_projection_matrix(); const auto vp_matrix = projection_matrix * view_matrix; m_mesh_renderer.render( m_mesh_render_mode, vp_matrix, view_matrix, m_view.position, m_settings.lighting ); m_point_cloud_renderer.render( m_point_cloud_render_mode, vp_matrix, m_view.position, m_settings.lighting ); m_window.display(); //std::cout << "f\n"; } }