#include "lib_ngon.h"

#ifdef __API_INTERN__
#include "polygonobject.h"
#include "basemath.h"
#else
#include "c4d_baseobject.h"
#include "c4d_tools.h"
#endif

#include "c4d_general.h"

Ngon::Ngon()
{
	points=NULL;
	segments=NULL;
	count=0;
	segcount=0;
}

Ngon::~Ngon()
{
	Free();
}

Ngon::Ngon(const CPolygon &ply)
{
	points=NULL;
	segments=NULL;
	count=0;
	segcount=0;

	if (ply.c==ply.d)
	{
		if (!Alloc(3,1)) return;
		points[0]=ply.a;
		points[1]=ply.b;
		points[2]=ply.c;
		segments[0]=3;
	}
	else
	{
		if (!Alloc(4,1)) return;
		points[0]=ply.a;
		points[1]=ply.b;
		points[2]=ply.c;
		points[3]=ply.d;
		segments[0]=4;
	}
}

Ngon::Ngon(const Ngon &src)
{
	points=NULL;
	segments=NULL;
	count=0;
	segcount=0;
	CopyFrom(&src);
}

Ngon &Ngon::operator=(const Ngon &src)
{
	CopyFrom(&src);
	return *this;
}

Bool Ngon::Alloc(LONG cnt, LONG scnt)
{
	Free();

	if (cnt)
	{
		points=bNew LONG[cnt];
		if (!points) return FALSE;

		if (scnt)
		{
			segments=bNew LONG[scnt];
			if (!segments)
			{
				bDelete(points);
				return FALSE;
			}
		}

		count=cnt;
		segcount=scnt;
		return TRUE;
	}

	return FALSE;
}

void Ngon::Free()
{
	bDelete(points);
	bDelete(segments);
	count=0;
	segcount=0;
}

Bool Ngon::CopyFrom(const Ngon *src)
{
	Free();

	if (src && src->points && src->count)
	{
		points=bNew LONG[src->count];
		if (!points) return FALSE;

		if (src->segments && src->segcount)
		{
			segments=bNew LONG[src->segcount];
			if (!segments)
			{
				Free();
				return FALSE;
			}
		}

		segcount=src->segcount;
		count=src->count;

		CopyMem(src->points,points,sizeof(LONG)*count);
		if (segments) CopyMem(src->segments,segments,sizeof(LONG)*segcount);
	}

	return TRUE;
}

Bool Ngon::CopyFrom(LONG cnt, LONG scnt, LONG* pts, LONG* segs)
{
	Free();

	if (pts && cnt)
	{
		points=bNew LONG[cnt];
		if (!points) return FALSE;

		if (segs && scnt)
		{
			segments=bNew LONG[scnt];
			if (!segments)
			{
				Free();
				return FALSE;
			}
			segcount=scnt;
		}
		else
		{
			segcount = 1;
			segments=bNew LONG[segcount];
			if (!segments)
			{
				Free();
				return FALSE;
			}
		}

		count=cnt;

		CopyMem((void*)pts,points,sizeof(LONG)*count);
		if (segs)
			CopyMem((void*)segs,segments,sizeof(LONG)*segcount);
		else
			segments[0] = cnt;
	}
	return TRUE;
}

void Ngon::Release()
{
	points=NULL;
	segments=NULL;
	count=0;
	segcount=0;
}

static Real CalcDet(const Vector &v1, const Vector &v2, const Vector &v3)
{
	return v1.x * (v2.y - v3.y) + v2.x * (v3.y - v1.y) + v3.x * (v1.y - v2.y);
}

