summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
authorJesse Buhagiar <jooster669@gmail.com>2021-05-03 00:19:50 +1000
committerAndreas Kling <kling@serenityos.org>2021-05-08 10:13:22 +0200
commit834f3c64f0b54d14d8d5a39d670934d3a64a7f80 (patch)
treea9f619439fcd7294b0fded0e04bc839c2a3f7ea3 /Userland
parent5ff248649bc171fe360e7c188aaab62c21f89639 (diff)
downloadserenity-834f3c64f0b54d14d8d5a39d670934d3a64a7f80.zip
Demos: Add OpenGL teapot demo :^)
Every GL library needs an implementation of this! Currently drawn with "pixel vomit" colours as we don't yet support lighting via the GL library. This also ships with a super basic Wavefront OBJ loader.
Diffstat (limited to 'Userland')
-rw-r--r--Userland/Demos/CMakeLists.txt1
-rw-r--r--Userland/Demos/GLTeapot/CMakeLists.txt8
-rw-r--r--Userland/Demos/GLTeapot/Common.h23
-rw-r--r--Userland/Demos/GLTeapot/Mesh.cpp42
-rw-r--r--Userland/Demos/GLTeapot/Mesh.h29
-rw-r--r--Userland/Demos/GLTeapot/MeshLoader.h20
-rw-r--r--Userland/Demos/GLTeapot/WavefrontOBJLoader.cpp59
-rw-r--r--Userland/Demos/GLTeapot/WavefrontOBJLoader.h21
-rw-r--r--Userland/Demos/GLTeapot/main.cpp126
9 files changed, 329 insertions, 0 deletions
diff --git a/Userland/Demos/CMakeLists.txt b/Userland/Demos/CMakeLists.txt
index 2239368dac..0d150f24bc 100644
--- a/Userland/Demos/CMakeLists.txt
+++ b/Userland/Demos/CMakeLists.txt
@@ -2,6 +2,7 @@ add_subdirectory(CatDog)
add_subdirectory(Cube)
add_subdirectory(Eyes)
add_subdirectory(Fire)
+add_subdirectory(GLTeapot)
add_subdirectory(LibGfxDemo)
add_subdirectory(LibGfxScaleDemo)
add_subdirectory(Mouse)
diff --git a/Userland/Demos/GLTeapot/CMakeLists.txt b/Userland/Demos/GLTeapot/CMakeLists.txt
new file mode 100644
index 0000000000..ff6420a889
--- /dev/null
+++ b/Userland/Demos/GLTeapot/CMakeLists.txt
@@ -0,0 +1,8 @@
+set(SOURCES
+ Mesh.cpp
+ WavefrontOBJLoader.cpp
+ main.cpp
+)
+
+serenity_app(GLTeapot ICON app-teapot)
+target_link_libraries(GLTeapot LibGUI LibGL)
diff --git a/Userland/Demos/GLTeapot/Common.h b/Userland/Demos/GLTeapot/Common.h
new file mode 100644
index 0000000000..feee346014
--- /dev/null
+++ b/Userland/Demos/GLTeapot/Common.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibGL/GL/gl.h>
+
+// Point in 3D space
+struct Vertex {
+ GLfloat x;
+ GLfloat y;
+ GLfloat z;
+};
+
+// A triangle defines a single "face" of a mesh
+struct Triangle {
+ Vertex a;
+ Vertex b;
+ Vertex c;
+};
diff --git a/Userland/Demos/GLTeapot/Mesh.cpp b/Userland/Demos/GLTeapot/Mesh.cpp
new file mode 100644
index 0000000000..89329186a4
--- /dev/null
+++ b/Userland/Demos/GLTeapot/Mesh.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibGL/GL/gl.h>
+#include <LibGfx/Color.h>
+#include <stdlib.h>
+
+#include "Mesh.h"
+
+const Color colors[] {
+ Color::Red,
+ Color::Green,
+ Color::Blue,
+ Color::Blue,
+ Color::Magenta,
+ Color::White,
+ Color::Yellow,
+};
+
+void Mesh::draw()
+{
+ u32 color_index = 0;
+ Color cur_color;
+
+ for (const auto& triangle : m_triangle_list) {
+ cur_color = colors[color_index];
+
+ glBegin(GL_TRIANGLES);
+ glColor4ub(cur_color.red(), cur_color.green(), cur_color.blue(), 255);
+
+ glVertex3f(triangle.a.x, triangle.a.y, triangle.a.z); // Vertex 1
+ glVertex3f(triangle.b.x, triangle.b.y, triangle.b.z); // Vertex 2
+ glVertex3f(triangle.c.x, triangle.c.y, triangle.c.z); // Vertex 3
+
+ glEnd();
+
+ color_index = ((color_index + 1) % 7);
+ }
+}
diff --git a/Userland/Demos/GLTeapot/Mesh.h b/Userland/Demos/GLTeapot/Mesh.h
new file mode 100644
index 0000000000..f0e2808f08
--- /dev/null
+++ b/Userland/Demos/GLTeapot/Mesh.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/RefCounted.h>
+#include <AK/Vector.h>
+
+#include "Common.h"
+
+// NOTE: We don't support indexed
+class Mesh : public RefCounted<Mesh> {
+public:
+ Mesh() = delete;
+ Mesh(const Vector<Triangle>& triangles)
+ : m_triangle_list(triangles)
+ {
+ }
+
+ void draw();
+
+ size_t triangle_count() const { return m_triangle_list.size(); }
+
+private:
+ Vector<Triangle> m_triangle_list;
+};
diff --git a/Userland/Demos/GLTeapot/MeshLoader.h b/Userland/Demos/GLTeapot/MeshLoader.h
new file mode 100644
index 0000000000..fd62d49e64
--- /dev/null
+++ b/Userland/Demos/GLTeapot/MeshLoader.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/String.h>
+
+#include "Common.h"
+#include "Mesh.h"
+
+class MeshLoader {
+public:
+ MeshLoader() { }
+ virtual ~MeshLoader() { }
+
+ virtual RefPtr<Mesh> load(const String& fname) = 0;
+};
diff --git a/Userland/Demos/GLTeapot/WavefrontOBJLoader.cpp b/Userland/Demos/GLTeapot/WavefrontOBJLoader.cpp
new file mode 100644
index 0000000000..677770ff03
--- /dev/null
+++ b/Userland/Demos/GLTeapot/WavefrontOBJLoader.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "WavefrontOBJLoader.h"
+#include <LibCore/File.h>
+#include <stdlib.h>
+
+RefPtr<Mesh> WavefrontOBJLoader::load(const String& fname)
+{
+ auto obj_file_or_error = Core::File::open(fname, Core::IODevice::OpenMode::ReadOnly);
+ Vector<Vertex> vertices;
+ Vector<Triangle> triangles;
+
+ dbgln("Wavefront: Loading {}...", fname);
+
+ if (obj_file_or_error.is_error())
+ return nullptr;
+
+ // Start reading file line by line
+ for (auto line = obj_file_or_error.value()->line_begin(); !line.at_end(); ++line) {
+ auto object_line = *line;
+
+ // This line describes a vertex (a position in 3D space)
+ if (object_line.starts_with("v")) {
+ auto vertex_line = object_line.split_view(' ');
+ if (vertex_line.size() != 4) {
+ dbgln("Wavefront: Malformed vertex line. Aborting.");
+ return nullptr;
+ }
+
+ vertices.append(
+ { static_cast<GLfloat>(atof(String(vertex_line.at(1)).characters())),
+ static_cast<GLfloat>(atof(String(vertex_line.at(2)).characters())),
+ static_cast<GLfloat>(atof(String(vertex_line.at(3)).characters())) });
+ }
+ // This line describes a face (a collection of 3 vertices, aka a triangle)
+ else if (object_line.starts_with("f")) {
+ auto face_line = object_line.split_view(' ');
+ if (face_line.size() != 4) {
+ dbgln("Wavefront: Malformed face line. Aborting.");
+ return nullptr;
+ }
+
+ // Create a new triangle
+ triangles.append(
+ {
+ vertices.at(face_line.at(1).to_uint().value() - 1),
+ vertices.at(face_line.at(2).to_uint().value() - 1),
+ vertices.at(face_line.at(3).to_uint().value() - 1),
+ });
+ }
+ }
+
+ dbgln("Wavefront: Done.");
+ return adopt_ref(*new Mesh(triangles));
+}
diff --git a/Userland/Demos/GLTeapot/WavefrontOBJLoader.h b/Userland/Demos/GLTeapot/WavefrontOBJLoader.h
new file mode 100644
index 0000000000..92ef601577
--- /dev/null
+++ b/Userland/Demos/GLTeapot/WavefrontOBJLoader.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/RefCounted.h>
+#include <AK/RefPtr.h>
+
+#include "Mesh.h"
+#include "MeshLoader.h"
+
+class WavefrontOBJLoader : public MeshLoader {
+public:
+ WavefrontOBJLoader() { }
+ ~WavefrontOBJLoader() override { }
+
+ RefPtr<Mesh> load(const String& fname) override;
+};
diff --git a/Userland/Demos/GLTeapot/main.cpp b/Userland/Demos/GLTeapot/main.cpp
new file mode 100644
index 0000000000..0107db298f
--- /dev/null
+++ b/Userland/Demos/GLTeapot/main.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibCore/ElapsedTimer.h>
+#include <LibGL/GL/gl.h>
+#include <LibGL/GLContext.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/Icon.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::Widget {
+ C_OBJECT(GLContextWidget)
+public:
+private:
+ GLContextWidget()
+ {
+ 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);
+
+ // Set projection matrix
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glFrustum(-0.5, 0.5, -0.5, 0.5, 1, 1500);
+
+ // Load the teapot
+ auto mesh_loader = adopt_own(*new WavefrontOBJLoader());
+ m_teapot = mesh_loader->load("/res/gl/teapot.obj");
+
+ dbgln("GLTeapot: teapot mesh has {} triangles.", m_teapot->triangle_count());
+ }
+
+ virtual void paint_event(GUI::PaintEvent&) override;
+ virtual void timer_event(Core::TimerEvent&) override;
+
+private:
+ RefPtr<Mesh> m_teapot;
+ RefPtr<Gfx::Bitmap> m_bitmap;
+ OwnPtr<GL::GLContext> m_context;
+};
+
+void GLContextWidget::paint_event(GUI::PaintEvent& event)
+{
+ GUI::Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ /* Blit it! */
+ painter.draw_scaled_bitmap(event.rect(), *m_bitmap, m_bitmap->rect());
+}
+
+void GLContextWidget::timer_event(Core::TimerEvent&)
+{
+ static float angle = 0.0f;
+
+ angle -= 0.01f;
+
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ auto matrix = FloatMatrix4x4::translate(FloatVector3(0, 0, -8.5))
+ * FloatMatrix4x4::rotate(FloatVector3(1, 0, 0), angle)
+ * FloatMatrix4x4::rotate(FloatVector3(0, 1, 0), 0.0f)
+ * FloatMatrix4x4::rotate(FloatVector3(0, 0, 1), angle);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadMatrixf((float*)matrix.elements());
+
+ m_teapot->draw();
+
+ m_context->present();
+ update();
+}
+
+int main(int argc, char** argv)
+{
+ auto app = GUI::Application::construct(argc, argv);
+
+ if (pledge("stdio recvfd sendfd rpath", nullptr) < 0) {
+ perror("pledge");
+ return 1;
+ }
+
+ if (unveil("/res", "r") < 0) {
+ perror("unveil");
+ return 1;
+ }
+
+ if (unveil(nullptr, nullptr) < 0) {
+ perror("unveil");
+ return 1;
+ }
+
+ // Construct the main window
+ auto window = GUI::Window::construct();
+ auto app_icon = GUI::Icon::default_icon("app-teapot");
+
+ window->set_icon(app_icon.bitmap_for_size(16));
+ window->set_title("GLTeapot");
+ window->resize(640, 480);
+ window->set_resizable(false);
+ window->set_double_buffering_enabled(true);
+ window->set_main_widget<GLContextWidget>();
+
+ window->show();
+
+ return app->exec();
+}