Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions include/nodec_rendering/components/camera.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
#include <nodec/flags.hpp>
#include <nodec/matrix4x4.hpp>

#include <cstdint>
#include <string>

namespace nodec_rendering {
namespace components {

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

Expand All @@ -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 {
Expand Down
35 changes: 35 additions & 0 deletions include/nodec_rendering/components/render_layer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#ifndef NODEC_RENDERING__COMPONENTS__RENDER_LAYER_HPP_
#define NODEC_RENDERING__COMPONENTS__RENDER_LAYER_HPP_

#include <cstdint>

namespace nodec_rendering {
namespace components {

/**
* @brief Tag component for layer-based rendering.
*
* Entities with RenderLayer<N> 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<std::uint32_t N>
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
84 changes: 84 additions & 0 deletions include/nodec_rendering/utilities/camera_utility.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#ifndef NODEC_RENDERING__UTILITIES__CAMERA_UTILITY_HPP_
#define NODEC_RENDERING__UTILITIES__CAMERA_UTILITY_HPP_

#include <utility>

#include <nodec/math/math.hpp>
#include <nodec/matrix4x4.hpp>
#include <nodec/vector2.hpp>
#include <nodec/vector3.hpp>

#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<nodec::Vector3f, nodec::Vector3f> {ray_origin, ray_direction}
*/
static std::pair<nodec::Vector3f, nodec::Vector3f> 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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

Expand All @@ -39,16 +49,42 @@ 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<class Archive>
void serialize(Archive &archive) {
archive(cereal::make_nvp("far_clip_plane", far_clip_plane));
archive(cereal::make_nvp("near_clip_plane", near_clip_plane));
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<class Archive>
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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#ifndef NODEC_RENDERING__SERIALIZATION__COMPONENTS__RENDER_LAYER_HPP_
#define NODEC_RENDERING__SERIALIZATION__COMPONENTS__RENDER_LAYER_HPP_

#include <nodec_rendering/components/render_layer.hpp>
#include <nodec_scene_serialization/serializable_component.hpp>

namespace nodec_rendering {
namespace components {

/**
* @brief Serializable version of RenderLayer<N>.
*
* Since RenderLayer is a tag component with no data, serialization
* is trivial. The type information itself carries the layer index.
*/
template<std::uint32_t N>
struct SerializableRenderLayer : public nodec_scene_serialization::BaseSerializableComponent {
static_assert(N < 32, "Layer index must be 0-31");

SerializableRenderLayer()
: BaseSerializableComponent(this) {}

SerializableRenderLayer(const RenderLayer<N> &)
: BaseSerializableComponent(this) {}

operator RenderLayer<N>() const noexcept {
return {};
}

template<class Archive>
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