#include "c4d_plugin.h"
#include "ge_matrix.h"
#include "c4d_tools.h"
#include "ge_lvector.h"
#include "c4d_file.h"
#include "c4d_basecontainer.h"

Matrix MatrixMove(const Vector &t)
{
	Matrix erg;
	erg.off = t;
	return erg;
}

Matrix MatrixScale(const Vector &s)
{
	Matrix erg;

	erg.v1.x=s.x;
	erg.v2.y=s.y;
	erg.v3.z=s.z;
	return erg;
}

Matrix MatrixRotX(Real w)
{
	Matrix erg;
	Real cs = Cos(w), sn = Sin(w);

	erg.v3.z= cs;
	erg.v2.z=-sn;
	erg.v3.y= sn;
	erg.v2.y= cs;
	return erg;
}

Matrix MatrixRotY(Real w)
{
	Matrix erg;
	Real cs = Cos(w), sn = Sin(w);

	erg.v1.x= cs;
	erg.v3.x=-sn;
	erg.v1.z= sn;
	erg.v3.z= cs;
	return erg;
}

Matrix MatrixRotZ(Real w)
{
	Matrix erg;
	Real cs = Cos(w), sn = Sin(w);

	erg.v1.x = cs;
	erg.v2.x = sn;
	erg.v1.y =-sn;
	erg.v2.y = cs;
	return erg;
}

static Real GetNear(Real alt, Real *neu)
{

	Real diff = (*neu-alt)/(pi2);
	diff = (diff-Floor(diff))*pi2;
	*neu = diff + alt;

	if (diff >= pi)
	{
		diff -= pi2;
		*neu -= pi2;
	}

	return diff;
}

Vector GetOptimalAngle(const Vector &orot, const Vector &nrot)
{
	Vector d1,d2,n2= Vector(pi+nrot.x,pi-nrot.y,pi+nrot.z),n1=nrot;

	d1.x = GetNear(orot.x,&n1.x);
	d1.y = GetNear(orot.y,&n1.y);
	d1.z = GetNear(orot.z,&n1.z);

	d2.x = GetNear(orot.x,&n2.x);
	d2.y = GetNear(orot.y,&n2.y);
	d2.z = GetNear(orot.z,&n2.z);

	if (Abs(d2.x)+Abs(d2.y)+Abs(d2.z)<Abs(d1.x)+Abs(d1.y)+Abs(d1.z))
		return n2;
	else
		return n1;
}

// Winkel eines Punkts bestimmen
Vector VectorToHPB(const Vector &p)
{
  Real l;
  Vector rot;

  l = Sqrt(p.x*p.x+p.z*p.z);

  if (l<0.00001)
	{
    if (p.y>0.0)
      rot = Vector(0.0, pi05,0.0);
    else
      rot = Vector(0.0,-pi05,0.0);
  }
	else
	{
    if (p.z>0.0)
      rot.x = -ASin(p.x/l);
    else
      rot.x = pi+ASin(p.x/l);
    rot.y = ATan(p.y/l);
    rot.z = 0.0;
  }
	return rot;
}

static Vector GetHPB(const Vector &v1, const Vector &v2, const Vector &v3)
{
  Vector rot;
  Real l = Sqrt(v3.x*v3.x+v3.z*v3.z);

  if (l<0.00001)
	{
		rot.x = 0.0;
		rot.z = ACos(Vector(1.0,0.0,0.0) * !v1);

    if (v3.y>0.0)
		{
      rot.y = pi05;
			if (v1.z<0.0) rot.z = pi2-rot.z;
    }
		else
		{
      rot.y = -pi05;
			if (v1.z>0.0) rot.z = pi2-rot.z;
		}
  }
	else
	{
    if (v3.z>0.0)
      rot.x = -ASin(v3.x/l);
    else
      rot.x = pi+ASin(v3.x/l);

		if (rot.x<0.0) rot.x += pi2;
    rot.y = ATan(v3.y/l);
		rot.z = ACos(Vector(Cos(rot.x),0.0,Sin(rot.x)) * !v1);
		if (v1.y>0.0) rot.z = pi2-rot.z;
  }

	return rot;
}

