//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
#ifndef _3D_UNITVEC_H
#define _3D_UNITVEC_H


#define UNITVEC_DECLARE_STATICS \
   float cUnitVector::mUVAdjustment[0x2000]; \
   Vector cUnitVector::mTmpVec;

// upper 3 bits
#define SIGN_MASK  0xe000
#define XSIGN_MASK 0x8000
#define YSIGN_MASK 0x4000
#define ZSIGN_MASK 0x2000

// middle 6 bits - xbits
#define TOP_MASK  0x1f80

// lower 7 bits - ybits
#define BOTTOM_MASK  0x007f

// unitcomp.cpp : A Unit Vector to 16-bit word conversion
// algorithm based on work of Rafael Baptista (rafael@oroboro.com)
// Accuracy improved by O.D. (punkfloyd@rocketmail.com)
// Used with Permission.

// a compressed unit vector. reasonable fidelty for unit
// vectors in a 16 bit package. Good enough for surface normals
// we hope.
class cUnitVector // : public c3dMathObject
{
public:
   cUnitVector() { mVec = 0; }
   cUnitVector( const Vector& vec )
   {
      packVector( vec );
   }
   cUnitVector( unsigned short val ) { mVec = val; }

   cUnitVector& operator=( const Vector& vec )
   { packVector( vec ); return *this; }

   operator Vector()
   {
      unpackVector( mTmpVec );
      return mTmpVec;
   }

   void packVector( const Vector& vec )
   {
      // convert from Vector to cUnitVector

      Assert( vec.IsValid());
      Vector tmp = vec;

      // input vector does not have to be unit length
      // Assert( tmp.length() <= 1.001f );

      mVec = 0;
      if ( tmp.x < 0 ) { mVec |= XSIGN_MASK; tmp.x = -tmp.x; }
      if ( tmp.y < 0 ) { mVec |= YSIGN_MASK; tmp.y = -tmp.y; }
      if ( tmp.z < 0 ) { mVec |= ZSIGN_MASK; tmp.z = -tmp.z; }

      // project the normal onto the plane that goes through
      // X0=(1,0,0),Y0=(0,1,0),Z0=(0,0,1).
      // on that plane we choose an (projective!) coordinate system
      // such that X0->(0,0), Y0->(126,0), Z0->(0,126),(0,0,0)->Infinity

      // a little slower... old pack was 4 multiplies and 2 adds.
      // This is 2 multiplies, 2 adds, and a divide....
      float w = 126.0f / ( tmp.x + tmp.y + tmp.z );
      long xbits = (long)( tmp.x * w );
      long ybits = (long)( tmp.y * w );

      Assert( xbits <  127 );
      Assert( xbits >= 0   );
      Assert( ybits <  127 );
      Assert( ybits >= 0   );

      // Now we can be sure that 0<=xp<=126, 0<=yp<=126, 0<=xp+yp<=126
      // however for the sampling we want to transform this triangle
      // into a rectangle.
      if ( xbits >= 64 )
      {
         xbits = 127 - xbits;
         ybits = 127 - ybits;
      }

      // now we that have xp in the range (0,127) and yp in
      // the range (0,63), we can pack all the bits together
      mVec |= ( xbits << 7 );
      mVec |= ybits;
   }

   void unpackVector( Vector& vec )
   {
      // if we do a straightforward backward transform
      // we will get points on the plane X0,Y0,Z0
      // however we need points on a sphere that goes through
      // these points. Therefore we need to adjust x,y,z so
      // that x^2+y^2+z^2=1 by normalizing the vector. We have
      // already precalculated the amount by which we need to
      // scale, so all we do is a table lookup and a
      // multiplication

      // get the x and y bits
      long xbits = (( mVec & TOP_MASK ) >> 7 );
      long ybits = ( mVec & BOTTOM_MASK );

      // map the numbers back to the triangle (0,0)-(0,126)-(126,0)
      if (( xbits + ybits ) >= 127 )
      {
         xbits = 127 - xbits;
         ybits = 127 - ybits;
      }

      // do the inverse transform and normalization
      // costs 3 extra multiplies and 2 subtracts. No big deal.
      float uvadj = mUVAdjustment[mVec & ~SIGN_MASK];
      vec.x = uvadj * (float) xbits;
      vec.y = uvadj * (float) ybits;
      vec.z = uvadj * (float)( 126 - xbits - ybits );

      // set all the sign bits
      if ( mVec & XSIGN_MASK ) vec.x = -vec.x;
      if ( mVec & YSIGN_MASK ) vec.y = -vec.y;
      if ( mVec & ZSIGN_MASK ) vec.z = -vec.z;

      Assert( vec.IsValid());
   }

