diff --git a/engine/include/XCEngine/Components/MeshFilterComponent.h b/engine/include/XCEngine/Components/MeshFilterComponent.h index 2da2f1b2..d0ad61eb 100644 --- a/engine/include/XCEngine/Components/MeshFilterComponent.h +++ b/engine/include/XCEngine/Components/MeshFilterComponent.h @@ -17,6 +17,7 @@ public: const Resources::ResourceHandle& GetMeshHandle() const { return m_mesh; } const std::string& GetMeshPath() const { return m_meshPath; } + void SetMeshPath(const std::string& meshPath); void SetMesh(const Resources::ResourceHandle& mesh); void SetMesh(Resources::Mesh* mesh); void ClearMesh(); diff --git a/engine/include/XCEngine/Components/MeshRendererComponent.h b/engine/include/XCEngine/Components/MeshRendererComponent.h index 31e533e9..5a3818e1 100644 --- a/engine/include/XCEngine/Components/MeshRendererComponent.h +++ b/engine/include/XCEngine/Components/MeshRendererComponent.h @@ -18,8 +18,10 @@ public: size_t GetMaterialCount() const { return m_materials.size(); } Resources::Material* GetMaterial(size_t index) const; const Resources::ResourceHandle& GetMaterialHandle(size_t index) const; + const std::string& GetMaterialPath(size_t index) const; const std::vector& GetMaterialPaths() const { return m_materialPaths; } + void SetMaterialPath(size_t index, const std::string& materialPath); void SetMaterial(size_t index, const Resources::ResourceHandle& material); void SetMaterial(size_t index, Resources::Material* material); void SetMaterials(const std::vector>& materials); diff --git a/engine/src/Components/MeshFilterComponent.cpp b/engine/src/Components/MeshFilterComponent.cpp index 6122f7cb..52d5b438 100644 --- a/engine/src/Components/MeshFilterComponent.cpp +++ b/engine/src/Components/MeshFilterComponent.cpp @@ -15,6 +15,16 @@ std::string ToStdString(const Containers::String& value) { } // namespace +void MeshFilterComponent::SetMeshPath(const std::string& meshPath) { + m_meshPath = meshPath; + if (m_meshPath.empty()) { + m_mesh.Reset(); + return; + } + + m_mesh = Resources::ResourceManager::Get().Load(m_meshPath.c_str()); +} + void MeshFilterComponent::SetMesh(const Resources::ResourceHandle& mesh) { m_mesh = mesh; m_meshPath = mesh.Get() != nullptr ? ToStdString(mesh->GetPath()) : std::string(); @@ -52,10 +62,7 @@ void MeshFilterComponent::Deserialize(std::istream& is) { const std::string value = token.substr(eqPos + 1); if (key == "mesh") { - m_meshPath = value; - if (!m_meshPath.empty()) { - m_mesh = Resources::ResourceManager::Get().Load(m_meshPath.c_str()); - } + SetMeshPath(value); } } } diff --git a/engine/src/Components/MeshRendererComponent.cpp b/engine/src/Components/MeshRendererComponent.cpp index aea79b80..83a5479d 100644 --- a/engine/src/Components/MeshRendererComponent.cpp +++ b/engine/src/Components/MeshRendererComponent.cpp @@ -39,6 +39,22 @@ const Resources::ResourceHandle& MeshRendererComponent::Get return index < m_materials.size() ? m_materials[index] : kNullHandle; } +const std::string& MeshRendererComponent::GetMaterialPath(size_t index) const { + static const std::string kEmptyPath; + return index < m_materialPaths.size() ? m_materialPaths[index] : kEmptyPath; +} + +void MeshRendererComponent::SetMaterialPath(size_t index, const std::string& materialPath) { + EnsureMaterialSlot(index); + m_materialPaths[index] = materialPath; + if (materialPath.empty()) { + m_materials[index].Reset(); + return; + } + + m_materials[index] = Resources::ResourceManager::Get().Load(materialPath.c_str()); +} + void MeshRendererComponent::SetMaterial(size_t index, const Resources::ResourceHandle& material) { EnsureMaterialSlot(index); m_materials[index] = material; @@ -100,9 +116,7 @@ void MeshRendererComponent::Deserialize(std::istream& is) { m_materialPaths = SplitMaterialPaths(value); m_materials.resize(m_materialPaths.size()); for (size_t i = 0; i < m_materialPaths.size(); ++i) { - if (!m_materialPaths[i].empty()) { - m_materials[i] = Resources::ResourceManager::Get().Load(m_materialPaths[i].c_str()); - } + SetMaterialPath(i, m_materialPaths[i]); } } else if (key == "castShadows") { m_castShadows = (std::stoi(value) != 0); diff --git a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp index 92999ef0..12ecb962 100644 --- a/engine/src/Scripting/Mono/MonoScriptRuntime.cpp +++ b/engine/src/Scripting/Mono/MonoScriptRuntime.cpp @@ -3,6 +3,8 @@ #include "Components/CameraComponent.h" #include "Components/GameObject.h" #include "Components/LightComponent.h" +#include "Components/MeshFilterComponent.h" +#include "Components/MeshRendererComponent.h" #include "Components/TransformComponent.h" #include "Debug/Logger.h" #include "Scene/Scene.h" @@ -39,6 +41,8 @@ enum class ManagedComponentKind { Transform, Camera, Light, + MeshFilter, + MeshRenderer, }; MonoRootState& GetMonoRootState() { @@ -116,6 +120,12 @@ ManagedComponentKind ResolveManagedComponentKind(MonoReflectionType* reflectionT if (namespaceName == "XCEngine" && className == "Light") { return ManagedComponentKind::Light; } + if (namespaceName == "XCEngine" && className == "MeshFilter") { + return ManagedComponentKind::MeshFilter; + } + if (namespaceName == "XCEngine" && className == "MeshRenderer") { + return ManagedComponentKind::MeshRenderer; + } return ManagedComponentKind::Unknown; } @@ -200,6 +210,10 @@ bool HasNativeComponent(Components::GameObject* gameObject, ManagedComponentKind return gameObject->GetComponent() != nullptr; case ManagedComponentKind::Light: return gameObject->GetComponent() != nullptr; + case ManagedComponentKind::MeshFilter: + return gameObject->GetComponent() != nullptr; + case ManagedComponentKind::MeshRenderer: + return gameObject->GetComponent() != nullptr; case ManagedComponentKind::Unknown: return false; } @@ -217,6 +231,16 @@ Components::LightComponent* FindLightComponent(uint64_t gameObjectUUID) { return gameObject ? gameObject->GetComponent() : nullptr; } +Components::MeshFilterComponent* FindMeshFilterComponent(uint64_t gameObjectUUID) { + Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID); + return gameObject ? gameObject->GetComponent() : nullptr; +} + +Components::MeshRendererComponent* FindMeshRendererComponent(uint64_t gameObjectUUID) { + Components::GameObject* gameObject = FindGameObjectByUUID(gameObjectUUID); + return gameObject ? gameObject->GetComponent() : nullptr; +} + Components::Space ResolveManagedSpace(int32_t value) { return value == static_cast(Components::Space::World) ? Components::Space::World @@ -827,6 +851,94 @@ void InternalCall_Light_SetCastsShadows(uint64_t gameObjectUUID, mono_bool value component->SetCastsShadows(value != 0); } +MonoString* InternalCall_MeshFilter_GetMeshPath(uint64_t gameObjectUUID) { + Components::MeshFilterComponent* component = FindMeshFilterComponent(gameObjectUUID); + return mono_string_new( + mono_domain_get(), + component ? component->GetMeshPath().c_str() : ""); +} + +void InternalCall_MeshFilter_SetMeshPath(uint64_t gameObjectUUID, MonoString* path) { + Components::MeshFilterComponent* component = FindMeshFilterComponent(gameObjectUUID); + if (!component) { + return; + } + + component->SetMeshPath(MonoStringToUtf8(path)); +} + +int32_t InternalCall_MeshRenderer_GetMaterialCount(uint64_t gameObjectUUID) { + Components::MeshRendererComponent* component = FindMeshRendererComponent(gameObjectUUID); + return component ? static_cast(component->GetMaterialCount()) : 0; +} + +MonoString* InternalCall_MeshRenderer_GetMaterialPath(uint64_t gameObjectUUID, int32_t index) { + Components::MeshRendererComponent* component = FindMeshRendererComponent(gameObjectUUID); + const std::string path = + (component && index >= 0) ? component->GetMaterialPath(static_cast(index)) : std::string(); + return mono_string_new(mono_domain_get(), path.c_str()); +} + +void InternalCall_MeshRenderer_SetMaterialPath(uint64_t gameObjectUUID, int32_t index, MonoString* path) { + Components::MeshRendererComponent* component = FindMeshRendererComponent(gameObjectUUID); + if (!component || index < 0) { + return; + } + + component->SetMaterialPath(static_cast(index), MonoStringToUtf8(path)); +} + +void InternalCall_MeshRenderer_ClearMaterials(uint64_t gameObjectUUID) { + Components::MeshRendererComponent* component = FindMeshRendererComponent(gameObjectUUID); + if (!component) { + return; + } + + component->ClearMaterials(); +} + +mono_bool InternalCall_MeshRenderer_GetCastShadows(uint64_t gameObjectUUID) { + Components::MeshRendererComponent* component = FindMeshRendererComponent(gameObjectUUID); + return (component && component->GetCastShadows()) ? 1 : 0; +} + +void InternalCall_MeshRenderer_SetCastShadows(uint64_t gameObjectUUID, mono_bool value) { + Components::MeshRendererComponent* component = FindMeshRendererComponent(gameObjectUUID); + if (!component) { + return; + } + + component->SetCastShadows(value != 0); +} + +mono_bool InternalCall_MeshRenderer_GetReceiveShadows(uint64_t gameObjectUUID) { + Components::MeshRendererComponent* component = FindMeshRendererComponent(gameObjectUUID); + return (component && component->GetReceiveShadows()) ? 1 : 0; +} + +void InternalCall_MeshRenderer_SetReceiveShadows(uint64_t gameObjectUUID, mono_bool value) { + Components::MeshRendererComponent* component = FindMeshRendererComponent(gameObjectUUID); + if (!component) { + return; + } + + component->SetReceiveShadows(value != 0); +} + +int32_t InternalCall_MeshRenderer_GetRenderLayer(uint64_t gameObjectUUID) { + Components::MeshRendererComponent* component = FindMeshRendererComponent(gameObjectUUID); + return component ? static_cast(component->GetRenderLayer()) : 0; +} + +void InternalCall_MeshRenderer_SetRenderLayer(uint64_t gameObjectUUID, int32_t value) { + Components::MeshRendererComponent* component = FindMeshRendererComponent(gameObjectUUID); + if (!component) { + return; + } + + component->SetRenderLayer(static_cast(std::max(value, 0))); +} + void RegisterInternalCalls() { if (GetInternalCallRegistrationState()) { return; @@ -893,6 +1005,18 @@ void RegisterInternalCalls() { mono_add_internal_call("XCEngine.InternalCalls::Light_SetSpotAngle", reinterpret_cast(&InternalCall_Light_SetSpotAngle)); mono_add_internal_call("XCEngine.InternalCalls::Light_GetCastsShadows", reinterpret_cast(&InternalCall_Light_GetCastsShadows)); mono_add_internal_call("XCEngine.InternalCalls::Light_SetCastsShadows", reinterpret_cast(&InternalCall_Light_SetCastsShadows)); + mono_add_internal_call("XCEngine.InternalCalls::MeshFilter_GetMeshPath", reinterpret_cast(&InternalCall_MeshFilter_GetMeshPath)); + mono_add_internal_call("XCEngine.InternalCalls::MeshFilter_SetMeshPath", reinterpret_cast(&InternalCall_MeshFilter_SetMeshPath)); + mono_add_internal_call("XCEngine.InternalCalls::MeshRenderer_GetMaterialCount", reinterpret_cast(&InternalCall_MeshRenderer_GetMaterialCount)); + mono_add_internal_call("XCEngine.InternalCalls::MeshRenderer_GetMaterialPath", reinterpret_cast(&InternalCall_MeshRenderer_GetMaterialPath)); + mono_add_internal_call("XCEngine.InternalCalls::MeshRenderer_SetMaterialPath", reinterpret_cast(&InternalCall_MeshRenderer_SetMaterialPath)); + mono_add_internal_call("XCEngine.InternalCalls::MeshRenderer_ClearMaterials", reinterpret_cast(&InternalCall_MeshRenderer_ClearMaterials)); + mono_add_internal_call("XCEngine.InternalCalls::MeshRenderer_GetCastShadows", reinterpret_cast(&InternalCall_MeshRenderer_GetCastShadows)); + mono_add_internal_call("XCEngine.InternalCalls::MeshRenderer_SetCastShadows", reinterpret_cast(&InternalCall_MeshRenderer_SetCastShadows)); + mono_add_internal_call("XCEngine.InternalCalls::MeshRenderer_GetReceiveShadows", reinterpret_cast(&InternalCall_MeshRenderer_GetReceiveShadows)); + mono_add_internal_call("XCEngine.InternalCalls::MeshRenderer_SetReceiveShadows", reinterpret_cast(&InternalCall_MeshRenderer_SetReceiveShadows)); + mono_add_internal_call("XCEngine.InternalCalls::MeshRenderer_GetRenderLayer", reinterpret_cast(&InternalCall_MeshRenderer_GetRenderLayer)); + mono_add_internal_call("XCEngine.InternalCalls::MeshRenderer_SetRenderLayer", reinterpret_cast(&InternalCall_MeshRenderer_SetRenderLayer)); GetInternalCallRegistrationState() = true; } diff --git a/managed/CMakeLists.txt b/managed/CMakeLists.txt index 0fc3e111..b70fbff8 100644 --- a/managed/CMakeLists.txt +++ b/managed/CMakeLists.txt @@ -50,13 +50,15 @@ foreach(XCENGINE_REQUIRED_PATH endforeach() set(XCENGINE_SCRIPT_CORE_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Camera.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Behaviour.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Camera.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Component.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Debug.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/GameObject.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/InternalCalls.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Light.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/MeshFilter.cs + ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/MeshRenderer.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/MonoBehaviour.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Quaternion.cs ${CMAKE_CURRENT_SOURCE_DIR}/XCEngine.ScriptCore/Space.cs @@ -71,6 +73,7 @@ set(XCENGINE_GAME_SCRIPT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/BuiltinComponentProbe.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/TransformConversionProbe.cs ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/TransformMotionProbe.cs ${CMAKE_CURRENT_SOURCE_DIR}/GameScripts/TransformOrientationProbe.cs diff --git a/managed/GameScripts/MeshComponentProbe.cs b/managed/GameScripts/MeshComponentProbe.cs new file mode 100644 index 00000000..17cbe6bb --- /dev/null +++ b/managed/GameScripts/MeshComponentProbe.cs @@ -0,0 +1,60 @@ +using XCEngine; + +namespace Gameplay +{ + public sealed class MeshComponentProbe : MonoBehaviour + { + public bool HasMeshFilter; + public bool HasMeshRenderer; + public bool MeshFilterLookupSucceeded; + public bool MeshRendererLookupSucceeded; + public string ObservedInitialMeshPath = string.Empty; + public string ObservedUpdatedMeshPath = string.Empty; + public int ObservedInitialMaterialCount; + public string ObservedInitialMaterial0Path = string.Empty; + public bool ObservedInitialCastShadows; + public bool ObservedInitialReceiveShadows; + public int ObservedInitialRenderLayer; + public int ObservedUpdatedMaterialCount; + public string ObservedUpdatedMaterial1Path = string.Empty; + public bool ObservedUpdatedCastShadows; + public bool ObservedUpdatedReceiveShadows; + public int ObservedUpdatedRenderLayer; + + public void Start() + { + HasMeshFilter = HasComponent(); + HasMeshRenderer = HasComponent(); + + MeshFilterLookupSucceeded = TryGetComponent(out MeshFilter meshFilter); + MeshRendererLookupSucceeded = TryGetComponent(out MeshRenderer meshRenderer); + + if (meshFilter != null) + { + ObservedInitialMeshPath = meshFilter.meshPath; + meshFilter.meshPath = "Meshes/runtime_override.mesh"; + ObservedUpdatedMeshPath = meshFilter.meshPath; + } + + if (meshRenderer != null) + { + ObservedInitialMaterialCount = meshRenderer.materialCount; + ObservedInitialMaterial0Path = meshRenderer.GetMaterialPath(0); + ObservedInitialCastShadows = meshRenderer.castShadows; + ObservedInitialReceiveShadows = meshRenderer.receiveShadows; + ObservedInitialRenderLayer = meshRenderer.renderLayer; + + meshRenderer.SetMaterialPath(1, "Materials/runtime_override.mat"); + meshRenderer.castShadows = false; + meshRenderer.receiveShadows = true; + meshRenderer.renderLayer = 11; + + ObservedUpdatedMaterialCount = meshRenderer.materialCount; + ObservedUpdatedMaterial1Path = meshRenderer.GetMaterialPath(1); + ObservedUpdatedCastShadows = meshRenderer.castShadows; + ObservedUpdatedReceiveShadows = meshRenderer.receiveShadows; + ObservedUpdatedRenderLayer = meshRenderer.renderLayer; + } + } + } +} diff --git a/managed/XCEngine.ScriptCore/InternalCalls.cs b/managed/XCEngine.ScriptCore/InternalCalls.cs index 67027db4..359ca775 100644 --- a/managed/XCEngine.ScriptCore/InternalCalls.cs +++ b/managed/XCEngine.ScriptCore/InternalCalls.cs @@ -187,5 +187,41 @@ namespace XCEngine [MethodImpl(MethodImplOptions.InternalCall)] internal static extern void Light_SetCastsShadows(ulong gameObjectUUID, bool value); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern string MeshFilter_GetMeshPath(ulong gameObjectUUID); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void MeshFilter_SetMeshPath(ulong gameObjectUUID, string path); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern int MeshRenderer_GetMaterialCount(ulong gameObjectUUID); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern string MeshRenderer_GetMaterialPath(ulong gameObjectUUID, int index); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void MeshRenderer_SetMaterialPath(ulong gameObjectUUID, int index, string path); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void MeshRenderer_ClearMaterials(ulong gameObjectUUID); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern bool MeshRenderer_GetCastShadows(ulong gameObjectUUID); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void MeshRenderer_SetCastShadows(ulong gameObjectUUID, bool value); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern bool MeshRenderer_GetReceiveShadows(ulong gameObjectUUID); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void MeshRenderer_SetReceiveShadows(ulong gameObjectUUID, bool value); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern int MeshRenderer_GetRenderLayer(ulong gameObjectUUID); + + [MethodImpl(MethodImplOptions.InternalCall)] + internal static extern void MeshRenderer_SetRenderLayer(ulong gameObjectUUID, int value); } } diff --git a/managed/XCEngine.ScriptCore/MeshFilter.cs b/managed/XCEngine.ScriptCore/MeshFilter.cs new file mode 100644 index 00000000..c51c394c --- /dev/null +++ b/managed/XCEngine.ScriptCore/MeshFilter.cs @@ -0,0 +1,22 @@ +namespace XCEngine +{ + public sealed class MeshFilter : Component + { + internal MeshFilter(ulong gameObjectUUID) + : base(gameObjectUUID) + { + } + + public string MeshPath + { + get => InternalCalls.MeshFilter_GetMeshPath(GameObjectUUID) ?? string.Empty; + set => InternalCalls.MeshFilter_SetMeshPath(GameObjectUUID, value ?? string.Empty); + } + + public string meshPath + { + get => MeshPath; + set => MeshPath = value; + } + } +} diff --git a/managed/XCEngine.ScriptCore/MeshRenderer.cs b/managed/XCEngine.ScriptCore/MeshRenderer.cs new file mode 100644 index 00000000..28180831 --- /dev/null +++ b/managed/XCEngine.ScriptCore/MeshRenderer.cs @@ -0,0 +1,64 @@ +namespace XCEngine +{ + public sealed class MeshRenderer : Component + { + internal MeshRenderer(ulong gameObjectUUID) + : base(gameObjectUUID) + { + } + + public int MaterialCount => InternalCalls.MeshRenderer_GetMaterialCount(GameObjectUUID); + public int materialCount => MaterialCount; + + public bool CastShadows + { + get => InternalCalls.MeshRenderer_GetCastShadows(GameObjectUUID); + set => InternalCalls.MeshRenderer_SetCastShadows(GameObjectUUID, value); + } + + public bool castShadows + { + get => CastShadows; + set => CastShadows = value; + } + + public bool ReceiveShadows + { + get => InternalCalls.MeshRenderer_GetReceiveShadows(GameObjectUUID); + set => InternalCalls.MeshRenderer_SetReceiveShadows(GameObjectUUID, value); + } + + public bool receiveShadows + { + get => ReceiveShadows; + set => ReceiveShadows = value; + } + + public int RenderLayer + { + get => InternalCalls.MeshRenderer_GetRenderLayer(GameObjectUUID); + set => InternalCalls.MeshRenderer_SetRenderLayer(GameObjectUUID, value); + } + + public int renderLayer + { + get => RenderLayer; + set => RenderLayer = value; + } + + public string GetMaterialPath(int index) + { + return InternalCalls.MeshRenderer_GetMaterialPath(GameObjectUUID, index) ?? string.Empty; + } + + public void SetMaterialPath(int index, string path) + { + InternalCalls.MeshRenderer_SetMaterialPath(GameObjectUUID, index, path ?? string.Empty); + } + + public void ClearMaterials() + { + InternalCalls.MeshRenderer_ClearMaterials(GameObjectUUID); + } + } +} diff --git a/tests/Components/test_mesh_render_components.cpp b/tests/Components/test_mesh_render_components.cpp index f05b524e..fd898273 100644 --- a/tests/Components/test_mesh_render_components.cpp +++ b/tests/Components/test_mesh_render_components.cpp @@ -66,6 +66,19 @@ TEST(MeshFilterComponent_Test, SerializeAndDeserializePreservesPath) { delete mesh; } +TEST(MeshFilterComponent_Test, SetMeshPathPreservesPathWithoutLoadedResource) { + MeshFilterComponent component; + + component.SetMeshPath("Meshes/runtime.mesh"); + + EXPECT_EQ(component.GetMeshPath(), "Meshes/runtime.mesh"); + EXPECT_EQ(component.GetMesh(), nullptr); + + component.SetMeshPath(""); + EXPECT_EQ(component.GetMeshPath(), ""); + EXPECT_EQ(component.GetMesh(), nullptr); +} + TEST(MeshRendererComponent_Test, SetMaterialsKeepsSlotsAndFlags) { GameObject gameObject("RendererHolder"); auto* component = gameObject.AddComponent(); @@ -122,4 +135,20 @@ TEST(MeshRendererComponent_Test, SerializeAndDeserializePreservesMaterialPathsAn delete material1; } +TEST(MeshRendererComponent_Test, SetMaterialPathPreservesPathWithoutLoadedResource) { + MeshRendererComponent component; + + component.SetMaterialPath(1, "Materials/runtime.mat"); + + ASSERT_EQ(component.GetMaterialCount(), 2u); + EXPECT_EQ(component.GetMaterial(0), nullptr); + EXPECT_EQ(component.GetMaterial(1), nullptr); + EXPECT_EQ(component.GetMaterialPath(0), ""); + EXPECT_EQ(component.GetMaterialPath(1), "Materials/runtime.mat"); + + component.SetMaterialPath(1, ""); + EXPECT_EQ(component.GetMaterialPath(1), ""); + EXPECT_EQ(component.GetMaterial(1), nullptr); +} + } // namespace diff --git a/tests/scripting/test_mono_script_runtime.cpp b/tests/scripting/test_mono_script_runtime.cpp index 0c0a2dc5..eaf0a5ed 100644 --- a/tests/scripting/test_mono_script_runtime.cpp +++ b/tests/scripting/test_mono_script_runtime.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include @@ -315,6 +317,82 @@ TEST_F(MonoScriptRuntimeTest, ManagedBuiltInComponentWrappersReadAndWriteCameraA EXPECT_TRUE(light->GetCastsShadows()); } +TEST_F(MonoScriptRuntimeTest, ManagedMeshComponentWrappersReadAndWritePathsAndFlags) { + Scene* runtimeScene = CreateScene("MonoRuntimeScene"); + GameObject* host = runtimeScene->CreateGameObject("Host"); + MeshFilterComponent* meshFilter = host->AddComponent(); + MeshRendererComponent* meshRenderer = host->AddComponent(); + ScriptComponent* component = AddScript(host, "Gameplay", "MeshComponentProbe"); + + meshFilter->SetMeshPath("Meshes/initial.mesh"); + meshRenderer->SetMaterialPath(0, "Materials/initial.mat"); + meshRenderer->SetCastShadows(true); + meshRenderer->SetReceiveShadows(false); + meshRenderer->SetRenderLayer(7); + + engine->OnRuntimeStart(runtimeScene); + engine->OnUpdate(0.016f); + + bool hasMeshFilter = false; + bool hasMeshRenderer = false; + bool meshFilterLookupSucceeded = false; + bool meshRendererLookupSucceeded = false; + std::string observedInitialMeshPath; + std::string observedUpdatedMeshPath; + int32_t observedInitialMaterialCount = 0; + std::string observedInitialMaterial0Path; + bool observedInitialCastShadows = false; + bool observedInitialReceiveShadows = true; + int32_t observedInitialRenderLayer = 0; + int32_t observedUpdatedMaterialCount = 0; + std::string observedUpdatedMaterial1Path; + bool observedUpdatedCastShadows = true; + bool observedUpdatedReceiveShadows = false; + int32_t observedUpdatedRenderLayer = 0; + + EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasMeshFilter", hasMeshFilter)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "HasMeshRenderer", hasMeshRenderer)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "MeshFilterLookupSucceeded", meshFilterLookupSucceeded)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "MeshRendererLookupSucceeded", meshRendererLookupSucceeded)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialMeshPath", observedInitialMeshPath)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedMeshPath", observedUpdatedMeshPath)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialMaterialCount", observedInitialMaterialCount)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialMaterial0Path", observedInitialMaterial0Path)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialCastShadows", observedInitialCastShadows)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialReceiveShadows", observedInitialReceiveShadows)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedInitialRenderLayer", observedInitialRenderLayer)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedMaterialCount", observedUpdatedMaterialCount)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedMaterial1Path", observedUpdatedMaterial1Path)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedCastShadows", observedUpdatedCastShadows)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedReceiveShadows", observedUpdatedReceiveShadows)); + EXPECT_TRUE(runtime->TryGetFieldValue(component, "ObservedUpdatedRenderLayer", observedUpdatedRenderLayer)); + + EXPECT_TRUE(hasMeshFilter); + EXPECT_TRUE(hasMeshRenderer); + EXPECT_TRUE(meshFilterLookupSucceeded); + EXPECT_TRUE(meshRendererLookupSucceeded); + EXPECT_EQ(observedInitialMeshPath, "Meshes/initial.mesh"); + EXPECT_EQ(observedUpdatedMeshPath, "Meshes/runtime_override.mesh"); + EXPECT_EQ(observedInitialMaterialCount, 1); + EXPECT_EQ(observedInitialMaterial0Path, "Materials/initial.mat"); + EXPECT_TRUE(observedInitialCastShadows); + EXPECT_FALSE(observedInitialReceiveShadows); + EXPECT_EQ(observedInitialRenderLayer, 7); + EXPECT_EQ(observedUpdatedMaterialCount, 2); + EXPECT_EQ(observedUpdatedMaterial1Path, "Materials/runtime_override.mat"); + EXPECT_FALSE(observedUpdatedCastShadows); + EXPECT_TRUE(observedUpdatedReceiveShadows); + EXPECT_EQ(observedUpdatedRenderLayer, 11); + + EXPECT_EQ(meshFilter->GetMeshPath(), "Meshes/runtime_override.mesh"); + ASSERT_EQ(meshRenderer->GetMaterialCount(), 2u); + EXPECT_EQ(meshRenderer->GetMaterialPath(0), "Materials/initial.mat"); + EXPECT_EQ(meshRenderer->GetMaterialPath(1), "Materials/runtime_override.mat"); + EXPECT_FALSE(meshRenderer->GetCastShadows()); + EXPECT_TRUE(meshRenderer->GetReceiveShadows()); + EXPECT_EQ(meshRenderer->GetRenderLayer(), 11u); +} + TEST_F(MonoScriptRuntimeTest, TransformHierarchyApiExposesParentChildAndReparenting) { Scene* runtimeScene = CreateScene("MonoRuntimeScene"); GameObject* root = runtimeScene->CreateGameObject("Root");