Fixed bug: UI component can't click correctly by moving UI and camera far away of origin.

This commit is contained in:
Vincent Yang 2015-06-30 14:32:14 +08:00
commit 2ba6f266ad
14 changed files with 254 additions and 78 deletions

View File

@ -24,6 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "2d/CCMenu.h"
#include "2d/CCCamera.h"
#include "base/CCDirector.h"
#include "base/CCTouch.h"
#include "base/CCEventListenerTouch.h"
@ -539,26 +540,25 @@ void Menu::alignItemsInRowsWithArray(const ValueVector& columns)
MenuItem* Menu::getItemForTouch(Touch *touch)
{
Vec2 touchLocation = touch->getLocation();
auto camera = Camera::getVisitingCamera();
if (!_children.empty())
if (!_children.empty() && nullptr != camera)
{
for (auto iter = _children.crbegin(); iter != _children.crend(); ++iter)
{
MenuItem* child = dynamic_cast<MenuItem*>(*iter);
if (child && child->isVisible() && child->isEnabled())
if (nullptr == child || false == child->isVisible() || false == child->isEnabled())
{
Vec2 local = child->convertToNodeSpace(touchLocation);
Rect r = child->rect();
r.origin.setZero();
if (r.containsPoint(local))
{
return child;
}
continue;
}
Rect rect;
rect.size = child->getContentSize();
if (isScreenPointInRect(touchLocation, camera, child->getWorldToNodeTransform(), rect, nullptr))
{
return child;
}
}
}
return nullptr;
}

View File

