diff --git a/.mailmap b/.mailmap index 300076fb84..fbf10ad63f 100644 --- a/.mailmap +++ b/.mailmap @@ -20,3 +20,4 @@ Tobias Feldballe Tobias Feldballe Jonas Borchelt Derek Frogget <114030121+derekfrogget@users.noreply.github.com> +Nikhil Ghosh \ No newline at end of file diff --git a/assets/test/shaders/demo_6_2d.frag.glsl b/assets/test/shaders/demo_6_2d.frag.glsl new file mode 100644 index 0000000000..c4beb1dd8e --- /dev/null +++ b/assets/test/shaders/demo_6_2d.frag.glsl @@ -0,0 +1,37 @@ +#version 330 + +in vec2 vert_uv; + +layout(location=0) out vec4 col; + +uniform sampler2D tex; + +// position (top left corner) and size: (x, y, width, height) +uniform vec4 tile_params; + +vec2 uv = vec2( + vert_uv.x * tile_params.z + tile_params.x, + vert_uv.y * tile_params.w + tile_params.y +); + +void main() { + vec4 tex_val = texture(tex, uv); + int alpha = int(round(tex_val.a * 255)); + switch (alpha) { + case 0: + col = tex_val; + discard; + case 254: + col = vec4(1.0f, 0.0f, 0.0f, 1.0f); + break; + case 252: + col = vec4(0.0f, 1.0f, 0.0f, 1.0f); + break; + case 250: + col = vec4(0.0f, 0.0f, 1.0f, 1.0f); + break; + default: + col = tex_val; + break; + } +} diff --git a/assets/test/shaders/demo_6_2d.vert.glsl b/assets/test/shaders/demo_6_2d.vert.glsl new file mode 100644 index 0000000000..6a17baf834 --- /dev/null +++ b/assets/test/shaders/demo_6_2d.vert.glsl @@ -0,0 +1,83 @@ +#version 330 + +layout(location=0) in vec2 v_position; +layout(location=1) in vec2 uv; + +out vec2 vert_uv; + +// camera parameters for transforming the object position +// and scaling the subtex to the correct size +layout (std140) uniform camera { + // view matrix (world to view space) + mat4 view; + // projection matrix (view to clip space) + mat4 proj; + // inverse zoom factor (1.0 / zoom) + // high zoom = upscale subtex + // low zoom = downscale subtex + float inv_zoom; + // inverse viewport size (1.0 / viewport size) + vec2 inv_viewport_size; +}; + +// position of the object in world space +uniform vec3 obj_world_position; + +// parameters for scaling and moving the subtex +// to the correct position in clip space + +// animation scalefactor +// scales the vertex positions so that they +// match the subtex dimensions +// +// high animation scale = downscale subtex +// low animation scale = upscale subtex +uniform float scale; + +// size of the subtex (in pixels) +uniform vec2 subtex_size; + +// offset of the subtex anchor point +// from the subtex center (in pixels) +// used to move the subtex so that the anchor point +// is at the object position +uniform vec2 anchor_offset; + +void main() { + // translate the position of the object from world space to clip space + // this is the position where we want to draw the subtex in 2D + vec4 obj_clip_pos = proj * view * vec4(obj_world_position, 1.0); + + // subtex has to be scaled to account for the zoom factor + // and the animation scale factor. essentially this is (animation scale / zoom). + float zoom_scale = scale * inv_zoom; + + // Scale the subtex vertices + // we have to account for the viewport size to get the correct dimensions + // and then scale the subtex to the zoom factor to get the correct size + vec2 vert_scale = zoom_scale * subtex_size * inv_viewport_size; + + // Scale the anchor offset with the same method as above + // to get the correct anchor position in the viewport + vec2 anchor_scale = zoom_scale * anchor_offset * inv_viewport_size; + + // offset the clip position by the offset of the subtex anchor + // imagine this as pinning the subtex to the object position at the subtex anchor point + obj_clip_pos += vec4(anchor_scale.x, anchor_scale.y, 0.0, 0.0); + + // create a move matrix for positioning the vertices + // uses the vert scale and the transformed object position in clip space + mat4 move = mat4(vert_scale.x, 0.0, 0.0, 0.0, + 0.0, vert_scale.y, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + obj_clip_pos.x, obj_clip_pos.y, obj_clip_pos.z, 1.0); + + // calculate the final vertex position + gl_Position = move * vec4(v_position, 0.0, 1.0); + + // flip y axis because OpenGL uses bottom-left as its origin + float uv_x = uv.x; + float uv_y = 1.0 - uv.y; + + vert_uv = vec2(uv_x, uv_y); +} diff --git a/assets/test/shaders/demo_6_2d_frame.frag.glsl b/assets/test/shaders/demo_6_2d_frame.frag.glsl new file mode 100644 index 0000000000..c67ca484cc --- /dev/null +++ b/assets/test/shaders/demo_6_2d_frame.frag.glsl @@ -0,0 +1,7 @@ +#version 330 + +out vec4 col; + +void main() { + col = vec4(1.0, 0.0, 0.0, 0.8); +} diff --git a/assets/test/shaders/demo_6_2d_frame.vert.glsl b/assets/test/shaders/demo_6_2d_frame.vert.glsl new file mode 100644 index 0000000000..8d7d13b6be --- /dev/null +++ b/assets/test/shaders/demo_6_2d_frame.vert.glsl @@ -0,0 +1,58 @@ +#version 330 + +layout(location=0) in vec2 v_position; + +// camera parameters for transforming the object position +// and scaling the subtex to the correct size +layout (std140) uniform camera { + // view matrix (world to view space) + mat4 view; + // projection matrix (view to clip space) + mat4 proj; + // inverse zoom factor (1.0 / zoom) + float inv_zoom; + // inverse viewport size (1.0 / viewport size) + vec2 inv_viewport_size; +}; + +// position of the object in world space +uniform vec3 obj_world_position; + +// parameters for scaling and moving the subtex +// to the correct position in clip space + +// animation scalefactor +// scales the vertex positions so that they +// match the subtex dimensions +// +// high animation scale = downscale subtex +// low animation scale = upscale subtex +uniform float scale; + +// size of the frame (in pixels) +uniform vec2 frame_size; + +void main() { + // translate the position of the object from world space to clip space + // this is the position where we want to draw the subtex in 2D + vec4 obj_clip_pos = proj * view * vec4(obj_world_position, 1.0); + + // subtex has to be scaled to account for the zoom factor + // and the animation scale factor. essentially this is (animation scale / zoom). + float zoom_scale = scale * inv_zoom; + + // Scale the subtex vertices + // we have to account for the viewport size to get the correct dimensions + // and then scale the frame to the zoom factor to get the correct size + vec2 vert_scale = zoom_scale * frame_size * inv_viewport_size; + + // create a move matrix for positioning the vertices + // uses the vert scale and the transformed object position in clip space + mat4 move = mat4(vert_scale.x, 0.0, 0.0, 0.0, + 0.0, vert_scale.y, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + obj_clip_pos.x, obj_clip_pos.y, obj_clip_pos.z, 1.0); + + // calculate the final vertex position + gl_Position = move * vec4(v_position, 0.0, 1.0); +} diff --git a/assets/test/shaders/demo_6_3d.frag.glsl b/assets/test/shaders/demo_6_3d.frag.glsl new file mode 100644 index 0000000000..f2d3c71bd8 --- /dev/null +++ b/assets/test/shaders/demo_6_3d.frag.glsl @@ -0,0 +1,13 @@ +#version 330 + +in vec2 tex_pos; + +layout(location=0) out vec4 out_col; + +uniform sampler2D tex; + +void main() +{ + vec4 tex_val = texture(tex, tex_pos); + out_col = tex_val; +} diff --git a/assets/test/shaders/demo_6_3d.vert.glsl b/assets/test/shaders/demo_6_3d.vert.glsl new file mode 100644 index 0000000000..95a552dcd6 --- /dev/null +++ b/assets/test/shaders/demo_6_3d.vert.glsl @@ -0,0 +1,24 @@ +#version 330 + +layout (location = 0) in vec3 position; +layout (location = 1) in vec2 uv; + +out vec2 tex_pos; + +// camera parameters for transforming the object position +// and scaling the subtex to the correct size +layout (std140) uniform camera { + // view matrix (world to view space) + mat4 view; + // projection matrix (view to clip space) + mat4 proj; + // inverse zoom factor (1.0 / zoom) + float inv_zoom; + // inverse viewport size (1.0 / viewport size) + vec2 inv_viewport_size; +}; + +void main() { + gl_Position = proj * view * vec4(position, 1.0); + tex_pos = vec2(uv.x, 1.0 - uv.y); +} diff --git a/assets/test/shaders/demo_6_display.frag.glsl b/assets/test/shaders/demo_6_display.frag.glsl new file mode 100644 index 0000000000..a6732d0c7f --- /dev/null +++ b/assets/test/shaders/demo_6_display.frag.glsl @@ -0,0 +1,10 @@ +#version 330 + +uniform sampler2D color_texture; + +in vec2 v_uv; +out vec4 col; + +void main() { + col = texture(color_texture, v_uv); +} diff --git a/assets/test/shaders/demo_6_display.vert.glsl b/assets/test/shaders/demo_6_display.vert.glsl new file mode 100644 index 0000000000..072e5d80b0 --- /dev/null +++ b/assets/test/shaders/demo_6_display.vert.glsl @@ -0,0 +1,10 @@ +#version 330 + +layout(location=0) in vec2 position; +layout(location=1) in vec2 uv; +out vec2 v_uv; + +void main() { + gl_Position = vec4(position, 0.0, 1.0); + v_uv = uv; +} diff --git a/buildsystem/codecompliance/authors.py b/buildsystem/codecompliance/authors.py index 502e3b2648..98b3a50e18 100644 --- a/buildsystem/codecompliance/authors.py +++ b/buildsystem/codecompliance/authors.py @@ -1,4 +1,4 @@ -# Copyright 2014-2023 the openage authors. See copying.md for legal info. +# Copyright 2014-2024 the openage authors. See copying.md for legal info. """ Checks whether all authors are properly listed in copying.md. @@ -39,7 +39,7 @@ def get_author_emails_copying_md(): """ with open("copying.md", encoding='utf8') as fobj: for line in fobj: - match = re.match("^.*\\|[^|]*\\|[^|]*\\|([^|]+)\\|.*$", line) + match = re.match(r"^.*\|[^|]*\|[^|]*\|([^|]+)\|.*$", line) if not match: continue diff --git a/copying.md b/copying.md index 13124fb993..fb6501bab0 100644 --- a/copying.md +++ b/copying.md @@ -153,6 +153,7 @@ _the openage authors_ are: | Astitva Kamble | askastitva | astitvakamble5 à gmail dawt com | | Haoyang Bi | AyiStar | ayistar à outlook dawt com | | Michael Seibt | RoboSchmied | github à roboschmie dawt de | +| Nikhil Ghosh | NikhilGhosh75 | nghosh606 à gmail dawt com | If you're a first-time committer, add yourself to the above list. This is not just for legal reasons, but also to keep an overview of all those nicknames. diff --git a/etc/gdb_pretty/printers.py b/etc/gdb_pretty/printers.py index 0dfac99af5..1a7803619f 100644 --- a/etc/gdb_pretty/printers.py +++ b/etc/gdb_pretty/printers.py @@ -276,7 +276,7 @@ def children(self): """ Get the displayed children of the keyframe. """ - yield ('time', self.__val['time']) + yield ('time', self.__val['timestamp']) yield ('value', self.__val['value']) diff --git a/libopenage/coord/pixel.cpp b/libopenage/coord/pixel.cpp index c7dac5f815..a480cba2dc 100644 --- a/libopenage/coord/pixel.cpp +++ b/libopenage/coord/pixel.cpp @@ -1,9 +1,11 @@ -// Copyright 2016-2023 the openage authors. See copying.md for legal info. +// Copyright 2016-2024 the openage authors. See copying.md for legal info. #include "pixel.h" #include "coord/phys.h" #include "renderer/camera/camera.h" +#include "renderer/camera/definitions.h" + namespace openage { namespace coord { @@ -54,7 +56,7 @@ phys3 input::to_phys3(const std::shared_ptr &camera) c scene3 input::to_scene3(const std::shared_ptr &camera) const { // Use raycasting to find the position // Direction and origin point are fetched from the camera - auto cam_dir = renderer::camera::cam_direction; + auto cam_dir = renderer::camera::CAM_DIRECTION; auto ray_origin = camera->get_input_pos(*this); // xz plane that we want to intersect with diff --git a/libopenage/pathfinding/demo/demo_0.cpp b/libopenage/pathfinding/demo/demo_0.cpp index da7ae17492..bcf4cca945 100644 --- a/libopenage/pathfinding/demo/demo_0.cpp +++ b/libopenage/pathfinding/demo/demo_0.cpp @@ -10,6 +10,7 @@ #include "pathfinding/flow_field.h" #include "pathfinding/integration_field.h" #include "renderer/camera/camera.h" +#include "renderer/camera/definitions.h" #include "renderer/gui/integration/public/gui_application_with_logger.h" #include "renderer/opengl/window.h" #include "renderer/render_pass.h" @@ -313,7 +314,7 @@ void RenderManager0::hide_vectors() { std::pair RenderManager0::select_tile(double x, double y) { auto grid_plane_normal = Eigen::Vector3f{0, 1, 0}; auto grid_plane_point = Eigen::Vector3f{0, 0, 0}; - auto camera_direction = renderer::camera::cam_direction; + auto camera_direction = renderer::camera::CAM_DIRECTION; auto camera_position = camera->get_input_pos( coord::input(x, y)); diff --git a/libopenage/renderer/camera/CMakeLists.txt b/libopenage/renderer/camera/CMakeLists.txt index f4ae421320..aea6391e7a 100644 --- a/libopenage/renderer/camera/CMakeLists.txt +++ b/libopenage/renderer/camera/CMakeLists.txt @@ -1,3 +1,6 @@ add_sources(libopenage camera.cpp + definitions.cpp + frustum_2d.cpp + frustum_3d.cpp ) diff --git a/libopenage/renderer/camera/camera.cpp b/libopenage/renderer/camera/camera.cpp index 884aceca1b..f70efbee64 100644 --- a/libopenage/renderer/camera/camera.cpp +++ b/libopenage/renderer/camera/camera.cpp @@ -19,9 +19,9 @@ Camera::Camera(const std::shared_ptr &renderer, util::Vector2s viewport_size) : scene_pos{Eigen::Vector3f(0.0f, 10.0f, 0.0f)}, viewport_size{viewport_size}, - zoom{1.0f}, - max_zoom_out{64.0f}, - default_zoom_ratio{1.0f / 49}, + zoom{DEFAULT_ZOOM}, + max_zoom_out{DEFAULT_MAX_ZOOM_OUT}, + default_zoom_ratio{DEFAULT_ZOOM_RATIO}, moved{true}, zoom_changed{true}, view{Eigen::Matrix4f::Identity()}, @@ -149,11 +149,11 @@ const Eigen::Matrix4f &Camera::get_view_matrix() { return this->view; } - auto direction = cam_direction.normalized(); + auto direction = CAM_DIRECTION.normalized(); Eigen::Vector3f eye = this->scene_pos; Eigen::Vector3f center = this->scene_pos + direction; // look in the direction of the camera - Eigen::Vector3f up = Eigen::Vector3f(0.0f, 1.0f, 0.0f); + Eigen::Vector3f up = CAM_UP; Eigen::Vector3f f = center - eye; f.normalize(); @@ -197,7 +197,7 @@ const Eigen::Matrix4f &Camera::get_projection_matrix() { float halfheight = this->viewport_size[1] / 2; // get zoom level - float real_zoom = 0.5f * this->default_zoom_ratio * this->zoom; + float real_zoom = this->get_real_zoom_factor(); // zoom by narrowing or widening viewport around focus point. // narrow viewport => zoom in (projected area gets *smaller* while screen size stays the same) @@ -210,10 +210,11 @@ const Eigen::Matrix4f &Camera::get_projection_matrix() { Eigen::Matrix4f mat = Eigen::Matrix4f::Identity(); mat(0, 0) = 2.0f / (right - left); mat(1, 1) = 2.0f / (top - bottom); - mat(2, 2) = -2.0f / (1000.0f - (-1.0f)); // clip near and far planes (TODO: necessary?) + mat(2, 2) = -2.0f / (DEFAULT_FAR_DISTANCE - DEFAULT_NEAR_DISTANCE); // clip near and far planes (TODO: necessary?) mat(0, 3) = -(right + left) / (right - left); mat(1, 3) = -(top + bottom) / (top - bottom); - mat(2, 3) = -(1000.0f + (-1.0f)) / (1000.0f - (-1.0f)); // clip near and far planes (TODO: necessary?) + mat(2, 3) = -(DEFAULT_FAR_DISTANCE + DEFAULT_NEAR_DISTANCE) + / (DEFAULT_FAR_DISTANCE - DEFAULT_NEAR_DISTANCE); // clip near and far planes (TODO: necessary?) // Cache matrix for subsequent calls this->proj = mat; @@ -231,10 +232,10 @@ Eigen::Vector3f Camera::get_input_pos(const coord::input &coord) const { // calculate up (u) and right (s) vectors for camera // these define the camera plane in 3D space that the input // coord exists on - auto direction = cam_direction.normalized(); + auto direction = CAM_DIRECTION.normalized(); Eigen::Vector3f eye = this->scene_pos; Eigen::Vector3f center = this->scene_pos + direction; - Eigen::Vector3f up = Eigen::Vector3f(0.0f, 1.0f, 0.0f); + Eigen::Vector3f up = CAM_UP; Eigen::Vector3f f = center - eye; f.normalize(); @@ -247,7 +248,7 @@ Eigen::Vector3f Camera::get_input_pos(const coord::input &coord) const { // this is the same calculation as for the projection matrix float halfwidth = this->viewport_size[0] / 2; float halfheight = this->viewport_size[1] / 2; - float real_zoom = 0.5f * this->default_zoom_ratio * this->zoom; + float real_zoom = this->get_real_zoom_factor(); // calculate x and y offset on the camera plane relative to the camera position float x = +(2.0f * coord.x / this->viewport_size[0] - 1) * (halfwidth * real_zoom); @@ -263,6 +264,23 @@ const std::shared_ptr &Camera::get_uniform_buffer() con return this->uniform_buffer; } +const Frustum2d Camera::get_frustum_2d() { + return {this->viewport_size, + this->get_view_matrix(), + this->get_projection_matrix(), + this->get_zoom()}; +} + +const Frustum3d Camera::get_frustum_3d() const { + return {this->viewport_size, + DEFAULT_NEAR_DISTANCE, + DEFAULT_FAR_DISTANCE, + this->scene_pos, + CAM_DIRECTION, + CAM_UP, + this->get_real_zoom_factor()}; +} + void Camera::init_uniform_buffer(const std::shared_ptr &renderer) { resources::UBOInput view_input{"view", resources::ubo_input_t::M4F32}; resources::UBOInput proj_input{"proj", resources::ubo_input_t::M4F32}; @@ -274,4 +292,8 @@ void Camera::init_uniform_buffer(const std::shared_ptr &renderer) { this->uniform_buffer = renderer->add_uniform_buffer(ubo_info); } +inline float Camera::get_real_zoom_factor() const { + return 0.5f * this->default_zoom_ratio * this->zoom; +} + } // namespace openage::renderer::camera diff --git a/libopenage/renderer/camera/camera.h b/libopenage/renderer/camera/camera.h index c8c5c23076..eccb5b40e6 100644 --- a/libopenage/renderer/camera/camera.h +++ b/libopenage/renderer/camera/camera.h @@ -12,27 +12,23 @@ #include "coord/scene.h" #include "util/vector.h" +#include "renderer/camera/definitions.h" +#include "renderer/camera/frustum_2d.h" +#include "renderer/camera/frustum_3d.h" + + namespace openage::renderer { class Renderer; class UniformBuffer; namespace camera { -/** - * Camera direction (= where it looks at). - * Uses a dimetric perspective like in AoE with the (fixed) angles - * yaw = -135 degrees - * pitch = -30 degrees - */ -static const Eigen::Vector3f cam_direction{ - -1 * (std::sqrt(6.f) / 4), - -0.5f, - -1 * (std::sqrt(6.f) / 4), -}; - /** * Camera for selecting what part of the ingame world is displayed. * + * The camera uses orthographic projection as it is primarily used for + * 2D rendering. + * * TODO: Vulkan version. */ class Camera { @@ -62,9 +58,9 @@ class Camera { Camera(const std::shared_ptr &renderer, util::Vector2s viewport_size, Eigen::Vector3f scene_pos, - float zoom = 1.0f, - float max_zoom_out = 64.0f, - float default_zoom_ratio = 1.0f / 49); + float zoom = DEFAULT_ZOOM, + float max_zoom_out = DEFAULT_MAX_ZOOM_OUT, + float default_zoom_ratio = DEFAULT_ZOOM_RATIO); ~Camera() = default; /** @@ -143,6 +139,8 @@ class Camera { /** * Get the current zoom level of the camera. * + * Determines the scale of rendered objects. + * * @return Zoom level. */ float get_zoom() const; @@ -188,6 +186,20 @@ class Camera { */ const std::shared_ptr &get_uniform_buffer() const; + /** + * Get a 2D frustum object for this camera. + * + * @return Frustum object. + */ + const Frustum2d get_frustum_2d(); + + /** + * Get a 3D frustum object for this camera. + * + * @return Frustum object. + */ + const Frustum3d get_frustum_3d() const; + private: /** * Create the uniform buffer for the camera. @@ -196,6 +208,21 @@ class Camera { */ void init_uniform_buffer(const std::shared_ptr &renderer); + /** + * Get the zoom factor applied to the camera projection. + * + * The zoom factor is calculated as + * + * zoom * zoom_ratio * 0.5f + * + * Note that this zoom factor should NOT be used for sprite scaling, but + * only for 3D projection matrix calculations. For sprite scaling, use + * \p get_zoom() . + * + * @return Zoom factor for projection. + */ + inline float get_real_zoom_factor() const; + /** * Position in the 3D scene. */ diff --git a/libopenage/renderer/camera/definitions.cpp b/libopenage/renderer/camera/definitions.cpp new file mode 100644 index 0000000000..9011323855 --- /dev/null +++ b/libopenage/renderer/camera/definitions.cpp @@ -0,0 +1,9 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "definitions.h" + +namespace openage::renderer::camera { + +// this file is intentionally empty + +} // namespace openage::renderer::camera diff --git a/libopenage/renderer/camera/definitions.h b/libopenage/renderer/camera/definitions.h new file mode 100644 index 0000000000..9586239a2f --- /dev/null +++ b/libopenage/renderer/camera/definitions.h @@ -0,0 +1,61 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + + +namespace openage::renderer::camera { + +/** + * Camera direction (= where it looks at). + * Uses a dimetric perspective like in AoE with the (fixed) angles + * yaw = -135 degrees + * pitch = -30 degrees + */ +static const Eigen::Vector3f CAM_DIRECTION{ + -1 * (std::sqrt(6.f) / 4), + -0.5f, + -1 * (std::sqrt(6.f) / 4), +}; + +/** + * Camera up vector. + */ +static const Eigen::Vector3f CAM_UP{0.0f, 1.0f, 0.0f}; + +/** + * Default near distance of the camera. + * + * Determines how close objects can be to the camera before they are not rendered anymore. + */ +static constexpr float DEFAULT_NEAR_DISTANCE = 0.01f; + +/** + * Default far distance of the camera. + * + * Determines how far objects can be from the camera before they are not rendered anymore. + */ +static constexpr float DEFAULT_FAR_DISTANCE = 100.0f; + +/** + * Default zoom level of the camera. + */ +static constexpr float DEFAULT_ZOOM = 1.0f; + +/** + * Maximum zoom out level of the camera. + */ +static constexpr float DEFAULT_MAX_ZOOM_OUT = 64.0f; + +/** + * Default zoom ratio. + * + * This adjusts the zoom level of the camera to the size of sprites in the game, + * so that 1 pixel in a sprite == 1 pixel on the screen. + * + * 1.0f / 49 is the default value for the AoE2 sprites. + */ +static constexpr float DEFAULT_ZOOM_RATIO = 1.0f / 49; + +} // namespace openage::renderer::camera diff --git a/libopenage/renderer/camera/frustum_2d.cpp b/libopenage/renderer/camera/frustum_2d.cpp new file mode 100644 index 0000000000..614b2b74a2 --- /dev/null +++ b/libopenage/renderer/camera/frustum_2d.cpp @@ -0,0 +1,60 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "frustum_2d.h" + + +namespace openage::renderer::camera { + +Frustum2d::Frustum2d(const util::Vector2s &viewport_size, + const Eigen::Matrix4f &view_matrix, + const Eigen::Matrix4f &projection_matrix, + const float zoom) { + this->update(viewport_size, view_matrix, projection_matrix, zoom); +} + +void Frustum2d::update(const util::Vector2s &viewport_size, + const Eigen::Matrix4f &view_matrix, + const Eigen::Matrix4f &projection_matrix, + const float zoom) { + this->inv_viewport_size = {1.0f / viewport_size[0], 1.0f / viewport_size[1]}; + this->inv_zoom_factor = 1.0f / zoom; + + // calculate the transformation matrix + this->transform_matrix = projection_matrix * view_matrix; +} + +bool Frustum2d::in_frustum(const Eigen::Vector3f &scene_pos, + const Eigen::Matrix4f &model_matrix, + const float scalefactor, + const util::Vector4i &boundaries) const { + // calculate the position of the scene object in screen space + Eigen::Vector4f clip_pos = this->transform_matrix * model_matrix * scene_pos.homogeneous(); + float x_ndc = clip_pos[0]; + float y_ndc = clip_pos[1]; + + float zoom_scale = scalefactor * this->inv_zoom_factor; + + // Scale the boundaries by the zoom factor and the viewport size + float left_bound = boundaries[0] * zoom_scale * this->inv_viewport_size[0]; + float right_bound = boundaries[1] * zoom_scale * this->inv_viewport_size[0]; + float top_bound = boundaries[2] * zoom_scale * this->inv_viewport_size[1]; + float bottom_bound = boundaries[3] * zoom_scale * this->inv_viewport_size[1]; + + // check if the object boundaries are inside the frustum + if (x_ndc - left_bound >= 1.0f) { + return false; + } + if (x_ndc + right_bound <= -1.0f) { + return false; + } + if (y_ndc + top_bound <= -1.0f) { + return false; + } + if (y_ndc - bottom_bound >= 1.0f) { + return false; + } + + return true; +} + +} // namespace openage::renderer::camera diff --git a/libopenage/renderer/camera/frustum_2d.h b/libopenage/renderer/camera/frustum_2d.h new file mode 100644 index 0000000000..3403d72a43 --- /dev/null +++ b/libopenage/renderer/camera/frustum_2d.h @@ -0,0 +1,81 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "util/vector.h" + +namespace openage::renderer::camera { + +/** + * Frustum for culling objects outside of a camera view in 2D screen space. + * This frustum object should be used for sprite culling as sprites do not exist in 3D world space. + * They are directly projected and reszied to the screen space. + * + * Used for frustum culling (https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling) + * in the renderer. + */ +class Frustum2d { +public: + /** + * Create a new 2D frustum. + * + * @param viewport_size Size of the camera viewport (width x height). + * @param view_matrix View matrix of the camera. + * @param projection_matrix Projection matrix of the camera. + * @param zoom Zoom of the camera. + */ + Frustum2d(const util::Vector2s &viewport_size, + const Eigen::Matrix4f &view_matrix, + const Eigen::Matrix4f &projection_matrix, + const float zoom); + + /** + * Update the frustum with new camera parameters. + * + * @param viewport_size Size of the camera viewport (width x height). + * @param view_matrix View matrix of the camera. + * @param projection_matrix Projection matrix of the camera. + * @param zoom Zoom of the camera. + */ + void update(const util::Vector2s &viewport_size, + const Eigen::Matrix4f &view_matrix, + const Eigen::Matrix4f &projection_matrix, + const float zoom); + + /** + * Check if a scene object is inside the frustum. + * + * @param scene_pos 3D scene coordinates. + * @param model_matrix Model matrix of the object. + * @param scalefactor Scale factor of the animation. + * @param boundaries Boundaries of the animation (in pixels): left, right, top, bottom. + * + * @return true if the object is inside the frustum, false otherwise. + */ + bool in_frustum(const Eigen::Vector3f &scene_pos, + const Eigen::Matrix4f &model_matrix, + const float scalefactor, + const util::Vector4i &boundaries) const; + +private: + /** + * Camera transformation matrix. + * + * Pre-calculated from view and projection matrix. + */ + Eigen::Matrix4f transform_matrix; + + /** + * Viewport size of the camera (width x height). + */ + Eigen::Vector2f inv_viewport_size; + + /** + * Zoom factor of the camera. + */ + float inv_zoom_factor; +}; + +} // namespace openage::renderer::camera diff --git a/libopenage/renderer/camera/frustum_3d.cpp b/libopenage/renderer/camera/frustum_3d.cpp new file mode 100644 index 0000000000..0f4b9bb340 --- /dev/null +++ b/libopenage/renderer/camera/frustum_3d.cpp @@ -0,0 +1,132 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "frustum_3d.h" + + +namespace openage::renderer::camera { + +Frustum3d::Frustum3d(const util::Vector2s &viewport_size, + const float near_distance, + const float far_distance, + const Eigen::Vector3f &camera_pos, + const Eigen::Vector3f &cam_direction, + const Eigen::Vector3f &up_direction, + const float zoom_factor) { + this->update(viewport_size, + near_distance, + far_distance, + camera_pos, + cam_direction, + up_direction, + zoom_factor); +} + +void Frustum3d::update(const util::Vector2s &viewport_size, + const float near_distance, + const float far_distance, + const Eigen::Vector3f &camera_pos, + const Eigen::Vector3f &cam_direction, + const Eigen::Vector3f &up_direction, + const float zoom_factor) { + // offsets are adjusted by zoom + // this is the same calculation as for the projection matrix + float halfscreenwidth = viewport_size[0] / 2; + float halfscreenheight = viewport_size[1] / 2; + + float halfwidth = halfscreenwidth * zoom_factor; + float halfheight = halfscreenheight * zoom_factor; + + Eigen::Vector3f direction = cam_direction.normalized(); + Eigen::Vector3f eye = camera_pos; + Eigen::Vector3f center = camera_pos + direction; + + // calculate up (u) and right (s) vectors for camera + // these define the camera plane in 3D space that the input + Eigen::Vector3f f = center - eye; + f.normalize(); + Eigen::Vector3f s = f.cross(up_direction); + s.normalize(); + Eigen::Vector3f u = s.cross(f); + u.normalize(); + + // calculate distance of the camera position to the near and far plane + Eigen::Vector3f near_position = camera_pos + direction * near_distance; + Eigen::Vector3f far_position = camera_pos + direction * far_distance; + + // The frustum is a cuboid box with 8 points defining it (4 on the near plane, 4 on the far plane) + Eigen::Vector3f near_top_left = near_position - s * halfwidth + u * halfheight; + Eigen::Vector3f near_top_right = near_position + s * halfwidth + u * halfheight; + Eigen::Vector3f near_bottom_left = near_position - s * halfwidth - u * halfheight; + Eigen::Vector3f near_bottom_right = near_position + s * halfwidth - u * halfheight; + + Eigen::Vector3f far_top_left = far_position - s * halfwidth + u * halfheight; + // Eigen::Vector3f far_top_right = far_position + s * halfwidth + u * halfheight; // unused + Eigen::Vector3f far_bottom_left = far_position - s * halfwidth - u * halfheight; + Eigen::Vector3f far_bottom_right = far_position + s * halfwidth - u * halfheight; + + // The near and far planes are easiest to compute, as they should be in the direction of the camera + this->near_face_normal = cam_direction.normalized(); + this->far_face_normal = -1.0f * cam_direction.normalized(); + + // The distance of the plane from the origin is the dot product of the normal and a point on the plane + this->near_face_distance = this->near_face_normal.dot(near_position); + this->far_face_distance = this->far_face_normal.dot(far_position); + + // Each left, right, top, and bottom plane are defined by three points on the plane + this->left_face_normal = (near_bottom_left - near_top_left).cross(far_bottom_left - near_bottom_left); + this->right_face_normal = (far_bottom_right - near_bottom_right).cross(near_bottom_right - near_top_right); + this->top_face_normal = (near_top_right - near_top_left).cross(near_top_left - far_top_left); + this->bottom_face_normal = (near_bottom_left - far_bottom_left).cross(near_bottom_right - near_bottom_left); + + // for orthographic projection, the normal of left/right should equal -s/s + // and the normal of top/bottom should equal u/-u + this->left_face_normal.normalize(); + this->right_face_normal.normalize(); + this->top_face_normal.normalize(); + this->bottom_face_normal.normalize(); + + // calculate the distance of the planes to the origin + this->left_face_distance = this->left_face_normal.dot(near_bottom_left); + this->right_face_distance = this->right_face_normal.dot(far_bottom_right); + this->top_face_distance = this->top_face_normal.dot(near_top_right); + this->bottom_face_distance = this->bottom_face_normal.dot(near_bottom_left); +} + +bool Frustum3d::in_frustum(const Eigen::Vector3f &pos) const { + // For each plane, if a point is behind one of the frustum planes, it is not within the frustum + float distance; + + distance = this->top_face_normal.dot(pos) - this->top_face_distance; + if (distance < 0) { + return false; + } + + distance = this->bottom_face_normal.dot(pos) - this->bottom_face_distance; + if (distance < 0) { + return false; + } + + distance = this->left_face_normal.dot(pos) - this->left_face_distance; + if (distance < 0) { + return false; + } + + distance = this->right_face_normal.dot(pos) - this->right_face_distance; + if (distance < 0) { + return false; + } + + distance = this->far_face_normal.dot(pos) - this->far_face_distance; + if (distance < 0) { + return false; + } + + distance = this->bottom_face_normal.dot(pos) - this->bottom_face_distance; + if (distance < 0) { + return false; + } + + return true; +} + +} // namespace openage::renderer::camera diff --git a/libopenage/renderer/camera/frustum_3d.h b/libopenage/renderer/camera/frustum_3d.h new file mode 100644 index 0000000000..789b7f1064 --- /dev/null +++ b/libopenage/renderer/camera/frustum_3d.h @@ -0,0 +1,133 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "util/vector.h" + + +namespace openage::renderer::camera { + +/** + * Frustum for culling objects outside of a camera viewcone in 3D space. The frustum + * is defined by 6 planes (top, bottom, right, left, far, near) that define the boundaries + * of the viewing volume of the camera. + * + * Used for frustum culling (https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling) + * in the renderer. + * + * As the openage camera uses orthographic projection, the frustum is a box, i.e. plane opposite + * to each other are parallel. + */ +class Frustum3d { +public: + /** + * Create a new frustum. + * + * @param viewport_size Size of the viewport (width x height). + * @param near_distance Near distance of the frustum. + * @param far_distance Far distance of the frustum. + * @param camera_pos Scene position of the camera. + * @param cam_direction Direction the camera is looking at. + * @param up_direction Up direction of the camera. + * @param zoom_factor Zoom factor of the camera. + */ + Frustum3d(const util::Vector2s &viewport_size, + const float near_distance, + const float far_distance, + const Eigen::Vector3f &camera_pos, + const Eigen::Vector3f &cam_direction, + const Eigen::Vector3f &up_direction, + const float zoom_factor); + + /** + * Update this frustum with the new camera parameters. + * + * @param viewport_size Size of the viewport (width x height). + * @param near_distance Near distance of the frustum. + * @param far_distance Far distance of the frustum. + * @param camera_pos Scene position of the camera. + * @param cam_direction Direction the camera is looking at. + * @param up_direction Up direction of the camera. + * @param zoom_factor Zoom factor of the camera. + */ + void update(const util::Vector2s &viewport_size, + const float near_distance, + const float far_distance, + const Eigen::Vector3f &camera_pos, + const Eigen::Vector3f &cam_direction, + const Eigen::Vector3f &up_direction, + const float zoom_factor); + + /** + * Check whether a point in the scene is inside the frustum. + * + * @param scene_pos 3D scene coordinates. + * + * @return true if the point is inside the frustum, else false. + */ + bool in_frustum(const Eigen::Vector3f &scene_pos) const; + +private: + /** + * Normal vector of the top face. + */ + Eigen::Vector3f top_face_normal; + + /** + * Normal vector of the bottom face. + */ + Eigen::Vector3f bottom_face_normal; + + /** + * Normal vector of the right face. + */ + Eigen::Vector3f right_face_normal; + + /** + * Normal vector of the left face. + */ + Eigen::Vector3f left_face_normal; + + /** + * Normal vector of the far face. + */ + Eigen::Vector3f far_face_normal; + + /** + * Normal vector of the near face. + */ + Eigen::Vector3f near_face_normal; + + /** + * Shortest distance from the top face to the scene origin. + */ + float top_face_distance; + + /** + * Shortest distance from the bottom face to the scene origin. + */ + float bottom_face_distance; + + /** + * Shortest distance from the right face to the scene origin. + */ + float right_face_distance; + + /** + * Shortest distance from the left face to the scene origin. + */ + float left_face_distance; + + /** + * Shortest distance from the far face to the scene origin. + */ + float far_face_distance; + + /** + * Shortest distance from the near face to the scene origin. + */ + float near_face_distance; +}; +} // namespace openage::renderer::camera diff --git a/libopenage/renderer/demo/CMakeLists.txt b/libopenage/renderer/demo/CMakeLists.txt index 24a5c20d84..fb93e5279b 100644 --- a/libopenage/renderer/demo/CMakeLists.txt +++ b/libopenage/renderer/demo/CMakeLists.txt @@ -5,7 +5,9 @@ add_sources(libopenage demo_3.cpp demo_4.cpp demo_5.cpp + demo_6.cpp stresstest_0.cpp + stresstest_1.cpp tests.cpp util.cpp ) diff --git a/libopenage/renderer/demo/demo_0.cpp b/libopenage/renderer/demo/demo_0.cpp index d82eb62381..38fd467d0b 100644 --- a/libopenage/renderer/demo/demo_0.cpp +++ b/libopenage/renderer/demo/demo_0.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "demo_0.h" diff --git a/libopenage/renderer/demo/demo_0.h b/libopenage/renderer/demo/demo_0.h index 4cdb1d831b..7ea7e68f19 100644 --- a/libopenage/renderer/demo/demo_0.h +++ b/libopenage/renderer/demo/demo_0.h @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/demo/demo_1.cpp b/libopenage/renderer/demo/demo_1.cpp index 1ecd7815a1..23dfdfa67b 100644 --- a/libopenage/renderer/demo/demo_1.cpp +++ b/libopenage/renderer/demo/demo_1.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "demo_1.h" diff --git a/libopenage/renderer/demo/demo_1.h b/libopenage/renderer/demo/demo_1.h index 6b1c3168ee..d978603040 100644 --- a/libopenage/renderer/demo/demo_1.h +++ b/libopenage/renderer/demo/demo_1.h @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/demo/demo_2.cpp b/libopenage/renderer/demo/demo_2.cpp index 7f6f398e12..e615d238ba 100644 --- a/libopenage/renderer/demo/demo_2.cpp +++ b/libopenage/renderer/demo/demo_2.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "demo_2.h" diff --git a/libopenage/renderer/demo/demo_2.h b/libopenage/renderer/demo/demo_2.h index 077f61ac33..211e5e9310 100644 --- a/libopenage/renderer/demo/demo_2.h +++ b/libopenage/renderer/demo/demo_2.h @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/demo/demo_3.cpp b/libopenage/renderer/demo/demo_3.cpp index 23de242fd7..408e699196 100644 --- a/libopenage/renderer/demo/demo_3.cpp +++ b/libopenage/renderer/demo/demo_3.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2024 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "demo_3.h" diff --git a/libopenage/renderer/demo/demo_3.h b/libopenage/renderer/demo/demo_3.h index fda210acbd..9eec5dc74c 100644 --- a/libopenage/renderer/demo/demo_3.h +++ b/libopenage/renderer/demo/demo_3.h @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once @@ -10,7 +10,6 @@ namespace openage::renderer::tests { * Show off the render stages in the level 2 renderer and the camera * system. * - Window creation - * - Loading shaders * - Creating a camera * - Initializing the level 2 render stages: skybox, terrain, world, screen * - Adding renderables to the render stages via the render factory diff --git a/libopenage/renderer/demo/demo_4.h b/libopenage/renderer/demo/demo_4.h index 7c80e947e1..c3ce3ad4b5 100644 --- a/libopenage/renderer/demo/demo_4.h +++ b/libopenage/renderer/demo/demo_4.h @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/demo/demo_6.cpp b/libopenage/renderer/demo/demo_6.cpp new file mode 100644 index 0000000000..038bb0240e --- /dev/null +++ b/libopenage/renderer/demo/demo_6.cpp @@ -0,0 +1,428 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "demo_6.h" + +#include + +#include "curve/continuous.h" +#include "curve/segmented.h" +#include "renderer/camera/camera.h" +#include "renderer/camera/frustum_2d.h" +#include "renderer/camera/frustum_3d.h" +#include "renderer/gui/integration/public/gui_application_with_logger.h" +#include "renderer/opengl/window.h" +#include "renderer/render_pass.h" +#include "renderer/renderer.h" +#include "renderer/resources/animation/angle_info.h" +#include "renderer/resources/animation/frame_info.h" +#include "renderer/resources/animation/layer_info.h" +#include "renderer/resources/parser/parse_sprite.h" +#include "renderer/resources/parser/parse_terrain.h" +#include "renderer/resources/shader_source.h" +#include "renderer/resources/texture_info.h" +#include "renderer/shader_program.h" +#include "renderer/texture.h" +#include "renderer/uniform_buffer.h" +#include "time/clock.h" +#include "util/path.h" +#include "util/vector.h" + + +namespace openage::renderer::tests { + +void renderer_demo_6(const util::Path &path) { + auto render_mgr = RenderManagerDemo6{path}; + + // Create render objects + auto renderables_2d = render_mgr.create_2d_obj(); + auto renderables_3d = render_mgr.create_3d_obj(); + auto renderables_frame = render_mgr.create_frame_obj(); + + // Add objects to the render passes + render_mgr.obj_2d_pass->add_renderables(std::move(renderables_2d)); + render_mgr.obj_3d_pass->add_renderables({renderables_3d}); + render_mgr.frame_pass->add_renderables(std::move(renderables_frame)); + + render_mgr.window->add_key_callback([&](const QKeyEvent &ev) { + if (ev.type() == QEvent::KeyPress) { + auto key = ev.key(); + + // move_frame moves the camera in the specified direction in the next drawn frame + switch (key) { + case Qt::Key_W: { // forward + render_mgr.camera->move_rel(Eigen::Vector3f(-1.0f, 0.0f, -1.0f), 0.25f); + } break; + case Qt::Key_A: { // left + render_mgr.camera->move_rel(Eigen::Vector3f(-1.0f, 0.0f, 1.0f), 0.25f); + } break; + case Qt::Key_S: { // back + render_mgr.camera->move_rel(Eigen::Vector3f(1.0f, 0.0f, 1.0f), 0.25f); + } break; + case Qt::Key_D: { // right + render_mgr.camera->move_rel(Eigen::Vector3f(1.0f, 0.0f, -1.0f), 0.25f); + } break; + default: + break; + } + + auto new_cam_unifs = render_mgr.camera->get_uniform_buffer()->new_uniform_input( + "view", + render_mgr.camera->get_view_matrix(), + "proj", + render_mgr.camera->get_projection_matrix()); + render_mgr.camera->get_uniform_buffer()->update_uniforms(new_cam_unifs); + + auto frustum_camera = *render_mgr.camera; + auto half_cam_size = util::Vector2s{render_mgr.camera->get_viewport_size()[0] * 0.7, + render_mgr.camera->get_viewport_size()[1] * 0.7}; + frustum_camera.resize(half_cam_size[0], half_cam_size[1]); + auto frustum_2d = frustum_camera.get_frustum_2d(); + frustum_2d.update(frustum_camera.get_viewport_size(), + frustum_camera.get_view_matrix(), + frustum_camera.get_projection_matrix(), + frustum_camera.get_zoom()); + + auto renderables_2d = render_mgr.create_2d_obj(); + std::vector renderables_in_frustum{}; + for (size_t i = 0; i < render_mgr.obj_2d_positions.size(); ++i) { + auto pos = render_mgr.obj_2d_positions.at(i); + bool in_frustum = frustum_2d.in_frustum(pos.to_world_space(), + Eigen::Matrix4f::Identity(), + render_mgr.animation_2d_info.get_scalefactor(), + render_mgr.animation_2d_info.get_max_bounds()); + if (in_frustum) { + renderables_in_frustum.push_back(renderables_2d.at(i)); + } + } + render_mgr.obj_2d_pass->clear_renderables(); + render_mgr.obj_2d_pass->add_renderables(std::move(renderables_in_frustum)); + } + }); + + render_mgr.run(); +} + +RenderManagerDemo6::RenderManagerDemo6(const util::Path &path) : + path{path} { + this->setup(); +} + +void RenderManagerDemo6::run() { + while (not window->should_close()) { + this->qtapp->process_events(); + + // Draw everything + renderer->render(this->obj_3d_pass); + renderer->render(this->obj_2d_pass); + renderer->render(this->frame_pass); + renderer->render(this->display_pass); + + // Display final output on screen + this->window->update(); + } +} + +const std::vector RenderManagerDemo6::create_2d_obj() { + std::vector renderables; + for (auto scene_pos : this->obj_2d_positions) { + // Create renderable for 2D animation + auto scale = this->animation_2d_info.get_scalefactor(); + auto tex_id = this->animation_2d_info.get_layer(0).get_angle(0)->get_frame(0)->get_texture_idx(); + auto subtex_id = this->animation_2d_info.get_layer(0).get_angle(0)->get_frame(0)->get_subtexture_idx(); + auto subtex = this->animation_2d_info.get_texture(tex_id)->get_subtex_info(subtex_id); + auto subtex_size = subtex.get_size(); + Eigen::Vector2f subtex_size_vec{ + static_cast(subtex_size[0]), + static_cast(subtex_size[1])}; + auto anchor_params = subtex.get_anchor_params(); + auto anchor_params_vec = Eigen::Vector2f{ + static_cast(anchor_params[0]), + static_cast(anchor_params[1])}; + auto tile_params = subtex.get_subtex_coords(); + auto animation_2d_unifs = this->obj_2d_shader->new_uniform_input( + "obj_world_position", + scene_pos.to_world_space(), + "scale", + scale, + "subtex_size", + subtex_size_vec, + "anchor_offset", + anchor_params_vec, + "tex", + this->obj_2d_texture, + "tile_params", + tile_params); + auto quad = this->renderer->add_mesh_geometry(resources::MeshData::make_quad()); + Renderable animation_2d_obj{ + animation_2d_unifs, + quad, + true, + true, + }; + + renderables.push_back(animation_2d_obj); + } + + return renderables; +} + +const renderer::Renderable RenderManagerDemo6::create_3d_obj() { + auto terrain_tex_info = this->terrain_3d_info.get_texture(0); + auto terrain_tex_data = resources::Texture2dData{*terrain_tex_info}; + this->obj_3d_texture = this->renderer->add_texture(terrain_tex_data); + + // Create renderable for terrain + auto terrain_unifs = this->obj_3d_shader->new_uniform_input( + "tex", + this->obj_3d_texture); + std::vector terrain_pos{}; + terrain_pos.push_back({-25, -25, 0}); + terrain_pos.push_back({25, -25, 0}); + terrain_pos.push_back({-25, 25, 0}); + terrain_pos.push_back({25, 25, 0}); + std::vector terrain_verts{}; + for (size_t i = 0; i < terrain_pos.size(); ++i) { + auto scene_pos = terrain_pos.at(i).to_world_space(); + terrain_verts.push_back(scene_pos[0]); + terrain_verts.push_back(scene_pos[1]); + terrain_verts.push_back(scene_pos[2]); + terrain_verts.push_back(0.0f + i / 2); + terrain_verts.push_back(0.0f + i % 2); + } + auto vert_info = resources::VertexInputInfo{ + {resources::vertex_input_t::V3F32, resources::vertex_input_t::V2F32}, + resources::vertex_layout_t::AOS, + resources::vertex_primitive_t::TRIANGLE_STRIP, + }; + std::vector vert_data(terrain_verts.size() * sizeof(float)); + std::memcpy(vert_data.data(), terrain_verts.data(), vert_data.size()); + auto terrain_mesh = resources::MeshData{std::move(vert_data), vert_info}; + auto terrain_geometry = this->renderer->add_mesh_geometry(terrain_mesh); + Renderable terrain_obj{ + terrain_unifs, + terrain_geometry, + true, + true, + }; + + return terrain_obj; +} + +const std::vector RenderManagerDemo6::create_frame_obj() { + std::vector renderables; + for (auto scene_pos : this->obj_2d_positions) { + // Create renderable for frame + std::array frame_verts{-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f}; + std::vector frame_vert_data(frame_verts.size() * sizeof(float)); + std::memcpy(frame_vert_data.data(), frame_verts.data(), frame_vert_data.size()); + auto frame_vert_info = resources::VertexInputInfo{ + {resources::vertex_input_t::V2F32}, + resources::vertex_layout_t::AOS, + resources::vertex_primitive_t::LINE_LOOP, + }; + auto frame_mesh = resources::MeshData{std::move(frame_vert_data), frame_vert_info}; + auto frame_geometry = this->renderer->add_mesh_geometry(frame_mesh); + + auto scale = this->animation_2d_info.get_scalefactor(); + auto max_frame_size = this->animation_2d_info.get_max_size(); + auto frame_size = Eigen::Vector2f{ + static_cast(max_frame_size[0]), + static_cast(max_frame_size[1])}; + auto frame_unifs = this->frame_shader->new_uniform_input( + "obj_world_position", + scene_pos.to_world_space(), + "scale", + scale, + "frame_size", + frame_size); + Renderable frame_obj{ + frame_unifs, + frame_geometry, + true, + true, + }; + + renderables.push_back(frame_obj); + } + + return renderables; +} + +void RenderManagerDemo6::setup() { + this->qtapp = std::make_shared(); + + // Create the window and renderer + window_settings settings; + settings.width = 1024; + settings.height = 768; + settings.debug = true; + this->window = std::make_shared("openage renderer test", settings); + this->renderer = window->make_renderer(); + + this->load_shaders(); + this->load_assets(); + this->create_camera(); + this->create_render_passes(); +} + +void RenderManagerDemo6::load_shaders() { + // Shader + auto shaderdir = this->path / "assets" / "test" / "shaders"; + + /* Shader for 3D objects*/ + auto obj_vshader_file = (shaderdir / "demo_6_3d.vert.glsl").open(); + auto obj_vshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + obj_vshader_file.read()); + obj_vshader_file.close(); + + auto obj_fshader_file = (shaderdir / "demo_6_3d.frag.glsl").open(); + auto obj_fshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + obj_fshader_file.read()); + obj_fshader_file.close(); + + /* Shader for 2D animations */ + auto obj_2d_vshader_file = (shaderdir / "demo_6_2d.vert.glsl").open(); + auto obj_2d_vshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + obj_2d_vshader_file.read()); + obj_2d_vshader_file.close(); + + auto obj_2d_fshader_file = (shaderdir / "demo_6_2d.frag.glsl").open(); + auto obj_2d_fshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + obj_2d_fshader_file.read()); + obj_2d_fshader_file.close(); + + /* Shader for frames */ + auto frame_vshader_file = (shaderdir / "demo_6_2d_frame.vert.glsl").open(); + auto frame_vshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + frame_vshader_file.read()); + frame_vshader_file.close(); + + auto frame_fshader_file = (shaderdir / "demo_6_2d_frame.frag.glsl").open(); + auto frame_fshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + frame_fshader_file.read()); + frame_fshader_file.close(); + + /* Shader for rendering to the screen */ + auto display_vshader_file = (shaderdir / "demo_6_display.vert.glsl").open(); + auto display_vshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::vertex, + display_vshader_file.read()); + display_vshader_file.close(); + + auto display_fshader_file = (shaderdir / "demo_6_display.frag.glsl").open(); + auto display_fshader_src = resources::ShaderSource( + resources::shader_lang_t::glsl, + resources::shader_stage_t::fragment, + display_fshader_file.read()); + display_fshader_file.close(); + + // Create shader programs + this->obj_3d_shader = this->renderer->add_shader({obj_vshader_src, obj_fshader_src}); + this->obj_2d_shader = this->renderer->add_shader({obj_2d_vshader_src, obj_2d_fshader_src}); + this->frame_shader = this->renderer->add_shader({frame_vshader_src, frame_fshader_src}); + this->display_shader = this->renderer->add_shader({display_vshader_src, display_fshader_src}); +} + +void RenderManagerDemo6::load_assets() { + // Load assets + auto animation_2d_path = this->path / "assets" / "test" / "textures" / "test_tank.sprite"; + this->animation_2d_info = resources::parser::parse_sprite_file(animation_2d_path); + + auto tex_info = this->animation_2d_info.get_texture(0); + auto tex_data = resources::Texture2dData{*tex_info}; + this->obj_2d_texture = this->renderer->add_texture(tex_data); + + // Load assets + auto terrain_path = this->path / "assets" / "test" / "textures" / "test_terrain.terrain"; + this->terrain_3d_info = resources::parser::parse_terrain_file(terrain_path); +} + +void RenderManagerDemo6::create_camera() { + // Camera + this->camera = std::make_shared(renderer, window->get_size()); + this->camera->set_zoom(2.0f); + + // Bind the camera uniform buffer to the shaders + obj_3d_shader->bind_uniform_buffer("camera", this->camera->get_uniform_buffer()); + obj_2d_shader->bind_uniform_buffer("camera", this->camera->get_uniform_buffer()); + frame_shader->bind_uniform_buffer("camera", this->camera->get_uniform_buffer()); + + // Update the camera uniform buffer + auto camera_unifs = camera->get_uniform_buffer()->new_uniform_input( + "view", + this->camera->get_view_matrix(), + "proj", + this->camera->get_projection_matrix(), + "inv_zoom", + 1.0f / this->camera->get_zoom()); + auto viewport_size = this->camera->get_viewport_size(); + Eigen::Vector2f viewport_size_vec{ + 1.0f / static_cast(viewport_size[0]), + 1.0f / static_cast(viewport_size[1])}; + camera_unifs->update("inv_viewport_size", viewport_size_vec); + this->camera->get_uniform_buffer()->update_uniforms(camera_unifs); +} + +void RenderManagerDemo6::create_render_passes() { + // Create render passes + auto window_size = window->get_size(); + auto color_texture0 = renderer->add_texture(resources::Texture2dInfo(window_size[0], + window_size[1], + resources::pixel_format::rgba8)); + auto fbo0 = renderer->create_texture_target({color_texture0}); + this->obj_3d_pass = renderer->add_render_pass({}, fbo0); + + auto color_texture1 = renderer->add_texture(resources::Texture2dInfo(window_size[0], + window_size[1], + resources::pixel_format::rgba8)); + auto fbo1 = renderer->create_texture_target({color_texture1}); + this->obj_2d_pass = renderer->add_render_pass({}, fbo1); + + auto color_texture2 = renderer->add_texture(resources::Texture2dInfo(window_size[0], + window_size[1], + resources::pixel_format::rgba8)); + auto fbo2 = renderer->create_texture_target({color_texture2}); + this->frame_pass = renderer->add_render_pass({}, fbo2); + + // Create render pass for rendering to screen + auto quad = renderer->add_mesh_geometry(resources::MeshData::make_quad()); + auto color_texture0_unif = display_shader->new_uniform_input("color_texture", color_texture0); + Renderable display_obj_3d{ + color_texture0_unif, + quad, + true, + true, + }; + auto color_texture1_unif = display_shader->new_uniform_input("color_texture", color_texture1); + Renderable display_obj_2d{ + color_texture1_unif, + quad, + true, + true, + }; + auto color_texture2_unif = display_shader->new_uniform_input("color_texture", color_texture2); + Renderable display_obj_frame{ + color_texture2_unif, + quad, + true, + true, + }; + this->display_pass = renderer->add_render_pass( + {display_obj_3d, display_obj_2d, display_obj_frame}, + renderer->get_display_target()); +} + +} // namespace openage::renderer::tests diff --git a/libopenage/renderer/demo/demo_6.h b/libopenage/renderer/demo/demo_6.h new file mode 100644 index 0000000000..7c5d3052c5 --- /dev/null +++ b/libopenage/renderer/demo/demo_6.h @@ -0,0 +1,101 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include + +#include "coord/scene.h" +#include "renderer/renderable.h" +#include "renderer/resources/animation/animation_info.h" +#include "renderer/resources/terrain/terrain_info.h" +#include "util/path.h" + + +namespace openage::renderer { +class RenderPass; +class Renderer; +class ShaderProgram; +class Texture2d; + +namespace camera { +class Camera; +} + +namespace gui { +class GuiApplicationWithLogger; +} + +namespace opengl { +class GlWindow; +} + +namespace tests { + +/** + * Show the usage of frustum culling in the renderer. + * - Window creation + * - Loading shaders + * - Creating a camera + * - 2D and 3D frustum retrieval + * - Manipulating the frustum + * - Rendering objects with frustum culling + * + * @param path Path to the openage asset directory. + */ +void renderer_demo_6(const util::Path &path); + + +class RenderManagerDemo6 { +public: + RenderManagerDemo6(const util::Path &path); + void run(); + + const std::vector create_2d_obj(); + const renderer::Renderable create_3d_obj(); + const std::vector create_frame_obj(); + + std::shared_ptr qtapp; + + std::shared_ptr window; + + std::shared_ptr camera; + + std::shared_ptr obj_2d_pass; + std::shared_ptr obj_3d_pass; + std::shared_ptr frame_pass; + std::shared_ptr display_pass; + + const std::array obj_2d_positions = { + coord::scene3{0, 0, 0}, + coord::scene3{-4, -4, 0}, + coord::scene3{4, 4, 0}, + coord::scene3{-2, 3, 0}, + coord::scene3{3, -2, 0}, + }; + + resources::Animation2dInfo animation_2d_info; + resources::TerrainInfo terrain_3d_info; + +private: + void setup(); + + void load_shaders(); + void load_assets(); + void create_camera(); + void create_render_passes(); + + util::Path path; + + std::shared_ptr renderer; + + std::shared_ptr obj_2d_shader; + std::shared_ptr obj_3d_shader; + std::shared_ptr frame_shader; + std::shared_ptr display_shader; + + std::shared_ptr obj_2d_texture; + std::shared_ptr obj_3d_texture; +}; + +} // namespace tests +} // namespace openage::renderer diff --git a/libopenage/renderer/demo/stresstest_1.cpp b/libopenage/renderer/demo/stresstest_1.cpp new file mode 100644 index 0000000000..ac6177f819 --- /dev/null +++ b/libopenage/renderer/demo/stresstest_1.cpp @@ -0,0 +1,236 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#include "stresstest_1.h" + +#include + +#include "coord/tile.h" +#include "renderer/camera/camera.h" +#include "renderer/gui/integration/public/gui_application_with_logger.h" +#include "renderer/opengl/window.h" +#include "renderer/render_factory.h" +#include "renderer/render_pass.h" +#include "renderer/resources/assets/asset_manager.h" +#include "renderer/resources/shader_source.h" +#include "renderer/stages/camera/manager.h" +#include "renderer/stages/screen/render_stage.h" +#include "renderer/stages/skybox/render_stage.h" +#include "renderer/stages/terrain/render_entity.h" +#include "renderer/stages/terrain/render_stage.h" +#include "renderer/stages/world/render_entity.h" +#include "renderer/stages/world/render_stage.h" +#include "renderer/uniform_buffer.h" +#include "time/clock.h" +#include "util/fps.h" + +namespace openage::renderer::tests { +void renderer_stresstest_1(const util::Path &path) { + auto qtapp = std::make_shared(); + + // Create the window and renderer + window_settings settings; + settings.width = 1024; + settings.height = 768; + settings.vsync = false; + settings.debug = true; + auto window = std::make_shared("openage renderer test", settings); + auto renderer = window->make_renderer(); + + // Clock required by world renderer for timing animation frames + auto clock = std::make_shared(); + + // Camera + // our viewport into the game world + // on this one, enable frustum culling + auto camera = std::make_shared(renderer, + window->get_size(), + Eigen::Vector3f{17.0f, 10.0f, 7.0f}, + 1.f, + 64.f, + 1.f / 49.f); + auto cam_unifs = camera->get_uniform_buffer()->create_empty_input(); + cam_unifs->update( + "view", + camera->get_view_matrix(), + "proj", + camera->get_projection_matrix(), + "inv_zoom", + 1.0f / camera->get_zoom()); + auto viewport_size = camera->get_viewport_size(); + Eigen::Vector2f viewport_size_vec{ + 1.0f / static_cast(viewport_size[0]), + 1.0f / static_cast(viewport_size[1])}; + cam_unifs->update("inv_viewport_size", viewport_size_vec); + camera->get_uniform_buffer()->update_uniforms(cam_unifs); + + // Render stages + // every stage use a different subrenderer that manages renderables, + // shaders, textures & more. + std::vector> + render_passes{}; + + // TODO: Make this optional for subrenderers? + auto asset_manager = std::make_shared( + renderer, + path["assets"]["test"]); + + // Renders the background + auto skybox_renderer = std::make_shared( + window, + renderer, + path["assets"]["shaders"]); + skybox_renderer->set_color(1.0f, 0.5f, 0.0f, 1.0f); // orange color + + // Renders the terrain in 3D + auto terrain_renderer = std::make_shared( + window, + renderer, + camera, + path["assets"]["shaders"], + asset_manager, + clock); + + // Renders units/buildings/other objects + auto world_renderer = std::make_shared( + window, + renderer, + camera, + path["assets"]["shaders"], + asset_manager, + clock); + + // Enable frustum culling + renderer::world::WorldRenderStage::ENABLE_FRUSTUM_CULLING = true; + + // Store the render passes of the renderers + // The order is important as its also the order in which they + // are rendered and drawn onto the screen. + render_passes.push_back(skybox_renderer->get_render_pass()); + render_passes.push_back(terrain_renderer->get_render_pass()); + render_passes.push_back(world_renderer->get_render_pass()); + + // Final output on screen has its own subrenderer + // It takes the outputs of all previous render passes + // and blends them together + auto screen_renderer = std::make_shared( + window, + renderer, + path["assets"]["shaders"]); + std::vector> targets{}; + for (auto &pass : render_passes) { + targets.push_back(pass->get_target()); + } + screen_renderer->set_render_targets(targets); + + window->add_resize_callback([&](size_t, size_t, double /*scale*/) { + // Acquire the render targets for all previous passes + std::vector> targets{}; + for (size_t i = 0; i < render_passes.size() - 1; ++i) { + targets.push_back(render_passes[i]->get_target()); + } + screen_renderer->set_render_targets(targets); + }); + + render_passes.push_back(screen_renderer->get_render_pass()); + + // Create some entities to populate the scene + auto render_factory = std::make_shared(terrain_renderer, world_renderer); + + // Fill a 10x10 terrain grid with height values + auto terrain_size = util::Vector2s{10, 10}; + std::vector> tiles{}; + tiles.reserve(terrain_size[0] * terrain_size[1]); + for (size_t i = 0; i < terrain_size[0] * terrain_size[1]; ++i) { + tiles.emplace_back(0.0f, "./textures/test_terrain.terrain"); + } + + // Create entity for terrain rendering + auto terrain0 = render_factory->add_terrain_render_entity(terrain_size, + coord::tile_delta{0, 0}); + + // send the terrain data to the terrain renderer + terrain0->update(terrain_size, tiles); + + std::vector> render_entities{}; + auto add_world_entity = [&](const coord::phys3 initial_pos, + const time::time_t time) { + const auto animation_path = "./textures/test_tank_mirrored.sprite"; + + auto position = curve::Continuous{nullptr, 0, "", nullptr, coord::phys3(0, 0, 0)}; + position.set_insert(time, initial_pos); + position.set_insert(time + 1, initial_pos + coord::phys3_delta{5, 0, 0}); + position.set_insert(time + 2, initial_pos + coord::phys3_delta{12, -2, 0}); + position.set_insert(time + 3, initial_pos + coord::phys3_delta{5, 5, 0}); + position.set_insert(time + 4, initial_pos + coord::phys3_delta{12, 12, 0}); + position.set_insert(time + 5, initial_pos + coord::phys3_delta{5, 9, 0}); + position.set_insert(time + 6, initial_pos + coord::phys3_delta{-2, 12, 0}); + position.set_insert(time + 7, initial_pos + coord::phys3_delta{0, 5, 0}); + position.set_insert(time + 8, initial_pos); + + auto angle = curve::Segmented{nullptr, 0}; + angle.set_insert(time, coord::phys_angle_t::from_int(225)); + angle.set_insert_jump(time + 1, coord::phys_angle_t::from_int(225), coord::phys_angle_t::from_int(210)); + angle.set_insert_jump(time + 2, coord::phys_angle_t::from_int(210), coord::phys_angle_t::from_int(0)); + angle.set_insert_jump(time + 3, coord::phys_angle_t::from_int(0), coord::phys_angle_t::from_int(270)); + angle.set_insert_jump(time + 4, coord::phys_angle_t::from_int(270), coord::phys_angle_t::from_int(60)); + angle.set_insert_jump(time + 5, coord::phys_angle_t::from_int(60), coord::phys_angle_t::from_int(45)); + angle.set_insert_jump(time + 6, coord::phys_angle_t::from_int(45), coord::phys_angle_t::from_int(120)); + angle.set_insert_jump(time + 7, coord::phys_angle_t::from_int(120), coord::phys_angle_t::from_int(135)); + angle.set_insert_jump(time + 8, coord::phys_angle_t::from_int(135), coord::phys_angle_t::from_int(225)); + + auto entity = render_factory->add_world_render_entity(); + entity->update(render_entities.size(), + position, + angle, + animation_path, + time); + render_entities.push_back(entity); + }; + + // Stop after 1000 entities + size_t entity_limit = 1000; + + clock->start(); + + util::FrameCounter timer; + + time::time_t next_entity = clock->get_real_time(); + while (render_entities.size() <= entity_limit) { + // Print FPS + timer.frame(); + std::cout + << "Entities: " << render_entities.size() + << " -- " + << "FPS: " << timer.fps << "\r" << std::flush; + + qtapp->process_events(); + + // Advance time + clock->update_time(); + auto current_time = clock->get_real_time(); + if (current_time > next_entity) { + add_world_entity(coord::phys3(0.5, 0.5, 0.0f), clock->get_time()); + next_entity = current_time + 0.05; + } + + // Update the renderables of the subrenderers + terrain_renderer->update(); + world_renderer->update(); + + // Draw everything + for (auto &pass : render_passes) { + renderer->render(pass); + } + + renderer->check_error(); + + // Display final output on screen + window->update(); + } + + clock->stop(); + log::log(MSG(info) << "Stopped after rendering " << render_entities.size() << " entities"); + + window->close(); +} +} // namespace openage::renderer::tests diff --git a/libopenage/renderer/demo/stresstest_1.h b/libopenage/renderer/demo/stresstest_1.h new file mode 100644 index 0000000000..a2dbde6136 --- /dev/null +++ b/libopenage/renderer/demo/stresstest_1.h @@ -0,0 +1,16 @@ +// Copyright 2024-2024 the openage authors. See copying.md for legal info. + +#pragma once + +#include "util/path.h" + +namespace openage::renderer::tests { + +/** + * Stresstest for the renderer's frustum culling feature. + * + * @param path Path to the openage asset directory. + */ +void renderer_stresstest_1(const util::Path &path); + +} diff --git a/libopenage/renderer/demo/tests.cpp b/libopenage/renderer/demo/tests.cpp index 95f6bd40af..d3fb0e3c21 100644 --- a/libopenage/renderer/demo/tests.cpp +++ b/libopenage/renderer/demo/tests.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2023 the openage authors. See copying.md for legal info. +// Copyright 2015-2024 the openage authors. See copying.md for legal info. #include "tests.h" @@ -11,8 +11,9 @@ #include "renderer/demo/demo_3.h" #include "renderer/demo/demo_4.h" #include "renderer/demo/demo_5.h" +#include "renderer/demo/demo_6.h" #include "renderer/demo/stresstest_0.h" - +#include "renderer/demo/stresstest_1.h" namespace openage::renderer::tests { @@ -42,6 +43,10 @@ void renderer_demo(int demo_id, const util::Path &path) { renderer_demo_5(path); break; + case 6: + renderer_demo_6(path); + break; + default: log::log(MSG(err) << "Unknown renderer demo requested: " << demo_id << "."); break; @@ -53,7 +58,9 @@ OAAPI void renderer_stresstest(int demo_id, const util::Path &path) { case 0: renderer_stresstest_0(path); break; - + case 1: + renderer_stresstest_1(path); + break; default: log::log(MSG(err) << "Unknown renderer stresstest requested: " << demo_id << "."); break; diff --git a/libopenage/renderer/opengl/lookup.h b/libopenage/renderer/opengl/lookup.h index e399e98399..a137101482 100644 --- a/libopenage/renderer/opengl/lookup.h +++ b/libopenage/renderer/opengl/lookup.h @@ -1,4 +1,4 @@ -// Copyright 2018-2023 the openage authors. See copying.md for legal info. +// Copyright 2018-2024 the openage authors. See copying.md for legal info. // Lookup tables for translating between OpenGL-specific values and generic renderer values, // as well as mapping things like type sizes within OpenGL. @@ -137,6 +137,7 @@ static constexpr auto GL_PRIMITIVE = datastructure::create_const_map &layers) : scalefactor{scalefactor}, texture_infos{textures}, - layers{layers} {} + layers{layers}, + max_bounds{} { + // Calculate max bounds + for (auto &layer : this->layers) { + for (size_t i = 0; i < layer.get_angle_count(); ++i) { + auto &angle = layer.get_angle(i); + for (size_t j = 0; j < angle->get_frame_count(); ++j) { + auto &frame = angle->get_frame(j); + auto tex_idx = frame->get_texture_idx(); + auto subtex_idx = frame->get_subtexture_idx(); + + auto &tex = this->texture_infos.at(tex_idx); + auto &subtex = tex->get_subtex_info(subtex_idx); + + auto subtex_size = subtex.get_size(); + auto anchor_pos = subtex.get_anchor_pos(); + + int left_margin = anchor_pos.x(); + int right_margin = subtex_size.x() - anchor_pos.x(); + int top_margin = anchor_pos.y(); + int bottom_margin = subtex_size.y() - anchor_pos.y(); + + this->max_bounds[0] = std::max(this->max_bounds[0], left_margin); + this->max_bounds[1] = std::max(this->max_bounds[1], right_margin); + this->max_bounds[2] = std::max(this->max_bounds[2], top_margin); + this->max_bounds[3] = std::max(this->max_bounds[3], bottom_margin); + } + } + } +} float Animation2dInfo::get_scalefactor() const { return this->scalefactor; @@ -31,4 +65,13 @@ const LayerInfo &Animation2dInfo::get_layer(size_t idx) const { return this->layers.at(idx); } +const util::Vector4i &Animation2dInfo::get_max_bounds() const { + return this->max_bounds; +} + +const util::Vector2s Animation2dInfo::get_max_size() const { + return {this->max_bounds[0] + this->max_bounds[1], + this->max_bounds[2] + this->max_bounds[3]}; +} + } // namespace openage::renderer::resources diff --git a/libopenage/renderer/resources/animation/animation_info.h b/libopenage/renderer/resources/animation/animation_info.h index be53acb180..1e778d892e 100644 --- a/libopenage/renderer/resources/animation/animation_info.h +++ b/libopenage/renderer/resources/animation/animation_info.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once @@ -7,6 +7,7 @@ #include #include "renderer/resources/animation/layer_info.h" +#include "util/vector.h" namespace openage::renderer::resources { @@ -79,6 +80,25 @@ class Animation2dInfo { */ const LayerInfo &get_layer(size_t idx) const; + /** + * Get the maximum boundaries for all frames in the animation. + * + * This represents the maximum distance from the anchor point to the + * left, right, top, bottom edges of the frames (in pixels). + * + * @return Boundaries of the animation (in pixels): [left, right, top, bottom] + */ + const util::Vector4i &get_max_bounds() const; + + /** + * Get the maximum size for all frames in the animation. + * + * This represents the maximum width and height of the frames (in pixels). + * + * @return Size of the animation (in pixels): [width, height] + */ + const util::Vector2s get_max_size() const; + private: /** * Scaling factor of the animation across all layers at default zoom level. @@ -94,6 +114,14 @@ class Animation2dInfo { * Layer information. */ std::vector layers; + + /** + * Maximum boundaries for all frames in the animation. + * + * This represents the maximum distance from the anchor point to the + * left, right, top, bottom edges of the frames (in pixels). + */ + util::Vector4i max_bounds; }; } // namespace openage::renderer::resources diff --git a/libopenage/renderer/resources/mesh_data.h b/libopenage/renderer/resources/mesh_data.h index 1ad0d7a3f4..a39124cbe3 100644 --- a/libopenage/renderer/resources/mesh_data.h +++ b/libopenage/renderer/resources/mesh_data.h @@ -1,4 +1,4 @@ -// Copyright 2017-2023 the openage authors. See copying.md for legal info. +// Copyright 2017-2024 the openage authors. See copying.md for legal info. #pragma once @@ -29,6 +29,7 @@ enum class vertex_primitive_t { POINTS, LINES, LINE_STRIP, + LINE_LOOP, TRIANGLES, TRIANGLE_STRIP, TRIANGLE_FAN, diff --git a/libopenage/renderer/resources/parser/common.cpp b/libopenage/renderer/resources/parser/common.cpp index 30fa97118c..483c2cb0dd 100644 --- a/libopenage/renderer/resources/parser/common.cpp +++ b/libopenage/renderer/resources/parser/common.cpp @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #include "common.h" @@ -20,7 +20,13 @@ TextureData parse_texture(const std::vector &args) { texture.texture_id = std::stoul(args[1]); // Call substr() to get rid of the quotes - texture.path = args[2].substr(1, args[2].size() - 2); + // If the line ends in a carriage return, remove it as well + if (args[2][args[2].size() - 1] == '\r') { + texture.path = args[2].substr(1, args[2].size() - 3); + } + else { + texture.path = args[2].substr(1, args[2].size() - 2); + } return texture; } diff --git a/libopenage/renderer/resources/parser/parse_sprite.cpp b/libopenage/renderer/resources/parser/parse_sprite.cpp index 9ea4afac1c..0974f98342 100644 --- a/libopenage/renderer/resources/parser/parse_sprite.cpp +++ b/libopenage/renderer/resources/parser/parse_sprite.cpp @@ -195,8 +195,8 @@ Animation2dInfo parse_sprite_file(const util::Path &file, })}; for (auto line : lines) { - // Skip empty lines and comments - if (line.empty() || line.substr(0, 1) == "#") { + // Skip empty lines, lines with carriage returns, and comments + if (line.empty() || line.substr(0, 1) == "#" || line[0] == '\r') { continue; } std::vector args{util::split(line, ' ')}; diff --git a/libopenage/renderer/resources/parser/parse_sprite.h b/libopenage/renderer/resources/parser/parse_sprite.h index b2021190b3..5f989f5aa3 100644 --- a/libopenage/renderer/resources/parser/parse_sprite.h +++ b/libopenage/renderer/resources/parser/parse_sprite.h @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/resources/parser/parse_terrain.cpp b/libopenage/renderer/resources/parser/parse_terrain.cpp index 4330606091..26dc77f10b 100644 --- a/libopenage/renderer/resources/parser/parse_terrain.cpp +++ b/libopenage/renderer/resources/parser/parse_terrain.cpp @@ -200,8 +200,8 @@ TerrainInfo parse_terrain_file(const util::Path &file, })}; for (auto line : lines) { - // Skip empty lines and comments - if (line.empty() || line.substr(0, 1) == "#") { + // Skip empty lines, lines with carriage returns, and comments + if (line.empty() || line.substr(0, 1) == "#" || line[0] == '\r') { continue; } std::vector args{util::split(line, ' ')}; diff --git a/libopenage/renderer/resources/parser/parse_terrain.h b/libopenage/renderer/resources/parser/parse_terrain.h index 6aad27ce6e..ea5c582f38 100644 --- a/libopenage/renderer/resources/parser/parse_terrain.h +++ b/libopenage/renderer/resources/parser/parse_terrain.h @@ -1,4 +1,4 @@ -// Copyright 2023-2023 the openage authors. See copying.md for legal info. +// Copyright 2023-2024 the openage authors. See copying.md for legal info. #pragma once diff --git a/libopenage/renderer/resources/parser/parse_texture.cpp b/libopenage/renderer/resources/parser/parse_texture.cpp index 7da91211f4..c3d90fa30a 100644 --- a/libopenage/renderer/resources/parser/parse_texture.cpp +++ b/libopenage/renderer/resources/parser/parse_texture.cpp @@ -1,4 +1,4 @@ -// Copyright 2021-2023 the openage authors. See copying.md for legal info. +// Copyright 2021-2024 the openage authors. See copying.md for legal info. #include "parse_texture.h" @@ -56,6 +56,11 @@ std::string parse_imagefile(const std::vector &args) { // it should result in an error if wrongly used here. // Call substr() to get rid of the quotes + // If the line ends in a carriage return, remove it as well + if (args[1][args[1].size() - 1] == '\r') { + return args[1].substr(1, args[1].size() - 3); + } + return args[1].substr(1, args[1].size() - 2); } @@ -179,8 +184,8 @@ Texture2dInfo parse_texture_file(const util::Path &file) { })}; for (auto line : lines) { - // Skip empty lines and comments - if (line.empty() || line.substr(0, 1) == "#") { + // Skip empty lines, lines with carriage returns, and comments + if (line.empty() || line.substr(0, 1) == "#" || line[0] == '\r') { continue; } std::vector args{util::split(line, ' ')}; diff --git a/libopenage/renderer/resources/texture_subinfo.cpp b/libopenage/renderer/resources/texture_subinfo.cpp index d5c2168c4e..047becc76d 100644 --- a/libopenage/renderer/resources/texture_subinfo.cpp +++ b/libopenage/renderer/resources/texture_subinfo.cpp @@ -1,4 +1,4 @@ -// Copyright 2013-2023 the openage authors. See copying.md for legal info. +// Copyright 2013-2024 the openage authors. See copying.md for legal info. #include "texture_subinfo.h" @@ -38,7 +38,7 @@ const Eigen::Vector2i &Texture2dSubInfo::get_anchor_pos() const { return this->anchor_pos; } -const Eigen::Vector4f &Texture2dSubInfo::get_tile_params() const { +const Eigen::Vector4f &Texture2dSubInfo::get_subtex_coords() const { return this->tile_params; } diff --git a/libopenage/renderer/resources/texture_subinfo.h b/libopenage/renderer/resources/texture_subinfo.h index c4114f0fd3..70b1d039f3 100644 --- a/libopenage/renderer/resources/texture_subinfo.h +++ b/libopenage/renderer/resources/texture_subinfo.h @@ -53,7 +53,7 @@ class Texture2dSubInfo { const Eigen::Vector2 &get_size() const; /** - * Get the position of the subtexture anchor. + * Get the position of the subtexture anchor (origin == top left). * * @return Anchor coordinates as 2-dimensional Eigen vector: (x, y) */ @@ -68,10 +68,10 @@ class Texture2dSubInfo { * * @return Tile parameters as 4-dimensional Eigen vector: (x, y, width, height) */ - const Eigen::Vector4f &get_tile_params() const; + const Eigen::Vector4f &get_subtex_coords() const; /** - * Get the anchor parameters of the subtexture center. Used in the model matrix + * Get the anchor parameters of the subtexture center. Used in the shader * to calculate the offset position for displaying the subtexture inside * the OpenGL viewport. * diff --git a/libopenage/renderer/stages/world/object.cpp b/libopenage/renderer/stages/world/object.cpp index 3b9edd6d3d..d4629f866b 100644 --- a/libopenage/renderer/stages/world/object.cpp +++ b/libopenage/renderer/stages/world/object.cpp @@ -10,7 +10,7 @@ #include -#include "renderer/camera/camera.h" +#include "renderer/camera/frustum_2d.h" #include "renderer/definitions.h" #include "renderer/resources/animation/angle_info.h" #include "renderer/resources/animation/animation_info.h" @@ -143,7 +143,7 @@ void WorldObject::update_uniforms(const time::time_t &time) { layer_unifs->update(this->tex, texture); // Subtexture coordinates.inside texture - auto coords = tex_info->get_subtex_info(subtex_idx).get_tile_params(); + auto coords = tex_info->get_subtex_info(subtex_idx).get_subtex_coords(); layer_unifs->update(this->tile_params, coords); // Animation scale factor @@ -224,4 +224,15 @@ void WorldObject::set_uniforms(std::vectorlayer_uniforms = std::move(uniforms); } +bool WorldObject::is_visible(const camera::Frustum2d &frustum, + const time::time_t &time) { + static const Eigen::Matrix4f model_matrix = this->get_model_matrix(); + Eigen::Vector3f current_pos = this->position.get(time).to_world_space(); + auto animation_info = this->animation_info.get(time); + return frustum.in_frustum(current_pos, + model_matrix, + animation_info->get_scalefactor(), + animation_info->get_max_bounds()); +} + } // namespace openage::renderer::world diff --git a/libopenage/renderer/stages/world/object.h b/libopenage/renderer/stages/world/object.h index c5e0aa8197..1302733954 100644 --- a/libopenage/renderer/stages/world/object.h +++ b/libopenage/renderer/stages/world/object.h @@ -20,7 +20,7 @@ namespace openage::renderer { class UniformInput; namespace camera { -class Camera; +class Frustum2d; } namespace resources { @@ -133,6 +133,17 @@ class WorldObject { */ void set_uniforms(std::vector> &&uniforms); + /** + * Check whether the object is visible in the camera view. + * + * @param frustum Camera frustum for culling. + * @param time Current simulation time. + * + * @return true if the object is visible, else false. + */ + bool is_visible(const camera::Frustum2d &frustum, + const time::time_t &time); + /** * Shader uniform IDs for setting uniform values. */ diff --git a/libopenage/renderer/stages/world/render_stage.cpp b/libopenage/renderer/stages/world/render_stage.cpp index 05827eb645..0434f3cf07 100644 --- a/libopenage/renderer/stages/world/render_stage.cpp +++ b/libopenage/renderer/stages/world/render_stage.cpp @@ -3,6 +3,7 @@ #include "render_stage.h" #include "renderer/camera/camera.h" +#include "renderer/camera/frustum_3d.h" #include "renderer/opengl/context.h" #include "renderer/render_pass.h" #include "renderer/render_target.h" @@ -18,6 +19,8 @@ namespace openage::renderer::world { +bool WorldRenderStage::ENABLE_FRUSTUM_CULLING = false; + WorldRenderStage::WorldRenderStage(const std::shared_ptr &window, const std::shared_ptr &renderer, const std::shared_ptr &camera, @@ -58,19 +61,26 @@ void WorldRenderStage::add_render_entity(const std::shared_ptrmutex}; auto current_time = this->clock->get_real_time(); + auto &camera_frustum = this->camera->get_frustum_2d(); for (auto &obj : this->render_objects) { obj->fetch_updates(current_time); + + if (WorldRenderStage::ENABLE_FRUSTUM_CULLING + and not obj->is_visible(camera_frustum, current_time)) { + continue; + } + if (obj->is_changed()) { if (obj->requires_renderable()) { auto layer_positions = obj->get_layer_positions(current_time); - Eigen::Matrix4f model_m = obj->get_model_matrix(); + static const Eigen::Matrix4f model_matrix = obj->get_model_matrix(); std::vector> transform_unifs; for (auto layer_pos : layer_positions) { // Set uniforms that don't change or are not changed often auto layer_unifs = this->display_shader->new_uniform_input( "model", - model_m, + model_matrix, "flip_x", false, "flip_y", diff --git a/libopenage/renderer/stages/world/render_stage.h b/libopenage/renderer/stages/world/render_stage.h index e22399180b..e6ae39c094 100644 --- a/libopenage/renderer/stages/world/render_stage.h +++ b/libopenage/renderer/stages/world/render_stage.h @@ -39,6 +39,11 @@ class WorldObject; */ class WorldRenderStage { public: + /** + * Enable or disable frustum culling (default = false). + */ + static bool ENABLE_FRUSTUM_CULLING; + /** * Create a new render stage for the game world. * @@ -95,9 +100,7 @@ class WorldRenderStage { * @param height Height of the FBO. * @param shaderdir Directory containg the shader source files. */ - void initialize_render_pass(size_t width, - size_t height, - const util::Path &shaderdir); + void initialize_render_pass(size_t width, size_t height, const util::Path &shaderdir); /** * Fetch the uniform IDs for the uniforms of the world shader from OpenGL