// Supakorn "Jamie" Rassameemasuang <[email protected]> and
// John C. Bowman

import three;
import v3dtypes;
import v3dheadertypes;

struct triangleGroup
{
 triple[] positions;
 triple[] normals;
 pen[] colors;

 int[][] positionIndices;
 int[][] normalIndices;
 int[][] colorIndices;
}

struct pixel
{
 triple point;
 real width;
}

struct CameraInformation
{
 int canvasWidth;
 int canvasHeight;
 bool absolute;

 triple b;
 triple B;
 bool orthographic;
 real angle;
 real Zoom0;
 pair viewportshift;
 pair viewportmargin;

 light light;

 void setCameraInfo()
 {
   size(canvasWidth,canvasHeight);
   triple center=0.5*(b.z+B.z)*Z;

   if(orthographic)
     currentprojection=orthographic(Z,target=center,Zoom0,
                                    viewportshift=viewportshift);
   else
     currentprojection=perspective(Z,Y,target=center,Zoom0,
                                   degrees(2.0*atan(tan(0.5*angle)/Zoom0)),
                                   viewportshift=viewportshift,
                                   autoadjust=false);
   light.specular=light.diffuse;
   currentlight=light;
 }
}

transform3 Align(real polar, real azimuth)
{
 return align(dir(degrees(polar),degrees(azimuth)));
}

struct v3dfile
{
 file xdrfile;
 int version;
 bool hasCameraInfo=false;
 CameraInformation info;
 bool singleprecision=false;

 triple[] centers;
 material[] materials;

 surface[][][] surfaces;
 path3[][][] paths3;
 triangleGroup[][][] triangles;
 pixel[][] pixels;

 void initSurface(int center, int material) {
   if(!surfaces.initialized(center))
     surfaces[center]=new surface[][];
   if(!surfaces[center].initialized(material))
     surfaces[center][material]=new surface[] {new surface};
 }

 void initPath3(int center, int material) {
   if(!paths3.initialized(center))
     paths3[center]=new path3[][];
   if(!paths3[center].initialized(material))
     paths3[center][material]=new path3[];
 }

 void initTriangleGroup(int center, int material) {
   if (!triangles.initialized(center))
     triangles[center]=new triangleGroup[][];
   if(!triangles[center].initialized(material))
     triangles[center][material]=new triangleGroup[];
 }

 void initPixel(int material) {
   if(!pixels.initialized(material))
     pixels[material]=new pixel[];
 }

 void surface(int center, int material, patch p) {
   initSurface(center,material);
   surfaces[center][material][0].s.push(p);
 }

 void primitive(int center, int material, surface s) {
   initSurface(center,material);
   surfaces[center][material].push(s);
 }

 void path3(int center, int material, path3 p) {
   initPath3(center,material);
   paths3[center][material].push(p);
 }

 void triangleGroup(int center, int material, triangleGroup g) {
   initTriangleGroup(center,material);
   triangles[center][material].push(g);
 }

 void pixel(int material, pixel P) {
   initPixel(material);
   pixels[material].push(P);
 }

 void operator init(string name)
 {
   xdrfile=input(name, mode="xdrgz");
   version=xdrfile;

   int doubleprecision=xdrfile;
   singleprecision=doubleprecision == 0;
   xdrfile.singlereal(singleprecision);
 }

 int getType()
 {
   return xdrfile;
 }

 void setCameraInfo()
 {
   if(hasCameraInfo)
     info.setCameraInfo();
 }

 pen[] readColorData(int size=4, bool alpha=true)
 {
   xdrfile.singlereal(true);

   xdrfile.dimension(alpha ? 4 : 3);
   pen[] newPen=new pen[size];
   for (int i=0; i < size; ++i)
     {
       newPen[i]=alpha ? rgba(xdrfile) : rgb((real[]) xdrfile);
     }

   xdrfile.singlereal(singleprecision);

   return newPen;
 }

