/*****
* bezierpatch.h
* Authors: John C. Bowman and Jesse Frohlich
*
* Render Bezier patches and triangles.
*****/

#ifndef BEZIERPATCH_H
#define BEZIERPATCH_H

#include "drawelement.h"

namespace camp {

#ifdef HAVE_LIBGLM

struct BezierPatch
{
 vertexBuffer data;
 bool transparent;
 bool color;
 double epsilon;
 double Epsilon;
 double res2;
 double Res2; // Reduced resolution for Bezier triangles flatness test.
 typedef GLuint (vertexBuffer::*vertexFunction)(const triple &v,
                                                const triple& n);
 vertexFunction pvertex;
 bool Onscreen;

 BezierPatch() : transparent(false), color(false), Onscreen(true) {}

 void init(double res);

 void init(double res, GLfloat *colors) {
   transparent=false;
   color=colors;
   init(res);
 }

 triple normal(triple left3, triple left2, triple left1, triple middle,
               triple right1, triple right2, triple right3) {
   triple lp=3.0*(left1-middle);
   triple rp=3.0*(right1-middle);

   triple n=cross(rp,lp);
   if(abs2(n) > epsilon)
     return n;

   triple lpp=bezierPP(middle,left1,left2);
   triple rpp=bezierPP(middle,right1,right2);

   n=cross(rpp,lp)+cross(rp,lpp);
   if(abs2(n) > epsilon)
     return n;

   triple lppp=bezierPPP(middle,left1,left2,left3);
   triple rppp=bezierPPP(middle,right1,right2,right3);

   n=cross(rpp,lpp)+cross(rppp,lp)+cross(rp,lppp);
   if(abs2(n) > epsilon)
     return n;

   n=cross(rppp,lpp)+cross(rpp,lppp);
   if(abs2(n) > epsilon)
     return n;

   return cross(rppp,lppp);
 }

 // Return the differential of the Bezier curve p0,p1,p2,p3 at 0
 triple differential(triple p0, triple p1, triple p2, triple p3) {
   triple p=p1-p0;
   if(abs2(p) > epsilon)
     return p;

   p=bezierPP(p0,p1,p2);
   if(abs2(p) > epsilon)
     return p;

   return bezierPPP(p0,p1,p2,p3);
 }

 // Determine the flatness of a Bezier patch.
 pair Distance(const triple *p) {
   triple p0=p[0];
   triple p3=p[3];
   triple p12=p[12];
   triple p15=p[15];

   // Check the horizontal flatness.
   double h=Flatness(p0,p12,p3,p15);
   // Check straightness of the horizontal edges and interior control curves.
   h=max(h,Straightness(p0,p[4],p[8],p12));
   h=max(h,Straightness(p[1],p[5],p[9],p[13]));
   h=max(h,Straightness(p[2],p[6],p[10],p[14]));
   h=max(h,Straightness(p3,p[7],p[11],p15));

   // Check the vertical flatness.
   double v=Flatness(p0,p3,p12,p15);
   // Check straightness of the vertical edges and interior control curves.
   v=max(v,Straightness(p0,p[1],p[2],p3));
   v=max(v,Straightness(p[4],p[5],p[6],p[7]));
   v=max(v,Straightness(p[8],p[9],p[10],p[11]));
   v=max(v,Straightness(p12,p[13],p[14],p15));

   return pair(h,v);
 }

 struct Split3 {
   triple m0,m2,m3,m4,m5;
   Split3() {}
   Split3(triple z0, triple c0, triple c1, triple z1) {
     m0=0.5*(z0+c0);
     triple m1=0.5*(c0+c1);
     m2=0.5*(c1+z1);
     m3=0.5*(m0+m1);
     m4=0.5*(m1+m2);
     m5=0.5*(m3+m4);
   }
 };

 // Approximate bounds by bounding box of control polyhedron.
 bool offscreen(size_t n, const triple *v) {
   if(bbox2(n,v).offscreen()) {
     Onscreen=false;
     return true;
   }
   return false;
 }

 virtual void render(const triple *p, bool straight, GLfloat *c0=NULL);
 void render(const triple *p,
             GLuint I0, GLuint I1, GLuint I2, GLuint I3,
             triple P0, triple P1, triple P2, triple P3,
             bool flat0, bool flat1, bool flat2, bool flat3,
             GLfloat *C0=NULL, GLfloat *C1=NULL, GLfloat *C2=NULL,
             GLfloat *C3=NULL);

 void append() {
   if(transparent)
     transparentData.Append(data);
   else {
     if(color)
       colorData.Append(data);
     else
       materialData.append(data);
   }
 }

 virtual void notRendered() {
   if(transparent)
     transparentData.rendered=false;
   else {
     if(color)
       colorData.rendered=false;
     else
       materialData.rendered=false;
   }
 }

 void queue(const triple *g, bool straight, double ratio, bool Transparent,
            GLfloat *colors=NULL) {
   data.clear();
   Onscreen=true;
   transparent=Transparent;
   color=colors;
   notRendered();
   init(pixelResolution*ratio);
   render(g,straight,colors);
 }

};

struct BezierTriangle : public BezierPatch {
public:
 BezierTriangle() : BezierPatch() {}

 double Distance(const triple *p) {
   triple p0=p[0];
   triple p6=p[6];
   triple p9=p[9];

   // Check how far the internal point is from the centroid of the vertices.
   double d=abs2((p0+p6+p9)*third-p[4]);

   // Determine how straight the edges are.
   d=max(d,Straightness(p0,p[1],p[3],p6));
   d=max(d,Straightness(p0,p[2],p[5],p9));
   return max(d,Straightness(p6,p[7],p[8],p9));
 }

 void render(const triple *p, bool straight, GLfloat *c0=NULL);
 void render(const triple *p,
             GLuint I0, GLuint I1, GLuint I2,
             triple P0, triple P1, triple P2,
             bool flat0, bool flat1, bool flat2,
             GLfloat *C0=NULL, GLfloat *C1=NULL, GLfloat *C2=NULL);
};

struct Triangles : public BezierPatch {
public:
 Triangles() : BezierPatch() {}

 void queue(size_t nP, const triple* P, size_t nN, const triple* N,
            size_t nC, const prc::RGBAColour* C, size_t nI,
            const uint32_t (*PI)[3], const uint32_t (*NI)[3],
            const uint32_t (*CI)[3], bool transparent);

 void append() {
   if(transparent)
     transparentData.Append(data);
   else
     triangleData.Append(data);
 }

 void notRendered() {
   if(transparent)
     transparentData.rendered=false;
   else
     triangleData.rendered=false;
 }

};

extern void sortTriangles();

#endif

} //namespace camp

#endif