Hello everyone, my previous game which is using cocos v3x was using pixel collision detection from this post : Pixel collision detection for v3.x . Its works nice and properly.
But now i want to upgrade my game into v4, so far i know cocos remove GlProgram to Program. So i tried to upgrade it by looking cocos2dx reference v4, but im ran out idea how to do it in v4. I mean the new Program doesnt have class that supposed to be as the GlProgram class be, Its totaly change. i’ve took a look at cocos2dx reference v4 and still cant make pixel detection in v4.
Any advice would be appreciated ? Thanks
Collision.h
#ifndef PIXEL_COLLISION_H_
#define PIXEL_COLLISION_H_
#include <iostream>
#include "../../Implement.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 cocos2d::Point &point);
private:
class PixelReaderNode : public Node {
public:
static PixelReaderNode* create(const cocos2d::Point &readPoint);
PixelReaderNode(const cocos2d::Point &readPoint);
virtual ~PixelReaderNode(void);
virtual void draw(cocos2d::Renderer *renderer, const cocos2d::Mat4& transform, uint32_t flags) override;
inline void reset();
inline const cocos2d::Point &getReadPoint(void) const;
inline void setReadPoint(const cocos2d::Point &readPoint);
inline const Size &getReadSize(void) const;
inline void setReadSize(const Size &readPoint);
inline GLubyte *getBuffer(void);
private:
void onDraw(void);
CustomCommand _readPixelCommand;
cocos2d::Point _readPoint;
Size _readSize;
GLubyte *_buffer;
};
PixelCollision(void);
virtual ~PixelCollision(void);
Rect getIntersection(const Rect &r1, const Rect &r2) const;
cocos2d::Point renderSprite(Sprite *sprite, CustomCommand &command, bool red);
void resetSprite(Sprite *sprite, const cocos2d::Point &oldPosition);
// Singleton
static PixelCollision *s_instance;
cocos2d::backend::Program *_glProgram;
RenderTexture *_rt;
PixelReaderNode *_pixelReader;
};
// Inline methods
inline void PixelCollision::PixelReaderNode::reset(void) {
memset(_buffer, 0, 4 * _readSize.width * _readSize.height);
}
inline const cocos2d::Point &PixelCollision::PixelReaderNode::getReadPoint(void) const {
return _readPoint;
}
inline void PixelCollision::PixelReaderNode::setReadPoint(const cocos2d::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_ */
Collision.cpp
#include “Collision.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) {
if (!FileUtils::getInstance()->isFileExist(EducaRes::additionalPath + kVertexShader)) return;
if (!FileUtils::getInstance()->isFileExist(EducaRes::additionalPath + kFragmentShader)) return;
//_glProgram = GLProgram::createWithFilenames(EducaRes::additionalPath + kVertexShader, kFragmentShader);
//_glProgram->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_POSITION, GLProgram::VERTEX_ATTRIB_POSITION);
//_glProgram->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_TEX_COORD, GLProgram::VERTEX_ATTRIB_TEX_COORD);
//_glProgram->bindAttribLocation(GLProgram::ATTRIBUTE_NAME_COLOR, GLProgram::VERTEX_ATTRIB_COLOR);
const Size &size = Director::getInstance()->getWinSize();
_rt = RenderTexture::create(size.width, size.height, cocos2d::backend::PixelFormat::RGBA8888);
_pixelReader = PixelReaderNode::create(cocos2d::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 cocos2d::Point &point) {
_rt->beginWithClear(0, 0, 0, 0);
glColorMask(1, 0, 0, 1);
auto oldColor = sprite->getColor();
auto prnt = sprite->getParent();
cocos2d::Point oldPosition = prnt->convertToWorldSpace(prnt->convertToNodeSpace(sprite->getPosition()));///sprite->getPosition();
sprite->setScale(prnt->getScaleX());
sprite->setColor(Color3B::WHITE);
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->setColor(oldColor);
sprite->setPosition(oldPosition);
sprite->setScale(1.0f);
return color[0] > 0;
}
// Private methods
PixelCollision::PixelReaderNode *PixelCollision::PixelReaderNode::create(const cocos2d::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 cocos2d::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());
}
cocos2d::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->set(_glProgram);
sprite->setBlendFunc(BlendFunc::ADDITIVE);
cocos2d::Point oldPosition = sprite->getPosition();
sprite->setPosition(sprite->getParent()->convertToWorldSpace(oldPosition));
sprite->visit();
return oldPosition;
}
void PixelCollision::resetSprite(Sprite *sprite, const cocos2d::Point &oldPosition) {
//auto program = ShaderCache::getInstance()->getGLProgram(
// GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP);
//sprite->setGLProgram(program);
sprite->setBlendFunc(BlendFunc::ALPHA_PREMULTIPLIED);
sprite->setPosition(oldPosition);
}