diff --git a/src/engine/ai/navmesh.cpp b/src/engine/ai/navmesh.cpp index 11418f1..eb883f3 100644 --- a/src/engine/ai/navmesh.cpp +++ b/src/engine/ai/navmesh.cpp @@ -26,7 +26,7 @@ namespace ai { -navmesh_traversal traverse_navmesh(const geom::brep_mesh& mesh, geom::brep_face* face, geom::ray ray, float distance) +navmesh_traversal traverse_navmesh(const geom::brep_mesh& mesh, geom::brep_face* face, const math::fvec3& start, const math::fvec3& end) { // Get vertex positions and face normals const auto& vertex_positions = mesh.vertices().attributes().at("position"); @@ -38,10 +38,9 @@ navmesh_traversal traverse_navmesh(const geom::brep_mesh& mesh, geom::brep_face* geom::triangle_region region; - auto target_point = ray.extrapolate(distance); - + auto target_point = end; + auto traversal_direction = math::normalize(end - start); math::fvec3 closest_point; - math::fvec3 traversal_direction = ray.direction; geom::brep_edge* previous_closest_edge{}; diff --git a/src/engine/ai/navmesh.hpp b/src/engine/ai/navmesh.hpp index a499e69..a3dd001 100644 --- a/src/engine/ai/navmesh.hpp +++ b/src/engine/ai/navmesh.hpp @@ -44,7 +44,7 @@ struct navmesh_traversal /** * Moves a point along the surface of a mesh. */ -[[nodiscard]] navmesh_traversal traverse_navmesh(const geom::brep_mesh& mesh, geom::brep_face* face, geom::ray ray, float distance); +[[nodiscard]] navmesh_traversal traverse_navmesh(const geom::brep_mesh& mesh, geom::brep_face* face, const math::fvec3& start, const math::fvec3& end); } // namespace ai diff --git a/src/engine/ai/steering/behavior/wander.cpp b/src/engine/ai/steering/behavior/wander.cpp index 7d9bb40..a3b7587 100644 --- a/src/engine/ai/steering/behavior/wander.cpp +++ b/src/engine/ai/steering/behavior/wander.cpp @@ -38,7 +38,7 @@ math::fvec3 wander_2d(const agent& agent, float noise, float distance, float rad auto [swing, twist] = math::swing_twist(agent.orientation, agent.up); // Calculate offset to point on wander circle - const math::fvec3 offset = math::conjugate(twist) * (math::angle_axis(angle, agent.up) * agent.forward * radius); + const math::fvec3 offset = (math::angle_axis(angle, agent.up) * agent.forward * radius) * twist; // Seek toward point on wander circle return seek(agent, center + offset); diff --git a/src/engine/animation/screen-transition.cpp b/src/engine/animation/screen-transition.cpp index 1ee4ef9..4dca144 100644 --- a/src/engine/animation/screen-transition.cpp +++ b/src/engine/animation/screen-transition.cpp @@ -32,7 +32,7 @@ screen_transition::screen_transition() // Setup billboard billboard.set_material(material); - //billboard.set_active(false); + billboard.set_layer_mask(0); // Add single channel to transition animation channel = animation.add_channel(0); @@ -94,7 +94,7 @@ void screen_transition::transition(float duration, bool reverse, ::animationbillboard.set_active(false); + this->billboard.set_layer_mask(0); if (this->callback) this->callback(); } @@ -111,4 +111,5 @@ void screen_transition::transition(float duration, bool reverse, ::animationbillboard.set_layer_mask(1); } diff --git a/src/engine/color/rgb.hpp b/src/engine/color/rgb.hpp index 75b2a65..27423ed 100644 --- a/src/engine/color/rgb.hpp +++ b/src/engine/color/rgb.hpp @@ -47,12 +47,12 @@ template { const math::mat3 m = { - r[0], r[1], T{1} - (r[0] + r[1]), - g[0], g[1], T{1} - (g[0] + g[1]), - b[0], b[1], T{1} - (b[0] + b[1]) + r[0], r[1], T{1} - r[0] - r[1], + g[0], g[1], T{1} - g[0] - g[1], + b[0], b[1], T{1} - b[0] - b[1] }; - const math::vec3 scale = math::inverse(m) * math::vec3{w[0] / w[1], T{1}, (T{1} - (w[0] + w[1])) / w[1]}; + const math::vec3 scale = math::inverse(m) * math::vec3{w[0] / w[1], T{1}, (T{1} - w[0] - w[1]) / w[1]}; return math::mat3 { diff --git a/src/engine/geom/brep/brep-operations.cpp b/src/engine/geom/brep/brep-operations.cpp index 1679e61..778fbf3 100644 --- a/src/engine/geom/brep/brep-operations.cpp +++ b/src/engine/geom/brep/brep-operations.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -132,4 +133,120 @@ void generate_loop_barycentric(brep_mesh& mesh) } } +std::unique_ptr generate_model(const brep_mesh& mesh, std::shared_ptr material) +{ + // Get vertex positions + const geom::brep_attribute* vertex_positions = nullptr; + if (auto attribute_it = mesh.vertices().attributes().find("position"); attribute_it != mesh.vertices().attributes().end()) + { + vertex_positions = &static_cast&>(*attribute_it); + } + + // Get vertex normals + const geom::brep_attribute* vertex_normals = nullptr; + if (auto attribute_it = mesh.vertices().attributes().find("normal"); attribute_it != mesh.vertices().attributes().end()) + { + vertex_normals = &static_cast&>(*attribute_it); + } + + // Allocate model + auto model = std::make_unique(); + + // Init model bounds + auto& bounds = model->get_bounds(); + bounds = {math::fvec3::infinity(), -math::fvec3::infinity()}; + + // Get model VBO and VAO + auto& vbo = model->get_vertex_buffer(); + auto& vao = model->get_vertex_array(); + + // Build vertex format + std::size_t vertex_size = 0; + + gl::vertex_attribute position_attribute; + if (vertex_positions) + { + position_attribute.buffer = vbo.get(); + position_attribute.offset = vertex_size; + position_attribute.type = gl::vertex_attribute_type::float_32; + position_attribute.components = 3; + + vertex_size += position_attribute.components * sizeof(float); + } + + gl::vertex_attribute normal_attribute; + if (vertex_normals) + { + normal_attribute.buffer = vbo.get(); + normal_attribute.offset = vertex_size; + normal_attribute.type = gl::vertex_attribute_type::float_32; + normal_attribute.components = 3; + + vertex_size += normal_attribute.components * sizeof(float); + } + + position_attribute.stride = vertex_size; + normal_attribute.stride = vertex_size; + + // Interleave vertex data + std::vector vertex_data(mesh.faces().size() * 3 * vertex_size); + if (vertex_positions) + { + std::byte* v = vertex_data.data() + position_attribute.offset; + for (auto face: mesh.faces()) + { + for (auto loop: face->loops()) + { + const auto& position = (*vertex_positions)[loop->vertex()->index()]; + std::memcpy(v, position.data(), sizeof(float) * 3); + v += position_attribute.stride; + + // Extend model bounds + bounds.extend(position); + } + } + } + if (vertex_normals) + { + std::byte* v = vertex_data.data() + normal_attribute.offset; + for (auto face: mesh.faces()) + { + for (auto loop: face->loops()) + { + const auto& normal = (*vertex_normals)[loop->vertex()->index()]; + std::memcpy(v, normal.data(), sizeof(float) * 3); + v += normal_attribute.stride; + } + } + } + + // Resize model VBO and upload interleaved vertex data + vbo->resize(vertex_data.size(), vertex_data); + + // Free interleaved vertex data + vertex_data.clear(); + + // Bind vertex attributes to VAO + if (vertex_positions) + { + vao->bind(render::vertex_attribute::position, position_attribute); + } + if (vertex_normals) + { + vao->bind(render::vertex_attribute::normal, normal_attribute); + } + + // Create material group + model->get_groups().resize(1); + render::model_group& model_group = model->get_groups().front(); + + model_group.id = "default"; + model_group.material = material; + model_group.drawing_mode = gl::drawing_mode::triangles; + model_group.start_index = 0; + model_group.index_count = static_cast(mesh.faces().size() * 3); + + return model; +} + } // namespace geom diff --git a/src/engine/geom/brep/brep-operations.hpp b/src/engine/geom/brep/brep-operations.hpp index 0e3eff8..84e2c44 100644 --- a/src/engine/geom/brep/brep-operations.hpp +++ b/src/engine/geom/brep/brep-operations.hpp @@ -21,6 +21,9 @@ #define ANTKEEPER_GEOM_BREP_OPERATIONS_HPP #include +#include +#include +#include namespace geom { @@ -50,6 +53,16 @@ void generate_vertex_normals(brep_mesh& mesh); */ void generate_loop_barycentric(brep_mesh& mesh); +/** + * Generates a model from a B-rep mesh. + * + * @param mesh Mesh for which to generate a model. + * @parma material Material to assign to the model. + * + * @return Generated model. + */ +std::unique_ptr generate_model(const brep_mesh& mesh, std::shared_ptr material = nullptr); + } // namespace geom #endif // ANTKEEPER_GEOM_BREP_OPERATIONS_HPP diff --git a/src/engine/gl/rasterizer.cpp b/src/engine/gl/rasterizer.cpp index 1dab386..764570c 100644 --- a/src/engine/gl/rasterizer.cpp +++ b/src/engine/gl/rasterizer.cpp @@ -84,6 +84,10 @@ rasterizer::rasterizer(): // Set clear depth to `0` for reversed depth glClearDepth(0.0f); + + glDisable(GL_MULTISAMPLE); + + dummy_vao = std::make_unique(); } rasterizer::~rasterizer() @@ -159,6 +163,19 @@ void rasterizer::draw_arrays(const vertex_array& vao, drawing_mode mode, std::si glDrawArrays(gl_mode, static_cast(offset), static_cast(count)); } +void rasterizer::draw_arrays(drawing_mode mode, std::size_t offset, std::size_t count) +{ + GLenum gl_mode = drawing_mode_lut[static_cast(mode)]; + + if (bound_vao != dummy_vao.get()) + { + glBindVertexArray(dummy_vao->gl_array_id); + bound_vao = dummy_vao.get(); + } + + glDrawArrays(gl_mode, static_cast(offset), static_cast(count)); +} + void rasterizer::draw_arrays_instanced(const vertex_array& vao, drawing_mode mode, std::size_t offset, std::size_t count, std::size_t instance_count) { GLenum gl_mode = drawing_mode_lut[static_cast(mode)]; @@ -186,4 +203,18 @@ void rasterizer::draw_elements(const vertex_array& vao, drawing_mode mode, std:: glDrawElements(gl_mode, static_cast(count), gl_type, reinterpret_cast(offset)); } +void rasterizer::draw_elements(drawing_mode mode, std::size_t offset, std::size_t count, element_array_type type) +{ + GLenum gl_mode = drawing_mode_lut[static_cast(mode)]; + GLenum gl_type = element_array_type_lut[static_cast(type)]; + + if (bound_vao != dummy_vao.get()) + { + glBindVertexArray(dummy_vao->gl_array_id); + bound_vao = dummy_vao.get(); + } + + glDrawElements(gl_mode, static_cast(count), gl_type, reinterpret_cast(offset)); +} + } // namespace gl diff --git a/src/engine/gl/rasterizer.hpp b/src/engine/gl/rasterizer.hpp index 547e767..5ce1964 100644 --- a/src/engine/gl/rasterizer.hpp +++ b/src/engine/gl/rasterizer.hpp @@ -111,6 +111,7 @@ public: * */ void draw_arrays(const vertex_array& vao, drawing_mode mode, std::size_t offset, std::size_t count); + void draw_arrays(drawing_mode mode, std::size_t offset, std::size_t count); void draw_arrays_instanced(const vertex_array& vao, drawing_mode mode, std::size_t offset, std::size_t count, std::size_t instance_count); @@ -118,6 +119,7 @@ public: * */ void draw_elements(const vertex_array& vao, drawing_mode mode, std::size_t offset, std::size_t count, element_array_type type); + void draw_elements(drawing_mode mode, std::size_t offset, std::size_t count, element_array_type type); /** * Returns the default framebuffer associated with the OpenGL context of a window. @@ -129,6 +131,7 @@ public: private: std::unique_ptr default_framebuffer; + std::unique_ptr dummy_vao; const framebuffer* bound_framebuffer{nullptr}; const vertex_array* bound_vao{nullptr}; const shader_program* bound_shader_program{nullptr}; diff --git a/src/engine/gl/texture-1d.cpp b/src/engine/gl/texture-1d.cpp index 5ae5744..23d77ed 100644 --- a/src/engine/gl/texture-1d.cpp +++ b/src/engine/gl/texture-1d.cpp @@ -21,13 +21,13 @@ namespace gl { -texture_1d::texture_1d(std::uint16_t width, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data): - texture(width, false, type, format, color_space, data) +texture_1d::texture_1d(std::uint16_t width, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data): + texture(width, false, type, format, transfer_function, data) {} -void texture_1d::resize(std::uint16_t width, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data) +void texture_1d::resize(std::uint16_t width, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) { - texture::resize(width, type, format, color_space, data); + texture::resize(width, type, format, transfer_function, data); } void texture_1d::set_wrapping(gl::texture_wrapping wrap_s) diff --git a/src/engine/gl/texture-1d.hpp b/src/engine/gl/texture-1d.hpp index b3d6e8b..66d0d84 100644 --- a/src/engine/gl/texture-1d.hpp +++ b/src/engine/gl/texture-1d.hpp @@ -30,15 +30,15 @@ namespace gl { class texture_1d: public texture { public: - explicit texture_1d(std::uint16_t width, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::color_space color_space = gl::color_space::linear, const std::byte* data = nullptr); + explicit texture_1d(std::uint16_t width, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::transfer_function transfer_function = gl::transfer_function::linear, const std::byte* data = nullptr); [[nodiscard]] inline constexpr texture_type get_texture_type() const noexcept override { return texture_type::one_dimensional; } - /// @copydoc texture::resize(std::uint16_t, gl::pixel_type, gl::pixel_format, gl::color_space, const std::byte*) - virtual void resize(std::uint16_t width, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data); + /// @copydoc texture::resize(std::uint16_t, gl::pixel_type, gl::pixel_format, gl::transfer_function, const std::byte*) + virtual void resize(std::uint16_t width, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data); /// @copydoc texture::set_wrapping(gl::texture_wrapping) virtual void set_wrapping(gl::texture_wrapping wrap_s); diff --git a/src/engine/gl/texture-2d.cpp b/src/engine/gl/texture-2d.cpp index ef4912b..1c3ab79 100644 --- a/src/engine/gl/texture-2d.cpp +++ b/src/engine/gl/texture-2d.cpp @@ -21,18 +21,18 @@ namespace gl { -texture_2d::texture_2d(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data): - texture(width, height, false, type, format, color_space, data) +texture_2d::texture_2d(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data): + texture(width, height, false, type, format, transfer_function, data) {} -void texture_2d::resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data) +void texture_2d::resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) { - texture::resize(width, height, type, format, color_space, data); + texture::resize(width, height, type, format, transfer_function, data); } void texture_2d::resize(std::uint16_t width, std::uint16_t height, const std::byte* data) { - texture::resize(width, height, get_pixel_type(), get_pixel_format(), get_color_space(), data); + texture::resize(width, height, get_pixel_type(), get_pixel_format(), get_transfer_function(), data); } void texture_2d::set_wrapping(gl::texture_wrapping wrap_s, texture_wrapping wrap_t) diff --git a/src/engine/gl/texture-2d.hpp b/src/engine/gl/texture-2d.hpp index cb216a0..3dba229 100644 --- a/src/engine/gl/texture-2d.hpp +++ b/src/engine/gl/texture-2d.hpp @@ -30,15 +30,15 @@ namespace gl { class texture_2d: public texture { public: - texture_2d(std::uint16_t width, std::uint16_t height, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::color_space color_space = gl::color_space::linear, const std::byte* data = nullptr); + texture_2d(std::uint16_t width, std::uint16_t height, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::transfer_function transfer_function = gl::transfer_function::linear, const std::byte* data = nullptr); [[nodiscard]] inline constexpr texture_type get_texture_type() const noexcept override { return texture_type::two_dimensional; } - /// @copydoc texture::resize(std::uint16_t, std::uint16_t, gl::pixel_type, gl::pixel_format, gl::color_space, const std::byte*) - void resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data) override; + /// @copydoc texture::resize(std::uint16_t, std::uint16_t, gl::pixel_type, gl::pixel_format, gl::transfer_function, const std::byte*) + void resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) override; /** * Resizes the texture. diff --git a/src/engine/gl/texture-3d.cpp b/src/engine/gl/texture-3d.cpp index 23fabf8..fda2957 100644 --- a/src/engine/gl/texture-3d.cpp +++ b/src/engine/gl/texture-3d.cpp @@ -21,13 +21,13 @@ namespace gl { -texture_3d::texture_3d(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data): - texture(width, height, depth, false, type, format, color_space, data) +texture_3d::texture_3d(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data): + texture(width, height, depth, false, type, format, transfer_function, data) {} -void texture_3d::resize(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data) +void texture_3d::resize(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) { - texture::resize(width, height, depth, type, format, color_space, data); + texture::resize(width, height, depth, type, format, transfer_function, data); } void texture_3d::set_wrapping(gl::texture_wrapping wrap_s, texture_wrapping wrap_t, texture_wrapping wrap_r) diff --git a/src/engine/gl/texture-3d.hpp b/src/engine/gl/texture-3d.hpp index 0fc8998..0c24830 100644 --- a/src/engine/gl/texture-3d.hpp +++ b/src/engine/gl/texture-3d.hpp @@ -30,15 +30,15 @@ namespace gl { class texture_3d: public texture { public: - texture_3d(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::color_space color_space = gl::color_space::linear, const std::byte* data = nullptr); + texture_3d(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::transfer_function transfer_function = gl::transfer_function::linear, const std::byte* data = nullptr); [[nodiscard]] inline constexpr texture_type get_texture_type() const noexcept override { return texture_type::three_dimensional; } - /// @copydoc texture::resize(std::uint16_t, std::uint16_t, std::uint16_t, gl::pixel_type, gl::pixel_format, gl::color_space, const std::byte*) - virtual void resize(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data); + /// @copydoc texture::resize(std::uint16_t, std::uint16_t, std::uint16_t, gl::pixel_type, gl::pixel_format, gl::transfer_function, const std::byte*) + virtual void resize(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data); /// @copydoc texture::set_wrapping(gl::texture_wrapping, gl::texture_wrapping, gl::texture_wrapping) virtual void set_wrapping(gl::texture_wrapping wrap_s, texture_wrapping wrap_t, texture_wrapping wrap_r); diff --git a/src/engine/gl/texture-cube.cpp b/src/engine/gl/texture-cube.cpp index b090945..14ba5a8 100644 --- a/src/engine/gl/texture-cube.cpp +++ b/src/engine/gl/texture-cube.cpp @@ -75,21 +75,21 @@ std::uint16_t texture_cube::infer_cube_map_face_size(cube_map_layout layout, std } } -texture_cube::texture_cube(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data): - texture(width, height, true, type, format, color_space, data) +texture_cube::texture_cube(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data): + texture(width, height, true, type, format, transfer_function, data) { resized(); } -void texture_cube::resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data) +void texture_cube::resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) { - texture::resize(width, height, type, format, color_space, data); + texture::resize(width, height, type, format, transfer_function, data); resized(); } void texture_cube::resize(std::uint16_t width, std::uint16_t height, const std::byte* data) { - texture::resize(width, height, get_pixel_type(), get_pixel_format(), get_color_space(), data); + texture::resize(width, height, get_pixel_type(), get_pixel_format(), get_transfer_function(), data); resized(); } diff --git a/src/engine/gl/texture-cube.hpp b/src/engine/gl/texture-cube.hpp index 72e3a2e..5cfe75a 100644 --- a/src/engine/gl/texture-cube.hpp +++ b/src/engine/gl/texture-cube.hpp @@ -53,15 +53,15 @@ public: [[nodiscard]] static std::uint16_t infer_cube_map_face_size(cube_map_layout layout, std::uint16_t w, std::uint16_t h) noexcept; /// Constructs a cube texture. - texture_cube(std::uint16_t width, std::uint16_t height, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::color_space color_space = gl::color_space::linear, const std::byte* data = nullptr); + texture_cube(std::uint16_t width, std::uint16_t height, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::transfer_function transfer_function = gl::transfer_function::linear, const std::byte* data = nullptr); [[nodiscard]] inline constexpr texture_type get_texture_type() const noexcept override { return texture_type::cube; } - /// @copydoc texture::resize(std::uint16_t, std::uint16_t, gl::pixel_type, gl::pixel_format, gl::color_space, const std::byte*) - void resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data) override; + /// @copydoc texture::resize(std::uint16_t, std::uint16_t, gl::pixel_type, gl::pixel_format, gl::transfer_function, const std::byte*) + void resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) override; /** * Resizes the texture. diff --git a/src/engine/gl/texture.cpp b/src/engine/gl/texture.cpp index cc4d3bb..28e9bbb 100644 --- a/src/engine/gl/texture.cpp +++ b/src/engine/gl/texture.cpp @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include @@ -125,23 +125,23 @@ static constexpr GLenum mag_filter_lut[] = GL_LINEAR }; -texture::texture(std::uint16_t width, std::uint16_t height, std::uint16_t depth, bool cube, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data) +texture::texture(std::uint16_t width, std::uint16_t height, std::uint16_t depth, bool cube, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) { m_gl_texture_target = static_cast(cube ? GL_TEXTURE_CUBE_MAP : (depth) ? GL_TEXTURE_3D : (height) ? GL_TEXTURE_2D : GL_TEXTURE_1D); glGenTextures(1, &m_gl_texture_id); - resize(width, height, depth, type, format, color_space, data); + resize(width, height, depth, type, format, transfer_function, data); set_wrapping(m_wrapping[0], m_wrapping[1], m_wrapping[2]); set_filters(std::get<0>(m_filters), std::get<1>(m_filters)); set_max_anisotropy(m_max_anisotropy); } -texture::texture(std::uint16_t width, std::uint16_t height, bool cube, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data): - texture(width, height, 0, cube, type, format, color_space, data) +texture::texture(std::uint16_t width, std::uint16_t height, bool cube, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data): + texture(width, height, 0, cube, type, format, transfer_function, data) {} -texture::texture(std::uint16_t width, bool cube, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data): - texture(width, 0, 0, cube, type, format, color_space, data) +texture::texture(std::uint16_t width, bool cube, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data): + texture(width, 0, 0, cube, type, format, transfer_function, data) {} texture::~texture() @@ -264,15 +264,15 @@ void texture::set_wrapping(gl::texture_wrapping wrap_s) glTexParameteri(m_gl_texture_target, GL_TEXTURE_WRAP_S, gl_wrap_s); } -void texture::resize(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data) +void texture::resize(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) { m_dimensions = {width, height, depth}; m_pixel_type = type; m_pixel_format = format; - m_color_space = color_space; + m_transfer_function = transfer_function; GLenum gl_internal_format; - if (m_color_space == gl::color_space::srgb) + if (m_transfer_function == gl::transfer_function::srgb) { gl_internal_format = srgb_internal_format_lut[std::to_underlying(format)][std::to_underlying(type)]; } @@ -336,14 +336,14 @@ void texture::resize(std::uint16_t width, std::uint16_t height, std::uint16_t de } } -void texture::resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data) +void texture::resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) { - resize(width, height, 0, type, format, color_space, data); + resize(width, height, 0, type, format, transfer_function, data); } -void texture::resize(std::uint16_t width, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data) +void texture::resize(std::uint16_t width, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data) { - resize(width, 0, 0, type, format, color_space, data); + resize(width, 0, 0, type, format, transfer_function, data); } void texture::update_cube_faces(unsigned int gl_internal_format, unsigned int gl_format, unsigned int gl_type, const std::byte* data) @@ -363,27 +363,27 @@ void texture::update_cube_faces(unsigned int gl_internal_format, unsigned int gl return; } - std::size_t channel_count = 0; + std::size_t channels = 0; switch (m_pixel_format) { case pixel_format::d: case pixel_format::r: - channel_count = 1; + channels = 1; break; case pixel_format::ds: case pixel_format::rg: - channel_count = 2; + channels = 2; break; case pixel_format::rgb: case pixel_format::bgr: - channel_count = 3; + channels = 3; break; case pixel_format::rgba: case pixel_format::bgra: - channel_count = 4; + channels = 4; break; default: @@ -414,7 +414,7 @@ void texture::update_cube_faces(unsigned int gl_internal_format, unsigned int gl break; } - const std::size_t pixel_stride = channel_count * channel_size; + const std::size_t pixel_stride = channels * channel_size; const std::size_t row_stride = static_cast(face_size) * pixel_stride; const std::size_t face_stride = static_cast(face_size) * row_stride; @@ -552,18 +552,14 @@ std::unique_ptr resource_loader::load(::resource // Load image auto image = resource_manager.load<::image>(image_filename); - // Read color space - gl::color_space color_space = gl::color_space::linear; - if (auto element = json_data->find("color_space"); element != json_data->end()) + // Read transfer function + gl::transfer_function transfer_function = gl::transfer_function::linear; + if (auto element = json_data->find("transfer_function"); element != json_data->end()) { std::string value = element.value().get(); - if (value == "linear") + if (value == "srgb") { - color_space = gl::color_space::linear; - } - else if (value == "srgb") - { - color_space = gl::color_space::srgb; + transfer_function = gl::transfer_function::srgb; } } @@ -616,33 +612,33 @@ std::unique_ptr resource_loader::load(::resource } // Determine pixel type - gl::pixel_type type = (image->component_size() == sizeof(float)) ? gl::pixel_type::float_32 : gl::pixel_type::uint_8; + gl::pixel_type type = (image->bit_depth() >> 3 == sizeof(float)) ? gl::pixel_type::float_32 : gl::pixel_type::uint_8; // Determine pixel format gl::pixel_format format; - if (image->channel_count() == 1) + if (image->channels() == 1) { format = gl::pixel_format::r; } - else if (image->channel_count() == 2) + else if (image->channels() == 2) { format = gl::pixel_format::rg; } - else if (image->channel_count() == 3) + else if (image->channels() == 3) { format = gl::pixel_format::rgb; } - else if (image->channel_count() == 4) + else if (image->channels() == 4) { format = gl::pixel_format::rgba; } else { - throw std::runtime_error(std::format("Texture image has unsupported number of channels ({})", image->channel_count())); + throw std::runtime_error(std::format("Texture image has unsupported number of channels ({})", image->channels())); } // Create texture - auto texture = std::make_unique(image->width(), type, format, color_space, image->data()); + auto texture = std::make_unique(static_cast(image->size().x()), type, format, transfer_function, image->data()); texture->set_wrapping(wrapping); texture->set_filters(min_filter, mag_filter); texture->set_max_anisotropy(max_anisotropy); @@ -667,17 +663,13 @@ std::unique_ptr resource_loader::load(::resource auto image = resource_manager.load<::image>(image_filename); // Read color space - gl::color_space color_space = gl::color_space::linear; - if (auto element = json_data->find("color_space"); element != json_data->end()) + gl::transfer_function transfer_function = gl::transfer_function::linear; + if (auto element = json_data->find("transfer_function"); element != json_data->end()) { std::string value = element.value().get(); - if (value == "linear") + if (value == "srgb") { - color_space = gl::color_space::linear; - } - else if (value == "srgb") - { - color_space = gl::color_space::srgb; + transfer_function = gl::transfer_function::srgb; } } @@ -730,33 +722,33 @@ std::unique_ptr resource_loader::load(::resource } // Determine pixel type - gl::pixel_type type = (image->component_size() == sizeof(float)) ? gl::pixel_type::float_32 : gl::pixel_type::uint_8; + gl::pixel_type type = (image->bit_depth() >> 3 == sizeof(float)) ? gl::pixel_type::float_32 : gl::pixel_type::uint_8; // Determine pixel format gl::pixel_format format; - if (image->channel_count() == 1) + if (image->channels() == 1) { format = gl::pixel_format::r; } - else if (image->channel_count() == 2) + else if (image->channels() == 2) { format = gl::pixel_format::rg; } - else if (image->channel_count() == 3) + else if (image->channels() == 3) { format = gl::pixel_format::rgb; } - else if (image->channel_count() == 4) + else if (image->channels() == 4) { format = gl::pixel_format::rgba; } else { - throw std::runtime_error(std::format("Texture image has unsupported number of channels ({})", image->channel_count())); + throw std::runtime_error(std::format("Texture image has unsupported number of channels ({})", image->channels())); } // Create texture - auto texture = std::make_unique(image->width(), image->height(), type, format, color_space, image->data()); + auto texture = std::make_unique(static_cast(image->size().x()), static_cast(image->size().y()), type, format, transfer_function, image->data()); texture->set_wrapping(wrapping, wrapping); texture->set_filters(min_filter, mag_filter); texture->set_max_anisotropy(max_anisotropy); @@ -787,17 +779,13 @@ std::unique_ptr resource_loader::load(::reso auto image = resource_manager.load<::image>(image_filename); // Read color space - gl::color_space color_space = gl::color_space::linear; - if (auto element = json_data->find("color_space"); element != json_data->end()) + gl::transfer_function transfer_function = gl::transfer_function::linear; + if (auto element = json_data->find("transfer_function"); element != json_data->end()) { std::string value = element.value().get(); - if (value == "linear") - { - color_space = gl::color_space::linear; - } - else if (value == "srgb") + if (value == "srgb") { - color_space = gl::color_space::srgb; + transfer_function = gl::transfer_function::srgb; } } @@ -850,33 +838,46 @@ std::unique_ptr resource_loader::load(::reso } // Determine pixel type - gl::pixel_type type = (image->component_size() == sizeof(float)) ? gl::pixel_type::float_32 : gl::pixel_type::uint_8; - + gl::pixel_type type; + switch (image->bit_depth()) + { + case 32u: + type = gl::pixel_type::float_32; + break; + case 16u: + type = gl::pixel_type::uint_16; + break; + case 8u: + default: + type = gl::pixel_type::uint_8; + break; + } + // Determine pixel format gl::pixel_format format; - if (image->channel_count() == 1) + if (image->channels() == 1) { format = gl::pixel_format::r; } - else if (image->channel_count() == 2) + else if (image->channels() == 2) { format = gl::pixel_format::rg; } - else if (image->channel_count() == 3) + else if (image->channels() == 3) { format = gl::pixel_format::rgb; } - else if (image->channel_count() == 4) + else if (image->channels() == 4) { format = gl::pixel_format::rgba; } else { - throw std::runtime_error(std::format("Texture image has unsupported number of channels ({})", image->channel_count())); + throw std::runtime_error(std::format("Texture image has unsupported number of channels ({})", image->channels())); } // Create texture - auto texture = std::make_unique(image->width(), image->height(), type, format, color_space, image->data()); + auto texture = std::make_unique(static_cast(image->size().x()), static_cast(image->size().y()), type, format, transfer_function, image->data()); texture->set_wrapping(wrapping, wrapping, wrapping); texture->set_filters(min_filter, mag_filter); texture->set_max_anisotropy(max_anisotropy); diff --git a/src/engine/gl/texture.hpp b/src/engine/gl/texture.hpp index bc902ff..effbd51 100644 --- a/src/engine/gl/texture.hpp +++ b/src/engine/gl/texture.hpp @@ -20,7 +20,7 @@ #ifndef ANTKEEPER_GL_TEXTURE_HPP #define ANTKEEPER_GL_TEXTURE_HPP -#include +#include #include #include #include @@ -151,10 +151,10 @@ public: return m_pixel_format; } - /// Returns the color space enumeration. - [[nodiscard]] inline color_space get_color_space() const noexcept + /// Returns the transfer function. + [[nodiscard]] inline transfer_function get_transfer_function() const noexcept { - return m_color_space; + return m_transfer_function; } /// Returns the wrapping modes of the texture. @@ -202,15 +202,15 @@ protected: * @param depth Texture depth, in pixels. For 3D textures only. * @param type Pixel component data type. * @param format Pixel format. - * @param color_space Color space of the pixel data. + * @param transfer_function Transfer function of the texture data. * @param data Pointer to pixel data. * * @warning If the sRGB color space is specified, pixel data will be stored internally as 8 bits per channel, and automatically converted to linear space before reading. */ /// @{ - texture(std::uint16_t width, std::uint16_t height, std::uint16_t depth, bool cube = false, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::color_space color_space = gl::color_space::linear, const std::byte* data = nullptr); - texture(std::uint16_t width, std::uint16_t height, bool cube = false, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::color_space color_space = gl::color_space::linear, const std::byte* data = nullptr); - explicit texture(std::uint16_t width, bool cube = false, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::color_space color_space = gl::color_space::linear, const std::byte* data = nullptr); + texture(std::uint16_t width, std::uint16_t height, std::uint16_t depth, bool cube = false, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::transfer_function transfer_function = gl::transfer_function::linear, const std::byte* data = nullptr); + texture(std::uint16_t width, std::uint16_t height, bool cube = false, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::transfer_function transfer_function = gl::transfer_function::linear, const std::byte* data = nullptr); + explicit texture(std::uint16_t width, bool cube = false, gl::pixel_type type = gl::pixel_type::uint_8, gl::pixel_format format = gl::pixel_format::rgba, gl::transfer_function transfer_function = gl::transfer_function::linear, const std::byte* data = nullptr); /// @} /** @@ -234,15 +234,15 @@ protected: * @param depth Texture depth, in pixels. For 3D textures only. * @param type Pixel component data type. * @param format Pixel format. - * @param color_space Color space of the pixel data. + * @param transfer_function Transfer_function the pixel data. * @param data Pointer to pixel data. * * @warning If the sRGB color space is specified, pixel data will be stored internally as 8 bits per channel, and automatically converted to linear space before reading. */ /// @{ - virtual void resize(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data); - virtual void resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data); - virtual void resize(std::uint16_t width, gl::pixel_type type, gl::pixel_format format, gl::color_space color_space, const std::byte* data); + virtual void resize(std::uint16_t width, std::uint16_t height, std::uint16_t depth, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data); + virtual void resize(std::uint16_t width, std::uint16_t height, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data); + virtual void resize(std::uint16_t width, gl::pixel_type type, gl::pixel_format format, gl::transfer_function transfer_function, const std::byte* data); /// @} private: @@ -259,7 +259,7 @@ private: std::array m_dimensions{}; gl::pixel_type m_pixel_type{}; gl::pixel_format m_pixel_format{}; - gl::color_space m_color_space{}; + gl::transfer_function m_transfer_function{gl::transfer_function::linear}; std::array m_wrapping{texture_wrapping::repeat, texture_wrapping::repeat, texture_wrapping::repeat}; std::tuple m_filters{texture_min_filter::linear_mipmap_linear, texture_mag_filter::linear}; std::uint8_t m_base_level{}; diff --git a/src/engine/gl/color-space.hpp b/src/engine/gl/transfer-function.hpp similarity index 75% rename from src/engine/gl/color-space.hpp rename to src/engine/gl/transfer-function.hpp index 5d2272d..6c42821 100644 --- a/src/engine/gl/color-space.hpp +++ b/src/engine/gl/transfer-function.hpp @@ -17,22 +17,25 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GL_COLOR_SPACE_HPP -#define ANTKEEPER_GL_COLOR_SPACE_HPP +#ifndef ANTKEEPER_GL_TRANSFER_FUNCTION_HPP +#define ANTKEEPER_GL_TRANSFER_FUNCTION_HPP #include namespace gl { -enum class color_space: std::uint8_t +/** + * Texture sampling transfer function. + */ +enum class transfer_function: std::uint8_t { - /// Linear color space. + /// Linear transfer function. linear, - /// sRGB color space. + /// sRGB transfer function. srgb }; } // namespace gl -#endif // ANTKEEPER_GL_COLOR_SPACE_HPP +#endif // ANTKEEPER_GL_TRANSFER_FUNCTION_HPP diff --git a/src/engine/gl/vertex-array.cpp b/src/engine/gl/vertex-array.cpp index f5ffa34..53ec5ff 100644 --- a/src/engine/gl/vertex-array.cpp +++ b/src/engine/gl/vertex-array.cpp @@ -66,14 +66,14 @@ void vertex_array::bind(attribute_location_type location, const vertex_attribute glBindVertexArray(gl_array_id); glBindBuffer(GL_ARRAY_BUFFER, attribute.buffer->gl_buffer_id); - if (gl_type == GL_FLOAT || gl_type == GL_HALF_FLOAT || gl_type == GL_DOUBLE) + if (attribute.normalized || gl_type == GL_FLOAT || gl_type == GL_HALF_FLOAT || gl_type == GL_DOUBLE) { glVertexAttribPointer ( static_cast(location), static_cast(attribute.components), gl_type, - GL_FALSE, + attribute.normalized ? GL_TRUE : GL_FALSE, static_cast(attribute.stride), reinterpret_cast(attribute.offset) ); diff --git a/src/engine/gl/vertex-attribute.hpp b/src/engine/gl/vertex-attribute.hpp index 41db4bf..bef0ec7 100644 --- a/src/engine/gl/vertex-attribute.hpp +++ b/src/engine/gl/vertex-attribute.hpp @@ -62,6 +62,9 @@ struct vertex_attribute /// Number of components per attribute instance. Supported values are `1`, `2`, `3`, and `4`. std::uint8_t components{0}; + + /// `true` if fixed point data should be normalized, `false` otherwise. + bool normalized{}; }; } // namespace gl diff --git a/src/engine/math/projection.hpp b/src/engine/math/projection.hpp index 834248e..f502167 100644 --- a/src/engine/math/projection.hpp +++ b/src/engine/math/projection.hpp @@ -269,7 +269,7 @@ template } /** - * Constructs a perspective projection matrix which will transform the near and far clipping planes to `[0, 1]`, respectively. + * Constructs a perspective projection matrix which will transform the near and far clipping planes to `[0, 1]`, respectively, along with its inverse. * * @param vertical_fov Vertical field of view angle, in radians. * @param aspect_ratio Aspect ratio which determines the horizontal field of view. @@ -306,6 +306,67 @@ template }; } +/** + * Constructs a perspective projection matrix, with an infinite far plane, which will transform the near and far clipping planes to `[1, 0]`, respectively. + * + * @param vertical_fov Vertical field of view angle, in radians. + * @param aspect_ratio Aspect ratio which determines the horizontal field of view. + * @param near Distance to the near clipping plane. + * + * @return Perspective projection matrix. + */ +template +[[nodiscard]] mat4 inf_perspective_half_z_reverse(T vertical_fov, T aspect_ratio, T near) +{ + const T half_fov = vertical_fov * T{0.5}; + const T f = std::cos(half_fov) / std::sin(half_fov); + + return + {{ + {f / aspect_ratio, T{0}, T{0}, T{0}}, + {T{0}, f, T{0}, T{0}}, + {T{0}, T{0}, T{0}, T{-1}}, + {T{0}, T{0}, near, T{0}} + }}; +} + +/** + * Constructs a perspective projection matrix, with an infinite far plane, which will transform the near and far clipping planes to `[1, 0]`, respectively, along with its inverse. + * + * @param vertical_fov Vertical field of view angle, in radians. + * @param aspect_ratio Aspect ratio which determines the horizontal field of view. + * @param near Distance to the near clipping plane. + * + * @return Tuple containing the perspective projection matrix, followed by its inverse. + * + * @note Constructing the inverse perspective projection matrix from projection parameters is faster and more precise than inverting matrix. + */ +template +[[nodiscard]] std::tuple, mat4> inf_perspective_half_z_reverse_inv(T vertical_fov, T aspect_ratio, T near) +{ + const T half_fov = vertical_fov * T{0.5}; + const T f = std::cos(half_fov) / std::sin(half_fov); + + return + { + mat4 + {{ + {f / aspect_ratio, T{0}, T{0}, T{0}}, + {T{0}, f, T{0}, T{0}}, + {T{0}, T{0}, T{0}, T{-1}}, + {T{0}, T{0}, near, T{0}} + }}, + + mat4 + {{ + {aspect_ratio / f, T{0}, T{0}, T{0}}, + {T{0}, T{1} / f, T{0}, T{0}}, + {T{0}, T{0}, T{0}, T{1} / near}, + {T{0}, T{0}, T{-1}, T{0}} + }} + }; +} + } // namespace math #endif // ANTKEEPER_MATH_PROJECTION_HPP diff --git a/src/engine/math/quaternion.hpp b/src/engine/math/quaternion.hpp index af698b9..7275c66 100644 --- a/src/engine/math/quaternion.hpp +++ b/src/engine/math/quaternion.hpp @@ -659,7 +659,8 @@ constexpr vec3 mul(const quaternion& q, const vec3& v) noexcept template inline constexpr vec3 mul(const vec3& v, const quaternion& q) noexcept { - return mul(conjugate(q), v); + const auto t = cross(v, q.i) * T{2}; + return v + q.r * t + cross(t, q.i); } template diff --git a/src/engine/math/se3.hpp b/src/engine/math/se3.hpp index de79434..9a87361 100644 --- a/src/engine/math/se3.hpp +++ b/src/engine/math/se3.hpp @@ -55,9 +55,7 @@ public: /// Returns the inverse of this transformation. [[nodiscard]] constexpr se3 inverse() const noexcept { - const quaternion_type inverse_r = conjugate(r); - const vector_type inverse_t = -(inverse_r * t); - return {inverse_t, inverse_r}; + return {-t * r, conjugate(r)}; } /// Returns a matrix representation of this transformation. diff --git a/src/engine/math/transform.hpp b/src/engine/math/transform.hpp index d50c30d..b22842a 100644 --- a/src/engine/math/transform.hpp +++ b/src/engine/math/transform.hpp @@ -154,13 +154,7 @@ template template transform inverse(const transform& t) noexcept { - transform inverse_t; - - inverse_t.scale = T{1} / t.scale; - inverse_t.rotation = conjugate(t.rotation); - inverse_t.translation = -(inverse_t.rotation * t.translation); - - return inverse_t; + return {-t.translation * t.rotation, conjugate(t.rotation), T{1} / t.scale}; } template diff --git a/src/engine/physics/kinematics/colliders/mesh-collider.cpp b/src/engine/physics/kinematics/colliders/mesh-collider.cpp index 1aeed2f..34bf67e 100644 --- a/src/engine/physics/kinematics/colliders/mesh-collider.cpp +++ b/src/engine/physics/kinematics/colliders/mesh-collider.cpp @@ -41,8 +41,15 @@ void mesh_collider::set_mesh(std::shared_ptr mesh) // If mesh has no face normals if (!m_mesh->faces().attributes().contains("normal")) { - // Generate normals - // generate_face_normals(*m_mesh); + // Generate face normals + generate_face_normals(*m_mesh); + } + + /// @TODO: vertex normals aren't needed for mesh colliders, they're generated here for the locomotion system (remove later) + // If mesh has no vertex normals + if (!m_mesh->vertices().attributes().contains("normal")) + { + // Generate vertex normals generate_vertex_normals(*m_mesh); } @@ -77,8 +84,7 @@ std::optional> mesh_collider::inte return std::nullopt; } - std::size_t box_hit_count = 0; - std::size_t triangle_hit_count = 0; + bool triangle_hit = false; float nearest_face_distance = std::numeric_limits::infinity(); std::uint32_t nearest_face_index; @@ -88,8 +94,6 @@ std::optional> mesh_collider::inte ray, [&](std::uint32_t index) { - ++box_hit_count; - // If ray is facing backside of face if (math::dot((*m_face_normals)[index], ray.direction) > 0.0f) { @@ -109,8 +113,6 @@ std::optional> mesh_collider::inte // If ray intersects face if (const auto intersection = geom::intersection(ray, a, b, c)) { - ++triangle_hit_count; - // If distance to point of intersection is nearer than nearest intersection float t = std::get<0>(*intersection); if (t < nearest_face_distance) @@ -118,14 +120,14 @@ std::optional> mesh_collider::inte // Update nearest intersection nearest_face_distance = t; nearest_face_index = index; + + triangle_hit = true; } } } ); - // debug::log::debug("mesh collider intersection test:\n\tboxes hit: {}\n\ttriangles hit: {}", box_hit_count, triangle_hit_count); - - if (!triangle_hit_count) + if (!triangle_hit) { return std::nullopt; } diff --git a/src/engine/render/passes/bloom-pass.cpp b/src/engine/render/passes/bloom-pass.cpp index 3693ec1..f8937b8 100644 --- a/src/engine/render/passes/bloom-pass.cpp +++ b/src/engine/render/passes/bloom-pass.cpp @@ -64,34 +64,6 @@ bloom_pass::bloom_pass(gl::rasterizer* rasterizer, resource_manager* resource_ma // Build upsample shader program upsample_shader = upsample_shader_template->build(); - - const math::fvec2 vertex_positions[] = - { - {-1.0f, 1.0f}, - {-1.0f, -1.0f}, - { 1.0f, 1.0f}, - { 1.0f, 1.0f}, - {-1.0f, -1.0f}, - { 1.0f, -1.0f} - }; - - const auto vertex_data = std::as_bytes(std::span{vertex_positions}); - std::size_t vertex_size = 2; - std::size_t vertex_stride = sizeof(float) * vertex_size; - - quad_vbo = std::make_unique(gl::buffer_usage::static_draw, vertex_data.size(), vertex_data); - quad_vao = std::make_unique(); - - // Define position vertex attribute - gl::vertex_attribute position_attribute; - position_attribute.buffer = quad_vbo.get(); - position_attribute.offset = 0; - position_attribute.stride = vertex_stride; - position_attribute.type = gl::vertex_attribute_type::float_32; - position_attribute.components = 2; - - // Bind vertex attributes to VAO - quad_vao->bind(render::vertex_attribute::position, position_attribute); } void bloom_pass::render(render::context& ctx) @@ -267,7 +239,7 @@ void bloom_pass::rebuild_command_buffer() source_texture_var->update(*source_texture); - rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::triangles, 0, 6); + rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); } ); } @@ -291,7 +263,7 @@ void bloom_pass::rebuild_command_buffer() // Use previous downsample texture as downsample source source_texture_var->update(*textures[i - 1]); - rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::triangles, 0, 6); + rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); } ); } @@ -335,7 +307,7 @@ void bloom_pass::rebuild_command_buffer() source_texture_var->update(*textures[i]); - rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::triangles, 0, 6); + rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); } ); } diff --git a/src/engine/render/passes/bloom-pass.hpp b/src/engine/render/passes/bloom-pass.hpp index 02c210b..bf7b255 100644 --- a/src/engine/render/passes/bloom-pass.hpp +++ b/src/engine/render/passes/bloom-pass.hpp @@ -99,9 +99,6 @@ private: std::unique_ptr downsample_shader; std::unique_ptr upsample_shader; - std::unique_ptr quad_vbo; - std::unique_ptr quad_vao; - unsigned int mip_chain_length; std::vector> framebuffers; std::vector> textures; diff --git a/src/engine/render/passes/final-pass.cpp b/src/engine/render/passes/final-pass.cpp index 17228fa..23c364d 100644 --- a/src/engine/render/passes/final-pass.cpp +++ b/src/engine/render/passes/final-pass.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -55,34 +56,6 @@ final_pass::final_pass(gl::rasterizer* rasterizer, const gl::framebuffer* frameb debug::log::error("Failed to final pass shader program: {}", shader_program->info()); debug::log::warning("{}", shader_template->configure(gl::shader_stage::vertex)); } - - const math::fvec2 vertex_positions[] = - { - {-1.0f, 1.0f}, - {-1.0f, -1.0f}, - { 1.0f, 1.0f}, - { 1.0f, 1.0f}, - {-1.0f, -1.0f}, - { 1.0f, -1.0f} - }; - - const auto vertex_data = std::as_bytes(std::span{vertex_positions}); - std::size_t vertex_size = 2; - std::size_t vertex_stride = sizeof(float) * vertex_size; - - quad_vbo = std::make_unique(gl::buffer_usage::static_draw, vertex_data.size(), vertex_data); - quad_vao = std::make_unique(); - - // Define position vertex attribute - gl::vertex_attribute position_attribute; - position_attribute.buffer = quad_vbo.get(); - position_attribute.offset = 0; - position_attribute.stride = vertex_stride; - position_attribute.type = gl::vertex_attribute_type::float_32; - position_attribute.components = 2; - - // Bind vertex attributes to VAO - quad_vao->bind(render::vertex_attribute::position, position_attribute); } void final_pass::render(render::context& ctx) @@ -199,7 +172,7 @@ void final_pass::rebuild_command_buffer() ( [&]() { - rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::triangles, 0, 6); + rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); } ); } diff --git a/src/engine/render/passes/final-pass.hpp b/src/engine/render/passes/final-pass.hpp index bd28d60..b58c39e 100644 --- a/src/engine/render/passes/final-pass.hpp +++ b/src/engine/render/passes/final-pass.hpp @@ -51,9 +51,7 @@ public: private: void rebuild_command_buffer(); - std::unique_ptr shader_program; - std::unique_ptr quad_vbo; - std::unique_ptr quad_vao; + std::unique_ptr shader_program; const gl::texture_2d* color_texture; const gl::texture_2d* bloom_texture; diff --git a/src/engine/render/passes/fxaa-pass.cpp b/src/engine/render/passes/fxaa-pass.cpp index baf842d..d69b4c8 100644 --- a/src/engine/render/passes/fxaa-pass.cpp +++ b/src/engine/render/passes/fxaa-pass.cpp @@ -43,34 +43,11 @@ fxaa_pass::fxaa_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuf // Build FXAA shader program shader = shader_template->build(); - - const math::fvec2 vertex_positions[] = + if (!shader->linked()) { - {-1.0f, 1.0f}, - {-1.0f, -1.0f}, - { 1.0f, 1.0f}, - { 1.0f, 1.0f}, - {-1.0f, -1.0f}, - { 1.0f, -1.0f} - }; - - const auto vertex_data = std::as_bytes(std::span{vertex_positions}); - std::size_t vertex_size = 2; - std::size_t vertex_stride = sizeof(float) * vertex_size; - - quad_vbo = std::make_unique(gl::buffer_usage::static_draw, vertex_data.size(), vertex_data); - quad_vao = std::make_unique(); - - // Define position vertex attribute - gl::vertex_attribute position_attribute; - position_attribute.buffer = quad_vbo.get(); - position_attribute.offset = 0; - position_attribute.stride = vertex_stride; - position_attribute.type = gl::vertex_attribute_type::float_32; - position_attribute.components = 2; - - // Bind vertex attributes to VAO - quad_vao->bind(render::vertex_attribute::position, position_attribute); + debug::log::error("Failed to build FXAA shader program: {}", shader->info()); + debug::log::warning("{}", shader_template->configure(gl::shader_stage::vertex)); + } } void fxaa_pass::render(render::context& ctx) @@ -136,7 +113,7 @@ void fxaa_pass::rebuild_command_buffer() ( [&]() { - rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::triangles, 0, 6); + rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); } ); } diff --git a/src/engine/render/passes/fxaa-pass.hpp b/src/engine/render/passes/fxaa-pass.hpp index 70d2f0b..ab14e5b 100644 --- a/src/engine/render/passes/fxaa-pass.hpp +++ b/src/engine/render/passes/fxaa-pass.hpp @@ -69,8 +69,6 @@ private: void rebuild_command_buffer(); std::unique_ptr shader; - std::unique_ptr quad_vbo; - std::unique_ptr quad_vao; const gl::texture_2d* source_texture{nullptr}; diff --git a/src/engine/render/passes/material-pass.cpp b/src/engine/render/passes/material-pass.cpp index f570151..4ad8468 100644 --- a/src/engine/render/passes/material-pass.cpp +++ b/src/engine/render/passes/material-pass.cpp @@ -286,9 +286,18 @@ void material_pass::render(render::context& ctx) active_lighting_state_hash = lighting_state_hash; } - // Update geometry-dependent shader variables + model = &operation->transform; + + // @see Persson, E., & Studios, A. (2012). Creating vast game worlds: Experiences from avalanche studios. In ACM SIGGRAPH 2012 Talks (pp. 1-1). + model_view = *model; + model_view[3] -= view_translation; + model_view = view_rotation * model_view; + // model_view = (*view) * (*model); + matrix_palette = operation->matrix_palette; + + // Update geometry-dependent shader variables for (const auto& command: active_cache_entry->geometry_command_buffer) { command(); @@ -316,16 +325,13 @@ void material_pass::set_fallback_material(std::shared_ptr fall void material_pass::evaluate_camera(const render::context& ctx) { view = &ctx.camera->get_view(); + inv_view = &ctx.camera->get_inv_view(); + view_translation = math::fvec4(ctx.camera->get_translation()); + view_rotation = math::fmat4(math::fmat3(*view)); projection = &ctx.camera->get_projection(); view_projection = &ctx.camera->get_view_projection(); camera_position = &ctx.camera->get_translation(); camera_exposure = ctx.camera->get_exposure_normalization(); - clip_depth = - { - ctx.camera->get_clip_near(), - ctx.camera->get_clip_far() - }; - log_depth_coef = 2.0f / std::log2(clip_depth[1] + 1.0f); } void material_pass::evaluate_lighting(const render::context& ctx, std::uint32_t layer_mask) @@ -382,7 +388,7 @@ void material_pass::evaluate_lighting(const render::context& ctx, std::uint32_t } directional_light_colors[index] = directional_light.get_colored_illuminance() * ctx.camera->get_exposure_normalization(); - directional_light_directions[index] = directional_light.get_direction(); + directional_light_directions[index] = directional_light.get_direction() * ctx.camera->get_rotation(); // Add directional shadow if (directional_light.is_shadow_caster() && directional_light.get_shadow_framebuffer()) @@ -423,8 +429,8 @@ void material_pass::evaluate_lighting(const render::context& ctx, std::uint32_t } spot_light_colors[index] = spot_light.get_luminous_flux() * ctx.camera->get_exposure_normalization(); - spot_light_positions[index] = spot_light.get_translation(); - spot_light_directions[index] = spot_light.get_direction(); + spot_light_positions[index] = spot_light.get_translation() - ctx.camera->get_translation(); + spot_light_directions[index] = spot_light.get_direction() * ctx.camera->get_rotation(); spot_light_cutoffs[index] = spot_light.get_cosine_cutoff(); break; } @@ -444,7 +450,7 @@ void material_pass::evaluate_lighting(const render::context& ctx, std::uint32_t } point_light_colors[index] = point_light.get_colored_luminous_flux() * ctx.camera->get_exposure_normalization(); - point_light_positions[index] = point_light.get_translation(); + point_light_positions[index] = point_light.get_translation() - ctx.camera->get_translation(); break; } @@ -468,7 +474,7 @@ void material_pass::evaluate_lighting(const render::context& ctx, std::uint32_t const auto corners = rectangle_light.get_corners(); for (std::size_t i = 0; i < 4; ++i) { - rectangle_light_corners[index * 4 + i] = corners[i]; + rectangle_light_corners[index * 4 + i] = (corners[i] - ctx.camera->get_translation()) * ctx.camera->get_rotation(); } break; @@ -553,6 +559,10 @@ void material_pass::build_shader_command_buffer(std::vectorupdate(*view);}); } + if (auto inv_view_var = shader_program.variable("inv_view")) + { + command_buffer.emplace_back([&, inv_view_var](){inv_view_var->update(*inv_view);}); + } if (auto projection_var = shader_program.variable("projection")) { command_buffer.emplace_back([&, projection_var](){projection_var->update(*projection);}); @@ -569,10 +579,6 @@ void material_pass::build_shader_command_buffer(std::vectorupdate(camera_exposure);}); } - if (auto clip_depth_var = shader_program.variable("clip_depth")) - { - command_buffer.emplace_back([&, clip_depth_var](){clip_depth_var->update(clip_depth);}); - } // Update IBL variables if (auto brdf_lut_var = shader_program.variable("brdf_lut")) @@ -820,7 +826,6 @@ void material_pass::build_geometry_command_buffer(std::vectorupdate(model_view); normal_model_view_var->update(math::transpose(math::inverse(math::fmat3(model_view)))); } @@ -830,7 +835,7 @@ void material_pass::build_geometry_command_buffer(std::vectorupdate((*view) * (*model));}); + command_buffer.emplace_back([&, model_view_var](){model_view_var->update(model_view);}); } else if (normal_model_view_var) { @@ -838,7 +843,6 @@ void material_pass::build_geometry_command_buffer(std::vectorupdate(math::transpose(math::inverse(math::fmat3(model_view)))); } ); @@ -848,7 +852,7 @@ void material_pass::build_geometry_command_buffer(std::vectorupdate((*view_projection) * (*model));}); + command_buffer.emplace_back([&, model_view_projection_var](){model_view_projection_var->update((*projection) * model_view);}); } // Update matrix palette variable diff --git a/src/engine/render/passes/material-pass.hpp b/src/engine/render/passes/material-pass.hpp index 41bfe28..72bc958 100644 --- a/src/engine/render/passes/material-pass.hpp +++ b/src/engine/render/passes/material-pass.hpp @@ -91,12 +91,14 @@ private: // Camera const math::fmat4* view; + const math::fmat4* inv_view; const math::fmat4* projection; const math::fmat4* view_projection; + math::fvec4 view_translation; + math::fmat4 view_rotation; + math::fmat4 model_view; const math::fvec3* camera_position; float camera_exposure; - math::fvec2 clip_depth; - float log_depth_coef; // Light probes const gl::texture_cube* light_probe_luminance_texture{}; diff --git a/src/engine/render/passes/resample-pass.cpp b/src/engine/render/passes/resample-pass.cpp index 6192739..5ba2927 100644 --- a/src/engine/render/passes/resample-pass.cpp +++ b/src/engine/render/passes/resample-pass.cpp @@ -43,34 +43,11 @@ resample_pass::resample_pass(gl::rasterizer* rasterizer, const gl::framebuffer* // Build resample shader program shader = shader_template->build(); - - const math::fvec2 vertex_positions[] = + if (!shader->linked()) { - {-1.0f, 1.0f}, - {-1.0f, -1.0f}, - { 1.0f, 1.0f}, - { 1.0f, 1.0f}, - {-1.0f, -1.0f}, - { 1.0f, -1.0f} - }; - - const auto vertex_data = std::as_bytes(std::span{vertex_positions}); - std::size_t vertex_size = 2; - std::size_t vertex_stride = sizeof(float) * vertex_size; - - quad_vbo = std::make_unique(gl::buffer_usage::static_draw, vertex_data.size(), vertex_data); - quad_vao = std::make_unique(); - - // Define position vertex attribute - gl::vertex_attribute position_attribute; - position_attribute.buffer = quad_vbo.get(); - position_attribute.offset = 0; - position_attribute.stride = vertex_stride; - position_attribute.type = gl::vertex_attribute_type::float_32; - position_attribute.components = 2; - - // Bind vertex attributes to VAO - quad_vao->bind(render::vertex_attribute::position, position_attribute); + debug::log::error("Failed to build resample shader program: {}", shader->info()); + debug::log::warning("{}", shader_template->configure(gl::shader_stage::vertex)); + } } void resample_pass::render(render::context& ctx) @@ -125,7 +102,7 @@ void resample_pass::rebuild_command_buffer() ( [&]() { - rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::triangles, 0, 6); + rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); } ); } diff --git a/src/engine/render/passes/resample-pass.hpp b/src/engine/render/passes/resample-pass.hpp index 538268d..a3a35a2 100644 --- a/src/engine/render/passes/resample-pass.hpp +++ b/src/engine/render/passes/resample-pass.hpp @@ -68,8 +68,6 @@ private: void rebuild_command_buffer(); std::unique_ptr shader; - std::unique_ptr quad_vbo; - std::unique_ptr quad_vao; const gl::texture_2d* source_texture; diff --git a/src/engine/render/passes/sky-pass.cpp b/src/engine/render/passes/sky-pass.cpp index b5e0a02..67b0a3e 100644 --- a/src/engine/render/passes/sky-pass.cpp +++ b/src/engine/render/passes/sky-pass.cpp @@ -77,32 +77,6 @@ sky_pass::sky_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffe moon_illuminance_tween(math::fvec3{0.0f, 0.0f, 0.0f}, math::lerp), magnification(1.0f) { - // Build quad VBO and VAO - const math::fvec2 vertex_positions[] = - { - {-1.0f, 1.0f}, - {-1.0f, -1.0f}, - { 1.0f, 1.0f}, - { 1.0f, -1.0f} - }; - - const auto vertex_data = std::as_bytes(std::span{vertex_positions}); - std::size_t vertex_size = 2; - std::size_t vertex_stride = sizeof(float) * vertex_size; - - quad_vbo = std::make_unique(gl::buffer_usage::static_draw, vertex_data.size(), vertex_data); - quad_vao = std::make_unique(); - - // Define position vertex attribute - gl::vertex_attribute position_attribute; - position_attribute.buffer = quad_vbo.get(); - position_attribute.offset = 0; - position_attribute.stride = vertex_stride; - position_attribute.type = gl::vertex_attribute_type::float_32; - position_attribute.components = 2; - - // Bind vertex attributes to VAO - quad_vao->bind(render::vertex_attribute::position, position_attribute); // Transmittance LUT { @@ -223,7 +197,7 @@ void sky_pass::render(render::context& ctx) // Construct matrices const scene::camera& camera = *ctx.camera; - math::fvec3 model_scale = math::fvec3{1.0f, 1.0f, 1.0f} * (camera.get_clip_near() + camera.get_clip_far()) * 0.5f; + math::fvec3 model_scale = math::fvec3{1.0f, 1.0f, 1.0f} * camera.get_clip_near() * 2.0f; math::fmat4 model = math::scale(model_scale); math::fmat4 view = math::fmat4(math::fmat3(camera.get_view())); math::fmat4 model_view = view * model; @@ -337,7 +311,7 @@ void sky_pass::render(render::context& ctx) //if (moon_position.y() >= -moon_angular_radius) if (moon_shader_program) { - float moon_distance = (camera.get_clip_near() + camera.get_clip_far()) * 0.5f; + float moon_distance = camera.get_clip_near() * 2.0f; float moon_radius = moon_angular_radius * moon_distance; math::transform moon_transform; @@ -387,7 +361,7 @@ void sky_pass::render(render::context& ctx) // Draw stars if (star_shader_program) { - float star_distance = (camera.get_clip_near() + camera.get_clip_far()) * 0.5f; + float star_distance = camera.get_clip_near() * 2.0f; model = math::fmat4(math::fmat3(icrf_to_eus.r)) * math::scale(math::fvec3{star_distance, star_distance, star_distance}); @@ -921,7 +895,7 @@ void sky_pass::rebuild_transmittance_lut_command_buffer() ( [&]() { - rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::triangle_strip, 0, 4); + rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); } ); } @@ -1001,7 +975,7 @@ void sky_pass::rebuild_multiscattering_lut_command_buffer() ( [&]() { - rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::triangle_strip, 0, 4); + rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); } ); } @@ -1096,7 +1070,7 @@ void sky_pass::rebuild_luminance_lut_command_buffer() ( [&]() { - rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::triangle_strip, 0, 4); + rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); } ); } @@ -1152,7 +1126,7 @@ void sky_pass::rebuild_sky_probe_command_buffer() ( [&]() { - rasterizer->draw_arrays(*quad_vao, gl::drawing_mode::points, 0, 1); + rasterizer->draw_arrays(gl::drawing_mode::points, 0, 1); m_sky_probe->set_luminance_outdated(true); m_sky_probe->set_illuminance_outdated(true); } diff --git a/src/engine/render/passes/sky-pass.hpp b/src/engine/render/passes/sky-pass.hpp index efe6376..74e8311 100644 --- a/src/engine/render/passes/sky-pass.hpp +++ b/src/engine/render/passes/sky-pass.hpp @@ -233,9 +233,6 @@ private: void rebuild_sky_lut_command_buffer(); void rebuild_sky_probe_command_buffer(); - std::unique_ptr quad_vbo; - std::unique_ptr quad_vao; - // Transmittance std::uint16_t m_transmittance_lut_sample_count{40}; math::vec2 m_transmittance_lut_resolution{256, 64}; diff --git a/src/engine/render/stages/cascaded-shadow-map-stage.cpp b/src/engine/render/stages/cascaded-shadow-map-stage.cpp index 12d8a5e..ce898ce 100644 --- a/src/engine/render/stages/cascaded-shadow-map-stage.cpp +++ b/src/engine/render/stages/cascaded-shadow-map-stage.cpp @@ -300,9 +300,20 @@ void cascaded_shadow_map_stage::render_shadow_atlas(render::context& ctx, scene: // Construct light view-projection matrix const auto light_view_projection = light_projection * light_view; + const auto light_view_translation = math::fvec4(subfrustum_centroid); + const auto light_view_rotation = math::fmat4(math::fmat3(light_view)); - // Update world-space to cascade texture-space transformation matrix - cascade_matrices[i] = light.get_shadow_scale_bias_matrices()[i] * light_view_projection; + // Update view-space to cascade texture-space transformation matrix + { + const auto vs_subfrustum_centroid = math::fvec3{0.0f, 0.0f, ((subfrustum_near + subfrustum_far) * -0.5f)}; + const auto vs_light_direction = light.get_direction() * camera.get_rotation(); + const auto vs_light_up = (light.get_rotation() * math::fvec3{0, 1, 0}) * camera.get_rotation(); + + const auto vs_light_view = math::look_at_rh(vs_subfrustum_centroid, vs_subfrustum_centroid + vs_light_direction, vs_light_up); + const auto vs_light_view_projection = light_projection * vs_light_view; + + cascade_matrices[i] = light.get_shadow_scale_bias_matrices()[i] * vs_light_view_projection; + } // Queue render operations queue(ctx, light, light_view_projection); @@ -352,7 +363,11 @@ void cascaded_shadow_map_stage::render_shadow_atlas(render::context& ctx, scene: } // Calculate model-view-projection matrix - const auto model_view_projection = light_view_projection * operation->transform; + // @see Persson, E., & Studios, A. (2012). Creating vast game worlds: Experiences from avalanche studios. In ACM SIGGRAPH 2012 Talks (pp. 1-1). + auto model_view = operation->transform; + model_view[3] -= light_view_translation; + model_view = light_view_rotation * model_view; + const auto model_view_projection = light_projection * model_view; // Upload operation-dependent parameters to shader program if (active_shader_program == m_static_mesh_shader_program.get()) diff --git a/src/engine/render/stages/light-probe-stage.cpp b/src/engine/render/stages/light-probe-stage.cpp index b817914..3f47a1a 100644 --- a/src/engine/render/stages/light-probe-stage.cpp +++ b/src/engine/render/stages/light-probe-stage.cpp @@ -31,35 +31,6 @@ namespace render { light_probe_stage::light_probe_stage(gl::rasterizer& rasterizer, ::resource_manager& resource_manager): m_rasterizer(&rasterizer) { - // Build quad VBO and VAO - { - const math::fvec2 vertex_positions[] = - { - {-1.0f, 1.0f}, - {-1.0f, -1.0f}, - { 1.0f, 1.0f}, - { 1.0f, -1.0f} - }; - - const auto vertex_data = std::as_bytes(std::span{vertex_positions}); - const std::size_t vertex_size = 2; - const std::size_t vertex_stride = sizeof(float) * vertex_size; - - m_quad_vbo = std::make_unique(gl::buffer_usage::static_draw, vertex_data.size(), vertex_data); - m_quad_vao = std::make_unique(); - - // Define position vertex attribute - gl::vertex_attribute position_attribute; - position_attribute.buffer = m_quad_vbo.get(); - position_attribute.offset = 0; - position_attribute.stride = vertex_stride; - position_attribute.type = gl::vertex_attribute_type::float_32; - position_attribute.components = 2; - - // Bind vertex attributes to VAO - m_quad_vao->bind(render::vertex_attribute::position, position_attribute); - } - // Load cubemap to spherical harmonics shader template and build shader program m_cubemap_to_sh_shader_template = resource_manager.load("cubemap-to-sh.glsl"); rebuild_cubemap_to_sh_shader_program(); @@ -148,7 +119,7 @@ void light_probe_stage::update_light_probes_luminance(const std::vectoruse_framebuffer(*light_probe.get_luminance_framebuffers()[i]); // Downsample - m_rasterizer->draw_arrays(*m_quad_vao, gl::drawing_mode::points, 0, 1); + m_rasterizer->draw_arrays(gl::drawing_mode::points, 0, 1); } // Bind cubemap filter shader program @@ -175,7 +146,7 @@ void light_probe_stage::update_light_probes_luminance(const std::vectoruse_framebuffer(*light_probe.get_luminance_framebuffers()[i]); // Filter - m_rasterizer->draw_arrays(*m_quad_vao, gl::drawing_mode::points, 0, 1); + m_rasterizer->draw_arrays(gl::drawing_mode::points, 0, 1); } // Restore cubemap mipmap range @@ -221,7 +192,7 @@ void light_probe_stage::update_light_probes_illuminance(const std::vectorupdate(*light_probe.get_luminance_texture()); // Draw quad - m_rasterizer->draw_arrays(*m_quad_vao, gl::drawing_mode::triangle_strip, 0, 4); + m_rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); // Mark light probe illuminance as current light_probe.set_illuminance_outdated(false); @@ -334,7 +305,7 @@ void light_probe_stage::rebuild_cubemap_filter_lut_texture() m_cubemap_filter_lut_resolution_var->update(math::fvec2{static_cast(m_cubemap_filter_lut_texture->get_width()), static_cast(m_cubemap_filter_lut_texture->get_height())}); m_cubemap_filter_lut_face_size_var->update(128.0f); m_cubemap_filter_lut_mip_bias_var->update(m_cubemap_filter_mip_bias); - m_rasterizer->draw_arrays(*m_quad_vao, gl::drawing_mode::triangle_strip, 0, 4); + m_rasterizer->draw_arrays(gl::drawing_mode::triangles, 0, 3); } void light_probe_stage::rebuild_cubemap_filter_shader_program() diff --git a/src/engine/render/stages/light-probe-stage.hpp b/src/engine/render/stages/light-probe-stage.hpp index 9025da8..55971dc 100644 --- a/src/engine/render/stages/light-probe-stage.hpp +++ b/src/engine/render/stages/light-probe-stage.hpp @@ -114,16 +114,15 @@ private: void update_light_probes_illuminance(const std::vector& light_probes); gl::rasterizer* m_rasterizer; - std::unique_ptr m_quad_vbo; - std::unique_ptr m_quad_vao; std::shared_ptr m_cubemap_to_sh_shader_template; std::unique_ptr m_cubemap_to_sh_shader_program; const gl::shader_variable* m_cubemap_to_sh_cubemap_var{}; - std::size_t m_sh_sample_count{1024}; + std::size_t m_sh_sample_count{512}; bool m_reproject_sh{true}; std::shared_ptr m_cubemap_downsample_shader_template; std::unique_ptr m_cubemap_downsample_shader_program; + const gl::shader_variable* m_cubemap_downsample_cubemap_var{}; std::vector> m_cubemap_downsample_framebuffers; std::unique_ptr m_cubemap_downsample_texture; diff --git a/src/engine/resources/physfs/physfs-deserialize-context.cpp b/src/engine/resources/physfs/physfs-deserialize-context.cpp index 1189873..c09bb41 100644 --- a/src/engine/resources/physfs/physfs-deserialize-context.cpp +++ b/src/engine/resources/physfs/physfs-deserialize-context.cpp @@ -23,8 +23,8 @@ physfs_deserialize_context::physfs_deserialize_context(const std::filesystem::path& path) { // Open file for reading using PhysicsFS - file = PHYSFS_openRead(path.string().c_str()); - if (!file) + m_file = PHYSFS_openRead(path.string().c_str()); + if (!m_file) { throw deserialize_error(PHYSFS_getLastError()); } @@ -33,29 +33,29 @@ physfs_deserialize_context::physfs_deserialize_context(const std::filesystem::pa m_path = path; // Set EOF and error flags if file not open. - m_eof = !file; - m_error = !file; + m_eof = !m_file; + m_error = !m_file; } physfs_deserialize_context::~physfs_deserialize_context() { - if (file) + if (m_file) { - PHYSFS_close(file); + PHYSFS_close(m_file); } } void physfs_deserialize_context::open(const std::filesystem::path& path) { // Close file, if open - if (file) + if (m_file) { - PHYSFS_close(file); + PHYSFS_close(m_file); } // Open file for reading using PhysicsFS - file = PHYSFS_openRead(path.string().c_str()); - if (!file) + m_file = PHYSFS_openRead(path.string().c_str()); + if (!m_file) { throw deserialize_error(PHYSFS_getLastError()); } @@ -64,16 +64,16 @@ void physfs_deserialize_context::open(const std::filesystem::path& path) m_path = path; // Set EOF and error flags if file not open. - m_eof = !file; - m_error = !file; + m_eof = !m_file; + m_error = !m_file; } void physfs_deserialize_context::close() noexcept { - if (file) + if (m_file) { - m_error = !PHYSFS_close(file); - file = nullptr; + m_error = !PHYSFS_close(m_file); + m_file = nullptr; m_path.clear(); m_eof = true; } @@ -81,7 +81,7 @@ void physfs_deserialize_context::close() noexcept bool physfs_deserialize_context::is_open() const noexcept { - return file; + return m_file; } const std::filesystem::path& physfs_deserialize_context::path() const noexcept @@ -101,7 +101,7 @@ bool physfs_deserialize_context::eof() const noexcept std::size_t physfs_deserialize_context::size() const noexcept { - PHYSFS_sint64 length = PHYSFS_fileLength(file); + const PHYSFS_sint64 length = PHYSFS_fileLength(m_file); if (length >= 0) { return static_cast(length); @@ -112,10 +112,10 @@ std::size_t physfs_deserialize_context::size() const noexcept std::size_t physfs_deserialize_context::tell() const { - PHYSFS_sint64 offset = PHYSFS_fileLength(file); + const PHYSFS_sint64 offset = PHYSFS_tell(m_file); if (offset < 0) { - //m_error = true; + // m_error = true; throw deserialize_error(PHYSFS_getLastError()); } @@ -124,22 +124,22 @@ std::size_t physfs_deserialize_context::tell() const void physfs_deserialize_context::seek(std::size_t offset) { - if (!PHYSFS_seek(file, static_cast(offset))) + if (!PHYSFS_seek(m_file, static_cast(offset))) { m_error = true; throw deserialize_error(PHYSFS_getLastError()); } - m_eof = (PHYSFS_eof(file) != 0); + m_eof = (PHYSFS_eof(m_file) != 0); } std::size_t physfs_deserialize_context::read8(std::byte* data, std::size_t count) { - const PHYSFS_sint64 status = PHYSFS_readBytes(file, data, count); + const PHYSFS_sint64 status = PHYSFS_readBytes(m_file, data, count); if (status != count) { - if (status < 0 || !PHYSFS_eof(file)) + if (status < 0 || !PHYSFS_eof(m_file)) { m_error = true; throw deserialize_error(PHYSFS_getLastError()); @@ -161,10 +161,10 @@ std::size_t physfs_deserialize_context::read16_le(std::byte* data, std::size_t c for (std::size_t i = 0; i < count; ++i) { - if (!PHYSFS_readULE16(file, data16)) + if (!PHYSFS_readULE16(m_file, data16)) { m_error = true; - m_eof = (PHYSFS_eof(file) != 0); + m_eof = (PHYSFS_eof(m_file) != 0); throw deserialize_error(PHYSFS_getLastError()); } @@ -180,10 +180,10 @@ std::size_t physfs_deserialize_context::read16_be(std::byte* data, std::size_t c for (std::size_t i = 0; i < count; ++i) { - if (!PHYSFS_readUBE16(file, data16)) + if (!PHYSFS_readUBE16(m_file, data16)) { m_error = true; - m_eof = (PHYSFS_eof(file) != 0); + m_eof = (PHYSFS_eof(m_file) != 0); throw deserialize_error(PHYSFS_getLastError()); } @@ -199,10 +199,10 @@ std::size_t physfs_deserialize_context::read32_le(std::byte* data, std::size_t c for (std::size_t i = 0; i < count; ++i) { - if (!PHYSFS_readULE32(file, data32)) + if (!PHYSFS_readULE32(m_file, data32)) { m_error = true; - m_eof = (PHYSFS_eof(file) != 0); + m_eof = (PHYSFS_eof(m_file) != 0); throw deserialize_error(PHYSFS_getLastError()); } @@ -218,10 +218,10 @@ std::size_t physfs_deserialize_context::read32_be(std::byte* data, std::size_t c for (std::size_t i = 0; i < count; ++i) { - if (!PHYSFS_readUBE32(file, data32)) + if (!PHYSFS_readUBE32(m_file, data32)) { m_error = true; - m_eof = (PHYSFS_eof(file) != 0); + m_eof = (PHYSFS_eof(m_file) != 0); throw deserialize_error(PHYSFS_getLastError()); } @@ -237,10 +237,10 @@ std::size_t physfs_deserialize_context::read64_le(std::byte* data, std::size_t c for (std::size_t i = 0; i < count; ++i) { - if (!PHYSFS_readULE64(file, data64)) + if (!PHYSFS_readULE64(m_file, data64)) { m_error = true; - m_eof = (PHYSFS_eof(file) != 0); + m_eof = (PHYSFS_eof(m_file) != 0); throw deserialize_error(PHYSFS_getLastError()); } @@ -256,10 +256,10 @@ std::size_t physfs_deserialize_context::read64_be(std::byte* data, std::size_t c for (std::size_t i = 0; i < count; ++i) { - if (!PHYSFS_readUBE64(file, data64)) + if (!PHYSFS_readUBE64(m_file, data64)) { m_error = true; - m_eof = (PHYSFS_eof(file) != 0); + m_eof = (PHYSFS_eof(m_file) != 0); throw deserialize_error(PHYSFS_getLastError()); } diff --git a/src/engine/resources/physfs/physfs-deserialize-context.hpp b/src/engine/resources/physfs/physfs-deserialize-context.hpp index 167da1d..32140c9 100644 --- a/src/engine/resources/physfs/physfs-deserialize-context.hpp +++ b/src/engine/resources/physfs/physfs-deserialize-context.hpp @@ -83,7 +83,7 @@ public: std::size_t read64_be(std::byte* data, std::size_t count) noexcept(false) override; private: - PHYSFS_File* file{nullptr}; + PHYSFS_File* m_file{nullptr}; std::filesystem::path m_path; bool m_eof{true}; bool m_error{false}; diff --git a/src/engine/scene/camera.cpp b/src/engine/scene/camera.cpp index 3576653..dd1d6b5 100644 --- a/src/engine/scene/camera.cpp +++ b/src/engine/scene/camera.cpp @@ -26,9 +26,8 @@ namespace scene { geom::ray camera::pick(const math::fvec2& ndc) const { const auto near = m_inv_view_projection * math::fvec4{ndc[0], ndc[1], 1.0f, 1.0f}; - const auto far = m_inv_view_projection * math::fvec4{ndc[0], ndc[1], 0.0f, 1.0f}; const auto origin = math::fvec3(near) / near[3]; - const auto direction = math::normalize(math::fvec3(far) / far[3] - origin); + const auto direction = math::normalize(origin - get_translation()); return {origin, direction}; } @@ -59,7 +58,7 @@ math::fvec3 camera::unproject(const math::fvec3& window, const math::fvec4& view return math::fvec3(result) * (1.0f / result[3]); } -void camera::set_perspective(float vertical_fov, float aspect_ratio, float clip_near, float clip_far) +void camera::set_perspective(float vertical_fov, float aspect_ratio, float near, float far) { // Set projection mode to perspective m_orthographic = false; @@ -67,11 +66,18 @@ void camera::set_perspective(float vertical_fov, float aspect_ratio, float clip_ // Update perspective projection parameters m_vertical_fov = vertical_fov; m_aspect_ratio = aspect_ratio; - m_clip_near = clip_near; - m_clip_far = clip_far; + m_clip_near = near; + m_clip_far = far; // Recalculate projection matrix (reversed depth) and its inverse - std::tie(m_projection, m_inv_projection) = math::perspective_half_z_inv(m_vertical_fov, m_aspect_ratio, m_clip_far, m_clip_near); + if (m_clip_far == std::numeric_limits::infinity()) + { + std::tie(m_projection, m_inv_projection) = math::inf_perspective_half_z_reverse_inv(m_vertical_fov, m_aspect_ratio, m_clip_near); + } + else + { + std::tie(m_projection, m_inv_projection) = math::perspective_half_z_inv(m_vertical_fov, m_aspect_ratio, m_clip_far, m_clip_near); + } // Recalculate view-projection matrix m_view_projection = m_projection * m_view; @@ -89,6 +95,14 @@ void camera::set_vertical_fov(float vertical_fov) } } +void camera::set_aspect_ratio(float aspect_ratio) +{ + if (!m_orthographic) + { + set_perspective(m_vertical_fov, aspect_ratio, m_clip_near, m_clip_far); + } +} + void camera::set_orthographic(float clip_left, float clip_right, float clip_bottom, float clip_top, float clip_near, float clip_far) { // Set projection mode to orthographic diff --git a/src/engine/scene/camera.hpp b/src/engine/scene/camera.hpp index 21b012d..32bb83b 100644 --- a/src/engine/scene/camera.hpp +++ b/src/engine/scene/camera.hpp @@ -70,10 +70,10 @@ public: * * @param vertical_fov Vertical field of view, in radians. * @param aspect_ratio Aspect ratio. - * @param clip_near Distance to near clipping plane. - * @param clip_far Distance to far clipping plane. + * @param near Distance to near clipping plane. + * @param far Distance to far clipping plane. */ - void set_perspective(float vertical_fov, float aspect_ratio, float clip_near, float clip_far); + void set_perspective(float vertical_fov, float aspect_ratio, float near, float far = std::numeric_limits::infinity()); /** * Sets the camera's vertical field of view. @@ -82,6 +82,13 @@ public: */ void set_vertical_fov(float vertical_fov); + /** + * Sets the camera's aspect ratio. + * + * @param aspect_ratio Aspect ratio. + */ + void set_aspect_ratio(float aspect_ratio); + /** * Sets the camera's projection matrix using orthographic projection. * diff --git a/src/engine/scene/text.cpp b/src/engine/scene/text.cpp index 59809a8..dcffc51 100644 --- a/src/engine/scene/text.cpp +++ b/src/engine/scene/text.cpp @@ -214,8 +214,8 @@ void text::update_content() positions[i].y() = std::round(positions[i].y()); // Normalize UVs - uvs[i].x() = uvs[i].x() / static_cast(font_bitmap.width()); - uvs[i].y() = uvs[i].y() / static_cast(font_bitmap.height()); + uvs[i].x() = uvs[i].x() / static_cast(font_bitmap.size().x()); + uvs[i].y() = uvs[i].y() / static_cast(font_bitmap.size().y()); } // Add vertex to vertex data buffer diff --git a/src/engine/type/bitmap-font.cpp b/src/engine/type/bitmap-font.cpp index 3adf0c7..b3d9397 100644 --- a/src/engine/type/bitmap-font.cpp +++ b/src/engine/type/bitmap-font.cpp @@ -75,8 +75,8 @@ bool bitmap_font::pack(bool resize) std::uint32_t max_glyph_h = 0; for (auto it = glyphs.begin(); it != glyphs.end(); ++it) { - max_glyph_w = std::max(max_glyph_w, it->second.bitmap.width()); - max_glyph_h = std::max(max_glyph_h, it->second.bitmap.height()); + max_glyph_w = std::max(max_glyph_w, static_cast(it->second.bitmap.size().x())); + max_glyph_h = std::max(max_glyph_h, static_cast(it->second.bitmap.size().y())); } // Find minimum power of two dimensions that can accommodate maximum glyph dimensions @@ -85,8 +85,8 @@ bool bitmap_font::pack(bool resize) } else { - bitmap_w = bitmap.width(); - bitmap_h = bitmap.height(); + bitmap_w = static_cast(bitmap.size().x()); + bitmap_h = static_cast(bitmap.size().y()); } bool packed = false; @@ -99,7 +99,7 @@ bool bitmap_font::pack(bool resize) for (auto it = glyphs.begin(); it != glyphs.end(); ++it) { // Attempt to pack glyph bitmap - const auto* node = glyph_pack.pack(it->second.bitmap.width(), it->second.bitmap.height()); + const auto* node = glyph_pack.pack(static_cast(it->second.bitmap.size().x()), static_cast(it->second.bitmap.size().y())); // Abort if packing failed if (!node) @@ -142,7 +142,7 @@ bool bitmap_font::pack(bool resize) if (packed) { // Resize font bitmap - bitmap.resize(bitmap_w, bitmap_h); + bitmap.resize({bitmap_w, bitmap_h, 1}); // For each glyph for (auto it = glyphs.begin(); it != glyphs.end(); ++it) @@ -152,14 +152,13 @@ bool bitmap_font::pack(bool resize) // Copy glyph bitmap data into font bitmap image& glyph_bitmap = it->second.bitmap; - bitmap.copy(glyph_bitmap, glyph_bitmap.width(), glyph_bitmap.height(), 0, 0, node->bounds.min.x(), node->bounds.min.y()); + bitmap.copy(glyph_bitmap, {glyph_bitmap.size().x(), glyph_bitmap.size().y()}, {0, 0}, math::uvec2{node->bounds.min.x(), node->bounds.min.y()}); // Record coordinates of glyph bitmap within font bitmap it->second.position = {node->bounds.min.x(), node->bounds.min.y()}; // Clear glyph bitmap data - glyph_bitmap.resize(0, 0); - + glyph_bitmap.resize({0u, 0u, 0u}); } } @@ -179,23 +178,23 @@ void bitmap_font::unpack(bool resize) // Reformat glyph bitmap if necessary if (!glyph.bitmap.compatible(bitmap)) { - glyph.bitmap.format(bitmap.component_size(), bitmap.channel_count()); + glyph.bitmap.format(bitmap.channels(), bitmap.bit_depth()); } // Resize glyph bitmap if necessary - if (glyph.bitmap.width() != glyph_width || glyph.bitmap.height() != glyph_height) + if (static_cast(glyph.bitmap.size().x()) != glyph_width || static_cast(glyph.bitmap.size().y()) != glyph_height) { - glyph.bitmap.resize(glyph_width, glyph_height); + glyph.bitmap.resize(math::uvec2{glyph_width, glyph_height}); } // Copy pixel data from font bitmap to glyph bitmap - glyph.bitmap.copy(bitmap, glyph_width, glyph_height, glyph.position.x(), glyph.position.y()); + glyph.bitmap.copy(bitmap, math::uvec2{glyph_width, glyph_height}, math::uvec2{glyph.position.x(), glyph.position.y()}); } // Free font bitmap pixel data if (resize) { - bitmap.resize(0, 0); + bitmap.resize({0, 0, 0}); } } diff --git a/src/engine/type/freetype/ft-typeface.cpp b/src/engine/type/freetype/ft-typeface.cpp index 9efdfad..8e88269 100644 --- a/src/engine/type/freetype/ft-typeface.cpp +++ b/src/engine/type/freetype/ft-typeface.cpp @@ -111,12 +111,12 @@ bool ft_typeface::get_bitmap(float height, char32_t code, image& bitmap) const } // Format and resize bitmap - bitmap.resize(0, 0); - bitmap.format(sizeof(FT_Byte), 1); - bitmap.resize(face->glyph->bitmap.width, face->glyph->bitmap.rows); + bitmap.resize({0, 0, 0}); + bitmap.format(1, sizeof(FT_Byte) * 8); + bitmap.resize({face->glyph->bitmap.width, face->glyph->bitmap.rows, 1}); // Copy glyph bitmap data in bitmap - std::memcpy(bitmap.data(), face->glyph->bitmap.buffer, bitmap.size()); + std::memcpy(bitmap.data(), face->glyph->bitmap.buffer, bitmap.size_bytes()); return true; } diff --git a/src/engine/utility/image.cpp b/src/engine/utility/image.cpp index 7ec2ccc..1e8f09a 100644 --- a/src/engine/utility/image.cpp +++ b/src/engine/utility/image.cpp @@ -22,24 +22,22 @@ #include #include #include +#include #include #include #include bool image::compatible(const image& other) const noexcept { - return (other.m_component_size == m_component_size && other.m_channel_count == m_channel_count); + return (other.m_channels == m_channels && other.m_bit_depth == m_bit_depth); } void image::copy ( const image& source, - std::uint32_t w, - std::uint32_t h, - std::uint32_t from_x, - std::uint32_t from_y, - std::uint32_t to_x, - std::uint32_t to_y + const math::uvec2& dimensions, + const math::uvec2& from, + const math::uvec2& to ) { if (!compatible(source)) @@ -47,64 +45,81 @@ void image::copy throw std::runtime_error("Cannot copy image with mismatched format"); } - const std::byte* from_pixels = source.pixels.data(); - std::byte* to_pixels = pixels.data(); - - for (std::uint32_t i = 0; i < h; ++i) + for (auto i = 0u; i < dimensions.y(); ++i) { // Calculate vertical pixel offset - std::uint32_t from_i = from_y + i; - std::uint32_t to_i = to_y + i; + const auto from_i = from.y() + i; + const auto to_i = to.y() + i; // Bounds check - if (from_i >= source.m_height || to_i >= m_height) + if (from_i >= source.m_size.y() || to_i >= m_size.y()) { break; } - for (std::uint32_t j = 0; j < w; ++j) + for (auto j = 0u; j < dimensions.x(); ++j) { // Calculate horizontal pixel offsets - std::uint32_t from_j = from_x + j; - std::uint32_t to_j = to_x + j; + const auto from_j = from.x() + j; + const auto to_j = to.x() + j; // Bounds check - if (from_j >= source.m_width || to_j >= m_width) + if (from_j >= source.m_size.x() || to_j >= m_size.x()) { continue; } // Calculate pixel data offset (in bytes) - std::size_t from_offset = (from_i * source.m_width + from_j) * m_pixel_size; - std::size_t to_offset = (to_i * m_width + to_j) * m_pixel_size; + const auto from_offset = (static_cast(from_i) * source.m_size.x() + from_j) * m_pixel_stride; + const auto to_offset = (static_cast(to_i) * m_size.x() + to_j) * m_pixel_stride; // Copy single pixel - std::memcpy(to_pixels + to_offset, from_pixels + from_offset, m_pixel_size); + std::memcpy(data() + to_offset, source.data() + from_offset, m_pixel_stride); } } } -void image::format(std::size_t component_size, std::uint8_t channel_count) +void image::format(unsigned int channels, unsigned int bit_depth) { - if (m_component_size != component_size || m_channel_count != channel_count) + if (bit_depth % 8 != 0) + { + throw std::runtime_error("Image bit depth must be byte-aligned"); + } + + if (m_channels != channels || m_bit_depth != bit_depth) { - m_component_size = component_size; - m_channel_count = channel_count; - m_pixel_size = m_component_size * m_channel_count; - pixels.resize(m_width * m_height * m_pixel_size); + m_channels = channels; + m_bit_depth = bit_depth; + m_pixel_stride = m_channels * (m_bit_depth >> 3); + m_sample_scale = static_cast(1.0 / (std::exp2(m_bit_depth) - 1.0)); + m_data.resize(static_cast(m_size.x()) * m_size.y() * m_size.z() * m_pixel_stride); } } -void image::resize(std::uint32_t width, std::uint32_t height) +void image::resize(const math::uvec3& size) { - if (m_width != width || m_height == height) + if (m_size.x() != size.x() || m_size.y() != size.y() || m_size.z() != size.z()) { - m_width = width; - m_height = height; - pixels.resize(m_width * m_height * m_pixel_size); + m_size = size; + m_data.resize(static_cast(m_size.x()) * m_size.y() * m_size.z() * m_pixel_stride); } } +math::fvec4 image::sample(std::size_t index) const +{ + math::fvec4 color{0, 0, 0, 1}; + + const auto pixel_data = data() + index * m_pixel_stride; + for (auto i = 0u; i < std::min(4u, m_channels); ++i) + { + std::uint32_t value = 0u; + std::memcpy(&value, pixel_data + (m_bit_depth >> 3) * i, m_bit_depth >> 3); + color[i] = static_cast(value) * m_sample_scale; + } + + return color; +} + static void deserialize_tinyexr(image& image, deserialize_context& ctx) { const char* error = nullptr; @@ -167,11 +182,11 @@ static void deserialize_tinyexr(image& image, deserialize_context& ctx) file_buffer.clear(); // Format and resize image - image.format(sizeof(float), static_cast(exr_image.num_channels)); - image.resize(static_cast(exr_image.width), static_cast(exr_image.height)); + image.format(exr_image.num_channels, sizeof(float) * 8); + image.resize({static_cast(exr_image.width), static_cast(exr_image.height), 1u}); // Fill image pixels - float* component = reinterpret_cast(image.data()); + std::byte* component = image.data(); for (int y = exr_image.height - 1; y >= 0; --y) { int row_offset = y * exr_image.width; @@ -182,7 +197,8 @@ static void deserialize_tinyexr(image& image, deserialize_context& ctx) for (int c = exr_image.num_channels - 1; c >= 0; --c) { - *(component++) = reinterpret_cast(exr_image.images)[c][pixel_index]; + std::memcpy(component, exr_image.images[c] + pixel_index * sizeof(float), sizeof(float)); + component += sizeof(float); } } } @@ -223,23 +239,46 @@ static void deserialize_stb_image(image& image, deserialize_context& ctx) &stb_io_eof }; - // Load image data int width = 0; int height = 0; int channels = 0; - stbi_uc* pixels = stbi_load_from_callbacks(&io_callbacks, &ctx, &width, &height, &channels, 0); - if (!pixels) + + if (stbi_is_16_bit_from_callbacks(&io_callbacks, &ctx)) { - throw deserialize_error(stbi_failure_reason()); + // Load 16-bit image + ctx.seek(0); + stbi_us* pixels = stbi_load_16_from_callbacks(&io_callbacks, &ctx, &width, &height, &channels, 0); + if (!pixels) + { + throw deserialize_error(stbi_failure_reason()); + } + + // Format image and resize image, then copy pixel data + image.format(static_cast(channels), 16u); + image.resize({static_cast(width), static_cast(height), 1u}); + std::memcpy(image.data(), pixels, image.size_bytes()); + + // Free loaded image data + stbi_image_free(pixels); + } + else + { + // Load 8-bit image + ctx.seek(0); + stbi_uc* pixels = stbi_load_from_callbacks(&io_callbacks, &ctx, &width, &height, &channels, 0); + if (!pixels) + { + throw deserialize_error(stbi_failure_reason()); + } + + // Format image and resize image, then copy pixel data + image.format(static_cast(channels), 8u); + image.resize({static_cast(width), static_cast(height), 1u}); + std::memcpy(image.data(), pixels, image.size_bytes()); + + // Free loaded image data + stbi_image_free(pixels); } - - // Create image - image.format(sizeof(stbi_uc), static_cast(channels)); - image.resize(static_cast(width), static_cast(height)); - std::memcpy(image.data(), pixels, image.size()); - - // Free loaded image data - stbi_image_free(pixels); } /** diff --git a/src/engine/utility/image.hpp b/src/engine/utility/image.hpp index e2abd47..e16f372 100644 --- a/src/engine/utility/image.hpp +++ b/src/engine/utility/image.hpp @@ -21,179 +21,218 @@ #define ANTKEEPER_UTILITY_IMAGE_HPP #include -#include -#include #include +#include /** - * Stores basic image data. + * Pixel data buffer. */ class image { public: /** - * Returns an iterator to the first pixel. + * Checks whether another image has the same number of channels and pixel size as this image. * - * @tparam T Pixel data type. + * @param other Image for with which to compare compatibility. + * @return `true` if the image formats are compatible, `false` otherwise. + */ + [[nodiscard]] bool compatible(const image& other) const noexcept; + + /** + * Copies pixel data from another image with a compatible format into this image. + * + * @param source Source image from which to copy pixel data. + * @param dimensions Dimensions of the subimage to copy. + * @param from Coordinates of the first pixel to copy from the source subimage. + * @param to Coordinates of the first pixel in the destination subimage. + * + * @except std::runtime_error Cannot copy image with mismatched format. + * + * @see image::compatible(const image&) const + */ + void copy + ( + const image& source, + const math::uvec2& dimensions, + const math::uvec2& from = {}, + const math::uvec2& to = {} + ); + + /** + * Changes the format of the image. + * + * @param channels Number of channels in the image. + * @param bit_depth Number of bits per channel. + * + * @warning Pre-existing pixel data will be invalidated. + * @warning Bit depth must be byte-aligned. + * + * @except std::runtime_error Image bit depth must be byte-aligned. + */ + void format(unsigned int channels, unsigned int bit_depth = 8u); + + /** + * Resizes the image. + * + * @param size New dimensions of the image, in pixels. + * + * @warning Pre-existing pixel data will be invalidated. */ /// @{ - template - [[nodiscard]] inline constexpr T* begin() noexcept + inline void resize(unsigned int size) { - static_assert(std::is_standard_layout::value, "Pixel iterator type is not standard-layout."); - static_assert(std::is_trivial::value, "Pixel iterator type is not trivial."); - return reinterpret_cast(pixels.data()); + resize(math::uvec3{size, 1u, 1u}); } - template - [[nodiscard]] inline constexpr const T* begin() const noexcept + + inline void resize(const math::uvec2& size) { - static_assert(std::is_standard_layout::value, "Pixel iterator type is not standard-layout."); - static_assert(std::is_trivial::value, "Pixel iterator type is not trivial."); - return reinterpret_cast(pixels.data()); + resize(math::uvec3{size.x(), size.y(), 1u}); } - template - [[nodiscard]] inline constexpr const T* cbegin() const noexcept + + void resize(const math::uvec3& size); + /// @} + + /// @name Pixel access + /// @{ + + /// Returns a pointer to the pixel data. + /// @{ + [[nodiscard]] inline constexpr const std::byte* data() const noexcept + { + return m_data.data(); + } + [[nodiscard]] inline constexpr std::byte* data() noexcept { - static_assert(std::is_standard_layout::value, "Pixel iterator type is not standard-layout."); - static_assert(std::is_trivial::value, "Pixel iterator type is not trivial."); - return reinterpret_cast(pixels.data()); + return m_data.data(); } /// @} /** - * Returns an iterator to the pixel following the last pixel. + * Returns the value of a pixel. * * @tparam T Pixel data type. + * + * @param position Coordinates of a pixel. + * + * @return Pixel value. */ /// @{ template - [[nodiscard]] inline constexpr T* end() noexcept + [[nodiscard]] T get(unsigned int position) const { - static_assert(std::is_standard_layout::value, "Pixel iterator type is not standard-layout."); - static_assert(std::is_trivial::value, "Pixel iterator type is not trivial."); - return reinterpret_cast(pixels.data() + pixels.size()); + T value; + std::memcpy(&value, data() + static_cast(position) * m_pixel_stride, sizeof(T)); + return value; } + template - [[nodiscard]] inline constexpr const T* end() const noexcept + [[nodiscard]] T get(const math::uvec2& position) const { - static_assert(std::is_standard_layout::value, "Pixel iterator type is not standard-layout."); - static_assert(std::is_trivial::value, "Pixel iterator type is not trivial."); - return reinterpret_cast(pixels.data() + pixels.size()); + const auto index = static_cast(position.y()) * m_size.x() + position.x(); + T value; + std::memcpy(&value, data() + index * m_pixel_stride, sizeof(T)); + return value; } + template - [[nodiscard]] inline constexpr const T* cend() const noexcept + [[nodiscard]] T get(const math::uvec3& position) const { - static_assert(std::is_standard_layout::value, "Pixel iterator type is not standard-layout."); - static_assert(std::is_trivial::value, "Pixel iterator type is not trivial."); - return reinterpret_cast(pixels.data() + pixels.size()); + const auto index = (static_cast(position.z()) * m_size.y() + position.y()) * m_size.x() + position.x(); + T value; + std::memcpy(&value, data() + index * m_pixel_stride, sizeof(T)); + return value; } /// @} /** - * Checks whether another image has the same number of channels and pixel size as this image. - * - * @param other Image for with which to compare compatibility. - * @return `true` if the image formats are compatible, `false` otherwise. - */ - [[nodiscard]] bool compatible(const image& other) const noexcept; - - /** - * Copies pixel data from another image with a compatible format into this image. + * Sets the value of a pixel. * - * @param source Source image from which to copy pixel data. - * @param w Width of the subimage to copy. - * @param h Height of the subimage to copy. - * @param from_x X-coordinate of the first pixel to copy from the source subimage. - * @param from_y Y-coordinate of the first pixel to copy from the source subimage. - * @param to_x X-coordinate of the first pixel in the destination subimage. - * @param to_y Y-coordinate of the first pixel in the destination subimage. - * - * @except std::runtime_error Cannot copy image with mismatched format. - * - * @see image::compatible(const image&) const - */ - void copy - ( - const image& source, - std::uint32_t w, - std::uint32_t h, - std::uint32_t from_x = 0, - std::uint32_t from_y = 0, - std::uint32_t to_x = 0, - std::uint32_t to_y = 0 - ); - - /** - * Changes the format of the image. Existing pixel data will be erased if the format has changed. - * - * @param component_size Size of channel components, in bytes. - * @param channel_count Number of channels in the image. - */ - void format(std::size_t component_size, std::uint8_t channel_count); - - /** - * Resizes the image. Existing pixel data will be erased if the size has changed. + * @tparam T Pixel data type. * - * @param width New width of the image, in pixels. - * @param height New height of the image, in pixels. + * @param position Coordinates of a pixel. + * @param value Pixel value. */ - void resize(std::uint32_t width, std::uint32_t height); - - /// Returns the width of the image, in pixels. - [[nodiscard]] inline std::uint32_t width() const noexcept + /// @{ + template + [[nodiscard]] void set(unsigned int position, const T& value) { - return m_width; + std::memcpy(data() + static_cast(position) * m_pixel_stride, &value, sizeof(T)); } - - /// Returns the height of the image, in pixels. - [[nodiscard]] inline std::uint32_t height() const noexcept + + template + [[nodiscard]] void set(const math::uvec2& position, const T& value) { - return m_height; + const auto index = static_cast(position.y()) * m_size.x() + position.x(); + std::memcpy(data() + index * m_pixel_stride, &value, sizeof(T)); } - - /// Returns the number of color channels in the image. - [[nodiscard]] inline std::uint8_t channel_count() const noexcept + + template + [[nodiscard]] void set(const math::uvec3& position, const T& value) { - return m_channel_count; + const auto index = (static_cast(position.z()) * m_size.y() + position.y()) * m_size.x() + position.x(); + std::memcpy(data() + index * m_pixel_stride, &value, sizeof(T)); } + /// @} - /// Returns a pointer to the pixel data. + /** + * Samples a texel. + * + * @param position Coordinates of a pixel. + * + * @return RGBA texel, on `[0, 1]`. + */ /// @{ - [[nodiscard]] inline const std::byte* data() const noexcept + [[nodiscard]] inline math::fvec4 sample(unsigned int position) const + { + return sample(static_cast(position)); + } + + [[nodiscard]] inline math::fvec4 sample(const math::uvec2& position) const { - return pixels.data(); + return sample(static_cast(position.y()) * m_size.x() + position.x()); } - [[nodiscard]] inline std::byte* data() noexcept + + [[nodiscard]] inline math::fvec4 sample(const math::uvec3& position) const { - return pixels.data(); + return sample((static_cast(position.z()) * m_size.y() + position.y()) * m_size.x() + position.x()); } /// @} - /// Returns the size of channel components, in bytes. - [[nodiscard]] inline std::size_t component_size() const noexcept + /// @} + + /// Returns the dimensions of the the image, in pixels. + [[nodiscard]] inline constexpr const math::uvec3& size() const noexcept + { + return m_size; + } + + /// Returns the number of channels in the image. + [[nodiscard]] inline constexpr unsigned int channels() const noexcept { - return m_component_size; + return m_channels; } - /// Returns the size of a single pixel, in bytes. - [[nodiscard]] inline std::size_t pixel_size() const noexcept + /// Returns the number of bits per channel in the image. + [[nodiscard]] inline constexpr unsigned int bit_depth() const noexcept { - return m_pixel_size; + return m_bit_depth; } /// Returns the size of the image, in bytes. - [[nodiscard]] inline std::size_t size() const noexcept + [[nodiscard]] inline constexpr std::size_t size_bytes() const noexcept { - return pixels.size(); + return m_data.size(); } private: - std::uint32_t m_width{0}; - std::uint32_t m_height{0}; - std::uint8_t m_channel_count{0}; - std::size_t m_component_size{0}; - std::size_t m_pixel_size{0}; - std::vector pixels; + [[nodiscard]] math::fvec4 sample(std::size_t index) const; + + math::uvec3 m_size{}; + unsigned int m_channels{}; + unsigned int m_bit_depth{}; + unsigned int m_pixel_stride{}; + float m_sample_scale{}; + std::vector m_data; }; #endif // ANTKEEPER_UTILITY_IMAGE_HPP diff --git a/src/game/components/navmesh-agent-component.hpp b/src/game/components/navmesh-agent-component.hpp index 647a716..350a19b 100644 --- a/src/game/components/navmesh-agent-component.hpp +++ b/src/game/components/navmesh-agent-component.hpp @@ -20,6 +20,7 @@ #ifndef ANTKEEPER_GAME_NAVMESH_AGENT_COMPONENT_HPP #define ANTKEEPER_GAME_NAVMESH_AGENT_COMPONENT_HPP +#include #include #include @@ -28,6 +29,9 @@ */ struct navmesh_agent_component { + /// Entity ID of the navmesh + entity::id navmesh_eid{entt::null}; + /// Pointer to the current mesh through which the agent is navigating. geom::brep_mesh* mesh{}; diff --git a/src/game/components/terrain-component.hpp b/src/game/components/terrain-component.hpp index d4b4fca..d60af31 100644 --- a/src/game/components/terrain-component.hpp +++ b/src/game/components/terrain-component.hpp @@ -20,20 +20,22 @@ #ifndef ANTKEEPER_GAME_TERRAIN_COMPONENT_HPP #define ANTKEEPER_GAME_TERRAIN_COMPONENT_HPP -#include -#include +#include +#include +#include +/// Grid of terrain cells. +struct terrain_grid_component +{ + math::uvec2 dimensions; + std::vector cells; +}; -struct terrain_component +/// Single cell in a terrain grid. +struct terrain_cell_component { - /// Function object which returns elevation (in meters) given latitude (radians) and longitude (radians). - std::function elevation; - - /// Maximum level of detail (maximum quadtree depth level) - std::size_t max_lod; - - /// Material for terrain patches; - render::material* patch_material; + entity::id grid_eid; + math::uvec2 coordinates; }; diff --git a/src/game/fonts.cpp b/src/game/fonts.cpp index d80cfe6..af98be8 100644 --- a/src/game/fonts.cpp +++ b/src/game/fonts.cpp @@ -38,7 +38,7 @@ static void build_bitmap_font(const type::typeface& typeface, float size, const // Format font bitmap image& font_bitmap = font.get_bitmap(); - font_bitmap.format(sizeof(std::byte), 1); + font_bitmap.format(1, sizeof(std::byte) * 8); // For each UTF-32 character code in the character set for (char32_t code: charset) @@ -64,12 +64,12 @@ static void build_bitmap_font(const type::typeface& typeface, float size, const { // Update font texture auto texture = std::static_pointer_cast(var)->get(); - texture->resize(font_bitmap.width(), font_bitmap.height(), font_bitmap.data()); + texture->resize(static_cast(font_bitmap.size().x()), static_cast(font_bitmap.size().y()), font_bitmap.data()); } else { // Create font texture from bitmap - std::shared_ptr font_texture = std::make_shared(font_bitmap.width(), font_bitmap.height(), gl::pixel_type::uint_8, gl::pixel_format::r, gl::color_space::linear, font_bitmap.data()); + std::shared_ptr font_texture = std::make_shared(static_cast(font_bitmap.size().x()), static_cast(font_bitmap.size().y()), gl::pixel_type::uint_8, gl::pixel_format::r, gl::transfer_function::linear, font_bitmap.data()); font_texture->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); font_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); diff --git a/src/game/game.cpp b/src/game/game.cpp index 87f6d1b..8a07ab7 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -803,7 +803,7 @@ void game::setup_scenes() // Allocate and init surface camera surface_camera = std::make_shared(); - surface_camera->set_perspective(math::radians(45.0f), viewport_aspect_ratio, 0.5f, 1000.0f); + surface_camera->set_perspective(math::radians(45.0f), viewport_aspect_ratio, 0.5f); surface_camera->set_compositor(surface_compositor.get()); surface_camera->set_composite_index(0); @@ -813,7 +813,7 @@ void game::setup_scenes() // Allocate and init underground camera underground_camera = std::make_shared(); - underground_camera->set_perspective(math::radians(45.0f), viewport_aspect_ratio, 0.1f, 200.0f); + underground_camera->set_perspective(math::radians(45.0f), viewport_aspect_ratio, 0.5f); underground_camera->set_compositor(underground_compositor.get()); underground_camera->set_composite_index(0); @@ -980,13 +980,7 @@ void game::setup_ui() ::graphics::change_render_resolution(*this, render_scale); // Update camera projection matrix - surface_camera->set_perspective - ( - surface_camera->get_vertical_fov(), - viewport_aspect_ratio, - surface_camera->get_clip_near(), - surface_camera->get_clip_far() - ); + surface_camera->set_aspect_ratio(viewport_aspect_ratio); // Update UI camera projection matrix ui_camera->set_orthographic diff --git a/src/game/graphics.cpp b/src/game/graphics.cpp index fb50ecc..c842c40 100644 --- a/src/game/graphics.cpp +++ b/src/game/graphics.cpp @@ -167,8 +167,8 @@ void save_screenshot(::game& ctx) // Allocate screenshot image std::shared_ptr frame = std::make_shared(); - frame->format(1, 3); - frame->resize(viewport_size.x(), viewport_size.y()); + frame->format(3, 8); + frame->resize({static_cast(viewport_size.x()), static_cast(viewport_size.y()), 1}); // Read pixel data from backbuffer into image glReadBuffer(GL_BACK); @@ -177,10 +177,10 @@ void save_screenshot(::game& ctx) // Write screenshot file in separate thread std::thread ( - [frame, path = std::move(screenshot_filepath_string)] + [frame = std::move(frame), path = std::move(screenshot_filepath_string)] { stbi_flip_vertically_on_write(1); - stbi_write_png(path.c_str(), frame->width(), frame->height(), frame->channel_count(), frame->data(), frame->width() * frame->channel_count()); + stbi_write_png(path.c_str(), static_cast(frame->size().x()), static_cast(frame->size().y()), static_cast(frame->channels()), frame->data(), static_cast(frame->size().x() * frame->channels())); debug::log::debug("Saved screenshot to \"{}\"", path); } diff --git a/src/game/states/experiments/treadmill-experiment-state.cpp b/src/game/states/experiments/treadmill-experiment-state.cpp index 7982675..d65fe0b 100644 --- a/src/game/states/experiments/treadmill-experiment-state.cpp +++ b/src/game/states/experiments/treadmill-experiment-state.cpp @@ -61,6 +61,7 @@ #include "game/systems/camera-system.hpp" #include "game/systems/collision-system.hpp" #include "game/systems/physics-system.hpp" +#include "game/systems/terrain-system.hpp" #include "game/world.hpp" #include #include @@ -117,14 +118,17 @@ treadmill_experiment_state::treadmill_experiment_state(::game& ctx): // Create nest exterior { scene_component nest_exterior_scene_component; - nest_exterior_scene_component.object = std::make_shared(ctx.resource_manager->load("cube-nest-200mm-interior.mdl")); + nest_exterior_scene_component.object = std::make_shared(ctx.resource_manager->load("cube-nest-200mm-exterior.mdl")); nest_exterior_scene_component.layer_mask = 1; - auto nest_exterior_mesh = ctx.resource_manager->load("cube-nest-200mm-interior.msh"); + auto nest_exterior_mesh = ctx.resource_manager->load("cube-nest-200mm-exterior.msh"); auto nest_exterior_rigid_body = std::make_unique(); nest_exterior_rigid_body->set_mass(0.0f); nest_exterior_rigid_body->set_collider(std::make_shared(std::move(nest_exterior_mesh))); + nest_exterior_rigid_body->set_position({10, -20, -5}); + nest_exterior_rigid_body->set_orientation(math::angle_axis(math::radians(30.0f), math::fvec3{1, 0, 0})); + nest_exterior_rigid_body->set_scale({0.5f, 1.0f, 0.75f}); auto nest_exterior_eid = ctx.entity_registry->create(); ctx.entity_registry->emplace(nest_exterior_eid, std::move(nest_exterior_scene_component)); @@ -150,6 +154,43 @@ treadmill_experiment_state::treadmill_experiment_state(::game& ctx): ctx.entity_registry->emplace(nest_interior_eid, std::move(nest_interior_rigid_body)); } + // Generate terrain + { + auto heightmap = ctx.resource_manager->load("chiricahua-s.png"); + auto subdivisions = math::uvec2{0, 0}; + // auto subdivisions = math::uvec2{3, 3}; + auto transform = math::transform::identity(); + transform.scale.x() = 2000.0f; + transform.scale.y() = 400.0f; + transform.scale.z() = transform.scale.x(); + // transform.scale *= 0.001f; + transform.translation.y() = -transform.scale.y() * 0.5f; + // transform.rotation = math::angle_axis(math::radians(45.0f), math::fvec3{0, 0, 1}); + auto material = ctx.resource_manager->load("desert-sand.mtl"); + ctx.terrain_system->generate(heightmap, subdivisions, transform, material); + } + + // Generate vegetation + { + auto planet_eid = ctx.entity_registry->create(); + scene_component scene; + scene.object = std::make_shared(ctx.resource_manager->load("yucca-plant-l.mdl")); + scene.object->set_translation({0, 0, 0}); + scene.layer_mask = 1; + + const auto placement_ray = geom::ray{{50.0f, 0.0f, 70.0f}, {0.0f, -1.0f, 0.0f}}; + if (auto trace = ctx.physics_system->trace(placement_ray, entt::null, 1u)) + { + const auto hit_distance = std::get<1>(*trace); + const auto& hit_normal = std::get<3>(*trace); + + scene.object->set_translation(placement_ray.extrapolate(hit_distance)); + scene.object->set_rotation(math::rotation(math::fvec3{0, 1, 0}, hit_normal)); + } + + ctx.entity_registry->emplace(planet_eid, std::move(scene)); + } + // Create rectangle light { @@ -534,6 +575,7 @@ void treadmill_experiment_state::setup_controls() worker_eid, [&](auto& component) { + component.navmesh_eid = std::get<0>(*trace); component.mesh = hit_mesh; component.face = hit_face; component.surface_normal = hit_normal; diff --git a/src/game/states/main-menu-state.cpp b/src/game/states/main-menu-state.cpp index 09aba70..8a1567b 100644 --- a/src/game/states/main-menu-state.cpp +++ b/src/game/states/main-menu-state.cpp @@ -266,11 +266,6 @@ main_menu_state::main_menu_state(::game& ctx, bool fade_in): const float ev100_sunny16 = physics::light::ev::from_settings(16.0f, 1.0f / 100.0f, 100.0f); ctx.surface_camera->set_exposure_value(ev100_sunny16); - const float aspect_ratio = viewport_size.x() / viewport_size.y(); - float fov = math::vertical_fov(math::radians(100.0f), aspect_ratio); - - ctx.surface_camera->look_at({0, 2.0f, 0}, {0, 0, 0}, {0, 0, 1}); - ctx.surface_camera->set_perspective(fov, ctx.surface_camera->get_aspect_ratio(), ctx.surface_camera->get_clip_near(), ctx.surface_camera->get_clip_far()); // Setup and enable sky and ground passes ctx.sky_pass->set_enabled(true); diff --git a/src/game/states/nest-view-state.cpp b/src/game/states/nest-view-state.cpp index 62d54aa..26ce8a5 100644 --- a/src/game/states/nest-view-state.cpp +++ b/src/game/states/nest-view-state.cpp @@ -416,20 +416,7 @@ void nest_view_state::handle_mouse_motion(const input::mouse_moved_event& event) void nest_view_state::update_third_person_camera() { - const math::dvec3 camera_position = third_person_camera_focal_point + third_person_camera_orientation * math::dvec3{0.0f, 0.0f, third_person_camera_focal_distance}; - - ctx.entity_registry->patch - ( - third_person_camera_rig_eid, - [&](auto& component) - { - auto& camera = static_cast(*component.object); - - camera.set_translation(math::fvec3(camera_position)); - camera.set_rotation(math::fquat(third_person_camera_orientation)); - camera.set_perspective(static_cast(third_person_camera_vfov), camera.get_aspect_ratio(), camera.get_clip_near(), camera.get_clip_far()); - } - ); + } geom::ray nest_view_state::get_mouse_ray(const math::vec2& mouse_position) const diff --git a/src/game/systems/camera-system.cpp b/src/game/systems/camera-system.cpp index bcc5140..725c5af 100644 --- a/src/game/systems/camera-system.cpp +++ b/src/game/systems/camera-system.cpp @@ -70,7 +70,7 @@ void camera_system::interpolate(float alpha) autofocus.focal_distance = autofocus.focal_plane_height * 0.5 / std::tan(autofocus.vfov * 0.5); // Update camera projection matrix - camera.set_perspective(static_cast(autofocus.vfov), camera.get_aspect_ratio(), camera.get_clip_near(), camera.get_clip_far()); + camera.set_vertical_fov(static_cast(autofocus.vfov)); } ); */ @@ -150,7 +150,7 @@ void camera_system::interpolate(float alpha) camera_transform.translation += math::fvec3(spring_arm.camera_rotation * math::dvec3{0, center_offset, 0}); camera.set_transform(camera_transform); - camera.set_perspective(static_cast(spring_arm.vfov), camera.get_aspect_ratio(), camera.get_clip_near(), camera.get_clip_far()); + camera.set_vertical_fov(static_cast(spring_arm.vfov)); } ); } diff --git a/src/game/systems/locomotion-system.cpp b/src/game/systems/locomotion-system.cpp index c32e28b..cb1e42b 100644 --- a/src/game/systems/locomotion-system.cpp +++ b/src/game/systems/locomotion-system.cpp @@ -83,17 +83,29 @@ void locomotion_system::update_legged(float t, float dt) auto& navmesh_agent = legged_group.get(entity_id); if (locomotion.speed != 0.0f/* && cos_target_direction >= 0.0f*/ && navmesh_agent.face) { - // Get rigid body - auto& rigid_body = *legged_group.get(entity_id).body; - const auto& rigid_body_transform = rigid_body.get_transform(); + // Get agent rigid body + auto& agent_rigid_body = *legged_group.get(entity_id).body; + const auto& agent_transform = agent_rigid_body.get_transform(); + + // Get navmesh rigid body + auto& navmesh_rigid_body = *registry.get(navmesh_agent.navmesh_eid).body; + const auto& navmesh_transform = navmesh_rigid_body.get_transform(); + + // Determine start and end points of traversal + const auto traversal_direction = agent_transform.rotation * math::fvec3{0, 0, 1}; + auto traversal_start = agent_transform.translation; + auto traversal_end = agent_transform.translation + traversal_direction * (locomotion.speed * dt); - // Construct ray with origin at agent position and forward-facing direction - geom::ray traversal_ray; - traversal_ray.origin = rigid_body_transform.translation; - traversal_ray.direction = rigid_body_transform.rotation * math::fvec3{0, 0, 1}; + // Transform traversal segment from world-space to navmesh-space + traversal_start = ((traversal_start - navmesh_transform.translation) * navmesh_transform.rotation) / navmesh_transform.scale; + traversal_end = ((traversal_end - navmesh_transform.translation) * navmesh_transform.rotation) / navmesh_transform.scale; - // Traverse navmesh along ray - const auto traversal = ai::traverse_navmesh(*navmesh_agent.mesh, navmesh_agent.face, traversal_ray, locomotion.speed * dt); + // Traverse navmesh + // NOTE: if the navmesh has a nonuniform scale, the traversal will be skewed + auto traversal = ai::traverse_navmesh(*navmesh_agent.mesh, navmesh_agent.face, traversal_start, traversal_end); + + // Transform traversal end point from navmesh-space world-space + traversal.closest_point = navmesh_transform.translation + (navmesh_transform.rotation * (navmesh_transform.scale * traversal.closest_point)); // Update navmesh agent face navmesh_agent.face = traversal.face; @@ -107,14 +119,17 @@ void locomotion_system::update_legged(float t, float dt) const auto& uvw = traversal.barycentric; navmesh_agent.surface_normal = math::normalize(na * uvw.x() + nb * uvw.y() + nc * uvw.z()); + // Transform surface normal from navmesh-space to world-space + navmesh_agent.surface_normal = math::normalize(navmesh_transform.rotation * (navmesh_agent.surface_normal / navmesh_transform.scale)); + // const auto& face_normals = navmesh_agent.mesh->faces().attributes().at("normal"); // navmesh_agent.surface_normal = face_normals[navmesh_agent.face->index()]; - // Update rigid body - rigid_body.set_position(traversal.closest_point); - // rigid_body.set_position(traversal_ray.extrapolate(locomotion.speed * dt)); - // rigid_body.set_orientation(math::normalize(math::rotation(rigid_body_transform.rotation * math::fvec3{0, 1, 0}, navmesh_agent.surface_normal) * rigid_body_transform.rotation)); - rigid_body.set_orientation(math::normalize(math::rotation(rigid_body_transform.rotation * math::fvec3{0, 1, 0}, navmesh_agent.surface_normal) * rigid_body_transform.rotation)); + // Update agent rigid body + agent_rigid_body.set_position(traversal.closest_point); + // agent_rigid_body.set_position(traversal_ray.extrapolate(locomotion.speed * dt)); + // agent_rigid_body.set_orientation(math::normalize(math::rotation(rigid_body_transform.rotation * math::fvec3{0, 1, 0}, navmesh_agent.surface_normal) * rigid_body_transform.rotation)); + agent_rigid_body.set_orientation(math::normalize(math::rotation(agent_transform.rotation * math::fvec3{0, 1, 0}, navmesh_agent.surface_normal) * agent_transform.rotation)); } // Animate legs diff --git a/src/game/systems/physics-system.cpp b/src/game/systems/physics-system.cpp index 7937817..f51b04d 100644 --- a/src/game/systems/physics-system.cpp +++ b/src/game/systems/physics-system.cpp @@ -112,7 +112,7 @@ void physics_system::interpolate(float alpha) std::optional> physics_system::trace(const geom::ray& ray, entity::id ignore_eid, std::uint32_t layer_mask) const { entity::id nearest_entity_id = entt::null; - float nearest_hit_distance = std::numeric_limits::infinity(); + float nearest_hit_sqr_distance = std::numeric_limits::infinity(); std::uint32_t nearest_face_index = 0; math::fvec3 nearest_hit_normal; @@ -142,23 +142,26 @@ std::optional> physics continue; } - // Transform ray into rigid body space - const auto inv_transform = math::inverse(rigid_body.get_transform()); - geom::ray bs_ray; - bs_ray.origin = inv_transform * ray.origin; - bs_ray.direction = inv_transform.rotation * ray.direction; - if (collider->type() == physics::collider_type::mesh) { + // Transform ray into rigid body space + const auto& transform = rigid_body.get_transform(); + geom::ray bs_ray; + bs_ray.origin = ((ray.origin - transform.translation) * transform.rotation) / transform.scale; + bs_ray.direction = math::normalize((ray.direction * transform.rotation) / transform.scale); + const auto& mesh = static_cast(*collider); if (auto intersection = mesh.intersection(bs_ray)) { - if (std::get<0>(*intersection) < nearest_hit_distance) + const auto point = rigid_body.get_transform() * bs_ray.extrapolate(std::get<0>(*intersection)); + const auto sqr_distance = math::sqr_distance(point, ray.origin); + + if (sqr_distance < nearest_hit_sqr_distance) { + nearest_hit_sqr_distance = sqr_distance; nearest_entity_id = entity_id; - - /// @TODO: doesn't take into account rigid body scale - std::tie(nearest_hit_distance, nearest_face_index, nearest_hit_normal) = *intersection; + nearest_face_index = std::get<1>(*intersection); + nearest_hit_normal = math::normalize(transform.rotation * (std::get<2>(*intersection) / transform.scale)); } } } @@ -169,7 +172,7 @@ std::optional> physics return std::nullopt; } - return std::make_tuple(nearest_entity_id, nearest_hit_distance, nearest_face_index, nearest_hit_normal); + return std::make_tuple(nearest_entity_id, std::sqrt(nearest_hit_sqr_distance), nearest_face_index, nearest_hit_normal); } void physics_system::integrate(float dt) diff --git a/src/game/systems/terrain-system.cpp b/src/game/systems/terrain-system.cpp index 51d5245..aabebf9 100644 --- a/src/game/systems/terrain-system.cpp +++ b/src/game/systems/terrain-system.cpp @@ -18,6 +18,21 @@ */ #include "game/systems/terrain-system.hpp" +#include "game/components/terrain-component.hpp" +#include "game/components/rigid-body-component.hpp" +#include "game/components/scene-component.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include terrain_system::terrain_system(entity::registry& registry): updatable_system(registry) @@ -29,3 +44,274 @@ terrain_system::~terrain_system() void terrain_system::update(float t, float dt) { } + +entity::id terrain_system::generate(std::shared_ptr heightmap, const math::uvec2& subdivisions, const math::transform& transform, std::shared_ptr material) +{ + if (!heightmap) + { + debug::log::error("Failed to generate terrain from null heightmap"); + throw std::invalid_argument("Failed to generate terrain from null heightmap"); + } + + if (heightmap->size().x() < 2 || heightmap->size().y() < 2) + { + debug::log::error("Heightmap size less than 2x2"); + throw std::runtime_error("Heightmap size less than 2x2"); + } + + if (((heightmap->size().x() - 1) % (subdivisions.x() + 1)) != 0 || + ((heightmap->size().y() - 1) % (subdivisions.y() + 1)) != 0) + { + debug::log::error("{}x{} heightmap cannot be subdivided {}x{} times", + heightmap->size().x(), + heightmap->size().y(), + subdivisions.x(), + subdivisions.y()); + throw std::runtime_error("Heightmap subdivision failed"); + } + + // Generate terrain grid + terrain_grid_component grid; + grid.dimensions = subdivisions + 1u; + grid.cells.resize(grid.dimensions.x() * grid.dimensions.y()); + auto grid_eid = registry.create(); + for (auto y = 0u; y < grid.dimensions.y(); ++y) + { + for (auto x = 0u; x < grid.dimensions.x(); ++x) + { + auto cell_eid = registry.create(); + registry.emplace(cell_eid, grid_eid, math::uvec2{x, y}); + grid.cells[y * grid.dimensions.x() + x] = cell_eid; + } + } + + // Calculate cell dimensions + const auto cell_quad_dimensions = math::uvec2{static_cast(heightmap->size().x() - 1) / grid.dimensions.x(), static_cast(heightmap->size().y() - 1) / grid.dimensions.y()}; + const auto cell_vert_dimensions = cell_quad_dimensions + 1u; + + const auto max_scale = math::max(transform.scale); + const auto scale_ratio = transform.scale / max_scale; + const auto vertex_scale = scale_ratio * math::fvec3{2.0f / static_cast(cell_quad_dimensions.x()), 2.0f, 2.0f / static_cast(cell_quad_dimensions.y())}; + const auto vertex_translation = -scale_ratio; + + // Generate terrain cell meshes + std::for_each + ( + std::execution::seq, + std::begin(grid.cells), + std::end(grid.cells), + [&](auto cell_eid) + { + const auto& cell = registry.get(cell_eid); + + // Allocate cell mesh and attributes + auto mesh = std::make_shared(); + auto& vertex_positions = static_cast&>(*mesh->vertices().attributes().emplace("position")); + + auto cell_pixel_bounds_min = cell.coordinates * cell_quad_dimensions; + auto cell_pixel_bounds_max = cell_pixel_bounds_min + cell_quad_dimensions; + + // Build cell vertices + math::uvec2 pixel_position; + for (pixel_position.y() = cell_pixel_bounds_min.y(); pixel_position.y() <= cell_pixel_bounds_max.y(); ++pixel_position.y()) + { + for (pixel_position.x() = cell_pixel_bounds_min.x(); pixel_position.x() <= cell_pixel_bounds_max.x(); ++pixel_position.x()) + { + // Allocate vertex + auto vertex = mesh->vertices().emplace_back(); + + // Get vertex height from heightmap + float height = heightmap->sample(pixel_position).x(); + + // Set vertex position + auto& position = vertex_positions[vertex->index()]; + position.x() = static_cast(pixel_position.x()) * vertex_scale.x() + vertex_translation.x(); + position.y() = height * vertex_scale.y() + vertex_translation.y(); + position.z() = static_cast(pixel_position.y()) * vertex_scale.z() + vertex_translation.z(); + } + } + + // Build cell faces + for (auto y = 0u; y < cell_quad_dimensions.y(); ++y) + { + for (auto x = 0u; x < cell_quad_dimensions.x(); ++x) + { + auto a = mesh->vertices()[y * cell_vert_dimensions.x() + x]; + auto b = mesh->vertices()[a->index() + cell_vert_dimensions.x()]; + auto c = mesh->vertices()[a->index() + 1]; + auto d = mesh->vertices()[b->index() + 1]; + + geom::brep_vertex* abc[3] = {a, b, c}; + geom::brep_vertex* cbd[3] = {c, b, d}; + + mesh->faces().emplace_back(abc); + mesh->faces().emplace_back(cbd); + } + } + + // Generate vertex normals + auto& vertex_normals = static_cast&>(*mesh->vertices().attributes().try_emplace("normal").first); + for (pixel_position.y() = cell_pixel_bounds_min.y(); pixel_position.y() <= cell_pixel_bounds_max.y(); ++pixel_position.y()) + { + for (pixel_position.x() = cell_pixel_bounds_min.x(); pixel_position.x() <= cell_pixel_bounds_max.x(); ++pixel_position.x()) + { + const auto pixel_w = pixel_position.x() >= 1u ? pixel_position - math::uvec2{1, 0} : pixel_position; + const auto pixel_e = pixel_position.x() < cell_pixel_bounds_max.x() ? pixel_position + math::uvec2{1, 0} : pixel_position; + const auto pixel_s = pixel_position.y() >= 1u ? pixel_position - math::uvec2{0, 1} : pixel_position; + const auto pixel_n = pixel_position.y() < cell_pixel_bounds_max.y() ? pixel_position + math::uvec2{0, 1} : pixel_position; + + const auto index_c = pixel_position.y() * (cell_pixel_bounds_max.x() + 1) + pixel_position.x(); + const auto index_w = pixel_w.y() * (cell_pixel_bounds_max.x() + 1) + pixel_w.x(); + const auto index_e = pixel_e.y() * (cell_pixel_bounds_max.x() + 1) + pixel_e.x(); + const auto index_s = pixel_s.y() * (cell_pixel_bounds_max.x() + 1) + pixel_s.x(); + const auto index_n = pixel_n.y() * (cell_pixel_bounds_max.x() + 1) + pixel_n.x(); + + const auto height_w = vertex_positions[index_w].y(); + const auto height_e = vertex_positions[index_e].y(); + const auto height_s = vertex_positions[index_s].y(); + const auto height_n = vertex_positions[index_n].y(); + + // float height_w = heightmap->sample(pixel_w).x(); + // float height_e = heightmap->sample(pixel_e).x(); + // float height_s = heightmap->sample(pixel_s).x(); + // float height_n = heightmap->sample(pixel_n).x(); + + auto& normal_c = vertex_normals[index_c]; + normal_c = math::normalize(math::fvec3{(height_w - height_e) / vertex_scale.x(), 2.0f, (height_s - height_n) / vertex_scale.z()}); + } + } + + // Construct terrain cell rigid body + auto rigid_body = std::make_unique(); + rigid_body->set_mass(0.0f); + rigid_body->set_collider(std::make_shared(mesh)); + rigid_body->set_transform({transform.translation, transform.rotation, math::fvec3{max_scale, max_scale, max_scale} * 0.5f}); + registry.emplace(cell_eid, std::move(rigid_body)); + + auto model = generate_terrain_model(*mesh, material, cell_quad_dimensions); + scene_component scene; + scene.object = std::make_shared(std::move(model)); + scene.layer_mask = 1; + registry.emplace(cell_eid, std::move(scene)); + } + ); + + registry.emplace(grid_eid, std::move(grid)); + return grid_eid; +} + +std::unique_ptr terrain_system::generate_terrain_model(const geom::brep_mesh& mesh, std::shared_ptr material, const math::uvec2& quad_dimensions) const +{ + const auto& vertex_positions = mesh.vertices().attributes().at("position"); + const auto& vertex_normals = mesh.vertices().attributes().at("normal"); + + // Allocate model + auto model = std::make_unique(); + + // Init model bounds + auto& bounds = model->get_bounds(); + bounds = {math::fvec3::infinity(), -math::fvec3::infinity()}; + + // Get model VBO and VAO + auto& vbo = model->get_vertex_buffer(); + auto& vao = model->get_vertex_array(); + + // Build vertex format + const std::size_t vertex_size = 3 * sizeof(std::int16_t) + 3 * sizeof(float); + gl::vertex_attribute position_attribute; + position_attribute.buffer = vbo.get(); + position_attribute.offset = 0; + position_attribute.stride = vertex_size; + position_attribute.type = gl::vertex_attribute_type::int_16; + position_attribute.components = 3; + position_attribute.normalized = true; + gl::vertex_attribute normal_attribute; + normal_attribute.buffer = vbo.get(); + normal_attribute.offset = 3 * sizeof(std::int16_t); + normal_attribute.stride = vertex_size; + normal_attribute.type = gl::vertex_attribute_type::float_32; + normal_attribute.components = 3; + + const auto vert_dimensions = quad_dimensions + 1u; + + // Interleave vertex data + const std::size_t vertex_count = 2 * (vert_dimensions.x() * quad_dimensions.y() + quad_dimensions.y() - 1); + std::vector vertex_data(vertex_count * vertex_size); + std::byte* v = vertex_data.data(); + + auto normalized_int16 = [](const math::fvec3& f) -> math::vec3 + { + math::vec3 i; + for (int j = 0; j < 3; ++j) + { + i[j] = static_cast(f[j] < 0.0f ? f[j] * 32768.0f : f[j] * 32767.0f); + } + return i; + }; + + for (auto y = 0u; y < quad_dimensions.y(); ++y) + { + std::size_t indices[2]; + + for (auto x = 0u; x < vert_dimensions.x(); ++x) + { + indices[0] = y * vert_dimensions.x() + x; + indices[1] = indices[0] + vert_dimensions.x(); + + for (auto i: indices) + { + auto position = normalized_int16(vertex_positions[i]); + + std::memcpy(v, &position[0], sizeof(std::int16_t) * 3); + v += sizeof(std::int16_t) * 3; + std::memcpy(v, &vertex_normals[i], sizeof(float) * 3); + v += sizeof(float) * 3; + + // Extend model bounds + bounds.extend(vertex_positions[i]); + } + } + + if (y < quad_dimensions.y() - 1) + { + // Restart triangle strip on next row using degenerate triangles + + + auto position = normalized_int16(vertex_positions[indices[1]]); + std::memcpy(v, &position[0], sizeof(std::int16_t) * 3); + v += sizeof(int16_t) * 3; + std::memcpy(v, &vertex_normals[indices[1]], sizeof(float) * 3); + v += sizeof(float) * 3; + + indices[0] = (y + 1) * vert_dimensions.x(); + + position = normalized_int16(vertex_positions[indices[0]]); + std::memcpy(v, &position[0], sizeof(std::int16_t) * 3); + v += sizeof(int16_t) * 3; + std::memcpy(v, &vertex_normals[indices[0]], sizeof(float) * 3); + v += sizeof(float) * 3; + } + } + + // Resize model VBO and upload interleaved vertex data + vbo->resize(vertex_data.size(), vertex_data); + + // Free interleaved vertex data + vertex_data.clear(); + + // Bind vertex attributes to VAO + vao->bind(render::vertex_attribute::position, position_attribute); + vao->bind(render::vertex_attribute::normal, normal_attribute); + + // Create material group + model->get_groups().resize(1); + render::model_group& model_group = model->get_groups().front(); + + model_group.id = {}; + model_group.material = material; + model_group.drawing_mode = gl::drawing_mode::triangle_strip; + model_group.start_index = 0; + model_group.index_count = static_cast(vertex_count); + + return model; +} diff --git a/src/game/systems/terrain-system.hpp b/src/game/systems/terrain-system.hpp index ecc39d3..2ca36c1 100644 --- a/src/game/systems/terrain-system.hpp +++ b/src/game/systems/terrain-system.hpp @@ -23,6 +23,13 @@ #include "game/systems/updatable-system.hpp" #include "game/components/terrain-component.hpp" #include +#include +#include +#include +#include +#include +#include +#include /** * Generates terrain patches and performs terrain patch LOD selection. @@ -34,9 +41,25 @@ public: ~terrain_system(); virtual void update(float t, float dt); + + /** + * Generates terrain entities from a heightmap. + * + * @param heightmap Heightmap from which the terrain should be generated. + * @param subdivisions Number of heightmap subdivisions on the x- and z-axes. Determines the number of terrain entities generated. + * @param transform Translation, rotation, and scale of the terrain. + * @param material Terrain material. + * + * @return Entity ID of the generated terrain grid. + * + * @except std::invalid_argument Failed to generate terrain from null heightmap. + * @except std::runtime_error Heightmap size less than 2x2. + * @except std::runtime_error Heightmap subdivision failed. + */ + entity::id generate(std::shared_ptr heightmap, const math::uvec2& subdivisions, const math::transform& transform, std::shared_ptr material); private: - + [[nodiscard]] std::unique_ptr generate_terrain_model(const geom::brep_mesh& mesh, std::shared_ptr material, const math::uvec2& quad_dimensions) const; }; #endif // ANTKEEPER_GAME_TERRAIN_SYSTEM_HPP diff --git a/src/game/textures/cocoon-silk-sdf.cpp b/src/game/textures/cocoon-silk-sdf.cpp index 76ad8ca..30a9d28 100644 --- a/src/game/textures/cocoon-silk-sdf.cpp +++ b/src/game/textures/cocoon-silk-sdf.cpp @@ -28,10 +28,11 @@ void generate_cocoon_silk_sdf(std::filesystem::path path) { + /* debug::log::info("Generating cocoon silk SDF image..."); image img; - img.format(1, 4); + img.format(4, 8); img.resize(2048, 2048); auto width = img.width(); @@ -98,4 +99,5 @@ void generate_cocoon_silk_sdf(std::filesystem::path path) stbi_write_png(path.string().c_str(), img.width(), img.height(), img.channel_count(), img.data(), img.width() * img.channel_count()); debug::log::info("Saved cocoon silk SDF image to \"{}\"", path.string()); + */ } diff --git a/src/game/textures/rgb-voronoi-noise.cpp b/src/game/textures/rgb-voronoi-noise.cpp index 8477285..3ffed8f 100644 --- a/src/game/textures/rgb-voronoi-noise.cpp +++ b/src/game/textures/rgb-voronoi-noise.cpp @@ -28,8 +28,9 @@ void generate_rgb_voronoi_noise(std::filesystem::path path) { + /* image img; - img.format(1, 4); + img.format(4, 8); img.resize(1024, 1024); auto width = img.width(); @@ -91,4 +92,5 @@ void generate_rgb_voronoi_noise(std::filesystem::path path) stbi_flip_vertically_on_write(1); stbi_write_png(path.string().c_str(), img.width(), img.height(), img.channel_count(), img.data(), img.width() * img.channel_count()); + */ } diff --git a/src/game/world.cpp b/src/game/world.cpp index 9277f74..f1943e7 100644 --- a/src/game/world.cpp +++ b/src/game/world.cpp @@ -299,7 +299,7 @@ void create_stars(::game& ctx) auto& vao = stars_model->get_vertex_array(); // Resize model VBO and upload vertex data - vbo->resize(star_vertex_data.size(), std::as_bytes(std::span{star_vertex_data})); + vbo->resize(star_vertex_data.size() * sizeof(float), std::as_bytes(std::span{star_vertex_data})); std::size_t attribute_offset = 0; @@ -330,7 +330,7 @@ void create_stars(::game& ctx) // Create model group stars_model->get_groups().resize(1); - render::model_group& stars_model_group = stars_model->get_groups().back(); + render::model_group& stars_model_group = stars_model->get_groups().front(); stars_model_group.id = "stars"; stars_model_group.material = star_material;