   static void initializeStatics()
   {
      for ( int idx = 0; idx < 0x2000; idx++ )
      {
         long xbits = idx >> 7;
         long ybits = idx & BOTTOM_MASK;

         // map the numbers back to the triangle (0,0)-(0,127)-(127,0)
         if (( xbits + ybits ) >= 127 )
         {
            xbits = 127 - xbits;
            ybits = 127 - ybits;
         }

         // convert to 3D vectors
         float x = (float)xbits;
         float y = (float)ybits;
         float z = (float)( 126 - xbits - ybits );
		
         // calculate the amount of normalization required
         mUVAdjustment[idx] = 1.0f / sqrtf( y*y + z*z + x*x );
         Assert( _finite( mUVAdjustment[idx]));

         //cerr << mUVAdjustment[idx] << "\t";
         //if ( xbits == 0 ) cerr << "\n";
      }
   }

#if 0
   void test()
   {
      #define TEST_RANGE 4
      #define TEST_RANDOM 100
      #define TEST_ANGERROR 1.0

      float maxError = 0;
      float avgError = 0;
      int numVecs = 0;

      {for ( int x = -TEST_RANGE; x < TEST_RANGE; x++ )
      {
         for ( int y = -TEST_RANGE; y < TEST_RANGE; y++ )
         {
            for ( int z = -TEST_RANGE; z < TEST_RANGE; z++ )
            {
               if (( x + y + z ) == 0 ) continue;

               Vector vec( (float)x, (float)y, (float)z );
               Vector vec2;

               vec.normalize();
               packVector( vec );
               unpackVector( vec2 );

               float ang = vec.dot( vec2 );
               ang = (( fabs( ang ) > 0.99999f ) ? 0 : (float)acos(ang));

               if (( ang > TEST_ANGERROR ) | ( !_finite( ang )))
               {
                  cerr << "error: " << ang << endl;
                  cerr << "orig vec:       " << vec.x << ",\t"
                       << vec.y << ",\t" << vec.z << "\tmVec: "
                       << mVec << endl;
                  cerr << "quantized vec2: " << vec2.x
                       << ",\t" << vec2.y << ",\t"
                       << vec2.z << endl << endl;
               }
               avgError += ang;
               numVecs++;
               if ( maxError < ang ) maxError = ang;
            }
         }
      }}

      for ( int w = 0; w < TEST_RANDOM; w++ )
      {
         Vector vec( genRandom(), genRandom(), genRandom());
         Vector vec2;
         vec.normalize();

         packVector( vec );
         unpackVector( vec2 );

         float ang =vec.dot( vec2 );
         ang = (( ang > 0.999f ) ? 0 : (float)acos(ang));

         if (( ang > TEST_ANGERROR ) | ( !_finite( ang )))
         {
            cerr << "error: " << ang << endl;
            cerr << "orig vec:       " << vec.x << ",\t"
                 << vec.y << ",\t" << vec.z << "\tmVec: "
                 << mVec << endl;
            cerr << "quantized vec2: " << vec2.x << ",\t"
                 << vec2.y << ",\t"
                 << vec2.z << endl << endl;
         }
         avgError += ang;
         numVecs++;
         if ( maxError < ang ) maxError = ang;
      }

      { for ( int x = 0; x < 50; x++ )
      {
         Vector vec( (float)x, 25.0f, 0.0f );
         Vector vec2;

         vec.normalize();
         packVector( vec );
         unpackVector( vec2 );

         float ang = vec.dot( vec2 );
         ang = (( fabs( ang ) > 0.999f ) ? 0 : (float)acos(ang));

         if (( ang > TEST_ANGERROR ) | ( !_finite( ang )))
         {
            cerr << "error: " << ang << endl;
            cerr << "orig vec:       " << vec.x << ",\t"
                 << vec.y << ",\t" << vec.z << "\tmVec: "
                 << mVec << endl;
            cerr << "   quantized vec2: " << vec2.x << ",\t"
                 << vec2.y << ",\t" << vec2.z << endl << endl;
         }

         avgError += ang;
         numVecs++;
         if ( maxError < ang ) maxError = ang;
      }}

      cerr << "max angle error: " << maxError
           << ", average error: " << avgError / numVecs
           << ", num tested vecs: " << numVecs << endl;
   }

   friend ostream& operator<< ( ostream& os, const cUnitVector& vec )
   { os << vec.mVec; return os; }
#endif

//protected: // !!!!

   unsigned short mVec;
   static float mUVAdjustment[0x2000];
   static Vector mTmpVec;
};

#endif // _3D_VECTOR_H