 CameraInformation processHeader()
 {
   CameraInformation ci;

   int entryCount=xdrfile;
   for (int i=0;i<entryCount;++i)
     {
       int headerKey=xdrfile;
       int headerSz=xdrfile;

       if (headerKey == v3dheadertypes.canvasWidth)
         {
           ci.canvasWidth=xdrfile;
         }
       else if (headerKey == v3dheadertypes.canvasHeight)
         {
           ci.canvasHeight=xdrfile;
         }
       else if (headerKey == v3dheadertypes.absolute)
         {
           int val=xdrfile;
           ci.absolute=(val != 0);
         }
       else if (headerKey == v3dheadertypes.minBound)
         {
           ci.b=xdrfile;
         }
       else if (headerKey == v3dheadertypes.maxBound)
         {
           ci.B=xdrfile;
         }
       else if (headerKey == v3dheadertypes.orthographic)
         {
           int val=xdrfile;
           ci.orthographic=(val != 0);
         }
       else if (headerKey == v3dheadertypes.angleOfView)
         {
           ci.angle=xdrfile;
         }
       else if (headerKey == v3dheadertypes.initialZoom)
         {
           ci.Zoom0=xdrfile;
         }
       else if (headerKey==v3dheadertypes.viewportShift)
         {
           ci.viewportshift=xdrfile;
         }
       else if (headerKey==v3dheadertypes.viewportMargin)
         {
           ci.viewportmargin=xdrfile;
         }
       else if (headerKey==v3dheadertypes.background)
         {
           ci.light.background=readColorData(1)[0];
         }
       else if (headerKey==v3dheadertypes.light)
         {
           triple position=xdrfile;
           ci.light.position.push(position);
           ci.light.diffuse.push(rgba(readColorData(1,false)[0]));
         }
       else
         {
           xdrfile.dimension(headerSz);
           int[] _dmp=xdrfile;
         }
     }
   return ci;
 }

 material readMaterial() {
   xdrfile.dimension(4);
   xdrfile.singlereal(true);

   pen diffusePen=rgba(xdrfile);
   pen emissivePen=rgba(xdrfile);
   pen specularPen=rgba(xdrfile);

   xdrfile.dimension(3);
   real[] params=xdrfile;
   real shininess=params[0];
   real metallic=params[1];
   real F0=params[2];

   xdrfile.singlereal(singleprecision);

   return material(diffusePen,emissivePen,specularPen,1,shininess,
                   metallic,F0);
 }

 triple[][] readRawPatchData() {
   triple[][] val;
   xdrfile.dimension(4,4);
   val=xdrfile;
   return val;
 }

 triple[][] readRawTriangleData() {
   triple[][] val;

   for(int i=0; i < 4; ++i) {
     xdrfile.dimension(i+1);
     triple[] v=xdrfile;
     val.push(v);
   }
   return val;
 }

 void readBezierPatch() {
   triple[][] val=readRawPatchData();
   int center=xdrfile;
   int material=xdrfile;
   surface(center,material,patch(val));
 }

 void readBezierTriangle() {
   triple[][] val=readRawTriangleData();
   int center=xdrfile;
   int material=xdrfile;
   surface(center,material,patch(val,triangular=true));
 }

 triple[] readCenters() {
   int centerCount=xdrfile;
   xdrfile.dimension(centerCount);
   triple[] centersFetched;
   if (centerCount>0)
     centersFetched=xdrfile;
   return centersFetched;
 }

 void readBezierPatchColor() {
   triple[][] val=readRawPatchData();
   int center=xdrfile;
   int material=xdrfile;
   pen[] colors=readColorData(4);
   surface(center,material,patch(val,colors=colors));
 }

 void readBezierTriangleColor() {
   triple[][] val=readRawTriangleData();
   int center=xdrfile;
   int material=xdrfile;
   pen[] colors=readColorData(3);
   surface(center,material,patch(val,triangular=true,colors=colors));
 }

 void readSphere() {
   triple origin=xdrfile;
   real radius=xdrfile;

   int center=xdrfile;
   int material=xdrfile;

   surface s=shift(origin)*scale3(radius)*unitsphere;
   s.primitive=unitsphere.primitive;
   primitive(center,material,s);
 }

