Pixel collision detection for v3.x

I’ve seen that a lot of people are struggling with pixel perfect collision detection in v3.x. There is code samples and tutorials to implement this feature for cocos2d-x v2.x but I couldn’t find a port to v3.x.

I therefore made it myself and I’d like to share it with you guys.

PixelCollision.h

#ifndef PIXEL_COLLISION_H_
#define PIXEL_COLLISION_H_

#include <iostream>
#include "cocos2d.h"

USING_NS_CC;

class PixelCollision {
public:
    static PixelCollision *getInstance(void);
    static void destroyInstance(void);

    bool collidesWithSprite(Sprite *sprite1, Sprite *sprite2, bool pp);
    bool collidesWithSprite(Sprite *sprite1, Sprite *sprite2);
    bool collidesWithPoint(Sprite *sprite, const Point &point);

private:
    class PixelReaderNode : public Node {
    public:
        static PixelReaderNode* create(const Point &readPoint);

        PixelReaderNode(const Point &readPoint);
        virtual ~PixelReaderNode(void);
        
        virtual void draw(cocos2d::Renderer *renderer, const cocos2d::Mat4& transform, uint32_t flags) override;
        inline void reset();

        inline const Point &getReadPoint(void) const;
        inline void setReadPoint(const Point &readPoint);

        inline const Size &getReadSize(void) const;
        inline void setReadSize(const Size &readPoint);

        inline GLubyte *getBuffer(void);

    private:
        void onDraw(void);

        CustomCommand _readPixelCommand;
        Point _readPoint;
        Size _readSize;
        GLubyte *_buffer;
    };

    PixelCollision(void);
    virtual ~PixelCollision(void);

    Rect getIntersection(const Rect &r1, const Rect &r2) const;
    Point renderSprite(Sprite *sprite, CustomCommand &command, bool red);
    void resetSprite(Sprite *sprite, const Point &oldPosition);

    // Singleton
    static PixelCollision *s_instance;
    
    GLProgram *_glProgram;
    RenderTexture *_rt;
    PixelReaderNode *_pixelReader;
};

// Inline methods
inline void PixelCollision::PixelReaderNode::reset(void) {
    memset(_buffer, 0, 4 * _readSize.width * _readSize.height);
}

inline const Point &PixelCollision::PixelReaderNode::getReadPoint(void) const {
    return _readPoint;
}

inline void PixelCollision::PixelReaderNode::setReadPoint(const Point &readPoint) {
    _readPoint = readPoint;
}

inline const Size &PixelCollision::PixelReaderNode::getReadSize(void) const {
    return _readSize;
}

inline void PixelCollision::PixelReaderNode::setReadSize(const Size &readSize) {
    if (_readSize.width * _readSize.height < readSize.width * readSize.height) {
        free(_buffer);
        _buffer = (GLubyte*) malloc(4 * readSize.width * readSize.height);
    }
    _readSize = readSize;
}

inline GLubyte *PixelCollision::PixelReaderNode::getBuffer(void) {
    return _buffer;
}

#endif /* PIXEL_COLLISION_H_ */

PixelCollision.cpp

#include "PixelCollision.h"

static const auto kVertexShader = "Shaders/SolidColorShader.vsh";
static const auto kFragmentShader = "Shaders/SolidColorShader.fsh";
static const auto kShaderRedUniform = "u_color_red";
static const auto kShaderBlueUniform = "u_color_blue";
static const auto kOpacityThreshold = 50;

PixelCollision* PixelCollision::s_instance = nullptr;

// Private Constructor being called from within the GetInstance handle
PixelCollision::PixelCollision(void) :
		_glProgram(nullptr),
		_rt(nullptr),
		_pixelReader(nullptr) {
	_glProgram = GLProgram::createWithFilenames(kVertexShader, kFragmentShader);
	_glProgram->addAttribute(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_POSITION);
	_glProgram->addAttribute(GLProgram::ATTRIBUTE_NAME_TEX_COORD, GLProgram::VERTEX_ATTRIB_TEX_COORD);
	_glProgram->addAttribute(GLProgram::ATTRIBUTE_NAME_COLOR, GLProgram::VERTEX_ATTRIB_COLOR);

	const Size &size = Director::sharedDirector()->getWinSize();
	_rt = RenderTexture::create(size.width, size.height, kCCTexture2DPixelFormat_RGBA8888);
	_pixelReader = PixelReaderNode::create(Point::ZERO);

	_glProgram->retain();
	_rt->retain();
	_pixelReader->retain();
}

