//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: particle system code
//
//===========================================================================//

#include "tier0/platform.h"
#include "particles/particles.h"
#include "filesystem.h"
#include "tier2/tier2.h"
#include "tier2/fileutils.h"
#include "tier1/UtlStringMap.h"
#include "tier1/strtools.h"
#include "mathlib/halton.h"
#include "bspflags.h"
#include "const.h"
#include "particles_internal.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"


class C_OP_ConstrainDistance : public CParticleOperatorInstance
{
	DECLARE_PARTICLE_OPERATOR( C_OP_ConstrainDistance );


	uint32 GetWrittenAttributes( void ) const
	{
		return PARTICLE_ATTRIBUTE_XYZ_MASK;
	}

	uint32 GetReadAttributes( void ) const
	{
		return PARTICLE_ATTRIBUTE_XYZ_MASK;
	}


	bool EnforceConstraint( int nStartBlock,
							int nEndBlock,
							CParticleCollection *pParticles,
							void *pContext,
							int nNumValidParticlesInLastChunk ) const;

	float m_fMinDistance, m_fMaxDistance;
	int m_nControlPointNumber;
	Vector m_CenterOffset;
	bool m_bGlobalCenter;

};

#ifdef NDEBUG
#define CHECKSYSTEM( p ) 0
#else
static void CHECKSYSTEM( CParticleCollection *pParticles )
{
//	Assert( pParticles->m_nActiveParticles <= pParticles->m_pDef->m_nMaxParticles );
	for ( int i = 0; i < pParticles->m_nActiveParticles; ++i )
	{
		const float *xyz = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_XYZ, i );
		const float *xyz_prev = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_PREV_XYZ, i );
		Assert( IsFinite( xyz[0] ) );
		Assert( IsFinite( xyz[4] ) );
		Assert( IsFinite( xyz[8] ) );
		Assert( IsFinite( xyz_prev[0] ) );
		Assert( IsFinite( xyz_prev[4] ) );
		Assert( IsFinite( xyz_prev[8] ) );
	}
}
#endif

bool C_OP_ConstrainDistance::EnforceConstraint( int nStartBlock,
												int nNumBlocks,
												CParticleCollection *pParticles,
												void *pContext, int nNumValidParticlesInLastChunk ) const
{
	size_t nStride;
	FourVectors *pXYZ=pParticles->Get4VAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ,
															 &nStride );
	pXYZ += nStride * nStartBlock;
	fltx4 SIMDMinDist=ReplicateX4( m_fMinDistance );
	fltx4 SIMDMaxDist=ReplicateX4( m_fMaxDistance );
	fltx4 SIMDMinDist2=ReplicateX4( m_fMinDistance*m_fMinDistance );
	fltx4 SIMDMaxDist2=ReplicateX4( m_fMaxDistance*m_fMaxDistance );

	Vector vecCenter;
	if ( m_bGlobalCenter )
		vecCenter = m_CenterOffset;
	else
	{
		pParticles->GetControlPointAtTime( m_nControlPointNumber, pParticles->m_flCurTime, &vecCenter );
		vecCenter += pParticles->TransformAxis( m_CenterOffset, true, m_nControlPointNumber );
	}
	FourVectors Center;
	Center.DuplicateVector( vecCenter );
	
	bool bChangedSomething = false;
	do
	{
		FourVectors pts = *(pXYZ);
		pts -= Center;
		fltx4 dist_squared= pts * pts;
		fltx4 TooFarMask = CmpGtSIMD( dist_squared, SIMDMaxDist2 );
		fltx4 TooCloseMask = CmpLtSIMD( dist_squared, SIMDMinDist2 );
		fltx4 NeedAdjust = OrSIMD( TooFarMask, TooCloseMask );
		if ( IsAnyNegative( NeedAdjust ) )				// any out of bounds?
		{
			// change squared distance into approximate rsqr root
			fltx4 guess = ReciprocalSqrtEstSaturateSIMD(dist_squared);
			// newton iteration for 1/sqrt(x) : y(n+1)=1/2 (y(n)*(3-x*y(n)^2));
			guess=MulSIMD(guess,SubSIMD(Four_Threes,MulSIMD(dist_squared,MulSIMD(guess,guess))));
			guess=MulSIMD(Four_PointFives,guess);
			pts *= guess;

			FourVectors clamp_far=pts;
			clamp_far *= SIMDMaxDist;
			clamp_far += Center;
			FourVectors clamp_near=pts;
			clamp_near *= SIMDMinDist;
			clamp_near += Center;
			pts.x = MaskedAssign( TooCloseMask, clamp_near.x, MaskedAssign( TooFarMask, clamp_far.x, pXYZ->x ));
			pts.y = MaskedAssign( TooCloseMask, clamp_near.y, MaskedAssign( TooFarMask, clamp_far.y, pXYZ->y ));
			pts.z = MaskedAssign( TooCloseMask, clamp_near.z, MaskedAssign( TooFarMask, clamp_far.z, pXYZ->z ));
			*(pXYZ) = pts;
			bChangedSomething = true;
		}
		pXYZ += nStride;
	} while (--nNumBlocks);
	return bChangedSomething;
}

DEFINE_PARTICLE_OPERATOR( C_OP_ConstrainDistance, "Constrain distance to control point", OPERATOR_GENERIC );

BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_ConstrainDistance ) 
	DMXELEMENT_UNPACK_FIELD( "minimum distance", "0", float, m_fMinDistance )
	DMXELEMENT_UNPACK_FIELD( "maximum distance", "100", float, m_fMaxDistance )
	DMXELEMENT_UNPACK_FIELD( "control point number", "0", int, m_nControlPointNumber )
	DMXELEMENT_UNPACK_FIELD( "offset of center", "0 0 0", Vector, m_CenterOffset )
	DMXELEMENT_UNPACK_FIELD( "global center point", "0", bool, m_bGlobalCenter )
END_PARTICLE_OPERATOR_UNPACK( C_OP_ConstrainDistance )

class C_OP_ConstrainDistanceToPath : public CParticleOperatorInstance
{
	DECLARE_PARTICLE_OPERATOR( C_OP_ConstrainDistanceToPath );

