/*******************************************************************************
*
* McStas, neutron ray-tracing package
*         Copyright (C) 1997-2015, All rights reserved
*         Risoe National Laboratory, Roskilde, Denmark
*         Institut Laue Langevin, Grenoble, France
*
* Component: PerfectCrystal
*
* %I
* Written by: Markus Appel
* Date: 2015-12-21
* Origin: ILL / FAU Erlangen-Nuernberg
* Based on a perfect crystal component by: Miguel A. Gonzalez, A. Dianoux June 2013 (ILL)
*
* Changelog:
* Version 1.1
*  - BUGFIX: correct neutron energy shift in Doppler mode
*  - added option 'debyescherrer' to select analyzer geometry
*  - added option 'facette' to approximate analyzer sphere by small, flat crystals
*
* Version 1.0
*  - inital release
*
*
* %D
* Perfect crystal component, primarily for use as monochromator and analyzer in
* backscattering spectrometers. Reflection from a single Bragg reflex of a flat or
* spherical surface is simulated using a Darwin, Ewald or Gaussian profile. Doppler
* movement of the monochromator is supported as well.
*
* Orientation of the crystal surface is *different* from other monochromator components!
* Gravitational energy shift for tall analyzers should work in principle, but it not tested yet.
* See the parameter description on how to define the geometry and properties.
*
* [1] Website for Backscattering Spectroscopy: http://www.ill.eu/sites/BS-review/index.htm
*
* Examples:
* IN16B (ILL) Si111 large angle analyzers (approximate dimensions):
* PerfectCrystal(radius=2, lambda=6.2708, sigma=0.24e-3,
*                ttmin=40, ttmax=165, phimin=-45, phimax=+45, centerfocus=1)
*
* IN16B (ILL) Si111 Doppler monochromator:
* PerfectCrystal(radius=2.165, lambda=6.2708, sigma=0.24e-3,
*                width = 0.500, height = 0.250, centerfocus=0,
*                speed = 4.7, amplitude = 0.075, exclusive=1)
*
* %P
* (A) Size, shape and position:
* =============================
* radius: [m]                  Radius of curvature, set to 0 for a flat surface. Default: 0
* centerfocus: [0/1]           Component origin is the center of the analyzer sphere if 1, if set to 0 the origin is on the analyzer surface. Default: 0
*
* option 1
* ttmin: [deg]                 
* ttmax: [deg]                 analyzer coverage angle in horizontal xz-plane between -180 and 180
* phimin: [deg]                
* phimax: [deg]                vertical analyzer coverage between -90 and 90 (-180 and 180 if debyescherrer==1)
* option 2
* tt0: [deg]                   
* ttwidth: [deg]               horizontal coverage as center and full width
* phi0: [deg]                  
* phiwidth: [deg]              vertical coverage as center and full width
* option 3
* tt0: [deg]                   angular center position in the horizontal plane (only if centerfocus==1)
* width: [m]                   absolute width
* phi0: [deg]                  angular vertical position (only if centerfocus==1)
* height: [m]                  absolute height (only if debyescherrer==0)
*
* debyescherrer: [-180...180]  (0/1)   bend analyzer following a Debye-Scherrer ring along scattering angle tt (twotheta) (phi = 
* facette: [m]                 width of square crystal facettes arranged on the spherical surface (set 0 to disable). Default: 0 Warning: Facettation will fail at the poles along +-y axis.
* facette_xi: [deg]            random misalignemt of each facette. Default: 0
*
* (B) Neutron optics
* ===================
* tau: [A^-1]                  Scattering vector of the reflex (sometimes also called Q...)
* lambda: [A]                  Alternatively to tau: backscattered wavelength
* R0: []                       Peak reflectivity. Default: 1
*
* Ewald/Darwin mode
* dtauovertau: [1]             Plateau width of the Ewald/Darwin curve (see 
* dtauovertau_ext: []          Relative variation of tau (randomized for each trajectory, full width)
* ewald: [0/1]                 Use Ewald curve if 1, Darwin curve if 0. Default: 1 (Ewald)
*
* Gaussian mode
* sigma: [meV]                 Width of the Gaussian reflectivity curve in meV (corresponding to the energy resolution). (The width will be transformed and the Gaussian is actually calculated in k-space.)
*
* Mirror mode
* ismirror: [0/1]              Simply reflect all neutrons if 1. Good for debugging/visualization. Default: 0
*
* (C) Doppler movement for monochromators (sinusoidal speed profile):
* ===================================================================
* speed: [m/s]                 Maximum Doppler speed. The actual monochromator velocity is randomized between -speed and +speed. Default: 0
* amplitude: [m]               Amplitude of the Doppler movement. Default: 0
*
* smartphase: [0/1]            Optimize Doppler phase for better MC efficiency if set to 1. *WARNING:* Experimental option! Always compare to a simulation without smartphase. Better do not use smartwidth with Ewald/Darwin curves due to their endless tails. Default: 0
* smartwidth: [meV]            Half width of the possible energy reflection window for smartphase. Default: 5*sigma OR 10*2*dtauovertau*E0
*
* (D) Miscellaneous
* ==================
* exclusive: [0/1]             If set to 1, absorb all neutrons that missed the monochromator/analyzer surface. Default: 0
* transmit: [0...1]            Monte-Carlo probability of transmitting an event through the monochromator/analyzer surface. (Events with R=1.0 for DarwinE/Ewald curves are always reflected!). Default: 0
*
* Output parameters
* ==================
* tt: [deg]                    Position where the neutron hit (or missed) the analyzer sphere
* phi: [deg]                   Position where the neutron hit (or missed) the analyzer sphere
* xi: [deg]                    Reflection angle between neutron trajectory and analyzer surface normal
* phid: [rad]                  Actual doppler phase during reflection [0,2*PI]
* vd: [m/s]                    Actual doppler speed during reflection
* zd: [m]                      Actual doppler displacement during reflection (zd>0 => displaced towards inc. beam from -z)
* v0: [m/s]                    Backscattered neutron velocity
* E0: [meV]                    Backscattered neutron energy
* R: [0...1]                   Reflectivity value (on Gauss/Darwin/Ewald curve)
* eps: [see 1]                 'abs(y)' from the Darwin/Ewald reflectivity formula 
*
* %E
*******************************************************************************/

