/*
 * Copyright 1993 by Cub'x Systemes
 * 
 * All Rights Reserved
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for the  purpose of making new intuitiv'3d modules.
 * This copyright notice must appears in all copies that you distribute.
 * The name of Cub'x Systeme should not be used in advertising or publicity
 * without specific, written prior permission. 
 * CUB'X SYSTEMES DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
 * CUB'X SYSTEMES BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 */
/******************************************************************************* 
 *                                                                             *
 *     Project intuitiv'3d                                                     *
 *     -------------------                                                     *
 *                                                                             *
 *                                                                             *
 *     File: SlideProjector.m                                                  *
 *                                                                             *
 *                                                                             *
 *     Creation date: Tue Jul 27 14:42:12 GMT+0200 1993                        *
 *                                                                             *
 *     Contents: A light object that uses the slideprojector shader             *
 *                                                                             *
 *                                                                             *
 *******************************************************************************/

#import "SlideProjector.h"
#import "SlideProjectorInsp.h"
#import <3Dkit/N3DShape.h>
#import <3Dkit/N3DCamera.h>
#import <i3d/CX3DLightTransformInsp.h>
#import <i3d/CX3DLightDisplayInsp.h>
#import <i3d/CX3DLightShaderInsp.h>
#import <stdio.h>

#define VERSION 1.0
#define DEGTORAD(a) (((2.0 * M_PI) / 360.0) * (a))
#define DEPTH .01
#define ARROWDEPTH (.04)
#define ARROWDEPTH_2 (ARROWDEPTH * 5.0 )
#define BODY_OFFSET (ARROWDEPTH_2 -.02)


/*
 *  Classes variable to store the inspector description
 */
static AIParameter displayParam[2],transformParam[4];

@implementation SlideProjector
/*******************************************************************************
 *                                                                             *
 *                                Class methods                                *
 *                                                                             *
 *******************************************************************************/
/*
 *  Makes inspectors description
 *  sets the version of the class
 */
+ initialize
{
  displayParam[0].type = AIP_BOOL;
  displayParam[0].dynamicBound = NO;
  strcpy(displayParam[0].parameterName,"Always display light representation");
  displayParam[0].data.boolp.setBoolValue = @selector(showQuickRepresentation:);
  displayParam[0].data.boolp.getBoolValue = @selector(showQuickRepresentation);
  strcpy(displayParam[0].data.boolp.trueTitle,"Yes");
  strcpy(displayParam[0].data.boolp.falseTitle,"No");
  displayParam[1].type = AIP_NULL;  

  transformParam[0].type = AIP_POINT;
  transformParam[0].dynamicBound = NO;
  strcpy(transformParam[0].parameterName,"From");
  transformParam[0].data.pointp.setXValueMethod = @selector(setFromX:);
  transformParam[0].data.pointp.setYValueMethod = @selector(setFromY:);
  transformParam[0].data.pointp.setZValueMethod = @selector(setFromZ:);
  transformParam[0].data.pointp.getXValueMethod = @selector(getFromX:);
  transformParam[0].data.pointp.getYValueMethod = @selector(getFromY:);
  transformParam[0].data.pointp.getZValueMethod = @selector(getFromZ:);
  transformParam[0].data.pointp.getXMinValueMethod = @selector(getMinCoord:);
  transformParam[0].data.pointp.getYMinValueMethod = @selector(getMinCoord:);
  transformParam[0].data.pointp.getZMinValueMethod = @selector(getMinCoord:);
  transformParam[0].data.pointp.getXMaxValueMethod = @selector(getMaxCoord:);
  transformParam[0].data.pointp.getYMaxValueMethod = @selector(getMaxCoord:);
  transformParam[0].data.pointp.getZMaxValueMethod = @selector(getMaxCoord:);

  transformParam[1].type = AIP_POINT;
  transformParam[1].dynamicBound = NO;
  strcpy(transformParam[1].parameterName,"To");
  transformParam[1].data.pointp.setXValueMethod = @selector(setToX:);
  transformParam[1].data.pointp.setYValueMethod = @selector(setToY:);
  transformParam[1].data.pointp.setZValueMethod = @selector(setToZ:);
  transformParam[1].data.pointp.getXValueMethod = @selector(getToX:);
  transformParam[1].data.pointp.getYValueMethod = @selector(getToY:);
  transformParam[1].data.pointp.getZValueMethod = @selector(getToZ:);
  transformParam[1].data.pointp.getXMinValueMethod = @selector(getMinCoord:);
  transformParam[1].data.pointp.getYMinValueMethod = @selector(getMinCoord:);
  transformParam[1].data.pointp.getZMinValueMethod = @selector(getMinCoord:);
  transformParam[1].data.pointp.getXMaxValueMethod = @selector(getMaxCoord:);
  transformParam[1].data.pointp.getYMaxValueMethod = @selector(getMaxCoord:);
  transformParam[1].data.pointp.getZMaxValueMethod = @selector(getMaxCoord:);

  transformParam[2].type = AIP_BOOL;
  transformParam[2].dynamicBound = NO;
  strcpy(transformParam[2].parameterName,"Moved point");
  transformParam[2].data.boolp.setBoolValue = @selector(setMoveTo:);
  transformParam[2].data.boolp.getBoolValue = @selector(moveTo);
  strcpy(transformParam[2].data.boolp.trueTitle,"To");
  strcpy(transformParam[2].data.boolp.falseTitle,"From");

  transformParam[3].type = AIP_NULL;


  [self setVersion: VERSION];
  return [super initialize];
}