	uint32 GetWrittenAttributes( void ) const
	{
		return PARTICLE_ATTRIBUTE_XYZ_MASK;
	}

	uint32 GetReadAttributes( void ) const
	{
		return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_CREATION_TIME_MASK;
	}

	virtual uint64 GetReadControlPointMask() const 
	{ 
		return ( 1ULL << m_PathParameters.m_nStartControlPointNumber ) |
			( 1ULL << m_PathParameters.m_nEndControlPointNumber ); 
	}

	bool EnforceConstraint( int nStartBlock,
							int nEndBlock,
							CParticleCollection *pParticles,
							void *pContext, int nNumValidParticlesInLastChunk ) const;

	float m_fMinDistance;

	float m_flMaxDistance0, m_flMaxDistanceMid, m_flMaxDistance1;
	CPathParameters m_PathParameters;

	float m_flTravelTime;

};

bool C_OP_ConstrainDistanceToPath::EnforceConstraint( int nStartBlock,
													  int nNumBlocks,
													  CParticleCollection *pParticles,
													  void *pContext,
													  int nNumValidParticlesInLastChunk ) const
{
	C4VAttributeWriteIterator pXYZ( PARTICLE_ATTRIBUTE_XYZ, pParticles );
	pXYZ += nStartBlock;

	CM128AttributeIterator pCreationTime( PARTICLE_ATTRIBUTE_CREATION_TIME, pParticles );
	pCreationTime += nStartBlock;


	Vector StartPnt, EndPnt, MidP;

	pParticles->CalculatePathValues( m_PathParameters, pParticles->m_flCurTime,
									 &StartPnt, &MidP, &EndPnt );

	fltx4 CurTime = ReplicateX4( pParticles->m_flCurTime );
	fltx4 TimeScale= ReplicateX4( 1.0/(max(0.001f,  m_flTravelTime ) ) );

	// calculate radius spline
	bool bConstantRadius = true;
	fltx4 Rad0=ReplicateX4(m_flMaxDistance0);
	fltx4 Radm=Rad0;

	if ( m_flMaxDistanceMid >= 0.0 )
	{
		bConstantRadius = ( m_flMaxDistanceMid == m_flMaxDistance0 );
		Radm=ReplicateX4( m_flMaxDistanceMid);
	}
	fltx4 Rad1=Radm;
	if ( m_flMaxDistance1 >= 0.0 )
	{
		bConstantRadius &= ( m_flMaxDistance1 == m_flMaxDistance0 );
		Rad1=ReplicateX4( m_flMaxDistance1 );
	}
	
	fltx4 RadmMinusRad0=SubSIMD( Radm, Rad0);
	fltx4 Rad1MinusRadm=SubSIMD( Rad1, Radm);
	
	fltx4 SIMDMinDist=ReplicateX4( m_fMinDistance );
	fltx4 SIMDMinDist2=ReplicateX4( m_fMinDistance*m_fMinDistance );

	fltx4 SIMDMaxDist=MaxSIMD( Rad0, MaxSIMD( Radm, Rad1 ) );
	fltx4 SIMDMaxDist2=MulSIMD( SIMDMaxDist, SIMDMaxDist);

	bool bChangedSomething = false;
	FourVectors StartP;
	StartP.DuplicateVector( StartPnt );
		
	FourVectors MiddleP;
	MiddleP.DuplicateVector( MidP );

	// form delta terms needed for quadratic bezier
	FourVectors Delta0;
	Delta0.DuplicateVector( MidP-StartPnt );

	FourVectors Delta1;
	Delta1.DuplicateVector( EndPnt-MidP );
	do
	{
		fltx4 TScale=MinSIMD(
			Four_Ones,
			MulSIMD( TimeScale, SubSIMD( CurTime, *pCreationTime ) ) );

		// bezier(a,b,c,t)=lerp( lerp(a,b,t),lerp(b,c,t),t)
		FourVectors L0 = Delta0;
		L0 *= TScale;
		L0 += StartP;

		FourVectors L1= Delta1;
		L1 *= TScale;
		L1 += MiddleP;

		FourVectors Center = L1;
		Center -= L0;
		Center *= TScale;
		Center += L0;

		FourVectors pts = *(pXYZ);
		pts -= Center;

		// calculate radius at the point. !!speed!! - use speical case for constant radius

		fltx4 dist_squared= pts * pts;
		fltx4 TooFarMask = CmpGtSIMD( dist_squared, SIMDMaxDist2 );
		if ( ( !bConstantRadius) && ( ! IsAnyNegative( TooFarMask ) ) )
		{
			// need to calculate and adjust for true radius =- we've only trivilally rejected note
			// voodoo here - we update simdmaxdist for true radius, but not max dist^2, since
			// that's used only for the trivial reject case, which we've already done
			fltx4 R0=AddSIMD( Rad0, MulSIMD( RadmMinusRad0, TScale ) );
			fltx4 R1=AddSIMD( Radm, MulSIMD( Rad1MinusRadm, TScale ) );
			SIMDMaxDist = AddSIMD( R0, MulSIMD( SubSIMD( R1, R0 ), TScale) );
			
			// now that we know the true radius, update our mask
			TooFarMask = CmpGtSIMD( dist_squared, MulSIMD( SIMDMaxDist, SIMDMaxDist ) );
		}

		fltx4 TooCloseMask = CmpLtSIMD( dist_squared, SIMDMinDist2 );
		fltx4 NeedAdjust = OrSIMD( TooFarMask, TooCloseMask );
		if ( IsAnyNegative( NeedAdjust ) )				// any out of bounds?
		{
			if ( ! bConstantRadius )
			{
				// need to calculate and adjust for true radius =- we've only trivilally rejected

			}
			
			// change squared distance into approximate rsqr root
			fltx4 guess=ReciprocalSqrtEstSIMD(dist_squared);
			// newton iteration for 1/sqrt(x) : y(n+1)=1/2 (y(n)*(3-x*y(n)^2));
			guess=MulSIMD(guess,SubSIMD(Four_Threes,MulSIMD(dist_squared,MulSIMD(guess,guess))));
			guess=MulSIMD(Four_PointFives,guess);
			pts *= guess;
			
			FourVectors clamp_far=pts;
			clamp_far *= SIMDMaxDist;
			clamp_far += Center;
			FourVectors clamp_near=pts;
			clamp_near *= SIMDMinDist;
			clamp_near += Center;
			pts.x = MaskedAssign( TooCloseMask, clamp_near.x, MaskedAssign( TooFarMask, clamp_far.x, pXYZ->x ));
			pts.y = MaskedAssign( TooCloseMask, clamp_near.y, MaskedAssign( TooFarMask, clamp_far.y, pXYZ->y ));
			pts.z = MaskedAssign( TooCloseMask, clamp_near.z, MaskedAssign( TooFarMask, clamp_far.z, pXYZ->z ));
			*(pXYZ) = pts;
			bChangedSomething = true;
		}
		++pXYZ;
		++pCreationTime;
	} while (--nNumBlocks);
	return bChangedSomething;
}

