Browse Source

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.

master
C. J. Howard 7 months ago
parent
commit
d5bb5887cc
67 changed files with 1211 additions and 679 deletions
  1. +3
    -4
      src/engine/ai/navmesh.cpp
  2. +1
    -1
      src/engine/ai/navmesh.hpp
  3. +1
    -1
      src/engine/ai/steering/behavior/wander.cpp
  4. +3
    -2
      src/engine/animation/screen-transition.cpp
  5. +4
    -4
      src/engine/color/rgb.hpp
  6. +117
    -0
      src/engine/geom/brep/brep-operations.cpp
  7. +13
    -0
      src/engine/geom/brep/brep-operations.hpp
  8. +31
    -0
      src/engine/gl/rasterizer.cpp
  9. +3
    -0
      src/engine/gl/rasterizer.hpp
  10. +4
    -4
      src/engine/gl/texture-1d.cpp
  11. +3
    -3
      src/engine/gl/texture-1d.hpp
  12. +5
    -5
      src/engine/gl/texture-2d.cpp
  13. +3
    -3
      src/engine/gl/texture-2d.hpp
  14. +4
    -4
      src/engine/gl/texture-3d.cpp
  15. +3
    -3
      src/engine/gl/texture-3d.hpp
  16. +5
    -5
      src/engine/gl/texture-cube.cpp
  17. +3
    -3
      src/engine/gl/texture-cube.hpp
  18. +68
    -67
      src/engine/gl/texture.cpp
  19. +13
    -13
      src/engine/gl/texture.hpp
  20. +9
    -6
      src/engine/gl/transfer-function.hpp
  21. +2
    -2
      src/engine/gl/vertex-array.cpp
  22. +3
    -0
      src/engine/gl/vertex-attribute.hpp
  23. +62
    -1
      src/engine/math/projection.hpp
  24. +2
    -1
      src/engine/math/quaternion.hpp
  25. +1
    -3
      src/engine/math/se3.hpp
  26. +1
    -7
      src/engine/math/transform.hpp
  27. +13
    -11
      src/engine/physics/kinematics/colliders/mesh-collider.cpp
  28. +3
    -31
      src/engine/render/passes/bloom-pass.cpp
  29. +0
    -3
      src/engine/render/passes/bloom-pass.hpp
  30. +2
    -29
      src/engine/render/passes/final-pass.cpp
  31. +1
    -3
      src/engine/render/passes/final-pass.hpp
  32. +5
    -28
      src/engine/render/passes/fxaa-pass.cpp
  33. +0
    -2
      src/engine/render/passes/fxaa-pass.hpp
  34. +24
    -20
      src/engine/render/passes/material-pass.cpp
  35. +4
    -2
      src/engine/render/passes/material-pass.hpp
  36. +5
    -28
      src/engine/render/passes/resample-pass.cpp
  37. +0
    -2
      src/engine/render/passes/resample-pass.hpp
  38. +7
    -33
      src/engine/render/passes/sky-pass.cpp
  39. +0
    -3
      src/engine/render/passes/sky-pass.hpp
  40. +18
    -3
      src/engine/render/stages/cascaded-shadow-map-stage.cpp
  41. +4
    -33
      src/engine/render/stages/light-probe-stage.cpp
  42. +2
    -3
      src/engine/render/stages/light-probe-stage.hpp
  43. +35
    -35
      src/engine/resources/physfs/physfs-deserialize-context.cpp
  44. +1
    -1
      src/engine/resources/physfs/physfs-deserialize-context.hpp
  45. +20
    -6
      src/engine/scene/camera.cpp
  46. +10
    -3
      src/engine/scene/camera.hpp
  47. +2
    -2
      src/engine/scene/text.cpp
  48. +13
    -14
      src/engine/type/bitmap-font.cpp
  49. +4
    -4
      src/engine/type/freetype/ft-typeface.cpp
  50. +87
    -48
      src/engine/utility/image.cpp
  51. +148
    -109
      src/engine/utility/image.hpp
  52. +4
    -0
      src/game/components/navmesh-agent-component.hpp
  53. +13
    -11
      src/game/components/terrain-component.hpp
  54. +3
    -3
      src/game/fonts.cpp
  55. +3
    -9
      src/game/game.cpp
  56. +4
    -4
      src/game/graphics.cpp
  57. +44
    -2
      src/game/states/experiments/treadmill-experiment-state.cpp
  58. +0
    -5
      src/game/states/main-menu-state.cpp
  59. +1
    -14
      src/game/states/nest-view-state.cpp
  60. +2
    -2
      src/game/systems/camera-system.cpp
  61. +29
    -14
      src/game/systems/locomotion-system.cpp
  62. +15
    -12
      src/game/systems/physics-system.cpp
  63. +286
    -0
      src/game/systems/terrain-system.cpp
  64. +24
    -1
      src/game/systems/terrain-system.hpp
  65. +3
    -1
      src/game/textures/cocoon-silk-sdf.cpp
  66. +3
    -1
      src/game/textures/rgb-voronoi-noise.cpp
  67. +2
    -2
      src/game/world.cpp

+ 3
- 4
src/engine/ai/navmesh.cpp View File

