diff --git a/include/nodec_rendering/components/camera.hpp b/include/nodec_rendering/components/camera.hpp index 76581e0..e451157 100644 --- a/include/nodec_rendering/components/camera.hpp +++ b/include/nodec_rendering/components/camera.hpp @@ -4,6 +4,9 @@ #include #include +#include +#include + namespace nodec_rendering { namespace components { @@ -16,6 +19,15 @@ struct Camera { Orthographic }; + //! Render type for camera stacking (Unity URP style). + enum class RenderType : std::uint8_t { + //! Base camera clears color buffer and renders first. + Base, + + //! Overlay camera renders on top without clearing color buffer. + Overlay + }; + float far_clip_plane{100.0f}; float near_clip_plane{0.01f}; @@ -26,6 +38,23 @@ struct Camera { nodec::Matrix4x4f world2camera_matrix; nodec::Matrix4x4f projection_matrix; + + // --- Camera Stack fields --- + + //! Determines if this camera is a base or overlay camera. + RenderType render_type{RenderType::Base}; + + //! Rendering priority within the camera group. Lower values render first. + std::int32_t priority{0}; + + //! Bitmask for layer-based culling. Entities with matching RenderLayer are rendered. + std::uint32_t culling_mask{0xFFFFFFFF}; + + //! Whether to clear depth buffer before rendering (useful for overlay cameras). + bool clear_depth{true}; + + //! Camera group name. Cameras in the same group are stacked together. + std::string camera_group{"default"}; }; enum class CameraDirtyFlag { diff --git a/include/nodec_rendering/components/render_layer.hpp b/include/nodec_rendering/components/render_layer.hpp new file mode 100644 index 0000000..aeb5fcd --- /dev/null +++ b/include/nodec_rendering/components/render_layer.hpp @@ -0,0 +1,35 @@ +#ifndef NODEC_RENDERING__COMPONENTS__RENDER_LAYER_HPP_ +#define NODEC_RENDERING__COMPONENTS__RENDER_LAYER_HPP_ + +#include + +namespace nodec_rendering { +namespace components { + +/** + * @brief Tag component for layer-based rendering. + * + * Entities with RenderLayer are only rendered by cameras whose + * culling_mask includes bit N. This enables separation of UI from + * scene objects, multiple render passes, etc. + * + * @tparam N Layer index (0-31). + * + * Usage: + * - Entities without any RenderLayer are treated as Layer 0. + * - Camera with culling_mask=0xFFFFFFFF renders all layers (default). + * - Camera with culling_mask=0x00000001 renders only Layer 0. + * - Camera with culling_mask=0x00000002 renders only Layer 1. + */ +template +struct RenderLayer { + static_assert(N < 32, "Layer index must be 0-31"); + + static constexpr std::uint32_t index = N; + static constexpr std::uint32_t mask = 1u << N; +}; + +} // namespace components +} // namespace nodec_rendering + +#endif diff --git a/include/nodec_rendering/utilities/camera_utility.hpp b/include/nodec_rendering/utilities/camera_utility.hpp new file mode 100644 index 0000000..1ab4428 --- /dev/null +++ b/include/nodec_rendering/utilities/camera_utility.hpp @@ -0,0 +1,84 @@ +#ifndef NODEC_RENDERING__UTILITIES__CAMERA_UTILITY_HPP_ +#define NODEC_RENDERING__UTILITIES__CAMERA_UTILITY_HPP_ + +#include + +#include +#include +#include +#include + +#include "../components/camera.hpp" + +namespace nodec_rendering { +namespace utilities { + +class CameraUtility { +public: + /** + * @brief Generates a ray from screen coordinates to world space. + * + * @param screen_pos Screen coordinates in pixels (top-left origin). + * @param screen_size Screen size in pixels. + * @param camera Camera component with projection_matrix and world2camera_matrix. + * @param camera_world_position World position of the camera. + * @return std::pair {ray_origin, ray_direction} + */ + static std::pair screen_to_world_ray( + const nodec::Vector2f &screen_pos, + const nodec::Vector2f &screen_size, + const components::Camera &camera, + const nodec::Vector3f &camera_world_position) { + // Convert screen coordinates to NDC (-1 to 1) + float ndc_x = (2.0f * screen_pos.x / screen_size.x) - 1.0f; + float ndc_y = 1.0f - (2.0f * screen_pos.y / screen_size.y); // Flip Y + + // Ray direction in NDC space (pointing into the screen) + nodec::Vector3f ray_ndc_near(ndc_x, ndc_y, -1.0f); + nodec::Vector3f ray_ndc_far(ndc_x, ndc_y, 1.0f); + + // Inverse projection matrix + auto inv_proj = nodec::math::inv(camera.projection_matrix); + + // Inverse view matrix (world2camera -> camera2world) + auto inv_view = nodec::math::inv(camera.world2camera_matrix); + + // Transform from NDC to view space + auto ray_view_near = transform_point(inv_proj, ray_ndc_near); + auto ray_view_far = transform_point(inv_proj, ray_ndc_far); + + // Transform from view space to world space + auto ray_world_near = transform_point(inv_view, ray_view_near); + auto ray_world_far = transform_point(inv_view, ray_view_far); + + // Calculate ray direction + nodec::Vector3f direction = ray_world_far - ray_world_near; + float length = std::sqrt(direction.x * direction.x + direction.y * direction.y + direction.z * direction.z); + if (length > 0.0f) { + direction = direction / length; + } + + return {camera_world_position, direction}; + } + +private: + /** + * @brief Transform a 3D point by a 4x4 matrix with perspective division. + * + * Treats the input as a homogeneous point (x, y, z, 1) and performs + * perspective division by the resulting w component. + */ + static nodec::Vector3f transform_point(const nodec::Matrix4x4f &m, const nodec::Vector3f &v) { + float w = m.m41 * v.x + m.m42 * v.y + m.m43 * v.z + m.m44; + if (w == 0.0f) w = 1.0f; + return { + (m.m11 * v.x + m.m12 * v.y + m.m13 * v.z + m.m14) / w, + (m.m21 * v.x + m.m22 * v.y + m.m23 * v.z + m.m24) / w, + (m.m31 * v.x + m.m32 * v.y + m.m33 * v.z + m.m34) / w}; + } +}; + +} // namespace utilities +} // namespace nodec_rendering + +#endif diff --git a/serialization/nodec_rendering/serialization/components/camera.hpp b/serialization/nodec_rendering/serialization/components/camera.hpp index 403ee12..e845397 100644 --- a/serialization/nodec_rendering/serialization/components/camera.hpp +++ b/serialization/nodec_rendering/serialization/components/camera.hpp @@ -19,7 +19,12 @@ class SerializableCamera : public nodec_scene_serialization::BaseSerializableCom near_clip_plane(other.near_clip_plane), projection(other.projection), fov_angle(other.fov_angle), - ortho_width(other.ortho_width) {} + ortho_width(other.ortho_width), + render_type(other.render_type), + priority(other.priority), + culling_mask(other.culling_mask), + clear_depth(other.clear_depth), + camera_group(other.camera_group) {} operator Camera() const noexcept { Camera value; @@ -28,6 +33,11 @@ class SerializableCamera : public nodec_scene_serialization::BaseSerializableCom value.projection = projection; value.fov_angle = fov_angle; value.ortho_width = ortho_width; + value.render_type = render_type; + value.priority = priority; + value.culling_mask = culling_mask; + value.clear_depth = clear_depth; + value.camera_group = camera_group; return value; } @@ -39,6 +49,13 @@ class SerializableCamera : public nodec_scene_serialization::BaseSerializableCom Camera::Projection projection{Camera::Projection::Perspective}; + // Camera Stack fields + Camera::RenderType render_type{Camera::RenderType::Base}; + std::int32_t priority{0}; + std::uint32_t culling_mask{0xFFFFFFFF}; + bool clear_depth{true}; + std::string camera_group{"default"}; + template void serialize(Archive &archive) { archive(cereal::make_nvp("far_clip_plane", far_clip_plane)); @@ -46,9 +63,28 @@ class SerializableCamera : public nodec_scene_serialization::BaseSerializableCom archive(cereal::make_nvp("projection", projection)); archive(cereal::make_nvp("fov_angle", fov_angle)); archive(cereal::make_nvp("ortho_width", ortho_width)); + archive(cereal::make_nvp("render_type", render_type)); + archive(cereal::make_nvp("priority", priority)); + archive(cereal::make_nvp("culling_mask", culling_mask)); + archive(cereal::make_nvp("clear_depth", clear_depth)); + archive(cereal::make_nvp("camera_group", camera_group)); } }; +template +void serialize(Archive &archive, Camera &camera) { + archive(cereal::make_nvp("far_clip_plane", camera.far_clip_plane)); + archive(cereal::make_nvp("near_clip_plane", camera.near_clip_plane)); + archive(cereal::make_nvp("projection", camera.projection)); + archive(cereal::make_nvp("fov_angle", camera.fov_angle)); + archive(cereal::make_nvp("ortho_width", camera.ortho_width)); + archive(cereal::make_nvp("render_type", camera.render_type)); + archive(cereal::make_nvp("priority", camera.priority)); + archive(cereal::make_nvp("culling_mask", camera.culling_mask)); + archive(cereal::make_nvp("clear_depth", camera.clear_depth)); + archive(cereal::make_nvp("camera_group", camera.camera_group)); +} + } // namespace components } // namespace nodec_rendering diff --git a/serialization/nodec_rendering/serialization/components/render_layer.hpp b/serialization/nodec_rendering/serialization/components/render_layer.hpp new file mode 100644 index 0000000..e6704d4 --- /dev/null +++ b/serialization/nodec_rendering/serialization/components/render_layer.hpp @@ -0,0 +1,73 @@ +#ifndef NODEC_RENDERING__SERIALIZATION__COMPONENTS__RENDER_LAYER_HPP_ +#define NODEC_RENDERING__SERIALIZATION__COMPONENTS__RENDER_LAYER_HPP_ + +#include +#include + +namespace nodec_rendering { +namespace components { + +/** + * @brief Serializable version of RenderLayer. + * + * Since RenderLayer is a tag component with no data, serialization + * is trivial. The type information itself carries the layer index. + */ +template +struct SerializableRenderLayer : public nodec_scene_serialization::BaseSerializableComponent { + static_assert(N < 32, "Layer index must be 0-31"); + + SerializableRenderLayer() + : BaseSerializableComponent(this) {} + + SerializableRenderLayer(const RenderLayer &) + : BaseSerializableComponent(this) {} + + operator RenderLayer() const noexcept { + return {}; + } + + template + void serialize(Archive &) { + // No data to serialize - type itself carries the layer index + } +}; + +} // namespace components +} // namespace nodec_rendering + +// Register all 32 layers with cereal polymorphic serialization +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<0>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<1>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<2>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<3>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<4>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<5>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<6>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<7>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<8>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<9>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<10>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<11>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<12>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<13>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<14>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<15>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<16>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<17>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<18>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<19>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<20>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<21>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<22>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<23>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<24>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<25>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<26>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<27>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<28>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<29>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<30>) +NODEC_SCENE_REGISTER_SERIALIZABLE_COMPONENT(nodec_rendering::components::SerializableRenderLayer<31>) + +#endif