DEFINE_PARTICLE_OPERATOR( C_OP_ConstrainDistanceToPath, "Constrain distance to path between two control points", OPERATOR_GENERIC );

BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_ConstrainDistanceToPath ) 
	DMXELEMENT_UNPACK_FIELD( "minimum distance", "0", float, m_fMinDistance )
	DMXELEMENT_UNPACK_FIELD( "maximum distance", "100", float, m_flMaxDistance0 )
	DMXELEMENT_UNPACK_FIELD( "maximum distance middle", "-1", float, m_flMaxDistanceMid )
	DMXELEMENT_UNPACK_FIELD( "maximum distance end", "-1", float, m_flMaxDistance1 )
	DMXELEMENT_UNPACK_FIELD( "travel time", "10", float, m_flTravelTime )
	DMXELEMENT_UNPACK_FIELD( "random bulge", "0", float, m_PathParameters.m_flBulge )
	DMXELEMENT_UNPACK_FIELD( "start control point number", "0", int, m_PathParameters.m_nStartControlPointNumber )
	DMXELEMENT_UNPACK_FIELD( "end control point number", "0", int, m_PathParameters.m_nEndControlPointNumber )
	DMXELEMENT_UNPACK_FIELD( "bulge control 0=random 1=orientation of start pnt 2=orientation of end point", "0", int, m_PathParameters.m_nBulgeControl )
	DMXELEMENT_UNPACK_FIELD( "mid point position", "0.5", float, m_PathParameters.m_flMidPoint )
END_PARTICLE_OPERATOR_UNPACK( C_OP_ConstrainDistanceToPath )



class C_OP_PlanarConstraint : public CParticleOperatorInstance
{
	DECLARE_PARTICLE_OPERATOR( C_OP_PlanarConstraint );

	uint32 GetWrittenAttributes( void ) const
	{
		return PARTICLE_ATTRIBUTE_XYZ_MASK;
	}

	uint32 GetReadAttributes( void ) const
	{
		return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_RADIUS_MASK;
	}

	virtual uint64 GetReadControlPointMask() const 
	{ 
		return 1ULL << m_nControlPointNumber; 
	}

	bool EnforceConstraint( int nStartBlock,
							int nEndBlock,
							CParticleCollection *pParticles,
							void *pContext, int nNumValidParticlesInLastChunk ) const;

	Vector m_PointOnPlane;
	Vector m_PlaneNormal;
	int m_nControlPointNumber;
	bool m_bGlobalOrigin;
	bool m_bGlobalNormal;

};

bool C_OP_PlanarConstraint::EnforceConstraint( int nStartBlock,
											   int nNumBlocks,
											   CParticleCollection *pParticles,
											   void *pContext,
											   int nNumValidParticlesInLastChunk ) const
{
	C4VAttributeWriteIterator pXYZ( PARTICLE_ATTRIBUTE_XYZ, pParticles );
	pXYZ += nStartBlock;

	CM128AttributeIterator pRadius( PARTICLE_ATTRIBUTE_RADIUS, pParticles );
	pRadius += nStartBlock;

	// now, transform and offset parameters
	FourVectors PlaneNormal;
	PlaneNormal.DuplicateVector( 
		pParticles->TransformAxis( m_PlaneNormal, ! m_bGlobalNormal, m_nControlPointNumber ) );
	PlaneNormal.VectorNormalize();

	FourVectors PlanePoint;
	if ( m_bGlobalOrigin )
	{
		PlanePoint.DuplicateVector( m_PointOnPlane );
	}
	else
	{
		Vector ofs=pParticles->TransformAxis( m_PointOnPlane, true, m_nControlPointNumber );
		Vector vecCenter;
		pParticles->GetControlPointAtTime( m_nControlPointNumber, 
										   pParticles->m_flCurTime, &vecCenter );
		PlanePoint.DuplicateVector( ofs + vecCenter );
	}


	bool bChangedSomething = false;
	do
	{
		FourVectors pts = *pXYZ;
		pts -= PlanePoint;
		fltx4 PlaneEq=pts * PlaneNormal;
		// where planeeq<0, inside
		PlaneEq = SubSIMD( PlaneEq, *pRadius );
		fltx4 BadPts=CmpLtSIMD( PlaneEq, Four_Zeros );
		if ( IsAnyNegative( BadPts ) )
		{
			bChangedSomething = true;
			// project points to plane surface
			fltx4 PenetrationDistance=MinSIMD( Four_Zeros, PlaneEq );
			FourVectors PenetrationVector = PlaneNormal;
			PenetrationVector *= PenetrationDistance;
			(*pXYZ) -= PenetrationVector;
		}
		++pXYZ;
		++pRadius;
	} while (--nNumBlocks);
	return bChangedSomething;
}

DEFINE_PARTICLE_OPERATOR( C_OP_PlanarConstraint, "Prevent passing through a plane", OPERATOR_GENERIC );

BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_PlanarConstraint ) 
	DMXELEMENT_UNPACK_FIELD( "control point number", "0", int, m_nControlPointNumber )
	DMXELEMENT_UNPACK_FIELD( "plane point", "0 0 0", Vector, m_PointOnPlane )
	DMXELEMENT_UNPACK_FIELD( "plane normal", "0 0 1", Vector, m_PlaneNormal )
	DMXELEMENT_UNPACK_FIELD( "global origin", "0", bool, m_bGlobalOrigin )
	DMXELEMENT_UNPACK_FIELD( "global normal", "0", bool, m_bGlobalNormal )