@ -26,7 +26,7 @@
namespace ai {
navmesh_traversal traverse_navmesh(const geom::brep_mesh& mesh, geom::brep_face* face, geom::ray<float, 3> 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<math::fvec3>("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{};

+ 1
- 1
src/engine/ai/navmesh.hpp View File

@ -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<float, 3> 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

+ 1
- 1
src/engine/ai/steering/behavior/wander.cpp View File

@ -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);

+ 3
- 2
src/engine/animation/screen-transition.cpp View File

@ -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, ::animation
(
[this]()
{
//this->billboard.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, ::animation
// Reset and play transition animation
animation.stop();
animation.play();
this->billboard.set_layer_mask(1);
}

+ 4
- 4
src/engine/color/rgb.hpp View File

@ -47,12 +47,12 @@ template
{
const math::mat3<T> 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<T> scale = math::inverse(m) * math::vec3<T>{w[0] / w[1], T{1}, (T{1} - (w[0] + w[1])) / w[1]};
const math::vec3<T> scale = math::inverse(m) * math::vec3<T>{w[0] / w[1], T{1}, (T{1} - w[0] - w[1]) / w[1]};
return math::mat3<T>
{

+ 117
- 0
src/engine/geom/brep/brep-operations.cpp View File

@ -19,6 +19,7 @@
#include <engine/geom/brep/brep-operations.hpp>
#include <engine/math/vector.hpp>
#include <engine/render/vertex-attribute.hpp>
#include <engine/debug/log.hpp>
#include <algorithm>
#include <cmath>
@ -132,4 +133,120 @@ void generate_loop_barycentric(brep_mesh& mesh)
}
}
std::unique_ptr<render::model> generate_model(const brep_mesh& mesh, std::shared_ptr<render::material> material)
{
// Get vertex positions
const geom::brep_attribute<math::fvec3>* vertex_positions = nullptr;
if (auto attribute_it = mesh.vertices().attributes().find("position"); attribute_it != mesh.vertices().attributes().end())
{
vertex_positions = &static_cast<const geom::brep_attribute<math::fvec3>&>(*attribute_it);
}
// Get vertex normals
const geom::brep_attribute<math::fvec3>* vertex_normals = nullptr;
if (auto attribute_it = mesh.vertices().attributes().find("normal"); attribute_it != mesh.vertices().attributes().end())
{
vertex_normals = &static_cast<const geom::brep_attribute<math::fvec3>&>(*attribute_it);
}
// Allocate model
auto model = std::make_unique<render::model>();
// 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<std::byte> 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<std::uint32_t>(mesh.faces().size() * 3);
return model;
}
} // namespace geom

+ 13
- 0
src/engine/geom/brep/brep-operations.hpp View File

@ -21,6 +21,9 @@
#define ANTKEEPER_GEOM_BREP_OPERATIONS_HPP
#include <engine/geom/brep/brep-mesh.hpp>
#include <engine/render/model.hpp>
#include <engine/render/material.hpp>
#include <memory>
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<render::model> generate_model(const brep_mesh& mesh, std::shared_ptr<render::material> material = nullptr);
} // namespace geom
#endif // ANTKEEPER_GEOM_BREP_OPERATIONS_HPP

+ 31
- 0
src/engine/gl/rasterizer.cpp View File

@ -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<gl::vertex_array>();
}
rasterizer::~rasterizer()
@ -159,6 +163,19 @@ void rasterizer::draw_arrays(const vertex_array& vao, drawing_mode mode, std::si
glDrawArrays(gl_mode, static_cast<GLint>(offset), static_cast<GLsizei>(count));
}
void rasterizer::draw_arrays(drawing_mode mode, std::size_t offset, std::size_t count)
{
GLenum gl_mode = drawing_mode_lut[static_cast<std::size_t>(mode)];
if (bound_vao != dummy_vao.get())
{
glBindVertexArray(dummy_vao->gl_array_id);
bound_vao = dummy_vao.get();
}
glDrawArrays(gl_mode, static_cast<GLint>(offset), static_cast<GLsizei>(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<std::size_t>(mode)];
@ -186,4 +203,18 @@ void rasterizer::draw_elements(const vertex_array& vao, drawing_mode mode, std::
glDrawElements(gl_mode, static_cast<GLsizei>(count), gl_type, reinterpret_cast<const GLvoid*>(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<std::size_t>(mode)];
GLenum gl_type = element_array_type_lut[static_cast<std::size_t>(type)];
if (bound_vao != dummy_vao.get())
{
glBindVertexArray(dummy_vao->gl_array_id);
bound_vao = dummy_vao.get();
}
glDrawElements(gl_mode, static_cast<GLsizei>(count), gl_type, reinterpret_cast<const GLvoid*>(offset));
}
} // namespace gl

+ 3
- 0
src/engine/gl/rasterizer.hpp View File

@ -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<framebuffer> default_framebuffer;
std::unique_ptr<vertex_array> dummy_vao;
const framebuffer* bound_framebuffer{nullptr};
const vertex_array* bound_vao{nullptr};
const shader_program* bound_shader_program{nullptr};

+ 4
- 4
src/engine/gl/texture-1d.cpp View File

@ -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)

+ 3
- 3
src/engine/gl/texture-1d.hpp View File

@ -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);

