#include "perlin.h"
#include "fastmath.h"

void PerlinInit(float*rv,const DWORD size,DWORD seed)
{
	for(DWORD i=0;i<size;i++)
		*rv++ = PerlinRand(seed);
}

void PerlinCloudize(float*data,int size,float CS,float CC)
{
	for(;size;size--)
	{
		float c = *data - CC; 
		if (c < 0) 
			c=0.f;
  		*data++ = pow(c,CS);  
	}
}

void PerlinScale(float*data,int size,float s)
{
	for(;size;size--)
		*data++ *= s;
}

void PerlinAdd(float*data,int size,float add)
{
	for(;size;size--)
		*data++ += add;
}

void Perlin1D::BuildMap(float*outdata,DWORD size,float strength,DWORD noct)
{
	ZeroMemory(outdata,sizeof(float)*size);

	float *pout;
	for(DWORD oct = 0;oct<noct;oct++)
	{
		pout = outdata;
		int p = 1<<oct;
		for(DWORD x=0;x<size;x++)
		{
			float xind = ((float)x/size)*p;
			*pout++ += GetP(xind)*strength/p;
		}
	}
}

float Perlin1D::GetP(float x)//valid only 0 <= x <= n
{
	int i1 = ((int)(x*m_Size))%m_Size;
	if(i1 == m_Size)
		i1 = 0;
	int i0 = i1-1;
	if(i0 < 0)
		i0 = m_Size-1;
	int i2 = i1+1;
	if(i2 >= (int)m_Size)
		i2 = 0;
	int i3 = i1+2;
	if(i3 >= (int)m_Size)
		i3 -= m_Size;

	float u = x*m_Size;
	u -= (int)u;

	return Cubic_Interpolate(m_RV[i0],m_RV[i1],m_RV[i2],m_RV[i3],u);
}

void Perlin2D::BuildMap(float*outdata,DWORD xsize,DWORD ysize,float strength,DWORD noct)
{
	ZeroMemory(outdata,sizeof(float)*xsize*ysize);

	for(DWORD oct = 0;oct<noct;oct++)
	{
		int p = 1<<oct;
		AddOct(outdata,xsize,ysize,strength/p,oct); 
	}
}

void Perlin2D::AddOct(float*data,const DWORD&xsize,const DWORD&ysize,const float&strength,DWORD oct)
{
	float p = 1.f;
	while(oct--)
		p *= 2.f;
		
	float yadd = p/ysize;
	float xadd = p/xsize;
	
	float yind = 0;
	for(DWORD y=0;y<ysize;y++)
	{
		float xind = 0;
		for(DWORD x=0;x<xsize;x++)
		{
			*data++ += GetP(xind,yind)*strength;
			xind += xadd;
		}
		yind += yadd;
	}
}

float Perlin2D::GetP(const float&x,const float&y)//valid only 0 <= x <= n
{
	float u = x*m_XSize;

	int x1 = ((int)u)%m_XSize;
	/*if(x1 == m_XSize)
		x1 = 0;*/
	int x0 = x1-1;
	if(x0 < 0)
		x0 = m_XSize-1;
	int x2 = x1+1;
	if(x2 >= (int)m_XSize)
		x2 = 0;
	int x3 = x1+2;
	if(x3 >= (int)m_XSize)
		x3 -= m_XSize;

	u -= (int)u;

	float v = y*m_YSize;

	int y0 = (((int)v)%m_YSize) - 1;
	if(y0 < 0)
		y0 = m_YSize-1;

	v -= (int)v;

	float *prow = m_RV + y0*m_XSize;
	float h0 = Cubic_Interpolate(prow[x0],prow[x1],prow[x2],prow[x3],u);
	if(y0 == m_YSize-1)
		prow = m_RV;
	else
		prow += m_XSize;
	float h1 = Cubic_Interpolate(prow[x0],prow[x1],prow[x2],prow[x3],u);
	if(++y0 == m_YSize-1)
		prow = m_RV;
	else
		prow += m_XSize;
	float h2 = Cubic_Interpolate(prow[x0],prow[x1],prow[x2],prow[x3],u);
	if(++y0 == m_YSize-1)
		prow = m_RV;
	else
		prow += m_XSize;

	return Cubic_Interpolate(h0,h1,h2,Cubic_Interpolate(prow[x0],prow[x1],prow[x2],prow[x3],u),v);
}

Perlin3D::Perlin3D(DWORD xsize,DWORD ysize,DWORD zsize,DWORD seed)
{
	m_XSize = xsize;
	m_YSize = ysize;
	m_ZSize = zsize;
	
	m_P = new Perlin2D*[zsize];

	for(unsigned i=0; i<zsize; i++)
	{
		DWORD pseed = (unsigned)(PerlinRand(seed)*0x7fffffff);
		m_P[i] = new Perlin2D(xsize,ysize,pseed);
	}
}

Perlin3D::~Perlin3D(void)
{
	for(unsigned i=0; i<m_ZSize; i++)
		delete m_P[i];
	delete m_P;
}

void Perlin3D::BuildFrame(float*outdata, float t, DWORD xsize, DWORD ysize, DWORD baseoct, DWORD octcount)
{
	ZeroMemory(outdata, sizeof(float)*xsize*ysize);

	for(unsigned oct = baseoct; oct < baseoct + octcount; oct++)
	{
		const int p = 1<<oct;
		const float str = 1.f/p;
		const float xadd = p/(float)xsize;
		const float yadd = p/(float)ysize;
		
		float ys = 0;
		for(unsigned y = 0; y < ysize; y++)
		{
			float xs = 0;
			for(unsigned x = 0; x < xsize; x++)
			{
				outdata[x + y * xsize] += GetP(xs, ys, t) * str;
				xs += xadd;
			}
			ys += yadd;
		}
	}
}

float Perlin3D::GetP(const float x, const float y, const float z)
{
	float w = z * m_ZSize;

	int t0, t1, t2, t3;

	t0 = (((int)w)%m_ZSize) - 1;
	if(t0 < 0)
		t0 = m_ZSize-1;

	w -= (int)w;
	
	if(t0 < ((int)m_ZSize)-1)
		t1 = t0 + 1;
	else
		t1 = 0;

	if(t1 < ((int)m_ZSize)-1)
		t2 = t1 + 1;
	else
		t2 = 0;

	if(t2 < ((int)m_ZSize)-1)
		t3 = t2 + 1;
	else
		t3 = 0;

	const float p0 = m_P[t0]->GetP(x,y);
	const float p1 = m_P[t1]->GetP(x,y);
	const float p2 = m_P[t2]->GetP(x,y);
	const float p3 = m_P[t3]->GetP(x,y);

	return Cubic_Interpolate(p0, p1, p2, p3, w);
}
