text:
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//===========================================================================//

#ifndef MATH_LIB_H
#define MATH_LIB_H

#include <math.h>
#include "tier0/basetypes.h"
#include "tier0/commonmacros.h"
#include "mathlib/vector.h"
#include "mathlib/vector2d.h"
#include "tier0/dbg.h"

#include "mathlib/math_pfns.h"

#if defined(__i386__) || defined(_M_IX86)
// For MMX intrinsics
#include <xmmintrin.h>
#endif

// XXX remove me
#undef clamp

// Uncomment this to enable FP exceptions in parts of the code.
// This can help track down FP bugs. However the code is not
// FP exception clean so this not a turnkey operation.
//#define FP_EXCEPTIONS_ENABLED

#ifdef FP_EXCEPTIONS_ENABLED
#include <float.h> // For _clearfp and _controlfp_s
#endif

// FPExceptionDisabler and FPExceptionEnabler taken from my blog post
// at http://www.altdevblogaday.com/2012/04/20/exceptional-floating-point/

// Declare an object of this type in a scope in order to suppress
// all floating-point exceptions temporarily. The old exception
// state will be reset at the end.
class FPExceptionDisabler
{
public:
#ifdef FP_EXCEPTIONS_ENABLED
	FPExceptionDisabler();
	~FPExceptionDisabler();

private:
	unsigned int mOldValues;
#else
	FPExceptionDisabler() {}
	~FPExceptionDisabler() {}
#endif

private:
	// Make the copy constructor and assignment operator private
	// and unimplemented to prohibit copying.
	FPExceptionDisabler(const FPExceptionDisabler&);
	FPExceptionDisabler& operator=(const FPExceptionDisabler&);
};

// Declare an object of this type in a scope in order to enable a
// specified set of floating-point exceptions temporarily. The old
// exception state will be reset at the end.
// This class can be nested.
class FPExceptionEnabler
{
public:
	// Overflow, divide-by-zero, and invalid-operation are the FP
	// exceptions most frequently associated with bugs.
#ifdef FP_EXCEPTIONS_ENABLED
	FPExceptionEnabler(unsigned int enableBits = _EM_OVERFLOW | _EM_ZERODIVIDE | _EM_INVALID);
	~FPExceptionEnabler();

private:
	unsigned int mOldValues;
#else
	FPExceptionEnabler(unsigned int enableBits = 0)
	{
	}
	~FPExceptionEnabler()
	{
	}
#endif

private:
	// Make the copy constructor and assignment operator private
	// and unimplemented to prohibit copying.
	FPExceptionEnabler(const FPExceptionEnabler&);
	FPExceptionEnabler& operator=(const FPExceptionEnabler&);
};

#ifdef DEBUG  // stop crashing edit-and-continue
FORCEINLINE float clamp( float val, float minVal, float maxVal )
{
	if ( maxVal < minVal )
		return maxVal;
	else if( val < minVal )
		return minVal;
	else if( val > maxVal )
		return maxVal;
	else
		return val;
}
#else // DEBUG
FORCEINLINE float clamp( float val, float minVal, float maxVal )
{
#if defined(__i386__) || defined(_M_IX86)
	_mm_store_ss( &val,
		_mm_min_ss(
			_mm_max_ss(
				_mm_load_ss(&val),
				_mm_load_ss(&minVal) ),
			_mm_load_ss(&maxVal) ) );
#else
	val = fpmax(minVal, val);
	val = fpmin(maxVal, val);
#endif
	return val;
}
#endif // DEBUG

//
// Returns a clamped value in the range [min, max].
//
template< class T >
inline T clamp( T const &val, T const &minVal, T const &maxVal )
{
	if ( maxVal < minVal )
		return maxVal;
	else if( val < minVal )
		return minVal;
	else if( val > maxVal )
		return maxVal;
	else
		return val;
}

// plane_t structure
// !!! if this is changed, it must be changed in asm code too !!!
// FIXME: does the asm code even exist anymore?
// FIXME: this should move to a different file
struct cplane_t
{
	Vector	normal;
	float	dist;
	byte	type;			// for fast side tests
	byte	signbits;		// signx + (signy<<1) + (signz<<1)
	byte	pad[2];

#ifdef VECTOR_NO_SLOW_OPERATIONS
	cplane_t() {}

private:
	// No copy constructors allowed if we're in optimal mode
	cplane_t(const cplane_t& vOther);
#endif
};

// structure offset for asm code
#define CPLANE_NORMAL_X			0
#define CPLANE_NORMAL_Y			4
#define CPLANE_NORMAL_Z			8
#define CPLANE_DIST				12
#define CPLANE_TYPE				16
#define CPLANE_SIGNBITS			17
#define CPLANE_PAD0				18
#define CPLANE_PAD1				19

// 0-2 are axial planes
#define	PLANE_X			0
#define	PLANE_Y			1
#define	PLANE_Z			2

// 3-5 are non-axial planes snapped to the nearest
#define	PLANE_ANYX		3
#define	PLANE_ANYY		4
#define	PLANE_ANYZ		5

//-----------------------------------------------------------------------------
// Frustum plane indices.
// WARNING: there is code that depends on these values
//-----------------------------------------------------------------------------

enum
{
	FRUSTUM_RIGHT		= 0,
	FRUSTUM_LEFT		= 1,
	FRUSTUM_TOP			= 2,
	FRUSTUM_BOTTOM		= 3,
	FRUSTUM_NEARZ		= 4,
	FRUSTUM_FARZ		= 5,
	FRUSTUM_NUMPLANES	= 6
};

extern int SignbitsForPlane( cplane_t *out );

class Frustum_t
{
public:
	void SetPlane( int i, int nType, const Vector &vecNormal, float dist )
	{
		m_Plane[i].normal = vecNormal;
		m_Plane[i].dist = dist;
		m_Plane[i].type = nType;
		m_Plane[i].signbits = SignbitsForPlane( &m_Plane[i] );
		m_AbsNormal[i].Init( fabs(vecNormal.x), fabs(vecNormal.y), fabs(vecNormal.z) );
	}

	inline const cplane_t *GetPlane( int i ) const { return &m_Plane[i]; }
	inline const Vector &GetAbsNormal( int i ) const { return m_AbsNormal[i]; }

private:
	cplane_t	m_Plane[FRUSTUM_NUMPLANES];
	Vector		m_AbsNormal[FRUSTUM_NUMPLANES];
};

// Computes Y fov from an X fov and a screen aspect ratio + X from Y
float CalcFovY( float flFovX, float flScreenAspect );
float CalcFovX( float flFovY, float flScreenAspect );

// Generate a frustum based on perspective view parameters
// NOTE: FOV is specified in degrees, as the *full* view angle (not half-angle)
void GeneratePerspectiveFrustum( const Vector& origin, const QAngle &angles, float flZNear, float flZFar, float flFovX, float flAspectRatio, Frustum_t &frustum );
void GeneratePerspectiveFrustum( const Vector& origin, const Vector &forward, const Vector &right, const Vector &up, float flZNear, float flZFar, float flFovX, float flFovY, Frustum_t &frustum );

// Cull the world-space bounding box to the specified frustum.
bool R_CullBox( const Vector& mins, const Vector& maxs, const Frustum_t &frustum );
bool R_CullBoxSkipNear( const Vector& mins, const Vector& maxs, const Frustum_t &frustum );

struct matrix3x4_t
{
	matrix3x4_t() {}
	matrix3x4_t(
		float m00, float m01, float m02, float m03,
		float m10, float m11, float m12, float m13,
		float m20, float m21, float m22, float m23 )
	{
		m_flMatVal[0][0] = m00;	m_flMatVal[0][1] = m01; m_flMatVal[0][2] = m02; m_flMatVal[0][3] = m03;
		m_flMatVal[1][0] = m10;	m_flMatVal[1][1] = m11; m_flMatVal[1][2] = m12; m_flMatVal[1][3] = m13;
		m_flMatVal[2][0] = m20;	m_flMatVal[2][1] = m21; m_flMatVal[2][2] = m22; m_flMatVal[2][3] = m23;
	}

	//-----------------------------------------------------------------------------
	// Creates a matrix where the X axis = forward
	// the Y axis = left, and the Z axis = up
	//-----------------------------------------------------------------------------
	void Init( const Vector& xAxis, const Vector& yAxis, const Vector& zAxis, const Vector &vecOrigin )
	{
		m_flMatVal[0][0] = xAxis.x; m_flMatVal[0][1] = yAxis.x; m_flMatVal[0][2] = zAxis.x; m_flMatVal[0][3] = vecOrigin.x;
		m_flMatVal[1][0] = xAxis.y; m_flMatVal[1][1] = yAxis.y; m_flMatVal[1][2] = zAxis.y; m_flMatVal[1][3] = vecOrigin.y;
		m_flMatVal[2][0] = xAxis.z; m_flMatVal[2][1] = yAxis.z; m_flMatVal[2][2] = zAxis.z; m_flMatVal[2][3] = vecOrigin.z;
	}

	//-----------------------------------------------------------------------------
	// Creates a matrix where the X axis = forward
	// the Y axis = left, and the Z axis = up
	//-----------------------------------------------------------------------------
	matrix3x4_t( const Vector& xAxis, const Vector& yAxis, const Vector& zAxis, const Vector &vecOrigin )
	{
		Init( xAxis, yAxis, zAxis, vecOrigin );
	}

	inline void Invalidate( void )
	{
		for (int i = 0; i < 3; i++)
		{
			for (int j = 0; j < 4; j++)
			{
				m_flMatVal[i][j] = VEC_T_NAN;
			}
		}
	}

	float *operator[]( int i )				{ Assert(( i >= 0 ) && ( i < 3 )); return m_flMatVal[i]; }
	const float *operator[]( int i ) const	{ Assert(( i >= 0 ) && ( i < 3 )); return m_flMatVal[i]; }
	float *Base()							{ return &m_flMatVal[0][0]; }
	const float *Base() const				{ return &m_flMatVal[0][0]; }

	float m_flMatVal[3][4];
};

#ifndef M_PI
	#define M_PI		3.14159265358979323846	// matches value in gcc v2 math.h
#endif

#define M_PI_F		((float)(M_PI))	// Shouldn't collide with anything.

// NJS: Inlined to prevent floats from being autopromoted to doubles, as with the old system.
#ifndef RAD2DEG
	#define RAD2DEG( x  )  ( (float)(x) * (float)(180.f / M_PI_F) )
#endif

#ifndef DEG2RAD
	#define DEG2RAD( x  )  ( (float)(x) * (float)(M_PI_F / 180.f) )
#endif

// Used to represent sides of things like planes.
#define	SIDE_FRONT	0
#define	SIDE_BACK	1
#define	SIDE_ON		2
#define SIDE_CROSS  -2      // necessary for polylib.c

#define ON_VIS_EPSILON  0.01    // necessary for vvis (flow.c) -- again look into moving later!
#define	EQUAL_EPSILON	0.001   // necessary for vbsp (faces.c) -- should look into moving it there?

extern bool s_bMathlibInitialized;

extern  const Vector vec3_origin;
extern  const QAngle vec3_angle;
extern	const Quaternion quat_identity;
extern const Vector vec3_invalid;
extern	const int nanmask;

#define	IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask)

FORCEINLINE vec_t DotProduct(const vec_t *v1, const vec_t *v2)
{
	return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
}
FORCEINLINE void VectorSubtract(const vec_t *a, const vec_t *b, vec_t *c)
{
	c[0]=a[0]-b[0];
	c[1]=a[1]-b[1];
	c[2]=a[2]-b[2];
}
FORCEINLINE void VectorAdd(const vec_t *a, const vec_t *b, vec_t *c)
{
	c[0]=a[0]+b[0];
	c[1]=a[1]+b[1];
	c[2]=a[2]+b[2];
}
FORCEINLINE void VectorCopy(const vec_t *a, vec_t *b)
{
	b[0]=a[0];
	b[1]=a[1];
	b[2]=a[2];
}
FORCEINLINE void VectorClear(vec_t *a)
{
	a[0]=a[1]=a[2]=0;
}

FORCEINLINE float VectorMaximum(const vec_t *v)
{
	return max( v[0], max( v[1], v[2] ) );
}

FORCEINLINE float VectorMaximum(const Vector& v)
{
	return max( v.x, max( v.y, v.z ) );
}

FORCEINLINE void VectorScale (const float* in, vec_t scale, float* out)
{
	out[0] = in[0]*scale;
	out[1] = in[1]*scale;
	out[2] = in[2]*scale;
}

// Cannot be forceinline as they have overloads:
inline void VectorFill(vec_t *a, float b)
{
	a[0]=a[1]=a[2]=b;
}

inline void VectorNegate(vec_t *a)
{
	a[0]=-a[0];
	a[1]=-a[1];
	a[2]=-a[2];
}

//#define VectorMaximum(a)		( max( (a)[0], max( (a)[1], (a)[2] ) ) )
#define Vector2Clear(x)			{(x)[0]=(x)[1]=0;}
#define Vector2Negate(x)		{(x)[0]=-((x)[0]);(x)[1]=-((x)[1]);}
#define Vector2Copy(a,b)		{(b)[0]=(a)[0];(b)[1]=(a)[1];}
#define Vector2Subtract(a,b,c)	{(c)[0]=(a)[0]-(b)[0];(c)[1]=(a)[1]-(b)[1];}
#define Vector2Add(a,b,c)		{(c)[0]=(a)[0]+(b)[0];(c)[1]=(a)[1]+(b)[1];}
#define Vector2Scale(a,b,c)		{(c)[0]=(b)*(a)[0];(c)[1]=(b)*(a)[1];}

