diff --git a/Source/MetaCorePhysics/Private/MetaCoreBulletPhysics.cpp b/Source/MetaCorePhysics/Private/MetaCoreBulletPhysics.cpp index f1c1959..87d2ad8 100644 --- a/Source/MetaCorePhysics/Private/MetaCoreBulletPhysics.cpp +++ b/Source/MetaCorePhysics/Private/MetaCoreBulletPhysics.cpp @@ -210,9 +210,9 @@ public: if (collider.Shape == MetaCoreColliderShape::Box) return std::make_unique(Bt(size * 0.5F)); if (collider.Shape == MetaCoreColliderShape::Sphere) return std::make_unique(std::max(0.001F, collider.Radius * std::max({absoluteScale.x, absoluteScale.y, absoluteScale.z}))); if (collider.Shape == MetaCoreColliderShape::Capsule) { - const float radius = std::max(0.001F, collider.Radius * std::max(absoluteScale.x, absoluteScale.z)); - const float cylinderHeight = std::max(0.0F, collider.Height * absoluteScale.y - 2.0F * radius); - return std::make_unique(radius, cylinderHeight); + const float radius = std::max(0.001F, collider.Radius * std::max(absoluteScale.x, absoluteScale.y)); + const float cylinderHeight = std::max(0.0F, collider.Height * absoluteScale.z - 2.0F * radius); + return std::make_unique(radius, cylinderHeight); } if (!MeshProvider || !collider.MeshAssetGuid.IsValid()) { Diagnostics.push_back("对象 " + std::to_string(objectId) + " 缺少网格碰撞载荷"); return {}; } const auto mesh = MeshProvider(collider.MeshAssetGuid); @@ -405,7 +405,7 @@ void MetaCorePhysicsWorld::Step(double fixedDeltaSeconds) { (void)id; if (!entry->Character) continue; btVector3 velocity = entry->Body->getLinearVelocity(); - velocity.setY(std::max(velocity.y(), -entry->CharacterMaxFallSpeed)); + velocity.setZ(std::max(velocity.z(), -entry->CharacterMaxFallSpeed)); entry->Body->setLinearVelocity(velocity); } Impl_->World->stepSimulation(static_cast(fixedDeltaSeconds), 0); @@ -434,7 +434,7 @@ std::optional MetaCorePhysicsWorld::Raycast(glm::vec3 origin static std::unique_ptr MetaCoreMakeQueryShape(const MetaCorePhysicsQueryShape& shape) { if (shape.Type == MetaCorePhysicsQueryShapeType::Sphere && shape.Radius > 0.0F) return std::make_unique(shape.Radius); - if (shape.Type == MetaCorePhysicsQueryShapeType::Capsule && shape.Radius > 0.0F && shape.Height >= shape.Radius * 2.0F) return std::make_unique(shape.Radius, shape.Height - shape.Radius * 2.0F); + if (shape.Type == MetaCorePhysicsQueryShapeType::Capsule && shape.Radius > 0.0F && shape.Height >= shape.Radius * 2.0F) return std::make_unique(shape.Radius, shape.Height - shape.Radius * 2.0F); if (shape.Type == MetaCorePhysicsQueryShapeType::Box && shape.HalfExtents.x > 0.0F && shape.HalfExtents.y > 0.0F && shape.HalfExtents.z > 0.0F) return std::make_unique(Bt(shape.HalfExtents)); return {}; } @@ -480,8 +480,8 @@ bool MetaCorePhysicsWorld::AddImpulse(MetaCoreId id, glm::vec3 value) { const au bool MetaCorePhysicsWorld::SetLinearVelocity(MetaCoreId id, glm::vec3 value) { const auto it = Impl_->Bodies.find(id); if (it == Impl_->Bodies.end()) return false; it->second->Body->setLinearVelocity(Bt(value)); it->second->Body->activate(true); return true; } std::optional MetaCorePhysicsWorld::GetLinearVelocity(MetaCoreId id) const { const auto it = Impl_->Bodies.find(id); return it == Impl_->Bodies.end() ? std::nullopt : std::optional(Glm(it->second->Body->getLinearVelocity())); } bool MetaCorePhysicsWorld::WakeUp(MetaCoreId id) { const auto it = Impl_->Bodies.find(id); if (it == Impl_->Bodies.end()) return false; it->second->Body->activate(true); return true; } -bool MetaCorePhysicsWorld::CharacterMove(MetaCoreId id, glm::vec3 displacement) { const auto it = Impl_->Bodies.find(id); if (it == Impl_->Bodies.end() || !Impl_->Scene.FindGameObject(id).HasComponent()) return false; auto velocity=it->second->Body->getLinearVelocity(); velocity.setX(displacement.x/static_cast(Impl_->Settings.FixedTimeStep)); velocity.setZ(displacement.z/static_cast(Impl_->Settings.FixedTimeStep)); it->second->Body->setLinearVelocity(velocity); it->second->Body->activate(true); return true; } -bool MetaCorePhysicsWorld::CharacterJump(MetaCoreId id, float speed) { if (!IsCharacterGrounded(id)) return false; auto value=GetLinearVelocity(id); if(!value)return false; value->y=speed; return SetLinearVelocity(id,*value); } +bool MetaCorePhysicsWorld::CharacterMove(MetaCoreId id, glm::vec3 displacement) { const auto it = Impl_->Bodies.find(id); if (it == Impl_->Bodies.end() || !Impl_->Scene.FindGameObject(id).HasComponent()) return false; auto velocity=it->second->Body->getLinearVelocity(); velocity.setX(displacement.x/static_cast(Impl_->Settings.FixedTimeStep)); velocity.setY(displacement.y/static_cast(Impl_->Settings.FixedTimeStep)); it->second->Body->setLinearVelocity(velocity); it->second->Body->activate(true); return true; } +bool MetaCorePhysicsWorld::CharacterJump(MetaCoreId id, float speed) { if (!IsCharacterGrounded(id)) return false; auto value=GetLinearVelocity(id); if(!value)return false; value->z=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; @@ -500,7 +500,7 @@ bool MetaCorePhysicsWorld::SetConstraintMotor(MetaCoreId id, bool enabled, float 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; + const float distance = it->second->CharacterHalfHeight * it->second->Scale.z + it->second->CharacterSkinWidth + 0.08F; struct Callback final : btCollisionWorld::ClosestRayResultCallback { const btCollisionObject* Ignored; Callback(const btVector3& start, const btVector3& end, const btCollisionObject* ignored) @@ -508,9 +508,9 @@ bool MetaCorePhysicsWorld::IsCharacterGrounded(MetaCoreId id) const { 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()); + } callback(from, from + btVector3(0.0F, 0.0F, -distance), it->second->Body.get()); Impl_->World->rayTest(callback.m_rayFromWorld, callback.m_rayToWorld, callback); - return callback.hasHit() && callback.m_hitNormalWorld.y() >= 0.5F; + return callback.hasHit() && callback.m_hitNormalWorld.z() >= 0.5F; } std::vector MetaCorePhysicsWorld::BuildDebugLines() const { diff --git a/Source/MetaCorePhysics/Private/MetaCorePhysicsSettings.cpp b/Source/MetaCorePhysics/Private/MetaCorePhysicsSettings.cpp index ae862f9..c4c3f43 100644 --- a/Source/MetaCorePhysics/Private/MetaCorePhysicsSettings.cpp +++ b/Source/MetaCorePhysics/Private/MetaCorePhysicsSettings.cpp @@ -20,6 +20,9 @@ MetaCorePhysicsSettingsDocument MetaCoreBuildDefaultPhysicsSettings() { bool MetaCoreValidatePhysicsSettings(MetaCorePhysicsSettingsDocument& document, std::vector* issues) { bool valid = true; const auto report = [&](std::string value) { valid = false; if (issues) issues->push_back(std::move(value)); }; + if (!std::isfinite(document.Gravity.x) || !std::isfinite(document.Gravity.y) || !std::isfinite(document.Gravity.z)) { + report("Gravity 必须为有限值"); document.Gravity = glm::vec3(0.0F, 0.0F, -9.81F); + } 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; } @@ -55,6 +58,12 @@ std::optional MetaCoreReadPhysicsSettings(const if (size && !input.read(reinterpret_cast(bytes.data()), static_cast(size))) return std::nullopt; MetaCorePhysicsSettingsDocument result; if (!MetaCoreDeserializeFromBytes(std::span(bytes.data(), bytes.size()), result, registry)) return std::nullopt; + // Version 1 used Bullet's Y-up convention. MetaCore is Z-up, so migrate the + // complete gravity vector instead of only replacing the old default value. + if (result.Version < 2U) { + result.Gravity = glm::vec3(result.Gravity.x, result.Gravity.z, result.Gravity.y); + result.Version = 2U; + } (void)MetaCoreValidatePhysicsSettings(result); return result; } diff --git a/Source/MetaCorePhysics/Public/MetaCorePhysics/MetaCorePhysics.h b/Source/MetaCorePhysics/Public/MetaCorePhysics/MetaCorePhysics.h index 0948954..47094e3 100644 --- a/Source/MetaCorePhysics/Public/MetaCorePhysics/MetaCorePhysics.h +++ b/Source/MetaCorePhysics/Public/MetaCorePhysics/MetaCorePhysics.h @@ -46,9 +46,9 @@ MC_STRUCT() struct MetaCorePhysicsSettingsDocument { MC_GENERATED_BODY() MC_PROPERTY() - std::uint32_t Version = 1U; + std::uint32_t Version = 2U; MC_PROPERTY() - glm::vec3 Gravity{0.0F, -9.81F, 0.0F}; + glm::vec3 Gravity{0.0F, 0.0F, -9.81F}; MC_PROPERTY() double FixedTimeStep = 1.0 / 60.0; MC_PROPERTY() diff --git a/tests/MetaCorePhysicsTests.cpp b/tests/MetaCorePhysicsTests.cpp index 32eb0dc..cf716b2 100644 --- a/tests/MetaCorePhysicsTests.cpp +++ b/tests/MetaCorePhysicsTests.cpp @@ -28,21 +28,22 @@ int main() { { auto settings = MetaCoreBuildDefaultPhysicsSettings(); Expect(settings.LayerNames.size() == 32U && settings.CollisionMasks.size() == 32U, "default settings expose 32 layers"); + Expect(settings.Version == 2U && settings.Gravity == glm::vec3(0.0F, 0.0F, -9.81F), "default settings use MetaCore Z-up gravity"); settings.FixedTimeStep = -1.0; std::vector issues; Expect(!MetaCoreValidatePhysicsSettings(settings, &issues) && !issues.empty(), "invalid settings are repaired and diagnosed"); } { MetaCoreScene scene; - auto ground = AddBox(scene, "Ground", {0.0F, -1.0F, 0.0F}, MetaCoreRigidBodyType::Static); - ground.GetComponent().Size = {20.0F, 1.0F, 20.0F}; - const auto falling = AddBox(scene, "Falling", {0.0F, 4.0F, 0.0F}, MetaCoreRigidBodyType::Dynamic); + auto ground = AddBox(scene, "Ground", {0.0F, 0.0F, -1.0F}, MetaCoreRigidBodyType::Static); + ground.GetComponent().Size = {20.0F, 20.0F, 1.0F}; + const auto falling = AddBox(scene, "Falling", {0.0F, 0.0F, 4.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); + auto hit = world.Raycast({0.0F, 0.0F, 10.0F}, {0.0F, 0.0F, -1.0F}, 30.0F); Expect(hit.has_value() && hit->ColliderObjectId == falling.GetId(), "raycast returns closest collider"); const auto invalid = world.Raycast(glm::vec3(0.0F), glm::vec3(0.0F), 10.0F); Expect(!invalid.has_value(), "zero direction is rejected"); for (int i = 0; i < 180; ++i) { scene.IncrementRevision(); world.Step(1.0 / 60.0); } - const float y = scene.FindGameObject(falling.GetId()).GetComponent().Position.y; - Expect(y > -0.1F && y < 1.0F, "dynamic body falls and rests on static body"); + const float z = scene.FindGameObject(falling.GetId()).GetComponent().Position.z; + Expect(z > -0.1F && z < 1.0F, "dynamic body falls along -Z and rests on static body"); Expect(world.GetStatistics().BodyCount == 2U, "world reports body statistics"); } { @@ -71,9 +72,9 @@ int main() { } { MetaCoreScene scene; - auto ground = AddBox(scene, "CharacterGround", {0.0F, -0.5F, 0.0F}, MetaCoreRigidBodyType::Static); - ground.GetComponent().Size = {20.0F, 1.0F, 20.0F}; - auto character=scene.CreateGameObject("Character"); character.GetComponent().Position={0.0F,2.0F,0.0F}; character.AddComponent(); + auto ground = AddBox(scene, "CharacterGround", {0.0F, 0.0F, -0.5F}, MetaCoreRigidBodyType::Static); + ground.GetComponent().Size = {20.0F, 20.0F, 1.0F}; + auto character=scene.CreateGameObject("Character"); character.GetComponent().Position={0.0F,0.0F,2.0F}; character.AddComponent(); MetaCorePhysicsWorld world(scene,MetaCoreBuildDefaultPhysicsSettings()); Expect(world.GetStatistics().BodyCount==2U,"character controller creates an implicit capsule body"); Expect(world.CharacterMove(character.GetId(),{0.1F,0.0F,0.0F}),"character accepts movement commands"); for (int index = 0; index < 120; ++index) world.Step(1.0 / 60.0);