DEFINE COMPONENT PerfectCrystal
DEFINITION PARAMETERS ()
SETTING PARAMETERS ( ttmin=NAN, ttmax=NAN, tt0=NAN, ttwidth=NAN, width=NAN,
phimin=NAN, phimax=NAN, phi0=NAN, phiwidth=NAN, height=NAN, debyescherrer=0,
facette=0, facette_xi=0, centerfocus=0, radius=0, tau=NAN, lambda=NAN, R0=1.0,
dtauovertau=NAN, dtauovertau_ext=0,
ewald=1,sigma=NAN,ismirror=0,speed=0,amplitude=0,smartphase=0,
smartwidth=NAN, exclusive=0, transmit=0, verbose=0 )
OUTPUT PARAMETERS ()
SHARE
%{
// convert between spherical (r,tt,phi) and cartesian (x,y,z) coordinates (angles in deg)
   // debyescherrer swaps axes
   void sph2cart(double *x,double *y,double *z, double r, double tt, double phi, int debyescherrer)
   {
      tt *= DEG2RAD;
      phi *= DEG2RAD;
      if (debyescherrer) {
         double sintt = sin(tt);
         *x = - r * cos(phi) * sintt;
         *y = r * sin(phi) * sintt;
         *z = r * cos(tt);
      } else {
         double cosphi = cos(phi);
         *x = - r * cosphi * sin(tt);
         *y = r * sin(phi);
         *z = r * cosphi * cos(tt);
      }
   }

   /*******************************************************************************
   * grandvec_target_circle: Choose random direction towards target at (x,y,z)
   * with given radius and gaussian area distribution.
   * If radius is zero, choose random direction in full 4PI, no target.
   ******************************************************************************/
   void
   grandvec_target_circle(double *xo, double *yo, double *zo, double *solid_angle,
                  double xi, double yi, double zi, double radius)
   {
     double l2, phi, theta, nx, ny, nz, xt, yt, zt, xu, yu, zu;

     if(radius == 0.0)
     {
       /* No target, choose uniformly a direction in full 4PI solid angle. */
       theta = acos (1 - rand0max(2));
       phi = rand0max(2 * PI);
       if(solid_angle)
         *solid_angle = 4*PI;
       nx = 1;
       ny = 0;
       nz = 0;
       yi = sqrt(xi*xi+yi*yi+zi*zi);
       zi = 0;
       xi = 0;
     }
     else
     {
       double costheta0;
       l2 = xi*xi + yi*yi + zi*zi; /* sqr Distance to target. */
       costheta0 = sqrt(l2/(radius*radius+l2));
       if (radius < 0) costheta0 *= -1;
       if(solid_angle)
       {
         /* Compute solid angle of target as seen from origin. */
           *solid_angle = 2*PI*(1 - costheta0);
       }

       /* Now choose point uniformly on circle surface within angle theta0 */
       double costheta;
       costheta = (1 - fabs(randnorm()*(1 - costheta0)) );

       if (costheta < -1)
         costheta = -1;

       theta = acos(costheta); /* radius on circle */
       phi = rand0max(2 * PI); /* rotation on circle at given radius */
       /* Now, to obtain the desired vector rotate (xi,yi,zi) angle theta around a
          perpendicular axis u=i x n and then angle phi around i. */
       if(xi == 0 && zi == 0)
       {
         nx = 1;
         ny = 0;
         nz = 0;
       }
       else
       {
         nx = -zi;
         nz = xi;
         ny = 0;
       }
     }

     /* [xyz]u = [xyz]i x n[xyz] (usually vertical) */
     vec_prod(xu,  yu,  zu, xi, yi, zi,        nx, ny, nz);
     /* [xyz]t = [xyz]i rotated theta around [xyz]u */
     rotate  (xt,  yt,  zt, xi, yi, zi, theta, xu, yu, zu);
     /* [xyz]o = [xyz]t rotated phi around n[xyz] */
     rotate (*xo, *yo, *zo, xt, yt, zt, phi, xi, yi, zi);
   } /* randvec_target_circle */


%}
DECLARE
%{
   // official output variables
   double tt;
   double phi;
   double xi;
   double phid;
   double vd;
   double zd;
   double v0;
   double vmin;
   double vmax;
   double vperp;
   double E0;
   double R;
   double eps;

   // internal stuff
   double sin_facette_xi;
%}
INITIALIZE
%{
   // checks and balances ...
   if ( radius < 0 )
   {
      MPI_MASTER(fprintf(stderr,
         "PerfectCrystal: %s ERROR: Invalid parameters: negative radius\n",
         NAME_CURRENT_COMP););
      exit(-1);
   }

   //******************************************
   // position and size
   if ( !radius )
   {
      // flat analyzer surface
      if ( isnan(width) || isnan(height) )
      {
         MPI_MASTER(fprintf(stderr,
            "PerfectCrystal: %s ERROR: Invalid parameters: Need width and height for radius==0\n",
            NAME_CURRENT_COMP););
            exit(-1);
      }
   }
   else
   {
      // curved analyzer surface

      //******************************************
      // Determine analyzer width

      // ttmin / ttmax
      if ( !isnan(ttmin) && !isnan(ttmax) && isnan(tt0) && isnan(ttwidth) && isnan(width) )
      {
         // all right.
      }
      // tt0 / ttwidth
      else if ( isnan(ttmin) && isnan(ttmax) && !isnan(ttwidth) && isnan(width) )
      {
         if ( isnan(tt0) )
         {
             MPI_MASTER(fprintf(stderr,
               "PerfectCrystal: %s WARNING: Missing parameter: tt0 set to zero\n",
               NAME_CURRENT_COMP););
            tt0 = 0;
         }
         ttmin = tt0 - ttwidth/2.0;
         ttmax = tt0 + ttwidth/2.0;

      }
      // tt0 / width
      else if ( isnan(ttmin) && isnan(ttmax) && isnan(ttwidth) && !isnan(width) )
      {
         if ( isnan(tt0) )
         {
             MPI_MASTER(fprintf(stderr,
               "PerfectCrystal: %s WARNING: Missing parameter: tt0 set to zero\n",
               NAME_CURRENT_COMP););
            tt0 = 0;
         }
         ttwidth = 2.0 * asin( width / 2.0 / radius ) * RAD2DEG;
         ttmin = tt0 - ttwidth/2.0;
         ttmax = tt0 + ttwidth/2.0;
      }
      else
      {
         MPI_MASTER(fprintf(stderr,
            "PerfectCrystal: %s ERROR: Invalid parameters: Cannot determine analyzer width/tt\n",
            NAME_CURRENT_COMP););
         exit(-1);
      }

      //******************************************
      // Determine analyzer height

      // phimin / phimax
      if ( !isnan(phimin) && !isnan(phimax) && isnan(phi0) && isnan(phiwidth) && isnan(height) )
      {
         // all right, nothing to do.
      }
      // phi0 / phiwidth
      else if ( isnan(phimin) && isnan(phimax) && !isnan(phiwidth) && isnan(height) )
      {
         if ( isnan(phi0) )
         {
             MPI_MASTER(fprintf(stderr,
               "PerfectCrystal: %s WARNING: Missing parameter: phi0 set to zero\n",
               NAME_CURRENT_COMP););
            phi0 = 0;
         }
         phimin = phi0 - phiwidth/2.0;
         phimax = phi0 + phiwidth/2.0;
      }
      // phi0 / height
      else if ( isnan(phimin) && isnan(phimax) && isnan(phiwidth) && !isnan(height) )
      {
         if ( isnan(phi0) )
         {
             MPI_MASTER(fprintf(stderr,
               "PerfectCrystal: %s WARNING: Missing parameter: phi0 set to zero\n",
               NAME_CURRENT_COMP););
            phi0 = 0;
         }
         phiwidth = 2.0 * asin( height / 2.0 / radius ) * RAD2DEG;
         phimin = phi0 - phiwidth/2.0;
         phimax = phi0 + phiwidth/2.0;
      }
      else
      {
         MPI_MASTER(fprintf(stderr,
            "PerfectCrystal: %s ERROR: Invalid parameters: Cannot determine analyzer height/phi\n",
            NAME_CURRENT_COMP););
         exit(-1);
      }
   }

   if ( centerfocus && !radius )
   {
      MPI_MASTER(fprintf(stderr,
         "PerfectCrystal: %s ERROR: Invalid parameters: centerfocus doesn't make sense with radius==0\n",
         NAME_CURRENT_COMP););
      exit(-1);
   }

   //******************************************
   // neutron optics
   if ( !ismirror )
   {
      if ( isnan(lambda) == isnan(tau) )
      {
         MPI_MASTER(fprintf(stderr,
            "PerfectCrystal: %s ERROR: Invalid parameters: provide either tau or lambda\n",
            NAME_CURRENT_COMP););
         exit(-1);
      }

      if ( isnan(tau) )
         tau = 4*PI / lambda;

      v0 = tau / 2.0 * K2V;
      E0 = SQR(v0) * VS2E;

      if ( isnan(dtauovertau) == isnan(sigma) )
      {
         MPI_MASTER(fprintf(stderr,
            "PerfectCrystal: %s ERROR: Invalid parameters: provide either sigma or dtauovertau, or switch on ismirror\n",
            NAME_CURRENT_COMP););
         exit(-1);
      }
   }
   else
   {
      if ( !isnan(dtauovertau) || !isnan(sigma) )
      {
         MPI_MASTER(fprintf(stderr,
            "PerfectCrystal: %s WARNING: dtauovertau and/or sigma is ignored with ismirror==1\n",
            NAME_CURRENT_COMP););
      }
   }


   if ( !( R0>=0 && R0<=1.0 ) )
   {
      MPI_MASTER(fprintf(stderr,
         "PerfectCrystal: %s ERROR: Invalid parameter: R0 must be between 0 and 1\n",
         NAME_CURRENT_COMP););
      exit(-1);
   }

   if ( transmit < 0.0 || transmit > 1.0 )
   {
      MPI_MASTER(fprintf(stderr,
         "PerfectCrystal: %s ERROR: Invalid parameter: transmit must be between 0 and 1\n",
         NAME_CURRENT_COMP););
      exit(-1);
   }

   // transform Gaussian sigma from energy (meV) to neutron velocity (m/s)
   if ( !isnan(sigma) )
      sigma /= 3.29106e-3 * tau;   // constant is hbar/2 in appropriate units

   // transform smartwidth from energy (meV) to neutron velocity (m/s)
   if ( !isnan(smartwidth) )
      smartwidth /= 3.29106e-3 * tau;   // constant is hbar/2 in appropriate units

   // set standard widths for smartphase
   if ( isnan(smartwidth) && !isnan(sigma) )
      smartwidth = 5.0 * sigma;

   if ( isnan(smartwidth) && !isnan(dtauovertau) )
      smartwidth = 10.0 * dtauovertau * v0;

   if ( smartphase && !isnan(dtauovertau) )
   {
       MPI_MASTER(fprintf(stderr,
         "PerfectCrystal: %s WARNING: Using smartphase for ewald/darwin curves is probably a bad idea, because these curves have very long wings which will be cut off!!!\n",
         NAME_CURRENT_COMP););
   }

   if (facette_xi)
   {
      if (facette_xi < 0)
      {
         MPI_MASTER(fprintf(stderr,
            "PerfectCrystal: %s ERROR: facette_xi must be >= 0\n",
            NAME_CURRENT_COMP););
         exit(-1);
      }

      sin_facette_xi = sin(DEG2RAD*facette_xi);
   }

   // talk to the user ...
   if ( verbose )
   {
      #define PRINTVAR(str,val) fprintf(stderr,"%s --- %s : %g\n",NAME_CURRENT_COMP,str,val);
      MPI_MASTER(
         PRINTVAR("ttmin",ttmin);
         PRINTVAR("ttmax",ttmax);
         PRINTVAR("ttwidth",ttwidth);
         PRINTVAR("width",width);
         PRINTVAR("phimin",phimin);
         PRINTVAR("phimax",phimax);
         PRINTVAR("phi0",phi0);
         PRINTVAR("phiwidth",phiwidth);
         PRINTVAR("height",height);
         PRINTVAR("centerfocus",centerfocus);
         PRINTVAR("debyescherrer",debyescherrer);
         PRINTVAR("radius",radius);
         PRINTVAR("facette",facette);
         PRINTVAR("facette_xi",facette_xi);
         PRINTVAR("sin_facette_xi",sin_facette_xi);
         PRINTVAR("tau",tau);
         PRINTVAR("lambda",lambda);
         PRINTVAR("v0",v0);
         PRINTVAR("E0",E0);
         PRINTVAR("dtauovertau",dtauovertau);
         PRINTVAR("dtauovertau_ext",dtauovertau_ext);
         PRINTVAR("ewald",ewald);
         PRINTVAR("R0",R0);
         PRINTVAR("sigma[m/s]",sigma);
         PRINTVAR("ismirror",ismirror);
         PRINTVAR("speed",speed);
         PRINTVAR("amplitude",amplitude);
         PRINTVAR("smartphase",smartphase);
         PRINTVAR("smartwidth[m/s]",smartwidth);
         PRINTVAR("exclusive",exclusive);
      );
   }

%}
TRACE
%{
   double dt1, dt2, q0mod;
   double nx,ny,nz;
   int missed = 0;

   // determine phase of doppler movement
   if ( (speed!=0) && smartphase)
   {
      // do something smart
      vmin = v0 - sqrt(SQR(vx)+SQR(vy)+SQR(vz)) - smartwidth;
      vmax = vmin + 2.0*smartwidth;

      if ( (vmin>speed) || (vmax<-speed) )
         ABSORB;

      if ( vmin < -speed )
         vmin = -speed;

      if ( vmax > speed )
         vmax = speed;

//      fprintf(stderr,"%g  %g\n",vmin,vmax);

      vd = vmin + (vmax-vmin)*rand01();
      phid = acos(vd/speed) + (rand01()<0.5?0:PI);
      zd = amplitude * sin(phid);
      p *= (vmax-vmin) / 2.0 / speed;
   }
   else if ( speed!=0 )
   {
      // random selection of phase
//      phid = rand01() * 2.0 * PI;
//      zd = amplitude * sin(phid);
//      vd = speed * cos(phid);

      // random selection of speed
      phid = acos(randpm1()) + (rand01()<0.5?0:PI);
      vd = speed * cos(phid);
      zd = amplitude * sin(phid);
   }
   else
   {
      // no movement
      zd = 0;
      vd = 0;
   }

   // Propagate to analyzer and determine surface normal
   if ( !radius )
   {
      // flat analyzer, use height and width

      // propoagate to surface
      if (!vz)
         ABSORB;
      dt2 = (-z-zd) / vz;
      PROP_DT(dt2);

      // see if the covered area is hit
      missed = !inside_rectangle(x,y,width,height);

      // surface normal in this case is simple
      nx=0;ny=0;nz=-1;
   }
   else
   {
      // spherical analyzer, use spherical coordinates ...

      // compute neutron path intersection with analyzer sphere
      if ( centerfocus )
         missed = !sphere_intersect(&dt1,&dt2,x,y,z+zd,vx,vy,vz,radius);
      else
         missed = !sphere_intersect(&dt1,&dt2,x,y,z+radius+zd,vx,vy,vz,radius);

      if ( !missed )
      {
         // propoagate to surface
         PROP_DT(dt2);

         // tt (twotheta) is calculated as in IN16B, positive values downstream rightwards
         // select coordinate system depending on 'debyescherrer' switch
         double zprime = z+(centerfocus?0:radius)+zd;
         if (debyescherrer) {
            tt = RAD2DEG * atan2( sqrt(SQR(x)+SQR(y)) , zprime );
            phi = RAD2DEG * atan2(y,-x);
         } else {
            tt = - RAD2DEG * atan2(x,zprime);
            phi = RAD2DEG * asin( y/sqrt( SQR(x) + SQR(y) + SQR(zprime) ) );
         }

         missed = (tt<ttmin || tt>ttmax || phi<phimin || phi>phimax);

         if ( !missed )
         {
            // analyzer surface normal
            if ( facette ) {
               // calculate center of the facette hit
               // always use spherical coordinates with y main axis (as in debyescherrer=0),
               // otherwise the pole will be in the analyzer center!
               // for the sake of confusion, use radians in this part.
               double tt1, ttfacette, ttn, phi1, phifacette, phin;
               if (debyescherrer) {
                  tt1 = - atan2(x,zprime);
                  phi1 = asin( y/sqrt( SQR(x) + SQR(y) + SQR(zprime) ) );
               } else {
                  tt1 = DEG2RAD * tt;
                  phi1 = DEG2RAD * phi;
               }

               phifacette = facette / radius;
               phin = floor( phi1 / phifacette + 0.5 ) * phifacette;
               ttfacette = facette / (radius*cos(phin));
               ttn = floor( tt1 / ttfacette + 0.5 ) * ttfacette;

               nx = cos(phin)*sin(ttn);
               ny = -sin(phin);
               nz = -cos(phin)*cos(ttn);

               if (facette_xi)
               {
                  grandvec_target_circle(&nx,&ny,&nz,NULL,nx,ny,nz,sin_facette_xi);
               }
            } else {
               nx = -x;
               ny = -y;
               nz = -z-(centerfocus?0:radius)-zd;
               NORM(nx,ny,nz);
            }
         }
      }
   }

   // Do the reflection
   if ( !missed )
   {
      // velocity vector projected on surface normal
      // in moving doppler frame
      vperp = scalar_prod(vx,vy,vz+vd,nx,ny,nz);

      // angle between surface normal and velocity (only used as output parameter for monitoring)
      xi = RAD2DEG * acos( - vperp / sqrt( SQR(vx) + SQR(vy) + SQR(vz+vd) ) );

      // energy selection
      if (!ismirror)
      {
         if ( !isnan(dtauovertau) )
         {
            // eps is actually abs(y)
            // vperp is negative!
            double this_tau = tau;
            if ( dtauovertau_ext )
            {
               this_tau *= 1.0 + 0.5*dtauovertau_ext*randpm1();
            }

            eps = fabs( 4.0*vperp*V2K/this_tau + 2.0 ) / dtauovertau;

            // Darwin/Ewald curve
            if ( eps > 1 )
            {
               if ( ewald )
               {
                  // energy selection with Ewald curve
                  R = 1.0 - sqrt(SQR(eps)-1.0) / eps;
               }
               else
               {
                  // energy selection with Darwin curve
                  R = eps - sqrt(SQR(eps)-1.0);
                  R *= R;
               }

               R *= R0;
            }
            else
            {
               R = R0;
            }
         }
         else
         {
            // Gauss curve (vperp is negative!)
            eps = fabs(v0 + vperp) / sigma;
            R = exp( -SQR(eps) / 2.0 );

            R *= R0;
         }
      }
      else
      {
         R = 1;
         eps = NAN;
      }

      if ( transmit && (R!=1) && (rand01()<transmit) )
      {
         p *= (1.0 - R) / transmit;
         SCATTER;
      }
      else
      {
         // reflection: vector kf = ki - qmod0  where qmod0 = 2*n*(n dot ki'),
         // n is the surface normal vector and ki' the incident wavevector
         // in the moving frame
         q0mod = 2.0 * vperp;
         // q0mod is now directly in velocity units (and contains energy shift due to Doppler effect!)

         vx -= q0mod*nx;
         vy -= q0mod*ny;
         vz -= q0mod*nz;
         if (R!=1)
            p *= R / (1.0-transmit);

         if (R<1e-10)
            ABSORB;
         else
            SCATTER;
      }

   }
   else if ( !exclusive )
   {
      R=NAN;
      xi=NAN;
      RESTORE_NEUTRON (INDEX_CURRENT_COMP,x, y, z, vx, vy, vz, t, sx, sy, sz, p);
   }
   else
      ABSORB;

%}