Real Ngon::CalcArea(LONG seg, const Vector* p, Matrix& m)
{
	Real a = 0.0f;
	LONG l, o = 0;
	Vector v1(DC), v2(DC), v3(DC);

	if (segments[seg] < 3)
		return 0.0f;

	if (seg == 0)
	{
		// the matrix m is used to project the ngon onto a plane
		Vector n, prev(DC);

		v1 = p[points[0]];
		prev = p[points[1]] - v1;
		for (l = 2; l < segments[0]; l++)
		{
			v2 = p[points[l]] - v1;
			n += prev % v2;
			prev = v2;
		}

		m.off = 0.0f;
		m.v3 = !n;
		if (Abs(n.x) > Abs(n.y) && Abs(n.x) > Abs(n.z))
		{
			m.v1 = Vector(0.0f, 1.0f, 0.0f) % m.v3; // x axis
			m.v2 = m.v3 % m.v1; // y axis
		}
		else if (Abs(n.y) > Abs(n.z))
		{
			m.v1 = m.v3 % Vector(0.0f, 0.0f, 1.0f);
			m.v2 = m.v3 % m.v1;
		}
		else
		{
			m.v2 = m.v3 % Vector(1.0f, 0.0f, 0.0f);
			m.v1 = m.v2 % m.v3;
		}
		m = !m;
	}
	
	for (l = 0; l < seg; l++)
		o += segments[l];

	v1 = m * p[points[o]];
	v2 = m * p[points[o + 1]];
	for (l = 0; l < segments[seg]; l++)
	{
		v3 = m * p[points[o + (l + 2) % segments[seg]]];
		a += CalcDet(v1, v2, v3);
		v1 = v2;
		v2 = v3;
	}

	return a;
}

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

NgonBaseLib *lib_ngonbase = NULL;

static NgonBaseLib *CheckNgonBaseLib(LONG offset)
{
	return (NgonBaseLib*)CheckLib(LIBRARY_NGON,offset,(C4DLibrary**)&lib_ngonbase);
}

#define NgonBaseLibCall(b) 		NgonBaseLib *lib = CheckNgonBaseLib(LIBOFFSET(NgonBaseLib, b)); \
															if (!lib || !lib->b) return; \
															(((iNgonBase*)this)->*lib->b)

#define NgonBaseLibCallR(a,b)  NgonBaseLib *lib = CheckNgonBaseLib(LIBOFFSET(NgonBaseLib, b)); \
															if (!lib || !lib->b) return a; \
															return (((iNgonBase*)this)->*lib->b)

NgonBase* NgonBase::Alloc()
{
	NgonBaseLib *lib = CheckNgonBaseLib(LIBOFFSET(NgonBaseLib,Alloc)); if (!lib) return NULL;
	return (NgonBase*)lib->Alloc();
}

void NgonBase::Free(NgonBase *&p)
{
	if (!p) return;
	NgonBaseLib *lib = CheckNgonBaseLib(LIBOFFSET(NgonBaseLib,Free)); if (!lib) return;
	iNgonBase* i = (iNgonBase*)p;
	lib->Free(i);
	p = NULL;
}

Bool NgonBase::CopyTo(NgonBase *dst)
{
	NgonBaseLibCallR(FALSE,CopyTo)((iNgonBase*)dst);
}

Bool NgonBase::Write(HyperFile *hf)
{
	NgonBaseLibCallR(FALSE,Write)(hf);
}

Bool NgonBase::Read(HyperFile *hf, LONG id, LONG level)
{
	NgonBaseLibCallR(FALSE,Read)(hf,id,level);
}

Bool NgonBase::Translate(VariableChanged *vc)
{
	NgonBaseLibCallR(FALSE,Translate)(vc);
}

Bool NgonBase::Changed(PolygonObject *op)
{
	NgonBaseLibCallR(FALSE,Changed)(op);
}

UCHAR NgonBase::GetEdgeStates(LONG id)
{
	NgonBaseLibCallR(FALSE,GetEdgeStates)(id);
}

LONG NgonBase::GetCount()
{
	NgonBaseLibCallR(0,GetCount)();
}

Pgon *NgonBase::GetNgons()
{
	NgonBaseLibCallR(NULL,GetNgons)();
}

Bool NgonBase::Resize(LONG cnt)
{
	NgonBaseLibCallR(FALSE,Resize)(cnt);
}

