BGE real-time soft bodies, step 2 / 3: create a btSoftBody. Next step is hooking up / deform graphics mesh and choose collision shape.
Note: feature is still disabled.
This commit is contained in:
parent
92829e821f
commit
1c29d02305
@ -119,7 +119,7 @@ void Process(const btDbvtNode* n)
|
|||||||
//
|
//
|
||||||
btDbvtBroadphase::btDbvtBroadphase(btOverlappingPairCache* paircache)
|
btDbvtBroadphase::btDbvtBroadphase(btOverlappingPairCache* paircache)
|
||||||
{
|
{
|
||||||
m_deferedcollide = true;//false;
|
m_deferedcollide = false;
|
||||||
m_needcleanup = true;
|
m_needcleanup = true;
|
||||||
m_releasepaircache = (paircache!=0)?false:true;
|
m_releasepaircache = (paircache!=0)?false:true;
|
||||||
m_prediction = 1/(btScalar)2;
|
m_prediction = 1/(btScalar)2;
|
||||||
@ -164,7 +164,7 @@ btBroadphaseProxy* btDbvtBroadphase::createProxy( const btVector3& aabbMin,
|
|||||||
void* userPtr,
|
void* userPtr,
|
||||||
short int collisionFilterGroup,
|
short int collisionFilterGroup,
|
||||||
short int collisionFilterMask,
|
short int collisionFilterMask,
|
||||||
btDispatcher* dispatcher,
|
btDispatcher* /*dispatcher*/,
|
||||||
void* /*multiSapProxy*/)
|
void* /*multiSapProxy*/)
|
||||||
{
|
{
|
||||||
btDbvtProxy* proxy=new(btAlignedAlloc(sizeof(btDbvtProxy),16)) btDbvtProxy( userPtr,
|
btDbvtProxy* proxy=new(btAlignedAlloc(sizeof(btDbvtProxy),16)) btDbvtProxy( userPtr,
|
||||||
@ -180,6 +180,7 @@ if(!m_deferedcollide)
|
|||||||
btDbvtTreeCollider collider(this);
|
btDbvtTreeCollider collider(this);
|
||||||
collider.proxy=proxy;
|
collider.proxy=proxy;
|
||||||
btDbvt::collideTV(m_sets[0].m_root,proxy->aabb,collider);
|
btDbvt::collideTV(m_sets[0].m_root,proxy->aabb,collider);
|
||||||
|
btDbvt::collideTV(m_sets[1].m_root,proxy->aabb,collider);
|
||||||
}
|
}
|
||||||
return(proxy);
|
return(proxy);
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,6 @@ int m_gid; // Gen id
|
|||||||
bool m_releasepaircache; // Release pair cache on delete
|
bool m_releasepaircache; // Release pair cache on delete
|
||||||
bool m_deferedcollide; // Defere dynamic/static collision to collide call
|
bool m_deferedcollide; // Defere dynamic/static collision to collide call
|
||||||
bool m_needcleanup; // Need to run cleanup?
|
bool m_needcleanup; // Need to run cleanup?
|
||||||
bool m_initialize; // Initialization
|
|
||||||
#if DBVT_BP_PROFILE
|
#if DBVT_BP_PROFILE
|
||||||
btClock m_clock;
|
btClock m_clock;
|
||||||
struct {
|
struct {
|
||||||
|
@ -143,7 +143,7 @@ public:
|
|||||||
|
|
||||||
virtual ~btCollisionObject();
|
virtual ~btCollisionObject();
|
||||||
|
|
||||||
void setCollisionShape(btCollisionShape* collisionShape)
|
virtual void setCollisionShape(btCollisionShape* collisionShape)
|
||||||
{
|
{
|
||||||
m_collisionShape = collisionShape;
|
m_collisionShape = collisionShape;
|
||||||
m_rootCollisionShape = collisionShape;
|
m_rootCollisionShape = collisionShape;
|
||||||
|
@ -69,7 +69,7 @@ btSoftBody::btSoftBody(btSoftBodyWorldInfo* worldInfo,int node_count, const btV
|
|||||||
pm->m_flags = fMaterial::Default;
|
pm->m_flags = fMaterial::Default;
|
||||||
/* Collision shape */
|
/* Collision shape */
|
||||||
///for now, create a collision shape internally
|
///for now, create a collision shape internally
|
||||||
setCollisionShape(new btSoftBodyCollisionShape(this));
|
m_collisionShape = new btSoftBodyCollisionShape(this);
|
||||||
m_collisionShape->setMargin(0.25);
|
m_collisionShape->setMargin(0.25);
|
||||||
/* Nodes */
|
/* Nodes */
|
||||||
const btScalar margin=getCollisionShape()->getMargin();
|
const btScalar margin=getCollisionShape()->getMargin();
|
||||||
|
@ -606,6 +606,13 @@ public:
|
|||||||
/* dtor */
|
/* dtor */
|
||||||
virtual ~btSoftBody();
|
virtual ~btSoftBody();
|
||||||
/* Check for existing link */
|
/* Check for existing link */
|
||||||
|
|
||||||
|
|
||||||
|
virtual void setCollisionShape(btCollisionShape* collisionShape)
|
||||||
|
{
|
||||||
|
//don't do anything, due to the internal shape hack: todo: fix this
|
||||||
|
}
|
||||||
|
|
||||||
bool checkLink( int node0,
|
bool checkLink( int node0,
|
||||||
int node1) const;
|
int node1) const;
|
||||||
bool checkLink( const Node* node0,
|
bool checkLink( const Node* node0,
|
||||||
|
@ -94,7 +94,6 @@ public:
|
|||||||
virtual void setLocalScaling(const btVector3& /*scaling*/)
|
virtual void setLocalScaling(const btVector3& /*scaling*/)
|
||||||
{
|
{
|
||||||
///na
|
///na
|
||||||
btAssert(0);
|
|
||||||
}
|
}
|
||||||
virtual const btVector3& getLocalScaling() const
|
virtual const btVector3& getLocalScaling() const
|
||||||
{
|
{
|
||||||
|
@ -1318,6 +1318,7 @@ void BL_CreatePhysicsObjectNew(KX_GameObject* gameobj,
|
|||||||
// ACTOR is now a separate feature
|
// ACTOR is now a separate feature
|
||||||
objprop.m_isactor = (blenderobject->gameflag & OB_ACTOR)!=0;
|
objprop.m_isactor = (blenderobject->gameflag & OB_ACTOR)!=0;
|
||||||
objprop.m_dyna = (blenderobject->gameflag & OB_DYNAMIC) != 0;
|
objprop.m_dyna = (blenderobject->gameflag & OB_DYNAMIC) != 0;
|
||||||
|
objprop.m_softbody = (blenderobject->gameflag & OB_SOFT_BODY) != 0;
|
||||||
objprop.m_angular_rigidbody = (blenderobject->gameflag & OB_RIGID_BODY) != 0;
|
objprop.m_angular_rigidbody = (blenderobject->gameflag & OB_RIGID_BODY) != 0;
|
||||||
objprop.m_ghost = (blenderobject->gameflag & OB_GHOST) != 0;
|
objprop.m_ghost = (blenderobject->gameflag & OB_GHOST) != 0;
|
||||||
objprop.m_disableSleeping = (blenderobject->gameflag & OB_COLLISION_RESPONSE) != 0;//abuse the OB_COLLISION_RESPONSE flag
|
objprop.m_disableSleeping = (blenderobject->gameflag & OB_COLLISION_RESPONSE) != 0;//abuse the OB_COLLISION_RESPONSE flag
|
||||||
|
@ -75,6 +75,7 @@ struct KX_CBounds
|
|||||||
struct KX_ObjectProperties
|
struct KX_ObjectProperties
|
||||||
{
|
{
|
||||||
bool m_dyna;
|
bool m_dyna;
|
||||||
|
bool m_softbody;
|
||||||
double m_radius;
|
double m_radius;
|
||||||
bool m_angular_rigidbody;
|
bool m_angular_rigidbody;
|
||||||
bool m_in_active_layer;
|
bool m_in_active_layer;
|
||||||
|
@ -917,6 +917,7 @@ void KX_ConvertBulletObject( class KX_GameObject* gameobj,
|
|||||||
ci.m_collisionFilterGroup = (isbulletdyna) ? short(CcdConstructionInfo::DefaultFilter) : short(CcdConstructionInfo::StaticFilter);
|
ci.m_collisionFilterGroup = (isbulletdyna) ? short(CcdConstructionInfo::DefaultFilter) : short(CcdConstructionInfo::StaticFilter);
|
||||||
ci.m_collisionFilterMask = (isbulletdyna) ? short(CcdConstructionInfo::AllFilter) : short(CcdConstructionInfo::AllFilter ^ CcdConstructionInfo::StaticFilter);
|
ci.m_collisionFilterMask = (isbulletdyna) ? short(CcdConstructionInfo::AllFilter) : short(CcdConstructionInfo::AllFilter ^ CcdConstructionInfo::StaticFilter);
|
||||||
ci.m_bRigid = objprop->m_dyna && objprop->m_angular_rigidbody;
|
ci.m_bRigid = objprop->m_dyna && objprop->m_angular_rigidbody;
|
||||||
|
ci.m_bSoft = objprop->m_softbody;
|
||||||
MT_Vector3 scaling = gameobj->NodeGetWorldScaling();
|
MT_Vector3 scaling = gameobj->NodeGetWorldScaling();
|
||||||
ci.m_scaling.setValue(scaling[0], scaling[1], scaling[2]);
|
ci.m_scaling.setValue(scaling[0], scaling[1], scaling[2]);
|
||||||
KX_BulletPhysicsController* physicscontroller = new KX_BulletPhysicsController(ci,isbulletdyna);
|
KX_BulletPhysicsController* physicscontroller = new KX_BulletPhysicsController(ci,isbulletdyna);
|
||||||
@ -933,10 +934,12 @@ void KX_ConvertBulletObject( class KX_GameObject* gameobj,
|
|||||||
|
|
||||||
gameobj->SetPhysicsController(physicscontroller,isbulletdyna);
|
gameobj->SetPhysicsController(physicscontroller,isbulletdyna);
|
||||||
physicscontroller->setNewClientInfo(gameobj->getClientInfo());
|
physicscontroller->setNewClientInfo(gameobj->getClientInfo());
|
||||||
|
{
|
||||||
btRigidBody* rbody = physicscontroller->GetRigidBody();
|
btRigidBody* rbody = physicscontroller->GetRigidBody();
|
||||||
|
|
||||||
if (objprop->m_disableSleeping)
|
if (rbody && objprop->m_disableSleeping)
|
||||||
rbody->setActivationState(DISABLE_DEACTIVATION);
|
rbody->setActivationState(DISABLE_DEACTIVATION);
|
||||||
|
}
|
||||||
|
|
||||||
//Now done directly in ci.m_collisionFlags so that it propagates to replica
|
//Now done directly in ci.m_collisionFlags so that it propagates to replica
|
||||||
//if (objprop->m_ghost)
|
//if (objprop->m_ghost)
|
||||||
|
@ -133,6 +133,13 @@ btSoftBody* CcdPhysicsController::GetSoftBody()
|
|||||||
return btSoftBody::upcast(m_object);
|
return btSoftBody::upcast(m_object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "BulletSoftBody/btSoftBodyHelpers.h"
|
||||||
|
|
||||||
|
btVector3 pts[3] = {btVector3(0,0,0),
|
||||||
|
btVector3(0,1,0),
|
||||||
|
btVector3(1,1,0)};
|
||||||
|
int triangles[3] = {0,1,2};
|
||||||
|
btSoftBodyWorldInfo sbi;
|
||||||
|
|
||||||
void CcdPhysicsController::CreateRigidbody()
|
void CcdPhysicsController::CreateRigidbody()
|
||||||
{
|
{
|
||||||
@ -143,14 +150,64 @@ void CcdPhysicsController::CreateRigidbody()
|
|||||||
///either create a btCollisionObject, btRigidBody or btSoftBody
|
///either create a btCollisionObject, btRigidBody or btSoftBody
|
||||||
|
|
||||||
//create a collision object
|
//create a collision object
|
||||||
if (0)//m_cci.m_mass==0.f)
|
|
||||||
|
//disable soft body until first sneak preview is ready
|
||||||
|
if (0)//m_cci.m_bSoft)
|
||||||
{
|
{
|
||||||
btRigidBody::btRigidBodyConstructionInfo rbci(m_cci.m_mass,m_bulletMotionState,m_collisionShape,m_cci.m_localInertiaTensor * m_cci.m_inertiaFactor);
|
btRigidBody::btRigidBodyConstructionInfo rbci(m_cci.m_mass,m_bulletMotionState,m_collisionShape,m_cci.m_localInertiaTensor * m_cci.m_inertiaFactor);
|
||||||
rbci.m_linearDamping = m_cci.m_linearDamping;
|
rbci.m_linearDamping = m_cci.m_linearDamping;
|
||||||
rbci.m_angularDamping = m_cci.m_angularDamping;
|
rbci.m_angularDamping = m_cci.m_angularDamping;
|
||||||
rbci.m_friction = m_cci.m_friction;
|
rbci.m_friction = m_cci.m_friction;
|
||||||
rbci.m_restitution = m_cci.m_restitution;
|
rbci.m_restitution = m_cci.m_restitution;
|
||||||
m_object = new btCollisionObject();
|
|
||||||
|
|
||||||
|
sbi.m_broadphase = this->m_cci.m_physicsEnv->getBroadphase();
|
||||||
|
sbi.m_dispatcher = (btCollisionDispatcher*) m_cci.m_physicsEnv->getDispatcher();
|
||||||
|
|
||||||
|
int nodecount = 0;
|
||||||
|
|
||||||
|
|
||||||
|
int numtriangles = 1;
|
||||||
|
|
||||||
|
btVector3 p = trans.getOrigin();
|
||||||
|
btScalar h = 1.f;
|
||||||
|
|
||||||
|
PHY__Vector3 grav;
|
||||||
|
m_cci.m_physicsEnv->getGravity(grav);
|
||||||
|
sbi.m_gravity.setValue(grav[0],grav[1],grav[2]);
|
||||||
|
|
||||||
|
const btVector3 c[]={ p+h*btVector3(-1,-1,-1),
|
||||||
|
p+h*btVector3(+1,-1,-1),
|
||||||
|
p+h*btVector3(-1,+1,-1),
|
||||||
|
p+h*btVector3(+1,+1,-1),
|
||||||
|
p+h*btVector3(-1,-1,+1),
|
||||||
|
p+h*btVector3(+1,-1,+1),
|
||||||
|
p+h*btVector3(-1,+1,+1),
|
||||||
|
p+h*btVector3(+1,+1,+1)};
|
||||||
|
|
||||||
|
int i=0;
|
||||||
|
const int n=15;
|
||||||
|
//btSoftBody* psb=btSoftBodyHelpers::CreateRope(sbi, btVector3(-10,0,i*0.25),btVector3(10,0,i*0.25), 16,1+2);
|
||||||
|
btSoftBody* psb = btSoftBodyHelpers::CreateFromConvexHull(sbi,c,8);
|
||||||
|
|
||||||
|
m_object = psb;//btSoftBodyHelpers::CreateFromTriMesh(sbi,&pts[0].getX(),triangles,numtriangles);
|
||||||
|
|
||||||
|
psb->m_cfg.collisions = btSoftBody::fCollision::SDF_RS;//btSoftBody::fCollision::CL_SS+ btSoftBody::fCollision::CL_RS;
|
||||||
|
|
||||||
|
sbi.m_sparsesdf.Reset();
|
||||||
|
sbi.m_sparsesdf.Initialize();
|
||||||
|
|
||||||
|
psb->generateBendingConstraints(2);
|
||||||
|
|
||||||
|
psb->m_cfg.kDF=1;
|
||||||
|
psb->activate();
|
||||||
|
psb->setActivationState(1);
|
||||||
|
psb->setDeactivationTime(1.f);
|
||||||
|
psb->m_cfg.piterations = 4;
|
||||||
|
//psb->m_materials[0]->m_kLST = 0.1+(i/(btScalar)(n-1))*0.9;
|
||||||
|
psb->setTotalMass(20);
|
||||||
|
psb->setCollisionFlags(0);
|
||||||
|
|
||||||
m_object->setCollisionShape(rbci.m_collisionShape);
|
m_object->setCollisionShape(rbci.m_collisionShape);
|
||||||
btTransform startTrans;
|
btTransform startTrans;
|
||||||
|
|
||||||
|
@ -149,6 +149,7 @@ struct CcdConstructionInfo
|
|||||||
m_margin(0.06f),
|
m_margin(0.06f),
|
||||||
m_collisionFlags(0),
|
m_collisionFlags(0),
|
||||||
m_bRigid(false),
|
m_bRigid(false),
|
||||||
|
m_bSoft(false),
|
||||||
m_collisionFilterGroup(DefaultFilter),
|
m_collisionFilterGroup(DefaultFilter),
|
||||||
m_collisionFilterMask(AllFilter),
|
m_collisionFilterMask(AllFilter),
|
||||||
m_collisionShape(0),
|
m_collisionShape(0),
|
||||||
@ -170,6 +171,7 @@ struct CcdConstructionInfo
|
|||||||
btScalar m_margin;
|
btScalar m_margin;
|
||||||
int m_collisionFlags;
|
int m_collisionFlags;
|
||||||
bool m_bRigid;
|
bool m_bRigid;
|
||||||
|
bool m_bSoft;
|
||||||
|
|
||||||
///optional use of collision group/mask:
|
///optional use of collision group/mask:
|
||||||
///only collision with object goups that match the collision mask.
|
///only collision with object goups that match the collision mask.
|
||||||
|
@ -24,6 +24,7 @@ subject to the following restrictions:
|
|||||||
#include "LinearMath/btIDebugDraw.h"
|
#include "LinearMath/btIDebugDraw.h"
|
||||||
#include "BulletCollision/CollisionDispatch/btSimulationIslandManager.h"
|
#include "BulletCollision/CollisionDispatch/btSimulationIslandManager.h"
|
||||||
#include "BulletSoftBody/btSoftRigidDynamicsWorld.h"
|
#include "BulletSoftBody/btSoftRigidDynamicsWorld.h"
|
||||||
|
#include "BulletSoftBody/btSoftBodyRigidBodyCollisionConfiguration.h"
|
||||||
|
|
||||||
//profiling/timings
|
//profiling/timings
|
||||||
#include "LinearMath/btQuickprof.h"
|
#include "LinearMath/btQuickprof.h"
|
||||||
@ -331,7 +332,9 @@ m_filterCallback(NULL)
|
|||||||
{
|
{
|
||||||
m_triggerCallbacks[i] = 0;
|
m_triggerCallbacks[i] = 0;
|
||||||
}
|
}
|
||||||
m_collisionConfiguration = new btDefaultCollisionConfiguration();
|
|
||||||
|
// m_collisionConfiguration = new btDefaultCollisionConfiguration();
|
||||||
|
m_collisionConfiguration = new btSoftBodyRigidBodyCollisionConfiguration();
|
||||||
|
|
||||||
if (!dispatcher)
|
if (!dispatcher)
|
||||||
{
|
{
|
||||||
@ -339,6 +342,8 @@ m_filterCallback(NULL)
|
|||||||
m_ownDispatcher = dispatcher;
|
m_ownDispatcher = dispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//m_broadphase = new btAxisSweep3(btVector3(-1000,-1000,-1000),btVector3(1000,1000,1000));
|
||||||
|
//m_broadphase = new btSimpleBroadphase();
|
||||||
m_broadphase = new btDbvtBroadphase();
|
m_broadphase = new btDbvtBroadphase();
|
||||||
|
|
||||||
m_filterCallback = new CcdOverlapFilterCallBack(this);
|
m_filterCallback = new CcdOverlapFilterCallBack(this);
|
||||||
@ -375,9 +380,8 @@ void CcdPhysicsEnvironment::addCcdPhysicsController(CcdPhysicsController* ctrl)
|
|||||||
{
|
{
|
||||||
if (ctrl->GetSoftBody())
|
if (ctrl->GetSoftBody())
|
||||||
{
|
{
|
||||||
//not yet
|
btSoftBody* softBody = ctrl->GetSoftBody();
|
||||||
btAssert(0);
|
m_dynamicsWorld->addSoftBody(softBody);
|
||||||
//m_dynamicsWorld->addSo
|
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
if (obj->getCollisionShape())
|
if (obj->getCollisionShape())
|
||||||
@ -460,8 +464,7 @@ void CcdPhysicsEnvironment::removeCcdPhysicsController(CcdPhysicsController* ctr
|
|||||||
//if a softbody
|
//if a softbody
|
||||||
if (ctrl->GetSoftBody())
|
if (ctrl->GetSoftBody())
|
||||||
{
|
{
|
||||||
//not yet
|
m_dynamicsWorld->removeSoftBody(ctrl->GetSoftBody());
|
||||||
btAssert(0);
|
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
m_dynamicsWorld->removeCollisionObject(ctrl->GetCollisionObject());
|
m_dynamicsWorld->removeCollisionObject(ctrl->GetCollisionObject());
|
||||||
@ -553,6 +556,7 @@ bool CcdPhysicsEnvironment::proceedDeltaTime(double curTime,float timeStep)
|
|||||||
float subStep = timeStep / float(m_numTimeSubSteps);
|
float subStep = timeStep / float(m_numTimeSubSteps);
|
||||||
for (i=0;i<m_numTimeSubSteps;i++)
|
for (i=0;i<m_numTimeSubSteps;i++)
|
||||||
{
|
{
|
||||||
|
// m_dynamicsWorld->stepSimulation(subStep,20,1./240.);//perform always a full simulation step
|
||||||
m_dynamicsWorld->stepSimulation(subStep,0);//perform always a full simulation step
|
m_dynamicsWorld->stepSimulation(subStep,0);//perform always a full simulation step
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -670,7 +674,13 @@ void CcdPhysicsEnvironment::setSolverType(int solverType)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void CcdPhysicsEnvironment::getGravity(PHY__Vector3& grav)
|
||||||
|
{
|
||||||
|
const btVector3& gravity = m_dynamicsWorld->getGravity();
|
||||||
|
grav[0] = gravity.getX();
|
||||||
|
grav[1] = gravity.getY();
|
||||||
|
grav[2] = gravity.getZ();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void CcdPhysicsEnvironment::setGravity(float x,float y,float z)
|
void CcdPhysicsEnvironment::setGravity(float x,float y,float z)
|
||||||
@ -955,6 +965,13 @@ btBroadphaseInterface* CcdPhysicsEnvironment::getBroadphase()
|
|||||||
return m_dynamicsWorld->getBroadphase();
|
return m_dynamicsWorld->getBroadphase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
btDispatcher* CcdPhysicsEnvironment::getDispatcher()
|
||||||
|
{
|
||||||
|
return m_dynamicsWorld->getDispatcher();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -119,6 +119,8 @@ protected:
|
|||||||
virtual void setDebugMode(int debugMode);
|
virtual void setDebugMode(int debugMode);
|
||||||
|
|
||||||
virtual void setGravity(float x,float y,float z);
|
virtual void setGravity(float x,float y,float z);
|
||||||
|
virtual void getGravity(PHY__Vector3& grav);
|
||||||
|
|
||||||
|
|
||||||
virtual int createConstraint(class PHY_IPhysicsController* ctrl,class PHY_IPhysicsController* ctrl2,PHY_ConstraintType type,
|
virtual int createConstraint(class PHY_IPhysicsController* ctrl,class PHY_IPhysicsController* ctrl2,PHY_ConstraintType type,
|
||||||
float pivotX,float pivotY,float pivotZ,
|
float pivotX,float pivotY,float pivotZ,
|
||||||
@ -195,8 +197,7 @@ protected:
|
|||||||
|
|
||||||
btBroadphaseInterface* getBroadphase();
|
btBroadphaseInterface* getBroadphase();
|
||||||
|
|
||||||
|
btDispatcher* getDispatcher();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool IsSatCollisionDetectionEnabled() const
|
bool IsSatCollisionDetectionEnabled() const
|
||||||
|
Loading…
Reference in New Issue
Block a user