物理系统

This commit is contained in:
Rowland 2026-07-16 14:28:52 +08:00
parent 2626070af7
commit b8b2dd5520
24 changed files with 1851 additions and 26 deletions

View File

@ -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 <filesystem>
#include <algorithm>
#include <cstring>
#include <fstream>
#include <iostream>
#include <memory>
@ -25,6 +32,41 @@
namespace {
struct MetaCorePackedPhysicsVertex { float px, py, pz, nx, ny, nz, u, v; };
std::unordered_map<MetaCore::MetaCoreAssetGuid, MetaCore::MetaCorePhysicsMeshData, MetaCore::MetaCoreAssetGuidHasher>
MetaCoreLoadPhysicsMeshes(const std::filesystem::path& root, const MetaCore::MetaCoreTypeRegistry& registry) {
std::unordered_map<MetaCore::MetaCoreAssetGuid, MetaCore::MetaCorePhysicsMeshData, MetaCore::MetaCoreAssetGuidHasher> 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<std::size_t>(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<MetaCore::MetaCoreAssetGuid, MetaCore::MetaCorePhysicsMaterialDocument, MetaCore::MetaCoreAssetGuidHasher> 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<MetaCore::MetaCorePhysicsMeshData>{const auto it=physicsMeshes.find(guid);return it==physicsMeshes.end()?std::nullopt:std::optional<MetaCore::MetaCorePhysicsMeshData>(it->second);},
[&physicsMaterials](MetaCore::MetaCoreAssetGuid guid)->std::optional<MetaCore::MetaCorePhysicsMaterialDocument>{const auto it=physicsMaterials.find(guid);return it==physicsMaterials.end()?std::nullopt:std::optional<MetaCore::MetaCorePhysicsMaterialDocument>(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<float>(inputWidth), static_cast<float>(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);

View File

@ -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

View File

@ -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"

View File

@ -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 <set>
#include <sstream>
#include <system_error>
#include <unordered_set>
#if defined(__linux__)
#include <sys/utsname.h>
#endif
@ -32,6 +36,53 @@ namespace {
using json = nlohmann::json;
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> MetaCoreCollectCookablePhysicsMeshes(
const std::filesystem::path& projectRoot,
const MetaCoreTypeRegistry& registry
) {
std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher> 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<std::size_t>(mesh.PayloadIndex)].size() >= sizeof(std::uint32_t) * 2U) {
result.insert(mesh.AssetGuid);
}
}
}
}
return result;
}
std::optional<std::string> MetaCoreValidateScenePhysicsForCook(
const MetaCoreSceneDocument& scene,
const std::unordered_set<MetaCoreAssetGuid, MetaCoreAssetGuidHasher>& 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<std::string> 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);
}

View File