PixelCollision::~PixelCollision(void) {
	_glProgram->release();
	_rt->release();
	_pixelReader->release();
}

PixelCollision* PixelCollision::getInstance(void) {
	if (!s_instance) {
		s_instance = new PixelCollision();
	}
	return s_instance;
}

void PixelCollision::destroyInstance(void) {
	delete s_instance;
	s_instance = nullptr;
}

bool PixelCollision::collidesWithSprite(Sprite *sprite1, Sprite *sprite2) {
	return this->collidesWithSprite(sprite1, sprite2, true);
}

bool PixelCollision::collidesWithSprite(Sprite *sprite1, Sprite *sprite2, bool pp) {
    Rect r1 = sprite1->getBoundingBox();
	Rect r2 = sprite2->getBoundingBox();
    
    if (r1.intersectsRect(r2)) {
        if (!pp) {
            return true;
        }
        
        Rect intersection = this->getIntersection(r1, r2);
        unsigned int numPixels = intersection.size.width * intersection.size.height;
        
        _rt->beginWithClear(0, 0, 0, 0);
        
        CustomCommand sprite1Command;
        CustomCommand sprite2Command;
        auto oldPosition1 = this->renderSprite(sprite1, sprite1Command, true);
        auto oldPosition2 = this->renderSprite(sprite2, sprite2Command, false);
        
	    _pixelReader->setReadPoint(intersection.origin);
	    _pixelReader->setReadSize(intersection.size);
        _pixelReader->reset();
	    _pixelReader->visit();

	    auto buffer = _pixelReader->getBuffer();
	    _rt->end();
	    Director::getInstance()->getRenderer()->render();

	    this->resetSprite(sprite1, oldPosition1);
	    this->resetSprite(sprite2, oldPosition2);

	    unsigned int maxIndex = numPixels * 4;
	    for (unsigned int i = 0; i < maxIndex; i += 4) {
	        if (buffer[i] > 0 && buffer[i + 2] > 0 && buffer[i + 3] > kOpacityThreshold) { // red and blue
		    return true;
		}
	    }
    }
    return false;
}

bool PixelCollision::collidesWithPoint(Sprite *sprite, const Point &point) {
    _rt->beginWithClear(0, 0, 0, 0);
    glColorMask(1, 0, 0, 1);

	Point oldPosition = sprite->getPosition();
	sprite->setPosition(sprite->getParent()->convertToWorldSpace(oldPosition));
    sprite->visit();

	auto readPoint = sprite->getParent()->convertToWorldSpace(point) * CC_CONTENT_SCALE_FACTOR();
	_pixelReader->setReadPoint(readPoint);
	_pixelReader->setReadSize(Size(1, 1));
	_pixelReader->visit();

	auto color = _pixelReader->getBuffer();
	_rt->end();
	Director::getInstance()->getRenderer()->render();
    glColorMask(1, 1, 1, 1);
	sprite->setPosition(oldPosition);
    
	return color[0] > 0;
}

// Private methods
PixelCollision::PixelReaderNode *PixelCollision::PixelReaderNode::create(const Point &readPoint) {
	auto pixelReader = new PixelReaderNode(readPoint);
	if (pixelReader && pixelReader->init()) {
		pixelReader->autorelease();
		return pixelReader;
	}
	CC_SAFE_DELETE(pixelReader);
	return nullptr;
}

PixelCollision::PixelReaderNode::PixelReaderNode(const Point &readPoint) :
		_readPoint(readPoint),
		_readSize(Size::ZERO),
		_buffer(nullptr) {
	this->setReadSize(Size(1, 1));
}

PixelCollision::PixelReaderNode::~PixelReaderNode(void) {
    free(_buffer);
}

void PixelCollision::PixelReaderNode::draw(Renderer *renderer, const Mat4& transform, uint32_t flags) {
	_readPixelCommand.init(_globalZOrder);
	_readPixelCommand.func = CC_CALLBACK_0(PixelCollision::PixelReaderNode::onDraw, this);
	renderer->addCommand(&_readPixelCommand);
}