+ 5
- 5
src/engine/gl/texture-2d.cpp View File

@ -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)

+ 3
- 3
src/engine/gl/texture-2d.hpp View File

@ -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.

+ 4
- 4
src/engine/gl/texture-3d.cpp View File

@ -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)

+ 3
- 3
src/engine/gl/texture-3d.hpp View File

@ -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);

+ 5
- 5
src/engine/gl/texture-cube.cpp View File

@ -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();
}

+ 3
- 3
src/engine/gl/texture-cube.hpp View File

@ -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.

+ 68
- 67
src/engine/gl/texture.cpp View File

@ -23,7 +23,7 @@
#include <engine/gl/texture-3d.hpp>
#include <engine/gl/texture-cube.hpp>
#include <algorithm>
#include <engine/gl/color-space.hpp>
#include <engine/gl/transfer-function.hpp>
#include <engine/gl/pixel-format.hpp>
#include <engine/gl/pixel-type.hpp>
#include <engine/gl/texture-filter.hpp>
@ -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<unsigned int>(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<std::size_t>(face_size) * pixel_stride;
const std::size_t face_stride = static_cast<std::size_t>(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<std::string>();
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<gl::texture_1d>(image->width(), type, format, color_space, image->data());
auto texture = std::make_unique<gl::texture_1d>(static_cast<std::uint16_t>(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<std::string>();
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<gl::texture_2d>(image->width(), image->height(), type, format, color_space, image->data());
auto texture = std::make_unique<gl::texture_2d>(static_cast<std::uint16_t>(image->size().x()), static_cast<std::uint16_t>(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<std::string>();
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<gl::texture_cube>(image->width(), image->height(), type, format, color_space, image->data());
auto texture = std::make_unique<gl::texture_cube>(static_cast<std::uint16_t>(image->size().x()), static_cast<std::uint16_t>(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);

+ 13
- 13
src/engine/gl/texture.hpp View File

@ -20,7 +20,7 @@
#ifndef ANTKEEPER_GL_TEXTURE_HPP
#define ANTKEEPER_GL_TEXTURE_HPP
#include <engine/gl/color-space.hpp>
#include <engine/gl/transfer-function.hpp>
#include <engine/gl/pixel-format.hpp>
#include <engine/gl/pixel-type.hpp>
#include <engine/gl/texture-filter.hpp>
@ -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<std::uint16_t, 3> 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<texture_wrapping, 3> m_wrapping{texture_wrapping::repeat, texture_wrapping::repeat, texture_wrapping::repeat};
std::tuple<texture_min_filter, texture_mag_filter> m_filters{texture_min_filter::linear_mipmap_linear, texture_mag_filter::linear};
std::uint8_t m_base_level{};

src/engine/gl/color-space.hpp → src/engine/gl/transfer-function.hpp View File

@ -17,22 +17,25 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <cstdint>
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

+ 2
- 2
src/engine/gl/vertex-array.cpp View File

@ -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<GLuint>(location),
static_cast<GLint>(attribute.components),
gl_type,
GL_FALSE,
attribute.normalized ? GL_TRUE : GL_FALSE,
static_cast<GLsizei>(attribute.stride),
reinterpret_cast<const GLvoid*>(attribute.offset)
);

+ 3
- 0
src/engine/gl/vertex-attribute.hpp View File

@ -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

+ 62
- 1
src/engine/math/projection.hpp View File

@ -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 <class T>
[[nodiscard]] mat4<T> 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 <class T>
[[nodiscard]] std::tuple<mat4<T>, mat4<T>> 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<T>
{{
{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<T>
{{
{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

+ 2
- 1
src/engine/math/quaternion.hpp View File

@ -659,7 +659,8 @@ constexpr vec3 mul(const quaternion& q, const vec3& v) noexcept
template <class T>
inline constexpr vec3<T> mul(const vec3<T>& v, const quaternion<T>& 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 <class T>

+ 1
- 3
src/engine/math/se3.hpp View File

@ -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.

+ 1
- 7
src/engine/math/transform.hpp View File

@ -154,13 +154,7 @@ template
template <class T>
transform<T> inverse(const transform<T>& t) noexcept
{
transform<T> 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 <class T>

+ 13
- 11
src/engine/physics/kinematics/colliders/mesh-collider.cpp View File

@ -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<float>::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;
}

+ 3
- 31
src/engine/render/passes/bloom-pass.cpp View File

@ -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::vertex_buffer>(gl::buffer_usage::static_draw, vertex_data.size(), vertex_data);
quad_vao = std::make_unique<gl::vertex_array>();
// 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);
}
);
}

+ 0
- 3
src/engine/render/passes/bloom-pass.hpp View File

@ -99,9 +99,6 @@ private:
std::unique_ptr<gl::shader_program> downsample_shader;
std::unique_ptr<gl::shader_program> upsample_shader;
std::unique_ptr<gl::vertex_buffer> quad_vbo;
std::unique_ptr<gl::vertex_array> quad_vao;
unsigned int mip_chain_length;
std::vector<std::unique_ptr<gl::framebuffer>> framebuffers;
std::vector<std::unique_ptr<gl::texture_2d>> textures;

+ 2
- 29
src/engine/render/passes/final-pass.cpp View File

@ -33,6 +33,7 @@
#include <engine/render/vertex-attribute.hpp>
#include <engine/render/context.hpp>
#include <cmath>
#include <cstdint>
#include <glad/glad.h>
#include <engine/utility/hash/fnv1a.hpp>
#include <engine/debug/log.hpp>
@ -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::vertex_buffer>(gl::buffer_usage::static_draw, vertex_data.size(), vertex_data);
quad_vao = std::make_unique<gl::vertex_array>();
// 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);
}
);
}

+ 1
- 3
src/engine/render/passes/final-pass.hpp View File

@ -51,9 +51,7 @@ public:
private:
void rebuild_command_buffer();
std::unique_ptr<gl::shader_program> shader_program;
std::unique_ptr<gl::vertex_buffer> quad_vbo;
std::unique_ptr<gl::vertex_array> quad_vao;
std::unique_ptr<gl::shader_program> shader_program;
const gl::texture_2d* color_texture;
const gl::texture_2d* bloom_texture;

+ 5
- 28
src/engine/render/passes/fxaa-pass.cpp View File

@ -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::vertex_buffer>(gl::buffer_usage::static_draw, vertex_data.size(), vertex_data);
quad_vao = std::make_unique<gl::vertex_array>();
// 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);
}
);
}

+ 0
- 2
src/engine/render/passes/fxaa-pass.hpp View File

@ -69,8 +69,6 @@ private:
void rebuild_command_buffer();
std::unique_ptr<gl::shader_program> shader;
std::unique_ptr<gl::vertex_buffer> quad_vbo;
std::unique_ptr<gl::vertex_array> quad_vao;
const gl::texture_2d* source_texture{nullptr};

+ 24
- 20
src/engine/render/passes/material-pass.cpp View File

@ -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::vector
{
command_buffer.emplace_back([&, view_var](){view_var->update(*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::vector
{
command_buffer.emplace_back([&, camera_exposure_var](){camera_exposure_var->update(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::vector
(
[&, model_view_var, normal_model_view_var]()
{
const auto model_view = (*view) * (*model);
model_view_var->update(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::vector
{
if (model_view_var)
{
command_buffer.emplace_back([&, model_view_var](){model_view_var->update((*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::vector
(
[&, normal_model_view_var]()
{
const auto model_view = (*view) * (*model);
normal_model_view_var->update(math::transpose(math::inverse(math::fmat3(model_view))));
}
);
@ -848,7 +852,7 @@ void material_pass::build_geometry_command_buffer(std::vector
// Update model-view-projection matrix variable
if (auto model_view_projection_var = shader_program.variable("model_view_projection"))
{
command_buffer.emplace_back([&, model_view_projection_var](){model_view_projection_var->update((*view_projection) * (*model));});
command_buffer.emplace_back([&, model_view_projection_var](){model_view_projection_var->update((*projection) * model_view);});
}
// Update matrix palette variable

+ 4
- 2
src/engine/render/passes/material-pass.hpp View File

@ -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{};

+ 5
- 28
src/engine/render/passes/resample-pass.cpp View File

@ -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::vertex_buffer>(gl::buffer_usage::static_draw, vertex_data.size(), vertex_data);
quad_vao = std::make_unique<gl::vertex_array>();
// 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);
}
);
}

+ 0
- 2
src/engine/render/passes/resample-pass.hpp View File

@ -68,8 +68,6 @@ private:
void rebuild_command_buffer();
std::unique_ptr<gl::shader_program> shader;
std::unique_ptr<gl::vertex_buffer> quad_vbo;
std::unique_ptr<gl::vertex_array> quad_vao;
const gl::texture_2d* source_texture;

+ 7
- 33
src/engine/render/passes/sky-pass.cpp View File

@ -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<math::fvec3, float>),
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::vertex_buffer>(gl::buffer_usage::static_draw, vertex_data.size(), vertex_data);
quad_vao = std::make_unique<gl::vertex_array>();
// 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<float> 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);
}

+ 0
- 3
src/engine/render/passes/sky-pass.hpp View File

@ -233,9 +233,6 @@ private:
void rebuild_sky_lut_command_buffer();
void rebuild_sky_probe_command_buffer();
std::unique_ptr<gl::vertex_buffer> quad_vbo;
std::unique_ptr<gl::vertex_array> quad_vao;
// Transmittance
std::uint16_t m_transmittance_lut_sample_count{40};
math::vec2<std::uint16_t> m_transmittance_lut_resolution{256, 64};

+ 18
- 3
src/engine/render/stages/cascaded-shadow-map-stage.cpp View File

@ -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())

+ 4
- 33
src/engine/render/stages/light-probe-stage.cpp View File

@ -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::vertex_buffer>(gl::buffer_usage::static_draw, vertex_data.size(), vertex_data);
m_quad_vao = std::make_unique<gl::vertex_array>();
// 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<gl::shader_template>("cubemap-to-sh.glsl");
rebuild_cubemap_to_sh_shader_program();
@ -148,7 +119,7 @@ void light_probe_stage::update_light_probes_luminance(const std::vector
m_rasterizer->use_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::vector
m_rasterizer->use_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::vector
m_cubemap_to_sh_cubemap_var->update(*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<float>(m_cubemap_filter_lut_texture->get_width()), static_cast<float>(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()

+ 2
- 3
src/engine/render/stages/light-probe-stage.hpp View File

@ -114,16 +114,15 @@ private:
void update_light_probes_illuminance(const std::vector<scene::object_base*>& light_probes);
gl::rasterizer* m_rasterizer;
std::unique_ptr<gl::vertex_buffer> m_quad_vbo;
std::unique_ptr<gl::vertex_array> m_quad_vao;
std::shared_ptr<gl::shader_template> m_cubemap_to_sh_shader_template;
std::unique_ptr<gl::shader_program> 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<gl::shader_template> m_cubemap_downsample_shader_template;
std::unique_ptr<gl::shader_program> m_cubemap_downsample_shader_program;
const gl::shader_variable* m_cubemap_downsample_cubemap_var{};
std::vector<std::unique_ptr<gl::framebuffer>> m_cubemap_downsample_framebuffers;
std::unique_ptr<gl::texture_cube> m_cubemap_downsample_texture;

+ 35
- 35
src/engine/resources/physfs/physfs-deserialize-context.cpp View File

@ -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<std::size_t>(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<PHYSFS_uint64>(offset)))
if (!PHYSFS_seek(m_file, static_cast<PHYSFS_uint64>(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());
}

+ 1
- 1
src/engine/resources/physfs/physfs-deserialize-context.hpp View File

@ -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};

+ 20
- 6
src/engine/scene/camera.cpp View File

@ -26,9 +26,8 @@ namespace scene {
geom::ray<float, 3> 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<float>::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

+ 10
- 3
src/engine/scene/camera.hpp View File

@ -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<float>::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.
*

+ 2
- 2
src/engine/scene/text.cpp View File

@ -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<float>(font_bitmap.width());
uvs[i].y() = uvs[i].y() / static_cast<float>(font_bitmap.height());
uvs[i].x() = uvs[i].x() / static_cast<float>(font_bitmap.size().x());
uvs[i].y() = uvs[i].y() / static_cast<float>(font_bitmap.size().y());
}
// Add vertex to vertex data buffer

+ 13
- 14
src/engine/type/bitmap-font.cpp View File

@ -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<std::uint32_t>(it->second.bitmap.size().x()));
max_glyph_h = std::max(max_glyph_h, static_cast<std::uint32_t>(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<std::uint32_t>(bitmap.size().x());
bitmap_h = static_cast<std::uint32_t>(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<std::uint32_t>(it->second.bitmap.size().x()), static_cast<std::uint32_t>(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<std::uint32_t>(glyph.bitmap.size().x()) != glyph_width || static_cast<std::uint32_t>(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});
}
}

+ 4
- 4
src/engine/type/freetype/ft-typeface.cpp View File

@ -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;
}

+ 87
- 48
src/engine/utility/image.cpp View File

@ -22,24 +22,22 @@
#include <engine/resources/resource-loader.hpp>
#include <engine/resources/deserialize-error.hpp>
#include <engine/resources/deserializer.hpp>
#include <engine/debug/log.hpp>
#include <stb/stb_image.h>
#include <stdexcept>
#include <tinyexr.h>
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<std::size_t>(from_i) * source.m_size.x() + from_j) * m_pixel_stride;
const auto to_offset = (static_cast<std::size_t>(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<float>(1.0 / (std::exp2(m_bit_depth) - 1.0));
m_data.resize(static_cast<std::size_t>(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<std::size_t>(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<float>(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<std::uint8_t>(exr_image.num_channels));
image.resize(static_cast<std::uint32_t>(exr_image.width), static_cast<std::uint32_t>(exr_image.height));
image.format(exr_image.num_channels, sizeof(float) * 8);
image.resize({static_cast<unsigned int>(exr_image.width), static_cast<unsigned int>(exr_image.height), 1u});
// Fill image pixels
float* component = reinterpret_cast<float*>(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<float**>(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<unsigned int>(channels), 16u);
image.resize({static_cast<unsigned int>(width), static_cast<unsigned int>(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<unsigned int>(channels), 8u);
image.resize({static_cast<unsigned int>(width), static_cast<unsigned int>(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<std::uint8_t>(channels));
image.resize(static_cast<std::uint32_t>(width), static_cast<std::uint32_t>(height));
std::memcpy(image.data(), pixels, image.size());
// Free loaded image data
stbi_image_free(pixels);
}
/**

+ 148
- 109
src/engine/utility/image.hpp View File

@ -21,179 +21,218 @@
#define ANTKEEPER_UTILITY_IMAGE_HPP
#include <engine/math/vector.hpp>
#include <cstdint>
#include <type_traits>
#include <vector>
#include <cstddef>
/**
* 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 <class T>
[[nodiscard]] inline constexpr T* begin() noexcept
inline void resize(unsigned int size)
{
static_assert(std::is_standard_layout<T>::value, "Pixel iterator type is not standard-layout.");
static_assert(std::is_trivial<T>::value, "Pixel iterator type is not trivial.");
return reinterpret_cast<T*>(pixels.data());
resize(math::uvec3{size, 1u, 1u});
}
template <class T>
[[nodiscard]] inline constexpr const T* begin() const noexcept
inline void resize(const math::uvec2& size)
{
static_assert(std::is_standard_layout<T>::value, "Pixel iterator type is not standard-layout.");
static_assert(std::is_trivial<T>::value, "Pixel iterator type is not trivial.");
return reinterpret_cast<const T*>(pixels.data());
resize(math::uvec3{size.x(), size.y(), 1u});
}
template <class T>
[[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<T>::value, "Pixel iterator type is not standard-layout.");
static_assert(std::is_trivial<T>::value, "Pixel iterator type is not trivial.");
return reinterpret_cast<const T*>(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 <class T>
[[nodiscard]] inline constexpr T* end() noexcept
[[nodiscard]] T get(unsigned int position) const
{
static_assert(std::is_standard_layout<T>::value, "Pixel iterator type is not standard-layout.");
static_assert(std::is_trivial<T>::value, "Pixel iterator type is not trivial.");
return reinterpret_cast<T*>(pixels.data() + pixels.size());
T value;
std::memcpy(&value, data() + static_cast<std::size_t>(position) * m_pixel_stride, sizeof(T));
return value;
}
template <class T>
[[nodiscard]] inline constexpr const T* end() const noexcept
[[nodiscard]] T get(const math::uvec2& position) const
{
static_assert(std::is_standard_layout<T>::value, "Pixel iterator type is not standard-layout.");
static_assert(std::is_trivial<T>::value, "Pixel iterator type is not trivial.");
return reinterpret_cast<const T*>(pixels.data() + pixels.size());
const auto index = static_cast<std::size_t>(position.y()) * m_size.x() + position.x();
T value;
std::memcpy(&value, data() + index * m_pixel_stride, sizeof(T));
return value;
}
template <class T>
[[nodiscard]] inline constexpr const T* cend() const noexcept
[[nodiscard]] T get(const math::uvec3& position) const
{
static_assert(std::is_standard_layout<T>::value, "Pixel iterator type is not standard-layout.");
static_assert(std::is_trivial<T>::value, "Pixel iterator type is not trivial.");
return reinterpret_cast<const T*>(pixels.data() + pixels.size());
const auto index = (static_cast<std::size_t>(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 <class T>
[[nodiscard]] void set(unsigned int position, const T& value)
{
return m_width;
std::memcpy(data() + static_cast<std::size_t>(position) * m_pixel_stride, &value, sizeof(T));
}
/// Returns the height of the image, in pixels.
[[nodiscard]] inline std::uint32_t height() const noexcept
template <class T>
[[nodiscard]] void set(const math::uvec2& position, const T& value)
{
return m_height;
const auto index = static_cast<std::size_t>(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 <class T>
[[nodiscard]] void set(const math::uvec3& position, const T& value)
{
return m_channel_count;
const auto index = (static_cast<std::size_t>(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<std::size_t>(position));
}
[[nodiscard]] inline math::fvec4 sample(const math::uvec2& position) const
{
return pixels.data();
return sample(static_cast<std::size_t>(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<std::size_t>(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<std::byte> 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<std::byte> m_data;
};
#endif // ANTKEEPER_UTILITY_IMAGE_HPP

+ 4
- 0
src/game/components/navmesh-agent-component.hpp View File

@ -20,6 +20,7 @@
#ifndef ANTKEEPER_GAME_NAVMESH_AGENT_COMPONENT_HPP
#define ANTKEEPER_GAME_NAVMESH_AGENT_COMPONENT_HPP
#include <engine/entity/id.hpp>
#include <engine/math/vector.hpp>
#include <engine/geom/brep/brep-mesh.hpp>
@ -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{};

+ 13
- 11
src/game/components/terrain-component.hpp View File

@ -20,20 +20,22 @@
#ifndef ANTKEEPER_GAME_TERRAIN_COMPONENT_HPP
#define ANTKEEPER_GAME_TERRAIN_COMPONENT_HPP
#include <engine/render/material.hpp>
#include <functional>
#include <engine/entity/id.hpp>
#include <engine/math/vector.hpp>
#include <vector>
/// Grid of terrain cells.
struct terrain_grid_component
{
math::uvec2 dimensions;
std::vector<entity::id> 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<double(double, double)> 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;
};

+ 3
- 3
src/game/fonts.cpp View File

@ -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<render::matvar_texture_2d>(var)->get();
texture->resize(font_bitmap.width(), font_bitmap.height(), font_bitmap.data());
texture->resize(static_cast<std::uint16_t>(font_bitmap.size().x()), static_cast<std::uint16_t>(font_bitmap.size().y()), font_bitmap.data());
}
else
{
// Create font texture from bitmap
std::shared_ptr<gl::texture_2d> font_texture = std::make_shared<gl::texture_2d>(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<gl::texture_2d> font_texture = std::make_shared<gl::texture_2d>(static_cast<std::uint16_t>(font_bitmap.size().x()), static_cast<std::uint16_t>(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);

+ 3
- 9
src/game/game.cpp View File

@ -803,7 +803,7 @@ void game::setup_scenes()
// Allocate and init surface camera
surface_camera = std::make_shared<scene::camera>();
surface_camera->set_perspective(math::radians<float>(45.0f), viewport_aspect_ratio, 0.5f, 1000.0f);
surface_camera->set_perspective(math::radians<float>(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<scene::camera>();
underground_camera->set_perspective(math::radians<float>(45.0f), viewport_aspect_ratio, 0.1f, 200.0f);
underground_camera->set_perspective(math::radians<float>(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

+ 4
- 4
src/game/graphics.cpp View File

@ -167,8 +167,8 @@ void save_screenshot(::game& ctx)
// Allocate screenshot image
std::shared_ptr<image> frame = std::make_shared<image>();
frame->format(1, 3);
frame->resize(viewport_size.x(), viewport_size.y());
frame->format(3, 8);
frame->resize({static_cast<std::size_t>(viewport_size.x()), static_cast<std::size_t>(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<int>(frame->size().x()), static_cast<int>(frame->size().y()), static_cast<int>(frame->channels()), frame->data(), static_cast<int>(frame->size().x() * frame->channels()));
debug::log::debug("Saved screenshot to \"{}\"", path);
}

+ 44
- 2
src/game/states/experiments/treadmill-experiment-state.cpp View File

@ -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 <engine/animation/ease.hpp>
#include <engine/animation/screen-transition.hpp>
@ -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<scene::static_mesh>(ctx.resource_manager->load<render::model>("cube-nest-200mm-interior.mdl"));
nest_exterior_scene_component.object = std::make_shared<scene::static_mesh>(ctx.resource_manager->load<render::model>("cube-nest-200mm-exterior.mdl"));
nest_exterior_scene_component.layer_mask = 1;
auto nest_exterior_mesh = ctx.resource_manager->load<geom::brep_mesh>("cube-nest-200mm-interior.msh");
auto nest_exterior_mesh = ctx.resource_manager->load<geom::brep_mesh>("cube-nest-200mm-exterior.msh");
auto nest_exterior_rigid_body = std::make_unique<physics::rigid_body>();
nest_exterior_rigid_body->set_mass(0.0f);
nest_exterior_rigid_body->set_collider(std::make_shared<physics::mesh_collider>(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<scene_component>(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<rigid_body_component>(nest_interior_eid, std::move(nest_interior_rigid_body));
}
// Generate terrain
{
auto heightmap = ctx.resource_manager->load<image>("chiricahua-s.png");
auto subdivisions = math::uvec2{0, 0};
// auto subdivisions = math::uvec2{3, 3};
auto transform = math::transform<float>::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<render::material>("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<scene::static_mesh>(ctx.resource_manager->load<render::model>("yucca-plant-l.mdl"));
scene.object->set_translation({0, 0, 0});
scene.layer_mask = 1;
const auto placement_ray = geom::ray<float, 3>{{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<scene_component>(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;

+ 0
- 5
src/game/states/main-menu-state.cpp View File

@ -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);

+ 1
- 14
src/game/states/nest-view-state.cpp View File

@ -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<scene_component>
(
third_person_camera_rig_eid,
[&](auto& component)
{
auto& camera = static_cast<scene::camera&>(*component.object);
camera.set_translation(math::fvec3(camera_position));
camera.set_rotation(math::fquat(third_person_camera_orientation));
camera.set_perspective(static_cast<float>(third_person_camera_vfov), camera.get_aspect_ratio(), camera.get_clip_near(), camera.get_clip_far());
}
);
}
geom::ray<float, 3> nest_view_state::get_mouse_ray(const math::vec2<std::int32_t>& mouse_position) const

+ 2
- 2
src/game/systems/camera-system.cpp View File

@ -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<float>(autofocus.vfov), camera.get_aspect_ratio(), camera.get_clip_near(), camera.get_clip_far());
camera.set_vertical_fov(static_cast<float>(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<float>(spring_arm.vfov), camera.get_aspect_ratio(), camera.get_clip_near(), camera.get_clip_far());
camera.set_vertical_fov(static_cast<float>(spring_arm.vfov));
}
);
}

+ 29
- 14
src/game/systems/locomotion-system.cpp View File

@ -83,17 +83,29 @@ void locomotion_system::update_legged(float t, float dt)
auto& navmesh_agent = legged_group.get<navmesh_agent_component>(entity_id);
if (locomotion.speed != 0.0f/* && cos_target_direction >= 0.0f*/ && navmesh_agent.face)
{
// Get rigid body
auto& rigid_body = *legged_group.get<rigid_body_component>(entity_id).body;
const auto& rigid_body_transform = rigid_body.get_transform();
// Get agent rigid body
auto& agent_rigid_body = *legged_group.get<rigid_body_component>(entity_id).body;
const auto& agent_transform = agent_rigid_body.get_transform();
// Get navmesh rigid body
auto& navmesh_rigid_body = *registry.get<rigid_body_component>(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<float, 3> 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<math::fvec3>("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

+ 15
- 12
src/game/systems/physics-system.cpp View File

@ -112,7 +112,7 @@ void physics_system::interpolate(float alpha)
std::optional<std::tuple<entity::id, float, std::uint32_t, math::fvec3>> physics_system::trace(const geom::ray<float, 3>& 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<float>::infinity();
float nearest_hit_sqr_distance = std::numeric_limits<float>::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<float, 3> 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<float, 3> 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<const physics::mesh_collider&>(*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)

+ 286
- 0
src/game/systems/terrain-system.cpp View File

@ -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 <engine/debug/log.hpp>
#include <engine/geom/primitives/box.hpp>
#include <engine/geom/brep/brep-mesh.hpp>
#include <engine/geom/brep/brep-operations.hpp>
#include <engine/physics/kinematics/rigid-body.hpp>
#include <engine/physics/kinematics/collider.hpp>
#include <engine/physics/kinematics/colliders/mesh-collider.hpp>
#include <engine/scene/static-mesh.hpp>
#include <engine/render/vertex-attribute.hpp>
#include <algorithm>
#include <execution>
#include <stdexcept>
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<image> heightmap, const math::uvec2& subdivisions, const math::transform<float>& transform, std::shared_ptr<render::material> 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<terrain_cell_component>(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<unsigned int>(heightmap->size().x() - 1) / grid.dimensions.x(), static_cast<unsigned int>(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<float>(cell_quad_dimensions.x()), 2.0f, 2.0f / static_cast<float>(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<terrain_cell_component>(cell_eid);
// Allocate cell mesh and attributes
auto mesh = std::make_shared<geom::brep_mesh>();
auto& vertex_positions = static_cast<geom::brep_attribute<math::fvec3>&>(*mesh->vertices().attributes().emplace<math::fvec3>("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<float>(pixel_position.x()) * vertex_scale.x() + vertex_translation.x();
position.y() = height * vertex_scale.y() + vertex_translation.y();
position.z() = static_cast<float>(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<geom::brep_attribute<math::fvec3>&>(*mesh->vertices().attributes().try_emplace<math::fvec3>("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<physics::rigid_body>();
rigid_body->set_mass(0.0f);
rigid_body->set_collider(std::make_shared<physics::mesh_collider>(mesh));
rigid_body->set_transform({transform.translation, transform.rotation, math::fvec3{max_scale, max_scale, max_scale} * 0.5f});
registry.emplace<rigid_body_component>(cell_eid, std::move(rigid_body));
auto model = generate_terrain_model(*mesh, material, cell_quad_dimensions);
scene_component scene;
scene.object = std::make_shared<scene::static_mesh>(std::move(model));
scene.layer_mask = 1;
registry.emplace<scene_component>(cell_eid, std::move(scene));
}
);
registry.emplace<terrain_grid_component>(grid_eid, std::move(grid));
return grid_eid;
}
std::unique_ptr<render::model> terrain_system::generate_terrain_model(const geom::brep_mesh& mesh, std::shared_ptr<render::material> material, const math::uvec2& quad_dimensions) const
{
const auto& vertex_positions = mesh.vertices().attributes().at<math::fvec3>("position");
const auto& vertex_normals = mesh.vertices().attributes().at<math::fvec3>("normal");
// Allocate model
auto model = std::make_unique<render::model>();
// 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<std::byte> vertex_data(vertex_count * vertex_size);
std::byte* v = vertex_data.data();
auto normalized_int16 = [](const math::fvec3& f) -> math::vec3<std::int16_t>
{
math::vec3<std::int16_t> i;
for (int j = 0; j < 3; ++j)
{
i[j] = static_cast<std::int16_t>(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<std::uint32_t>(vertex_count);
return model;
}

+ 24
- 1
src/game/systems/terrain-system.hpp View File

@ -23,6 +23,13 @@
#include "game/systems/updatable-system.hpp"
#include "game/components/terrain-component.hpp"
#include <engine/entity/id.hpp>
#include <engine/utility/image.hpp>
#include <engine/math/transform.hpp>
#include <engine/math/vector.hpp>
#include <engine/render/model.hpp>
#include <engine/render/material.hpp>
#include <engine/geom/brep/brep-mesh.hpp>
#include <memory>
/**
* 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<image> heightmap, const math::uvec2& subdivisions, const math::transform<float>& transform, std::shared_ptr<render::material> material);
private:
[[nodiscard]] std::unique_ptr<render::model> generate_terrain_model(const geom::brep_mesh& mesh, std::shared_ptr<render::material> material, const math::uvec2& quad_dimensions) const;
};
#endif // ANTKEEPER_GAME_TERRAIN_SYSTEM_HPP

+ 3
- 1
src/game/textures/cocoon-silk-sdf.cpp View File

@ -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());
*/
}

+ 3
- 1
src/game/textures/rgb-voronoi-noise.cpp View File

@ -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());
*/
}

+ 2
- 2
src/game/world.cpp View File

@ -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;

Loading…
Cancel
Save