@ -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<MetaCoreColliderComponent>()) return;
auto& value = gameObject.GetComponent<MetaCoreColliderComponent>(); bool changed = ImGui::Checkbox("启用", &value.Enabled);
int shape = static_cast<int>(value.Shape); const char* shapes[]{"Box", "Sphere", "Capsule", "Convex Hull", "Triangle Mesh"};
if (ImGui::Combo("形状", &shape, shapes, IM_ARRAYSIZE(shapes))) { value.Shape=static_cast<MetaCoreColliderShape>(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<int>(value.CollisionLayer); if (ImGui::SliderInt("碰撞层", &layer, 0, 31)) { value.CollisionLayer=static_cast<std::uint32_t>(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<MetaCoreRigidBodyComponent>()) return; auto& value=gameObject.GetComponent<MetaCoreRigidBodyComponent>(); bool changed=false;
int type=static_cast<int>(value.BodyType); const char* types[]{"Static","Kinematic","Dynamic"}; if(ImGui::Combo("类型",&type,types,3)){value.BodyType=static_cast<MetaCoreRigidBodyType>(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<MetaCoreCharacterControllerComponent>()) 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<MetaCorePhysicsConstraintComponent>())return; auto& value=gameObject.GetComponent<MetaCorePhysicsConstraintComponent>(); bool changed=ImGui::Checkbox("启用",&value.Enabled);
int type=static_cast<int>(value.Type); const char* types[]{"Fixed","Hinge","Slider","6DoF"}; if(ImGui::Combo("约束类型",&type,types,4)){value.Type=static_cast<MetaCorePhysicsConstraintType>(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<MetaCoreCharacterControllerComponent>())return; auto& value=gameObject.GetComponent<MetaCoreCharacterControllerComponent>(); 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<MetaCoreRigidBodyComponent>())ImGui::TextColored({1.0F,0.25F,0.2F,1.0F},"请移除 RigidBody 后再使用角色控制器");
if(changed)editorContext.GetScene().IncrementRevision();
}
template <typename TComponent>
std::optional<std::vector<std::byte>> MetaCoreSerializeComponentValue(
const TComponent& component,
const MetaCoreTypeRegistry& registry
);
template <typename TComponent>
bool MetaCoreDeserializeComponentValue(
std::span<const std::byte> payload,
TComponent& component,
const MetaCoreTypeRegistry& registry
);
template<typename T>
MetaCoreIComponentTypeRegistry::MetaCoreComponentDescriptor MetaCoreMakePhysicsComponentDescriptor(std::string typeId, std::string displayName,
std::function<void(MetaCoreEditorContext&, MetaCoreGameObject&)> draw) {
return MetaCoreIComponentTypeRegistry::MetaCoreComponentDescriptor{
std::move(typeId), std::move(displayName),
[](const MetaCoreGameObject& object){return object.HasComponent<T>();},
[](MetaCoreGameObject& object){
if(object.HasComponent<T>())return false;
if constexpr(std::is_same_v<T,MetaCoreRigidBodyComponent>) if(object.HasComponent<MetaCoreCharacterControllerComponent>())return false;
if constexpr(std::is_same_v<T,MetaCoreCharacterControllerComponent>) if(object.HasComponent<MetaCoreRigidBodyComponent>())return false;
object.AddComponent<T>(T{}); return true;
},
[](MetaCoreGameObject& object){if(!object.HasComponent<T>())return false;object.RemoveComponent<T>();return true;},
[](MetaCoreGameObject& object){if(!object.HasComponent<T>())return false;object.GetComponent<T>()={};return true;},
[](const MetaCoreGameObject& object,const MetaCoreTypeRegistry& registry)->std::optional<std::vector<std::byte>>{if(!object.HasComponent<T>())return std::nullopt;return MetaCoreSerializeComponentValue(object.GetComponent<T>(),registry);},
[](MetaCoreGameObject& object,std::span<const std::byte> payload,const MetaCoreTypeRegistry& registry){T value{};if(!MetaCoreDeserializeComponentValue(payload,value,registry))return false;if(object.HasComponent<T>())object.GetComponent<T>()=value;else object.AddComponent<T>(value);return true;},
std::move(draw)
};
}
void MetaCoreDrawMeshRendererComponentInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) {
if (!gameObject.HasComponent<MetaCoreMeshRendererComponent>()) {
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<const std::byte> payload, const MetaCoreTypeRegistry& registry) { MetaCoreInteractableComponent value{}; if (!MetaCoreDeserializeComponentValue(payload, value, registry)) return false; if (object.HasComponent<MetaCoreInteractableComponent>()) object.GetComponent<MetaCoreInteractableComponent>() = value; else object.AddComponent<MetaCoreInteractableComponent>(value); return true; },
MetaCoreDrawInteractableComponentInspector
});
Descriptors_.push_back(MetaCoreMakePhysicsComponentDescriptor<MetaCoreColliderComponent>("Collider", "碰撞体", MetaCoreDrawColliderComponentInspector));
Descriptors_.push_back(MetaCoreMakePhysicsComponentDescriptor<MetaCoreRigidBodyComponent>("RigidBody", "刚体", MetaCoreDrawRigidBodyComponentInspector));
Descriptors_.push_back(MetaCoreMakePhysicsComponentDescriptor<MetaCorePhysicsConstraintComponent>("PhysicsConstraint", "物理约束", MetaCoreDrawPhysicsConstraintComponentInspector));
Descriptors_.push_back(MetaCoreMakePhysicsComponentDescriptor<MetaCoreCharacterControllerComponent>("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<MetaCorePhysicsWorld*()> getter) { PhysicsWorldGetter_ = std::move(getter); }
void OnPlayStart(MetaCoreEditorContext& editorContext) override {
MetaCoreTypeRegistry registry; MetaCoreRegisterRuntimeDataGeneratedTypes(registry); MetaCoreRegisterRuntimeInputGeneratedTypes(registry);
const auto assets = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
@ -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<float>(deltaSeconds));
} else {
Runtime_.CancelAll();
@ -10426,13 +10535,83 @@ private:
MetaCoreRuntimeInput Runtime_{};
MetaCoreRuntimeInteraction Interaction_{};
MetaCoreRuntimeUiSystem RuntimeUi_{};
MetaCoreSceneRenderSync CameraSync_{};
std::function<MetaCorePhysicsWorld*()> 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<MetaCoreIAssetDatabaseService>();
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<MetaCorePhysicsMaterialDocument> {
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<MetaCoreIAssetEditingService>();
const auto packages = editorContext.GetModuleRegistry().ResolveService<MetaCoreIPackageService>();
if (editing && packages) {
meshes = [assets, editing, packages](MetaCoreAssetGuid guid)->std::optional<MetaCorePhysicsMeshData> {
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<std::size_t>(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<MetaCorePhysicsWorld>(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<MetaCorePhysicsWorld> World_{};
};
class MetaCoreScriptPlayModeComponentSystem final : public MetaCoreIPlayModeComponentSystem {
public:
explicit MetaCoreScriptPlayModeComponentSystem(std::shared_ptr<MetaCoreInputPlayModeComponentSystem> input) : Input_(std::move(input)) {}
MetaCoreScriptPlayModeComponentSystem(std::shared_ptr<MetaCoreInputPlayModeComponentSystem> input,
std::shared_ptr<MetaCorePhysicsPlayModeComponentSystem> 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<MetaCoreInputPlayModeComponentSystem> Input_{};
std::shared_ptr<MetaCorePhysicsPlayModeComponentSystem> Physics_{};
std::unique_ptr<MetaCoreScriptRuntime> Runtime_{};
std::uint64_t InputSubscription_ = 0;
};
@ -10625,7 +10818,12 @@ public:
RegisterSystem(input);
RegisterSystem(std::make_shared<MetaCoreAnimationPlayModeComponentSystem>());
RegisterSystem(std::make_shared<MetaCoreRotatorPlayModeComponentSystem>());
RegisterSystem(std::make_shared<MetaCoreScriptPlayModeComponentSystem>(std::move(input)));
auto physics = std::make_shared<MetaCorePhysicsPlayModeComponentSystem>();
input->SetPhysicsWorldGetter([weakPhysics = std::weak_ptr<MetaCorePhysicsPlayModeComponentSystem>(physics)] {
const auto value = weakPhysics.lock(); return value ? value->GetWorld() : nullptr;
});
RegisterSystem(std::make_shared<MetaCoreScriptPlayModeComponentSystem>(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<MetaCoreIAssetDatabaseService>(); 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<int>(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<MetaCoreAssetRecord> MetaCoreBuiltinPrefabService::FindPrefabAsset(const MetaCoreAssetGuid& prefabAssetGuid) const {
@ -11105,6 +11319,10 @@ std::optional<std::filesystem::path> MetaCoreBuiltinPrefabService::CreatePrefabF
if (gameObject.HasComponent<MetaCoreScriptComponent>()) data.Script = gameObject.GetComponent<MetaCoreScriptComponent>();
if (gameObject.HasComponent<MetaCoreUiRendererComponent>()) data.UiRenderer = gameObject.GetComponent<MetaCoreUiRendererComponent>();
if (gameObject.HasComponent<MetaCoreMeshRendererComponent>()) data.MeshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
if (gameObject.HasComponent<MetaCoreColliderComponent>()) data.Collider = gameObject.GetComponent<MetaCoreColliderComponent>();
if (gameObject.HasComponent<MetaCoreRigidBodyComponent>()) data.RigidBody = gameObject.GetComponent<MetaCoreRigidBodyComponent>();
if (gameObject.HasComponent<MetaCorePhysicsConstraintComponent>()) data.PhysicsConstraint = gameObject.GetComponent<MetaCorePhysicsConstraintComponent>();
if (gameObject.HasComponent<MetaCoreCharacterControllerComponent>()) data.CharacterController = gameObject.GetComponent<MetaCoreCharacterControllerComponent>();
prefabDataObjects.push_back(std::move(data));
}
prefabDocument.GameObjects = std::move(prefabDataObjects);
@ -11284,6 +11502,10 @@ bool MetaCoreBuiltinPrefabService::ApplySelectedPrefabInstance(MetaCoreEditorCon
if (gameObject.HasComponent<MetaCoreScriptComponent>()) data.Script = gameObject.GetComponent<MetaCoreScriptComponent>();
if (gameObject.HasComponent<MetaCoreUiRendererComponent>()) data.UiRenderer = gameObject.GetComponent<MetaCoreUiRendererComponent>();
if (gameObject.HasComponent<MetaCoreMeshRendererComponent>()) data.MeshRenderer = gameObject.GetComponent<MetaCoreMeshRendererComponent>();
if (gameObject.HasComponent<MetaCoreColliderComponent>()) data.Collider = gameObject.GetComponent<MetaCoreColliderComponent>();
if (gameObject.HasComponent<MetaCoreRigidBodyComponent>()) data.RigidBody = gameObject.GetComponent<MetaCoreRigidBodyComponent>();
if (gameObject.HasComponent<MetaCorePhysicsConstraintComponent>()) data.PhysicsConstraint = gameObject.GetComponent<MetaCorePhysicsConstraintComponent>();
if (gameObject.HasComponent<MetaCoreCharacterControllerComponent>()) data.CharacterController = gameObject.GetComponent<MetaCoreCharacterControllerComponent>();
prefabDataObjects.push_back(std::move(data));
}

View File

@ -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<MetaCoreIAssetDatabaseService>();
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<float>(Settings_.FixedTimeStep);if(ImGui::DragFloat("固定步长",&step,0.001F,0.001F,1.0F,"%.4f")){Settings_.FixedTimeStep=step;Dirty_=true;}
int maxSteps=static_cast<int>(Settings_.MaxFixedStepsPerFrame);if(ImGui::SliderInt("每帧最大步数",&maxSteps,1,32)){Settings_.MaxFixedStepsPerFrame=static_cast<std::uint32_t>(maxSteps);Dirty_=true;}
if(ImGui::CollapsingHeader("碰撞层与矩阵",ImGuiTreeNodeFlags_DefaultOpen)){
for(std::uint32_t i=0;i<32U;++i){ImGui::PushID(static_cast<int>(i));std::array<char,64> 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<<j;Settings_.CollisionMasks[j]|=1U<<i;}else{Settings_.CollisionMasks[i]&=~(1U<<j);Settings_.CollisionMasks[j]&=~(1U<<i);}Dirty_=true;}}ImGui::TreePop();}ImGui::PopID();}
}
if(ImGui::Button("保存")){std::vector<std::string> 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<MetaCoreAssetGuid, MetaCorePhysicsMaterialDocument, MetaCoreAssetGuidHasher> 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<char, 128> 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<int>(material.FrictionCombine);
int restitutionCombine = static_cast<int>(material.RestitutionCombine);
if (ImGui::Combo("摩擦合并", &frictionCombine, combineModes, 4)) {
material.FrictionCombine = static_cast<MetaCorePhysicsMaterialCombineMode>(frictionCombine);
}
if (ImGui::Combo("弹性合并", &restitutionCombine, combineModes, 4)) {
material.RestitutionCombine = static_cast<MetaCorePhysicsMaterialCombineMode>(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<MetaCoreScenePanelProvider>());
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreRuntimeUiPanelProvider>());
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreInputMapPanelProvider>());
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCorePhysicsSettingsPanelProvider>());
moduleRegistry.RegisterPanelProvider(std::make_unique<MetaCoreRmlUiVisualEditorPanelProvider>());
moduleRegistry.RegisterService<MetaCoreIUiDesignerService>(std::make_shared<MetaCoreNativeUiDesignerService>());
// The central Scene / Game viewport is not a dockable provider, but it keeps

View File

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

View File

@ -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

View File

@ -0,0 +1,537 @@
#include "MetaCorePhysics/MetaCorePhysics.h"
#include "MetaCoreScene/MetaCoreScene.h"
#include "MetaCoreScene/MetaCoreTransformUtils.h"
#include <btBulletDynamicsCommon.h>
#include <BulletDynamics/ConstraintSolver/btFixedConstraint.h>
#include <algorithm>
#include <bit>
#include <chrono>
#include <cmath>
#include <limits>
#include <map>
#include <memory>
#include <tuple>
#include <unordered_map>
#include <utility>
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<MetaCoreTransformComponent>());
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<MetaCoreTransformComponent>()) * 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<btCollisionShape> Shape{};
std::unique_ptr<btTriangleMesh> TriangleMesh{};
std::unique_ptr<btDefaultMotionState> MotionState{};
std::unique_ptr<btRigidBody> 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<btTypedConstraint> 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<const btCollisionObject*>(lhs->m_clientObject);
const auto* b = static_cast<const btCollisionObject*>(rhs->m_clientObject);
const auto* ma = a ? static_cast<const Metadata*>(a->getUserPointer()) : nullptr;
const auto* mb = b ? static_cast<const Metadata*>(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<btDefaultCollisionConfiguration> CollisionConfiguration = std::make_unique<btDefaultCollisionConfiguration>();
std::unique_ptr<btCollisionDispatcher> Dispatcher = std::make_unique<btCollisionDispatcher>(CollisionConfiguration.get());
std::unique_ptr<btDbvtBroadphase> Broadphase = std::make_unique<btDbvtBroadphase>();
std::unique_ptr<btSequentialImpulseConstraintSolver> Solver = std::make_unique<btSequentialImpulseConstraintSolver>();
std::unique_ptr<btDiscreteDynamicsWorld> World = std::make_unique<btDiscreteDynamicsWorld>(Dispatcher.get(), Broadphase.get(), Solver.get(), CollisionConfiguration.get());
Filter OverlapFilter{this};
std::unordered_map<MetaCoreId, std::unique_ptr<BodyEntry>> Bodies{};
std::vector<ConstraintEntry> Constraints{};
std::map<PairKey, MetaCorePhysicsContactEvent> PreviousPairs{};
MetaCorePhysicsEventCallback EventCallback{};
MetaCorePhysicsStatistics Statistics{};
std::vector<std::string> Diagnostics{};
std::uint64_t SceneRevision = std::numeric_limits<std::uint64_t>::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<const Metadata*>(lhs->getCollisionObject()->getUserPointer()) : nullptr;
const auto* b = rhs ? static_cast<const Metadata*>(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<std::uint32_t>(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<MetaCoreTransformComponent>().Scale);
add(object.HasComponent<MetaCoreColliderComponent>()); add(object.HasComponent<MetaCoreRigidBodyComponent>());
add(object.HasComponent<MetaCorePhysicsConstraintComponent>()); add(object.HasComponent<MetaCoreCharacterControllerComponent>());
if (object.HasComponent<MetaCoreColliderComponent>()) {
const auto& value = object.GetComponent<MetaCoreColliderComponent>(); add(value.Enabled); add(static_cast<std::uint64_t>(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<MetaCoreRigidBodyComponent>()) {
const auto& value = object.GetComponent<MetaCoreRigidBodyComponent>(); add(static_cast<std::uint64_t>(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<MetaCorePhysicsConstraintComponent>()) {
const auto& value = object.GetComponent<MetaCorePhysicsConstraintComponent>(); add(value.Enabled); add(static_cast<std::uint64_t>(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<MetaCoreCharacterControllerComponent>()) {
const auto& value = object.GetComponent<MetaCoreCharacterControllerComponent>(); 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<btCollisionShape> MakeShape(const MetaCoreColliderComponent& collider, glm::vec3 absoluteScale,
std::unique_ptr<btTriangleMesh>& 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<btBoxShape>(Bt(size * 0.5F));
if (collider.Shape == MetaCoreColliderShape::Sphere) return std::make_unique<btSphereShape>(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<btCapsuleShape>(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<btConvexHullShape>();
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<btTriangleMesh>();
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<btBvhTriangleMeshShape>(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<MetaCoreCharacterControllerComponent>() && object.GetComponent<MetaCoreCharacterControllerComponent>().Enabled;
if (!object.HasComponent<MetaCoreColliderComponent>() && !character) continue;
MetaCoreColliderComponent automaticCollider;
if (character) { const auto& controller = object.GetComponent<MetaCoreCharacterControllerComponent>(); automaticCollider.Shape=MetaCoreColliderShape::Capsule; automaticCollider.Radius=controller.Radius; automaticCollider.Height=controller.Height; automaticCollider.CollisionLayer=controller.CollisionLayer; }
const auto collider = object.HasComponent<MetaCoreColliderComponent>() ? object.GetComponent<MetaCoreColliderComponent>() : automaticCollider; if (!collider.Enabled) continue;
if (object.HasComponent<MetaCoreCharacterControllerComponent>() && object.HasComponent<MetaCoreRigidBodyComponent>()) {
Diagnostics.push_back("对象 " + std::to_string(object.GetId()) + " 同时包含 CharacterController 与 RigidBody"); continue;
}
auto bodyComponent = object.HasComponent<MetaCoreRigidBodyComponent>() ? object.GetComponent<MetaCoreRigidBodyComponent>() : MetaCoreRigidBodyComponent{};
if (character) { bodyComponent.BodyType = MetaCoreRigidBodyType::Dynamic; bodyComponent.Mass = 80.0F; bodyComponent.AllowSleep = false; }
auto entry = std::make_unique<BodyEntry>(); entry->Type = bodyComponent.BodyType; entry->Center = collider.Center; entry->Character = character;
if (character) {
const auto& controller = object.GetComponent<MetaCoreCharacterControllerComponent>();
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<btDefaultMotionState>(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<btRigidBody>(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<MetaCorePhysicsConstraintComponent>()) continue;
const auto& source = object.GetComponent<MetaCorePhysicsConstraintComponent>(); 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<btTypedConstraint> constraint;
if (source.Type == MetaCorePhysicsConstraintType::Fixed) constraint = std::make_unique<btFixedConstraint>(a, b, frameA, frameB);
else if (source.Type == MetaCorePhysicsConstraintType::Hinge) {
auto value = std::make_unique<btHingeConstraint>(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<btSliderConstraint>(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<btGeneric6DofConstraint>(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<float>(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<const Metadata*>(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<PairKey, MetaCorePhysicsContactEvent> 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<const Metadata*>(manifold->getBody0()->getUserPointer()); const auto* b = static_cast<const Metadata*>(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<MetaCoreTransformComponent>());
}
}
};
MetaCorePhysicsWorld::MetaCorePhysicsWorld(MetaCoreScene& scene, MetaCorePhysicsSettingsDocument settings,
MetaCorePhysicsMeshProvider meshProvider, MetaCorePhysicsMaterialProvider materialProvider)
: Impl_(std::make_unique<Impl>(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<btScalar>(fixedDeltaSeconds), 0);
Impl_->WriteBack(); Impl_->EmitContacts(); Impl_->Statistics.LastStepMilliseconds = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - begin).count();
}
void MetaCorePhysicsWorld::Stop() {
if (Impl_->Stopped) return;
Impl_->FlushExitEvents();
Impl_->Stopped = true; Impl_->Clear();
}
std::vector<MetaCorePhysicsHit> MetaCorePhysicsWorld::RaycastAll(glm::vec3 origin, glm::vec3 direction, float maxDistance, const MetaCorePhysicsQueryFilter& filter) {
++Impl_->Statistics.QueryCount; std::vector<MetaCorePhysicsHit> 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<const btCollisionObject*>(proxy->m_clientObject); return Owner->Accept(object ? static_cast<const Impl::Metadata*>(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<MetaCorePhysicsHit> 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<MetaCorePhysicsHit>(all.front()); }
static std::unique_ptr<btConvexShape> MetaCoreMakeQueryShape(const MetaCorePhysicsQueryShape& shape) {
if (shape.Type == MetaCorePhysicsQueryShapeType::Sphere && shape.Radius > 0.0F) return std::make_unique<btSphereShape>(shape.Radius);
if (shape.Type == MetaCorePhysicsQueryShapeType::Capsule && shape.Radius > 0.0F && shape.Height >= shape.Radius * 2.0F) return std::make_unique<btCapsuleShape>(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<btBoxShape>(Bt(shape.HalfExtents));
return {};
}
std::vector<MetaCorePhysicsHit> MetaCorePhysicsWorld::ShapeCastAll(const MetaCorePhysicsQueryShape& shape, glm::vec3 origin, glm::vec3 direction, float maxDistance, const MetaCorePhysicsQueryFilter& filter) {
++Impl_->Statistics.QueryCount; std::vector<MetaCorePhysicsHit> 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<MetaCorePhysicsHit>& Hits; float Distance;
Callback(const btVector3& a, const btVector3& b, Impl* owner, const MetaCorePhysicsQueryFilter& filter, std::vector<MetaCorePhysicsHit>& 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<const btCollisionObject*>(proxy->m_clientObject); return Owner->Accept(object ? static_cast<const Impl::Metadata*>(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<MetaCorePhysicsHit> 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<MetaCorePhysicsHit>(all.front()); }
std::vector<MetaCorePhysicsHit> MetaCorePhysicsWorld::Overlap(const MetaCorePhysicsQueryShape& shape, glm::vec3 center, const MetaCorePhysicsQueryFilter& filter) {
++Impl_->Statistics.QueryCount; std::vector<MetaCorePhysicsHit> 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<MetaCorePhysicsHit>& Hits;
Callback(Impl* owner, const MetaCorePhysicsQueryFilter& filter, std::vector<MetaCorePhysicsHit>& hits) : Owner(owner), Filter(filter), Hits(hits) {}
bool needsCollision(btBroadphaseProxy* proxy) const override { const auto* object = static_cast<const btCollisionObject*>(proxy->m_clientObject); return Owner->Accept(object ? static_cast<const Impl::Metadata*>(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<glm::vec3> MetaCorePhysicsWorld::GetLinearVelocity(MetaCoreId id) const { const auto it = Impl_->Bodies.find(id); return it == Impl_->Bodies.end() ? std::nullopt : std::optional<glm::vec3>(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<MetaCoreCharacterControllerComponent>()) return false; auto velocity=it->second->Body->getLinearVelocity(); velocity.setX(displacement.x/static_cast<float>(Impl_->Settings.FixedTimeStep)); velocity.setZ(displacement.z/static_cast<float>(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<btHingeConstraint*>(iterator->Value.get())) { hinge->enableAngularMotor(enabled, targetVelocity, maxImpulse); return true; }
if (auto* slider = dynamic_cast<btSliderConstraint*>(iterator->Value.get())) { slider->setPoweredLinMotor(enabled); slider->setTargetLinMotorVelocity(targetVelocity); slider->setMaxLinMotorForce(maxImpulse); return true; }
if (auto* sixDof = dynamic_cast<btGeneric6DofConstraint*>(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<MetaCorePhysicsDebugLine> MetaCorePhysicsWorld::BuildDebugLines() const {
std::vector<MetaCorePhysicsDebugLine> 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<std::string>& 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<MetaCorePhysicsWorld> CreateWorld(MetaCoreScene& scene, const MetaCorePhysicsSettingsDocument& settings,
MetaCorePhysicsMeshProvider mesh, MetaCorePhysicsMaterialProvider material) override {
return std::make_unique<MetaCorePhysicsWorld>(scene, settings, std::move(mesh), std::move(material));
}
};
std::unique_ptr<MetaCoreIPhysicsBackend> MetaCoreCreateBulletPhysicsBackend() { return std::make_unique<MetaCoreBulletPhysicsBackend>(); }
} // namespace MetaCore

View File

@ -0,0 +1,73 @@
#include "MetaCorePhysics/MetaCorePhysics.h"
#include "MetaCoreFoundation/MetaCoreArchive.h"
#include <algorithm>
#include <cmath>
#include <fstream>
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<std::string>* 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<const char*>(bytes->data()), static_cast<std::streamsize>(bytes->size())); return output.good();
}
std::optional<MetaCorePhysicsSettingsDocument> 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<std::size_t>(input.tellg()); input.seekg(0); std::vector<std::byte> bytes(size);
if (size && !input.read(reinterpret_cast<char*>(bytes.data()), static_cast<std::streamsize>(size))) return std::nullopt;
MetaCorePhysicsSettingsDocument result;
if (!MetaCoreDeserializeFromBytes(std::span<const std::byte>(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<const char*>(bytes->data()),static_cast<std::streamsize>(bytes->size()));return output.good();
}
std::optional<MetaCorePhysicsMaterialDocument> 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<std::size_t>(input.tellg());input.seekg(0);std::vector<std::byte> bytes(size);
if(size&&!input.read(reinterpret_cast<char*>(bytes.data()),static_cast<std::streamsize>(size)))return std::nullopt;MetaCorePhysicsMaterialDocument result;if(!MetaCoreDeserializeFromBytes(std::span<const std::byte>(bytes.data(),bytes.size()),result,registry))return std::nullopt;return result;
}
} // namespace MetaCore

View File

@ -0,0 +1,178 @@
#pragma once
#include "MetaCoreFoundation/MetaCoreAssetGuid.h"
#include "MetaCoreFoundation/MetaCoreId.h"
#include "MetaCoreFoundation/MetaCoreReflection.h"
#include <glm/vec3.hpp>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <unordered_set>
#include <vector>
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<std::string> LayerNames{};
MC_PROPERTY()
std::vector<std::uint32_t> 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<MetaCoreId> IgnoredObjectIds{};
std::unordered_set<MetaCoreId> 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<glm::vec3> Positions{}; std::vector<std::uint32_t> Indices{}; };
using MetaCorePhysicsMeshProvider = std::function<std::optional<MetaCorePhysicsMeshData>(MetaCoreAssetGuid)>;
using MetaCorePhysicsMaterialProvider = std::function<std::optional<MetaCorePhysicsMaterialDocument>(MetaCoreAssetGuid)>;
using MetaCorePhysicsEventCallback = std::function<void(const MetaCorePhysicsContactEvent&)>;
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<MetaCorePhysicsHit> Raycast(glm::vec3 origin, glm::vec3 direction, float maxDistance,
const MetaCorePhysicsQueryFilter& filter = {});
[[nodiscard]] std::vector<MetaCorePhysicsHit> RaycastAll(glm::vec3 origin, glm::vec3 direction, float maxDistance,
const MetaCorePhysicsQueryFilter& filter = {});
[[nodiscard]] std::optional<MetaCorePhysicsHit> ShapeCast(const MetaCorePhysicsQueryShape& shape, glm::vec3 origin,
glm::vec3 direction, float maxDistance, const MetaCorePhysicsQueryFilter& filter = {});
[[nodiscard]] std::vector<MetaCorePhysicsHit> ShapeCastAll(const MetaCorePhysicsQueryShape& shape, glm::vec3 origin,
glm::vec3 direction, float maxDistance, const MetaCorePhysicsQueryFilter& filter = {});
[[nodiscard]] std::vector<MetaCorePhysicsHit> 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<glm::vec3> 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<MetaCorePhysicsDebugLine> BuildDebugLines() const;
[[nodiscard]] const MetaCorePhysicsStatistics& GetStatistics() const;
[[nodiscard]] const std::vector<std::string>& GetDiagnostics() const;
[[nodiscard]] const MetaCorePhysicsSettingsDocument& GetSettings() const;
private:
class Impl;
std::unique_ptr<Impl> Impl_;
};
class MetaCoreIPhysicsBackend {
public:
virtual ~MetaCoreIPhysicsBackend() = default;
[[nodiscard]] virtual std::string GetBackendId() const = 0;
[[nodiscard]] virtual std::unique_ptr<MetaCorePhysicsWorld> CreateWorld(MetaCoreScene& scene,
const MetaCorePhysicsSettingsDocument& settings, MetaCorePhysicsMeshProvider meshProvider = {},
MetaCorePhysicsMaterialProvider materialProvider = {}) = 0;
};
[[nodiscard]] std::unique_ptr<MetaCoreIPhysicsBackend> MetaCoreCreateBulletPhysicsBackend();
[[nodiscard]] MetaCorePhysicsSettingsDocument MetaCoreBuildDefaultPhysicsSettings();
[[nodiscard]] bool MetaCoreValidatePhysicsSettings(MetaCorePhysicsSettingsDocument& document, std::vector<std::string>* issues = nullptr);
[[nodiscard]] bool MetaCoreWritePhysicsSettings(const std::filesystem::path& path, const MetaCorePhysicsSettingsDocument& document,
const MetaCoreTypeRegistry& registry);
[[nodiscard]] std::optional<MetaCorePhysicsSettingsDocument> 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<MetaCorePhysicsMaterialDocument> MetaCoreReadPhysicsMaterial(const std::filesystem::path& path,
const MetaCoreTypeRegistry& registry);
} // namespace MetaCore

View File

@ -122,6 +122,9 @@ struct MetaCoreRuntimeProjectDocument {
MC_PROPERTY()
std::filesystem::path InputMapPath{};
MC_PROPERTY()
std::filesystem::path PhysicsSettingsPath{};
};
MC_STRUCT()

View File

@ -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 <algorithm>
#include <cmath>
#include <deque>
#include <limits>
#include <memory>
#include <mutex>
#include <utility>
@ -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<float>(x),
viewport.Top + viewport.Height - 1.0F - static_cast<float>(y)};
const MetaCoreCameraRay ray = MetaCoreScreenPointToRay(sceneView, viewport, screenPosition);
std::optional<MetaCorePhysicsHit> 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<float>::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<Impl::Completed> completed;
{ std::scoped_lock lock(Impl_->Async->Mutex); completed.swap(Impl_->Async->CompletedQueries); }

View File

@ -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:

View File

@ -295,6 +295,14 @@ std::vector<MetaCoreId> MetaCoreScene::DuplicateGameObjects(const std::vector<Me
clonedObject.AddComponent<MetaCoreUiRendererComponent>(sourceObject.GetComponent<MetaCoreUiRendererComponent>());
if (sourceObject.HasComponent<MetaCoreInteractableComponent>())
clonedObject.AddComponent<MetaCoreInteractableComponent>(sourceObject.GetComponent<MetaCoreInteractableComponent>());
if (sourceObject.HasComponent<MetaCoreColliderComponent>())
clonedObject.AddComponent<MetaCoreColliderComponent>(sourceObject.GetComponent<MetaCoreColliderComponent>());
if (sourceObject.HasComponent<MetaCoreRigidBodyComponent>())
clonedObject.AddComponent<MetaCoreRigidBodyComponent>(sourceObject.GetComponent<MetaCoreRigidBodyComponent>());
if (sourceObject.HasComponent<MetaCorePhysicsConstraintComponent>())
clonedObject.AddComponent<MetaCorePhysicsConstraintComponent>(sourceObject.GetComponent<MetaCorePhysicsConstraintComponent>());
if (sourceObject.HasComponent<MetaCoreCharacterControllerComponent>())
clonedObject.AddComponent<MetaCoreCharacterControllerComponent>(sourceObject.GetComponent<MetaCoreCharacterControllerComponent>());
if (sourceObject.HasComponent<MetaCorePrefabInstanceMetadata>())
clonedObject.AddComponent<MetaCorePrefabInstanceMetadata>(sourceObject.GetComponent<MetaCorePrefabInstanceMetadata>());
@ -463,6 +471,14 @@ MetaCoreSceneSnapshot MetaCoreScene::CaptureSnapshot() const {
data.UiRenderer = obj.GetComponent<MetaCoreUiRendererComponent>();
if (obj.HasComponent<MetaCoreInteractableComponent>())
data.Interactable = obj.GetComponent<MetaCoreInteractableComponent>();
if (obj.HasComponent<MetaCoreColliderComponent>())
data.Collider = obj.GetComponent<MetaCoreColliderComponent>();
if (obj.HasComponent<MetaCoreRigidBodyComponent>())
data.RigidBody = obj.GetComponent<MetaCoreRigidBodyComponent>();
if (obj.HasComponent<MetaCorePhysicsConstraintComponent>())
data.PhysicsConstraint = obj.GetComponent<MetaCorePhysicsConstraintComponent>();
if (obj.HasComponent<MetaCoreCharacterControllerComponent>())
data.CharacterController = obj.GetComponent<MetaCoreCharacterControllerComponent>();
if (obj.HasComponent<MetaCorePrefabInstanceMetadata>())
data.PrefabInstance = obj.GetComponent<MetaCorePrefabInstanceMetadata>();
if (obj.HasComponent<MetaCoreModelRootTag>())
@ -500,6 +516,14 @@ void MetaCoreScene::RestoreSnapshot(const MetaCoreSceneSnapshot& snapshot) {
obj.AddComponent<MetaCoreUiRendererComponent>(data.UiRenderer.value());
if (data.Interactable.has_value())
obj.AddComponent<MetaCoreInteractableComponent>(data.Interactable.value());
if (data.Collider.has_value())
obj.AddComponent<MetaCoreColliderComponent>(data.Collider.value());
if (data.RigidBody.has_value())
obj.AddComponent<MetaCoreRigidBodyComponent>(data.RigidBody.value());
if (data.PhysicsConstraint.has_value())
obj.AddComponent<MetaCorePhysicsConstraintComponent>(data.PhysicsConstraint.value());
if (data.CharacterController.has_value())
obj.AddComponent<MetaCoreCharacterControllerComponent>(data.CharacterController.value());
if (data.PrefabInstance.has_value())
obj.AddComponent<MetaCorePrefabInstanceMetadata>(data.PrefabInstance.value());
if (data.ModelRootTag.has_value())

View File

@ -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<int>(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<int>(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<int>(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<MetaCoreCameraComponent>();
if (camDesc) {
@ -392,6 +422,54 @@ static MetaCoreGameObjectData JsonToGameObjectData(const json& objJson, const Me
if (value.contains("EventMask")) interactable.EventMask = value["EventMask"].get<std::uint32_t>();
if (value.contains("DragThresholdPixels")) interactable.DragThresholdPixels = value["DragThresholdPixels"].get<float>();
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<MetaCoreColliderShape>(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<std::string>());
if (value.contains("MaterialAssetGuid")) component.MaterialAssetGuid = StringToGuid(value["MaterialAssetGuid"].get<std::string>());
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<MetaCoreRigidBodyType>(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<MetaCorePhysicsConstraintType>(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<MetaCoreCameraComponent>();

View File

@ -1,6 +1,7 @@
#pragma once
#include "MetaCoreFoundation/MetaCoreAssetGuid.h"
#include "MetaCoreFoundation/MetaCoreId.h"
#include "MetaCoreFoundation/MetaCoreReflection.h"
#include <glm/vec3.hpp>
@ -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()

View File

@ -139,6 +139,14 @@ struct MetaCoreGameObjectData {
std::optional<MetaCoreUiRendererComponent> UiRenderer;
MC_PROPERTY()
std::optional<MetaCoreInteractableComponent> Interactable;
MC_PROPERTY()
std::optional<MetaCoreColliderComponent> Collider;
MC_PROPERTY()
std::optional<MetaCoreRigidBodyComponent> RigidBody;
MC_PROPERTY()
std::optional<MetaCorePhysicsConstraintComponent> PhysicsConstraint;
MC_PROPERTY()
std::optional<MetaCoreCharacterControllerComponent> CharacterController;
};
} // namespace MetaCore

View File

@ -1,5 +1,6 @@
#include "MetaCoreScripting/MetaCoreScripting.h"
#include "MetaCoreRuntimeInput/MetaCoreRuntimeInput.h"
#include "MetaCorePhysics/MetaCorePhysics.h"
#include "MetaCoreFoundation/MetaCoreHash.h"
#include <nlohmann/json.hpp>
@ -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<Impl>(*this, scene, registry, std::move(logger), input)) {}
MetaCoreScriptRuntime::MetaCoreScriptRuntime(MetaCoreScene& scene, MetaCoreScriptRegistry& registry, MetaCoreScriptLogHandler logger,
MetaCoreRuntimeInput* input, MetaCorePhysicsWorld* physics)
: Impl_(std::make_unique<Impl>(*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<MetaCoreScriptComponent>()) for (auto& i : o.GetComponent<MetaCoreScriptComponent>().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<MetaCoreScriptValueV1> fields) { Impl_->Events.push_back({0, std::move(topic), std::move(fields)}); }
void MetaCoreScriptRuntime::PublishToObject(MetaCoreId object, std::string topic, std::vector<MetaCoreScriptValueV1> 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<void()> 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<bool>(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<MetaCoreScriptRuntime*>(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<MetaCoreScriptRuntime*>(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<MetaCoreScriptRuntime*>(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<float>(value[0]), static_cast<float>(value[1]), static_cast<float>(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<MetaCorePhysicsQueryShapeType>(source.Type); result.HalfExtents=HVec(source.HalfExtents); result.Radius=static_cast<float>(source.Radius); result.Height=static_cast<float>(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<MetaCoreScriptRuntime*>(c); auto* physics=runtime?runtime->GetPhysicsWorld():nullptr; if(!physics||!out)return false; auto hit=physics->Raycast(HVec(o),HVec(d),static_cast<float>(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<MetaCoreScriptRuntime*>(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<float>(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<MetaCoreScriptRuntime*>(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;i<count;++i)HHit(hits[i],runtime->GetWorldGeneration(),out[i]); return hits.size();}catch(...){return 0;} }
bool HPhysicsForce(void* c,MetaCoreScriptObjectHandleV1 h,const double* v) noexcept { try { auto* r=static_cast<MetaCoreScriptRuntime*>(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<MetaCoreScriptRuntime*>(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<MetaCoreScriptRuntime*>(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<MetaCoreScriptRuntime*>(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<MetaCoreScriptRuntime*>(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<MetaCoreScriptRuntime*>(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<MetaCoreScriptRuntime*>(c); return r&&r->IsHandleValid(h)&&r->GetPhysicsWorld()&&r->GetPhysicsWorld()->CharacterJump(h.ObjectId,static_cast<float>(speed));}catch(...){return false;} }
bool HPhysicsGrounded(void* c,MetaCoreScriptObjectHandleV1 h,bool* out) noexcept { try { auto* r=static_cast<MetaCoreScriptRuntime*>(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<MetaCoreScriptRuntime*>(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<MetaCoreScriptRuntime*>(c); return r&&r->IsHandleValid(h)&&r->GetPhysicsWorld()&&r->GetPhysicsWorld()->SetConstraintMotor(h.ObjectId,enabled,static_cast<float>(velocity),static_cast<float>(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) {

View File

@ -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 {

View File

@ -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<MetaCoreScriptValueV1> fields = {});
void PublishToObject(MetaCoreId objectId, std::string topic, std::vector<MetaCoreScriptValueV1> 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<void()> callback);
void CancelTimer(std::uint64_t timerId);

View File

@ -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 保持未完全关闭,不宣称达到一次性全量计划的最终验收状态。
第一阶段若只展示静态数字孪生场景,可以延后刚体模拟,但空间查询和触发交互通常应优先实现。

View File

@ -0,0 +1,113 @@
#include "MetaCorePhysics/MetaCorePhysics.h"
#include "MetaCoreScene/MetaCoreScene.h"
#include "MetaCoreScene/MetaCoreSceneSerializer.h"
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <filesystem>
#include <vector>
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<MetaCore::MetaCoreTransformComponent>().Position = position;
auto& collider = object.AddComponent<MetaCore::MetaCoreColliderComponent>(); collider.IsTrigger = trigger; collider.CollisionLayer = layer;
auto& body = object.AddComponent<MetaCore::MetaCoreRigidBodyComponent>(); 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<std::string> 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<MetaCoreColliderComponent>().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<MetaCoreTransformComponent>().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<MetaCorePhysicsConstraintComponent>(); 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<MetaCorePhysicsConstraintComponent>(); 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<MetaCoreColliderComponent>().Size = {20.0F, 1.0F, 20.0F};
auto character=scene.CreateGameObject("Character"); character.GetComponent<MetaCoreTransformComponent>().Position={0.0F,2.0F,0.0F}; character.AddComponent<MetaCoreCharacterControllerComponent>();
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<MetaCoreCharacterControllerComponent>();
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;
}

View File

@ -3,6 +3,7 @@
"version-string": "1.0.0",
"dependencies": [
"basisu",
"bullet3",
"glm",
"entt",
"rmlui",