 void readHemisphere() {
   triple origin=xdrfile;
   real radius=xdrfile;

   int center=xdrfile;
   int material=xdrfile;

   real polar=xdrfile;
   real azimuth=xdrfile;

   surface s=shift(origin)*Align(polar,azimuth)*scale3(radius)*unithemisphere;
   s.primitive=unithemisphere.primitive;
   primitive(center,material,s);
 }

 void readDisk() {
   triple origin=xdrfile;
   real radius=xdrfile;

   int center=xdrfile;
   int material=xdrfile;

   real polar=xdrfile;
   real azimuth=xdrfile;

   surface s=shift(origin)*Align(polar,azimuth)*scale3(radius)*unitdisk;
   s.primitive=unitdisk.primitive;
   primitive(center,material,s);
 }

 void readPolygon(int n)
 {
   xdrfile.dimension(n);
   triple[] val=xdrfile;

   int center=xdrfile;
   int material=xdrfile;
   surface(center,material,patch(val));
 }

 void readPolygonColor(int n)
 {
   xdrfile.dimension(n);
   triple[] val=xdrfile;

   int center=xdrfile;
   int material=xdrfile;

   pen[] colors=readColorData(n);
   surface(center,material,patch(val,colors=colors));
 }

 void readCylinder() {
   triple origin=xdrfile;
   real radius=xdrfile;
   real height=xdrfile;

   int center=xdrfile;
   int material=xdrfile;

   real polar=xdrfile;
   real azimuth=xdrfile;

   int core=xdrfile;

   transform3 T=shift(origin)*Align(polar,azimuth)*
     scale(radius,radius,height);
   if(core != 0)
     path3(center,material,T*(O--Z));

   surface s=T*unitcylinder;
   s.primitive=unitcylinder.primitive;
   primitive(center,material,s);
 }

 void readTube() {
   xdrfile.dimension(4);
   triple[] g=xdrfile;

   real width=xdrfile;
   int center=xdrfile;
   int material=xdrfile;

   int core=xdrfile;

   if(core != 0)
     path3(center,material,g[0]..controls g[1] and g[2]..g[3]);

   surface s=tube(g[0],g[1],g[2],g[3],width);
   s.primitive=primitive(drawTube(g,width,info.b,info.B),
                         new bool(transform3 t) {
                           return unscaled(t,X,Y);
                         });
   primitive(center,material,s);
 }

 void readCurve() {
   xdrfile.dimension(4);
   triple[] points=xdrfile;

   int center=xdrfile;
   int material=xdrfile;

   path3(center,material,
         points[0]..controls points[1] and points[2]..points[3]);
 }

 void readLine() {
   xdrfile.dimension(2);
   triple[] points=xdrfile;

   int center=xdrfile;
   int material=xdrfile;

   path3(center,material,points[0]--points[1]);
 }

 void readTriangles() {
   triangleGroup g;

   int nI=xdrfile;

   int nP=xdrfile;
   xdrfile.dimension(nP);
   g.positions=xdrfile;

   int nN=xdrfile;
   xdrfile.dimension(nN);
   g.normals=xdrfile;

   int explicitNI=xdrfile;

   int nC=xdrfile;
   int explicitCI;
   if (nC > 0) {
     g.colors=readColorData(nC);
     explicitCI=xdrfile;
   }

   g.positionIndices=new int[nI][3];
   g.normalIndices=new int[nI][3];
   int[][] colorIndices;
   if (nC > 0)
     g.colorIndices=new int[nI][3];

   for (int i=0; i < nI; ++i) {
     xdrfile.dimension(3);
     g.positionIndices[i]=xdrfile;
     g.normalIndices[i]=explicitNI != 0 ? xdrfile :
       g.positionIndices[i];
     g.colorIndices[i]=nC > 0 && explicitCI != 0 ? xdrfile :
       g.positionIndices[i];
   }
   int center=xdrfile;
   int material=xdrfile;
   triangleGroup(center,material,g);
 }

 void readPixel() {
   pixel P;
   P.point=xdrfile;
   P.width=xdrfile;
   int material=xdrfile;
   pixel(material,P);
 }

