mirror of https://github.com/axmolengine/axmol.git
Merge pull request #4486 from boyu0/iss2771_physical
[ci skip]issue #2771: physical
This commit is contained in:
commit
53092b359b
|
@ -739,7 +739,7 @@ bool PhysicsBody::isResting() const
|
|||
void PhysicsBody::update(float delta)
|
||||
{
|
||||
// damping compute
|
||||
if (_dynamic)
|
||||
if (_dynamic && !isResting())
|
||||
{
|
||||
_info->getBody()->v.x *= cpfclamp(1.0f - delta * _linearDamping, 0.0f, 1.0f);
|
||||
_info->getBody()->v.y *= cpfclamp(1.0f - delta * _linearDamping, 0.0f, 1.0f);
|
||||
|
|
|
@ -54,13 +54,19 @@ public:
|
|||
inline int getTag() const { return _tag; }
|
||||
inline void setTag(int tag) { _tag = tag; }
|
||||
inline bool isEnabled() const { return _enable; }
|
||||
/** Enable/Disable the joint */
|
||||
void setEnable(bool enable);
|
||||
inline bool isCollisionEnabled() const { return _collisionEnable; }
|
||||
/** Enable/disable the collision between two bodies */
|
||||
void setCollisionEnable(bool enable);
|
||||
/** Remove the joint from the world */
|
||||
void removeFormWorld();
|
||||
/** Distory the joint*/
|
||||
static void destroy(PhysicsJoint* joint);
|
||||
|
||||
/** Set the max force between two bodies */
|
||||
void setMaxForce(float force);
|
||||
/** Get the max force setting */
|
||||
float getMaxForce() const;
|
||||
|
||||
protected:
|
||||
|
@ -145,6 +151,7 @@ protected:
|
|||
virtual ~PhysicsJointPin() {}
|
||||
};
|
||||
|
||||
/** Set the fixed distance with two bodies */
|
||||
class PhysicsJointDistance : public PhysicsJoint
|
||||
{
|
||||
public:
|
||||
|
@ -161,6 +168,7 @@ protected:
|
|||
virtual ~PhysicsJointDistance() {}
|
||||
};
|
||||
|
||||
/** Connecting two physics bodies together with a spring. */
|
||||
class PhysicsJointSpring : public PhysicsJoint
|
||||
{
|
||||
public:
|
||||
|
@ -184,6 +192,7 @@ protected:
|
|||
virtual ~PhysicsJointSpring() {}
|
||||
};
|
||||
|
||||
/** Attach body a to a line, and attach body b to a dot */
|
||||
class PhysicsJointGroove : public PhysicsJoint
|
||||
{
|
||||
public:
|
||||
|
@ -204,6 +213,7 @@ protected:
|
|||
virtual ~PhysicsJointGroove() {}
|
||||
};
|
||||
|
||||
/** Likes a spring joint, but works with rotary */
|
||||
class PhysicsJointRotarySpring : public PhysicsJoint
|
||||
{
|
||||
public:
|
||||
|
@ -224,6 +234,7 @@ protected:
|
|||
virtual ~PhysicsJointRotarySpring() {}
|
||||
};
|
||||
|
||||
/** Likes a limit joint, but works with rotary */
|
||||
class PhysicsJointRotaryLimit : public PhysicsJoint
|
||||
{
|
||||
public:
|
||||
|
@ -243,6 +254,7 @@ protected:
|
|||
virtual ~PhysicsJointRotaryLimit() {}
|
||||
};
|
||||
|
||||
/** Works like a socket wrench. */
|
||||
class PhysicsJointRatchet : public PhysicsJoint
|
||||
{
|
||||
public:
|
||||
|
@ -263,6 +275,7 @@ protected:
|
|||
virtual ~PhysicsJointRatchet() {}
|
||||
};
|
||||
|
||||
/** Keeps the angular velocity ratio of a pair of bodies constant. */
|
||||
class PhysicsJointGear : public PhysicsJoint
|
||||
{
|
||||
public:
|
||||
|
@ -281,6 +294,7 @@ protected:
|
|||
virtual ~PhysicsJointGear() {}
|
||||
};
|
||||
|
||||
/** Keeps the relative angular velocity of a pair of bodies constant */
|
||||
class PhysicsJointMotor : public PhysicsJoint
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -306,7 +306,7 @@ bool PhysicsShapeCircle::init(float radius, const PhysicsMaterial& material/* =
|
|||
|
||||
_info->add(shape);
|
||||
|
||||
_area = calculateDefaultArea();
|
||||
_area = calculateArea();
|
||||
_mass = material.density == PHYSICS_INFINITY ? PHYSICS_INFINITY : material.density * _area;
|
||||
_moment = calculateDefaultMoment();
|
||||
|
||||
|
@ -331,7 +331,7 @@ float PhysicsShapeCircle::calculateMoment(float mass, float radius, const Point&
|
|||
PhysicsHelper::point2cpv(offset)));
|
||||
}
|
||||
|
||||
float PhysicsShapeCircle::calculateDefaultArea()
|
||||
float PhysicsShapeCircle::calculateArea()
|
||||
{
|
||||
return PhysicsHelper::cpfloat2float(cpAreaForCircle(0, cpCircleShapeGetRadius(_info->getShapes().front())));
|
||||
}
|
||||
|
@ -447,7 +447,7 @@ bool PhysicsShapeBox::init(const Size& size, const PhysicsMaterial& material/* =
|
|||
_info->add(shape);
|
||||
|
||||
_offset = offset;
|
||||
_area = calculateDefaultArea();
|
||||
_area = calculateArea();
|
||||
_mass = material.density == PHYSICS_INFINITY ? PHYSICS_INFINITY : material.density * _area;
|
||||
_moment = calculateDefaultMoment();
|
||||
|
||||
|
@ -484,7 +484,7 @@ float PhysicsShapeBox::calculateMoment(float mass, const Size& size, const Point
|
|||
PhysicsHelper::point2cpv(offset)));
|
||||
}
|
||||
|
||||
float PhysicsShapeBox::calculateDefaultArea()
|
||||
float PhysicsShapeBox::calculateArea()
|
||||
{
|
||||
cpShape* shape = _info->getShapes().front();
|
||||
return PhysicsHelper::cpfloat2float(cpAreaForPoly(((cpPolyShape*)shape)->numVerts, ((cpPolyShape*)shape)->verts));
|
||||
|
@ -539,7 +539,7 @@ bool PhysicsShapePolygon::init(const Point* points, int count, const PhysicsMate
|
|||
|
||||
_info->add(shape);
|
||||
|
||||
_area = calculateDefaultArea();
|
||||
_area = calculateArea();
|
||||
_mass = material.density == PHYSICS_INFINITY ? PHYSICS_INFINITY : material.density * _area;
|
||||
_moment = calculateDefaultMoment();
|
||||
_center = PhysicsHelper::cpv2point(cpCentroidForPoly(((cpPolyShape*)shape)->numVerts, ((cpPolyShape*)shape)->verts));
|
||||
|
@ -573,7 +573,7 @@ float PhysicsShapePolygon::calculateMoment(float mass, const Point* points, int
|
|||
return moment;
|
||||
}
|
||||
|
||||
float PhysicsShapePolygon::calculateDefaultArea()
|
||||
float PhysicsShapePolygon::calculateArea()
|
||||
{
|
||||
cpShape* shape = _info->getShapes().front();
|
||||
return PhysicsHelper::cpfloat2float(cpAreaForPoly(((cpPolyShape*)shape)->numVerts, ((cpPolyShape*)shape)->verts));
|
||||
|
|
|
@ -40,9 +40,9 @@ class PhysicsBodyInfo;
|
|||
|
||||
typedef struct PhysicsMaterial
|
||||
{
|
||||
float density;
|
||||
float restitution;
|
||||
float friction;
|
||||
float density; ///< The density of the object.
|
||||
float restitution; ///< The bounciness of the physics body.
|
||||
float friction; ///< The roughness of the surface of a shape.
|
||||
|
||||
PhysicsMaterial()
|
||||
: density(0.0f)
|
||||
|
@ -78,35 +78,65 @@ public:
|
|||
};
|
||||
|
||||
public:
|
||||
/** Get the body that this shape attaches */
|
||||
inline PhysicsBody* getBody() const { return _body; }
|
||||
/** Return the type of this shape */
|
||||
inline Type getType() const { return _type; }
|
||||
/** return the area of this shape */
|
||||
inline float getArea() const { return _area; }
|
||||
/** get moment */
|
||||
inline float getMoment() const { return _moment; }
|
||||
/** Set moment, it will change the body's moment this shape attaches */
|
||||
void setMoment(float moment);
|
||||
inline void setTag(int tag) { _tag = tag; }
|
||||
inline int getTag() const { return _tag; }
|
||||
|
||||
/** get mass */
|
||||
inline float getMass() const { return _mass; }
|
||||
/** Set mass, it will change the body's mass this shape attaches */
|
||||
void setMass(float mass);
|
||||
inline float getDensity() const { return _material.density; }
|
||||
void setDensity(float density);
|
||||
inline float getRestitution() const { return _material.restitution; }
|
||||
void setRestitution(float restitution);
|
||||
inline float getFriction() const { return _material.friction; }
|
||||
void setFriction(float friction);
|
||||
const PhysicsMaterial& getMaterial() const { return _material; }
|
||||
void setMaterial(const PhysicsMaterial& material);
|
||||
|
||||
virtual float calculateDefaultMoment() { return 0; }
|
||||
virtual float calculateDefaultArea() { return 0; }
|
||||
/** Calculate the default moment value */
|
||||
virtual float calculateDefaultMoment() { return 0.0f; }
|
||||
/** Get offset */
|
||||
virtual Point getOffset() { return Point::ZERO; }
|
||||
/** Get center of this shape */
|
||||
virtual Point getCenter() { return getOffset(); }
|
||||
/** Test point is in shape or not */
|
||||
bool containsPoint(const Point& point) const;
|
||||
|
||||
/** move the points to the center */
|
||||
static Point* recenterPoints(Point* points, int count, const Point& center = Point::ZERO);
|
||||
/** get center of the polyon points */
|
||||
static Point getPolyonCenter(const Point* points, int count);
|
||||
|
||||
/**
|
||||
* A mask that defines which categories this physics body belongs to.
|
||||
* Every physics body in a scene can be assigned to up to 32 different categories, each corresponding to a bit in the bit mask. You define the mask values used in your game. In conjunction with the collisionBitMask and contactTestBitMask properties, you define which physics bodies interact with each other and when your game is notified of these interactions.
|
||||
* The default value is 0xFFFFFFFF (all bits set).
|
||||
*/
|
||||
inline void setCategoryBitmask(int bitmask) { _categoryBitmask = bitmask; }
|
||||
inline int getCategoryBitmask() const { return _categoryBitmask; }
|
||||
/**
|
||||
* A mask that defines which categories of bodies cause intersection notifications with this physics body.
|
||||
* When two bodies share the same space, each body’s category mask is tested against the other body’s contact mask by performing a logical AND operation. If either comparison results in a non-zero value, an PhysicsContact object is created and passed to the physics world’s delegate. For best performance, only set bits in the contacts mask for interactions you are interested in.
|
||||
* The default value is 0x00000000 (all bits cleared).
|
||||
*/
|
||||
inline void setContactTestBitmask(int bitmask) { _contactTestBitmask = bitmask; }
|
||||
inline int getContactTestBitmask() const { return _contactTestBitmask; }
|
||||
/**
|
||||
* A mask that defines which categories of physics bodies can collide with this physics body.
|
||||
* When two physics bodies contact each other, a collision may occur. This body’s collision mask is compared to the other body’s category mask by performing a logical AND operation. If the result is a non-zero value, then this body is affected by the collision. Each body independently chooses whether it wants to be affected by the other body. For example, you might use this to avoid collision calculations that would make negligible changes to a body’s velocity.
|
||||
* The default value is 0xFFFFFFFF (all bits set).
|
||||
*/
|
||||
inline void setCollisionBitmask(int bitmask) { _collisionBitmask = bitmask; }
|
||||
inline int getCollisionBitmask() const { return _collisionBitmask; }
|
||||
|
||||
|
@ -123,6 +153,9 @@ protected:
|
|||
|
||||
void setBody(PhysicsBody* body);
|
||||
|
||||
/** calculate the area of this shape */
|
||||
virtual float calculateArea() { return 0.0f; }
|
||||
|
||||
protected:
|
||||
PhysicsShape();
|
||||
virtual ~PhysicsShape() = 0;
|
||||
|
@ -155,13 +188,13 @@ public:
|
|||
static float calculateArea(float radius);
|
||||
static float calculateMoment(float mass, float radius, const Point& offset = Point::ZERO);
|
||||
|
||||
virtual float calculateDefaultArea() override;
|
||||
virtual float calculateDefaultMoment() override;
|
||||
|
||||
float getRadius() const;
|
||||
virtual Point getOffset() override;
|
||||
protected:
|
||||
bool init(float radius, const PhysicsMaterial& material = PHYSICSSHAPE_MATERIAL_DEFAULT, const Point& offset = Point::ZERO);
|
||||
virtual float calculateArea() override;
|
||||
|
||||
protected:
|
||||
PhysicsShapeCircle();
|
||||
|
@ -176,7 +209,6 @@ public:
|
|||
static float calculateArea(const Size& size);
|
||||
static float calculateMoment(float mass, const Size& size, const Point& offset = Point::ZERO);
|
||||
|
||||
virtual float calculateDefaultArea() override;
|
||||
virtual float calculateDefaultMoment() override;
|
||||
|
||||
void getPoints(Point* outPoints) const;
|
||||
|
@ -185,6 +217,7 @@ public:
|
|||
|
||||
protected:
|
||||
bool init(const Size& size, const PhysicsMaterial& material = PHYSICSSHAPE_MATERIAL_DEFAULT, const Point& offset = Point::ZERO);
|
||||
virtual float calculateArea() override;
|
||||
|
||||
protected:
|
||||
PhysicsShapeBox();
|
||||
|
@ -202,7 +235,6 @@ public:
|
|||
static float calculateArea(const Point* points, int count);
|
||||
static float calculateMoment(float mass, const Point* points, int count, const Point& offset = Point::ZERO);
|
||||
|
||||
float calculateDefaultArea() override;
|
||||
float calculateDefaultMoment() override;
|
||||
|
||||
Point getPoint(int i) const;
|
||||
|
@ -211,6 +243,7 @@ public:
|
|||
virtual Point getCenter() override;
|
||||
protected:
|
||||
bool init(const Point* points, int count, const PhysicsMaterial& material = PHYSICSSHAPE_MATERIAL_DEFAULT, const Point& offset = Point::ZERO);
|
||||
float calculateArea() override;
|
||||
|
||||
protected:
|
||||
PhysicsShapePolygon();
|
||||
|
|
|
@ -75,14 +75,14 @@ namespace
|
|||
typedef struct RectQueryCallbackInfo
|
||||
{
|
||||
PhysicsWorld* world;
|
||||
PhysicsRectQueryCallbackFunc func;
|
||||
PhysicsQueryRectCallbackFunc func;
|
||||
void* data;
|
||||
}RectQueryCallbackInfo;
|
||||
|
||||
typedef struct PointQueryCallbackInfo
|
||||
{
|
||||
PhysicsWorld* world;
|
||||
PhysicsPointQueryCallbackFunc func;
|
||||
PhysicsQueryPointCallbackFunc func;
|
||||
void* data;
|
||||
}PointQueryCallbackInfo;
|
||||
}
|
||||
|
@ -355,7 +355,7 @@ void PhysicsWorld::rayCast(PhysicsRayCastCallbackFunc func, const Point& point1,
|
|||
}
|
||||
|
||||
|
||||
void PhysicsWorld::queryRect(PhysicsRectQueryCallbackFunc func, const Rect& rect, void* data)
|
||||
void PhysicsWorld::queryRect(PhysicsQueryRectCallbackFunc func, const Rect& rect, void* data)
|
||||
{
|
||||
CCASSERT(func != nullptr, "func shouldn't be nullptr");
|
||||
|
||||
|
@ -373,7 +373,7 @@ void PhysicsWorld::queryRect(PhysicsRectQueryCallbackFunc func, const Rect& rect
|
|||
}
|
||||
}
|
||||
|
||||
void PhysicsWorld::queryPoint(PhysicsPointQueryCallbackFunc func, const Point& point, void* data)
|
||||
void PhysicsWorld::queryPoint(PhysicsQueryPointCallbackFunc func, const Point& point, void* data)
|
||||
{
|
||||
CCASSERT(func != nullptr, "func shouldn't be nullptr");
|
||||
|
||||
|
@ -1022,7 +1022,13 @@ void PhysicsWorld::update(float delta)
|
|||
body->update(delta);
|
||||
}
|
||||
|
||||
_info->step(delta);
|
||||
_updateTime += delta;
|
||||
if (++_updateRateCount >= _updateRate)
|
||||
{
|
||||
_info->step(_updateTime * _speed);
|
||||
_updateRateCount = 0;
|
||||
_updateTime = 0.0f;
|
||||
}
|
||||
|
||||
if (_debugDrawMask != DEBUGDRAW_NONE)
|
||||
{
|
||||
|
@ -1033,6 +1039,9 @@ void PhysicsWorld::update(float delta)
|
|||
PhysicsWorld::PhysicsWorld()
|
||||
: _gravity(Point(0.0f, -98.0f))
|
||||
, _speed(1.0f)
|
||||
, _updateRate(1)
|
||||
, _updateRateCount(0)
|
||||
, _updateTime(0.0f)
|
||||
, _info(nullptr)
|
||||
, _scene(nullptr)
|
||||
, _delayDirty(false)
|
||||
|
|
|
@ -73,8 +73,8 @@ typedef struct PhysicsRayCastInfo
|
|||
* @return true to continue, false to terminate
|
||||
*/
|
||||
typedef std::function<bool(PhysicsWorld& world, const PhysicsRayCastInfo& info, void* data)> PhysicsRayCastCallbackFunc;
|
||||
typedef std::function<bool(PhysicsWorld&, PhysicsShape&, void*)> PhysicsRectQueryCallbackFunc;
|
||||
typedef PhysicsRectQueryCallbackFunc PhysicsPointQueryCallbackFunc;
|
||||
typedef std::function<bool(PhysicsWorld&, PhysicsShape&, void*)> PhysicsQueryRectCallbackFunc;
|
||||
typedef PhysicsQueryRectCallbackFunc PhysicsQueryPointCallbackFunc;
|
||||
|
||||
/**
|
||||
* @brief An PhysicsWorld object simulates collisions and other physical properties. You do not create PhysicsWorld objects directly; instead, you can get it from an Scene object.
|
||||
|
@ -82,45 +82,64 @@ typedef PhysicsRectQueryCallbackFunc PhysicsPointQueryCallbackFunc;
|
|||
class PhysicsWorld
|
||||
{
|
||||
public:
|
||||
static const int DEBUGDRAW_NONE;
|
||||
static const int DEBUGDRAW_SHAPE;
|
||||
static const int DEBUGDRAW_JOINT;
|
||||
static const int DEBUGDRAW_CONTACT;
|
||||
static const int DEBUGDRAW_ALL;
|
||||
static const int DEBUGDRAW_NONE; ///< draw nothing
|
||||
static const int DEBUGDRAW_SHAPE; ///< draw shapes
|
||||
static const int DEBUGDRAW_JOINT; ///< draw joints
|
||||
static const int DEBUGDRAW_CONTACT; ///< draw contact
|
||||
static const int DEBUGDRAW_ALL; ///< draw all
|
||||
|
||||
public:
|
||||
/** Adds a joint to the physics world.*/
|
||||
virtual void addJoint(PhysicsJoint* joint);
|
||||
/** Removes a joint from the physics world.*/
|
||||
/** Remove a joint from physics world.*/
|
||||
virtual void removeJoint(PhysicsJoint* joint, bool destroy);
|
||||
/** Remove all joints from the physics world.*/
|
||||
/** Remove all joints from physics world.*/
|
||||
virtual void removeAllJoints(bool destroy);
|
||||
|
||||
/** Remove a body from physics world. */
|
||||
virtual void removeBody(PhysicsBody* body);
|
||||
/** Remove body by tag. */
|
||||
virtual void removeBody(int tag);
|
||||
/** Remove all bodies from physics world. */
|
||||
virtual void removeAllBodies();
|
||||
|
||||
void rayCast(PhysicsRayCastCallbackFunc func, const Point& point1, const Point& point2, void* data);
|
||||
void queryRect(PhysicsRectQueryCallbackFunc func, const Rect& rect, void* data);
|
||||
void queryPoint(PhysicsPointQueryCallbackFunc func, const Point& point, void* data);
|
||||
/** Searches for physics shapes that intersects the ray. */
|
||||
void rayCast(PhysicsRayCastCallbackFunc func, const Point& start, const Point& end, void* data);
|
||||
/** Searches for physics shapes that contains in the rect. */
|
||||
void queryRect(PhysicsQueryRectCallbackFunc func, const Rect& rect, void* data);
|
||||
/** Searches for physics shapes that contains the point. */
|
||||
void queryPoint(PhysicsQueryPointCallbackFunc func, const Point& point, void* data);
|
||||
/** Get phsyics shapes that contains the point. */
|
||||
Vector<PhysicsShape*> getShapes(const Point& point) const;
|
||||
/** return physics shape that contains the point. */
|
||||
PhysicsShape* getShape(const Point& point) const;
|
||||
/** Get all the bodys that in the physics world. */
|
||||
const Vector<PhysicsBody*>& getAllBodies() const;
|
||||
/** Get body by tag */
|
||||
PhysicsBody* getBody(int tag) const;
|
||||
|
||||
/** Register a listener to receive contact callbacks*/
|
||||
//inline void registerContactListener(EventListenerPhysicsContact* delegate) { _listener = delegate; }
|
||||
/** Unregister a listener. */
|
||||
//inline void unregisterContactListener() { _listener = nullptr; }
|
||||
|
||||
/** Get scene contain this physics world */
|
||||
inline Scene& getScene() const { return *_scene; }
|
||||
/** get the gravity value */
|
||||
inline Vect getGravity() const { return _gravity; }
|
||||
/** set the gravity value */
|
||||
void setGravity(const Vect& gravity);
|
||||
/** Set the speed of physics world, speed is the rate at which the simulation executes. default value is 1.0 */
|
||||
inline void setSpeed(float speed) { if(speed >= 0.0f) { _speed = speed; } }
|
||||
/** get the speed of physics world */
|
||||
inline float getSpeed() { return _speed; }
|
||||
/**
|
||||
* set the update rate of physics world, update rate is the value of EngineUpdateTimes/PhysicsWorldUpdateTimes.
|
||||
* set it higher can improve performance, set it lower can improve accuracy of physics world simulation.
|
||||
* default value is 1.0
|
||||
*/
|
||||
inline void setUpdateRate(int rate) { if(rate > 0) { _updateRate = rate; } }
|
||||
/** get the update rate */
|
||||
inline int getUpdateRate() { return _updateRate; }
|
||||
|
||||
/** set the debug draw */
|
||||
/** set the debug draw mask */
|
||||
void setDebugDrawMask(int mask);
|
||||
/** get the bebug draw mask */
|
||||
inline int getDebugDrawMask() { return _debugDrawMask; }
|
||||
|
||||
protected:
|
||||
|
@ -153,6 +172,9 @@ protected:
|
|||
protected:
|
||||
Vect _gravity;
|
||||
float _speed;
|
||||
int _updateRate;
|
||||
int _updateRateCount;
|
||||
float _updateTime;
|
||||
PhysicsWorldInfo* _info;
|
||||
|
||||
Vector<PhysicsBody*> _bodies;
|
||||
|
|
|
@ -418,6 +418,7 @@ void PhysicsDemoLogoSmash::onEnter()
|
|||
PhysicsDemo::onEnter();
|
||||
|
||||
_scene->getPhysicsWorld()->setGravity(Point(0, 0));
|
||||
_scene->getPhysicsWorld()->setUpdateRate(5.0f);
|
||||
|
||||
_ball = SpriteBatchNode::create("Images/ball.png", sizeof(logo_image)/sizeof(logo_image[0]));
|
||||
addChild(_ball);
|
||||
|
@ -445,9 +446,9 @@ void PhysicsDemoLogoSmash::onEnter()
|
|||
|
||||
|
||||
auto bullet = makeBall(Point(400, 0), 10, PhysicsMaterial(PHYSICS_INFINITY, 0, 0));
|
||||
bullet->getPhysicsBody()->setVelocity(Point(400, 0));
|
||||
bullet->getPhysicsBody()->setVelocity(Point(200, 0));
|
||||
|
||||
bullet->setPosition(Point(-1000, VisibleRect::getVisibleRect().size.height/2));
|
||||
bullet->setPosition(Point(-500, VisibleRect::getVisibleRect().size.height/2));
|
||||
|
||||
_ball->addChild(bullet);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue