/*
======================================================================
lwscene.c

Ernie Wright  03 Jun 05

Functions for creating a LightWave scene file from SPD settings.
====================================================================== */

#include <stdio.h>
#include <math.h>
#include "def.h"


typedef struct st_lwLight {
   float    pos[ 3 ];
   float    intensity;
} lwLight;

typedef struct st_lwScene {
   int      width;
   int      height;
   float    pos[ 3 ];
   float    rot[ 3 ];
   float    zoom;
   float    paspect;
   float    bkcolor[ 3 ];
   int      nlights;
   lwLight  light[ 20 ];
   float    ambient;
} lwScene;


static lwScene scene;


/*
======================================================================
copy(), sub(), dot(), scale(), normalize()

Some useful vector functions.
====================================================================== */

static void copy( float *to, float *from )
{
   to[ 0 ] = from[ 0 ];
   to[ 1 ] = from[ 1 ];
   to[ 2 ] = from[ 2 ];
}


static void sub( float *result, float *a, float *b )
{
   result[ 0 ] = a[ 0 ] - b[ 0 ];
   result[ 1 ] = a[ 1 ] - b[ 1 ];
   result[ 2 ] = a[ 2 ] - b[ 2 ];
}


static float dot( float *a, float *b )
{
   return a[ 0 ] * b[ 0 ] + a[ 1 ] * b[ 1 ] + a[ 2 ] * b[ 2 ];
}


static void scale( float *a, float s )
{
   a[ 0 ] *= s;
   a[ 1 ] *= s;
   a[ 2 ] *= s;
}


static void normalize( float *a )
{
   float d;

   d = ( float ) sqrt( dot( a, a ));
   if ( d > 0.0f )
      scale( a, 1.0f / d );
}


static void write_env( FILE *fp, int channel, float value )
{
   fprintf( fp,
      "Channel %d\n"
      "{ Envelope\n"
      "  1\n"
      "  Key %g 0 0 0 0 0 0 0 0\n"
      "  Behaviors 1 1\n"
      "}\n",
      channel, value );
}


static void write_camera( FILE *fp )
{
   int i;

   fprintf( fp, "\n"
      "AddCamera\n"
      "CameraName Camera\n"
      "ShowCamera 1 2\n"
      "CameraMotion\n"
      "NumChannels 6\n" );

   for ( i = 0; i < 3; i++ )
      write_env( fp, i, scene.pos[ i ] );
   for ( i = 0; i < 3; i++ )
      write_env( fp, i + 3, scene.rot[ i ] );

   fprintf( fp, "\n"
      "ZoomFactor %g\n"
      "ResolutionMultiplier 1.0\n"
      "FrameSize %d %d\n"
      "PixelAspect 1\n"
      "MaskPosition 0 0 %d %d\n"
      "MotionBlur 0\n"
      "FieldRendering 0\n",
      scene.zoom,
      scene.width,
      scene.height,
      scene.width,
      scene.height );

   fprintf( fp, "\n"
      "ApertureHeight 0.015\n"
      "Antialiasing 0\n"
      "AdaptiveSampling 1\n"
      "AdaptiveThreshold 0.1\n" );
}


static void write_light( FILE *fp, int index )
{
   int i;

   fprintf( fp, "\n"
      "AddLight\n"
      "LightName Light%d\n"
      "ShowLight 1 5\n"
      "LightMotion\n"
      "NumChannels 9\n",
      index + 1 );

   for ( i = 0; i < 3; i++ )
      write_env( fp, i, scene.light[ index ].pos[ i ] );
   for ( i = 3; i < 9; i++ )
      write_env( fp, i, i < 6 ? 0.0f : 1.0f );

   fprintf( fp, "\n"
      "LightColor 1 1 1\n"
      "LightIntensity %g\n"
      "AffectCaustics 1\n"
      "LightType 1\n"
      "ShadowType 1\n",
      scene.light[ index ].intensity );
}


static void write_backdrop( FILE *fp )
{
   fprintf( fp, "\n"
      "SolidBackdrop 1\n"
      "BackdropColor %g %g %g\n"
      "ZenithColor 0 0.1569 0.3137\n"
      "SkyColor 0.4706 0.7059 0.9412\n"
      "GroundColor 0.1961 0.1569 0.1176\n"
      "NadirColor 0.3922 0.3137 0.2353\n"
      "FogType 0\n"
      "DitherIntensity 1\n"
      "AnimatedDither 0\n",
      scene.bkcolor[ 0 ],
      scene.bkcolor[ 1 ],
      scene.bkcolor[ 2 ] );
}


static void write_header( FILE *fp )
{
   fprintf( fp,
      "LWSC\n"
      "3\n\n"
      "FirstFrame 1\n"
      "LastFrame 60\n"
      "FrameStep 1\n"
      "PreviewFirstFrame 0\n"
      "PreviewLastFrame 60\n"
      "PreviewFrameStep 1\n"
      "CurrentFrame 0\n"
      "FramesPerSecond 30\n\n"
      "AmbientColor 1 1 1\n"
      "AmbientIntensity %g\n",
      scene.ambient );
}


static void write_footer( FILE *fp )
{
   fprintf( fp, "\n"
      "RenderMode 2\n"
      "RayTraceEffects 3\n"
      "RayRecursionLimit 16\n"
      "RayTraceOptimization 1\n"
      "DataOverlayLabel\n"
      "SaveRGB 0\n"
      "SaveAlpha 0\n" );
}


/*
======================================================================
lwInitScene()

Initialize the lwScene with default values.  If these values aren't
overwritten by settings specific to one of the SPD objects, we'll
still have valid values for writing a default scene.  This also undoes
the previous routine's settings.
====================================================================== */

void lwInitScene( void )
{
   scene.width    = 640;
   scene.height   = 480;
   scene.zoom     = 3.2f;
   scene.paspect  = 1.0f;

   scene.pos[ 0 ] = 0.0f;
   scene.pos[ 1 ] = 0.0f;
   scene.pos[ 2 ] = -5.0f;

   scene.rot[ 0 ] =
   scene.rot[ 1 ] =
   scene.rot[ 2 ] = 0.0f;

   scene.bkcolor[ 0 ] =
   scene.bkcolor[ 1 ] =
   scene.bkcolor[ 2 ] = 0.0f;

   scene.nlights = 0;
   scene.light[ 0 ].pos[ 0 ] = -2.0f;
   scene.light[ 0 ].pos[ 1 ] =  2.0f;
   scene.light[ 0 ].pos[ 2 ] = -2.0f;
   scene.light[ 0 ].intensity = 1.0f;
   scene.ambient = 0.25f;
}


/*
======================================================================
lwSetCamera()

Set scene parameters related to the camera.

   pos            the camera location
   at             the center of the image, AKA "lookat"
   up             up vector
   vfov           vertical field of view of the camera
   faspect        aspect ratio of horizontal fov to vertical fov
   width          pixel width of the image
   height         pixel height of the image

The vectors are all right-handed, with Z up, so we need to exchange y
and z,

   x' = x
   y' = z
   z' = -y

The LW camera's rotation is expressed as Euler angles in radians.
The field of view is defined by a zoom factor, the ratio of eye
distance to half the view height.
====================================================================== */

void lwSetCamera( double *pos, double *at, double *up, double vfov,
   double faspect, int width, int height )
{
   double h, p, b, w;
   float v[ 3 ], n[ 3 ], temp[ 3 ];

   n[ 0 ] = ( float )( at[ 0 ] - pos[ 0 ] );   //  x
   n[ 1 ] = ( float )( at[ 2 ] - pos[ 2 ] );   //  z
   n[ 2 ] = ( float )( pos[ 1 ] - at[ 1 ] );   // -y
   normalize( n );

   v[ 0 ] = ( float )  up[ 0 ];
   v[ 1 ] = ( float )  up[ 2 ];
   v[ 2 ] = ( float ) -up[ 1 ];
   normalize( v );

   p = asin( -n[ 1 ] );
   if ( n[ 0 ] == 0.0 && n[ 2 ] == 0.0 )
      h = 0.0;
   else
      h = atan2( n[ 0 ], n[ 2 ] );

   /* The up vector is notional, a suggestion of which way up is, like
      (0, 1, 0), rather than an actual axis orthogonal to the look
      vector, so we need to project it onto the view plane before we
      use it to try to find the bank angle.  But first we test for the
      common (0, 1, 0) case. */

   if ( v[ 1 ] == 1.0f )
      b = 0.0f;
   else if ( v[ 1 ] == -1.0f )
      b = PI;
   else {
      copy( temp, n );
      scale( temp, dot( n, v ));
      sub( v, v, temp );
      normalize( v );
      if ( fabs( n[ 1 ] ) < 0.9999 )
         b = acos( v[ 1 ] / cos( p ));
      else
         b = atan2( v[ 0 ], v[ 2 ] );
   }

   scene.pos[ 0 ] = ( float )  pos[ 0 ];
   scene.pos[ 1 ] = ( float )  pos[ 2 ];
   scene.pos[ 2 ] = ( float ) -pos[ 1 ];
   scene.rot[ 0 ] = ( float ) h;
   scene.rot[ 1 ] = ( float ) p;
   scene.rot[ 2 ] = ( float ) b;

   vfov = DEG2RAD( vfov );
   if ( vfov > 0.0 && vfov < PI / 2.0 )
      scene.zoom = 1.0f / ( float ) tan( vfov / 2.0 );

   w = scene.zoom * tan( vfov * faspect / 2.0 );
   scene.paspect = ( float )( w * height / width );
   scene.width = width;
   scene.height = height;
}


/*
======================================================================
lwAddLight()

Add a point light to the scene.

   pos            the light location
   intensity      light intensity
====================================================================== */

void lwAddLight( double *pos, double intensity )
{
   int i = scene.nlights;

   if ( i >= 20 )
      return;
   scene.light[ i ].pos[ 0 ] = ( float )  pos[ 0 ];
   scene.light[ i ].pos[ 1 ] = ( float )  pos[ 2 ];
   scene.light[ i ].pos[ 2 ] = ( float ) -pos[ 1 ];
   scene.light[ i ].intensity = ( float ) intensity;
   ++scene.nlights;
}


/*
======================================================================
lwSetBackgroundColor()

Set the backdrop color.
====================================================================== */

void lwSetBackgroundColor( double *color )
{
   scene.bkcolor[ 0 ] = ( float ) color[ 0 ];
   scene.bkcolor[ 1 ] = ( float ) color[ 1 ];
   scene.bkcolor[ 2 ] = ( float ) color[ 2 ];
}


/*
======================================================================
lwWriteScene()

Create a LightWave scene file based on the settings in the lwScene.
====================================================================== */

int lwWriteScene( char *filename )
{
   FILE *fp;
   int i;

   fp = fopen( filename, "w" );
   if ( !fp ) return 0;

   write_header( fp );
   if ( scene.nlights == 0 )
      write_light( fp, 1 );
   else
      for ( i = 0; i < scene.nlights; i++ )
         write_light( fp, i );
   write_camera( fp );
   write_backdrop( fp );
   write_footer( fp );
   fclose( fp );
   return 1;
}
