// Critical definitions for transform3 needed by projection and picture.

pair viewportmargin=settings.viewportmargin;

using transform3=real[][];
restricted transform3 identity4=identity(4);

// A uniform 3D scaling.
transform3 scale3(real s)
{
 transform3 t=identity(4);
 t[0][0]=t[1][1]=t[2][2]=s;
 return t;
}

// Simultaneous 3D scalings in the x, y, and z directions.
transform3 scale(real x, real y, real z)
{
 transform3 t=identity(4);
 t[0][0]=x;
 t[1][1]=y;
 t[2][2]=z;
 return t;
}

transform3 shiftless(transform3 t)
{
 transform3 T=copy(t);
 T[0][3]=T[1][3]=T[2][3]=0;
 return T;
}

real camerafactor=2;       // Factor used for camera adjustment.

struct transformation {
 transform3 modelview;  // For orientation and positioning
 transform3 projection; // For 3D to 2D projection
 bool infinity;
 void operator init(transform3 modelview) {
   this.modelview=modelview;
   this.projection=identity4;
   infinity=true;
 }
 void operator init(transform3 modelview, transform3 projection) {
   this.modelview=modelview;
   this.projection=projection;
   infinity=false;
 }
 transform3 compute() {
   return infinity ? modelview : projection*modelview;
 }
 transformation copy() {
   transformation T=new transformation;
   T.modelview=copy(modelview);
   T.projection=copy(projection);
   T.infinity=infinity;
   return T;
 }
}

struct projection {
 transform3 t;         // projection*modelview (cached)
 bool infinity;
 bool absolute=false;
 triple camera;        // Position of camera.
 triple up;            // A vector that should be projected to direction (0,1).
 triple target;        // Point where camera is looking at.
 triple normal;        // Normal vector from target to projection plane.
 pair viewportshift;   // Fractional viewport shift.
 real zoom=1;          // Zoom factor.
 real angle;           // Lens angle (for perspective projection).
 bool showtarget=true; // Expand bounding volume to include target?
 using projector=transformation(triple camera, triple up, triple target);
 projector projector;
 bool autoadjust=true; // Adjust camera to lie outside bounding volume?
 bool center=false;    // Center target within bounding volume?
 int ninterpolate;     // Used for projecting nurbs to 2D Bezier curves.
 bool bboxonly=true;   // Typeset label bounding box only.

 transformation T;

 void calculate() {
   T=projector(camera,up,target);
   t=T.compute();
   infinity=T.infinity;
   ninterpolate=infinity ? 1 : 16;
 }

 triple vector() {
   return camera-target;
 }

 void operator init(triple camera, triple up=(0,0,1), triple target=(0,0,0),
                    triple normal=camera-target,
                    real zoom=1, real angle=0, pair viewportshift=0,
                    bool showtarget=true, bool autoadjust=true,
                    bool center=false, projector projector) {
   this.camera=camera;
   this.up=up;
   this.target=target;
   this.normal=normal;
   this.zoom=zoom;
   this.angle=angle;
   this.viewportshift=viewportshift;
   this.showtarget=showtarget;
   this.autoadjust=autoadjust;
   this.center=center;
   this.projector=projector;
   calculate();
 }

 projection copy() {
   projection P=new projection;
   P.t=t;
   P.infinity=infinity;
   P.absolute=absolute;
   P.camera=camera;
   P.up=up;
   P.target=target;
   P.normal=normal;
   P.zoom=zoom;
   P.angle=angle;
   P.viewportshift=viewportshift;
   P.showtarget=showtarget;
   P.autoadjust=autoadjust;
   P.center=center;
   P.projector=projector;
   P.ninterpolate=ninterpolate;
   P.bboxonly=bboxonly;
   P.T=T.copy();
   return P;
 }

 // Return the maximum distance of box(m,M) from target.
 real distance(triple m, triple M) {
   triple[] c={m,(m.x,m.y,M.z),(m.x,M.y,m.z),(m.x,M.y,M.z),
               (M.x,m.y,m.z),(M.x,m.y,M.z),(M.x,M.y,m.z),M};
   return max(abs(c-target));
 }


 // This is redefined here to make projection as self-contained as possible.
 static private real sqrtEpsilon=sqrt(realEpsilon);

 // Move the camera so that the box(m,M) rotated about target will always
 // lie in front of the far clipping plane.
 bool adjust(triple m, triple M) {
   triple v=camera-target;
   real d=distance(m,M);
   static real lambda=camerafactor*(1-sqrtEpsilon);
   if(lambda*d >= abs(v)) {
     camera=target+camerafactor*d*unit(v);
     calculate();
     return true;
   }
   return false;
 }
}

projection currentprojection;

struct light {
 real[][] diffuse;
 real[][] specular;
 pen background=nullpen; // Background color of the 3D canvas.
 real specularfactor;
 triple[] position; // Only directional lights are currently implemented.

 transform3 T=identity(4); // Transform to apply to normal vectors.

 bool on() {return position.length > 0;}

 void operator init(pen[] diffuse,
                    pen[] specular=diffuse, pen background=nullpen,
                    real specularfactor=1,
                    triple[] position) {
   int n=diffuse.length;
   assert(specular.length == n && position.length == n);

   this.diffuse=new real[n][];
   this.specular=new real[n][];
   this.background=background;
   this.position=new triple[n];
   for(int i=0; i < position.length; ++i) {
     this.diffuse[i]=rgba(diffuse[i]);
     this.specular[i]=rgba(specular[i]);
     this.position[i]=unit(position[i]);
   }
   this.specularfactor=specularfactor;
 }

 void operator init(pen diffuse=white, pen specular=diffuse,
                    pen background=nullpen, real specularfactor=1 ...triple[] position) {
   int n=position.length;
   operator init(array(n,diffuse),array(n,specular),
                 background,specularfactor,position);
 }

 void operator init(pen diffuse=white, pen specular=diffuse,
                    pen background=nullpen, real x, real y, real z) {
   operator init(diffuse,specular,background,(x,y,z));
 }

 void operator init(explicit light light) {
   diffuse=copy(light.diffuse);
   specular=copy(light.specular);
   background=light.background;
   specularfactor=light.specularfactor;
   position=copy(light.position);
 }

 real[] background() {return rgba(background == nullpen ? white : background);}
}

light currentlight;