Merge branch 'upto-0.99.5' of https://github.com/yangws/cocos2d-x into yangws-upto-0.99.5

This commit is contained in:
minggo 2010-12-27 10:55:59 +08:00
commit 4e35a660dc
12 changed files with 1443 additions and 1197 deletions

View File

@ -23,35 +23,38 @@ THE SOFTWARE.
****************************************************************************/
#ifndef __CCMOTION_STREAK_H__
#define __CCMOTION_STREAK_H__
#include "CCNode.h"
#include "CCProtocols.h"
namespace cocos2d {
class CCRibbon;
/**
* @brief CCMotionStreak manages a Ribbon based on it's motion in absolute space.
* You construct it with a fadeTime, minimum segment size, texture path, texture
* length and color. The fadeTime controls how long it takes each vertex in
* the streak to fade out, the minimum segment size it how many pixels the
* streak will move before adding a new ribbon segement, and the texture
* length is the how many pixels the texture is stretched across. The texture
* is vertically aligned along the streak segemnts.
*
* Limitations:
* CCMotionStreak, by default, will use the GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA blending function.
* This blending function might not be the correct one for certain textures.
* But you can change it by using:
* [obj setBlendFunc: (ccBlendfunc) {new_src_blend_func, new_dst_blend_func}];
*
* @since v0.8.1
*/
class CCX_DLL CCMotionStreak : public CCNode, public CCTextureProtocol
{
class CCRibbon;
/**
* @brief CCMotionStreak manages a Ribbon based on it's motion in absolute space.
* You construct it with a fadeTime, minimum segment size, texture path, texture
* length and color. The fadeTime controls how long it takes each vertex in
* the streak to fade out, the minimum segment size it how many pixels the
* streak will move before adding a new ribbon segement, and the texture
* length is the how many pixels the texture is stretched across. The texture
* is vertically aligned along the streak segemnts.
*
* Limitations:
* CCMotionStreak, by default, will use the GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA blending function.
* This blending function might not be the correct one for certain textures.
* But you can change it by using:
* [obj setBlendFunc: (ccBlendfunc) {new_src_blend_func, new_dst_blend_func}];
*
* @since v0.8.1
*/
class CCX_DLL CCMotionStreak : public CCNode, public CCTextureProtocol
{
/** Ribbon used by MotionStreak (weak reference) */
CCX_PROPERTY_READONLY(CCRibbon*, m_pRibbon, Ribbon)
//CCTextureProtocol methods
CCX_PROPERTY(CCTexture2D*, m_pTexture, Texture)
CCX_PROPERTY(ccBlendFunc, m_tBlendFunc, BlendFunc)
public:
public:
CCMotionStreak(){}
virtual ~CCMotionStreak(){}
/** creates the a MotionStreak. The image will be loaded using the TextureMgr. */
@ -62,11 +65,12 @@ namespace cocos2d {
/** polling function */
void update(ccTime delta);
protected:
protected:
float m_fSegThreshold;
float m_fWidth;
CGPoint m_tLastLocation;
};
};
} // namespace cocos2d
#endif //__CCMOTION_STREAK_H__

View File

@ -80,7 +80,7 @@ public:
static CCProgressTimer* progressWithTexture(CCTexture2D *pTexture);
protected:
CGPoint vertexFromTexCoord(CGPoint texCoord);
ccVertex2F vertexFromTexCoord(CGPoint texCoord);
void updateProgress(void);
void updateBar(void);
void updateRadial(void);

View File

@ -24,50 +24,81 @@ THE SOFTWARE.
#ifndef __CCRENDER_TEXTURE_H__
#define __CCRENDER_TEXTURE_H__
#include "NSData.h"
#include "CCNode.h"
#include "CCSprite.h"
namespace cocos2d {
enum tImageFormat
{
kImageFormatJPG = 0,
kImageFormatPNG = 1
};
/**
@brief RenderTexture is a generic rendering target. To render things into it,
simply construct a render target, call begin on it, call visit on any cocos
scenes or objects to render them, and call end. For convienience, render texture
adds a sprite as it's display child with the results, so you can simply add
the render texture to your scene and treat it like any other CocosNode.
There are also functions for saving the render texture to disk in PNG or JPG format.
@since v0.8.1
enum eImageFormat
{
kCCImageFormatJPG = 0,
kCCImageFormatPNG = 1,
kCCImageFormatRawData = 2
};
/**
@brief CCRenderTexture is a generic rendering target. To render things into it,
simply construct a render target, call begin on it, call visit on any cocos
scenes or objects to render them, and call end. For convienience, render texture
adds a sprite as it's display child with the results, so you can simply add
the render texture to your scene and treat it like any other CocosNode.
There are also functions for saving the render texture to disk in PNG or JPG format.
@since v0.8.1
*/
class CCX_DLL CCRenderTexture : public CCNode
{
/** The CCSprite being used.
The sprite, by default, will use the following blending function: GL_ONE, GL_ONE_MINUS_SRC_ALPHA.
The blending function can be changed in runtime by calling:
- [[renderTexture sprite] setBlendFunc:(ccBlendFunc){GL_ONE, GL_ONE_MINUS_SRC_ALPHA}];
*/
class CCX_DLL CCRenderTexture : public CCNode
{
/** sprite being used */
CCX_PROPERTY(CCSprite*, m_pSprite, Sprite)
public:
public:
CCRenderTexture(){}
virtual ~CCRenderTexture();
/** creates a RenderTexture object with width and height */
static CCRenderTexture *renderTextureWithWidthAndHeight(int width, int height);
/** initializes a RenderTexture object with width and height */
bool initWithWidthAndHeight(int width, int height);
/** creates a RenderTexture object with width and height in Points and a pixel format, only RGB and RGBA formats are valid */
static CCRenderTexture * renderTextureWithWidthAndHeight(int w, int h, CCTexture2DPixelFormat eFormat);
/** creates a RenderTexture object with width and height in Points, pixel format is RGBA8888 */
static CCRenderTexture * renderTextureWithWidthAndHeight(int w, int h);
/** initializes a RenderTexture object with width and height in Points and a pixel format, only RGB and RGBA formats are valid */
bool initWithWidthAndHeight(int w, int h, CCTexture2DPixelFormat eFormat);
/** starts grabbing */
void begin();
/** starts rendering to the texture while clearing the texture first.
This is more efficient then calling -clear first and then -begin */
void beginWithClear(float r, float g, float b, float a);
/** ends grabbing */
void end();
/** get buffer as UIImage */
UIImage *getUIImageFromBuffer();
/** clears the texture with a color */
void clear(float r, float g, float b, float a);
/** saves the texture into a file */
bool saveBuffer(const char *name);
/** saves the texture into a file. The format can be JPG or PNG */
bool saveBuffer(const char *name, int format);
/** clears the texture with a color */
void clear(float r, float g, float b, float a);
protected:
/* get buffer as UIImage, can only save a render buffer which has a RGBA8888 pixel format */
NSData *getUIImageAsDataFromBuffer(int format);
protected:
GLuint m_uFBO;
GLint m_nOldFBO;
CCTexture2D* m_pTexture;
};
GLenum m_ePixelFormat;
GLfloat m_aClearColor[4];
private:
void saveGLstate();
void restoreGLstate();
};
} // namespace cocos2d
#endif //__CCRENDER_TEXTURE_H__

View File

@ -23,39 +23,44 @@ THE SOFTWARE.
****************************************************************************/
#ifndef __CCRIBBON_H__
#define __CCRIBBON_H__
/*#include <GLES/egl.h>*/
#include "CCNode.h"
#include "CCProtocols.h"
namespace cocos2d {
class CCRibbonSegment;
/**
* @brief A CCRibbon is a dynamically generated list of polygons drawn as a single or series
* of triangle strips. The primary use of CCRibbon is as the drawing class of Motion Streak,
* but it is quite useful on it's own. When manually drawing a ribbon, you can call addPointAt
* and pass in the parameters for the next location in the ribbon. The system will automatically
* generate new polygons, texture them accourding to your texture width, etc, etc.
*
* CCRibbon data is stored in a CCRibbonSegment class. This class statically allocates enough verticies and
* texture coordinates for 50 locations (100 verts or 48 triangles). The ribbon class will allocate
* new segments when they are needed, and reuse old ones if available. The idea is to avoid constantly
* allocating new memory and prefer a more static method. However, since there is no way to determine
* the maximum size of some ribbons (motion streaks), a truely static allocation is not possible.
*
* @since v0.8.1
*/
class CCX_DLL CCRibbon : public CCNode, public CCTextureProtocol
{
class CCRibbonSegment;
/**
* @brief A CCRibbon is a dynamically generated list of polygons drawn as a single or series
* of triangle strips. The primary use of CCRibbon is as the drawing class of Motion Streak,
* but it is quite useful on it's own. When manually drawing a ribbon, you can call addPointAt
* and pass in the parameters for the next location in the ribbon. The system will automatically
* generate new polygons, texture them accourding to your texture width, etc, etc.
*
* CCRibbon data is stored in a CCRibbonSegment class. This class statically allocates enough verticies and
* texture coordinates for 50 locations (100 verts or 48 triangles). The ribbon class will allocate
* new segments when they are needed, and reuse old ones if available. The idea is to avoid constantly
* allocating new memory and prefer a more static method. However, since there is no way to determine
* the maximum size of some ribbons (motion streaks), a truely static allocation is not possible.
*
* @since v0.8.1
*/
class CCX_DLL CCRibbon : public CCNode, public CCTextureProtocol
{
/** Texture used by the ribbon. Conforms to CCTextureProtocol protocol */
CCX_PROPERTY(CCTexture2D*, m_pTexture, Texture)
/** Texture lenghts in pixels */
/** Texture lengths in pixels */
CCX_PROPERTY(float, m_fTextureLength, TextureLength)
/** GL blendind function */
CCX_PROPERTY(ccBlendFunc, m_tBlendFunc, BlendFunc)
/** color used by the Ribbon (RGBA) */
CCX_PROPERTY(ccColor4B, m_tColor, Color)
public:
CCRibbon(){}
public:
CCRibbon() : m_pTexture(0), m_pSegments(0), m_pDeletedSegments(0){}
virtual ~CCRibbon();
/** creates the ribbon */
static CCRibbon * ribbonWithWidth(float w, const char *path, float length, ccColor4B color, float fade);
/** init the ribbon */
@ -68,17 +73,17 @@ namespace cocos2d {
float sideOfLine(CGPoint p, CGPoint l1, CGPoint l2);
// super method
virtual void draw();
private:
private:
/** rotates a point around 0, 0 */
CGPoint rotatePoint(CGPoint vec, float rotation);
protected:
protected:
NSMutableArray<CCRibbonSegment*> *m_pSegments;
NSMutableArray<CCRibbonSegment*> *m_pDeletedSegments;
CGPoint m_tLastPoint1;
CGPoint m_tLastPoint2;
CGPoint m_tLastLocation;
int m_nVertCount_;
// int m_nVertCount_;
float m_fTexVPos;
float m_fCurTime;
float m_fFadeTime;
@ -86,12 +91,12 @@ namespace cocos2d {
float m_fLastWidth;
float m_fLastSign;
bool m_bPastFirstPoint;
};
};
/** @brief object to hold ribbon segment data */
class CCX_DLL CCRibbonSegment : public NSObject
{
public:
/** @brief object to hold ribbon segment data */
class CCX_DLL CCRibbonSegment : public NSObject
{
public:
GLfloat m_pVerts[50*6];
GLfloat m_pCoords[50*4];
GLubyte m_pColors[50*8];
@ -99,13 +104,15 @@ namespace cocos2d {
bool m_bFinished;
unsigned int m_uEnd;
unsigned int m_uBegin;
public:
public:
CCRibbonSegment(){}
virtual ~CCRibbonSegment();
char * description();
bool init();
void reset();
void draw(float curTime, float fadeTime, ccColor4B color);
};
};
} // namespace cocos2d
#endif //__CCRIBBON_H__

View File

@ -25,7 +25,8 @@ THE SOFTWARE.
#include "CGPointExtension.h"
#include "CCRibbon.h"
namespace cocos2d {
/*
/*
* Motion Streak manages a Ribbon based on it's motion in absolute space.
* You construct it with a fadeTime, minimum segment size, texture path, texture
* length and color. The fadeTime controls how long it takes each vertex in
@ -34,10 +35,10 @@ namespace cocos2d {
* length is the how many pixels the texture is stretched across. The texture
* is vertically aligned along the streak segemnts.
*/
//implementation CCMotionStreak
//implementation CCMotionStreak
CCMotionStreak * CCMotionStreak::streakWithFade(float fade, float seg, const char *imagePath, float width, float length, ccColor4B color)
{
CCMotionStreak * CCMotionStreak::streakWithFade(float fade, float seg, const char *imagePath, float width, float length, ccColor4B color)
{
CCMotionStreak *pRet = new CCMotionStreak();
if(pRet && pRet->initWithFade(fade, seg, imagePath, width, length, color))
{
@ -46,9 +47,10 @@ namespace cocos2d {
}
CCX_SAFE_DELETE(pRet)
return NULL;
}
bool CCMotionStreak::initWithFade(float fade, float seg, const char *imagePath, float width, float length, ccColor4B color)
{
}
bool CCMotionStreak::initWithFade(float fade, float seg, const char *imagePath, float width, float length, ccColor4B color)
{
m_fSegThreshold = seg;
m_fWidth = width;
m_tLastLocation = CGPointZero;
@ -58,41 +60,46 @@ namespace cocos2d {
// update ribbon position
this->schedule(schedule_selector(CCMotionStreak::update), 0);
return true;
}
void CCMotionStreak::update(ccTime delta)
{
}
void CCMotionStreak::update(ccTime delta)
{
CGPoint location = this->convertToWorldSpace(CGPointZero);
m_pRibbon->setPosition(ccp(-1*location.x, -1*location.y));
float len = sqrtf(powf(m_tLastLocation.x - location.x, 2) + powf(m_tLastLocation.y - location.y, 2));
float len = ccpLength(ccpSub(m_tLastLocation, location));
if (len > m_fSegThreshold)
{
m_pRibbon->addPointAt(location, m_fWidth);
m_tLastLocation = location;
}
m_pRibbon->update(delta);
}
}
//MotionStreak - CocosNodeTexture protocol
//MotionStreak - CocosNodeTexture protocol
void CCMotionStreak::setTexture(CCTexture2D* texture)
{
void CCMotionStreak::setTexture(CCTexture2D* texture)
{
m_pRibbon->setTexture(texture);
}
CCTexture2D * CCMotionStreak::getTexture()
{
}
CCTexture2D * CCMotionStreak::getTexture()
{
return m_pRibbon->getTexture();
}
ccBlendFunc CCMotionStreak::getBlendFunc()
{
}
ccBlendFunc CCMotionStreak::getBlendFunc()
{
return m_pRibbon->getBlendFunc();
}
void CCMotionStreak::setBlendFunc(ccBlendFunc blendFunc)
{
}
void CCMotionStreak::setBlendFunc(ccBlendFunc blendFunc)
{
m_pRibbon->setBlendFunc(blendFunc);
}
CCRibbon * CCMotionStreak::getRibbon()
{
}
CCRibbon * CCMotionStreak::getRibbon()
{
return m_pRibbon;
}
}
}// namespace cocos2d

View File

@ -29,13 +29,13 @@ THE SOFTWARE.
#include <float.h>
namespace cocos2d
{
#define kProgressTextureCoordsCount 4
const char kProgressTextureCoords = 0x1e;
namespace cocos2d {
CCProgressTimer* CCProgressTimer::progressWithFile(const char *pszFileName)
{
#define kProgressTextureCoordsCount 4
const char kProgressTextureCoords = 0x1e;
CCProgressTimer* CCProgressTimer::progressWithFile(const char *pszFileName)
{
CCProgressTimer *pProgressTimer = new CCProgressTimer();
if (pProgressTimer->initWithFile(pszFileName))
{
@ -48,15 +48,15 @@ namespace cocos2d
}
return pProgressTimer;
}
}
bool CCProgressTimer::initWithFile(const char *pszFileName)
{
bool CCProgressTimer::initWithFile(const char *pszFileName)
{
return this->initWithTexture(CCTextureCache::sharedTextureCache()->addImage(pszFileName));
}
}
CCProgressTimer* CCProgressTimer::progressWithTexture(cocos2d::CCTexture2D *pTexture)
{
CCProgressTimer* CCProgressTimer::progressWithTexture(cocos2d::CCTexture2D *pTexture)
{
CCProgressTimer *pProgressTimer = new CCProgressTimer();
if (pProgressTimer->initWithTexture(pTexture))
{
@ -69,10 +69,10 @@ namespace cocos2d
}
return pProgressTimer;
}
}
bool CCProgressTimer::initWithTexture(cocos2d::CCTexture2D *pTexture)
{
bool CCProgressTimer::initWithTexture(cocos2d::CCTexture2D *pTexture)
{
m_pSprite = CCSprite::spriteWithTexture(pTexture);
CCX_SAFE_RETAIN(m_pSprite);
m_fPercentage = 0.f;
@ -83,20 +83,20 @@ namespace cocos2d
m_eType = kCCProgressTimerTypeRadialCCW;
return true;
}
}
CCProgressTimer::~CCProgressTimer(void)
{
CCProgressTimer::~CCProgressTimer(void)
{
if (m_pVertexData)
{
delete[] m_pVertexData;
}
m_pSprite->release();
}
}
void CCProgressTimer::setPercentage(float fPercentage)
{
void CCProgressTimer::setPercentage(float fPercentage)
{
if (m_fPercentage != fPercentage)
{
if (m_fPercentage < 0.f)
@ -114,10 +114,10 @@ namespace cocos2d
updateProgress();
}
}
}
void CCProgressTimer::setSprite(cocos2d::CCSprite *pSprite)
{
void CCProgressTimer::setSprite(cocos2d::CCSprite *pSprite)
{
if (m_pSprite != pSprite)
{
CCX_SAFE_RETAIN(pSprite);
@ -132,10 +132,10 @@ namespace cocos2d
m_nVertexDataCount = 0;
}
}
}
}
void CCProgressTimer::setType(cocos2d::CCProgressTimerType type)
{
void CCProgressTimer::setType(cocos2d::CCProgressTimerType type)
{
if (type != m_eType)
{
// release all previous information
@ -148,28 +148,37 @@ namespace cocos2d
m_eType = type;
}
}
}
// Interval
// Interval
///
// @returns the vertex position from the texture coordinate
///
CGPoint CCProgressTimer::vertexFromTexCoord(cocos2d::CGPoint texCoord)
///
// @returns the vertex position from the texture coordinate
///
ccVertex2F CCProgressTimer::vertexFromTexCoord(cocos2d::CGPoint texCoord)
{
CGPoint tmp;
ccVertex2F ret;
CCTexture2D *pTexture = m_pSprite->getTexture();
if (pTexture)
{
if (m_pSprite->getTexture())
{
return ccp(m_pSprite->getTexture()->getContentSize().width * texCoord.x / m_pSprite->getTexture()->getMaxS(),
m_pSprite->getTexture()->getContentSize().height * (1 - (texCoord.y / m_pSprite->getTexture()->getMaxT())));
CGSize texSize = pTexture->getContentSizeInPixels();
tmp = ccp(texSize.width * texCoord.x / pTexture->getMaxS(),
texSize.height * (1 - (texCoord.y / pTexture->getMaxT())));
}
else
{
return CGPointZero;
}
tmp = CGPointZero;
}
void CCProgressTimer::updateColor(void)
{
ret.x = tmp.x;
ret.y = tmp.y;
return ret;
}
void CCProgressTimer::updateColor(void)
{
ccColor4F color = ccc4FFromccc3B(m_pSprite->getColor());
if (m_pSprite->getTexture()->getHasPremultipliedAlpha())
{
@ -191,10 +200,10 @@ namespace cocos2d
m_pVertexData[i].colors = color;
}
}
}
}
void CCProgressTimer::updateProgress(void)
{
void CCProgressTimer::updateProgress(void)
{
switch (m_eType)
{
case kCCProgressTimerTypeRadialCW:
@ -210,19 +219,19 @@ namespace cocos2d
default:
break;
}
}
}
///
// Update does the work of mapping the texture onto the triangles
// It now doesn't occur the cost of free/alloc data every update cycle.
// It also only changes the percentage point but no other points if they have not
// been modified.
//
// It now deals with flipped texture. If you run into this problem, just use the
// sprite property and enable the methods flipX, flipY.
///
void CCProgressTimer::updateRadial(void)
{
///
// Update does the work of mapping the texture onto the triangles
// It now doesn't occur the cost of free/alloc data every update cycle.
// It also only changes the percentage point but no other points if they have not
// been modified.
//
// It now deals with flipped texture. If you run into this problem, just use the
// sprite property and enable the methods flipX, flipY.
///
void CCProgressTimer::updateRadial(void)
{
// Texture Max is the actual max coordinates to deal with non-power of 2 textures
CGPoint tMax = ccp(m_pSprite->getTexture()->getMaxS(),
m_pSprite->getTexture()->getMaxT());
@ -396,19 +405,19 @@ namespace cocos2d
m_pVertexData[m_nVertexDataCount - 1].texCoords.v = tMax.y - m_pVertexData[m_nVertexDataCount - 1].texCoords.v;
}
}
}
}
///
// Update does the work of mapping the texture onto the triangles for the bar
// It now doesn't occur the cost of free/alloc data every update cycle.
// It also only changes the percentage point but no other points if they have not
// been modified.
//
// It now deals with flipped texture. If you run into this problem, just use the
// sprite property and enable the methods flipX, flipY.
///
void CCProgressTimer::updateBar(void)
{
///
// Update does the work of mapping the texture onto the triangles for the bar
// It now doesn't occur the cost of free/alloc data every update cycle.
// It also only changes the percentage point but no other points if they have not
// been modified.
//
// It now deals with flipped texture. If you run into this problem, just use the
// sprite property and enable the methods flipX, flipY.
///
void CCProgressTimer::updateBar(void)
{
float alpha = m_fPercentage / 100.f;
CGPoint tMax = ccp(m_pSprite->getTexture()->getMaxS(), m_pSprite->getTexture()->getMaxT());
@ -521,10 +530,10 @@ namespace cocos2d
m_pVertexData[index].texCoords.v = tMax.y - m_pVertexData[index].texCoords.v;
}
}
}
}
CGPoint CCProgressTimer::boundaryTexCoord(char index)
{
CGPoint CCProgressTimer::boundaryTexCoord(char index)
{
if (index < kProgressTextureCoordsCount)
{
switch (m_eType)
@ -539,10 +548,10 @@ namespace cocos2d
}
return CGPointZero;
}
}
void CCProgressTimer::draw(void)
{
void CCProgressTimer::draw(void)
{
if(! m_pVertexData)
{
return;
@ -553,12 +562,10 @@ namespace cocos2d
return;
}
bool newBlend = false;
ccBlendFunc bf = m_pSprite->getBlendFunc();
if (bf.src != CC_BLEND_SRC || bf.dst != CC_BLEND_DST)
bool newBlend = (bf.src != CC_BLEND_SRC || bf.dst != CC_BLEND_DST) ? true : false;
if (newBlend)
{
newBlend = true;
glBlendFunc(bf.src, bf.dst);
}
@ -585,6 +592,9 @@ namespace cocos2d
/// ========================================================================
if( newBlend )
{
glBlendFunc(CC_BLEND_SRC, CC_BLEND_DST);
}
}
} // namespace cocos2d

View File

@ -26,145 +26,312 @@ THE SOFTWARE.
#include "CCDirector.h"
#include "platform/platform.h"
#include "CCXUIImage.h"
#include "Support/ccUtls.h"
#include <GLES/glext.h>
namespace cocos2d {
// implementation CCRenderTexture
CCSprite * CCRenderTexture::getSprite()
{
return m_pSprite;
}
void CCRenderTexture::setSprite(CCSprite* var)
{
m_pSprite = var;
}
// implementation CCRenderTexture
CCRenderTexture::CCRenderTexture()
: m_pSprite(NULL)
, m_uFBO(0)
, m_nOldFBO(0)
, m_pTexture(0)
, m_ePixelFormat(kCCPixelFormatRGBA8888)
{
memset(m_aClearColor, 0, sizeof(m_aClearColor));
}
CCRenderTexture * CCRenderTexture::renderTextureWithWidthAndHeight(int width, int height)
{
CCRenderTexture::~CCRenderTexture()
{
removeAllChildrenWithCleanup(true);
ccglDeleteFramebuffers(1, &m_uFBO);
}
CCSprite * CCRenderTexture::getSprite()
{
return m_pSprite;
}
void CCRenderTexture::setSprite(CCSprite* var)
{
m_pSprite = var;
}
CCRenderTexture * CCRenderTexture::renderTextureWithWidthAndHeight(int w, int h, CCTexture2DPixelFormat eFormat)
{
CCRenderTexture *pRet = new CCRenderTexture();
if(pRet && pRet->initWithWidthAndHeight(width,height))
if(pRet && pRet->initWithWidthAndHeight(w, h, eFormat))
{
pRet->autorelease();
return pRet;
}
CCX_SAFE_DELETE(pRet);
return NULL;
}
CCRenderTexture * CCRenderTexture::renderTextureWithWidthAndHeight(int w, int h)
{
CCRenderTexture *pRet = new CCRenderTexture();
if(pRet && pRet->initWithWidthAndHeight(w, h, kCCTexture2DPixelFormat_RGBA8888))
{
pRet->autorelease();
return pRet;
}
CCX_SAFE_DELETE(pRet)
return NULL;
}
bool CCRenderTexture::initWithWidthAndHeight(int width, int height)
}
bool CCRenderTexture::initWithWidthAndHeight(int w, int h, CCTexture2DPixelFormat eFormat)
{
bool bRet = false;
do
{
w *= CC_CONTENT_SCALE_FACTOR();
h *= CC_CONTENT_SCALE_FACTOR();
glGetIntegerv(GL_FRAMEBUFFER_BINDING_OES, &m_nOldFBO);
CCTexture2DPixelFormat format = kCCTexture2DPixelFormat_RGBA8888;
// textures must be power of two squared
int pow = 8;
while (pow < width || pow < height)
{
pow*=2;
}
void *data = malloc((int)(pow * pow * 4));
memset(data, 0, (int)(pow * pow * 4));
unsigned int powW = ccNextPOT(w);
unsigned int powH = ccNextPOT(h);
void *data = malloc((int)(powW * powH * 4));
CCX_BREAK_IF(! data);
memset(data, 0, (int)(powW * powH * 4));
m_ePixelFormat = eFormat;
m_pTexture = new CCTexture2D();
m_pTexture->initWithData(data, format, pow, pow, CGSizeMake((float)width, (float)height));
CCX_BREAK_IF(! m_pTexture);
m_pTexture->initWithData(data, m_ePixelFormat, powW, powH, CGSizeMake((float)w, (float)h));
free( data );
// generate FBO
glGenFramebuffersOES(1, &m_uFBO);
glBindFramebufferOES(GL_FRAMEBUFFER_OES, m_uFBO);
ccglGenFramebuffers(1, &m_uFBO);
ccglBindFramebuffer(CC_GL_FRAMEBUFFER, m_uFBO);
// associate texture with FBO
glFramebufferTexture2DOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_TEXTURE_2D, m_pTexture->getName(), 0);
ccglFramebufferTexture2D(CC_GL_FRAMEBUFFER, CC_GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_pTexture->getName(), 0);
// check if it worked (probably worth doing :) )
GLuint status = glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES);
if (status != GL_FRAMEBUFFER_COMPLETE_OES)
GLuint status = ccglCheckFramebufferStatus(CC_GL_FRAMEBUFFER);
if (status != CC_GL_FRAMEBUFFER_COMPLETE)
{
NSAssert(0, "Render Texture : Could not attach texture to framebuffer")
NSAssert(0, "Render Texture : Could not attach texture to framebuffer");
CCX_SAFE_DELETE(m_pTexture);
return false;
break;
}
m_pTexture->setAliasTexParameters();
m_pSprite = CCSprite::spriteWithTexture(m_pTexture);
m_pTexture->release();
m_pSprite->setScaleY(-1);
this->addChild(m_pSprite);
glBindFramebufferOES(GL_FRAMEBUFFER_OES, m_nOldFBO);
return true;
}
CCRenderTexture::~CCRenderTexture()
{
glDeleteFramebuffersOES(1, &m_uFBO);
}
void CCRenderTexture::begin()
{
ccBlendFunc tBlendFunc = {GL_ONE, GL_ONE_MINUS_SRC_ALPHA };
m_pSprite->setBlendFunc(tBlendFunc);
ccglBindFramebuffer(CC_GL_FRAMEBUFFER, m_nOldFBO);
bRet = true;
} while (0);
return bRet;
}
void CCRenderTexture::begin()
{
saveGLstate();
CC_DISABLE_DEFAULT_GL_STATES();
// Save the current matrix
glPushMatrix();
CGSize textureSize = m_pTexture->getContentSize();
CGSize texSize = m_pTexture->getContentSizeInPixels();
// Calculate the adjustment ratios based on the old and new projections
CGSize size = CCDirector::sharedDirector()->getDisplaySize();
float widthRatio = size.width / textureSize.width;
float heightRatio = size.height / textureSize.height;
CGSize size = CCDirector::sharedDirector()->getDisplaySizeInPixels();
float widthRatio = size.width / texSize.width;
float heightRatio = size.height / texSize.height;
// Adjust the orthographic propjection and viewport
glOrthof((float)-1.0 / widthRatio, (float)1.0 / widthRatio, (float)-1.0 / heightRatio, (float)1.0 / heightRatio, -1,1);
glViewport(0, 0, (GLsizei)textureSize.width, (GLsizei)textureSize.height);
ccglOrtho((float)-1.0 / widthRatio, (float)1.0 / widthRatio, (float)-1.0 / heightRatio, (float)1.0 / heightRatio, -1,1);
glViewport(0, 0, (GLsizei)texSize.width, (GLsizei)texSize.height);
glGetIntegerv(GL_FRAMEBUFFER_BINDING_OES, &m_nOldFBO);
glBindFramebufferOES(GL_FRAMEBUFFER_OES, m_uFBO);//Will direct drawing to the frame buffer created above
glGetIntegerv(CC_GL_FRAMEBUFFER_BINDING, &m_nOldFBO);
ccglBindFramebuffer(CC_GL_FRAMEBUFFER, m_uFBO);//Will direct drawing to the frame buffer created above
CC_ENABLE_DEFAULT_GL_STATES();
}
void CCRenderTexture::end()
{
glBindFramebufferOES(GL_FRAMEBUFFER_OES, m_nOldFBO);
}
void CCRenderTexture::beginWithClear(float r, float g, float b, float a)
{
this->saveGLstate();
CC_DISABLE_DEFAULT_GL_STATES();
// Save the current matrix
glPushMatrix();
CGSize texSize = m_pTexture->getContentSizeInPixels();
// Calculate the adjustment ratios based on the old and new projections
CGSize size = CCDirector::sharedDirector()->getDisplaySizeInPixels();
float widthRatio = size.width / texSize.width;
float heightRatio = size.height / texSize.height;
// Adjust the orthographic propjection and viewport
ccglOrtho((float)-1.0 / widthRatio, (float)1.0 / widthRatio, (float)-1.0 / heightRatio, (float)1.0 / heightRatio, -1,1);
glViewport(0, 0, texSize.width, texSize.height);
glGetIntegerv(CC_GL_FRAMEBUFFER_BINDING, &m_oldFBO);
ccglBindFramebuffer(CC_GL_FRAMEBUFFER, m_fbo);//Will direct drawing to the frame buffer created above
glClearColor(r, g, b, a);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
CC_ENABLE_DEFAULT_GL_STATES();
}
void CCRenderTexture::end()
{
ccglBindFramebuffer(CC_GL_FRAMEBUFFER, m_nOldFBO);
// Restore the original matrix and viewport
glPopMatrix();
CGSize size = CCDirector::sharedDirector()->getDisplaySize();
CGSize size = CCDirector::sharedDirector()->getDisplaySizeInPixels();
glViewport(0, 0, (GLsizei)size.width, (GLsizei)size.height);
this->restoreGLstate();
}
glColorMask(true, true, true, true);
}
void CCRenderTexture::clear(float r, float g, float b, float a)
{
void CCRenderTexture::clear(float r, float g, float b, float a)
{
this->begin();
glClearColor(r, g, b, a);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColorMask(true, true, true, false);
this->end();
}
bool CCRenderTexture::saveBuffer(const char *name)
{
return this->saveBuffer(name, kImageFormatJPG);
}
bool CCRenderTexture::saveBuffer(const char *name, int format)
{
}
void CCRenderTexture::saveGLstate()
{
glGetFloatv(GL_COLOR_CLEAR_VALUE, m_aClearColor);
}
void CCRenderTexture::restoreGLstate()
{
glClearColor(m_aClearColor[0], m_aClearColor[1], m_aClearColor[2], m_aClearColor[3]);
}
bool CCRenderTexture::saveBuffer(const char *name)
{
return this->saveBuffer(name, kCCImageFormatJPG);
}
bool CCRenderTexture::saveBuffer(const char *fileName, int format)
{
bool bRet = false;
UIImage *myImage = this->getUIImageFromBuffer();
if (myImage)
{
bRet = myImage->save(name, format);
delete myImage;
}
//@ todo CCRenderTexture::saveBuffer
// UIImage *myImage = this->getUIImageFromBuffer(format);
// NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
// NSString *documentsDirectory = [paths objectAtIndex:0];
// NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:fileName];
// NSData * data = this->getUIImageAsDataFromBuffer(format);
// if (data)
// {
// bRet = data->writeToFile(path, true);
// delete data;
// bRet = true;
// }
return bRet;
}
}
/* get buffer as UIImage */
UIImage * CCRenderTexture::getUIImageFromBuffer()
{
int tx = (int)m_pTexture->getContentSize().width;
int ty = (int)m_pTexture->getContentSize().height;
NSData * CCRenderTexture::getUIImageAsDataFromBuffer(int format)
{
NSData * pData = NULL;
//@ todo CCRenderTexture::getUIImageAsDataFromBuffer
char * pBuffer = new char[tx * ty * 4];
// #include "Availability.h"
// #include "UIKit.h"
this->begin();
glReadPixels(0,0,tx,ty,GL_RGBA,GL_UNSIGNED_BYTE, pBuffer);
this->end();
// GLubyte * pBuffer = NULL;
// GLubyte * pPixels = NULL;
// do
// {
// CCX_BREAK_IF(! m_pTexture);
//
// NSAssert(m_ePixelFormat == kCCTexture2DPixelFormat_RGBA8888, "only RGBA8888 can be saved as image");
//
// CGSize s = m_pTexture->getContentSizeInPixels();
// int tx = s.width;
// int ty = s.height;
//
// int bitsPerComponent = 8;
// int bitsPerPixel = 32;
//
// int bytesPerRow = (bitsPerPixel / 8) * tx;
// int myDataLength = bytesPerRow * ty;
//
// CCX_BREAK_IF(! (pBuffer = new GLubyte[tx * ty * 4]));
// CCX_BREAK_IF(! (pPixels = new GLubyte[tx * ty * 4]));
//
// this->begin();
// glReadPixels(0,0,tx,ty,GL_RGBA,GL_UNSIGNED_BYTE, pBuffer);
// this->end();
//
// int x,y;
//
// for(y = 0; y <ty; y++) {
// for(x = 0; x <tx * 4; x++) {
// pPixels[((ty - 1 - y) * tx * 4 + x)] = pBuffer[(y * 4 * tx + x)];
// }
// }
//
// if (format == kCCImageFormatRawData)
// {
// pData = NSData::dataWithBytesNoCopy(pPixels, myDataLength);
// break;
// }
UIImage *pRet = new UIImage();
pRet->initWithBuffer(tx, ty, (unsigned char*)pBuffer);
delete[] pBuffer;
return pRet;
}
//@ todo impliment save to jpg or png
/*
CGImageCreate(size_t width, size_t height,
size_t bitsPerComponent, size_t bitsPerPixel, size_t bytesPerRow,
CGColorSpaceRef space, CGBitmapInfo bitmapInfo, CGDataProviderRef provider,
const CGFloat decode[], bool shouldInterpolate,
CGColorRenderingIntent intent)
*/
// make data provider with data.
// CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault;
// CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, pixels, myDataLength, NULL);
// CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
// CGImageRef iref = CGImageCreate(tx, ty,
// bitsPerComponent, bitsPerPixel, bytesPerRow,
// colorSpaceRef, bitmapInfo, provider,
// NULL, false,
// kCGRenderingIntentDefault);
//
// UIImage* image = [[UIImage alloc] initWithCGImage:iref];
//
// CGImageRelease(iref);
// CGColorSpaceRelease(colorSpaceRef);
// CGDataProviderRelease(provider);
//
//
//
// if (format == kCCImageFormatPNG)
// data = UIImagePNGRepresentation(image);
// else
// data = UIImageJPEGRepresentation(image, 1.0f);
//
// [image release];
// } while (0);
//
// CCX_SAFE_DELETE_ARRAY(pBuffer);
// CCX_SAFE_DELETE_ARRAY(pPixels);
return pData;
}
} // namespace cocos2d