/*
 *  ExportedVariables allows you to speed-up the implementation of the read: and
 *  write: methods. Please read the documentation.
 */
+ (ExportedVariable*) exportedVariable
{
  static ExportedVariable ev[] = {
    {"fov",EV_FLOAT,0},
    {"up" ,EV_3DPOINT,0},
    {"shadowDetail",EV_FLOAT,0},
    {"useShadow",EV_BOOL,0},
    {"slideImageIsTiff",EV_BOOL,0},
    {NULL,0,0}
  };
 return ev;
}

/*******************************************************************************
 *                                                                             *
 *                               Instance methods                              *
 *                                                                             *
 *******************************************************************************/

- free
{
  free( shadowBuf );
  free( txImagePath );
  return [super free];
}

/*
 *  The init method should set some default values for your instance variables.
 */
- init
{
  [super init];
  [self setSurface: SURF_WIREFRAME];
  [self setIconFromMachO:"SlideProjector"];
  shadowBuf = (char*) malloc(MAXPATHLEN+1);
  sprintf(shadowBuf,"%s/slideprojector%d_%x.z",[NXApp tmpFolder],(int)getpid(),(int)self);
  [NXApp unlinkThisFileSomeday: shadowBuf];
  txImagePath   = (char*) malloc(MAXPATHLEN+1);
  sprintf(txImagePath,"%s/slideprojector_tx.%d_%x.tx",[NXApp tmpFolder],(int)getpid(),(int)self);
  [NXApp unlinkThisFileSomeday: txImagePath];

  fov = 30.0;
  CX(up) = 0.0; CY(up) = 1.0; CZ(up) = 0.0;
  
  shadowDetail = 40.0;
  useShadow = NO;
  strcpy(slideName,"/NextLibrary/Textures/brick.tx");
  slideImageIsTiff = NO;
  txDone = NO;
  return self;
}

/*
 *  When the user drops a light from the library, this method is
 *  called. This is your chance to get the current camera location
 *  and to calculate a from / to vector for the new light instance.
 */
- initSelfFromCamera: aCamera
{
  float ddx,ddy,ddz;
  RtPoint f,t;
  float roll;
  [aCamera getEyeAt: &f toward: &t roll: &roll];
  N3D_CopyPoint(f,from);
  N3D_CopyPoint(t,to);

  ddx = CX(from) - CX(to);
  ddy = CY(from) - CY(to);
  ddz = CZ(from) - CZ(to);
  CX(from) = CX(to) + (ddx * 1.05);
  CY(from) = CY(to) + (ddy * 1.05);
  CZ(from) = CZ(to) + (ddz * 1.05);
  return self;
}

