mirror of https://github.com/axmolengine/axmol.git
400 lines
12 KiB
C++
400 lines
12 KiB
C++
/****************************************************************************
|
|
Copyright (c) 2010 cocos2d-x.org
|
|
|
|
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 "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.
|
|
*
|
|
*/
|
|
|
|
//
|
|
// 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))
|
|
{
|
|
pRet->autorelease();
|
|
return pRet;
|
|
}
|
|
CCX_SAFE_DELETE(pRet)
|
|
return NULL;
|
|
}
|
|
bool CCRibbon::initWithWidth(float w, const char *path, float length, ccColor4B color, float fade)
|
|
{
|
|
m_pSegments = new NSMutableArray<CCRibbonSegment*>();
|
|
m_pDeletedSegments = new NSMutableArray<CCRibbonSegment*>();
|
|
|
|
/* 1 initial segment */
|
|
CCRibbonSegment* seg = new CCRibbonSegment();
|
|
seg->init();
|
|
m_pSegments->addObject(seg);
|
|
seg->release();
|
|
|
|
m_fTextureLength = length;
|
|
|
|
m_tColor = color;
|
|
m_fFadeTime = fade;
|
|
m_tLastLocation = CGPointZero;
|
|
m_fLastWidth = w/2;
|
|
m_fTexVPos = 0.0f;
|
|
|
|
m_fCurTime = 0;
|
|
m_bPastFirstPoint = false;
|
|
|
|
/* XXX:
|
|
Ribbon, by default uses this blend function, which might not be correct
|
|
if you are using premultiplied alpha images,
|
|
but 99% you might want to use this blending function regarding of the texture
|
|
*/
|
|
m_tBlendFunc.src = GL_SRC_ALPHA;
|
|
m_tBlendFunc.dst = GL_ONE_MINUS_SRC_ALPHA;
|
|
|
|
m_pTexture = CCTextureCache::sharedTextureCache()->addImage(path);
|
|
CCX_SAFE_RETAIN(m_pTexture);
|
|
|
|
/* default texture parameter */
|
|
ccTexParams params = { GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT };
|
|
m_pTexture->setTexParameters(¶ms);
|
|
return true;
|
|
}
|
|
CCRibbon::~CCRibbon()
|
|
{
|
|
m_pSegments->release();
|
|
m_pDeletedSegments->release();
|
|
m_pTexture->release();
|
|
}
|
|
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)
|
|
{
|
|
m_fCurTime += delta;
|
|
m_fDelta = delta;
|
|
}
|
|
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)
|
|
{
|
|
width = width * 0.5f;
|
|
// if this is the first point added, cache it and return
|
|
if (!m_bPastFirstPoint)
|
|
{
|
|
m_fLastWidth = width;
|
|
m_tLastLocation = location;
|
|
m_bPastFirstPoint = true;
|
|
return;
|
|
}
|
|
|
|
CGPoint sub = ccpSub(m_tLastLocation, location);
|
|
float r = ccpToAngle(sub) + (float)M_PI_2;
|
|
CGPoint p1 = ccpAdd(this->rotatePoint(ccp(-width, 0), r), location);
|
|
CGPoint p2 = ccpAdd(this->rotatePoint(ccp(+width, 0), r), location);
|
|
float len = sqrtf(powf(m_tLastLocation.x - location.x, 2) + powf(m_tLastLocation.y - location.y, 2));
|
|
float tend = m_fTexVPos + len/m_fTextureLength;
|
|
CCRibbonSegment* seg;
|
|
// grab last segment
|
|
seg = m_pSegments->getLastObject();
|
|
// lets kill old segments
|
|
if (m_pSegments && m_pSegments->count()>0)
|
|
{
|
|
NSMutableArray<CCRibbonSegment*>::NSMutableArrayIterator it;
|
|
for (it = m_pSegments->begin(); it != m_pSegments->end(); ++it)
|
|
{
|
|
if (*it != seg && (*it)->m_bFinished)
|
|
{
|
|
m_pDeletedSegments->addObject(*it);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_pSegments->removeObjectsInArray(m_pDeletedSegments);
|
|
// is the segment full?
|
|
if (seg->m_uEnd >= 50)
|
|
{
|
|
m_pSegments->removeObjectsInArray(m_pDeletedSegments);
|
|
}
|
|
// grab last segment and append to it if it's not full
|
|
seg = m_pSegments->getLastObject();
|
|
// is the segment full?
|
|
if (seg->m_uEnd >= 50)
|
|
{
|
|
CCRibbonSegment* newSeg;
|
|
// grab it from the cache if we can
|
|
if (m_pDeletedSegments->count() > 0)
|
|
{
|
|
newSeg = m_pDeletedSegments->getObjectAtIndex(0);
|
|
newSeg->retain(); // will be released later
|
|
m_pDeletedSegments->removeObject(newSeg);
|
|
newSeg->reset();
|
|
}
|
|
else
|
|
{
|
|
newSeg = new CCRibbonSegment(); // will be released later
|
|
newSeg->init();
|
|
}
|
|
|
|
newSeg->m_pCreationTime[0] = seg->m_pCreationTime[seg->m_uEnd- 1];
|
|
int v = (seg->m_uEnd-1)*6;
|
|
int c = (seg->m_uEnd-1)*4;
|
|
newSeg->m_pVerts[0] = seg->m_pVerts[v];
|
|
newSeg->m_pVerts[1] = seg->m_pVerts[v+1];
|
|
newSeg->m_pVerts[2] = seg->m_pVerts[v+2];
|
|
newSeg->m_pVerts[3] = seg->m_pVerts[v+3];
|
|
newSeg->m_pVerts[4] = seg->m_pVerts[v+4];
|
|
newSeg->m_pVerts[5] = seg->m_pVerts[v+5];
|
|
|
|
newSeg->m_pCoords[0] = seg->m_pCoords[c];
|
|
newSeg->m_pCoords[1] = seg->m_pCoords[c+1];
|
|
newSeg->m_pCoords[2] = seg->m_pCoords[c+2];
|
|
newSeg->m_pCoords[3] = seg->m_pCoords[c+3];
|
|
newSeg->m_uEnd++;
|
|
seg = newSeg;
|
|
m_pSegments->addObject(seg);
|
|
newSeg->release();// it was retained before
|
|
}
|
|
if (seg->m_uEnd == 0)
|
|
{
|
|
// first edge has to get rotation from the first real polygon
|
|
CGPoint lp1 = ccpAdd(this->rotatePoint(ccp(-m_fLastWidth, 0), r), m_tLastLocation);
|
|
CGPoint lp2 = ccpAdd(this->rotatePoint(ccp(+m_fLastWidth, 0), r), m_tLastLocation);
|
|
seg->m_pCreationTime[0] = m_fCurTime - m_fDelta;
|
|
seg->m_pVerts[0] = lp1.x;
|
|
seg->m_pVerts[1] = lp1.y;
|
|
seg->m_pVerts[2] = 0.0f;
|
|
seg->m_pVerts[3] = lp2.x;
|
|
seg->m_pVerts[4] = lp2.y;
|
|
seg->m_pVerts[5] = 0.0f;
|
|
seg->m_pCoords[0] = 0.0f;
|
|
seg->m_pCoords[1] = m_fTexVPos;
|
|
seg->m_pCoords[2] = 1.0f;
|
|
seg->m_pCoords[3] = m_fTexVPos;
|
|
seg->m_uEnd++;
|
|
}
|
|
|
|
int v = seg->m_uEnd*6;
|
|
int c = seg->m_uEnd*4;
|
|
// add new vertex
|
|
seg->m_pCreationTime[seg->m_uEnd] = m_fCurTime;
|
|
seg->m_pVerts[v] = p1.x;
|
|
seg->m_pVerts[v+1] = p1.y;
|
|
seg->m_pVerts[v+2] = 0.0f;
|
|
seg->m_pVerts[v+3] = p2.x;
|
|
seg->m_pVerts[v+4] = p2.y;
|
|
seg->m_pVerts[v+5] = 0.0f;
|
|
|
|
|
|
seg->m_pCoords[c] = 0.0f;
|
|
seg->m_pCoords[c+1] = tend;
|
|
seg->m_pCoords[c+2] = 1.0f;
|
|
seg->m_pCoords[c+3] = tend;
|
|
|
|
m_fTexVPos = tend;
|
|
m_tLastLocation = location;
|
|
m_tLastPoint1 = p1;
|
|
m_tLastPoint2 = p2;
|
|
m_fLastWidth = width;
|
|
seg->m_uEnd++;
|
|
}
|
|
void CCRibbon::draw()
|
|
{
|
|
if (m_pSegments->count() > 0)
|
|
{
|
|
// Default GL states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY
|
|
// Needed states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_TEXTURE_COORD_ARRAY
|
|
// Unneeded states: GL_COLOR_ARRAY
|
|
glDisableClientState(GL_COLOR_ARRAY);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, m_pTexture->getName());
|
|
|
|
bool newBlend = false;
|
|
if( m_tBlendFunc.src != CC_BLEND_SRC || m_tBlendFunc.dst != CC_BLEND_DST )
|
|
{
|
|
newBlend = true;
|
|
glBlendFunc( m_tBlendFunc.src, m_tBlendFunc.dst );
|
|
}
|
|
|
|
if(m_pSegments && m_pSegments->count() > 0)
|
|
{
|
|
CCRibbonSegment* seg;
|
|
NSMutableArray<CCRibbonSegment*>::NSMutableArrayIterator it;
|
|
for( it = m_pSegments->begin(); it != m_pSegments->end(); it++)
|
|
{
|
|
seg = (CCRibbonSegment*)*it;
|
|
seg->draw(m_fCurTime, m_fFadeTime, m_tColor);
|
|
}
|
|
}
|
|
|
|
if( newBlend )
|
|
{
|
|
glBlendFunc(CC_BLEND_SRC, CC_BLEND_DST);
|
|
}
|
|
|
|
// restore default GL state
|
|
glEnableClientState( GL_COLOR_ARRAY );
|
|
}
|
|
}
|
|
|
|
// Ribbon - CocosNodeTexture protocol
|
|
void CCRibbon::setTexture(CCTexture2D* var)
|
|
{
|
|
CCX_SAFE_RELEASE(m_pTexture);
|
|
m_pTexture = var;
|
|
CCX_SAFE_RETAIN(m_pTexture);
|
|
this->setContentSize(m_pTexture->getContentSize());
|
|
/* 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()
|
|
{
|
|
this->reset();
|
|
return true;
|
|
}
|
|
char * CCRibbonSegment::description()
|
|
{
|
|
char *ret = new char[100] ;
|
|
sprintf(ret, "<CCRibbonSegment | end = %u, begin = %>", m_uEnd, m_uBegin);
|
|
return ret;
|
|
}
|
|
CCRibbonSegment::~CCRibbonSegment()
|
|
{
|
|
CCLOGINFO("cocos2d: deallocing.");
|
|
}
|
|
void CCRibbonSegment::reset()
|
|
{
|
|
m_uEnd = 0;
|
|
m_uBegin = 0;
|
|
m_bFinished = false;
|
|
}
|
|
void CCRibbonSegment::draw(float curTime, float fadeTime, ccColor4B color)
|
|
{
|
|
GLubyte r = color.r;
|
|
GLubyte g = color.g;
|
|
GLubyte b = color.b;
|
|
GLubyte a = color.a;
|
|
|
|
if (m_uBegin < 50)
|
|
{
|
|
// the motion streak class will call update and cause time to change, thus, if curTime_ != 0
|
|
// we have to generate alpha for the ribbon each frame.
|
|
if (curTime == 0)
|
|
{
|
|
// no alpha over time, so just set the color
|
|
glColor4ub(r,g,b,a);
|
|
}
|
|
else
|
|
{
|
|
// generate alpha/color for each point
|
|
glEnableClientState(GL_COLOR_ARRAY);
|
|
unsigned int i = m_uBegin;
|
|
for (; i < m_uEnd; ++i)
|
|
{
|
|
int idx = i*8;
|
|
m_pColors[idx] = r;
|
|
m_pColors[idx+1] = g;
|
|
m_pColors[idx+2] = b;
|
|
m_pColors[idx+4] = r;
|
|
m_pColors[idx+5] = g;
|
|
m_pColors[idx+6] = b;
|
|
float alive = ((curTime - m_pCreationTime[i]) / fadeTime);
|
|
if (alive > 1)
|
|
{
|
|
m_uBegin++;
|
|
m_pColors[idx+3] = 0;
|
|
m_pColors[idx+7] = 0;
|
|
}
|
|
else
|
|
{
|
|
m_pColors[idx+3] = (GLubyte)(255.f - (alive * 255.f));
|
|
m_pColors[idx+7] = m_pColors[idx+3];
|
|
}
|
|
}
|
|
glColorPointer(4, GL_UNSIGNED_BYTE, 0, &m_pColors[m_uBegin*8]);
|
|
}
|
|
glVertexPointer(3, GL_FLOAT, 0, &m_pVerts[m_uBegin*6]);
|
|
glTexCoordPointer(2, GL_FLOAT, 0, &m_pCoords[m_uBegin*4]);
|
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, (m_uEnd - m_uBegin) * 2);
|
|
}
|
|
else
|
|
{
|
|
m_bFinished = true;
|
|
}
|
|
}
|
|
|
|
}// namespace cocos2d
|