LONG NgonBase::FindPolygon(LONG id, LONG l)
{
	NgonBaseLibCallR(NOTOK,FindPolygon)(id,l);
}

Bool NgonBase::CompactPgons()
{
	NgonBaseLibCallR(FALSE,CompactPgons)();
}

void NgonBase::CheckPoints(PolygonObject *op)
{
	NgonBaseLibCall(CheckPoints)(op);
}

void NgonBase::UpdateNgons(PolygonObject *op)
{
	NgonBaseLibCall(UpdateNgons)(op);
}

void NgonBase::ResetPoints(PolygonObject *op)
{
	NgonBaseLibCall(ResetPoints)(op);
}

void NgonBase::SetFlags(LONG flgs)
{
	NgonBaseLibCall(SetFlags)(flgs);
}

LONG NgonBase::GetFlags()
{
	NgonBaseLibCallR(FALSE,GetFlags)();
}

void NgonBase::Dirty()
{
	NgonBaseLibCall(Dirty)();
}

void NgonBase::InitMap()
{
	NgonBaseLibCall(InitMap)();
}

void NgonBase::FreeMap()
{
	NgonBaseLibCall(FreeMap)();
}

Bool NgonBase::Remove(LONG id, Bool force)
{
	NgonBaseLibCallR(FALSE,Remove)(id,force);
}

Bool NgonBase::ToSelect(BaseSelect *pSelect, LONG mode)
{
	NgonBaseLibCallR(FALSE,ToSelect)(pSelect,mode);
}

void NgonBase::GetCenter(LONG id, const CPolygon *vadr, const Vector *padr, Vector *ip, Vector *in)
{
	NgonBaseLibCall(GetCenter)(id,vadr,padr,ip,in);
}

Bool NgonBase::RemapEdge(LONG oedge, LONG *nedges, LONG ncnt)
{
	NgonBaseLibCallR(FALSE,RemapEdge)(oedge,nedges,ncnt);
}

Bool NgonBase::Create(ULONG *nedges, LONG ncnt)
{
	NgonBaseLibCallR(FALSE,Create)(nedges,ncnt);
}

Bool NgonBase::RemapPolygon(LONG oply, LONG nply)
{
	NgonBaseLibCallR(FALSE,RemapPolygon)(oply,nply);
}

Bool NgonBase::Copy(Pgon *src, Pgon *dst)
{
	NgonBaseLibCallR(FALSE,Copy)(src,dst);
}

Bool NgonBase::Validate(PolygonObject *op)
{
	NgonBaseLibCallR(FALSE,Validate)(op);
}

Bool NgonBase::IsValidNgon(Pgon *pgon, const CPolygon *vadr)
{
	NgonBaseLibCallR(FALSE,IsValidNgon)(pgon,vadr);
}

LONG NgonBase::BuildNgon(LONG *inner, LONG *outer, LONG icnt, LONG ocnt, const CPolygon *vadr, const Vector *padr)
{
	NgonBaseLibCallR(NOTOK,BuildNgon)(inner,outer,icnt,ocnt,vadr,padr);
}

LONG NgonBase::BuildNgonFromPolys(LONG *polys, LONG *outer, LONG pcnt, LONG ocnt, const CPolygon *vadr, const Vector *padr)
{
	NgonBaseLibCallR(NOTOK,BuildNgonFromPolys)(polys,outer,pcnt,ocnt,vadr,padr);
}

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

Vector Pgon::CalcNormal(const Vector* padr, const CPolygon* polys)
{
	Vector v;
	LONG i;
	for (i = 0; i < m_Count; i++)
	{
		if (!m_Edge[i].IsFirst()) continue;

		const CPolygon &p = polys[m_Edge[i].ID()];

		if (p.c==p.d)
			v += ((padr[p.b]-padr[p.a])%(padr[p.c]-padr[p.a]));
		else
			v += ((padr[p.b]-padr[p.d])%(padr[p.c]-padr[p.a]));
	}
	return !v;
}