END_PARTICLE_OPERATOR_UNPACK( C_OP_PlanarConstraint )




static Vector s_OrientationRelativeTraceVectors[] = {
	Vector( 0, .1962, .784929 ),
	Vector( -.1962, 0, .784929 ),
	Vector( .1962, 0, .784929 ),
	Vector( 0, -.1962, .78929 ),
};

void CWorldCollideContextData::SetBaseTrace(  int nIndex, Vector const &rayStart, Vector const &traceDir, int nCollisionGroup, bool bKeepMisses )
{
	CBaseTrace tr;
	Vector rayEnd = rayStart + traceDir;
	g_pParticleSystemMgr->Query()->TraceLine( rayStart, rayEnd, MASK_SOLID, NULL, nCollisionGroup, &tr );
	if ( tr.fraction < 1.0 )
	{
		m_bPlaneActive[nIndex] = true;
		m_PointOnPlane[nIndex].DuplicateVector( rayStart + tr.fraction * traceDir );
		m_PlaneNormal[nIndex].DuplicateVector( tr.plane.normal );
		m_TraceStartPnt[nIndex].DuplicateVector( rayStart );
		m_TraceEndPnt[nIndex].DuplicateVector( rayEnd );
	}
	else
	{
		if ( bKeepMisses )
		{
			m_PlaneNormal[nIndex].x = Four_Zeros;
			m_PlaneNormal[nIndex].y = Four_Zeros;
			m_PlaneNormal[nIndex].z = Four_Zeros;
			m_TraceStartPnt[nIndex].DuplicateVector( rayStart );
			m_TraceEndPnt[nIndex].DuplicateVector( rayEnd );
			m_bPlaneActive[nIndex] = true;
		}
		else
			m_bPlaneActive[nIndex] = false;
	}
}

void CWorldCollideContextData::CalculatePlanes( CParticleCollection *pParticles, int nCollisionMode,
												int nCollisionGroup, Vector const *pCPOffset, 
												float flDistanceTolerance )
{
	// fire some rays to find the convex around the control point
	if ( m_nActivePlanes && ( nCollisionMode == COLLISION_MODE_INITIAL_TRACE_DOWN ) )
		return;
	Vector rayStart = pParticles->GetControlPointAtCurrentTime( 0 ); // allow config + offset

	if ( pCPOffset )
		rayStart += *pCPOffset;

	if ( ( m_flLastUpdateTime > 0. ) && ( ( rayStart - m_vecLastUpdateOrigin ).LengthSqr() < Square( flDistanceTolerance ) ) )
		return;

	m_vecLastUpdateOrigin = rayStart;
	m_nActivePlanes = 0;
	switch( nCollisionMode )
	{
		case COLLISION_MODE_INITIAL_TRACE_DOWN:
		{
			SetBaseTrace( 0, rayStart, 1000.0 * Vector( -1, 0, 0 ), nCollisionGroup, false );
			m_nActivePlanes = 1;
			m_nNumFixedPlanes = 1;
			break;
		}
		case COLLISION_MODE_PER_FRAME_PLANESET:
		{
			int nIndexOut = 0;
			for( int i = -1; i <= 1; i++ )
				for( int j = -1; j <= 1; j++ )
					for( int k = -1; k <= 1; k++ )
					{
						if ( i || j || k )
						{
							SetBaseTrace( nIndexOut++, rayStart, 1000.0 * Vector( i, j, k ), nCollisionGroup, false );
						}
					}
			m_nNumFixedPlanes = nIndexOut;
			m_nActivePlanes = nIndexOut;
		}
		// Long missing break. Added to Source2 in change 700053.
		// It's a bug, but changing it now could cause regressions, so
		// leaving it for now until someone decides it's worth fixing.
#ifdef FP_EXCEPTIONS_ENABLED
		// This break is necessary when exceptions are enabled because otherwise
		// m_bPlaneActive[21] is set even though that plane is filled with
		// NaNs. We should perhaps put this break in, but we need to do
		// careful particle testing.
		break;
#endif

		case COLLISION_MODE_USE_NEAREST_TRACE:
		{
			int nIndexOut = 0;
			for( int i = -1; i <= 1; i++ )
				for( int j = -1; j <= 1; j++ )
					for( int k = -1; k <= 1; k++ )
					{
						if ( i || j || k )
						{
							SetBaseTrace( nIndexOut++, rayStart, 1000.0 * Vector( i, j, k ), nCollisionGroup, true );
						}
					}
			m_nNumFixedPlanes = nIndexOut;
			m_nActivePlanes = nIndexOut;
		}
	}
}

class C_OP_WorldCollideConstraint : public CParticleOperatorInstance
{
	DECLARE_PARTICLE_OPERATOR( C_OP_WorldCollideConstraint );

	uint32 GetWrittenAttributes( void ) const
	{
		return PARTICLE_ATTRIBUTE_XYZ_MASK;
	}

	uint32 GetReadAttributes( void ) const
	{
		return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_RADIUS_MASK;
	}

	virtual uint64 GetReadControlPointMask() const 
	{ 
		return 1ULL << 0; 
	}

	size_t GetRequiredContextBytes( ) const
	{
		return sizeof( CWorldCollideContextData );
	}

	bool EnforceConstraint( int nStartBlock,
							int nEndBlock,
							CParticleCollection *pParticles,
							void *pContext, int nNumValidParticlesInLastChunk ) const;

	void SetupConstraintPerFrameData( CParticleCollection *pParticles,
									  void *pContext ) const;
};


void C_OP_WorldCollideConstraint::SetupConstraintPerFrameData( CParticleCollection *pParticles,
															   void *pContext ) const
{
	CWorldCollideContextData *pCtx =
		reinterpret_cast<CWorldCollideContextData *>( pContext );
	pCtx->CalculatePlanes( pParticles, COLLISION_MODE_PER_FRAME_PLANESET, COLLISION_GROUP_NONE );
}