@ -2312,6 +2312,52 @@ void Node::disableCascadeColor()
}
}
bool isScreenPointInRect(const Vec2 &pt, const Camera* camera, const Mat4& w2l, const Rect& rect, Vec3 *p)
{
if (nullptr == camera || rect.size.width <= 0 || rect.size.height <= 0)
{
return false;
}
// first, convert pt to near/far plane, get Pn and Pf
Vec3 Pn(pt.x, pt.y, -1), Pf(pt.x, pt.y, 1);
Pn = camera->unprojectGL(Pn);
Pf = camera->unprojectGL(Pf);
// then convert Pn and Pf to node space
w2l.transformPoint(&Pn);
w2l.transformPoint(&Pf);
// Pn and Pf define a line Q(t) = D + t * E which D = Pn
auto E = Pf - Pn;
// second, get three points which define content plane
// these points define a plane P(u, w) = A + uB + wC
Vec3 A = Vec3(rect.origin.x, rect.origin.y, 0);
Vec3 B(rect.size.width, 0, 0);
Vec3 C(0, rect.size.height, 0);
B = B - A;
C = C - A;
// the line Q(t) intercept with plane P(u, w)
// calculate the intercept point P = Q(t)
// (BxC).A - (BxC).D
// t = -----------------
// (BxC).E
Vec3 BxC;
Vec3::cross(B, C, &BxC);
auto BxCdotE = BxC.dot(E);
if (BxCdotE == 0) {
return false;
}
auto t = (BxC.dot(A) - BxC.dot(Pn)) / BxCdotE;
Vec3 P = Pn + t * E;
if (p) {
*p = P;
}
return rect.containsPoint(Vec2(P.x, P.y));
}
// MARK: Camera
void Node::setCameraMask(unsigned short mask, bool applyChildren)
{

View File

@ -57,6 +57,7 @@ class Material;
class PhysicsBody;
class PhysicsWorld;
#endif
class Camera;
/**
* @addtogroup _2d
@ -1875,6 +1876,24 @@ private:
#endif //CC_USTPS
};
/**
* This is a helper function, checks a GL screen point is in content rectangle space.
*
* The content rectangle defined by origin(0,0) and content size.
* This function convert GL screen point to near and far planes as points Pn and Pf,
* then calculate the intersect point P which the line PnPf intersect with content rectangle.
* If P in content rectangle means this node be hitted.
*
* @param pt The point in GL screen space.
* @param camera Which camera used to unproject pt to near/far planes.
* @param w2l World to local transform matrix, used to convert Pn and Pf to rectangle space.
* @param rect The test recangle in local space.
* @parma p Point to a Vec3 for store the intersect point, if don't need them set to nullptr.
* @return true if the point is in content rectangle, flase otherwise.
*/
bool CC_DLL isScreenPointInRect(const Vec2 &pt, const Camera* camera, const Mat4& w2l, const Rect& rect, Vec3 *p);
// NodeRGBA
/** @class __NodeRGBA

View File

@ -163,18 +163,23 @@ static bool camera_cmp(const Camera* a, const Camera* b)
return a->getRenderOrder() < b->getRenderOrder();
}
void Scene::render(Renderer* renderer)
const std::vector<Camera*>& Scene::getCameras()
{
auto director = Director::getInstance();
Camera* defaultCamera = nullptr;
const auto& transform = getNodeToParentTransform();
if (_cameraOrderDirty)
{
stable_sort(_cameras.begin(), _cameras.end(), camera_cmp);
_cameraOrderDirty = false;
}
return _cameras;
}
for (const auto& camera : _cameras)
void Scene::render(Renderer* renderer)
{
auto director = Director::getInstance();
Camera* defaultCamera = nullptr;
const auto& transform = getNodeToParentTransform();
for (const auto& camera : getCameras())
{
if (!camera->isVisible())
continue;

View File

@ -88,10 +88,10 @@ public:
/** Get all cameras.
*
* @return The vector of all cameras.
* @return The vector of all cameras, ordered by camera depth.
* @js NA
*/
const std::vector<Camera*>& getCameras() const { return _cameras; }
const std::vector<Camera*>& getCameras();
/** Get the default camera.
* @js NA

View File

@ -37,7 +37,7 @@
#include "2d/CCScene.h"
#include "base/CCDirector.h"
#include "base/CCEventType.h"
#include "2d/CCCamera.h"
#define DUMP_LISTENER_ITEM_PRIORITY_INFO 0
@ -783,6 +783,98 @@ void EventDispatcher::dispatchEventToListeners(EventListenerVector* listeners, c
}
}
void EventDispatcher::dispatchTouchEventToListeners(EventListenerVector* listeners, const std::function<bool(EventListener*)>& onEvent)
{
bool shouldStopPropagation = false;
auto fixedPriorityListeners = listeners->getFixedPriorityListeners();
auto sceneGraphPriorityListeners = listeners->getSceneGraphPriorityListeners();
ssize_t i = 0;
// priority < 0
if (fixedPriorityListeners)
{
CCASSERT(listeners->getGt0Index() <= static_cast<ssize_t>(fixedPriorityListeners->size()), "Out of range exception!");
if (!fixedPriorityListeners->empty())
{
for (; i < listeners->getGt0Index(); ++i)
{
auto l = fixedPriorityListeners->at(i);
if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))
{
shouldStopPropagation = true;
break;
}
}
}
}
if (sceneGraphPriorityListeners)
{
if (!shouldStopPropagation)
{
// priority == 0, scene graph priority
// first, get all enabled, unPaused and registered listeners
std::vector<EventListener*> sceneListeners;
for (auto& l : *sceneGraphPriorityListeners)
{
if (l->isEnabled() && !l->isPaused() && l->isRegistered())
{
sceneListeners.push_back(l);
}
}
// second, for all camera call all listeners
// get a copy of cameras, prevent it's been modified in linstener callback
// if camera's depth is greater, process it earler
auto cameras = Director::getInstance()->getRunningScene()->getCameras();
Camera* camera;
for (int j = int(cameras.size()) - 1; j >= 0; --j)
{
camera = cameras[j];
if (camera->isVisible() == false)
{
continue;
}
Camera::_visitingCamera = camera;
for (auto& l : sceneListeners)
{
if (onEvent(l))
{
shouldStopPropagation = true;
break;
}
}
if (shouldStopPropagation)
{
break;
}
}
Camera::_visitingCamera = nullptr;
}
}
if (fixedPriorityListeners)
{
if (!shouldStopPropagation)
{
// priority > 0
ssize_t size = fixedPriorityListeners->size();
for (; i < size; ++i)
{
auto l = fixedPriorityListeners->at(i);
if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))
{
shouldStopPropagation = true;
break;
}
}
}
}
}
void EventDispatcher::dispatchEvent(Event* event)
{
if (!_isEnabled)
@ -803,6 +895,10 @@ void EventDispatcher::dispatchEvent(Event* event)
sortEventListeners(listenerID);
auto pfnDispatchEventToListeners = &EventDispatcher::dispatchEventToListeners;
if (event->getType() == Event::Type::MOUSE) {
pfnDispatchEventToListeners = &EventDispatcher::dispatchTouchEventToListeners;
}
auto iter = _listenerMap.find(listenerID);
if (iter != _listenerMap.end())
{
@ -814,7 +910,7 @@ void EventDispatcher::dispatchEvent(Event* event)
return event->isStopped();
};
dispatchEventToListeners(listeners, onEvent);
(this->*pfnDispatchEventToListeners)(listeners, onEvent);
}
updateListeners(event);
@ -945,7 +1041,7 @@ void EventDispatcher::dispatchTouchEvent(EventTouch* event)
};
//
dispatchEventToListeners(oneByOneListeners, onTouchEvent);
dispatchTouchEventToListeners(oneByOneListeners, onTouchEvent);
if (event->isStopped())
{
return;
@ -1011,7 +1107,7 @@ void EventDispatcher::dispatchTouchEvent(EventTouch* event)
return false;
};
dispatchEventToListeners(allAtOnceListeners, onTouchesEvent);
dispatchTouchEventToListeners(allAtOnceListeners, onTouchesEvent);
if (event->isStopped())
{
return;

View File

@ -276,6 +276,16 @@ protected:
/** Dispatches event to listeners with a specified listener type */
void dispatchEventToListeners(EventListenerVector* listeners, const std::function<bool(EventListener*)>& onEvent);
/** Special version dispatchEventToListeners for touch/mouse event.
*
* Touch/mouse event process flow different with common event,
* for scene graph node listeners, touch event process flow should
* order by viewport/camera first, because the touch location convert
* to 3D world space is different by different camera.
* When listener process touch event, can get current camera by Camera::getVisitingCamera().
*/
void dispatchTouchEventToListeners(EventListenerVector* listeners, const std::function<bool(EventListener*)>& onEvent);
/// Priority dirty flag
enum class DirtyFlag
{

View File

@ -27,7 +27,7 @@ THE SOFTWARE.
#include "base/ccUtils.h"
#include "platform/CCDevice.h"
#include "2d/CCTweenFunction.h"
#include "2d/CCCamera.h"
NS_CC_BEGIN
namespace ui {
@ -764,6 +764,7 @@ void ScrollView::endRecordSlidAction()
if(!bounceBackStarted && _inertiaScrollEnabled)
{
startInertiaScroll();
}
}
@ -779,9 +780,15 @@ void ScrollView::handlePressLogic(Touch *touch)
void ScrollView::handleMoveLogic(Touch *touch)
{
Vec2 touchPositionInNodeSpace = this->convertToNodeSpace(touch->getLocation());
Vec2 previousTouchPositionInNodeSpace = this->convertToNodeSpace(touch->getPreviousLocation());
Vec2 delta = touchPositionInNodeSpace - previousTouchPositionInNodeSpace;
Vec3 currPt, prevPt;
if (nullptr == _hittedByCamera ||
false == hitTest(touch->getLocation(), _hittedByCamera, &currPt) ||
false == hitTest(touch->getPreviousLocation(), _hittedByCamera, &prevPt))
{
return;
}
Vec3 delta3 = currPt - prevPt;
Vec2 delta(delta3.x, delta3.y);
scrollChildren(delta.x, delta.y);
while(_inertiaTouchDisplacements.size() > 5)

View File

@ -26,6 +26,7 @@ THE SOFTWARE.
#include "ui/UIScale9Sprite.h"
#include "ui/UIHelper.h"
#include "2d/CCSprite.h"
#include "2d/CCCamera.h"
NS_CC_BEGIN
@ -425,16 +426,12 @@ void Slider::setPercent(int percent)
}
}
bool Slider::hitTest(const cocos2d::Vec2 &pt)
bool Slider::hitTest(const cocos2d::Vec2 &pt, const Camera *camera, Vec3 *p) const
{
Vec2 nsp = this->_slidBallNormalRenderer->convertToNodeSpace(pt);
Size ballSize = this->_slidBallNormalRenderer->getContentSize();
Rect ballRect = Rect(0,0, ballSize.width, ballSize.height);
if (ballRect.containsPoint(nsp))
{
return true;
}
return false;
Rect rect;
rect.size = _slidBallNormalRenderer->getContentSize();
auto w2l = _slidBallNormalRenderer->getWorldToNodeTransform();
return isScreenPointInRect(pt, camera, w2l, rect, nullptr);
}
bool Slider::onTouchBegan(Touch *touch, Event *unusedEvent)
@ -442,8 +439,7 @@ bool Slider::onTouchBegan(Touch *touch, Event *unusedEvent)
bool pass = Widget::onTouchBegan(touch, unusedEvent);
if (_hitted)
{
Vec2 nsp = convertToNodeSpace(_touchBeganPosition);
setPercent(getPercentWithBallPos(nsp.x));
setPercent(getPercentWithBallPos(_touchBeganPosition));
percentChangedEvent(EventType::ON_SLIDEBALL_DOWN);
}
return pass;
@ -452,8 +448,7 @@ bool Slider::onTouchBegan(Touch *touch, Event *unusedEvent)
void Slider::onTouchMoved(Touch *touch, Event *unusedEvent)
{
_touchMovePosition = touch->getLocation();
Vec2 nsp = convertToNodeSpace(_touchMovePosition);
setPercent(getPercentWithBallPos(nsp.x));
setPercent(getPercentWithBallPos(_touchMovePosition));
percentChangedEvent(EventType::ON_PERCENTAGE_CHANGED);
}
@ -469,9 +464,11 @@ void Slider::onTouchCancelled(Touch *touch, Event *unusedEvent)
percentChangedEvent(EventType::ON_SLIDEBALL_CANCEL);
}
float Slider::getPercentWithBallPos(float px)const
float Slider::getPercentWithBallPos(const Vec2 &pt) const
{
return ((px/_barLength) * static_cast<float>(_maxPercent));
Vec3 p;
Widget::hitTest(pt, _hittedByCamera, &p);
return ((p.x/_barLength) * static_cast<float>(_maxPercent));
}
void Slider::addEventListenerSlider(Ref *target, SEL_SlidPercentChangedEvent selector)

