From a34fd5b6fd31dac569dd2c629b9f9ee8b3d4146a Mon Sep 17 00:00:00 2001 From: boyu0 Date: Mon, 21 Oct 2013 23:16:21 +0800 Subject: [PATCH 1/5] issue #2771: implements rayCast and rectQuery --- cocos/physics/CCPhysicsBody.cpp | 8 +- cocos/physics/CCPhysicsBody.h | 2 +- cocos/physics/CCPhysicsContact.h | 13 ++- cocos/physics/CCPhysicsWorld.cpp | 136 +++++++++++++++++++---- cocos/physics/CCPhysicsWorld.h | 42 ++++--- cocos/physics/chipmunk/CCPhysicsHelper.h | 2 + 6 files changed, 155 insertions(+), 48 deletions(-) diff --git a/cocos/physics/CCPhysicsBody.cpp b/cocos/physics/CCPhysicsBody.cpp index 8f1e9e5c1d2b..22a0010cbefa 100644 --- a/cocos/physics/CCPhysicsBody.cpp +++ b/cocos/physics/CCPhysicsBody.cpp @@ -77,6 +77,9 @@ PhysicsBody::PhysicsBody() , _linearDamping(0.0f) , _angularDamping(0.0f) , _tag(0) +, _categoryBitmask(UINT_MAX) +, _collisionBitmask(UINT_MAX) +, _contactTestBitmask(0) { } @@ -109,11 +112,6 @@ PhysicsBody* PhysicsBody::create() return nullptr; } -void update(float delta) -{ - -} - PhysicsBody* PhysicsBody::createCircle(float radius, PhysicsMaterial material) { PhysicsBody* body = new PhysicsBody(); diff --git a/cocos/physics/CCPhysicsBody.h b/cocos/physics/CCPhysicsBody.h index 72cad042bc3a..65d9f7318aa0 100644 --- a/cocos/physics/CCPhysicsBody.h +++ b/cocos/physics/CCPhysicsBody.h @@ -276,8 +276,8 @@ class PhysicsBody : public Object//, public Clonable int _tag; int _categoryBitmask; - int _contactTestBitmask; int _collisionBitmask; + int _contactTestBitmask; friend class PhysicsWorld; friend class PhysicsShape; diff --git a/cocos/physics/CCPhysicsContact.h b/cocos/physics/CCPhysicsContact.h index 3f2ef8659c02..9b9e3044c0a7 100644 --- a/cocos/physics/CCPhysicsContact.h +++ b/cocos/physics/CCPhysicsContact.h @@ -80,6 +80,7 @@ class PhysicsContact bool _notify; friend class PhysicsWorld; + friend class PhysicsWorldCallback; }; /* @@ -94,7 +95,7 @@ class PhysicsContactPreSolve static PhysicsContactPreSolve* create(); bool init(); - friend class PhysicsWorld; + friend class PhysicsWorldCallback; }; /* @@ -109,7 +110,7 @@ class PhysicsContactPostSolve static PhysicsContactPostSolve* create(); bool init(); - friend class PhysicsWorld; + friend class PhysicsWorldCallback; }; /* @@ -125,20 +126,20 @@ class PhysicsContactListener /* * @brief it will called at two shapes start to contact, and only call it once. */ - std::function onContactBegin; + std::function onContactBegin; /* * @brief Two shapes are touching during this step. Return false from the callback to make world ignore the collision this step or true to process it normally. Additionally, you may override collision values, elasticity, or surface velocity values. */ - std::function onContactPreSolve; + std::function onContactPreSolve; /* * @brief Two shapes are touching and their collision response has been processed. You can retrieve the collision impulse or kinetic energy at this time if you want to use it to calculate sound volumes or damage amounts. See cpArbiter for more info */ - std::function onContactPostSolve; + std::function onContactPostSolve; /* * @brief it will called at two shapes separated, and only call it once. * onContactBegin and onContactEnd will called in pairs. */ - std::function onContactEnd; + std::function onContactEnd; }; NS_CC_END diff --git a/cocos/physics/CCPhysicsWorld.cpp b/cocos/physics/CCPhysicsWorld.cpp index 30939dc9fde8..6ca256bc6737 100644 --- a/cocos/physics/CCPhysicsWorld.cpp +++ b/cocos/physics/CCPhysicsWorld.cpp @@ -60,12 +60,45 @@ NS_CC_BEGIN #if (CC_PHYSICS_ENGINE == CC_PHYSICS_CHIPMUNK) -const float PHYSICS_INFINITY = INFINITY; +namespace +{ + typedef struct RayCastCallbackInfo + { + PhysicsWorld* world; + PhysicsRayCastCallback* callback; + Point p1; + Point p2; + void* data; + }RayCastCallbackInfo; + + typedef struct RectQueryCallbackInfo + { + PhysicsWorld* world; + PhysicsRectQueryCallback* callback; + void* data; + }RectQueryCallbackInfo; +} -int PhysicsWorld::collisionBeginCallbackFunc(cpArbiter *arb, struct cpSpace *space, void *data) +class PhysicsWorldCallback { - PhysicsWorld* world = static_cast(data); +public: + static int collisionBeginCallbackFunc(cpArbiter *arb, struct cpSpace *space, PhysicsWorld *world); + static int collisionPreSolveCallbackFunc(cpArbiter *arb, cpSpace *space, PhysicsWorld *world); + static void collisionPostSolveCallbackFunc(cpArbiter *arb, cpSpace *space, PhysicsWorld *world); + static void collisionSeparateCallbackFunc(cpArbiter *arb, cpSpace *space, PhysicsWorld *world); + static void rayCastCallbackFunc(cpShape *shape, cpFloat t, cpVect n, RayCastCallbackInfo *info); + static void rectQueryCallbackFunc(cpShape *shape, RectQueryCallbackInfo *info); +private: + static bool continues; +}; + +bool PhysicsWorldCallback::continues = true; + +const float PHYSICS_INFINITY = INFINITY; + +int PhysicsWorldCallback::collisionBeginCallbackFunc(cpArbiter *arb, struct cpSpace *space, PhysicsWorld *world) +{ CP_ARBITER_GET_SHAPES(arb, a, b); auto ita = PhysicsShapeInfo::map.find(a); @@ -78,23 +111,20 @@ int PhysicsWorld::collisionBeginCallbackFunc(cpArbiter *arb, struct cpSpace *spa return world->collisionBeginCallback(*static_cast(arb->data)); } -int PhysicsWorld::collisionPreSolveCallbackFunc(cpArbiter *arb, cpSpace *space, void *data) +int PhysicsWorldCallback::collisionPreSolveCallbackFunc(cpArbiter *arb, cpSpace *space, PhysicsWorld *world) { - PhysicsWorld* world = static_cast(data); return world->collisionPreSolveCallback(*static_cast(arb->data), PhysicsContactPreSolve()); } -void PhysicsWorld::collisionPostSolveCallbackFunc(cpArbiter *arb, cpSpace *space, void *data) +void PhysicsWorldCallback::collisionPostSolveCallbackFunc(cpArbiter *arb, cpSpace *space, PhysicsWorld *world) { - PhysicsWorld* world = static_cast(data); world->collisionPostSolveCallback(*static_cast(arb->data), PhysicsContactPostSolve()); } -void PhysicsWorld::collisionSeparateCallbackFunc(cpArbiter *arb, cpSpace *space, void *data) +void PhysicsWorldCallback::collisionSeparateCallbackFunc(cpArbiter *arb, cpSpace *space, PhysicsWorld *world) { - PhysicsWorld* world = static_cast(data); PhysicsContact* contact = static_cast(arb->data); world->collisionSeparateCallback(*contact); @@ -102,6 +132,40 @@ void PhysicsWorld::collisionSeparateCallbackFunc(cpArbiter *arb, cpSpace *space, delete contact; } +void PhysicsWorldCallback::rayCastCallbackFunc(cpShape *shape, cpFloat t, cpVect n, RayCastCallbackInfo *info) +{ + if (!PhysicsWorldCallback::continues) + { + return; + } + + auto it = PhysicsShapeInfo::map.find(shape); + CC_ASSERT(it != PhysicsShapeInfo::map.end()); + + PhysicsWorldCallback::continues = info->callback->report(*info->world, + *it->second->shape, + Point(info->p1.x+(info->p2.x-info->p1.x)*t, info->p1.y+(info->p2.y-info->p1.y)*t), + Point(n.x, n.y), + (float)t, + info->data); +} + +void PhysicsWorldCallback::rectQueryCallbackFunc(cpShape *shape, RectQueryCallbackInfo *info) +{ + auto it = PhysicsShapeInfo::map.find(shape); + + CC_ASSERT(it != PhysicsShapeInfo::map.end()); + + if (!PhysicsWorldCallback::continues) + { + return; + } + + PhysicsWorldCallback::continues = info->callback->report(*info->world, + *it->second->shape, + info->data); +} + bool PhysicsWorld::init() { _info = new PhysicsWorldInfo(); @@ -109,10 +173,10 @@ bool PhysicsWorld::init() cpSpaceSetGravity(_info->space, PhysicsHelper::point2cpv(_gravity)); cpSpaceSetDefaultCollisionHandler(_info->space, - PhysicsWorld::collisionBeginCallbackFunc, - PhysicsWorld::collisionPreSolveCallbackFunc, - PhysicsWorld::collisionPostSolveCallbackFunc, - PhysicsWorld::collisionSeparateCallbackFunc, + (cpCollisionBeginFunc)PhysicsWorldCallback::collisionBeginCallbackFunc, + (cpCollisionPreSolveFunc)PhysicsWorldCallback::collisionPreSolveCallbackFunc, + (cpCollisionPostSolveFunc)PhysicsWorldCallback::collisionPostSolveCallbackFunc, + (cpCollisionSeparateFunc)PhysicsWorldCallback::collisionSeparateCallbackFunc, this); return true; @@ -368,12 +432,14 @@ int PhysicsWorld::collisionBeginCallback(PhysicsContact& contact) PhysicsBody* bodyA = contact.getShapeA()->getBody(); PhysicsBody* bodyB = contact.getShapeB()->getBody(); - if ((bodyA->getCategoryBitmask() & bodyB->getContactTestBitmask()) == 0) + if ((bodyA->getCategoryBitmask() & bodyB->getContactTestBitmask()) == 0 + || (bodyB->getContactTestBitmask() & bodyA->getCategoryBitmask()) == 0) { contact.setNotify(false); } - if ((bodyA->getCategoryBitmask() & bodyB->getCollisionBitmask()) == 0) + if ((bodyA->getCategoryBitmask() & bodyB->getCollisionBitmask()) == 0 + || (bodyB->getCategoryBitmask() & bodyA->getCollisionBitmask()) == 0) { ret = false; } @@ -384,10 +450,10 @@ int PhysicsWorld::collisionBeginCallback(PhysicsContact& contact) // so if the mask test is false, the two bodies won't have collision. if (ret) { - ret = _listener->onContactBegin(contact); + ret = _listener->onContactBegin(*this, contact); }else { - _listener->onContactBegin(contact); + _listener->onContactBegin(*this, contact); } } @@ -403,7 +469,7 @@ int PhysicsWorld::collisionPreSolveCallback(PhysicsContact& contact, const Physi if (_listener && _listener->onContactPreSolve) { - return _listener->onContactPreSolve(contact, solve); + return _listener->onContactPreSolve(*this, contact, solve); } return true; @@ -418,7 +484,7 @@ void PhysicsWorld::collisionPostSolveCallback(PhysicsContact& contact, const Phy if (_listener && _listener->onContactPreSolve) { - _listener->onContactPostSolve(contact, solve); + _listener->onContactPostSolve(*this, contact, solve); } } @@ -431,7 +497,7 @@ void PhysicsWorld::collisionSeparateCallback(PhysicsContact& contact) if (_listener && _listener->onContactEnd) { - _listener->onContactEnd(contact); + _listener->onContactEnd(*this, contact); } } @@ -456,6 +522,36 @@ void PhysicsWorld::setGravity(Point gravity) cpSpaceSetGravity(_info->space, PhysicsHelper::point2cpv(gravity)); } + +void PhysicsWorld::rayCast(PhysicsRayCastCallback& callback, Point point1, Point point2, void* data) +{ + RayCastCallbackInfo info = {this, &callback, point1, point2, data}; + cpSpaceSegmentQuery(this->_info->space, + PhysicsHelper::point2cpv(point1), + PhysicsHelper::point2cpv(point2), + CP_ALL_LAYERS, + CP_NO_GROUP, + (cpSpaceSegmentQueryFunc)PhysicsWorldCallback::rayCastCallbackFunc, + &info); +} + + +void PhysicsWorld::rectQuery(PhysicsRectQueryCallback& callback, Rect rect, void* data) +{ + RectQueryCallbackInfo info = {this, &callback, data}; + cpSpaceBBQuery(this->_info->space, + PhysicsHelper::rect2cpbb(rect), + CP_ALL_LAYERS, + CP_NO_GROUP, + (cpSpaceBBQueryFunc)PhysicsWorldCallback::rectQueryCallbackFunc, + &info); +} + +Array* PhysicsWorld::getAllBody() const +{ + return _bodys; +} + #elif (CC_PHYSICS_ENGINE == CC_PHYSICS_BOX2D) #endif diff --git a/cocos/physics/CCPhysicsWorld.h b/cocos/physics/CCPhysicsWorld.h index 3247ab52217a..fa6a509dfc69 100644 --- a/cocos/physics/CCPhysicsWorld.h +++ b/cocos/physics/CCPhysicsWorld.h @@ -33,12 +33,6 @@ #include "CCObject.h" #include "CCGeometry.h" - -#if (CC_PHYSICS_ENGINE == CC_PHYSICS_CHIPMUNK) -typedef struct cpArbiter cpArbiter; -typedef struct cpSpace cpSpace; -#endif - NS_CC_BEGIN class PhysicsBody; @@ -55,6 +49,29 @@ class Sprite; class Scene; class DrawNode; +class PhysicsWorld; +class PhysicsRayCastCallback +{ +public: + /** + * @brief Called for each fixture found in the query. You control how the ray cast + * proceeds by returning a float: + * return true: continue + * return false: terminate the ray cast + * @param fixture the fixture hit by the ray + * @param point the point of initial intersection + * @param normal the normal vector at the point of intersection + * @return true to continue, false to terminate + */ + std::function report; +}; + +class PhysicsRectQueryCallback +{ +public: + std::function report; +}; + /** * @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. */ @@ -68,9 +85,8 @@ class PhysicsWorld /** Remove all joints from the physics world.*/ void removeAllJoints(); - Array* getBodysAlongRay(Point start, Point end) const; - Array* getBodysAtPoint(Point point) const; - Array* getBodysInRect(Rect rect) const; + void rayCast(PhysicsRayCastCallback& callback, Point point1, Point point2, void* data); + void rectQuery(PhysicsRectQueryCallback& callback, Rect rect, void* data); Array* getAllBody() const; /** Register a listener to receive contact callbacks*/ @@ -111,13 +127,6 @@ class PhysicsWorld virtual void collisionPostSolveCallback(PhysicsContact& contact, const PhysicsContactPostSolve& solve); virtual void collisionSeparateCallback(PhysicsContact& contact); -#if (CC_PHYSICS_ENGINE == CC_PHYSICS_CHIPMUNK) - static int collisionBeginCallbackFunc(cpArbiter *arb, struct cpSpace *space, void *data); - static int collisionPreSolveCallbackFunc(cpArbiter *arb, cpSpace *space, void *data); - static void collisionPostSolveCallbackFunc(cpArbiter *arb, cpSpace *space, void *data); - static void collisionSeparateCallbackFunc(cpArbiter *arb, cpSpace *space, void *data); -#endif - protected: Point _gravity; float _speed; @@ -140,6 +149,7 @@ class PhysicsWorld friend class Scene; friend class PhysicsBody; friend class PhysicsShape; + friend class PhysicsWorldCallback; }; NS_CC_END diff --git a/cocos/physics/chipmunk/CCPhysicsHelper.h b/cocos/physics/chipmunk/CCPhysicsHelper.h index 337bb1a1dfb5..bf6f6d14dbd6 100644 --- a/cocos/physics/chipmunk/CCPhysicsHelper.h +++ b/cocos/physics/chipmunk/CCPhysicsHelper.h @@ -43,6 +43,8 @@ class PhysicsHelper static cpVect size2cpv(const Size& size) { return cpv(size.width, size.height); } static float cpfloat2float(cpFloat f) { return f; } static cpFloat float2cpfloat(float f) { return f; } + static cpBB rect2cpbb(const Rect& rect) { return cpBBNew(rect.origin.x, rect.origin.y, rect.origin.x + rect.size.width, rect.origin.y + rect.size.height); } + static Rect cpbb2rect(const cpBB& bb) { return Rect(bb.l, bb.b, bb.r, bb.t); } static Point* cpvs2points(const cpVect* cpvs, Point* points, int count) { From 82376e30e99a1b8d238a86d02eece3607d2952c3 Mon Sep 17 00:00:00 2001 From: boyu0 Date: Tue, 22 Oct 2013 18:00:24 +0800 Subject: [PATCH 2/5] issue #2771: add ray cast test --- cocos/physics/CCPhysicsShape.h | 2 +- cocos/physics/CCPhysicsWorld.cpp | 58 ++-- cocos/physics/CCPhysicsWorld.h | 14 +- .../Classes/PhysicsTest/PhysicsTest.cpp | 291 +++++++++++++++++- .../TestCpp/Classes/PhysicsTest/PhysicsTest.h | 32 +- 5 files changed, 349 insertions(+), 48 deletions(-) diff --git a/cocos/physics/CCPhysicsShape.h b/cocos/physics/CCPhysicsShape.h index 4191c7e6b54c..d1b5e0db049a 100644 --- a/cocos/physics/CCPhysicsShape.h +++ b/cocos/physics/CCPhysicsShape.h @@ -97,7 +97,7 @@ class PhysicsShape : public Object virtual Point getOffset() { return Point::ZERO; } virtual Point getCenter() { return getOffset(); } - static Point* recenterPoints(Point* points, int count, Point center); + static Point* recenterPoints(Point* points, int count, Point center = Point::ZERO); static Point getPolyonCenter(Point* points, int count); protected: diff --git a/cocos/physics/CCPhysicsWorld.cpp b/cocos/physics/CCPhysicsWorld.cpp index 6ca256bc6737..cab183ba7a83 100644 --- a/cocos/physics/CCPhysicsWorld.cpp +++ b/cocos/physics/CCPhysicsWorld.cpp @@ -89,7 +89,7 @@ class PhysicsWorldCallback static void rayCastCallbackFunc(cpShape *shape, cpFloat t, cpVect n, RayCastCallbackInfo *info); static void rectQueryCallbackFunc(cpShape *shape, RectQueryCallbackInfo *info); -private: +public: static bool continues; }; @@ -391,12 +391,16 @@ void PhysicsWorld::drawWithShape(DrawNode* node, PhysicsShape* shape) Point centre = PhysicsHelper::cpv2point(cpBodyGetPos(cpShapeGetBody(shape))) + PhysicsHelper::cpv2point(cpCircleShapeGetOffset(shape)); - Point seg[4] = {}; - seg[0] = Point(centre.x - radius, centre.y - radius); - seg[1] = Point(centre.x - radius, centre.y + radius); - seg[2] = Point(centre.x + radius, centre.y + radius); - seg[3] = Point(centre.x + radius, centre.y - radius); - node->drawPolygon(seg, 4, Color4F(), 1, Color4F(1, 0, 0, 1)); + static const int CIRCLE_SEG_NUM = 12; + Point seg[CIRCLE_SEG_NUM] = {}; + + for (int i = 0; i < CIRCLE_SEG_NUM; ++i) + { + float angle = (float)i * M_PI / (float)CIRCLE_SEG_NUM * 2.0f; + Point d(radius * cosf(angle), radius * sinf(angle)); + seg[i] = centre + d; + } + node->drawPolygon(seg, CIRCLE_SEG_NUM, Color4F(1.0f, 0.0f, 0.0f, 0.3f), 1, Color4F(1, 0, 0, 1)); break; } case CP_SEGMENT_SHAPE: @@ -525,26 +529,36 @@ void PhysicsWorld::setGravity(Point gravity) void PhysicsWorld::rayCast(PhysicsRayCastCallback& callback, Point point1, Point point2, void* data) { - RayCastCallbackInfo info = {this, &callback, point1, point2, data}; - cpSpaceSegmentQuery(this->_info->space, - PhysicsHelper::point2cpv(point1), - PhysicsHelper::point2cpv(point2), - CP_ALL_LAYERS, - CP_NO_GROUP, - (cpSpaceSegmentQueryFunc)PhysicsWorldCallback::rayCastCallbackFunc, - &info); + if (callback.report != nullptr) + { + RayCastCallbackInfo info = {this, &callback, point1, point2, data}; + + PhysicsWorldCallback::continues = true; + cpSpaceSegmentQuery(this->_info->space, + PhysicsHelper::point2cpv(point1), + PhysicsHelper::point2cpv(point2), + CP_ALL_LAYERS, + CP_NO_GROUP, + (cpSpaceSegmentQueryFunc)PhysicsWorldCallback::rayCastCallbackFunc, + &info); + } } void PhysicsWorld::rectQuery(PhysicsRectQueryCallback& callback, Rect rect, void* data) { - RectQueryCallbackInfo info = {this, &callback, data}; - cpSpaceBBQuery(this->_info->space, - PhysicsHelper::rect2cpbb(rect), - CP_ALL_LAYERS, - CP_NO_GROUP, - (cpSpaceBBQueryFunc)PhysicsWorldCallback::rectQueryCallbackFunc, - &info); + if (callback.report != nullptr) + { + RectQueryCallbackInfo info = {this, &callback, data}; + + PhysicsWorldCallback::continues = true; + cpSpaceBBQuery(this->_info->space, + PhysicsHelper::rect2cpbb(rect), + CP_ALL_LAYERS, + CP_NO_GROUP, + (cpSpaceBBQueryFunc)PhysicsWorldCallback::rectQueryCallbackFunc, + &info); + } } Array* PhysicsWorld::getAllBody() const diff --git a/cocos/physics/CCPhysicsWorld.h b/cocos/physics/CCPhysicsWorld.h index fa6a509dfc69..e61cde276399 100644 --- a/cocos/physics/CCPhysicsWorld.h +++ b/cocos/physics/CCPhysicsWorld.h @@ -53,6 +53,10 @@ class PhysicsWorld; class PhysicsRayCastCallback { public: + PhysicsRayCastCallback() + : report(nullptr) + {} + virtual ~PhysicsRayCastCallback(){} /** * @brief Called for each fixture found in the query. You control how the ray cast * proceeds by returning a float: @@ -63,13 +67,19 @@ class PhysicsRayCastCallback * @param normal the normal vector at the point of intersection * @return true to continue, false to terminate */ - std::function report; + std::function report; }; class PhysicsRectQueryCallback { public: - std::function report; + PhysicsRectQueryCallback() + : report(nullptr) + {} + virtual ~PhysicsRectQueryCallback(){} + +public: + std::function report; }; /** diff --git a/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.cpp b/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.cpp index 91c67da54d6c..0c67ddc20476 100644 --- a/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.cpp +++ b/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.cpp @@ -9,6 +9,7 @@ namespace CL(PhysicsDemoPyramidStack), CL(PhysicsDemoPlink), CL(PhysicsDemoClickAdd), + CL(PhysicsDemoRayCast), }; static int sceneIdx=-1; @@ -48,6 +49,8 @@ namespace return layer; } + + static const Color4F STATIC_COLOR = {1.0f, 0.0f, 0.0f, 1.0f}; } @@ -87,6 +90,8 @@ void PhysicsTestScene::toggleDebug() PhysicsDemo::PhysicsDemo() : _scene(nullptr) +, _ball(nullptr) +, _spriteTexture(nullptr) { } @@ -304,22 +309,62 @@ namespace } } -Node* PhysicsDemoLogoSmash::makeBall(float x, float y) +Sprite* PhysicsDemo::makeBall(float x, float y, float radius, PhysicsMaterial material) { - auto ball = Sprite::createWithTexture(_ball->getTexture()); - ball->setScale(0.1); + Sprite* ball = nullptr; + if (_ball != nullptr) + { + ball = Sprite::createWithTexture(_ball->getTexture()); + }else + { + ball = Sprite::create("Images/ball.png"); + } - auto body = PhysicsBody::createCircle(0.95, PhysicsMaterial(1, 0, 0)); - body->setMass(1.0); - body->setMoment(PHYSICS_INFINITY); + ball->setScale(0.13f * radius); + auto body = PhysicsBody::createCircle(radius, material); ball->setPhysicsBody(body); - ball->setPosition(Point(x, y)); return ball; } +Sprite* PhysicsDemo::makeBox(float x, float y, Size size, PhysicsMaterial material) +{ + auto box = CCRANDOM_0_1() > 0.5f ? Sprite::create("Images/YellowSquare.png") : Sprite::create("Images/CyanSquare.png"); + + box->setScaleX(size.width/100.0f); + box->setScaleY(size.height/100.0f); + + auto body = PhysicsBody::createBox(size); + box->setPhysicsBody(body); + box->setPosition(Point(x, y)); + + return box; +} + +Sprite* PhysicsDemo::makeTriangle(float x, float y, Size size, PhysicsMaterial material) +{ + auto triangle = CCRANDOM_0_1() > 0.5f ? Sprite::create("Images/YellowTriangle.png") : Sprite::create("Images/CyanTriangle.png"); + + if(size.height == 0) + { + triangle->setScale(size.width/100.0f); + }else + { + triangle->setScaleX(size.width/50.0f); + triangle->setScaleY(size.height/43.5f); + } + + Point vers[] = { Point(0, size.height/2), Point(size.width/2, -size.height/2), Point(-size.width/2, -size.height/2)}; + + auto body = PhysicsBody::createPolygon(vers, 3); + triangle->setPhysicsBody(body); + triangle->setPosition(Point(x, y)); + + return triangle; +} + void PhysicsDemoLogoSmash::onEnter() { PhysicsDemo::onEnter(); @@ -337,19 +382,22 @@ void PhysicsDemoLogoSmash::onEnter() float x_jitter = 0.05*frand(); float y_jitter = 0.05*frand(); - _ball->addChild(makeBall(2*(x - logo_width/2 + x_jitter) + VisibleRect::getVisibleRect().size.width/2, - 2*(logo_height-y + y_jitter) + VisibleRect::getVisibleRect().size.height/2 - logo_height/2)); + Node* ball = makeBall(2*(x - logo_width/2 + x_jitter) + VisibleRect::getVisibleRect().size.width/2, + 2*(logo_height-y + y_jitter) + VisibleRect::getVisibleRect().size.height/2 - logo_height/2, + 0.95f, PhysicsMaterial(1.0f, 0.0f, 0.0f)); + + ball->getPhysicsBody()->setMass(1.0); + ball->getPhysicsBody()->setMoment(PHYSICS_INFINITY); + + _ball->addChild(ball); + } } } - auto bullet = Sprite::createWithTexture(_ball->getTexture()); - bullet->setScale(0.5); - - auto body = PhysicsBody::createCircle(8, PhysicsMaterial(PHYSICS_INFINITY, 0, 0)); - body->setVelocity(Point(400, 0)); - bullet->setPhysicsBody(body); + auto bullet = makeBall(400, 0, 10, PhysicsMaterial(PHYSICS_INFINITY, 0, 0)); + bullet->getPhysicsBody()->setVelocity(Point(400, 0)); bullet->setPosition(Point(-1000, VisibleRect::getVisibleRect().size.height/2)); @@ -393,7 +441,7 @@ void PhysicsDemoPlink::onEnter() { PhysicsDemo::onEnter(); - auto node = Node::create(); + auto node = DrawNode::create(); auto body = PhysicsBody::create(); body->setDynamic(false); node->setPhysicsBody(body); @@ -405,7 +453,11 @@ void PhysicsDemoPlink::onEnter() { for (int j = 0; j < 4; ++j) { - body->addShape(PhysicsShapePolygon::create(tris, 3, PHYSICSSHAPE_MATERIAL_DEFAULT, Point(rect.origin.x + rect.size.width/9*i + (j%2)*40 - 20, rect.origin.y + j*70))); + Point offset(rect.origin.x + rect.size.width/9*i + (j%2)*40 - 20, rect.origin.y + j*70); + body->addShape(PhysicsShapePolygon::create(tris, 3, PHYSICSSHAPE_MATERIAL_DEFAULT, offset)); + + Point drawVec[] = {tris[0] + offset, tris[1] + offset, tris[2] + offset}; + node->drawPolygon(drawVec, 3, STATIC_COLOR, 1, STATIC_COLOR); } } @@ -416,4 +468,209 @@ void PhysicsDemoPlink::onEnter() std::string PhysicsDemoPlink::title() { return "Plink"; +} + +PhysicsDemoRayCast::PhysicsDemoRayCast() +: _angle(0.0f) +, _node(nullptr) +, _mode(0) +{} + +void PhysicsDemoRayCast::onEnter() +{ + PhysicsDemo::onEnter(); + setTouchEnabled(true); + + _scene->getPhysicsWorld()->setGravity(Point::ZERO); + + auto node = DrawNode::create(); + node->setPhysicsBody(PhysicsBody::createEdgeSegment(VisibleRect::leftBottom() + Point(0, 50), VisibleRect::rightBottom() + Point(0, 50))); + node->drawSegment(VisibleRect::leftBottom() + Point(0, 50), VisibleRect::rightBottom() + Point(0, 50), 1, STATIC_COLOR); + this->addChild(node); + + MenuItemFont::setFontSize(18); + auto item = MenuItemFont::create("Change Mode(any)", CC_CALLBACK_1(PhysicsDemoRayCast::changeModeCallback, this)); + + auto menu = Menu::create(item, NULL); + this->addChild(menu); + menu->setPosition(Point(VisibleRect::left().x+100, VisibleRect::top().y-10)); + + scheduleUpdate(); +} + +void PhysicsDemoRayCast::changeModeCallback(Object* sender) +{ + _mode = (_mode + 1) % 3; + + switch (_mode) + { + case 0: + ((MenuItemFont*)sender)->setString("Change Mode(any)"); + break; + case 1: + ((MenuItemFont*)sender)->setString("Change Mode(nearest)"); + break; + case 2: + ((MenuItemFont*)sender)->setString("Change Mode(multiple)"); + break; + + default: + break; + } +} + +bool PhysicsDemoRayCast::anyRay(PhysicsWorld& world, PhysicsShape& shape, Point point, Point normal, float fraction, void* data) +{ + *((Point*)data) = point; + return false; +} + +class PhysicsDemoNearestRayCastCallback : public PhysicsRayCastCallback +{ +public: + PhysicsDemoNearestRayCastCallback(); + +private: + float _friction; +}; + +PhysicsDemoNearestRayCastCallback::PhysicsDemoNearestRayCastCallback() +: _friction(1.0f) +{ + report = [this](PhysicsWorld& world, PhysicsShape& shape, Point point, Point normal, float fraction, void* data)->bool + { + if (_friction > fraction) + { + *((Point*)data) = point; + _friction = fraction; + } + + return true; + }; +} + +namespace +{ + static const int MAX_MULTI_RAYCAST_NUM = 5; +} + +class PhysicsDemoMultiRayCastCallback : public PhysicsRayCastCallback +{ +public: + PhysicsDemoMultiRayCastCallback(); + +public: + Point points[MAX_MULTI_RAYCAST_NUM]; + int num; +}; + +PhysicsDemoMultiRayCastCallback::PhysicsDemoMultiRayCastCallback() +: num(0) +{ + report = [this](PhysicsWorld& world, PhysicsShape& shape, Point point, Point normal, float fraction, void* data)->bool + { + if (num < MAX_MULTI_RAYCAST_NUM) + { + points[num++] = point; + } + + return true; + }; +} + +void PhysicsDemoRayCast::update(float delta) +{ + float L = 150.0f; + Point point1 = VisibleRect::center(); + Point d(L * cosf(_angle), L * sinf(_angle)); + Point point2 = point1 + d; + + removeChild(_node); + _node = DrawNode::create(); + switch (_mode) + { + case 0: + { + PhysicsRayCastCallback callback; + Point point3 = point2; + callback.report = anyRay; + + _scene->getPhysicsWorld()->rayCast(callback, point1, point2, &point3); + _node->drawSegment(point1, point3, 1, STATIC_COLOR); + + if (point2 != point3) + { + _node->drawDot(point3, 2, Color4F(1.0f, 1.0f, 1.0f, 1.0f)); + } + addChild(_node); + + break; + } + case 1: + { + PhysicsDemoNearestRayCastCallback callback; + Point point3 = point2; + + _scene->getPhysicsWorld()->rayCast(callback, point1, point2, &point3); + _node->drawSegment(point1, point3, 1, STATIC_COLOR); + + if (point2 != point3) + { + _node->drawDot(point3, 2, Color4F(1.0f, 1.0f, 1.0f, 1.0f)); + } + addChild(_node); + + break; + } + case 2: + { + PhysicsDemoMultiRayCastCallback callback; + + _scene->getPhysicsWorld()->rayCast(callback, point1, point2, nullptr); + + _node->drawSegment(point1, point2, 1, STATIC_COLOR); + + for (int i = 0; i < callback.num; ++i) + { + _node->drawDot(callback.points[i], 2, Color4F(1.0f, 1.0f, 1.0f, 1.0f)); + } + + addChild(_node); + + break; + } + + default: + break; + } + + _angle += 0.25f * M_PI / 180.0f; +} + +void PhysicsDemoRayCast::onTouchesEnded(const std::vector& touches, Event* event) +{ + //Add a new body/atlas sprite at the touched location + + for( auto &touch: touches) + { + auto location = touch->getLocation(); + + float r = CCRANDOM_0_1(); + + if (r < 1.0f/3.0f) + { + addChild(makeBall(location.x, location.y, 5 + CCRANDOM_0_1()*10)); + }else if(r < 2.0f/3.0f) + { + addChild(makeBox(location.x, location.y, Size(10 + CCRANDOM_0_1()*15, 10 + CCRANDOM_0_1()*15))); + }else + { + addChild(makeTriangle(location.x, location.y, Size(10 + CCRANDOM_0_1()*20, 10 + CCRANDOM_0_1()*20))); + } + } +} + +std::string PhysicsDemoRayCast::title() +{ + return "Ray Cast"; } \ No newline at end of file diff --git a/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.h b/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.h index 34f8053b3721..65c1c6bad91c 100644 --- a/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.h +++ b/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.h @@ -37,9 +37,13 @@ class PhysicsDemo : public BaseTest void toggleDebugCallback(Object* sender); void addGrossiniAtPosition(Point p, float scale = 1.0); + Sprite* makeBall(float x, float y, float radius, PhysicsMaterial material = {1.0f, 1.0f, 1.0f}); + Sprite* makeBox(float x, float y, Size size, PhysicsMaterial material = {1.0f, 1.0f, 1.0f}); + Sprite* makeTriangle(float x, float y, Size size, PhysicsMaterial material = {1.0f, 1.0f, 1.0f}); -private: +protected: Texture2D* _spriteTexture; // weak ref + SpriteBatchNode* _ball; }; class PhysicsDemoClickAdd : public PhysicsDemo @@ -57,11 +61,6 @@ class PhysicsDemoLogoSmash : public PhysicsDemo public: void onEnter() override; std::string title() override; - - Node* makeBall(float x, float y); - -private: - SpriteBatchNode* _ball; }; class PhysicsDemoPyramidStack : public PhysicsDemo @@ -78,4 +77,25 @@ class PhysicsDemoPlink : public PhysicsDemo std::string title() override; }; +class PhysicsDemoRayCast : public PhysicsDemo +{ +public: + PhysicsDemoRayCast(); +public: + void onEnter() override; + std::string title() override; + void update(float delta) override; + + void onTouchesEnded(const std::vector& touches, Event* event) override; + + void changeModeCallback(Object* sender); + + static bool anyRay(PhysicsWorld& world, PhysicsShape& shape, Point point, Point normal, float fraction, void* data); + +private: + float _angle; + DrawNode* _node; + int _mode; +}; + #endif From 352688c1f9d129a2496eb90e38c3bc843f650e81 Mon Sep 17 00:00:00 2001 From: boyu0 Date: Wed, 23 Oct 2013 10:11:08 +0800 Subject: [PATCH 3/5] issue #2771: fix some compile error in android and win32 --- cocos/physics/CCPhysicsJoint.cpp | 18 ++++++------ cocos/physics/CCPhysicsWorld.cpp | 5 ++++ cocos/physics/CCPhysicsWorld.h | 1 + cocos/physics/chipmunk/CCPhysicsJointInfo.cpp | 3 ++ .../Classes/PhysicsTest/PhysicsTest.cpp | 29 +++++++++++++++++++ .../TestCpp/Classes/PhysicsTest/PhysicsTest.h | 9 +++++- 6 files changed, 55 insertions(+), 10 deletions(-) diff --git a/cocos/physics/CCPhysicsJoint.cpp b/cocos/physics/CCPhysicsJoint.cpp index 18f63b710193..09b1de36dd22 100644 --- a/cocos/physics/CCPhysicsJoint.cpp +++ b/cocos/physics/CCPhysicsJoint.cpp @@ -103,15 +103,15 @@ void PhysicsJoint::setEnable(bool enable) } } -//PhysicsJointPin::PhysicsJointPin() -//{ -// -//} -// -//PhysicsJointPin::~PhysicsJointPin() -//{ -// -//} +PhysicsJointPin::PhysicsJointPin() +{ + +} + +PhysicsJointPin::~PhysicsJointPin() +{ + +} PhysicsJointFixed::PhysicsJointFixed() { diff --git a/cocos/physics/CCPhysicsWorld.cpp b/cocos/physics/CCPhysicsWorld.cpp index cab183ba7a83..ec7514ef6da6 100644 --- a/cocos/physics/CCPhysicsWorld.cpp +++ b/cocos/physics/CCPhysicsWorld.cpp @@ -561,6 +561,11 @@ void PhysicsWorld::rectQuery(PhysicsRectQueryCallback& callback, Rect rect, void } } +Array* getShapesAtPoint(Point point) +{ + +} + Array* PhysicsWorld::getAllBody() const { return _bodys; diff --git a/cocos/physics/CCPhysicsWorld.h b/cocos/physics/CCPhysicsWorld.h index e61cde276399..868889b539c1 100644 --- a/cocos/physics/CCPhysicsWorld.h +++ b/cocos/physics/CCPhysicsWorld.h @@ -97,6 +97,7 @@ class PhysicsWorld void rayCast(PhysicsRayCastCallback& callback, Point point1, Point point2, void* data); void rectQuery(PhysicsRectQueryCallback& callback, Rect rect, void* data); + Array* getShapesAtPoint(Point point); Array* getAllBody() const; /** Register a listener to receive contact callbacks*/ diff --git a/cocos/physics/chipmunk/CCPhysicsJointInfo.cpp b/cocos/physics/chipmunk/CCPhysicsJointInfo.cpp index 7a44587ba56d..b3fc37671050 100644 --- a/cocos/physics/chipmunk/CCPhysicsJointInfo.cpp +++ b/cocos/physics/chipmunk/CCPhysicsJointInfo.cpp @@ -24,8 +24,11 @@ #include "CCPhysicsJointInfo.h" #if (CC_PHYSICS_ENGINE == CC_PHYSICS_CHIPMUNK) +#include NS_CC_BEGIN +std::map PhysicsJointInfo::map; + PhysicsJointInfo::PhysicsJointInfo(PhysicsJoint* joint) : joint(joint) { diff --git a/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.cpp b/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.cpp index 0c67ddc20476..240d3ee2e8ef 100644 --- a/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.cpp +++ b/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.cpp @@ -673,4 +673,33 @@ void PhysicsDemoRayCast::onTouchesEnded(const std::vector& touches, Even std::string PhysicsDemoRayCast::title() { return "Ray Cast"; +} + + +void PhysicsDemoJoints::onEnter() +{ + PhysicsDemo::onEnter(); + + setTouchEnabled(true); + + _scene->getPhysicsWorld()->setGravity(Point::ZERO); + + +} + +void PhysicsDemoJoints::onTouchesEnded(const std::vector& touches, Event* event) +{ + //Add a new body/atlas sprite at the touched location + + for( auto &touch: touches) + { + auto location = touch->getLocation(); + + + } +} + +std::string PhysicsDemoJoints::title() +{ + return "Joints"; } \ No newline at end of file diff --git a/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.h b/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.h index 65c1c6bad91c..9df12624de61 100644 --- a/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.h +++ b/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.h @@ -85,7 +85,6 @@ class PhysicsDemoRayCast : public PhysicsDemo void onEnter() override; std::string title() override; void update(float delta) override; - void onTouchesEnded(const std::vector& touches, Event* event) override; void changeModeCallback(Object* sender); @@ -98,4 +97,12 @@ class PhysicsDemoRayCast : public PhysicsDemo int _mode; }; +class PhysicsDemoJoints : public PhysicsDemo +{ +public: + void onEnter() override; + std::string title() override; + void onTouchesEnded(const std::vector& touches, Event* event) override; +}; + #endif From b2388af4f872ca79797a411eea05852c921ed951 Mon Sep 17 00:00:00 2001 From: boyu0 Date: Wed, 23 Oct 2013 11:08:21 +0800 Subject: [PATCH 4/5] issue #2771: fix some compile error in win32 --- cocos/physics/CCPhysicsShape.h | 14 +++++--------- .../TestCpp/Classes/PhysicsTest/PhysicsTest.cpp | 4 ++-- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/cocos/physics/CCPhysicsShape.h b/cocos/physics/CCPhysicsShape.h index d1b5e0db049a..ebbe646466f3 100644 --- a/cocos/physics/CCPhysicsShape.h +++ b/cocos/physics/CCPhysicsShape.h @@ -44,15 +44,11 @@ typedef struct PhysicsMaterial float restitution; float friction; - PhysicsMaterial() - : density(0.0f) - , restitution(0.0f) - , friction(0.0f){} - - PhysicsMaterial(float density, float restitution, float friction) - : density(density) - , restitution(restitution) - , friction(friction){} + static PhysicsMaterial make(float density, float restitution, float friction) + { + PhysicsMaterial var = {density, restitution, friction}; + return var; + } }PhysicsMaterial; const PhysicsMaterial PHYSICSSHAPE_MATERIAL_DEFAULT = {0.0f, 1.0f, 1.0f}; diff --git a/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.cpp b/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.cpp index 240d3ee2e8ef..907a0df83a5a 100644 --- a/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.cpp +++ b/samples/Cpp/TestCpp/Classes/PhysicsTest/PhysicsTest.cpp @@ -384,7 +384,7 @@ void PhysicsDemoLogoSmash::onEnter() Node* ball = makeBall(2*(x - logo_width/2 + x_jitter) + VisibleRect::getVisibleRect().size.width/2, 2*(logo_height-y + y_jitter) + VisibleRect::getVisibleRect().size.height/2 - logo_height/2, - 0.95f, PhysicsMaterial(1.0f, 0.0f, 0.0f)); + 0.95f, PhysicsMaterial::make(1.0f, 0.0f, 0.0f)); ball->getPhysicsBody()->setMass(1.0); ball->getPhysicsBody()->setMoment(PHYSICS_INFINITY); @@ -396,7 +396,7 @@ void PhysicsDemoLogoSmash::onEnter() } - auto bullet = makeBall(400, 0, 10, PhysicsMaterial(PHYSICS_INFINITY, 0, 0)); + auto bullet = makeBall(400, 0, 10, PhysicsMaterial::make(PHYSICS_INFINITY, 0, 0)); bullet->getPhysicsBody()->setVelocity(Point(400, 0)); bullet->setPosition(Point(-1000, VisibleRect::getVisibleRect().size.height/2)); From e9e82b83673d39c81cac9cd786962b13c59e252f Mon Sep 17 00:00:00 2001 From: boyu0 Date: Wed, 23 Oct 2013 11:18:35 +0800 Subject: [PATCH 5/5] issue #2771: add resource --- .../TestCpp/Resources/Images/CyanTriangle.png | Bin 0 -> 9726 bytes .../TestCpp/Resources/Images/YellowTriangle.png | Bin 0 -> 8679 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 samples/Cpp/TestCpp/Resources/Images/CyanTriangle.png create mode 100644 samples/Cpp/TestCpp/Resources/Images/YellowTriangle.png diff --git a/samples/Cpp/TestCpp/Resources/Images/CyanTriangle.png b/samples/Cpp/TestCpp/Resources/Images/CyanTriangle.png new file mode 100644 index 0000000000000000000000000000000000000000..b443ce9ba9bab15ea269a9d9e467001d6f67f2e3 GIT binary patch literal 9726 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 M07*qoM6N<$g2bStHUIzs literal 0 HcmV?d00001 diff --git a/samples/Cpp/TestCpp/Resources/Images/YellowTriangle.png b/samples/Cpp/TestCpp/Resources/Images/YellowTriangle.png new file mode 100644 index 0000000000000000000000000000000000000000..e6e03b352ecc8f369da083e7591b909ba44a0f22 GIT binary patch literal 8679 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 FV1g52yZQhC literal 0 HcmV?d00001