From d5bb5887cc1705a80042c1f9b3b11cb188e0c99c Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Sat, 9 Sep 2023 02:15:09 +0800 Subject: [PATCH] Rename gl::color_space to gl::transfer_function. Add infinite perspective projection matrix functions. Improve floating-point accuracy when rendering large coordinates. Improve image class. Replace fullscreen quads with fullscreen triangles. Improve physics tracing to work with transformed meshes. Improve locomotion system to work on transformed meshes. --- src/engine/ai/navmesh.cpp | 7 +- src/engine/ai/navmesh.hpp | 2 +- src/engine/ai/steering/behavior/wander.cpp | 2 +- src/engine/animation/screen-transition.cpp | 5 +- src/engine/color/rgb.hpp | 8 +- src/engine/geom/brep/brep-operations.cpp | 117 +++++++ src/engine/geom/brep/brep-operations.hpp | 13 + src/engine/gl/rasterizer.cpp | 31 ++ src/engine/gl/rasterizer.hpp | 3 + src/engine/gl/texture-1d.cpp | 8 +- src/engine/gl/texture-1d.hpp | 6 +- src/engine/gl/texture-2d.cpp | 10 +- src/engine/gl/texture-2d.hpp | 6 +- src/engine/gl/texture-3d.cpp | 8 +- src/engine/gl/texture-3d.hpp | 6 +- src/engine/gl/texture-cube.cpp | 10 +- src/engine/gl/texture-cube.hpp | 6 +- src/engine/gl/texture.cpp | 135 +++++---- src/engine/gl/texture.hpp | 26 +- ...{color-space.hpp => transfer-function.hpp} | 15 +- src/engine/gl/vertex-array.cpp | 4 +- src/engine/gl/vertex-attribute.hpp | 3 + src/engine/math/projection.hpp | 63 +++- src/engine/math/quaternion.hpp | 3 +- src/engine/math/se3.hpp | 4 +- src/engine/math/transform.hpp | 8 +- .../kinematics/colliders/mesh-collider.cpp | 24 +- src/engine/render/passes/bloom-pass.cpp | 34 +-- src/engine/render/passes/bloom-pass.hpp | 3 - src/engine/render/passes/final-pass.cpp | 31 +- src/engine/render/passes/final-pass.hpp | 4 +- src/engine/render/passes/fxaa-pass.cpp | 33 +- src/engine/render/passes/fxaa-pass.hpp | 2 - src/engine/render/passes/material-pass.cpp | 44 +-- src/engine/render/passes/material-pass.hpp | 6 +- src/engine/render/passes/resample-pass.cpp | 33 +- src/engine/render/passes/resample-pass.hpp | 2 - src/engine/render/passes/sky-pass.cpp | 40 +-- src/engine/render/passes/sky-pass.hpp | 3 - .../stages/cascaded-shadow-map-stage.cpp | 21 +- .../render/stages/light-probe-stage.cpp | 37 +-- .../render/stages/light-probe-stage.hpp | 5 +- .../physfs/physfs-deserialize-context.cpp | 70 ++--- .../physfs/physfs-deserialize-context.hpp | 2 +- src/engine/scene/camera.cpp | 26 +- src/engine/scene/camera.hpp | 13 +- src/engine/scene/text.cpp | 4 +- src/engine/type/bitmap-font.cpp | 27 +- src/engine/type/freetype/ft-typeface.cpp | 8 +- src/engine/utility/image.cpp | 135 ++++++--- src/engine/utility/image.hpp | 257 +++++++++------- .../components/navmesh-agent-component.hpp | 4 + src/game/components/terrain-component.hpp | 24 +- src/game/fonts.cpp | 6 +- src/game/game.cpp | 12 +- src/game/graphics.cpp | 8 +- .../treadmill-experiment-state.cpp | 46 ++- src/game/states/main-menu-state.cpp | 5 - src/game/states/nest-view-state.cpp | 15 +- src/game/systems/camera-system.cpp | 4 +- src/game/systems/locomotion-system.cpp | 43 ++- src/game/systems/physics-system.cpp | 27 +- src/game/systems/terrain-system.cpp | 286 ++++++++++++++++++ src/game/systems/terrain-system.hpp | 25 +- src/game/textures/cocoon-silk-sdf.cpp | 4 +- src/game/textures/rgb-voronoi-noise.cpp | 4 +- src/game/world.cpp | 4 +- 67 files changed, 1211 insertions(+), 679 deletions(-) rename src/engine/gl/{color-space.hpp => transfer-function.hpp} (75%) 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;