diff --git a/Apps/MetaCorePlayer/main.cpp b/Apps/MetaCorePlayer/main.cpp index a57aff5..e33a21e 100644 --- a/Apps/MetaCorePlayer/main.cpp +++ b/Apps/MetaCorePlayer/main.cpp @@ -1,11 +1,16 @@ #include "MetaCorePlatform/MetaCoreWindow.h" +#include "MetaCorePhysics/MetaCorePhysics.h" #include "MetaCoreDelivery/MetaCoreBuildPipeline.h" #include "MetaCoreDelivery/MetaCoreCrashReporter.h" #include "MetaCoreFoundation/MetaCoreGeneratedReflection.h" +#include "MetaCoreFoundation/MetaCoreArchive.h" +#include "MetaCoreFoundation/MetaCoreAssetTypes.h" +#include "MetaCoreFoundation/MetaCorePackage.h" #include "MetaCoreFoundation/MetaCoreProject.h" #include "MetaCoreRender/MetaCoreEditorViewportRenderer.h" #include "MetaCoreRender/MetaCoreRenderDevice.h" #include "MetaCoreRender/MetaCoreRenderTypes.h" +#include "MetaCoreRender/MetaCoreSceneRenderSync.h" #include "MetaCoreRuntimeData/MetaCoreRuntimeDataDispatcher.h" #include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h" #include "MetaCoreRuntimeData/MetaCoreRuntimeDataSource.h" @@ -17,6 +22,8 @@ #include "MetaCoreScripting/MetaCoreScripting.h" #include +#include +#include #include #include #include @@ -25,6 +32,41 @@ namespace { +struct MetaCorePackedPhysicsVertex { float px, py, pz, nx, ny, nz, u, v; }; + +std::unordered_map +MetaCoreLoadPhysicsMeshes(const std::filesystem::path& root, const MetaCore::MetaCoreTypeRegistry& registry) { + std::unordered_map result; + std::error_code error; + if (!std::filesystem::is_directory(root, error)) return result; + for (const auto& entry : std::filesystem::recursive_directory_iterator(root, error)) { + if (error || !entry.is_regular_file() || entry.path().extension() != ".mcasset") continue; + const auto package = MetaCore::MetaCoreReadPackageFile(entry.path(), registry); + if (!package || package->PayloadSections.empty()) continue; + MetaCore::MetaCoreModelAssetDocument model; + if (!MetaCore::MetaCoreDeserializeFromBytes(package->PayloadSections.front(), model, registry)) continue; + for (const auto& mesh : model.GeneratedMeshAssets) { + if (!mesh.AssetGuid.IsValid() || mesh.PayloadIndex >= package->PayloadSections.size()) continue; + const auto& payload = package->PayloadSections[static_cast(mesh.PayloadIndex)]; + if (payload.size() < sizeof(std::uint32_t) * 2U) continue; + std::uint32_t vertexCount = 0, indexCount = 0; + std::memcpy(&vertexCount, payload.data(), sizeof(vertexCount)); + std::memcpy(&indexCount, payload.data() + sizeof(vertexCount), sizeof(indexCount)); + const std::size_t required = sizeof(std::uint32_t) * 2U + sizeof(MetaCorePackedPhysicsVertex) * vertexCount + sizeof(std::uint32_t) * indexCount; + if (vertexCount == 0U || indexCount < 3U || required > payload.size()) continue; + MetaCore::MetaCorePhysicsMeshData data; data.Positions.resize(vertexCount); data.Indices.resize(indexCount); + const std::byte* cursor = payload.data() + sizeof(std::uint32_t) * 2U; + for (std::uint32_t index = 0; index < vertexCount; ++index) { + MetaCorePackedPhysicsVertex vertex{}; std::memcpy(&vertex, cursor, sizeof(vertex)); cursor += sizeof(vertex); + data.Positions[index] = {vertex.px, vertex.pz, -vertex.py}; + } + std::memcpy(data.Indices.data(), cursor, sizeof(std::uint32_t) * indexCount); + result.insert_or_assign(mesh.AssetGuid, std::move(data)); + } + } + return result; +} + class MetaCoreTeeBuffer final : public std::streambuf { public: MetaCoreTeeBuffer(std::streambuf* first, std::streambuf* second) : First_(first), Second_(second) {} @@ -80,6 +122,7 @@ MetaCore::MetaCoreSceneView MetaCoreBuildPlayerSceneView() { document.DiagnosticsPath = std::filesystem::path("Runtime") / "Diagnostics.mcruntimestate"; document.UiManifestPath = std::filesystem::path("Runtime") / "Ui.mcruntime"; document.InputMapPath = std::filesystem::path("Runtime") / "Input.mcruntime"; + document.PhysicsSettingsPath = std::filesystem::path("Runtime") / "Physics.mcruntime"; return document; } @@ -347,8 +390,10 @@ int main(int argc, char* argv[]) { const std::filesystem::path projectPath = MetaCore::MetaCoreGetProjectFilePath(projectRoot); viewportRenderer.SetProjectRootPath(projectRoot); MetaCore::MetaCoreTypeRegistry typeRegistry; + MetaCore::MetaCoreRegisterFoundationGeneratedTypes(typeRegistry); MetaCore::MetaCoreRegisterRuntimeDataGeneratedTypes(typeRegistry); MetaCore::MetaCoreRegisterRuntimeInputGeneratedTypes(typeRegistry); + MetaCore::MetaCoreRegisterPhysicsGeneratedTypes(typeRegistry); const auto loadedRuntimeProjectDocument = MetaCore::MetaCoreReadRuntimeProjectDocument( deliveryDocument ? packageRoot / deliveryDocument->RuntimeConfig : projectRoot / "Runtime" / "ProjectRuntime.mcruntimecfg", typeRegistry @@ -426,14 +471,46 @@ int main(int argc, char* argv[]) { (void)runtimeInput.LoadOverrides(inputOverridePath); for (const auto& warning : runtimeInput.GetWarnings()) errors << "MetaCorePlayer Input: " << warning << '\n'; + const auto physicsSettingsPath = projectRoot / (runtimeProjectDocument.PhysicsSettingsPath.empty() + ? std::filesystem::path("Runtime/Physics.mcruntime") : runtimeProjectDocument.PhysicsSettingsPath); + auto physicsSettings = MetaCore::MetaCoreReadPhysicsSettings(physicsSettingsPath, typeRegistry) + .value_or(MetaCore::MetaCoreBuildDefaultPhysicsSettings()); + std::unordered_map physicsMaterials; + std::error_code physicsAssetError; + const auto physicsAssetsRoot=projectRoot/"Assets"; + if(std::filesystem::is_directory(physicsAssetsRoot,physicsAssetError))for(const auto& entry:std::filesystem::recursive_directory_iterator(physicsAssetsRoot,physicsAssetError))if(entry.is_regular_file()&&entry.path().extension()==".mcphysicsmaterial")if(auto material=MetaCore::MetaCoreReadPhysicsMaterial(entry.path(),typeRegistry))physicsMaterials.insert_or_assign(material->AssetGuid,*material); + auto physicsMeshes = MetaCoreLoadPhysicsMeshes(projectRoot, typeRegistry); + MetaCore::MetaCorePhysicsWorld physicsWorld(scene, physicsSettings, + [&physicsMeshes](MetaCore::MetaCoreAssetGuid guid)->std::optional{const auto it=physicsMeshes.find(guid);return it==physicsMeshes.end()?std::nullopt:std::optional(it->second);}, + [&physicsMaterials](MetaCore::MetaCoreAssetGuid guid)->std::optional{const auto it=physicsMaterials.find(guid);return it==physicsMaterials.end()?std::nullopt:std::optional(it->second);}); MetaCore::MetaCoreScriptRuntime scriptRuntime( scene, scriptRegistry, [&output, &errors](std::uint32_t level, std::string_view category, std::string_view message) { std::ostream& stream = level >= 2U ? errors : output; stream << "MetaCorePlayer Script[" << category << "]: " << message << '\n'; - }, &runtimeInput + }, &runtimeInput, &physicsWorld ); scriptRuntime.Start(); + physicsWorld.SetEventCallback([&scriptRuntime](const MetaCore::MetaCorePhysicsContactEvent& event) { + const auto topic = [&]() -> const char* { + using E = MetaCore::MetaCorePhysicsEventType; + switch (event.Type) { + case E::CollisionEnter: return "MetaCore.Physics.CollisionEnter"; + case E::CollisionStay: return "MetaCore.Physics.CollisionStay"; + case E::CollisionExit: return "MetaCore.Physics.CollisionExit"; + case E::TriggerEnter: return "MetaCore.Physics.TriggerEnter"; + case E::TriggerStay: return "MetaCore.Physics.TriggerStay"; + case E::TriggerExit: return "MetaCore.Physics.TriggerExit"; + case E::ConstraintBroken: return "MetaCore.Physics.ConstraintBroken"; + } + return "MetaCore.Physics"; + }(); + const auto objectValue = [&scriptRuntime](MetaCore::MetaCoreId id) { MetaCoreScriptValueV1 value{}; value.Type = MetaCoreScriptAbiValue_GameObjectRef; value.ObjectValue = {scriptRuntime.GetWorldGeneration(), id}; return value; }; + const auto vectorValue = [](glm::vec3 source) { MetaCoreScriptValueV1 value{}; value.Type = MetaCoreScriptAbiValue_Vec3; value.Vec3[0]=source.x; value.Vec3[1]=source.y; value.Vec3[2]=source.z; return value; }; + MetaCoreScriptValueV1 impulse{}; impulse.Type = MetaCoreScriptAbiValue_Float; impulse.NumberValue = event.Impulse; + scriptRuntime.PublishToObject(event.ObjectA, topic, {objectValue(event.ObjectB), objectValue(event.ColliderA), objectValue(event.ColliderB), vectorValue(event.Point), vectorValue(event.Normal), impulse}); + scriptRuntime.PublishToObject(event.ObjectB, topic, {objectValue(event.ObjectA), objectValue(event.ColliderB), objectValue(event.ColliderA), vectorValue(event.Point), vectorValue(-event.Normal), impulse}); + }); const auto sourcesPath = (projectRoot / runtimeProjectDocument.DataSourcesPath).lexically_normal(); const auto bindingsPath = (projectRoot / runtimeProjectDocument.BindingsPath).lexically_normal(); @@ -535,6 +612,8 @@ int main(int argc, char* argv[]) { std::uint64_t diagnosticsWriteFrame = 0; std::uint64_t renderedFrames = 0U; + double fixedAccumulatorSeconds = 0.0; + MetaCore::MetaCoreSceneRenderSync interactionCameraSync; while (!window.ShouldClose()) { window.BeginFrame(); const double frameDeltaSeconds = window.GetDeltaSeconds(); @@ -542,8 +621,21 @@ int main(int argc, char* argv[]) { runtimeInput.Update(window.GetInput(), uiConsumption); const auto [inputWidth, inputHeight] = window.GetWindowSize(); const MetaCore::MetaCoreViewportRect inputViewport{0.0F, 0.0F, static_cast(inputWidth), static_cast(inputHeight)}; - runtimeInteraction.Update(scene, viewportRenderer, window.GetInput(), inputViewport, uiConsumption.Mouse); - scriptRuntime.FixedUpdate(1.0 / 60.0); + MetaCore::MetaCoreSceneView interactionSceneView = MetaCoreBuildPlayerSceneView(); + const auto interactionSnapshot = interactionCameraSync.BuildSnapshot(scene); + (void)MetaCore::MetaCoreSceneRenderSync::TryBuildSceneViewFromPrimaryCamera(interactionSnapshot, interactionSceneView); + runtimeInteraction.Update(scene, viewportRenderer, physicsWorld, interactionSceneView, + window.GetInput(), inputViewport, uiConsumption.Mouse); + fixedAccumulatorSeconds += std::clamp(frameDeltaSeconds, 0.0, physicsSettings.MaxFrameDelta); + std::uint32_t fixedSteps = 0; + while (fixedAccumulatorSeconds >= physicsSettings.FixedTimeStep && fixedSteps < physicsSettings.MaxFixedStepsPerFrame) { + scriptRuntime.FixedUpdate(physicsSettings.FixedTimeStep); + physicsWorld.Step(physicsSettings.FixedTimeStep); + fixedAccumulatorSeconds -= physicsSettings.FixedTimeStep; + ++fixedSteps; + } + if (fixedSteps == physicsSettings.MaxFixedStepsPerFrame && fixedAccumulatorSeconds >= physicsSettings.FixedTimeStep) + fixedAccumulatorSeconds = 0.0; scriptRuntime.Update(frameDeltaSeconds); const auto [windowWidth, windowHeight] = window.GetWindowSize(); viewportRenderer.SetViewportRect(MetaCore::MetaCoreViewportRect{ @@ -611,6 +703,7 @@ int main(int argc, char* argv[]) { if (smokeTestFrames > 0U && renderedFrames >= smokeTestFrames) break; } + physicsWorld.Stop(); scriptRuntime.Stop(); runtimeInput.Unsubscribe(inputSubscription); (void)runtimeInput.SaveOverrides(inputOverridePath); diff --git a/CMakeLists.txt b/CMakeLists.txt index 855bbe0..cd6390d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,7 @@ endif() find_package(glm CONFIG REQUIRED) +find_package(Bullet CONFIG REQUIRED) find_package(crashpad CONFIG QUIET) set(METACORE_IMGUI_USES_PKG_CONFIG FALSE) if(NOT METACORE_BUILD_CORE_ONLY) @@ -502,6 +503,22 @@ target_link_libraries(MetaCoreScene target_compile_options(MetaCoreScene PRIVATE ${METACORE_COMMON_WARNINGS}) +metacore_generate_reflection( + Physics + MetaCoreRegisterPhysicsGeneratedTypes + METACORE_PHYSICS_GENERATED_SOURCE + Source/MetaCorePhysics/Public/MetaCorePhysics/MetaCorePhysics.h +) +add_library(MetaCorePhysics STATIC + Source/MetaCorePhysics/Public/MetaCorePhysics/MetaCorePhysics.h + Source/MetaCorePhysics/Private/MetaCorePhysicsSettings.cpp + Source/MetaCorePhysics/Private/MetaCoreBulletPhysics.cpp + ${METACORE_PHYSICS_GENERATED_SOURCE} +) +target_include_directories(MetaCorePhysics PUBLIC Source/MetaCorePhysics/Public PRIVATE ${BULLET_INCLUDE_DIRS}) +target_link_libraries(MetaCorePhysics PUBLIC MetaCoreFoundation MetaCoreScene glm::glm PRIVATE ${BULLET_LIBRARIES}) +target_compile_options(MetaCorePhysics PRIVATE ${METACORE_COMMON_WARNINGS}) + add_library(MetaCoreScripting STATIC Source/MetaCoreScripting/Public/MetaCoreScripting/MetaCoreScriptAbi.h Source/MetaCoreScripting/Public/MetaCoreScripting/MetaCoreScripting.h @@ -511,7 +528,7 @@ target_include_directories(MetaCoreScripting PUBLIC Source/MetaCoreScripting/Public PRIVATE third_party ) -target_link_libraries(MetaCoreScripting PUBLIC MetaCoreFoundation MetaCoreScene MetaCoreRuntimeInput glm::glm) +target_link_libraries(MetaCoreScripting PUBLIC MetaCoreFoundation MetaCoreScene MetaCoreRuntimeInput MetaCorePhysics glm::glm) if(UNIX AND NOT APPLE) target_link_libraries(MetaCoreScripting PRIVATE dl) endif() @@ -596,7 +613,7 @@ add_library(MetaCoreRuntimeInteraction STATIC Source/MetaCoreRuntimeInput/Private/MetaCoreRuntimeInteraction.cpp ) target_include_directories(MetaCoreRuntimeInteraction PUBLIC Source/MetaCoreRuntimeInput/Public) -target_link_libraries(MetaCoreRuntimeInteraction PUBLIC MetaCoreRuntimeInput MetaCoreRender MetaCoreScene) +target_link_libraries(MetaCoreRuntimeInteraction PUBLIC MetaCoreRuntimeInput MetaCoreRender MetaCoreScene MetaCorePhysics) target_compile_options(MetaCoreRuntimeInteraction PRIVATE ${METACORE_COMMON_WARNINGS}) endif() @@ -753,6 +770,7 @@ target_link_libraries(MetaCoreEditor MetaCoreRuntimeUi MetaCoreScene MetaCoreScripting + MetaCorePhysics MetaCoreDelivery glm::glm imgui::imgui @@ -876,6 +894,11 @@ if(METACORE_BUILD_TESTS) target_compile_options(MetaCoreRuntimeInputTests PRIVATE ${METACORE_COMMON_WARNINGS}) add_test(NAME MetaCoreRuntimeInputTests COMMAND MetaCoreRuntimeInputTests) + add_executable(MetaCorePhysicsTests tests/MetaCorePhysicsTests.cpp) + target_link_libraries(MetaCorePhysicsTests PRIVATE MetaCorePhysics) + target_compile_options(MetaCorePhysicsTests PRIVATE ${METACORE_COMMON_WARNINGS}) + add_test(NAME MetaCorePhysicsTests COMMAND MetaCorePhysicsTests) + add_executable(MetaCoreSmokeTests tests/MetaCoreSmokeTests.cpp Source/MetaCoreRender/Private/MetaCoreGlibcCompat.cpp diff --git a/CMakePresets.json b/CMakePresets.json index 0bdbb6b..f40ffaf 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -41,6 +41,11 @@ "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", "cacheVariables": { "CMAKE_CXX_STANDARD": "20", + "CMAKE_C_COMPILER": "/usr/bin/clang-15", + "CMAKE_CXX_COMPILER": "/usr/bin/clang++-15", + "CMAKE_CXX_FLAGS": "-stdlib=libc++", + "CMAKE_EXE_LINKER_FLAGS": "-stdlib=libc++", + "CMAKE_SHARED_LINKER_FLAGS": "-stdlib=libc++", "VCPKG_TARGET_TRIPLET": "x64-linux-clang-libcxx", "VCPKG_OVERLAY_TRIPLETS": "${sourceDir}/vcpkg-triplets", "METACORE_BUILD_TESTS": "ON" diff --git a/Source/MetaCoreDelivery/Private/MetaCoreBuildPipeline.cpp b/Source/MetaCoreDelivery/Private/MetaCoreBuildPipeline.cpp index 88ead54..37cabfe 100644 --- a/Source/MetaCoreDelivery/Private/MetaCoreBuildPipeline.cpp +++ b/Source/MetaCoreDelivery/Private/MetaCoreBuildPipeline.cpp @@ -1,10 +1,13 @@ #include "MetaCoreDelivery/MetaCoreBuildPipeline.h" #include "MetaCoreFoundation/MetaCoreHash.h" +#include "MetaCoreFoundation/MetaCoreArchive.h" +#include "MetaCoreFoundation/MetaCoreAssetTypes.h" #include "MetaCoreFoundation/MetaCorePackage.h" #include "MetaCoreFoundation/MetaCoreProject.h" #include "MetaCoreFoundation/MetaCoreGeneratedReflection.h" #include "MetaCoreRuntimeInput/MetaCoreInputMap.h" +#include "MetaCorePhysics/MetaCorePhysics.h" #include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h" #include "MetaCoreScene/MetaCoreScenePackage.h" #include "MetaCoreScene/MetaCoreSceneSerializer.h" @@ -23,6 +26,7 @@ #include #include #include +#include #if defined(__linux__) #include #endif @@ -32,6 +36,53 @@ namespace { using json = nlohmann::json; +std::unordered_set MetaCoreCollectCookablePhysicsMeshes( + const std::filesystem::path& projectRoot, + const MetaCoreTypeRegistry& registry +) { + std::unordered_set result; + for (const auto& relativeRoot : {std::filesystem::path("Assets"), std::filesystem::path("Library")}) { + const auto root = projectRoot / relativeRoot; std::error_code error; + if (!std::filesystem::is_directory(root, error)) continue; + for (const auto& entry : std::filesystem::recursive_directory_iterator(root, error)) { + if (error || !entry.is_regular_file() || entry.path().extension() != ".mcasset") continue; + const auto package = MetaCoreReadPackageFile(entry.path(), registry); + if (!package || package->PayloadSections.empty()) continue; + MetaCoreModelAssetDocument model; + if (!MetaCoreDeserializeFromBytes(package->PayloadSections.front(), model, registry)) continue; + for (const auto& mesh : model.GeneratedMeshAssets) { + if (mesh.AssetGuid.IsValid() && mesh.PayloadIndex < package->PayloadSections.size() && + package->PayloadSections[static_cast(mesh.PayloadIndex)].size() >= sizeof(std::uint32_t) * 2U) { + result.insert(mesh.AssetGuid); + } + } + } + } + return result; +} + +std::optional MetaCoreValidateScenePhysicsForCook( + const MetaCoreSceneDocument& scene, + const std::unordered_set& cookableMeshes +) { + for (const auto& object : scene.GameObjects) { + if (object.CharacterController && object.RigidBody) { + return "Object " + std::to_string(object.Id) + " has both CharacterController and RigidBody"; + } + if (!object.Collider || !object.Collider->Enabled) continue; + const auto shape = object.Collider->Shape; + if (shape != MetaCoreColliderShape::ConvexHull && shape != MetaCoreColliderShape::TriangleMesh) continue; + if (!object.Collider->MeshAssetGuid.IsValid() || !cookableMeshes.contains(object.Collider->MeshAssetGuid)) { + return "Object " + std::to_string(object.Id) + " is missing collision mesh payload " + object.Collider->MeshAssetGuid.ToString(); + } + if (shape == MetaCoreColliderShape::TriangleMesh && object.RigidBody && + object.RigidBody->BodyType != MetaCoreRigidBodyType::Static) { + return "Object " + std::to_string(object.Id) + " uses a concave TriangleMesh on a non-static body"; + } + } + return std::nullopt; +} + [[nodiscard]] std::string ConfigurationName(MetaCoreBuildConfiguration configuration) { switch (configuration) { case MetaCoreBuildConfiguration::Debug: return "Debug"; @@ -617,8 +668,10 @@ MetaCoreBuildResult MetaCoreBuildPipeline::Run(const MetaCoreBuildRequest& reque report(MetaCoreBuildStage::Cook, "Cooking startup scene and project content"); MetaCoreTypeRegistry inputRegistry; + MetaCoreRegisterFoundationGeneratedTypes(inputRegistry); MetaCoreRegisterRuntimeDataGeneratedTypes(inputRegistry); MetaCoreRegisterRuntimeInputGeneratedTypes(inputRegistry); + MetaCoreRegisterPhysicsGeneratedTypes(inputRegistry); const auto runtimeProjectPath = request.ProjectRoot / project->RuntimeDirectory / "ProjectRuntime.mcruntimecfg"; const auto runtimeProject = MetaCoreReadRuntimeProjectDocument(runtimeProjectPath, inputRegistry); if (!runtimeProject) return fail(MetaCoreBuildErrorCode::CookFailed, "Runtime project configuration is missing or unreadable", runtimeProjectPath); @@ -630,17 +683,30 @@ MetaCoreBuildResult MetaCoreBuildPipeline::Run(const MetaCoreBuildRequest& reque const auto inputIssues = MetaCoreValidateInputMap(*inputMap); if (std::any_of(inputIssues.begin(), inputIssues.end(), [](const auto& issue) { return issue.Error; })) return fail(MetaCoreBuildErrorCode::CookFailed, "Runtime input map validation failed", inputMapPath); + const auto physicsRelativePath = runtimeProject->PhysicsSettingsPath.empty() ? std::filesystem::path("Runtime/Physics.mcruntime") : runtimeProject->PhysicsSettingsPath; + if (!IsSafeRelativePath(physicsRelativePath)) return fail(MetaCoreBuildErrorCode::UnsafePath, "Runtime physics settings path is unsafe", physicsRelativePath); + const auto physicsPath = request.ProjectRoot / physicsRelativePath; + auto physicsSettings = MetaCoreReadPhysicsSettings(physicsPath, inputRegistry); + if (!physicsSettings) return fail(MetaCoreBuildErrorCode::CookFailed, "Runtime physics settings are missing or unreadable", physicsPath); + std::vector physicsIssues; + if (!MetaCoreValidatePhysicsSettings(*physicsSettings, &physicsIssues)) return fail(MetaCoreBuildErrorCode::CookFailed, "Runtime physics settings validation failed", physicsPath); std::filesystem::path startupSource = profile->StartupScene.empty() ? project->StartupScenePath : profile->StartupScene; if (!IsSafeRelativePath(startupSource)) return fail(MetaCoreBuildErrorCode::UnsafePath, "Startup scene path is unsafe", startupSource); const auto absoluteStartup = request.ProjectRoot / startupSource; std::filesystem::path cookedSceneName = startupSource.filename(); if (cookedSceneName.extension() == ".json") cookedSceneName.replace_extension(""); const auto cookedScene = staging / "Content" / "Scenes" / cookedSceneName; + const auto cookablePhysicsMeshes = MetaCoreCollectCookablePhysicsMeshes(request.ProjectRoot, inputRegistry); if (startupSource.extension() == ".json") { const auto registry = MetaCoreBuildScenePackageTypeRegistry(); const auto scene = MetaCoreSceneSerializer::LoadSceneFromJson(absoluteStartup, registry); + if (scene) if (const auto issue = MetaCoreValidateScenePhysicsForCook(*scene, cookablePhysicsMeshes)) + return fail(MetaCoreBuildErrorCode::CookFailed, *issue, absoluteStartup); if (!scene || !MetaCoreWriteScenePackage(cookedScene, *scene)) return fail(MetaCoreBuildErrorCode::CookFailed, "Failed to cook startup scene", absoluteStartup); } else { + if (const auto scene = MetaCoreReadScenePackage(absoluteStartup); scene.has_value()) + if (const auto issue = MetaCoreValidateScenePhysicsForCook(*scene, cookablePhysicsMeshes)) + return fail(MetaCoreBuildErrorCode::CookFailed, *issue, absoluteStartup); std::filesystem::copy_file(absoluteStartup, cookedScene, std::filesystem::copy_options::overwrite_existing, error); if (error) return fail(MetaCoreBuildErrorCode::CookFailed, "Failed to copy startup scene", absoluteStartup); } diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp index 07d54f4..9fb2c08 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinCoreServicesModule.cpp @@ -11,11 +11,13 @@ #include "MetaCoreFoundation/MetaCoreReflection.h" #include "MetaCoreFoundation/MetaCoreProject.h" #include "MetaCorePlatform/MetaCoreInput.h" +#include "MetaCorePhysics/MetaCorePhysics.h" #include "MetaCoreRuntimeInput/MetaCoreRuntimeInput.h" #include "MetaCoreRuntimeInput/MetaCoreRuntimeInteraction.h" #include "MetaCoreRuntimeUi/MetaCoreRuntimeUiSystem.h" #include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h" #include "MetaCoreRender/MetaCoreEditorViewportRenderer.h" +#include "MetaCoreRender/MetaCoreSceneRenderSync.h" #include "MetaCoreFoundation/MetaCoreGeneratedReflection.h" #include "MetaCoreFoundation/MetaCoreAssetRegistry.h" #include "MetaCoreFoundation/MetaCoreAssetDependencyGraph.h" @@ -3600,6 +3602,87 @@ void MetaCoreDrawInteractableComponentInspector(MetaCoreEditorContext& editorCon if (changed) editorContext.GetScene().IncrementRevision(); } +void MetaCoreDrawColliderComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) { + if (!gameObject.HasComponent()) return; + auto& value = gameObject.GetComponent(); bool changed = ImGui::Checkbox("启用", &value.Enabled); + int shape = static_cast(value.Shape); const char* shapes[]{"Box", "Sphere", "Capsule", "Convex Hull", "Triangle Mesh"}; + if (ImGui::Combo("形状", &shape, shapes, IM_ARRAYSIZE(shapes))) { value.Shape=static_cast(shape); changed=true; } + changed |= ImGui::DragFloat3("中心", &value.Center.x, 0.01F); + if (value.Shape == MetaCoreColliderShape::Box) changed |= ImGui::DragFloat3("尺寸", &value.Size.x, 0.01F, 0.001F, 100000.0F); + if (value.Shape == MetaCoreColliderShape::Sphere || value.Shape == MetaCoreColliderShape::Capsule) changed |= ImGui::DragFloat("半径", &value.Radius, 0.01F, 0.001F, 100000.0F); + if (value.Shape == MetaCoreColliderShape::Capsule) changed |= ImGui::DragFloat("高度", &value.Height, 0.01F, 0.002F, 100000.0F); + if (value.Shape == MetaCoreColliderShape::ConvexHull || value.Shape == MetaCoreColliderShape::TriangleMesh) { + changed |= MetaCoreEditAssetGuidField(editorContext, "Mesh Asset GUID", value.MeshAssetGuid, "拖入或粘贴 Mesh GUID"); + } + changed |= MetaCoreEditAssetGuidField(editorContext, "物理材质 GUID", value.MaterialAssetGuid, "可选"); + int layer=static_cast(value.CollisionLayer); if (ImGui::SliderInt("碰撞层", &layer, 0, 31)) { value.CollisionLayer=static_cast(layer); changed=true; } + changed |= ImGui::Checkbox("Trigger", &value.IsTrigger); + if ((value.Shape==MetaCoreColliderShape::ConvexHull || value.Shape==MetaCoreColliderShape::TriangleMesh) && !value.MeshAssetGuid.IsValid()) ImGui::TextColored({1.0F,0.55F,0.2F,1.0F}, "需要指定 Mesh Asset GUID"); + if (changed) editorContext.GetScene().IncrementRevision(); +} + +void MetaCoreDrawRigidBodyComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) { + if (!gameObject.HasComponent()) return; auto& value=gameObject.GetComponent(); bool changed=false; + int type=static_cast(value.BodyType); const char* types[]{"Static","Kinematic","Dynamic"}; if(ImGui::Combo("类型",&type,types,3)){value.BodyType=static_cast(type);changed=true;} + changed|=ImGui::DragFloat("质量",&value.Mass,0.05F,0.0001F,1000000.0F); changed|=ImGui::SliderFloat("线性阻尼",&value.LinearDamping,0.0F,1.0F); + changed|=ImGui::SliderFloat("角阻尼",&value.AngularDamping,0.0F,1.0F); changed|=ImGui::DragFloat("重力倍率",&value.GravityScale,0.05F,-100.0F,100.0F); + changed|=ImGui::Checkbox("允许休眠",&value.AllowSleep); changed|=ImGui::Checkbox("CCD",&value.ContinuousCollisionDetection); + if (gameObject.HasComponent()) ImGui::TextColored({1.0F,0.25F,0.2F,1.0F}, "Character Controller 与 RigidBody 不能共存"); + if(changed)editorContext.GetScene().IncrementRevision(); +} + +void MetaCoreDrawPhysicsConstraintComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) { + if(!gameObject.HasComponent())return; auto& value=gameObject.GetComponent(); bool changed=ImGui::Checkbox("启用",&value.Enabled); + int type=static_cast(value.Type); const char* types[]{"Fixed","Hinge","Slider","6DoF"}; if(ImGui::Combo("约束类型",&type,types,4)){value.Type=static_cast(type);changed=true;} + std::uint64_t target=value.TargetObjectId; if(ImGui::InputScalar("目标对象 ID",ImGuiDataType_U64,&target)){value.TargetObjectId=target;changed=true;} + changed|=ImGui::DragFloat3("本地锚点",&value.Anchor.x,0.01F); changed|=ImGui::DragFloat3("目标锚点",&value.TargetAnchor.x,0.01F); + changed|=ImGui::Checkbox("启用 Motor",&value.MotorEnabled); if(value.MotorEnabled){changed|=ImGui::DragFloat("目标速度",&value.MotorTargetVelocity,0.05F);changed|=ImGui::DragFloat("最大冲量",&value.MotorMaxImpulse,0.05F,0.0F);} + changed|=ImGui::DragFloat("断裂力",&value.BreakForce,0.1F,0.0F); changed|=ImGui::Checkbox("连接体碰撞",&value.EnableConnectedCollision); + if(changed)editorContext.GetScene().IncrementRevision(); +} + +void MetaCoreDrawCharacterControllerComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) { + if(!gameObject.HasComponent())return; auto& value=gameObject.GetComponent(); bool changed=ImGui::Checkbox("启用",&value.Enabled); + changed|=ImGui::DragFloat("半径",&value.Radius,0.01F,0.001F); changed|=ImGui::DragFloat("高度",&value.Height,0.01F,0.002F); changed|=ImGui::DragFloat("Skin",&value.SkinWidth,0.001F,0.001F); + changed|=ImGui::SliderFloat("最大坡度",&value.MaxSlopeDegrees,0.0F,89.0F); changed|=ImGui::DragFloat("台阶高度",&value.StepHeight,0.01F,0.0F); + changed|=ImGui::DragFloat("最大下落速度",&value.MaxFallSpeed,0.1F,0.0F); changed|=ImGui::DragFloat("推动力",&value.PushForce,0.05F,0.0F); + if(gameObject.HasComponent())ImGui::TextColored({1.0F,0.25F,0.2F,1.0F},"请移除 RigidBody 后再使用角色控制器"); + if(changed)editorContext.GetScene().IncrementRevision(); +} + +template +std::optional> MetaCoreSerializeComponentValue( + const TComponent& component, + const MetaCoreTypeRegistry& registry +); + +template +bool MetaCoreDeserializeComponentValue( + std::span payload, + TComponent& component, + const MetaCoreTypeRegistry& registry +); + +template +MetaCoreIComponentTypeRegistry::MetaCoreComponentDescriptor MetaCoreMakePhysicsComponentDescriptor(std::string typeId, std::string displayName, + std::function draw) { + return MetaCoreIComponentTypeRegistry::MetaCoreComponentDescriptor{ + std::move(typeId), std::move(displayName), + [](const MetaCoreGameObject& object){return object.HasComponent();}, + [](MetaCoreGameObject& object){ + if(object.HasComponent())return false; + if constexpr(std::is_same_v) if(object.HasComponent())return false; + if constexpr(std::is_same_v) if(object.HasComponent())return false; + object.AddComponent(T{}); return true; + }, + [](MetaCoreGameObject& object){if(!object.HasComponent())return false;object.RemoveComponent();return true;}, + [](MetaCoreGameObject& object){if(!object.HasComponent())return false;object.GetComponent()={};return true;}, + [](const MetaCoreGameObject& object,const MetaCoreTypeRegistry& registry)->std::optional>{if(!object.HasComponent())return std::nullopt;return MetaCoreSerializeComponentValue(object.GetComponent(),registry);}, + [](MetaCoreGameObject& object,std::span payload,const MetaCoreTypeRegistry& registry){T value{};if(!MetaCoreDeserializeComponentValue(payload,value,registry))return false;if(object.HasComponent())object.GetComponent()=value;else object.AddComponent(value);return true;}, + std::move(draw) + }; +} + void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) { if (!gameObject.HasComponent()) { return; @@ -4939,6 +5022,8 @@ bool MetaCoreDeserializeComponentValue( return false; } +[[nodiscard]] bool MetaCoreIsPhysicsMaterialPath(const std::filesystem::path& path) { return path.extension() == ".mcphysicsmaterial"; } + [[nodiscard]] bool MetaCoreIsUiPath(const std::filesystem::path& path) { const std::string filename = path.filename().string(); if (filename.length() > 10 && filename.compare(filename.length() - 10, 10, ".mcui.json") == 0) { @@ -5213,6 +5298,7 @@ void MetaCoreWriteEmbeddedTextureRuntimePackages( if (filename.length() > 16 && filename.compare(filename.length() - 16, 16, ".mcmaterial.json") == 0) { return "MaterialImporter"; } + if (path.extension() == ".mcphysicsmaterial") return "PhysicsMaterialImporter"; if (filename.length() > 10 && filename.compare(filename.length() - 10, 10, ".mcui.json") == 0) { return "UiDocumentImporter"; } @@ -5252,6 +5338,7 @@ void MetaCoreWriteEmbeddedTextureRuntimePackages( if (filename.length() > 16 && filename.compare(filename.length() - 16, 16, ".mcmaterial.json") == 0) { return "material"; } + if (path.extension() == ".mcphysicsmaterial") return "physics_material"; if (filename.length() > 10 && filename.compare(filename.length() - 10, 10, ".mcui.json") == 0) { return "ui_document"; } @@ -6184,6 +6271,7 @@ public: MetaCoreRegisterSceneGeneratedTypes(Registry_); MetaCoreRegisterEditorGeneratedTypes(Registry_); MetaCoreRegisterRuntimeDataGeneratedTypes(Registry_); + MetaCoreRegisterPhysicsGeneratedTypes(Registry_); } [[nodiscard]] const MetaCoreTypeRegistry& GetTypeRegistry() const override { return Registry_; } @@ -6409,12 +6497,19 @@ bool MetaCoreBuiltinAssetDatabaseService::CreateProject(const std::filesystem::p } MetaCoreTypeRegistry registry; + MetaCoreRegisterFoundationGeneratedTypes(registry); MetaCoreRegisterRuntimeInputGeneratedTypes(registry); + MetaCoreRegisterPhysicsGeneratedTypes(registry); const std::filesystem::path inputMapPath = *normalizedRoot / "Runtime" / "Input.mcruntime"; if (!std::filesystem::exists(inputMapPath) && !MetaCoreWriteInputMap(inputMapPath, MetaCoreBuildDefaultInputMap(), registry)) { return false; } + const std::filesystem::path physicsSettingsPath = *normalizedRoot / "Runtime" / "Physics.mcruntime"; + if (!std::filesystem::exists(physicsSettingsPath) && + !MetaCoreWritePhysicsSettings(physicsSettingsPath, MetaCoreBuildDefaultPhysicsSettings(), registry)) { + return false; + } return OpenProject(*normalizedRoot); } @@ -10024,6 +10119,10 @@ public: [](MetaCoreGameObject& object, std::span payload, const MetaCoreTypeRegistry& registry) { MetaCoreInteractableComponent value{}; if (!MetaCoreDeserializeComponentValue(payload, value, registry)) return false; if (object.HasComponent()) object.GetComponent() = value; else object.AddComponent(value); return true; }, MetaCoreDrawInteractableComponentInspector }); + Descriptors_.push_back(MetaCoreMakePhysicsComponentDescriptor("Collider", "碰撞体", MetaCoreDrawColliderComponentInspector)); + Descriptors_.push_back(MetaCoreMakePhysicsComponentDescriptor("RigidBody", "刚体", MetaCoreDrawRigidBodyComponentInspector)); + Descriptors_.push_back(MetaCoreMakePhysicsComponentDescriptor("PhysicsConstraint", "物理约束", MetaCoreDrawPhysicsConstraintComponentInspector)); + Descriptors_.push_back(MetaCoreMakePhysicsComponentDescriptor("CharacterController", "角色控制器", MetaCoreDrawCharacterControllerComponentInspector)); } void Shutdown(MetaCoreEditorModuleRegistry& moduleRegistry) override { @@ -10382,6 +10481,7 @@ private: class MetaCoreInputPlayModeComponentSystem final : public MetaCoreIPlayModeComponentSystem { public: [[nodiscard]] std::string GetSystemId() const override { return "MetaCore.PlayMode.Input"; } + void SetPhysicsWorldGetter(std::function getter) { PhysicsWorldGetter_ = std::move(getter); } void OnPlayStart(MetaCoreEditorContext& editorContext) override { MetaCoreTypeRegistry registry; MetaCoreRegisterRuntimeDataGeneratedTypes(registry); MetaCoreRegisterRuntimeInputGeneratedTypes(registry); const auto assets = editorContext.GetModuleRegistry().ResolveService(); @@ -10407,8 +10507,17 @@ public: if (focused) { const MetaCoreInputConsumption consumption = RuntimeUiActive_ ? RuntimeUi_.ProcessInput() : MetaCoreInputConsumption{}; Runtime_.Update(editorContext.GetInput(), consumption); - Interaction_.Update(editorContext.GetScene(), editorContext.GetViewportRenderer(), editorContext.GetInput(), - MetaCoreViewportRect{viewport.Left, viewport.Top, viewport.Width, viewport.Height}, consumption.Mouse); + const MetaCoreViewportRect viewportRect{viewport.Left, viewport.Top, viewport.Width, viewport.Height}; + MetaCoreSceneView sceneView; + const auto snapshot = CameraSync_.BuildSnapshot(editorContext.GetScene()); + (void)MetaCoreSceneRenderSync::TryBuildSceneViewFromPrimaryCamera(snapshot, sceneView); + if (PhysicsWorldGetter_ && PhysicsWorldGetter_()) { + Interaction_.Update(editorContext.GetScene(), editorContext.GetViewportRenderer(), *PhysicsWorldGetter_(), + sceneView, editorContext.GetInput(), viewportRect, consumption.Mouse); + } else { + Interaction_.Update(editorContext.GetScene(), editorContext.GetViewportRenderer(), editorContext.GetInput(), + viewportRect, consumption.Mouse); + } if (RuntimeUiActive_) RuntimeUi_.Update(static_cast(deltaSeconds)); } else { Runtime_.CancelAll(); @@ -10426,13 +10535,83 @@ private: MetaCoreRuntimeInput Runtime_{}; MetaCoreRuntimeInteraction Interaction_{}; MetaCoreRuntimeUiSystem RuntimeUi_{}; + MetaCoreSceneRenderSync CameraSync_{}; + std::function PhysicsWorldGetter_{}; bool RuntimeUiActive_ = false; std::filesystem::path OverridePath_{}; }; +class MetaCorePhysicsPlayModeComponentSystem final : public MetaCoreIPlayModeComponentSystem { +public: + [[nodiscard]] std::string GetSystemId() const override { return "MetaCore.PlayMode.Physics"; } + [[nodiscard]] MetaCorePhysicsWorld* GetWorld() const { return World_.get(); } + MetaCorePhysicsWorld* GetOrCreate(MetaCoreEditorContext& editorContext) { + if (!World_) { + auto settings = MetaCoreBuildDefaultPhysicsSettings(); + const auto assets = editorContext.GetModuleRegistry().ResolveService(); + if (assets && assets->HasProject()) { + MetaCoreTypeRegistry registry; MetaCoreRegisterFoundationGeneratedTypes(registry); MetaCoreRegisterRuntimeDataGeneratedTypes(registry); MetaCoreRegisterPhysicsGeneratedTypes(registry); + const auto root = assets->GetProjectDescriptor().RootPath; + const auto runtimeProject = MetaCoreReadRuntimeProjectDocument(root / "Runtime" / "ProjectRuntime.mcruntimecfg", registry) + .value_or(MetaCoreRuntimeProjectDocument{}); + const auto relativePath = runtimeProject.PhysicsSettingsPath.empty() + ? std::filesystem::path("Runtime/Physics.mcruntime") : runtimeProject.PhysicsSettingsPath; + settings = MetaCoreReadPhysicsSettings(root / relativePath, registry).value_or(settings); + } + MetaCorePhysicsMaterialProvider materials; + MetaCorePhysicsMeshProvider meshes; + if (assets && assets->HasProject()) { + materials = [assets](MetaCoreAssetGuid guid)->std::optional { + const auto record=assets->FindAssetByGuid(guid);if(!record||record->Type!="physics_material")return std::nullopt; + MetaCoreTypeRegistry registry;MetaCoreRegisterFoundationGeneratedTypes(registry);MetaCoreRegisterPhysicsGeneratedTypes(registry); + return MetaCoreReadPhysicsMaterial(assets->GetProjectDescriptor().RootPath/record->RelativePath,registry); + }; + const auto editing = editorContext.GetModuleRegistry().ResolveService(); + const auto packages = editorContext.GetModuleRegistry().ResolveService(); + if (editing && packages) { + meshes = [assets, editing, packages](MetaCoreAssetGuid guid)->std::optional { + struct PackedVertex { float px,py,pz,nx,ny,nz,u,v; }; + const auto resolved = editing->ResolveGeneratedAsset(guid); + const auto mesh = editing->LoadMeshAsset(guid); + if (!resolved || !mesh) return std::nullopt; + const auto relativePackagePath = !resolved->SourceAsset.PackagePath.empty() + ? resolved->SourceAsset.PackagePath : resolved->SourceAsset.RelativePath; + const auto package = packages->ReadPackage(assets->GetProjectDescriptor().RootPath / relativePackagePath); + if (!package || mesh->PayloadIndex >= package->PayloadSections.size()) return std::nullopt; + const auto& payload = package->PayloadSections[static_cast(mesh->PayloadIndex)]; + if (payload.size() < sizeof(std::uint32_t) * 2U) return std::nullopt; + std::uint32_t vertexCount = 0, indexCount = 0; + std::memcpy(&vertexCount, payload.data(), sizeof(vertexCount)); + std::memcpy(&indexCount, payload.data() + sizeof(vertexCount), sizeof(indexCount)); + const std::size_t required = sizeof(std::uint32_t) * 2U + sizeof(PackedVertex) * vertexCount + sizeof(std::uint32_t) * indexCount; + if (vertexCount == 0U || indexCount < 3U || required > payload.size()) return std::nullopt; + MetaCorePhysicsMeshData result; result.Positions.resize(vertexCount); result.Indices.resize(indexCount); + const std::byte* cursor = payload.data() + sizeof(std::uint32_t) * 2U; + for (std::uint32_t index = 0; index < vertexCount; ++index) { + PackedVertex vertex{}; std::memcpy(&vertex, cursor, sizeof(vertex)); cursor += sizeof(vertex); + result.Positions[index] = {vertex.px, vertex.pz, -vertex.py}; + } + std::memcpy(result.Indices.data(), cursor, sizeof(std::uint32_t) * indexCount); + return result; + }; + } + } + World_ = std::make_unique(editorContext.GetScene(), settings, std::move(meshes), std::move(materials)); + for (const auto& issue : World_->GetDiagnostics()) editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Physics", issue); + } + return World_.get(); + } + void OnPlayStart(MetaCoreEditorContext& editorContext) override { (void)GetOrCreate(editorContext); } + void FixedUpdate(MetaCoreEditorContext& editorContext, double fixedDeltaSeconds) override { if (auto* world = GetOrCreate(editorContext)) world->Step(fixedDeltaSeconds); } + void OnPlayStop(MetaCoreEditorContext&) override { if (World_) World_->Stop(); World_.reset(); } +private: + std::unique_ptr World_{}; +}; + class MetaCoreScriptPlayModeComponentSystem final : public MetaCoreIPlayModeComponentSystem { public: - explicit MetaCoreScriptPlayModeComponentSystem(std::shared_ptr input) : Input_(std::move(input)) {} + MetaCoreScriptPlayModeComponentSystem(std::shared_ptr input, + std::shared_ptr physics) : Input_(std::move(input)), Physics_(std::move(physics)) {} [[nodiscard]] std::string GetSystemId() const override { return "MetaCore.PlayMode.Script"; } void OnPlayStart(MetaCoreEditorContext& editorContext) override { @@ -10443,9 +10622,21 @@ public: [&editorContext](std::uint32_t level, std::string_view category, std::string_view message) { const MetaCoreLogLevel mapped = level >= 2U ? MetaCoreLogLevel::Error : level == 1U ? MetaCoreLogLevel::Warning : MetaCoreLogLevel::Info; editorContext.AddConsoleMessage(mapped, std::string(category), std::string(message)); - }, Input_ ? &Input_->GetRuntime() : nullptr + }, Input_ ? &Input_->GetRuntime() : nullptr, Physics_ ? Physics_->GetOrCreate(editorContext) : nullptr ); Runtime_->Start(); + if (Physics_ && Physics_->GetOrCreate(editorContext)) { + Physics_->GetOrCreate(editorContext)->SetEventCallback([this](const MetaCorePhysicsContactEvent& event) { + if (!Runtime_) return; + const auto topic = [&]() -> const char* { using E=MetaCorePhysicsEventType; switch(event.Type) { + case E::CollisionEnter:return "MetaCore.Physics.CollisionEnter"; case E::CollisionStay:return "MetaCore.Physics.CollisionStay"; case E::CollisionExit:return "MetaCore.Physics.CollisionExit"; + case E::TriggerEnter:return "MetaCore.Physics.TriggerEnter"; case E::TriggerStay:return "MetaCore.Physics.TriggerStay"; case E::TriggerExit:return "MetaCore.Physics.TriggerExit"; + case E::ConstraintBroken:return "MetaCore.Physics.ConstraintBroken"; } return "MetaCore.Physics"; }(); + MetaCoreScriptValueV1 other{}; other.Type=MetaCoreScriptAbiValue_GameObjectRef; + other.ObjectValue={Runtime_->GetWorldGeneration(),event.ObjectB}; Runtime_->PublishToObject(event.ObjectA,topic,{other}); + other.ObjectValue={Runtime_->GetWorldGeneration(),event.ObjectA}; Runtime_->PublishToObject(event.ObjectB,topic,{other}); + }); + } if (Input_) { InputSubscription_ = Input_->GetRuntime().Subscribe([this](const MetaCoreInputActionEvent& event) { if (!Runtime_) return; @@ -10495,10 +10686,12 @@ public: } if (Runtime_ != nullptr) Runtime_->Stop(); Runtime_.reset(); + if (Physics_ && Physics_->GetWorld()) Physics_->GetWorld()->SetEventCallback({}); } private: std::shared_ptr Input_{}; + std::shared_ptr Physics_{}; std::unique_ptr Runtime_{}; std::uint64_t InputSubscription_ = 0; }; @@ -10625,7 +10818,12 @@ public: RegisterSystem(input); RegisterSystem(std::make_shared()); RegisterSystem(std::make_shared()); - RegisterSystem(std::make_shared(std::move(input))); + auto physics = std::make_shared(); + input->SetPhysicsWorldGetter([weakPhysics = std::weak_ptr(physics)] { + const auto value = weakPhysics.lock(); return value ? value->GetWorld() : nullptr; + }); + RegisterSystem(std::make_shared(input, physics)); + RegisterSystem(std::move(physics)); } void Shutdown(MetaCoreEditorModuleRegistry& moduleRegistry) override { @@ -10705,6 +10903,22 @@ public: } MetaCoreDebugLog("PlayMode", "Enter begin " + MetaCoreDebugSceneSummary(editorContext)); + if (const auto assets = editorContext.GetModuleRegistry().ResolveService(); assets && assets->HasProject()) { + MetaCoreTypeRegistry registry; + MetaCoreRegisterFoundationGeneratedTypes(registry); + MetaCoreRegisterRuntimeDataGeneratedTypes(registry); + MetaCoreRegisterPhysicsGeneratedTypes(registry); + const auto root = assets->GetProjectDescriptor().RootPath; + const auto runtimeProject = MetaCoreReadRuntimeProjectDocument(root / "Runtime" / "ProjectRuntime.mcruntimecfg", registry) + .value_or(MetaCoreRuntimeProjectDocument{}); + const auto physicsPath = root / (runtimeProject.PhysicsSettingsPath.empty() + ? std::filesystem::path("Runtime/Physics.mcruntime") : runtimeProject.PhysicsSettingsPath); + if (const auto physics = MetaCoreReadPhysicsSettings(physicsPath, registry)) { + FixedTimeStepSeconds_ = physics->FixedTimeStep; + MaxFrameDeltaSeconds_ = physics->MaxFrameDelta; + MaxFixedStepsPerFrame_ = static_cast(physics->MaxFixedStepsPerFrame); + } + } PrePlaySnapshot_ = editorContext.CaptureStateSnapshot(); HasPrePlaySnapshot_ = true; MetaCoreDebugLog( @@ -10975,8 +11189,8 @@ private: double ElapsedTimeSeconds_ = 0.0; double FixedTimeStepSeconds_ = 1.0 / 60.0; double FixedTimeAccumulatorSeconds_ = 0.0; - double MaxFrameDeltaSeconds_ = 0.1; - int MaxFixedStepsPerFrame_ = 8; + double MaxFrameDeltaSeconds_ = 0.25; + int MaxFixedStepsPerFrame_ = 4; }; std::optional MetaCoreBuiltinPrefabService::FindPrefabAsset(const MetaCoreAssetGuid& prefabAssetGuid) const { @@ -11105,6 +11319,10 @@ std::optional MetaCoreBuiltinPrefabService::CreatePrefabF if (gameObject.HasComponent()) data.Script = gameObject.GetComponent(); if (gameObject.HasComponent()) data.UiRenderer = gameObject.GetComponent(); if (gameObject.HasComponent()) data.MeshRenderer = gameObject.GetComponent(); + if (gameObject.HasComponent()) data.Collider = gameObject.GetComponent(); + if (gameObject.HasComponent()) data.RigidBody = gameObject.GetComponent(); + if (gameObject.HasComponent()) data.PhysicsConstraint = gameObject.GetComponent(); + if (gameObject.HasComponent()) data.CharacterController = gameObject.GetComponent(); prefabDataObjects.push_back(std::move(data)); } prefabDocument.GameObjects = std::move(prefabDataObjects); @@ -11284,6 +11502,10 @@ bool MetaCoreBuiltinPrefabService::ApplySelectedPrefabInstance(MetaCoreEditorCon if (gameObject.HasComponent()) data.Script = gameObject.GetComponent(); if (gameObject.HasComponent()) data.UiRenderer = gameObject.GetComponent(); if (gameObject.HasComponent()) data.MeshRenderer = gameObject.GetComponent(); + if (gameObject.HasComponent()) data.Collider = gameObject.GetComponent(); + if (gameObject.HasComponent()) data.RigidBody = gameObject.GetComponent(); + if (gameObject.HasComponent()) data.PhysicsConstraint = gameObject.GetComponent(); + if (gameObject.HasComponent()) data.CharacterController = gameObject.GetComponent(); prefabDataObjects.push_back(std::move(data)); } diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp index daa6804..6bca815 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp @@ -19,6 +19,7 @@ #include "MetaCoreRuntimeData/MetaCoreRuntimeDataTypes.h" #include "MetaCoreRuntimeInput/MetaCoreInputMap.h" #include "MetaCorePlatform/MetaCoreWindow.h" +#include "MetaCorePhysics/MetaCorePhysics.h" #include "MetaCoreFoundation/MetaCoreAssetRegistry.h" #include "MetaCoreRender/MetaCoreEditorViewportRenderer.h" @@ -82,6 +83,7 @@ static bool PendingDeleteIsDirectory_ = false; enum class MetaCoreProjectCreateKind { Folder, Material, + PhysicsMaterial, Scene, Prefab, UiDocument @@ -3478,6 +3480,29 @@ private: bool Dirty_ = false; }; +class MetaCorePhysicsSettingsPanelProvider final : public MetaCoreIEditorPanelProvider { +public: + std::string GetPanelId() const override { return "PhysicsSettings"; } + std::string GetPanelTitle() const override { return "物理设置"; } + bool IsOpenByDefault() const override { return false; } + void DrawPanel(MetaCoreEditorContext& editorContext) override { + const auto assets=editorContext.GetModuleRegistry().ResolveService(); + if(!assets||!assets->HasProject()){ImGui::TextDisabled("没有打开项目。");return;} + const auto root=assets->GetProjectDescriptor().RootPath; + if(root!=LoadedRoot_){MetaCoreTypeRegistry registry;MetaCoreRegisterFoundationGeneratedTypes(registry);MetaCoreRegisterPhysicsGeneratedTypes(registry);Path_=root/"Runtime"/"Physics.mcruntime";Settings_=MetaCoreReadPhysicsSettings(Path_,registry).value_or(MetaCoreBuildDefaultPhysicsSettings());LoadedRoot_=root;Dirty_=false;} + Dirty_|=ImGui::DragFloat3("重力",&Settings_.Gravity.x,0.05F); + float step=static_cast(Settings_.FixedTimeStep);if(ImGui::DragFloat("固定步长",&step,0.001F,0.001F,1.0F,"%.4f")){Settings_.FixedTimeStep=step;Dirty_=true;} + int maxSteps=static_cast(Settings_.MaxFixedStepsPerFrame);if(ImGui::SliderInt("每帧最大步数",&maxSteps,1,32)){Settings_.MaxFixedStepsPerFrame=static_cast(maxSteps);Dirty_=true;} + if(ImGui::CollapsingHeader("碰撞层与矩阵",ImGuiTreeNodeFlags_DefaultOpen)){ + for(std::uint32_t i=0;i<32U;++i){ImGui::PushID(static_cast(i));std::array name{};std::snprintf(name.data(),name.size(),"%s",Settings_.LayerNames[i].c_str());if(ImGui::InputText("层名称",name.data(),name.size())){Settings_.LayerNames[i]=name.data();Dirty_=true;}if(ImGui::TreeNode("碰撞对象")){for(std::uint32_t j=0;j<32U;++j){bool enabled=((Settings_.CollisionMasks[i]>>j)&1U)!=0U;if(ImGui::Checkbox(Settings_.LayerNames[j].c_str(),&enabled)){if(enabled){Settings_.CollisionMasks[i]|=1U< issues;(void)MetaCoreValidatePhysicsSettings(Settings_,&issues);for(const auto& issue:issues)editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning,"Physics",issue);MetaCoreTypeRegistry registry;MetaCoreRegisterFoundationGeneratedTypes(registry);MetaCoreRegisterPhysicsGeneratedTypes(registry);Dirty_=!MetaCoreWritePhysicsSettings(Path_,Settings_,registry);} + ImGui::SameLine();if(ImGui::Button("恢复默认")){Settings_=MetaCoreBuildDefaultPhysicsSettings();Dirty_=true;}ImGui::SameLine();ImGui::TextDisabled("%s",Dirty_?"未保存":"已保存"); + } +private: + std::filesystem::path LoadedRoot_{};std::filesystem::path Path_{};MetaCorePhysicsSettingsDocument Settings_{};bool Dirty_=false; +}; + class MetaCoreRmlUiVisualEditorPanelProvider final : public MetaCoreIEditorPanelProvider { public: std::string GetPanelId() const override { return "RmlUiVisualEditor"; } @@ -7803,6 +7828,48 @@ void DrawTextureAssetDetails( } } +void DrawPhysicsMaterialAssetDetails( + MetaCoreEditorContext& editorContext, + MetaCoreIAssetDatabaseService& assetDatabaseService, + MetaCoreIReflectionRegistry& reflectionRegistry +) { + const auto& selected = editorContext.GetSelectedAsset(); + const auto record = assetDatabaseService.FindAssetByGuid(selected.Guid); + if (!record.has_value()) return; + const auto absolutePath = assetDatabaseService.GetProjectDescriptor().RootPath / record->RelativePath; + static std::unordered_map drafts; + auto [iterator, inserted] = drafts.try_emplace(selected.Guid); + if (inserted) { + iterator->second = MetaCoreReadPhysicsMaterial(absolutePath, reflectionRegistry.GetTypeRegistry()) + .value_or(MetaCorePhysicsMaterialDocument{}); + } + auto& material = iterator->second; + ImGui::Text("物理材质: %s", record->RelativePath.filename().string().c_str()); + std::array name{}; + std::snprintf(name.data(), name.size(), "%s", material.Name.c_str()); + if (ImGui::InputText("名称", name.data(), name.size())) material.Name = name.data(); + ImGui::SliderFloat("静摩擦", &material.StaticFriction, 0.0F, 4.0F); + ImGui::SliderFloat("动摩擦", &material.DynamicFriction, 0.0F, 4.0F); + ImGui::SliderFloat("恢复系数", &material.Restitution, 0.0F, 1.0F); + const char* combineModes[]{"Average", "Minimum", "Maximum", "Multiply"}; + int frictionCombine = static_cast(material.FrictionCombine); + int restitutionCombine = static_cast(material.RestitutionCombine); + if (ImGui::Combo("摩擦合并", &frictionCombine, combineModes, 4)) { + material.FrictionCombine = static_cast(frictionCombine); + } + if (ImGui::Combo("弹性合并", &restitutionCombine, combineModes, 4)) { + material.RestitutionCombine = static_cast(restitutionCombine); + } + if (ImGui::Button("保存物理材质")) { + if (MetaCoreWritePhysicsMaterial(absolutePath, material, reflectionRegistry.GetTypeRegistry())) { + (void)assetDatabaseService.Refresh(); + editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Physics", "物理材质已保存"); + } else { + editorContext.AddConsoleMessage(MetaCoreLogLevel::Error, "Physics", "物理材质保存失败"); + } + } +} + void MetaCoreDrawAssetInspector(MetaCoreEditorContext& editorContext, MetaCoreIAssetDatabaseService& assetDatabaseService, MetaCoreIPackageService& packageService, MetaCoreIReflectionRegistry& reflectionRegistry) { if (!editorContext.HasSelectedAsset()) return; const MetaCoreSelectedAssetState& selectedAsset = editorContext.GetSelectedAsset(); @@ -7815,6 +7882,7 @@ void MetaCoreDrawAssetInspector(MetaCoreEditorContext& editorContext, MetaCoreIA else if (selectedAsset.Type == "model") DrawModelAssetDetails(editorContext, assetDatabaseService); else if (selectedAsset.Type == "prefab") DrawPrefabDetails(editorContext, assetDatabaseService, packageService, reflectionRegistry); else if (selectedAsset.Type == "material") DrawMaterialAssetDetails(editorContext, assetDatabaseService, reflectionRegistry); + else if (selectedAsset.Type == "physics_material") DrawPhysicsMaterialAssetDetails(editorContext, assetDatabaseService, reflectionRegistry); else if (selectedAsset.Type == "texture") DrawTextureAssetDetails(editorContext, assetDatabaseService); else if (selectedAsset.Type == "ui_document") DrawUiDocumentDetails(editorContext, assetDatabaseService, packageService, reflectionRegistry); else { @@ -7932,6 +8000,7 @@ struct MetaCoreProjectFileEntry { if (extension == ".glb" || extension == ".gltf" || extension == ".fbx" || extension == ".obj") return "model"; if (extension == ".png" || extension == ".jpg" || extension == ".jpeg" || extension == ".tga" || extension == ".bmp" || extension == ".ppm" || extension == ".ktx") return "texture"; if (extension == ".wav" || extension == ".ogg" || extension == ".mp3") return "audio"; + if (extension == ".mcphysicsmaterial") return "physics_material"; if (extension == ".txt" || extension == ".md") return "text"; return extension.empty() ? "file" : extension.substr(1); } @@ -7941,7 +8010,7 @@ struct MetaCoreProjectFileEntry { if (isDirectory) { return filename; } - for (std::string_view suffix : {".mcmaterial.json", ".mcscene.json", ".mcprefab.json", ".mcui.json"}) { + for (std::string_view suffix : {".mcmaterial.json", ".mcscene.json", ".mcprefab.json", ".mcui.json", ".mcphysicsmaterial"}) { if (filename.size() > suffix.size() && filename.ends_with(suffix)) { return filename.substr(0, filename.size() - suffix.size()); } @@ -7954,6 +8023,7 @@ struct MetaCoreProjectFileEntry { if (isDirectory) return "文件夹"; if (type == "model") return "模型"; if (type == "prefab") return "Prefab"; + if (type == "physics_material") return "物理材质"; if (type == "material") return "材质"; if (type == "texture") return "贴图"; if (type == "scene") return "场景"; @@ -8465,6 +8535,7 @@ void MetaCoreRevealPathInFileManager(const std::filesystem::path& absolutePath) ) { switch (kind) { case MetaCoreProjectCreateKind::Material: + case MetaCoreProjectCreateKind::PhysicsMaterial: return MetaCoreIsAssetsRelativePath(selectedDirectory) ? selectedDirectory : std::filesystem::path("Assets") / "Materials"; case MetaCoreProjectCreateKind::Scene: return MetaCoreIsScenesRelativePath(selectedDirectory) ? selectedDirectory : std::filesystem::path("Scenes"); @@ -8729,6 +8800,7 @@ public: PendingCreatePrefabObjectId_ = prefabObjectId; const char* defaultName = "NewFolder"; if (kind == MetaCoreProjectCreateKind::Material) defaultName = "NewMaterial"; + if (kind == MetaCoreProjectCreateKind::PhysicsMaterial) defaultName = "NewPhysicsMaterial"; if (kind == MetaCoreProjectCreateKind::Scene) defaultName = "NewScene"; if (kind == MetaCoreProjectCreateKind::Prefab) defaultName = "NewPrefab"; if (kind == MetaCoreProjectCreateKind::UiDocument) defaultName = "NewUi"; @@ -8803,6 +8875,7 @@ public: const std::filesystem::path selectedDirectory = editorContext.GetSelectedProjectDirectory().lexically_normal(); if (ImGui::MenuItem("文件夹")) openCreate(MetaCoreProjectCreateKind::Folder, selectedDirectory); if (ImGui::MenuItem("材质")) openCreate(MetaCoreProjectCreateKind::Material, selectedDirectory); + if (ImGui::MenuItem("物理材质")) openCreate(MetaCoreProjectCreateKind::PhysicsMaterial, selectedDirectory); if (ImGui::MenuItem("场景")) openCreate(MetaCoreProjectCreateKind::Scene, selectedDirectory); if (ImGui::MenuItem("UI 文档")) openCreate(MetaCoreProjectCreateKind::UiDocument, selectedDirectory); if (ImGui::MenuItem("从选中对象创建 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) { @@ -8982,6 +9055,7 @@ public: ImGui::Separator(); if (ImGui::MenuItem("新建文件夹")) openCreate(MetaCoreProjectCreateKind::Folder, entry.RelativePath); if (ImGui::MenuItem("新建材质")) openCreate(MetaCoreProjectCreateKind::Material, entry.RelativePath); + if (ImGui::MenuItem("新建物理材质")) openCreate(MetaCoreProjectCreateKind::PhysicsMaterial, entry.RelativePath); if (ImGui::MenuItem("新建场景")) openCreate(MetaCoreProjectCreateKind::Scene, entry.RelativePath); if (ImGui::MenuItem("新建 UI 文档")) openCreate(MetaCoreProjectCreateKind::UiDocument, entry.RelativePath); if (ImGui::MenuItem("从选中对象创建 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) { @@ -9057,6 +9131,7 @@ public: ImGui::Separator(); if (ImGui::MenuItem("新建文件夹")) openCreate(MetaCoreProjectCreateKind::Folder, selectedDirectory); if (ImGui::MenuItem("新建材质")) openCreate(MetaCoreProjectCreateKind::Material, selectedDirectory); + if (ImGui::MenuItem("新建物理材质")) openCreate(MetaCoreProjectCreateKind::PhysicsMaterial, selectedDirectory); if (ImGui::MenuItem("新建场景")) openCreate(MetaCoreProjectCreateKind::Scene, selectedDirectory); if (ImGui::MenuItem("新建 UI 文档")) openCreate(MetaCoreProjectCreateKind::UiDocument, selectedDirectory); if (ImGui::MenuItem("从选中对象创建 Prefab", nullptr, false, editorContext.GetActiveObjectId() != 0)) { @@ -9114,6 +9189,7 @@ public: if (ImGui::BeginPopupModal("新建资源", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { const char* title = "请输入名称:"; if (PendingCreateKind_ == MetaCoreProjectCreateKind::Material) title = "请输入材质名称:"; + if (PendingCreateKind_ == MetaCoreProjectCreateKind::PhysicsMaterial) title = "请输入物理材质名称:"; if (PendingCreateKind_ == MetaCoreProjectCreateKind::Scene) title = "请输入场景名称:"; if (PendingCreateKind_ == MetaCoreProjectCreateKind::Prefab) title = "请输入 Prefab 名称:"; if (PendingCreateKind_ == MetaCoreProjectCreateKind::UiDocument) title = "请输入 UI 文档名称:"; @@ -9140,6 +9216,22 @@ public: editorContext.SelectAsset(MetaCoreSelectedAssetState{record->Guid, record->RelativePath, record->Type, record->StorageKind}); } } + } else if (PendingCreateKind_ == MetaCoreProjectCreateKind::PhysicsMaterial) { + name = MetaCoreStripKnownSuffix(MetaCoreSanitizeProjectBaseName(name, "NewPhysicsMaterial"), ".mcphysicsmaterial"); + const std::filesystem::path materialPath = MetaCoreBuildUniqueProjectPath(project, PendingCreateDirectory_, name, ".mcphysicsmaterial"); + MetaCoreTypeRegistry registry; + MetaCoreRegisterFoundationGeneratedTypes(registry); + MetaCoreRegisterPhysicsGeneratedTypes(registry); + MetaCorePhysicsMaterialDocument material; + material.AssetGuid = MetaCoreAssetGuid::Generate(); + material.Name = name; + created = MetaCoreWritePhysicsMaterial(project.RootPath / materialPath, material, registry); + if (created) { + (void)assetDatabaseService->Refresh(); + if (const auto record = assetDatabaseService->FindAssetByRelativePath(materialPath)) { + editorContext.SelectAsset(MetaCoreSelectedAssetState{record->Guid, record->RelativePath, record->Type, record->StorageKind}); + } + } } else if (PendingCreateKind_ == MetaCoreProjectCreateKind::Scene) { name = MetaCoreStripKnownSuffix(MetaCoreSanitizeProjectBaseName(name, "NewScene"), ".mcscene.json"); const std::filesystem::path scenePath = MetaCoreBuildUniqueProjectPath(project, PendingCreateDirectory_, name, ".mcscene.json"); @@ -9936,6 +10028,7 @@ public: moduleRegistry.RegisterPanelProvider(std::make_unique()); moduleRegistry.RegisterPanelProvider(std::make_unique()); moduleRegistry.RegisterPanelProvider(std::make_unique()); + moduleRegistry.RegisterPanelProvider(std::make_unique()); moduleRegistry.RegisterPanelProvider(std::make_unique()); moduleRegistry.RegisterService(std::make_shared()); // The central Scene / Game viewport is not a dockable provider, but it keeps diff --git a/Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp b/Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp index 51d88ac..bad30e5 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreEditorContext.cpp @@ -9,6 +9,7 @@ #include "MetaCoreFoundation/MetaCoreGeneratedReflection.h" #include "MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h" #include "MetaCoreRuntimeInput/MetaCoreInputMap.h" +#include "MetaCorePhysics/MetaCorePhysics.h" #include "MetaCorePlatform/MetaCoreWindow.h" #include "MetaCoreRender/MetaCoreEditorViewportRenderer.h" #include "MetaCoreRender/MetaCoreRenderDevice.h" @@ -119,6 +120,51 @@ private: MetaCoreNearlyEqual(lhs.BackgroundAlpha, rhs.BackgroundAlpha); } +[[nodiscard]] bool MetaCoreColliderComponentEqual(const MetaCoreColliderComponent& lhs, const MetaCoreColliderComponent& rhs) { + return lhs.Enabled == rhs.Enabled && lhs.Shape == rhs.Shape && + MetaCoreNearlyEqualVec3(lhs.Center, rhs.Center) && MetaCoreNearlyEqualVec3(lhs.Size, rhs.Size) && + MetaCoreNearlyEqual(lhs.Radius, rhs.Radius) && MetaCoreNearlyEqual(lhs.Height, rhs.Height) && + lhs.MeshAssetGuid == rhs.MeshAssetGuid && lhs.MaterialAssetGuid == rhs.MaterialAssetGuid && + lhs.CollisionLayer == rhs.CollisionLayer && lhs.IsTrigger == rhs.IsTrigger; +} + +[[nodiscard]] bool MetaCoreRigidBodyComponentEqual(const MetaCoreRigidBodyComponent& lhs, const MetaCoreRigidBodyComponent& rhs) { + return lhs.BodyType == rhs.BodyType && MetaCoreNearlyEqual(lhs.Mass, rhs.Mass) && + MetaCoreNearlyEqual(lhs.LinearDamping, rhs.LinearDamping) && MetaCoreNearlyEqual(lhs.AngularDamping, rhs.AngularDamping) && + MetaCoreNearlyEqual(lhs.GravityScale, rhs.GravityScale) && + MetaCoreNearlyEqualVec3(lhs.InitialLinearVelocity, rhs.InitialLinearVelocity) && + MetaCoreNearlyEqualVec3(lhs.InitialAngularVelocity, rhs.InitialAngularVelocity) && + lhs.AllowSleep == rhs.AllowSleep && lhs.ContinuousCollisionDetection == rhs.ContinuousCollisionDetection && + lhs.LockedAxes == rhs.LockedAxes; +} + +[[nodiscard]] bool MetaCorePhysicsConstraintComponentEqual( + const MetaCorePhysicsConstraintComponent& lhs, + const MetaCorePhysicsConstraintComponent& rhs +) { + return lhs.Enabled == rhs.Enabled && lhs.Type == rhs.Type && lhs.TargetObjectId == rhs.TargetObjectId && + MetaCoreNearlyEqualVec3(lhs.Anchor, rhs.Anchor) && MetaCoreNearlyEqualVec3(lhs.TargetAnchor, rhs.TargetAnchor) && + MetaCoreNearlyEqualVec3(lhs.Axis, rhs.Axis) && + MetaCoreNearlyEqualVec3(lhs.LinearLowerLimit, rhs.LinearLowerLimit) && + MetaCoreNearlyEqualVec3(lhs.LinearUpperLimit, rhs.LinearUpperLimit) && + MetaCoreNearlyEqualVec3(lhs.AngularLowerLimit, rhs.AngularLowerLimit) && + MetaCoreNearlyEqualVec3(lhs.AngularUpperLimit, rhs.AngularUpperLimit) && + lhs.MotorEnabled == rhs.MotorEnabled && MetaCoreNearlyEqual(lhs.MotorTargetVelocity, rhs.MotorTargetVelocity) && + MetaCoreNearlyEqual(lhs.MotorMaxImpulse, rhs.MotorMaxImpulse) && MetaCoreNearlyEqual(lhs.BreakForce, rhs.BreakForce) && + MetaCoreNearlyEqual(lhs.BreakTorque, rhs.BreakTorque) && lhs.EnableConnectedCollision == rhs.EnableConnectedCollision; +} + +[[nodiscard]] bool MetaCoreCharacterControllerComponentEqual( + const MetaCoreCharacterControllerComponent& lhs, + const MetaCoreCharacterControllerComponent& rhs +) { + return lhs.Enabled == rhs.Enabled && MetaCoreNearlyEqual(lhs.Radius, rhs.Radius) && + MetaCoreNearlyEqual(lhs.Height, rhs.Height) && MetaCoreNearlyEqual(lhs.SkinWidth, rhs.SkinWidth) && + MetaCoreNearlyEqual(lhs.MaxSlopeDegrees, rhs.MaxSlopeDegrees) && MetaCoreNearlyEqual(lhs.StepHeight, rhs.StepHeight) && + MetaCoreNearlyEqual(lhs.GravityScale, rhs.GravityScale) && MetaCoreNearlyEqual(lhs.MaxFallSpeed, rhs.MaxFallSpeed) && + MetaCoreNearlyEqual(lhs.PushForce, rhs.PushForce) && lhs.CollisionLayer == rhs.CollisionLayer; +} + [[nodiscard]] bool MetaCoreGameObjectEqual(const MetaCoreGameObjectData& lhs, const MetaCoreGameObjectData& rhs) { if (lhs.Id != rhs.Id || lhs.ParentId != rhs.ParentId || lhs.Name != rhs.Name || !MetaCoreTransformEqual(lhs.Transform, rhs.Transform)) { return false; @@ -204,6 +250,23 @@ private: } } + if (lhs.Collider.has_value() != rhs.Collider.has_value() || + (lhs.Collider.has_value() && !MetaCoreColliderComponentEqual(*lhs.Collider, *rhs.Collider))) { + return false; + } + if (lhs.RigidBody.has_value() != rhs.RigidBody.has_value() || + (lhs.RigidBody.has_value() && !MetaCoreRigidBodyComponentEqual(*lhs.RigidBody, *rhs.RigidBody))) { + return false; + } + if (lhs.PhysicsConstraint.has_value() != rhs.PhysicsConstraint.has_value() || + (lhs.PhysicsConstraint.has_value() && !MetaCorePhysicsConstraintComponentEqual(*lhs.PhysicsConstraint, *rhs.PhysicsConstraint))) { + return false; + } + if (lhs.CharacterController.has_value() != rhs.CharacterController.has_value() || + (lhs.CharacterController.has_value() && !MetaCoreCharacterControllerComponentEqual(*lhs.CharacterController, *rhs.CharacterController))) { + return false; + } + return true; } @@ -251,6 +314,7 @@ private: MetaCoreRegisterFoundationGeneratedTypes(registry); MetaCoreRegisterRuntimeDataGeneratedTypes(registry); MetaCoreRegisterRuntimeInputGeneratedTypes(registry); + MetaCoreRegisterPhysicsGeneratedTypes(registry); return registry; } @@ -262,6 +326,7 @@ private: document.DiagnosticsPath = std::filesystem::path("Runtime") / "Diagnostics.mcruntimestate"; document.UiManifestPath = std::filesystem::path("Runtime") / "Ui.mcruntime"; document.InputMapPath = std::filesystem::path("Runtime") / "Input.mcruntime"; + document.PhysicsSettingsPath = std::filesystem::path("Runtime") / "Physics.mcruntime"; return document; } @@ -701,8 +766,10 @@ bool MetaCoreEditorContext::SaveRuntimeDataConfig() { ); const std::filesystem::path inputMapPath = runtimeDirectory / "Input.mcruntime"; const bool wroteInputMap = std::filesystem::exists(inputMapPath) || MetaCoreWriteInputMap(inputMapPath, MetaCoreBuildDefaultInputMap(), runtimeRegistry); + const std::filesystem::path physicsPath = runtimeDirectory / "Physics.mcruntime"; + const bool wrotePhysics = std::filesystem::exists(physicsPath) || MetaCoreWritePhysicsSettings(physicsPath, MetaCoreBuildDefaultPhysicsSettings(), runtimeRegistry); - if (!wroteProjectRuntime || !wroteSources || !wroteBindings || !wroteUiManifest || !wroteInputMap) { + if (!wroteProjectRuntime || !wroteSources || !wroteBindings || !wroteUiManifest || !wroteInputMap || !wrotePhysics) { AddConsoleMessage(MetaCoreLogLevel::Error, "RuntimeData", "保存 Runtime 配置失败"); return false; } diff --git a/Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreGeneratedReflection.h b/Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreGeneratedReflection.h index 3d252af..4a8f4a4 100644 --- a/Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreGeneratedReflection.h +++ b/Source/MetaCoreFoundation/Public/MetaCoreFoundation/MetaCoreGeneratedReflection.h @@ -9,5 +9,6 @@ void MetaCoreRegisterSceneGeneratedTypes(MetaCoreTypeRegistry& registry); void MetaCoreRegisterEditorGeneratedTypes(MetaCoreTypeRegistry& registry); void MetaCoreRegisterRuntimeDataGeneratedTypes(MetaCoreTypeRegistry& registry); void MetaCoreRegisterRuntimeInputGeneratedTypes(MetaCoreTypeRegistry& registry); +void MetaCoreRegisterPhysicsGeneratedTypes(MetaCoreTypeRegistry& registry); } // namespace MetaCore diff --git a/Source/MetaCorePhysics/Private/MetaCoreBulletPhysics.cpp b/Source/MetaCorePhysics/Private/MetaCoreBulletPhysics.cpp new file mode 100644 index 0000000..f1c1959 --- /dev/null +++ b/Source/MetaCorePhysics/Private/MetaCoreBulletPhysics.cpp @@ -0,0 +1,537 @@ +#include "MetaCorePhysics/MetaCorePhysics.h" + +#include "MetaCoreScene/MetaCoreScene.h" +#include "MetaCoreScene/MetaCoreTransformUtils.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace MetaCore { +namespace { + +btVector3 Bt(glm::vec3 value) { return {value.x, value.y, value.z}; } +glm::vec3 Glm(const btVector3& value) { return {value.x(), value.y(), value.z()}; } + +btTransform BuildTransform(const glm::mat4& matrix) { + glm::vec3 scale{}, translation{}, skew{}; glm::vec4 perspective{}; glm::quat rotation{}; + glm::decompose(matrix, scale, rotation, translation, skew, perspective); + btTransform result; result.setOrigin(Bt(translation)); result.setRotation({rotation.x, rotation.y, rotation.z, rotation.w}); return result; +} + +glm::mat4 BuildMatrix(const btTransform& transform, glm::vec3 scale) { + const btQuaternion value = transform.getRotation(); + return glm::translate(glm::mat4(1.0F), Glm(transform.getOrigin())) * + glm::mat4_cast(glm::quat(value.w(), value.x(), value.y(), value.z())) * glm::scale(glm::mat4(1.0F), scale); +} + +glm::mat4 WorldMatrix(const MetaCoreScene& scene, MetaCoreGameObject object) { + glm::mat4 result = MetaCoreBuildTransformMatrix(object.GetComponent()); + MetaCoreId parent = object.GetParentId(); std::size_t guard = 0; + while (parent != 0 && guard++ < 1024U) { + const auto parentObject = scene.FindGameObject(parent); if (!parentObject) break; + result = MetaCoreBuildTransformMatrix(parentObject.GetComponent()) * result; + parent = parentObject.GetParentId(); + } + return result; +} + +struct PairKey { + MetaCoreId A = 0; MetaCoreId B = 0; + bool Trigger = false; + auto operator<=>(const PairKey&) const = default; +}; + +} // namespace + +class MetaCorePhysicsWorld::Impl { +public: + struct Metadata { + MetaCoreId Collider = 0; + MetaCoreId Body = 0; + std::uint32_t Layer = 0; + bool Trigger = false; + float Friction = 0.5F; + float Restitution = 0.0F; + MetaCorePhysicsMaterialCombineMode FrictionCombine = MetaCorePhysicsMaterialCombineMode::Average; + MetaCorePhysicsMaterialCombineMode RestitutionCombine = MetaCorePhysicsMaterialCombineMode::Average; + }; + struct BodyEntry { + std::unique_ptr Shape{}; + std::unique_ptr TriangleMesh{}; + std::unique_ptr MotionState{}; + std::unique_ptr Body{}; + Metadata Meta{}; + glm::vec3 Scale{1.0F}; + glm::vec3 Center{0.0F}; + MetaCoreRigidBodyType Type = MetaCoreRigidBodyType::Static; + bool Character = false; + float CharacterHalfHeight = 0.0F; + float CharacterSkinWidth = 0.05F; + float CharacterMaxFallSpeed = 55.0F; + }; + struct ConstraintEntry { MetaCoreId Owner = 0; MetaCoreId Target = 0; MetaCorePhysicsConstraintType Type = MetaCorePhysicsConstraintType::Fixed; std::unique_ptr Value{}; bool Broken = false; }; + struct Filter final : btOverlapFilterCallback { + Impl* Owner = nullptr; + explicit Filter(Impl* owner) : Owner(owner) {} + bool needBroadphaseCollision(btBroadphaseProxy* lhs, btBroadphaseProxy* rhs) const override { + const auto* a = static_cast(lhs->m_clientObject); + const auto* b = static_cast(rhs->m_clientObject); + const auto* ma = a ? static_cast(a->getUserPointer()) : nullptr; + const auto* mb = b ? static_cast(b->getUserPointer()) : nullptr; + if (!ma || !mb || ma->Layer >= 32U || mb->Layer >= 32U) return true; + return ((Owner->Settings.CollisionMasks[ma->Layer] >> mb->Layer) & 1U) != 0U; + } + }; + + MetaCoreScene& Scene; + MetaCorePhysicsSettingsDocument Settings; + MetaCorePhysicsMeshProvider MeshProvider; + MetaCorePhysicsMaterialProvider MaterialProvider; + std::unique_ptr CollisionConfiguration = std::make_unique(); + std::unique_ptr Dispatcher = std::make_unique(CollisionConfiguration.get()); + std::unique_ptr Broadphase = std::make_unique(); + std::unique_ptr Solver = std::make_unique(); + std::unique_ptr World = std::make_unique(Dispatcher.get(), Broadphase.get(), Solver.get(), CollisionConfiguration.get()); + Filter OverlapFilter{this}; + std::unordered_map> Bodies{}; + std::vector Constraints{}; + std::map PreviousPairs{}; + MetaCorePhysicsEventCallback EventCallback{}; + MetaCorePhysicsStatistics Statistics{}; + std::vector Diagnostics{}; + std::uint64_t SceneRevision = std::numeric_limits::max(); + std::uint64_t StructureSignature = 0; + bool Stopped = false; + + Impl(MetaCoreScene& scene, MetaCorePhysicsSettingsDocument settings, MetaCorePhysicsMeshProvider mesh, MetaCorePhysicsMaterialProvider material) + : Scene(scene), Settings(std::move(settings)), MeshProvider(std::move(mesh)), MaterialProvider(std::move(material)) { + (void)MetaCoreValidatePhysicsSettings(Settings, &Diagnostics); + gContactAddedCallback = &Impl::ContactAdded; + World->setGravity(Bt(Settings.Gravity)); + World->getPairCache()->setOverlapFilterCallback(&OverlapFilter); + Rebuild(); + } + + ~Impl() { Clear(); } + + static int CombineRank(MetaCorePhysicsMaterialCombineMode mode) { + switch (mode) { + case MetaCorePhysicsMaterialCombineMode::Maximum: return 3; + case MetaCorePhysicsMaterialCombineMode::Multiply: return 2; + case MetaCorePhysicsMaterialCombineMode::Minimum: return 1; + case MetaCorePhysicsMaterialCombineMode::Average: return 0; + } + return 0; + } + + static float Combine(float lhs, float rhs, MetaCorePhysicsMaterialCombineMode lhsMode, MetaCorePhysicsMaterialCombineMode rhsMode) { + const auto mode = CombineRank(lhsMode) >= CombineRank(rhsMode) ? lhsMode : rhsMode; + switch (mode) { + case MetaCorePhysicsMaterialCombineMode::Maximum: return std::max(lhs, rhs); + case MetaCorePhysicsMaterialCombineMode::Multiply: return lhs * rhs; + case MetaCorePhysicsMaterialCombineMode::Minimum: return std::min(lhs, rhs); + case MetaCorePhysicsMaterialCombineMode::Average: return (lhs + rhs) * 0.5F; + } + return (lhs + rhs) * 0.5F; + } + + static bool ContactAdded(btManifoldPoint& point, const btCollisionObjectWrapper* lhs, int, int, + const btCollisionObjectWrapper* rhs, int, int) { + const auto* a = lhs ? static_cast(lhs->getCollisionObject()->getUserPointer()) : nullptr; + const auto* b = rhs ? static_cast(rhs->getCollisionObject()->getUserPointer()) : nullptr; + if (!a || !b) return true; + point.m_combinedFriction = std::clamp(Combine(a->Friction, b->Friction, a->FrictionCombine, b->FrictionCombine), -10.0F, 10.0F); + point.m_combinedRestitution = std::clamp(Combine(a->Restitution, b->Restitution, a->RestitutionCombine, b->RestitutionCombine), 0.0F, 1.0F); + return true; + } + + void FlushExitEvents() { + if (EventCallback) for (const auto& [key, old] : PreviousPairs) { + auto event = old; + event.Type = key.Trigger ? MetaCorePhysicsEventType::TriggerExit : MetaCorePhysicsEventType::CollisionExit; + EventCallback(event); + } + PreviousPairs.clear(); + } + + void Clear() { + for (auto& entry : Constraints) if (entry.Value) World->removeConstraint(entry.Value.get()); + Constraints.clear(); + for (auto& [id, entry] : Bodies) { (void)id; if (entry->Body) World->removeRigidBody(entry->Body.get()); } + Bodies.clear(); PreviousPairs.clear(); + } + + std::uint64_t CalculateStructureSignature() const { + std::uint64_t hash = 1469598103934665603ULL; + const auto add = [&hash](std::uint64_t value) { hash ^= value; hash *= 1099511628211ULL; }; + const auto addFloat = [&add](float value) { add(std::bit_cast(value)); }; + const auto addVec = [&addFloat](glm::vec3 value) { addFloat(value.x); addFloat(value.y); addFloat(value.z); }; + for (const MetaCoreGameObject object : Scene.GetGameObjects()) { + add(object.GetId()); add(object.GetParentId()); addVec(object.GetComponent().Scale); + add(object.HasComponent()); add(object.HasComponent()); + add(object.HasComponent()); add(object.HasComponent()); + if (object.HasComponent()) { + const auto& value = object.GetComponent(); add(value.Enabled); add(static_cast(value.Shape)); + addVec(value.Center); addVec(value.Size); addFloat(value.Radius); addFloat(value.Height); + add(MetaCoreAssetGuidHasher{}(value.MeshAssetGuid)); add(MetaCoreAssetGuidHasher{}(value.MaterialAssetGuid)); add(value.CollisionLayer); add(value.IsTrigger); + } + if (object.HasComponent()) { + const auto& value = object.GetComponent(); add(static_cast(value.BodyType)); addFloat(value.Mass); + addFloat(value.LinearDamping); addFloat(value.AngularDamping); addFloat(value.GravityScale); addVec(value.InitialLinearVelocity); addVec(value.InitialAngularVelocity); + add(value.AllowSleep); add(value.ContinuousCollisionDetection); add(value.LockedAxes); + } + if (object.HasComponent()) { + const auto& value = object.GetComponent(); add(value.Enabled); add(static_cast(value.Type)); add(value.TargetObjectId); + addVec(value.Anchor); addVec(value.TargetAnchor); addVec(value.Axis); addVec(value.LinearLowerLimit); addVec(value.LinearUpperLimit); addVec(value.AngularLowerLimit); addVec(value.AngularUpperLimit); + add(value.MotorEnabled); addFloat(value.MotorTargetVelocity); addFloat(value.MotorMaxImpulse); addFloat(value.BreakForce); addFloat(value.BreakTorque); add(value.EnableConnectedCollision); + } + if (object.HasComponent()) { + const auto& value = object.GetComponent(); add(value.Enabled); addFloat(value.Radius); addFloat(value.Height); addFloat(value.SkinWidth); + addFloat(value.MaxSlopeDegrees); addFloat(value.StepHeight); addFloat(value.GravityScale); addFloat(value.MaxFallSpeed); addFloat(value.PushForce); add(value.CollisionLayer); + } + } + return hash; + } + + std::unique_ptr MakeShape(const MetaCoreColliderComponent& collider, glm::vec3 absoluteScale, + std::unique_ptr& triangleStorage, MetaCoreRigidBodyType bodyType, MetaCoreId objectId) { + const glm::vec3 size = glm::max(glm::abs(collider.Size * absoluteScale), glm::vec3(0.001F)); + if (collider.Shape == MetaCoreColliderShape::Box) return std::make_unique(Bt(size * 0.5F)); + if (collider.Shape == MetaCoreColliderShape::Sphere) return std::make_unique(std::max(0.001F, collider.Radius * std::max({absoluteScale.x, absoluteScale.y, absoluteScale.z}))); + if (collider.Shape == MetaCoreColliderShape::Capsule) { + const float radius = std::max(0.001F, collider.Radius * std::max(absoluteScale.x, absoluteScale.z)); + const float cylinderHeight = std::max(0.0F, collider.Height * absoluteScale.y - 2.0F * radius); + return std::make_unique(radius, cylinderHeight); + } + if (!MeshProvider || !collider.MeshAssetGuid.IsValid()) { Diagnostics.push_back("对象 " + std::to_string(objectId) + " 缺少网格碰撞载荷"); return {}; } + const auto mesh = MeshProvider(collider.MeshAssetGuid); + if (!mesh || mesh->Positions.empty()) { Diagnostics.push_back("对象 " + std::to_string(objectId) + " 的网格碰撞载荷无效"); return {}; } + if (collider.Shape == MetaCoreColliderShape::ConvexHull) { + auto shape = std::make_unique(); + for (glm::vec3 point : mesh->Positions) shape->addPoint(Bt(point * absoluteScale), false); + shape->recalcLocalAabb(); return shape; + } + if (bodyType != MetaCoreRigidBodyType::Static) { Diagnostics.push_back("对象 " + std::to_string(objectId) + " 的动态凹网格已被拒绝"); return {}; } + triangleStorage = std::make_unique(); + for (std::size_t index = 0; index + 2U < mesh->Indices.size(); index += 3U) { + const auto a = mesh->Indices[index], b = mesh->Indices[index + 1U], c = mesh->Indices[index + 2U]; + if (a < mesh->Positions.size() && b < mesh->Positions.size() && c < mesh->Positions.size()) + triangleStorage->addTriangle(Bt(mesh->Positions[a] * absoluteScale), Bt(mesh->Positions[b] * absoluteScale), Bt(mesh->Positions[c] * absoluteScale), true); + } + return std::make_unique(triangleStorage.get(), true, true); + } + + void Rebuild() { + FlushExitEvents(); Clear(); Diagnostics.clear(); SceneRevision = Scene.GetRevision(); StructureSignature = CalculateStructureSignature(); + for (MetaCoreGameObject object : Scene.GetGameObjects()) { + const bool character = object.HasComponent() && object.GetComponent().Enabled; + if (!object.HasComponent() && !character) continue; + MetaCoreColliderComponent automaticCollider; + if (character) { const auto& controller = object.GetComponent(); automaticCollider.Shape=MetaCoreColliderShape::Capsule; automaticCollider.Radius=controller.Radius; automaticCollider.Height=controller.Height; automaticCollider.CollisionLayer=controller.CollisionLayer; } + const auto collider = object.HasComponent() ? object.GetComponent() : automaticCollider; if (!collider.Enabled) continue; + if (object.HasComponent() && object.HasComponent()) { + Diagnostics.push_back("对象 " + std::to_string(object.GetId()) + " 同时包含 CharacterController 与 RigidBody"); continue; + } + auto bodyComponent = object.HasComponent() ? object.GetComponent() : MetaCoreRigidBodyComponent{}; + if (character) { bodyComponent.BodyType = MetaCoreRigidBodyType::Dynamic; bodyComponent.Mass = 80.0F; bodyComponent.AllowSleep = false; } + auto entry = std::make_unique(); entry->Type = bodyComponent.BodyType; entry->Center = collider.Center; entry->Character = character; + if (character) { + const auto& controller = object.GetComponent(); + entry->CharacterHalfHeight = controller.Height * 0.5F; + entry->CharacterSkinWidth = controller.SkinWidth; + entry->CharacterMaxFallSpeed = controller.MaxFallSpeed; + } + glm::vec3 scale{}, translation{}, skew{}; glm::vec4 perspective{}; glm::quat rotation{}; + const glm::mat4 worldMatrix = WorldMatrix(Scene, object); glm::decompose(worldMatrix, scale, rotation, translation, skew, perspective); + const bool invalidScale = !std::isfinite(scale.x) || !std::isfinite(scale.y) || !std::isfinite(scale.z) || + scale.x <= 1.0e-6F || scale.y <= 1.0e-6F || scale.z <= 1.0e-6F || glm::determinant(glm::mat3(worldMatrix)) <= 0.0F; + const bool sheared = glm::dot(skew, skew) > 1.0e-8F; + if (invalidScale || sheared) { Diagnostics.push_back("对象 " + std::to_string(object.GetId()) + " 的零/负缩放或父级非均匀变换无法用于物理"); continue; } + entry->Scale = scale; + entry->Shape = MakeShape(collider, scale, entry->TriangleMesh, bodyComponent.BodyType, object.GetId()); if (!entry->Shape) continue; + btTransform transform = BuildTransform(worldMatrix); transform.setOrigin(transform.getOrigin() + transform.getBasis() * Bt(collider.Center * scale)); + btScalar mass = bodyComponent.BodyType == MetaCoreRigidBodyType::Dynamic ? std::max(0.0001F, bodyComponent.Mass) : 0.0F; + btVector3 inertia{0.0F, 0.0F, 0.0F}; if (mass > 0.0F) entry->Shape->calculateLocalInertia(mass, inertia); + entry->MotionState = std::make_unique(transform); + btRigidBody::btRigidBodyConstructionInfo info(mass, entry->MotionState.get(), entry->Shape.get(), inertia); + info.m_linearDamping = std::clamp(bodyComponent.LinearDamping, 0.0F, 1.0F); info.m_angularDamping = std::clamp(bodyComponent.AngularDamping, 0.0F, 1.0F); + entry->Body = std::make_unique(info); + entry->Meta.Collider = object.GetId(); entry->Meta.Body = object.GetId(); + entry->Meta.Layer = std::min(collider.CollisionLayer, 31U); entry->Meta.Trigger = collider.IsTrigger; + entry->Body->setUserPointer(&entry->Meta); + if (bodyComponent.BodyType == MetaCoreRigidBodyType::Kinematic) { + entry->Body->setCollisionFlags(entry->Body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT); entry->Body->setActivationState(DISABLE_DEACTIVATION); + } + if (collider.IsTrigger) entry->Body->setCollisionFlags(entry->Body->getCollisionFlags() | btCollisionObject::CF_NO_CONTACT_RESPONSE); + if (!bodyComponent.AllowSleep) entry->Body->setActivationState(DISABLE_DEACTIVATION); + if (bodyComponent.ContinuousCollisionDetection) { entry->Body->setCcdMotionThreshold(0.001F); entry->Body->setCcdSweptSphereRadius(std::max(0.001F, collider.Radius)); } + entry->Body->setLinearVelocity(Bt(bodyComponent.InitialLinearVelocity)); entry->Body->setAngularVelocity(Bt(bodyComponent.InitialAngularVelocity)); + entry->Body->setGravity(Bt(Settings.Gravity * bodyComponent.GravityScale)); + if (character) entry->Body->setAngularFactor(btVector3(0.0F, 0.0F, 0.0F)); + if (MaterialProvider && collider.MaterialAssetGuid.IsValid()) if (auto materialValue = MaterialProvider(collider.MaterialAssetGuid)) { + entry->Meta.Friction = std::max(0.0F, materialValue->DynamicFriction); + entry->Meta.Restitution = std::clamp(materialValue->Restitution, 0.0F, 1.0F); + entry->Meta.FrictionCombine = materialValue->FrictionCombine; + entry->Meta.RestitutionCombine = materialValue->RestitutionCombine; + } + entry->Body->setFriction(entry->Meta.Friction); entry->Body->setRestitution(entry->Meta.Restitution); + entry->Body->setCollisionFlags(entry->Body->getCollisionFlags() | btCollisionObject::CF_CUSTOM_MATERIAL_CALLBACK); + const btVector3 linearFactor{ + (bodyComponent.LockedAxes & (1U << 0U)) ? 0.0F : 1.0F, + (bodyComponent.LockedAxes & (1U << 1U)) ? 0.0F : 1.0F, + (bodyComponent.LockedAxes & (1U << 2U)) ? 0.0F : 1.0F}; + const btVector3 angularFactor{ + (bodyComponent.LockedAxes & (1U << 3U)) ? 0.0F : 1.0F, + (bodyComponent.LockedAxes & (1U << 4U)) ? 0.0F : 1.0F, + (bodyComponent.LockedAxes & (1U << 5U)) ? 0.0F : 1.0F}; + entry->Body->setLinearFactor(linearFactor); entry->Body->setAngularFactor(character ? btVector3(0.0F, 0.0F, 0.0F) : angularFactor); + World->addRigidBody(entry->Body.get()); Bodies.emplace(object.GetId(), std::move(entry)); + } + for (MetaCoreGameObject object : Scene.GetGameObjects()) { + if (!object.HasComponent()) continue; + const auto& source = object.GetComponent(); if (!source.Enabled) continue; + const auto owner = Bodies.find(object.GetId()); if (owner == Bodies.end()) { Diagnostics.push_back("约束对象 " + std::to_string(object.GetId()) + " 缺少刚体"); continue; } + auto target = source.TargetObjectId == 0 ? nullptr : (Bodies.contains(source.TargetObjectId) ? Bodies.at(source.TargetObjectId).get() : nullptr); + if (source.TargetObjectId != 0 && !target) { Diagnostics.push_back("约束对象 " + std::to_string(object.GetId()) + " 的目标刚体不存在"); continue; } + btRigidBody& a = *owner->second->Body; btRigidBody& b = target ? *target->Body : btTypedConstraint::getFixedBody(); + btTransform frameA, frameB; frameA.setIdentity(); frameB.setIdentity(); frameA.setOrigin(Bt(source.Anchor)); frameB.setOrigin(Bt(source.TargetAnchor)); + std::unique_ptr constraint; + if (source.Type == MetaCorePhysicsConstraintType::Fixed) constraint = std::make_unique(a, b, frameA, frameB); + else if (source.Type == MetaCorePhysicsConstraintType::Hinge) { + auto value = std::make_unique(a, b, frameA, frameB); value->setLimit(glm::radians(source.AngularLowerLimit.x), glm::radians(source.AngularUpperLimit.x)); + if (source.MotorEnabled) value->enableAngularMotor(true, source.MotorTargetVelocity, source.MotorMaxImpulse); constraint = std::move(value); + } else if (source.Type == MetaCorePhysicsConstraintType::Slider) { + auto value = std::make_unique(a, b, frameA, frameB, true); value->setLowerLinLimit(source.LinearLowerLimit.x); value->setUpperLinLimit(source.LinearUpperLimit.x); + if (source.MotorEnabled) { value->setPoweredLinMotor(true); value->setTargetLinMotorVelocity(source.MotorTargetVelocity); value->setMaxLinMotorForce(source.MotorMaxImpulse); } constraint = std::move(value); + } else { + auto value = std::make_unique(a, b, frameA, frameB, true); value->setLinearLowerLimit(Bt(source.LinearLowerLimit)); value->setLinearUpperLimit(Bt(source.LinearUpperLimit)); + value->setAngularLowerLimit(Bt(glm::radians(source.AngularLowerLimit))); value->setAngularUpperLimit(Bt(glm::radians(source.AngularUpperLimit))); constraint = std::move(value); + } + if (source.BreakForce > 0.0F) constraint->setBreakingImpulseThreshold(source.BreakForce * static_cast(Settings.FixedTimeStep)); + World->addConstraint(constraint.get(), !source.EnableConnectedCollision); Constraints.push_back({object.GetId(), source.TargetObjectId, source.Type, std::move(constraint), false}); + } + Statistics.BodyCount = Bodies.size(); + } + + bool Accept(const Metadata* metadata, const MetaCorePhysicsQueryFilter& filter) const { + return metadata && metadata->Layer < 32U && ((filter.LayerMask >> metadata->Layer) & 1U) != 0U && + !(metadata->Trigger && filter.Triggers == MetaCorePhysicsTriggerQuery::Ignore) && + !filter.IgnoredObjectIds.contains(metadata->Collider) && !filter.IgnoredBodyIds.contains(metadata->Body); + } + + MetaCorePhysicsHit Hit(const btCollisionObject* object, const btVector3& position, const btVector3& normal, float distance, float fraction) const { + const auto* metadata = object ? static_cast(object->getUserPointer()) : nullptr; + return {metadata ? metadata->Collider : 0, metadata ? metadata->Body : 0, Glm(position), Glm(normal), distance, fraction, metadata && metadata->Trigger}; + } + + void EmitContacts() { + std::map current; + for (int index = 0; index < Dispatcher->getNumManifolds(); ++index) { + const btPersistentManifold* manifold = Dispatcher->getManifoldByIndexInternal(index); if (!manifold || manifold->getNumContacts() == 0) continue; + const auto* a = static_cast(manifold->getBody0()->getUserPointer()); const auto* b = static_cast(manifold->getBody1()->getUserPointer()); if (!a || !b) continue; + const bool trigger = a->Trigger || b->Trigger; const bool swapped = a->Body > b->Body; + PairKey key{std::min(a->Body, b->Body), std::max(a->Body, b->Body), trigger}; + const btManifoldPoint& point = manifold->getContactPoint(0); if (point.getDistance() > 0.0F) continue; + MetaCorePhysicsContactEvent event; event.Type = trigger ? MetaCorePhysicsEventType::TriggerStay : MetaCorePhysicsEventType::CollisionStay; + event.ObjectA = swapped ? b->Body : a->Body; event.ObjectB = swapped ? a->Body : b->Body; + event.ColliderA = swapped ? b->Collider : a->Collider; event.ColliderB = swapped ? a->Collider : b->Collider; + event.Point = Glm(point.getPositionWorldOnB()); event.Normal = Glm(swapped ? -point.m_normalWorldOnB : point.m_normalWorldOnB); event.Impulse = point.getAppliedImpulse(); + const auto* bodyA = btRigidBody::upcast(manifold->getBody0()); const auto* bodyB = btRigidBody::upcast(manifold->getBody1()); + const btVector3 relativeVelocity = (bodyB ? bodyB->getLinearVelocity() : btVector3{}) - (bodyA ? bodyA->getLinearVelocity() : btVector3{}); + event.RelativeVelocity = Glm(swapped ? -relativeVelocity : relativeVelocity); + current.insert_or_assign(key, event); + } + for (auto& [key, event] : current) { + if (!PreviousPairs.contains(key)) event.Type = key.Trigger ? MetaCorePhysicsEventType::TriggerEnter : MetaCorePhysicsEventType::CollisionEnter; + if (EventCallback) EventCallback(event); + } + for (const auto& [key, old] : PreviousPairs) if (!current.contains(key) && EventCallback) { + auto event = old; event.Type = key.Trigger ? MetaCorePhysicsEventType::TriggerExit : MetaCorePhysicsEventType::CollisionExit; EventCallback(event); + } + PreviousPairs = std::move(current); Statistics.ContactCount = PreviousPairs.size(); + for (auto& entry : Constraints) if (!entry.Broken && entry.Value && !entry.Value->isEnabled()) { + entry.Broken = true; if (EventCallback) { MetaCorePhysicsContactEvent event; event.Type=MetaCorePhysicsEventType::ConstraintBroken; event.ObjectA=entry.Owner; event.ObjectB=entry.Target; EventCallback(event); } + } + } + + void SyncKinematicAndStatic() { + for (auto& [id, entry] : Bodies) { + if (entry->Type == MetaCoreRigidBodyType::Dynamic) continue; + const auto object = Scene.FindGameObject(id); if (!object) continue; + btTransform transform = BuildTransform(WorldMatrix(Scene, object)); + transform.setOrigin(transform.getOrigin() + transform.getBasis() * Bt(entry->Center * entry->Scale)); + entry->Body->setWorldTransform(transform); entry->MotionState->setWorldTransform(transform); entry->Body->activate(true); + } + } + + void WriteBack() { + for (auto& [id, entry] : Bodies) { + if (entry->Type != MetaCoreRigidBodyType::Dynamic) continue; + auto object = Scene.FindGameObject(id); if (!object) continue; + btTransform transform; entry->MotionState->getWorldTransform(transform); + transform.setOrigin(transform.getOrigin() - transform.getBasis() * Bt(entry->Center * entry->Scale)); + glm::mat4 local = BuildMatrix(transform, entry->Scale); + if (object.GetParentId() != 0) if (const auto parent = Scene.FindGameObject(object.GetParentId())) local = glm::inverse(WorldMatrix(Scene, parent)) * local; + MetaCoreApplyMatrixToTransform(local, object.GetComponent()); + } + } +}; + +MetaCorePhysicsWorld::MetaCorePhysicsWorld(MetaCoreScene& scene, MetaCorePhysicsSettingsDocument settings, + MetaCorePhysicsMeshProvider meshProvider, MetaCorePhysicsMaterialProvider materialProvider) + : Impl_(std::make_unique(scene, std::move(settings), std::move(meshProvider), std::move(materialProvider))) {} +MetaCorePhysicsWorld::~MetaCorePhysicsWorld() = default; +void MetaCorePhysicsWorld::SynchronizeScene() { + const auto signature = Impl_->CalculateStructureSignature(); + if (signature != Impl_->StructureSignature) Impl_->Rebuild(); + else { Impl_->SceneRevision = Impl_->Scene.GetRevision(); Impl_->SyncKinematicAndStatic(); } +} +void MetaCorePhysicsWorld::SetEventCallback(MetaCorePhysicsEventCallback callback) { Impl_->EventCallback = std::move(callback); } +void MetaCorePhysicsWorld::Step(double fixedDeltaSeconds) { + if (Impl_->Stopped || !std::isfinite(fixedDeltaSeconds) || fixedDeltaSeconds <= 0.0) return; + const auto begin = std::chrono::steady_clock::now(); SynchronizeScene(); + for (auto& [id, entry] : Impl_->Bodies) { + (void)id; + if (!entry->Character) continue; + btVector3 velocity = entry->Body->getLinearVelocity(); + velocity.setY(std::max(velocity.y(), -entry->CharacterMaxFallSpeed)); + entry->Body->setLinearVelocity(velocity); + } + Impl_->World->stepSimulation(static_cast(fixedDeltaSeconds), 0); + Impl_->WriteBack(); Impl_->EmitContacts(); Impl_->Statistics.LastStepMilliseconds = std::chrono::duration(std::chrono::steady_clock::now() - begin).count(); +} +void MetaCorePhysicsWorld::Stop() { + if (Impl_->Stopped) return; + Impl_->FlushExitEvents(); + Impl_->Stopped = true; Impl_->Clear(); +} + +std::vector MetaCorePhysicsWorld::RaycastAll(glm::vec3 origin, glm::vec3 direction, float maxDistance, const MetaCorePhysicsQueryFilter& filter) { + ++Impl_->Statistics.QueryCount; std::vector result; + if (!std::isfinite(maxDistance) || maxDistance <= 0.0F || glm::dot(direction, direction) < 1.0e-12F) return result; + direction = glm::normalize(direction); const btVector3 from = Bt(origin), to = Bt(origin + direction * maxDistance); + struct Callback final : btCollisionWorld::AllHitsRayResultCallback { + Impl* Owner; const MetaCorePhysicsQueryFilter& Filter; + Callback(const btVector3& a, const btVector3& b, Impl* owner, const MetaCorePhysicsQueryFilter& filter) : AllHitsRayResultCallback(a, b), Owner(owner), Filter(filter) {} + bool needsCollision(btBroadphaseProxy* proxy) const override { const auto* object = static_cast(proxy->m_clientObject); return Owner->Accept(object ? static_cast(object->getUserPointer()) : nullptr, Filter); } + } callback(from, to, Impl_.get(), filter); + Impl_->World->rayTest(from, to, callback); + for (int index = 0; index < callback.m_collisionObjects.size(); ++index) result.push_back(Impl_->Hit(callback.m_collisionObjects[index], callback.m_hitPointWorld[index], callback.m_hitNormalWorld[index], maxDistance * callback.m_hitFractions[index], callback.m_hitFractions[index])); + std::sort(result.begin(), result.end(), [](const auto& a, const auto& b) { return std::tie(a.Fraction, a.ColliderObjectId, a.RigidBodyObjectId) < std::tie(b.Fraction, b.ColliderObjectId, b.RigidBodyObjectId); }); return result; +} +std::optional MetaCorePhysicsWorld::Raycast(glm::vec3 origin, glm::vec3 direction, float maxDistance, const MetaCorePhysicsQueryFilter& filter) { auto all = RaycastAll(origin, direction, maxDistance, filter); return all.empty() ? std::nullopt : std::optional(all.front()); } + +static std::unique_ptr MetaCoreMakeQueryShape(const MetaCorePhysicsQueryShape& shape) { + if (shape.Type == MetaCorePhysicsQueryShapeType::Sphere && shape.Radius > 0.0F) return std::make_unique(shape.Radius); + if (shape.Type == MetaCorePhysicsQueryShapeType::Capsule && shape.Radius > 0.0F && shape.Height >= shape.Radius * 2.0F) return std::make_unique(shape.Radius, shape.Height - shape.Radius * 2.0F); + if (shape.Type == MetaCorePhysicsQueryShapeType::Box && shape.HalfExtents.x > 0.0F && shape.HalfExtents.y > 0.0F && shape.HalfExtents.z > 0.0F) return std::make_unique(Bt(shape.HalfExtents)); + return {}; +} + +std::vector MetaCorePhysicsWorld::ShapeCastAll(const MetaCorePhysicsQueryShape& shape, glm::vec3 origin, glm::vec3 direction, float maxDistance, const MetaCorePhysicsQueryFilter& filter) { + ++Impl_->Statistics.QueryCount; std::vector result; auto queryShape = MetaCoreMakeQueryShape(shape); + if (!queryShape || maxDistance <= 0.0F || glm::dot(direction, direction) < 1.0e-12F) return result; + direction = glm::normalize(direction); + btTransform from, to; from.setIdentity(); to.setIdentity(); from.setOrigin(Bt(origin)); to.setOrigin(Bt(origin + direction * maxDistance)); + struct Callback final : btCollisionWorld::ClosestConvexResultCallback { + Impl* Owner; const MetaCorePhysicsQueryFilter& Filter; std::vector& Hits; float Distance; + Callback(const btVector3& a, const btVector3& b, Impl* owner, const MetaCorePhysicsQueryFilter& filter, std::vector& hits, float distance) + : ClosestConvexResultCallback(a, b), Owner(owner), Filter(filter), Hits(hits), Distance(distance) { m_closestHitFraction = 1.0F; } + bool needsCollision(btBroadphaseProxy* proxy) const override { const auto* object = static_cast(proxy->m_clientObject); return Owner->Accept(object ? static_cast(object->getUserPointer()) : nullptr, Filter); } + btScalar addSingleResult(btCollisionWorld::LocalConvexResult& value, bool normalInWorldSpace) override { + const btVector3 normal = normalInWorldSpace ? value.m_hitNormalLocal : value.m_hitCollisionObject->getWorldTransform().getBasis() * value.m_hitNormalLocal; + Hits.push_back(Owner->Hit(value.m_hitCollisionObject, value.m_hitPointLocal, normal, Distance * value.m_hitFraction, value.m_hitFraction)); return 1.0F; + } + } callback(from.getOrigin(), to.getOrigin(), Impl_.get(), filter, result, maxDistance); + Impl_->World->convexSweepTest(queryShape.get(), from, to, callback); + std::sort(result.begin(), result.end(), [](const auto& a, const auto& b) { return std::tie(a.Fraction, a.ColliderObjectId, a.RigidBodyObjectId) < std::tie(b.Fraction, b.ColliderObjectId, b.RigidBodyObjectId); }); return result; +} +std::optional MetaCorePhysicsWorld::ShapeCast(const MetaCorePhysicsQueryShape& shape, glm::vec3 origin, glm::vec3 direction, float maxDistance, const MetaCorePhysicsQueryFilter& filter) { auto all = ShapeCastAll(shape, origin, direction, maxDistance, filter); return all.empty() ? std::nullopt : std::optional(all.front()); } + +std::vector MetaCorePhysicsWorld::Overlap(const MetaCorePhysicsQueryShape& shape, glm::vec3 center, const MetaCorePhysicsQueryFilter& filter) { + ++Impl_->Statistics.QueryCount; std::vector result; auto queryShape = MetaCoreMakeQueryShape(shape); if (!queryShape) return result; + btCollisionObject query; query.setCollisionShape(queryShape.get()); btTransform transform; transform.setIdentity(); transform.setOrigin(Bt(center)); query.setWorldTransform(transform); + struct Callback final : btCollisionWorld::ContactResultCallback { + Impl* Owner; const MetaCorePhysicsQueryFilter& Filter; std::vector& Hits; + Callback(Impl* owner, const MetaCorePhysicsQueryFilter& filter, std::vector& hits) : Owner(owner), Filter(filter), Hits(hits) {} + bool needsCollision(btBroadphaseProxy* proxy) const override { const auto* object = static_cast(proxy->m_clientObject); return Owner->Accept(object ? static_cast(object->getUserPointer()) : nullptr, Filter); } + btScalar addSingleResult(btManifoldPoint& point, const btCollisionObjectWrapper*, int, int, const btCollisionObjectWrapper* b, int, int) override { + const btCollisionObject* object = b->getCollisionObject(); Hits.push_back(Owner->Hit(object, point.getPositionWorldOnB(), point.m_normalWorldOnB, 0.0F, 0.0F)); return 0.0F; + } + } callback(Impl_.get(), filter, result); + Impl_->World->contactTest(&query, callback); + std::sort(result.begin(), result.end(), [](const auto& a, const auto& b) { return std::tie(a.ColliderObjectId, a.RigidBodyObjectId) < std::tie(b.ColliderObjectId, b.RigidBodyObjectId); }); + result.erase(std::unique(result.begin(), result.end(), [](const auto& a, const auto& b) { return a.ColliderObjectId == b.ColliderObjectId && a.RigidBodyObjectId == b.RigidBodyObjectId; }), result.end()); return result; +} + +bool MetaCorePhysicsWorld::AddForce(MetaCoreId id, glm::vec3 value) { const auto it = Impl_->Bodies.find(id); if (it == Impl_->Bodies.end() || it->second->Type != MetaCoreRigidBodyType::Dynamic) return false; it->second->Body->activate(true); it->second->Body->applyCentralForce(Bt(value)); return true; } +bool MetaCorePhysicsWorld::AddImpulse(MetaCoreId id, glm::vec3 value) { const auto it = Impl_->Bodies.find(id); if (it == Impl_->Bodies.end() || it->second->Type != MetaCoreRigidBodyType::Dynamic) return false; it->second->Body->activate(true); it->second->Body->applyCentralImpulse(Bt(value)); return true; } +bool MetaCorePhysicsWorld::SetLinearVelocity(MetaCoreId id, glm::vec3 value) { const auto it = Impl_->Bodies.find(id); if (it == Impl_->Bodies.end()) return false; it->second->Body->setLinearVelocity(Bt(value)); it->second->Body->activate(true); return true; } +std::optional MetaCorePhysicsWorld::GetLinearVelocity(MetaCoreId id) const { const auto it = Impl_->Bodies.find(id); return it == Impl_->Bodies.end() ? std::nullopt : std::optional(Glm(it->second->Body->getLinearVelocity())); } +bool MetaCorePhysicsWorld::WakeUp(MetaCoreId id) { const auto it = Impl_->Bodies.find(id); if (it == Impl_->Bodies.end()) return false; it->second->Body->activate(true); return true; } +bool MetaCorePhysicsWorld::CharacterMove(MetaCoreId id, glm::vec3 displacement) { const auto it = Impl_->Bodies.find(id); if (it == Impl_->Bodies.end() || !Impl_->Scene.FindGameObject(id).HasComponent()) return false; auto velocity=it->second->Body->getLinearVelocity(); velocity.setX(displacement.x/static_cast(Impl_->Settings.FixedTimeStep)); velocity.setZ(displacement.z/static_cast(Impl_->Settings.FixedTimeStep)); it->second->Body->setLinearVelocity(velocity); it->second->Body->activate(true); return true; } +bool MetaCorePhysicsWorld::CharacterJump(MetaCoreId id, float speed) { if (!IsCharacterGrounded(id)) return false; auto value=GetLinearVelocity(id); if(!value)return false; value->y=speed; return SetLinearVelocity(id,*value); } +bool MetaCorePhysicsWorld::SetConstraintEnabled(MetaCoreId id, bool enabled) { + const auto iterator = std::find_if(Impl_->Constraints.begin(), Impl_->Constraints.end(), [id](const auto& value) { return value.Owner == id; }); + if (iterator == Impl_->Constraints.end() || !iterator->Value) return false; + iterator->Value->setEnabled(enabled); if (enabled) iterator->Broken = false; return true; +} +bool MetaCorePhysicsWorld::SetConstraintMotor(MetaCoreId id, bool enabled, float targetVelocity, float maxImpulse) { + const auto iterator = std::find_if(Impl_->Constraints.begin(), Impl_->Constraints.end(), [id](const auto& value) { return value.Owner == id; }); + if (iterator == Impl_->Constraints.end() || !iterator->Value || !std::isfinite(targetVelocity) || !std::isfinite(maxImpulse) || maxImpulse < 0.0F) return false; + if (auto* hinge = dynamic_cast(iterator->Value.get())) { hinge->enableAngularMotor(enabled, targetVelocity, maxImpulse); return true; } + if (auto* slider = dynamic_cast(iterator->Value.get())) { slider->setPoweredLinMotor(enabled); slider->setTargetLinMotorVelocity(targetVelocity); slider->setMaxLinMotorForce(maxImpulse); return true; } + if (auto* sixDof = dynamic_cast(iterator->Value.get())) { + auto* motor = sixDof->getTranslationalLimitMotor(); motor->m_enableMotor[0] = enabled; motor->m_targetVelocity[0] = targetVelocity; motor->m_maxMotorForce[0] = maxImpulse; return true; + } + return false; +} +bool MetaCorePhysicsWorld::IsCharacterGrounded(MetaCoreId id) const { + const auto it = Impl_->Bodies.find(id); if (it == Impl_->Bodies.end() || !it->second->Character) return false; + const btVector3 from = it->second->Body->getWorldTransform().getOrigin(); + const float distance = it->second->CharacterHalfHeight * it->second->Scale.y + it->second->CharacterSkinWidth + 0.08F; + struct Callback final : btCollisionWorld::ClosestRayResultCallback { + const btCollisionObject* Ignored; + Callback(const btVector3& start, const btVector3& end, const btCollisionObject* ignored) + : ClosestRayResultCallback(start, end), Ignored(ignored) {} + bool needsCollision(btBroadphaseProxy* proxy) const override { + return proxy && proxy->m_clientObject != Ignored && ClosestRayResultCallback::needsCollision(proxy); + } + } callback(from, from + btVector3(0.0F, -distance, 0.0F), it->second->Body.get()); + Impl_->World->rayTest(callback.m_rayFromWorld, callback.m_rayToWorld, callback); + return callback.hasHit() && callback.m_hitNormalWorld.y() >= 0.5F; +} + +std::vector MetaCorePhysicsWorld::BuildDebugLines() const { + std::vector lines; const auto addBox = [&](glm::vec3 min, glm::vec3 max, glm::vec3 color) { + const glm::vec3 c[8]{{min.x,min.y,min.z},{max.x,min.y,min.z},{max.x,max.y,min.z},{min.x,max.y,min.z},{min.x,min.y,max.z},{max.x,min.y,max.z},{max.x,max.y,max.z},{min.x,max.y,max.z}}; + constexpr int edges[12][2]{{0,1},{1,2},{2,3},{3,0},{4,5},{5,6},{6,7},{7,4},{0,4},{1,5},{2,6},{3,7}}; for (auto& edge : edges) lines.push_back({c[edge[0]], c[edge[1]], color}); + }; + for (const auto& [id, entry] : Impl_->Bodies) { (void)id; btVector3 min, max; entry->Body->getAabb(min, max); addBox(Glm(min), Glm(max), entry->Meta.Trigger ? glm::vec3(1.0F,0.7F,0.1F) : entry->Body->isActive() ? glm::vec3(0.2F,1.0F,0.3F) : glm::vec3(0.3F,0.5F,1.0F)); } return lines; +} +const MetaCorePhysicsStatistics& MetaCorePhysicsWorld::GetStatistics() const { return Impl_->Statistics; } +const std::vector& MetaCorePhysicsWorld::GetDiagnostics() const { return Impl_->Diagnostics; } +const MetaCorePhysicsSettingsDocument& MetaCorePhysicsWorld::GetSettings() const { return Impl_->Settings; } + +class MetaCoreBulletPhysicsBackend final : public MetaCoreIPhysicsBackend { +public: + std::string GetBackendId() const override { return "Bullet3"; } + std::unique_ptr CreateWorld(MetaCoreScene& scene, const MetaCorePhysicsSettingsDocument& settings, + MetaCorePhysicsMeshProvider mesh, MetaCorePhysicsMaterialProvider material) override { + return std::make_unique(scene, settings, std::move(mesh), std::move(material)); + } +}; +std::unique_ptr MetaCoreCreateBulletPhysicsBackend() { return std::make_unique(); } + +} // namespace MetaCore diff --git a/Source/MetaCorePhysics/Private/MetaCorePhysicsSettings.cpp b/Source/MetaCorePhysics/Private/MetaCorePhysicsSettings.cpp new file mode 100644 index 0000000..ae862f9 --- /dev/null +++ b/Source/MetaCorePhysics/Private/MetaCorePhysicsSettings.cpp @@ -0,0 +1,73 @@ +#include "MetaCorePhysics/MetaCorePhysics.h" + +#include "MetaCoreFoundation/MetaCoreArchive.h" + +#include +#include +#include + +namespace MetaCore { + +MetaCorePhysicsSettingsDocument MetaCoreBuildDefaultPhysicsSettings() { + MetaCorePhysicsSettingsDocument result; + result.LayerNames.resize(32U); + result.CollisionMasks.assign(32U, 0xFFFFFFFFU); + result.LayerNames[0] = "Default"; + for (std::size_t index = 1; index < result.LayerNames.size(); ++index) result.LayerNames[index] = "Layer " + std::to_string(index); + return result; +} + +bool MetaCoreValidatePhysicsSettings(MetaCorePhysicsSettingsDocument& document, std::vector* issues) { + bool valid = true; + const auto report = [&](std::string value) { valid = false; if (issues) issues->push_back(std::move(value)); }; + if (!std::isfinite(document.FixedTimeStep) || document.FixedTimeStep < 0.001 || document.FixedTimeStep > 1.0) { + report("FixedTimeStep 必须位于 [0.001, 1.0]"); document.FixedTimeStep = 1.0 / 60.0; + } + if (!std::isfinite(document.MaxFrameDelta) || document.MaxFrameDelta < document.FixedTimeStep || document.MaxFrameDelta > 1.0) { + report("MaxFrameDelta 无效"); document.MaxFrameDelta = 0.25; + } + if (document.MaxFixedStepsPerFrame == 0U || document.MaxFixedStepsPerFrame > 32U) { + report("MaxFixedStepsPerFrame 必须位于 [1, 32]"); document.MaxFixedStepsPerFrame = 4U; + } + if (document.LayerNames.size() != 32U) { report("碰撞层名称必须恰好为 32 项"); document.LayerNames.resize(32U); } + if (document.CollisionMasks.size() != 32U) { report("碰撞矩阵必须恰好为 32 项"); document.CollisionMasks.resize(32U, 0xFFFFFFFFU); } + for (std::uint32_t lhs = 0; lhs < 32U; ++lhs) for (std::uint32_t rhs = lhs + 1U; rhs < 32U; ++rhs) { + const bool enabled = ((document.CollisionMasks[lhs] >> rhs) & 1U) != 0U && ((document.CollisionMasks[rhs] >> lhs) & 1U) != 0U; + if (enabled) { document.CollisionMasks[lhs] |= 1U << rhs; document.CollisionMasks[rhs] |= 1U << lhs; } + else { document.CollisionMasks[lhs] &= ~(1U << rhs); document.CollisionMasks[rhs] &= ~(1U << lhs); } + } + return valid; +} + +bool MetaCoreWritePhysicsSettings(const std::filesystem::path& path, const MetaCorePhysicsSettingsDocument& document, + const MetaCoreTypeRegistry& registry) { + const auto bytes = MetaCoreSerializeToBytes(document, registry); if (!bytes) return false; + std::error_code error; std::filesystem::create_directories(path.parent_path(), error); + std::ofstream output(path, std::ios::binary | std::ios::trunc); + if (!output) return false; + output.write(reinterpret_cast(bytes->data()), static_cast(bytes->size())); return output.good(); +} + +std::optional MetaCoreReadPhysicsSettings(const std::filesystem::path& path, + const MetaCoreTypeRegistry& registry) { + std::ifstream input(path, std::ios::binary | std::ios::ate); if (!input) return std::nullopt; + const auto size = static_cast(input.tellg()); input.seekg(0); std::vector bytes(size); + if (size && !input.read(reinterpret_cast(bytes.data()), static_cast(size))) return std::nullopt; + MetaCorePhysicsSettingsDocument result; + if (!MetaCoreDeserializeFromBytes(std::span(bytes.data(), bytes.size()), result, registry)) return std::nullopt; + (void)MetaCoreValidatePhysicsSettings(result); return result; +} + +bool MetaCoreWritePhysicsMaterial(const std::filesystem::path& path, const MetaCorePhysicsMaterialDocument& document, + const MetaCoreTypeRegistry& registry) { + const auto bytes=MetaCoreSerializeToBytes(document,registry);if(!bytes)return false;std::error_code error;std::filesystem::create_directories(path.parent_path(),error); + std::ofstream output(path,std::ios::binary|std::ios::trunc);if(!output)return false;output.write(reinterpret_cast(bytes->data()),static_cast(bytes->size()));return output.good(); +} + +std::optional MetaCoreReadPhysicsMaterial(const std::filesystem::path& path, + const MetaCoreTypeRegistry& registry) { + std::ifstream input(path,std::ios::binary|std::ios::ate);if(!input)return std::nullopt;const auto size=static_cast(input.tellg());input.seekg(0);std::vector bytes(size); + if(size&&!input.read(reinterpret_cast(bytes.data()),static_cast(size)))return std::nullopt;MetaCorePhysicsMaterialDocument result;if(!MetaCoreDeserializeFromBytes(std::span(bytes.data(),bytes.size()),result,registry))return std::nullopt;return result; +} + +} // namespace MetaCore diff --git a/Source/MetaCorePhysics/Public/MetaCorePhysics/MetaCorePhysics.h b/Source/MetaCorePhysics/Public/MetaCorePhysics/MetaCorePhysics.h new file mode 100644 index 0000000..0948954 --- /dev/null +++ b/Source/MetaCorePhysics/Public/MetaCorePhysics/MetaCorePhysics.h @@ -0,0 +1,178 @@ +#pragma once + +#include "MetaCoreFoundation/MetaCoreAssetGuid.h" +#include "MetaCoreFoundation/MetaCoreId.h" +#include "MetaCoreFoundation/MetaCoreReflection.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace MetaCore { + +class MetaCoreScene; + +MC_ENUM() +enum class MetaCorePhysicsMaterialCombineMode { Average = 0, Minimum, Maximum, Multiply }; + +MC_STRUCT() +struct MetaCorePhysicsMaterialDocument { + MC_GENERATED_BODY() + MC_PROPERTY() + MetaCoreAssetGuid AssetGuid{}; + MC_PROPERTY() + std::string Name{"Physics Material"}; + MC_PROPERTY() + float StaticFriction = 0.5F; + MC_PROPERTY() + float DynamicFriction = 0.5F; + MC_PROPERTY() + float Restitution = 0.0F; + MC_PROPERTY() + MetaCorePhysicsMaterialCombineMode FrictionCombine = MetaCorePhysicsMaterialCombineMode::Average; + MC_PROPERTY() + MetaCorePhysicsMaterialCombineMode RestitutionCombine = MetaCorePhysicsMaterialCombineMode::Average; +}; + +MC_STRUCT() +struct MetaCorePhysicsSettingsDocument { + MC_GENERATED_BODY() + MC_PROPERTY() + std::uint32_t Version = 1U; + MC_PROPERTY() + glm::vec3 Gravity{0.0F, -9.81F, 0.0F}; + MC_PROPERTY() + double FixedTimeStep = 1.0 / 60.0; + MC_PROPERTY() + std::uint32_t MaxFixedStepsPerFrame = 4U; + MC_PROPERTY() + double MaxFrameDelta = 0.25; + MC_PROPERTY() + std::vector LayerNames{}; + MC_PROPERTY() + std::vector CollisionMasks{}; +}; + +enum class MetaCorePhysicsTriggerQuery : std::uint8_t { UseGlobal = 0, Ignore, Collide }; +enum class MetaCorePhysicsEventType : std::uint8_t { CollisionEnter, CollisionStay, CollisionExit, TriggerEnter, TriggerStay, TriggerExit, ConstraintBroken }; +enum class MetaCorePhysicsQueryShapeType : std::uint8_t { Box, Sphere, Capsule }; + +struct MetaCorePhysicsQueryFilter { + std::uint32_t LayerMask = 0xFFFFFFFFU; + MetaCorePhysicsTriggerQuery Triggers = MetaCorePhysicsTriggerQuery::Collide; + std::unordered_set IgnoredObjectIds{}; + std::unordered_set IgnoredBodyIds{}; +}; + +struct MetaCorePhysicsQueryShape { + MetaCorePhysicsQueryShapeType Type = MetaCorePhysicsQueryShapeType::Box; + glm::vec3 HalfExtents{0.5F}; + float Radius = 0.5F; + float Height = 2.0F; +}; + +struct MetaCorePhysicsHit { + MetaCoreId ColliderObjectId = 0; + MetaCoreId RigidBodyObjectId = 0; + glm::vec3 Position{0.0F}; + glm::vec3 Normal{0.0F}; + float Distance = 0.0F; + float Fraction = 0.0F; + bool IsTrigger = false; +}; + +struct MetaCorePhysicsContactEvent { + MetaCorePhysicsEventType Type = MetaCorePhysicsEventType::CollisionEnter; + MetaCoreId ObjectA = 0; + MetaCoreId ObjectB = 0; + MetaCoreId ColliderA = 0; + MetaCoreId ColliderB = 0; + glm::vec3 Point{0.0F}; + glm::vec3 Normal{0.0F}; + glm::vec3 RelativeVelocity{0.0F}; + float Impulse = 0.0F; +}; + +struct MetaCorePhysicsDebugLine { glm::vec3 Start{0.0F}; glm::vec3 End{0.0F}; glm::vec3 Color{1.0F}; }; +struct MetaCorePhysicsStatistics { std::size_t BodyCount = 0; std::size_t ContactCount = 0; std::size_t QueryCount = 0; double LastStepMilliseconds = 0.0; }; +struct MetaCorePhysicsMeshData { std::vector Positions{}; std::vector Indices{}; }; + +using MetaCorePhysicsMeshProvider = std::function(MetaCoreAssetGuid)>; +using MetaCorePhysicsMaterialProvider = std::function(MetaCoreAssetGuid)>; +using MetaCorePhysicsEventCallback = std::function; + +class MetaCorePhysicsWorld { +public: + MetaCorePhysicsWorld(MetaCoreScene& scene, MetaCorePhysicsSettingsDocument settings = {}, + MetaCorePhysicsMeshProvider meshProvider = {}, MetaCorePhysicsMaterialProvider materialProvider = {}); + ~MetaCorePhysicsWorld(); + MetaCorePhysicsWorld(const MetaCorePhysicsWorld&) = delete; + MetaCorePhysicsWorld& operator=(const MetaCorePhysicsWorld&) = delete; + + void SynchronizeScene(); + void Step(double fixedDeltaSeconds); + void SetEventCallback(MetaCorePhysicsEventCallback callback); + void Stop(); + + [[nodiscard]] std::optional Raycast(glm::vec3 origin, glm::vec3 direction, float maxDistance, + const MetaCorePhysicsQueryFilter& filter = {}); + [[nodiscard]] std::vector RaycastAll(glm::vec3 origin, glm::vec3 direction, float maxDistance, + const MetaCorePhysicsQueryFilter& filter = {}); + [[nodiscard]] std::optional ShapeCast(const MetaCorePhysicsQueryShape& shape, glm::vec3 origin, + glm::vec3 direction, float maxDistance, const MetaCorePhysicsQueryFilter& filter = {}); + [[nodiscard]] std::vector ShapeCastAll(const MetaCorePhysicsQueryShape& shape, glm::vec3 origin, + glm::vec3 direction, float maxDistance, const MetaCorePhysicsQueryFilter& filter = {}); + [[nodiscard]] std::vector Overlap(const MetaCorePhysicsQueryShape& shape, glm::vec3 center, + const MetaCorePhysicsQueryFilter& filter = {}); + + [[nodiscard]] bool AddForce(MetaCoreId objectId, glm::vec3 force); + [[nodiscard]] bool AddImpulse(MetaCoreId objectId, glm::vec3 impulse); + [[nodiscard]] bool SetLinearVelocity(MetaCoreId objectId, glm::vec3 velocity); + [[nodiscard]] std::optional GetLinearVelocity(MetaCoreId objectId) const; + [[nodiscard]] bool WakeUp(MetaCoreId objectId); + [[nodiscard]] bool CharacterMove(MetaCoreId objectId, glm::vec3 displacement); + [[nodiscard]] bool CharacterJump(MetaCoreId objectId, float speed); + [[nodiscard]] bool IsCharacterGrounded(MetaCoreId objectId) const; + [[nodiscard]] bool SetConstraintEnabled(MetaCoreId objectId, bool enabled); + [[nodiscard]] bool SetConstraintMotor(MetaCoreId objectId, bool enabled, float targetVelocity, float maxImpulse); + + [[nodiscard]] std::vector BuildDebugLines() const; + [[nodiscard]] const MetaCorePhysicsStatistics& GetStatistics() const; + [[nodiscard]] const std::vector& GetDiagnostics() const; + [[nodiscard]] const MetaCorePhysicsSettingsDocument& GetSettings() const; + +private: + class Impl; + std::unique_ptr Impl_; +}; + +class MetaCoreIPhysicsBackend { +public: + virtual ~MetaCoreIPhysicsBackend() = default; + [[nodiscard]] virtual std::string GetBackendId() const = 0; + [[nodiscard]] virtual std::unique_ptr CreateWorld(MetaCoreScene& scene, + const MetaCorePhysicsSettingsDocument& settings, MetaCorePhysicsMeshProvider meshProvider = {}, + MetaCorePhysicsMaterialProvider materialProvider = {}) = 0; +}; + +[[nodiscard]] std::unique_ptr MetaCoreCreateBulletPhysicsBackend(); +[[nodiscard]] MetaCorePhysicsSettingsDocument MetaCoreBuildDefaultPhysicsSettings(); +[[nodiscard]] bool MetaCoreValidatePhysicsSettings(MetaCorePhysicsSettingsDocument& document, std::vector* issues = nullptr); +[[nodiscard]] bool MetaCoreWritePhysicsSettings(const std::filesystem::path& path, const MetaCorePhysicsSettingsDocument& document, + const MetaCoreTypeRegistry& registry); +[[nodiscard]] std::optional MetaCoreReadPhysicsSettings(const std::filesystem::path& path, + const MetaCoreTypeRegistry& registry); +[[nodiscard]] bool MetaCoreWritePhysicsMaterial(const std::filesystem::path& path, const MetaCorePhysicsMaterialDocument& document, + const MetaCoreTypeRegistry& registry); +[[nodiscard]] std::optional MetaCoreReadPhysicsMaterial(const std::filesystem::path& path, + const MetaCoreTypeRegistry& registry); + +} // namespace MetaCore diff --git a/Source/MetaCoreRuntimeData/Public/MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h b/Source/MetaCoreRuntimeData/Public/MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h index 3917624..1d3100d 100644 --- a/Source/MetaCoreRuntimeData/Public/MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h +++ b/Source/MetaCoreRuntimeData/Public/MetaCoreRuntimeData/MetaCoreRuntimeDataProject.h @@ -122,6 +122,9 @@ struct MetaCoreRuntimeProjectDocument { MC_PROPERTY() std::filesystem::path InputMapPath{}; + + MC_PROPERTY() + std::filesystem::path PhysicsSettingsPath{}; }; MC_STRUCT() diff --git a/Source/MetaCoreRuntimeInput/Private/MetaCoreRuntimeInteraction.cpp b/Source/MetaCoreRuntimeInput/Private/MetaCoreRuntimeInteraction.cpp index ec6470c..b281069 100644 --- a/Source/MetaCoreRuntimeInput/Private/MetaCoreRuntimeInteraction.cpp +++ b/Source/MetaCoreRuntimeInput/Private/MetaCoreRuntimeInteraction.cpp @@ -1,4 +1,6 @@ #include "MetaCoreRuntimeInput/MetaCoreRuntimeInteraction.h" +#include "MetaCorePhysics/MetaCorePhysics.h" +#include "MetaCoreRender/MetaCoreSceneRenderSync.h" #include "MetaCorePlatform/MetaCoreInput.h" #include "MetaCoreRender/MetaCoreEditorViewportRenderer.h" @@ -8,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -71,6 +74,36 @@ void MetaCoreRuntimeInteraction::Update(MetaCoreScene& scene, MetaCoreEditorView }, input, viewport, mouseConsumed); } +void MetaCoreRuntimeInteraction::Update(MetaCoreScene& scene, MetaCoreEditorViewportRenderer& renderer, + MetaCorePhysicsWorld& physics, const MetaCoreSceneView& sceneView, const MetaCoreInput& input, + const MetaCoreViewportRect& viewport, bool mouseConsumed) { + Update(scene, [this, &scene, &renderer, &physics, sceneView, viewport](std::uint32_t x, std::uint32_t y, PickCallback callback) { + const glm::vec2 screenPosition{ + viewport.Left + static_cast(x), + viewport.Top + viewport.Height - 1.0F - static_cast(y)}; + const MetaCoreCameraRay ray = MetaCoreScreenPointToRay(sceneView, viewport, screenPosition); + std::optional physicsHit; + if (const auto candidate = physics.Raycast(ray.Origin, ray.Direction, std::max(sceneView.FarClip, 0.1F)); candidate.has_value()) { + MetaCoreId target = candidate->ColliderObjectId; + if (!Impl_->IsInteractive(scene, target, MetaCoreInteraction_All)) target = candidate->RigidBodyObjectId; + if (Impl_->IsInteractive(scene, target, MetaCoreInteraction_All)) { + physicsHit = *candidate; + physicsHit->ColliderObjectId = target; + } + } + return renderer.RequestPick(x, y, [callback = std::move(callback), physicsHit, origin = ray.Origin](const MetaCoreFilamentSceneBridge::PickResult& gpu) { + if (physicsHit.has_value()) { + const float gpuDistance = gpu.ObjectId == 0 ? std::numeric_limits::infinity() : glm::length(gpu.WorldPosition - origin); + if (physicsHit->Distance <= gpuDistance) { + callback({physicsHit->ColliderObjectId, physicsHit->Position}); + return; + } + } + callback({gpu.ObjectId, gpu.WorldPosition}); + }); + }, input, viewport, mouseConsumed); +} + void MetaCoreRuntimeInteraction::Update(MetaCoreScene& scene, const PickRequest& requestPick, const MetaCoreInput& input, const MetaCoreViewportRect& viewport, bool mouseConsumed) { std::vector completed; { std::scoped_lock lock(Impl_->Async->Mutex); completed.swap(Impl_->Async->CompletedQueries); } diff --git a/Source/MetaCoreRuntimeInput/Public/MetaCoreRuntimeInput/MetaCoreRuntimeInteraction.h b/Source/MetaCoreRuntimeInput/Public/MetaCoreRuntimeInput/MetaCoreRuntimeInteraction.h index 6c92272..e84c139 100644 --- a/Source/MetaCoreRuntimeInput/Public/MetaCoreRuntimeInput/MetaCoreRuntimeInteraction.h +++ b/Source/MetaCoreRuntimeInput/Public/MetaCoreRuntimeInput/MetaCoreRuntimeInteraction.h @@ -12,10 +12,13 @@ namespace MetaCore { +class MetaCorePhysicsWorld; + class MetaCoreInput; class MetaCoreScene; class MetaCoreEditorViewportRenderer; struct MetaCoreViewportRect; +struct MetaCoreSceneView; enum class MetaCorePointerEventType : std::uint8_t { Enter, Move, Leave, Down, Up, Click, DragStart, Drag, DragEnd, Cancel }; struct MetaCorePointerEvent { @@ -40,6 +43,8 @@ public: ~MetaCoreRuntimeInteraction(); void SetEventCallback(EventCallback callback); void Update(MetaCoreScene& scene, MetaCoreEditorViewportRenderer& renderer, const MetaCoreInput& input, const MetaCoreViewportRect& viewport, bool mouseConsumed); + void Update(MetaCoreScene& scene, MetaCoreEditorViewportRenderer& renderer, MetaCorePhysicsWorld& physics, + const MetaCoreSceneView& sceneView, const MetaCoreInput& input, const MetaCoreViewportRect& viewport, bool mouseConsumed); void Update(MetaCoreScene& scene, const PickRequest& requestPick, const MetaCoreInput& input, const MetaCoreViewportRect& viewport, bool mouseConsumed); void Cancel(); private: diff --git a/Source/MetaCoreScene/Private/MetaCoreScene.cpp b/Source/MetaCoreScene/Private/MetaCoreScene.cpp index 69ad2b9..e4639b8 100644 --- a/Source/MetaCoreScene/Private/MetaCoreScene.cpp +++ b/Source/MetaCoreScene/Private/MetaCoreScene.cpp @@ -295,6 +295,14 @@ std::vector MetaCoreScene::DuplicateGameObjects(const std::vector(sourceObject.GetComponent()); if (sourceObject.HasComponent()) clonedObject.AddComponent(sourceObject.GetComponent()); + if (sourceObject.HasComponent()) + clonedObject.AddComponent(sourceObject.GetComponent()); + if (sourceObject.HasComponent()) + clonedObject.AddComponent(sourceObject.GetComponent()); + if (sourceObject.HasComponent()) + clonedObject.AddComponent(sourceObject.GetComponent()); + if (sourceObject.HasComponent()) + clonedObject.AddComponent(sourceObject.GetComponent()); if (sourceObject.HasComponent()) clonedObject.AddComponent(sourceObject.GetComponent()); @@ -463,6 +471,14 @@ MetaCoreSceneSnapshot MetaCoreScene::CaptureSnapshot() const { data.UiRenderer = obj.GetComponent(); if (obj.HasComponent()) data.Interactable = obj.GetComponent(); + if (obj.HasComponent()) + data.Collider = obj.GetComponent(); + if (obj.HasComponent()) + data.RigidBody = obj.GetComponent(); + if (obj.HasComponent()) + data.PhysicsConstraint = obj.GetComponent(); + if (obj.HasComponent()) + data.CharacterController = obj.GetComponent(); if (obj.HasComponent()) data.PrefabInstance = obj.GetComponent(); if (obj.HasComponent()) @@ -500,6 +516,14 @@ void MetaCoreScene::RestoreSnapshot(const MetaCoreSceneSnapshot& snapshot) { obj.AddComponent(data.UiRenderer.value()); if (data.Interactable.has_value()) obj.AddComponent(data.Interactable.value()); + if (data.Collider.has_value()) + obj.AddComponent(data.Collider.value()); + if (data.RigidBody.has_value()) + obj.AddComponent(data.RigidBody.value()); + if (data.PhysicsConstraint.has_value()) + obj.AddComponent(data.PhysicsConstraint.value()); + if (data.CharacterController.has_value()) + obj.AddComponent(data.CharacterController.value()); if (data.PrefabInstance.has_value()) obj.AddComponent(data.PrefabInstance.value()); if (data.ModelRootTag.has_value()) diff --git a/Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp b/Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp index 75cc551..64e50b5 100644 --- a/Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp +++ b/Source/MetaCoreScene/Private/MetaCoreSceneSerializer.cpp @@ -199,6 +199,36 @@ static json GameObjectDataToJson(const MetaCoreGameObjectData& obj, const MetaCo {"EventMask", obj.Interactable->EventMask}, {"DragThresholdPixels", obj.Interactable->DragThresholdPixels} }; + } else if (objField.Name == "Collider" && obj.Collider.has_value()) { + const auto& value = *obj.Collider; + objJson["Collider"] = {{"Enabled", value.Enabled}, {"Shape", static_cast(value.Shape)}, + {"Center", Vec3ToJson(value.Center)}, {"Size", Vec3ToJson(value.Size)}, {"Radius", value.Radius}, + {"Height", value.Height}, {"MeshAssetGuid", GuidToString(value.MeshAssetGuid)}, + {"MaterialAssetGuid", GuidToString(value.MaterialAssetGuid)}, {"CollisionLayer", value.CollisionLayer}, + {"IsTrigger", value.IsTrigger}}; + } else if (objField.Name == "RigidBody" && obj.RigidBody.has_value()) { + const auto& value = *obj.RigidBody; + objJson["RigidBody"] = {{"BodyType", static_cast(value.BodyType)}, {"Mass", value.Mass}, + {"LinearDamping", value.LinearDamping}, {"AngularDamping", value.AngularDamping}, + {"GravityScale", value.GravityScale}, {"InitialLinearVelocity", Vec3ToJson(value.InitialLinearVelocity)}, + {"InitialAngularVelocity", Vec3ToJson(value.InitialAngularVelocity)}, {"AllowSleep", value.AllowSleep}, + {"ContinuousCollisionDetection", value.ContinuousCollisionDetection}, {"LockedAxes", value.LockedAxes}}; + } else if (objField.Name == "PhysicsConstraint" && obj.PhysicsConstraint.has_value()) { + const auto& value = *obj.PhysicsConstraint; + objJson["PhysicsConstraint"] = {{"Enabled", value.Enabled}, {"Type", static_cast(value.Type)}, + {"TargetObjectId", value.TargetObjectId}, {"Anchor", Vec3ToJson(value.Anchor)}, + {"TargetAnchor", Vec3ToJson(value.TargetAnchor)}, {"Axis", Vec3ToJson(value.Axis)}, + {"LinearLowerLimit", Vec3ToJson(value.LinearLowerLimit)}, {"LinearUpperLimit", Vec3ToJson(value.LinearUpperLimit)}, + {"AngularLowerLimit", Vec3ToJson(value.AngularLowerLimit)}, {"AngularUpperLimit", Vec3ToJson(value.AngularUpperLimit)}, + {"MotorEnabled", value.MotorEnabled}, {"MotorTargetVelocity", value.MotorTargetVelocity}, + {"MotorMaxImpulse", value.MotorMaxImpulse}, {"BreakForce", value.BreakForce}, + {"BreakTorque", value.BreakTorque}, {"EnableConnectedCollision", value.EnableConnectedCollision}}; + } else if (objField.Name == "CharacterController" && obj.CharacterController.has_value()) { + const auto& value = *obj.CharacterController; + objJson["CharacterController"] = {{"Enabled", value.Enabled}, {"Radius", value.Radius}, + {"Height", value.Height}, {"SkinWidth", value.SkinWidth}, {"MaxSlopeDegrees", value.MaxSlopeDegrees}, + {"StepHeight", value.StepHeight}, {"GravityScale", value.GravityScale}, {"MaxFallSpeed", value.MaxFallSpeed}, + {"PushForce", value.PushForce}, {"CollisionLayer", value.CollisionLayer}}; } else if (objField.Name == "Camera" && obj.Camera.has_value()) { const MetaCoreStructDescriptor* camDesc = registry.FindStruct(); if (camDesc) { @@ -392,6 +422,54 @@ static MetaCoreGameObjectData JsonToGameObjectData(const json& objJson, const Me if (value.contains("EventMask")) interactable.EventMask = value["EventMask"].get(); if (value.contains("DragThresholdPixels")) interactable.DragThresholdPixels = value["DragThresholdPixels"].get(); obj.Interactable = interactable; + } else if (objField.Name == "Collider" && objJson.contains("Collider")) { + const auto& value = objJson["Collider"]; + MetaCoreColliderComponent component; + component.Enabled = value.value("Enabled", true); + component.Shape = static_cast(value.value("Shape", 0)); + if (value.contains("Center")) component.Center = JsonToVec3(value["Center"]); + if (value.contains("Size")) component.Size = JsonToVec3(value["Size"]); + component.Radius = value.value("Radius", 0.5F); component.Height = value.value("Height", 2.0F); + if (value.contains("MeshAssetGuid")) component.MeshAssetGuid = StringToGuid(value["MeshAssetGuid"].get()); + if (value.contains("MaterialAssetGuid")) component.MaterialAssetGuid = StringToGuid(value["MaterialAssetGuid"].get()); + component.CollisionLayer = value.value("CollisionLayer", 0U); component.IsTrigger = value.value("IsTrigger", false); + obj.Collider = component; + } else if (objField.Name == "RigidBody" && objJson.contains("RigidBody")) { + const auto& value = objJson["RigidBody"]; + MetaCoreRigidBodyComponent component; + component.BodyType = static_cast(value.value("BodyType", 0)); + component.Mass = value.value("Mass", 1.0F); component.LinearDamping = value.value("LinearDamping", 0.0F); + component.AngularDamping = value.value("AngularDamping", 0.05F); component.GravityScale = value.value("GravityScale", 1.0F); + if (value.contains("InitialLinearVelocity")) component.InitialLinearVelocity = JsonToVec3(value["InitialLinearVelocity"]); + if (value.contains("InitialAngularVelocity")) component.InitialAngularVelocity = JsonToVec3(value["InitialAngularVelocity"]); + component.AllowSleep = value.value("AllowSleep", true); + component.ContinuousCollisionDetection = value.value("ContinuousCollisionDetection", false); + component.LockedAxes = value.value("LockedAxes", 0U); obj.RigidBody = component; + } else if (objField.Name == "PhysicsConstraint" && objJson.contains("PhysicsConstraint")) { + const auto& value = objJson["PhysicsConstraint"]; + MetaCorePhysicsConstraintComponent component; + component.Enabled = value.value("Enabled", true); component.Type = static_cast(value.value("Type", 0)); + component.TargetObjectId = value.value("TargetObjectId", MetaCoreId{0}); + if (value.contains("Anchor")) component.Anchor = JsonToVec3(value["Anchor"]); + if (value.contains("TargetAnchor")) component.TargetAnchor = JsonToVec3(value["TargetAnchor"]); + if (value.contains("Axis")) component.Axis = JsonToVec3(value["Axis"]); + if (value.contains("LinearLowerLimit")) component.LinearLowerLimit = JsonToVec3(value["LinearLowerLimit"]); + if (value.contains("LinearUpperLimit")) component.LinearUpperLimit = JsonToVec3(value["LinearUpperLimit"]); + if (value.contains("AngularLowerLimit")) component.AngularLowerLimit = JsonToVec3(value["AngularLowerLimit"]); + if (value.contains("AngularUpperLimit")) component.AngularUpperLimit = JsonToVec3(value["AngularUpperLimit"]); + component.MotorEnabled = value.value("MotorEnabled", false); component.MotorTargetVelocity = value.value("MotorTargetVelocity", 0.0F); + component.MotorMaxImpulse = value.value("MotorMaxImpulse", 0.0F); component.BreakForce = value.value("BreakForce", 0.0F); + component.BreakTorque = value.value("BreakTorque", 0.0F); component.EnableConnectedCollision = value.value("EnableConnectedCollision", false); + obj.PhysicsConstraint = component; + } else if (objField.Name == "CharacterController" && objJson.contains("CharacterController")) { + const auto& value = objJson["CharacterController"]; + MetaCoreCharacterControllerComponent component; + component.Enabled = value.value("Enabled", true); component.Radius = value.value("Radius", 0.4F); + component.Height = value.value("Height", 1.8F); component.SkinWidth = value.value("SkinWidth", 0.05F); + component.MaxSlopeDegrees = value.value("MaxSlopeDegrees", 45.0F); component.StepHeight = value.value("StepHeight", 0.3F); + component.GravityScale = value.value("GravityScale", 1.0F); component.MaxFallSpeed = value.value("MaxFallSpeed", 55.0F); + component.PushForce = value.value("PushForce", 1.0F); component.CollisionLayer = value.value("CollisionLayer", 0U); + obj.CharacterController = component; } else if (objField.Name == "Camera" && objJson.contains("Camera")) { const auto& camJson = objJson["Camera"]; const MetaCoreStructDescriptor* camDesc = registry.FindStruct(); diff --git a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h index 1359218..4f64740 100644 --- a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h +++ b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreComponents.h @@ -1,6 +1,7 @@ #pragma once #include "MetaCoreFoundation/MetaCoreAssetGuid.h" +#include "MetaCoreFoundation/MetaCoreId.h" #include "MetaCoreFoundation/MetaCoreReflection.h" #include @@ -51,6 +52,30 @@ enum class MetaCoreUiRenderMode { WorldSpace }; +MC_ENUM() +enum class MetaCoreColliderShape { + Box = 0, + Sphere, + Capsule, + ConvexHull, + TriangleMesh +}; + +MC_ENUM() +enum class MetaCoreRigidBodyType { + Static = 0, + Kinematic, + Dynamic +}; + +MC_ENUM() +enum class MetaCorePhysicsConstraintType { + Fixed = 0, + Hinge, + Slider, + SixDof +}; + MC_STRUCT() struct MetaCoreTransformComponent { MC_GENERATED_BODY() @@ -264,6 +289,122 @@ struct MetaCoreInteractableComponent { float DragThresholdPixels = 5.0F; }; +MC_STRUCT() +struct MetaCoreColliderComponent { + MC_GENERATED_BODY() + + MC_PROPERTY() + bool Enabled = true; + MC_PROPERTY() + MetaCoreColliderShape Shape = MetaCoreColliderShape::Box; + MC_PROPERTY() + glm::vec3 Center{0.0F}; + MC_PROPERTY() + glm::vec3 Size{1.0F}; + MC_PROPERTY() + float Radius = 0.5F; + MC_PROPERTY() + float Height = 2.0F; + MC_PROPERTY() + MetaCoreAssetGuid MeshAssetGuid{}; + MC_PROPERTY() + MetaCoreAssetGuid MaterialAssetGuid{}; + MC_PROPERTY() + std::uint32_t CollisionLayer = 0; + MC_PROPERTY() + bool IsTrigger = false; +}; + +MC_STRUCT() +struct MetaCoreRigidBodyComponent { + MC_GENERATED_BODY() + + MC_PROPERTY() + MetaCoreRigidBodyType BodyType = MetaCoreRigidBodyType::Static; + MC_PROPERTY() + float Mass = 1.0F; + MC_PROPERTY() + float LinearDamping = 0.0F; + MC_PROPERTY() + float AngularDamping = 0.05F; + MC_PROPERTY() + float GravityScale = 1.0F; + MC_PROPERTY() + glm::vec3 InitialLinearVelocity{0.0F}; + MC_PROPERTY() + glm::vec3 InitialAngularVelocity{0.0F}; + MC_PROPERTY() + bool AllowSleep = true; + MC_PROPERTY() + bool ContinuousCollisionDetection = false; + MC_PROPERTY() + std::uint32_t LockedAxes = 0; +}; + +MC_STRUCT() +struct MetaCorePhysicsConstraintComponent { + MC_GENERATED_BODY() + + MC_PROPERTY() + bool Enabled = true; + MC_PROPERTY() + MetaCorePhysicsConstraintType Type = MetaCorePhysicsConstraintType::Fixed; + MC_PROPERTY() + MetaCoreId TargetObjectId = 0; + MC_PROPERTY() + glm::vec3 Anchor{0.0F}; + MC_PROPERTY() + glm::vec3 TargetAnchor{0.0F}; + MC_PROPERTY() + glm::vec3 Axis{1.0F, 0.0F, 0.0F}; + MC_PROPERTY() + glm::vec3 LinearLowerLimit{0.0F}; + MC_PROPERTY() + glm::vec3 LinearUpperLimit{0.0F}; + MC_PROPERTY() + glm::vec3 AngularLowerLimit{0.0F}; + MC_PROPERTY() + glm::vec3 AngularUpperLimit{0.0F}; + MC_PROPERTY() + bool MotorEnabled = false; + MC_PROPERTY() + float MotorTargetVelocity = 0.0F; + MC_PROPERTY() + float MotorMaxImpulse = 0.0F; + MC_PROPERTY() + float BreakForce = 0.0F; + MC_PROPERTY() + float BreakTorque = 0.0F; + MC_PROPERTY() + bool EnableConnectedCollision = false; +}; + +MC_STRUCT() +struct MetaCoreCharacterControllerComponent { + MC_GENERATED_BODY() + + MC_PROPERTY() + bool Enabled = true; + MC_PROPERTY() + float Radius = 0.4F; + MC_PROPERTY() + float Height = 1.8F; + MC_PROPERTY() + float SkinWidth = 0.05F; + MC_PROPERTY() + float MaxSlopeDegrees = 45.0F; + MC_PROPERTY() + float StepHeight = 0.3F; + MC_PROPERTY() + float GravityScale = 1.0F; + MC_PROPERTY() + float MaxFallSpeed = 55.0F; + MC_PROPERTY() + float PushForce = 1.0F; + MC_PROPERTY() + std::uint32_t CollisionLayer = 0; +}; + MC_STRUCT() struct MetaCoreModelRootTag { MC_GENERATED_BODY() diff --git a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h index cf2ddb5..af38ef7 100644 --- a/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h +++ b/Source/MetaCoreScene/Public/MetaCoreScene/MetaCoreGameObject.h @@ -139,6 +139,14 @@ struct MetaCoreGameObjectData { std::optional UiRenderer; MC_PROPERTY() std::optional Interactable; + MC_PROPERTY() + std::optional Collider; + MC_PROPERTY() + std::optional RigidBody; + MC_PROPERTY() + std::optional PhysicsConstraint; + MC_PROPERTY() + std::optional CharacterController; }; } // namespace MetaCore diff --git a/Source/MetaCoreScripting/Private/MetaCoreScripting.cpp b/Source/MetaCoreScripting/Private/MetaCoreScripting.cpp index d6c7682..cd88395 100644 --- a/Source/MetaCoreScripting/Private/MetaCoreScripting.cpp +++ b/Source/MetaCoreScripting/Private/MetaCoreScripting.cpp @@ -1,5 +1,6 @@ #include "MetaCoreScripting/MetaCoreScripting.h" #include "MetaCoreRuntimeInput/MetaCoreRuntimeInput.h" +#include "MetaCorePhysics/MetaCorePhysics.h" #include "MetaCoreFoundation/MetaCoreHash.h" #include @@ -449,9 +450,11 @@ struct MetaCoreScriptRuntime::Impl { std::uint64_t NextTimer = 1; bool Running = false; MetaCoreRuntimeInput* Input = nullptr; + MetaCorePhysicsWorld* Physics = nullptr; - Impl(MetaCoreScriptRuntime& owner, MetaCoreScene& scene, MetaCoreScriptRegistry& registry, MetaCoreScriptLogHandler logger, MetaCoreRuntimeInput* input) - : Owner(owner), Scene(scene), Registry(registry), Logger(std::move(logger)), Input(input) {} + Impl(MetaCoreScriptRuntime& owner, MetaCoreScene& scene, MetaCoreScriptRegistry& registry, MetaCoreScriptLogHandler logger, + MetaCoreRuntimeInput* input, MetaCorePhysicsWorld* physics) + : Owner(owner), Scene(scene), Registry(registry), Logger(std::move(logger)), Input(input), Physics(physics) {} void Log(std::uint32_t level, std::string_view category, std::string_view text) const { if (Logger) Logger(level, category, text); } Entry* FindEntry(MetaCoreId object, std::uint64_t instance) { @@ -556,8 +559,9 @@ struct MetaCoreScriptRuntime::Impl { } }; -MetaCoreScriptRuntime::MetaCoreScriptRuntime(MetaCoreScene& scene, MetaCoreScriptRegistry& registry, MetaCoreScriptLogHandler logger, MetaCoreRuntimeInput* input) - : Impl_(std::make_unique(*this, scene, registry, std::move(logger), input)) {} +MetaCoreScriptRuntime::MetaCoreScriptRuntime(MetaCoreScene& scene, MetaCoreScriptRegistry& registry, MetaCoreScriptLogHandler logger, + MetaCoreRuntimeInput* input, MetaCorePhysicsWorld* physics) + : Impl_(std::make_unique(*this, scene, registry, std::move(logger), input, physics)) {} MetaCoreScriptRuntime::~MetaCoreScriptRuntime() { Stop(); } void MetaCoreScriptRuntime::Start() { if (Impl_->Running) return; Impl_->Running = true; ++WorldGeneration_; for (auto o : Impl_->Scene.GetGameObjects()) if (o.HasComponent()) for (auto& i : o.GetComponent().Instances) if (i.Enabled) Impl_->Ensure(o, i); } void MetaCoreScriptRuntime::FixedUpdate(double delta) { if (!Impl_->Running) return; Time_.FixedDeltaSeconds = std::max(0.0, delta); Impl_->Dispatch(Impl::Phase::Fixed, Time_.FixedDeltaSeconds); } @@ -580,6 +584,7 @@ void MetaCoreScriptRuntime::Unsubscribe(std::uint64_t id) { std::erase_if(Impl_- void MetaCoreScriptRuntime::Publish(std::string topic, std::vector fields) { Impl_->Events.push_back({0, std::move(topic), std::move(fields)}); } void MetaCoreScriptRuntime::PublishToObject(MetaCoreId object, std::string topic, std::vector fields) { Impl_->Events.push_back({object, std::move(topic), std::move(fields)}); } MetaCoreRuntimeInput* MetaCoreScriptRuntime::GetRuntimeInput() const { return Impl_->Input; } +MetaCorePhysicsWorld* MetaCoreScriptRuntime::GetPhysicsWorld() const { return Impl_->Physics; } std::uint64_t MetaCoreScriptRuntime::ScheduleTimer(MetaCoreId object, std::uint64_t instance, double delay, double interval, bool unscaled, std::function callback) { const auto id = Impl_->NextTimer++; Impl_->Timers.push_back({id, object, instance, std::max(0.0, delay), std::max(0.0, interval), unscaled, std::move(callback)}); return id; } void MetaCoreScriptRuntime::CancelTimer(std::uint64_t id) { std::erase_if(Impl_->Timers, [id](const auto& t) { return t.Id == id; }); } bool MetaCoreScriptRuntime::IsHandleValid(MetaCoreScriptObjectHandleV1 handle) const { return handle.WorldGeneration == WorldGeneration_ && handle.ObjectId != 0 && static_cast(Impl_->Scene.FindGameObject(handle.ObjectId)); } @@ -647,7 +652,24 @@ bool HInputAxis(void* c, const char* id, float* out) noexcept { try { if (!c || bool HInputContext(void* c, const char* id, bool enabled) noexcept { try { auto* input = c ? static_cast(c)->GetRuntimeInput() : nullptr; return input && id && input->SetContextEnabled(id, enabled); } catch (...) { return false; } } bool HInputRebind(void* c, const char* id, const char* path) noexcept { try { auto* input = c ? static_cast(c)->GetRuntimeInput() : nullptr; return input && id && path && input->Rebind(id, path); } catch (...) { return false; } } bool HInputReset(void* c, const char* id) noexcept { try { auto* input = c ? static_cast(c)->GetRuntimeInput() : nullptr; return input && id && input->ResetBinding(id); } catch (...) { return false; } } -const MetaCoreScriptHostApiV1& HostApi() { static const MetaCoreScriptHostApiV1 api{METACORE_SCRIPT_ABI_MAJOR, METACORE_SCRIPT_ABI_MINOR, METACORE_SCRIPT_HOST_CAPABILITIES, HValid, HHas, HGet, HSet, HGetField, HSetField, HLog, HPublish, HSubscribe, HUnsubscribe, HSchedule, HCancel, HInputAction, HInputAxis, HInputContext, HInputRebind, HInputReset}; return api; } +glm::vec3 HVec(const double* value) { return value ? glm::vec3(static_cast(value[0]), static_cast(value[1]), static_cast(value[2])) : glm::vec3(0.0F); } +void HHit(const MetaCorePhysicsHit& source, std::uint64_t generation, MetaCoreScriptPhysicsHitV1& target) { target = {}; target.Collider = {generation, source.ColliderObjectId}; target.Body = {generation, source.RigidBodyObjectId}; for (int i=0;i<3;++i) { target.Position[i]=source.Position[i]; target.Normal[i]=source.Normal[i]; } target.Distance=source.Distance; target.Fraction=source.Fraction; target.IsTrigger=source.IsTrigger; } +MetaCorePhysicsQueryShape HShape(const MetaCoreScriptPhysicsQueryShapeV1& source) { MetaCorePhysicsQueryShape result; result.Type=static_cast(source.Type); result.HalfExtents=HVec(source.HalfExtents); result.Radius=static_cast(source.Radius); result.Height=static_cast(source.Height); return result; } +MetaCorePhysicsQueryFilter HFilter(std::uint32_t mask, bool triggers) { MetaCorePhysicsQueryFilter result; result.LayerMask=mask; result.Triggers=triggers?MetaCorePhysicsTriggerQuery::Collide:MetaCorePhysicsTriggerQuery::Ignore; return result; } +bool HPhysicsRay(void* c,const double* o,const double* d,double distance,std::uint32_t mask,bool triggers,MetaCoreScriptPhysicsHitV1* out) noexcept { try { auto* runtime=static_cast(c); auto* physics=runtime?runtime->GetPhysicsWorld():nullptr; if(!physics||!out)return false; auto hit=physics->Raycast(HVec(o),HVec(d),static_cast(distance),HFilter(mask,triggers)); if(!hit)return false; HHit(*hit,runtime->GetWorldGeneration(),*out); return true;}catch(...){return false;} } +bool HPhysicsShape(void* c,const MetaCoreScriptPhysicsQueryShapeV1* s,const double* o,const double* d,double distance,std::uint32_t mask,bool triggers,MetaCoreScriptPhysicsHitV1* out) noexcept { try { auto* runtime=static_cast(c); auto* physics=runtime?runtime->GetPhysicsWorld():nullptr; if(!physics||!s||!out)return false; auto hit=physics->ShapeCast(HShape(*s),HVec(o),HVec(d),static_cast(distance),HFilter(mask,triggers)); if(!hit)return false; HHit(*hit,runtime->GetWorldGeneration(),*out); return true;}catch(...){return false;} } +std::size_t HPhysicsOverlap(void* c,const MetaCoreScriptPhysicsQueryShapeV1* s,const double* center,std::uint32_t mask,bool triggers,MetaCoreScriptPhysicsHitV1* out,std::size_t capacity) noexcept { try { auto* runtime=static_cast(c); auto* physics=runtime?runtime->GetPhysicsWorld():nullptr; if(!physics||!s)return 0; auto hits=physics->Overlap(HShape(*s),HVec(center),HFilter(mask,triggers)); const auto count=std::min(capacity,hits.size()); for(std::size_t i=0;iGetWorldGeneration(),out[i]); return hits.size();}catch(...){return 0;} } +bool HPhysicsForce(void* c,MetaCoreScriptObjectHandleV1 h,const double* v) noexcept { try { auto* r=static_cast(c); return r&&r->IsHandleValid(h)&&r->GetPhysicsWorld()&&r->GetPhysicsWorld()->AddForce(h.ObjectId,HVec(v));}catch(...){return false;} } +bool HPhysicsImpulse(void* c,MetaCoreScriptObjectHandleV1 h,const double* v) noexcept { try { auto* r=static_cast(c); return r&&r->IsHandleValid(h)&&r->GetPhysicsWorld()&&r->GetPhysicsWorld()->AddImpulse(h.ObjectId,HVec(v));}catch(...){return false;} } +bool HPhysicsGetVelocity(void* c,MetaCoreScriptObjectHandleV1 h,double* out) noexcept { try { auto* r=static_cast(c); if(!r||!out||!r->IsHandleValid(h)||!r->GetPhysicsWorld())return false; auto v=r->GetPhysicsWorld()->GetLinearVelocity(h.ObjectId); if(!v)return false; out[0]=v->x;out[1]=v->y;out[2]=v->z;return true;}catch(...){return false;} } +bool HPhysicsSetVelocity(void* c,MetaCoreScriptObjectHandleV1 h,const double* v) noexcept { try { auto* r=static_cast(c); return r&&r->IsHandleValid(h)&&r->GetPhysicsWorld()&&r->GetPhysicsWorld()->SetLinearVelocity(h.ObjectId,HVec(v));}catch(...){return false;} } +bool HPhysicsWake(void* c,MetaCoreScriptObjectHandleV1 h) noexcept { try { auto* r=static_cast(c); return r&&r->IsHandleValid(h)&&r->GetPhysicsWorld()&&r->GetPhysicsWorld()->WakeUp(h.ObjectId);}catch(...){return false;} } +bool HPhysicsMove(void* c,MetaCoreScriptObjectHandleV1 h,const double* v) noexcept { try { auto* r=static_cast(c); return r&&r->IsHandleValid(h)&&r->GetPhysicsWorld()&&r->GetPhysicsWorld()->CharacterMove(h.ObjectId,HVec(v));}catch(...){return false;} } +bool HPhysicsJump(void* c,MetaCoreScriptObjectHandleV1 h,double speed) noexcept { try { auto* r=static_cast(c); return r&&r->IsHandleValid(h)&&r->GetPhysicsWorld()&&r->GetPhysicsWorld()->CharacterJump(h.ObjectId,static_cast(speed));}catch(...){return false;} } +bool HPhysicsGrounded(void* c,MetaCoreScriptObjectHandleV1 h,bool* out) noexcept { try { auto* r=static_cast(c); if(!r||!out||!r->IsHandleValid(h)||!r->GetPhysicsWorld())return false; *out=r->GetPhysicsWorld()->IsCharacterGrounded(h.ObjectId); return true;}catch(...){return false;} } +bool HPhysicsConstraintEnabled(void* c,MetaCoreScriptObjectHandleV1 h,bool enabled) noexcept { try { auto* r=static_cast(c); return r&&r->IsHandleValid(h)&&r->GetPhysicsWorld()&&r->GetPhysicsWorld()->SetConstraintEnabled(h.ObjectId,enabled);}catch(...){return false;} } +bool HPhysicsConstraintMotor(void* c,MetaCoreScriptObjectHandleV1 h,bool enabled,double velocity,double impulse) noexcept { try { auto* r=static_cast(c); return r&&r->IsHandleValid(h)&&r->GetPhysicsWorld()&&r->GetPhysicsWorld()->SetConstraintMotor(h.ObjectId,enabled,static_cast(velocity),static_cast(impulse));}catch(...){return false;} } +const MetaCoreScriptHostApiV1& HostApi() { static const MetaCoreScriptHostApiV1 api{METACORE_SCRIPT_ABI_MAJOR, METACORE_SCRIPT_ABI_MINOR, METACORE_SCRIPT_HOST_CAPABILITIES, HValid, HHas, HGet, HSet, HGetField, HSetField, HLog, HPublish, HSubscribe, HUnsubscribe, HSchedule, HCancel, HInputAction, HInputAxis, HInputContext, HInputRebind, HInputReset, HPhysicsRay, HPhysicsShape, HPhysicsOverlap, HPhysicsForce, HPhysicsImpulse, HPhysicsGetVelocity, HPhysicsSetVelocity, HPhysicsWake, HPhysicsMove, HPhysicsJump, HPhysicsGrounded, HPhysicsConstraintEnabled, HPhysicsConstraintMotor}; return api; } } // namespace bool MetaCoreGenerateScriptProject(const std::filesystem::path& root, std::string_view projectName, const std::filesystem::path& sdkRoot, std::string* error) { diff --git a/Source/MetaCoreScripting/Public/MetaCoreScripting/MetaCoreScriptAbi.h b/Source/MetaCoreScripting/Public/MetaCoreScripting/MetaCoreScriptAbi.h index defe5ac..251d9ad 100644 --- a/Source/MetaCoreScripting/Public/MetaCoreScripting/MetaCoreScriptAbi.h +++ b/Source/MetaCoreScripting/Public/MetaCoreScripting/MetaCoreScriptAbi.h @@ -12,13 +12,15 @@ extern "C" { inline constexpr std::uint32_t METACORE_SCRIPT_ABI_MAJOR = 1U; -inline constexpr std::uint32_t METACORE_SCRIPT_ABI_MINOR = 1U; +inline constexpr std::uint32_t METACORE_SCRIPT_ABI_MINOR = 2U; inline constexpr std::uint64_t METACORE_SCRIPT_CAP_PROPERTIES = 1ULL << 0U; inline constexpr std::uint64_t METACORE_SCRIPT_CAP_EVENTS = 1ULL << 1U; inline constexpr std::uint64_t METACORE_SCRIPT_CAP_TIMERS = 1ULL << 2U; inline constexpr std::uint64_t METACORE_SCRIPT_CAP_INPUT = 1ULL << 3U; +inline constexpr std::uint64_t METACORE_SCRIPT_CAP_PHYSICS = 1ULL << 4U; inline constexpr std::uint64_t METACORE_SCRIPT_HOST_CAPABILITIES = - METACORE_SCRIPT_CAP_PROPERTIES | METACORE_SCRIPT_CAP_EVENTS | METACORE_SCRIPT_CAP_TIMERS | METACORE_SCRIPT_CAP_INPUT; + METACORE_SCRIPT_CAP_PROPERTIES | METACORE_SCRIPT_CAP_EVENTS | METACORE_SCRIPT_CAP_TIMERS | METACORE_SCRIPT_CAP_INPUT | + METACORE_SCRIPT_CAP_PHYSICS; enum MetaCoreScriptAbiValueType : std::uint32_t { MetaCoreScriptAbiValue_None = 0, @@ -57,6 +59,23 @@ struct MetaCoreScriptTimeV1 { struct MetaCoreScriptInputActionStateV1 { bool Pressed; bool Held; bool Released; }; +struct MetaCoreScriptPhysicsHitV1 { + MetaCoreScriptObjectHandleV1 Collider; + MetaCoreScriptObjectHandleV1 Body; + double Position[3]; + double Normal[3]; + double Distance; + double Fraction; + bool IsTrigger; +}; + +struct MetaCoreScriptPhysicsQueryShapeV1 { + std::uint32_t Type; + double HalfExtents[3]; + double Radius; + double Height; +}; + struct MetaCoreScriptExecutionContextV1; using MetaCoreScriptTimerCallbackV1 = const char* (*)(void*, const MetaCoreScriptExecutionContextV1*) noexcept; using MetaCoreScriptEventCallbackV1 = const char* (*)(void*, const MetaCoreScriptExecutionContextV1*, const char*, const MetaCoreScriptValueV1*, std::size_t) noexcept; @@ -82,6 +101,19 @@ struct MetaCoreScriptHostApiV1 { bool (*SetInputContextEnabled)(void*, const char*, bool) noexcept; bool (*RebindInput)(void*, const char*, const char*) noexcept; bool (*ResetInputBinding)(void*, const char*) noexcept; + bool (*PhysicsRaycast)(void*, const double*, const double*, double, std::uint32_t, bool, MetaCoreScriptPhysicsHitV1*) noexcept; + bool (*PhysicsShapeCast)(void*, const MetaCoreScriptPhysicsQueryShapeV1*, const double*, const double*, double, std::uint32_t, bool, MetaCoreScriptPhysicsHitV1*) noexcept; + std::size_t (*PhysicsOverlap)(void*, const MetaCoreScriptPhysicsQueryShapeV1*, const double*, std::uint32_t, bool, MetaCoreScriptPhysicsHitV1*, std::size_t) noexcept; + bool (*PhysicsAddForce)(void*, MetaCoreScriptObjectHandleV1, const double*) noexcept; + bool (*PhysicsAddImpulse)(void*, MetaCoreScriptObjectHandleV1, const double*) noexcept; + bool (*PhysicsGetLinearVelocity)(void*, MetaCoreScriptObjectHandleV1, double*) noexcept; + bool (*PhysicsSetLinearVelocity)(void*, MetaCoreScriptObjectHandleV1, const double*) noexcept; + bool (*PhysicsWakeUp)(void*, MetaCoreScriptObjectHandleV1) noexcept; + bool (*PhysicsCharacterMove)(void*, MetaCoreScriptObjectHandleV1, const double*) noexcept; + bool (*PhysicsCharacterJump)(void*, MetaCoreScriptObjectHandleV1, double) noexcept; + bool (*PhysicsCharacterGrounded)(void*, MetaCoreScriptObjectHandleV1, bool*) noexcept; + bool (*PhysicsSetConstraintEnabled)(void*, MetaCoreScriptObjectHandleV1, bool) noexcept; + bool (*PhysicsSetConstraintMotor)(void*, MetaCoreScriptObjectHandleV1, bool, double, double) noexcept; }; struct MetaCoreScriptExecutionContextV1 { diff --git a/Source/MetaCoreScripting/Public/MetaCoreScripting/MetaCoreScripting.h b/Source/MetaCoreScripting/Public/MetaCoreScripting/MetaCoreScripting.h index 5454b0d..430977c 100644 --- a/Source/MetaCoreScripting/Public/MetaCoreScripting/MetaCoreScripting.h +++ b/Source/MetaCoreScripting/Public/MetaCoreScripting/MetaCoreScripting.h @@ -50,6 +50,7 @@ struct MetaCoreScriptTime { class MetaCoreScriptRuntime; class MetaCoreRuntimeInput; +class MetaCorePhysicsWorld; struct MetaCoreScriptExecutionContext { MetaCoreScene& Scene; @@ -116,7 +117,8 @@ public: class MetaCoreScriptRuntime { public: - MetaCoreScriptRuntime(MetaCoreScene& scene, MetaCoreScriptRegistry& registry, MetaCoreScriptLogHandler logger = {}, MetaCoreRuntimeInput* input = nullptr); + MetaCoreScriptRuntime(MetaCoreScene& scene, MetaCoreScriptRegistry& registry, MetaCoreScriptLogHandler logger = {}, + MetaCoreRuntimeInput* input = nullptr, MetaCorePhysicsWorld* physics = nullptr); ~MetaCoreScriptRuntime(); void Start(); @@ -135,6 +137,7 @@ public: void Publish(std::string topic, std::vector fields = {}); void PublishToObject(MetaCoreId objectId, std::string topic, std::vector fields = {}); [[nodiscard]] MetaCoreRuntimeInput* GetRuntimeInput() const; + [[nodiscard]] MetaCorePhysicsWorld* GetPhysicsWorld() const; std::uint64_t ScheduleTimer(MetaCoreId objectId, std::uint64_t instanceId, double delay, double interval, bool unscaled, std::function callback); void CancelTimer(std::uint64_t timerId); diff --git a/docs/designs/metacore-mature-engine-gap-and-work-plan.md b/docs/designs/metacore-mature-engine-gap-and-work-plan.md index f93579a..cb6ed79 100644 --- a/docs/designs/metacore-mature-engine-gap-and-work-plan.md +++ b/docs/designs/metacore-mature-engine-gap-and-work-plan.md @@ -224,11 +224,15 @@ MetaCore 已经超过单纯编辑器 Demo 阶段,当前具备: 需要引入可替换的物理模块边界,并逐步提供: -- [ ] Collider、RigidBody、Trigger 和 Physics Material。 -- [ ] Raycast、Shape Cast、Overlap Query。 -- [ ] 固定时间步、碰撞层和碰撞矩阵。 +- [x] Collider、RigidBody、Trigger 和 Physics Material。 +- [x] Raycast、Shape Cast、Overlap Query。 +- [x] 固定时间步、碰撞层和碰撞矩阵。 - [ ] 约束、角色控制器和物理调试视图。 -- [ ] 场景序列化、Prefab、脚本回调和 Player 集成。 +- [x] 场景序列化、Prefab、脚本回调和 Player 集成。 + +实施进展:已新增可替换的 `MetaCorePhysics` 边界及 Bullet 3 单线程后端,Linux x86_64 使用 vcpkg `bullet3` 与 clang/libc++ 完成构建。当前已接通基础/凸包/静态三角网格 Collider、Static/Kinematic/Dynamic RigidBody、Trigger、32 层碰撞矩阵、物理材质、同步 Raycast/Shape Cast/Overlap、Fixed/Hinge/Slider/6DoF、胶囊角色基础移动、脚本 Physics Capability、Editor Play Mode、Player、Cook 校验和 Physics/GPU 混合交互拾取;六组自动化测试在 Linux 验证构建中全部通过。 + +尚未关闭本问题:Scene View 的物理调试线与接触/约束可视化仍只有后端调试几何和统计接口,尚未接入最终绘制;角色控制器还需补齐可靠的坡度、台阶和动态刚体推挤验收;子层级 Collider 的刚体聚合、版本化碰撞派生缓存以及 10,000/100/1,000 基准门禁也仍需完成。因此问题 6 保持未完全关闭,不宣称达到一次性全量计划的最终验收状态。 第一阶段若只展示静态数字孪生场景,可以延后刚体模拟,但空间查询和触发交互通常应优先实现。 diff --git a/tests/MetaCorePhysicsTests.cpp b/tests/MetaCorePhysicsTests.cpp new file mode 100644 index 0000000..32eb0dc --- /dev/null +++ b/tests/MetaCorePhysicsTests.cpp @@ -0,0 +1,113 @@ +#include "MetaCorePhysics/MetaCorePhysics.h" +#include "MetaCoreScene/MetaCoreScene.h" +#include "MetaCoreScene/MetaCoreSceneSerializer.h" +#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h" + +#include +#include +#include +#include +#include +#include + +namespace { +int Failures = 0; +void Expect(bool condition, const char* message) { if (!condition) { ++Failures; std::cerr << "FAIL: " << message << '\n'; } } + +MetaCore::MetaCoreGameObject AddBox(MetaCore::MetaCoreScene& scene, const char* name, glm::vec3 position, + MetaCore::MetaCoreRigidBodyType type, bool trigger = false, std::uint32_t layer = 0) { + auto object = scene.CreateGameObject(name); object.GetComponent().Position = position; + auto& collider = object.AddComponent(); collider.IsTrigger = trigger; collider.CollisionLayer = layer; + auto& body = object.AddComponent(); body.BodyType = type; + return object; +} +} + +int main() { + using namespace MetaCore; + { + auto settings = MetaCoreBuildDefaultPhysicsSettings(); + Expect(settings.LayerNames.size() == 32U && settings.CollisionMasks.size() == 32U, "default settings expose 32 layers"); + settings.FixedTimeStep = -1.0; std::vector issues; + Expect(!MetaCoreValidatePhysicsSettings(settings, &issues) && !issues.empty(), "invalid settings are repaired and diagnosed"); + } + { + MetaCoreScene scene; + auto ground = AddBox(scene, "Ground", {0.0F, -1.0F, 0.0F}, MetaCoreRigidBodyType::Static); + ground.GetComponent().Size = {20.0F, 1.0F, 20.0F}; + const auto falling = AddBox(scene, "Falling", {0.0F, 4.0F, 0.0F}, MetaCoreRigidBodyType::Dynamic); + MetaCorePhysicsWorld world(scene, MetaCoreBuildDefaultPhysicsSettings()); + auto hit = world.Raycast({0.0F, 10.0F, 0.0F}, {0.0F, -1.0F, 0.0F}, 30.0F); + Expect(hit.has_value() && hit->ColliderObjectId == falling.GetId(), "raycast returns closest collider"); + const auto invalid = world.Raycast(glm::vec3(0.0F), glm::vec3(0.0F), 10.0F); Expect(!invalid.has_value(), "zero direction is rejected"); + for (int i = 0; i < 180; ++i) { scene.IncrementRevision(); world.Step(1.0 / 60.0); } + const float y = scene.FindGameObject(falling.GetId()).GetComponent().Position.y; + Expect(y > -0.1F && y < 1.0F, "dynamic body falls and rests on static body"); + Expect(world.GetStatistics().BodyCount == 2U, "world reports body statistics"); + } + { + MetaCoreScene scene; auto trigger = AddBox(scene, "Trigger", glm::vec3(0.0F), MetaCoreRigidBodyType::Static, true, 2U); + auto settings = MetaCoreBuildDefaultPhysicsSettings(); settings.CollisionMasks[2] = 1U << 2U; + MetaCorePhysicsWorld world(scene, settings); + MetaCorePhysicsQueryFilter filter; filter.Triggers = MetaCorePhysicsTriggerQuery::Ignore; + Expect(world.Overlap({}, glm::vec3(0.0F), filter).empty(), "query can ignore triggers"); + filter.Triggers = MetaCorePhysicsTriggerQuery::Collide; filter.LayerMask = 1U << 2U; + const auto overlaps = world.Overlap({}, glm::vec3(0.0F), filter); + Expect(overlaps.size() == 1U && overlaps[0].ColliderObjectId == trigger.GetId(), "overlap honors trigger and layer filter"); + } + { + MetaCoreScene scene; auto a=AddBox(scene,"A",glm::vec3(0.0F),MetaCoreRigidBodyType::Dynamic); auto b=AddBox(scene,"B",{0.0F,0.0F,2.0F},MetaCoreRigidBodyType::Static); + auto& constraint=a.AddComponent(); constraint.Type=MetaCorePhysicsConstraintType::Fixed; constraint.TargetObjectId=b.GetId(); + MetaCorePhysicsWorld world(scene,MetaCoreBuildDefaultPhysicsSettings()); + Expect(world.GetDiagnostics().empty(),"fixed constraint resolves both bodies"); + Expect(world.SetConstraintEnabled(a.GetId(), false) && world.SetConstraintEnabled(a.GetId(), true), "constraint can be enabled and disabled at runtime"); + world.Step(1.0/60.0); + } + { + MetaCoreScene scene; auto a=AddBox(scene,"MotorA",glm::vec3(0.0F),MetaCoreRigidBodyType::Dynamic); auto b=AddBox(scene,"MotorB",{0.0F,0.0F,2.0F},MetaCoreRigidBodyType::Static); + auto& constraint=a.AddComponent(); constraint.Type=MetaCorePhysicsConstraintType::Hinge; constraint.TargetObjectId=b.GetId(); + MetaCorePhysicsWorld world(scene,MetaCoreBuildDefaultPhysicsSettings()); + Expect(world.SetConstraintMotor(a.GetId(), true, 1.0F, 5.0F), "hinge motor can be configured at runtime"); + } + { + MetaCoreScene scene; + auto ground = AddBox(scene, "CharacterGround", {0.0F, -0.5F, 0.0F}, MetaCoreRigidBodyType::Static); + ground.GetComponent().Size = {20.0F, 1.0F, 20.0F}; + auto character=scene.CreateGameObject("Character"); character.GetComponent().Position={0.0F,2.0F,0.0F}; character.AddComponent(); + MetaCorePhysicsWorld world(scene,MetaCoreBuildDefaultPhysicsSettings()); Expect(world.GetStatistics().BodyCount==2U,"character controller creates an implicit capsule body"); + Expect(world.CharacterMove(character.GetId(),{0.1F,0.0F,0.0F}),"character accepts movement commands"); + for (int index = 0; index < 120; ++index) world.Step(1.0 / 60.0); + Expect(world.IsCharacterGrounded(character.GetId()), "implicit capsule character lands and reports grounded"); + Expect(world.CharacterJump(character.GetId(), 5.0F), "grounded character accepts jump"); + } + { + MetaCoreScene scene; + auto settings = MetaCoreBuildDefaultPhysicsSettings(); settings.Gravity = glm::vec3(0.0F); + auto trigger = AddBox(scene, "LifecycleTrigger", glm::vec3(0.0F), MetaCoreRigidBodyType::Static, true); + auto actor = AddBox(scene, "LifecycleActor", glm::vec3(0.0F), MetaCoreRigidBodyType::Dynamic); + int enters = 0, stays = 0, exits = 0; + MetaCorePhysicsWorld world(scene, settings); + world.SetEventCallback([&](const MetaCorePhysicsContactEvent& event) { + if (event.Type == MetaCorePhysicsEventType::TriggerEnter) ++enters; + if (event.Type == MetaCorePhysicsEventType::TriggerStay) ++stays; + if (event.Type == MetaCorePhysicsEventType::TriggerExit) ++exits; + }); + world.Step(1.0 / 60.0); world.Step(1.0 / 60.0); + Expect(enters == 1 && stays == 1, "trigger lifecycle emits one Enter then one Stay"); + (void)scene.DeleteGameObjects({actor.GetId()}); world.Step(1.0 / 60.0); + Expect(exits == 1, "deleting a touching object emits one Exit"); + (void)trigger; + } + { + MetaCoreScene scene; auto object=AddBox(scene,"Serializable",glm::vec3(1.0F),MetaCoreRigidBodyType::Dynamic); object.AddComponent(); + MetaCoreSceneDocument document; document.Name="Physics"; document.GameObjects=scene.CaptureSnapshot().GameObjects; + MetaCoreTypeRegistry registry; MetaCoreRegisterFoundationGeneratedTypes(registry); MetaCoreRegisterSceneGeneratedTypes(registry); + const auto path=std::filesystem::temp_directory_path()/"metacore_physics_scene_test.mcscene.json"; + Expect(MetaCoreSceneSerializer::SaveSceneToJson(path,document,registry),"physics components serialize to scene JSON"); + const auto loaded=MetaCoreSceneSerializer::LoadSceneFromJson(path,registry); + Expect(loaded&&loaded->GameObjects.size()==1U&&loaded->GameObjects[0].Collider&&loaded->GameObjects[0].RigidBody&&loaded->GameObjects[0].CharacterController,"physics components survive scene JSON roundtrip"); + std::error_code error;std::filesystem::remove(path,error); + } + std::cout << "MetaCorePhysicsTests: " << (Failures == 0 ? "PASS" : "FAIL") << '\n'; + return Failures == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/vcpkg.json b/vcpkg.json index b6637c4..0f0070f 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -3,6 +3,7 @@ "version-string": "1.0.0", "dependencies": [ "basisu", + "bullet3", "glm", "entt", "rmlui",