diff --git a/cocos/2d/CCMenu.cpp b/cocos/2d/CCMenu.cpp index 617610bf51..2b8ceea7d5 100644 --- a/cocos/2d/CCMenu.cpp +++ b/cocos/2d/CCMenu.cpp @@ -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(); - - if (!_children.empty()) + auto camera = Camera::getVisitingCamera(); + + if (!_children.empty() && nullptr != camera) { for (auto iter = _children.crbegin(); iter != _children.crend(); ++iter) { MenuItem* child = dynamic_cast(*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; } diff --git a/cocos/2d/CCNode.cpp b/cocos/2d/CCNode.cpp index 2cff686d9c..946b40bf28 100644 --- a/cocos/2d/CCNode.cpp +++ b/cocos/2d/CCNode.cpp @@ -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) { diff --git a/cocos/2d/CCNode.h b/cocos/2d/CCNode.h index 59d8cd2c94..77ab67766f 100644 --- a/cocos/2d/CCNode.h +++ b/cocos/2d/CCNode.h @@ -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 diff --git a/cocos/2d/CCScene.cpp b/cocos/2d/CCScene.cpp index d2945449bb..bd1a8ddab6 100644 --- a/cocos/2d/CCScene.cpp +++ b/cocos/2d/CCScene.cpp @@ -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& 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; } - - for (const auto& camera : _cameras) + return _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; diff --git a/cocos/2d/CCScene.h b/cocos/2d/CCScene.h index 34f2063e84..ae154083e4 100644 --- a/cocos/2d/CCScene.h +++ b/cocos/2d/CCScene.h @@ -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& getCameras() const { return _cameras; } + const std::vector& getCameras(); /** Get the default camera. * @js NA diff --git a/cocos/base/CCEventDispatcher.cpp b/cocos/base/CCEventDispatcher.cpp index 10798145ee..1f7b862203 100644 --- a/cocos/base/CCEventDispatcher.cpp +++ b/cocos/base/CCEventDispatcher.cpp @@ -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 @@ -762,7 +762,99 @@ void EventDispatcher::dispatchEventToListeners(EventListenerVector* listeners, c } } } + + 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::dispatchTouchEventToListeners(EventListenerVector* listeners, const std::function& 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(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 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) @@ -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; diff --git a/cocos/base/CCEventDispatcher.h b/cocos/base/CCEventDispatcher.h index aed0ca830d..5948c170c6 100644 --- a/cocos/base/CCEventDispatcher.h +++ b/cocos/base/CCEventDispatcher.h @@ -276,6 +276,16 @@ protected: /** Dispatches event to listeners with a specified listener type */ void dispatchEventToListeners(EventListenerVector* listeners, const std::function& 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& onEvent); + /// Priority dirty flag enum class DirtyFlag { diff --git a/cocos/ui/UIScrollView.cpp b/cocos/ui/UIScrollView.cpp index 301908b0af..c57ee7bd1b 100644 --- a/cocos/ui/UIScrollView.cpp +++ b/cocos/ui/UIScrollView.cpp @@ -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) diff --git a/cocos/ui/UISlider.cpp b/cocos/ui/UISlider.cpp index 29e195d668..ab192678c2 100644 --- a/cocos/ui/UISlider.cpp +++ b/cocos/ui/UISlider.cpp @@ -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(_maxPercent)); + Vec3 p; + Widget::hitTest(pt, _hittedByCamera, &p); + return ((p.x/_barLength) * static_cast(_maxPercent)); } void Slider::addEventListenerSlider(Ref *target, SEL_SlidPercentChangedEvent selector) diff --git a/cocos/ui/UISlider.h b/cocos/ui/UISlider.h index 538d86ef81..8304870210 100644 --- a/cocos/ui/UISlider.h +++ b/cocos/ui/UISlider.h @@ -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; diff --git a/cocos/ui/UITextField.cpp b/cocos/ui/UITextField.cpp index 0b112490b8..73af19d349 100644 --- a/cocos/ui/UITextField.cpp +++ b/cocos/ui/UITextField.cpp @@ -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,26 +381,17 @@ 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; - } + return Widget::hitTest(pt, camera, nullptr); } - else - { - return Widget::hitTest(pt); - } - - return false; + + Rect rect(0, 0, _touchWidth, _touchHeight); + return isScreenPointInRect(pt, camera, getWorldToNodeTransform(), rect, nullptr); } - + Size TextField::getTouchSize()const { return Size(_touchWidth, _touchHeight); diff --git a/cocos/ui/UITextField.h b/cocos/ui/UITextField.h index acfa199877..2840ee453e 100644 --- a/cocos/ui/UITextField.h +++ b/cocos/ui/UITextField.h @@ -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; /** diff --git a/cocos/ui/UIWidget.cpp b/cocos/ui/UIWidget.cpp index e277f0fac9..638df39696 100644 --- a/cocos/ui/UIWidget.cpp +++ b/cocos/ui/UIWidget.cpp @@ -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; } diff --git a/cocos/ui/UIWidget.h b/cocos/ui/UIWidget.h index a283cc7c26..de83ba094c 100644 --- a/cocos/ui/UIWidget.h +++ b/cocos/ui/UIWidget.h @@ -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;