diff --git a/engine/include/XCEngine/Components/CameraComponent.h b/engine/include/XCEngine/Components/CameraComponent.h index 4d58ff23..b60ee33b 100644 --- a/engine/include/XCEngine/Components/CameraComponent.h +++ b/engine/include/XCEngine/Components/CameraComponent.h @@ -46,6 +46,9 @@ public: CameraClearMode GetClearMode() const { return m_clearMode; } void SetClearMode(CameraClearMode value) { m_clearMode = value; } + uint32_t GetCullingMask() const { return m_cullingMask; } + void SetCullingMask(uint32_t value) { m_cullingMask = value; } + const Math::Color& GetClearColor() const { return m_clearColor; } void SetClearColor(const Math::Color& value) { m_clearColor = value; } @@ -61,6 +64,7 @@ private: float m_depth = 0.0f; bool m_primary = true; CameraClearMode m_clearMode = CameraClearMode::Auto; + uint32_t m_cullingMask = 0xFFFFFFFFu; Math::Color m_clearColor = Math::Color(0.192f, 0.302f, 0.475f, 1.0f); }; diff --git a/engine/include/XCEngine/Components/GameObject.h b/engine/include/XCEngine/Components/GameObject.h index 67627021..3f9c5b6d 100644 --- a/engine/include/XCEngine/Components/GameObject.h +++ b/engine/include/XCEngine/Components/GameObject.h @@ -30,6 +30,8 @@ public: uint64_t GetUUID() const { return m_uuid; } const std::string& GetName() const { return m_name; } void SetName(const std::string& name) { m_name = name; } + uint8_t GetLayer() const { return m_layer; } + void SetLayer(uint8_t layer) { m_layer = std::min(layer, 31u); } void DetachFromParent(); @@ -217,6 +219,7 @@ private: std::string m_name; bool m_activeSelf = true; bool m_started = false; + uint8_t m_layer = 0; GameObject* m_parent = nullptr; std::vector m_children; diff --git a/engine/include/XCEngine/Rendering/RenderSceneExtractor.h b/engine/include/XCEngine/Rendering/RenderSceneExtractor.h index 21812d6e..e5a30aef 100644 --- a/engine/include/XCEngine/Rendering/RenderSceneExtractor.h +++ b/engine/include/XCEngine/Rendering/RenderSceneExtractor.h @@ -67,6 +67,7 @@ private: void ExtractVisibleItems( Components::GameObject* gameObject, const Math::Vector3& cameraPosition, + uint32_t cullingMask, std::vector& visibleItems) const; }; diff --git a/engine/src/Components/CameraComponent.cpp b/engine/src/Components/CameraComponent.cpp index eab10883..abedcdc1 100644 --- a/engine/src/Components/CameraComponent.cpp +++ b/engine/src/Components/CameraComponent.cpp @@ -34,6 +34,7 @@ void CameraComponent::Serialize(std::ostream& os) const { os << "depth=" << m_depth << ";"; os << "primary=" << (m_primary ? 1 : 0) << ";"; os << "clearMode=" << static_cast(m_clearMode) << ";"; + os << "cullingMask=" << m_cullingMask << ";"; os << "clearColor=" << m_clearColor.r << "," << m_clearColor.g << "," << m_clearColor.b << "," << m_clearColor.a << ";"; } @@ -68,6 +69,8 @@ void CameraComponent::Deserialize(std::istream& is) { m_primary = (std::stoi(value) != 0); } else if (key == "clearMode") { m_clearMode = static_cast(std::stoi(value)); + } else if (key == "cullingMask") { + m_cullingMask = static_cast(std::stoul(value)); } else if (key == "clearColor") { std::replace(value.begin(), value.end(), ',', ' '); std::istringstream ss(value); diff --git a/engine/src/Components/GameObject.cpp b/engine/src/Components/GameObject.cpp index 19b0494d..ad4ead47 100644 --- a/engine/src/Components/GameObject.cpp +++ b/engine/src/Components/GameObject.cpp @@ -317,6 +317,7 @@ void GameObject::Destroy() { void GameObject::Serialize(std::ostream& os) const { os << "name=" << m_name << ";"; os << "active=" << (m_activeSelf ? "1" : "0") << ";"; + os << "layer=" << static_cast(m_layer) << ";"; os << "id=" << m_id << ";"; os << "uuid=" << m_uuid << ";"; os << "transform="; @@ -346,6 +347,11 @@ void GameObject::Deserialize(std::istream& is) { is.get(val); m_activeSelf = (val == '1'); if (is.peek() == ';') is.get(); + } else if (strcmp(key, "layer") == 0) { + uint32_t layer = 0; + is >> layer; + SetLayer(static_cast(layer)); + if (is.peek() == ';') is.get(); } else if (strcmp(key, "id") == 0) { is >> m_id; if (is.peek() == ';') is.get(); diff --git a/engine/src/Rendering/RenderSceneExtractor.cpp b/engine/src/Rendering/RenderSceneExtractor.cpp index 06be0c99..c7804d5b 100644 --- a/engine/src/Rendering/RenderSceneExtractor.cpp +++ b/engine/src/Rendering/RenderSceneExtractor.cpp @@ -64,10 +64,11 @@ RenderSceneData RenderSceneExtractor::Extract( sceneData.cameraData = BuildCameraData(*sceneData.camera, viewportWidth, viewportHeight); const Math::Vector3 cameraPosition = sceneData.cameraData.worldPosition; + const uint32_t cullingMask = sceneData.camera->GetCullingMask(); const std::vector rootGameObjects = scene.GetRootGameObjects(); for (Components::GameObject* rootGameObject : rootGameObjects) { - ExtractVisibleItems(rootGameObject, cameraPosition, sceneData.visibleItems); + ExtractVisibleItems(rootGameObject, cameraPosition, cullingMask, sceneData.visibleItems); } std::stable_sort( @@ -92,10 +93,11 @@ RenderSceneData RenderSceneExtractor::ExtractForCamera( sceneData.camera = &camera; sceneData.cameraData = BuildCameraData(camera, viewportWidth, viewportHeight); const Math::Vector3 cameraPosition = sceneData.cameraData.worldPosition; + const uint32_t cullingMask = camera.GetCullingMask(); const std::vector rootGameObjects = scene.GetRootGameObjects(); for (Components::GameObject* rootGameObject : rootGameObjects) { - ExtractVisibleItems(rootGameObject, cameraPosition, sceneData.visibleItems); + ExtractVisibleItems(rootGameObject, cameraPosition, cullingMask, sceneData.visibleItems); } std::stable_sort( @@ -224,15 +226,20 @@ void RenderSceneExtractor::ExtractLighting( void RenderSceneExtractor::ExtractVisibleItems( Components::GameObject* gameObject, const Math::Vector3& cameraPosition, + uint32_t cullingMask, std::vector& visibleItems) const { if (gameObject == nullptr || !gameObject->IsActiveInHierarchy()) { return; } + const uint32_t gameObjectLayerMask = 1u << gameObject->GetLayer(); + const bool isVisibleInCameraMask = (cullingMask & gameObjectLayerMask) != 0; + auto* meshFilter = gameObject->GetComponent(); auto* meshRenderer = gameObject->GetComponent(); if (meshFilter != nullptr && meshRenderer != nullptr && + isVisibleInCameraMask && meshFilter->IsEnabled() && meshRenderer->IsEnabled()) { Resources::Mesh* mesh = meshFilter->GetMesh(); @@ -279,7 +286,7 @@ void RenderSceneExtractor::ExtractVisibleItems( } for (Components::GameObject* child : gameObject->GetChildren()) { - ExtractVisibleItems(child, cameraPosition, visibleItems); + ExtractVisibleItems(child, cameraPosition, cullingMask, visibleItems); } } diff --git a/tests/Components/test_camera_light_component.cpp b/tests/Components/test_camera_light_component.cpp index 79798eab..2c0d7221 100644 --- a/tests/Components/test_camera_light_component.cpp +++ b/tests/Components/test_camera_light_component.cpp @@ -19,6 +19,7 @@ TEST(CameraComponent_Test, DefaultValues) { EXPECT_FLOAT_EQ(camera.GetFarClipPlane(), 1000.0f); EXPECT_TRUE(camera.IsPrimary()); EXPECT_EQ(camera.GetClearMode(), CameraClearMode::Auto); + EXPECT_EQ(camera.GetCullingMask(), 0xFFFFFFFFu); } TEST(CameraComponent_Test, SetterClamping) { @@ -38,6 +39,7 @@ TEST(CameraComponent_Test, SetterClamping) { TEST(CameraComponent_Test, SerializeRoundTripPreservesClearMode) { CameraComponent source; source.SetClearMode(CameraClearMode::DepthOnly); + source.SetCullingMask(0x0000000Fu); std::stringstream stream; source.Serialize(stream); @@ -46,6 +48,7 @@ TEST(CameraComponent_Test, SerializeRoundTripPreservesClearMode) { target.Deserialize(stream); EXPECT_EQ(target.GetClearMode(), CameraClearMode::DepthOnly); + EXPECT_EQ(target.GetCullingMask(), 0x0000000Fu); } TEST(LightComponent_Test, DefaultValues) { diff --git a/tests/Components/test_game_object.cpp b/tests/Components/test_game_object.cpp index 60d6c33f..b41ba02f 100644 --- a/tests/Components/test_game_object.cpp +++ b/tests/Components/test_game_object.cpp @@ -4,6 +4,8 @@ #include #include +#include + using namespace XCEngine::Components; using namespace XCEngine::Math; @@ -64,6 +66,20 @@ TEST(GameObject_Test, NamedConstructor) { EXPECT_EQ(go.GetName(), "TestObject"); } +TEST(GameObject_Test, Layer_GetSetAndSerializeRoundTrip) { + GameObject source("LayeredObject"); + source.SetLayer(7); + + std::stringstream stream; + source.Serialize(stream); + + GameObject target; + target.Deserialize(stream); + + EXPECT_EQ(source.GetLayer(), 7u); + EXPECT_EQ(target.GetLayer(), 7u); +} + TEST(GameObject_Test, AddComponent_Single) { GameObject go; diff --git a/tests/Rendering/unit/test_render_scene_extractor.cpp b/tests/Rendering/unit/test_render_scene_extractor.cpp index 2a9dcb4d..d4b8cac6 100644 --- a/tests/Rendering/unit/test_render_scene_extractor.cpp +++ b/tests/Rendering/unit/test_render_scene_extractor.cpp @@ -180,6 +180,41 @@ TEST(RenderSceneExtractor_Test, ExtractsBrightestDirectionalLightAsMainLight) { mainLightObject->GetTransform()->GetForward().Normalized() * -1.0f); } +TEST(RenderSceneExtractor_Test, FiltersVisibleItemsByCameraCullingMask) { + Scene scene("CullingMaskScene"); + + GameObject* cameraObject = scene.CreateGameObject("Camera"); + auto* camera = cameraObject->AddComponent(); + camera->SetPrimary(true); + camera->SetCullingMask(1u << 5); + + GameObject* visibleObject = scene.CreateGameObject("VisibleLayer5"); + visibleObject->SetLayer(5); + auto* visibleMeshFilter = visibleObject->AddComponent(); + visibleObject->AddComponent(); + Mesh* visibleMesh = CreateTestMesh("Meshes/layer5.mesh"); + visibleMeshFilter->SetMesh(visibleMesh); + + GameObject* hiddenObject = scene.CreateGameObject("HiddenLayer0"); + hiddenObject->SetLayer(0); + auto* hiddenMeshFilter = hiddenObject->AddComponent(); + hiddenObject->AddComponent(); + Mesh* hiddenMesh = CreateTestMesh("Meshes/layer0.mesh"); + hiddenMeshFilter->SetMesh(hiddenMesh); + + RenderSceneExtractor extractor; + const RenderSceneData sceneData = extractor.Extract(scene, nullptr, 800, 600); + + ASSERT_EQ(sceneData.visibleItems.size(), 1u); + EXPECT_EQ(sceneData.visibleItems[0].gameObject, visibleObject); + EXPECT_EQ(sceneData.visibleItems[0].mesh, visibleMesh); + + visibleMeshFilter->ClearMesh(); + hiddenMeshFilter->ClearMesh(); + delete visibleMesh; + delete hiddenMesh; +} + TEST(RenderSceneExtractor_Test, ExtractsSectionLevelVisibleItemsAndSortsByRenderQueue) { Scene scene("SectionScene");