// NJS: Some functions in VBSP still need to use these for dealing with mixing vec4's and shorts with vec_t's.
// remove when no longer needed.
#define VECTOR_COPY( A, B ) do { (B)[0] = (A)[0]; (B)[1] = (A)[1]; (B)[2]=(A)[2]; } while(0)
#define DOT_PRODUCT( A, B ) ( (A)[0]*(B)[0] + (A)[1]*(B)[1] + (A)[2]*(B)[2] )

FORCEINLINE void VectorMAInline( const float* start, float scale, const float* direction, float* dest )
{
	dest[0]=start[0]+direction[0]*scale;
	dest[1]=start[1]+direction[1]*scale;
	dest[2]=start[2]+direction[2]*scale;
}

FORCEINLINE void VectorMAInline( const Vector& start, float scale, const Vector& direction, Vector& dest )
{
	dest.x=start.x+direction.x*scale;
	dest.y=start.y+direction.y*scale;
	dest.z=start.z+direction.z*scale;
}

FORCEINLINE void VectorMA( const Vector& start, float scale, const Vector& direction, Vector& dest )
{
	VectorMAInline(start, scale, direction, dest);
}

FORCEINLINE void VectorMA( const float * start, float scale, const float *direction, float *dest )
{
	VectorMAInline(start, scale, direction, dest);
}

int VectorCompare (const float *v1, const float *v2);

inline float VectorLength(const float *v)
{
	return FastSqrt( v[0]*v[0] + v[1]*v[1] + v[2]*v[2] + FLT_EPSILON );
}

void CrossProduct (const float *v1, const float *v2, float *cross);

qboolean VectorsEqual( const float *v1, const float *v2 );

inline vec_t RoundInt (vec_t in)
{
	return floor(in + 0.5f);
}

int Q_log2(int val);

// Math routines done in optimized assembly math package routines
void inline SinCos( float radians, float *sine, float *cosine )
{
#if defined( _X360 )
	XMScalarSinCos( sine, cosine, radians );
#elif defined( PLATFORM_WINDOWS_PC32 )
	_asm
	{
		fld		DWORD PTR [radians]
		fsincos

		mov edx, DWORD PTR [cosine]
		mov eax, DWORD PTR [sine]

		fstp DWORD PTR [edx]
		fstp DWORD PTR [eax]
	}
#elif defined( PLATFORM_WINDOWS_PC64 )
	*sine = sin( radians );
	*cosine = cos( radians );
#elif defined( POSIX )
	register double __cosr, __sinr;
	__asm ("fsincos" : "=t" (__cosr), "=u" (__sinr) : "0" (radians));

  	*sine = __sinr;
  	*cosine = __cosr;
#endif
}

#define SIN_TABLE_SIZE	256
#define FTOIBIAS		12582912.f
extern float SinCosTable[SIN_TABLE_SIZE];

inline float TableCos( float theta )
{
	union
	{
		int i;
		float f;
	} ftmp;

	// ideally, the following should compile down to: theta * constant + constant, changing any of these constants from defines sometimes fubars this.
	ftmp.f = theta * ( float )( SIN_TABLE_SIZE / ( 2.0f * M_PI ) ) + ( FTOIBIAS + ( SIN_TABLE_SIZE / 4 ) );
	return SinCosTable[ ftmp.i & ( SIN_TABLE_SIZE - 1 ) ];
}

inline float TableSin( float theta )
{
	union
	{
		int i;
		float f;
	} ftmp;

	// ideally, the following should compile down to: theta * constant + constant
	ftmp.f = theta * ( float )( SIN_TABLE_SIZE / ( 2.0f * M_PI ) ) + FTOIBIAS;
	return SinCosTable[ ftmp.i & ( SIN_TABLE_SIZE - 1 ) ];
}

template<class T>
FORCEINLINE T Square( T const &a )
{
	return a * a;
}

// return the smallest power of two >= x.
// returns 0 if x == 0 or x > 0x80000000 (ie numbers that would be negative if x was signed)
// NOTE: the old code took an int, and if you pass in an int of 0x80000000 casted to a uint,
//       you'll get 0x80000000, which is correct for uints, instead of 0, which was correct for ints
FORCEINLINE uint SmallestPowerOfTwoGreaterOrEqual( uint x )
{
	x -= 1;
	x |= x >> 1;
	x |= x >> 2;
	x |= x >> 4;
	x |= x >> 8;
	x |= x >> 16;
	return x + 1;
}

// return the largest power of two <= x. Will return 0 if passed 0
FORCEINLINE uint LargestPowerOfTwoLessThanOrEqual( uint x )
{
	if ( x >= 0x80000000 )
		return 0x80000000;

	return SmallestPowerOfTwoGreaterOrEqual( x + 1 ) >> 1;
}

// Math routines for optimizing division
void FloorDivMod (double numer, double denom, int *quotient, int *rem);
int GreatestCommonDivisor (int i1, int i2);

// Test for FPU denormal mode
bool IsDenormal( const float &val );

// MOVEMENT INFO
enum
{
	PITCH = 0,	// up / down
	YAW,		// left / right
	ROLL		// fall over
};

void MatrixAngles( const matrix3x4_t & matrix, float *angles ); // !!!!
void MatrixVectors( const matrix3x4_t &matrix, Vector* pForward, Vector *pRight, Vector *pUp );
void VectorTransform (const float *in1, const matrix3x4_t & in2, float *out);
void VectorITransform (const float *in1, const matrix3x4_t & in2, float *out);
void VectorRotate( const float *in1, const matrix3x4_t & in2, float *out);
void VectorRotate( const Vector &in1, const QAngle &in2, Vector &out );
void VectorRotate( const Vector &in1, const Quaternion &in2, Vector &out );
void VectorIRotate( const float *in1, const matrix3x4_t & in2, float *out);

#ifndef VECTOR_NO_SLOW_OPERATIONS

QAngle TransformAnglesToLocalSpace( const QAngle &angles, const matrix3x4_t &parentMatrix );
QAngle TransformAnglesToWorldSpace( const QAngle &angles, const matrix3x4_t &parentMatrix );

#endif

void MatrixInitialize( matrix3x4_t &mat, const Vector &vecOrigin, const Vector &vecXAxis, const Vector &vecYAxis, const Vector &vecZAxis );
void MatrixCopy( const matrix3x4_t &in, matrix3x4_t &out );
void MatrixInvert( const matrix3x4_t &in, matrix3x4_t &out );

// Matrix equality test
bool MatricesAreEqual( const matrix3x4_t &src1, const matrix3x4_t &src2, float flTolerance = 1e-5 );

void MatrixGetColumn( const matrix3x4_t &in, int column, Vector &out );
void MatrixSetColumn( const Vector &in, int column, matrix3x4_t &out );

inline void MatrixGetTranslation( const matrix3x4_t &in, Vector &out )
{
	MatrixGetColumn ( in, 3, out );
}

inline void MatrixSetTranslation( const Vector &in, matrix3x4_t &out )
{
	MatrixSetColumn ( in, 3, out );
}

void MatrixScaleBy ( const float flScale, matrix3x4_t &out );
void MatrixScaleByZero ( matrix3x4_t &out );

//void DecomposeRotation( const matrix3x4_t &mat, float *out );
void ConcatRotations (const matrix3x4_t &in1, const matrix3x4_t &in2, matrix3x4_t &out);
void ConcatTransforms (const matrix3x4_t &in1, const matrix3x4_t &in2, matrix3x4_t &out);

// For identical interface w/ VMatrix
inline void MatrixMultiply ( const matrix3x4_t &in1, const matrix3x4_t &in2, matrix3x4_t &out )
{
	ConcatTransforms( in1, in2, out );
}

void QuaternionSlerp( const Quaternion &p, const Quaternion &q, float t, Quaternion &qt );
void QuaternionSlerpNoAlign( const Quaternion &p, const Quaternion &q, float t, Quaternion &qt );
void QuaternionBlend( const Quaternion &p, const Quaternion &q, float t, Quaternion &qt );
void QuaternionBlendNoAlign( const Quaternion &p, const Quaternion &q, float t, Quaternion &qt );
void QuaternionIdentityBlend( const Quaternion &p, float t, Quaternion &qt );
float QuaternionAngleDiff( const Quaternion &p, const Quaternion &q );
void QuaternionScale( const Quaternion &p, float t, Quaternion &q );
void QuaternionAlign( const Quaternion &p, const Quaternion &q, Quaternion &qt );
float QuaternionDotProduct( const Quaternion &p, const Quaternion &q );
void QuaternionConjugate( const Quaternion &p, Quaternion &q );
void QuaternionInvert( const Quaternion &p, Quaternion &q );
float QuaternionNormalize( Quaternion &q );
void QuaternionAdd( const Quaternion &p, const Quaternion &q, Quaternion &qt );
void QuaternionMult( const Quaternion &p, const Quaternion &q, Quaternion &qt );
void QuaternionMatrix( const Quaternion &q, matrix3x4_t &matrix );
void QuaternionMatrix( const Quaternion &q, const Vector &pos, matrix3x4_t &matrix );
void QuaternionAngles( const Quaternion &q, QAngle &angles );
void AngleQuaternion( const QAngle& angles, Quaternion &qt );
void QuaternionAngles( const Quaternion &q, RadianEuler &angles );
void AngleQuaternion( RadianEuler const &angles, Quaternion &qt );
void QuaternionAxisAngle( const Quaternion &q, Vector &axis, float &angle );
void AxisAngleQuaternion( const Vector &axis, float angle, Quaternion &q );
void BasisToQuaternion( const Vector &vecForward, const Vector &vecRight, const Vector &vecUp, Quaternion &q );
void MatrixQuaternion( const matrix3x4_t &mat, Quaternion &q );

// A couple methods to find the dot product of a vector with a matrix row or column...
inline float MatrixRowDotProduct( const matrix3x4_t &in1, int row, const Vector& in2 )
{
	Assert( (row >= 0) && (row < 3) );
	return DotProduct( in1[row], in2.Base() );
}

inline float MatrixColumnDotProduct( const matrix3x4_t &in1, int col, const Vector& in2 )
{
	Assert( (col >= 0) && (col < 4) );
	return in1[0][col] * in2[0] + in1[1][col] * in2[1] + in1[2][col] * in2[2];
}

int __cdecl BoxOnPlaneSide (const float *emins, const float *emaxs, const cplane_t *plane);

inline float anglemod(float a)
{
	a = (360.f/65536) * ((int)(a*(65536.f/360.0f)) & 65535);
	return a;
}

// Remap a value in the range [A,B] to [C,D].
inline float RemapVal( float val, float A, float B, float C, float D)
{
	if ( A == B )
		return val >= B ? D : C;
	return C + (D - C) * (val - A) / (B - A);
}

inline float RemapValClamped( float val, float A, float B, float C, float D)
{
	if ( A == B )
		return val >= B ? D : C;
	float cVal = (val - A) / (B - A);
	cVal = clamp( cVal, 0.0f, 1.0f );

	return C + (D - C) * cVal;
}

// Returns A + (B-A)*flPercent.
// float Lerp( float flPercent, float A, float B );
template <class T>
FORCEINLINE T Lerp( float flPercent, T const &A, T const &B )
{
	return A + (B - A) * flPercent;
}

FORCEINLINE float Sqr( float f )
{
	return f*f;
}

// 5-argument floating point linear interpolation.
// FLerp(f1,f2,i1,i2,x)=
//    f1 at x=i1
//    f2 at x=i2
//   smooth lerp between f1 and f2 at x>i1 and x<i2
//   extrapolation for x<i1 or x>i2
//
//   If you know a function f(x)'s value (f1) at position i1, and its value (f2) at position i2,
//   the function can be linearly interpolated with FLerp(f1,f2,i1,i2,x)
//    i2=i1 will cause a divide by zero.
static inline float FLerp(float f1, float f2, float i1, float i2, float x)
{
  return f1+(f2-f1)*(x-i1)/(i2-i1);
}

#ifndef VECTOR_NO_SLOW_OPERATIONS

// YWB:  Specialization for interpolating euler angles via quaternions...
template<> FORCEINLINE QAngle Lerp<QAngle>( float flPercent, const QAngle& q1, const QAngle& q2 )
{
	// Avoid precision errors
	if ( q1 == q2 )
		return q1;

	Quaternion src, dest;

	// Convert to quaternions
	AngleQuaternion( q1, src );
	AngleQuaternion( q2, dest );

	Quaternion result;

	// Slerp
	QuaternionSlerp( src, dest, flPercent, result );

	// Convert to euler
	QAngle output;
	QuaternionAngles( result, output );
	return output;
}

#else

#pragma error

// NOTE NOTE: I haven't tested this!! It may not work! Check out interpolatedvar.cpp in the client dll to try it
template<> FORCEINLINE QAngleByValue Lerp<QAngleByValue>( float flPercent, const QAngleByValue& q1, const QAngleByValue& q2 )
{
	// Avoid precision errors
	if ( q1 == q2 )
		return q1;

	Quaternion src, dest;

	// Convert to quaternions
	AngleQuaternion( q1, src );
	AngleQuaternion( q2, dest );

	Quaternion result;

	// Slerp
	QuaternionSlerp( src, dest, flPercent, result );

	// Convert to euler
	QAngleByValue output;
	QuaternionAngles( result, output );
	return output;
}

