ladybird/Userland/Applications/3DFileViewer/main.cpp
Ali Mohammad Pur 51c2c69357 AK+Everywhere: Disallow constructing Functions from incompatible types
Previously, AK::Function would accept _any_ callable type, and try to
call it when called, first with the given set of arguments, then with
zero arguments, and if all of those failed, it would simply not call the
function and **return a value-constructed Out type**.
This lead to many, many, many hard to debug situations when someone
forgot a `const` in their lambda argument types, and many cases of
people taking zero arguments in their lambdas to ignore them.
This commit reworks the Function interface to not include any such
surprising behaviour, if your function instance is not callable with
the declared argument set of the Function, it can simply not be
assigned to that Function instance, end of story.
2021-06-06 00:27:30 +04:30

272 lines
8.9 KiB
C++

/*
* Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCore/ElapsedTimer.h>
#include <LibCore/File.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/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 <unistd.h>
#include "Mesh.h"
#include "MeshLoader.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(const String& 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; }
private:
GLContextWidget()
: m_mesh_loader(adopt_own(*new WavefrontOBJLoader()))
{
m_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, { RENDER_WIDTH, RENDER_HEIGHT });
m_context = GL::create_context(*m_bitmap);
start_timer(20);
GL::make_context_current(m_context);
glFrontFace(GL_CW);
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 timer_event(Core::TimerEvent&) 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_rotation_speed = 1.f;
};
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::timer_event(Core::TimerEvent&)
{
static float angle = 0.0f;
glCallList(m_init_list);
angle -= 0.01f;
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0, 0, -8.5);
glRotatef(m_rotate_x ? angle * m_rotation_speed : 0.f, 1, 0, 0);
glRotatef(m_rotate_y ? angle * m_rotation_speed : 0.f, 0, 1, 0);
glRotatef(m_rotate_z ? angle * m_rotation_speed : 0.f, 0, 0, 1);
if (!m_mesh.is_null())
m_mesh->draw();
m_context->present();
update();
}
bool GLContextWidget::load(const String& filename)
{
auto file = Core::File::construct(filename);
if (!file->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->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;
}
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");
// Attempt to open the texture file from disk
auto texture_image = Gfx::Bitmap::load_from_file(builder.string_view());
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_RGBA, 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;
}
int main(int argc, char** argv)
{
auto app = GUI::Application::construct(argc, argv);
if (pledge("stdio thread recvfd sendfd rpath", nullptr) < 0) {
perror("pledge");
return 1;
}
// 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 menubar = GUI::Menubar::construct();
auto& file_menu = menubar->add_menu("&File");
auto load_model = [&](StringView const& filename) {
if (widget.load(filename)) {
auto canonical_path = Core::File::real_path_for(filename);
window->set_title(String::formatted("{} - 3D File Viewer", canonical_path));
}
};
file_menu.add_action(GUI::CommonActions::make_open_action([&](auto&) {
Optional<String> open_path = GUI::FilePicker::get_open_filepath(window);
if (!open_path.has_value())
return;
load_model(open_path.value());
}));
file_menu.add_separator();
file_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) {
app->quit();
}));
auto& view_menu = menubar->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(0.5f);
});
auto normal_rotation_action = GUI::Action::create_checkable("&Normal", [&widget](auto&) {
widget.set_rotation_speed(1.f);
});
auto fast_rotation_action = GUI::Action::create_checkable("&Fast", [&widget](auto&) {
widget.set_rotation_speed(1.5f);
});
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& help_menu = menubar->add_menu("&Help");
help_menu.add_action(GUI::CommonActions::make_about_action("3D File Viewer", app_icon, window));
window->set_menubar(move(menubar));
window->show();
auto filename = argc > 1 ? argv[1] : "/home/anon/Documents/3D Models/teapot.obj";
load_model(filename);
return app->exec();
}