/*
 * brefract.c
 *
 * blender plugin for doing refraction with environment maps
 *
 * Eeshlo 2001, do with it as you wish, no credits necessary
 * As most of this is the result of badly educated guess work,
 * feel free to flame, ridicule, and generally make fun of me... [<%O(]
 *
 * First succesfull version: july 8, 2001
 * added Thickness param. july 9, 2001
 * added Fresnel: july 14, 2001
 * added Fresnel switch: july 15, 2001
 *
 * TODO: There must be some way to make use of an environment map as seen from 
 * the object, instead of the camera, but at the moment I can't figure 
 *      this out. It is probably related to the camera distance from the object.
 *      The viewvector probably has to be offset some way, but this would mean 
 *      the plugin would have to know about camera distance, and that might 
 * not be possible(?).
 * If anyone knows how to do this, please feel free to change the source 
 *      code, and claim it all your own, I don't care !!!
 *
 */

#include <math.h>
#include <stdio.h>
#include <plugin.h>

#define NR_TYPES 1

/***** vector macros *****
------------------------------------------------------------------------------*/
struct Vector { 
   float x,y,z; 
};

#define NORM(v) {float vl=(v).x*(v).x+(v).y*(v).y+(v).z*(v).z; if (vl!=0.f) { vl=1.f/sqrt(vl); (v).x*=vl; (v).y*=vl; (v).z*=vl;}}
#define VDOT(a,b) ((a)->x*(b)->x + (a)->y*(b)->y + (a)->z*(b)->z)
#define VSUB(a,b,c) {(c).x=(a).x-(b).x; (c).y=(a).y-(b).y; (c).z=(a).z-(b).z;}
#define VADD(a,b,c) {(c).x=(a).x+(b).x; (c).y=(a).y+(b).y; (c).z=(a).z+(b).z;}
#define VSCALE(a,s,r) {(r)->x=(a)->x*s; (r)->y=(a)->y*s; (r)->z=(a)->z*s;}
/*----------------------------------------------------------------------------*/

float result[8];
float cfra;

int do_reset = FALSE;

/* set up plugin menu */

char name[]= "Refraction plugin";
char stnames[NR_TYPES][16]= {"Refractor"};

VarStruct varstr[]= {
   /* type, name,  default,min,    max,  tooltips */
   { LABEL, "EESHLO 2001", 0, 0, 0, ""},
   { NUM|FLO, "IOR    : ", 1.f, 0.f, 10.f, "Index Of Refraction" },
   { NUM|FLO, "Thickns: ", 0.f, 0.f, 1.f, 
 "Compensation for flat glass, wall thickness, 0=infinite"},
   { TOG|INT, "Use Fresnel", 0,  0,  1,
 "Use the Fresnel equation for reflection"},
   { NUM|FLO, "Reflect: ", 0.f, 0.f, 1.f, 
 "Amount of Reflection, not used when Fresnel set"}
};

typedef struct Cast {
 int nix;
 float IOR, thns;
 int fresnel;
 float refl_amt;
} Cast;

/********************************************************************/

int plugin_tex_doit(int, Cast*, float*, float*, float*);

int plugin_tex_getversion(void)
{
 return B_PLUGIN_VERSION;
}

void plugin_but_changed(int but)
{
}

void plugin_init(void)
{
}

void plugin_getinfo(PluginInfo *info)
{
 info->name= name;
 info->stypes= NR_TYPES;
 info->nvars= sizeof(varstr)/sizeof(VarStruct);

 info->snames= stnames[0];
 info->result= result;
 info->cfra= &cfra;
 info->varstr= varstr;

 info->init= plugin_init;
 info->tex_doit=  (TexDoit) plugin_tex_doit;
 info->callback= plugin_but_changed;
}