#endif // VECTOR_NO_SLOW_OPERATIONS

/// Same as swap(), but won't cause problems with std::swap
template <class T>
FORCEINLINE void V_swap( T& x, T& y )
{
	T temp = x;
	x = y;
	y = temp;
}

template <class T> FORCEINLINE T AVG(T a, T b)
{
	return (a+b)/2;
}

// number of elements in an array of static size
#define NELEMS(x) ARRAYSIZE(x)

// XYZ macro, for printf type functions - ex printf("%f %f %f",XYZ(myvector));
#define XYZ(v) (v).x,(v).y,(v).z

inline float Sign( float x )
{
	return (x <0.0f) ? -1.0f : 1.0f;
}

//
// Clamps the input integer to the given array bounds.
// Equivalent to the following, but without using any branches:
//
// if( n < 0 ) return 0;
// else if ( n > maxindex ) return maxindex;
// else return n;
//
// This is not always a clear performance win, but when you have situations where a clamped
// value is thrashing against a boundary this is a big win. (ie, valid, invalid, valid, invalid, ...)
//
// Note: This code has been run against all possible integers.
//
inline int ClampArrayBounds( int n, unsigned maxindex )
{
	// mask is 0 if less than 4096, 0xFFFFFFFF if greater than
	unsigned int inrangemask = 0xFFFFFFFF + (((unsigned) n) > maxindex );
	unsigned int lessthan0mask = 0xFFFFFFFF + ( n >= 0 );

	// If the result was valid, set the result, (otherwise sets zero)
	int result = (inrangemask & n);

	// if the result was out of range or zero.
	result |= ((~inrangemask) & (~lessthan0mask)) & maxindex;

	return result;
}

#define BOX_ON_PLANE_SIDE(emins, emaxs, p)	\
	(((p)->type < 3)?						\
	(										\
		((p)->dist <= (emins)[(p)->type])?	\
			1								\
		:									\
		(									\
			((p)->dist >= (emaxs)[(p)->type])?\
				2							\
			:								\
				3							\
		)									\
	)										\
	:										\
		BoxOnPlaneSide( (emins), (emaxs), (p)))

//-----------------------------------------------------------------------------
// FIXME: Vector versions.... the float versions will go away hopefully soon!
//-----------------------------------------------------------------------------

void AngleVectors (const QAngle& angles, Vector *forward);
void AngleVectors (const QAngle& angles, Vector *forward, Vector *right, Vector *up);
void AngleVectorsTranspose (const QAngle& angles, Vector *forward, Vector *right, Vector *up);
void AngleMatrix (const QAngle &angles, matrix3x4_t &mat );
void AngleMatrix( const QAngle &angles, const Vector &position, matrix3x4_t &mat );
void AngleMatrix (const RadianEuler &angles, matrix3x4_t &mat );
void AngleMatrix( RadianEuler const &angles, const Vector &position, matrix3x4_t &mat );
void AngleIMatrix (const QAngle &angles, matrix3x4_t &mat );
void AngleIMatrix (const QAngle &angles, const Vector &position, matrix3x4_t &mat );
void AngleIMatrix (const RadianEuler &angles, matrix3x4_t &mat );
void VectorAngles( const Vector &forward, QAngle &angles );
void VectorAngles( const Vector &forward, const Vector &pseudoup, QAngle &angles );
void VectorMatrix( const Vector &forward, matrix3x4_t &mat );
void VectorVectors( const Vector &forward, Vector &right, Vector &up );
void SetIdentityMatrix( matrix3x4_t &mat );
void SetScaleMatrix( float x, float y, float z, matrix3x4_t &dst );
void MatrixBuildRotationAboutAxis( const Vector &vAxisOfRot, float angleDegrees, matrix3x4_t &dst );

inline void SetScaleMatrix( float flScale, matrix3x4_t &dst )
{
	SetScaleMatrix( flScale, flScale, flScale, dst );
}

inline void SetScaleMatrix( const Vector& scale, matrix3x4_t &dst )
{
	SetScaleMatrix( scale.x, scale.y, scale.z, dst );
}

// Computes the inverse transpose
void MatrixTranspose( matrix3x4_t& mat );
void MatrixTranspose( const matrix3x4_t& src, matrix3x4_t& dst );
void MatrixInverseTranspose( const matrix3x4_t& src, matrix3x4_t& dst );

inline void PositionMatrix( const Vector &position, matrix3x4_t &mat )
{
	MatrixSetColumn( position, 3, mat );
}

inline void MatrixPosition( const matrix3x4_t &matrix, Vector &position )
{
	MatrixGetColumn( matrix, 3, position );
}

inline void VectorRotate( const Vector& in1, const matrix3x4_t &in2, Vector &out)
{
	VectorRotate( &in1.x, in2, &out.x );
}

inline void VectorIRotate( const Vector& in1, const matrix3x4_t &in2, Vector &out)
{
	VectorIRotate( &in1.x, in2, &out.x );
}

inline void MatrixAngles( const matrix3x4_t &matrix, QAngle &angles )
{
	MatrixAngles( matrix, &angles.x );
}

inline void MatrixAngles( const matrix3x4_t &matrix, QAngle &angles, Vector &position )
{
	MatrixAngles( matrix, angles );
	MatrixPosition( matrix, position );
}

inline void MatrixAngles( const matrix3x4_t &matrix, RadianEuler &angles )
{
	MatrixAngles( matrix, &angles.x );

	angles.Init( DEG2RAD( angles.z ), DEG2RAD( angles.x ), DEG2RAD( angles.y ) );
}

void MatrixAngles( const matrix3x4_t &mat, RadianEuler &angles, Vector &position );

void MatrixAngles( const matrix3x4_t &mat, Quaternion &q, Vector &position );

inline int VectorCompare (const Vector& v1, const Vector& v2)
{
	return v1 == v2;
}

inline void VectorTransform (const Vector& in1, const matrix3x4_t &in2, Vector &out)
{
	VectorTransform( &in1.x, in2, &out.x );
}

inline void VectorITransform (const Vector& in1, const matrix3x4_t &in2, Vector &out)
{
	VectorITransform( &in1.x, in2, &out.x );
}

/*
inline void DecomposeRotation( const matrix3x4_t &mat, Vector &out )
{
	DecomposeRotation( mat, &out.x );
}
*/

inline int BoxOnPlaneSide (const Vector& emins, const Vector& emaxs, const cplane_t *plane )
{
	return BoxOnPlaneSide( &emins.x, &emaxs.x, plane );
}

inline void VectorFill(Vector& a, float b)
{
	a[0]=a[1]=a[2]=b;
}

inline void VectorNegate(Vector& a)
{
	a[0] = -a[0];
	a[1] = -a[1];
	a[2] = -a[2];
}

inline vec_t VectorAvg(Vector& a)
{
	return ( a[0] + a[1] + a[2] ) / 3;
}

//-----------------------------------------------------------------------------
// Box/plane test (slow version)
//-----------------------------------------------------------------------------
inline int FASTCALL BoxOnPlaneSide2 (const Vector& emins, const Vector& emaxs, const cplane_t *p, float tolerance = 0.f )
{
	Vector	corners[2];

	if (p->normal[0] < 0)
	{
		corners[0][0] = emins[0];
		corners[1][0] = emaxs[0];
	}
	else
	{
		corners[1][0] = emins[0];
		corners[0][0] = emaxs[0];
	}

	if (p->normal[1] < 0)
	{
		corners[0][1] = emins[1];
		corners[1][1] = emaxs[1];
	}
	else
	{
		corners[1][1] = emins[1];
		corners[0][1] = emaxs[1];
	}

	if (p->normal[2] < 0)
	{
		corners[0][2] = emins[2];
		corners[1][2] = emaxs[2];
	}
	else
	{
		corners[1][2] = emins[2];
		corners[0][2] = emaxs[2];
	}

	int sides = 0;

	float dist1 = DotProduct (p->normal, corners[0]) - p->dist;
	if (dist1 >= tolerance)
		sides = 1;

	float dist2 = DotProduct (p->normal, corners[1]) - p->dist;
	if (dist2 < -tolerance)
		sides |= 2;

	return sides;
}

//-----------------------------------------------------------------------------
// Helpers for bounding box construction
//-----------------------------------------------------------------------------

void ClearBounds (Vector& mins, Vector& maxs);
void AddPointToBounds (const Vector& v, Vector& mins, Vector& maxs);

//
// COLORSPACE/GAMMA CONVERSION STUFF
//
void BuildGammaTable( float gamma, float texGamma, float brightness, int overbright );

// convert texture to linear 0..1 value
inline float TexLightToLinear( int c, int exponent )
{
	extern float power2_n[256];
	Assert( exponent >= -128 && exponent <= 127 );
	return ( float )c * power2_n[exponent+128];
}

// convert texture to linear 0..1 value
int LinearToTexture( float f );
// converts 0..1 linear value to screen gamma (0..255)
int LinearToScreenGamma( float f );
float TextureToLinear( int c );

// compressed color format
struct ColorRGBExp32
{
	byte r, g, b;
	signed char exponent;
};

void ColorRGBExp32ToVector( const ColorRGBExp32& in, Vector& out );
void VectorToColorRGBExp32( const Vector& v, ColorRGBExp32 &c );

// solve for "x" where "a x^2 + b x + c = 0", return true if solution exists
bool SolveQuadratic( float a, float b, float c, float &root1, float &root2 );

// solves for "a, b, c" where "a x^2 + b x + c = y", return true if solution exists
bool SolveInverseQuadratic( float x1, float y1, float x2, float y2, float x3, float y3, float &a, float &b, float &c );

// solves for a,b,c specified as above, except that it always creates a monotonically increasing or
// decreasing curve if the data is monotonically increasing or decreasing. In order to enforce the
// monoticity condition, it is possible that the resulting quadratic will only approximate the data
// instead of interpolating it. This code is not especially fast.
bool SolveInverseQuadraticMonotonic( float x1, float y1, float x2, float y2,
									 float x3, float y3, float &a, float &b, float &c );

// solves for "a, b, c" where "1/(a x^2 + b x + c ) = y", return true if solution exists
bool SolveInverseReciprocalQuadratic( float x1, float y1, float x2, float y2, float x3, float y3, float &a, float &b, float &c );

// rotate a vector around the Z axis (YAW)
void VectorYawRotate( const Vector& in, float flYaw, Vector &out);

// Bias takes an X value between 0 and 1 and returns another value between 0 and 1
// The curve is biased towards 0 or 1 based on biasAmt, which is between 0 and 1.
// Lower values of biasAmt bias the curve towards 0 and higher values bias it towards 1.
//
// For example, with biasAmt = 0.2, the curve looks like this:
//
// 1
// |				  *
// |				  *
// |			     *
// |			   **
// |			 **
// |	  	 ****
// |*********
// |___________________
// 0                   1
//
//
// With biasAmt = 0.8, the curve looks like this:
//
// 1
// | 	**************
// |  **
// | *
// | *
// |*
// |*
// |*
// |___________________
// 0                   1
//
// With a biasAmt of 0.5, Bias returns X.
float Bias( float x, float biasAmt );

// Gain is similar to Bias, but biasAmt biases towards or away from 0.5.
// Lower bias values bias towards 0.5 and higher bias values bias away from it.
//
// For example, with biasAmt = 0.2, the curve looks like this:
//
// 1
// | 				  *
// | 				 *
// | 				**
// |  ***************
// | **
// | *
// |*
// |___________________
// 0                   1
//
//
// With biasAmt = 0.8, the curve looks like this:
//
// 1
// |  		    *****
// |  		 ***
// |  		*
// | 		*
// | 		*
// |   	 ***
// |*****
// |___________________
// 0                   1
float Gain( float x, float biasAmt );

// SmoothCurve maps a 0-1 value into another 0-1 value based on a cosine wave
// where the derivatives of the function at 0 and 1 (and 0.5) are 0. This is useful for
// any fadein/fadeout effect where it should start and end smoothly.
//
// The curve looks like this:
//
// 1
// |  		**
// | 	   *  *
// | 	  *	   *
// | 	  *	   *
// | 	 *		*
// |   **		 **
// |***			   ***
// |___________________
// 0                   1
//
float SmoothCurve( float x );

// This works like SmoothCurve, with two changes:
//
// 1. Instead of the curve peaking at 0.5, it will peak at flPeakPos.
//    (So if you specify flPeakPos=0.2, then the peak will slide to the left).
//
// 2. flPeakSharpness is a 0-1 value controlling the sharpness of the peak.
//    Low values blunt the peak and high values sharpen the peak.
float SmoothCurve_Tweak( float x, float flPeakPos=0.5, float flPeakSharpness=0.5 );

//float ExponentialDecay( float halflife, float dt );
//float ExponentialDecay( float decayTo, float decayTime, float dt );

// halflife is time for value to reach 50%
inline float ExponentialDecay( float halflife, float dt )
{
	// log(0.5) == -0.69314718055994530941723212145818
	return expf( -0.69314718f / halflife * dt);
}

// decayTo is factor the value should decay to in decayTime
inline float ExponentialDecay( float decayTo, float decayTime, float dt )
{
	return expf( logf( decayTo ) / decayTime * dt);
}

// Get the integrated distanced traveled
// decayTo is factor the value should decay to in decayTime
// dt is the time relative to the last velocity update
inline float ExponentialDecayIntegral( float decayTo, float decayTime, float dt  )
{
	return (powf( decayTo, dt / decayTime) * decayTime - decayTime) / logf( decayTo );
}

