Add JoltPhysics3D for a performance test
This commit is contained in:
committed by
Jérôme Leclercq
parent
bd4c2d6ee7
commit
c5ac142888
@@ -72,7 +72,7 @@ namespace Nz
|
||||
std::size_t primitiveCount = list.GetSize();
|
||||
if (primitiveCount > 1)
|
||||
{
|
||||
std::vector<CompoundCollider3D::ChildCollider> childColliders(primitiveCount);
|
||||
std::vector<BulletCompoundCollider3D::ChildCollider> childColliders(primitiveCount);
|
||||
|
||||
for (unsigned int i = 0; i < primitiveCount; ++i)
|
||||
{
|
||||
@@ -81,12 +81,12 @@ namespace Nz
|
||||
childColliders[i].offsetMatrix = primitive.matrix;
|
||||
}
|
||||
|
||||
return std::make_shared<CompoundCollider3D>(std::move(childColliders));
|
||||
return std::make_shared<BulletCompoundCollider3D>(std::move(childColliders));
|
||||
}
|
||||
else if (primitiveCount > 0)
|
||||
return CreateGeomFromPrimitive(list.GetPrimitive(0));
|
||||
else
|
||||
return std::make_shared<NullCollider3D>();
|
||||
return std::make_shared<BulletNullCollider3D>();
|
||||
}
|
||||
|
||||
std::shared_ptr<BulletCollider3D> BulletCollider3D::CreateGeomFromPrimitive(const Primitive& primitive)
|
||||
@@ -94,17 +94,17 @@ namespace Nz
|
||||
switch (primitive.type)
|
||||
{
|
||||
case PrimitiveType::Box:
|
||||
return std::make_shared<BoxCollider3D>(primitive.box.lengths);
|
||||
return std::make_shared<BulletBoxCollider3D>(primitive.box.lengths);
|
||||
|
||||
case PrimitiveType::Cone:
|
||||
return std::make_shared<ConeCollider3D>(primitive.cone.length, primitive.cone.radius);
|
||||
return std::make_shared<BulletConeCollider3D>(primitive.cone.length, primitive.cone.radius);
|
||||
|
||||
case PrimitiveType::Plane:
|
||||
return std::make_shared<BoxCollider3D>(Vector3f(primitive.plane.size.x, 0.01f, primitive.plane.size.y));
|
||||
return std::make_shared<BulletBoxCollider3D>(Vector3f(primitive.plane.size.x, 0.01f, primitive.plane.size.y));
|
||||
///TODO: PlaneGeom?
|
||||
|
||||
case PrimitiveType::Sphere:
|
||||
return std::make_shared<SphereCollider3D>(primitive.sphere.size);
|
||||
return std::make_shared<BulletSphereCollider3D>(primitive.sphere.size);
|
||||
}
|
||||
|
||||
NazaraError("Primitive type not handled (0x" + NumberToString(UnderlyingCast(primitive.type), 16) + ')');
|
||||
@@ -113,15 +113,15 @@ namespace Nz
|
||||
|
||||
/********************************** BoxCollider3D **********************************/
|
||||
|
||||
BoxCollider3D::BoxCollider3D(const Vector3f& lengths) :
|
||||
BulletBoxCollider3D::BulletBoxCollider3D(const Vector3f& lengths) :
|
||||
m_lengths(lengths)
|
||||
{
|
||||
m_shape = std::make_unique<btBoxShape>(ToBullet(m_lengths * 0.5f));
|
||||
}
|
||||
|
||||
BoxCollider3D::~BoxCollider3D() = default;
|
||||
BulletBoxCollider3D::~BulletBoxCollider3D() = default;
|
||||
|
||||
void BoxCollider3D::BuildDebugMesh(std::vector<Vector3f>& vertices, std::vector<UInt16>& indices, const Matrix4f& offsetMatrix) const
|
||||
void BulletBoxCollider3D::BuildDebugMesh(std::vector<Vector3f>& vertices, std::vector<UInt16>& indices, const Matrix4f& offsetMatrix) const
|
||||
{
|
||||
auto InsertVertex = [&](float x, float y, float z) -> UInt16
|
||||
{
|
||||
@@ -163,59 +163,59 @@ namespace Nz
|
||||
InsertEdge(v3, v7);
|
||||
}
|
||||
|
||||
btCollisionShape* BoxCollider3D::GetShape() const
|
||||
btCollisionShape* BulletBoxCollider3D::GetShape() const
|
||||
{
|
||||
return m_shape.get();
|
||||
}
|
||||
|
||||
Vector3f BoxCollider3D::GetLengths() const
|
||||
Vector3f BulletBoxCollider3D::GetLengths() const
|
||||
{
|
||||
return m_lengths;
|
||||
}
|
||||
|
||||
ColliderType3D BoxCollider3D::GetType() const
|
||||
ColliderType3D BulletBoxCollider3D::GetType() const
|
||||
{
|
||||
return ColliderType3D::Box;
|
||||
}
|
||||
|
||||
/******************************** CapsuleCollider3D ********************************/
|
||||
|
||||
CapsuleCollider3D::CapsuleCollider3D(float length, float radius) :
|
||||
BulletCapsuleCollider3D::BulletCapsuleCollider3D(float length, float radius) :
|
||||
m_length(length),
|
||||
m_radius(radius)
|
||||
{
|
||||
m_shape = std::make_unique<btCapsuleShape>(radius, length);
|
||||
}
|
||||
|
||||
CapsuleCollider3D::~CapsuleCollider3D() = default;
|
||||
BulletCapsuleCollider3D::~BulletCapsuleCollider3D() = default;
|
||||
|
||||
void CapsuleCollider3D::BuildDebugMesh(std::vector<Vector3f>& vertices, std::vector<UInt16>& indices, const Matrix4f& offsetMatrix) const
|
||||
void BulletCapsuleCollider3D::BuildDebugMesh(std::vector<Vector3f>& vertices, std::vector<UInt16>& indices, const Matrix4f& offsetMatrix) const
|
||||
{
|
||||
}
|
||||
|
||||
float CapsuleCollider3D::GetLength() const
|
||||
float BulletCapsuleCollider3D::GetLength() const
|
||||
{
|
||||
return m_length;
|
||||
}
|
||||
|
||||
float CapsuleCollider3D::GetRadius() const
|
||||
float BulletCapsuleCollider3D::GetRadius() const
|
||||
{
|
||||
return m_radius;
|
||||
}
|
||||
|
||||
btCollisionShape* CapsuleCollider3D::GetShape() const
|
||||
btCollisionShape* BulletCapsuleCollider3D::GetShape() const
|
||||
{
|
||||
return m_shape.get();
|
||||
}
|
||||
|
||||
ColliderType3D CapsuleCollider3D::GetType() const
|
||||
ColliderType3D BulletCapsuleCollider3D::GetType() const
|
||||
{
|
||||
return ColliderType3D::Capsule;
|
||||
}
|
||||
|
||||
/******************************* CompoundCollider3D ********************************/
|
||||
|
||||
CompoundCollider3D::CompoundCollider3D(std::vector<ChildCollider> childs) :
|
||||
BulletCompoundCollider3D::BulletCompoundCollider3D(std::vector<ChildCollider> childs) :
|
||||
m_childs(std::move(childs))
|
||||
{
|
||||
m_shape = std::make_unique<btCompoundShape>();
|
||||
@@ -226,67 +226,67 @@ namespace Nz
|
||||
}
|
||||
}
|
||||
|
||||
CompoundCollider3D::~CompoundCollider3D() = default;
|
||||
BulletCompoundCollider3D::~BulletCompoundCollider3D() = default;
|
||||
|
||||
void CompoundCollider3D::BuildDebugMesh(std::vector<Vector3f>& vertices, std::vector<UInt16>& indices, const Matrix4f& offsetMatrix) const
|
||||
void BulletCompoundCollider3D::BuildDebugMesh(std::vector<Vector3f>& vertices, std::vector<UInt16>& indices, const Matrix4f& offsetMatrix) const
|
||||
{
|
||||
for (const auto& child : m_childs)
|
||||
child.collider->BuildDebugMesh(vertices, indices, offsetMatrix * child.offsetMatrix);
|
||||
}
|
||||
|
||||
auto CompoundCollider3D::GetGeoms() const -> const std::vector<ChildCollider>&
|
||||
auto BulletCompoundCollider3D::GetGeoms() const -> const std::vector<ChildCollider>&
|
||||
{
|
||||
return m_childs;
|
||||
}
|
||||
|
||||
btCollisionShape* CompoundCollider3D::GetShape() const
|
||||
btCollisionShape* BulletCompoundCollider3D::GetShape() const
|
||||
{
|
||||
return m_shape.get();
|
||||
}
|
||||
|
||||
ColliderType3D CompoundCollider3D::GetType() const
|
||||
ColliderType3D BulletCompoundCollider3D::GetType() const
|
||||
{
|
||||
return ColliderType3D::Compound;
|
||||
}
|
||||
|
||||
/********************************* ConeCollider3D **********************************/
|
||||
|
||||
ConeCollider3D::ConeCollider3D(float length, float radius) :
|
||||
BulletConeCollider3D::BulletConeCollider3D(float length, float radius) :
|
||||
m_length(length),
|
||||
m_radius(radius)
|
||||
{
|
||||
m_shape = std::make_unique<btConeShape>(radius, length);
|
||||
}
|
||||
|
||||
ConeCollider3D::~ConeCollider3D() = default;
|
||||
BulletConeCollider3D::~BulletConeCollider3D() = default;
|
||||
|
||||
void ConeCollider3D::BuildDebugMesh(std::vector<Vector3f>& vertices, std::vector<UInt16>& indices, const Matrix4f& offsetMatrix) const
|
||||
void BulletConeCollider3D::BuildDebugMesh(std::vector<Vector3f>& vertices, std::vector<UInt16>& indices, const Matrix4f& offsetMatrix) const
|
||||
{
|
||||
}
|
||||
|
||||
float ConeCollider3D::GetLength() const
|
||||
float BulletConeCollider3D::GetLength() const
|
||||
{
|
||||
return m_length;
|
||||
}
|
||||
|
||||
float ConeCollider3D::GetRadius() const
|
||||
float BulletConeCollider3D::GetRadius() const
|
||||
{
|
||||
return m_radius;
|
||||
}
|
||||
|
||||
btCollisionShape* ConeCollider3D::GetShape() const
|
||||
btCollisionShape* BulletConeCollider3D::GetShape() const
|
||||
{
|
||||
return m_shape.get();
|
||||
}
|
||||
|
||||
ColliderType3D ConeCollider3D::GetType() const
|
||||
ColliderType3D BulletConeCollider3D::GetType() const
|
||||
{
|
||||
return ColliderType3D::Cone;
|
||||
}
|
||||
|
||||
/****************************** ConvexCollider3D *******************************/
|
||||
|
||||
ConvexCollider3D::ConvexCollider3D(SparsePtr<const Vector3f> vertices, unsigned int vertexCount)
|
||||
BulletConvexCollider3D::BulletConvexCollider3D(SparsePtr<const Vector3f> vertices, unsigned int vertexCount)
|
||||
{
|
||||
static_assert(std::is_same_v<btScalar, float>);
|
||||
|
||||
@@ -294,9 +294,9 @@ namespace Nz
|
||||
m_shape->optimizeConvexHull();
|
||||
}
|
||||
|
||||
ConvexCollider3D::~ConvexCollider3D() = default;
|
||||
BulletConvexCollider3D::~BulletConvexCollider3D() = default;
|
||||
|
||||
void ConvexCollider3D::BuildDebugMesh(std::vector<Vector3f>& vertices, std::vector<UInt16>& indices, const Matrix4f& offsetMatrix) const
|
||||
void BulletConvexCollider3D::BuildDebugMesh(std::vector<Vector3f>& vertices, std::vector<UInt16>& indices, const Matrix4f& offsetMatrix) const
|
||||
{
|
||||
tsl::ordered_map<Vector3f, UInt16> vertexCache;
|
||||
auto InsertVertex = [&](const Vector3f& position) -> UInt16
|
||||
@@ -324,19 +324,19 @@ namespace Nz
|
||||
}
|
||||
}
|
||||
|
||||
btCollisionShape* ConvexCollider3D::GetShape() const
|
||||
btCollisionShape* BulletConvexCollider3D::GetShape() const
|
||||
{
|
||||
return m_shape.get();
|
||||
}
|
||||
|
||||
ColliderType3D ConvexCollider3D::GetType() const
|
||||
ColliderType3D BulletConvexCollider3D::GetType() const
|
||||
{
|
||||
return ColliderType3D::ConvexHull;
|
||||
}
|
||||
|
||||
/******************************* CylinderCollider3D ********************************/
|
||||
|
||||
CylinderCollider3D::CylinderCollider3D(float length, float radius) :
|
||||
BulletCylinderCollider3D::BulletCylinderCollider3D(float length, float radius) :
|
||||
m_length(length),
|
||||
m_radius(radius)
|
||||
{
|
||||
@@ -344,85 +344,85 @@ namespace Nz
|
||||
m_shape = std::make_unique<btCylinderShape>(btVector3{ radius, length, radius });
|
||||
}
|
||||
|
||||
CylinderCollider3D::~CylinderCollider3D() = default;
|
||||
BulletCylinderCollider3D::~BulletCylinderCollider3D() = default;
|
||||
|
||||
void CylinderCollider3D::BuildDebugMesh(std::vector<Vector3f>& vertices, std::vector<UInt16>& indices, const Matrix4f& offsetMatrix) const
|
||||
void BulletCylinderCollider3D::BuildDebugMesh(std::vector<Vector3f>& vertices, std::vector<UInt16>& indices, const Matrix4f& offsetMatrix) const
|
||||
{
|
||||
}
|
||||
|
||||
float CylinderCollider3D::GetLength() const
|
||||
float BulletCylinderCollider3D::GetLength() const
|
||||
{
|
||||
return m_length;
|
||||
}
|
||||
|
||||
float CylinderCollider3D::GetRadius() const
|
||||
float BulletCylinderCollider3D::GetRadius() const
|
||||
{
|
||||
return m_radius;
|
||||
}
|
||||
|
||||
btCollisionShape* CylinderCollider3D::GetShape() const
|
||||
btCollisionShape* BulletCylinderCollider3D::GetShape() const
|
||||
{
|
||||
return m_shape.get();
|
||||
}
|
||||
|
||||
ColliderType3D CylinderCollider3D::GetType() const
|
||||
ColliderType3D BulletCylinderCollider3D::GetType() const
|
||||
{
|
||||
return ColliderType3D::Cylinder;
|
||||
}
|
||||
|
||||
/********************************* NullCollider3D **********************************/
|
||||
|
||||
NullCollider3D::NullCollider3D()
|
||||
BulletNullCollider3D::BulletNullCollider3D()
|
||||
{
|
||||
m_shape = std::make_unique<btEmptyShape>();
|
||||
}
|
||||
|
||||
NullCollider3D::~NullCollider3D() = default;
|
||||
BulletNullCollider3D::~BulletNullCollider3D() = default;
|
||||
|
||||
void NullCollider3D::BuildDebugMesh(std::vector<Vector3f>& /*vertices*/, std::vector<UInt16>& /*indices*/, const Matrix4f& /*offsetMatrix*/) const
|
||||
void BulletNullCollider3D::BuildDebugMesh(std::vector<Vector3f>& /*vertices*/, std::vector<UInt16>& /*indices*/, const Matrix4f& /*offsetMatrix*/) const
|
||||
{
|
||||
}
|
||||
|
||||
void NullCollider3D::ComputeInertia(float /*mass*/, Vector3f* inertia) const
|
||||
void BulletNullCollider3D::ComputeInertia(float /*mass*/, Vector3f* inertia) const
|
||||
{
|
||||
inertia->Set(1.f, 1.f, 1.f);
|
||||
}
|
||||
|
||||
btCollisionShape* NullCollider3D::GetShape() const
|
||||
btCollisionShape* BulletNullCollider3D::GetShape() const
|
||||
{
|
||||
return m_shape.get();
|
||||
}
|
||||
|
||||
ColliderType3D NullCollider3D::GetType() const
|
||||
ColliderType3D BulletNullCollider3D::GetType() const
|
||||
{
|
||||
return ColliderType3D::Null;
|
||||
}
|
||||
|
||||
/******************************** SphereCollider3D *********************************/
|
||||
|
||||
SphereCollider3D::SphereCollider3D(float radius) :
|
||||
BulletSphereCollider3D::BulletSphereCollider3D(float radius) :
|
||||
m_radius(radius)
|
||||
{
|
||||
m_shape = std::make_unique<btSphereShape>(radius);
|
||||
}
|
||||
|
||||
SphereCollider3D::~SphereCollider3D() = default;
|
||||
BulletSphereCollider3D::~BulletSphereCollider3D() = default;
|
||||
|
||||
void SphereCollider3D::BuildDebugMesh(std::vector<Vector3f>& vertices, std::vector<UInt16>& indices, const Matrix4f& offsetMatrix) const
|
||||
void BulletSphereCollider3D::BuildDebugMesh(std::vector<Vector3f>& vertices, std::vector<UInt16>& indices, const Matrix4f& offsetMatrix) const
|
||||
{
|
||||
}
|
||||
|
||||
float SphereCollider3D::GetRadius() const
|
||||
float BulletSphereCollider3D::GetRadius() const
|
||||
{
|
||||
return m_radius;
|
||||
}
|
||||
|
||||
btCollisionShape* SphereCollider3D::GetShape() const
|
||||
btCollisionShape* BulletSphereCollider3D::GetShape() const
|
||||
{
|
||||
return m_shape.get();
|
||||
}
|
||||
|
||||
ColliderType3D SphereCollider3D::GetType() const
|
||||
ColliderType3D BulletSphereCollider3D::GetType() const
|
||||
{
|
||||
return ColliderType3D::Sphere;
|
||||
}
|
||||
|
||||
@@ -121,11 +121,19 @@ namespace Nz
|
||||
}
|
||||
}
|
||||
|
||||
#define HACK 0
|
||||
|
||||
btRigidBody* BulletPhysWorld3D::AddRigidBody(std::size_t& rigidBodyIndex, FunctionRef<void(btRigidBody* body)> constructor)
|
||||
{
|
||||
#if HACK
|
||||
static std::size_t index = 0;
|
||||
rigidBodyIndex = index++;
|
||||
btRigidBody* rigidBody = (btRigidBody*) ::operator new(sizeof(btRigidBody));
|
||||
constructor(rigidBody);
|
||||
m_world->dynamicWorld.addRigidBody(rigidBody);
|
||||
#else
|
||||
btRigidBody* rigidBody = m_world->rigidBodyPool.Allocate(m_world->rigidBodyPool.DeferConstruct, rigidBodyIndex);
|
||||
constructor(rigidBody);
|
||||
|
||||
m_world->dynamicWorld.addRigidBody(rigidBody);
|
||||
|
||||
// Small hack to order rigid bodies to make it cache friendly
|
||||
@@ -144,6 +152,7 @@ namespace Nz
|
||||
*it = rigidBody;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return rigidBody;
|
||||
}
|
||||
@@ -152,6 +161,10 @@ namespace Nz
|
||||
{
|
||||
// TODO: Improve deletion (since rigid bodies are sorted)
|
||||
m_world->dynamicWorld.removeRigidBody(rigidBody); //< this does a linear search
|
||||
#if HACK
|
||||
::operator delete(rigidBody);
|
||||
#else
|
||||
m_world->rigidBodyPool.Free(rigidBodyIndex);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Nz
|
||||
NazaraAssert(m_world, "Invalid world");
|
||||
|
||||
if (!m_geom)
|
||||
m_geom = std::make_shared<NullCollider3D>();
|
||||
m_geom = std::make_shared<BulletNullCollider3D>();
|
||||
|
||||
Vector3f inertia;
|
||||
m_geom->ComputeInertia(1.f, &inertia);
|
||||
@@ -214,7 +214,7 @@ namespace Nz
|
||||
if (geom)
|
||||
m_geom = std::move(geom);
|
||||
else
|
||||
m_geom = std::make_shared<NullCollider3D>();
|
||||
m_geom = std::make_shared<BulletNullCollider3D>();
|
||||
|
||||
m_body->setCollisionShape(m_geom->GetShape());
|
||||
if (recomputeInertia)
|
||||
@@ -267,6 +267,15 @@ namespace Nz
|
||||
m_body->setWorldTransform(worldTransform);
|
||||
}
|
||||
|
||||
void BulletRigidBody3D::SetPositionAndRotation(const Vector3f& position, const Quaternionf& rotation)
|
||||
{
|
||||
btTransform worldTransform;
|
||||
worldTransform.setOrigin(ToBullet(position));
|
||||
worldTransform.setRotation(ToBullet(rotation));
|
||||
|
||||
m_body->setWorldTransform(worldTransform);
|
||||
}
|
||||
|
||||
void BulletRigidBody3D::SetRotation(const Quaternionf& rotation)
|
||||
{
|
||||
btTransform worldTransform = m_body->getWorldTransform();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <Nazara/BulletPhysics3D/Systems/BulletPhysics3DSystem.hpp>
|
||||
#include <Nazara/Utility/Components/NodeComponent.hpp>
|
||||
#include <iostream>
|
||||
#include <Nazara/BulletPhysics3D/Debug.hpp>
|
||||
|
||||
namespace Nz
|
||||
@@ -14,6 +15,10 @@ namespace Nz
|
||||
{
|
||||
m_constructConnection = registry.on_construct<BulletRigidBody3DComponent>().connect<&BulletPhysics3DSystem::OnConstruct>(this);
|
||||
m_destructConnection = registry.on_destroy<BulletRigidBody3DComponent>().connect<&BulletPhysics3DSystem::OnDestruct>(this);
|
||||
|
||||
m_stepCount = 0;
|
||||
m_physicsTime = Time::Zero();
|
||||
m_updateTime = Time::Zero();
|
||||
}
|
||||
|
||||
BulletPhysics3DSystem::~BulletPhysics3DSystem()
|
||||
@@ -26,6 +31,20 @@ namespace Nz
|
||||
rigidBodyComponent.Destroy();
|
||||
}
|
||||
|
||||
void BulletPhysics3DSystem::Dump()
|
||||
{
|
||||
if (m_stepCount == 0)
|
||||
m_stepCount = 1;
|
||||
|
||||
std::cout << "Physics time: " << (m_physicsTime / Time::Nanoseconds(m_stepCount)) << std::endl;
|
||||
std::cout << "Update time: " << (m_updateTime / Time::Nanoseconds(m_stepCount)) << std::endl;
|
||||
std::cout << "--" << std::endl;
|
||||
|
||||
m_stepCount = 0;
|
||||
m_physicsTime = Time::Zero();
|
||||
m_updateTime = Time::Zero();
|
||||
}
|
||||
|
||||
bool BulletPhysics3DSystem::RaycastQueryFirst(const Vector3f& from, const Vector3f& to, RaycastHit* hitInfo)
|
||||
{
|
||||
if (!m_physWorld.RaycastQueryFirst(from, to, hitInfo))
|
||||
@@ -49,12 +68,16 @@ namespace Nz
|
||||
BulletRigidBody3DComponent& entityPhysics = m_registry.get<BulletRigidBody3DComponent>(entity);
|
||||
NodeComponent& entityNode = m_registry.get<NodeComponent>(entity);
|
||||
|
||||
entityPhysics.SetPosition(entityNode.GetPosition(CoordSys::Global));
|
||||
entityPhysics.SetRotation(entityNode.GetRotation(CoordSys::Global));
|
||||
entityPhysics.SetPositionAndRotation(entityNode.GetPosition(CoordSys::Global), entityNode.GetRotation(CoordSys::Global));
|
||||
});
|
||||
|
||||
Time t1 = GetElapsedNanoseconds();
|
||||
|
||||
// Update the physics world
|
||||
m_physWorld.Step(elapsedTime);
|
||||
m_stepCount++;
|
||||
|
||||
Time t2 = GetElapsedNanoseconds();
|
||||
|
||||
// Replicate rigid body position to their node components
|
||||
// TODO: Only replicate active entities
|
||||
@@ -67,6 +90,11 @@ namespace Nz
|
||||
nodeComponent.SetPosition(rigidBodyComponent.GetPosition(), CoordSys::Global);
|
||||
nodeComponent.SetRotation(rigidBodyComponent.GetRotation(), CoordSys::Global);
|
||||
}
|
||||
|
||||
Time t3 = GetElapsedNanoseconds();
|
||||
|
||||
m_physicsTime += (t2 - t1);
|
||||
m_updateTime += (t3 - t2);
|
||||
}
|
||||
|
||||
void BulletPhysics3DSystem::OnConstruct(entt::registry& registry, entt::entity entity)
|
||||
|
||||
Reference in New Issue
Block a user