mirror of https://github.com/axmolengine/axmol.git
1244 lines
34 KiB
C++
1244 lines
34 KiB
C++
/****************************************************************************
|
|
Copyright (c) 2010-2012 cocos2d-x.org
|
|
Copyright (c) 2008-2010 Ricardo Quesada
|
|
Copyright (c) 2011 Zynga Inc.
|
|
|
|
http://www.cocos2d-x.org
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
****************************************************************************/
|
|
|
|
#include "CCSpriteBatchNode.h"
|
|
#include <string.h>
|
|
#include <algorithm>
|
|
#include "CCAnimation.h"
|
|
#include "CCAnimationCache.h"
|
|
#include "ccConfig.h"
|
|
#include "CCSprite.h"
|
|
#include "CCSpriteFrame.h"
|
|
#include "CCSpriteFrameCache.h"
|
|
#include "CCTextureCache.h"
|
|
#include "CCDrawingPrimitives.h"
|
|
#include "CCShaderCache.h"
|
|
#include "ccGLStateCache.h"
|
|
#include "CCGLProgram.h"
|
|
#include "CCDirector.h"
|
|
#include "CCGeometry.h"
|
|
#include "CCTexture2D.h"
|
|
#include "CCAffineTransform.h"
|
|
#include "TransformUtils.h"
|
|
#include "CCProfiling.h"
|
|
#include "CCRenderer.h"
|
|
#include "CCQuadCommand.h"
|
|
#include "CCFrustum.h"
|
|
|
|
// external
|
|
#include "kazmath/GL/matrix.h"
|
|
|
|
|
|
using namespace std;
|
|
|
|
NS_CC_BEGIN
|
|
|
|
#if CC_SPRITEBATCHNODE_RENDER_SUBPIXEL
|
|
#define RENDER_IN_SUBPIXEL
|
|
#else
|
|
#define RENDER_IN_SUBPIXEL(__ARGS__) (ceil(__ARGS__))
|
|
#endif
|
|
|
|
Sprite* Sprite::createWithTexture(Texture2D *texture)
|
|
{
|
|
Sprite *sprite = new Sprite();
|
|
if (sprite && sprite->initWithTexture(texture))
|
|
{
|
|
sprite->autorelease();
|
|
return sprite;
|
|
}
|
|
CC_SAFE_DELETE(sprite);
|
|
return nullptr;
|
|
}
|
|
|
|
Sprite* Sprite::createWithTexture(Texture2D *texture, const Rect& rect, bool rotated)
|
|
{
|
|
Sprite *sprite = new Sprite();
|
|
if (sprite && sprite->initWithTexture(texture, rect, rotated))
|
|
{
|
|
sprite->autorelease();
|
|
return sprite;
|
|
}
|
|
CC_SAFE_DELETE(sprite);
|
|
return nullptr;
|
|
}
|
|
|
|
Sprite* Sprite::create(const std::string& filename)
|
|
{
|
|
Sprite *sprite = new Sprite();
|
|
if (sprite && sprite->initWithFile(filename))
|
|
{
|
|
sprite->autorelease();
|
|
return sprite;
|
|
}
|
|
CC_SAFE_DELETE(sprite);
|
|
return nullptr;
|
|
}
|
|
|
|
Sprite* Sprite::create(const std::string& filename, const Rect& rect)
|
|
{
|
|
Sprite *sprite = new Sprite();
|
|
if (sprite && sprite->initWithFile(filename, rect))
|
|
{
|
|
sprite->autorelease();
|
|
return sprite;
|
|
}
|
|
CC_SAFE_DELETE(sprite);
|
|
return nullptr;
|
|
}
|
|
|
|
Sprite* Sprite::createWithSpriteFrame(SpriteFrame *spriteFrame)
|
|
{
|
|
Sprite *sprite = new Sprite();
|
|
if (spriteFrame && sprite && sprite->initWithSpriteFrame(spriteFrame))
|
|
{
|
|
sprite->autorelease();
|
|
return sprite;
|
|
}
|
|
CC_SAFE_DELETE(sprite);
|
|
return nullptr;
|
|
}
|
|
|
|
Sprite* Sprite::createWithSpriteFrameName(const std::string& spriteFrameName)
|
|
{
|
|
SpriteFrame *frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(spriteFrameName);
|
|
|
|
#if COCOS2D_DEBUG > 0
|
|
char msg[256] = {0};
|
|
sprintf(msg, "Invalid spriteFrameName: %s", spriteFrameName.c_str());
|
|
CCASSERT(frame != nullptr, msg);
|
|
#endif
|
|
|
|
return createWithSpriteFrame(frame);
|
|
}
|
|
|
|
Sprite* Sprite::create()
|
|
{
|
|
Sprite *sprite = new Sprite();
|
|
if (sprite && sprite->init())
|
|
{
|
|
sprite->autorelease();
|
|
return sprite;
|
|
}
|
|
CC_SAFE_DELETE(sprite);
|
|
return nullptr;
|
|
}
|
|
|
|
bool Sprite::init(void)
|
|
{
|
|
return initWithTexture(nullptr, Rect::ZERO );
|
|
}
|
|
|
|
bool Sprite::initWithTexture(Texture2D *texture)
|
|
{
|
|
CCASSERT(texture != nullptr, "Invalid texture for sprite");
|
|
|
|
Rect rect = Rect::ZERO;
|
|
rect.size = texture->getContentSize();
|
|
|
|
return initWithTexture(texture, rect);
|
|
}
|
|
|
|
bool Sprite::initWithTexture(Texture2D *texture, const Rect& rect)
|
|
{
|
|
return initWithTexture(texture, rect, false);
|
|
}
|
|
|
|
bool Sprite::initWithFile(const std::string& filename)
|
|
{
|
|
CCASSERT(filename.size()>0, "Invalid filename for sprite");
|
|
|
|
Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);
|
|
if (texture)
|
|
{
|
|
Rect rect = Rect::ZERO;
|
|
rect.size = texture->getContentSize();
|
|
return initWithTexture(texture, rect);
|
|
}
|
|
|
|
// don't release here.
|
|
// when load texture failed, it's better to get a "transparent" sprite then a crashed program
|
|
// this->release();
|
|
return false;
|
|
}
|
|
|
|
bool Sprite::initWithFile(const std::string &filename, const Rect& rect)
|
|
{
|
|
CCASSERT(filename.size()>0, "Invalid filename");
|
|
|
|
Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);
|
|
if (texture)
|
|
{
|
|
return initWithTexture(texture, rect);
|
|
}
|
|
|
|
// don't release here.
|
|
// when load texture failed, it's better to get a "transparent" sprite then a crashed program
|
|
// this->release();
|
|
return false;
|
|
}
|
|
|
|
bool Sprite::initWithSpriteFrameName(const std::string& spriteFrameName)
|
|
{
|
|
CCASSERT(spriteFrameName.size() > 0, "Invalid spriteFrameName");
|
|
|
|
SpriteFrame *frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(spriteFrameName);
|
|
return initWithSpriteFrame(frame);
|
|
}
|
|
|
|
bool Sprite::initWithSpriteFrame(SpriteFrame *spriteFrame)
|
|
{
|
|
CCASSERT(spriteFrame != nullptr, "");
|
|
|
|
bool bRet = initWithTexture(spriteFrame->getTexture(), spriteFrame->getRect());
|
|
setSpriteFrame(spriteFrame);
|
|
|
|
return bRet;
|
|
}
|
|
|
|
// designated initializer
|
|
bool Sprite::initWithTexture(Texture2D *texture, const Rect& rect, bool rotated)
|
|
{
|
|
if (NodeRGBA::init())
|
|
{
|
|
_batchNode = nullptr;
|
|
|
|
_recursiveDirty = false;
|
|
setDirty(false);
|
|
|
|
_opacityModifyRGB = true;
|
|
|
|
_blendFunc = BlendFunc::ALPHA_PREMULTIPLIED;
|
|
|
|
_flippedX = _flippedY = false;
|
|
|
|
// default transform anchor: center
|
|
setAnchorPoint(Point(0.5f, 0.5f));
|
|
|
|
// zwoptex default values
|
|
_offsetPosition = Point::ZERO;
|
|
|
|
_hasChildren = false;
|
|
|
|
// clean the Quad
|
|
memset(&_quad, 0, sizeof(_quad));
|
|
|
|
// Atlas: Color
|
|
_quad.bl.colors = Color4B::WHITE;
|
|
_quad.br.colors = Color4B::WHITE;
|
|
_quad.tl.colors = Color4B::WHITE;
|
|
_quad.tr.colors = Color4B::WHITE;
|
|
|
|
// shader program
|
|
setShaderProgram(ShaderCache::getInstance()->getProgram(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP));
|
|
|
|
// update texture (calls updateBlendFunc)
|
|
setTexture(texture);
|
|
setTextureRect(rect, rotated, rect.size);
|
|
|
|
// by default use "Self Render".
|
|
// if the sprite is added to a batchnode, then it will automatically switch to "batchnode Render"
|
|
setBatchNode(nullptr);
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Sprite::Sprite(void)
|
|
: _shouldBeHidden(false)
|
|
, _texture(nullptr)
|
|
{
|
|
}
|
|
|
|
Sprite::~Sprite(void)
|
|
{
|
|
CC_SAFE_RELEASE(_texture);
|
|
}
|
|
|
|
/*
|
|
* Texture methods
|
|
*/
|
|
|
|
/*
|
|
* This array is the data of a white image with 2 by 2 dimension.
|
|
* It's used for creating a default texture when sprite's texture is set to nullptr.
|
|
* Supposing codes as follows:
|
|
*
|
|
* auto sp = new Sprite();
|
|
* sp->init(); // Texture was set to nullptr, in order to make opacity and color to work correctly, we need to create a 2x2 white texture.
|
|
*
|
|
* The test is in "TestCpp/SpriteTest/Sprite without texture".
|
|
*/
|
|
static unsigned char cc_2x2_white_image[] = {
|
|
// RGBA8888
|
|
0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF,
|
|
0xFF, 0xFF, 0xFF, 0xFF
|
|
};
|
|
|
|
#define CC_2x2_WHITE_IMAGE_KEY "/cc_2x2_white_image"
|
|
|
|
void Sprite::setTexture(const std::string &filename)
|
|
{
|
|
Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);
|
|
setTexture(texture);
|
|
|
|
Rect rect = Rect::ZERO;
|
|
rect.size = texture->getContentSize();
|
|
setTextureRect(rect);
|
|
}
|
|
|
|
void Sprite::setTexture(Texture2D *texture)
|
|
{
|
|
// If batchnode, then texture id should be the same
|
|
CCASSERT(! _batchNode || texture->getName() == _batchNode->getTexture()->getName(), "CCSprite: Batched sprites should use the same texture as the batchnode");
|
|
// accept texture==nil as argument
|
|
CCASSERT( !texture || dynamic_cast<Texture2D*>(texture), "setTexture expects a Texture2D. Invalid argument");
|
|
|
|
if (texture == nullptr)
|
|
{
|
|
// Gets the texture by key firstly.
|
|
texture = Director::getInstance()->getTextureCache()->getTextureForKey(CC_2x2_WHITE_IMAGE_KEY);
|
|
|
|
// If texture wasn't in cache, create it from RAW data.
|
|
if (texture == nullptr)
|
|
{
|
|
Image* image = new Image();
|
|
bool isOK = image->initWithRawData(cc_2x2_white_image, sizeof(cc_2x2_white_image), 2, 2, 8);
|
|
CCASSERT(isOK, "The 2x2 empty texture was created unsuccessfully.");
|
|
|
|
texture = Director::getInstance()->getTextureCache()->addImage(image, CC_2x2_WHITE_IMAGE_KEY);
|
|
CC_SAFE_RELEASE(image);
|
|
}
|
|
}
|
|
|
|
if (!_batchNode && _texture != texture)
|
|
{
|
|
CC_SAFE_RETAIN(texture);
|
|
CC_SAFE_RELEASE(_texture);
|
|
_texture = texture;
|
|
updateBlendFunc();
|
|
}
|
|
}
|
|
|
|
Texture2D* Sprite::getTexture() const
|
|
{
|
|
return _texture;
|
|
}
|
|
|
|
void Sprite::setTextureRect(const Rect& rect)
|
|
{
|
|
setTextureRect(rect, false, rect.size);
|
|
}
|
|
|
|
void Sprite::setTextureRect(const Rect& rect, bool rotated, const Size& untrimmedSize)
|
|
{
|
|
_rectRotated = rotated;
|
|
|
|
setContentSize(untrimmedSize);
|
|
setVertexRect(rect);
|
|
setTextureCoords(rect);
|
|
|
|
Point relativeOffset = _unflippedOffsetPositionFromCenter;
|
|
|
|
// issue #732
|
|
if (_flippedX)
|
|
{
|
|
relativeOffset.x = -relativeOffset.x;
|
|
}
|
|
if (_flippedY)
|
|
{
|
|
relativeOffset.y = -relativeOffset.y;
|
|
}
|
|
|
|
_offsetPosition.x = relativeOffset.x + (_contentSize.width - _rect.size.width) / 2;
|
|
_offsetPosition.y = relativeOffset.y + (_contentSize.height - _rect.size.height) / 2;
|
|
|
|
// rendering using batch node
|
|
if (_batchNode)
|
|
{
|
|
// update dirty_, don't update recursiveDirty_
|
|
setDirty(true);
|
|
}
|
|
else
|
|
{
|
|
// self rendering
|
|
|
|
// Atlas: Vertex
|
|
float x1 = 0 + _offsetPosition.x;
|
|
float y1 = 0 + _offsetPosition.y;
|
|
float x2 = x1 + _rect.size.width;
|
|
float y2 = y1 + _rect.size.height;
|
|
|
|
// Don't update Z.
|
|
_quad.bl.vertices = Vertex3F(x1, y1, 0);
|
|
_quad.br.vertices = Vertex3F(x2, y1, 0);
|
|
_quad.tl.vertices = Vertex3F(x1, y2, 0);
|
|
_quad.tr.vertices = Vertex3F(x2, y2, 0);
|
|
}
|
|
}
|
|
|
|
// override this method to generate "double scale" sprites
|
|
void Sprite::setVertexRect(const Rect& rect)
|
|
{
|
|
_rect = rect;
|
|
}
|
|
|
|
void Sprite::setTextureCoords(Rect rect)
|
|
{
|
|
rect = CC_RECT_POINTS_TO_PIXELS(rect);
|
|
|
|
Texture2D *tex = _batchNode ? _textureAtlas->getTexture() : _texture;
|
|
if (! tex)
|
|
{
|
|
return;
|
|
}
|
|
|
|
float atlasWidth = (float)tex->getPixelsWide();
|
|
float atlasHeight = (float)tex->getPixelsHigh();
|
|
|
|
float left, right, top, bottom;
|
|
|
|
if (_rectRotated)
|
|
{
|
|
#if CC_FIX_ARTIFACTS_BY_STRECHING_TEXEL
|
|
left = (2*rect.origin.x+1)/(2*atlasWidth);
|
|
right = left+(rect.size.height*2-2)/(2*atlasWidth);
|
|
top = (2*rect.origin.y+1)/(2*atlasHeight);
|
|
bottom = top+(rect.size.width*2-2)/(2*atlasHeight);
|
|
#else
|
|
left = rect.origin.x/atlasWidth;
|
|
right = (rect.origin.x+rect.size.height) / atlasWidth;
|
|
top = rect.origin.y/atlasHeight;
|
|
bottom = (rect.origin.y+rect.size.width) / atlasHeight;
|
|
#endif // CC_FIX_ARTIFACTS_BY_STRECHING_TEXEL
|
|
|
|
if (_flippedX)
|
|
{
|
|
CC_SWAP(top, bottom, float);
|
|
}
|
|
|
|
if (_flippedY)
|
|
{
|
|
CC_SWAP(left, right, float);
|
|
}
|
|
|
|
_quad.bl.texCoords.u = left;
|
|
_quad.bl.texCoords.v = top;
|
|
_quad.br.texCoords.u = left;
|
|
_quad.br.texCoords.v = bottom;
|
|
_quad.tl.texCoords.u = right;
|
|
_quad.tl.texCoords.v = top;
|
|
_quad.tr.texCoords.u = right;
|
|
_quad.tr.texCoords.v = bottom;
|
|
}
|
|
else
|
|
{
|
|
#if CC_FIX_ARTIFACTS_BY_STRECHING_TEXEL
|
|
left = (2*rect.origin.x+1)/(2*atlasWidth);
|
|
right = left + (rect.size.width*2-2)/(2*atlasWidth);
|
|
top = (2*rect.origin.y+1)/(2*atlasHeight);
|
|
bottom = top + (rect.size.height*2-2)/(2*atlasHeight);
|
|
#else
|
|
left = rect.origin.x/atlasWidth;
|
|
right = (rect.origin.x + rect.size.width) / atlasWidth;
|
|
top = rect.origin.y/atlasHeight;
|
|
bottom = (rect.origin.y + rect.size.height) / atlasHeight;
|
|
#endif // ! CC_FIX_ARTIFACTS_BY_STRECHING_TEXEL
|
|
|
|
if(_flippedX)
|
|
{
|
|
CC_SWAP(left,right,float);
|
|
}
|
|
|
|
if(_flippedY)
|
|
{
|
|
CC_SWAP(top,bottom,float);
|
|
}
|
|
|
|
_quad.bl.texCoords.u = left;
|
|
_quad.bl.texCoords.v = bottom;
|
|
_quad.br.texCoords.u = right;
|
|
_quad.br.texCoords.v = bottom;
|
|
_quad.tl.texCoords.u = left;
|
|
_quad.tl.texCoords.v = top;
|
|
_quad.tr.texCoords.u = right;
|
|
_quad.tr.texCoords.v = top;
|
|
}
|
|
}
|
|
|
|
void Sprite::updateTransform(void)
|
|
{
|
|
CCASSERT(_batchNode, "updateTransform is only valid when Sprite is being rendered using an SpriteBatchNode");
|
|
|
|
#ifdef CC_USE_PHYSICS
|
|
if (updatePhysicsTransform())
|
|
{
|
|
setDirty(true);
|
|
};
|
|
#endif
|
|
|
|
// recalculate matrix only if it is dirty
|
|
if( isDirty() ) {
|
|
|
|
// If it is not visible, or one of its ancestors is not visible, then do nothing:
|
|
if( !_visible || ( _parent && _parent != _batchNode && static_cast<Sprite*>(_parent)->_shouldBeHidden) )
|
|
{
|
|
_quad.br.vertices = _quad.tl.vertices = _quad.tr.vertices = _quad.bl.vertices = Vertex3F(0,0,0);
|
|
_shouldBeHidden = true;
|
|
}
|
|
else
|
|
{
|
|
_shouldBeHidden = false;
|
|
|
|
if( ! _parent || _parent == _batchNode )
|
|
{
|
|
_transformToBatch = getNodeToParentTransform();
|
|
}
|
|
else
|
|
{
|
|
CCASSERT( dynamic_cast<Sprite*>(_parent), "Logic error in Sprite. Parent must be a Sprite");
|
|
kmMat4 nodeToParent = getNodeToParentTransform();
|
|
kmMat4 parentTransform = static_cast<Sprite*>(_parent)->_transformToBatch;
|
|
kmMat4Multiply(&_transformToBatch, &nodeToParent, &parentTransform);
|
|
}
|
|
|
|
//
|
|
// calculate the Quad based on the Affine Matrix
|
|
//
|
|
|
|
Size size = _rect.size;
|
|
|
|
float x1 = _offsetPosition.x;
|
|
float y1 = _offsetPosition.y;
|
|
|
|
float x2 = x1 + size.width;
|
|
float y2 = y1 + size.height;
|
|
float x = _transformToBatch.mat[12];
|
|
float y = _transformToBatch.mat[13];
|
|
|
|
float cr = _transformToBatch.mat[0];
|
|
float sr = _transformToBatch.mat[1];
|
|
float cr2 = _transformToBatch.mat[5];
|
|
float sr2 = -_transformToBatch.mat[4];
|
|
float ax = x1 * cr - y1 * sr2 + x;
|
|
float ay = x1 * sr + y1 * cr2 + y;
|
|
|
|
float bx = x2 * cr - y1 * sr2 + x;
|
|
float by = x2 * sr + y1 * cr2 + y;
|
|
|
|
float cx = x2 * cr - y2 * sr2 + x;
|
|
float cy = x2 * sr + y2 * cr2 + y;
|
|
|
|
float dx = x1 * cr - y2 * sr2 + x;
|
|
float dy = x1 * sr + y2 * cr2 + y;
|
|
|
|
_quad.bl.vertices = Vertex3F( RENDER_IN_SUBPIXEL(ax), RENDER_IN_SUBPIXEL(ay), _vertexZ );
|
|
_quad.br.vertices = Vertex3F( RENDER_IN_SUBPIXEL(bx), RENDER_IN_SUBPIXEL(by), _vertexZ );
|
|
_quad.tl.vertices = Vertex3F( RENDER_IN_SUBPIXEL(dx), RENDER_IN_SUBPIXEL(dy), _vertexZ );
|
|
_quad.tr.vertices = Vertex3F( RENDER_IN_SUBPIXEL(cx), RENDER_IN_SUBPIXEL(cy), _vertexZ );
|
|
}
|
|
|
|
// MARMALADE CHANGE: ADDED CHECK FOR nullptr, TO PERMIT SPRITES WITH NO BATCH NODE / TEXTURE ATLAS
|
|
if (_textureAtlas)
|
|
{
|
|
_textureAtlas->updateQuad(&_quad, _atlasIndex);
|
|
}
|
|
|
|
_recursiveDirty = false;
|
|
setDirty(false);
|
|
}
|
|
|
|
// MARMALADE CHANGED
|
|
// recursively iterate over children
|
|
/* if( _hasChildren )
|
|
{
|
|
// MARMALADE: CHANGED TO USE Node*
|
|
// NOTE THAT WE HAVE ALSO DEFINED virtual Node::updateTransform()
|
|
arrayMakeObjectsPerformSelector(_children, updateTransform, Sprite*);
|
|
}*/
|
|
Node::updateTransform();
|
|
|
|
#if CC_SPRITE_DEBUG_DRAW
|
|
// draw bounding box
|
|
Point vertices[4] = {
|
|
Point( _quad.bl.vertices.x, _quad.bl.vertices.y ),
|
|
Point( _quad.br.vertices.x, _quad.br.vertices.y ),
|
|
Point( _quad.tr.vertices.x, _quad.tr.vertices.y ),
|
|
Point( _quad.tl.vertices.x, _quad.tl.vertices.y ),
|
|
};
|
|
ccDrawPoly(vertices, 4, true);
|
|
#endif // CC_SPRITE_DEBUG_DRAW
|
|
}
|
|
|
|
// draw
|
|
|
|
//void Sprite::draw(void)
|
|
//{
|
|
// CC_PROFILER_START_CATEGORY(kProfilerCategorySprite, "CCSprite - draw");
|
|
//
|
|
// CCASSERT(!_batchNode, "If Sprite is being rendered by SpriteBatchNode, Sprite#draw SHOULD NOT be called");
|
|
//
|
|
// CC_NODE_DRAW_SETUP();
|
|
//
|
|
// GL::blendFunc( _blendFunc.src, _blendFunc.dst );
|
|
//
|
|
// GL::bindTexture2D( _texture->getName() );
|
|
// GL::enableVertexAttribs( GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX );
|
|
//
|
|
//#define kQuadSize sizeof(_quad.bl)
|
|
//#ifdef EMSCRIPTEN
|
|
// long offset = 0;
|
|
// setGLBufferData(&_quad, 4 * kQuadSize, 0);
|
|
//#else
|
|
// long offset = (long)&_quad;
|
|
//#endif // EMSCRIPTEN
|
|
//
|
|
// // vertex
|
|
// int diff = offsetof( V3F_C4B_T2F, vertices);
|
|
// glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, kQuadSize, (void*) (offset + diff));
|
|
//
|
|
// // texCoods
|
|
// diff = offsetof( V3F_C4B_T2F, texCoords);
|
|
// glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORDS, 2, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff));
|
|
//
|
|
// // color
|
|
// diff = offsetof( V3F_C4B_T2F, colors);
|
|
// glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (void*)(offset + diff));
|
|
//
|
|
//
|
|
// glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
|
//
|
|
// CHECK_GL_ERROR_DEBUG();
|
|
//
|
|
//
|
|
//#if CC_SPRITE_DEBUG_DRAW == 1
|
|
// // draw bounding box
|
|
// Point vertices[4]={
|
|
// Point(_quad.tl.vertices.x,_quad.tl.vertices.y),
|
|
// Point(_quad.bl.vertices.x,_quad.bl.vertices.y),
|
|
// Point(_quad.br.vertices.x,_quad.br.vertices.y),
|
|
// Point(_quad.tr.vertices.x,_quad.tr.vertices.y),
|
|
// };
|
|
// ccDrawPoly(vertices, 4, true);
|
|
//#elif CC_SPRITE_DEBUG_DRAW == 2
|
|
// // draw texture box
|
|
// Size s = this->getTextureRect().size;
|
|
// Point offsetPix = this->getOffsetPosition();
|
|
// Point vertices[4] = {
|
|
// Point(offsetPix.x,offsetPix.y), Point(offsetPix.x+s.width,offsetPix.y),
|
|
// Point(offsetPix.x+s.width,offsetPix.y+s.height), Point(offsetPix.x,offsetPix.y+s.height)
|
|
// };
|
|
// ccDrawPoly(vertices, 4, true);
|
|
//#endif // CC_SPRITE_DEBUG_DRAW
|
|
//
|
|
// CC_INCREMENT_GL_DRAWS(1);
|
|
//
|
|
// CC_PROFILER_STOP_CATEGORY(kProfilerCategorySprite, "CCSprite - draw");
|
|
//}
|
|
|
|
void Sprite::draw(void)
|
|
{
|
|
//TODO implement z order
|
|
QuadCommand* renderCommand = QuadCommand::getCommandPool().generateCommand();
|
|
renderCommand->init(0, _vertexZ, _texture->getName(), _shaderProgram, _blendFunc, &_quad, 1, _modelViewTransform);
|
|
|
|
// if(!culling())
|
|
// {
|
|
// renderCommand->releaseToCommandPool();
|
|
// }
|
|
// else
|
|
{
|
|
Director::getInstance()->getRenderer()->addCommand(renderCommand);
|
|
}
|
|
}
|
|
|
|
bool Sprite::culling() const
|
|
{
|
|
Frustum* frustum = Director::getInstance()->getFrustum();
|
|
//TODO optimize this transformation, should use parent's transformation instead
|
|
kmMat4 worldTM = getNodeToWorldTransform();
|
|
//generate aabb
|
|
|
|
//
|
|
// calculate the Quad based on the Affine Matrix
|
|
//
|
|
Rect newRect = RectApplyTransform(_rect, worldTM);
|
|
|
|
kmVec3 point = {newRect.getMinX(), newRect.getMinY(), _vertexZ};
|
|
|
|
AABB aabb(point,point);
|
|
kmVec3Fill(&point,newRect.getMaxX(), newRect.getMinY(), _vertexZ);
|
|
aabb.expand(point);
|
|
kmVec3Fill(&point,newRect.getMinX(), newRect.getMaxY(), _vertexZ);
|
|
aabb.expand(point);
|
|
kmVec3Fill(&point,newRect.getMaxX(), newRect.getMaxY(), _vertexZ);
|
|
aabb.expand(point);
|
|
|
|
return Frustum::IntersectResult::OUTSIDE !=frustum->intersectAABB(aabb);
|
|
}
|
|
|
|
void Sprite::updateQuadVertices()
|
|
{
|
|
#ifdef CC_USE_PHYSICS
|
|
updatePhysicsTransform();
|
|
setDirty(true);
|
|
#endif
|
|
|
|
//TODO optimize the performance cache affineTransformation
|
|
|
|
// recalculate matrix only if it is dirty
|
|
if(isDirty())
|
|
{
|
|
|
|
// if( ! _parent || _parent == (Node*)_batchNode )
|
|
// {
|
|
// _transformToBatch = getNodeToParentTransform();
|
|
// }
|
|
// else
|
|
// {
|
|
// CCASSERT( dynamic_cast<NewSprite*>(_parent), "Logic error in Sprite. Parent must be a Sprite");
|
|
// _transformToBatch = AffineTransformConcat( getNodeToParentTransform() , static_cast<NewSprite*>(_parent)->_transformToBatch );
|
|
// }
|
|
|
|
//TODO optimize this transformation, should use parent's transformation instead
|
|
_transformToBatch = getNodeToWorldTransform();
|
|
|
|
//
|
|
// calculate the Quad based on the Affine Matrix
|
|
//
|
|
Rect newRect = RectApplyTransform(_rect, _transformToBatch);
|
|
|
|
_quad.bl.vertices = Vertex3F( RENDER_IN_SUBPIXEL(newRect.getMinX()), RENDER_IN_SUBPIXEL(newRect.getMinY()), _vertexZ );
|
|
_quad.br.vertices = Vertex3F( RENDER_IN_SUBPIXEL(newRect.getMaxX()), RENDER_IN_SUBPIXEL(newRect.getMinY()), _vertexZ );
|
|
_quad.tl.vertices = Vertex3F( RENDER_IN_SUBPIXEL(newRect.getMinX()), RENDER_IN_SUBPIXEL(newRect.getMaxY()), _vertexZ );
|
|
_quad.tr.vertices = Vertex3F( RENDER_IN_SUBPIXEL(newRect.getMaxX()), RENDER_IN_SUBPIXEL(newRect.getMaxY()), _vertexZ );
|
|
|
|
_recursiveDirty = false;
|
|
setDirty(false);
|
|
}
|
|
}
|
|
|
|
// Node overrides
|
|
|
|
void Sprite::addChild(Node *child)
|
|
{
|
|
Node::addChild(child);
|
|
}
|
|
|
|
void Sprite::addChild(Node *child, int zOrder)
|
|
{
|
|
Node::addChild(child, zOrder);
|
|
}
|
|
|
|
void Sprite::addChild(Node *child, int zOrder, int tag)
|
|
{
|
|
CCASSERT(child != nullptr, "Argument must be non-nullptr");
|
|
|
|
if (_batchNode)
|
|
{
|
|
Sprite* childSprite = dynamic_cast<Sprite*>(child);
|
|
CCASSERT( childSprite, "CCSprite only supports Sprites as children when using SpriteBatchNode");
|
|
CCASSERT(childSprite->getTexture()->getName() == _textureAtlas->getTexture()->getName(), "");
|
|
//put it in descendants array of batch node
|
|
_batchNode->appendChild(childSprite);
|
|
|
|
if (!_reorderChildDirty)
|
|
{
|
|
setReorderChildDirtyRecursively();
|
|
}
|
|
}
|
|
//CCNode already sets isReorderChildDirty_ so this needs to be after batchNode check
|
|
Node::addChild(child, zOrder, tag);
|
|
_hasChildren = true;
|
|
}
|
|
|
|
void Sprite::reorderChild(Node *child, int zOrder)
|
|
{
|
|
CCASSERT(child != nullptr, "");
|
|
CCASSERT(_children.contains(child), "");
|
|
|
|
if (zOrder == child->getZOrder())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( _batchNode && ! _reorderChildDirty)
|
|
{
|
|
setReorderChildDirtyRecursively();
|
|
_batchNode->reorderBatch(true);
|
|
}
|
|
|
|
Node::reorderChild(child, zOrder);
|
|
}
|
|
|
|
void Sprite::removeChild(Node *child, bool cleanup)
|
|
{
|
|
if (_batchNode)
|
|
{
|
|
_batchNode->removeSpriteFromAtlas((Sprite*)(child));
|
|
}
|
|
|
|
Node::removeChild(child, cleanup);
|
|
}
|
|
|
|
void Sprite::removeAllChildrenWithCleanup(bool cleanup)
|
|
{
|
|
if (_batchNode)
|
|
{
|
|
for(const auto &child : _children) {
|
|
Sprite* sprite = dynamic_cast<Sprite*>(child);
|
|
if (sprite)
|
|
{
|
|
_batchNode->removeSpriteFromAtlas(sprite);
|
|
}
|
|
}
|
|
}
|
|
|
|
Node::removeAllChildrenWithCleanup(cleanup);
|
|
|
|
_hasChildren = false;
|
|
}
|
|
|
|
void Sprite::sortAllChildren()
|
|
{
|
|
if (_reorderChildDirty)
|
|
{
|
|
#if 0
|
|
int i = 0, j = 0, length = _children->count();
|
|
|
|
// insertion sort
|
|
for(i=1; i<length; i++)
|
|
{
|
|
j = i-1;
|
|
auto tempI = static_cast<Node*>( _children->getObjectAtIndex(i) );
|
|
auto tempJ = static_cast<Node*>( _children->getObjectAtIndex(j) );
|
|
|
|
//continue moving element downwards while zOrder is smaller or when zOrder is the same but mutatedIndex is smaller
|
|
while(j>=0 && ( tempI->getZOrder() < tempJ->getZOrder() ||
|
|
( tempI->getZOrder() == tempJ->getZOrder() &&
|
|
tempI->getOrderOfArrival() < tempJ->getOrderOfArrival() ) ) )
|
|
{
|
|
_children->fastSetObject( tempJ, j+1 );
|
|
j = j-1;
|
|
if(j>=0)
|
|
tempJ = static_cast<Node*>( _children->getObjectAtIndex(j) );
|
|
}
|
|
_children->fastSetObject(tempI, j+1);
|
|
}
|
|
#else
|
|
std::sort(std::begin(_children), std::end(_children), nodeComparisonLess);
|
|
#endif
|
|
|
|
if ( _batchNode)
|
|
{
|
|
for(const auto &child : _children)
|
|
child->sortAllChildren();
|
|
}
|
|
|
|
_reorderChildDirty = false;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Node property overloads
|
|
// used only when parent is SpriteBatchNode
|
|
//
|
|
|
|
void Sprite::setReorderChildDirtyRecursively(void)
|
|
{
|
|
//only set parents flag the first time
|
|
if ( ! _reorderChildDirty )
|
|
{
|
|
_reorderChildDirty = true;
|
|
Node* node = static_cast<Node*>(_parent);
|
|
while (node && node != _batchNode)
|
|
{
|
|
static_cast<Sprite*>(node)->setReorderChildDirtyRecursively();
|
|
node=node->getParent();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Sprite::setDirtyRecursively(bool bValue)
|
|
{
|
|
_recursiveDirty = bValue;
|
|
setDirty(bValue);
|
|
// recursively set dirty
|
|
if (_hasChildren)
|
|
{
|
|
for(const auto &child: _children) {
|
|
Sprite* sp = dynamic_cast<Sprite*>(child);
|
|
if (sp)
|
|
{
|
|
sp->setDirtyRecursively(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// XXX HACK: optimization
|
|
#define SET_DIRTY_RECURSIVELY() { \
|
|
if (! _recursiveDirty) { \
|
|
_recursiveDirty = true; \
|
|
setDirty(true); \
|
|
if ( _hasChildren) \
|
|
setDirtyRecursively(true); \
|
|
} \
|
|
}
|
|
|
|
void Sprite::setPosition(const Point& pos)
|
|
{
|
|
Node::setPosition(pos);
|
|
SET_DIRTY_RECURSIVELY();
|
|
}
|
|
|
|
void Sprite::setPosition(float x, float y)
|
|
{
|
|
Node::setPosition(x, y);
|
|
SET_DIRTY_RECURSIVELY();
|
|
}
|
|
|
|
void Sprite::setRotation(float rotation)
|
|
{
|
|
Node::setRotation(rotation);
|
|
|
|
SET_DIRTY_RECURSIVELY();
|
|
}
|
|
|
|
void Sprite::setRotationX(float fRotationX)
|
|
{
|
|
Node::setRotationX(fRotationX);
|
|
SET_DIRTY_RECURSIVELY();
|
|
}
|
|
|
|
void Sprite::setRotationY(float fRotationY)
|
|
{
|
|
Node::setRotationY(fRotationY);
|
|
SET_DIRTY_RECURSIVELY();
|
|
}
|
|
|
|
void Sprite::setSkewX(float sx)
|
|
{
|
|
Node::setSkewX(sx);
|
|
SET_DIRTY_RECURSIVELY();
|
|
}
|
|
|
|
void Sprite::setSkewY(float sy)
|
|
{
|
|
Node::setSkewY(sy);
|
|
SET_DIRTY_RECURSIVELY();
|
|
}
|
|
|
|
void Sprite::setScaleX(float scaleX)
|
|
{
|
|
Node::setScaleX(scaleX);
|
|
SET_DIRTY_RECURSIVELY();
|
|
}
|
|
|
|
void Sprite::setScaleY(float scaleY)
|
|
{
|
|
Node::setScaleY(scaleY);
|
|
SET_DIRTY_RECURSIVELY();
|
|
}
|
|
|
|
void Sprite::setScale(float fScale)
|
|
{
|
|
Node::setScale(fScale);
|
|
SET_DIRTY_RECURSIVELY();
|
|
}
|
|
|
|
void Sprite::setScale(float scaleX, float scaleY)
|
|
{
|
|
Node::setScale(scaleX, scaleY);
|
|
SET_DIRTY_RECURSIVELY();
|
|
}
|
|
|
|
void Sprite::setVertexZ(float fVertexZ)
|
|
{
|
|
Node::setVertexZ(fVertexZ);
|
|
SET_DIRTY_RECURSIVELY();
|
|
}
|
|
|
|
void Sprite::setAnchorPoint(const Point& anchor)
|
|
{
|
|
Node::setAnchorPoint(anchor);
|
|
SET_DIRTY_RECURSIVELY();
|
|
}
|
|
|
|
void Sprite::ignoreAnchorPointForPosition(bool value)
|
|
{
|
|
CCASSERT(! _batchNode, "ignoreAnchorPointForPosition is invalid in Sprite");
|
|
Node::ignoreAnchorPointForPosition(value);
|
|
}
|
|
|
|
void Sprite::setVisible(bool bVisible)
|
|
{
|
|
Node::setVisible(bVisible);
|
|
SET_DIRTY_RECURSIVELY();
|
|
}
|
|
|
|
void Sprite::setFlippedX(bool flippedX)
|
|
{
|
|
if (_flippedX != flippedX)
|
|
{
|
|
_flippedX = flippedX;
|
|
setTextureRect(_rect, _rectRotated, _contentSize);
|
|
}
|
|
}
|
|
|
|
bool Sprite::isFlippedX(void) const
|
|
{
|
|
return _flippedX;
|
|
}
|
|
|
|
void Sprite::setFlippedY(bool flippedY)
|
|
{
|
|
if (_flippedY != flippedY)
|
|
{
|
|
_flippedY = flippedY;
|
|
setTextureRect(_rect, _rectRotated, _contentSize);
|
|
}
|
|
}
|
|
|
|
bool Sprite::isFlippedY(void) const
|
|
{
|
|
return _flippedY;
|
|
}
|
|
|
|
//
|
|
// RGBA protocol
|
|
//
|
|
|
|
void Sprite::updateColor(void)
|
|
{
|
|
Color4B color4( _displayedColor.r, _displayedColor.g, _displayedColor.b, _displayedOpacity );
|
|
|
|
// special opacity for premultiplied textures
|
|
if (_opacityModifyRGB)
|
|
{
|
|
color4.r *= _displayedOpacity/255.0f;
|
|
color4.g *= _displayedOpacity/255.0f;
|
|
color4.b *= _displayedOpacity/255.0f;
|
|
}
|
|
|
|
_quad.bl.colors = color4;
|
|
_quad.br.colors = color4;
|
|
_quad.tl.colors = color4;
|
|
_quad.tr.colors = color4;
|
|
|
|
// renders using batch node
|
|
if (_batchNode)
|
|
{
|
|
if (_atlasIndex != INDEX_NOT_INITIALIZED)
|
|
{
|
|
_textureAtlas->updateQuad(&_quad, _atlasIndex);
|
|
}
|
|
else
|
|
{
|
|
// no need to set it recursively
|
|
// update dirty_, don't update recursiveDirty_
|
|
setDirty(true);
|
|
}
|
|
}
|
|
|
|
// self render
|
|
// do nothing
|
|
}
|
|
|
|
void Sprite::setOpacity(GLubyte opacity)
|
|
{
|
|
NodeRGBA::setOpacity(opacity);
|
|
|
|
updateColor();
|
|
}
|
|
|
|
void Sprite::setColor(const Color3B& color3)
|
|
{
|
|
NodeRGBA::setColor(color3);
|
|
|
|
updateColor();
|
|
}
|
|
|
|
void Sprite::setOpacityModifyRGB(bool modify)
|
|
{
|
|
if (_opacityModifyRGB != modify)
|
|
{
|
|
_opacityModifyRGB = modify;
|
|
updateColor();
|
|
}
|
|
}
|
|
|
|
bool Sprite::isOpacityModifyRGB(void) const
|
|
{
|
|
return _opacityModifyRGB;
|
|
}
|
|
|
|
void Sprite::updateDisplayedColor(const Color3B& parentColor)
|
|
{
|
|
NodeRGBA::updateDisplayedColor(parentColor);
|
|
|
|
updateColor();
|
|
}
|
|
|
|
void Sprite::updateDisplayedOpacity(GLubyte opacity)
|
|
{
|
|
NodeRGBA::updateDisplayedOpacity(opacity);
|
|
|
|
updateColor();
|
|
}
|
|
|
|
// Frames
|
|
|
|
void Sprite::setSpriteFrame(const std::string &spriteFrameName)
|
|
{
|
|
SpriteFrameCache *cache = SpriteFrameCache::getInstance();
|
|
SpriteFrame *spriteFrame = cache->getSpriteFrameByName(spriteFrameName);
|
|
|
|
CCASSERT(spriteFrame, "Invalid spriteFrameName");
|
|
|
|
setSpriteFrame(spriteFrame);
|
|
}
|
|
|
|
void Sprite::setSpriteFrame(SpriteFrame *spriteFrame)
|
|
{
|
|
_unflippedOffsetPositionFromCenter = spriteFrame->getOffset();
|
|
|
|
Texture2D *texture = spriteFrame->getTexture();
|
|
// update texture before updating texture rect
|
|
if (texture != _texture)
|
|
{
|
|
setTexture(texture);
|
|
}
|
|
|
|
// update rect
|
|
_rectRotated = spriteFrame->isRotated();
|
|
setTextureRect(spriteFrame->getRect(), _rectRotated, spriteFrame->getOriginalSize());
|
|
}
|
|
|
|
void Sprite::setDisplayFrameWithAnimationName(const std::string& animationName, ssize_t frameIndex)
|
|
{
|
|
CCASSERT(animationName.size()>0, "CCSprite#setDisplayFrameWithAnimationName. animationName must not be nullptr");
|
|
|
|
Animation *a = AnimationCache::getInstance()->getAnimation(animationName);
|
|
|
|
CCASSERT(a, "CCSprite#setDisplayFrameWithAnimationName: Frame not found");
|
|
|
|
AnimationFrame* frame = a->getFrames().at(frameIndex);
|
|
|
|
CCASSERT(frame, "CCSprite#setDisplayFrame. Invalid frame");
|
|
|
|
setSpriteFrame(frame->getSpriteFrame());
|
|
}
|
|
|
|
bool Sprite::isFrameDisplayed(SpriteFrame *frame) const
|
|
{
|
|
Rect r = frame->getRect();
|
|
|
|
return (r.equals(_rect) &&
|
|
frame->getTexture()->getName() == _texture->getName() &&
|
|
frame->getOffset().equals(_unflippedOffsetPositionFromCenter));
|
|
}
|
|
|
|
SpriteFrame* Sprite::getSpriteFrame() const
|
|
{
|
|
return SpriteFrame::createWithTexture(_texture,
|
|
CC_RECT_POINTS_TO_PIXELS(_rect),
|
|
_rectRotated,
|
|
CC_POINT_POINTS_TO_PIXELS(_unflippedOffsetPositionFromCenter),
|
|
CC_SIZE_POINTS_TO_PIXELS(_contentSize));
|
|
}
|
|
|
|
SpriteBatchNode* Sprite::getBatchNode()
|
|
{
|
|
return _batchNode;
|
|
}
|
|
|
|
void Sprite::setBatchNode(SpriteBatchNode *spriteBatchNode)
|
|
{
|
|
_batchNode = spriteBatchNode; // weak reference
|
|
|
|
// self render
|
|
if( ! _batchNode ) {
|
|
_atlasIndex = INDEX_NOT_INITIALIZED;
|
|
setTextureAtlas(nullptr);
|
|
_recursiveDirty = false;
|
|
setDirty(false);
|
|
|
|
float x1 = _offsetPosition.x;
|
|
float y1 = _offsetPosition.y;
|
|
float x2 = x1 + _rect.size.width;
|
|
float y2 = y1 + _rect.size.height;
|
|
_quad.bl.vertices = Vertex3F( x1, y1, 0 );
|
|
_quad.br.vertices = Vertex3F( x2, y1, 0 );
|
|
_quad.tl.vertices = Vertex3F( x1, y2, 0 );
|
|
_quad.tr.vertices = Vertex3F( x2, y2, 0 );
|
|
|
|
} else {
|
|
|
|
// using batch
|
|
kmMat4Identity(&_transformToBatch);
|
|
setTextureAtlas(_batchNode->getTextureAtlas()); // weak ref
|
|
}
|
|
}
|
|
|
|
// Texture protocol
|
|
|
|
void Sprite::updateBlendFunc(void)
|
|
{
|
|
CCASSERT(! _batchNode, "CCSprite: updateBlendFunc doesn't work when the sprite is rendered using a SpriteBatchNode");
|
|
|
|
// it is possible to have an untextured sprite
|
|
if (! _texture || ! _texture->hasPremultipliedAlpha())
|
|
{
|
|
_blendFunc = BlendFunc::ALPHA_NON_PREMULTIPLIED;
|
|
setOpacityModifyRGB(false);
|
|
}
|
|
else
|
|
{
|
|
_blendFunc = BlendFunc::ALPHA_PREMULTIPLIED;
|
|
setOpacityModifyRGB(true);
|
|
}
|
|
}
|
|
|
|
std::string Sprite::getDescription() const
|
|
{
|
|
int texture_id = -1;
|
|
if( _batchNode )
|
|
texture_id = _batchNode->getTextureAtlas()->getTexture()->getName();
|
|
else
|
|
texture_id = _texture->getName();
|
|
return StringUtils::format("<Sprite | Tag = %d, TextureID = %d>", _tag, texture_id );
|
|
}
|
|
|
|
NS_CC_END
|