// hermite basis function for smooth interpolation
// Similar to Gain() above, but very cheap to call
// value should be between 0 & 1 inclusive
inline float SimpleSpline( float value )
{
	float valueSquared = value * value;

	// Nice little ease-in, ease-out spline-like curve
	return (3 * valueSquared - 2 * valueSquared * value);
}

// remaps a value in [startInterval, startInterval+rangeInterval] from linear to
// spline using SimpleSpline
inline float SimpleSplineRemapVal( float val, float A, float B, float C, float D)
{
	if ( A == B )
		return val >= B ? D : C;
	float cVal = (val - A) / (B - A);
	return C + (D - C) * SimpleSpline( cVal );
}

// remaps a value in [startInterval, startInterval+rangeInterval] from linear to
// spline using SimpleSpline
inline float SimpleSplineRemapValClamped( float val, float A, float B, float C, float D )
{
	if ( A == B )
		return val >= B ? D : C;
	float cVal = (val - A) / (B - A);
	cVal = clamp( cVal, 0.0f, 1.0f );
	return C + (D - C) * SimpleSpline( cVal );
}

FORCEINLINE int RoundFloatToInt(float f)
{
#if defined(__i386__) || defined(_M_IX86) || defined( PLATFORM_WINDOWS_PC64 )
	return _mm_cvtss_si32(_mm_load_ss(&f));
#elif defined( _X360 )
#ifdef Assert
	Assert( IsFPUControlWordSet() );
#endif
	union
	{
		double flResult;
		int pResult[2];
	};
	flResult = __fctiw( f );
	return pResult[1];
#else
#error Unknown architecture
#endif
}

FORCEINLINE unsigned char RoundFloatToByte(float f)
{
	int nResult = RoundFloatToInt(f);
#ifdef Assert
	Assert( (nResult & ~0xFF) == 0 );
#endif
	return (unsigned char) nResult;
}

FORCEINLINE unsigned long RoundFloatToUnsignedLong(float f)
{
#if defined( _X360 )
#ifdef Assert
	Assert( IsFPUControlWordSet() );
#endif
	union
	{
		double flResult;
		int pIntResult[2];
		unsigned long pResult[2];
	};
	flResult = __fctiw( f );
	Assert( pIntResult[1] >= 0 );
	return pResult[1];
#else  // !X360

#if defined( PLATFORM_WINDOWS_PC64 )
	uint nRet = ( uint ) f;
	if ( nRet & 1 )
	{
		if ( ( f - floor( f ) >= 0.5 ) )
		{
			nRet++;
		}
	}
	else
	{
		if ( ( f - floor( f ) > 0.5 ) )
		{
			nRet++;
		}
	}
	return nRet;
#else // PLATFORM_WINDOWS_PC64
	unsigned char nResult[8];

	#if defined( _WIN32 )
		__asm
		{
			fld f
			fistp       qword ptr nResult
		}
	#elif POSIX
		__asm __volatile__ (
			"fistpl %0;": "=m" (nResult): "t" (f) : "st"
		);
	#endif

		return *((unsigned long*)nResult);
#endif // PLATFORM_WINDOWS_PC64
#endif // !X360
}

FORCEINLINE bool IsIntegralValue( float flValue, float flTolerance = 0.001f )
{
	return fabs( RoundFloatToInt( flValue ) - flValue ) < flTolerance;
}

// Fast, accurate ftol:
FORCEINLINE int Float2Int( float a )
{
#if defined( _X360 )
	union
	{
		double flResult;
		int pResult[2];
	};
	flResult = __fctiwz( a );
	return pResult[1];
#else  // !X360
	// Rely on compiler to generate CVTTSS2SI on x86
	return (int) a;
#endif
}

// Over 15x faster than: (int)floor(value)
inline int Floor2Int( float a )
{
	int RetVal;
#if defined( __i386__ )
	// Convert to int and back, compare, subtract one if too big
	__m128 a128 = _mm_set_ss(a);
	RetVal = _mm_cvtss_si32(a128);
    __m128 rounded128 = _mm_cvt_si2ss(_mm_setzero_ps(), RetVal);
	RetVal -= _mm_comigt_ss( rounded128, a128 );
#else
	RetVal = static_cast<int>( floor(a) );
#endif
	return RetVal;
}

//-----------------------------------------------------------------------------
// Fast color conversion from float to unsigned char
//-----------------------------------------------------------------------------
FORCEINLINE unsigned int FastFToC( float c )
{
#if defined( __i386__ )
	// IEEE float bit manipulation works for values between [0, 1<<23)
	union { float f; int i; } convert = { c*255.0f + (float)(1<<23) };
	return convert.i & 255;
#else
	// consoles CPUs suffer from load-hit-store penalty
	return Float2Int( c * 255.0f );
#endif
}

//-----------------------------------------------------------------------------
// Fast conversion from float to integer with magnitude less than 2**22
//-----------------------------------------------------------------------------
FORCEINLINE int FastFloatToSmallInt( float c )
{
#if defined( __i386__ )
	// IEEE float bit manipulation works for values between [-1<<22, 1<<22)
	union { float f; int i; } convert = { c + (float)(3<<22) };
	return (convert.i & ((1<<23)-1)) - (1<<22);
#else
	// consoles CPUs suffer from load-hit-store penalty
	return Float2Int( c );
#endif
}

//-----------------------------------------------------------------------------
// Purpose: Bound input float to .001 (millisecond) boundary
// Input  : in -
// Output : inline float
//-----------------------------------------------------------------------------
inline float ClampToMsec( float in )
{
	int msec = Floor2Int( in * 1000.0f + 0.5f );
	return 0.001f * msec;
}

// Over 15x faster than: (int)ceil(value)
inline int Ceil2Int( float a )
{
   int RetVal;
#if defined( __i386__ )
   // Convert to int and back, compare, add one if too small
   __m128 a128 = _mm_load_ss(&a);
   RetVal = _mm_cvtss_si32(a128);
   __m128 rounded128 = _mm_cvt_si2ss(_mm_setzero_ps(), RetVal);
   RetVal += _mm_comilt_ss( rounded128, a128 );
#else
   RetVal = static_cast<int>( ceil(a) );
#endif
	return RetVal;
}

// Regular signed area of triangle
#define TriArea2D( A, B, C ) \
	( 0.5f * ( ( B.x - A.x ) * ( C.y - A.y ) - ( B.y - A.y ) * ( C.x - A.x ) ) )

// This version doesn't premultiply by 0.5f, so it's the area of the rectangle instead
#define TriArea2DTimesTwo( A, B, C ) \
	( ( ( B.x - A.x ) * ( C.y - A.y ) - ( B.y - A.y ) * ( C.x - A.x ) ) )

// Get the barycentric coordinates of "pt" in triangle [A,B,C].
inline void GetBarycentricCoords2D(
	Vector2D const &A,
	Vector2D const &B,
	Vector2D const &C,
	Vector2D const &pt,
	float bcCoords[3] )
{
	// Note, because to top and bottom are both x2, the issue washes out in the composite
	float invTriArea = 1.0f / TriArea2DTimesTwo( A, B, C );

	// NOTE: We assume here that the lightmap coordinate vertices go counterclockwise.
	// If not, TriArea2D() is negated so this works out right.
	bcCoords[0] = TriArea2DTimesTwo( B, C, pt ) * invTriArea;
	bcCoords[1] = TriArea2DTimesTwo( C, A, pt ) * invTriArea;
	bcCoords[2] = TriArea2DTimesTwo( A, B, pt ) * invTriArea;
}

// Return true of the sphere might touch the box (the sphere is actually treated
// like a box itself, so this may return true if the sphere's bounding box touches
// a corner of the box but the sphere itself doesn't).
inline bool QuickBoxSphereTest(
	const Vector& vOrigin,
	float flRadius,
	const Vector& bbMin,
	const Vector& bbMax )
{
	return vOrigin.x - flRadius < bbMax.x && vOrigin.x + flRadius > bbMin.x &&
		vOrigin.y - flRadius < bbMax.y && vOrigin.y + flRadius > bbMin.y &&
		vOrigin.z - flRadius < bbMax.z && vOrigin.z + flRadius > bbMin.z;
}

// Return true of the boxes intersect (but not if they just touch).
inline bool QuickBoxIntersectTest(
	const Vector& vBox1Min,
	const Vector& vBox1Max,
	const Vector& vBox2Min,
	const Vector& vBox2Max )
{
	return
		vBox1Min.x < vBox2Max.x && vBox1Max.x > vBox2Min.x &&
		vBox1Min.y < vBox2Max.y && vBox1Max.y > vBox2Min.y &&
		vBox1Min.z < vBox2Max.z && vBox1Max.z > vBox2Min.z;
}

extern float GammaToLinearFullRange( float gamma );
extern float LinearToGammaFullRange( float linear );
extern float GammaToLinear( float gamma );
extern float LinearToGamma( float linear );

extern float SrgbGammaToLinear( float flSrgbGammaValue );
extern float SrgbLinearToGamma( float flLinearValue );
extern float X360GammaToLinear( float fl360GammaValue );
extern float X360LinearToGamma( float flLinearValue );
extern float SrgbGammaTo360Gamma( float flSrgbGammaValue );

// linear (0..4) to screen corrected vertex space (0..1?)
FORCEINLINE float LinearToVertexLight( float f )
{
	extern float lineartovertex[4096];

	// Gotta clamp before the multiply; could overflow...
	// assume 0..4 range
	int i = RoundFloatToInt( f * 1024.f );

	// Presumably the comman case will be not to clamp, so check that first:
	if( (unsigned)i > 4095 )
	{
		if ( i < 0 )
			i = 0;		// Compare to zero instead of 4095 to save 4 bytes in the instruction stream
		else
			i = 4095;
	}

	return lineartovertex[i];
}

FORCEINLINE unsigned char LinearToLightmap( float f )
{
	extern unsigned char lineartolightmap[4096];

	// Gotta clamp before the multiply; could overflow...
	int i = RoundFloatToInt( f * 1024.f );	// assume 0..4 range

	// Presumably the comman case will be not to clamp, so check that first:
	if ( (unsigned)i > 4095 )
	{
		if ( i < 0 )
			i = 0;		// Compare to zero instead of 4095 to save 4 bytes in the instruction stream
		else
			i = 4095;
	}

	return lineartolightmap[i];
}

FORCEINLINE void ColorClamp( Vector& color )
{
	float maxc = max( color.x, max( color.y, color.z ) );
	if ( maxc > 1.0f )
	{
		float ooMax = 1.0f / maxc;
		color.x *= ooMax;
		color.y *= ooMax;
		color.z *= ooMax;
	}

	if ( color[0] < 0.f ) color[0] = 0.f;
	if ( color[1] < 0.f ) color[1] = 0.f;
	if ( color[2] < 0.f ) color[2] = 0.f;
}

inline void ColorClampTruncate( Vector& color )
{
	if (color[0] > 1.0f) color[0] = 1.0f; else if (color[0] < 0.0f) color[0] = 0.0f;
	if (color[1] > 1.0f) color[1] = 1.0f; else if (color[1] < 0.0f) color[1] = 0.0f;
	if (color[2] > 1.0f) color[2] = 1.0f; else if (color[2] < 0.0f) color[2] = 0.0f;
}

// Interpolate a Catmull-Rom spline.
// t is a [0,1] value and interpolates a curve between p2 and p3.
void Catmull_Rom_Spline(
	const Vector &p1,
	const Vector &p2,
	const Vector &p3,
	const Vector &p4,
	float t,
	Vector &output );

// Interpolate a Catmull-Rom spline.
// Returns the tangent of the point at t of the spline
void Catmull_Rom_Spline_Tangent(
	const Vector &p1,
	const Vector &p2,
	const Vector &p3,
	const Vector &p4,
	float t,
	Vector &output );

// area under the curve [0..t]
void Catmull_Rom_Spline_Integral(
	const Vector &p1,
	const Vector &p2,
	const Vector &p3,
	const Vector &p4,
	float t,
	Vector& output );

// area under the curve [0..1]
void Catmull_Rom_Spline_Integral(
	const Vector &p1,
	const Vector &p2,
	const Vector &p3,
	const Vector &p4,
	Vector& output );

// Interpolate a Catmull-Rom spline.
// Normalize p2->p1 and p3->p4 to be the same length as p2->p3
void Catmull_Rom_Spline_Normalize(
	const Vector &p1,
	const Vector &p2,
	const Vector &p3,
	const Vector &p4,
	float t,
	Vector &output );

// area under the curve [0..t]
// Normalize p2->p1 and p3->p4 to be the same length as p2->p3
void Catmull_Rom_Spline_Integral_Normalize(
	const Vector &p1,
	const Vector &p2,
	const Vector &p3,
	const Vector &p4,
	float t,
	Vector& output );

// Interpolate a Catmull-Rom spline.
// Normalize p2.x->p1.x and p3.x->p4.x to be the same length as p2.x->p3.x
void Catmull_Rom_Spline_NormalizeX(
	const Vector &p1,
	const Vector &p2,
	const Vector &p3,
	const Vector &p4,
	float t,
	Vector &output );

// area under the curve [0..t]
void Catmull_Rom_Spline_NormalizeX(
	const Vector &p1,
	const Vector &p2,
	const Vector &p3,
	const Vector &p4,
	float t,
	Vector& output );