void PixelCollision::PixelReaderNode::onDraw(void) {
	glReadPixels(_readPoint.x, _readPoint.y, _readSize.width, _readSize.height,
		GL_RGBA, GL_UNSIGNED_BYTE, _buffer);
}

Rect PixelCollision::getIntersection(const Rect &r1, const Rect &r2) const {
	float tempX;
	float tempY;
	float tempWidth;
	float tempHeight;

	if (r1.getMaxX() > r2.getMinX()) {
		tempX = r2.getMinX();
		tempWidth = r1.getMaxX() - r2.getMinX();
	} else {
		tempX = r1.getMinX();
		tempWidth = r2.getMaxX() - r1.getMinX();
	}
	if (r2.getMaxY() < r1.getMaxY()) {
		tempY = r1.getMinY();
		tempHeight = r2.getMaxY() - r1.getMinY();
	} else {
		tempY = r2.getMinY();
		tempHeight = r1.getMaxY() - r2.getMinY();
	}

	return Rect(tempX * CC_CONTENT_SCALE_FACTOR(), tempY * CC_CONTENT_SCALE_FACTOR(),
		tempWidth * CC_CONTENT_SCALE_FACTOR(), tempHeight * CC_CONTENT_SCALE_FACTOR());
}

Point PixelCollision::renderSprite(Sprite *sprite, CustomCommand &command, bool red) {
	command.init(sprite->getGlobalZOrder());
	command.func = [=]() {
		sprite->getGLProgramState()->setUniformInt(kShaderRedUniform, red ? 255 : 0);
		sprite->getGLProgramState()->setUniformInt(kShaderBlueUniform, red ? 0 : 255);
	};
	Director::getInstance()->getRenderer()->addCommand(&command);

	sprite->setGLProgram(_glProgram);
	sprite->setBlendFunc(BlendFunc::ADDITIVE);
	Point oldPosition = sprite->getPosition();
	sprite->setPosition(sprite->getParent()->convertToWorldSpace(oldPosition));
	sprite->visit();

	return oldPosition;
}

void PixelCollision::resetSprite(Sprite *sprite, const Point &oldPosition) {
	auto program = ShaderCache::sharedShaderCache()->programForKey(
		GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP);
	sprite->setGLProgram(program);
	sprite->setBlendFunc(BlendFunc::ALPHA_PREMULTIPLIED);
	sprite->setPosition(oldPosition);
}

For sprite on sprite collision I am using a solid color shader :

SolidColorShader.vsh

attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;

#ifdef GL_ES
varying lowp vec4 v_fragmentColor;
varying mediump vec2 v_texCoord;
#else
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
#endif

void main()
{
    gl_Position = CC_PMatrix * a_position;
    v_fragmentColor = a_color;
    v_texCoord = a_texCoord;
}

SolidColorShader.fsh

#ifdef GL_ES
precision lowp float;
#endif

varying vec2 v_texCoord;
uniform int u_color_red;
uniform int u_color_blue;

void main()
{
    vec4 color = texture2D(CC_Texture0, v_texCoord);
    gl_FragColor = vec4(u_color_red, 0, u_color_blue, color.a);
}

Note: In order to make this work on WP8, you’d need to precompile the custom shaders. See this post for more information on how to do it.

I’d like to get feedback on how to improve it performance-wise, if possible.

I hope it will help someone.
Best.

5 Likes

Thanks. But it did not work well with me. Here it works like the rectangle boundbox Collision Detection, not the pixel Collision Detection. It works with .plist and png images?

For sprite collisions, the first check is to verify whether the boundboxes of the two sprites collides. If they don’t, then false is returned directly.

If they do collide, then if the bool parameter pp is set to true (it is by default) it will draw the two sprites on a render texture. One in red, one in blue, both in additive blending mode.
The common bounding box area is then scanned, and if there is at least one pixel with blue AND red then the two sprites are colliding.
It works with any type of images as long as it is a cocos2d::Sprite.

Make sure you have the boolean parameter to true.

Thankyou.

my problem is like @netolopes it works like normal bounding box collision.
I use cocos2d::Sprite as my parameter to method collidesWithSprite( sprite1, sprite2, true);
but it works like bounding box. please help thanks!:slight_smile:

