diff --git a/changes.txt b/changes.txt index ea8809ca..fd0bc1b4 100644 --- a/changes.txt +++ b/changes.txt @@ -1,5 +1,7 @@ Changes in version 1.6 + - Added an ICollisionCallback to ISceneNodeAnimatorCollisionResponse, to inform the application that a collision has occured. Thanks to garrittg for this. + - Added an startPosition parameter to createFlyCircleAnimator() to allow starting the animator at any position on the circle. - Many uses of dimension2d changed to dimension2d, including IImage, ITexture and screen dimensions. You will have to change (at least) createDevice() calls to use dimension2d diff --git a/include/ISceneNodeAnimatorCollisionResponse.h b/include/ISceneNodeAnimatorCollisionResponse.h index 5d8da684..2d18eb9f 100644 --- a/include/ISceneNodeAnimatorCollisionResponse.h +++ b/include/ISceneNodeAnimatorCollisionResponse.h @@ -12,6 +12,30 @@ namespace irr namespace scene { + class ISceneNodeAnimatorCollisionResponse; + + //! Callback interface for catching events of collisions. + /** Implement this interface and use + ISceneNodeAnimatorCollisionResponse::setCollisionCallback to be able to + be notified if a collision has occurred. + **/ + class ICollisionCallback : public virtual IReferenceCounted + { + public: + + //! Will be called when a collision occurrs. + /** See ISceneNodeAnimatorCollisionResponse::setCollisionCallback for more information. + \param animator: Collision response animator in which the collision occurred. You can call + this animator's methods to find the node, collisionPoint and/or collision triangle. + \retval true if the collision was handled in the animator. The animator's target + node will *not* be moved to the collision point, but will instead move directly + to the location that triggered the collision check. + \retval false if the collision was not handled in the animator. The animator's + target node will be moved to the collision position. + */ + virtual bool onCollision(ISceneNodeAnimatorCollisionResponse* animator) = 0; + }; + //! Special scene node animator for doing automatic collision detection and response. /** This scene node animator can be attached to any single scene node and will then prevent it from moving through specified collision geometry @@ -108,6 +132,21 @@ namespace scene /** \return The node that this animator is acting on. */ virtual ISceneNode* getTargetNode(void) const = 0; + //! Returns true if a collision occurred during the last animateNode() + virtual bool collisionOccurred() const = 0; + + //! Returns the point of collision + virtual core::vector3df getCollisionPoint() const = 0; + + //! Returns the last triangle that caused a collision + virtual core::triangle3df getCollisionTriangle() const = 0; + + //! Sets a callback interface which will be called if a collision occurs. + /** \param callback: collision callback handler that will be called when a collision + occurs. Set this to 0 to disable the callback. + */ + virtual void setCollisionCallback(ICollisionCallback* callback) = 0; + }; diff --git a/source/Irrlicht/CSceneNodeAnimatorCollisionResponse.cpp b/source/Irrlicht/CSceneNodeAnimatorCollisionResponse.cpp index cc10f8c8..91004608 100644 --- a/source/Irrlicht/CSceneNodeAnimatorCollisionResponse.cpp +++ b/source/Irrlicht/CSceneNodeAnimatorCollisionResponse.cpp @@ -24,12 +24,13 @@ CSceneNodeAnimatorCollisionResponse::CSceneNodeAnimatorCollisionResponse( : Radius(ellipsoidRadius), Gravity(gravityPerSecond), Translation(ellipsoidTranslation), World(world), Object(object), SceneManager(scenemanager), LastTime(0), SlidingSpeed(slidingSpeed), Falling(false), IsCamera(false), - AnimateCameraTarget(true) + AnimateCameraTarget(true), CollisionOccurred(false), + CollisionCallback(0) { #ifdef _DEBUG setDebugName("CSceneNodeAnimatorCollisionResponse"); #endif - + if (World) World->grab(); @@ -42,6 +43,9 @@ CSceneNodeAnimatorCollisionResponse::~CSceneNodeAnimatorCollisionResponse() { if (World) World->drop(); + + if (CollisionCallback) + CollisionCallback->drop(); } @@ -136,6 +140,8 @@ ITriangleSelector* CSceneNodeAnimatorCollisionResponse::getWorld() const void CSceneNodeAnimatorCollisionResponse::animateNode(ISceneNode* node, u32 timeMs) { + CollisionOccurred = false; + if (node != Object) setNode(node); @@ -150,7 +156,8 @@ void CSceneNodeAnimatorCollisionResponse::animateNode(ISceneNode* node, u32 time FallingVelocity += Gravity * (f32)diff * 0.001f; - core::triangle3df triangle = RefTriangle; + CollisionTriangle = RefTriangle; + CollisionPoint = core::vector3df(); core::vector3df force = vel + FallingVelocity; @@ -161,14 +168,15 @@ void CSceneNodeAnimatorCollisionResponse::animateNode(ISceneNode* node, u32 time // TODO: divide SlidingSpeed by frame time bool f = false; - core::vector3df collisionPosition; // Not used. pos = SceneManager->getSceneCollisionManager()->getCollisionResultPosition( World, LastPosition-Translation, - Radius, vel, triangle, collisionPosition, f, SlidingSpeed, FallingVelocity); + Radius, vel, CollisionTriangle, CollisionPoint, f, SlidingSpeed, FallingVelocity); + + CollisionOccurred = (CollisionTriangle != RefTriangle); pos += Translation; - if (f)//triangle == RefTriangle) + if (f)//CollisionTriangle == RefTriangle) { Falling = true; } @@ -178,7 +186,20 @@ void CSceneNodeAnimatorCollisionResponse::animateNode(ISceneNode* node, u32 time FallingVelocity.set(0, 0, 0); } - Object->setPosition(pos); + bool collisionConsumed = false; + + CollisionPoint = pos; + + if (CollisionOccurred) + { + CollisionPoint = pos; + + if(CollisionCallback) + collisionConsumed = CollisionCallback->onCollision(this); + } + + if(!collisionConsumed) + Object->setPosition(pos); } // move camera target @@ -231,13 +252,23 @@ ISceneNodeAnimator* CSceneNodeAnimatorCollisionResponse::createClone(ISceneNode* { if (!newManager) newManager = SceneManager; - CSceneNodeAnimatorCollisionResponse * newAnimator = + CSceneNodeAnimatorCollisionResponse * newAnimator = new CSceneNodeAnimatorCollisionResponse(newManager, World, Object, Radius, (Gravity * 1000.0f), Translation, SlidingSpeed); return newAnimator; } +void CSceneNodeAnimatorCollisionResponse::setCollisionCallback(ICollisionCallback* callback) +{ + if (CollisionCallback) + CollisionCallback->drop(); + + CollisionCallback = callback; + + if (CollisionCallback) + CollisionCallback->grab(); +} } // end namespace scene } // end namespace irr diff --git a/source/Irrlicht/CSceneNodeAnimatorCollisionResponse.h b/source/Irrlicht/CSceneNodeAnimatorCollisionResponse.h index 7d98e818..dadb33ff 100644 --- a/source/Irrlicht/CSceneNodeAnimatorCollisionResponse.h +++ b/source/Irrlicht/CSceneNodeAnimatorCollisionResponse.h @@ -95,6 +95,21 @@ namespace scene //! Gets the single node that this animator is acting on. virtual ISceneNode* getTargetNode(void) const { return Object; } + //! Returns true if a collision occurred during the last animateNode() + virtual bool collisionOccurred() const { return CollisionOccurred; } + + //! Returns the last point of collision. + virtual core::vector3df getCollisionPoint() const { return CollisionPoint; } + + //! Returns the last triangle that caused a collision. + virtual core::triangle3df getCollisionTriangle() const { return CollisionTriangle; } + + //! Sets a callback interface which will be called if a collision occurs. + /** \param callback: collision callback handler that will be called when a collision + occurs. Set this to 0 to disable the callback. + */ + virtual void setCollisionCallback(ICollisionCallback* callback); + private: void setNode(ISceneNode* node); @@ -115,6 +130,12 @@ namespace scene bool Falling; bool IsCamera; bool AnimateCameraTarget; + + bool CollisionOccurred; + core::vector3df CollisionPoint; + core::triangle3df CollisionTriangle; + + ICollisionCallback* CollisionCallback; }; } // end namespace scene diff --git a/tests/collisionResponseAnimator.cpp b/tests/collisionResponseAnimator.cpp index a1ccaa13..4d8207e7 100644 --- a/tests/collisionResponseAnimator.cpp +++ b/tests/collisionResponseAnimator.cpp @@ -10,6 +10,50 @@ using namespace core; using namespace scene; using namespace video; +static bool expectedCollisionCallbackPositions = true; + +class CMyCollisionCallback : public ICollisionCallback +{ +public: + bool onCollision(ISceneNodeAnimatorCollisionResponse* animator) + { + const vector3df & collisionPoint = animator->getCollisionPoint(); + + logTestString("Collision callback at %f %f %f\n", + collisionPoint.X, collisionPoint.Y, collisionPoint.Z); + + if(collisionPoint != ExpectedCollisionPoint) + { + logTestString("*** Error: expected %f %f %f\n", + ExpectedCollisionPoint.X, ExpectedCollisionPoint.Y, ExpectedCollisionPoint.Z); + expectedCollisionCallbackPositions = false; + assert(false); + } + + if(animator->getTargetNode() != ExpectedTarget) + { + logTestString("*** Error: wrong node\n"); + expectedCollisionCallbackPositions = false; + assert(false); + } + + return ConsumeCollision; + } + + void setNextExpectedCollision(ISceneNode* target, const vector3df& point, bool consume) + { + ExpectedTarget = target; + ExpectedCollisionPoint = point; + ConsumeCollision = consume; + } + +private: + + ISceneNode * ExpectedTarget; + vector3df ExpectedCollisionPoint; + bool ConsumeCollision; + +}; /** Test that collision response animator will reset itself when removed from a scene node, so that the scene node can then be moved without the animator @@ -39,6 +83,10 @@ bool collisionResponseAnimator(void) vector3df(10,10,10), vector3df(0, 0, 0)); testNode1->addAnimator(collisionAnimator1); + + CMyCollisionCallback collisionCallback; + collisionAnimator1->setCollisionCallback(&collisionCallback); + collisionAnimator1->drop(); collisionAnimator1 = 0; @@ -48,6 +96,7 @@ bool collisionResponseAnimator(void) vector3df(10,10,10), vector3df(0, 0, 0)); testNode2->addAnimator(collisionAnimator2); + collisionAnimator2->setCollisionCallback(&collisionCallback); wallSelector->drop(); // Don't drop() collisionAnimator2 since we're going to use it. @@ -59,6 +108,7 @@ bool collisionResponseAnimator(void) // Try to move both nodes to the right of the wall. // This one should be stopped by its animator. testNode1->setPosition(vector3df(50, 0,0)); + collisionCallback.setNextExpectedCollision(testNode1, vector3df(-15.004999f, 0, 0), false); // Whereas this one, by forcing the animator to update its target node, should be // able to pass through the wall. (In <=1.6 it was stopped by the wall even if @@ -89,17 +139,38 @@ bool collisionResponseAnimator(void) // Now try to move the second node back through the wall again. Now it should be // stopped by the wall. testNode2->setPosition(vector3df(-50, 0, 0)); + + // We'll consume this collision, so the node will actually move all the way through. + collisionCallback.setNextExpectedCollision(testNode2, vector3df(15.004999f, 0, 0), true); + device->run(); smgr->drawAll(); - if(testNode2->getAbsolutePosition().X < 15.f) + if(testNode2->getAbsolutePosition().X != -50.f) { - logTestString("collisionResponseAnimator test node 2 wasn't stopped from moving.\n"); + logTestString("collisionResponseAnimator test node 2 was stopped from moving.\n"); assert(false); result = false; } + // Now we'll try to move it back to the right and allow it to be stopped. + collisionCallback.setNextExpectedCollision(testNode2, vector3df(-15.004999f, 0, 0), false); + testNode2->setPosition(vector3df(50, 0, 0)); + + device->run(); + smgr->drawAll(); + + if(testNode2->getAbsolutePosition().X > -15.f) + { + logTestString("collisionResponseAnimator test node 2 moved too far.\n"); + assert(false); + result = false; + } + + device->drop(); + + result &= expectedCollisionCallbackPositions; return result; } diff --git a/tests/tests-last-passed-at.txt b/tests/tests-last-passed-at.txt index 8a786578..960200ad 100644 --- a/tests/tests-last-passed-at.txt +++ b/tests/tests-last-passed-at.txt @@ -1,2 +1,2 @@ -Test suite pass at GMT Tue Jan 20 12:53:02 2009 +Test suite pass at GMT Tue Jan 20 19:08:37 2009