物理系统
This commit is contained in:
parent
2626070af7
commit
b8b2dd5520
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
537
Source/MetaCorePhysics/Private/MetaCoreBulletPhysics.cpp
Normal file
537
Source/MetaCorePhysics/Private/MetaCoreBulletPhysics.cpp
Normal 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
|
||||
73
Source/MetaCorePhysics/Private/MetaCorePhysicsSettings.cpp
Normal file
73
Source/MetaCorePhysics/Private/MetaCorePhysicsSettings.cpp
Normal 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
|
||||
178
Source/MetaCorePhysics/Public/MetaCorePhysics/MetaCorePhysics.h
Normal file
178
Source/MetaCorePhysics/Public/MetaCorePhysics/MetaCorePhysics.h
Normal 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
|
||||
@ -122,6 +122,9 @@ struct MetaCoreRuntimeProjectDocument {
|
||||
|
||||
MC_PROPERTY()
|
||||
std::filesystem::path InputMapPath{};
|
||||
|
||||
MC_PROPERTY()
|
||||
std::filesystem::path PhysicsSettingsPath{};
|
||||
};
|
||||
|
||||
MC_STRUCT()
|
||||
|
||||
@ -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); }
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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>();
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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 保持未完全关闭,不宣称达到一次性全量计划的最终验收状态。
|
||||
|
||||
第一阶段若只展示静态数字孪生场景,可以延后刚体模拟,但空间查询和触发交互通常应优先实现。
|
||||
|
||||
|
||||
113
tests/MetaCorePhysicsTests.cpp
Normal file
113
tests/MetaCorePhysicsTests.cpp
Normal 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;
|
||||
}
|
||||
@ -3,6 +3,7 @@
|
||||
"version-string": "1.0.0",
|
||||
"dependencies": [
|
||||
"basisu",
|
||||
"bullet3",
|
||||
"glm",
|
||||
"entt",
|
||||
"rmlui",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user