note: i use cocos2d-x v3.2 and my sprite is PNG.

Cool! I will have a try after then.

I must apologize, I was testing it wrong.

There was indeed a problem with sprite collisions. I have edited the first post with updated code.
It should work correctly now. Let me know if you find any problem.

I also added an opacity threshold (kOpacityThreshold constant in .cpp), to avoid collision detection with almost-invisible pixels.

@sgleizes Thank you so much for this post. I am kind of confusing using RenderTexture and GLProgram in version 3.
But this did not work well with my test, the buffer array values are always zeros . Thus, the result is always false. I am using version 3.2 final.
Could you please have a check at your code ?
I have saved image from TextureRender _rt and the image was all transparent

Thank you for giving feedback, any problem you find prevents unexpected future problems from happening to me :smile:.

I could not reproduce what you’re having though. Could you provide more info about how you instantiate your sprites in your scene and how/where you do the call ?
Please also mention the platform with which you’re testing.

Thank you again.

1 Like

@sgleizes I am sorry that was my mistake in using the wrong file path. Your code works like a charm. You are the real MVP.

@sgleizes I have just came up with an idea about performance of pixel perfect. Instead of testing 1 Sprite with 1 Sprite each time. We can render all the obstacles sprites of which bounding boxes collide with player sprite blue. Do you think it is feasible ?

Thanks a lot for sharing the code. I’ve managed to get this working under cocos2d-js 3.5 using jsb.
However, this code as is did not always worked well. I had to fix the getIntersection calculation.
I’ve also added some debug visualization so you can see the rendered collision.

Please comment out my debug utility SDEBUG.

My fixed code PixelCollision.cpp:

//
//  PixelCollision.cpp
//

#include "PixelCollision.h"

static const auto kVertexShader = "res/Shaders/SolidColorShader.vsh";
static const auto kFragmentShader = "res/Shaders/SolidColorShader.fsh";
static const auto kShaderRedUniform = "u_color_red";
static const auto kShaderBlueUniform = "u_color_blue";
static const auto kOpacityThreshold = 50;

PixelCollision* PixelCollision::s_instance = nullptr;

// Private Constructor being called from within the GetInstance handle
PixelCollision::PixelCollision(void) :
		_glProgram(nullptr),
		_rt(nullptr),
		_pixelReader(nullptr) {
	_glProgram = GLProgram::createWithFilenames(kVertexShader, kFragmentShader);
	_glProgram->addAttribute(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_POSITION);
	_glProgram->addAttribute(GLProgram::ATTRIBUTE_NAME_TEX_COORD, GLProgram::VERTEX_ATTRIB_TEX_COORD);
	_glProgram->addAttribute(GLProgram::ATTRIBUTE_NAME_COLOR, GLProgram::VERTEX_ATTRIB_COLOR);

	const Size &size = Director::sharedDirector()->getWinSize();
	_rt = RenderTexture::create(size.width, size.height, kCCTexture2DPixelFormat_RGBA8888);
	_pixelReader = PixelReaderNode::create(Point::ZERO);

	_glProgram->retain();
	_rt->retain();
	_pixelReader->retain();
}

PixelCollision::~PixelCollision(void) {
	_glProgram->release();
	_rt->release();
	_pixelReader->release();
}

PixelCollision* PixelCollision::getInstance(void) {
	if (!s_instance) {
		s_instance = new PixelCollision();
	}
	return s_instance;
}

void PixelCollision::destroyInstance(void) {
	delete s_instance;
	s_instance = nullptr;
}

bool PixelCollision::collidesWithSprite(Sprite *sprite1, Sprite *sprite2) {
	return this->collidesWithSprite(sprite1, sprite2, true);
}