/*
 *  The copy method should be implemented. 
 *  Duplicate the receiver.
 */
- copy
{
  SlideProjector *s = [super copy];
  s->fov = fov;
  N3D_CopyPoint(up,s->up);
  strcpy(s->slideName,slideName);
  s->shadowDetail = shadowDetail;
  s->useShadow = useShadow;
  s->slideImageIsTiff = slideImageIsTiff;
  return s;
}

/*
 *  This method is called by the rendering pipeline
 *  when lights have to be rendered the illuminate
 *  method is called. You must use the RiLightSource()
 *  function
 */
- illuminate
{
  static int light_id = 0;
  char item[64];

  if( [self isOn] )
    {
      char *slideNamePtr = &slideName[0];
      float radFov = ((M_PI / 2.0) / 180.0) * fov;
      RtToken fov_t,from_t,to_t,up_t,slidename_t,shadowname_t;

      fov_t  = RiDeclare("fieldofview","varying float");
      from_t = RiDeclare("from","varying point");
      to_t   = RiDeclare("to",  "varying point");
      up_t   = RiDeclare("up",  "varying point");
      slidename_t = RiDeclare("slidename","uniform string");
      sprintf(item,"%x%d",(unsigned int) self, light_id++);
      if( useShadow && [NXApp fileExists:  shadowBuf] )
	{
	  shadowname_t = RiDeclare("shadowname","uniform string");
	  if( slideImageIsTiff )
	    RiLightSource(item,"slideprojector",
			  fov_t, &radFov,
			  from_t,&from,
			  to_t  ,&to,
			  up_t  ,&up,
			  slidename_t , &txImagePath,
			  shadowname_t , &shadowBuf,
			  RI_NULL);
	  else
	    RiLightSource(item,"slideprojector",
			  fov_t, &radFov,
			  from_t,&from,
			  to_t  ,&to,
			  up_t  ,&up,
			  slidename_t , &slideNamePtr,
			  shadowname_t , &shadowBuf,
			  RI_NULL);
	    
			
	}
      else
	{
	  if( slideImageIsTiff )
	    RiLightSource(item,"slideprojector",
			  fov_t, &radFov,
			  from_t,&from,
			  to_t  ,&to,
			  up_t  ,&up,
			  slidename_t , &txImagePath,
			  RI_NULL);
	  else
	    RiLightSource(item,"slideprojector",
			  fov_t, &radFov,
			  from_t,&from,
			  to_t  ,&to,
			  up_t  ,&up,
			  slidename_t , &slideNamePtr,
			  RI_NULL);
	}
    }
  return self;
}

/*
 *  The preRender method is called for every object that implements it.
 *  The preRender cycle is used to calculate  images (map) that
 *  are used after to calculate the final image. This is currently
 *  use for texture mapping, shadow-mapping and environment mapping.
 *  A preRender level variable in the displayer describes rendering
 *  level. There are currently two rendering levels:
 *  PRERENDER_TEXTURE: This is the time to translate tiff into .tx
 *  PRERENDER_DEFAULT: This is the time to perform any other task.
 * 
 *  PRERENDER_LEVEL exists because some prerendering tasks requires some
 *  other prerendering tasks.
 */
