/*
* cocos2d for iPhone: http://www.cocos2d-iphone.org
*
* Copyright (c) 2010 Lam Pham
*
* 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.
*
*/
#import "CCProgressTimer.h"
#import "ccMacros.h"
#import "CCTextureCache.h"
#import "Support/CGPointExtension.h"
#define kProgressTextureCoordsCount 4
// kProgressTextureCoords holds points {0,0} {0,1} {1,1} {1,0} we can represent it as bits
const char kProgressTextureCoords = 0x1e;
@interface CCProgressTimer (Internal)
-(void)updateProgress;
-(void)updateBar;
-(void)updateRadial;
-(void)updateColor;
-(CGPoint)boundaryTexCoord:(char)index;
@end
@implementation CCProgressTimer
@synthesize percentage = percentage_;
@synthesize sprite = sprite_;
@synthesize type = type_;
+(id)progressWithFile:(NSString*) filename
{
return [[[self alloc]initWithFile:filename] autorelease];
}
-(id)initWithFile:(NSString*) filename
{
return [self initWithTexture:[[CCTextureCache sharedTextureCache] addImage: filename]];
}
+(id)progressWithTexture:(CCTexture2D*) texture
{
return [[[self alloc]initWithTexture:texture] autorelease];
}
-(id)initWithTexture:(CCTexture2D*) texture
{
if(( self = [super init] )){
self.sprite = [CCSprite spriteWithTexture:texture];
percentage_ = 0.f;
vertexData_ = NULL;
vertexDataCount_ = 0;
self.anchorPoint = ccp(.5f,.5f);
self.contentSize = sprite_.contentSize;
self.type = kCCProgressTimerTypeRadialCCW;
}
return self;
}
-(void)dealloc
{
if(vertexData_)
free(vertexData_);
[sprite_ release];
[super dealloc];
}
-(void)setPercentage:(float) percentage
{
if(percentage_ != percentage) {
percentage_ = clampf( percentage, 0, 100);
[self updateProgress];
}
}
-(void)setSprite:(CCSprite *)newSprite
{
if(sprite_ != newSprite){
[sprite_ release];
sprite_ = [newSprite retain];
// Everytime we set a new sprite, we free the current vertex data
if(vertexData_){
free(vertexData_);
vertexData_ = NULL;
vertexDataCount_ = 0;
}
}
}
-(void)setType:(CCProgressTimerType)newType
{
if (newType != type_) {
// release all previous information
if(vertexData_){
free(vertexData_);
vertexData_ = NULL;
vertexDataCount_ = 0;
}
type_ = newType;
}
}
@end
@implementation CCProgressTimer(Internal)
///
// @returns the vertex position from the texture coordinate
///
-(ccVertex2F)vertexFromTexCoord:(CGPoint) texCoord
{
CGPoint tmp;
ccVertex2F ret;
if (sprite_.texture) {
CCTexture2D *texture = [sprite_ texture];
CGSize texSize = [texture contentSizeInPixels];
tmp = ccp(texSize.width * texCoord.x/texture.maxS,
texSize.height * (1 - (texCoord.y/texture.maxT)));
} else
tmp = CGPointZero;
ret.x = tmp.x;
ret.y = tmp.y;
return ret;
}
-(void)updateColor
{
GLubyte op = sprite_.opacity;
ccColor3B c3b = sprite_.color;
ccColor4B color = { c3b.r, c3b.g, c3b.b, op };
if([sprite_.texture hasPremultipliedAlpha]){
color.r *= op/255;
color.g *= op/255;
color.b *= op/255;
}
if(vertexData_){
for (int i=0; i < vertexDataCount_; ++i) {
vertexData_[i].colors = color;
}
}
}
-(void)updateProgress
{
switch (type_) {
case kCCProgressTimerTypeRadialCW:
case kCCProgressTimerTypeRadialCCW:
[self updateRadial];
break;
case kCCProgressTimerTypeHorizontalBarLR:
case kCCProgressTimerTypeHorizontalBarRL:
case kCCProgressTimerTypeVerticalBarBT:
case kCCProgressTimerTypeVerticalBarTB:
[self updateBar];
break;
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)updateRadial
{
// Texture Max is the actual max coordinates to deal with non-power of 2 textures
CGPoint tMax = ccp(sprite_.texture.maxS,sprite_.texture.maxT);
// Grab the midpoint
CGPoint midpoint = ccpCompMult(self.anchorPoint, tMax);
float alpha = percentage_ / 100.f;
// Otherwise we can get the angle from the alpha
float angle = 2.f*((float)M_PI) * ( type_ == kCCProgressTimerTypeRadialCW? alpha : 1.f - alpha);
// We find the vector to do a hit detection based on the percentage
// We know the first vector is the one @ 12 o'clock (top,mid) so we rotate
// from that by the progress angle around the midpoint pivot
CGPoint topMid = ccp(midpoint.x, 0.f);
CGPoint percentagePt = ccpRotateByAngle(topMid, midpoint, angle);
int index = 0;
CGPoint hit = CGPointZero;
if (alpha == 0.f) {
// More efficient since we don't always need to check intersection
// If the alpha is zero then the hit point is top mid and the index is 0.
hit = topMid;
index = 0;
} else if (alpha == 1.f) {
// More efficient since we don't always need to check intersection
// If the alpha is one then the hit point is top mid and the index is 4.
hit = topMid;
index = 4;
} else {
// We run a for loop checking the edges of the texture to find the
// intersection point
// We loop through five points since the top is split in half
float min_t = FLT_MAX;
for (int i = 0; i <= kProgressTextureCoordsCount; ++i) {
int pIndex = (i + (kProgressTextureCoordsCount - 1))%kProgressTextureCoordsCount;
CGPoint edgePtA = ccpCompMult([self boundaryTexCoord:i % kProgressTextureCoordsCount],tMax);
CGPoint edgePtB = ccpCompMult([self boundaryTexCoord:pIndex],tMax);
// Remember that the top edge is split in half for the 12 o'clock position
// Let's deal with that here by finding the correct endpoints
if(i == 0){
edgePtB = ccpLerp(edgePtA,edgePtB,.5f);
} else if(i == 4){
edgePtA = ccpLerp(edgePtA,edgePtB,.5f);
}
// s and t are returned by ccpLineIntersect
float s = 0, t = 0;
if(ccpLineIntersect(edgePtA, edgePtB, midpoint, percentagePt, &s, &t))
{
// Since our hit test is on rays we have to deal with the top edge
// being in split in half so we have to test as a segment
if ((i == 0 || i == 4)) {
// s represents the point between edgePtA--edgePtB
if (!(0.f <= s && s <= 1.f)) {
continue;
}
}
// As long as our t isn't negative we are at least finding a
// correct hitpoint from midpoint to percentagePt.
if (t >= 0.f) {
// Because the percentage line and all the texture edges are
// rays we should only account for the shortest intersection
if (t < min_t) {
min_t = t;
index = i;
}
}
}
}
// Now that we have the minimum magnitude we can use that to find our intersection
hit = ccpAdd(midpoint, ccpMult(ccpSub(percentagePt, midpoint),min_t));
}
// The size of the vertex data is the index from the hitpoint
// the 3 is for the midpoint, 12 o'clock point and hitpoint position.
BOOL sameIndexCount = YES;
if(vertexDataCount_ != index + 3){
sameIndexCount = NO;
if(vertexData_){
free(vertexData_);
vertexData_ = NULL;
vertexDataCount_ = 0;
}
}
if(!vertexData_) {
vertexDataCount_ = index + 3;
vertexData_ = malloc(vertexDataCount_ * sizeof(ccV2F_C4B_T2F));
NSAssert( vertexData_, @"CCProgressTimer. Not enough memory");
[self updateColor];
}
if (!sameIndexCount) {
// First we populate the array with the midpoint, then all
// vertices/texcoords/colors of the 12 'o clock start and edges and the hitpoint
vertexData_[0].texCoords = (ccTex2F){midpoint.x, midpoint.y};
vertexData_[0].vertices = [self vertexFromTexCoord:midpoint];
vertexData_[1].texCoords = (ccTex2F){midpoint.x, 0.f};
vertexData_[1].vertices = [self vertexFromTexCoord:ccp(midpoint.x, 0.f)];
for(int i = 0; i < index; ++i){
CGPoint texCoords = ccpCompMult([self boundaryTexCoord:i], tMax);
vertexData_[i+2].texCoords = (ccTex2F){texCoords.x, texCoords.y};
vertexData_[i+2].vertices = [self vertexFromTexCoord:texCoords];
}
// Flip the texture coordinates if set
if (sprite_.flipY || sprite_.flipX) {
for(int i = 0; i < vertexDataCount_ - 1; ++i){
if (sprite_.flipX) {
vertexData_[i].texCoords.u = tMax.x - vertexData_[i].texCoords.u;
}
if(sprite_.flipY){
vertexData_[i].texCoords.v = tMax.y - vertexData_[i].texCoords.v;
}
}
}
}
// hitpoint will go last
vertexData_[vertexDataCount_ - 1].texCoords = (ccTex2F){hit.x, hit.y};
vertexData_[vertexDataCount_ - 1].vertices = [self vertexFromTexCoord:hit];
if (sprite_.flipY || sprite_.flipX) {
if (sprite_.flipX) {
vertexData_[vertexDataCount_ - 1].texCoords.u = tMax.x - vertexData_[vertexDataCount_ - 1].texCoords.u;
}
if(sprite_.flipY){
vertexData_[vertexDataCount_ - 1].texCoords.v = tMax.y - vertexData_[vertexDataCount_ - 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)updateBar
{
float alpha = percentage_ / 100.f;
CGPoint tMax = ccp(sprite_.texture.maxS,sprite_.texture.maxT);
unsigned char vIndexes[2] = {0,0};
unsigned char index = 0;
// We know vertex data is always equal to the 4 corners
// If we don't have vertex data then we create it here and populate
// the side of the bar vertices that won't ever change.
if (!vertexData_) {
vertexDataCount_ = kProgressTextureCoordsCount;
vertexData_ = malloc(vertexDataCount_ * sizeof(ccV2F_C4B_T2F));
NSAssert( vertexData_, @"CCProgressTimer. Not enough memory");
if(type_ == kCCProgressTimerTypeHorizontalBarLR){
vertexData_[vIndexes[0] = 0].texCoords = (ccTex2F){0,0};
vertexData_[vIndexes[1] = 1].texCoords = (ccTex2F){0, tMax.y};
}else if (type_ == kCCProgressTimerTypeHorizontalBarRL) {
vertexData_[vIndexes[0] = 2].texCoords = (ccTex2F){tMax.x, tMax.y};
vertexData_[vIndexes[1] = 3].texCoords = (ccTex2F){tMax.x, 0.f};
}else if (type_ == kCCProgressTimerTypeVerticalBarBT) {
vertexData_[vIndexes[0] = 1].texCoords = (ccTex2F){0, tMax.y};
vertexData_[vIndexes[1] = 3].texCoords = (ccTex2F){tMax.x, tMax.y};
}else if (type_ == kCCProgressTimerTypeVerticalBarTB) {
vertexData_[vIndexes[0] = 0].texCoords = (ccTex2F){0, 0};
vertexData_[vIndexes[1] = 2].texCoords = (ccTex2F){tMax.x, 0};
}
index = vIndexes[0];
vertexData_[index].vertices = [self vertexFromTexCoord:ccp(vertexData_[index].texCoords.u, vertexData_[index].texCoords.v)];
index = vIndexes[1];
vertexData_[index].vertices = [self vertexFromTexCoord:ccp(vertexData_[index].texCoords.u, vertexData_[index].texCoords.v)];
if (sprite_.flipY || sprite_.flipX) {
if (sprite_.flipX) {
index = vIndexes[0];
vertexData_[index].texCoords.u = tMax.x - vertexData_[index].texCoords.u;
index = vIndexes[1];
vertexData_[index].texCoords.u = tMax.x - vertexData_[index].texCoords.u;
}
if(sprite_.flipY){
index = vIndexes[0];
vertexData_[index].texCoords.v = tMax.y - vertexData_[index].texCoords.v;
index = vIndexes[1];
vertexData_[index].texCoords.v = tMax.y - vertexData_[index].texCoords.v;
}
}
[self updateColor];
}
if(type_ == kCCProgressTimerTypeHorizontalBarLR){
vertexData_[vIndexes[0] = 3].texCoords = (ccTex2F){tMax.x*alpha, tMax.y};
vertexData_[vIndexes[1] = 2].texCoords = (ccTex2F){tMax.x*alpha, 0};
}else if (type_ == kCCProgressTimerTypeHorizontalBarRL) {
vertexData_[vIndexes[0] = 1].texCoords = (ccTex2F){tMax.x*(1.f - alpha), 0};
vertexData_[vIndexes[1] = 0].texCoords = (ccTex2F){tMax.x*(1.f - alpha), tMax.y};
}else if (type_ == kCCProgressTimerTypeVerticalBarBT) {
vertexData_[vIndexes[0] = 0].texCoords = (ccTex2F){0, tMax.y*(1.f - alpha)};
vertexData_[vIndexes[1] = 2].texCoords = (ccTex2F){tMax.x, tMax.y*(1.f - alpha)};
}else if (type_ == kCCProgressTimerTypeVerticalBarTB) {
vertexData_[vIndexes[0] = 1].texCoords = (ccTex2F){0, tMax.y*alpha};
vertexData_[vIndexes[1] = 3].texCoords = (ccTex2F){tMax.x, tMax.y*alpha};
}
index = vIndexes[0];
vertexData_[index].vertices = [self vertexFromTexCoord:ccp(vertexData_[index].texCoords.u, vertexData_[index].texCoords.v)];
index = vIndexes[1];
vertexData_[index].vertices = [self vertexFromTexCoord:ccp(vertexData_[index].texCoords.u, vertexData_[index].texCoords.v)];
if (sprite_.flipY || sprite_.flipX) {
if (sprite_.flipX) {
index = vIndexes[0];
vertexData_[index].texCoords.u = tMax.x - vertexData_[index].texCoords.u;
index = vIndexes[1];
vertexData_[index].texCoords.u = tMax.x - vertexData_[index].texCoords.u;
}
if(sprite_.flipY){
index = vIndexes[0];
vertexData_[index].texCoords.v = tMax.y - vertexData_[index].texCoords.v;
index = vIndexes[1];
vertexData_[index].texCoords.v = tMax.y - vertexData_[index].texCoords.v;
}
}
}
-(CGPoint)boundaryTexCoord:(char)index
{
if (index < kProgressTextureCoordsCount) {
switch (type_) {
case kCCProgressTimerTypeRadialCW:
return ccp((kProgressTextureCoords>>((index<<1)+1))&1,(kProgressTextureCoords>>(index<<1))&1);
case kCCProgressTimerTypeRadialCCW:
return ccp((kProgressTextureCoords>>(7-(index<<1)))&1,(kProgressTextureCoords>>(7-((index<<1)+1)))&1);
default:
break;
}
}
return CGPointZero;
}
-(void)draw
{
[super draw];
if(!vertexData_)return;
if(!sprite_)return;
ccBlendFunc blendFunc = sprite_.blendFunc;
BOOL newBlend = blendFunc.src != CC_BLEND_SRC || blendFunc.dst != CC_BLEND_DST;
if( newBlend )
glBlendFunc( blendFunc.src, blendFunc.dst );
/// ========================================================================
// Replaced [texture_ drawAtPoint:CGPointZero] with my own vertexData
// Everything above me and below me is copied from CCTextureNode's draw
glBindTexture(GL_TEXTURE_2D, sprite_.texture.name);
glVertexPointer(2, GL_FLOAT, sizeof(ccV2F_C4B_T2F), &vertexData_[0].vertices);
glTexCoordPointer(2, GL_FLOAT, sizeof(ccV2F_C4B_T2F), &vertexData_[0].texCoords);
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ccV2F_C4B_T2F), &vertexData_[0].colors);
if(type_ == kCCProgressTimerTypeRadialCCW || type_ == kCCProgressTimerTypeRadialCW){
glDrawArrays(GL_TRIANGLE_FAN, 0, vertexDataCount_);
} else if (type_ == kCCProgressTimerTypeHorizontalBarLR ||
type_ == kCCProgressTimerTypeHorizontalBarRL ||
type_ == kCCProgressTimerTypeVerticalBarBT ||
type_ == kCCProgressTimerTypeVerticalBarTB) {
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexDataCount_);
}
//glDrawElements(GL_TRIANGLES, indicesCount_, GL_UNSIGNED_BYTE, indices_);
/// ========================================================================
if( newBlend )
glBlendFunc(CC_BLEND_SRC, CC_BLEND_DST);
}
@end