Vector MatrixToHPB(const Matrix &m)
{
	return GetHPB(m.v1,m.v2,m.v3);
}

// Winkel im Rad-/Euler-System
Matrix HPBToMatrix(const Vector &w)
{
	Real cn,sn,ck,sk,cs,ss,cosksinn,sinksinn;

	SinCos(w.x,ss,cs);
	SinCos(w.y,sn,cn);
	SinCos(w.z,sk,ck);

	cosksinn=ck*sn;
	sinksinn=sk*sn;

	return Matrix(Vector(0.0),
								Vector(ck*cs-sinksinn*ss,-sk*cn,ck*ss+sinksinn*cs),
								Vector(sk*cs+cosksinn*ss,ck*cn,sk*ss-cosksinn*cs),
								Vector(-cn*ss,sn,cn*cs));
}

void MatrixToRotAxis(const Matrix &mm, Vector *v, Real *w)
{
	Matrix m = mm;
	// MatrixVectoren MUESSEN normiert sein!!!
	m.v1=!m.v1;
	m.v2=!m.v2;
	m.v3=!m.v3;

	// Winkel berechnen
	*w = ACos((m.v1.x+m.v2.y+m.v3.z-1.0)/2.0);

	// Achse berechnen
	v->x= m.v2.z-m.v3.y;
	v->y= m.v3.x-m.v1.z;
	v->z= m.v1.y-m.v2.x;
	*v = !(*v);

	if (*v==0.0)
	{
		*v = Vector(0.0,1.0,0.0);
		*w = 0.0;
	}
}

Matrix RotAxisToMatrix(const Vector &v, Real w)
{
	Matrix m;

	if ((Abs(v.x)+Abs(v.y)+Abs(v.z))!=0.0 && w!=0.0)
	{
		// zuerst mal den Vector normieren ...
		m.v2 = !v;

		// jetzt ein rechtes KS basteln
		m.v1 = m.v2%Vector(0.0,0.0,1.0);

		if (Len(m.v1)>MINSIZE)
		{
			m.v3 = m.v1%m.v2;
		}
		else
		{
			m.v3 = Vector(1.0,0.0,0.0)%m.v2;
			m.v1 = m.v2%m.v3;
		}

		// Rotationsmatrix im Pleft- und Rechtssystem um Y ist gleich
		m = (m*MatrixRotY(w)) * !m;
	}
	return m;
}

Random::Random(void)
{
	Init(100);
}

void Random::Init(ULONG s)
{
	iset = 0;
	seed = s;
}

Real Random::Get01(void)
{
	const Real teiler = 2147483648.0+1.0; // +1.0, falls evtl. Ungenauigkeiten auftreten

  seed = ((seed+1)*69069) & 0x7FFFFFFF;
  return Real(seed)/teiler;
}

Real Random::Get11(void)
{
	return Get01()*2.0-1.0;
}

Real Random::GetG01(void)
{
	Real fac,rsq,v1,v2;

	if (iset==0)
	{
		do
		{
			v1 = Get11();
			v2 = Get11();
			rsq = v1*v1 + v2*v2;
		}
		while (rsq>=1.0 || rsq==0.0);

		fac  = Sqrt(-2.0*Ln(rsq)/rsq);
		gset = v1*fac;
		iset = 1;
		return v2*fac*0.09+0.5;
	}
	else
	{
		iset=0;
		return gset*0.09+0.5;
	}
}