bool PixelCollision::collidesWithSprite(Sprite *sprite1, Sprite *sprite2, bool pp)
{
#ifdef DEBUG_COLLISION_TEST_VISUALIZATION
    // debug
    //
    Scene *cur_n = Director::getInstance()->getRunningScene();
    Sprite *s_blend = dynamic_cast<Sprite*>(cur_n->getChildByTag(11));
    if (s_blend) {
        s_blend->removeFromParent();
    }
    //
    // /debug
#endif

    Rect r1 = Rect(0, 0, sprite1->getContentSize().width, sprite1->getContentSize().height);
    r1 = RectApplyAffineTransform(r1, sprite1->getNodeToWorldAffineTransform());
    //SDEBUG(LEVEL_DEBUG, "dragging", "Collision test: r1_new=%s;", RectStr(r1));


    Rect r2 = Rect(0, 0, sprite2->getContentSize().width, sprite2->getContentSize().height);
    r2 = RectApplyAffineTransform(r2, sprite2->getNodeToWorldAffineTransform());
    //SDEBUG(LEVEL_DEBUG, "dragging", "Collision test: r2_new=%s;", RectStr(r2));

    if (r1.intersectsRect(r2)) {
        if (!pp) {
            SDEBUG(LEVEL_DEBUG, "dragging", "Collision test (intersects/pixelPerfect): ACCEPTED by !pp; rect1=%s; rect2=%s;", RectStr(r1), RectStr(r2));
            return true;
        }

        Rect intersection = this->getIntersection(r1, r2);
        unsigned int numPixels = intersection.size.width * intersection.size.height;

        _rt->beginWithClear(0, 0, 0, 0);

        CustomCommand sprite1Command;
        CustomCommand sprite2Command;
        auto oldPosition1 = this->renderSprite(sprite1, sprite1Command, true);
        auto oldPosition2 = this->renderSprite(sprite2, sprite2Command, false);

#ifdef DEBUG_COLLISION_TEST_VISUALIZATION
        // debug
        //
        Node *n;
        n = this->drawRect(intersection, Color4F(255, 255, 0, 1));
        n->visit();
        //
        // /debug
#endif

	    _pixelReader->setReadPoint(intersection.origin);
	    _pixelReader->setReadSize(intersection.size);
        _pixelReader->reset();
	    _pixelReader->visit();

	    auto buffer = _pixelReader->getBuffer();
	    _rt->end();
	    Director::getInstance()->getRenderer()->render();

	    this->resetSprite(sprite1, oldPosition1);
	    this->resetSprite(sprite2, oldPosition2);

	    unsigned int maxIndex = numPixels * 4;
        bool retVal = false;
	    for (unsigned int i = 0; i < maxIndex; i += 4) {
	        if (buffer[i] > 0 && buffer[i + 2] > 0 && buffer[i + 3] > kOpacityThreshold) { // red and blue
                retVal = true;
                break;
            }
	    }
        SDEBUG(LEVEL_DEBUG, "dragging", "Collision test (intersects/pixelPerfect): retVal=%s; rect1=%s; rect2=%s; intersection=%s; numPixels=%d; CC_CONTENT_SCALE_FACTOR=%.2f; s1.scale=%.2f; s2.scale=%.2f;",
            BOOLStr(retVal), RectStr(r1), RectStr(r2), RectStr(intersection), numPixels, CC_CONTENT_SCALE_FACTOR(), sprite1->getScale(), sprite2->getScale());
        
#ifdef DEBUG_COLLISION_TEST_VISUALIZATION
        // debug
        //
        s_blend = CCSprite::createWithTexture(_rt->getSprite()->getTexture());
        s_blend->setTag(11);
        s_blend->setPosition(ccp(0,0));
        s_blend->setAnchorPoint(ccp(0,0));
        cur_n->addChild(s_blend);
        //
        // debug
#endif
        return retVal;
    }
    
	SDEBUG(LEVEL_DEBUG, "dragging", "Collision test (intersects/pixelPerfect): REJECTED by intersectsRect(); rect1=%s; rect2=%s;", RectStr(r1), RectStr(r2));

    return false;
}

Node* PixelCollision::drawRect (Rect r, const Color4F &color)
{
    r.origin.x /= CC_CONTENT_SCALE_FACTOR();
    r.origin.y /= CC_CONTENT_SCALE_FACTOR();
    r.size.width /= CC_CONTENT_SCALE_FACTOR();
    r.size.height /= CC_CONTENT_SCALE_FACTOR();

    Size winSize = r.size;
    Point pos = r.origin;
    auto rectNode = DrawNode::create();
    Vec2 rectangle[4];
    rectangle[0] = Vec2(pos.x+0, pos.y+0);
    rectangle[1] = Vec2(pos.x+winSize.width, pos.y+0);
    rectangle[2] = Vec2(pos.x+winSize.width, pos.y+winSize.height);
    rectangle[3] = Vec2(pos.x+0, pos.y+winSize.height);

    rectNode->drawPolygon(rectangle, 4, Color4F(255, 0, 0, 255), 1, color);
    return rectNode;
}