int Refract(Cast *cast, const struct Vector *V, const struct Vector *N, 
 struct Vector *T, float *Fr, int entr) {
 int TIR = TRUE;
 float ndv, ndt, r1, r2, t, nint, ni, nt;
 struct Vector *St, *NN;
 /* if entering object, refr.ratio air/object, exit object/air 
  (air approx 1.)
 */
        St = malloc(sizeof(struct Vector));
        NN = malloc(sizeof(struct Vector));
        NN->x = N->x; NN->y = N->y; NN->z = N->z;
        nt=cast->IOR;
 if (!entr) {
  VSCALE(NN,-1.f,NN); /*reverse normal for exit */
  ni = nt;
  nt = 1.f;
 }
 else ni=1.f;
 if ((nint=nt)!=0.f) nint=ni/nint;
 ndv = VDOT(NN,V);
 St->x = (NN->x * ndv - V->x) * nint;
 St->y = (NN->y * ndv - V->y) * nint;
 St->z = (NN->z * ndv - V->z) * nint;
 if ((ndt=VDOT(St,St)) < 1.f) {
  ndt = sqrt(1.f-ndt);
  /* Transmitted view-vector */
  T->x = St->x - NN->x * ndt;
  T->y = St->y - NN->y * ndt;
  T->z = St->z - NN->z * ndt;
  TIR = FALSE;
 }
 else T->x = T->y = T->z = 0.f;
 /*  when the previous texture is an environment map set up for 
  reflection as seen from the object, and 'stencil' is 
  selected, this value will control
  the amount of reflection. */
 if (cast->fresnel) {
  /* Fresnel reflectance equation (or is it?) */
  ndt = VDOT(NN, T);
  if ((t = nt*ndv - ni*ndt) != 0.f) t = 1.f/t;
  r1 = (nt*ndv + ni*ndt)*t;
  if ((t = ni*ndv - nt*ndt) != 0.f) t = 1.f/t;
  r2 = (ni*ndv + nt*ndt)*t;
  *Fr = (float)(0.5 * (r1*r1 + r2*r2));
  *Fr = (float)(1.0 - CLAMP(*Fr, 0.f, 1.f));
 } else *Fr = 1.0 -cast->refl_amt;
        free(St);
        free(NN);
 return TIR;
}

int plugin_tex_doit(int stype, Cast *cast, float *texvec, float *dxt, 
   float *dyt) {
    struct Vector vw, nrm, Tr;
    float refm;
 /* With the mapping method set to object, and the object set to 
  camera, texvec normalized will be the equivalent of the view 
  direction vector.  Maybe not, I'm not really sure at all, but
   it works nevertheless... !!?! */
 vw.x = texvec[0];
 vw.y = texvec[1];
 vw.z = texvec[2];
 NORM(vw);
 nrm.x = result[5];
 nrm.y = result[6];
 nrm.z = result[7];
 if (!Refract(cast, &vw, &nrm, &Tr, &refm, TRUE)) {
  /* if thickness not zero, make refracted ray travel through 
   and exit imaginary wall (are you sure?) */
  if (cast->thns != 0.f) {
   Tr.x -= cast->thns * nrm.x;
   Tr.y -= cast->thns * nrm.y;
   Tr.z -= cast->thns * nrm.z;
   NORM(Tr);
   Refract(cast, &Tr, &nrm, &Tr, &refm, FALSE);
  }
  /* Transmit vector minus the original normal is the normal 
   modifier (or..., never mind!) */
  VSUB(Tr, nrm, Tr);
 }
 else /* Total Internal Reflection, IOR<1, no normal modifier */
  refm = 0.f; /* full reflection at this point */
 /* return normal modifier, and just for fun & visual feedback, set 
  color equal to it */
 result[1] = result[5] = Tr.x;
 result[2] = result[6] = Tr.y;
 result[3] = result[7] = Tr.z;
 result[4] = refm; /* the reflection factor, used with stencil
      'must return' intensity value, here average of normal modifier */

 result[0] = .5f + .16666666f * (Tr.x + Tr.y + Tr.z);
 return 3;
}
