From 30ddaf7d39452ae25c4c043595c8e486038f65f1 Mon Sep 17 00:00:00 2001 From: portersky <24420859+portersky@users.noreply.github.com> Date: Tue, 5 May 2026 22:19:33 +0200 Subject: [PATCH] feat: render spinning 3D cube with glm - Add glm dependency for matrix transformations - Replace triangle with 6-face colored cube - Add MVP shader uniforms (model, view, projection) - Enable depth testing for proper 3D rendering - Spin cube around (1, 0.5, 0.3) axis style: fix trailing return type for move assignment operators - buffer/texture/shader/vao: use auto fn() -> T& style - Document trailing return type convention in AGENTS.md --- AGENTS.md | 3 +- CMakeLists.txt | 3 +- cbt/opengl/buffer.cpp | 2 +- cbt/opengl/shader.cpp | 2 +- cbt/opengl/texture.cpp | 2 +- cbt/opengl/vao.cpp | 2 +- cuber.cpp | 95 ++++++++++++++++++++++++++++++++++++------ 7 files changed, 91 insertions(+), 18 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index bf08955..7479557 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,11 +38,12 @@ to the custom scripts instead of system-installed packages. ## Coding Conventions - **Language:** C++23 -- **Trailing return type** for function signatures +- **Trailing return type** for function signatures (e.g. `auto fn() -> void`) - **4-space indentation** - **No semicolons after closing braces** for namespaces/classes - `auto` for obvious types (e.g. `auto main(...) -> int`) - **East const** (e.g. `char const*` not `const char*`) +- **Trailing return type** for all function definitions, including operators (e.g. `auto operator=(T&&) noexcept -> T&`) - `<>` includes only for system headers (std, OS, etc.) - `""` includes for third-party dependencies (e.g. `fmt`, `nlohmann/json`) - **Naming:** `snake_case` for variables, functions, and classes diff --git a/CMakeLists.txt b/CMakeLists.txt index c74db0c..d3571b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ find_package(fmt REQUIRED) find_package(glfw3 REQUIRED) find_package(glad REQUIRED) find_package(asio REQUIRED) +find_package(glm REQUIRED) # Target setup add_executable(cuber "cuber.cpp" @@ -30,4 +31,4 @@ target_include_directories(cuber PRIVATE ".") target_compile_features(cuber PRIVATE cxx_std_23) target_compile_options(cuber PRIVATE ${BASE_OPTIONS}) target_compile_definitions(cuber PRIVATE ${BASE_DEFINITIONS}) -target_link_libraries(cuber PRIVATE fmt::fmt glfw::glfw glad::glad asio::asio ${BASE_LIBRARIES}) +target_link_libraries(cuber PRIVATE fmt::fmt glfw::glfw glad::glad asio::asio glm::glm ${BASE_LIBRARIES}) diff --git a/cbt/opengl/buffer.cpp b/cbt/opengl/buffer.cpp index 1cd9766..38fa22e 100644 --- a/cbt/opengl/buffer.cpp +++ b/cbt/opengl/buffer.cpp @@ -25,7 +25,7 @@ buffer::buffer(buffer&& other) noexcept other.m_id = 0; } -buffer& buffer::operator=(buffer&& other) noexcept { +auto buffer::operator=(buffer&& other) noexcept -> buffer& { if (this != &other) { if (m_id) { glDeleteBuffers(1, &m_id); diff --git a/cbt/opengl/shader.cpp b/cbt/opengl/shader.cpp index b730d43..b78098d 100644 --- a/cbt/opengl/shader.cpp +++ b/cbt/opengl/shader.cpp @@ -20,7 +20,7 @@ shader::shader(shader&& other) noexcept other.m_id = 0; } -shader& shader::operator=(shader&& other) noexcept { +auto shader::operator=(shader&& other) noexcept -> shader& { if (this != &other) { if (m_id) { glDeleteProgram(m_id); diff --git a/cbt/opengl/texture.cpp b/cbt/opengl/texture.cpp index 4702a64..6a494d5 100644 --- a/cbt/opengl/texture.cpp +++ b/cbt/opengl/texture.cpp @@ -25,7 +25,7 @@ texture::texture(texture&& other) noexcept other.m_id = 0; } -texture& texture::operator=(texture&& other) noexcept { +auto texture::operator=(texture&& other) noexcept -> texture& { if (this != &other) { if (m_id) { glDeleteTextures(1, &m_id); diff --git a/cbt/opengl/vao.cpp b/cbt/opengl/vao.cpp index 9be7d13..a51a305 100644 --- a/cbt/opengl/vao.cpp +++ b/cbt/opengl/vao.cpp @@ -17,7 +17,7 @@ vao::vao(vao&& other) noexcept other.m_id = 0; } -vao& vao::operator=(vao&& other) noexcept { +auto vao::operator=(vao&& other) noexcept -> vao& { if (this != &other) { if (m_id) { glDeleteVertexArrays(1, &m_id); diff --git a/cuber.cpp b/cuber.cpp index 5ec52b7..17a4470 100644 --- a/cuber.cpp +++ b/cuber.cpp @@ -6,10 +6,16 @@ #include "cbt/opengl/buffer.hpp" #include "cbt/opengl/vao.hpp" -#include "asio.hpp" +#include + +#include +#include +#include +#include + #include "glad/glad.h" -auto main([[maybe_unused]]int argc, [[maybe_unused]]char const* argv[]) -> int { +auto main(int, char const*[]) -> int { auto ctx = cbt::opengl::context("cuber", 1280, 720); if (!ctx.valid()) { @@ -23,9 +29,12 @@ auto main([[maybe_unused]]int argc, [[maybe_unused]]char const* argv[]) -> int { #version 410 core layout(location = 0) in vec3 a_pos; layout(location = 1) in vec3 a_color; + uniform mat4 u_model; + uniform mat4 u_view; + uniform mat4 u_proj; out vec3 v_color; void main() { - gl_Position = vec4(a_pos, 1.0); + gl_Position = u_proj * u_view * u_model * vec4(a_pos, 1.0); v_color = a_color; } )glsl"; @@ -43,12 +52,56 @@ auto main([[maybe_unused]]int argc, [[maybe_unused]]char const* argv[]) -> int { return 1; } - // vertex data: triangle with per-vertex colors - // red, green, blue corners - std::array vertices = { - -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, - 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, + // cube vertices: 6 faces, 2 triangles per face, 3 verts each = 36 vertices + // each vertex: x, y, z, r, g, b + std::array vertices = { + // front face (cyan) + 0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, + -0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, + -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, + 0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, + -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, + 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, + + // back face (magenta) + 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f, + -0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f, + -0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 1.0f, + 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 1.0f, + -0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 1.0f, + 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 1.0f, + + // top face (yellow) + 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f, + -0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f, + -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f, + -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f, + 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f, + + // bottom face (white) + 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, + -0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, + -0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 1.0f, + 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, + -0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 1.0f, + 0.5f, -0.5f, 0.5f, 1.0f, 1.0f, 1.0f, + + // right face (lime) + 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, + + // left face (orange) + -0.5f, -0.5f, -0.5f, 1.0f, 0.5f, 0.0f, + -0.5f, -0.5f, 0.5f, 1.0f, 0.5f, 0.0f, + -0.5f, 0.5f, 0.5f, 1.0f, 0.5f, 0.0f, + -0.5f, -0.5f, -0.5f, 1.0f, 0.5f, 0.0f, + -0.5f, 0.5f, 0.5f, 1.0f, 0.5f, 0.0f, + -0.5f, 0.5f, -0.5f, 1.0f, 0.5f, 0.0f, }; auto vbo = cbt::opengl::buffer(cbt::opengl::buffer_type::vertex); @@ -66,6 +119,9 @@ auto main([[maybe_unused]]int argc, [[maybe_unused]]char const* argv[]) -> int { vbo.unbind(); vao.unbind(); + // enable depth testing + glEnable(GL_DEPTH_TEST); + // signal handling asio::io_context io; asio::signal_set signals(io, SIGINT, SIGTERM); @@ -81,6 +137,8 @@ auto main([[maybe_unused]]int argc, [[maybe_unused]]char const* argv[]) -> int { }; // render loop + auto start = std::chrono::steady_clock::now(); + while (!ctx.should_close()) { process_signals(); @@ -88,12 +146,25 @@ auto main([[maybe_unused]]int argc, [[maybe_unused]]char const* argv[]) -> int { break; } - glClearColor(0.6f, 0.8f, 1.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); + auto now = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration(now - start).count(); + + glClearColor(0.15f, 0.15f, 0.2f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // compute MVP matrices + auto aspect = 1280.0f / 720.0f; + auto proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f); + auto view = glm::translate(glm::mat4{1.0f}, glm::vec3{0.0f, 0.0f, -3.0f}); + auto model = glm::rotate(glm::mat4{1.0f}, elapsed, glm::vec3{1.0f, 0.5f, 0.3f}); prog.use(); + glUniformMatrix4fv(glGetUniformLocation(prog.id(), "u_proj"), 1, GL_FALSE, glm::value_ptr(proj)); + glUniformMatrix4fv(glGetUniformLocation(prog.id(), "u_view"), 1, GL_FALSE, glm::value_ptr(view)); + glUniformMatrix4fv(glGetUniformLocation(prog.id(), "u_model"), 1, GL_FALSE, glm::value_ptr(model)); + vao.bind(); - glDrawArrays(GL_TRIANGLES, 0, 3); + glDrawArrays(GL_TRIANGLES, 0, 36); vao.unbind(); prog.unuse();