/*
 * cocos2d for iPhone: http://www.cocos2d-iphone.org
 *
 * Copyright (c) 2009 On-Core
 *
 * 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 "CCActionGrid3D.h"
#import "ccMacros.h"
#import "Support/CGPointExtension.h"

#pragma mark -
#pragma mark Waves3D

@implementation CCWaves3D

@synthesize amplitude;
@synthesize amplitudeRate;

+(id)actionWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d
{
	return [[[self alloc] initWithWaves:wav amplitude:amp grid:gridSize duration:d] autorelease];
}

-(id)initWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gSize duration:(ccTime)d
{
	if ( (self = [super initWithSize:gSize duration:d]) )
	{
		waves = wav;
		amplitude = amp;
		amplitudeRate = 1.0f;
	}
	
	return self;
}

-(id) copyWithZone: (NSZone*) zone
{
	CCGridAction *copy = [[[self class] allocWithZone:zone] initWithWaves:waves amplitude:amplitude grid:gridSize_ duration:duration_];
	return copy;
}


-(void)update:(ccTime)time
{
	int i, j;
	
	for( i = 0; i < (gridSize_.x+1); i++ )
	{
		for( j = 0; j < (gridSize_.y+1); j++ )
		{
			ccVertex3F	v = [self originalVertex:ccg(i,j)];
			v.z += (sinf((CGFloat)M_PI*time*waves*2 + (v.y+v.x) * .01f) * amplitude * amplitudeRate);
			[self setVertex:ccg(i,j) vertex:v];
		}
	}
}
@end

////////////////////////////////////////////////////////////

#pragma mark -
#pragma mark FlipX3D

@implementation CCFlipX3D

+(id) actionWithDuration:(ccTime)d
{
	return [[[self alloc] initWithSize:ccg(1,1) duration:d] autorelease];
}

-(id) initWithDuration:(ccTime)d
{
	return [super initWithSize:ccg(1,1) duration:d];
}

-(id)initWithSize:(ccGridSize)gSize duration:(ccTime)d
{
	if ( gSize.x != 1 || gSize.y != 1 )
	{
		[NSException raise:@"FlipX3D" format:@"Grid size must be (1,1)"];
	}
	
	return [super initWithSize:gSize duration:d];
}

-(id) copyWithZone: (NSZone*) zone
{
	CCGridAction *copy = [[[self class] allocWithZone:zone] initWithSize:gridSize_ duration:duration_];
	return copy;
}


-(void)update:(ccTime)time
{
	CGFloat angle = (CGFloat)M_PI * time; // 180 degrees
	CGFloat mz = sinf( angle );
	angle = angle / 2.0f;     // x calculates degrees from 0 to 90
	CGFloat mx = cosf( angle );
	
	ccVertex3F	v0, v1, v, diff;
	
	v0 = [self originalVertex:ccg(1,1)];
	v1 = [self originalVertex:ccg(0,0)];
	
	CGFloat	x0 = v0.x;
	CGFloat	x1 = v1.x;
	CGFloat x;
	ccGridSize	a, b, c, d;
	
	if ( x0 > x1 )
	{
		// Normal Grid
		a = ccg(0,0);
		b = ccg(0,1);
		c = ccg(1,0);
		d = ccg(1,1);
		x = x0;
	}
	else
	{
		// Reversed Grid
		c = ccg(0,0);
		d = ccg(0,1);
		a = ccg(1,0);
		b = ccg(1,1);
		x = x1;
	}
	
	diff.x = ( x - x * mx );
	diff.z = fabsf( floorf( (x * mz) / 4.0f ) );
	
// bottom-left
	v = [self originalVertex:a];
	v.x = diff.x;
	v.z += diff.z;
	[self setVertex:a vertex:v];
	
// upper-left
	v = [self originalVertex:b];
	v.x = diff.x;
	v.z += diff.z;
	[self setVertex:b vertex:v];
	
// bottom-right
	v = [self originalVertex:c];
	v.x -= diff.x;
	v.z -= diff.z;
	[self setVertex:c vertex:v];
	
// upper-right
	v = [self originalVertex:d];
	v.x -= diff.x;
	v.z -= diff.z;
	[self setVertex:d vertex:v];
}

@end

////////////////////////////////////////////////////////////

#pragma mark -
#pragma mark FlipY3D

@implementation CCFlipY3D

-(void)update:(ccTime)time
{
	CGFloat angle = (CGFloat)M_PI * time; // 180 degrees
	CGFloat mz = sinf( angle );
	angle = angle / 2.0f;     // x calculates degrees from 0 to 90
	CGFloat my = cosf( angle );
	
	ccVertex3F	v0, v1, v, diff;
	
	v0 = [self originalVertex:ccg(1,1)];
	v1 = [self originalVertex:ccg(0,0)];
	
	CGFloat	y0 = v0.y;
	CGFloat	y1 = v1.y;
	CGFloat y;
	ccGridSize	a, b, c, d;
	
	if ( y0 > y1 )
	{
		// Normal Grid
		a = ccg(0,0);
		b = ccg(0,1);
		c = ccg(1,0);
		d = ccg(1,1);
		y = y0;
	}
	else
	{
		// Reversed Grid
		b = ccg(0,0);
		a = ccg(0,1);
		d = ccg(1,0);
		c = ccg(1,1);
		y = y1;
	}
	
	diff.y = y - y * my;
	diff.z = fabsf( floorf( (y * mz) / 4.0f ) );
	
	// bottom-left
	v = [self originalVertex:a];
	v.y = diff.y;
	v.z += diff.z;
	[self setVertex:a vertex:v];
	
	// upper-left
	v = [self originalVertex:b];
	v.y -= diff.y;
	v.z -= diff.z;
	[self setVertex:b vertex:v];
	
	// bottom-right
	v = [self originalVertex:c];
	v.y = diff.y;
	v.z += diff.z;
	[self setVertex:c vertex:v];
	
	// upper-right
	v = [self originalVertex:d];
	v.y -= diff.y;
	v.z -= diff.z;
	[self setVertex:d vertex:v];
}

@end

////////////////////////////////////////////////////////////

#pragma mark -
#pragma mark Lens3D

@implementation CCLens3D

@synthesize lensEffect=lensEffect_;

+(id)actionWithPosition:(CGPoint)pos radius:(float)r grid:(ccGridSize)gridSize duration:(ccTime)d
{
	return [[[self alloc] initWithPosition:pos radius:r grid:gridSize duration:d] autorelease];
}

-(id)initWithPosition:(CGPoint)pos radius:(float)r grid:(ccGridSize)gSize duration:(ccTime)d
{
	if ( (self = [super initWithSize:gSize duration:d]) )
	{
		position_ = ccp(-1,-1);
		self.position = pos;
		radius_ = r;
		lensEffect_ = 0.7f;
		dirty_ = YES;
	}
	
	return self;
}

-(id) copyWithZone: (NSZone*) zone
{
	CCGridAction *copy = [[[self class] allocWithZone:zone] initWithPosition:position_ radius:radius_ grid:gridSize_ duration:duration_];
	return copy;
}

-(void) setPosition:(CGPoint)pos
{
	if( ! CGPointEqualToPoint(pos, position_) ) {
		position_ = pos;
		positionInPixels_.x = pos.x * CC_CONTENT_SCALE_FACTOR();
		positionInPixels_.y = pos.y * CC_CONTENT_SCALE_FACTOR();
		
		dirty_ = YES;
	}
}

-(CGPoint) position
{
	return position_;
}
	
-(void)update:(ccTime)time
{
	if ( dirty_ )
	{
		int i, j;
		
		for( i = 0; i < gridSize_.x+1; i++ )
		{
			for( j = 0; j < gridSize_.y+1; j++ )
			{
				ccVertex3F	v = [self originalVertex:ccg(i,j)];
				CGPoint vect = ccpSub(positionInPixels_, ccp(v.x,v.y));
				CGFloat r = ccpLength(vect);
				
				if ( r < radius_ )
				{
					r = radius_ - r;
					CGFloat pre_log = r / radius_;
					if ( pre_log == 0 ) pre_log = 0.001f;
					float l = logf(pre_log) * lensEffect_;
					float new_r = expf( l ) * radius_;
					
					if ( ccpLength(vect) > 0 )
					{
						vect = ccpNormalize(vect);
						CGPoint new_vect = ccpMult(vect, new_r);
						v.z += ccpLength(new_vect) * lensEffect_;
					}
				}
				
				[self setVertex:ccg(i,j) vertex:v];
			}
		}
		
		dirty_ = NO;
	}
}

@end

////////////////////////////////////////////////////////////

#pragma mark -
#pragma mark Ripple3D

@implementation CCRipple3D

@synthesize amplitude = amplitude_;
@synthesize amplitudeRate = amplitudeRate_;

+(id)actionWithPosition:(CGPoint)pos radius:(float)r waves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d
{
	return [[[self alloc] initWithPosition:pos radius:r waves:wav amplitude:amp grid:gridSize duration:d] autorelease];
}

-(id)initWithPosition:(CGPoint)pos radius:(float)r waves:(int)wav amplitude:(float)amp grid:(ccGridSize)gSize duration:(ccTime)d
{
	if ( (self = [super initWithSize:gSize duration:d]) )
	{
		self.position = pos;
		radius_ = r;
		waves_ = wav;
		amplitude_ = amp;
		amplitudeRate_ = 1.0f;
	}
	
	return self;
}

-(CGPoint) position
{
	return position_;
}

-(void) setPosition:(CGPoint)pos
{
	position_ = pos;
	positionInPixels_.x = pos.x * CC_CONTENT_SCALE_FACTOR();
	positionInPixels_.y = pos.y * CC_CONTENT_SCALE_FACTOR();
}

-(id) copyWithZone: (NSZone*) zone
{
	CCGridAction *copy = [[[self class] allocWithZone:zone] initWithPosition:position_ radius:radius_ waves:waves_ amplitude:amplitude_ grid:gridSize_ duration:duration_];
	return copy;
}


-(void)update:(ccTime)time
{
	int i, j;
	
	for( i = 0; i < (gridSize_.x+1); i++ )
	{
		for( j = 0; j < (gridSize_.y+1); j++ )
		{
			ccVertex3F	v = [self originalVertex:ccg(i,j)];
			CGPoint vect = ccpSub(positionInPixels_, ccp(v.x,v.y));
			CGFloat r = ccpLength(vect);
			
			if ( r < radius_ )
			{
				r = radius_ - r;
				CGFloat rate = powf( r / radius_, 2);
				v.z += (sinf( time*(CGFloat)M_PI*waves_*2 + r * 0.1f) * amplitude_ * amplitudeRate_ * rate );
			}
			
			[self setVertex:ccg(i,j) vertex:v];
		}
	}
}

@end

////////////////////////////////////////////////////////////

#pragma mark -
#pragma mark Shaky3D

@implementation CCShaky3D

+(id)actionWithRange:(int)range shakeZ:(BOOL)sz grid:(ccGridSize)gridSize duration:(ccTime)d
{
	return [[[self alloc] initWithRange:range shakeZ:sz grid:gridSize duration:d] autorelease];
}

-(id)initWithRange:(int)range shakeZ:(BOOL)sz grid:(ccGridSize)gSize duration:(ccTime)d
{
	if ( (self = [super initWithSize:gSize duration:d]) )
	{
		randrange = range;
		shakeZ = sz;
	}
	
	return self;
}

-(id) copyWithZone: (NSZone*) zone
{
	CCGridAction *copy = [[[self class] allocWithZone:zone] initWithRange:randrange shakeZ:shakeZ grid:gridSize_ duration:duration_];
	return copy;
}

-(void)update:(ccTime)time
{
	int i, j;
	
	for( i = 0; i < (gridSize_.x+1); i++ )
	{
		for( j = 0; j < (gridSize_.y+1); j++ )
		{
			ccVertex3F	v = [self originalVertex:ccg(i,j)];
			v.x += ( rand() % (randrange*2) ) - randrange;
			v.y += ( rand() % (randrange*2) ) - randrange;
			if( shakeZ )
				v.z += ( rand() % (randrange*2) ) - randrange;
			
			[self setVertex:ccg(i,j) vertex:v];
		}
	}
}

@end

////////////////////////////////////////////////////////////

#pragma mark -
#pragma mark Liquid

@implementation CCLiquid

@synthesize amplitude;
@synthesize amplitudeRate;

+(id)actionWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d
{
	return [[[self alloc] initWithWaves:wav amplitude:amp grid:gridSize duration:d] autorelease];
}

-(id)initWithWaves:(int)wav amplitude:(float)amp grid:(ccGridSize)gSize duration:(ccTime)d
{
	if ( (self = [super initWithSize:gSize duration:d]) )
	{
		waves = wav;
		amplitude = amp;
		amplitudeRate = 1.0f;
	}
	
	return self;
}

-(void)update:(ccTime)time
{
	int i, j;
	
	for( i = 1; i < gridSize_.x; i++ )
	{
		for( j = 1; j < gridSize_.y; j++ )
		{
			ccVertex3F	v = [self originalVertex:ccg(i,j)];
			v.x = (v.x + (sinf(time*(CGFloat)M_PI*waves*2 + v.x * .01f) * amplitude * amplitudeRate));
			v.y = (v.y + (sinf(time*(CGFloat)M_PI*waves*2 + v.y * .01f) * amplitude * amplitudeRate));
			[self setVertex:ccg(i,j) vertex:v];
		}
	}
}	

-(id) copyWithZone: (NSZone*) zone
{
	CCGridAction *copy = [[[self class] allocWithZone:zone] initWithWaves:waves amplitude:amplitude grid:gridSize_ duration:duration_];
	return copy;
}

@end

////////////////////////////////////////////////////////////

#pragma mark -
#pragma mark Waves

@implementation CCWaves

@synthesize amplitude;
@synthesize amplitudeRate;

+(id)actionWithWaves:(int)wav amplitude:(float)amp horizontal:(BOOL)h vertical:(BOOL)v grid:(ccGridSize)gridSize duration:(ccTime)d
{
	return [[[self alloc] initWithWaves:wav amplitude:amp horizontal:h vertical:v grid:gridSize duration:d] autorelease];
}

-(id)initWithWaves:(int)wav amplitude:(float)amp horizontal:(BOOL)h vertical:(BOOL)v grid:(ccGridSize)gSize duration:(ccTime)d
{
	if ( (self = [super initWithSize:gSize duration:d]) )
	{
		waves = wav;
		amplitude = amp;
		amplitudeRate = 1.0f;
		horizontal = h;
		vertical = v;
	}
	
	return self;
}

-(void)update:(ccTime)time
{
	int i, j;
	
	for( i = 0; i < (gridSize_.x+1); i++ )
	{
		for( j = 0; j < (gridSize_.y+1); j++ )
		{
			ccVertex3F	v = [self originalVertex:ccg(i,j)];
			
			if ( vertical )
				v.x = (v.x + (sinf(time*(CGFloat)M_PI*waves*2 + v.y * .01f) * amplitude * amplitudeRate));
			
			if ( horizontal )
				v.y = (v.y + (sinf(time*(CGFloat)M_PI*waves*2 + v.x * .01f) * amplitude * amplitudeRate));
					
			[self setVertex:ccg(i,j) vertex:v];
		}
	}
}

-(id) copyWithZone: (NSZone*) zone
{
	CCGridAction *copy = [[[self class] allocWithZone:zone] initWithWaves:waves amplitude:amplitude horizontal:horizontal vertical:vertical grid:gridSize_ duration:duration_];
	return copy;
}

@end

////////////////////////////////////////////////////////////

#pragma mark -
#pragma mark Twirl

@implementation CCTwirl

@synthesize amplitude = amplitude_;
@synthesize amplitudeRate = amplitudeRate_;

+(id)actionWithPosition:(CGPoint)pos twirls:(int)t amplitude:(float)amp grid:(ccGridSize)gridSize duration:(ccTime)d
{
	return [[[self alloc] initWithPosition:pos twirls:t amplitude:amp grid:gridSize duration:d] autorelease];
}

-(id)initWithPosition:(CGPoint)pos twirls:(int)t amplitude:(float)amp grid:(ccGridSize)gSize duration:(ccTime)d
{
	if ( (self = [super initWithSize:gSize duration:d]) )
	{
		self.position = pos;
		twirls_ = t;
		amplitude_ = amp;
		amplitudeRate_ = 1.0f;
	}
	
	return self;
}

-(void) setPosition:(CGPoint)pos
{
	position_ = pos;
	positionInPixels_.x = pos.x * CC_CONTENT_SCALE_FACTOR();
	positionInPixels_.y = pos.y * CC_CONTENT_SCALE_FACTOR();
}

-(CGPoint) position
{
	return position_;
}

-(void)update:(ccTime)time
{
	int i, j;
	CGPoint	c = positionInPixels_;
	
	for( i = 0; i < (gridSize_.x+1); i++ )
	{
		for( j = 0; j < (gridSize_.y+1); j++ )
		{
			ccVertex3F v = [self originalVertex:ccg(i,j)];
			
			CGPoint	avg = ccp(i-(gridSize_.x/2.0f), j-(gridSize_.y/2.0f));
			CGFloat r = ccpLength( avg );
			
			CGFloat amp = 0.1f * amplitude_ * amplitudeRate_;
			CGFloat a = r * cosf( (CGFloat)M_PI/2.0f + time * (CGFloat)M_PI * twirls_ * 2 ) * amp;
			
			float cosA = cosf(a);
			float sinA = sinf(a);
			
			CGPoint	d = {
				sinA * (v.y-c.y) + cosA * (v.x-c.x),
				cosA * (v.y-c.y) - sinA * (v.x-c.x)
			};
			
			v.x = c.x + d.x;
			v.y = c.y + d.y;
			
			[self setVertex:ccg(i,j) vertex:v];
		}
	}
}

-(id) copyWithZone: (NSZone*) zone
{
	CCGridAction *copy = [[[self class] allocWithZone:zone] initWithPosition:position_
																	  twirls:twirls_
																   amplitude:amplitude_
																		grid:gridSize_
																	duration:duration_];
	return copy;
}


@end