// Interpolate a Hermite spline.
// t is a [0,1] value and interpolates a curve between p1 and p2 with the deltas d1 and d2.
void Hermite_Spline(
	const Vector &p1,
	const Vector &p2,
	const Vector &d1,
	const Vector &d2,
	float t,
	Vector& output );

float Hermite_Spline(
	float p1,
	float p2,
	float d1,
	float d2,
	float t );

// t is a [0,1] value and interpolates a curve between p1 and p2 with the slopes p0->p1 and p1->p2
void Hermite_Spline(
	const Vector &p0,
	const Vector &p1,
	const Vector &p2,
	float t,
	Vector& output );

float Hermite_Spline(
	float p0,
	float p1,
	float p2,
	float t );

void Hermite_SplineBasis( float t, float basis[] );

void Hermite_Spline(
	const Quaternion &q0,
	const Quaternion &q1,
	const Quaternion &q2,
	float t,
	Quaternion &output );

// See http://en.wikipedia.org/wiki/Kochanek-Bartels_curves
//
// Tension:  -1 = Round -> 1 = Tight
// Bias:     -1 = Pre-shoot (bias left) -> 1 = Post-shoot (bias right)
// Continuity: -1 = Box corners -> 1 = Inverted corners
//
// If T=B=C=0 it's the same matrix as Catmull-Rom.
// If T=1 & B=C=0 it's the same as Cubic.
// If T=B=0 & C=-1 it's just linear interpolation
//
// See http://news.povray.org/povray.binaries.tutorials/attachment/%3CXns91B880592482seed7@povray.org%3E/Splines.bas.txt
// for example code and descriptions of various spline types...
//
void Kochanek_Bartels_Spline(
	float tension,
	float bias,
	float continuity,
	const Vector &p1,
	const Vector &p2,
	const Vector &p3,
	const Vector &p4,
	float t,
	Vector& output );

void Kochanek_Bartels_Spline_NormalizeX(
	float tension,
	float bias,
	float continuity,
	const Vector &p1,
	const Vector &p2,
	const Vector &p3,
	const Vector &p4,
	float t,
	Vector& output );

// See link at Kochanek_Bartels_Spline for info on the basis matrix used
void Cubic_Spline(
	const Vector &p1,
	const Vector &p2,
	const Vector &p3,
	const Vector &p4,
	float t,
	Vector& output );

void Cubic_Spline_NormalizeX(
	const Vector &p1,
	const Vector &p2,
	const Vector &p3,
	const Vector &p4,
	float t,
	Vector& output );

// See link at Kochanek_Bartels_Spline for info on the basis matrix used
void BSpline(
	const Vector &p1,
	const Vector &p2,
	const Vector &p3,
	const Vector &p4,
	float t,
	Vector& output );

void BSpline_NormalizeX(
	const Vector &p1,
	const Vector &p2,
	const Vector &p3,
	const Vector &p4,
	float t,
	Vector& output );

// See link at Kochanek_Bartels_Spline for info on the basis matrix used
void Parabolic_Spline(
	const Vector &p1,
	const Vector &p2,
	const Vector &p3,
	const Vector &p4,
	float t,
	Vector& output );

void Parabolic_Spline_NormalizeX(
	const Vector &p1,
	const Vector &p2,
	const Vector &p3,
	const Vector &p4,
	float t,
	Vector& output );

// quintic interpolating polynomial from Perlin.
// 0->0, 1->1, smooth-in between with smooth tangents
FORCEINLINE float QuinticInterpolatingPolynomial(float t)
{
	// 6t^5-15t^4+10t^3
	return t * t * t *( t * ( t* 6.0 - 15.0 ) + 10.0 );
}

// given a table of sorted tabulated positions, return the two indices and blendfactor to linear
// interpolate. Does a search. Can be used to find the blend value to interpolate between
// keyframes.
void GetInterpolationData( float const *pKnotPositions,
						   float const *pKnotValues,
						   int nNumValuesinList,
						   int nInterpolationRange,
						   float flPositionToInterpolateAt,
						   bool bWrap,
						   float *pValueA,
						   float *pValueB,
						   float *pInterpolationValue);

float RangeCompressor( float flValue, float flMin, float flMax, float flBase );

// Get the minimum distance from vOrigin to the bounding box defined by [mins,maxs]
// using voronoi regions.
// 0 is returned if the origin is inside the box.
float CalcSqrDistanceToAABB( const Vector &mins, const Vector &maxs, const Vector &point );
void CalcClosestPointOnAABB( const Vector &mins, const Vector &maxs, const Vector &point, Vector &closestOut );
void CalcSqrDistAndClosestPointOnAABB( const Vector &mins, const Vector &maxs, const Vector &point, Vector &closestOut, float &distSqrOut );

inline float CalcDistanceToAABB( const Vector &mins, const Vector &maxs, const Vector &point )
{
	float flDistSqr = CalcSqrDistanceToAABB( mins, maxs, point );
	return sqrt(flDistSqr);
}

// Get the closest point from P to the (infinite) line through vLineA and vLineB and
// calculate the shortest distance from P to the line.
// If you pass in a value for t, it will tell you the t for (A + (B-A)t) to get the closest point.
// If the closest point lies on the segment between A and B, then 0 <= t <= 1.
void  CalcClosestPointOnLine( const Vector &P, const Vector &vLineA, const Vector &vLineB, Vector &vClosest, float *t=0 );
float CalcDistanceToLine( const Vector &P, const Vector &vLineA, const Vector &vLineB, float *t=0 );
float CalcDistanceSqrToLine( const Vector &P, const Vector &vLineA, const Vector &vLineB, float *t=0 );

// The same three functions as above, except now the line is closed between A and B.
void  CalcClosestPointOnLineSegment( const Vector &P, const Vector &vLineA, const Vector &vLineB, Vector &vClosest, float *t=0 );
float CalcDistanceToLineSegment( const Vector &P, const Vector &vLineA, const Vector &vLineB, float *t=0 );
float CalcDistanceSqrToLineSegment( const Vector &P, const Vector &vLineA, const Vector &vLineB, float *t=0 );

// A function to compute the closes line segment connnection two lines (or false if the lines are parallel, etc.)
bool CalcLineToLineIntersectionSegment(
   const Vector& p1,const Vector& p2,const Vector& p3,const Vector& p4,Vector *s1,Vector *s2,
   float *t1, float *t2 );

// The above functions in 2D
void  CalcClosestPointOnLine2D( Vector2D const &P, Vector2D const &vLineA, Vector2D const &vLineB, Vector2D &vClosest, float *t=0 );
float CalcDistanceToLine2D( Vector2D const &P, Vector2D const &vLineA, Vector2D const &vLineB, float *t=0 );
float CalcDistanceSqrToLine2D( Vector2D const &P, Vector2D const &vLineA, Vector2D const &vLineB, float *t=0 );
void  CalcClosestPointOnLineSegment2D( Vector2D const &P, Vector2D const &vLineA, Vector2D const &vLineB, Vector2D &vClosest, float *t=0 );
float CalcDistanceToLineSegment2D( Vector2D const &P, Vector2D const &vLineA, Vector2D const &vLineB, float *t=0 );
float CalcDistanceSqrToLineSegment2D( Vector2D const &P, Vector2D const &vLineA, Vector2D const &vLineB, float *t=0 );

// Init the mathlib
void MathLib_Init( float gamma = 2.2f, float texGamma = 2.2f, float brightness = 0.0f, int overbright = 2.0f, bool bAllow3DNow = true, bool bAllowSSE = true, bool bAllowSSE2 = true, bool bAllowMMX = true );
bool MathLib_3DNowEnabled( void );
bool MathLib_MMXEnabled( void );
bool MathLib_SSEEnabled( void );
bool MathLib_SSE2Enabled( void );

float Approach( float target, float value, float speed );
float ApproachAngle( float target, float value, float speed );
float AngleDiff( float destAngle, float srcAngle );
float AngleDistance( float next, float cur );
float AngleNormalize( float angle );

// ensure that 0 <= angle <= 360
float AngleNormalizePositive( float angle );

bool AnglesAreEqual( float a, float b, float tolerance = 0.0f );

void RotationDeltaAxisAngle( const QAngle &srcAngles, const QAngle &destAngles, Vector &deltaAxis, float &deltaAngle );
void RotationDelta( const QAngle &srcAngles, const QAngle &destAngles, QAngle *out );

void ComputeTrianglePlane( const Vector& v1, const Vector& v2, const Vector& v3, Vector& normal, float& intercept );
int PolyFromPlane( Vector *outVerts, const Vector& normal, float dist, float fHalfScale = 9000.0f );
int ClipPolyToPlane( Vector *inVerts, int vertCount, Vector *outVerts, const Vector& normal, float dist, float fOnPlaneEpsilon = 0.1f );
int ClipPolyToPlane_Precise( double *inVerts, int vertCount, double *outVerts, const double *normal, double dist, double fOnPlaneEpsilon = 0.1 );

//-----------------------------------------------------------------------------
// Computes a reasonable tangent space for a triangle
//-----------------------------------------------------------------------------
void CalcTriangleTangentSpace( const Vector &p0, const Vector &p1, const Vector &p2,
							  const Vector2D &t0, const Vector2D &t1, const Vector2D& t2,
							  Vector &sVect, Vector &tVect );

//-----------------------------------------------------------------------------
// Transforms a AABB into another space; which will inherently grow the box.
//-----------------------------------------------------------------------------
void TransformAABB( const matrix3x4_t &in1, const Vector &vecMinsIn, const Vector &vecMaxsIn, Vector &vecMinsOut, Vector &vecMaxsOut );

//-----------------------------------------------------------------------------
// Uses the inverse transform of in1
//-----------------------------------------------------------------------------
void ITransformAABB( const matrix3x4_t &in1, const Vector &vecMinsIn, const Vector &vecMaxsIn, Vector &vecMinsOut, Vector &vecMaxsOut );

//-----------------------------------------------------------------------------
// Rotates a AABB into another space; which will inherently grow the box.
// (same as TransformAABB, but doesn't take the translation into account)
//-----------------------------------------------------------------------------
void RotateAABB( const matrix3x4_t &in1, const Vector &vecMinsIn, const Vector &vecMaxsIn, Vector &vecMinsOut, Vector &vecMaxsOut );

//-----------------------------------------------------------------------------
// Uses the inverse transform of in1
//-----------------------------------------------------------------------------
void IRotateAABB( const matrix3x4_t &in1, const Vector &vecMinsIn, const Vector &vecMaxsIn, Vector &vecMinsOut, Vector &vecMaxsOut );

//-----------------------------------------------------------------------------
// Transform a plane
//-----------------------------------------------------------------------------
inline void MatrixTransformPlane( const matrix3x4_t &src, const cplane_t &inPlane, cplane_t &outPlane )
{
	// What we want to do is the following:
	// 1) transform the normal into the new space.
	// 2) Determine a point on the old plane given by plane dist * plane normal
	// 3) Transform that point into the new space
	// 4) Plane dist = DotProduct( new normal, new point )

	// An optimized version, which works if the plane is orthogonal.
	// 1) Transform the normal into the new space
	// 2) Realize that transforming the old plane point into the new space
	// is given by [ d * n'x + Tx, d * n'y + Ty, d * n'z + Tz ]
	// where d = old plane dist, n' = transformed normal, Tn = translational component of transform
	// 3) Compute the new plane dist using the dot product of the normal result of #2

	// For a correct result, this should be an inverse-transpose matrix
	// but that only matters if there are nonuniform scale or skew factors in this matrix.
	VectorRotate( inPlane.normal, src, outPlane.normal );
	outPlane.dist = inPlane.dist * DotProduct( outPlane.normal, outPlane.normal );
	outPlane.dist += outPlane.normal.x * src[0][3] + outPlane.normal.y * src[1][3] + outPlane.normal.z * src[2][3];
}

inline void MatrixITransformPlane( const matrix3x4_t &src, const cplane_t &inPlane, cplane_t &outPlane )
{
	// The trick here is that Tn = translational component of transform,
	// but for an inverse transform, Tn = - R^-1 * T
	Vector vecTranslation;
	MatrixGetColumn( src, 3, vecTranslation );

	Vector vecInvTranslation;
	VectorIRotate( vecTranslation, src, vecInvTranslation );

	VectorIRotate( inPlane.normal, src, outPlane.normal );
	outPlane.dist = inPlane.dist * DotProduct( outPlane.normal, outPlane.normal );
	outPlane.dist -= outPlane.normal.x * vecInvTranslation[0] + outPlane.normal.y * vecInvTranslation[1] + outPlane.normal.z * vecInvTranslation[2];
}

int CeilPow2( int in );
int FloorPow2( int in );

FORCEINLINE float * UnpackNormal_HEND3N( const unsigned int *pPackedNormal, float *pNormal )
{
	int temp[3];
	temp[0] = ((*pPackedNormal >> 0L) & 0x7ff);
	if ( temp[0] & 0x400 )
	{
		temp[0] = 2048 - temp[0];
	}
	temp[1] = ((*pPackedNormal >> 11L) & 0x7ff);
	if ( temp[1] & 0x400 )
	{
		temp[1] = 2048 - temp[1];
	}
	temp[2] = ((*pPackedNormal >> 22L) & 0x3ff);
	if ( temp[2] & 0x200 )
	{
		temp[2] = 1024 - temp[2];
	}
	pNormal[0] = (float)temp[0] * 1.0f/1023.0f;
	pNormal[1] = (float)temp[1] * 1.0f/1023.0f;
	pNormal[2] = (float)temp[2] * 1.0f/511.0f;
	return pNormal;
}

