#ifndef __STFilter__
#define __STFilter__
#include "cocos2d.h"
#include "STMacro.h"
NS_ST_BEGIN
USING_NS_CC;
using namespace std;
/**
类似滤镜的效果。如显微镜等。
*/
class STFilter : public Node
{
typedef std::function<void(const string &,const Rect &)> OverlapListener;
CC_SYNTHESIZE_READONLY(ClippingNode*, m_pClipper, ClippingNode);
CC_SYNTHESIZE_READONLY(Sprite*, m_pStencil, Stencil);
CC_SYNTHESIZE_READONLY(Sprite*, m_pContent, Content);
CC_SYNTHESIZE_READONLY(Sprite*, m_pFilterEffect, FilterEffect);
CC_SYNTHESIZE(bool, m_limitedInScreen, LimitedInScreen);
private:
Vec2 _moveLeftBottom, _moveRightTop;
map<string, Rect> m_CheckList;
float _overlapThreshold;
float _targetOverlapArea;
OverlapListener _overlapListener;
public:
/**
使用图片路径来初始化
@param stencil 模板图片路径
@param content 底板图片路径
*/
static STFilter * create(Sprite *stencil, Sprite *content);
/**
使用图片路径来初始化
@param stencil 模板图片路径
@param content 底板图片路径
*/
static STFilter * createWithPath(const string &stencil, const string &content);
/**
使用图片Frame名称来初始化
@param stencil 模板图片Frame名称
@param content 底板图片Frame名称
*/
static STFilter * createWithFrameName(const string &stencil, const string &content);
/** 设置绘制底板需要过滤的Alpha */
void setAlphaThreshold(float alphaThreshold);
/**
设置模板范围内可见,还是模板范围外可见
@param true 模板范围内可见, false 模板范围外可见
*/
void setInverted(bool inverted);
void addFilterEffect(Sprite *effect);
/**
模板中点默认在中间,位置为 Vec2(0, 0).
左下角 Vec2(-visibleRect.width / 2, -visibleRect.height / 2)
右上角 Vec2(visibleRect.width / 2, visibleRect.height / 2)
*/
void move(const Vec2 &pos);
protected:
STFilter();
virtual ~STFilter();
virtual bool init(Sprite *stencil, Sprite *content);
virtual bool initWithFrameName(const string &stencil, const string &content);
virtual bool initWithPath(const string &stencil, const string &content);
private:
CC_SYNTHESIZE(bool, _overlapEnable, OverlapEnable);
/** 判断 stencil 和 target 之间是否重叠 */
void checkOverlap();
public:
/**
设置重叠检测成功的阀值。
当设置为1时,表示全部覆盖了目标物才算成功。设置为 0.5 表示,当覆盖面积达到目标物的50%时,检测成功。
取值范围在 [0, 1] 之间。默认为 1
*/
void setOverlapThreshold(float threshold)
{
_overlapThreshold = clampf(threshold, 0.f, 1.f);
}
void setOverlapListener(OverlapListener lsr)
{
_overlapListener = lsr;
_overlapEnable = true;
}
/**
name作为key,需保持唯一,否则不会添加。
矩形区域是基于世界坐标系。
*/
void addCheckRect(const string &name, const Rect &rect);
void addCheckObject(const string &name, Node *target);
void removeCheckRect(const string &name);
int getCheckCount()
{
return m_CheckList.size();
}
bool isCheckListEmpty()
{
return m_CheckList.empty();
}
};
NS_ST_END
#endif
#include "STFilter.h"
#include "VisibleRect.h"
USING_NS_ST;
#define NODE_WIDTH(obj) (obj)->getContentSize().width
#define NODE_HEIGHT(obj) (obj)->getContentSize().height
STFilter * STFilter::create(Sprite *stencil, Sprite *content)
{
auto node = new (std::nothrow) STFilter();
if ( node && node->init(stencil, content) )
{
node->autorelease();
return node;
}
CC_SAFE_DELETE(node);
return nullptr;
}
STFilter * STFilter::createWithPath(const std::string &stencil, const std::string &content)
{
auto node = new (std::nothrow) STFilter();
if ( node && node->initWithPath(stencil, content) )
{
node->autorelease();
return node;
}
CC_SAFE_DELETE(node);
return nullptr;
}
STFilter * STFilter::createWithFrameName(const std::string &stencil, const std::string &content)
{
auto node = new (std::nothrow) STFilter();
if ( node && node->initWithFrameName(stencil, content) )
{
node->autorelease();
return node;
}
CC_SAFE_DELETE(node);
return nullptr;
}
STFilter::STFilter()
: m_pFilterEffect(nullptr)
, _moveLeftBottom(Vec2::ZERO)
, _moveRightTop(Vec2::ZERO)
, m_CheckList()
, _overlapEnable(false)
, _overlapThreshold(1.f)
, _targetOverlapArea(0.f)
, _overlapListener(nullptr)
, m_limitedInScreen(true)
{
}
STFilter::~STFilter()
{
}
bool STFilter::initWithFrameName(const std::string &stencilName, const std::string &contentName)
{
auto stencil = Sprite::createWithSpriteFrameName(stencilName);
if (!stencil)
return false;
auto content = Sprite::createWithSpriteFrameName(contentName);
if (!content)
return false;
return init(stencil, content);
}
bool STFilter::initWithPath(const std::string &stencilPath, const std::string &contentPath)
{
auto stencil = Sprite::create(stencilPath);
if (!stencil)
return false;
auto content = Sprite::create(contentPath);
if (!content)
return false;
return init(stencil, content);
}
bool STFilter::init(Sprite *stencil, Sprite *content)
{
bool bRet = false;
do
{
CC_BREAK_IF(!stencil);
CC_BREAK_IF(!content);
CC_BREAK_IF( !Node::init() );
//
m_pStencil = stencil;
m_pContent = content;
//
m_pClipper = ClippingNode::create();
CC_BREAK_IF(!m_pClipper);
this->addChild(m_pClipper, 0);
// at the center of screen as default
m_pClipper->setPosition( VisibleRect::center() );
m_pClipper->setContentSize( m_pContent->getContentSize() );
// 设置裁剪模板
m_pClipper->setStencil(m_pStencil);
m_pClipper->addChild(m_pContent, 0);
auto rect = VisibleRect::getVisibleRect();
_moveLeftBottom = Vec2(-rect.size.width / 2 + NODE_WIDTH(m_pStencil) / 2,
-rect.size.height / 2 + NODE_HEIGHT(m_pStencil) / 2);
_moveRightTop = Vec2(rect.size.width / 2 - NODE_WIDTH(m_pStencil) / 2,
rect.size.height / 2 - NODE_HEIGHT(m_pStencil) / 2);
//
auto boxSize = m_pStencil->getBoundingBox().size;
float stenArea = boxSize.width * boxSize.height;
_targetOverlapArea = MAX(_targetOverlapArea, stenArea);
bRet = true;
} while (false);
return bRet;
}
void STFilter::setAlphaThreshold(float alphaThreshold)
{
m_pClipper->setAlphaThreshold(0.f);
}
void STFilter::setInverted(bool inverted)
{
m_pClipper->setInverted(inverted);
}
void STFilter::addFilterEffect(Sprite *effect)
{
if (effect == nullptr)
return;
m_pFilterEffect = effect;
m_pClipper->addChild(m_pFilterEffect, 10);
}
void STFilter::move(const Vec2 &deltaPos)
{
auto beforePos = m_pStencil->getPosition();
auto newPos = beforePos + deltaPos;
if(m_limitedInScreen)
{
newPos.x = clampf(newPos.x, _moveLeftBottom.x, _moveRightTop.x);
newPos.y = clampf(newPos.y, _moveLeftBottom.y, _moveRightTop.y);
}
m_pStencil->setPosition(newPos);
auto sten = m_pClipper->convertToWorldSpace(newPos);
if (m_pFilterEffect)
{
auto pos = m_pFilterEffect->getPosition() + (newPos - beforePos);
m_pFilterEffect->setPosition(pos);
}
if (_overlapEnable)
{
checkOverlap();
}
}
void STFilter::checkOverlap()
{
Rect steRect = m_pStencil->boundingBox();
steRect.origin = m_pClipper->convertToWorldSpaceAR(steRect.origin);
for (auto iter = m_CheckList.begin(); iter != m_CheckList.end(); ++iter)
{
Rect rect = iter->second;
auto big = rect.unionWithRect(steRect);
const float bigZone = big.size.width * big.size.height;
if ( bigZone <= _targetOverlapArea * (2 - _overlapThreshold) )
{
if (_overlapListener)
_overlapListener(iter->first,steRect);
return;
}
}
}
void STFilter::addCheckRect(const string &name, const Rect &rect)
{
auto itr = m_CheckList.find(name);
// the unique key
if (itr == m_CheckList.end())
{
m_CheckList.insert( {name, rect} );
//
float targetArea = rect.size.width * rect.size.height;
// 和Stencil面积相比较,因为可能出现完全包含的情况,所以取较大的面积作为测试条件
_targetOverlapArea = MAX(_targetOverlapArea, targetArea);
}
}
void STFilter::addCheckObject(const string &name, Node *target)
{
Rect rect;
rect.origin = target->convertToWorldSpace(Vec2::ZERO);
rect.size = target->getBoundingBox().size;
this->addCheckRect(name, rect);
}
void STFilter::removeCheckRect(const string &name)
{
auto itr = m_CheckList.find(name);
if (itr != m_CheckList.end())
{
m_CheckList.erase(itr);
}
}