View File

@ -247,7 +247,7 @@ public:
virtual void ignoreContentAdaptWithSize(bool ignore) override;
//override the widget's hitTest function to perfom its own
virtual bool hitTest(const Vec2 &pt) override;
virtual bool hitTest(const Vec2 &pt, const Camera* camera, Vec3 *p) const override;
/**
* Returns the "class name" of widget.
*/
@ -270,7 +270,7 @@ CC_CONSTRUCTOR_ACCESS:
protected:
virtual void initRenderer() override;
float getPercentWithBallPos(float location)const;
float getPercentWithBallPos(const Vec2 &pt) const;
void percentChangedEvent(EventType event);
virtual void onPressStateChangedToNormal() override;
virtual void onPressStateChangedToPressed() override;

View File

@ -26,6 +26,7 @@ THE SOFTWARE.
#include "platform/CCFileUtils.h"
#include "ui/UIHelper.h"
#include "base/ccUTF8.h"
#include "2d/CCCamera.h"
NS_CC_BEGIN
@ -380,24 +381,15 @@ void TextField::setTouchAreaEnabled(bool enable)
_useTouchArea = enable;
}
bool TextField::hitTest(const Vec2 &pt)
bool TextField::hitTest(const Vec2 &pt, const Camera* camera, Vec3 *p) const
{
if (_useTouchArea)
if (false == _useTouchArea)
{
Vec2 nsp = convertToNodeSpace(pt);
Rect bb = Rect(-_touchWidth * _anchorPoint.x, -_touchHeight * _anchorPoint.y, _touchWidth, _touchHeight);
if (nsp.x >= bb.origin.x && nsp.x <= bb.origin.x + bb.size.width
&& nsp.y >= bb.origin.y && nsp.y <= bb.origin.y + bb.size.height)
{
return true;
}
}
else
{
return Widget::hitTest(pt);
return Widget::hitTest(pt, camera, nullptr);
}
return false;
Rect rect(0, 0, _touchWidth, _touchHeight);
return isScreenPointInRect(pt, camera, getWorldToNodeTransform(), rect, nullptr);
}
Size TextField::getTouchSize()const