FORCEINLINE unsigned int * PackNormal_HEND3N( const float *pNormal, unsigned int *pPackedNormal )
{
	int temp[3];

	temp[0] = Float2Int( pNormal[0] * 1023.0f );
	temp[1] = Float2Int( pNormal[1] * 1023.0f );
	temp[2] = Float2Int( pNormal[2] * 511.0f );

	// the normal is out of bounds, determine the source and fix
	// clamping would be even more of a slowdown here
	Assert( temp[0] >= -1023 && temp[0] <= 1023 );
	Assert( temp[1] >= -1023 && temp[1] <= 1023 );
	Assert( temp[2] >= -511 && temp[2] <= 511 );

	*pPackedNormal = ( ( temp[2] & 0x3ff ) << 22L ) |
                     ( ( temp[1] & 0x7ff ) << 11L ) |
                     ( ( temp[0] & 0x7ff ) << 0L );
	return pPackedNormal;
}

FORCEINLINE unsigned int * PackNormal_HEND3N( float nx, float ny, float nz, unsigned int *pPackedNormal )
{
	int temp[3];

	temp[0] = Float2Int( nx * 1023.0f );
	temp[1] = Float2Int( ny * 1023.0f );
	temp[2] = Float2Int( nz * 511.0f );

	// the normal is out of bounds, determine the source and fix
	// clamping would be even more of a slowdown here
	Assert( temp[0] >= -1023 && temp[0] <= 1023 );
	Assert( temp[1] >= -1023 && temp[1] <= 1023 );
	Assert( temp[2] >= -511 && temp[2] <= 511 );

	*pPackedNormal = ( ( temp[2] & 0x3ff ) << 22L ) |
                     ( ( temp[1] & 0x7ff ) << 11L ) |
                     ( ( temp[0] & 0x7ff ) << 0L );
	return pPackedNormal;
}

FORCEINLINE float * UnpackNormal_SHORT2( const unsigned int *pPackedNormal, float *pNormal, bool bIsTangent = FALSE )
{
	// Unpacks from Jason's 2-short format (fills in a 4th binormal-sign (+1/-1) value, if this is a tangent vector)

	// FIXME: short math is slow on 360 - use ints here instead (bit-twiddle to deal w/ the sign bits)
	short iX = (*pPackedNormal & 0x0000FFFF);
	short iY = (*pPackedNormal & 0xFFFF0000) >> 16;

	float zSign = +1;
	if ( iX < 0 )
	{
		zSign = -1;
		iX    = -iX;
	}
	float tSign = +1;
	if ( iY < 0 )
	{
		tSign = -1;
		iY    = -iY;
	}

	pNormal[0] = ( iX - 16384.0f ) / 16384.0f;
	pNormal[1] = ( iY - 16384.0f ) / 16384.0f;
	pNormal[2] = zSign*sqrtf( 1.0f - ( pNormal[0]*pNormal[0] + pNormal[1]*pNormal[1] ) );
	if ( bIsTangent )
	{
		pNormal[3] = tSign;
	}

	return pNormal;
}

FORCEINLINE unsigned int * PackNormal_SHORT2( float nx, float ny, float nz, unsigned int *pPackedNormal, float binormalSign = +1.0f )
{
	// Pack a vector (ASSUMED TO BE NORMALIZED) into Jason's 4-byte (SHORT2) format.
	// This simply reconstructs Z from X & Y. It uses the sign bits of the X & Y coords
	// to reconstruct the sign of Z and, if this is a tangent vector, the sign of the
	// binormal (this is needed because tangent/binormal vectors are supposed to follow
	// UV gradients, but shaders reconstruct the binormal from the tangent and normal
	// assuming that they form a right-handed basis).

	nx += 1;					// [-1,+1] -> [0,2]
	ny += 1;
	nx *= 16384.0f;				// [ 0, 2] -> [0,32768]
	ny *= 16384.0f;

	// '0' and '32768' values are invalid encodings
	nx = max( nx, 1.0f );		// Make sure there are no zero values
	ny = max( ny, 1.0f );
	nx = min( nx, 32767.0f );	// Make sure there are no 32768 values
	ny = min( ny, 32767.0f );

	if ( nz < 0.0f )
		nx = -nx;				// Set the sign bit for z

	ny *= binormalSign;			// Set the sign bit for the binormal (use when encoding a tangent vector)

	// FIXME: short math is slow on 360 - use ints here instead (bit-twiddle to deal w/ the sign bits), also use Float2Int()
	short sX = (short)nx;		// signed short [1,32767]
	short sY = (short)ny;

	*pPackedNormal = ( sX & 0x0000FFFF ) | ( sY << 16 ); // NOTE: The mask is necessary (if sX is negative and cast to an int...)

	return pPackedNormal;
}

FORCEINLINE unsigned int * PackNormal_SHORT2( const float *pNormal, unsigned int *pPackedNormal, float binormalSign = +1.0f )
{
	return PackNormal_SHORT2( pNormal[0], pNormal[1], pNormal[2], pPackedNormal, binormalSign );
}

// Unpacks a UBYTE4 normal (for a tangent, the result's fourth component receives the binormal 'sign')
FORCEINLINE float * UnpackNormal_UBYTE4( const unsigned int *pPackedNormal, float *pNormal, bool bIsTangent = FALSE )
{
	unsigned char cX, cY;
	if ( bIsTangent )
	{
		cX = *pPackedNormal >> 16;					// Unpack Z
		cY = *pPackedNormal >> 24;					// Unpack W
	}
	else
	{
		cX = *pPackedNormal >>  0;					// Unpack X
		cY = *pPackedNormal >>  8;					// Unpack Y
	}

	float x = cX - 128.0f;
	float y = cY - 128.0f;
	float z;

	float zSignBit = x < 0 ? 1.0f : 0.0f;			// z and t negative bits (like slt asm instruction)
	float tSignBit = y < 0 ? 1.0f : 0.0f;
	float zSign    = -( 2*zSignBit - 1 );			// z and t signs
	float tSign    = -( 2*tSignBit - 1 );

	x = x*zSign - zSignBit;							// 0..127
	y = y*tSign - tSignBit;
	x = x - 64;										// -64..63
	y = y - 64;

	float xSignBit = x < 0 ? 1.0f : 0.0f;	// x and y negative bits (like slt asm instruction)
	float ySignBit = y < 0 ? 1.0f : 0.0f;
	float xSign    = -( 2*xSignBit - 1 );			// x and y signs
	float ySign    = -( 2*ySignBit - 1 );

	x = ( x*xSign - xSignBit ) / 63.0f;				// 0..1 range
	y = ( y*ySign - ySignBit ) / 63.0f;
	z = 1.0f - x - y;

	float oolen	 = 1.0f / sqrt( x*x + y*y + z*z );	// Normalize and
	x			*= oolen * xSign;					// Recover signs
	y			*= oolen * ySign;
	z			*= oolen * zSign;

	pNormal[0] = x;
	pNormal[1] = y;
	pNormal[2] = z;
	if ( bIsTangent )
	{
		pNormal[3] = tSign;
	}

	return pNormal;
}

//////////////////////////////////////////////////////////////////////////////
// See: http://www.oroboro.com/rafael/docserv.php/index/programming/article/unitv2
//
// UBYTE4 encoding, using per-octant projection onto x+y+z=1
// Assume input vector is already unit length
//
// binormalSign specifies 'sign' of binormal, stored in t sign bit of tangent
// (lets the shader know whether norm/tan/bin form a right-handed basis)
//
// bIsTangent is used to specify which WORD of the output to store the data
// The expected usage is to call once with the normal and once with
// the tangent and binormal sign flag, bitwise OR'ing the returned DWORDs
FORCEINLINE unsigned int * PackNormal_UBYTE4( float nx, float ny, float nz, unsigned int *pPackedNormal, bool bIsTangent = false, float binormalSign = +1.0f )
{
	float xSign = nx < 0.0f ? -1.0f : 1.0f;			// -1 or 1 sign
	float ySign = ny < 0.0f ? -1.0f : 1.0f;
	float zSign = nz < 0.0f ? -1.0f : 1.0f;
	float tSign = binormalSign;
	Assert( ( binormalSign == +1.0f ) || ( binormalSign == -1.0f ) );

	float xSignBit = 0.5f*( 1 - xSign );			// [-1,+1] -> [1,0]
	float ySignBit = 0.5f*( 1 - ySign );			// 1 is negative bit (like slt instruction)
	float zSignBit = 0.5f*( 1 - zSign );
	float tSignBit = 0.5f*( 1 - binormalSign );

	float absX = xSign*nx;							// 0..1 range (abs)
	float absY = ySign*ny;
	float absZ = zSign*nz;

	float xbits = absX / ( absX + absY + absZ );	// Project onto x+y+z=1 plane
	float ybits = absY / ( absX + absY + absZ );

	xbits *= 63;									// 0..63
	ybits *= 63;

	xbits  = xbits * xSign - xSignBit;				// -64..63 range
	ybits  = ybits * ySign - ySignBit;
	xbits += 64.0f;									// 0..127 range
	ybits += 64.0f;

	xbits  = xbits * zSign - zSignBit;				// Negate based on z and t
	ybits  = ybits * tSign - tSignBit;				// -128..127 range

	xbits += 128.0f;								// 0..255 range
	ybits += 128.0f;

	unsigned char cX = (unsigned char) xbits;
	unsigned char cY = (unsigned char) ybits;

	if ( !bIsTangent )
		*pPackedNormal = (cX <<  0) | (cY <<  8);	// xy for normal
	else
		*pPackedNormal = (cX << 16) | (cY << 24);	// zw for tangent

	return pPackedNormal;
}

FORCEINLINE unsigned int * PackNormal_UBYTE4( const float *pNormal, unsigned int *pPackedNormal, bool bIsTangent = false, float binormalSign = +1.0f )
{
	return PackNormal_UBYTE4( pNormal[0], pNormal[1], pNormal[2], pPackedNormal, bIsTangent, binormalSign );
}

//-----------------------------------------------------------------------------
// Convert RGB to HSV
//-----------------------------------------------------------------------------
void RGBtoHSV( const Vector &rgb, Vector &hsv );

//-----------------------------------------------------------------------------
// Convert HSV to RGB
//-----------------------------------------------------------------------------
void HSVtoRGB( const Vector &hsv, Vector &rgb );

//-----------------------------------------------------------------------------
// Fast version of pow and log
//-----------------------------------------------------------------------------

float FastLog2(float i);			// log2( i )
float FastPow2(float i);			// 2^i
float FastPow(float a, float b);	// a^b
float FastPow10( float i );			// 10^i

//-----------------------------------------------------------------------------
// For testing float equality
//-----------------------------------------------------------------------------

inline bool CloseEnough( float a, float b, float epsilon = EQUAL_EPSILON )
{
	return fabs( a - b ) <= epsilon;
}

inline bool CloseEnough( const Vector &a, const Vector &b, float epsilon = EQUAL_EPSILON )
{
	return fabs( a.x - b.x ) <= epsilon &&
		fabs( a.y - b.y ) <= epsilon &&
		fabs( a.z - b.z ) <= epsilon;
}

// Fast compare
// maxUlps is the maximum error in terms of Units in the Last Place. This
// specifies how big an error we are willing to accept in terms of the value
// of the least significant digit of the floating point number's
// representation. maxUlps can also be interpreted in terms of how many
// representable floats we are willing to accept between A and B.
// This function will allow maxUlps-1 floats between A and B.
bool AlmostEqual(float a, float b, int maxUlps = 10);

inline bool AlmostEqual( const Vector &a, const Vector &b, int maxUlps = 10)
{
	return AlmostEqual( a.x, b.x, maxUlps ) &&
		AlmostEqual( a.y, b.y, maxUlps ) &&
		AlmostEqual( a.z, b.z, maxUlps );
}

#endif	// MATH_BASE_H

redpajama_set_name: RedPajamaGithub


A Seven-Part Series Begins the Fourth Saturday in April 2017. The Life in the Spirit Seminar will be presented beginning at 9:30am Saturday, April 22, 2017 continuing for 7 consecutive weeks in the Welcome Center Chapel. This is an opportunity to establish, re-establish or deepen an individual relationship with God through Jesus, the Christ and experience a fuller life in the Spirit.


text:
Psy. D. Degree
@sopp_wsu
Interested in earning your Doctor of Psychology degree? Apply now to Wright State's SOPP! Apps are due Nov. 24, 202… https://twitter.com/i/web/status/1321100605105078273
RT @CCHPsych: Join us at 7pm ET tonight for a live panel discussion on current topics in LGBTQ+ psychology and mental health, fea… https://t.co/yvhkSeRpnT
Congratulations to the SOPP graduates of 2020! While we weren't able to be in person on our originally scheduled gr… https://twitter.com/i/web/status/1285603200557621255
RT @APApsycCareers: Join @APA on 5/14 for a webinar on helping current and prospective doctoral interns in clinical, counseling, and sc… https://t.co/AUdRnhUSmU
2020 SOPP Graduation
You are invited to stream the 2020 SOPP Graduation.
September 19, 3 p.m.
Stream the 2020 Graduation
Wright State's School of Professional Psychology receives 10-year re-accreditation from the American Psychological Association
With a rich 40-year history, the School of Professional Psychology has nationally acclaimed faculty and a rigorous curriculum to prepare the next generation of psychologists.
Wright State announces plans to reopen for fall semester
Wright State announced plans to return to teaching on its Dayton and Lake Campuses for the fall 2020 semester on Aug. 24 with a dynamic and flexible mixture of in-person and remote courses.
Actively engaged
Wright State graduate student Janelle Dixon serving national leadership position in The Association of Black Psychologists' Student Circle
Janelle Dixon plans to turn her passion for social justice advocacy to pursue a career as a forensic psychologist after earning a doctoral degree from the Wright State School of Professional Psychology.
Psychological moment
Wright State graduate student Gillian LaRue serving national leadership position in The Association of Black Psychologists' Student Circle
Gillian LaRue is pursuing a PsyD. at the Wright State School of Professional Psychology and hopes to work with patients at a state mental hospital after graduating.