- preRender: aDisplay
{
  int level = [aDisplay preRenderLevel];

  if( level == PRERENDER_DEFAULT )
    {
      /*
       *  Make the shadow map
       */
      if( useShadow && [self isOn] )
	{
	  RtPoint from_bis,to_bis;

	  N3D_CopyPoint( from, from_bis );
	  N3D_CopyPoint( to  , to_bis   );

	  [shape convertPoints: &from_bis count: 1 fromAncestor: [aDisplay worldShape]];
	  [shape convertPoints: &to_bis   count: 1 fromAncestor: [aDisplay worldShape]];

	  [aDisplay renderShadowMapIn: shadowBuf 
	                         from: from_bis
	                       toward: to_bis
	                 shadowDetail: shadowDetail
	                  perspective: YES
	                 screenWindow: NULL
	                          fov: fov];
	}
    }
  else
    /*
     *  Make the .tx from the tiff file if needed
     */
    if (level == PRERENDER_TEXTURE && slideImageIsTiff && !txDone)
    {
      
      char *tmprib = malloc( MAXPATHLEN + 1);
      FILE *f;
      sprintf(tmprib,"%s/sp_tmprib_%d_%x.rib",[NXApp tmpFolder],(int)getpid(),(int)self);
      f = fopen(tmprib,"w+");
      if( f )
	{
	  txDone = YES;
	  fprintf(f,"MakeTexture \"%s\" \"%s\" \"black\" \"black\" \"gaussian\" 1.0 1.0\n", slideName, txImagePath );
	  fclose(f);
	  [aDisplay executeRibFile: tmprib  panelTitle:"Making slide projector texture"];
	  unlink(tmprib);
	}
      else
	NXRunAlertPanel("Error","Can't make the texture for the slide projector.","Ok",0,0);
      free( tmprib );
    }
  return self;
}


/*
 *  post-rendering is used to delete any file or to free any memory used
 *  during pre-rendering.
 */
- postRender: aDisplay
{
  return self;
}

/*
 *  Into a light object, the render method is used to describe the
 *  light representation. This method is called only when the
 *  display is quick-renderman
 */