void Pgon::GetMpRad(const Vector* padr, const CPolygon* polys, Vector &mp, Vector &rad)
{
	MinMax mm;
	LONG i;
	for (i = 0; i < m_Count; i++)
	{
		if (!m_Edge[i].IsFirst()) continue;

		const CPolygon &p = polys[m_Edge[i].ID()];
		mm.AddPoint(padr[p.a]);
		mm.AddPoint(padr[p.b]);
		mm.AddPoint(padr[p.c]);
		if (p.c != p.d)
			mm.AddPoint(padr[p.d]);
	}
	mm.GetMpRad(&mp, &rad);
}

LONG Pgon::GetSegmentCount()
{
	LONG s=0,e;

	if (m_Count<1 || !m_Edge) return 0;

	for (e=0;e<m_Count;e++)
	{
		if (m_Edge[e].IsSegmentEnd()) s++;
	}

	return s;
}

LONG Pgon::GetSegmentLen(LONG startedge)
{
	LONG e;
	for (e=startedge;e<m_Count;e++)
	{
		if (m_Edge[e].IsSegmentEnd()) return (e-startedge+1);
	}
	return -1;
}

LONG Pgon::FindPolygon(LONG id)
{
	if (id<0 || !m_Edge) return -1;

	for (LONG i=0;i<m_Count;i++)
	{
		if ((m_Edge[i].ID())==id) return i;
	}

	return -1;
}

UCHAR Pgon::GetEdgeState(LONG id)
{
	UCHAR state=0;

	for (LONG n=0;n<m_Count;n++)
	{
		if ((m_Edge[n].ID())==id)
		{
			state=state|m_Edge[n].State();
		}
	}

	return ~state;
}

LONG Pgon::GetPointCount()
{
	LONG cnt=0;

	for (LONG n=0;n<m_Count;n++)
	{
		if (m_Edge[n].Edge()!=NOTOK) cnt++;
	}

	return cnt;
}

LONG Pgon::GetPolygonCount()
{
	LONG cnt=0;

	for (LONG n=0;n<m_Count;n++)
	{
		if (m_Edge[n].IsFirst()) cnt++;
	}

	return cnt;
}

void Pgon::UpdateStates()
{
	LONG i,l,id;

	for (i=0;i<m_Count;i++)
	{
		m_Edge[i].edge_index&=PGONEDGE_RESET;
	}

	for (i=0;i<m_Count;i++)
	{
		if (!(m_Edge[i].edge_index&PGONEDGE_REPEAT))
		{
			id=m_Edge[i].ID();
			for (l=i+1;l<m_Count;l++)
			{
				if (m_Edge[l].ID()==id)
				{
					m_Edge[l].edge_index|=PGONEDGE_REPEAT;
				}
			}
		}
	}
}

void PgonEdge::GetEdge(const CPolygon *vadr, LONG &p1, LONG &p2)
{
	const CPolygon &ply=vadr[ID()];
	switch (Edge())
	{
		case -1: p1=NOTINDEX; p2=NOTINDEX; break;
		case 0: p1=ply.a; p2=ply.b; break;
		case 1: p1=ply.b; p2=ply.c; break;
		case 2: p1=ply.c; p2=ply.d; break;
		case 3: p1=ply.d; p2=ply.a; break;
	}
}

void PgonEdge::RemapPolygon(LONG new_poly, LONG new_edge)
{
	LONG oldid=ID(),edge=Edge();

	if ((new_poly==oldid || new_poly==NOTINDEX) && (new_edge==edge || new_edge==NOTINDEX)) return;

	if (new_edge!=NOTINDEX) edge=new_edge;
	if (new_poly!=NOTINDEX) oldid=new_poly;

	if (edge!=NOTOK)
		edge_index=(oldid*4+edge)|(edge_index&(PGONEDGE_ENDSEGMENT|PGONEDGE_REPEAT));
	else
		edge_index=(oldid*4)|PGONEDGE_NOEDGES|(edge_index&(PGONEDGE_ENDSEGMENT|PGONEDGE_REPEAT));
}