redpajama_set_name: RedPajamaCommonCrawl


Crusted Hake served with simple chips.
In a mixing bowl, mix together the bread crumbs, parmesan, lemon zest, butter, herbs and garlic together.
Season well with salt and pepper and set aside,Preheat the oven grill.
Heat a 26cm Le Creuset Toughened Non Stick Saute Pan, and place the hake skin side down in the pan.
Cook till golden and crispy. Turn the fish over and place the crumb on top.
Put the pan under the grill, and cook until the crumb is golden and crispy.
In the meantime, heat the oil to 180°C and fry the chips till golden.
Serve with fish and chips with lemon wedges.


text:
SEARCH FOR AN ARTIST CHOOSE A COMPETITION JUDGE FOR ME
Judge for P.E.A.C.E. the Poetess
Help this artist rise in the charts by judging for them in battle! Your votes will help determine the top artists on OurStage. Also, as a fan, you can win prizes from gift cards, to festival tickets, and more. Choose one of these competitions to start judging! Or, if the artist is not in a competition, ask them to enter and try again next month.
Choose a Competition and Start Judging
Note: You must be logged in to judge for an artist.
Log in or sign up for free here.
This band is not entered in any currently active competitions
P.E.A.C.E. has been writing poetry since the age of 9,only to evolve to conscious rap,performance poetry and music.After years of putting her writing on the back burner to focus on family,she accepted a challenge by a once stranger, now close friend to pick up the pen and write again.After much procrastination,she accepted the challenge and stepped out to participate in her first poetry slam, and won 1st place. Shortley after, P.E.A.C.E. entered into the Detroit "Bring It"Talent Competition and again won 1st place in the category of Spoken Word,Rap and Comedy.
Many doors and opportunities have forged since the win,which led to a 40 school tour themed,"Project Pride" with Detroit City Councilman, Kwame Kenyatta and Spoken Word Artist and Mcee, Mic Write,currently an intricate part of the "Cease Fire" tour initiated by radio personality Reggie Reg Davis,community leaders, activist, and local artist. P.E.A.C.E. continues to move crowds with her energetic delivery and inspiring play on words and topics.She is constantly sought out and featured around the State of Michigan, and is forging her way through the country via modern technology.Her current projects include writing in preparation for publishing her first book entitled "Late Bloomer" ,working on her second recording project to accompany her book ,touring and feature performances.
« View This Artist's Complete Profile
Connect with OurStage: Facebook Twitter YouTube FourSquare Newsletter RSS feed
Earn a Predictor Badge
» Judge any of our channels to put your favorite artists at the top of the charts. You could earn a predictor badge!
LADY LAMB
» New single, album, video, tour!
"FEBRUARY"
» The new fuzzed-out power pop single from LATE CAMBRIAN
» Stream the latest OurStage spotlight show.


Use your voice to stream your favorite music services through your HEOS-enabled devices. Play Spotify, iHeartRadio and more — all you have to do is ask.
 AFTER THAT, VOICE CONTROL IS WHAT YOU MAKE OF IT.
Take a look at how to download the HEOS Home Entertainment Skill and get a full list of voice commands. Plus, see our comprehensive list of FAQs.
GO AHEAD — ASK ALEXA TO STREAM SPECIFIC AUDIO CONTENT TO YOUR HEOS-ENABLED DEVICES NOW!
You want to receive news about our products or upcoming promotions? Just sign up to our newsletter now.


Find affordable portable toilets in Louisville, New Albany, Jeffersonville, and Sheperdsville, Kentucky and parts of Indiana from the leading Louisville porta potty rental companies. When Louisville residents are searching for a high quality source for sanitation services, they turn to Provenportabletoilets.com because they know that when they find pre-screened Louisville portable toilet providers on our site they will not only get the best priced restroom trailers in Jefferson County, but reliable service as well.
We can handle events of all sizes, making us your number one choice whether you need 500 portable toilets in Louisville for the Kentucky Derby at Churchill Downs, or just need to rent one porta potty in La Grange during a remodeling project.
Finding high quality, affordable Louisville portable showers and toilets for rent in Louisville, Taylorsville, Crestwood, Mt Washington, and Prospect, Kentucky is easier than ever before with Provenportabletoilets.com. In order to make portable toilet rentals in Louisville as easy as possible, PPT researches Louisville portable toilet companies in order to make it easy for customers to get into contact with companies who offer low prices, dependable service, and a large selection of sanitation products and services available throughout Jefferson County, Kentucky. Fill out a quote right now to get prices, or call one of the companies listed to start comparing prices on porta potties in Louisville, KY.


text:
Wiktor Cezary Bukato (ur. 25 lutego 1949, zm. 26 lipca 2021) – polski tłumacz i wydawca szczególnie fantastyki, działacz fandomu, także tłumacz przysięgły języka angielskiego i rosyjskiego.

Życiorys
Absolwent Wyższego Studium Języków Obcych przy Uniwersytecie Warszawskim oraz Instytutu Lingwistyki Stosowanej UW.

W połowie lat 70. nauczyciel języka angielskiego w XVII Liceum Ogólnokształcącym w Warszawie; równocześnie pracownik redakcji anglojęzycznej Polskiego Radia.

Od końca lat 70. działacz OKMFiSF, a następnie Klubu Fantastyki SFan. Współpracownik wydawnictwa Iskry (1982), gdzie stworzył tzw. "serię zeszytową Iskier". W latach 1983–1990 redaktor w Wydawnictwie Alfa, twórca Biblioteki Fantastyki, w tym, w latach 1989–1990, kierownik redakcji Beta Books, przybudówki Alfy, zajmującej się paperbackowymi wydawnictwami małoformatowymi. Następnie kierownik warszawskiego oddziału wydawnictwa Phantom Press (1991–1992) oraz redaktor naczelny wydawnictwa Alkazar (1992–1995).

Był przewodniczącym Europejskiego Stowarzyszenia Science Fiction i koordynatorem Euroconu 1991 w Krakowie. Do 1990 r. członek Science Fiction Writers of America.

Był autorem rubryki Z ansibla w miesięczniku Fenix oraz wspomnieniowego cyklu Sceny z życia smoków w Miesięczniku ŚKF.

Życie prywatne
Mieszkał w Rybnie. Żonaty, miał dwoje dzieci.

Nagrody i wyróżnienia
 Trzykrotny laureat Śląkfy w kategorii Wydawca Roku – za lata 1985, 1986 i 1988,
 Nagroda Europejskiego Stowarzyszenia Science Fiction dla najlepszego wydawcy, (1990)
 Nagrody "Karel" (dla najlepszego tłumacza) przyznana w 1987 r. przez World SF.

Tłumaczenia
 Poul Anderson –
 James White – Szpital kosmiczny
 Poul Anderson –
 Poul Anderson –
 Poul Anderson –
 Poul Anderson – Wojna skrzydlatych
 J.T. McIntosh – Dziesiąte podejście

Redakcja
 seria zeszytowa Iskier
 Droga do science fiction
 Don Wollheim proponuje
 Rakietowe dzieci
 Arthur C. Clarke – Gwiazda
 J.T. McIntosh – Dziesiąte podejście
 Steven Weinberg – Sen o teorii ostatecznej
 Stephen Hawking – Czarne dziury i wszechświaty niemowlęce
 Angielski i francuski słownik obrazkowy

Przypisy

Absolwenci Wydziału Lingwistyki Stosowanej Uniwersytetu Warszawskiego
Laureaci Śląkfy
Polscy tłumacze literatury angielskojęzycznej
Polscy tłumacze przysięgli
Polscy wydawcy
Postacie fandomu polskiego
Urodzeni w 1949
Zmarli w 2021

redpajama_set_name: RedPajamaWikipedia


text:
We use third-party cookies to provide support. By continuing to browse this website, you accept our privacy policy.
Stocks and ETFs
Invest in 5,500+ global companies
Curated, ready-made portfolios
Managed Funds by World's Renowed Companies.
Cash managment
Save, invest and spend from a single account
Home Stocks and ETFs NU
Nu Holdings Ltd
47 own
1 Week Returns
3 Months Returns
Start Investing in
Get Started with as little as $1.00
20,378.09M
52-Week High
52-Week Low
Market Capitalization20,378.09M
52-Week High11.83
52-Week Low3.26
Average Volume26.09M
Dividend Yield--
P/E Ratio--
What does the Nu Holdings Ltd do?
Nu Holdings Ltd. operates as a digital financial services platform and technology company primarily in Brazil, Mexico, and Colombia. It offers Nu credit and debit cards; Ultraviolet credit and debit cards; and mobile payment solutions for NuAccount customers to make and receive transfers, pay bills, and make everyday purchases through their mobile phones. The company provides savings solutions, such as Nu Personal Accounts, a digital account solution that supports all personal finance activities, from daily purchases and money transfers to savings; and Nu business accounts designed specifically for entrepreneur customers and their businesses. In addition, it offers NuInvest, an investment product that provides equity, fixed-income, options, and ETF products, as well as multimarket funds with curated asset allocations based on the customer s risk profile and financial position; personal unsecured loans; in-app 'buy now pay later' solution for Nu card customers to pay credit and debit purchases, and banking payment slips over time in up to twelve installments; and NuInsurance protecting solutions to help its customers secure life insurance and funeral benefits. The company was founded in 2013 and is headquartered in Sao Paulo, Brazil.
News & Events about Nu Holdings Ltd.
Nu Holdings Ltd. ('Nu' or the 'Company') (NYSE: NU | B3: NUBR33), one of the worlds largest digital financial services platforms, today announced that it will host a conference call to discuss its Q4 and full year 2022 financial results on Tuesday, February 14, 2023 at 5:00 p.m...
Nu Holdings Ltd. ('Nu' or the 'Company') (NYSE: NU | B3: NUBR33), one of the worlds largest digital financial services platforms, today announced that it will host a conference call to discuss its third quarter 2022 financial results on Monday, November 14, 2022 at 5:00 p.m...
Thinking about buying stock in Endo International, T2 Biosystems, SoFi Technologies, Nu Holdings, or Bloom Energy? Thinking about buying stock in Endo International, T2 Biosystems, SoFi Technologies, Nu Holdings, or Bloom Energy? PR Newswire NEW YORK, Aug. 17, 2022 NEW YORK, Aug. 17, 2022...
Thinking about buying stock in Ferroglobe, Genius Sports, HyreCar, Embark Technology, or Nu Holdings? Thinking about buying stock in Ferroglobe, Genius Sports, HyreCar, Embark Technology, or Nu Holdings? PR Newswire NEW YORK, Aug. 16, 2022 NEW YORK, Aug. 16, 2022 /PRNewswire...
What is Nu Holdings Ltd share price today?
Can Indians buy Nu Holdings Ltd shares?
How can I buy Nu Holdings Ltd shares from India?
Can Fractional shares of Nu Holdings Ltd be purchased?
What are the documents required to start investing in Nu Holdings Ltd stocks?
What is today's traded volume of Nu Holdings Ltd?
What is today's market capitalisation of Nu Holdings Ltd?
What is the 52-Week High and Low Range of Nu Holdings Ltd?
What percentage is Nu Holdings Ltd down from its 52-Week High?
What percentage is Nu Holdings Ltd up from its 52-Week Low?
Start Investing in NU
COREBRIDGE FINANCIAL, INC. (SAFG RETIREMENT SERVICES)
$-0.16 (-0.74 %)
Blue Owl Capital Inc
$0.04 (0.32 %)
Healthpeak Properties, Inc.
CBRE Group, Inc.
Sign up and dive into a whole new world of investing.
Join over 280,000 who have signed up and are using Stockal successfully already.
Use Stockal on the go!
Download our app today.
Stocks & ETFs
2023 Borderless Investing Inc. All Rights Reserved.
Invest in GOOG
Invest in META
Invest in AMZN
Invest in NFLX
Invest in DIS
Invest in US
Borderless Investing Inc., ("Borderless Investing"), the owner of the Stockal Platform, is not a broker-dealer nor an investment advisor and through its affiliate stockbroker Stockal Securities Pvt. Ltd., (SEBI INZ000301236), acting as the introducing entity has entered into a clearing agreement with DriveWealth LLC., to offer U.S. equities on the Stockal Platform. The Stockal Platform enables investments in a diverse set of global assets from a single account from anywhere in the world. As such, Borderless Investing makes no warranties or representations, express or implied, on products and services offered through its website/platform. It accepts no liability for any damages or losses, however, caused in connection with the use of related services.
All investments involve risk and returns will fluctuate, are subject to market volatility and may cause losses. The material on this website/platform is provided solely for informational and educational purposes and shall not in any manner be considered a recommendation or endorsement of any strategy or investment, nor an offer to sell or a solicitation of an offer to buy securities, products or services nor advice. Borderless Investing makes no guarantee as to the currency, accuracy, or quality of information published and/or archived on this website/platform, nor will Borderless Investing accept any responsibility for other organizations, businesses, and private persons that provide information on this website/platform. All information on this website/platform regarding products and services provided by Borderless Investing is subject to change without notice. Reasonable efforts are taken to ensure the accuracy and integrity of all information provided here, but Borderless Investing is not responsible for misprints, out of date information, or errors. Any investment decision and/or strategy that investors make or employ, whether or not such decision or strategy derives from or relies upon material accessed or provided through this website/platform, is undertaken at the investor's sole discretion and risk. Before making any investment decisions, investors should consult additional sources of information and/or their legal or tax advisor. PLEASE READ THIS FULL DISCLOSURE CAREFULLY BEFORE USING THIS SERVICE. BY USING THE SERVICE OR CLICKING AGREE YOU ARE AGREEING TO BE BOUND BY THESE DISCLOSURES AND ALL TERMS AND CONDITIONS ON THIS WEBSITE/PLATFORM.
*DriveWealth LLC., a member of the Securities Investor Protection Corporation (SIPC), will serve as the custodian for your securities account. In the event that DriveWealth LLC., fails and is placed in liquidation under the Securities Investor Protection Act, securities in your brokerage account may be protected up to $500,000. For details, please see www.sipc.org.
All brands and logos used on this website are the property of their respective owners.