bool PixelCollision::collidesWithPoint(Sprite *sprite, const Point &point) {
    _rt->beginWithClear(0, 0, 0, 0);
    glColorMask(1, 0, 0, 1);

	Point oldPosition = sprite->getPosition();
	sprite->setPosition(sprite->getParent()->convertToWorldSpace(oldPosition));
    sprite->visit();

	auto readPoint = sprite->getParent()->convertToWorldSpace(point) * CC_CONTENT_SCALE_FACTOR();
	_pixelReader->setReadPoint(readPoint);
	_pixelReader->setReadSize(Size(1, 1));
	_pixelReader->visit();

	auto color = _pixelReader->getBuffer();
	_rt->end();
	Director::getInstance()->getRenderer()->render();
    glColorMask(1, 1, 1, 1);
	sprite->setPosition(oldPosition);

	return color[0] > 0;
}

// Private methods
PixelCollision::PixelReaderNode *PixelCollision::PixelReaderNode::create(const Point &readPoint) {
	auto pixelReader = new PixelReaderNode(readPoint);
	if (pixelReader && pixelReader->init()) {
		pixelReader->autorelease();
		return pixelReader;
	}
	CC_SAFE_DELETE(pixelReader);
	return nullptr;
}

PixelCollision::PixelReaderNode::PixelReaderNode(const Point &readPoint) :
		_readPoint(readPoint),
		_readSize(Size::ZERO),
		_buffer(nullptr) {
	this->setReadSize(Size(1, 1));
}

PixelCollision::PixelReaderNode::~PixelReaderNode(void) {
    free(_buffer);
}

void PixelCollision::PixelReaderNode::draw(Renderer *renderer, const Mat4& transform, uint32_t flags) {
	_readPixelCommand.init(_globalZOrder);
	_readPixelCommand.func = CC_CALLBACK_0(PixelCollision::PixelReaderNode::onDraw, this);
	renderer->addCommand(&_readPixelCommand);
}

void PixelCollision::PixelReaderNode::onDraw(void) {
	glReadPixels(_readPoint.x, _readPoint.y, _readSize.width, _readSize.height,
		GL_RGBA, GL_UNSIGNED_BYTE, _buffer);
}

Rect PixelCollision::getIntersection(const Rect &r1, const Rect &r2) const
{
// <Pp>
    Rect intersection;
  intersection = Rect(std::max(r1.getMinX(),r2.getMinX()), std::max(r1.getMinY(),r2.getMinY()),0,0);

intersection.size.width = std::min(r1.getMaxX(), r2.getMaxX()) - intersection.getMinX();
intersection.size.height = std::min(r1.getMaxY(), r2.getMaxY()) - intersection.getMinY();

intersection.origin.x *= CC_CONTENT_SCALE_FACTOR();
intersection.origin.y *= CC_CONTENT_SCALE_FACTOR();
intersection.size.width *= CC_CONTENT_SCALE_FACTOR();
intersection.size.height *= CC_CONTENT_SCALE_FACTOR();

return intersection;
// </Pp>

/* <ORIG>
	float tempX;
	float tempY;
	float tempWidth;
	float tempHeight;

	if (r1.getMaxX() > r2.getMinX()) {
		tempX = r2.getMinX();
		tempWidth = r1.getMaxX() - r2.getMinX();
	} else {
		tempX = r1.getMinX();
		tempWidth = r2.getMaxX() - r1.getMinX();
	}
	if (r2.getMaxY() < r1.getMaxY()) {
		tempY = r1.getMinY();
		tempHeight = r2.getMaxY() - r1.getMinY();
	} else {
		tempY = r2.getMinY();
		tempHeight = r1.getMaxY() - r2.getMinY();
	}

	return Rect(tempX * CC_CONTENT_SCALE_FACTOR(), tempY * CC_CONTENT_SCALE_FACTOR(),
		tempWidth * CC_CONTENT_SCALE_FACTOR(), tempHeight * CC_CONTENT_SCALE_FACTOR());*/
}