bool C_OP_WorldCollideConstraint::EnforceConstraint( int nStartBlock,
													 int nNumBlocks,
													 CParticleCollection *pParticles,
													 void *pContext,
													 int nNumValidParticlesInLastChunk ) const
{
	C4VAttributeWriteIterator pXYZ( PARTICLE_ATTRIBUTE_XYZ, pParticles );
	pXYZ += nStartBlock;

	CM128AttributeIterator pRadius( PARTICLE_ATTRIBUTE_RADIUS, pParticles );
	pRadius += nStartBlock;

	CWorldCollideContextData *pCtx = 
		reinterpret_cast<CWorldCollideContextData *>( pContext );

	bool bChangedSomething = false;
	do
	{
		for( int i=0; i < pCtx->m_nActivePlanes; i++ )
		{
			if ( pCtx->m_bPlaneActive[ i ] )
			{
				FourVectors pts = *pXYZ;
				pts -= pCtx->m_PointOnPlane[i];
				fltx4 PlaneEq=pts * pCtx->m_PlaneNormal[i];
				// where planeeq<0, inside
				PlaneEq = SubSIMD( PlaneEq, *pRadius );
				fltx4 BadPts=CmpLtSIMD( PlaneEq, Four_Zeros );
				if ( IsAnyNegative( BadPts ) )
				{
					bChangedSomething = true;
					// project points to plane surface
					fltx4 PenetrationDistance=MinSIMD( Four_Zeros, PlaneEq );
					FourVectors PenetrationVector = pCtx->m_PlaneNormal[i];
					PenetrationVector *= PenetrationDistance;
					(*pXYZ) -= PenetrationVector;
				}
			}
		}
		++pXYZ;
		++pRadius;
	} while (--nNumBlocks);
	return bChangedSomething;
}

DEFINE_PARTICLE_OPERATOR( C_OP_WorldCollideConstraint, "Prevent passing through static part of world", OPERATOR_GENERIC );

BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_WorldCollideConstraint ) 
END_PARTICLE_OPERATOR_UNPACK( C_OP_WorldCollideConstraint )



class C_OP_WorldTraceConstraint : public CParticleOperatorInstance
{
	DECLARE_PARTICLE_OPERATOR( C_OP_WorldTraceConstraint );

	uint32 GetWrittenAttributes( void ) const
	{
		int nRet = PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ;
		if ( m_bKillonContact )
			nRet |= PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK;
		return nRet;
	}

	uint32 GetReadAttributes( void ) const
	{
		return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_RADIUS_MASK;
	}

	virtual uint64 GetReadControlPointMask() const 
	{ 
		return 1ULL << 0; 
	}

	Vector m_vecCpOffset;
	int m_nCollisionMode;
	float m_flBounceAmount;
	float m_flSlideAmount;
	float m_flRadiusScale;
	float m_flCpMovementTolerance;
	float m_flTraceTolerance;

	bool m_bKillonContact;

	virtual bool IsFinalConstraint( void ) const
	{
		return ( m_flBounceAmount != 0. ) || ( m_flSlideAmount != 0. );
	}

	void InitializeContextData( CParticleCollection *pParticles,
								void *pContext ) const
	{
	}

	char m_CollisionGroupName[128];
	int m_nCollisionGroupNumber;
	bool m_bBrushOnly;

	void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement );

	bool EnforceConstraint( int nStartBlock,
							int nEndBlock,
							CParticleCollection *pParticles,
							void *pContext,
							int nNumValidParticlesInLastChunk ) const;
	template<bool bKillOnContact, bool bCached> bool EnforceConstraintInternal( int nStartBlock,
																				int nEndBlock,
																				CParticleCollection *pParticles,
																				void *pContext, int nNumValidParticlesInLastChunk ) const;
};

void C_OP_WorldTraceConstraint::InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement )
{
	m_nCollisionGroupNumber = g_pParticleSystemMgr->Query()->GetCollisionGroupFromName( m_CollisionGroupName );
}


struct ISectData_t
{
	fltx4 m_ISectT;											// "t" of intersection
	fltx4 m_LeftOverT;										// "left-over" amount
	FourVectors m_ISectNormal;								// normal at intersection if any
};