 void process()
 {
   static bool processed;
   if(processed) return;

   while (!eof(xdrfile))
     {
       int ty=getType();
       if(ty == v3dtypes.header)
         {
           hasCameraInfo=true;
           info=processHeader();
         }
       else if(ty == v3dtypes.material)
         {
           materials.push(readMaterial());
         }
       else if(ty == v3dtypes.bezierPatch)
         {
           readBezierPatch();
         }
       else if(ty == v3dtypes.bezierTriangle)
         {
           readBezierTriangle();
         }
       else if(ty == v3dtypes.bezierPatchColor)
         {
           readBezierPatchColor();
         }
       else if(ty == v3dtypes.bezierTriangleColor)
         {
           readBezierTriangleColor();
         }
       else if(ty == v3dtypes.quad)
         {
           readPolygon(4);
         }
       else if(ty == v3dtypes.quadColor)
         {
           readPolygonColor(4);
         }
       else if(ty == v3dtypes.triangle)
         {
           readPolygon(3);
         }
       else if(ty == v3dtypes.triangleColor)
         {
           readPolygonColor(3);
         }
       else if(ty == v3dtypes.sphere)
         {
           readSphere();
         }
       else if(ty == v3dtypes.halfSphere)
         {
           readHemisphere();
         }
       else if(ty == v3dtypes.cylinder)
         {
           readCylinder();
         }
       else if(ty == v3dtypes.tube)
         {
           readTube();
         }
       else if(ty == v3dtypes.disk)
         {
           readDisk();
         }
       else if(ty == v3dtypes.curve)
         {
           readCurve();
         }
       else if(ty == v3dtypes.line)
         {
           readLine();
         }
       else if(ty == v3dtypes.triangles)
         {
           readTriangles();
         }
       else if(ty == v3dtypes.centers)
         {
           centers=readCenters();
         }
       else if(ty == v3dtypes.pixel)
         {
           readPixel();
         }
       else
         {
           //  abort("Unknown type:"+string(ty));
         }
     }
   processed=true;
 }
}

void importv3d(string name)
{
 if(name == stripextension(name)) name += ".v3d";
 v3dfile xf=v3dfile(name);
 xf.process();
 xf.setCameraInfo();

 for(int c=0; c < xf.surfaces.length; ++c) {
   triple center=c > 0 ? xf.centers[c-1] : O;
   render r=render(interaction(c == 0 ? Embedded : Billboard,center=center));
   surface[][] S=xf.surfaces[c];
   for(int m=0; m < S.length; ++m)
     if(S.initialized(m))
       draw(S[m],xf.materials[m],r);
 }

 for(int c=0; c < xf.paths3.length; ++c) {
   triple center=c > 0 ? xf.centers[c-1] : O;
   render r=render(interaction(c == 0 ? Embedded : Billboard,center=center));
   path3[][] G=xf.paths3[c];
   for(int m=0; m < G.length; ++m)
     if(G.initialized(m)) {
       material material=material(xf.materials[m]);
       material.p[0] += thin();
       draw(G[m],material,currentlight,r);
     }
 }

 for(int c=0;c < xf.triangles.length; ++c) {
   triple center=c > 0 ? xf.centers[c-1] : O;
   render r=render(interaction(c == 0 ? Embedded : Billboard,center=center));
   triangleGroup[][] groups=xf.triangles[c];
   for(int m=0; m < groups.length; ++m) {
     if(groups.initialized(m)) {
       material material=xf.materials[m];
       triangleGroup[] triangleGroups=groups[m];
       for(triangleGroup g : triangleGroups) {
         if(g.colors.length > 0)
           draw(g.positions,g.positionIndices,g.normals,g.normalIndices,
                g.colors,g.colorIndices,r);
         else
           draw(g.positions,g.positionIndices,g.normals,g.normalIndices,
                material,r);
       }
     }
   }
 }

 for(int m=0; m < xf.pixels.length; ++m) {
   if(xf.pixels.initialized(m)) {
     material material=xf.materials[m];
     pixel[] pixels=xf.pixels[m];
     for(pixel P : pixels)
       pixel(P.point,material.p[0],P.width);
   }
 }
}