main.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /*
  2. * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibCore/ElapsedTimer.h>
  7. #include <LibCore/File.h>
  8. #include <LibGL/GL/gl.h>
  9. #include <LibGL/GLContext.h>
  10. #include <LibGUI/ActionGroup.h>
  11. #include <LibGUI/Application.h>
  12. #include <LibGUI/FilePicker.h>
  13. #include <LibGUI/Icon.h>
  14. #include <LibGUI/Menu.h>
  15. #include <LibGUI/Menubar.h>
  16. #include <LibGUI/MessageBox.h>
  17. #include <LibGUI/Painter.h>
  18. #include <LibGUI/Widget.h>
  19. #include <LibGUI/Window.h>
  20. #include <LibGfx/Bitmap.h>
  21. #include <unistd.h>
  22. #include "Mesh.h"
  23. #include "MeshLoader.h"
  24. #include "WavefrontOBJLoader.h"
  25. static constexpr u16 RENDER_WIDTH = 640;
  26. static constexpr u16 RENDER_HEIGHT = 480;
  27. class GLContextWidget final : public GUI::Frame {
  28. C_OBJECT(GLContextWidget);
  29. public:
  30. bool load(const String& fname);
  31. void toggle_rotate_x() { m_rotate_x = !m_rotate_x; }
  32. void toggle_rotate_y() { m_rotate_y = !m_rotate_y; }
  33. void toggle_rotate_z() { m_rotate_z = !m_rotate_z; }
  34. void set_rotation_speed(float speed) { m_rotation_speed = speed; }
  35. private:
  36. GLContextWidget()
  37. : m_mesh_loader(adopt_own(*new WavefrontOBJLoader()))
  38. {
  39. m_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, { RENDER_WIDTH, RENDER_HEIGHT });
  40. m_context = GL::create_context(*m_bitmap);
  41. start_timer(20);
  42. GL::make_context_current(m_context);
  43. glFrontFace(GL_CW);
  44. glEnable(GL_CULL_FACE);
  45. glEnable(GL_DEPTH_TEST);
  46. // Set projection matrix
  47. glMatrixMode(GL_PROJECTION);
  48. glLoadIdentity();
  49. glFrustum(-0.5, 0.5, -0.5, 0.5, 1, 1500);
  50. m_init_list = glGenLists(1);
  51. glNewList(m_init_list, GL_COMPILE);
  52. {
  53. glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  54. glClearDepth(1.0);
  55. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  56. }
  57. glEndList();
  58. }
  59. virtual void paint_event(GUI::PaintEvent&) override;
  60. virtual void timer_event(Core::TimerEvent&) override;
  61. private:
  62. RefPtr<Mesh> m_mesh;
  63. RefPtr<Gfx::Bitmap> m_bitmap;
  64. OwnPtr<GL::GLContext> m_context;
  65. NonnullOwnPtr<WavefrontOBJLoader> m_mesh_loader;
  66. GLuint m_init_list { 0 };
  67. bool m_rotate_x = true;
  68. bool m_rotate_y = false;
  69. bool m_rotate_z = true;
  70. float m_rotation_speed = 1.f;
  71. };
  72. void GLContextWidget::paint_event(GUI::PaintEvent& event)
  73. {
  74. GUI::Frame::paint_event(event);
  75. GUI::Painter painter(*this);
  76. painter.add_clip_rect(event.rect());
  77. painter.draw_scaled_bitmap(frame_inner_rect(), *m_bitmap, m_bitmap->rect());
  78. }
  79. void GLContextWidget::timer_event(Core::TimerEvent&)
  80. {
  81. static float angle = 0.0f;
  82. glCallList(m_init_list);
  83. angle -= 0.01f;
  84. glMatrixMode(GL_MODELVIEW);
  85. glLoadIdentity();
  86. glTranslatef(0, 0, -8.5);
  87. glRotatef(m_rotate_x ? angle * m_rotation_speed : 0.f, 1, 0, 0);
  88. glRotatef(m_rotate_y ? angle * m_rotation_speed : 0.f, 0, 1, 0);
  89. glRotatef(m_rotate_z ? angle * m_rotation_speed : 0.f, 0, 0, 1);
  90. if (!m_mesh.is_null())
  91. m_mesh->draw();
  92. m_context->present();
  93. update();
  94. }
  95. bool GLContextWidget::load(const String& filename)
  96. {
  97. auto file = Core::File::construct(filename);
  98. if (!file->filename().ends_with(".obj")) {
  99. GUI::MessageBox::show(window(), String::formatted("Opening \"{}\" failed: invalid file type", filename), "Error", GUI::MessageBox::Type::Error);
  100. return false;
  101. }
  102. if (!file->open(Core::OpenMode::ReadOnly) && file->error() != ENOENT) {
  103. GUI::MessageBox::show(window(), String::formatted("Opening \"{}\" failed: {}", filename, strerror(errno)), "Error", GUI::MessageBox::Type::Error);
  104. return false;
  105. }
  106. if (file->is_device()) {
  107. GUI::MessageBox::show(window(), String::formatted("Opening \"{}\" failed: Can't open device files", filename), "Error", GUI::MessageBox::Type::Error);
  108. return false;
  109. }
  110. if (file->is_directory()) {
  111. GUI::MessageBox::show(window(), String::formatted("Opening \"{}\" failed: Can't open directories", filename), "Error", GUI::MessageBox::Type::Error);
  112. return false;
  113. }
  114. auto new_mesh = m_mesh_loader->load(file);
  115. if (new_mesh.is_null()) {
  116. GUI::MessageBox::show(window(), String::formatted("Reading \"{}\" failed.", filename), "Error", GUI::MessageBox::Type::Error);
  117. return false;
  118. }
  119. // Determine whether or not a texture for this model resides within the same directory
  120. StringBuilder builder;
  121. builder.append(filename.split('.').at(0));
  122. builder.append(".bmp");
  123. // Attempt to open the texture file from disk
  124. auto texture_image = Gfx::Bitmap::load_from_file(builder.string_view());
  125. GLuint tex;
  126. glGenTextures(1, &tex);
  127. if (texture_image) {
  128. // Upload texture data to the GL
  129. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture_image->width(), texture_image->height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, texture_image->scanline(0));
  130. } else {
  131. dbgln("3DFileViewer: Couldn't load texture for {}", filename);
  132. }
  133. m_mesh = new_mesh;
  134. dbgln("3DFileViewer: mesh has {} triangles.", m_mesh->triangle_count());
  135. return true;
  136. }
  137. int main(int argc, char** argv)
  138. {
  139. auto app = GUI::Application::construct(argc, argv);
  140. if (pledge("stdio thread recvfd sendfd rpath", nullptr) < 0) {
  141. perror("pledge");
  142. return 1;
  143. }
  144. // Construct the main window
  145. auto window = GUI::Window::construct();
  146. auto app_icon = GUI::Icon::default_icon("app-3d-file-viewer");
  147. window->set_icon(app_icon.bitmap_for_size(16));
  148. window->set_title("3D File Viewer");
  149. window->resize(640 + 4, 480 + 4);
  150. window->set_resizable(false);
  151. window->set_double_buffering_enabled(true);
  152. auto& widget = window->set_main_widget<GLContextWidget>();
  153. auto menubar = GUI::Menubar::construct();
  154. auto& file_menu = menubar->add_menu("&File");
  155. auto load_model = [&](StringView const& filename) {
  156. if (widget.load(filename)) {
  157. auto canonical_path = Core::File::real_path_for(filename);
  158. window->set_title(String::formatted("{} - 3D File Viewer", canonical_path));
  159. }
  160. };
  161. file_menu.add_action(GUI::CommonActions::make_open_action([&](auto&) {
  162. Optional<String> open_path = GUI::FilePicker::get_open_filepath(window);
  163. if (!open_path.has_value())
  164. return;
  165. load_model(open_path.value());
  166. }));
  167. file_menu.add_action(GUI::CommonActions::make_quit_action([&] {
  168. app->quit();
  169. }));
  170. auto& view_menu = menubar->add_menu("&View");
  171. view_menu.add_action(GUI::CommonActions::make_fullscreen_action([&] {
  172. window->set_fullscreen(!window->is_fullscreen());
  173. }));
  174. auto& rotation_axis_menu = view_menu.add_submenu("Rotation &Axis");
  175. auto rotation_x_action = GUI::Action::create_checkable("&X", [&widget] {
  176. widget.toggle_rotate_x();
  177. });
  178. auto rotation_y_action = GUI::Action::create_checkable("&Y", [&widget] {
  179. widget.toggle_rotate_y();
  180. });
  181. auto rotation_z_action = GUI::Action::create_checkable("&Z", [&widget] {
  182. widget.toggle_rotate_z();
  183. });
  184. rotation_axis_menu.add_action(*rotation_x_action);
  185. rotation_axis_menu.add_action(*rotation_y_action);
  186. rotation_axis_menu.add_action(*rotation_z_action);
  187. rotation_x_action->set_checked(true);
  188. rotation_z_action->set_checked(true);
  189. auto& rotation_speed_menu = view_menu.add_submenu("Rotation &Speed");
  190. GUI::ActionGroup rotation_speed_actions;
  191. rotation_speed_actions.set_exclusive(true);
  192. auto no_rotation_action = GUI::Action::create_checkable("N&o Rotation", [&widget] {
  193. widget.set_rotation_speed(0.f);
  194. });
  195. auto slow_rotation_action = GUI::Action::create_checkable("&Slow", [&widget] {
  196. widget.set_rotation_speed(0.5f);
  197. });
  198. auto normal_rotation_action = GUI::Action::create_checkable("&Normal", [&widget] {
  199. widget.set_rotation_speed(1.f);
  200. });
  201. auto fast_rotation_action = GUI::Action::create_checkable("&Fast", [&widget] {
  202. widget.set_rotation_speed(1.5f);
  203. });
  204. rotation_speed_actions.add_action(*no_rotation_action);
  205. rotation_speed_actions.add_action(*slow_rotation_action);
  206. rotation_speed_actions.add_action(*normal_rotation_action);
  207. rotation_speed_actions.add_action(*fast_rotation_action);
  208. rotation_speed_menu.add_action(*no_rotation_action);
  209. rotation_speed_menu.add_action(*slow_rotation_action);
  210. rotation_speed_menu.add_action(*normal_rotation_action);
  211. rotation_speed_menu.add_action(*fast_rotation_action);
  212. normal_rotation_action->set_checked(true);
  213. auto& help_menu = menubar->add_menu("&Help");
  214. help_menu.add_action(GUI::CommonActions::make_about_action("3D File Viewer", app_icon, window));
  215. window->set_menubar(move(menubar));
  216. window->show();
  217. auto filename = argc > 1 ? argv[1] : "/home/anon/Documents/3D Models/teapot.obj";
  218. load_model(filename);
  219. return app->exec();
  220. }