static void WorldIntersectTNew( FourVectors const *pStartPnt, FourVectors const *pEndPnt, 
								int nCollisionGroup, int nMask, ISectData_t *pISectData,
								int nCollisionMode, CWorldCollideContextData *pCtx, fltx4 const &fl4ParticleValidMask,
								float flTolerance = 0.0 )
{
	pISectData->m_ISectT = Four_Zeros;
	pISectData->m_LeftOverT = Four_Zeros;
	pISectData->m_ISectNormal.x = Four_Zeros;
	pISectData->m_ISectNormal.y = Four_Zeros;
	pISectData->m_ISectNormal.z = Four_Zeros;

	if ( pCtx )
	{
		pISectData->m_ISectT = Four_Twos;
		// do simd interseciton against planes
		if ( nCollisionMode == COLLISION_MODE_USE_NEAREST_TRACE )
		{
			// find which of our traces is closest to our start / end points
			pISectData->m_ISectT = Four_Twos;				// no hit

			FourVectors v4PointOnPlane;
			FourVectors v4PlaneNormal;
			fltx4 fl4ClosestDist = Four_FLT_MAX;
			for( int i = 0 ; i < pCtx->m_nActivePlanes; i++ )
			{
				if ( pCtx->m_bPlaneActive[i] )
				{
					fltx4 fl4TrialDistance = MaxSIMD( 
						pStartPnt->DistSqrToLineSegment( pCtx->m_TraceStartPnt[i], pCtx->m_TraceEndPnt[i] ),
						pEndPnt->DistSqrToLineSegment( pCtx->m_TraceStartPnt[i], pCtx->m_TraceEndPnt[i] ) );
					fltx4 fl4Nearestmask = CmpLeSIMD( fl4TrialDistance, fl4ClosestDist );
					fl4ClosestDist = MaskedAssign( fl4ClosestDist, fl4TrialDistance, fl4Nearestmask );
					v4PointOnPlane.x = MaskedAssign( fl4Nearestmask, pCtx->m_PointOnPlane[i].x, v4PointOnPlane.x );
					v4PointOnPlane.y = MaskedAssign( fl4Nearestmask, pCtx->m_PointOnPlane[i].y, v4PointOnPlane.y );
					v4PointOnPlane.z = MaskedAssign( fl4Nearestmask, pCtx->m_PointOnPlane[i].z, v4PointOnPlane.z );
					v4PlaneNormal.x = MaskedAssign( fl4Nearestmask, pCtx->m_PlaneNormal[i].x, v4PlaneNormal.x );
					v4PlaneNormal.y = MaskedAssign( fl4Nearestmask, pCtx->m_PlaneNormal[i].y, v4PlaneNormal.y );
					v4PlaneNormal.z = MaskedAssign( fl4Nearestmask, pCtx->m_PlaneNormal[i].z, v4PlaneNormal.z );
				}
			}
			fltx4 fl4OutOfRange = AndSIMD( fl4ParticleValidMask, 
										   CmpGtSIMD( fl4ClosestDist, ReplicateX4( flTolerance ) ) );
			if ( IsAnyNegative( fl4OutOfRange ) )
			{
				nMask = TestSignSIMD( fl4OutOfRange );
				for(int i=0; i < 4; i++ )
				{
					if ( nMask & ( 1 << i ) )
					{
						Vector start = pStartPnt->Vec( i );
						Vector delta = pEndPnt->Vec( i ) - start;
						
						float ln = delta.Length();

						float traceScale = max( 5.0, 300.0 / ( ln + .01 ) );

						Vector end = start + delta * traceScale;

						CBaseTrace tr;
						g_pParticleSystemMgr->Query()->TraceLine( start, end,
																  nMask, NULL, nCollisionGroup, &tr );
		
						if ( tr.fraction < 1.0 )
						{
							SubFloat( v4PointOnPlane.x, i ) = start.x + ( tr.fraction * ( end.x - start.x ) );
							SubFloat( v4PointOnPlane.y, i ) = start.y + ( tr.fraction * ( end.y - start.y ) );
							SubFloat( v4PointOnPlane.z, i ) = start.z + ( tr.fraction * ( end.z - start.z ) );
							SubFloat( v4PlaneNormal.x, i ) = tr.plane.normal.x;
							SubFloat( v4PlaneNormal.y, i ) = tr.plane.normal.y;
							SubFloat( v4PlaneNormal.z, i ) = tr.plane.normal.z;
						}
						else
						{
							// no hit.  a normal of 0 will prevent the crossing check from ever
							// finding a crossing, since it will check for (p - origin ) dot normal
							// < 0
							SubFloat( v4PlaneNormal.x, i ) = 0;
							SubFloat( v4PlaneNormal.y, i ) = 0;
							SubFloat( v4PlaneNormal.z, i ) = 0;
						}
					}
				}
			}
			FourVectors v4StartD = *pStartPnt;
			FourVectors v4EndD = *pEndPnt;
			v4StartD -= v4PointOnPlane;
			v4EndD -= v4PointOnPlane;
			fltx4 fl4StartDist = v4StartD * v4PlaneNormal;
			fltx4 fl4EndDist = v4EndD * v4PlaneNormal;
			fltx4 fl4CrossMask = AndSIMD( CmpGeSIMD( fl4StartDist, Four_Zeros ), CmpLtSIMD( fl4EndDist, Four_Zeros ) );
			fl4CrossMask = AndSIMD( fl4CrossMask, fl4ParticleValidMask );
			if ( IsAnyNegative( fl4CrossMask ) )
			{
				// a hit!
				fltx4 fl4T = DivSIMD( fl4StartDist, SubSIMD( fl4StartDist, fl4EndDist ) );
				fl4CrossMask = AndSIMD( fl4CrossMask, CmpLtSIMD( fl4T, pISectData->m_ISectT ) );
				if ( IsAnyNegative( fl4CrossMask ) )
				{
					pISectData->m_ISectT = MaskedAssign( fl4CrossMask, fl4T, pISectData->m_ISectT );
					pISectData->m_ISectNormal.x = MaskedAssign( fl4CrossMask, v4PlaneNormal.x, pISectData->m_ISectNormal.x );
					pISectData->m_ISectNormal.y = MaskedAssign( fl4CrossMask, v4PlaneNormal.y, pISectData->m_ISectNormal.y );
					pISectData->m_ISectNormal.z = MaskedAssign( fl4CrossMask, v4PlaneNormal.z, pISectData->m_ISectNormal.z );
				}
			}
		}
		pISectData->m_LeftOverT = MaxSIMD( Four_Zeros, SubSIMD( Four_Ones, pISectData->m_ISectT ) );
	}
}