- render
{
  RtPoint p[4],pt[4],triangle[3],from_bis,to_bis;
  RtMatrix tm;
  double normeXZ,normeXYZ;
  float ddd;
  double inc,angle,max = 2.0 * M_PI;
  double radius;

  if( [super render] )
    {
      /*
       *  Translates to the origin and computes the transformation matrix
       */
      N3D_CopyPoint(from,from_bis);
      N3D_CopyPoint(to,to_bis);
  
      CX(from_bis) -= CX(to);
      CY(from_bis) -= CY(to);
      CZ(from_bis) -= CZ(to);

      CX(to_bis) = CY(to_bis) = CZ(to_bis) = 0.0;

      /*
       *  Computes rotation matrix
       */
      normeXZ  = sqrt( CX(from_bis) * CX(from_bis) + CZ(from_bis) * CZ(from_bis));
      normeXYZ = sqrt( CX(from_bis) * CX(from_bis) + CY(from_bis) * CY(from_bis) + 
		      CZ(from_bis) * CZ(from_bis));
      if( normeXZ != 0.0 )
	{
	  tm[0][0] = CX(from_bis);
	  tm[1][0] = -(CX(from_bis) * CY(from_bis)) / normeXZ;
	  tm[2][0] = (-CZ(from_bis) /normeXZ) * normeXYZ;
	  tm[0][1] = CY(from_bis);
	  tm[1][1] = (CX(from_bis) * CX(from_bis) + CZ(from_bis) * CZ(from_bis)) / normeXZ;
	  tm[2][1] = 0;
	  tm[0][2] = CZ(from_bis);
	  tm[1][2] = - (CY(from_bis) * CZ(from_bis)) / normeXZ;
	  tm[2][2] = (CX(from_bis) /normeXZ) * normeXYZ;
	  tm[0][3] = 0; 
	  tm[1][3] = 0.0;
	  tm[2][3] = 0.0;
	  tm[3][0] = 0.0;
	  tm[3][1] = 0.0;
	  tm[3][2] = 0.0;
	  tm[3][3] = 1.0;
	}
      else
	{
	  tm[0][0] = 0;            tm[1][0] = -CY(from_bis); tm[2][0] = 0;  tm[3][0] = 0;
	  tm[0][1] = CY(from_bis); tm[1][1] = 0;             tm[2][1] = 0;  tm[3][1] = 0;
	  tm[0][2] = 0;            tm[1][2] = 0;             tm[2][2] = normeXYZ;  tm[3][2] = 0;
	  tm[0][3] = 0;            tm[1][3] = 0;             tm[2][3] = 0;  tm[3][3] = 1;
	}

  
      ddd = 1.0; 


      RiTransformBegin();
      RiTranslate(CX(to),CY(to),CZ(to));
      /*
       *  Draws the vector body
       */
      CX(p[0]) = BODY_OFFSET;
      CY(p[0]) = DEPTH;
      CZ(p[0]) = -DEPTH;

      CX(p[1]) = ddd;
      CY(p[1]) = DEPTH;
      CZ(p[1]) = -DEPTH;

      CX(p[2]) =  ddd;
      CY(p[2]) =  -DEPTH;
      CZ(p[2]) =  -DEPTH;

      CX(p[3]) =  BODY_OFFSET;
      CY(p[3]) =  -DEPTH;
      CZ(p[3]) =  -DEPTH;
      CX3DMult3DPoints(p,4,tm,pt);
      RiLine((RtInt)4,RI_P,(RtPointer) pt, RI_NULL );

      CX(p[0]) = BODY_OFFSET;
      CY(p[0]) = DEPTH;
      CZ(p[0]) = DEPTH;

      CX(p[1]) = ddd;
      CY(p[1]) = DEPTH;
      CZ(p[1]) = DEPTH;

      CX(p[2]) =  ddd;
      CY(p[2]) =  -DEPTH;
      CZ(p[2]) =  DEPTH;

      CX(p[3]) =  BODY_OFFSET;
      CY(p[3]) =  -DEPTH;
      CZ(p[3]) =  DEPTH;
      CX3DMult3DPoints(p,4,tm,pt);
      RiLine((RtInt)4,RI_P,(RtPointer) pt, RI_NULL );

      CX(p[0]) = BODY_OFFSET;
      CY(p[0]) = -DEPTH;
      CZ(p[0]) = DEPTH;

      CX(p[1]) = ddd;
      CY(p[1]) = -DEPTH;
      CZ(p[1]) = DEPTH;

      CX(p[2]) =  ddd;
      CY(p[2]) =  -DEPTH;
      CZ(p[2]) =  -DEPTH;

      CX(p[3]) =  BODY_OFFSET;
      CY(p[3]) =  -DEPTH;
      CZ(p[3]) =  -DEPTH;
      CX3DMult3DPoints(p,4,tm,pt);
      RiLine((RtInt)4,RI_P,(RtPointer) pt, RI_NULL );


      CX(p[0]) = BODY_OFFSET;
      CY(p[0]) = DEPTH;
      CZ(p[0]) = DEPTH;

      CX(p[1]) = ddd;
      CY(p[1]) = DEPTH;
      CZ(p[1]) = DEPTH;

      CX(p[2]) =  ddd;
      CY(p[2]) =  DEPTH;
      CZ(p[2]) =  -DEPTH;

      CX(p[3]) =  BODY_OFFSET;
      CY(p[3]) =  DEPTH;
      CZ(p[3]) =  -DEPTH;
      CX3DMult3DPoints(p,4,tm,pt);
      RiLine((RtInt)4,RI_P,(RtPointer) pt, RI_NULL );

      /*
       *  Draws the vector arrow
       */
      CX(p[0]) = ARROWDEPTH_2;
      CY(p[0]) = ARROWDEPTH;
      CZ(p[0]) = -ARROWDEPTH;
      CX(p[1]) = ARROWDEPTH_2;
      CY(p[1]) = ARROWDEPTH;
      CZ(p[1]) = ARROWDEPTH;
      CX(p[2]) = ARROWDEPTH_2;
      CY(p[2]) = -ARROWDEPTH;
      CZ(p[2]) = ARROWDEPTH;
      CX(p[3]) = ARROWDEPTH_2;
      CY(p[3]) = -ARROWDEPTH;
      CZ(p[3]) = -ARROWDEPTH;

      CX3DMult3DPoints(p,4,tm,pt);
      N3D_CopyPoint( pt[0], triangle[0] );
      N3D_CopyPoint( to_bis  ,   triangle[1] );
      N3D_CopyPoint( pt[1], triangle[2] );
      RiLine((RtInt)3,RI_P,(RtPointer) triangle, RI_NULL );
      N3D_CopyPoint( pt[1], triangle[0] );
      N3D_CopyPoint( to_bis  ,   triangle[1] );
      N3D_CopyPoint( pt[2], triangle[2] );
      RiLine((RtInt)3,RI_P,(RtPointer) triangle, RI_NULL );
      N3D_CopyPoint( pt[2], triangle[0] );
      N3D_CopyPoint( to_bis  ,   triangle[1] );
      N3D_CopyPoint( pt[3], triangle[2] );
      RiLine((RtInt)3,RI_P,(RtPointer) triangle, RI_NULL );
      N3D_CopyPoint( pt[3], triangle[0] );
      N3D_CopyPoint( to_bis  ,   triangle[1] );
      N3D_CopyPoint( pt[0], triangle[2] );
      RiLine((RtInt)3,RI_P,(RtPointer) triangle, RI_NULL );

      if( fov < 180.0 )
	{
	  /*
	   * Displays the angle cone
	   */
	  inc = (2.0 * M_PI) / 8.0;
	  radius = ddd * tan( DEGTORAD( (fov / 2.0) ) );

	  for( angle = 0.0 ; angle < max ; angle += inc )
	    {
	      CX(triangle[0]) = 0.0; 
	      CY(triangle[0]) = radius * sin( angle );
	      CZ(triangle[0]) = radius * cos( angle ); 

	      CX(triangle[1]) = 0.0;
	      CY(triangle[1]) = radius * sin( angle + inc);
	      CZ(triangle[1]) = radius * cos( angle + inc);

	      CX(triangle[2]) = ddd;
	      CY(triangle[2]) = 0.0;
	      CZ(triangle[2]) = 0.0;
      
	      CX3DMult3DPoints(triangle,3,tm,pt);
	      RiLine((RtInt)3, RI_P, (RtPointer) pt, RI_NULL);
	    }
	}
      RiTransformEnd();
    }
  return self;
}