MCDISPLAY
%{
   int ttsegments;
   int phisegments;
   int steps;
//   const double centerspheresize = 0.1*radius;

   int i, itt, iphi, istep;
   double phi,tt,dphi,dtt;
   double x1,y1,z1,x2,y2,z2;
   double z0;

   if (debyescherrer) {
      ttsegments = 4;
      phisegments = 20;
      steps = 50;
   } else {
      ttsegments = 10;
      phisegments = 10;
      steps = 30;
   }


   // mark the center with a sphere
//   circle("xy",0,0,0,centerspheresize);
//   circle("xz",0,0,0,centerspheresize);
//   circle("yz",0,0,z0,centerspheresize);

   // draw crystal surface
   if ( radius )
   {
      z0 = (centerfocus ? 0 : radius);

      // curved surface
      dtt = (ttmax - ttmin) / (double)steps;
      dphi = (phimax - phimin) / (double)steps;

      for (itt=0; itt<=ttsegments; itt++) {
         tt = ttmin + (ttmax - ttmin) * (double)itt / (double)ttsegments;
         for (istep=0; istep<steps; istep++) {
            phi = phimin + dphi*istep;
            sph2cart(&x1,&y1,&z1,radius,tt,phi,debyescherrer);
            sph2cart(&x2,&y2,&z2,radius,tt,phi+dphi,debyescherrer);
            line(x1,y1,z1-z0,x2,y2,z2-z0);
         }
      }

      for (iphi=0; iphi<=phisegments; iphi++) {
         phi = phimin + (phimax - phimin) * (double)iphi / (double)phisegments;
         for (istep=0; istep<steps; istep++) {
            tt = ttmin + dtt*istep;
            sph2cart(&x1,&y1,&z1,radius,tt,phi,debyescherrer);
            sph2cart(&x2,&y2,&z2,radius,tt+dtt,phi,debyescherrer);
            line(x1,y1,z1-z0,x2,y2,z2-z0);
         }
      }

      // additional dashed moving one
      if ( amplitude )
      {

         sph2cart(&x1,&y1,&z1,radius,ttmin,phimin,debyescherrer);
         dashed_line(x1,y1,z1-z0-amplitude,x1,y1,z1-z0+amplitude,6);
         sph2cart(&x1,&y1,&z1,radius,ttmax,phimin,debyescherrer);
         dashed_line(x1,y1,z1-z0-amplitude,x1,y1,z1-z0+amplitude,6);
         sph2cart(&x1,&y1,&z1,radius,ttmin,phimax,debyescherrer);
         dashed_line(x1,y1,z1-z0-amplitude,x1,y1,z1-z0+amplitude,6);
         sph2cart(&x1,&y1,&z1,radius,ttmax,phimax,debyescherrer);
         dashed_line(x1,y1,z1-z0-amplitude,x1,y1,z1-z0+amplitude,6);

         for (i=-1;i<=1;i+=2)
         {
            z0 = (centerfocus ? 0 : radius) + i * amplitude;

            for (itt=0; itt<=ttsegments; itt++) {
               tt = ttmin + (ttmax - ttmin) * (double)itt / (double)ttsegments;
               for (istep=0; istep<steps; istep++) {
                  phi = phimin + dphi*istep;
                  sph2cart(&x1,&y1,&z1,radius,tt,phi,debyescherrer);
                  sph2cart(&x2,&y2,&z2,radius,tt,phi+dphi,debyescherrer);
                  dashed_line(x1,y1,z1-z0,x2,y2,z2-z0,1);
               }
            }

            for (iphi=0; iphi<=phisegments; iphi++) {
               phi = phimin + (phimax - phimin) * (double)iphi / (double)phisegments;
               for (istep=0; istep<steps; istep++) {
                  tt = ttmin + dtt*istep;
                  sph2cart(&x1,&y1,&z1,radius,tt,phi,debyescherrer);
                  sph2cart(&x2,&y2,&z2,radius,tt+dtt,phi,debyescherrer);
                  dashed_line(x1,y1,z1-z0,x2,y2,z2-z0,1);
               }
            }
         }
      }

   }
   else
   {
      // flat surface
      rectangle("xy",0.0,0.0,0.0,width,height);
      // additional moving one
      if ( amplitude )
      {
         dashed_line(+width/2.0,+height/2.0,-amplitude,+width/2.0,+height/2.0,+amplitude,6);
         dashed_line(+width/2.0,-height/2.0,-amplitude,+width/2.0,-height/2.0,+amplitude,6);
         dashed_line(-width/2.0,+height/2.0,-amplitude,-width/2.0,+height/2.0,+amplitude,6);
         dashed_line(-width/2.0,-height/2.0,-amplitude,-width/2.0,-height/2.0,+amplitude,6);
         for (i=-1;i<=1;i+=2)
            rectangle("xy",0.0,0.0,i * amplitude,width,height);
      }
   }

%}

END