View File

@ -21,31 +21,33 @@ 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 "CCRibbon.h"
#include "CCTextureCache.h"
#include "CGPointExtension.h"
namespace cocos2d {
/*
* A ribbon is a dynamically generated list of polygons drawn as a single or series
* of triangle strips. The primary use of Ribbon is as the drawing class of Motion Streak,
* but it is quite useful on it's own. When manually drawing a ribbon, you can call addPointAt
* and pass in the parameters for the next location in the ribbon. The system will automatically
* generate new polygons, texture them accourding to your texture width, etc, etc.
*
* Ribbon data is stored in a RibbonSegment class. This class statically allocates enough verticies and
* texture coordinates for 50 locations (100 verts or 48 triangles). The ribbon class will allocate
* new segments when they are needed, and reuse old ones if available. The idea is to avoid constantly
* allocating new memory and prefer a more static method. However, since there is no way to determine
* the maximum size of some ribbons (motion streaks), a truely static allocation is not possible.
*
*/
/*
* A ribbon is a dynamically generated list of polygons drawn as a single or series
* of triangle strips. The primary use of Ribbon is as the drawing class of Motion Streak,
* but it is quite useful on it's own. When manually drawing a ribbon, you can call addPointAt
* and pass in the parameters for the next location in the ribbon. The system will automatically
* generate new polygons, texture them accourding to your texture width, etc, etc.
*
* Ribbon data is stored in a RibbonSegment class. This class statically allocates enough verticies and
* texture coordinates for 50 locations (100 verts or 48 triangles). The ribbon class will allocate
* new segments when they are needed, and reuse old ones if available. The idea is to avoid constantly
* allocating new memory and prefer a more static method. However, since there is no way to determine
* the maximum size of some ribbons (motion streaks), a truely static allocation is not possible.
*
*/
//
// Ribbon
//
CCRibbon * CCRibbon::ribbonWithWidth(float w, const char *path, float length, ccColor4B color, float fade)
{
//
// Ribbon
//
CCRibbon * CCRibbon::ribbonWithWidth(float w, const char *path, float length, ccColor4B color, float fade)
{
CCRibbon *pRet = new CCRibbon();
if(pRet && pRet->initWithWidth(w, path, length, color, fade))
{
@ -54,9 +56,10 @@ namespace cocos2d {
}
CCX_SAFE_DELETE(pRet)
return NULL;
}
bool CCRibbon::initWithWidth(float w, const char *path, float length, ccColor4B color, float fade)
{
}
bool CCRibbon::initWithWidth(float w, const char *path, float length, ccColor4B color, float fade)
{
m_pSegments = new NSMutableArray<CCRibbonSegment*>();
m_pDeletedSegments = new NSMutableArray<CCRibbonSegment*>();
@ -92,35 +95,42 @@ namespace cocos2d {
ccTexParams params = { GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT };
m_pTexture->setTexParameters(&params);
return true;
}
CCRibbon::~CCRibbon()
{
m_pSegments->release();
m_pDeletedSegments->release();
m_pTexture->release();
}
CGPoint CCRibbon::rotatePoint(CGPoint vec, float rotation)
{
}
CCRibbon::~CCRibbon()
{
CCX_SAFE_RELEASE(m_pSegments);
CCX_SAFE_RELEASE(m_pDeletedSegments);
CCX_SAFE_RELEASE(m_pTexture);
}
CGPoint CCRibbon::rotatePoint(CGPoint vec, float rotation)
{
float xtemp = (vec.x * cosf(rotation)) - (vec.y * sinf(rotation));
vec.y = (vec.x * sinf(rotation)) + (vec.y * cosf(rotation));
vec.x = xtemp;
return vec;
}
void CCRibbon::update(ccTime delta)
{
}
void CCRibbon::update(ccTime delta)
{
m_fCurTime += delta;
m_fDelta = delta;
}
float CCRibbon::sideOfLine(CGPoint p, CGPoint l1, CGPoint l2)
{
}
float CCRibbon::sideOfLine(CGPoint p, CGPoint l1, CGPoint l2)
{
CGPoint vp = ccpPerp(ccpSub(l1, l2));
CGPoint vx = ccpSub(p, l1);
return ccpDot(vx, vp);
}
}
// adds a new segment to the ribbon
void CCRibbon::addPointAt(CGPoint location, float width)
{
location.x *= CC_CONTENT_SCALE_FACTOR();
location.y *= CC_CONTENT_SCALE_FACTOR();
// adds a new segment to the ribbon
void CCRibbon::addPointAt(CGPoint location, float width)
{
width = width * 0.5f;
// if this is the first point added, cache it and return
if (!m_bPastFirstPoint)
@ -240,9 +250,10 @@ namespace cocos2d {
m_tLastPoint2 = p2;
m_fLastWidth = width;
seg->m_uEnd++;
}
void CCRibbon::draw()
{
}
void CCRibbon::draw()
{
if (m_pSegments->count() > 0)
{
// Default GL states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY
@ -252,10 +263,9 @@ namespace cocos2d {
glBindTexture(GL_TEXTURE_2D, m_pTexture->getName());
bool newBlend = false;
if( m_tBlendFunc.src != CC_BLEND_SRC || m_tBlendFunc.dst != CC_BLEND_DST )
bool newBlend = ( m_tBlendFunc.src != CC_BLEND_SRC || m_tBlendFunc.dst != CC_BLEND_DST ) ? true : false;
if( newBlend )
{
newBlend = true;
glBlendFunc( m_tBlendFunc.src, m_tBlendFunc.dst );
}
@ -278,72 +288,81 @@ namespace cocos2d {
// restore default GL state
glEnableClientState( GL_COLOR_ARRAY );
}
}
}
// Ribbon - CocosNodeTexture protocol
void CCRibbon::setTexture(CCTexture2D* var)
{
// Ribbon - CocosNodeTexture protocol
void CCRibbon::setTexture(CCTexture2D* var)
{
CCX_SAFE_RETAIN(var);
CCX_SAFE_RELEASE(m_pTexture);
m_pTexture = var;
this->setContentSize(m_pTexture->getContentSize());
this->setContentSize(m_pTexture->getContentSizeInPixels());
/* XXX Don't update blending function in Ribbons */
}
CCTexture2D *CCRibbon::getTexture()
{
return m_pTexture;
}
void CCRibbon::setTextureLength(float var)
{
m_fTextureLength = var;
}
float CCRibbon::getTextureLength()
{
return m_fTextureLength;
}
void CCRibbon::setBlendFunc(ccBlendFunc var)
{
m_tBlendFunc = var;
}
ccBlendFunc CCRibbon::getBlendFunc()
{
return m_tBlendFunc;
}
void CCRibbon::setColor(ccColor4B var)
{
m_tColor = var;
}
ccColor4B CCRibbon::getColor()
{
return m_tColor;
}
}
//
//RibbonSegment
//
bool CCRibbonSegment::init()
{
CCTexture2D *CCRibbon::getTexture()
{
return m_pTexture;
}
void CCRibbon::setTextureLength(float var)
{
m_fTextureLength = var;
}
float CCRibbon::getTextureLength()
{
return m_fTextureLength;
}
void CCRibbon::setBlendFunc(ccBlendFunc var)
{
m_tBlendFunc = var;
}
ccBlendFunc CCRibbon::getBlendFunc()
{
return m_tBlendFunc;
}
void CCRibbon::setColor(ccColor4B var)
{
m_tColor = var;
}
ccColor4B CCRibbon::getColor()
{
return m_tColor;
}
//
//RibbonSegment
//
bool CCRibbonSegment::init()
{
this->reset();
return true;
}
char * CCRibbonSegment::description()
{
}
char * CCRibbonSegment::description()
{
char *ret = new char[100] ;
sprintf(ret, "<CCRibbonSegment | end = %u, begin = %u>", m_uEnd, m_uBegin);
return ret;
}
CCRibbonSegment::~CCRibbonSegment()
{
}
CCRibbonSegment::~CCRibbonSegment()
{
CCLOGINFO("cocos2d: deallocing.");
}
void CCRibbonSegment::reset()
{
}
void CCRibbonSegment::reset()
{
m_uEnd = 0;
m_uBegin = 0;
m_bFinished = false;
}
void CCRibbonSegment::draw(float curTime, float fadeTime, ccColor4B color)
{
}
void CCRibbonSegment::draw(float curTime, float fadeTime, ccColor4B color)
{
GLubyte r = color.r;
GLubyte g = color.g;
GLubyte b = color.b;
@ -395,6 +414,6 @@ namespace cocos2d {
{
m_bFinished = true;
}
}
}
}// namespace cocos2d

View File

@ -81,6 +81,7 @@ public: inline varType get##funName(void){ return varName; }\
public: inline void set##funName(varType var){ varName = var; }
#define CCX_SAFE_DELETE(p) if(p) { delete p; p=NULL; }
#define CCX_SAFE_DELETE_ARRAY(p) if(p) { delete[] p; p=NULL; }
#define CCX_SAFE_FREE(p) if(p) { free(p); p=NULL; }
#define CCX_SAFE_RELEASE(p) if(p) { p->release(); }
#define CCX_SAFE_RELEASE_NULL(p) if(p) { p->release(); p = NULL; }

View File

@ -116,7 +116,7 @@ UIImage::~UIImage(void)
}
}
bool UIImage::initWithContentsOfFile(const string &strPath, tImageFormat imageType)
bool UIImage::initWithContentsOfFile(const string &strPath, eImageFormat imageType)
{
bool bRet = false;
@ -138,11 +138,11 @@ bool UIImage::initWithContentsOfFile(const string &strPath, tImageFormat imageTy
{
switch (imageType)
{
case kImageFormatPNG:
case kCCImageFormatPNG:
// use libpng load image
bRet = loadPngFromStream(pBuffer, nSize);
break;
case kImageFormatJPG:
case kCCImageFormatJPG:
bRet = loadJpgFromStream(pBuffer, nSize);
break;
default:

View File

@ -64,7 +64,7 @@ public:
- kImageFormatJPG -> jpeg
@return true if load correctly
*/
bool initWithContentsOfFile(const std::string &strPath, tImageFormat imageType = kImageFormatPNG);
bool initWithContentsOfFile(const std::string &strPath, eImageFormat imageType = kCCImageFormatPNG);
/**
Load image from stream buffer.
@param pBuffer stream buffer that hold the image data

View File

@ -207,7 +207,7 @@ CCTexture2D * CCTextureCache::addImage(const char * path)
else if (std::string::npos != lowerCase.find(".jpg") || std::string::npos != lowerCase.find(".jpeg"))
{
UIImage * image = new UIImage();
if(! image->initWithContentsOfFile(fullpath, kImageFormatJPG))
if(! image->initWithContentsOfFile(fullpath, kCCImageFormatJPG))
{
delete image;
break;
@ -235,7 +235,7 @@ CCTexture2D * CCTextureCache::addImage(const char * path)
#else
// prevents overloading the autorelease pool
UIImage * image = new UIImage();
if(! image->initWithContentsOfFile(fullpath, kImageFormatPNG))
if(! image->initWithContentsOfFile(fullpath, kCCImageFormatPNG))
{
delete image;
break;