View File

@ -316,7 +316,7 @@ public:
*/
void setTouchAreaEnabled(bool enable);
virtual bool hitTest(const Vec2 &pt) override;
virtual bool hitTest(const Vec2 &pt, const Camera* camera, Vec3 *p) const override;
/**

View File

@ -34,6 +34,7 @@ THE SOFTWARE.
#include "renderer/CCGLProgram.h"
#include "renderer/CCGLProgramState.h"
#include "renderer/ccShaders.h"
#include "2d/CCCamera.h"
NS_CC_BEGIN
@ -154,6 +155,7 @@ _positionType(PositionType::ABSOLUTE),
_actionTag(0),
_customSize(Size::ZERO),
_hitted(false),
_hittedByCamera(nullptr),
_touchListener(nullptr),
_flippedX(false),
_flippedY(false),
@ -749,9 +751,13 @@ bool Widget::onTouchBegan(Touch *touch, Event *unusedEvent)
if (isVisible() && isEnabled() && isAncestorsEnabled() && isAncestorsVisible(this) )
{
_touchBeganPosition = touch->getLocation();
if(hitTest(_touchBeganPosition) && isClippingParentContainsPoint(_touchBeganPosition))
auto camera = Camera::getVisitingCamera();
if(hitTest(_touchBeganPosition, camera, nullptr))
{
_hitted = true;
_hittedByCamera = camera;
if (isClippingParentContainsPoint(_touchBeganPosition)) {
_hitted = true;
}
}
}
if (!_hitted)
@ -785,7 +791,7 @@ void Widget::onTouchMoved(Touch *touch, Event *unusedEvent)
{
_touchMovePosition = touch->getLocation();
setHighlighted(hitTest(_touchMovePosition));
setHighlighted(hitTest(_touchMovePosition, _hittedByCamera, nullptr));
/*
* Propagate touch events to its parents
@ -914,16 +920,11 @@ void Widget::addCCSEventListener(const ccWidgetEventCallback &callback)
this->_ccEventCallback = callback;
}
bool Widget::hitTest(const Vec2 &pt)
bool Widget::hitTest(const Vec2 &pt, const Camera* camera, Vec3 *p) const
{
Vec2 nsp = convertToNodeSpace(pt);
Rect bb;
bb.size = _contentSize;
if (bb.containsPoint(nsp))
{
return true;
}
return false;
Rect rect;
rect.size = getContentSize();
return isScreenPointInRect(pt, camera, getWorldToNodeTransform(), rect, p);
}
bool Widget::isClippingParentContainsPoint(const Vec2 &pt)
@ -955,7 +956,7 @@ bool Widget::isClippingParentContainsPoint(const Vec2 &pt)
if (clippingParent)
{
bool bRet = false;
if (clippingParent->hitTest(pt))
if (clippingParent->hitTest(pt, _hittedByCamera, nullptr))
{
bRet = true;
}

View File

@ -38,7 +38,7 @@ THE SOFTWARE.
NS_CC_BEGIN
class EventListenerTouchOneByOne;
class Camera;
namespace ui {
class LayoutComponent;
@ -543,10 +543,12 @@ public:
* Checks a point is in widget's content space.
* This function is used for determining touch area of widget.
*
* @param pt The point in `Vec2`.
* @param pt The point in `Vec2`.
* @param camera The camera look at widget, used to convert GL screen point to near/far plane.
* @param p Point to a Vec3 for store the intersect point, if don't need them set to nullptr.
* @return true if the point is in widget's content space, flase otherwise.
*/
virtual bool hitTest(const Vec2 &pt);
virtual bool hitTest(const Vec2 &pt, const Camera* camera, Vec3 *p) const;
/**
* A callback which will be called when touch began event is issued.
@ -956,6 +958,7 @@ protected:
Vec2 _positionPercent;
bool _hitted;
const Camera *_hittedByCamera;
EventListenerTouchOneByOne* _touchListener;
Vec2 _touchBeganPosition;
Vec2 _touchMovePosition;