diff --git a/engine/src/Components/MeshRendererComponent.cpp b/engine/src/Components/MeshRendererComponent.cpp index 83a5479d..4c04ad62 100644 --- a/engine/src/Components/MeshRendererComponent.cpp +++ b/engine/src/Components/MeshRendererComponent.cpp @@ -15,14 +15,20 @@ std::string ToStdString(const Containers::String& value) { std::vector SplitMaterialPaths(const std::string& value) { std::vector paths; - std::stringstream stream(value); - std::string item; - while (std::getline(stream, item, '|')) { - paths.push_back(item); + if (value.empty()) { + return paths; } - if (value.empty()) { - paths.clear(); + size_t start = 0; + while (true) { + const size_t separator = value.find('|', start); + if (separator == std::string::npos) { + paths.push_back(value.substr(start)); + break; + } + + paths.push_back(value.substr(start, separator - start)); + start = separator + 1; } return paths; diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index 12ecb962..a8a88db9 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -221,6 +221,37 @@ bool HasNativeComponent(Components::GameObject* gameObject, ManagedComponentKind return false; } +Components::Component* AddOrGetNativeComponent(Components::GameObject* gameObject, ManagedComponentKind componentKind) { + if (!gameObject) { + return nullptr; + } + + switch (componentKind) { + case ManagedComponentKind::Transform: + return gameObject->GetTransform(); + case ManagedComponentKind::Camera: + return gameObject->GetComponent() + ? static_cast(gameObject->GetComponent()) + : static_cast(gameObject->AddComponent()); + case ManagedComponentKind::Light: + return gameObject->GetComponent() + ? static_cast(gameObject->GetComponent()) + : static_cast(gameObject->AddComponent()); + case ManagedComponentKind::MeshFilter: + return gameObject->GetComponent() + ? static_cast(gameObject->GetComponent()) + : static_cast(gameObject->AddComponent()); + case ManagedComponentKind::MeshRenderer: + return gameObject->GetComponent() + ? static_cast(gameObject->GetComponent()) + : static_cast(gameObject->AddComponent()); + case ManagedComponentKind::Unknown: + return nullptr; + } + + return nullptr; +} + Components::CameraComponent* FindCameraComponent(uint64_t gameObjectUUID) { Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID); return gameObject ? gameObject->GetComponent() : nullptr; @@ -318,6 +349,11 @@ uint64_t InternalCall_GameObject_GetComponent(uint64_t gameObjectUUID, MonoRefle return gameObjectUUID; } +uint64_t InternalCall_GameObject_AddComponent(uint64_t gameObjectUUID, MonoReflectionType* componentType) { + Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID); + return AddOrGetNativeComponent(gameObject, ResolveManagedComponentKind(componentType)) ? gameObjectUUID : 0; +} + mono_bool InternalCall_Behaviour_GetEnabled(uint64_t scriptComponentUUID) { ScriptComponent* component = FindScriptComponentByUUID(scriptComponentUUID); return (component && component->IsEnabled()) ? 1 : 0; @@ -955,6 +991,7 @@ void RegisterInternalCalls() { mono_add_internal_call("XCEngine.InternalCalls::GameObject_SetActive", reinterpret_cast(&InternalCall_GameObject_SetActive)); mono_add_internal_call("XCEngine.InternalCalls::GameObject_HasComponent", reinterpret_cast(&InternalCall_GameObject_HasComponent)); mono_add_internal_call("XCEngine.InternalCalls::GameObject_GetComponent", reinterpret_cast(&InternalCall_GameObject_GetComponent)); + mono_add_internal_call("XCEngine.InternalCalls::GameObject_AddComponent", reinterpret_cast(&InternalCall_GameObject_AddComponent)); mono_add_internal_call("XCEngine.InternalCalls::Behaviour_GetEnabled", reinterpret_cast(&InternalCall_Behaviour_GetEnabled)); mono_add_internal_call("XCEngine.InternalCalls::Behaviour_SetEnabled", reinterpret_cast(&InternalCall_Behaviour_SetEnabled)); mono_add_internal_call("XCEngine.InternalCalls::Transform_GetLocalPosition", reinterpret_cast(&InternalCall_Transform_GetLocalPosition)); diff --git a/managed/CMakeLists.txt b/managed/CMakeLists.txt index b70fbff8..54933dd5 100644 --- a/managed/CMakeLists.txt +++ b/managed/CMakeLists.txt @@ -71,9 +71,11 @@ set(XCENGINE_SCRIPT_CORE_SOURCES set(XCENGINE_GAME_SCRIPT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/BuiltinComponentProbe.cs + ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/AddComponentProbe.cs ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/HierarchyProbe.cs ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/LifecycleProbe.cs ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/MeshComponentProbe.cs + ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/MeshRendererEdgeCaseProbe.cs ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/TransformConversionProbe.cs ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/TransformMotionProbe.cs ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/TransformOrientationProbe.cs @@ -134,3 +136,4 @@ add_custom_target( ${XCENGINE_GAME_SCRIPTS_DLL} ${XCENGINE_MANAGED_OUTPUT_DIR}/mscorlib.dll ) + diff --git a/managed/GameScripts/AddComponentProbe.cs b/managed/GameScripts/AddComponentProbe.cs new file mode 100644 index 00000000..ff3680b7 --- /dev/null +++ b/managed/GameScripts/AddComponentProbe.cs @@ -0,0 +1,87 @@ +using XCEngine; + +namespace Gameplay +{ + public sealed class AddComponentProbe : MonoBehaviour + { + public bool InitialHasCamera; + public bool InitialHasLight; + public bool InitialHasMeshFilter; + public bool InitialHasMeshRenderer; + public bool AddedTransform; + public bool AddedCamera; + public bool AddedLight; + public bool AddedMeshFilter; + public bool AddedMeshRenderer; + public bool HasCameraAfterAdd; + public bool HasLightAfterAdd; + public bool HasMeshFilterAfterAdd; + public bool HasMeshRendererAfterAdd; + public bool CameraLookupSucceeded; + public bool LightLookupSucceeded; + public bool MeshFilterLookupSucceeded; + public bool MeshRendererLookupSucceeded; + public float ObservedCameraFieldOfView; + public float ObservedLightIntensity; + public string ObservedMeshPath = string.Empty; + public int ObservedMaterialCount; + public string ObservedMaterial0Path = string.Empty; + public int ObservedRenderLayer; + + public void Start() + { + InitialHasCamera = HasComponent(); + InitialHasLight = HasComponent(); + InitialHasMeshFilter = HasComponent(); + InitialHasMeshRenderer = HasComponent(); + + AddedTransform = AddComponent() != null; + AddedCamera = AddComponent() != null; + AddedLight = gameObject.AddComponent() != null; + AddedMeshFilter = AddComponent() != null; + AddedMeshRenderer = gameObject.AddComponent() != null; + + gameObject.AddComponent(); + AddComponent(); + gameObject.AddComponent(); + AddComponent(); + + HasCameraAfterAdd = HasComponent(); + HasLightAfterAdd = HasComponent(); + HasMeshFilterAfterAdd = HasComponent(); + HasMeshRendererAfterAdd = HasComponent(); + + CameraLookupSucceeded = TryGetComponent(out Camera camera); + LightLookupSucceeded = TryGetComponent(out Light light); + MeshFilterLookupSucceeded = TryGetComponent(out MeshFilter meshFilter); + MeshRendererLookupSucceeded = TryGetComponent(out MeshRenderer meshRenderer); + + if (camera != null) + { + camera.fieldOfView = 82.0f; + ObservedCameraFieldOfView = camera.fieldOfView; + } + + if (light != null) + { + light.intensity = 4.5f; + ObservedLightIntensity = light.intensity; + } + + if (meshFilter != null) + { + meshFilter.meshPath = "Meshes/added.mesh"; + ObservedMeshPath = meshFilter.meshPath; + } + + if (meshRenderer != null) + { + meshRenderer.SetMaterialPath(0, "Materials/added.mat"); + meshRenderer.renderLayer = 6; + ObservedMaterialCount = meshRenderer.materialCount; + ObservedMaterial0Path = meshRenderer.GetMaterialPath(0); + ObservedRenderLayer = meshRenderer.renderLayer; + } + } + } +} \ No newline at end of file diff --git a/managed/GameScripts/MeshRendererEdgeCaseProbe.cs b/managed/GameScripts/MeshRendererEdgeCaseProbe.cs new file mode 100644 index 00000000..d37535a5 --- /dev/null +++ b/managed/GameScripts/MeshRendererEdgeCaseProbe.cs @@ -0,0 +1,51 @@ +using XCEngine; + +namespace Gameplay +{ + public sealed class MeshRendererEdgeCaseProbe : MonoBehaviour + { + public int ObservedInitialMaterialCount; + public string ObservedNegativeIndexPath = string.Empty; + public string ObservedOutOfRangePathBeforeClear = string.Empty; + public string ObservedMaterial0PathAfterNegativeWrite = string.Empty; + public string ObservedMaterial1PathAfterNegativeWrite = string.Empty; + public int ObservedMaterialCountAfterNegativeWrite; + public int ObservedRenderLayerAfterNegativeWrite; + public int ObservedMaterialCountAfterClear; + public string ObservedMaterial0PathAfterClear = string.Empty; + public string ObservedMaterial3PathAfterClear = string.Empty; + public bool ObservedCastShadowsAfterClear; + public bool ObservedReceiveShadowsAfterClear; + public int ObservedRenderLayerAfterClear; + + public void Start() + { + if (!TryGetComponent(out MeshRenderer meshRenderer) || meshRenderer == null) + { + return; + } + + ObservedInitialMaterialCount = meshRenderer.materialCount; + ObservedNegativeIndexPath = meshRenderer.GetMaterialPath(-1); + ObservedOutOfRangePathBeforeClear = meshRenderer.GetMaterialPath(3); + + meshRenderer.SetMaterialPath(-1, "Materials/ignored.mat"); + + ObservedMaterial0PathAfterNegativeWrite = meshRenderer.GetMaterialPath(0); + ObservedMaterial1PathAfterNegativeWrite = meshRenderer.GetMaterialPath(1); + ObservedMaterialCountAfterNegativeWrite = meshRenderer.materialCount; + + meshRenderer.renderLayer = -5; + ObservedRenderLayerAfterNegativeWrite = meshRenderer.renderLayer; + + meshRenderer.ClearMaterials(); + + ObservedMaterialCountAfterClear = meshRenderer.materialCount; + ObservedMaterial0PathAfterClear = meshRenderer.GetMaterialPath(0); + ObservedMaterial3PathAfterClear = meshRenderer.GetMaterialPath(3); + ObservedCastShadowsAfterClear = meshRenderer.castShadows; + ObservedReceiveShadowsAfterClear = meshRenderer.receiveShadows; + ObservedRenderLayerAfterClear = meshRenderer.renderLayer; + } + } +} \ No newline at end of file diff --git a/managed/XCEngine.ScriptCore/Component.cs b/managed/XCEngine.ScriptCore/Component.cs index 57f262ed..5585dbb2 100644 --- a/managed/XCEngine.ScriptCore/Component.cs +++ b/managed/XCEngine.ScriptCore/Component.cs @@ -34,6 +34,11 @@ namespace XCEngine return GameObject.GetComponent(); } + public T AddComponent() where T : Component + { + return GameObject.AddComponent(); + } + public bool TryGetComponent(out T component) where T : Component { component = GetComponent(); diff --git a/managed/XCEngine.ScriptCore/GameObject.cs b/managed/XCEngine.ScriptCore/GameObject.cs index 2b726fa1..41cd4df6 100644 --- a/managed/XCEngine.ScriptCore/GameObject.cs +++ b/managed/XCEngine.ScriptCore/GameObject.cs @@ -46,6 +46,12 @@ namespace XCEngine return Component.Create(componentOwnerUUID); } + public T AddComponent() where T : Component + { + ulong componentOwnerUUID = InternalCalls.GameObject_AddComponent(UUID, typeof(T)); + return Component.Create(componentOwnerUUID); + } + public bool TryGetComponent(out T component) where T : Component { component = GetComponent(); diff --git a/managed/XCEngine.ScriptCore/InternalCalls.cs b/managed/XCEngine.ScriptCore/InternalCalls.cs index 359ca775..60fab4b8 100644 --- a/managed/XCEngine.ScriptCore/InternalCalls.cs +++ b/managed/XCEngine.ScriptCore/InternalCalls.cs @@ -38,6 +38,9 @@ namespace XCEngine [MethodImpl(MethodImplOptions.InternalCall)] internal static extern ulong GameObject_GetComponent(ulong gameObjectUUID, Type componentType); + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern ulong GameObject_AddComponent(ulong gameObjectUUID, Type componentType); + [MethodImpl(MethodImplOptions.InternalCall)] internal static extern bool Behaviour_GetEnabled(ulong scriptComponentUUID); diff --git a/tests/Components/test_mesh_render_components.cpp b/tests/Components/test_mesh_render_components.cpp index fd898273..3b0d7429 100644 --- a/tests/Components/test_mesh_render_components.cpp +++ b/tests/Components/test_mesh_render_components.cpp @@ -135,6 +135,34 @@ TEST(MeshRendererComponent_Test, SerializeAndDeserializePreservesMaterialPathsAn delete material1; } +TEST(MeshRendererComponent_Test, SerializeAndDeserializePreservesTrailingEmptyMaterialSlots) { + MeshRendererComponent source; + Material* material0 = CreateTestMaterial("M0", "Materials/serialized0.mat"); + source.SetMaterial(0, material0); + source.SetMaterialPath(1, ""); + source.SetCastShadows(false); + source.SetReceiveShadows(true); + source.SetRenderLayer(9); + + std::stringstream stream; + source.Serialize(stream); + + MeshRendererComponent target; + target.Deserialize(stream); + + ASSERT_EQ(target.GetMaterialCount(), 2u); + EXPECT_EQ(target.GetMaterial(0), nullptr); + EXPECT_EQ(target.GetMaterial(1), nullptr); + EXPECT_EQ(target.GetMaterialPath(0), "Materials/serialized0.mat"); + EXPECT_EQ(target.GetMaterialPath(1), ""); + EXPECT_FALSE(target.GetCastShadows()); + EXPECT_TRUE(target.GetReceiveShadows()); + EXPECT_EQ(target.GetRenderLayer(), 9u); + + source.ClearMaterials(); + delete material0; +} + TEST(MeshRendererComponent_Test, SetMaterialPathPreservesPathWithoutLoadedResource) { MeshRendererComponent component; diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index eaf0a5ed..8c0988d0 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -393,6 +393,175 @@ TEST_F(MonoScriptRuntimeTest, ManagedMeshComponentWrappersReadAndWritePathsAndFl EXPECT_EQ(meshRenderer->GetRenderLayer(), 11u); } +TEST_F(MonoScriptRuntimeTest, ManagedMeshRendererWrapperHandlesClearAndBoundaryCases) { + Scene* runtimeScene = CreateScene("MonoRuntimeScene"); + GameObject* host = runtimeScene->CreateGameObject("Host"); + MeshRendererComponent* meshRenderer = host->AddComponent(); + ScriptComponent* component = AddScript(host, "Gameplay", "MeshRendererEdgeCaseProbe"); + + meshRenderer->SetMaterialPath(0, "Materials/initial0.mat"); + meshRenderer->SetMaterialPath(1, "Materials/initial1.mat"); + meshRenderer->SetCastShadows(false); + meshRenderer->SetReceiveShadows(true); + meshRenderer->SetRenderLayer(9); + + engine->OnRuntimeStart(runtimeScene); + engine->OnUpdate(0.016f); + + int32_t observedInitialMaterialCount = 0; + std::string observedNegativeIndexPath; + std::string observedOutOfRangePathBeforeClear; + std::string observedMaterial0PathAfterNegativeWrite; + std::string observedMaterial1PathAfterNegativeWrite; + int32_t observedMaterialCountAfterNegativeWrite = 0; + int32_t observedRenderLayerAfterNegativeWrite = -1; + int32_t observedMaterialCountAfterClear = -1; + std::string observedMaterial0PathAfterClear; + std::string observedMaterial3PathAfterClear; + bool observedCastShadowsAfterClear = true; + bool observedReceiveShadowsAfterClear = false; + int32_t observedRenderLayerAfterClear = -1; + + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialMaterialCount", observedInitialMaterialCount)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedNegativeIndexPath", observedNegativeIndexPath)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedOutOfRangePathBeforeClear", observedOutOfRangePathBeforeClear)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterial0PathAfterNegativeWrite", observedMaterial0PathAfterNegativeWrite)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterial1PathAfterNegativeWrite", observedMaterial1PathAfterNegativeWrite)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterialCountAfterNegativeWrite", observedMaterialCountAfterNegativeWrite)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRenderLayerAfterNegativeWrite", observedRenderLayerAfterNegativeWrite)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterialCountAfterClear", observedMaterialCountAfterClear)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterial0PathAfterClear", observedMaterial0PathAfterClear)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterial3PathAfterClear", observedMaterial3PathAfterClear)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedCastShadowsAfterClear", observedCastShadowsAfterClear)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedReceiveShadowsAfterClear", observedReceiveShadowsAfterClear)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRenderLayerAfterClear", observedRenderLayerAfterClear)); + + EXPECT_EQ(observedInitialMaterialCount, 2); + EXPECT_EQ(observedNegativeIndexPath, ""); + EXPECT_EQ(observedOutOfRangePathBeforeClear, ""); + EXPECT_EQ(observedMaterial0PathAfterNegativeWrite, "Materials/initial0.mat"); + EXPECT_EQ(observedMaterial1PathAfterNegativeWrite, "Materials/initial1.mat"); + EXPECT_EQ(observedMaterialCountAfterNegativeWrite, 2); + EXPECT_EQ(observedRenderLayerAfterNegativeWrite, 0); + EXPECT_EQ(observedMaterialCountAfterClear, 0); + EXPECT_EQ(observedMaterial0PathAfterClear, ""); + EXPECT_EQ(observedMaterial3PathAfterClear, ""); + EXPECT_FALSE(observedCastShadowsAfterClear); + EXPECT_TRUE(observedReceiveShadowsAfterClear); + EXPECT_EQ(observedRenderLayerAfterClear, 0); + + EXPECT_EQ(meshRenderer->GetMaterialCount(), 0u); + EXPECT_EQ(meshRenderer->GetMaterialPath(0), ""); + EXPECT_EQ(meshRenderer->GetMaterialPath(3), ""); + EXPECT_FALSE(meshRenderer->GetCastShadows()); + EXPECT_TRUE(meshRenderer->GetReceiveShadows()); + EXPECT_EQ(meshRenderer->GetRenderLayer(), 0u); +} + +TEST_F(MonoScriptRuntimeTest, GameObjectAddComponentApiCreatesBuiltinComponentsAndAvoidsDuplicates) { + Scene* runtimeScene = CreateScene("MonoRuntimeScene"); + GameObject* host = runtimeScene->CreateGameObject("Host"); + ScriptComponent* component = AddScript(host, "Gameplay", "AddComponentProbe"); + + engine->OnRuntimeStart(runtimeScene); + engine->OnUpdate(0.016f); + + bool initialHasCamera = true; + bool initialHasLight = true; + bool initialHasMeshFilter = true; + bool initialHasMeshRenderer = true; + bool addedTransform = false; + bool addedCamera = false; + bool addedLight = false; + bool addedMeshFilter = false; + bool addedMeshRenderer = false; + bool hasCameraAfterAdd = false; + bool hasLightAfterAdd = false; + bool hasMeshFilterAfterAdd = false; + bool hasMeshRendererAfterAdd = false; + bool cameraLookupSucceeded = false; + bool lightLookupSucceeded = false; + bool meshFilterLookupSucceeded = false; + bool meshRendererLookupSucceeded = false; + float observedCameraFieldOfView = 0.0f; + float observedLightIntensity = 0.0f; + std::string observedMeshPath; + int32_t observedMaterialCount = 0; + std::string observedMaterial0Path; + int32_t observedRenderLayer = 0; + + EXPECT_TRUE(runtime->TryGetFieldValue(component, "InitialHasCamera", initialHasCamera)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "InitialHasLight", initialHasLight)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "InitialHasMeshFilter", initialHasMeshFilter)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "InitialHasMeshRenderer", initialHasMeshRenderer)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "AddedTransform", addedTransform)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "AddedCamera", addedCamera)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "AddedLight", addedLight)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "AddedMeshFilter", addedMeshFilter)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "AddedMeshRenderer", addedMeshRenderer)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasCameraAfterAdd", hasCameraAfterAdd)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasLightAfterAdd", hasLightAfterAdd)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasMeshFilterAfterAdd", hasMeshFilterAfterAdd)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasMeshRendererAfterAdd", hasMeshRendererAfterAdd)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "CameraLookupSucceeded", cameraLookupSucceeded)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "LightLookupSucceeded", lightLookupSucceeded)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "MeshFilterLookupSucceeded", meshFilterLookupSucceeded)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "MeshRendererLookupSucceeded", meshRendererLookupSucceeded)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedCameraFieldOfView", observedCameraFieldOfView)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedLightIntensity", observedLightIntensity)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMeshPath", observedMeshPath)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterialCount", observedMaterialCount)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedMaterial0Path", observedMaterial0Path)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedRenderLayer", observedRenderLayer)); + + EXPECT_FALSE(initialHasCamera); + EXPECT_FALSE(initialHasLight); + EXPECT_FALSE(initialHasMeshFilter); + EXPECT_FALSE(initialHasMeshRenderer); + EXPECT_TRUE(addedTransform); + EXPECT_TRUE(addedCamera); + EXPECT_TRUE(addedLight); + EXPECT_TRUE(addedMeshFilter); + EXPECT_TRUE(addedMeshRenderer); + EXPECT_TRUE(hasCameraAfterAdd); + EXPECT_TRUE(hasLightAfterAdd); + EXPECT_TRUE(hasMeshFilterAfterAdd); + EXPECT_TRUE(hasMeshRendererAfterAdd); + EXPECT_TRUE(cameraLookupSucceeded); + EXPECT_TRUE(lightLookupSucceeded); + EXPECT_TRUE(meshFilterLookupSucceeded); + EXPECT_TRUE(meshRendererLookupSucceeded); + EXPECT_FLOAT_EQ(observedCameraFieldOfView, 82.0f); + EXPECT_FLOAT_EQ(observedLightIntensity, 4.5f); + EXPECT_EQ(observedMeshPath, "Meshes/added.mesh"); + EXPECT_EQ(observedMaterialCount, 1); + EXPECT_EQ(observedMaterial0Path, "Materials/added.mat"); + EXPECT_EQ(observedRenderLayer, 6); + + ASSERT_NE(host->GetTransform(), nullptr); + EXPECT_EQ(host->GetComponents().size(), 1u); + EXPECT_EQ(host->GetComponents().size(), 1u); + EXPECT_EQ(host->GetComponents().size(), 1u); + EXPECT_EQ(host->GetComponents().size(), 1u); + + CameraComponent* camera = host->GetComponent(); + LightComponent* light = host->GetComponent(); + MeshFilterComponent* meshFilter = host->GetComponent(); + MeshRendererComponent* meshRenderer = host->GetComponent(); + + ASSERT_NE(camera, nullptr); + ASSERT_NE(light, nullptr); + ASSERT_NE(meshFilter, nullptr); + ASSERT_NE(meshRenderer, nullptr); + + EXPECT_FLOAT_EQ(camera->GetFieldOfView(), 82.0f); + EXPECT_FLOAT_EQ(light->GetIntensity(), 4.5f); + EXPECT_EQ(meshFilter->GetMeshPath(), "Meshes/added.mesh"); + ASSERT_EQ(meshRenderer->GetMaterialCount(), 1u); + EXPECT_EQ(meshRenderer->GetMaterialPath(0), "Materials/added.mat"); + EXPECT_EQ(meshRenderer->GetRenderLayer(), 6u); +} + TEST_F(MonoScriptRuntimeTest, TransformHierarchyApiExposesParentChildAndReparenting) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* root = runtimeScene->CreateGameObject("Root");