You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
284 lines
8.2 KiB
284 lines
8.2 KiB
//========= 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 |
|
|
|
|
|
|