static void WorldIntersectT( FourVectors const *pStartPnt, FourVectors const *pEndPnt, 
							 int nCollisionGroup, int nMask, ISectData_t *pISectData,
							 CWorldCollideContextData *pCtx )
{
	pISectData->m_ISectT = Four_Zeros;
	pISectData->m_LeftOverT = Four_Zeros;
	pISectData->m_ISectNormal.x = Four_Zeros;
	pISectData->m_ISectNormal.y = Four_Zeros;
	pISectData->m_ISectNormal.z = Four_Zeros;

	if ( pCtx )
	{
		pISectData->m_ISectT = Four_Twos;
		// do simd interseciton against planes
		for( int i=0 ; i < pCtx->m_nActivePlanes; i++ )
		{
			if ( pCtx->m_bPlaneActive[ i ] )
			{
				FourVectors v4StartD = *pStartPnt;
				FourVectors v4EndD = *pEndPnt;
				v4StartD -= pCtx->m_PointOnPlane[i];
				v4EndD -= pCtx->m_PointOnPlane[i];
				fltx4 fl4StartDist = v4StartD * pCtx->m_PlaneNormal[i];
				fltx4 fl4EndDist = v4EndD * pCtx->m_PlaneNormal[i];
				fltx4 fl4CrossMask = AndSIMD( CmpGeSIMD( fl4StartDist, Four_Zeros ), CmpLtSIMD( fl4EndDist, Four_Zeros ) );
				if ( IsAnyNegative( fl4CrossMask ) )
				{
#ifdef FP_EXCEPTIONS_ENABLED
					// Wherever fl4CrossMask is zero we need to ensure that fl4StartDist does
					// not equal fl4EndDist to avoid divide-by-zero.
					//fl4FadeWindow = OrSIMD( AndSIMD( fl4GoodMask, fl4EndTime ), AndNotSIMD( fl4GoodMask, fl4EndTime ) );
					fl4EndDist = AddSIMD( fl4EndDist, AndNotSIMD( fl4CrossMask, Four_Ones ) );
#endif
					// a hit!
					fltx4 fl4T = DivSIMD( fl4StartDist, SubSIMD( fl4StartDist, fl4EndDist ) );
					fl4CrossMask = AndSIMD( fl4CrossMask, CmpLtSIMD( fl4T, pISectData->m_ISectT ) );
					if ( IsAnyNegative( fl4CrossMask ) )
					{
						pISectData->m_ISectT = MaskedAssign( fl4CrossMask, fl4T, pISectData->m_ISectT );
						pISectData->m_ISectNormal.x = MaskedAssign( fl4CrossMask, pCtx->m_PlaneNormal[i].x, pISectData->m_ISectNormal.x );
						pISectData->m_ISectNormal.y = MaskedAssign( fl4CrossMask, pCtx->m_PlaneNormal[i].y, pISectData->m_ISectNormal.y );
						pISectData->m_ISectNormal.z = MaskedAssign( fl4CrossMask, pCtx->m_PlaneNormal[i].z, pISectData->m_ISectNormal.z );
					}
				}
			}
		}
		pISectData->m_LeftOverT = MaxSIMD( Four_Zeros, SubSIMD( Four_Ones, pISectData->m_ISectT ) );
	}
	else
	{
		// assumes they don't start solid
		for(int i=0; i < 4; i++ )
		{
			Vector start=pStartPnt->Vec( i );
			Vector end=pEndPnt->Vec( i );
			Assert( start.IsValid() );
			Assert( end.IsValid() );

			CBaseTrace tr;
			g_pParticleSystemMgr->Query()->TraceLine( start, end,
													  nMask, NULL, nCollisionGroup, &tr );
		
			SubFloat( pISectData->m_ISectT, i ) = tr.fraction;
			if ( tr.startsolid )
			{
				SubFloat( pISectData->m_LeftOverT, i ) = 0;					// don't bounce if stuck
			}
			else
			{
				SubFloat( pISectData->m_LeftOverT, i ) = 1.0 - tr.fraction;
			}
			SubFloat( pISectData->m_ISectNormal.x, i ) = tr.plane.normal.x;
			SubFloat( pISectData->m_ISectNormal.y, i ) = tr.plane.normal.y;
			SubFloat( pISectData->m_ISectNormal.z, i ) = tr.plane.normal.z;
		}
	}
}

bool C_OP_WorldTraceConstraint::EnforceConstraint( int nStartBlock,
												   int nNumBlocks,
												   CParticleCollection *pParticles,
												   void *pContext, int nNumValidParticlesInLastChunk ) const
{
	if ( m_nCollisionMode == COLLISION_MODE_USE_NEAREST_TRACE )
	{
		if ( m_bKillonContact )
			return EnforceConstraintInternal<true, true>( nStartBlock, nNumBlocks, pParticles, pContext, nNumValidParticlesInLastChunk );
		else
			return EnforceConstraintInternal<false, true>( nStartBlock, nNumBlocks, pParticles, pContext, nNumValidParticlesInLastChunk );
	}
	else
	{
		if ( m_bKillonContact )
			return EnforceConstraintInternal<true, false>( nStartBlock, nNumBlocks, pParticles, pContext, nNumValidParticlesInLastChunk );
		else
			return EnforceConstraintInternal<false, false>( nStartBlock, nNumBlocks, pParticles, pContext, nNumValidParticlesInLastChunk );
	}
}