text:
Home » Boston » Bay Village
Salt Bae learns quickly that Boston takes its Covid-19, fire and food-preparation regulations seriously
By adamg on Sat, 09/26/2020 - 9:56pm
Salt Bar's joint is closed for now, on city orders.
Boston licensing officials ordered the Nusr-et steak place in Park Square shut less than a week after it opened, for violations of city and state Covid-19 and fire regulations.
Separately, an ISD health inspector cited the 100 Arlington St. restaurant for several violations - including opening without a health permit on Wednesday, although most had been fixed during a followup inspection the next day.
Nusr-et faces two hearings on Tuesday morning before the Boston Licensing Board.
One of the hearings is for a citation issued on Sept. 18 - the restaurant's first day - for violating a state order banning lines outside restaurants due to the risk queued people could spread Covid-19. Several Covid-19 complaints were filed with 311.
The second hearing is for a citation issued on Sept. 23 for having two blocked fire exits - just a block from the scene of the infamous Cocoanut Grove fire that to this day remains a major reason for Boston's vigilance over blocked fire exits.
The board's hearings start at 10 a.m.
Meanwhile, on Sept. 23, an ISD health inspector faulted the restaurant for several issues in addition to not obtaining a permit, including "evidence that the [person in charge] is not overseeing day to day operations to ensure establishment is operating in compliance with the food code."
Also, the inspector cited the restaurant because two refrigerators were too warm while the temperature inside two dishwashers was not set high enough. The refrigerator issue was deemed a "critical" problem, but the inspector noted that the chef was able to get somebody in to adjust the temperatures before the inspector left.
A follow-up inspection the next day showed all the problems had been fixed, except that the dishwashers remained set with their water temperatures too low.
Park Square
Arlington Street
By Tollhouse on Sat, 09/26/2020 - 10:14pm.
"just blocks from the scene of the infamous Cocoanut Grove fire "
Meaningless when everything in Boston is just blocks away from each other.
OK, I made it more meaningful
By adamg on Sat, 09/26/2020 - 11:56pm.
Salt Bae's joint is actually only about a block away from where the Cocoanut Grove was. Walking directions.
Alternative plans....
By Friartuck on Sun, 09/27/2020 - 8:48am.
"Instead of Cocoanut Grove, we can stop in at Napoleon's just a few doors up. And since it's Saturday, the dance floor at the upstairs bar, Josephine's, will be open."
Next weekend wer'e going to Luxor on Church Street.
The Other Side and Jacques
By anon on Sun, 09/27/2020 - 9:58am.
And at the end of Piedmont was the Other Side and Jacques. The Other Side is long gone, but Jacques is still there. Luxor was Mario's back when the Other Side was open.
The good old days
By anon on Sun, 09/27/2020 - 10:09am.
The Cocoanut Grove of course burned down, but after that was The Other Side, Jacques, Napoleon, and later Luxor . Dancing, sex, drugs, alcohol, and prostitution, all with a large dose of drag. And all within a stones throw of each other.
Good times !
O RLY?
By brianjdamico on Sun, 09/27/2020 - 9:54am.
Are restaurants in the
By anon on Sun, 09/27/2020 - 3:51pm.
Are restaurants in the vicinity of the former site of the Coconut Grove more susceptible to fires?
The original comment was
By brianjdamico on Sun, 09/27/2020 - 9:09pm.
The original comment was about everything in Boston being only a few blocks from everything else in Boston, which is what my comment was focused on.
But I'll drop this here since it's as good as any: if the Boston College football team had won that night, the death toll would have been significantly higher. But they lost to Holy Cross and cancelled their party at the nightclub.
The curse of the Coconut
The curse of the Coconut Grove lives on. Watch out for Buck Jones.
Boston Magazine commentary
By Michael Maggard on Sat, 09/26/2020 - 10:33pm.
Apparently Salt Bae's ad-buy wasn't sufficient for Boston Magazine:
The Boston Magazine um "commentary"?
By Michael Maggard on Sun, 09/27/2020 - 1:55am.
https://www.bostonmagazine.com/restaurants/2020/09/19/salt-bae-nusret-bo...
"Please Hold the Salt Bae"
By Cleary Squared on Sun, 09/27/2020 - 1:27pm.
I didn't know that $1,000 steaks wrapped in gold leaf were a trend...
Please Hold the Salt Bae
Wait. Huh?
By Waquiot on Sat, 09/26/2020 - 10:50pm.
Isn't the health permit the basic thing one needs to open a restaurant, and has been required for decades? How do you even start planning to open an eatery in Boston without finding out what permits one needs?
Hate to see the looks on their face when they find out about the collection of meals taxes.
If Fire gives you the OK
By J.R. Dobbs on Sun, 09/27/2020 - 6:06am.
You could roll the dice and open the doors.
Mr. Scanlon used to pop in, check the place out, sign the permit, and the doors would open 15 mins later.
From the list of violations it seems obtaining permits is the last thing on managements mind.
Perhaps a few too many at the soft opening?
This guy is definitely a move fast, break rules nitwit
By Parkwayne on Sun, 09/27/2020 - 7:43am.
https://www.vice.com/en_us/article/z3b3x5/salt-baes-meat-empire-is-rife-...
It always ends the same - corporate buy out or bankruptcy where he clears a ton of money and his investors get hosed.
You mean he's " foreign "
By Walsh on Sun, 09/27/2020 - 8:43am.
The world doesn't revolve around this country's rules.
LOL what?
By Parkwayne on Sun, 09/27/2020 - 5:50pm.
Running a restaurant in Boston revolves about something much more specific and local than 'this country's rules' - local code and health policies.
This is a fascinating take - can I just open a cafe in Paris and demand that I get to pay waiters $4/hr plus tips without health insurance because I'm not from France?
A tale as old as time.
Salt guy is obvs trying to go big by fleecing really rich investors but this happens here in town more often than people think.
It usually involves a rotating cast of characters (insert generic restaurant group LLC) that open big, dont pay taxes, abscond with wages, then pop up 6-12 months later as an investor of a new "group" that opens one or more places, takes the cash, closes the LLC, etc...
There are 3 of these (ahem) organized LLC groups I can think of that all have drifted in and out of several places under questionable circumstances and still operate today despite being chased out of back bay and retreating to the north end not that long ago.
Maybe don't give your money
Maybe don't give your money to a guy whose claim to fame is getting made fun of online?
Holy shit this article
By erik g on Sun, 09/27/2020 - 11:31am.
This guy is one age-of-consent rant away from evolving into the final form of the douchebag libertarian entrepreneur.
When the ISD showed up on day 5, and found a bunch of serious health violations on top of the restaurant NOT HAVING A LICENSE TO OPERATE, why didn't they close his doors for him? He has a long and proud history of ignoring every regulation he doesn't like, and has apparently decided to start picking fights with Boston literally from the day the place opened. The only language these idiots understand is dollars. Shut him down until he proves he's not going to be a hazard to his employees, his customers, and his neighborhood.
By Cleary Squared on Sun, 09/27/2020 - 12:57pm.
If I'm not mistaken, the ISD shuts down restaurants until they've been corrected and brought up to code, correct? Unless he's got the goods on someone (or something) within Boston, he should not be pinching his fingers on anything until he conforms with the laws everyone else has to follow.
Update: Bah...talk about not reading the article. It looks like they've closed the restaurant.
On an unrelated note: I went to Falafel King yesterday - before Covid Times, they would hand out a free piece of falafel with some hummus while they made your meal (which was excellent, BTW). Are restaurants forbidden from giving out free food samples due to current health situation?
*facepalm*
By erik g on Sun, 09/27/2020 - 2:14pm.
Yup, you're right... I read all the linked complaints and citations, but failed to notice the giant picture at the top of the article. Good on you for actually enforcing the rules, ISD.
The photo wasn't there at first
By adamg on Sun, 09/27/2020 - 6:07pm.
When I first wrote the article Saturday, I found the citations, but nothing that said specifically the place was closed, so didn't write that.
Then I drove up to Arlington Street today, and took the photo and changed the story.
Erik, be careful
You might be a racist for this take. He's not from here so gets to do things his way.
As is having a certified food
As is having a certified food safety manager on the premises. I'm pretty sure the sale of just 1 of the steaks he sells would pay for a couple of employees to take the ServSafe manager course and exam.
Is this one of those
By anon on Sun, 09/27/2020 - 12:09pm.
Is this one of those bureaucratic issues where one city agency says, "Here's your permit. Congratulations on your new business!", but they don't bother to tell you about the other permits required by other city agencies?
Yeah, it's the business owner's job to know The requirements. But at the same time, the city should support small businesses by making it easy to get this information and hard to screw up the process.
No, he can't play that card
That was an issue in the Menino days with like Dominican immigrants trying to open a small place in JP. ISD almost seemed to delight in screwing people like that.
But that doesn't happen anymore, that I can tell. Also, when our Bae applied to buy Liquid Art House's liquor license in March, he was represented by Dennis Quilty, who is probably THE licensing lawyer in Boston. No way are you going to pay his rates and not be told about all the permits and requirements to open a Boston restaurant.
By Tish on Sun, 09/27/2020 - 8:16pm.
You can't file for your building permit until you apply for your health department permit . My guess is they filed for a health perdidn't complete the inspections. Maybe somethings changed but you couldn't go pick up your liquor license until you had all your permits signed off.
Just realized it's that stupid salt guy
I know they say everyone has their "15 minutes" but I give this place 8 months.
If no health permit, why weren't they shut down?
A restaurant without a health permit should have been cleared of patrons until they got one. They were not even compliant with the standards, so not a technicality.
Otherwise, what is the point?
They are shut down
I've updated my post.
Example of the contemporary philosophy:
By Daan on Sun, 09/27/2020 - 11:18am.
Act now, ask for forgiveness later. After people have become sick or died.
And if enough money is behind you worry not, you can manipulate bankruptcy rules or know folks in high places, do not worry.
Perhaps it is a century issue. Seems that at federal and state levels we are back to a repeating the corruptions of the early 20th century. Where money, power, wealth are given preference to everyone else.
By M8ke on Sun, 09/27/2020 - 11:19pm.
They're more concerned with fixed issues here, than they are with the mess on "Methadone Mile."
I wonder who didn't get their palms greased?
And what would you have the licensing board do?
By adamg on Mon, 09/28/2020 - 10:22am.
Boston's a big city and has a big city government. It's possible to have a board that exists to consider health and safety issues at restaurants, no?
And your suggestion is?
By Waquiot on Mon, 09/28/2020 - 4:11pm.
Closing the Methadone Mile?
These are completely different issues. Oversight of licensed premises is very different than figuring out how do deal with mental health and substance abuse issues.
The restaurant is amazing will be back
By IA on Sun, 09/27/2020 - 5:21pm.
The restaurant is amazing can't wait to be back after it re-opens, the dude is a celebrity and people were out of control but the food is amazing
The Globe gave you props
By Gary C on Mon, 09/28/2020 - 1:27pm.
The Globe story noted that this was first reported here!
Gotta respect the licensing
By Refugee on Mon, 09/28/2020 - 2:24pm.
Gotta respect the licensing board for their passion.
It's not just "Closed", but "Closed!"
In other Bay Village news
Barbara Walters, Brookline native who became international reporter and interviewer, dies at 93
Legal plans return to Park Square
Boston getting LED-powered 'gas' lamps
Board approves permanent home at Park Square Castle for giant 'immersive' video art displays
Entering one Bay Village cabaret will soon be a bit of a drag without a vax card
It wouldn't be a Donna Summer disco party without a disco ball


This is a really simple composition, and that very simple composition along with the post processing in Lightroom, really brings out the spring warmth and peacefulness of the scene.
There is a really nice sense of freedom when you get on a bike and just amble around the countryside for no other reason than to enjoy the outdoors. There is no need to always go to the top of the highest mountain to get a photograph that you enjoy taking.
Taking photos in winter and spring is very different to taking photos in the summer and autumn, as the leaves on the trees and the colours completely change a scene as the seasons trundle on throughout the year. Also the weather plays a huge part too, so it's always a good idea to think spontaneously from a compositional point of view when out and about taking photographs.
Anyway, below I have provided before and after versions of the image so that you can see the effect of the post processing on the image.