123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- /*
- * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #include <LibCore/ElapsedTimer.h>
- #include <LibCore/File.h>
- #include <LibCore/System.h>
- #include <LibFileSystemAccessClient/Client.h>
- #include <LibGL/GL/gl.h>
- #include <LibGL/GLContext.h>
- #include <LibGUI/ActionGroup.h>
- #include <LibGUI/Application.h>
- #include <LibGUI/FilePicker.h>
- #include <LibGUI/Icon.h>
- #include <LibGUI/Label.h>
- #include <LibGUI/Menu.h>
- #include <LibGUI/Menubar.h>
- #include <LibGUI/MessageBox.h>
- #include <LibGUI/Painter.h>
- #include <LibGUI/Widget.h>
- #include <LibGUI/Window.h>
- #include <LibGfx/Bitmap.h>
- #include <LibGfx/Palette.h>
- #include <LibMain/Main.h>
- #include "Mesh.h"
- #include "WavefrontOBJLoader.h"
- static constexpr u16 RENDER_WIDTH = 640;
- static constexpr u16 RENDER_HEIGHT = 480;
- class GLContextWidget final : public GUI::Frame {
- C_OBJECT(GLContextWidget);
- public:
- bool load_path(String const& fname);
- bool load_fd_and_close(int fd, String const& fname);
- bool load_file(Core::File& file, String const& fname);
- void toggle_rotate_x() { m_rotate_x = !m_rotate_x; }
- void toggle_rotate_y() { m_rotate_y = !m_rotate_y; }
- void toggle_rotate_z() { m_rotate_z = !m_rotate_z; }
- void set_rotation_speed(float speed) { m_rotation_speed = speed; }
- void set_stat_label(RefPtr<GUI::Label> l) { m_stats = l; };
- void set_wrap_s_mode(GLint mode) { m_wrap_s_mode = mode; }
- void set_wrap_t_mode(GLint mode) { m_wrap_t_mode = mode; }
- void set_texture_scale(float scale) { m_texture_scale = scale; }
- void set_mag_filter(GLint filter) { m_mag_filter = filter; }
- void toggle_show_frame_rate()
- {
- m_show_frame_rate = !m_show_frame_rate;
- m_stats->set_visible(m_show_frame_rate);
- }
- private:
- GLContextWidget()
- : m_mesh_loader(adopt_own(*new WavefrontOBJLoader()))
- {
- m_bitmap = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRx8888, { RENDER_WIDTH, RENDER_HEIGHT }).release_value_but_fixme_should_propagate_errors();
- m_context = GL::create_context(*m_bitmap);
- start_timer(20);
- GL::make_context_current(m_context);
- glFrontFace(GL_CCW);
- glEnable(GL_CULL_FACE);
- glEnable(GL_DEPTH_TEST);
- // Set projection matrix
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity();
- glFrustum(-0.5, 0.5, -0.5, 0.5, 1, 1500);
- m_init_list = glGenLists(1);
- glNewList(m_init_list, GL_COMPILE);
- {
- glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
- glClearDepth(1.0);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- }
- glEndList();
- }
- virtual void paint_event(GUI::PaintEvent&) override;
- virtual void resize_event(GUI::ResizeEvent&) override;
- virtual void timer_event(Core::TimerEvent&) override;
- virtual void mousemove_event(GUI::MouseEvent&) override;
- virtual void mousewheel_event(GUI::MouseEvent&) override;
- private:
- RefPtr<Mesh> m_mesh;
- RefPtr<Gfx::Bitmap> m_bitmap;
- OwnPtr<GL::GLContext> m_context;
- NonnullOwnPtr<WavefrontOBJLoader> m_mesh_loader;
- GLuint m_init_list { 0 };
- bool m_rotate_x = true;
- bool m_rotate_y = false;
- bool m_rotate_z = true;
- float m_angle_x = 0.0;
- float m_angle_y = 0.0;
- float m_angle_z = 0.0;
- Gfx::IntPoint m_last_mouse;
- float m_rotation_speed = 60.f;
- bool m_show_frame_rate = false;
- int m_cycles = 0;
- int m_accumulated_time = 0;
- RefPtr<GUI::Label> m_stats;
- GLint m_wrap_s_mode = GL_REPEAT;
- GLint m_wrap_t_mode = GL_REPEAT;
- float m_texture_scale = 1.0f;
- GLint m_mag_filter = GL_NEAREST;
- float m_zoom = 1;
- };
- void GLContextWidget::paint_event(GUI::PaintEvent& event)
- {
- GUI::Frame::paint_event(event);
- GUI::Painter painter(*this);
- painter.add_clip_rect(event.rect());
- painter.draw_scaled_bitmap(frame_inner_rect(), *m_bitmap, m_bitmap->rect());
- }
- void GLContextWidget::resize_event(GUI::ResizeEvent& event)
- {
- GUI::Frame::resize_event(event);
- if (m_stats)
- m_stats->set_x(width() - m_stats->width());
- };
- void GLContextWidget::mousemove_event(GUI::MouseEvent& event)
- {
- if (event.buttons() == GUI::MouseButton::Primary) {
- int delta_x = m_last_mouse.x() - event.x();
- int delta_y = m_last_mouse.y() - event.y();
- m_angle_x -= delta_y / 2.0f;
- m_angle_y -= delta_x / 2.0f;
- }
- m_last_mouse = event.position();
- }
- void GLContextWidget::mousewheel_event(GUI::MouseEvent& event)
- {
- if (event.wheel_delta() > 0)
- m_zoom /= 1.1f;
- else
- m_zoom *= 1.1f;
- }
- void GLContextWidget::timer_event(Core::TimerEvent&)
- {
- auto timer = Core::ElapsedTimer::start_new();
- glCallList(m_init_list);
- if (m_rotate_x)
- m_angle_x -= m_rotation_speed * 0.01f;
- if (m_rotate_y)
- m_angle_y -= m_rotation_speed * 0.01f;
- if (m_rotate_z)
- m_angle_z -= m_rotation_speed * 0.01f;
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
- glTranslatef(0, 0, -8.5);
- glRotatef(m_angle_x, 1, 0, 0);
- glRotatef(m_angle_y, 0, 1, 0);
- glRotatef(m_angle_z, 0, 0, 1);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, m_wrap_s_mode);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, m_wrap_t_mode);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_mag_filter);
- glScalef(m_zoom, m_zoom, m_zoom);
- if (!m_mesh.is_null())
- m_mesh->draw(m_texture_scale);
- m_context->present();
- if ((m_cycles % 30) == 0) {
- int render_time = m_accumulated_time / 30;
- int frame_rate = render_time > 0 ? 1000 / render_time : 0;
- m_stats->set_text(String::formatted("{} fps, {} ms", frame_rate, render_time));
- m_accumulated_time = 0;
- }
- update();
- m_accumulated_time += timer.elapsed();
- m_cycles++;
- }
- bool GLContextWidget::load_path(String const& filename)
- {
- auto file = Core::File::construct(filename);
- if (!file->open(Core::OpenMode::ReadOnly) && file->error() != ENOENT) {
- GUI::MessageBox::show(window(), String::formatted("Opening \"{}\" failed: {}", filename, strerror(errno)), "Error", GUI::MessageBox::Type::Error);
- return false;
- }
- return load_file(file, filename);
- }
- bool GLContextWidget::load_fd_and_close(int fd, String const& filename)
- {
- auto file = Core::File::construct();
- if (!file->open(fd, Core::OpenMode::ReadOnly, Core::File::ShouldCloseFileDescriptor::Yes) && file->error() != ENOENT) {
- GUI::MessageBox::show(window(), String::formatted("Opening \"{}\" failed: {}", filename, strerror(errno)), "Error", GUI::MessageBox::Type::Error);
- return false;
- }
- return load_file(file, filename);
- }
- bool GLContextWidget::load_file(Core::File& file, String const& filename)
- {
- if (!filename.ends_with(".obj")) {
- GUI::MessageBox::show(window(), String::formatted("Opening \"{}\" failed: invalid file type", filename), "Error", GUI::MessageBox::Type::Error);
- return false;
- }
- if (file.is_device()) {
- GUI::MessageBox::show(window(), String::formatted("Opening \"{}\" failed: Can't open device files", filename), "Error", GUI::MessageBox::Type::Error);
- return false;
- }
- if (file.is_directory()) {
- GUI::MessageBox::show(window(), String::formatted("Opening \"{}\" failed: Can't open directories", filename), "Error", GUI::MessageBox::Type::Error);
- return false;
- }
- auto new_mesh = m_mesh_loader->load(file);
- if (new_mesh.is_null()) {
- GUI::MessageBox::show(window(), String::formatted("Reading \"{}\" failed.", filename), "Error", GUI::MessageBox::Type::Error);
- return false;
- }
- // Determine whether or not a texture for this model resides within the same directory
- StringBuilder builder;
- builder.append(filename.split('.').at(0));
- builder.append(".bmp");
- String texture_path = Core::File::absolute_path(builder.string_view());
- // Attempt to open the texture file from disk
- RefPtr<Gfx::Bitmap> texture_image;
- if (Core::File::exists(texture_path)) {
- auto bitmap_or_error = Gfx::Bitmap::try_load_from_file(texture_path);
- if (!bitmap_or_error.is_error())
- texture_image = bitmap_or_error.release_value_but_fixme_should_propagate_errors();
- } else {
- auto result = FileSystemAccessClient::Client::the().request_file(window()->window_id(), builder.string_view(), Core::OpenMode::ReadOnly);
- if (result.error != 0) {
- return false;
- }
- auto bitmap_or_error = Gfx::Bitmap::try_load_from_fd_and_close(*result.fd, *result.chosen_file);
- if (!bitmap_or_error.is_error())
- texture_image = bitmap_or_error.release_value_but_fixme_should_propagate_errors();
- }
- GLuint tex;
- glGenTextures(1, &tex);
- if (texture_image) {
- // Upload texture data to the GL
- glBindTexture(GL_TEXTURE_2D, tex);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture_image->width(), texture_image->height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, texture_image->scanline(0));
- } else {
- dbgln("3DFileViewer: Couldn't load texture for {}", filename);
- }
- m_mesh = new_mesh;
- dbgln("3DFileViewer: mesh has {} triangles.", m_mesh->triangle_count());
- return true;
- }
- ErrorOr<int> serenity_main(Main::Arguments arguments)
- {
- auto app = GUI::Application::construct(arguments);
- TRY(Core::System::pledge("stdio thread recvfd sendfd rpath unix", nullptr));
- TRY(Core::System::unveil("/tmp/portal/filesystemaccess", "rw"));
- TRY(Core::System::unveil("/home/anon/Documents/3D Models/teapot.obj", "r"));
- TRY(Core::System::unveil("/home/anon/Documents/3D Models/teapot.bmp", "r"));
- TRY(Core::System::unveil("/res", "r"));
- TRY(Core::System::unveil(nullptr, nullptr));
- // Construct the main window
- auto window = GUI::Window::construct();
- auto app_icon = GUI::Icon::default_icon("app-3d-file-viewer");
- window->set_icon(app_icon.bitmap_for_size(16));
- window->set_title("3D File Viewer");
- window->resize(640 + 4, 480 + 4);
- window->set_resizable(false);
- window->set_double_buffering_enabled(true);
- auto& widget = window->set_main_widget<GLContextWidget>();
- auto& time = widget.add<GUI::Label>();
- time.set_visible(false);
- time.set_foreground_role(ColorRole::HoverHighlight);
- time.set_relative_rect({ 0, 8, 86, 10 });
- time.set_x(widget.width() - time.width());
- widget.set_stat_label(time);
- auto& file_menu = window->add_menu("&File");
- file_menu.add_action(GUI::CommonActions::make_open_action([&](auto&) {
- auto result = FileSystemAccessClient::Client::the().open_file(window->window_id());
- if (result.error != 0)
- return;
- if (widget.load_fd_and_close(*result.fd, *result.chosen_file)) {
- auto canonical_path = Core::File::absolute_path(*result.chosen_file);
- window->set_title(String::formatted("{} - 3D File Viewer", canonical_path));
- }
- }));
- file_menu.add_separator();
- file_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) {
- app->quit();
- }));
- auto& view_menu = window->add_menu("&View");
- view_menu.add_action(GUI::CommonActions::make_fullscreen_action([&](auto&) {
- window->set_fullscreen(!window->is_fullscreen());
- }));
- auto& rotation_axis_menu = view_menu.add_submenu("Rotation &Axis");
- auto rotation_x_action = GUI::Action::create_checkable("&X", [&widget](auto&) {
- widget.toggle_rotate_x();
- });
- auto rotation_y_action = GUI::Action::create_checkable("&Y", [&widget](auto&) {
- widget.toggle_rotate_y();
- });
- auto rotation_z_action = GUI::Action::create_checkable("&Z", [&widget](auto&) {
- widget.toggle_rotate_z();
- });
- rotation_axis_menu.add_action(*rotation_x_action);
- rotation_axis_menu.add_action(*rotation_y_action);
- rotation_axis_menu.add_action(*rotation_z_action);
- rotation_x_action->set_checked(true);
- rotation_z_action->set_checked(true);
- auto& rotation_speed_menu = view_menu.add_submenu("Rotation &Speed");
- GUI::ActionGroup rotation_speed_actions;
- rotation_speed_actions.set_exclusive(true);
- auto no_rotation_action = GUI::Action::create_checkable("N&o Rotation", [&widget](auto&) {
- widget.set_rotation_speed(0.f);
- });
- auto slow_rotation_action = GUI::Action::create_checkable("&Slow", [&widget](auto&) {
- widget.set_rotation_speed(30.f);
- });
- auto normal_rotation_action = GUI::Action::create_checkable("&Normal", [&widget](auto&) {
- widget.set_rotation_speed(60.f);
- });
- auto fast_rotation_action = GUI::Action::create_checkable("&Fast", [&widget](auto&) {
- widget.set_rotation_speed(90.f);
- });
- rotation_speed_actions.add_action(*no_rotation_action);
- rotation_speed_actions.add_action(*slow_rotation_action);
- rotation_speed_actions.add_action(*normal_rotation_action);
- rotation_speed_actions.add_action(*fast_rotation_action);
- rotation_speed_menu.add_action(*no_rotation_action);
- rotation_speed_menu.add_action(*slow_rotation_action);
- rotation_speed_menu.add_action(*normal_rotation_action);
- rotation_speed_menu.add_action(*fast_rotation_action);
- normal_rotation_action->set_checked(true);
- auto show_frame_rate_action = GUI::Action::create_checkable("Show Frame &Rate", [&widget](auto&) {
- widget.toggle_show_frame_rate();
- });
- view_menu.add_action(*show_frame_rate_action);
- auto& texture_menu = window->add_menu("&Texture");
- auto& wrap_u_menu = texture_menu.add_submenu("Wrap &S");
- GUI::ActionGroup wrap_s_actions;
- wrap_s_actions.set_exclusive(true);
- auto wrap_u_repeat_action = GUI::Action::create_checkable("&Repeat", [&widget](auto&) {
- widget.set_wrap_s_mode(GL_REPEAT);
- });
- auto wrap_u_mirrored_repeat_action = GUI::Action::create_checkable("&Mirrored Repeat", [&widget](auto&) {
- widget.set_wrap_s_mode(GL_MIRRORED_REPEAT);
- });
- auto wrap_u_clamp_action = GUI::Action::create_checkable("&Clamp", [&widget](auto&) {
- widget.set_wrap_s_mode(GL_CLAMP);
- });
- wrap_s_actions.add_action(*wrap_u_repeat_action);
- wrap_s_actions.add_action(*wrap_u_mirrored_repeat_action);
- wrap_s_actions.add_action(*wrap_u_clamp_action);
- wrap_u_menu.add_action(*wrap_u_repeat_action);
- wrap_u_menu.add_action(*wrap_u_mirrored_repeat_action);
- wrap_u_menu.add_action(*wrap_u_clamp_action);
- wrap_u_repeat_action->set_checked(true);
- auto& wrap_t_menu = texture_menu.add_submenu("Wrap &T");
- GUI::ActionGroup wrap_t_actions;
- wrap_t_actions.set_exclusive(true);
- auto wrap_t_repeat_action = GUI::Action::create_checkable("&Repeat", [&widget](auto&) {
- widget.set_wrap_t_mode(GL_REPEAT);
- });
- auto wrap_t_mirrored_repeat_action = GUI::Action::create_checkable("&Mirrored Repeat", [&widget](auto&) {
- widget.set_wrap_t_mode(GL_MIRRORED_REPEAT);
- });
- auto wrap_t_clamp_action = GUI::Action::create_checkable("&Clamp", [&widget](auto&) {
- widget.set_wrap_t_mode(GL_CLAMP);
- });
- wrap_t_actions.add_action(*wrap_t_repeat_action);
- wrap_t_actions.add_action(*wrap_t_mirrored_repeat_action);
- wrap_t_actions.add_action(*wrap_t_clamp_action);
- wrap_t_menu.add_action(*wrap_t_repeat_action);
- wrap_t_menu.add_action(*wrap_t_mirrored_repeat_action);
- wrap_t_menu.add_action(*wrap_t_clamp_action);
- wrap_t_repeat_action->set_checked(true);
- auto& texture_scale_menu = texture_menu.add_submenu("S&cale");
- GUI::ActionGroup texture_scale_actions;
- texture_scale_actions.set_exclusive(true);
- auto texture_scale_025_action = GUI::Action::create_checkable("0.25x", [&widget](auto&) {
- widget.set_texture_scale(0.25f);
- });
- auto texture_scale_05_action = GUI::Action::create_checkable("0.5x", [&widget](auto&) {
- widget.set_texture_scale(0.5f);
- });
- auto texture_scale_1_action = GUI::Action::create_checkable("1x", [&widget](auto&) {
- widget.set_texture_scale(1);
- });
- auto texture_scale_2_action = GUI::Action::create_checkable("2x", [&widget](auto&) {
- widget.set_texture_scale(2);
- });
- auto texture_scale_4_action = GUI::Action::create_checkable("4x", [&widget](auto&) {
- widget.set_texture_scale(4);
- });
- texture_scale_actions.add_action(*texture_scale_025_action);
- texture_scale_actions.add_action(*texture_scale_05_action);
- texture_scale_actions.add_action(*texture_scale_1_action);
- texture_scale_actions.add_action(*texture_scale_2_action);
- texture_scale_actions.add_action(*texture_scale_4_action);
- texture_scale_menu.add_action(*texture_scale_025_action);
- texture_scale_menu.add_action(*texture_scale_05_action);
- texture_scale_menu.add_action(*texture_scale_1_action);
- texture_scale_menu.add_action(*texture_scale_2_action);
- texture_scale_menu.add_action(*texture_scale_4_action);
- texture_scale_1_action->set_checked(true);
- auto& texture_mag_filter_menu = texture_menu.add_submenu("Mag Filter");
- GUI::ActionGroup texture_mag_filter_actions;
- texture_mag_filter_actions.set_exclusive(true);
- auto texture_mag_filter_nearest_action = GUI::Action::create_checkable("&Nearest", [&widget](auto&) {
- widget.set_mag_filter(GL_NEAREST);
- });
- auto texture_mag_filter_linear_action = GUI::Action::create_checkable("&Linear", [&widget](auto&) {
- widget.set_mag_filter(GL_LINEAR);
- });
- texture_mag_filter_actions.add_action(*texture_mag_filter_nearest_action);
- texture_mag_filter_actions.add_action(*texture_mag_filter_linear_action);
- texture_mag_filter_menu.add_action(*texture_mag_filter_nearest_action);
- texture_mag_filter_menu.add_action(*texture_mag_filter_linear_action);
- texture_mag_filter_nearest_action->set_checked(true);
- auto& help_menu = window->add_menu("&Help");
- help_menu.add_action(GUI::CommonActions::make_about_action("3D File Viewer", app_icon, window));
- window->show();
- auto filename = arguments.argc > 1 ? arguments.argv[1] : "/home/anon/Documents/3D Models/teapot.obj";
- if (widget.load_path(filename)) {
- auto canonical_path = Core::File::absolute_path(filename);
- window->set_title(String::formatted("{} - 3D File Viewer", canonical_path));
- }
- return app->exec();
- }
|