/*
 *  The bounding box of a light is the bounding box
 *  of its representation (as rendered by the render 
 *  method)
 */
- getBoundingBox:(RtBound) aBox
{
  int i;
  RtPoint p[8];
  [self getBoundingBoxInWorldSpace:p];
  BB_MINX(aBox) = BB_MAXX(aBox) = CX(p[0]);
  BB_MINY(aBox) = BB_MAXY(aBox) = CY(p[0]);
  BB_MINZ(aBox) = BB_MAXZ(aBox) = CZ(p[0]);
  for( i = 1 ; i < 8 ; i++ )
    {
      BB_MINX(aBox) = MIN(BB_MINX(aBox),CX(p[i]));
      BB_MAXX(aBox) = MAX(BB_MAXX(aBox),CX(p[i]));
      BB_MINY(aBox) = MIN(BB_MINY(aBox),CY(p[i]));
      BB_MAXY(aBox) = MAX(BB_MAXY(aBox),CY(p[i]));
      BB_MINZ(aBox) = MIN(BB_MINZ(aBox),CZ(p[i]));
      BB_MAXZ(aBox) = MAX(BB_MAXZ(aBox),CZ(p[i]));
    }
  return self;
}

/*
 *  The bounding box of a light is the bounding box
 *  of its representation (as rendered by the render 
 *  method) This method returns the bounding box
 *  of the light in the world coordinate system.
 */