Real Random::GetG11(void)
{
	Real fac,rsq,v1,v2;

	if (iset==0)
	{
		do
		{
			v1 = Get11();
			v2 = Get11();
			rsq = v1*v1 + v2*v2;
		}
		while (rsq>=1.0 || rsq==0.0);

		fac  = Sqrt(-2.0*Ln(rsq)/rsq);
		gset = v1*fac;
		iset = 1;
		return v2*fac*0.18;
	}
	else
	{
		iset=0;
		return gset*0.18;
	}
}

LVector ReflectRay(LVector *v, Vector *n)
{
	LVector nn = LV(*n);
	return (*v)-nn*(2.0*(nn*(*v)));
}

Vector RGBToHSV(const Vector &col)
{
  Real r,g,b,h,s,v,delta,max,min;

  r = col.x;
  g = col.y;
  b = col.z;

  max = r; if (g>max) max = g; if (b>max) max = b;
  min = r; if (g<min) min = g; if (b<min) min = b;

  v  = max;
  if (max>0.0)
  {
    s = 1.0-min/max;

    if (s<MINSIZE)
    {
      return Vector(0.0,0.0,min);
    }
	}
	else
	{
    return 0.0;
  }

  delta = max-min;
  if (r==max)
    h = (g-b)/delta;
  else if (g==max)
    h = 2.0+(b-r)/delta;
  else
    h = 4.0+(r-g)/delta;

  h /= 6.0;
  if (h<0.0) h += 1.0;
  return Vector(h,s,v);
}

Vector HSVToRGB(const Vector &col)
{
	Real r=0.0,g=0.0,b=0.0,h,s,v,f,p,q,t,i;
	LONG j;

  h = col.x;
  s = col.y;
  v = col.z;

  if (s<MINSIZE)
	{
    r=g=b=v;
	}
	else
	{
    if (h==1.0) h=0.0;

    h *= 6.0;
    i = Floor(h);
    f = h-i;
    p = v*(1.0-s);
    q = v*(1.0-s*f);
    t = v*(1.0-s*(1.0-f));
    j=i;
    switch (j)
		{
      case 0: r = v; g = t; b = p; break;
      case 1: r = q; g = v; b = p; break;
      case 2: r = p; g = v; b = t; break;
      case 3: r = p; g = q; b = v; break;
      case 4: r = t; g = p; b = v; break;
      case 5: r = v; g = p; b = q; break;
    }
	}
  return Vector(r,g,b);
}

Vector PointLineDistance(const Vector &p0, const Vector &v, const Vector &p)
{
	return p-(p0+(((p-p0)*v)/(v*v))*v);
}

VolumeData *VolumeData::Alloc(void)
{
	return C4DOS.Sh->AllocVolumeData();
}

void VolumeData::Free(VolumeData *&vd)
{
	C4DOS.Sh->FreeVolumeData(vd);
	vd=NULL;
}

TexData *TexData::Alloc(void)
{
	return C4DOS.Sh->AllocTexData();
}

void TexData::Free(TexData *&vd)
{
	C4DOS.Sh->FreeTexData(vd);
	vd=NULL;
}

void TexData::Init(void)
{
	C4DOS.Sh->InitTexData(this);
}

MinMax VolumeData::GetSceneBoundaries(void)
{
	RayObject *op=NULL;
	LONG			i,ocnt=GetObjCount();
	MinMax		mm;

	for (i=0; i<ocnt; i++)
	{
		op=GetObj(i);
		if (op->type!=O_SPHERE && op->type!=O_POLYGON) continue;
		mm.AddPoint(op->mp-op->rad);
		mm.AddPoint(op->mp+op->rad);
	}

	return mm;
}

void VolumeData::LightCalcSpecial(RayLight *rl, Real exponent, Bool nodif, Bool nospec, const Vector &rayv, Real cosc, const Vector &p, const Vector &bumpn, Real &diffuse, Real &specular)
{
	Vector d,s;
	C4DOS.Sh->LightCalcSpecial(NULL,rl,0.0,nospec?0.0:exponent,rayv,cosc,p,bumpn,&d,&s);
	diffuse=nodif?0.0:d.x;
	specular=s.x;
}