template<bool bKillonContact, bool bCached> bool C_OP_WorldTraceConstraint::EnforceConstraintInternal( 
	int nStartBlock,
	int nNumBlocks,
	CParticleCollection *pParticles,
	void *pContext, int nNumValidParticlesInLastChunk ) const
{
	C4VAttributeWriteIterator pPrevXYZ( PARTICLE_ATTRIBUTE_PREV_XYZ, pParticles );
	pPrevXYZ += nStartBlock;

	C4VAttributeWriteIterator pXYZ( PARTICLE_ATTRIBUTE_XYZ, pParticles );
	pXYZ += nStartBlock;

	CM128AttributeIterator pRadius( PARTICLE_ATTRIBUTE_RADIUS, pParticles );
	pRadius += nStartBlock;

	CM128AttributeWriteIterator pLifetime;

	if ( bKillonContact )
	{
		pLifetime.Init( PARTICLE_ATTRIBUTE_LIFE_DURATION, pParticles );
		pLifetime += nStartBlock;
	}
	
	
	fltx4 bounceScale = ReplicateX4( m_flBounceAmount );

	fltx4 slideScale = ReplicateX4( m_flSlideAmount );

	bool bBouncingOrSliding = ( m_flBounceAmount != 0.0 ) || ( m_flSlideAmount != 0.0 );

	fltx4 radAdjustScale = ReplicateX4( m_flRadiusScale );

	bool bChangedSomething = false;

	int nMask = MASK_SOLID;

	if ( m_bBrushOnly )
		nMask = MASK_SOLID_BRUSHONLY;
	

	CWorldCollideContextData **ppCtx;
	if ( pParticles->m_pParent )
		ppCtx = &( pParticles->m_pParent->m_pCollisionCacheData[m_nCollisionMode] );
	else
		ppCtx = &( pParticles->m_pCollisionCacheData[m_nCollisionMode] );

	CWorldCollideContextData *pCtx = NULL;
	if ( ( m_nCollisionMode == COLLISION_MODE_PER_FRAME_PLANESET ) ||
		 ( m_nCollisionMode == COLLISION_MODE_USE_NEAREST_TRACE ) ||
		 ( m_nCollisionMode == COLLISION_MODE_INITIAL_TRACE_DOWN ) )
	{
		if ( ! *ppCtx )
		{
			*ppCtx = new CWorldCollideContextData;
			(*ppCtx)->m_nActivePlanes = 0;
			(*ppCtx)->m_flLastUpdateTime = -1.0;
		}
		pCtx = *ppCtx;
		if ( pCtx->m_flLastUpdateTime != pParticles->m_flCurTime )
		{
			pCtx->CalculatePlanes( pParticles, m_nCollisionMode, m_nCollisionGroupNumber, &m_vecCpOffset, m_flCpMovementTolerance );
			pCtx->m_flLastUpdateTime = pParticles->m_flCurTime;
		}
	}
	float flTol = m_flTraceTolerance * m_flTraceTolerance;
	do
	{
		// compute radius adjust factor for intersection
		
		fltx4 radiusFactor = MulSIMD( *pRadius, radAdjustScale );
		
		// compute movement delta
		FourVectors delta = *pXYZ;
		delta -= *pPrevXYZ;
		
		
		// now, add two components - the non-intersecting movement vector, and the
		// then the movement vector with the components normal to the plane removed.
		FourVectors deltanormalized = delta;
		fltx4 len2 = delta * delta;
		
		fltx4 bBadDeltas = CmpLeSIMD( len2, Four_Zeros );
		
		len2 = ReciprocalSqrtEstSIMD( len2 );
		
		deltanormalized *= AndNotSIMD( bBadDeltas, len2 );
		
		
		FourVectors endPnt = *pXYZ;
		
		FourVectors radadjust = deltanormalized;
		radadjust *= radiusFactor;
		
		endPnt += radadjust;
		
		ISectData_t iData;
		
		if ( bCached )
		{
			fltx4 fl4TailMask;
			if ( nNumBlocks > 1 )
				fl4TailMask = LoadAlignedIntSIMD( g_SIMD_AllOnesMask );
			else
				fl4TailMask = LoadAlignedIntSIMD( g_SIMD_SkipTailMask[nNumValidParticlesInLastChunk] );
			
			WorldIntersectTNew( pPrevXYZ, &endPnt, m_nCollisionGroupNumber, nMask, &iData, m_nCollisionMode, pCtx, fl4TailMask, flTol );
		}
		else
			WorldIntersectT( pPrevXYZ, &endPnt, m_nCollisionGroupNumber, nMask, &iData, pCtx );

		
		fltx4 didhit = CmpLtSIMD( iData.m_ISectT, Four_Ones );
		// mask off zero-length deltas
		didhit = AndNotSIMD( bBadDeltas, didhit );
		
		if ( IsAnyNegative( didhit ) )						// any penetration?
		{

			bChangedSomething = true;
			if ( bKillonContact )
			{	
				*pLifetime = MaskedAssign( didhit, Four_Zeros, *pLifetime );
			}
			else
			{
				FourVectors newPnt = delta;
				newPnt *= iData.m_ISectT;
				newPnt += *pPrevXYZ;

				if ( bBouncingOrSliding )
				{
					// need to compute movement due to sliding and bouncing, and add it to the point,
					// and also compute the new velocity, adjust prev pnt to reflect that new velocity

					FourVectors bouncePart = VectorReflect( deltanormalized, iData.m_ISectNormal );
					bouncePart *= bounceScale;
					FourVectors newVel = bouncePart;

					bouncePart *= iData.m_LeftOverT;
					newPnt += bouncePart;

					FourVectors slidePart = VectorSlide( delta, iData.m_ISectNormal );
					slidePart *= slideScale;
					newVel += slidePart;

					slidePart *= iData.m_LeftOverT;

					newPnt += slidePart;

					FourVectors newPrev = newPnt;
					newPrev -= newVel;
					pPrevXYZ->x = MaskedAssign( didhit, newPrev.x, pPrevXYZ->x );
					pPrevXYZ->y = MaskedAssign( didhit, newPrev.y, pPrevXYZ->y );
					pPrevXYZ->z = MaskedAssign( didhit, newPrev.z, pPrevXYZ->z );
				}
				pXYZ->x = MaskedAssign( didhit, newPnt.x, pXYZ->x );
				pXYZ->y = MaskedAssign( didhit, newPnt.y, pXYZ->y );
				pXYZ->z = MaskedAssign( didhit, newPnt.z, pXYZ->z );
			}

			CHECKSYSTEM( pParticles );
		}
		++pXYZ;
		++pPrevXYZ;
		++pRadius;
		if ( bKillonContact )
			++pLifetime;
	} while (--nNumBlocks);
	return bChangedSomething;
}



DEFINE_PARTICLE_OPERATOR( C_OP_WorldTraceConstraint, "Collision via traces", OPERATOR_GENERIC );

BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_WorldTraceConstraint ) 
	DMXELEMENT_UNPACK_FIELD( "collision mode", "0", int, m_nCollisionMode )
	DMXELEMENT_UNPACK_FIELD( "amount of bounce", "0", float, m_flBounceAmount )
	DMXELEMENT_UNPACK_FIELD( "amount of slide", "0", float, m_flSlideAmount )
	DMXELEMENT_UNPACK_FIELD( "radius scale", "1", float, m_flRadiusScale )
	DMXELEMENT_UNPACK_FIELD( "brush only", "0", bool, m_bBrushOnly )
	DMXELEMENT_UNPACK_FIELD_STRING( "collision group", "NONE", m_CollisionGroupName )
	DMXELEMENT_UNPACK_FIELD( "control point offset for fast collisions", "0 0 0", Vector, m_vecCpOffset )
	DMXELEMENT_UNPACK_FIELD( "control point movement distance tolerance", "5", float, m_flCpMovementTolerance )
	DMXELEMENT_UNPACK_FIELD( "kill particle on collision", "0", bool, m_bKillonContact )
	DMXELEMENT_UNPACK_FIELD( "trace accuracy tolerance", "24", float, m_flTraceTolerance )
END_PARTICLE_OPERATOR_UNPACK( C_OP_WorldTraceConstraint )

void AddBuiltInParticleConstraints( void )
{
	REGISTER_PARTICLE_OPERATOR( FUNCTION_CONSTRAINT, C_OP_ConstrainDistance );
	REGISTER_PARTICLE_OPERATOR( FUNCTION_CONSTRAINT, C_OP_PlanarConstraint );
	REGISTER_PARTICLE_OPERATOR( FUNCTION_CONSTRAINT, C_OP_WorldCollideConstraint );
	REGISTER_PARTICLE_OPERATOR( FUNCTION_CONSTRAINT, C_OP_WorldTraceConstraint );
	REGISTER_PARTICLE_OPERATOR( FUNCTION_CONSTRAINT, C_OP_ConstrainDistanceToPath );
}