- getBoundingBoxInWorldSpace:(RtPoint*) p
{
  RtMatrix tm;
  double normeXZ,normeXYZ;
  RtPoint from_bis,to_bis;
  int i;

  N3D_CopyPoint(from,from_bis);
  N3D_CopyPoint(to,to_bis);
  
  CX(from_bis) -= CX(to);
  CY(from_bis) -= CY(to);
  CZ(from_bis) -= CZ(to);

  CX(to_bis) = CY(to_bis) = CZ(to_bis) = 0.0;

  /*
   *  Computes rotation matrix
   */
  normeXZ  = sqrt( CX(from_bis) * CX(from_bis) + CZ(from_bis) * CZ(from_bis));
  normeXYZ = sqrt( CX(from_bis) * CX(from_bis) + CY(from_bis) * CY(from_bis) +
		   CZ(from_bis) * CZ(from_bis));

  if( normeXZ != 0.0 )
    {
      tm[0][0] = CX(from_bis);
      tm[1][0] = -(CX(from_bis) * CY(from_bis)) / normeXZ;
      tm[2][0] = (-CZ(from_bis) /normeXZ) * normeXYZ;
      tm[0][1] = CY(from_bis);
      tm[1][1] = (CX(from_bis) * CX(from_bis) + CZ(from_bis) * CZ(from_bis)) / normeXZ;
      tm[2][1] = 0;
      tm[0][2] = CZ(from_bis);
      tm[1][2] = - (CY(from_bis) * CZ(from_bis)) / normeXZ;
      tm[2][2] = (CX(from_bis) /normeXZ) * normeXYZ;
      tm[0][3] = 0; 
      tm[1][3] = 0.0;
      tm[2][3] = 0.0;
      tm[3][0] = 0.0;
      tm[3][1] = 0.0;
      tm[3][2] = 0.0;
      tm[3][3] = 1.0;
    }
  else
    {
      tm[0][0] = 0;            tm[1][0] = -CY(from_bis); tm[2][0] = 0;  tm[3][0] = 0;
      tm[0][1] = CY(from_bis); tm[1][1] = 0;             tm[2][1] = 0;  tm[3][1] = 0;
      tm[0][2] = 0;            tm[1][2] = 0;             tm[2][2] = normeXYZ;  tm[3][2] = 0;
      tm[0][3] = 0;            tm[1][3] = 0;             tm[2][3] = 0;  tm[3][3] = 1;
    }
  /*
   *  Makes the box at identity
   */
  CX(p[0]) = 0.0; CY(p[0]) = -DEPTH; CZ(p[0]) = -DEPTH;
  CX(p[1]) = 1.0; CY(p[1]) = -DEPTH; CZ(p[1]) = -DEPTH;
  CX(p[2]) = 1.0; CY(p[2]) =  DEPTH; CZ(p[2]) = -DEPTH;
  CX(p[3]) = 0.0; CY(p[3]) =  DEPTH; CZ(p[3]) = -DEPTH;
  CX(p[4]) = 0.0; CY(p[4]) = -DEPTH; CZ(p[4]) =  DEPTH;
  CX(p[5]) = 1.0; CY(p[5]) = -DEPTH; CZ(p[5]) =  DEPTH;
  CX(p[6]) = 1.0; CY(p[6]) =  DEPTH; CZ(p[6]) =  DEPTH;
  CX(p[7]) = 0.0; CY(p[7]) =  DEPTH; CZ(p[7]) =  DEPTH;

  CX3DMult3DPoints(p,8,tm,p);

  for( i = 0 ; i < 8 ;  i++ )
    {
      CX(p[i]) += CX(to);
      CY(p[i]) += CY(to);
      CZ(p[i]) += CZ(to);
    }
  return self;
}

/*******************************************************************************
 *                                                                             *
 *                          Parameters and inspectors                          *
 *                                                                             *
 *******************************************************************************/

/*
 *  Please open SlideProjectorInsp.[hm] and SlideProjectorInsp.nib
 * to understand theses methods
 */
- setFieldOfView: (float) aValue
{
  fov = aValue;
  return self;
}

- (float) fieldOfView
{
  return fov;
}

- setUpX:(float) aFloat
{
  CX(up) = aFloat;
  return self;
}

- (float) upX
{
  return CX(up);
}

- setUpY:(float) aFloat
{
  CY(up) = aFloat;
  return self;
}

- (float) upY
{
  return CY(up);
}

- setUpZ:(float) aFloat
{
  CZ(up) = aFloat;
  return self;
}

- (float) upZ
{
  return CZ(up);
}

- setSlideName:(const char*) aName
{
  int len;
  strcpy( slideName , aName);
  len = strlen( slideName );
  if( len > 4                 &&
      slideName[len-1] == 'f' && 
      slideName[len-2] == 'f' &&
      slideName[len-3] == 'i' &&
      slideName[len-4] == 't' )
    {
      slideImageIsTiff = YES;
      txDone = NO;
    }
  else
    slideImageIsTiff = NO;
  return self;
}

- (const char*) slideName
{
  return slideName;
}

- setShadowDetail:(float) aFloat
{
  shadowDetail = aFloat;
  return self;
}

