Looks like it’s working nice with multiple children. In my test I’ve added 2 sprites in different location, one was flipped and spine model. All these were inside one node, where I was performing hit test.
I wanted to create github page for this, but I’m not an author of this algorithm so I’ll just post final code here:
source:
//
// HitDetectHelper.cpp
//
// Created by Alexander Wong on 1/21/15.
//
//
#include "HitDetectHelper.h"
USING_NS_CC;
#pragma mark - HitDetectHelper
RenderTexture* HitDetectHelper::_sharedRenderTexture = NULL;
HitDetectHelper::HitDetectHelperNode* HitDetectHelper::_helperNode = NULL;
#pragma mark - Pixel Test
bool HitDetectHelper::hitTest(Node* node, Touch* touch, float extraWidth, float extraHeight){
if(!hitTestJustBounds(node, touch, extraWidth, extraHeight)){
return false;
}
return hitTestJustPixels(node, touch);
}
bool HitDetectHelper::hitTestJustBounds(cocos2d::Node* node, cocos2d::Touch* touch, float extraWidth, float extraHeight){
return Rect(0, 0, node->getContentSize().width + extraWidth, node->getContentSize().height + extraHeight).containsPoint(node->convertToNodeSpace(touch->getLocation()));
}
bool HitDetectHelper::hitTestJustPixels(Node* node, cocos2d::Touch* touch)
{
Point point = node->convertToNodeSpace(touch->getLocation());
if (!_sharedRenderTexture) {
_sharedRenderTexture = RenderTexture::create(1, 1);
_sharedRenderTexture->retain();
_sharedRenderTexture->setKeepMatrix(true);
Size winSize = Director::getInstance()->getWinSize();
Size winSizeInPixels = Director::getInstance()->getWinSizeInPixels();
_sharedRenderTexture->setVirtualViewport(Director::getInstance()->getVisibleOrigin(), Rect(0.0f, 0.0f, winSize.width, winSize.height), Rect(0.0f, 0.0f, winSizeInPixels.width, winSizeInPixels.height));
_sharedRenderTexture->setAutoDraw(false);
}
if (!_helperNode) {
_helperNode = HitDetectHelperNode::create(Point::ZERO);
_helperNode->retain();
}
else {
_helperNode->reset();
}
_sharedRenderTexture->beginWithClear(0.0f, 0.0f, 0.0f, 0.0f);
if(auto sprite = dynamic_cast<Sprite* >(node)){
if (sprite->getBatchNode()) {
Sprite* tempSprite = Sprite::createWithTexture(sprite->getTexture());
tempSprite->setTextureRect(sprite->getTextureRect(), sprite->isTextureRectRotated(), sprite->getContentSize());
sprite = tempSprite;
}
}
// store transforms
Point position = node->getPosition();
Point anchorPoint = node->getAnchorPoint();
float rotationX = node->getRotationSkewX();
float rotationY = node->getRotationSkewY();
float skewX = node->getSkewX();
float skewY = node->getSkewY();
// unset transforms
node->setAnchorPoint(Point::ANCHOR_BOTTOM_LEFT);
node->setPosition(Vec2(-point.x, -point.y) + Director::getInstance()->getVisibleOrigin());
node->setRotationSkewX(0.0f);
node->setRotationSkewY(0.0f);
node->setSkewX(0.0f);
node->setSkewY(0.0f);
// draw node
node->visit();
// add command to read pixel
_helperNode->visit();
// revert transforms
node->setAnchorPoint(anchorPoint);
node->setPosition(position);
node->setRotationSkewX(rotationX);
node->setRotationSkewY(rotationY);
node->setSkewX(skewX);
node->setSkewY(skewY);
_sharedRenderTexture->end();
Director::getInstance()->getRenderer()->render(); // render texture fix
uint8_t p0 = _helperNode->getPixelValue(0);
uint8_t p1 = _helperNode->getPixelValue(1);
uint8_t p2 = _helperNode->getPixelValue(2);
uint8_t p3 = _helperNode->getPixelValue(3);
return p0 || p1 || p2 || p3;
}
#pragma mark - HitDetectHelperNode
#pragma mark - Reset
void HitDetectHelper::HitDetectHelperNode::reset()
{
_pixelRead = false;
_pixelBuffer[0] = 0;
_pixelBuffer[1] = 0;
_pixelBuffer[2] = 0;
_pixelBuffer[3] = 0;
}
#pragma mark - Pixel Buffer
uint8_t HitDetectHelper::HitDetectHelperNode::getPixelValue(unsigned int index)
{
CCASSERT(_pixelRead, "Pixel not read yet.");
CCASSERT(index < 4, "Out of bounds.");
return _pixelBuffer[index];
}
#pragma mark - Draw
void HitDetectHelper::HitDetectHelperNode::draw(Renderer *renderer, const Mat4& transform, uint32_t flags)
{
_customCommand.init(_globalZOrder);
_customCommand.func = CC_CALLBACK_0(HitDetectHelper::HitDetectHelperNode::readPixel, this);
renderer->addCommand(&_customCommand);
}
#pragma mark - Read Pixel
void HitDetectHelper::HitDetectHelperNode::readPixel()
{
_pixelRead = true;
glReadPixels(_pixelPoint.x, _pixelPoint.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, _pixelBuffer);
}
#pragma mark - Constructors
HitDetectHelper::HitDetectHelperNode* HitDetectHelper::HitDetectHelperNode::create(const Point& pixelPoint)
{
HitDetectHelper::HitDetectHelperNode* node = new HitDetectHelper::HitDetectHelperNode();
if (node && node->init(pixelPoint)) {
node->autorelease();
}
else {
CC_SAFE_DELETE(node);
}
return node;
}
HitDetectHelper::HitDetectHelperNode::HitDetectHelperNode()
// instance variables
: _pixelRead(false)
{
}
bool HitDetectHelper::HitDetectHelperNode::init(const Point &pixelPoint)
{
_pixelPoint = pixelPoint;
reset();
return Node::init();
}
header:
//
// HitDetectHelper.h
//
// Created by Alexander Wong on 1/21/15.
//
//
#ifndef __HitDetectHelper__
#define __HitDetectHelper__
#include "cocos2d.h"
class HitDetectHelper
{
class HitDetectHelperNode;
private:
static cocos2d::RenderTexture* _sharedRenderTexture;
static HitDetectHelperNode* _helperNode;
#pragma mark - Hit Test
public:
static bool hitTest(cocos2d::Node* node, cocos2d::Touch* touch, float extraWidth = 0.0f, float extraHeight = 0.0f);
static bool hitTestJustBounds(cocos2d::Node* node, cocos2d::Touch* touch, float extraWidth = 0.0f, float extraHeight = 0.0f);
static bool hitTestJustPixels(cocos2d::Node* node, cocos2d::Touch* touch);
private:
CC_DISALLOW_IMPLICIT_CONSTRUCTORS(HitDetectHelper)
#pragma mark - HitDetectHelperNode
private:
class HitDetectHelperNode : public cocos2d::Node
{
private:
cocos2d::CustomCommand _customCommand;
uint8_t _pixelBuffer[4];
#pragma mark - Properties
CC_SYNTHESIZE(bool, _pixelRead, PixelRead);
CC_SYNTHESIZE(cocos2d::Point, _pixelPoint, PixelPoint);
#pragma mark - Reset
public:
void reset();
#pragma mark - Pixel Buffer
public:
uint8_t getPixelValue(unsigned int index);
#pragma mark - Draw
public:
virtual void draw(cocos2d::Renderer *renderer, const cocos2d::Mat4& transform, uint32_t flags) override;
#pragma mark - Read Pixel
private:
void readPixel();
#pragma mark - Constructors
public:
static HitDetectHelperNode* create(const cocos2d::Point& pixelPoint);
CC_CONSTRUCTOR_ACCESS:
HitDetectHelperNode();
virtual ~HitDetectHelperNode() {};
bool init(const cocos2d::Point& pixelPoint);
private:
CC_DISALLOW_COPY_AND_ASSIGN(HitDetectHelperNode);
};
};
#endif /* defined(__HitDetectHelper__) */