LONG VPBuffer::GetInfo(LONG type)
{
	return C4DOS.Sh->VPGetInfo(this,type);
}

Bool VPBuffer::GetLine(LONG x, LONG y, LONG cnt, void *data, LONG bitdepth, Bool dithering)
{
	return C4DOS.Sh->VPGetLine(this,x,y,cnt,data,bitdepth,dithering);
}

Bool VPBuffer::SetLine(LONG x, LONG y, LONG cnt, void *data, LONG bitdepth, Bool dithering)
{
	return C4DOS.Sh->VPSetLine(this,x,y,cnt,data,bitdepth,dithering);
}

Bool Render::AllocateBuffer(LONG id, LONG subid, LONG bitdepth, Bool visible)
{
	return C4DOS.Sh->VPAllocateBuffer(this,id,subid,bitdepth,visible);
}

LONG Render::AllocateBufferFX(LONG id, const String &name, LONG bitdepth, Bool visible)
{
	return C4DOS.Sh->VPAllocateBufferFX(this,id,name,bitdepth,visible);
}

VPBuffer *Render::GetBuffer(LONG id, LONG subid)
{
	return C4DOS.Sh->VPGetBuffer(this,id,subid);
}

BaseContainer Render::GetRenderData(void)
{
	BaseContainer bc;
	C4DOS.Sh->VPGetRenderData(this,&bc);
	return bc;
}

void Render::SetRenderData(const BaseContainer &ct)
{
	C4DOS.Sh->VPSetRenderData(this,&ct);
}

void Render::UpdateScreen(void)
{
	C4DOS.Sh->VPUpdateScreen(this);
}

VolumeData *Render::GetInitialVolumeData(LONG cpu)
{
	return C4DOS.Sh->VPGetInitialVolumeData(this,cpu);
}

RayLight *AllocRayLight(BaseObject *op)
{
	return C4DOS.Sh->AllocRayLight(op);
}

void FreeRayLight(RayLight *&lgt)
{
	C4DOS.Sh->FreeRayLight(lgt);
	lgt=NULL;
}

Bool IlluminateRayLight(RayLight *rl, Vector *color, Vector *light_vector, const Vector &p, const Vector &n)
{
	return C4DOS.Sh->Illuminate(NULL,rl,color,light_vector,p,n,0,0,NULL);
}

void RayFilter::AddLine(Real *line, LONG y)
{
	C4DOS.Sh->RfAddLine(this,line,y);
}

void RayFilter::EvaluateLine(Bool display, LONG y, LONG xcnt, BaseBitmap *bmp, LONG alpha)
{
	C4DOS.Sh->RfEvaluateLine(this,display,y,xcnt,bmp,alpha);
}

LONG RayFilter::GetYRadius(void)
{
	return C4DOS.Sh->RfGetYRadius(this);
}

void RayFilter::SetRange(LONG t_top, LONG t_bottom)
{
	C4DOS.Sh->RfSetRange(this,t_top,t_bottom);
}

RayFilter *RayFilter::Alloc(LONG xres, LONG yres, LONG components, LONG left, LONG right, LONG type, Real softness, Bool dithering)
{
	return C4DOS.Sh->RfAllocRayFilter(xres,yres,components,left,right,type,softness,dithering);
}

void RayFilter::Free(RayFilter *&filter)
{
	C4DOS.Sh->RfFreeRayFilter(filter);
	filter=NULL;
}

void CalcRestrictionInc(LightRestriction *lr, RayObject *op, Bool &nodif, Bool &nospec)
{
	if (!op->link || op->lightrestriction_index>=lr->object_cnt) return; 
	if (lr->object[op->lightrestriction_index]&LIGHTRESTRICTION_NODIFFUSE) nodif=TRUE;
	if (lr->object[op->lightrestriction_index]&LIGHTRESTRICTION_NOSPECULAR) nospec=TRUE;
}