Point PixelCollision::renderSprite(Sprite *sprite, CustomCommand &command, bool red) {
	command.init(sprite->getGlobalZOrder());
	command.func = [=]() {
		sprite->getGLProgramState()->setUniformInt(kShaderRedUniform, red ? 255 : 0);
		sprite->getGLProgramState()->setUniformInt(kShaderBlueUniform, red ? 0 : 255);
	};
	Director::getInstance()->getRenderer()->addCommand(&command);

	sprite->setGLProgram(_glProgram);
	sprite->setBlendFunc(BlendFunc::ADDITIVE);
	Point oldPosition = sprite->getPosition();
	sprite->setPosition(sprite->getParent()->convertToWorldSpace(oldPosition));
	sprite->visit();

	return oldPosition;
}

void PixelCollision::resetSprite(Sprite *sprite, const Point &oldPosition) {
	auto program = ShaderCache::sharedShaderCache()->programForKey(
		GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP);
	sprite->setGLProgram(program);
	sprite->setBlendFunc(BlendFunc::ALPHA_PREMULTIPLIED);
	sprite->setPosition(oldPosition);
}

Hi pepeek,

Thanks for sharing the code.

Can you share code of “PixelCollision.h” ?

Thanks.

"PixelCollision.h"

#ifndef __Brilla__PixelCollision__
#define __Brilla__PixelCollision__

#include <iostream>
#include "cocos2d.h"

USING_NS_CC;

class PixelCollision
{
public:
    static PixelCollision *getInstance(void);
    static void destroyInstance(void);

    bool collidesWithSprite(Sprite *sprite1, Sprite *sprite2, bool pp);
    bool collidesWithSprite(Sprite *sprite1, Sprite *sprite2);
    bool collidesWithPoint(Sprite *sprite, const Point &point);
    Node* drawRect (Rect r, const Color4F &color);

private:
    class PixelReaderNode : public Node {
    public:
        static PixelReaderNode* create(const Point &readPoint);

        PixelReaderNode(const Point &readPoint);
        virtual ~PixelReaderNode(void);

        virtual void draw(cocos2d::Renderer *renderer, const cocos2d::Mat4& transform, uint32_t flags) override;
        inline void reset();

        inline const Point &getReadPoint(void) const;
        inline void setReadPoint(const Point &readPoint);

        inline const Size &getReadSize(void) const;
        inline void setReadSize(const Size &readPoint);

        inline GLubyte *getBuffer(void);

    private:
       void onDraw(void);

        CustomCommand _readPixelCommand;
        Point _readPoint;
        Size _readSize;
        GLubyte *_buffer;
    };

    PixelCollision(void);
    virtual ~PixelCollision(void);

    Rect getIntersection(const Rect &r1, const Rect &r2) const;
    Rect getUnion (const Rect &r1, const Rect &r2) const;
    Point renderSprite(Sprite *sprite, CustomCommand &command, bool red);
    void resetSprite(Sprite *sprite, const Point &oldPosition);

    // Singleton
    static PixelCollision *s_instance;

    GLProgram *_glProgram;
    RenderTexture *_rt;
    PixelReaderNode *_pixelReader;
};

// Inline methods
inline void PixelCollision::PixelReaderNode::reset(void) {
    memset(_buffer, 0, 4 * _readSize.width * _readSize.height);
}

inline const Point &PixelCollision::PixelReaderNode::getReadPoint(void) const {
    return _readPoint;
}

inline void PixelCollision::PixelReaderNode::setReadPoint(const Point &readPoint) {
    _readPoint = readPoint;
}

inline const Size &PixelCollision::PixelReaderNode::getReadSize(void) const {
    return _readSize;
}

inline void PixelCollision::PixelReaderNode::setReadSize(const Size &readSize) {
    if (_readSize.width * _readSize.height < readSize.width * readSize.height) {
        free(_buffer);
        _buffer = (GLubyte*) malloc(4 * readSize.width * readSize.height);
    }
    _readSize = readSize;
}

inline GLubyte *PixelCollision::PixelReaderNode::getBuffer(void) {
    return _buffer;
}