- (float) shadowDetail
{
  return shadowDetail;
}

- setUseShadow:(BOOL) aFlag
{
  useShadow = aFlag;
  return self;
}

- (BOOL) useShadow
{
  return useShadow;
}

/*
 *  This methods returns the inspector class for
 *  the attribute inspector
 */
- attributeInspClass
{
  return [SlideProjectorInsp class];
}

/*
 *  This methods returns the inspector class for
 *  the transformation inspector
 */
- transformationInspClass
{
  return [CX3DLightTransformInsp class];
}

/*
 *  This method returns the inspector class for
 *  the display inspector 
 */
- shadingInspClass
{
  return [CX3DLightDisplayInsp class];
}

/*
 *  This method returns the inspector class for
 *  the shader inspector 
 */
- shadersInspClass
{
  return [CX3DLightShaderInsp class];
}

/*
 *  This method returns the description of the display
 *  inspector.Please see the + initialize method
 */
- (AIParameter*) displayParameters
{
  return displayParam;
}

/*
 *  This method returns the description of the transform
 *  inspector. Please see the + initialize method
 */
- (AIParameter*) transformParameters
{
  return transformParam;
}

/*
 *  Return the name of the inspector
 */
- (const char*) inspectorName
{
  return "Slide projector inspector";
}

/*
 *  This methods are here for the LightTransform inspector
 */
- setFromX:(float*) v       { CX(from) = *v; return self;            }
- setFromY:(float*) v       { CY(from) = *v; return self;            }
- setFromZ: (float*)v       { CZ(from) = *v; return self;            }
- getFromX:(float*) v       { *v = CX(from); return self;            }
- getFromY:(float*) v       { *v = CY(from); return self;            }
- getFromZ:(float*) v       { *v = CZ(from); return self;            }
- setToX:(float*) v       { CX(to) = *v; return self;            }
- setToY:(float*) v       { CY(to) = *v; return self;            }
- setToZ: (float*)v       { CZ(to) = *v; return self;            }
- getToX:(float*) v       { *v = CX(to); return self;            }
- getToY:(float*) v       { *v = CY(to); return self;            }
- getToZ:(float*) v       { *v = CZ(to); return self;            }

- getMinCoord:(float*)v     { *v = -1.0; return self; }
- getMaxCoord:(float*)v     { *v = 1.0;  return self; }
- getMinAngle:(float*)v     { *v = 0.0; return self; }
- getMaxAngle:(float*)v     { *v = 360.0;  return self; }

/*******************************************************************************
 *                                                                             *
 *                             Reading and writing                             *
 *                                                                             *
 *******************************************************************************/
- awake
{
  [super awake];
  shadowBuf = (char*) malloc(MAXPATHLEN+1);
  sprintf(shadowBuf,"%s/slideprojector%d_%x.z",[NXApp tmpFolder],(int)getpid(),(int)self);
  [NXApp unlinkThisFileSomeday: shadowBuf];
  txImagePath   = (char*) malloc(MAXPATHLEN+1);
  sprintf(txImagePath,"%s/slideprojector_tx.%d_%x.tx",[NXApp tmpFolder],(int)getpid(),(int)self);
  [NXApp unlinkThisFileSomeday: txImagePath];
  txDone = NO;
  /*
   *  Initializes the un-saved variable
   */
  [self setSlideName: slideName];
  return self;
}

/*
 *  We have to overload the read and write method in order to
 *  save the string slideName
 */
- read:(NXTypedStream*)s
{
  int len;
  [super read:s];

  NXReadType(s,@encode(int),(void*)&len);
  if( len > 0 )
    {
      NXReadArray(s,@encode(char),len,(void*)slideName);
      slideName[len] = '\0';
    }
  else
    slideName[0] = '\0';
  return self;
}

- write:(NXTypedStream*) s
{
  int len;
  [super write:s];
  len = strlen( slideName );
  if( len > 0 )
    {
      NXWriteType(s,@encode(int),(const void*) &len);
      NXWriteArray(s,@encode(char), len , (const void*) slideName );
    }
  return self;
}

@end