#endif /* defined(__Brilla__PixelCollision__) */

Hi pepeek,

Thank you very much for code.

I will try it and tell you the result of it.

Thanks.

Ty very much for your post @sgleizes
This is a very big help for my game.
In 3.6 version, we don’t need to make a precompile shaders. [I don’t know why. Correct me if I am wrong.]
I just put the shaders in

“res/Shader/…”

So I just call it from this code in update function:

if (PixelCollision::getInstance()->collidesWithSprite(sprite1, sprite2, true))
{
    CCLog("Collision happened! %f", delta); 
}

then I drag first image of grossini.png to second image.
It will display Message: “Collision happened! 0.01500” if its texture collides, not the bounding box.
thank you, thank you.
I will give u a credit in my game later.
[along with cocos2d-x team and @happybirthday]

Hi pepeek.

Thanks your share. I have a problem when using it, please help me.

When i include PixelCollision class to my project. if i check collision in init function, it work fine. but when i check in update game function (60f/s) it cannot detect collision.

globle init:
 Sprite Ship1;
 Sprite Ship2;


init function: 
   Ship1  = Sprite::createWithSpriteFrameName("ship1.png");
    Ship1->setPosition(visibleSize.width/2, visibleSize.height/2);
    GameSpriteBatch->addChild(Ship1);
    
    Ship2 = Sprite::createWithSpriteFrameName("ship2.png");
    Ship2->setPosition(visibleSize.width/2 + 30, visibleSize.height/2 - 40);
    GameSpriteBatch->addChild(Ship2);
    
    if (PixelCollision::getInstance()->collidesWithSprite(Ship1, Ship2)) {
        CCLOG("is collision");
    }

void Game::update(float dt) {
CCLOG("next checking!!!");
if (PixelCollision::getInstance()->collidesWithSprite(Ship1, Ship2)) {
    CCLOG("is collision in update function");
}}

and logs in console:
"is collision
"next checking!!!
"next checking!!!
"next checking!!!
"next checking!!!
"next checking!!!

i cant find the false in my code, please help me. Thanks

u don’t need to declare it in main function.
Only ONCE in update function:

void game::update(float dt)
{

}

@sgleizes
I tried this method and it only works for Sprite not SpriteFrameCache or SpriteFrame.
Is there a way to use it for animation?

Hi gOzaru

Sorry i dont understand the mean of:
Only ONCE in update function

I want to check any collision of objects in update function. but i dont know why the collision just apear just one.

Thanks.

Hi,

It is my full function, and it never collision between Ship1 and Ship2

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Layer::init() )
    {
        return false;
    }
    
visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();

/////////////////////////////
// 2. add a menu item with "X" image, which is clicked to quit the program
//    you may modify it.

// add a "close" icon to exit the progress. it's an autorelease object
auto closeItem = MenuItemImage::create(
                                       "CloseNormal.png",
                                       "CloseSelected.png",
                                       CC_CALLBACK_1(HelloWorld::menuCloseCallback, this));

closeItem->setPosition(Vec2(origin.x + visibleSize.width - closeItem->getContentSize().width/2 ,
                            origin.y + closeItem->getContentSize().height/2));

// create menu, it's an autorelease object
auto menu = Menu::create(closeItem, NULL);
menu->setPosition(Vec2::ZERO);
this->addChild(menu, 1);

GameSpriteBatch = SpriteBatchNode::create("testbatch.png");
SpriteFrameCache::getInstance()->addSpriteFramesWithFile("testbatch.plist");
this->addChild(GameSpriteBatch);
GameSpriteBatch->retain();

Ship1 = Sprite::createWithSpriteFrameName("ship1.png");
Ship1->setPosition(visibleSize.width/2, visibleSize.height/2);
GameSpriteBatch->addChild(Ship1);

Ship2 = Sprite::createWithSpriteFrameName("ship2.png");
Ship2->setPosition(visibleSize.width/2 + 30, visibleSize.height/2 - 35);
GameSpriteBatch->addChild(Ship2);

this->scheduleUpdate();

return true;
}


void HelloWorld::update(float delta) {
    
    if (PixelCollision::getInstance()->collidesWithSprite(Ship1, Ship2)) {
        CCLOG("is collision");
    }
}