diff --git a/particles/addbuiltin_ops.cpp b/particles/addbuiltin_ops.cpp new file mode 100644 index 0000000..568b37d --- /dev/null +++ b/particles/addbuiltin_ops.cpp @@ -0,0 +1,42 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: particle system code +// +//===========================================================================// + +#include "tier0/platform.h" +#include "particles/particles.h" + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +void AddBuiltInParticleOperators( void ); +void AddBuiltInParticleRenderers( void ); +void AddBuiltInParticleInitializers( void ); +void AddBuiltInParticleEmitters( void ); +void AddBuiltInParticleForceGenerators( void ); +void AddBuiltInParticleConstraints( void ); + +void CParticleSystemMgr::AddBuiltinSimulationOperators( void ) +{ + static bool s_DidAddSim = false; + if ( ! s_DidAddSim ) + { + s_DidAddSim = true; + AddBuiltInParticleOperators(); + AddBuiltInParticleInitializers(); + AddBuiltInParticleEmitters(); + AddBuiltInParticleForceGenerators(); + AddBuiltInParticleConstraints(); + } +} + +void CParticleSystemMgr::AddBuiltinRenderingOperators( void ) +{ + static bool s_DidAddRenderers = false; + if ( ! s_DidAddRenderers ) + { + s_DidAddRenderers = true; + AddBuiltInParticleRenderers(); + } +} diff --git a/particles/builtin_constraints.cpp b/particles/builtin_constraints.cpp new file mode 100644 index 0000000..51bf59a --- /dev/null +++ b/particles/builtin_constraints.cpp @@ -0,0 +1,1109 @@ +//========= 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( 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( 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 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( nStartBlock, nNumBlocks, pParticles, pContext, nNumValidParticlesInLastChunk ); + else + return EnforceConstraintInternal( nStartBlock, nNumBlocks, pParticles, pContext, nNumValidParticlesInLastChunk ); + } + else + { + if ( m_bKillonContact ) + return EnforceConstraintInternal( nStartBlock, nNumBlocks, pParticles, pContext, nNumValidParticlesInLastChunk ); + else + return EnforceConstraintInternal( nStartBlock, nNumBlocks, pParticles, pContext, nNumValidParticlesInLastChunk ); + } +} + +template 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 ); +} + + diff --git a/particles/builtin_initializers.cpp b/particles/builtin_initializers.cpp new file mode 100644 index 0000000..b43f18f --- /dev/null +++ b/particles/builtin_initializers.cpp @@ -0,0 +1,4741 @@ +//========= 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 "tier2/renderutils.h" +#include "tier1/UtlStringMap.h" +#include "tier1/strtools.h" +#include "dmxloader/dmxelement.h" +#include "psheet.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" + + +void CParticleOperatorInstance::InitScalarAttributeRandomRangeBlock( + int attr_num, float fMin, float fMax, + CParticleCollection *pParticles, int start_block, int n_blocks ) const +{ + size_t attr_stride; + fltx4 *pAttr = pParticles->GetM128AttributePtrForWrite( attr_num, &attr_stride ); + pAttr += attr_stride * start_block; + fltx4 val0 = ReplicateX4( fMin ); + fltx4 val_d = ReplicateX4( fMax - fMin ); + int nRandContext = GetSIMDRandContext(); + while( n_blocks-- ) + { + *( pAttr ) = AddSIMD( val0, MulSIMD( RandSIMD( nRandContext ), val_d ) ); + pAttr += attr_stride; + } + ReleaseSIMDRandContext( nRandContext ); + +} + +void CParticleOperatorInstance::InitScalarAttributeRandomRangeExpBlock( + int attr_num, float fMin, float fMax, float fExp, + CParticleCollection *pParticles, int start_block, int n_blocks ) const +{ + size_t attr_stride; + fltx4 *pAttr = pParticles->GetM128AttributePtrForWrite( attr_num, &attr_stride ); + pAttr += attr_stride * start_block; + fltx4 val0 = ReplicateX4( fMin ); + fltx4 val_d = ReplicateX4( fMax - fMin ); + //fltx4 val_e = ReplicateX4( fExp ); + int nExp = (int)(4.0f * fExp); + int nRandContext = GetSIMDRandContext(); + while( n_blocks-- ) + { + *( pAttr ) = AddSIMD( val0, MulSIMD( Pow_FixedPoint_Exponent_SIMD( RandSIMD( nRandContext ), nExp ), val_d ) ); + pAttr += attr_stride; + } + ReleaseSIMDRandContext( nRandContext ); +} + +void CParticleOperatorInstance::AddScalarAttributeRandomRangeBlock( + int nAttributeId, float fMin, float fMax, float fExp, + CParticleCollection *pParticles, int nStartBlock, int nBlockCount, bool bRandomlyInvert ) const +{ + size_t nAttrStride; + fltx4 *pAttr = pParticles->GetM128AttributePtrForWrite( nAttributeId, &nAttrStride ); + pAttr += nAttrStride * nStartBlock; + fltx4 val0 = ReplicateX4( fMin ); + fltx4 val_d = ReplicateX4( fMax - fMin ); + int nRandContext = GetSIMDRandContext(); + if ( !bRandomlyInvert ) + { + if ( fExp != 1.0f ) + { + int nExp = (int)(4.0f * fExp); + while( nBlockCount-- ) + { + *( pAttr ) = AddSIMD( *pAttr, AddSIMD( val0, MulSIMD( Pow_FixedPoint_Exponent_SIMD( RandSIMD( nRandContext ), nExp ), val_d ) ) ); + pAttr += nAttrStride; + } + } + else + { + while( nBlockCount-- ) + { + *pAttr = AddSIMD( *pAttr, AddSIMD( val0, MulSIMD( RandSIMD( nRandContext ), val_d ) ) ); + pAttr += nAttrStride; + } + } + } + else + { + fltx4 fl4NegOne = ReplicateX4( -1.0f ); + if ( fExp != 1.0f ) + { + int nExp = (int)(4.0f * fExp); + while( nBlockCount-- ) + { + fltx4 fl4RandVal = AddSIMD( val0, MulSIMD( Pow_FixedPoint_Exponent_SIMD( RandSIMD( nRandContext ), nExp ), val_d ) ); + fltx4 fl4Sign = MaskedAssign( CmpGeSIMD( RandSIMD( nRandContext ), Four_PointFives ), Four_Ones, fl4NegOne ); + *pAttr = AddSIMD( *pAttr, MulSIMD( fl4RandVal, fl4Sign ) ); + pAttr += nAttrStride; + } + } + else + { + while( nBlockCount-- ) + { + fltx4 fl4RandVal = AddSIMD( val0, MulSIMD( RandSIMD( nRandContext ), val_d ) ); + fltx4 fl4Sign = MaskedAssign( CmpGeSIMD( RandSIMD( nRandContext ), Four_PointFives ), Four_Ones, fl4NegOne ); + *pAttr = AddSIMD( *pAttr, MulSIMD( fl4RandVal, fl4Sign ) ); + pAttr += nAttrStride; + } + } + } + ReleaseSIMDRandContext( nRandContext ); +} + + +class C_INIT_CreateOnModel : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_CreateOnModel ); + + int m_nControlPointNumber; + int m_nForceInModel; + float m_flHitBoxScale; + Vector m_vecDirectionBias; + + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK | + PARTICLE_ATTRIBUTE_HITBOX_RELATIVE_XYZ_MASK | PARTICLE_ATTRIBUTE_HITBOX_INDEX_MASK; + + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + return 1ULL << m_nControlPointNumber; + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_CreateOnModel, "Position on Model Random", OPERATOR_PI_POSITION ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_CreateOnModel ) + DMXELEMENT_UNPACK_FIELD( "control_point_number", "0", int, m_nControlPointNumber ) + DMXELEMENT_UNPACK_FIELD( "force to be inside model", "0", int, m_nForceInModel ) + DMXELEMENT_UNPACK_FIELD( "hitbox scale", "1.0", int, m_flHitBoxScale ) + DMXELEMENT_UNPACK_FIELD( "direction bias", "0 0 0", Vector, m_vecDirectionBias ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_CreateOnModel ) + +void C_INIT_CreateOnModel::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + pParticles->UpdateHitBoxInfo( m_nControlPointNumber ); + while( nParticleCount ) + { + Vector vecPnts[100]; // minimize stack usage + Vector vecUVW[100]; + int nHitBoxIndex[100]; + int nToDo = min( (int)ARRAYSIZE( vecPnts ), nParticleCount ); + + Assert( m_nControlPointNumber <= pParticles->GetHighestControlPoint() ); + + g_pParticleSystemMgr->Query()->GetRandomPointsOnControllingObjectHitBox( + pParticles, m_nControlPointNumber, + nToDo, m_flHitBoxScale, m_nForceInModel, vecPnts, m_vecDirectionBias, vecUVW, + nHitBoxIndex ); + + for( int i=0; iGetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + float *pHitboxRelXYZ = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_HITBOX_RELATIVE_XYZ, start_p ); + int *pHitboxIndex = pParticles->GetIntAttributePtrForWrite( PARTICLE_ATTRIBUTE_HITBOX_INDEX, start_p ); + start_p++; + + Vector randpos = vecPnts[i]; + xyz[0] = randpos.x; + xyz[4] = randpos.y; + xyz[8] = randpos.z; + if ( pxyz && ( nAttributeWriteMask & PARTICLE_ATTRIBUTE_PREV_XYZ_MASK ) ) + { + pxyz[0] = randpos.x; + pxyz[4] = randpos.y; + pxyz[8] = randpos.z; + } + if ( pHitboxRelXYZ && ( nAttributeWriteMask & PARTICLE_ATTRIBUTE_HITBOX_RELATIVE_XYZ_MASK ) ) + { + pHitboxRelXYZ[0] = vecUVW[i].x; + pHitboxRelXYZ[4] = vecUVW[i].y; + pHitboxRelXYZ[8] = vecUVW[i].z; + } + if ( pHitboxIndex && ( nAttributeWriteMask & PARTICLE_ATTRIBUTE_HITBOX_INDEX_MASK ) ) + { + *pHitboxIndex = nHitBoxIndex[i]; + } + + } + nParticleCount -= nToDo; + } +} + + +static inline void RandomPointOnUnitSphere( int nRandContext, FourVectors &out ) +{ + // generate 4 random points on the unit sphere. uses Marsaglia (1972) method from + // http://mathworld.wolfram.com/SpherePointPicking.html + + fltx4 f4x1 = SubSIMD( MulSIMD( Four_Twos, RandSIMD( nRandContext ) ), Four_Ones ); // -1..1 + fltx4 f4x2 = SubSIMD( MulSIMD( Four_Twos, RandSIMD( nRandContext ) ), Four_Ones ); // -1..1 + fltx4 f4x1SQ = MulSIMD( f4x1, f4x1 ); + fltx4 f4x2SQ = MulSIMD( f4x2, f4x2 ); + fltx4 badMask = CmpGeSIMD( AddSIMD( f4x1SQ, f4x2SQ ), Four_Ones ); + while( IsAnyNegative( badMask ) ) + { + f4x1 = MaskedAssign( badMask, SubSIMD( MulSIMD( Four_Twos, RandSIMD( nRandContext ) ), Four_Ones ), f4x1 ); + f4x2 = MaskedAssign( badMask, SubSIMD( MulSIMD( Four_Twos, RandSIMD( nRandContext ) ), Four_Ones ), f4x2 ); + f4x1SQ = MulSIMD( f4x1, f4x1 ); + f4x2SQ = MulSIMD( f4x2, f4x2 ); + badMask = CmpGeSIMD( AddSIMD( f4x1SQ, f4x2SQ ), Four_Ones ); + } + // now, we have 2 points on the unit circle + fltx4 f4OuterArea = SqrtEstSIMD( SubSIMD( Four_Ones, SubSIMD( f4x1SQ, f4x2SQ ) ) ); + out.x = MulSIMD( AddSIMD( f4x1, f4x1 ), f4OuterArea ); + out.y = MulSIMD( AddSIMD( f4x2, f4x2 ), f4OuterArea ); + out.z = SubSIMD( Four_Ones, MulSIMD( Four_Twos, AddSIMD( f4x1, f4x2 ) ) ); +} + +static inline void RandomPointInUnitSphere( int nRandContext, FourVectors &out ) +{ + // generate 4 random points inside the unit sphere. uses rejection method. + out.x = SubSIMD( MulSIMD( Four_Twos, RandSIMD( nRandContext ) ), Four_Ones ); // -1..1 + out.y = SubSIMD( MulSIMD( Four_Twos, RandSIMD( nRandContext ) ), Four_Ones ); // -1..1 + out.z = SubSIMD( MulSIMD( Four_Twos, RandSIMD( nRandContext ) ), Four_Ones ); // -1..1 + fltx4 f4xSQ = MulSIMD( out.x, out.x ); + fltx4 f4ySQ = MulSIMD( out.y, out.y ); + fltx4 f4zSQ = MulSIMD( out.z, out.z ); + fltx4 badMask = CmpGtSIMD( AddSIMD( AddSIMD( f4xSQ, f4ySQ ), f4zSQ ), Four_Ones ); + while( IsAnyNegative( badMask ) ) + { + out.x = MaskedAssign( badMask, SubSIMD( MulSIMD( Four_Twos, RandSIMD( nRandContext ) ), Four_Ones ), out.x ); + out.y = MaskedAssign( badMask, SubSIMD( MulSIMD( Four_Twos, RandSIMD( nRandContext ) ), Four_Ones ), out.y ); + out.z = MaskedAssign( badMask, SubSIMD( MulSIMD( Four_Twos, RandSIMD( nRandContext ) ), Four_Ones ), out.z ); + f4xSQ = MulSIMD( out.x, out.x ); + f4ySQ = MulSIMD( out.y, out.y ); + f4zSQ = MulSIMD( out.z, out.z ); + badMask = CmpGeSIMD( AddSIMD( AddSIMD( f4xSQ, f4ySQ ), f4zSQ ), Four_Ones ); + } +} + + + +class C_INIT_CreateWithinSphere : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_CreateWithinSphere ); + + float m_fRadiusMin; + float m_fRadiusMax; + Vector m_vecDistanceBias, m_vecDistanceBiasAbs; + int m_nControlPointNumber; + float m_fSpeedMin; + float m_fSpeedMax; + float m_fSpeedRandExp; + bool m_bLocalCoords; + bool m_bDistanceBiasAbs; + bool m_bUseHighestEndCP; + bool m_bDistanceBias; + float m_flEndCPGrowthTime; + + Vector m_LocalCoordinateSystemSpeedMin; + Vector m_LocalCoordinateSystemSpeedMax; + int m_nCreateInModel; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + if ( !m_bUseHighestEndCP ) + return 1ULL << m_nControlPointNumber; + return ~( ( 1ULL << m_nControlPointNumber ) - 1 ); + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + + virtual void InitNewParticlesBlock( CParticleCollection *pParticles, + int start_block, int n_blocks, int nAttributeWriteMask, + void *pContext ) const; + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nControlPointNumber = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nControlPointNumber ) ); + m_bDistanceBias = ( m_vecDistanceBias.x != 1.0f ) || ( m_vecDistanceBias.y != 1.0f ) || ( m_vecDistanceBias.z != 1.0f ); + m_bDistanceBiasAbs = ( m_vecDistanceBiasAbs.x != 0.0f ) || ( m_vecDistanceBiasAbs.y != 0.0f ) || ( m_vecDistanceBiasAbs.z != 0.0f ); + } + + void Render( CParticleCollection *pParticles ) const; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_CreateWithinSphere, "Position Within Sphere Random", OPERATOR_PI_POSITION ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_CreateWithinSphere ) + DMXELEMENT_UNPACK_FIELD( "distance_min", "0", float, m_fRadiusMin ) + DMXELEMENT_UNPACK_FIELD( "distance_max", "0", float, m_fRadiusMax ) + DMXELEMENT_UNPACK_FIELD( "distance_bias", "1 1 1", Vector, m_vecDistanceBias ) + DMXELEMENT_UNPACK_FIELD( "distance_bias_absolute_value", "0 0 0", Vector, m_vecDistanceBiasAbs ) + DMXELEMENT_UNPACK_FIELD( "bias in local system", "0", bool, m_bLocalCoords ) + DMXELEMENT_UNPACK_FIELD( "control_point_number", "0", int, m_nControlPointNumber ) + DMXELEMENT_UNPACK_FIELD( "speed_min", "0", float, m_fSpeedMin ) + DMXELEMENT_UNPACK_FIELD( "speed_max", "0", float, m_fSpeedMax ) + DMXELEMENT_UNPACK_FIELD( "speed_random_exponent", "1", float, m_fSpeedRandExp ) + DMXELEMENT_UNPACK_FIELD( "speed_in_local_coordinate_system_min", "0 0 0", Vector, m_LocalCoordinateSystemSpeedMin ) + DMXELEMENT_UNPACK_FIELD( "speed_in_local_coordinate_system_max", "0 0 0", Vector, m_LocalCoordinateSystemSpeedMax ) + DMXELEMENT_UNPACK_FIELD( "create in model", "0", int, m_nCreateInModel ) + DMXELEMENT_UNPACK_FIELD( "randomly distribute to highest supplied Control Point", "0", bool, m_bUseHighestEndCP ) + DMXELEMENT_UNPACK_FIELD( "randomly distribution growth time", "0", float, m_flEndCPGrowthTime ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_CreateWithinSphere ) + + +ConVar r_sse_s( "r_sse_s", "1", 0, "sse ins for particle sphere create" ); + +void C_INIT_CreateWithinSphere::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + for( ; nParticleCount--; start_p++ ) + { + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + const float *ct = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + int nCurrentControlPoint = m_nControlPointNumber; + if ( m_bUseHighestEndCP ) + { + //hack for growth time instead of using strength as currenly initializers don't support it. + float flStrength = 1.0; + if ( m_flEndCPGrowthTime != 0.0f ) + { + flStrength = min ( pParticles->m_flCurTime, m_flEndCPGrowthTime ) / m_flEndCPGrowthTime ; + } + int nHighestControlPoint = floor ( pParticles->GetHighestControlPoint() * flStrength ); + nCurrentControlPoint = pParticles->RandomInt( m_nControlPointNumber, nHighestControlPoint ); + } + Vector randpos, randDir; + for( int nTryCtr = 0 ; nTryCtr < 10; nTryCtr++ ) + { + float flLength = pParticles->RandomVectorInUnitSphere( &randpos ); + + // Absolute value and biasing for creating hemispheres and ovoids. + if ( m_bDistanceBiasAbs ) + { + if ( m_vecDistanceBiasAbs.x != 0.0f ) + { + randpos.x = fabs(randpos.x); + } + if ( m_vecDistanceBiasAbs.y != 0.0f ) + { + randpos.y = fabs(randpos.y); + } + if ( m_vecDistanceBiasAbs.z != 0.0f ) + { + randpos.z = fabs(randpos.z); + } + } + randpos *= m_vecDistanceBias; + randpos.NormalizeInPlace(); + + + randDir = randpos; + randpos *= Lerp( flLength, m_fRadiusMin, m_fRadiusMax ); + + if ( !m_bDistanceBias || !m_bLocalCoords ) + { + Vector vecControlPoint; + pParticles->GetControlPointAtTime( nCurrentControlPoint, *ct, &vecControlPoint ); + randpos += vecControlPoint; + } + else + { + matrix3x4_t mat; + pParticles->GetControlPointTransformAtTime( nCurrentControlPoint, *ct, &mat ); + Vector vecTransformLocal = vec3_origin; + VectorTransform( randpos, mat, vecTransformLocal ); + randpos = vecTransformLocal; + } + + // now, force to be in model if we can + if ( + ( m_nCreateInModel == 0 ) || + (g_pParticleSystemMgr->Query()->MovePointInsideControllingObject( + pParticles, pParticles->m_ControlPoints[nCurrentControlPoint].m_pObject, &randpos ) ) ) + break; + } + + xyz[0] = randpos.x; + xyz[4] = randpos.y; + xyz[8] = randpos.z; + + // FIXME: Remove this into a speed setting initializer + if ( pxyz && ( nAttributeWriteMask & PARTICLE_ATTRIBUTE_PREV_XYZ_MASK ) ) + { + Vector poffset(0,0,0); + if ( m_fSpeedMax > 0.0 ) + { + float rand_speed = pParticles->RandomFloatExp( m_fSpeedMin, m_fSpeedMax, m_fSpeedRandExp ); + poffset.x -= rand_speed * randDir.x; + poffset.y -= rand_speed * randDir.y; + poffset.z -= rand_speed * randDir.z; + } + poffset -= + pParticles->RandomFloat( m_LocalCoordinateSystemSpeedMin.x, m_LocalCoordinateSystemSpeedMax.x )* + pParticles->m_ControlPoints[ nCurrentControlPoint ].m_ForwardVector; + poffset -= + pParticles->RandomFloat( m_LocalCoordinateSystemSpeedMin.y, m_LocalCoordinateSystemSpeedMax.y )* + pParticles->m_ControlPoints[ nCurrentControlPoint ].m_RightVector; + poffset -= + pParticles->RandomFloat( m_LocalCoordinateSystemSpeedMin.z, m_LocalCoordinateSystemSpeedMax.z )* + pParticles->m_ControlPoints[ nCurrentControlPoint ].m_UpVector; + + poffset *= pParticles->m_flPreviousDt; + randpos += poffset; + pxyz[0] = randpos.x; + pxyz[4] = randpos.y; + pxyz[8] = randpos.z; + } + } +} + +void C_INIT_CreateWithinSphere::InitNewParticlesBlock( CParticleCollection *pParticles, + int start_block, int n_blocks, int nAttributeWriteMask, + void *pContext ) const +{ + // sse-favorable settings + bool bMustUseScalar = m_bUseHighestEndCP || m_nCreateInModel; + if ( m_bDistanceBias && m_bLocalCoords ) + bMustUseScalar = true; + + if ( ( !bMustUseScalar ) && + // (( nAttributeWriteMask & PARTICLE_ATTRIBUTE_PREV_XYZ_MASK ) == 0 ) && + r_sse_s.GetInt() ) + { + C4VAttributeWriteIterator pXYZ( PARTICLE_ATTRIBUTE_XYZ, pParticles ); + pXYZ += start_block; + C4VAttributeWriteIterator pPrevXYZ( PARTICLE_ATTRIBUTE_PREV_XYZ, pParticles ); + pPrevXYZ += start_block; + CM128AttributeIterator pCT( PARTICLE_ATTRIBUTE_CREATION_TIME, pParticles ); + pCT += start_block; + + // now, calculate the terms we need for interpolating control points + FourVectors v4PrevControlPointPosition; + v4PrevControlPointPosition.DuplicateVector( pParticles->m_ControlPoints[m_nControlPointNumber].m_PrevPosition ); + FourVectors v4ControlPointDelta; + v4ControlPointDelta.DuplicateVector( pParticles->m_ControlPoints[m_nControlPointNumber].m_Position ); + v4ControlPointDelta -= v4PrevControlPointPosition; + + float flOODT = ( pParticles->m_flDt > 0.0 ) ? ( 1.0 / pParticles->m_flDt ) : 0.0; + fltx4 fl4OODt = ReplicateX4( flOODT ); + fltx4 fl4PrevTime = ReplicateX4( pParticles->m_flCurTime - pParticles->m_flDt ); + int nContext = GetSIMDRandContext(); + + FourVectors v4DistanceBias; + v4DistanceBias.DuplicateVector( m_vecDistanceBias ); + FourVectors v4ConditionalAbsMask; + for( int nComp = 0 ; nComp < 3; nComp++ ) + { + v4ConditionalAbsMask[nComp] = ( m_vecDistanceBiasAbs[nComp] > 0 ) ? + LoadAlignedSIMD( ( const float *) g_SIMD_clear_signmask ) : + LoadAlignedSIMD( ( const float *) g_SIMD_AllOnesMask ); + } + fltx4 fl4RadiusMin = ReplicateX4( m_fRadiusMin ); + fltx4 fl4RadiusSpread = ReplicateX4( m_fRadiusMax - m_fRadiusMin ); + int nPowSSEMask = 4.0 * m_fSpeedRandExp; + + bool bDoRandSpeed = + ( m_fSpeedMax > 0. ) || + ( m_LocalCoordinateSystemSpeedMax.x != 0 ) || + ( m_LocalCoordinateSystemSpeedMax.y != 0 ) || + ( m_LocalCoordinateSystemSpeedMax.z != 0 ) || + ( m_LocalCoordinateSystemSpeedMin.x != 0 ) || + ( m_LocalCoordinateSystemSpeedMin.y != 0 ) || + ( m_LocalCoordinateSystemSpeedMin.z != 0 ); + + + fltx4 fl4SpeedMin = ReplicateX4( m_fSpeedMin ); + fltx4 fl4SpeedRange = ReplicateX4( m_fSpeedMax - m_fSpeedMin ); + + fltx4 fl4LocalSpeedMinX = ReplicateX4( m_LocalCoordinateSystemSpeedMin.x ); + fltx4 fl4LocalSpeedXSpread = ReplicateX4( m_LocalCoordinateSystemSpeedMax.x - + m_LocalCoordinateSystemSpeedMin.x ); + fltx4 fl4LocalSpeedMinY = ReplicateX4( m_LocalCoordinateSystemSpeedMin.y ); + fltx4 fl4LocalSpeedYSpread = ReplicateX4( m_LocalCoordinateSystemSpeedMax.y - + m_LocalCoordinateSystemSpeedMin.y ); + fltx4 fl4LocalSpeedMinZ = ReplicateX4( m_LocalCoordinateSystemSpeedMin.z ); + fltx4 fl4LocalSpeedZSpread = ReplicateX4( m_LocalCoordinateSystemSpeedMax.z - + m_LocalCoordinateSystemSpeedMin.z ); + + FourVectors v4CPForward; + v4CPForward.DuplicateVector( pParticles->m_ControlPoints[m_nControlPointNumber].m_ForwardVector ); + FourVectors v4CPUp; + v4CPUp.DuplicateVector( pParticles->m_ControlPoints[m_nControlPointNumber].m_UpVector ); + FourVectors v4CPRight; + v4CPRight.DuplicateVector( pParticles->m_ControlPoints[m_nControlPointNumber].m_RightVector ); + + fltx4 fl4PreviousDt = ReplicateX4( pParticles->m_flPreviousDt ); + + while( n_blocks-- ) + { + FourVectors v4RandPos; + RandomPointInUnitSphere( nContext, v4RandPos ); + + fltx4 fl4Length = v4RandPos.length(); + + // conditional absolute value + v4RandPos.x = AndSIMD( v4RandPos.x, v4ConditionalAbsMask.x ); + v4RandPos.y = AndSIMD( v4RandPos.y, v4ConditionalAbsMask.y ); + v4RandPos.z = AndSIMD( v4RandPos.z, v4ConditionalAbsMask.z ); + + v4RandPos *= v4DistanceBias; + v4RandPos.VectorNormalizeFast(); + + FourVectors v4randDir = v4RandPos; + + // lerp radius + v4RandPos *= AddSIMD( fl4RadiusMin, MulSIMD( fl4Length, fl4RadiusSpread ) ); + v4RandPos += v4PrevControlPointPosition; + + FourVectors cpnt = v4ControlPointDelta; + cpnt *= MulSIMD( SubSIMD( *pCT, fl4PrevTime ), fl4OODt ); + v4RandPos += cpnt; + + *(pXYZ) = v4RandPos; + + if ( nAttributeWriteMask & PARTICLE_ATTRIBUTE_PREV_XYZ_MASK ) + { + if ( bDoRandSpeed ) + { + fltx4 fl4Rand_speed = Pow_FixedPoint_Exponent_SIMD( RandSIMD( nContext ), nPowSSEMask ); + fl4Rand_speed = AddSIMD( fl4SpeedMin, MulSIMD( fl4SpeedRange, fl4Rand_speed ) ); + v4randDir *= fl4Rand_speed; + + // local speed + FourVectors v4LocalOffset = v4CPForward; + v4LocalOffset *= AddSIMD( fl4LocalSpeedMinX, + MulSIMD( fl4LocalSpeedXSpread, RandSIMD( nContext ) ) ); + v4randDir += v4LocalOffset; + + v4LocalOffset = v4CPRight; + v4LocalOffset *= AddSIMD( fl4LocalSpeedMinY, + MulSIMD( fl4LocalSpeedYSpread, RandSIMD( nContext ) ) ); + v4randDir += v4LocalOffset; + + + v4LocalOffset = v4CPUp; + v4LocalOffset *= AddSIMD( fl4LocalSpeedMinZ, + MulSIMD( fl4LocalSpeedZSpread, RandSIMD( nContext ) ) ); + v4randDir += v4LocalOffset; + v4randDir *= fl4PreviousDt; + v4RandPos -= v4randDir; + } + *(pPrevXYZ) = v4RandPos; + + } + + + + ++pXYZ; + ++pPrevXYZ; + ++pCT; + } + ReleaseSIMDRandContext( nContext ); + + } + else + CParticleOperatorInstance::InitNewParticlesBlock( pParticles, start_block, n_blocks, nAttributeWriteMask, pContext ); + +} + + +//----------------------------------------------------------------------------- +// Render visualization +//----------------------------------------------------------------------------- +void C_INIT_CreateWithinSphere::Render( CParticleCollection *pParticles ) const +{ + Vector vecOrigin; + pParticles->GetControlPointAtTime( m_nControlPointNumber, pParticles->m_flCurTime, &vecOrigin ); + RenderWireframeSphere( vecOrigin, m_fRadiusMin, 16, 8, Color( 192, 192, 0, 255 ), false ); + RenderWireframeSphere( vecOrigin, m_fRadiusMax, 16, 8, Color( 128, 128, 0, 255 ), false ); +} + + + + +class C_INIT_CreateWithinBox : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_CreateWithinBox ); + + Vector m_vecMin; + Vector m_vecMax; + int m_nControlPointNumber; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + return 1ULL << m_nControlPointNumber; + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + + void Render( CParticleCollection *pParticles ) const; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_CreateWithinBox, "Position Within Box Random", OPERATOR_PI_POSITION ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_CreateWithinBox ) + DMXELEMENT_UNPACK_FIELD( "min", "0 0 0", Vector, m_vecMin ) + DMXELEMENT_UNPACK_FIELD( "max", "0 0 0", Vector, m_vecMax ) + DMXELEMENT_UNPACK_FIELD( "control point number", "0", int, m_nControlPointNumber ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_CreateWithinBox ) + + +void C_INIT_CreateWithinBox::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + int nControlPointNumber = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nControlPointNumber ) ); + for( ; nParticleCount--; start_p++ ) + { + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + const float *ct = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + + Vector randpos; + pParticles->RandomVector( m_vecMin, m_vecMax, &randpos ); + + Vector vecControlPoint; + pParticles->GetControlPointAtTime( nControlPointNumber, *ct, &vecControlPoint ); + randpos += vecControlPoint; + + xyz[0] = randpos.x; + xyz[4] = randpos.y; + xyz[8] = randpos.z; + if ( pxyz && ( nAttributeWriteMask & PARTICLE_ATTRIBUTE_PREV_XYZ_MASK ) ) + { + pxyz[0] = randpos.x; + pxyz[4] = randpos.y; + pxyz[8] = randpos.z; + } + } +} + +//----------------------------------------------------------------------------- +// Render visualization +//----------------------------------------------------------------------------- +void C_INIT_CreateWithinBox::Render( CParticleCollection *pParticles ) const +{ + Vector vecOrigin; + pParticles->GetControlPointAtTime( m_nControlPointNumber, pParticles->m_flCurTime, &vecOrigin ); + RenderWireframeBox( vecOrigin, vec3_angle, m_vecMin, m_vecMax, Color( 192, 192, 0, 255 ), false ); +} + + + +//----------------------------------------------------------------------------- +// Position Offset Initializer +// offsets initial position of particles within a random vector range, +// while still respecting spherical/conical spacial and velocity initialization +//----------------------------------------------------------------------------- +class C_INIT_PositionOffset : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_PositionOffset ); + + Vector m_OffsetMin; + Vector m_OffsetMax; + int m_nControlPointNumber; + bool m_bLocalCoords; + bool m_bProportional; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK | PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | PARTICLE_ATTRIBUTE_RADIUS_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + return 1ULL << m_nControlPointNumber; + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nControlPointNumber = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nControlPointNumber ) ); + } + + bool InitMultipleOverride ( void ) { return true; } + + void Render( CParticleCollection *pParticles ) const; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_PositionOffset, "Position Modify Offset Random", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_PositionOffset ) + DMXELEMENT_UNPACK_FIELD( "control_point_number", "0", int, m_nControlPointNumber ) + DMXELEMENT_UNPACK_FIELD( "offset min", "0 0 0", Vector, m_OffsetMin ) + DMXELEMENT_UNPACK_FIELD( "offset max", "0 0 0", Vector, m_OffsetMax ) + DMXELEMENT_UNPACK_FIELD( "offset in local space 0/1", "0", bool, m_bLocalCoords ) + DMXELEMENT_UNPACK_FIELD( "offset proportional to radius 0/1", "0", bool, m_bProportional ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_PositionOffset ) + + +void C_INIT_PositionOffset::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + for( ; nParticleCount--; start_p++ ) + { + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + const float *ct = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + const float *radius = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_RADIUS, start_p ); + + Vector randpos; + + if ( m_bProportional ) + { + pParticles->RandomVector( (m_OffsetMin * *radius), (m_OffsetMax * *radius), &randpos ); + } + else + { + pParticles->RandomVector( m_OffsetMin, m_OffsetMax, &randpos ); + } + + if ( m_bLocalCoords ) + { + matrix3x4_t mat; + pParticles->GetControlPointTransformAtTime( m_nControlPointNumber, *ct, &mat ); + Vector vecTransformLocal = vec3_origin; + VectorRotate( randpos, mat, vecTransformLocal ); + randpos = vecTransformLocal; + } + + xyz[0] += randpos.x; + xyz[4] += randpos.y; + xyz[8] += randpos.z; + pxyz[0] += randpos.x; + pxyz[4] += randpos.y; + pxyz[8] += randpos.z; + } +} + + +//----------------------------------------------------------------------------- +// Render visualization +//----------------------------------------------------------------------------- +void C_INIT_PositionOffset::Render( CParticleCollection *pParticles ) const +{ + Vector vecOrigin (0,0,0); + Vector vecMinExtent = m_OffsetMin; + Vector vecMaxExtent = m_OffsetMax; + if ( m_bLocalCoords ) + { + matrix3x4_t mat; + pParticles->GetControlPointTransformAtTime( m_nControlPointNumber, pParticles->m_flCurTime, &mat ); + VectorRotate( m_OffsetMin, mat, vecMinExtent ); + VectorRotate( m_OffsetMax, mat, vecMaxExtent ); + } + else + { + pParticles->GetControlPointAtTime( m_nControlPointNumber, pParticles->m_flCurTime, &vecOrigin ); + } + RenderWireframeBox( vecOrigin, vec3_angle, vecMinExtent , vecMaxExtent , Color( 192, 192, 0, 255 ), false ); +} + + +//----------------------------------------------------------------------------- +// +// Velocity-based Operators +// +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Random velocity initializer +//----------------------------------------------------------------------------- +class C_INIT_VelocityRandom : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_VelocityRandom ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_PREV_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_PREV_XYZ_MASK | PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + if ( m_bHasLocalSpeed ) + return 1ULL << m_nControlPointNumber; + return 0; + } + + virtual bool InitMultipleOverride() { return true; } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nControlPointNumber = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nControlPointNumber ) ); + m_bHasLocalSpeed = ( m_LocalCoordinateSystemSpeedMin != vec3_origin ) || ( m_LocalCoordinateSystemSpeedMax != vec3_origin ); + if ( m_fSpeedMax < m_fSpeedMin ) + { + V_swap( m_fSpeedMin, m_fSpeedMax ); + } + } + +private: + int m_nControlPointNumber; + float m_fSpeedMin; + float m_fSpeedMax; + Vector m_LocalCoordinateSystemSpeedMin; + Vector m_LocalCoordinateSystemSpeedMax; + bool m_bHasLocalSpeed; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_VelocityRandom, "Velocity Random", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_VelocityRandom ) + DMXELEMENT_UNPACK_FIELD( "control_point_number", "0", int, m_nControlPointNumber ) + DMXELEMENT_UNPACK_FIELD( "random_speed_min", "0", float, m_fSpeedMin ) + DMXELEMENT_UNPACK_FIELD( "random_speed_max", "0", float, m_fSpeedMax ) + DMXELEMENT_UNPACK_FIELD( "speed_in_local_coordinate_system_min", "0 0 0", Vector, m_LocalCoordinateSystemSpeedMin ) + DMXELEMENT_UNPACK_FIELD( "speed_in_local_coordinate_system_max", "0 0 0", Vector, m_LocalCoordinateSystemSpeedMax ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_VelocityRandom ) + + +void C_INIT_VelocityRandom::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + for( ; nParticleCount--; start_p++ ) + { + const float *ct = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + + Vector vecVelocity( 0.0f, 0.0f, 0.0f ); + if ( m_bHasLocalSpeed ) + { + Vector vecRandomSpeed, vecForward, vecUp, vecRight; + pParticles->RandomVector( m_LocalCoordinateSystemSpeedMin, m_LocalCoordinateSystemSpeedMax, &vecRandomSpeed ); + pParticles->GetControlPointOrientationAtTime( m_nControlPointNumber, *ct, &vecForward, &vecRight, &vecUp ); + VectorMA( vecVelocity, vecRandomSpeed.x, vecForward, vecVelocity ); + VectorMA( vecVelocity, -vecRandomSpeed.y, vecRight, vecVelocity ); + VectorMA( vecVelocity, vecRandomSpeed.z, vecUp, vecVelocity ); + } + + if ( m_fSpeedMax > 0.0f ) + { + Vector vecRandomSpeed; + pParticles->RandomVector( m_fSpeedMin, m_fSpeedMax, &vecRandomSpeed ); + vecVelocity += vecRandomSpeed; + } + + vecVelocity *= pParticles->m_flPreviousDt; + pxyz[0] -= vecVelocity.x; + pxyz[4] -= vecVelocity.y; + pxyz[8] -= vecVelocity.z; + } +} + + +//----------------------------------------------------------------------------- +// Initial Velocity Noise Operator +//----------------------------------------------------------------------------- +class C_INIT_InitialVelocityNoise : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_InitialVelocityNoise ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_PREV_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK | PARTICLE_ATTRIBUTE_XYZ_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + return 1ULL << m_nControlPointNumber; + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + + void InitNewParticlesBlock( CParticleCollection *pParticles, + int start_block, int n_blocks, int nAttributeWriteMask, + void *pContext ) const; + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nControlPointNumber = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nControlPointNumber ) ); + } + + virtual bool InitMultipleOverride() { return true; } + + Vector m_vecAbsVal, m_vecAbsValInv, m_vecOffsetLoc; + float m_flOffset; + Vector m_vecOutputMin; + Vector m_vecOutputMax; + float m_flNoiseScale, m_flNoiseScaleLoc; + int nRemainingBlocks, m_nControlPointNumber; + bool m_bLocalSpace; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_InitialVelocityNoise, "Velocity Noise", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_InitialVelocityNoise ) + DMXELEMENT_UNPACK_FIELD( "Control Point Number","0",int,m_nControlPointNumber) + DMXELEMENT_UNPACK_FIELD( "Time Noise Coordinate Scale","1",float,m_flNoiseScale) + DMXELEMENT_UNPACK_FIELD( "Spatial Noise Coordinate Scale","0.01",float,m_flNoiseScaleLoc) + DMXELEMENT_UNPACK_FIELD( "Time Coordinate Offset","0", float, m_flOffset ) + DMXELEMENT_UNPACK_FIELD( "Spatial Coordinate Offset","0 0 0", Vector, m_vecOffsetLoc ) + DMXELEMENT_UNPACK_FIELD( "Absolute Value","0 0 0", Vector, m_vecAbsVal ) + DMXELEMENT_UNPACK_FIELD( "Invert Abs Value","0 0 0", Vector, m_vecAbsValInv ) + DMXELEMENT_UNPACK_FIELD( "output minimum","0 0 0", Vector, m_vecOutputMin ) + DMXELEMENT_UNPACK_FIELD( "output maximum","1 1 1", Vector, m_vecOutputMax ) + DMXELEMENT_UNPACK_FIELD( "Apply Velocity in Local Space (0/1)","0", bool, m_bLocalSpace ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_InitialVelocityNoise ); + + +void C_INIT_InitialVelocityNoise::InitNewParticlesBlock( CParticleCollection *pParticles, + int start_block, int n_blocks, int nAttributeWriteMask, + void *pContext ) const +{ + float flAbsScaleX, flAbsScaleY, flAbsScaleZ; + fltx4 fl4AbsValX, fl4AbsValY, fl4AbsValZ; + fl4AbsValX = CmpEqSIMD( Four_Zeros, Four_Zeros ); + fl4AbsValY = fl4AbsValX; + fl4AbsValZ = fl4AbsValX; + flAbsScaleX = 0.5; + flAbsScaleY = 0.5; + flAbsScaleZ = 0.5; + + // Set up single if check for absolute value inversion inside the loop + bool m_bNoiseAbs = ( m_vecAbsValInv.x != 0.0f ) || ( m_vecAbsValInv.y != 0.0f ) || ( m_vecAbsValInv.z != 0.0f ); + // Set up values for more optimal absolute value calculations inside the loop + if ( m_vecAbsVal.x != 0.0f ) + { + fl4AbsValX = LoadAlignedSIMD( (float *) g_SIMD_clear_signmask ); + flAbsScaleX = 1.0; + } + if ( m_vecAbsVal.y != 0.0f ) + { + fl4AbsValY = LoadAlignedSIMD( (float *) g_SIMD_clear_signmask ); + flAbsScaleY = 1.0; + } + if ( m_vecAbsVal.z != 0.0f ) + { + fl4AbsValZ = LoadAlignedSIMD( (float *) g_SIMD_clear_signmask ); + flAbsScaleZ = 1.0; + } + + float ValueScaleX, ValueScaleY, ValueScaleZ, ValueBaseX, ValueBaseY, ValueBaseZ; + + ValueScaleX = ( flAbsScaleX *(m_vecOutputMax.x-m_vecOutputMin.x ) ); + ValueBaseX = (m_vecOutputMin.x+ ( ( 1.0 - flAbsScaleX ) *( m_vecOutputMax.x-m_vecOutputMin.x ) ) ); + + ValueScaleY = ( flAbsScaleY *(m_vecOutputMax.y-m_vecOutputMin.y ) ); + ValueBaseY = (m_vecOutputMin.y+ ( ( 1.0 - flAbsScaleY ) *( m_vecOutputMax.y-m_vecOutputMin.y ) ) ); + + ValueScaleZ = ( flAbsScaleZ *(m_vecOutputMax.z-m_vecOutputMin.z ) ); + ValueBaseZ = (m_vecOutputMin.z+ ( ( 1.0 - flAbsScaleZ ) *( m_vecOutputMax.z-m_vecOutputMin.z ) ) ); + + fltx4 fl4ValueBaseX = ReplicateX4( ValueBaseX ); + fltx4 fl4ValueBaseY = ReplicateX4( ValueBaseY ); + fltx4 fl4ValueBaseZ = ReplicateX4( ValueBaseZ ); + + fltx4 fl4ValueScaleX = ReplicateX4( ValueScaleX ); + fltx4 fl4ValueScaleY = ReplicateX4( ValueScaleY ); + fltx4 fl4ValueScaleZ = ReplicateX4( ValueScaleZ ); + + float CoordScale = m_flNoiseScale; + float CoordScaleLoc = m_flNoiseScaleLoc; + + Vector ofs_y = Vector( 100000.5, 300000.25, 9000000.75 ); + Vector ofs_z = Vector( 110000.25, 310000.75, 9100000.5 ); + + size_t attr_stride; + + const FourVectors *xyz = pParticles->Get4VAttributePtr( PARTICLE_ATTRIBUTE_XYZ, &attr_stride ); + xyz += attr_stride * start_block; + FourVectors *pxyz = pParticles->Get4VAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, &attr_stride ); + pxyz += attr_stride * start_block; + const fltx4 *pCreationTime = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, &attr_stride ); + pCreationTime += attr_stride * start_block; + + // setup + fltx4 fl4Offset = ReplicateX4( m_flOffset ); + FourVectors fvOffsetLoc; + fvOffsetLoc.DuplicateVector( m_vecOffsetLoc ); + CParticleSIMDTransformation CPTransform; + float flCreationTime = SubFloat( *pCreationTime, 0 ); + pParticles->GetControlPointTransformAtTime( m_nControlPointNumber, flCreationTime, &CPTransform ); + + while( n_blocks-- ) + { + FourVectors fvCoordLoc = *xyz; + fvCoordLoc += fvOffsetLoc; + + FourVectors fvCoord; + fvCoord.x = AddSIMD(*pCreationTime, fl4Offset); + fvCoord.y = AddSIMD(*pCreationTime, fl4Offset); + fvCoord.z = AddSIMD(*pCreationTime, fl4Offset); + fvCoordLoc *= CoordScaleLoc; + fvCoord *= CoordScale; + fvCoord += fvCoordLoc; + + FourVectors fvCoord2 = fvCoord; + FourVectors fvOffsetTemp; + fvOffsetTemp.DuplicateVector( ofs_y ); + fvCoord2 += fvOffsetTemp; + FourVectors fvCoord3 = fvCoord; + fvOffsetTemp.DuplicateVector( ofs_z ); + fvCoord3 += fvOffsetTemp; + + fltx4 fl4NoiseX; + fltx4 fl4NoiseY; + fltx4 fl4NoiseZ; + + fl4NoiseX = NoiseSIMD( fvCoord ); + + fl4NoiseY = NoiseSIMD( fvCoord2 ); + + fl4NoiseZ = NoiseSIMD( fvCoord3 ); + + fl4NoiseX = AndSIMD ( fl4NoiseX, fl4AbsValX ); + fl4NoiseY = AndSIMD ( fl4NoiseY, fl4AbsValY ); + fl4NoiseZ = AndSIMD ( fl4NoiseZ, fl4AbsValZ ); + + if ( m_bNoiseAbs ) + { + if ( m_vecAbsValInv.x != 0.0f ) + { + fl4NoiseX = SubSIMD( Four_Ones, fl4NoiseX ); + } + + if ( m_vecAbsValInv.y != 0.0f ) + { + fl4NoiseY = SubSIMD( Four_Ones, fl4NoiseY ); + } + if ( m_vecAbsValInv.z != 0.0f ) + { + fl4NoiseZ = SubSIMD( Four_Ones, fl4NoiseZ ); + } + } + + FourVectors fvOffset; + + fvOffset.x = AddSIMD( fl4ValueBaseX, ( MulSIMD( fl4ValueScaleX , fl4NoiseX ) ) ); + fvOffset.y = AddSIMD( fl4ValueBaseY, ( MulSIMD( fl4ValueScaleY , fl4NoiseY ) ) ); + fvOffset.z = AddSIMD( fl4ValueBaseZ, ( MulSIMD( fl4ValueScaleZ , fl4NoiseZ ) ) ); + + fvOffset *= pParticles->m_flPreviousDt; + + if ( m_bLocalSpace ) + { + CPTransform.VectorRotate( fvOffset ); + } + + *pxyz -= fvOffset; + + xyz += attr_stride; + pxyz += attr_stride; + pCreationTime += attr_stride; + + } +} + + +void C_INIT_InitialVelocityNoise::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + float flAbsScaleX, flAbsScaleY, flAbsScaleZ; + int nAbsValX, nAbsValY, nAbsValZ; + nAbsValX = 0xffffffff; + nAbsValY = 0xffffffff; + nAbsValZ = 0xffffffff; + flAbsScaleX = 0.5; + flAbsScaleY = 0.5; + flAbsScaleZ = 0.5; + // Set up single if check for absolute value inversion inside the loop + bool m_bNoiseAbs = ( m_vecAbsValInv.x != 0.0f ) || ( m_vecAbsValInv.y != 0.0f ) || ( m_vecAbsValInv.z != 0.0f ); + // Set up values for more optimal absolute value calculations inside the loop + if ( m_vecAbsVal.x != 0.0f ) + { + nAbsValX = 0x7fffffff; + flAbsScaleX = 1.0; + } + if ( m_vecAbsVal.y != 0.0f ) + { + nAbsValY = 0x7fffffff; + flAbsScaleY = 1.0; + } + if ( m_vecAbsVal.z != 0.0f ) + { + nAbsValZ = 0x7fffffff; + flAbsScaleZ = 1.0; + } + + float ValueScaleX, ValueScaleY, ValueScaleZ, ValueBaseX, ValueBaseY, ValueBaseZ; + + ValueScaleX = ( flAbsScaleX *(m_vecOutputMax.x-m_vecOutputMin.x ) ); + ValueBaseX = (m_vecOutputMin.x+ ( ( 1.0 - flAbsScaleX ) *( m_vecOutputMax.x-m_vecOutputMin.x ) ) ); + + ValueScaleY = ( flAbsScaleY *(m_vecOutputMax.y-m_vecOutputMin.y ) ); + ValueBaseY = (m_vecOutputMin.y+ ( ( 1.0 - flAbsScaleY ) *( m_vecOutputMax.y-m_vecOutputMin.y ) ) ); + + ValueScaleZ = ( flAbsScaleZ *(m_vecOutputMax.z-m_vecOutputMin.z ) ); + ValueBaseZ = (m_vecOutputMin.z+ ( ( 1.0 - flAbsScaleZ ) *( m_vecOutputMax.z-m_vecOutputMin.z ) ) ); + + + float CoordScale = m_flNoiseScale; + float CoordScaleLoc = m_flNoiseScaleLoc; + + Vector ofs_y = Vector( 100000.5, 300000.25, 9000000.75 ); + Vector ofs_z = Vector( 110000.25, 310000.75, 9100000.5 ); + + for( ; nParticleCount--; start_p++ ) + { + const float *xyz = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_XYZ, start_p ); + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + const float *pCreationTime = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + + Vector Coord, Coord2, Coord3, CoordLoc; + SetVectorFromAttribute( CoordLoc, xyz ); + CoordLoc += m_vecOffsetLoc; + + float Offset = m_flOffset; + Coord = Vector ( (*pCreationTime + Offset), (*pCreationTime + Offset), (*pCreationTime + Offset) ); + + Coord *= CoordScale; + CoordLoc *= CoordScaleLoc; + Coord += CoordLoc; + + Coord2 = ( Coord ); + Coord3 = ( Coord ); + + fltx4 flNoise128; + FourVectors fvNoise; + + fvNoise.DuplicateVector( Coord ); + flNoise128 = NoiseSIMD( fvNoise ); + float flNoiseX = SubFloat( flNoise128, 0 ); + + fvNoise.DuplicateVector( Coord2 + ofs_y ); + flNoise128 = NoiseSIMD( fvNoise ); + float flNoiseY = SubFloat( flNoise128, 0 ); + + fvNoise.DuplicateVector( Coord3 + ofs_z ); + flNoise128 = NoiseSIMD( fvNoise ); + float flNoiseZ = SubFloat( flNoise128, 0 ); + + *( (int *) &flNoiseX) &= nAbsValX; + *( (int *) &flNoiseY) &= nAbsValY; + *( (int *) &flNoiseZ) &= nAbsValZ; + + if ( m_bNoiseAbs ) + { + if ( m_vecAbsValInv.x != 0.0f ) + { + flNoiseX = 1.0 - flNoiseX; + } + + if ( m_vecAbsValInv.y != 0.0f ) + { + flNoiseY = 1.0 - flNoiseY; + } + if ( m_vecAbsValInv.z != 0.0f ) + { + flNoiseZ = 1.0 - flNoiseZ; + } + } + + Vector poffset; + poffset.x = ( ValueBaseX + ( ValueScaleX * flNoiseX ) ); + poffset.y = ( ValueBaseY + ( ValueScaleY * flNoiseY ) ); + poffset.z = ( ValueBaseZ + ( ValueScaleZ * flNoiseZ ) ); + + poffset *= pParticles->m_flPreviousDt; + + if ( m_bLocalSpace ) + { + matrix3x4_t mat; + pParticles->GetControlPointTransformAtTime( m_nControlPointNumber, *pCreationTime, &mat ); + Vector vecTransformLocal = vec3_origin; + VectorRotate( poffset, mat, vecTransformLocal ); + poffset = vecTransformLocal; + } + pxyz[0] -= poffset.x; + pxyz[4] -= poffset.y; + pxyz[8] -= poffset.z; + } +} + + + + +class C_INIT_RandomLifeTime : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_RandomLifeTime ); + + float m_fLifetimeMin; + float m_fLifetimeMax; + float m_fLifetimeRandExponent; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const; + + void InitNewParticlesBlock( CParticleCollection *pParticles, + int start_block, int n_blocks, int nAttributeWriteMask, + void *pContext ) const + { + if ( m_fLifetimeRandExponent != 1.0f ) + { + InitScalarAttributeRandomRangeExpBlock( PARTICLE_ATTRIBUTE_LIFE_DURATION, + m_fLifetimeMin, m_fLifetimeMax, m_fLifetimeRandExponent, + pParticles, start_block, n_blocks ); + } + else + { + InitScalarAttributeRandomRangeBlock( PARTICLE_ATTRIBUTE_LIFE_DURATION, + m_fLifetimeMin, m_fLifetimeMax, pParticles, start_block, n_blocks ); + } + + } + +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_RandomLifeTime, "Lifetime Random", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomLifeTime ) + DMXELEMENT_UNPACK_FIELD( "lifetime_min", "0", float, m_fLifetimeMin ) + DMXELEMENT_UNPACK_FIELD( "lifetime_max", "0", float, m_fLifetimeMax ) + DMXELEMENT_UNPACK_FIELD( "lifetime_random_exponent", "1", float, m_fLifetimeRandExponent ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomLifeTime ) + +void C_INIT_RandomLifeTime::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + for( ; nParticleCount--; start_p++ ) + { + float *dtime = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_LIFE_DURATION, start_p ); + *dtime = pParticles->RandomFloatExp( m_fLifetimeMin, m_fLifetimeMax, m_fLifetimeRandExponent ); + } +} + + +//----------------------------------------------------------------------------- +// Random radius +//----------------------------------------------------------------------------- +class C_INIT_RandomRadius : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_RandomRadius ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_RADIUS_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const; + + virtual void InitNewParticlesBlock( CParticleCollection *pParticles, + int start_block, int n_blocks, int nAttributeWriteMask, + void *pContext ) const + { + if ( m_flRadiusRandExponent != 1.0f ) + { + InitScalarAttributeRandomRangeExpBlock( PARTICLE_ATTRIBUTE_RADIUS, + m_flRadiusMin, m_flRadiusMax, m_flRadiusRandExponent, + pParticles, start_block, n_blocks ); + } + else + { + InitScalarAttributeRandomRangeBlock( PARTICLE_ATTRIBUTE_RADIUS, + m_flRadiusMin, m_flRadiusMax, + pParticles, start_block, n_blocks ); + } + + } + + float m_flRadiusMin; + float m_flRadiusMax; + float m_flRadiusRandExponent; +}; + + +DEFINE_PARTICLE_OPERATOR( C_INIT_RandomRadius, "Radius Random", OPERATOR_PI_RADIUS ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomRadius ) + DMXELEMENT_UNPACK_FIELD( "radius_min", "1", float, m_flRadiusMin ) + DMXELEMENT_UNPACK_FIELD( "radius_max", "1", float, m_flRadiusMax ) + DMXELEMENT_UNPACK_FIELD( "radius_random_exponent", "1", float, m_flRadiusRandExponent ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomRadius ) + +void C_INIT_RandomRadius::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const +{ + for( ; nParticleCount--; start_p++ ) + { + float *r = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_RADIUS, start_p ); + *r = pParticles->RandomFloatExp( m_flRadiusMin, m_flRadiusMax, m_flRadiusRandExponent ); + } +} + + +//----------------------------------------------------------------------------- +// Random alpha +//----------------------------------------------------------------------------- +class C_INIT_RandomAlpha : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_RandomAlpha ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_ALPHA_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_flAlphaMin = m_nAlphaMin / 255.0f; + m_flAlphaMax = m_nAlphaMax / 255.0f; + } + + virtual void InitNewParticlesBlock( CParticleCollection *pParticles, + int start_block, int n_blocks, int nAttributeWriteMask, + void *pContext ) const + { + if ( m_flAlphaRandExponent != 1.0f ) + { + InitScalarAttributeRandomRangeExpBlock( PARTICLE_ATTRIBUTE_ALPHA, + m_flAlphaMin, m_flAlphaMax, m_flAlphaRandExponent, + pParticles, start_block, n_blocks ); + } + else + { + InitScalarAttributeRandomRangeBlock( PARTICLE_ATTRIBUTE_ALPHA, + m_flAlphaMin, m_flAlphaMax, + pParticles, start_block, n_blocks ); + } + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, int nParticleCount, int nAttributeWriteMask, void *pContext ) const + { + for( ; nParticleCount--; start_p++ ) + { + float *pAlpha = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_ALPHA, start_p ); + *pAlpha = pParticles->RandomFloatExp( m_flAlphaMin, m_flAlphaMax, m_flAlphaRandExponent ); + } + } + + int m_nAlphaMin; + int m_nAlphaMax; + float m_flAlphaMin; + float m_flAlphaMax; + float m_flAlphaRandExponent; +}; + + +DEFINE_PARTICLE_OPERATOR( C_INIT_RandomAlpha, "Alpha Random", OPERATOR_PI_ALPHA ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomAlpha ) + DMXELEMENT_UNPACK_FIELD( "alpha_min", "255", int, m_nAlphaMin ) + DMXELEMENT_UNPACK_FIELD( "alpha_max", "255", int, m_nAlphaMax ) + DMXELEMENT_UNPACK_FIELD( "alpha_random_exponent", "1", float, m_flAlphaRandExponent ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomAlpha ) + + +//----------------------------------------------------------------------------- +// Random rotation +//----------------------------------------------------------------------------- +class CGeneralRandomRotation : public CParticleOperatorInstance +{ +protected: + virtual int GetAttributeToInit( void ) const = 0; + + uint32 GetWrittenAttributes( void ) const + { + return (1 << GetAttributeToInit() ); + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_flRadians = m_flDegrees * ( M_PI / 180.0f ); + m_flRadiansMin = m_flDegreesMin * ( M_PI / 180.0f ); + m_flRadiansMax = m_flDegreesMax * ( M_PI / 180.0f ); + } + + virtual void InitNewParticlesBlock( CParticleCollection *pParticles, + int start_block, int n_blocks, int nAttributeWriteMask, + void *pContext ) const + { + if ( m_flRotationRandExponent != 1.0f ) + { + InitScalarAttributeRandomRangeExpBlock( GetAttributeToInit(), + m_flRadiansMin, m_flRadiansMax, m_flRotationRandExponent, + pParticles, start_block, n_blocks ); + } + else + { + InitScalarAttributeRandomRangeBlock( GetAttributeToInit(), + m_flRadiansMin, m_flRadiansMax, + pParticles, start_block, n_blocks ); + } + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, int nParticleCount, int nAttributeWriteMask, void *pContext ) const + { + for( ; nParticleCount--; start_p++ ) + { + float *drot = pParticles->GetFloatAttributePtrForWrite( GetAttributeToInit(), start_p ); + *drot = m_flRadians + pParticles->RandomFloatExp( m_flRadiansMin, m_flRadiansMax, m_flRotationRandExponent ); + } + } + + // User-specified range + float m_flDegreesMin; + float m_flDegreesMax; + float m_flDegrees; + + // Converted range + float m_flRadiansMin; + float m_flRadiansMax; + float m_flRadians; + float m_flRotationRandExponent; +}; + + +class CAddGeneralRandomRotation : public CParticleOperatorInstance +{ +protected: + virtual int GetAttributeToInit( void ) const = 0; + + uint32 GetWrittenAttributes( void ) const + { + return (1 << GetAttributeToInit() ); + } + + uint32 GetReadAttributes( void ) const + { + return (1 << GetAttributeToInit() ); + } + + virtual bool InitMultipleOverride() { return true; } + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_flRadians = m_flDegrees * ( M_PI / 180.0f ); + m_flRadiansMin = m_flDegreesMin * ( M_PI / 180.0f ); + m_flRadiansMax = m_flDegreesMax * ( M_PI / 180.0f ); + } + + virtual void InitNewParticlesBlock( CParticleCollection *pParticles, + int start_block, int n_blocks, int nAttributeWriteMask, + void *pContext ) const + { + AddScalarAttributeRandomRangeBlock( GetAttributeToInit(), + m_flRadiansMin, m_flRadiansMax, m_flRotationRandExponent, + pParticles, start_block, n_blocks, m_bRandomlyFlipDirection ); + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, int nParticleCount, int nAttributeWriteMask, void *pContext ) const + { + if ( !m_bRandomlyFlipDirection ) + { + for( ; nParticleCount--; start_p++ ) + { + float *pAttr = pParticles->GetFloatAttributePtrForWrite( GetAttributeToInit(), start_p ); + *pAttr += m_flRadians + pParticles->RandomFloatExp( m_flRadiansMin, m_flRadiansMax, m_flRotationRandExponent ); + } + } + else + { + for( ; nParticleCount--; start_p++ ) + { + float *pAttr = pParticles->GetFloatAttributePtrForWrite( GetAttributeToInit(), start_p ); + float flSpeed = m_flRadians + pParticles->RandomFloatExp( m_flRadiansMin, m_flRadiansMax, m_flRotationRandExponent ); + bool bFlip = ( pParticles->RandomFloat( -1.0f, 1.0f ) >= 0.0f ); + *pAttr += bFlip ? -flSpeed : flSpeed; + } + } + } + + // User-specified range + float m_flDegreesMin; + float m_flDegreesMax; + float m_flDegrees; + + // Converted range + float m_flRadiansMin; + float m_flRadiansMax; + float m_flRadians; + float m_flRotationRandExponent; + bool m_bRandomlyFlipDirection; +}; + + +//----------------------------------------------------------------------------- +// Random rotation +//----------------------------------------------------------------------------- +class C_INIT_RandomRotation : public CGeneralRandomRotation +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_RandomRotation ); + + virtual int GetAttributeToInit( void ) const + { + return PARTICLE_ATTRIBUTE_ROTATION; + } +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_RandomRotation, "Rotation Random", OPERATOR_PI_ROTATION ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomRotation ) + DMXELEMENT_UNPACK_FIELD( "rotation_initial", "0", float, m_flDegrees ) + DMXELEMENT_UNPACK_FIELD( "rotation_offset_min", "0", float, m_flDegreesMin ) + DMXELEMENT_UNPACK_FIELD( "rotation_offset_max", "360", float, m_flDegreesMax ) + DMXELEMENT_UNPACK_FIELD( "rotation_random_exponent", "1", float, m_flRotationRandExponent ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomRotation ) + + +//----------------------------------------------------------------------------- +// Random rotation speed +//----------------------------------------------------------------------------- +class C_INIT_RandomRotationSpeed : public CAddGeneralRandomRotation +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_RandomRotationSpeed ); + + virtual int GetAttributeToInit( void ) const + { + return PARTICLE_ATTRIBUTE_ROTATION_SPEED; + } +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_RandomRotationSpeed, "Rotation Speed Random", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomRotationSpeed ) + DMXELEMENT_UNPACK_FIELD( "rotation_speed_constant", "0", float, m_flDegrees ) + DMXELEMENT_UNPACK_FIELD( "rotation_speed_random_min", "0", float, m_flDegreesMin ) + DMXELEMENT_UNPACK_FIELD( "rotation_speed_random_max", "360", float, m_flDegreesMax ) + DMXELEMENT_UNPACK_FIELD( "rotation_speed_random_exponent", "1", float, m_flRotationRandExponent ) + DMXELEMENT_UNPACK_FIELD( "randomly_flip_direction", "1", bool, m_bRandomlyFlipDirection ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomRotationSpeed ) + + +//----------------------------------------------------------------------------- +// Random yaw +//----------------------------------------------------------------------------- +class C_INIT_RandomYaw : public CGeneralRandomRotation +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_RandomYaw ); + + virtual int GetAttributeToInit( void ) const + { + return PARTICLE_ATTRIBUTE_YAW; + } +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_RandomYaw, "Rotation Yaw Random", OPERATOR_PI_YAW ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomYaw ) + DMXELEMENT_UNPACK_FIELD( "yaw_initial", "0", float, m_flDegrees ) + DMXELEMENT_UNPACK_FIELD( "yaw_offset_min", "0", float, m_flDegreesMin ) + DMXELEMENT_UNPACK_FIELD( "yaw_offset_max", "360", float, m_flDegreesMax ) + DMXELEMENT_UNPACK_FIELD( "yaw_random_exponent", "1", float, m_flRotationRandExponent ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomYaw ) + + +//----------------------------------------------------------------------------- +// Random color +//----------------------------------------------------------------------------- +class C_INIT_RandomColor : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_RandomColor ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_TINT_RGB_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + struct C_OP_RandomColorContext_t + { + Vector m_vPrevPosition; + }; + + size_t GetRequiredContextBytes( void ) const + { + return sizeof( C_OP_RandomColorContext_t ); + } + + virtual void InitializeContextData( CParticleCollection *pParticles, void *pContext ) const + { + C_OP_RandomColorContext_t *pCtx=reinterpret_cast( pContext ); + pCtx->m_vPrevPosition = vec3_origin; + } + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_flNormColorMin[0] = (float) m_ColorMin[0] / 255.0f; + m_flNormColorMin[1] = (float) m_ColorMin[1] / 255.0f; + m_flNormColorMin[2] = (float) m_ColorMin[2] / 255.0f; + + m_flNormColorMax[0] = (float) m_ColorMax[0] / 255.0f; + m_flNormColorMax[1] = (float) m_ColorMax[1] / 255.0f; + m_flNormColorMax[2] = (float) m_ColorMax[2] / 255.0f; + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, int nParticleCount, int nAttributeWriteMask, void *pContext ) const + { + C_OP_RandomColorContext_t *pCtx=reinterpret_cast( pContext ); + + Color tint( 255, 255, 255, 255 ); + + // If we're factoring in luminosity or tint, then get our lighting info for this position + if ( m_flTintPerc ) + { + if ( pParticles->m_pParent && pParticles->m_pParent->m_LocalLightingCP == m_nTintCP ) + { + tint = pParticles->m_pParent->m_LocalLighting; + } + else + { + // FIXME: Really, we want the emission point for each particle, but for now, we do it more cheaply + // Get our control point + Vector vecOrigin; + pParticles->GetControlPointAtTime( m_nTintCP, pParticles->m_flCurTime, &vecOrigin ); + + if ( ( ( pCtx->m_vPrevPosition - vecOrigin ).Length() >= m_flUpdateThreshold ) || ( pParticles->m_LocalLightingCP == -1 ) ) + { + g_pParticleSystemMgr->Query()->GetLightingAtPoint( vecOrigin, tint ); + pParticles->m_LocalLighting = tint; + pParticles->m_LocalLightingCP = m_nTintCP; + pCtx->m_vPrevPosition = vecOrigin; + } + else + tint = pParticles->m_LocalLighting; + + } + tint[0] = max ( m_TintMin[0], min( tint[0], m_TintMax[0] ) ); + tint[1] = max ( m_TintMin[1], min( tint[1], m_TintMax[1] ) ); + tint[2] = max ( m_TintMin[2], min( tint[2], m_TintMax[2] ) ); + } + + float randomPerc; + float *pColor; + for( ; nParticleCount--; start_p++ ) + { + pColor = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_TINT_RGB, start_p ); + + randomPerc = pParticles->RandomFloat( 0.0f, 1.0f ); + + // Randomly choose a range between the two colors + pColor[0] = m_flNormColorMin[0] + ( ( m_flNormColorMax[0] - m_flNormColorMin[0] ) * randomPerc ); + pColor[4] = m_flNormColorMin[1] + ( ( m_flNormColorMax[1] - m_flNormColorMin[1] ) * randomPerc ); + pColor[8] = m_flNormColorMin[2] + ( ( m_flNormColorMax[2] - m_flNormColorMin[2] ) * randomPerc ); + + // Tint the particles + if ( m_flTintPerc ) + { + pColor[0] = Lerp( m_flTintPerc, (float) pColor[0], (float) tint.r() / 255.0f ); + pColor[4] = Lerp( m_flTintPerc, (float) pColor[4], (float) tint.g() / 255.0f ); + pColor[8] = Lerp( m_flTintPerc, (float) pColor[8], (float) tint.b() / 255.0f ); + } + } + } + + virtual void InitNewParticlesBlock( CParticleCollection *pParticles, + int start_block, int n_blocks, int nAttributeWriteMask, + void *pContext ) const + { + C_OP_RandomColorContext_t *pCtx=reinterpret_cast( pContext ); + + Color tint( 255, 255, 255, 255 ); + + size_t attr_stride; + + FourVectors *pColor = pParticles->Get4VAttributePtrForWrite( PARTICLE_ATTRIBUTE_TINT_RGB, &attr_stride ); + + pColor += attr_stride * start_block; + + FourVectors fvColorMin; + fvColorMin.DuplicateVector( Vector (m_flNormColorMin[0], m_flNormColorMin[1], m_flNormColorMin[2] ) ); + FourVectors fvColorWidth; + fvColorWidth.DuplicateVector( Vector (m_flNormColorMax[0] - m_flNormColorMin[0], m_flNormColorMax[1] - m_flNormColorMin[1], m_flNormColorMax[2] - m_flNormColorMin[2] ) ); + + int nRandContext = GetSIMDRandContext(); + + // If we're factoring in luminosity or tint, then get our lighting info for this position + if ( m_flTintPerc ) + { + if ( pParticles->m_pParent && pParticles->m_pParent->m_LocalLightingCP == m_nTintCP ) + { + tint = pParticles->m_pParent->m_LocalLighting; + } + else + { + // FIXME: Really, we want the emission point for each particle, but for now, we do it more cheaply + // Get our control point + Vector vecOrigin; + pParticles->GetControlPointAtTime( m_nTintCP, pParticles->m_flCurTime, &vecOrigin ); + + if ( ( ( pCtx->m_vPrevPosition - vecOrigin ).Length() >= m_flUpdateThreshold ) || ( pParticles->m_LocalLightingCP == -1 ) ) + { + g_pParticleSystemMgr->Query()->GetLightingAtPoint( vecOrigin, tint ); + pParticles->m_LocalLighting = tint; + pParticles->m_LocalLightingCP = m_nTintCP; + pCtx->m_vPrevPosition = vecOrigin; + } + else + tint = pParticles->m_LocalLighting; + } + + tint[0] = max ( m_TintMin[0], min( tint[0], m_TintMax[0] ) ); + tint[1] = max ( m_TintMin[1], min( tint[1], m_TintMax[1] ) ); + tint[2] = max ( m_TintMin[2], min( tint[2], m_TintMax[2] ) ); + + FourVectors fvTint; + fvTint.DuplicateVector( Vector ( tint[0], tint[1], tint[2] ) ); + fltx4 fl4Divisor = ReplicateX4( 1.0f / 255.0f ); + fvTint *= fl4Divisor; + fltx4 fl4TintPrc = ReplicateX4( m_flTintPerc ); + + while( n_blocks-- ) + { + FourVectors fvColor = fvColorWidth; + FourVectors fvColor2 = fvTint; + fvColor *= RandSIMD( nRandContext ); + fvColor += fvColorMin; + fvColor2 -= fvColor; + fvColor2 *= fl4TintPrc; + fvColor2 += fvColor; + *pColor = fvColor2; + pColor += attr_stride; + } + } + else + { + while( n_blocks-- ) + { + FourVectors fvColor = fvColorWidth; + fvColor *= RandSIMD( nRandContext ); + fvColor += fvColorMin; + *pColor = fvColor; + pColor += attr_stride; + } + } + ReleaseSIMDRandContext( nRandContext ); + } + + virtual uint64 GetReadControlPointMask() const + { + return 1ULL << m_nTintCP; + } + + float m_flNormColorMin[3]; + float m_flNormColorMax[3]; + Color m_ColorMin; + Color m_ColorMax; + Color m_TintMin; + Color m_TintMax; + float m_flTintPerc; + float m_flUpdateThreshold; + int m_nTintCP; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_RandomColor, "Color Random", OPERATOR_PI_TINT_RGB ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomColor ) + DMXELEMENT_UNPACK_FIELD( "color1", "255 255 255 255", Color, m_ColorMin ) + DMXELEMENT_UNPACK_FIELD( "color2", "255 255 255 255", Color, m_ColorMax ) + DMXELEMENT_UNPACK_FIELD( "tint_perc", "0.0", float, m_flTintPerc ) + DMXELEMENT_UNPACK_FIELD( "tint control point", "0", int, m_nTintCP ) + DMXELEMENT_UNPACK_FIELD( "tint clamp min", "0 0 0 0", Color, m_TintMin ) + DMXELEMENT_UNPACK_FIELD( "tint clamp max", "255 255 255 255", Color, m_TintMax ) + DMXELEMENT_UNPACK_FIELD( "tint update movement threshold", "32", float, m_flUpdateThreshold ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomColor ) + + +//----------------------------------------------------------------------------- +// Trail Length +//----------------------------------------------------------------------------- +class C_INIT_RandomTrailLength : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_RandomTrailLength ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_TRAIL_LENGTH_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + } + + virtual void InitNewParticlesBlock( CParticleCollection *pParticles, + int start_block, int n_blocks, int nAttributeWriteMask, + void *pContext ) const + { + if ( m_flLengthRandExponent != 1.0f ) + { + InitScalarAttributeRandomRangeExpBlock( PARTICLE_ATTRIBUTE_TRAIL_LENGTH, + m_flMinLength, m_flMaxLength, m_flLengthRandExponent, + pParticles, start_block, n_blocks ); + } + else + { + InitScalarAttributeRandomRangeBlock( PARTICLE_ATTRIBUTE_TRAIL_LENGTH, + m_flMinLength, m_flMaxLength, + pParticles, start_block, n_blocks ); + } + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, int nParticleCount, int nAttributeWriteMask, void *pContext ) const + { + float *pLength; + for( ; nParticleCount--; start_p++ ) + { + pLength = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_TRAIL_LENGTH, start_p ); + *pLength = pParticles->RandomFloatExp( m_flMinLength, m_flMaxLength, m_flLengthRandExponent ); + } + } + + float m_flMinLength; + float m_flMaxLength; + float m_flLengthRandExponent; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_RandomTrailLength, "Trail Length Random", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomTrailLength ) + DMXELEMENT_UNPACK_FIELD( "length_min", "0.1", float, m_flMinLength ) + DMXELEMENT_UNPACK_FIELD( "length_max", "0.1", float, m_flMaxLength ) + DMXELEMENT_UNPACK_FIELD( "length_random_exponent", "1", float, m_flLengthRandExponent ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomTrailLength ) + +//----------------------------------------------------------------------------- +// Random sequence +//----------------------------------------------------------------------------- +class C_INIT_RandomSequence : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_RandomSequence ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + // TODO: Validate the ranges here! + } + + virtual void InitNewParticlesBlock( CParticleCollection *pParticles, + int start_block, int n_blocks, int nAttributeWriteMask, + void *pContext ) const + { + InitScalarAttributeRandomRangeBlock( PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER, + m_nSequenceMin, m_nSequenceMax, + pParticles, start_block, n_blocks ); + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, int nParticleCount, int nAttributeWriteMask, void *pContext ) const + { + float *pSequence; + for( ; nParticleCount--; start_p++ ) + { + pSequence = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER, start_p ); + *pSequence = pParticles->RandomInt( m_nSequenceMin, m_nSequenceMax ); + } + } + + int m_nSequenceMin; + int m_nSequenceMax; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_RandomSequence, "Sequence Random", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomSequence ) + DMXELEMENT_UNPACK_FIELD( "sequence_min", "0", int, m_nSequenceMin ) + DMXELEMENT_UNPACK_FIELD( "sequence_max", "0", int, m_nSequenceMax ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomSequence ) + + +//----------------------------------------------------------------------------- +// Position Warp Initializer +// Scales initial position and velocity of particles within a random vector range +//----------------------------------------------------------------------------- +class C_INIT_PositionWarp : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_PositionOffset ); + + Vector m_vecWarpMin; + Vector m_vecWarpMax; + int m_nControlPointNumber; + float m_flWarpTime, m_flWarpStartTime; + bool m_bInvertWarp; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME; + } + + virtual uint64 GetReadControlPointMask() const + { + return 1ULL << m_nControlPointNumber; + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nControlPointNumber = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nControlPointNumber ) ); + } + + bool InitMultipleOverride ( void ) { return true; } + +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_PositionWarp, "Position Modify Warp Random", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_PositionWarp ) + DMXELEMENT_UNPACK_FIELD( "control point number", "0", int, m_nControlPointNumber ) + DMXELEMENT_UNPACK_FIELD( "warp min", "1 1 1", Vector, m_vecWarpMin ) + DMXELEMENT_UNPACK_FIELD( "warp max", "1 1 1", Vector, m_vecWarpMax ) + DMXELEMENT_UNPACK_FIELD( "warp transition time (treats min/max as start/end sizes)", "0", float , m_flWarpTime ) + DMXELEMENT_UNPACK_FIELD( "warp transition start time", "0", float , m_flWarpStartTime ) + DMXELEMENT_UNPACK_FIELD( "reverse warp (0/1)", "0", bool , m_bInvertWarp ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_PositionWarp ) + + +void C_INIT_PositionWarp::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + Vector vecWarpStart = m_vecWarpMin; + Vector vecWarpEnd = m_vecWarpMax; + + if ( m_bInvertWarp ) + { + vecWarpStart = m_vecWarpMax; + vecWarpEnd = m_vecWarpMin; + } + + for( ; nParticleCount--; start_p++ ) + { + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + const float *ct = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + + Vector randpos; + + if ( m_flWarpTime != 0.0f ) + { + float flWarpEnd = m_flWarpStartTime + m_flWarpTime; + float flPercentage = RemapValClamped( *ct, m_flWarpStartTime, flWarpEnd, 0.0, 1.0 ); + VectorLerp( vecWarpStart, vecWarpEnd, flPercentage, randpos ); + } + else + { + pParticles->RandomVector( m_vecWarpMin, m_vecWarpMax, &randpos ); + } + + + matrix3x4_t mat; + pParticles->GetControlPointTransformAtTime( m_nControlPointNumber, *ct, &mat ); + Vector vecTransformLocal = vec3_origin; + Vector vecParticlePosition, vecParticlePosition_prev ; + SetVectorFromAttribute( vecParticlePosition, xyz ); + SetVectorFromAttribute( vecParticlePosition_prev, pxyz ); + // rotate particles from world space into local + VectorITransform( vecParticlePosition, mat, vecTransformLocal ); + // multiply position by desired amount + vecTransformLocal.x *= randpos.x; + vecTransformLocal.y *= randpos.y; + vecTransformLocal.z *= randpos.z; + // rotate back into world space + VectorTransform( vecTransformLocal, mat, vecParticlePosition ); + // rinse, repeat + VectorITransform( vecParticlePosition_prev, mat, vecTransformLocal ); + vecTransformLocal.x *= randpos.x; + vecTransformLocal.y *= randpos.y; + vecTransformLocal.z *= randpos.z; + VectorTransform( vecTransformLocal, mat, vecParticlePosition_prev ); + // set positions into floats + SetVectorAttribute( xyz, vecParticlePosition ); + SetVectorAttribute( pxyz, vecParticlePosition_prev ); + } +} + + +//----------------------------------------------------------------------------- +// noise initializer +//----------------------------------------------------------------------------- +class C_INIT_CreationNoise : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_CreationNoise ); + + uint32 GetWrittenAttributes( void ) const + { + return 1 << m_nFieldOutput; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | PARTICLE_ATTRIBUTE_XYZ_MASK; + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + + void InitNewParticlesBlock( CParticleCollection *pParticles, + int start_block, int n_blocks, int nAttributeWriteMask, + void *pContext ) const; + + virtual bool IsScrubSafe() { return true; } + int m_nFieldOutput; + bool m_bAbsVal, m_bAbsValInv; + float m_flOffset; + float m_flOutputMin; + float m_flOutputMax; + float m_flNoiseScale, m_flNoiseScaleLoc; + Vector m_vecOffsetLoc; + float m_flWorldTimeScale; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_CreationNoise, "Remap Noise to Scalar", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_CreationNoise ) + DMXELEMENT_UNPACK_FIELD( "time noise coordinate scale","0.1",float,m_flNoiseScale) + DMXELEMENT_UNPACK_FIELD( "spatial noise coordinate scale","0.001",float,m_flNoiseScaleLoc) + DMXELEMENT_UNPACK_FIELD_USERDATA( "output field", "3", int, m_nFieldOutput, "intchoice particlefield_scalar" ) + DMXELEMENT_UNPACK_FIELD( "time coordinate offset","0", float, m_flOffset ) + DMXELEMENT_UNPACK_FIELD( "spatial coordinate offset","0 0 0", Vector, m_vecOffsetLoc ) + DMXELEMENT_UNPACK_FIELD( "absolute value","0", bool, m_bAbsVal ) + DMXELEMENT_UNPACK_FIELD( "invert absolute value","0", bool, m_bAbsValInv ) + DMXELEMENT_UNPACK_FIELD( "output minimum","0", float, m_flOutputMin ) + DMXELEMENT_UNPACK_FIELD( "output maximum","1", float, m_flOutputMax ) + DMXELEMENT_UNPACK_FIELD( "world time noise coordinate scale","0", float, m_flWorldTimeScale ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_CreationNoise ); + + + + +void C_INIT_CreationNoise::InitNewParticlesBlock( CParticleCollection *pParticles, + int start_block, int n_blocks, int nAttributeWriteMask, + void *pContext ) const +{ + float flAbsScale; + fltx4 fl4AbsVal; + fl4AbsVal = CmpEqSIMD( Four_Zeros, Four_Zeros ); + flAbsScale = 0.5; + + // Set up values for more optimal absolute value calculations inside the loop + if ( m_bAbsVal ) + { + fl4AbsVal = LoadAlignedSIMD( (float *) g_SIMD_clear_signmask ); + flAbsScale = 1.0; + } + + float fMin = m_flOutputMin; + float fMax = m_flOutputMax; + + if ( ATTRIBUTES_WHICH_ARE_ANGLES & (1 << m_nFieldOutput ) ) + { + fMin *= ( M_PI / 180.0f ); + fMax *= ( M_PI / 180.0f ); + } + + float CoordScale = m_flNoiseScale; + float CoordScaleLoc = m_flNoiseScaleLoc; + + float ValueScale, ValueBase; + ValueScale = ( flAbsScale *( fMax - fMin ) ); + ValueBase = ( fMin+ ( ( 1.0 - flAbsScale ) *( fMax - fMin ) ) ); + + fltx4 fl4ValueBase = ReplicateX4( ValueBase ); + fltx4 fl4ValueScale = ReplicateX4( ValueScale ); + + size_t attr_stride; + + fltx4 *pAttr = pParticles->GetM128AttributePtrForWrite( m_nFieldOutput, &attr_stride ); + pAttr += attr_stride * start_block; + const FourVectors *pxyz = pParticles->Get4VAttributePtr( PARTICLE_ATTRIBUTE_XYZ, &attr_stride ); + pxyz += attr_stride * start_block; + const fltx4 *pCreationTime = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, &attr_stride ); + pCreationTime += attr_stride * start_block; + + //setup + fltx4 fl4Offset = ReplicateX4( m_flOffset ); + FourVectors fvOffsetLoc; + fvOffsetLoc.DuplicateVector( m_vecOffsetLoc ); + FourVectors fvCoordBase; + fvCoordBase.x = AddSIMD(*pCreationTime, fl4Offset); + fvCoordBase.y = AddSIMD(*pCreationTime, fl4Offset); + fvCoordBase.z = AddSIMD(*pCreationTime, fl4Offset); + fvCoordBase *= CoordScale; + + while( n_blocks-- ) + { + FourVectors fvCoordLoc = *pxyz; + fvCoordLoc += fvOffsetLoc; + FourVectors fvCoord = fvCoordBase; + fvCoordLoc *= CoordScaleLoc; + fvCoord += fvCoordLoc; + + fltx4 fl4Noise; + + fl4Noise = NoiseSIMD( fvCoord ); + + fl4Noise = AndSIMD ( fl4Noise, fl4AbsVal ); + + if ( m_bAbsValInv ) + { + fl4Noise = SubSIMD( Four_Ones, fl4Noise ); + } + + fltx4 fl4InitialNoise; + + fl4InitialNoise = AddSIMD( fl4ValueBase, ( MulSIMD( fl4ValueScale, fl4Noise ) ) ); + + if ( ATTRIBUTES_WHICH_ARE_0_TO_1 & (1 << m_nFieldOutput ) ) + { + fl4InitialNoise = MinSIMD( Four_Ones, fl4InitialNoise ); + fl4InitialNoise = MaxSIMD( Four_Zeros, fl4InitialNoise ); + } + + *( pAttr ) = fl4InitialNoise; + + pAttr += attr_stride; + pxyz += attr_stride; + + } +} + + + +void C_INIT_CreationNoise::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + float flAbsScale; + int nAbsVal; + nAbsVal = 0xffffffff; + flAbsScale = 0.5; + if ( m_bAbsVal ) + { + nAbsVal = 0x7fffffff; + flAbsScale = 1.0; + } + + float fMin = m_flOutputMin; + float fMax = m_flOutputMax; + + if ( ATTRIBUTES_WHICH_ARE_ANGLES & (1 << m_nFieldOutput ) ) + { + fMin *= ( M_PI / 180.0f ); + fMax *= ( M_PI / 180.0f ); + } + + float CoordScale = m_flNoiseScale; + float CoordScaleLoc = m_flNoiseScaleLoc; + + float ValueScale, ValueBase; + ValueScale = ( flAbsScale *( fMax - fMin ) ); + ValueBase = ( fMin+ ( ( 1.0 - flAbsScale ) *( fMax - fMin ) ) ); + + Vector CoordLoc, CoordWorldTime, CoordBase; + const float *pCreationTime = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + float Offset = m_flOffset; + CoordBase = Vector ( (*pCreationTime + Offset), (*pCreationTime + Offset), (*pCreationTime + Offset) ); + CoordBase *= CoordScale; + CoordWorldTime = Vector( (Plat_MSTime() * m_flWorldTimeScale), (Plat_MSTime() * m_flWorldTimeScale), (Plat_MSTime() * m_flWorldTimeScale) ); + CoordBase += CoordWorldTime; + + for( ; nParticleCount--; start_p++ ) + { + const float *pxyz = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_XYZ, start_p ); + float *pAttr = pParticles->GetFloatAttributePtrForWrite( m_nFieldOutput, start_p ); + + Vector Coord = CoordBase; + + CoordLoc.x = pxyz[0]; + CoordLoc.y = pxyz[4]; + CoordLoc.z = pxyz[8]; + CoordLoc += m_vecOffsetLoc; + + CoordLoc *= CoordScaleLoc; + Coord += CoordLoc; + + fltx4 flNoise128; + FourVectors fvNoise; + + fvNoise.DuplicateVector( Coord ); + flNoise128 = NoiseSIMD( fvNoise ); + float flNoise = SubFloat( flNoise128, 0 ); + + *( (int *) &flNoise) &= nAbsVal; + + if ( m_bAbsValInv ) + { + flNoise = 1.0 - flNoise; + } + + float flInitialNoise = ( ValueBase + ( ValueScale * flNoise ) ); + + if ( ATTRIBUTES_WHICH_ARE_0_TO_1 & (1 << m_nFieldOutput ) ) + { + flInitialNoise = clamp(flInitialNoise, 0.0f, 1.0f ); + } + + *( pAttr ) = flInitialNoise; + } +} + + + + + + +class C_INIT_CreateAlongPath : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_CreateAlongPath ); + + float m_fMaxDistance; + struct CPathParameters m_PathParams; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + uint64 nStartMask = ( 1ULL << m_PathParams.m_nStartControlPointNumber ) - 1; + uint64 nEndMask = ( 1ULL << ( m_PathParams.m_nEndControlPointNumber + 1 ) ) - 1; + return nEndMask & (~nStartMask); + } + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_PathParams.ClampControlPointIndices(); + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_CreateAlongPath, "Position Along Path Random", OPERATOR_PI_POSITION ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_CreateAlongPath ) + DMXELEMENT_UNPACK_FIELD( "maximum distance", "0", float, m_fMaxDistance ) + DMXELEMENT_UNPACK_FIELD( "bulge", "0", float, m_PathParams.m_flBulge ) + DMXELEMENT_UNPACK_FIELD( "start control point number", "0", int, m_PathParams.m_nStartControlPointNumber ) + DMXELEMENT_UNPACK_FIELD( "end control point number", "0", int, m_PathParams.m_nEndControlPointNumber ) + DMXELEMENT_UNPACK_FIELD( "bulge control 0=random 1=orientation of start pnt 2=orientation of end point", "0", int, m_PathParams.m_nBulgeControl ) + DMXELEMENT_UNPACK_FIELD( "mid point position", "0.5", float, m_PathParams.m_flMidPoint ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_CreateAlongPath ) + + +void C_INIT_CreateAlongPath::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + for( ; nParticleCount--; start_p++ ) + { + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + const float *ct = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + + + Vector StartPnt, MidP, EndPnt; + pParticles->CalculatePathValues( m_PathParams, *ct, &StartPnt, &MidP, &EndPnt); + + float t=pParticles->RandomFloat( 0.0, 1.0 ); + + Vector randpos; + pParticles->RandomVector( -m_fMaxDistance, m_fMaxDistance, &randpos ); + + // form delta terms needed for quadratic bezier + Vector Delta0=MidP-StartPnt; + Vector Delta1 = EndPnt-MidP; + + Vector L0 = StartPnt+t*Delta0; + Vector L1 = MidP+t*Delta1; + + Vector Pnt = L0+(L1-L0)*t; + + Pnt+=randpos; + + xyz[0] = Pnt.x; + xyz[4] = Pnt.y; + xyz[8] = Pnt.z; + if ( pxyz && ( nAttributeWriteMask & PARTICLE_ATTRIBUTE_PREV_XYZ_MASK ) ) + { + pxyz[0] = Pnt.x; + pxyz[4] = Pnt.y; + pxyz[8] = Pnt.z; + } + } +} + + + + + +class C_INIT_MoveBetweenPoints : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_MoveBetweenPoints ); + + float m_flSpeedMin, m_flSpeedMax; + float m_flEndSpread; + float m_flStartOffset; + int m_nEndControlPointNumber; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK | PARTICLE_ATTRIBUTE_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK | PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + return 1ULL << m_nEndControlPointNumber; + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_MoveBetweenPoints, "Move Particles Between 2 Control Points", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_MoveBetweenPoints ) + DMXELEMENT_UNPACK_FIELD( "minimum speed", "1", float, m_flSpeedMin ) + DMXELEMENT_UNPACK_FIELD( "maximum speed", "1", float, m_flSpeedMax ) + DMXELEMENT_UNPACK_FIELD( "end spread", "0", float, m_flEndSpread ) + DMXELEMENT_UNPACK_FIELD( "start offset", "0", float, m_flStartOffset ) + DMXELEMENT_UNPACK_FIELD( "end control point", "1", int, m_nEndControlPointNumber ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_MoveBetweenPoints ) + + +void C_INIT_MoveBetweenPoints::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + bool bMoveStartPnt = ( m_flStartOffset > 0.0 ); + for( ; nParticleCount--; start_p++ ) + { + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + float *pPrevXYZ = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + const float *ct = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + + float *dtime = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_LIFE_DURATION, start_p ); + + + Vector StartPnt( pxyz[0], pxyz[4], pxyz[8] ); + + Vector vecControlPoint; + + pParticles->GetControlPointAtTime( m_nEndControlPointNumber, *ct, &vecControlPoint ); + + Vector randpos(0,0,0); + + if ( m_flEndSpread > 0.0 ) + { + pParticles->RandomVectorInUnitSphere( &randpos ); + randpos *= m_flEndSpread; + } + + vecControlPoint += randpos; + + Vector vDelta = vecControlPoint - StartPnt; + float flLen = VectorLength( vDelta ); + + if ( bMoveStartPnt ) + { + StartPnt += ( m_flStartOffset/(flLen+FLT_EPSILON) ) * vDelta; + vDelta = vecControlPoint - StartPnt; + flLen = VectorLength( vDelta ); + } + + float flVel = pParticles->RandomFloat( m_flSpeedMin, m_flSpeedMax ); + + *dtime = flLen/( flVel+FLT_EPSILON); + + Vector poffset = vDelta * (flVel/flLen ) ; + + poffset *= pParticles->m_flPreviousDt; + + if ( bMoveStartPnt ) + { + pxyz[0] = StartPnt.x; + pxyz[1] = StartPnt.y; + pxyz[2] = StartPnt.z; + } + + pPrevXYZ[0] = pxyz[0] - poffset.x; + pPrevXYZ[4] = pxyz[4] - poffset.y; + pPrevXYZ[8] = pxyz[8] - poffset.z; + } +} + + + + +//----------------------------------------------------------------------------- +// Remap Scalar Initializer +//----------------------------------------------------------------------------- +class C_INIT_RemapScalar : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_RemapScalar ); + + uint32 GetWrittenAttributes( void ) const + { + return 1 << m_nFieldOutput; + } + + uint32 GetReadAttributes( void ) const + { + return 1 << m_nFieldInput; + } + + bool InitMultipleOverride ( void ) { return true; } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + + int m_nFieldInput; + int m_nFieldOutput; + float m_flInputMin; + float m_flInputMax; + float m_flOutputMin; + float m_flOutputMax; + float m_flStartTime; + float m_flEndTime; + bool m_bScaleInitialRange; + bool m_bActiveRange; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_RemapScalar, "Remap Initial Scalar", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_RemapScalar ) + DMXELEMENT_UNPACK_FIELD( "emitter lifetime start time (seconds)", "-1", float, m_flStartTime ) + DMXELEMENT_UNPACK_FIELD( "emitter lifetime end time (seconds)", "-1", float, m_flEndTime ) + DMXELEMENT_UNPACK_FIELD_USERDATA( "input field", "8", int, m_nFieldInput, "intchoice particlefield_scalar" ) + DMXELEMENT_UNPACK_FIELD( "input minimum","0", float, m_flInputMin ) + DMXELEMENT_UNPACK_FIELD( "input maximum","1", float, m_flInputMax ) + DMXELEMENT_UNPACK_FIELD_USERDATA( "output field", "3", int, m_nFieldOutput, "intchoice particlefield_scalar" ) + DMXELEMENT_UNPACK_FIELD( "output minimum","0", float, m_flOutputMin ) + DMXELEMENT_UNPACK_FIELD( "output maximum","1", float, m_flOutputMax ) + DMXELEMENT_UNPACK_FIELD( "output is scalar of initial random range","0", bool, m_bScaleInitialRange ) + DMXELEMENT_UNPACK_FIELD( "only active within specified input range","0", bool, m_bActiveRange ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_RemapScalar ) + +void C_INIT_RemapScalar::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + const float *pCreationTime; + // clamp the result to 0 and 1 if it's alpha + float flMin=m_flOutputMin; + float flMax=m_flOutputMax; + if ( ATTRIBUTES_WHICH_ARE_0_TO_1 & ( 1 << m_nFieldOutput ) ) + { + flMin = clamp(m_flOutputMin, 0.0f, 1.0f ); + flMax = clamp(m_flOutputMax, 0.0f, 1.0f ); + } + + // FIXME: SSE-ize + for( ; nParticleCount--; start_p++ ) + { + pCreationTime = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + // using raw creation time to map to emitter lifespan + float flLifeTime = *pCreationTime; + + float flInput; + if ( ATTRIBUTES_WHICH_ARE_INTS & ( 1 << m_nFieldInput ) ) + { + const int *pInput = pParticles->GetIntAttributePtr( m_nFieldInput, start_p ); + flInput = float( *pInput ); + } + else + { + const float *pInput = pParticles->GetFloatAttributePtr( m_nFieldInput, start_p ); + flInput = *pInput; + } + + // only use within start/end time frame and, if set, active input range + if ( ( ( ( flLifeTime < m_flStartTime ) || ( flLifeTime >= m_flEndTime ) ) && ( ( m_flStartTime != -1.0f) && ( m_flEndTime != -1.0f) ) ) || ( m_bActiveRange && ( flInput < m_flInputMin || flInput > m_flInputMax ) ) ) + continue; + + float *pOutput = pParticles->GetFloatAttributePtrForWrite( m_nFieldOutput, start_p ); + float flOutput = RemapValClamped( flInput, m_flInputMin, m_flInputMax, flMin, flMax ); + if ( m_bScaleInitialRange ) + { + flOutput = *pOutput * flOutput; + } + if ( ATTRIBUTES_WHICH_ARE_INTS & ( 1 << m_nFieldOutput ) ) + { + *pOutput = int ( flOutput ); + } + else + { + *pOutput = flOutput; + } + } +} + + + + +//----------------------------------------------------------------------------- +// Inherit Velocity Initializer +// Causes particles to inherit the velocity of their CP at spawn +// +//----------------------------------------------------------------------------- +class C_INIT_InheritVelocity : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_InheritVelocity ); + + int m_nControlPointNumber; + float m_flVelocityScale; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK ; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_CREATION_TIME; + } + + virtual uint64 GetReadControlPointMask() const + { + return 1ULL << m_nControlPointNumber; + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nControlPointNumber = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nControlPointNumber ) ); + } + + bool InitMultipleOverride ( void ) { return true; } + +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_InheritVelocity, "Velocity Inherit from Control Point", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_InheritVelocity ) +DMXELEMENT_UNPACK_FIELD( "control point number", "0", int, m_nControlPointNumber ) +DMXELEMENT_UNPACK_FIELD( "velocity scale", "1", float, m_flVelocityScale ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_InheritVelocity ) + + +void C_INIT_InheritVelocity::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + for( ; nParticleCount--; start_p++ ) + { + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + const float *ct = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + + Vector vecControlPoint; + pParticles->GetControlPointAtTime( m_nControlPointNumber, *ct, &vecControlPoint ); + Vector vecControlPointPrev; + pParticles->GetControlPointAtPrevTime( m_nControlPointNumber, &vecControlPointPrev ); + + Vector vecDeltaPos = (vecControlPoint - vecControlPointPrev); + //Vector vecDeltaPos = (vecControlPoint - vecControlPointPrev) * pParticles->m_flDt; + vecDeltaPos.x *= m_flVelocityScale; + vecDeltaPos.y *= m_flVelocityScale; + vecDeltaPos.z *= m_flVelocityScale; + + xyz[0] += vecDeltaPos.x; + xyz[4] += vecDeltaPos.y; + xyz[8] += vecDeltaPos.z; + } +} + + +//----------------------------------------------------------------------------- +// Pre-Age Noise +// Sets particle creation time back to treat newly spawned particle as if +// part of its life has already elapsed. +//----------------------------------------------------------------------------- +class C_INIT_AgeNoise : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_AgeNoise ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK; + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + + bool InitMultipleOverride ( void ) { return true; } + + bool m_bAbsVal, m_bAbsValInv; + float m_flOffset; + float m_flAgeMin; + float m_flAgeMax; + float m_flNoiseScale, m_flNoiseScaleLoc; + Vector m_vecOffsetLoc; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_AgeNoise, "Lifetime Pre-Age Noise", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_AgeNoise ) +DMXELEMENT_UNPACK_FIELD( "time noise coordinate scale","1.0",float,m_flNoiseScale) +DMXELEMENT_UNPACK_FIELD( "spatial noise coordinate scale","1.0",float,m_flNoiseScaleLoc) +DMXELEMENT_UNPACK_FIELD( "time coordinate offset","0", float, m_flOffset ) +DMXELEMENT_UNPACK_FIELD( "spatial coordinate offset","0 0 0", Vector, m_vecOffsetLoc ) +DMXELEMENT_UNPACK_FIELD( "absolute value","0", bool, m_bAbsVal ) +DMXELEMENT_UNPACK_FIELD( "invert absolute value","0", bool, m_bAbsValInv ) +DMXELEMENT_UNPACK_FIELD( "start age minimum","0", float, m_flAgeMin ) +DMXELEMENT_UNPACK_FIELD( "start age maximum","1", float, m_flAgeMax ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_AgeNoise ); + +void C_INIT_AgeNoise::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + float flAbsScale; + int nAbsVal; + nAbsVal = 0xffffffff; + flAbsScale = 0.5; + if ( m_bAbsVal ) + { + nAbsVal = 0x7fffffff; + flAbsScale = 1.0; + } + + float fMin = m_flAgeMin; + float fMax = m_flAgeMax; + + float CoordScale = m_flNoiseScale; + float CoordScaleLoc = m_flNoiseScaleLoc; + + for( ; nParticleCount--; start_p++ ) + { + const float *pxyz = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_XYZ, start_p ); + const float *pCreationTime = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + const float *pLifespan = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_LIFE_DURATION, start_p ); + float *pAttr = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + + float ValueScale, ValueBase; + + Vector Coord, CoordLoc; + CoordLoc.x = pxyz[0]; + CoordLoc.y = pxyz[4]; + CoordLoc.z = pxyz[8]; + CoordLoc += m_vecOffsetLoc; + + float Offset = m_flOffset; + Coord = Vector ( (*pCreationTime + Offset), (*pCreationTime + Offset), (*pCreationTime + Offset) ); + Coord *= CoordScale; + CoordLoc *= CoordScaleLoc; + Coord += CoordLoc; + + fltx4 flNoise128; + FourVectors fvNoise; + + fvNoise.DuplicateVector( Coord ); + flNoise128 = NoiseSIMD( fvNoise ); + float flNoise = SubFloat( flNoise128, 0 ); + + *( (int *) &flNoise) &= nAbsVal; + + ValueScale = ( flAbsScale *( fMax - fMin ) ); + ValueBase = ( fMin+ ( ( 1.0 - flAbsScale ) *( fMax - fMin ) ) ); + + if ( m_bAbsValInv ) + { + flNoise = 1.0 - flNoise; + } + + float flInitialNoise = ( ValueBase + ( ValueScale * flNoise ) ); + + + flInitialNoise = clamp(flInitialNoise, 0.0f, 1.0f ); + flInitialNoise *= *pLifespan; + + *( pAttr ) = *pCreationTime - flInitialNoise; + } +} + + + + +//----------------------------------------------------------------------------- +// LifeTime Sequence Length +//----------------------------------------------------------------------------- +class C_INIT_SequenceLifeTime : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_SequenceLifeTime ); + + float m_flFramerate; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER_MASK; + } + + bool InitMultipleOverride ( void ) { return true; } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_SequenceLifeTime, "Lifetime From Sequence", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_SequenceLifeTime ) +DMXELEMENT_UNPACK_FIELD( "Frames Per Second", "30", float, m_flFramerate ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_SequenceLifeTime ) + +void C_INIT_SequenceLifeTime::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + if ( ( m_flFramerate != 0.0f ) && ( pParticles->m_Sheet() ) ) + { + for( ; nParticleCount--; start_p++ ) + { + const float *flSequence = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER, start_p ); + float *dtime = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_LIFE_DURATION, start_p ); + int nSequence = *flSequence; + + if ( pParticles->m_Sheet()->m_flFrameSpan[nSequence] != 0 ) + { + *dtime = pParticles->m_Sheet()->m_flFrameSpan[nSequence] / m_flFramerate; + } + else + { + *dtime = 1.0; + } + } + } +} + + + + +//----------------------------------------------------------------------------- +// Create In Hierarchy +//----------------------------------------------------------------------------- +class C_INIT_CreateInHierarchy : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_CreateInHierarchy ); + + float m_fMaxDistance; + float m_flGrowthTime; + //float m_flTraceDist; + float m_flDesiredMidPoint; + int m_nOrientation; + float m_flBulgeFactor; + int m_nDesiredEndPoint; + int m_nDesiredStartPoint; + bool m_bUseHighestEndCP; + Vector m_vecDistanceBias, m_vecDistanceBiasAbs; + bool m_bDistanceBias, m_bDistanceBiasAbs; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + uint64 nStartMask = ( 1ULL << m_nDesiredStartPoint ) - 1; + uint64 nEndMask = m_bUseHighestEndCP ? 0xFFFFFFFFFFFFFFFFll : ( 1ULL << ( m_nDesiredEndPoint + 1 ) ) - 1; + return nEndMask & (~nStartMask); + } + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + //fixme - confirm CPs + // m_PathParams.ClampControlPointIndices(); + m_bDistanceBias = ( m_vecDistanceBias.x != 1.0f ) || ( m_vecDistanceBias.y != 1.0f ) || ( m_vecDistanceBias.z != 1.0f ); + m_bDistanceBiasAbs = ( m_vecDistanceBiasAbs.x != 0.0f ) || ( m_vecDistanceBiasAbs.y != 0.0f ) || ( m_vecDistanceBiasAbs.z != 0.0f ); + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_CreateInHierarchy, "Position In CP Hierarchy", OPERATOR_PI_POSITION ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_CreateInHierarchy ) + DMXELEMENT_UNPACK_FIELD( "maximum distance", "0", float, m_fMaxDistance ) + DMXELEMENT_UNPACK_FIELD( "bulge", "0", float, m_flBulgeFactor ) + DMXELEMENT_UNPACK_FIELD( "start control point number", "0", int, m_nDesiredStartPoint ) + DMXELEMENT_UNPACK_FIELD( "end control point number", "1", int, m_nDesiredEndPoint ) + DMXELEMENT_UNPACK_FIELD( "bulge control 0=random 1=orientation of start pnt 2=orientation of end point", "0", int, m_nOrientation ) + DMXELEMENT_UNPACK_FIELD( "mid point position", "0.5", float, m_flDesiredMidPoint ) + DMXELEMENT_UNPACK_FIELD( "growth time", "0.0", float, m_flGrowthTime ) + //DMXELEMENT_UNPACK_FIELD( "trace distance for optional culling", "0.0", float, m_flTraceDist ) + DMXELEMENT_UNPACK_FIELD( "use highest supplied end point", "0", bool, m_bUseHighestEndCP ) + DMXELEMENT_UNPACK_FIELD( "distance_bias", "1 1 1", Vector, m_vecDistanceBias ) + DMXELEMENT_UNPACK_FIELD( "distance_bias_absolute_value", "0 0 0", Vector, m_vecDistanceBiasAbs ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_CreateInHierarchy ) + + +void C_INIT_CreateInHierarchy::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + int nEndCP; + float flGrowth; + struct CPathParameters PathParams; + PathParams.m_flBulge = m_flBulgeFactor; + PathParams.m_nBulgeControl = m_nOrientation; + PathParams.m_flMidPoint = m_flDesiredMidPoint; + int nRealEndPoint; + + if ( m_bUseHighestEndCP ) + { + nRealEndPoint = pParticles->GetHighestControlPoint(); + } + else + { + nRealEndPoint = m_nDesiredEndPoint; + } + + for( ; nParticleCount--; start_p++ ) + { + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + const float *ct = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + + if ( ( pParticles->m_flCurTime <= m_flGrowthTime ) && ( nRealEndPoint > 0 ) ) + { + float flCurrentEndCP = RemapValClamped( *ct, 0.0f, m_flGrowthTime, min( m_nDesiredStartPoint + 1, nRealEndPoint ), nRealEndPoint ); + nEndCP = pParticles->RandomInt( min( m_nDesiredStartPoint + 1, (int)flCurrentEndCP ), flCurrentEndCP ); + + // clamp growth to the appropriate values... + float flEndTime = flCurrentEndCP / float(nRealEndPoint) ; + flGrowth = RemapValClamped( *ct, 0.0f, m_flGrowthTime, 0.0, flEndTime ); + } + else + { + int nLowestStartPoint = min( m_nDesiredStartPoint + 1, nRealEndPoint ); + nEndCP = pParticles->RandomInt( nLowestStartPoint, nRealEndPoint ); + flGrowth = 1.0; + } + + + PathParams.m_nStartControlPointNumber = pParticles->m_ControlPoints[nEndCP].m_nParent; + PathParams.m_nEndControlPointNumber = nEndCP; + Vector StartPnt, MidP, EndPnt; + + pParticles->CalculatePathValues( PathParams, *ct, &StartPnt, &MidP, &EndPnt); + EndPnt *= flGrowth; + + float t=pParticles->RandomFloat( 0.0, 1.0 ); + + Vector randpos; + pParticles->RandomVector( -m_fMaxDistance, m_fMaxDistance, &randpos ); + + if ( m_bDistanceBiasAbs ) + { + if ( m_vecDistanceBiasAbs.x != 0.0f ) + { + randpos.x = fabs(randpos.x); + } + if ( m_vecDistanceBiasAbs.y != 0.0f ) + { + randpos.y = fabs(randpos.y); + } + if ( m_vecDistanceBiasAbs.z != 0.0f ) + { + randpos.z = fabs(randpos.z); + } + } + randpos *= m_vecDistanceBias; + + // form delta terms needed for quadratic bezier + Vector Delta0=MidP-StartPnt; + Vector Delta1 = EndPnt-MidP; + + Vector L0 = StartPnt+t*Delta0; + Vector L1 = MidP+t*Delta1; + + Vector Pnt = L0+(L1-L0)*t; + + Pnt+=randpos; + // Optional Culling based on configurable trace distance. Failing particle are destroyed + //disabled for now. + //if ( m_flTraceDist != 0.0f ) + //{ + // // Trace down + // Vector TraceDir=Vector(0, 0, -1); + // // now set the trace distance + // // note - probably need to offset Pnt upwards for some fudge factor on irregular surfaces + // CBaseTrace tr; + // Vector RayStart=Pnt; + // float flRadius = m_flTraceDist; + // g_pParticleSystemMgr->Query()->TraceLine( RayStart, ( RayStart + ( TraceDir * flRadius ) ), MASK_SOLID, NULL, COLLISION_GROUP_NONE, &tr ); + // if ( tr.fraction == 1.0 ) + // { + // //If the trace hit nothing, kill the particle. + // pParticles->KillParticle( start_p ); + // } + // else + // { + // //If we hit something, set particle position to collision position + // Pnt += tr.endpos; + // //FIXME - if we add a concept of a particle normal (for example, aligned quads or decals, set it here) + // } + //} + + xyz[0] = Pnt.x; + xyz[4] = Pnt.y; + xyz[8] = Pnt.z; + if ( pxyz && ( nAttributeWriteMask & PARTICLE_ATTRIBUTE_PREV_XYZ_MASK ) ) + { + pxyz[0] = Pnt.x; + pxyz[4] = Pnt.y; + pxyz[8] = Pnt.z; + } + } +} + + + +//----------------------------------------------------------------------------- +// Remap initial Scalar to Vector Initializer +//----------------------------------------------------------------------------- +class C_INIT_RemapScalarToVector : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_RemapScalarToVector ); + + uint32 GetWrittenAttributes( void ) const + { + return 1 << m_nFieldOutput | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return 1 << m_nFieldInput; + } + + virtual uint64 GetReadControlPointMask() const + { + return 1ULL << m_nControlPointNumber; + } + + bool InitMultipleOverride ( void ) { return true; } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + + int m_nFieldInput; + int m_nFieldOutput; + float m_flInputMin; + float m_flInputMax; + Vector m_vecOutputMin; + Vector m_vecOutputMax; + float m_flStartTime; + float m_flEndTime; + bool m_bScaleInitialRange; + int m_nControlPointNumber; + bool m_bLocalCoords; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_RemapScalarToVector, "Remap Scalar to Vector", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_RemapScalarToVector ) +DMXELEMENT_UNPACK_FIELD( "emitter lifetime start time (seconds)", "-1", float, m_flStartTime ) +DMXELEMENT_UNPACK_FIELD( "emitter lifetime end time (seconds)", "-1", float, m_flEndTime ) +DMXELEMENT_UNPACK_FIELD_USERDATA( "input field", "8", int, m_nFieldInput, "intchoice particlefield_scalar" ) +DMXELEMENT_UNPACK_FIELD( "input minimum","0", float, m_flInputMin ) +DMXELEMENT_UNPACK_FIELD( "input maximum","1", float, m_flInputMax ) +DMXELEMENT_UNPACK_FIELD_USERDATA( "output field", "0", int, m_nFieldOutput, "intchoice particlefield_vector" ) +DMXELEMENT_UNPACK_FIELD( "output minimum","0 0 0", Vector, m_vecOutputMin ) +DMXELEMENT_UNPACK_FIELD( "output maximum","1 1 1", Vector, m_vecOutputMax ) +DMXELEMENT_UNPACK_FIELD( "output is scalar of initial random range","0", bool, m_bScaleInitialRange ) +DMXELEMENT_UNPACK_FIELD( "use local system", "1", bool, m_bLocalCoords ) +DMXELEMENT_UNPACK_FIELD( "control_point_number", "0", int, m_nControlPointNumber ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_RemapScalarToVector ) + +void C_INIT_RemapScalarToVector::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + const float *pCreationTime; + // FIXME: SSE-ize + for( ; nParticleCount--; start_p++ ) + { + pCreationTime = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + // using raw creation time to map to emitter lifespan + float flLifeTime = *pCreationTime; + + // only use within start/end time frame + if ( ( ( flLifeTime < m_flStartTime ) || ( flLifeTime >= m_flEndTime ) ) && ( ( m_flStartTime != -1.0f) && ( m_flEndTime != -1.0f) ) ) + continue; + + const float *pInput = pParticles->GetFloatAttributePtr( m_nFieldInput, start_p ); + float *pOutput = pParticles->GetFloatAttributePtrForWrite( m_nFieldOutput, start_p ); + Vector vecOutput = vec3_origin; + vecOutput.x = RemapValClamped( *pInput, m_flInputMin, m_flInputMax, m_vecOutputMin.x, m_vecOutputMax.x ); + vecOutput.y = RemapValClamped( *pInput, m_flInputMin, m_flInputMax, m_vecOutputMin.y, m_vecOutputMax.y ); + vecOutput.z = RemapValClamped( *pInput, m_flInputMin, m_flInputMax, m_vecOutputMin.z, m_vecOutputMax.z ); + + + if ( m_nFieldOutput == 0 ) + { + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + if ( !m_bLocalCoords ) + { + Vector vecControlPoint; + pParticles->GetControlPointAtTime( m_nControlPointNumber, *pCreationTime, &vecControlPoint ); + vecOutput += vecControlPoint; + Vector vecOutputPrev = vecOutput; + if ( m_bScaleInitialRange ) + { + Vector vecScaleInitial; + Vector vecScaleInitialPrev; + SetVectorFromAttribute ( vecScaleInitial, pOutput ); + SetVectorFromAttribute ( vecScaleInitialPrev, pxyz ); + vecOutput *= vecScaleInitial; + vecOutputPrev *= vecScaleInitialPrev; + } + SetVectorAttribute( pOutput, vecOutput ); + SetVectorAttribute( pxyz, vecOutputPrev ); + } + else + { + matrix3x4_t mat; + pParticles->GetControlPointTransformAtTime( m_nControlPointNumber, *pCreationTime, &mat ); + Vector vecTransformLocal = vec3_origin; + VectorTransform( vecOutput, mat, vecTransformLocal ); + vecOutput = vecTransformLocal; + Vector vecOutputPrev = vecOutput; + if ( m_bScaleInitialRange ) + { + Vector vecScaleInitial; + Vector vecScaleInitialPrev; + SetVectorFromAttribute ( vecScaleInitial, pOutput ); + SetVectorFromAttribute ( vecScaleInitialPrev, pxyz ); + vecOutput *= vecScaleInitial; + vecOutputPrev *= vecScaleInitialPrev; + } + SetVectorAttribute( pOutput, vecOutput ); + SetVectorAttribute( pxyz, vecOutput ); + } + } + else + { + if ( m_bScaleInitialRange ) + { + Vector vecScaleInitial; + SetVectorFromAttribute ( vecScaleInitial, pOutput ); + vecOutput *= vecScaleInitial; + } + SetVectorAttribute( pOutput, vecOutput ); + } + } +} + + +//----------------------------------------------------------------------------- +// Create particles sequentially along a path +//----------------------------------------------------------------------------- +struct SequentialPathContext_t +{ + int m_nParticleCount; + float m_flStep; + int m_nCountAmount; +}; +class C_INIT_CreateSequentialPath : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_CreateSequentialPath ); + + float m_fMaxDistance; + float m_flNumToAssign; + bool m_bLoop; + struct CPathParameters m_PathParams; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + uint64 nStartMask = ( 1ULL << m_PathParams.m_nStartControlPointNumber ) - 1; + uint64 nEndMask = ( 1ULL << ( m_PathParams.m_nEndControlPointNumber + 1 ) ) - 1; + return nEndMask & (~nStartMask); + } + + virtual void InitializeContextData( CParticleCollection *pParticles, void *pContext ) const + { + SequentialPathContext_t *pCtx = reinterpret_cast( pContext ); + pCtx->m_nParticleCount = 0; + if ( m_flNumToAssign > 1.0f ) + { + pCtx->m_flStep = 1.0f / ( m_flNumToAssign - 1 ); + } + else + { + pCtx->m_flStep = 0.0f; + } + pCtx->m_nCountAmount = 1; + } + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_PathParams.ClampControlPointIndices(); + + + } + + size_t GetRequiredContextBytes( void ) const + { + return sizeof( SequentialPathContext_t ); + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_CreateSequentialPath, "Position Along Path Sequential", OPERATOR_PI_POSITION ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_CreateSequentialPath ) +DMXELEMENT_UNPACK_FIELD( "maximum distance", "0", float, m_fMaxDistance ) +DMXELEMENT_UNPACK_FIELD( "bulge", "0", float, m_PathParams.m_flBulge ) +DMXELEMENT_UNPACK_FIELD( "start control point number", "0", int, m_PathParams.m_nStartControlPointNumber ) +DMXELEMENT_UNPACK_FIELD( "end control point number", "0", int, m_PathParams.m_nEndControlPointNumber ) +DMXELEMENT_UNPACK_FIELD( "bulge control 0=random 1=orientation of start pnt 2=orientation of end point", "0", int, m_PathParams.m_nBulgeControl ) +DMXELEMENT_UNPACK_FIELD( "mid point position", "0.5", float, m_PathParams.m_flMidPoint ) +DMXELEMENT_UNPACK_FIELD( "particles to map from start to end", "100", float, m_flNumToAssign ) +DMXELEMENT_UNPACK_FIELD( "restart behavior (0 = bounce, 1 = loop )", "1", bool, m_bLoop ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_CreateSequentialPath ) + + +void C_INIT_CreateSequentialPath::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + // NOTE: Using C_OP_ContinuousEmitter:: avoids a virtual function call + SequentialPathContext_t *pCtx = reinterpret_cast( pContext ); + + for( ; nParticleCount--; start_p++ ) + { + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + const float *ct = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + + Vector StartPnt, MidP, EndPnt; + pParticles->CalculatePathValues( m_PathParams, *ct, &StartPnt, &MidP, &EndPnt); + if ( pCtx->m_nParticleCount >= m_flNumToAssign || pCtx->m_nParticleCount < 0 ) + { + if ( m_bLoop ) + { + pCtx->m_nParticleCount = 0; + } + else + { + pCtx->m_nCountAmount *= -1; + pCtx->m_nParticleCount = min ( pCtx->m_nParticleCount, (int)( m_flNumToAssign - 1) ); + pCtx->m_nParticleCount = max ( pCtx->m_nParticleCount, 1 ); + } + } + + float t= pCtx->m_nParticleCount * pCtx->m_flStep; + + Vector randpos; + pParticles->RandomVector( -m_fMaxDistance, m_fMaxDistance, &randpos ); + + // form delta terms needed for quadratic bezier + Vector Delta0=MidP-StartPnt; + Vector Delta1 = EndPnt-MidP; + + Vector L0 = StartPnt+t*Delta0; + Vector L1 = MidP+t*Delta1; + + Vector Pnt = L0+(L1-L0)*t; + + Pnt+=randpos; + + xyz[0] = Pnt.x; + xyz[4] = Pnt.y; + xyz[8] = Pnt.z; + if ( pxyz && ( nAttributeWriteMask & PARTICLE_ATTRIBUTE_PREV_XYZ_MASK ) ) + { + pxyz[0] = Pnt.x; + pxyz[4] = Pnt.y; + pxyz[8] = Pnt.z; + } + pCtx->m_nParticleCount += pCtx->m_nCountAmount; + } +} + + +//----------------------------------------------------------------------------- +// Initial Repulsion Velocity - repulses the particles from nearby surfaces +// on spawn +//----------------------------------------------------------------------------- +class C_INIT_InitialRepulsionVelocity : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_InitialRepulsionVelocity ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | PARTICLE_ATTRIBUTE_RADIUS_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + return 1ULL << m_nControlPointNumber; + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nCollisionGroupNumber = g_pParticleSystemMgr->Query()->GetCollisionGroupFromName( m_CollisionGroupName ); + m_nControlPointNumber = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nControlPointNumber ) ); + } + + bool InitMultipleOverride ( void ) { return true; } + + char m_CollisionGroupName[128]; + int m_nCollisionGroupNumber; + Vector m_vecOutputMin; + Vector m_vecOutputMax; + int nRemainingBlocks; + int m_nControlPointNumber; + bool m_bPerParticle; + bool m_bTranslate; + bool m_bProportional; + float m_flTraceLength; + bool m_bPerParticleTR; + bool m_bInherit; + int m_nChildCP; + int m_nChildGroupID; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_InitialRepulsionVelocity, "Velocity Repulse from World", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_InitialRepulsionVelocity ) +DMXELEMENT_UNPACK_FIELD( "minimum velocity","0 0 0", Vector, m_vecOutputMin ) +DMXELEMENT_UNPACK_FIELD( "maximum velocity","1 1 1", Vector, m_vecOutputMax ) +DMXELEMENT_UNPACK_FIELD_STRING( "collision group", "NONE", m_CollisionGroupName ) +DMXELEMENT_UNPACK_FIELD( "control_point_number", "0", int, m_nControlPointNumber ) +DMXELEMENT_UNPACK_FIELD( "Per Particle World Collision Tests", "0", bool, m_bPerParticle ) +DMXELEMENT_UNPACK_FIELD( "Use radius for Per Particle Trace Length", "0", bool, m_bPerParticleTR ) +DMXELEMENT_UNPACK_FIELD( "Offset instead of accelerate", "0", bool, m_bTranslate ) +DMXELEMENT_UNPACK_FIELD( "Offset proportional to radius 0/1", "0", bool, m_bProportional ) +DMXELEMENT_UNPACK_FIELD( "Trace Length", "64.0", float, m_flTraceLength ) +DMXELEMENT_UNPACK_FIELD( "Inherit from Parent", "0", bool, m_bInherit ) +DMXELEMENT_UNPACK_FIELD( "control points to broadcast to children (n + 1)", "-1", int, m_nChildCP ) +DMXELEMENT_UNPACK_FIELD( "Child Group ID to affect", "0", int, m_nChildGroupID ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_InitialRepulsionVelocity ); + + +void C_INIT_InitialRepulsionVelocity::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + + Vector d[6]; + + //All cardinal directions + d[0] = Vector( 1, 0, 0 ); + d[1] = Vector( -1, 0, 0 ); + d[2] = Vector( 0, 1, 0 ); + d[3] = Vector( 0, -1, 0 ); + d[4] = Vector( 0, 0, 1 ); + d[5] = Vector( 0, 0, -1 ); + + //Init the results + Vector resultDirection; + float resultForce; + if ( m_bPerParticle ) + { + for( ; nParticleCount--; start_p++ ) + { + + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + float *pxyz_prev = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + const float *radius = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_RADIUS, start_p ); + Vector vecCurrentPos; + SetVectorFromAttribute( vecCurrentPos, pxyz ); + + resultDirection.Init(); + resultForce = 0.0f; + + //Get the aggregate force vector + for ( int i = 0; i < 6; i++ ) + { + //Press out + float flTraceDistance = m_flTraceLength; + if ( m_bPerParticleTR ) + { + flTraceDistance = *radius; + } + Vector endpos = vecCurrentPos + ( d[i] * flTraceDistance ); + + //Trace into the world + CBaseTrace tr; + g_pParticleSystemMgr->Query()->TraceLine( vecCurrentPos, endpos, CONTENTS_SOLID, NULL, m_nCollisionGroupNumber, &tr ); + + //Push back a proportional amount to the probe + d[i] = -d[i] * (1.0f-tr.fraction); + + assert(( 1.0f - tr.fraction ) >= 0.0f ); + + resultForce += 1.0f-tr.fraction; + resultDirection += d[i]; + } + + //If we've hit nothing, then point up + if ( resultDirection == vec3_origin ) + { + resultDirection = Vector( 0, 0, 1 ); + resultForce = 0.0f; + } + + //Just return the direction + VectorNormalize( resultDirection ); + resultDirection *= resultForce; + + Vector vecRepulsionAmount; + + vecRepulsionAmount.x = Lerp( resultForce, m_vecOutputMin.x, m_vecOutputMax.x ); + vecRepulsionAmount.y = Lerp( resultForce, m_vecOutputMin.y, m_vecOutputMax.y ); + vecRepulsionAmount.z = Lerp( resultForce, m_vecOutputMin.z, m_vecOutputMax.z ); + + + vecRepulsionAmount *= resultDirection; + + + if ( m_bProportional ) + { + vecRepulsionAmount *= *radius; + } + + pxyz[0] += vecRepulsionAmount.x; + pxyz[4] += vecRepulsionAmount.y; + pxyz[8] += vecRepulsionAmount.z; + + if ( m_bTranslate ) + { + pxyz_prev[0] += vecRepulsionAmount.x; + pxyz_prev[4] += vecRepulsionAmount.y; + pxyz_prev[8] += vecRepulsionAmount.z; + } + } + } + else + { + + Vector vecRepulsionAmount; + + if ( m_bInherit ) + { + float *ct = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + pParticles->GetControlPointAtTime( m_nControlPointNumber, *ct, &resultDirection ); + Vector vecPassedForce; + pParticles->GetControlPointAtTime( m_nControlPointNumber+1, *ct, &vecPassedForce ); + + vecRepulsionAmount.x = Lerp( vecPassedForce.x, m_vecOutputMin.x, m_vecOutputMax.x ); + vecRepulsionAmount.y = Lerp( vecPassedForce.x, m_vecOutputMin.y, m_vecOutputMax.y ); + vecRepulsionAmount.z = Lerp( vecPassedForce.x, m_vecOutputMin.z, m_vecOutputMax.z ); + + vecRepulsionAmount *= resultDirection; + } + else + { + float *ct = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + Vector vecControlPoint; + pParticles->GetControlPointAtTime( m_nControlPointNumber, *ct, &vecControlPoint ); + + Vector vecCurrentPos = vecControlPoint; + + resultDirection.Init(); + resultForce = 0.0f; + + //Get the aggregate force vector + for ( int i = 0; i < 6; i++ ) + { + //Press out + Vector endpos = vecCurrentPos + ( d[i] * m_flTraceLength ); + + //Trace into the world + CBaseTrace tr; + g_pParticleSystemMgr->Query()->TraceLine( vecCurrentPos, endpos, CONTENTS_SOLID, NULL, m_nCollisionGroupNumber, &tr ); + + //Push back a proportional amount to the probe + d[i] = -d[i] * (1.0f-tr.fraction); + + assert(( 1.0f - tr.fraction ) >= 0.0f ); + + resultForce += 1.0f-tr.fraction; + resultDirection += d[i]; + } + + //If we've hit nothing, then point up + if ( resultDirection == vec3_origin ) + { + resultDirection = Vector( 0, 0, 1 ); + resultForce = 0.0f; + } + + //Just return the direction + VectorNormalize( resultDirection ); + resultDirection *= resultForce; + + vecRepulsionAmount.x = Lerp( resultForce, m_vecOutputMin.x, m_vecOutputMax.x ); + vecRepulsionAmount.y = Lerp( resultForce, m_vecOutputMin.y, m_vecOutputMax.y ); + vecRepulsionAmount.z = Lerp( resultForce, m_vecOutputMin.z, m_vecOutputMax.z ); + + vecRepulsionAmount *= resultDirection; + + if ( m_nChildCP != -1 ) + { + for( CParticleCollection *pChild = pParticles->m_Children.m_pHead; pChild; pChild = pChild->m_pNext ) + { + if ( pChild->GetGroupID() == m_nChildGroupID ) + { + Vector vecPassForce = Vector(resultForce, 0, 0); + pChild->SetControlPoint( m_nChildCP, resultDirection ); + pChild->SetControlPoint( m_nChildCP+1, vecPassForce ); + } + } + } + } + + for( ; nParticleCount--; start_p++ ) + { + + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + float *pxyz_prev = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + const float *radius = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_RADIUS, start_p ); + + if ( m_bProportional ) + { + vecRepulsionAmount *= *radius; + } + + pxyz[0] += vecRepulsionAmount.x; + pxyz[4] += vecRepulsionAmount.y; + pxyz[8] += vecRepulsionAmount.z; + + if ( m_bTranslate ) + { + pxyz_prev[0] += vecRepulsionAmount.x; + pxyz_prev[4] += vecRepulsionAmount.y; + pxyz_prev[8] += vecRepulsionAmount.z; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Random Yaw Flip +//----------------------------------------------------------------------------- +class C_INIT_RandomYawFlip : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_RandomYawFlip ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_YAW_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + bool InitMultipleOverride ( void ) { return true; } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const; + + float m_flPercent; + +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_RandomYawFlip, "Rotation Yaw Flip Random", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomYawFlip ) +DMXELEMENT_UNPACK_FIELD( "Flip Percentage", ".5", float, m_flPercent ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomYawFlip ) + +void C_INIT_RandomYawFlip::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + for( ; nParticleCount--; start_p++ ) + { + float flChance = pParticles->RandomFloat( 0.0, 1.0 ); + if ( flChance < m_flPercent ) + { + float flRadians = 180 * ( M_PI / 180.0f ); + float *drot = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_YAW, start_p ); + *drot += flRadians; + } + } +} + + + +//----------------------------------------------------------------------------- +// Random second sequence +//----------------------------------------------------------------------------- +class C_INIT_RandomSecondSequence : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_RandomSecondSequence ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER1_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + // TODO: Validate the ranges here! + } + + virtual void InitNewParticlesBlock( CParticleCollection *pParticles, + int start_block, int n_blocks, int nAttributeWriteMask, + void *pContext ) const + { + InitScalarAttributeRandomRangeBlock( PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER1, + m_nSequenceMin, m_nSequenceMax, + pParticles, start_block, n_blocks ); + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, int nParticleCount, int nAttributeWriteMask, void *pContext ) const + { + float *pSequence; + for( ; nParticleCount--; start_p++ ) + { + pSequence = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER1, start_p ); + *pSequence = pParticles->RandomInt( m_nSequenceMin, m_nSequenceMax ); + } + } + + int m_nSequenceMin; + int m_nSequenceMax; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_RandomSecondSequence, "Sequence Two Random", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomSecondSequence ) + DMXELEMENT_UNPACK_FIELD( "sequence_min", "0", int, m_nSequenceMin ) + DMXELEMENT_UNPACK_FIELD( "sequence_max", "0", int, m_nSequenceMax ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_RandomSecondSequence ) + + + +//----------------------------------------------------------------------------- +// Remap CP to Scalar Initializer +//----------------------------------------------------------------------------- +class C_INIT_RemapCPtoScalar : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_RemapCPtoScalar ); + + uint32 GetWrittenAttributes( void ) const + { + return 1 << m_nFieldOutput; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + virtual uint64 GetReadControlPointMask() const + { + return 1ULL << m_nCPInput; + } + + bool InitMultipleOverride ( void ) { return true; } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nField = int (clamp (m_nField, 0, 2)); + } + + int m_nCPInput; + int m_nFieldOutput; + int m_nField; + float m_flInputMin; + float m_flInputMax; + float m_flOutputMin; + float m_flOutputMax; + float m_flStartTime; + float m_flEndTime; + bool m_bScaleInitialRange; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_RemapCPtoScalar, "Remap Control Point to Scalar", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_RemapCPtoScalar ) +DMXELEMENT_UNPACK_FIELD( "emitter lifetime start time (seconds)", "-1", float, m_flStartTime ) +DMXELEMENT_UNPACK_FIELD( "emitter lifetime end time (seconds)", "-1", float, m_flEndTime ) +DMXELEMENT_UNPACK_FIELD( "input control point number", "0", int, m_nCPInput ) +DMXELEMENT_UNPACK_FIELD( "input minimum","0", float, m_flInputMin ) +DMXELEMENT_UNPACK_FIELD( "input maximum","1", float, m_flInputMax ) +DMXELEMENT_UNPACK_FIELD( "input field 0-2 X/Y/Z","0", int, m_nField ) +DMXELEMENT_UNPACK_FIELD_USERDATA( "output field", "3", int, m_nFieldOutput, "intchoice particlefield_scalar" ) +DMXELEMENT_UNPACK_FIELD( "output minimum","0", float, m_flOutputMin ) +DMXELEMENT_UNPACK_FIELD( "output maximum","1", float, m_flOutputMax ) +DMXELEMENT_UNPACK_FIELD( "output is scalar of initial random range","0", bool, m_bScaleInitialRange ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_RemapCPtoScalar ) + +void C_INIT_RemapCPtoScalar::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + const float *pCreationTime; + // clamp the result to 0 and 1 if it's alpha + float flMin=m_flOutputMin; + float flMax=m_flOutputMax; + if ( ATTRIBUTES_WHICH_ARE_0_TO_1 & ( 1 << m_nFieldOutput ) ) + { + flMin = clamp(m_flOutputMin, 0.0f, 1.0f ); + flMax = clamp(m_flOutputMax, 0.0f, 1.0f ); + } + Vector vecControlPoint; + float *ct = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + pParticles->GetControlPointAtTime( m_nCPInput, *ct, &vecControlPoint ); + + float flInput = vecControlPoint[m_nField]; + + // FIXME: SSE-ize + for( ; nParticleCount--; start_p++ ) + { + pCreationTime = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + // using raw creation time to map to emitter lifespan + float flLifeTime = *pCreationTime; + + // only use within start/end time frame + if ( ( ( flLifeTime < m_flStartTime ) || ( flLifeTime >= m_flEndTime ) ) && ( ( m_flStartTime != -1.0f) && ( m_flEndTime != -1.0f) ) ) + continue; + + + float *pOutput = pParticles->GetFloatAttributePtrForWrite( m_nFieldOutput, start_p ); + float flOutput = RemapValClamped( flInput, m_flInputMin, m_flInputMax, flMin, flMax ); + if ( m_bScaleInitialRange ) + { + flOutput = *pOutput * flOutput; + } + if ( ATTRIBUTES_WHICH_ARE_INTS & ( 1 << m_nFieldOutput ) ) + { + *pOutput = int ( flOutput ); + } + else + { + *pOutput = flOutput; + } + } +} + + + +//----------------------------------------------------------------------------- +// Remap CP to Vector Initializer +//----------------------------------------------------------------------------- +class C_INIT_RemapCPtoVector : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_RemapCPtoVector ); + + uint32 GetWrittenAttributes( void ) const + { + return 1 << m_nFieldOutput | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + uint64 nMask = ( 1ULL << m_nCPInput ); + if ( m_nLocalSpaceCP != -1 ) + { + nMask |= ( 1ULL << m_nLocalSpaceCP ); + } + return nMask; + } + + bool InitMultipleOverride ( void ) { return true; } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nField = int (clamp (m_nField, 0, 2)); + } + + int m_nCPInput; + int m_nFieldOutput; + int m_nField; + Vector m_vInputMin; + Vector m_vInputMax; + Vector m_vOutputMin; + Vector m_vOutputMax; + float m_flStartTime; + float m_flEndTime; + bool m_bScaleInitialRange; + bool m_bOffset; + bool m_bAccelerate; + int m_nLocalSpaceCP; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_RemapCPtoVector, "Remap Control Point to Vector", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_RemapCPtoVector ) +DMXELEMENT_UNPACK_FIELD( "emitter lifetime start time (seconds)", "-1", float, m_flStartTime ) +DMXELEMENT_UNPACK_FIELD( "emitter lifetime end time (seconds)", "-1", float, m_flEndTime ) +DMXELEMENT_UNPACK_FIELD( "input control point number", "0", int, m_nCPInput ) +DMXELEMENT_UNPACK_FIELD( "input minimum","0 0 0", Vector, m_vInputMin ) +DMXELEMENT_UNPACK_FIELD( "input maximum","0 0 0", Vector, m_vInputMax ) +DMXELEMENT_UNPACK_FIELD_USERDATA( "output field", "0", int, m_nFieldOutput, "intchoice particlefield_vector" ) +DMXELEMENT_UNPACK_FIELD( "output minimum","0 0 0", Vector, m_vOutputMin ) +DMXELEMENT_UNPACK_FIELD( "output maximum","0 0 0", Vector, m_vOutputMax ) +DMXELEMENT_UNPACK_FIELD( "output is scalar of initial random range","0", bool, m_bScaleInitialRange ) +DMXELEMENT_UNPACK_FIELD( "offset position","0", bool, m_bOffset ) +DMXELEMENT_UNPACK_FIELD( "accelerate position","0", bool, m_bAccelerate ) +DMXELEMENT_UNPACK_FIELD( "local space CP","-1", int, m_nLocalSpaceCP ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_RemapCPtoVector ) + +void C_INIT_RemapCPtoVector::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + Vector vecControlPoint; + pParticles->GetControlPointAtTime( m_nCPInput, pParticles->m_flCurTime, &vecControlPoint ); + Vector vOutputMinLocal = m_vOutputMin; + Vector vOutputMaxLocal = m_vOutputMax; + if ( m_nLocalSpaceCP != -1 ) + { + matrix3x4_t mat; + pParticles->GetControlPointTransformAtTime( m_nLocalSpaceCP, pParticles->m_flCurTime, &mat ); + Vector vecTransformLocal = vec3_origin; + VectorRotate( vOutputMinLocal, mat, vecTransformLocal ); + vOutputMinLocal = vecTransformLocal; + VectorRotate( vOutputMaxLocal, mat, vecTransformLocal ); + vOutputMaxLocal = vecTransformLocal; + } + + // FIXME: SSE-ize + for( ; nParticleCount--; start_p++ ) + { + const float *pCreationTime = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + // using raw creation time to map to emitter lifespan + float flLifeTime = *pCreationTime; + + // only use within start/end time frame + if ( ( ( flLifeTime < m_flStartTime ) || ( flLifeTime >= m_flEndTime ) ) && ( ( m_flStartTime != -1.0f) && ( m_flEndTime != -1.0f) ) ) + continue; + + + float *pOutput = pParticles->GetFloatAttributePtrForWrite( m_nFieldOutput, start_p ); + + Vector vOutput; + vOutput.x = RemapValClamped( vecControlPoint.x, m_vInputMin.x, m_vInputMax.x, vOutputMinLocal.x, vOutputMaxLocal.x ); + vOutput.y = RemapValClamped( vecControlPoint.y, m_vInputMin.y, m_vInputMax.y, vOutputMinLocal.y, vOutputMaxLocal.y ); + vOutput.z = RemapValClamped( vecControlPoint.z, m_vInputMin.z, m_vInputMax.z, vOutputMinLocal.z, vOutputMaxLocal.z ); + + if ( m_bScaleInitialRange ) + { + Vector vOrgValue; + SetVectorFromAttribute ( vOrgValue, pOutput ); + vOutput *= vOrgValue; + } + if ( m_nFieldOutput == 6 ) + { + pOutput[0] = max( 0.0f, min( vOutput.x, 1.0f) ); + pOutput[4] = max( 0.0f, min( vOutput.y, 1.0f) ); + pOutput[8] = max( 0.0f, min( vOutput.z, 1.0f) ); + } + else + { + float *pXYZ_Prev = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + Vector vXYZPrev; + if ( m_bAccelerate ) + { + if ( m_bOffset ) + { + Vector vOrgValue; + SetVectorFromAttribute ( vOrgValue, pOutput ); + SetVectorFromAttribute ( vXYZPrev, pXYZ_Prev ); + vOutput += vOrgValue; + vXYZPrev += vOutput; + vOutput += vOutput * pParticles->m_flDt; + SetVectorAttribute ( pOutput, vOutput ); + SetVectorAttribute ( pXYZ_Prev, vXYZPrev ); + } + else + { + vOutput *= pParticles->m_flDt; + SetVectorAttribute ( pOutput, vOutput ); + } + + } + else + { + vXYZPrev = vOutput; + if ( m_bOffset ) + { + Vector vOrgValue; + SetVectorFromAttribute ( vOrgValue, pOutput ); + SetVectorFromAttribute ( vXYZPrev, pXYZ_Prev ); + vOutput += vOrgValue; + vXYZPrev += vOutput; + + } + SetVectorAttribute ( pOutput, vOutput ); + SetVectorAttribute ( pXYZ_Prev, vXYZPrev ); + } + } + } +} + + + +class C_INIT_CreateFromParentParticles : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_CreateFromParentParticles ); + + struct ParentParticlesContext_t + { + int m_nCurrentParentParticle; + }; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK | PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; + } + + virtual void InitializeContextData( CParticleCollection *pParticles, void *pContext ) const + { + ParentParticlesContext_t *pCtx = reinterpret_cast( pContext ); + pCtx->m_nCurrentParentParticle = 0; + } + + size_t GetRequiredContextBytes( void ) const + { + return sizeof( ParentParticlesContext_t ); + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + + float m_flVelocityScale; + bool m_bRandomDistribution; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_CreateFromParentParticles, "Position From Parent Particles", OPERATOR_PI_POSITION ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_CreateFromParentParticles ) +DMXELEMENT_UNPACK_FIELD( "Inherited Velocity Scale","0", float, m_flVelocityScale ) +DMXELEMENT_UNPACK_FIELD( "Random Parent Particle Distribution","0", bool, m_bRandomDistribution ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_CreateFromParentParticles ) + +void C_INIT_CreateFromParentParticles::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + if ( !pParticles->m_pParent ) + { + for( ; nParticleCount--; start_p++ ) + { + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + + SetVectorAttribute( xyz, vec3_origin ); + SetVectorAttribute( pxyz, vec3_origin ); + } + return; + } + ParentParticlesContext_t *pCtx = reinterpret_cast( pContext ); + int nActiveParticles = pParticles->m_pParent->m_nActiveParticles; + + + if ( nActiveParticles == 0 ) + { + while( nParticleCount-- ) + { + float *lifespan = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_LIFE_DURATION, start_p ); + *lifespan = 0.0f; + start_p++; + } + return; + } + + nActiveParticles = max ( 0, nActiveParticles - 1 ); + + for( ; nParticleCount--; start_p++ ) + { + if ( m_bRandomDistribution ) + { + pCtx->m_nCurrentParentParticle = pParticles->RandomInt( 0, nActiveParticles ); + } + else if ( pCtx->m_nCurrentParentParticle > nActiveParticles ) + { + pCtx->m_nCurrentParentParticle = 0; + } + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + const float *ct = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, start_p ); + const float *pParent_xyz = pParticles->m_pParent->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_XYZ, pCtx->m_nCurrentParentParticle ); + const float *pParent_pxyz = pParticles->m_pParent->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_PREV_XYZ, pCtx->m_nCurrentParentParticle ); + + Vector vecParentXYZ; + Vector vecParentPrevXYZ; + Vector vecScaledXYZ; + + float flPrevTime = pParticles->m_flCurTime - pParticles->m_flDt; + float flSubFrame = RemapValClamped( *ct, flPrevTime, pParticles->m_flCurTime, 0, 1 ); + + + vecParentXYZ.x = pParent_xyz[0]; + vecParentXYZ.y = pParent_xyz[4]; + vecParentXYZ.z = pParent_xyz[8]; + vecParentPrevXYZ.x = pParent_pxyz[0]; + vecParentPrevXYZ.y = pParent_pxyz[4]; + vecParentPrevXYZ.z = pParent_pxyz[8]; + + VectorLerp( vecParentPrevXYZ, vecParentXYZ, flSubFrame, vecParentXYZ ); + VectorLerp( vecParentXYZ, vecParentPrevXYZ, m_flVelocityScale, vecScaledXYZ ); + SetVectorAttribute( pxyz, vecScaledXYZ ); + SetVectorAttribute( xyz, vecParentXYZ ); + + pCtx->m_nCurrentParentParticle++; + } +} + + + + +//----------------------------------------------------------------------------- +// Distance to CP Initializer +//----------------------------------------------------------------------------- +class C_INIT_DistanceToCPInit : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_DistanceToCPInit ); + + uint32 GetWrittenAttributes( void ) const + { + return 1 << m_nFieldOutput; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + return 1ULL << m_nStartCP; + } + + bool InitMultipleOverride ( void ) { return true; } + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nCollisionGroupNumber = g_pParticleSystemMgr->Query()->GetCollisionGroupFromName( m_CollisionGroupName ); + m_nStartCP = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nStartCP ) ); + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + + int m_nFieldOutput; + float m_flInputMin; + float m_flInputMax; + float m_flOutputMin; + float m_flOutputMax; + int m_nStartCP; + bool m_bLOS; + char m_CollisionGroupName[128]; + int m_nCollisionGroupNumber; + float m_flMaxTraceLength; + float m_flLOSScale; + bool m_bScaleInitialRange; + bool m_bActiveRange; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_DistanceToCPInit, "Remap Initial Distance to Control Point to Scalar", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_DistanceToCPInit ) +DMXELEMENT_UNPACK_FIELD( "distance minimum","0", float, m_flInputMin ) +DMXELEMENT_UNPACK_FIELD( "distance maximum","128", float, m_flInputMax ) +DMXELEMENT_UNPACK_FIELD_USERDATA( "output field", "3", int, m_nFieldOutput, "intchoice particlefield_scalar" ) +DMXELEMENT_UNPACK_FIELD( "output minimum","0", float, m_flOutputMin ) +DMXELEMENT_UNPACK_FIELD( "output maximum","1", float, m_flOutputMax ) +DMXELEMENT_UNPACK_FIELD( "control point","0", int, m_nStartCP ) +DMXELEMENT_UNPACK_FIELD( "ensure line of sight","0", bool, m_bLOS ) +DMXELEMENT_UNPACK_FIELD_STRING( "LOS collision group", "NONE", m_CollisionGroupName ) +DMXELEMENT_UNPACK_FIELD( "Maximum Trace Length", "-1", float, m_flMaxTraceLength ) +DMXELEMENT_UNPACK_FIELD( "LOS Failure Scalar", "0", float, m_flLOSScale ) +DMXELEMENT_UNPACK_FIELD( "output is scalar of initial random range","0", bool, m_bScaleInitialRange ) +DMXELEMENT_UNPACK_FIELD( "only active within specified distance","0", bool, m_bActiveRange ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_DistanceToCPInit ) + +void C_INIT_DistanceToCPInit::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + // clamp the result to 0 and 1 if it's alpha + float flMin=m_flOutputMin; + float flMax=m_flOutputMax; + if ( ATTRIBUTES_WHICH_ARE_0_TO_1 & ( 1 << m_nFieldOutput ) ) + { + flMin = clamp(m_flOutputMin, 0.0f, 1.0f ); + flMax = clamp(m_flOutputMax, 0.0f, 1.0f ); + } + Vector vecControlPoint1 = pParticles->GetControlPointAtCurrentTime( m_nStartCP ); + + // FIXME: SSE-ize + for( ; nParticleCount--; start_p++ ) + { + Vector vecPosition2; + const float *pXYZ = pParticles->GetFloatAttributePtr(PARTICLE_ATTRIBUTE_XYZ, start_p ); + vecPosition2 = Vector(pXYZ[0], pXYZ[4], pXYZ[8]); + Vector vecDelta = vecControlPoint1 - vecPosition2; + float flDistance = vecDelta.Length(); + if ( m_bActiveRange && ( flDistance < m_flInputMin || flDistance > m_flInputMax ) ) + { + continue; + } + if ( m_bLOS ) + { + Vector vecEndPoint = vecPosition2; + if ( m_flMaxTraceLength != -1.0f && m_flMaxTraceLength < flDistance ) + { + VectorNormalize(vecEndPoint); + vecEndPoint *= m_flMaxTraceLength; + vecEndPoint += vecControlPoint1; + } + CBaseTrace tr; + g_pParticleSystemMgr->Query()->TraceLine( vecControlPoint1, vecEndPoint, MASK_OPAQUE_AND_NPCS, NULL , m_nCollisionGroupNumber, &tr ); + if (tr.fraction != 1.0f) + { + flDistance *= tr.fraction * m_flLOSScale; + } + + } + + float flOutput = RemapValClamped( flDistance, m_flInputMin, m_flInputMax, flMin, flMax ); + if ( m_bScaleInitialRange ) + { + const float *pInitialOutput = pParticles->GetFloatAttributePtr( m_nFieldOutput, start_p ); + flOutput = *pInitialOutput * flOutput; + } + float *pOutput = pParticles->GetFloatAttributePtrForWrite( m_nFieldOutput, start_p ); + + *pOutput = flOutput; + } +} + + + + +class C_INIT_LifespanFromVelocity : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_LifespanFromVelocity ); + + Vector m_vecComponentScale; + float m_flTraceOffset; + float m_flMaxTraceLength; + float m_flTraceTolerance; + int m_nCollisionGroupNumber; + int m_nMaxPlanes; + int m_nAllowedPlanes; + char m_CollisionGroupName[128]; + + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK | PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; + } + + void InitializeContextData( CParticleCollection *pParticles, + void *pContext ) const + { + } + + size_t GetRequiredContextBytes( ) const + { + return sizeof( CWorldCollideContextData ); + } + + bool InitMultipleOverride ( void ) { return true; } + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nCollisionGroupNumber = g_pParticleSystemMgr->Query()->GetCollisionGroupFromName( m_CollisionGroupName ); + m_nAllowedPlanes = ( min ( MAX_WORLD_PLANAR_CONSTRAINTS, m_nMaxPlanes ) - 1 ); + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; + + virtual void InitNewParticlesBlock( CParticleCollection *pParticles, + int start_block, int n_blocks, int nAttributeWriteMask, + void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_LifespanFromVelocity, "Lifetime from Time to Impact", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_LifespanFromVelocity ) +DMXELEMENT_UNPACK_FIELD_STRING( "trace collision group", "NONE", m_CollisionGroupName ) +DMXELEMENT_UNPACK_FIELD( "maximum trace length", "1024", float, m_flMaxTraceLength ) +DMXELEMENT_UNPACK_FIELD( "trace offset", "0", float, m_flTraceOffset ) +DMXELEMENT_UNPACK_FIELD( "trace recycle tolerance", "64", float, m_flTraceTolerance ) +DMXELEMENT_UNPACK_FIELD( "maximum points to cache", "16", int, m_nMaxPlanes ) +DMXELEMENT_UNPACK_FIELD( "bias distance", "1 1 1", Vector, m_vecComponentScale ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_LifespanFromVelocity ) + + +void C_INIT_LifespanFromVelocity::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + CWorldCollideContextData **ppCtx; + if ( pParticles->m_pParent ) + ppCtx = &( pParticles->m_pParent->m_pCollisionCacheData[COLLISION_MODE_INITIAL_TRACE_DOWN] ); + else + ppCtx = &( pParticles->m_pCollisionCacheData[COLLISION_MODE_INITIAL_TRACE_DOWN] ); + + CWorldCollideContextData *pCtx = NULL; + if ( ! *ppCtx ) + { + *ppCtx = new CWorldCollideContextData; + (*ppCtx)->m_nActivePlanes = 0; + (*ppCtx)->m_nActivePlanes = 0; + (*ppCtx)->m_nNumFixedPlanes = 0; + } + pCtx = *ppCtx; + + float flTol = m_flTraceTolerance * m_flTraceTolerance; + + //Trace length takes the max trace and subtracts the offset to get the actual total. + float flTotalTraceDist = m_flMaxTraceLength - m_flTraceOffset; + + //Offset percentage to account for if we've hit something within the offset (but not spawn) area + float flOffsetPct = m_flMaxTraceLength / ( flTotalTraceDist + FLT_EPSILON ); + + FourVectors v4ComponentScale; + v4ComponentScale.DuplicateVector( m_vecComponentScale ); + while( nParticleCount-- ) + { + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + float *pPrevXYZ = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + + float *dtime = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_LIFE_DURATION, start_p ); + + + Vector vecXYZ( pxyz[0], pxyz[4], pxyz[8] ); + Vector vecXYZ_Prev( pPrevXYZ[0], pPrevXYZ[4], pPrevXYZ[8] ); + + //Calculate velocity and account for frame delta time + Vector vDelta = vecXYZ - vecXYZ_Prev; + float flVelocity = VectorLength( vDelta ); + flVelocity /= pParticles->m_flPreviousDt; + + fltx4 fl4TraceOffset = ReplicateX4( m_flTraceOffset ); + + //Normalize the delta and get the offset to use from the normalized delta times the offset + VectorNormalize( vDelta ); + Vector vecOffset = vDelta * m_flTraceOffset; + + Vector vecStartPnt = vecXYZ + vecOffset; + Vector vecEndPnt = ( vDelta * flTotalTraceDist ) + vecStartPnt; + + // Use SIMD section to interface with plane cache, even though we're not SIMD here + // Test versus existing Data + FourVectors fvStartPnt; + fvStartPnt.DuplicateVector( vecStartPnt ); + FourVectors fvEndPnt; + fvEndPnt.DuplicateVector( vecEndPnt ); + FourVectors v4PointOnPlane; + FourVectors v4PlaneNormal; + FourVectors v4Delta; + fltx4 fl4ClosestDist = Four_FLT_MAX; + for( int i = 0 ; i < pCtx->m_nActivePlanes; i++ ) + { + if ( pCtx->m_bPlaneActive[i] ) + { + fltx4 fl4TrialDistance = MaxSIMD( + fvStartPnt.DistSqrToLineSegment( pCtx->m_TraceStartPnt[i], pCtx->m_TraceEndPnt[i] ), + fvEndPnt.DistSqrToLineSegment( pCtx->m_TraceStartPnt[i], pCtx->m_TraceEndPnt[i] ) ); + // If the trial distance is closer than the existing closest, replace. + if ( !IsAllGreaterThan( fl4TrialDistance, fl4ClosestDist ) ) + { + fl4ClosestDist = fl4TrialDistance; + v4PointOnPlane = pCtx->m_PointOnPlane[i]; + } + } + } + fl4ClosestDist = fabs( fl4ClosestDist ); + // If we're outside the tolerance range, do a new trace and store it. + if ( IsAllGreaterThan( fl4ClosestDist, ReplicateX4( flTol ) ) ) + { + //replace this with fast raycaster when available + CBaseTrace tr; + tr.plane.normal = vec3_invalid; + g_pParticleSystemMgr->Query()->TraceLine( vecStartPnt, vecEndPnt, CONTENTS_SOLID, NULL , m_nCollisionGroupNumber, &tr ); + + //Set the lifespan to 0 if we start solid, our trace distance is 0, or we hit within the offset area + if ( ( tr.fraction < ( 1 - flOffsetPct ) ) || tr.startsolid || flTotalTraceDist == 0.0f ) + { + *dtime = 0.0f; + fl4TraceOffset = ReplicateX4( 0.0f ); + fvStartPnt.DuplicateVector( vec3_origin ); + v4PointOnPlane.DuplicateVector( vec3_origin ); + } + else + { + int nIndex = pCtx->m_nNumFixedPlanes; + Vector vPointOnPlane = vecStartPnt + ( tr.fraction * ( vecEndPnt - vecStartPnt ) ) ; + pCtx->m_bPlaneActive[nIndex] = true; + pCtx->m_PointOnPlane[nIndex].DuplicateVector( vPointOnPlane ); + pCtx->m_PlaneNormal[nIndex].DuplicateVector( tr.plane.normal ); + pCtx->m_TraceStartPnt[nIndex].DuplicateVector( vecStartPnt ); + pCtx->m_TraceEndPnt[nIndex].DuplicateVector( vecEndPnt ); + + fvStartPnt.DuplicateVector( vecStartPnt ); + v4PointOnPlane.DuplicateVector( vPointOnPlane ); + + pCtx->m_nNumFixedPlanes = pCtx->m_nNumFixedPlanes + 1; + if ( pCtx->m_nNumFixedPlanes > m_nAllowedPlanes ) + pCtx->m_nNumFixedPlanes = 0; + pCtx->m_nActivePlanes = min( m_nAllowedPlanes, pCtx->m_nActivePlanes + 1 ); + } + } + + fvStartPnt -= v4PointOnPlane; + //Scale components to remove undesired axis + fvStartPnt *= v4ComponentScale; + //Find the length of the trace + //Need to use the adjusted value of the trace length and collision point to account for the offset + fltx4 fl4Dist = AddSIMD ( fvStartPnt.length(), fl4TraceOffset ); + flVelocity += FLT_EPSILON; + //Divide by Velocity to get Lifespan + *dtime = SubFloat( fl4Dist, 0) / flVelocity; + + } +} + + +void C_INIT_LifespanFromVelocity::InitNewParticlesBlock( CParticleCollection *pParticles, + int start_block, int n_blocks, int nAttributeWriteMask, + void *pContext ) const +{ + CWorldCollideContextData **ppCtx; + if ( pParticles->m_pParent ) + ppCtx = &( pParticles->m_pParent->m_pCollisionCacheData[COLLISION_MODE_INITIAL_TRACE_DOWN] ); + else + ppCtx = &( pParticles->m_pCollisionCacheData[COLLISION_MODE_INITIAL_TRACE_DOWN] ); + + CWorldCollideContextData *pCtx = NULL; + if ( ! *ppCtx ) + { + *ppCtx = new CWorldCollideContextData; + (*ppCtx)->m_nActivePlanes = 0; + (*ppCtx)->m_nActivePlanes = 0; + (*ppCtx)->m_nNumFixedPlanes = 0; + } + pCtx = *ppCtx; + + float flTol = m_flTraceTolerance * m_flTraceTolerance; + + size_t attr_stride; + + FourVectors *pXYZ = pParticles->Get4VAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, &attr_stride ); + pXYZ += attr_stride * start_block; + FourVectors *pPrev_XYZ = pParticles->Get4VAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, &attr_stride ); + pPrev_XYZ += attr_stride * start_block; + fltx4 *pLifespan = pParticles->GetM128AttributePtrForWrite( PARTICLE_ATTRIBUTE_LIFE_DURATION, &attr_stride ); + pLifespan += attr_stride * start_block; + + //Trace length takes the max trace and subtracts the offset to get the actual total. + float flTotalTraceDist = m_flMaxTraceLength - m_flTraceOffset; + fltx4 fl4TotalTraceDist = ReplicateX4( flTotalTraceDist ); + + //Offset percentage to account for if we've hit something within the offset (but not spawn) area + float flOffsetPct = m_flMaxTraceLength / ( flTotalTraceDist + FLT_EPSILON ); + + fltx4 fl4PrevDT = ReplicateX4( 1.0f / pParticles->m_flPreviousDt ); + + FourVectors v4ComponentScale; + v4ComponentScale.DuplicateVector( m_vecComponentScale ); + + while( n_blocks-- ) + { + // Determine Velocity + FourVectors fvDelta = *pXYZ; + fvDelta -= *pPrev_XYZ; + fltx4 fl4Velocity = fvDelta.length(); + fl4Velocity = MulSIMD ( fl4Velocity, fl4PrevDT ); + + fltx4 fl4TraceOffset = ReplicateX4( m_flTraceOffset ); + + //Normalize the delta and get the offset to use from the normalized delta times the offset + FourVectors fvDeltaNormalized = fvDelta; + fvDeltaNormalized.VectorNormalizeFast(); + FourVectors fvOffset = fvDeltaNormalized; + fvOffset *= m_flTraceOffset; + + //Start/Endpoints for our traces + FourVectors fvStartPnt = *pXYZ; + fvStartPnt += fvOffset; + FourVectors fvEndPnt = fvDeltaNormalized; + fvEndPnt *= fl4TotalTraceDist; + fvEndPnt += fvStartPnt; + + // Test versus existing Data + 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( + fvStartPnt.DistSqrToLineSegment( pCtx->m_TraceStartPnt[i], pCtx->m_TraceEndPnt[i] ), + fvEndPnt.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 ); + } + } + + // If we're outside the tolerance range, do a new trace and store it. + fltx4 fl4OutOfRange = CmpGtSIMD( fl4ClosestDist, ReplicateX4( flTol ) ); + if ( IsAnyNegative( fl4OutOfRange ) ) + { + int nMask = TestSignSIMD( fl4OutOfRange ); + for(int i=0; i < 4; i++ ) + { + if ( nMask & ( 1 << i ) ) + { + Vector start = fvStartPnt.Vec( i ); + Vector end = fvEndPnt.Vec( i ); + + //replace this with fast raycaster when available + CBaseTrace tr; + tr.plane.normal = vec3_invalid; + g_pParticleSystemMgr->Query()->TraceLine( start, end, CONTENTS_SOLID, NULL , m_nCollisionGroupNumber, &tr ); + + //Set the lifespan to 0 if we start solid, our trace distance is 0, or we hit within the offset area + if ( ( tr.fraction < ( 1 - flOffsetPct ) ) || tr.startsolid || flTotalTraceDist == 0.0f ) + { + SubFloat( fvStartPnt.x, i ) = 0.0f; + SubFloat( fvStartPnt.y, i ) = 0.0f; + SubFloat( fvStartPnt.z, i ) = 0.0f; + SubFloat( v4PointOnPlane.x, i ) = 0.0f; + SubFloat( v4PointOnPlane.y, i ) = 0.0f; + SubFloat( v4PointOnPlane.z, i ) = 0.0f; + SubFloat( fl4TraceOffset, i ) = 0.0f; + } + else + { + int nIndex = pCtx->m_nNumFixedPlanes; + Vector vPointOnPlane = start + ( tr.fraction * ( end - start ) ) ; + SubFloat( v4PointOnPlane.x, i ) = vPointOnPlane.x; + SubFloat( v4PointOnPlane.y, i ) = vPointOnPlane.y; + SubFloat( v4PointOnPlane.z, i ) = vPointOnPlane.z; + pCtx->m_bPlaneActive[nIndex] = true; + pCtx->m_PointOnPlane[nIndex].DuplicateVector( vPointOnPlane ); + pCtx->m_PlaneNormal[nIndex].DuplicateVector( tr.plane.normal ); + pCtx->m_TraceStartPnt[nIndex].DuplicateVector( start ); + pCtx->m_TraceEndPnt[nIndex].DuplicateVector( end ); + pCtx->m_nNumFixedPlanes = pCtx->m_nNumFixedPlanes + 1; + if ( pCtx->m_nNumFixedPlanes > m_nAllowedPlanes ) + pCtx->m_nNumFixedPlanes = 0; + pCtx->m_nActivePlanes = min( m_nAllowedPlanes, pCtx->m_nActivePlanes + 1 ); + } + } + } + } + + //Find the length of the trace + fvStartPnt -= v4PointOnPlane; + fvStartPnt *= v4ComponentScale; + //Need to use the adjusted value of the trace length and collision point to account for the offset + fltx4 fl4Dist = AddSIMD ( fvStartPnt.length(), fl4TraceOffset ); + fl4Velocity = AddSIMD( fl4Velocity, Four_Epsilons ); + //Divide by Velocity to get Lifespan + *pLifespan = DivSIMD( fl4Dist, fl4Velocity ); + + pXYZ += attr_stride; + pPrev_XYZ += attr_stride; + pLifespan += attr_stride; + } +} + + + + + +class C_INIT_CreateFromPlaneCache : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_INIT_CreateFromPlaneCache ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK | PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + size_t GetRequiredContextBytes( ) const + { + return sizeof( CWorldCollideContextData ); + } + + void InitNewParticlesScalar( CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, + void *pContext) const; +}; + +DEFINE_PARTICLE_OPERATOR( C_INIT_CreateFromPlaneCache, "Position from Parent Cache", OPERATOR_PI_POSITION ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_INIT_CreateFromPlaneCache ) +END_PARTICLE_OPERATOR_UNPACK( C_INIT_CreateFromPlaneCache ) + +void C_INIT_CreateFromPlaneCache::InitNewParticlesScalar( + CParticleCollection *pParticles, int start_p, + int nParticleCount, int nAttributeWriteMask, void *pContext ) const +{ + if ( !pParticles->m_pParent ) + { + for( ; nParticleCount--; start_p++ ) + { + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + + SetVectorAttribute( xyz, vec3_origin ); + SetVectorAttribute( pxyz, vec3_origin ); + } + return; + } + + + CWorldCollideContextData **ppCtx; + if ( pParticles->m_pParent ) + ppCtx = &( pParticles->m_pParent->m_pCollisionCacheData[COLLISION_MODE_INITIAL_TRACE_DOWN] ); + else + ppCtx = &( pParticles->m_pCollisionCacheData[COLLISION_MODE_INITIAL_TRACE_DOWN] ); + + CWorldCollideContextData *pCtx = NULL; + if ( ! *ppCtx ) + { + *ppCtx = new CWorldCollideContextData; + (*ppCtx)->m_nActivePlanes = 0; + (*ppCtx)->m_nNumFixedPlanes = 0; + FourVectors fvEmpty; + fvEmpty.DuplicateVector( vec3_origin ); + (*ppCtx)->m_PointOnPlane[0] = fvEmpty; + } + pCtx = *ppCtx; + if ( pCtx->m_nActivePlanes > 0 ) + { + for( ; nParticleCount--; start_p++ ) + { + int nIndex = pParticles->RandomInt( 0, pCtx->m_nActivePlanes - 1 ); + if ( pCtx->m_PlaneNormal[nIndex].Vec( 0 ) == vec3_invalid ) + { + float *plifespan = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_LIFE_DURATION, start_p ); + *plifespan = 0.0f; + } + else + { + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + FourVectors fvPoint = pCtx->m_PointOnPlane[nIndex]; + Vector vPoint = fvPoint.Vec( 0 ); + SetVectorAttribute( xyz, vPoint ); + SetVectorAttribute( pxyz, vPoint ); + } + } + } + else + { + for( ; nParticleCount--; start_p++ ) + { + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, start_p ); + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, start_p ); + SetVectorAttribute( xyz, vec3_origin ); + SetVectorAttribute( pxyz, vec3_origin ); + } + } +} + + + + + +// +// +// +// + +//----------------------------------------------------------------------------- +// Purpose: Add all operators to be considered active, here +//----------------------------------------------------------------------------- +void AddBuiltInParticleInitializers( void ) +{ + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_CreateAlongPath ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_MoveBetweenPoints ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_CreateWithinSphere ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_VelocityRandom ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_CreateOnModel ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_CreateWithinBox ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_RandomRotationSpeed ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_RandomLifeTime ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_RandomAlpha ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_RandomRadius ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_RandomRotation ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_RandomYaw ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_RandomColor ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_RandomTrailLength ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_RandomSequence ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_PositionOffset ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_PositionWarp ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_CreationNoise ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_InitialVelocityNoise ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_RemapScalar ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_InheritVelocity ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_AgeNoise ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_SequenceLifeTime ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_CreateInHierarchy ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_RemapScalarToVector ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_CreateSequentialPath ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_InitialRepulsionVelocity ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_RandomYawFlip ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_RandomSecondSequence ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_RemapCPtoScalar ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_RemapCPtoVector ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_CreateFromParentParticles ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_DistanceToCPInit ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_LifespanFromVelocity ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_INITIALIZER, C_INIT_CreateFromPlaneCache ); +} + diff --git a/particles/builtin_particle_emitters.cpp b/particles/builtin_particle_emitters.cpp new file mode 100644 index 0000000..b7a8742 --- /dev/null +++ b/particles/builtin_particle_emitters.cpp @@ -0,0 +1,876 @@ +//========= 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" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern int g_nParticle_Multiplier; + +//----------------------------------------------------------------------------- +// Emits particles immediately +//----------------------------------------------------------------------------- +struct InstantaneousEmitterContext_t +{ + int m_nRemainingParticles; + int m_ActualParticlesToEmit; + float m_flTimeOffset; + bool m_bReadScaleFactor; +}; + +class C_OP_InstantaneousEmitter : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_InstantaneousEmitter ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + virtual uint64 GetReadControlPointMask() const + { + if ( m_nScaleControlPoint >= 0 ) + return ( 1ULL << m_nScaleControlPoint ); + return 0; + } + + virtual uint32 Emit( CParticleCollection *pParticles, float flCurStrength, + void *pContext ) const; + + // unpack structure will be applied by creator. add extra initialization needed here + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + if ( m_nMinParticlesToEmit >= 0 ) + { + if ( m_nMinParticlesToEmit > m_nParticlesToEmit ) + { + V_swap( m_nParticlesToEmit, m_nMinParticlesToEmit ); + } + } + + if ( m_nPerFrameNum < 0 ) + { + m_nPerFrameNum = INT_MAX; + } + m_nScaleControlPointField = clamp( m_nScaleControlPointField, 0, 2 ); + } + + virtual void StopEmission( CParticleCollection *pParticles, void *pContext, bool bInfiniteOnly ) const + { + InstantaneousEmitterContext_t *pCtx = reinterpret_cast( pContext ); + if ( !bInfiniteOnly ) + { + pCtx->m_nRemainingParticles = 0; + } + } + virtual void StartEmission( CParticleCollection *pParticles, void *pContext, bool bInfiniteOnly ) const + { + InstantaneousEmitterContext_t *pCtx = reinterpret_cast( pContext ); + if ( !bInfiniteOnly ) + { + pCtx->m_nRemainingParticles = pCtx->m_ActualParticlesToEmit; + SkipToTime( pParticles->m_flCurTime, pParticles, pCtx ); + } + } + + // Called when the SFM wants to skip forward in time + virtual void SkipToTime( float flTime, CParticleCollection *pParticles, void *pContext ) const + { + // NOTE: This is a bit of a hack. We're saying that if we're skipping more than two seconds, that we're + // probably not going to bother emitting at all. Really, this would have to know the maximum + // lifetime of the child particles and only skip if past that. + + InstantaneousEmitterContext_t *pCtx = reinterpret_cast( pContext ); + float flStartTime = m_flStartTime + pCtx->m_flTimeOffset; + if ( flTime > ( flStartTime + 2.0f ) ) + { + pCtx->m_nRemainingParticles = 0; + } + } + + virtual void InitializeContextData( CParticleCollection *pParticles, void *pContext ) const + { + InstantaneousEmitterContext_t *pCtx = reinterpret_cast( pContext ); + if ( m_nMinParticlesToEmit >= 0 ) + { + pCtx->m_ActualParticlesToEmit = pParticles->RandomInt( m_nMinParticlesToEmit, m_nParticlesToEmit ); + } + else + { + pCtx->m_ActualParticlesToEmit = m_nParticlesToEmit; + } + pCtx->m_nRemainingParticles = pCtx->m_ActualParticlesToEmit; + pCtx->m_flTimeOffset = 0.0f; + pCtx->m_bReadScaleFactor = false; + } + + virtual void Restart( CParticleCollection *pParticles, void *pContext ) + { + InstantaneousEmitterContext_t *pCtx = reinterpret_cast( pContext ); + pCtx->m_nRemainingParticles = pCtx->m_ActualParticlesToEmit; + pCtx->m_flTimeOffset = pParticles->m_flCurTime; + pCtx->m_bReadScaleFactor = false; + } + + size_t GetRequiredContextBytes( void ) const + { + return sizeof( InstantaneousEmitterContext_t ); + } + + virtual bool MayCreateMoreParticles( CParticleCollection *pParticles, void *pContext ) const + { + InstantaneousEmitterContext_t *pCtx = reinterpret_cast( pContext ); + return !(pCtx->m_nRemainingParticles <= 0); + } + + int m_nParticlesToEmit; + int m_nMinParticlesToEmit; + float m_flStartTime; + int m_nPerFrameNum; + int m_nScaleControlPoint; + int m_nScaleControlPointField; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_InstantaneousEmitter, "emit_instantaneously", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_InstantaneousEmitter ) + DMXELEMENT_UNPACK_FIELD( "emission_start_time", "0", float, m_flStartTime ) + DMXELEMENT_UNPACK_FIELD( "num_to_emit_minimum", "-1", int, m_nMinParticlesToEmit ) + DMXELEMENT_UNPACK_FIELD( "num_to_emit", "100", int, m_nParticlesToEmit ) + DMXELEMENT_UNPACK_FIELD( "maximum emission per frame", "-1", int, m_nPerFrameNum ) + DMXELEMENT_UNPACK_FIELD( "emission count scale control point", "-1", int, m_nScaleControlPoint ) + DMXELEMENT_UNPACK_FIELD( "emission count scale control point field", "0", int, m_nScaleControlPointField ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_InstantaneousEmitter ) + + +uint32 C_OP_InstantaneousEmitter::Emit( CParticleCollection *pParticles, float flCurStrength, + void *pContext ) const +{ + // Don't emit any more if the particle system has emitted all it's supposed to. + InstantaneousEmitterContext_t *pCtx = reinterpret_cast( pContext ); + if ( pCtx->m_nRemainingParticles <= 0 ) + return 0; + + // Wait until we're told to start emitting + float flStartTime = m_flStartTime + pCtx->m_flTimeOffset; + if ( pParticles->m_flCurTime < flStartTime ) + return 0; + + if ( pCtx->m_ActualParticlesToEmit == 0 ) + return 0; + + if ( ( m_nScaleControlPoint >= 0 ) && !pCtx->m_bReadScaleFactor ) + { + Vector vecScale; + if ( flStartTime <= pParticles->m_flCurTime && flStartTime >= pParticles->m_flCurTime - pParticles->m_flPreviousDt ) + { + pParticles->GetControlPointAtTime( m_nScaleControlPoint, flStartTime, &vecScale ); + } + else + { + pParticles->GetControlPointAtPrevTime( m_nScaleControlPoint, &vecScale ); + } + + pCtx->m_ActualParticlesToEmit *= vecScale[m_nScaleControlPointField]; + pCtx->m_nRemainingParticles *= vecScale[m_nScaleControlPointField]; + pCtx->m_bReadScaleFactor = true; + } + + pCtx->m_nRemainingParticles = max( pCtx->m_nRemainingParticles, 0 ); + + // NOTE: Applying the scale here because I don't believe we can sample the control point + // values inside + // We're only allowed to emit so many particles, though.. + // If we run out of room, only emit the last N particles + int nAllowedParticlesToEmit = pParticles->m_nMaxAllowedParticles - pParticles->m_nActiveParticles; + // Cap to the maximum emission per frame + int nParticlesThisFrame = min( m_nPerFrameNum, pCtx->m_nRemainingParticles ); + nAllowedParticlesToEmit = min( nAllowedParticlesToEmit, nParticlesThisFrame ); + int nActualParticlesToEmit = min( nAllowedParticlesToEmit, pCtx->m_ActualParticlesToEmit * g_nParticle_Multiplier ); + pCtx->m_nRemainingParticles -= nParticlesThisFrame; + Assert( pCtx->m_nRemainingParticles >= 0 ); + + if ( nActualParticlesToEmit == 0 ) + return 0; + + int nStartParticle = pParticles->m_nActiveParticles; + pParticles->SetNActiveParticles( nActualParticlesToEmit + pParticles->m_nActiveParticles ); + + // !! speed!! do sse init here + for( int i = nStartParticle; i < nStartParticle + nActualParticlesToEmit; i++ ) + { + float *pTimeStamp = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_CREATION_TIME, i ); + *pTimeStamp = flStartTime; + } + + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; +} + + +//----------------------------------------------------------------------------- +// Emits particles over time +//----------------------------------------------------------------------------- +struct ContinuousEmitterContext_t +{ + float m_flTotalActualParticlesSoFar; + int m_nTotalEmittedSoFar; + float m_flNextEmitTime; + float m_flTimeOffset; + bool m_bStoppedEmission; +}; + +bool g_bDontMakeSkipToTimeTakeForever = false; + + +class C_OP_ContinuousEmitter : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_ContinuousEmitter ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + if ( m_flEmitRate < 0.0f ) + { + m_flEmitRate = 0.0f; + } + if ( m_flEmissionDuration < 0.0f ) + { + m_flEmissionDuration = 0.0f; + } + m_flEmitRate *= g_nParticle_Multiplier; + } + + virtual uint32 Emit( CParticleCollection *pParticles, float flCurStrength, + void *pContext ) const ; + + inline bool IsInfinitelyEmitting() const + { + return ( m_flEmissionDuration == 0.0f ); + } + + virtual void StopEmission( CParticleCollection *pParticles, void *pContext, bool bInfiniteOnly ) const + { + ContinuousEmitterContext_t *pCtx = reinterpret_cast( pContext ); + if ( !bInfiniteOnly || IsInfinitelyEmitting() ) + { + pCtx->m_bStoppedEmission = true; + } + } + virtual void StartEmission( CParticleCollection *pParticles, void *pContext, bool bInfiniteOnly ) const + { + ContinuousEmitterContext_t *pCtx = reinterpret_cast( pContext ); + if ( !bInfiniteOnly || IsInfinitelyEmitting() ) + { + pCtx->m_bStoppedEmission = false; + SkipToTime( pParticles->m_flCurTime, pParticles, pCtx ); + } + } + + virtual void InitializeContextData( CParticleCollection *pParticles, void *pContext ) const + { + ContinuousEmitterContext_t *pCtx = reinterpret_cast( pContext ); + pCtx->m_flNextEmitTime = m_flStartTime; + pCtx->m_flTotalActualParticlesSoFar = 0.0f; + pCtx->m_nTotalEmittedSoFar = 0; + pCtx->m_flTimeOffset = 0.0f; + pCtx->m_bStoppedEmission = false; + } + + virtual void Restart( CParticleCollection *pParticles, void *pContext ) + { + if ( !IsInfinitelyEmitting() ) + { + ContinuousEmitterContext_t *pCtx = reinterpret_cast( pContext ); + pCtx->m_flNextEmitTime = pParticles->m_flCurTime + m_flStartTime; + pCtx->m_flTotalActualParticlesSoFar = 0.0f; + pCtx->m_nTotalEmittedSoFar = 0; + pCtx->m_flTimeOffset = pParticles->m_flCurTime; + } + } + + // Called when the SFM wants to skip forward in time + // Currently hacked for save/load pre-sim - correct solution is to serialize rather + // than skip-to-time and simulate + virtual void SkipToTime( float flTime, CParticleCollection *pParticles, void *pContext ) const + { + ContinuousEmitterContext_t *pCtx = reinterpret_cast( pContext ); + float flStartTime = m_flStartTime + pCtx->m_flTimeOffset; + if ( flTime <= flStartTime ) + return; + + float flControlPointScale = pParticles->GetHighestControlPoint(); + flControlPointScale *= m_flEmissionScale; + float flEmissionRate = m_flEmitRate; + + float flEmitStrength; + if ( pParticles->CheckIfOperatorShouldRun( this, &flEmitStrength ) ) + { + flEmissionRate *= flEmitStrength; + } + + if ( flControlPointScale != 0.0f ) + { + flEmissionRate *= flControlPointScale; + } + + float flPrevDrawTime = pParticles->m_flCurTime - flTime; + float flCurrDrawTime = pParticles->m_flCurTime; + + if ( !IsInfinitelyEmitting() ) + { + if ( flPrevDrawTime < flStartTime ) + { + flPrevDrawTime = flStartTime; + } + //if ( flCurrDrawTime > flStartTime + m_flEmissionDuration ) + //{ + // flCurrDrawTime = flStartTime + m_flEmissionDuration; + //} + } + float flDeltaTime = flCurrDrawTime - flPrevDrawTime; + flDeltaTime = min( flDeltaTime, 4.f ); + flPrevDrawTime = flCurrDrawTime - flDeltaTime; + //disabled for now + pCtx->m_flTotalActualParticlesSoFar = flDeltaTime * flEmissionRate; + + + //if ( !IsInfinitelyEmitting() ) + // pCtx->m_flTotalActualParticlesSoFar = min( pCtx->m_ActualParticlesToEmit, pCtx->m_flTotalActualParticlesSoFar ); + pCtx->m_nTotalEmittedSoFar = 0; + //simulate a bunch + int nActualParticlesToEmit = floor (pCtx->m_flTotalActualParticlesSoFar); + int nStartParticle = pParticles->m_nActiveParticles; + + if ( pParticles->m_nMaxAllowedParticles < nStartParticle + nActualParticlesToEmit ) + { + nActualParticlesToEmit = pParticles->m_nMaxAllowedParticles - nStartParticle; + } + + pParticles->SetNActiveParticles( nActualParticlesToEmit + pParticles->m_nActiveParticles ); + + float flTimeStampStep = ( flDeltaTime ) / ( nActualParticlesToEmit ); + float flTimeStep = flPrevDrawTime + flTimeStampStep; + + // Set the particle creation time to the exact sub-frame particle emission time + // !! speed!! do sse init here + for( int i = nStartParticle; i < nStartParticle + nActualParticlesToEmit; i++ ) + { + float *pTimeStamp = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_CREATION_TIME, i ); + flTimeStep = min( flTimeStep, flCurrDrawTime ); + *pTimeStamp = flTimeStep; + flTimeStep += flTimeStampStep; + } + + if ( !g_bDontMakeSkipToTimeTakeForever ) + { + flPrevDrawTime = max( flPrevDrawTime, flCurrDrawTime - pParticles->m_pDef->m_flNoDrawTimeToGoToSleep ); + pParticles->m_flCurTime = flPrevDrawTime; + pParticles->m_fl4CurTime = ReplicateX4( flPrevDrawTime ); + for( float i = flPrevDrawTime; i < flCurrDrawTime; i += 0.1 ) + { + pParticles->Simulate( .1, false ); + } + } + } + + size_t GetRequiredContextBytes( void ) const + { + return sizeof( ContinuousEmitterContext_t ); + } + + virtual bool MayCreateMoreParticles( CParticleCollection *pParticles, void *pContext ) const + { + ContinuousEmitterContext_t *pCtx = reinterpret_cast( pContext ); + if ( pCtx->m_bStoppedEmission ) + return false; + + if ( m_flEmitRate <= 0.0f ) + return false; + + float flStartTime = m_flStartTime + pCtx->m_flTimeOffset; + if ( m_flEmissionDuration != 0.0f && ( pParticles->m_flCurTime - pParticles->m_flDt ) > ( flStartTime + m_flEmissionDuration ) ) + return false; + + return true; + } + + float m_flEmissionDuration; + float m_flStartTime; + float m_flEmitRate; + float m_flTimePerEmission; + float m_flEmissionScale; + bool m_bScalePerParticle; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_ContinuousEmitter, "emit_continuously", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_ContinuousEmitter ) + DMXELEMENT_UNPACK_FIELD( "emission_start_time", "0", float, m_flStartTime ) + DMXELEMENT_UNPACK_FIELD( "emission_rate", "100", float, m_flEmitRate ) + DMXELEMENT_UNPACK_FIELD( "emission_duration", "0", float, m_flEmissionDuration ) + DMXELEMENT_UNPACK_FIELD( "scale emission to used control points", "0.0", float, m_flEmissionScale ) + DMXELEMENT_UNPACK_FIELD( "use parent particles for emission scaling", "0", bool, m_bScalePerParticle ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_ContinuousEmitter ) + +uint32 C_OP_ContinuousEmitter::Emit( CParticleCollection *pParticles, float flCurStrength, + void *pContext ) const +{ + // Have we emitted all the particles we're going to emit? + // NOTE: Using C_OP_ContinuousEmitter:: avoids a virtual function call + ContinuousEmitterContext_t *pCtx = reinterpret_cast( pContext ); + + //Allows for dynamic scaling via changes in number of control points. + float flControlPointScale = pParticles->GetHighestControlPoint(); + //The emission scale here allows for a scalar value per controlpoint, like 2 or .25... + flControlPointScale *= m_flEmissionScale; + //Global strength scale brought in by operator fade in/fade out/oscillate + float flEmissionRate = m_flEmitRate * flCurStrength; + if ( flControlPointScale != 0.0f || m_bScalePerParticle ) + { + if ( m_bScalePerParticle ) + { + if ( pParticles->m_pParent ) + { + flControlPointScale = pParticles->m_pParent->m_nActiveParticles * m_flEmissionScale; + } + else + { + flControlPointScale = m_flEmissionScale; + } + + } + flEmissionRate *= flControlPointScale; + } + + if ( flEmissionRate == 0.0f ) + return 0; + + if ( !C_OP_ContinuousEmitter::MayCreateMoreParticles( pParticles, pContext ) ) + return 0; + + float flStartTime = m_flStartTime + pCtx->m_flTimeOffset; + if ( pParticles->m_flCurTime < flStartTime ) + return 0; + + Assert( flEmissionRate != 0.0f ); + + // determine our previous and current draw times and clamp them to start time and emission duration + float flPrevDrawTime = pParticles->m_flCurTime - pParticles->m_flDt; + float flCurrDrawTime = pParticles->m_flCurTime; + + if ( !IsInfinitelyEmitting() ) + { + if ( flPrevDrawTime < flStartTime ) + { + flPrevDrawTime = flStartTime; + } + if ( flCurrDrawTime > flStartTime + m_flEmissionDuration ) + { + flCurrDrawTime = flStartTime + m_flEmissionDuration; + } + } + + float flDeltaTime = flCurrDrawTime - flPrevDrawTime; + + //Calculate emission rate by delta time from last frame to determine number of particles to emit this frame as a fractional float + float flActualParticlesToEmit = flEmissionRate * flDeltaTime; + + //Add emitted particle to float counter to allow for fractional emission + pCtx->m_flTotalActualParticlesSoFar += flActualParticlesToEmit; + + //Floor float accumulated value and subtract whole int emitted so far from the result to determine total whole particles to emit this frame + int nParticlesToEmit = floor ( pCtx->m_flTotalActualParticlesSoFar ) - pCtx->m_nTotalEmittedSoFar; + + //Add emitted particles to running int total. + pCtx->m_nTotalEmittedSoFar += nParticlesToEmit; + + + if ( nParticlesToEmit == 0 ) + return 0; + + // We're only allowed to emit so many particles, though.. + // If we run out of room, only emit the last N particles + int nActualParticlesToEmit = nParticlesToEmit; + int nAllowedParticlesToEmit = pParticles->m_nMaxAllowedParticles - pParticles->m_nActiveParticles; + if ( nAllowedParticlesToEmit < nParticlesToEmit ) + { + nActualParticlesToEmit = nAllowedParticlesToEmit; + //flStartEmissionTime = pCtx->m_flNextEmitTime - flTimePerEmission * nActualParticlesToEmit; + } + if ( nActualParticlesToEmit == 0 ) + return 0; + + int nStartParticle = pParticles->m_nActiveParticles; + pParticles->SetNActiveParticles( nActualParticlesToEmit + pParticles->m_nActiveParticles ); + + + float flTimeStampStep = ( flDeltaTime ) / ( nActualParticlesToEmit ); + float flTimeStep = flPrevDrawTime + flTimeStampStep; + + // Set the particle creation time to the exact sub-frame particle emission time + // !! speed!! do sse init here + for( int i = nStartParticle; i < nStartParticle + nActualParticlesToEmit; i++ ) + { + float *pTimeStamp = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_CREATION_TIME, i ); + flTimeStep = min( flTimeStep, flCurrDrawTime ); + *pTimeStamp = flTimeStep; + flTimeStep += flTimeStampStep; + } + + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; +} + + +//----------------------------------------------------------------------------- +// Noise Emitter +//----------------------------------------------------------------------------- +struct NoiseEmitterContext_t +{ + float m_flTotalActualParticlesSoFar; + int m_nTotalEmittedSoFar; + float m_flNextEmitTime; + float m_flTimeOffset; + bool m_bStoppedEmission; +}; + +class C_OP_NoiseEmitter : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_NoiseEmitter ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + if ( m_flEmitRate < 0.0f ) + { + m_flEmitRate = 0.0f; + } + if ( m_flEmissionDuration < 0.0f ) + { + m_flEmissionDuration = 0.0f; + } + m_flEmitRate *= g_nParticle_Multiplier; + } + + virtual uint32 Emit( CParticleCollection *pParticles, float flCurStrength, + void *pContext ) const ; + + inline bool IsInfinitelyEmitting() const + { + return ( m_flEmissionDuration == 0.0f ); + } + + virtual void StopEmission( CParticleCollection *pParticles, void *pContext, bool bInfiniteOnly ) const + { + NoiseEmitterContext_t *pCtx = reinterpret_cast( pContext ); + if ( !bInfiniteOnly || IsInfinitelyEmitting() ) + { + pCtx->m_bStoppedEmission = true; + } + } + virtual void StartEmission( CParticleCollection *pParticles, void *pContext, bool bInfiniteOnly ) const + { + NoiseEmitterContext_t *pCtx = reinterpret_cast( pContext ); + if ( !bInfiniteOnly || IsInfinitelyEmitting() ) + { + pCtx->m_bStoppedEmission = false; + SkipToTime( pParticles->m_flCurTime, pParticles, pCtx ); + } + } + + virtual void InitializeContextData( CParticleCollection *pParticles, void *pContext ) const + { + NoiseEmitterContext_t *pCtx = reinterpret_cast( pContext ); + pCtx->m_flNextEmitTime = m_flStartTime; + pCtx->m_flTotalActualParticlesSoFar = 1.0f; + pCtx->m_nTotalEmittedSoFar = 0; + pCtx->m_flTimeOffset = 0.0f; + pCtx->m_bStoppedEmission = false; + } + + virtual void Restart( CParticleCollection *pParticles, void *pContext ) + { + if ( !IsInfinitelyEmitting() ) + { + NoiseEmitterContext_t *pCtx = reinterpret_cast( pContext ); + pCtx->m_flNextEmitTime = m_flStartTime + pParticles->m_flCurTime; + pCtx->m_flTotalActualParticlesSoFar = 1.0f; + pCtx->m_nTotalEmittedSoFar = 0; + pCtx->m_flTimeOffset = pParticles->m_flCurTime; + } + } + + // Called when the SFM wants to skip forward in time + virtual void SkipToTime( float flTime, CParticleCollection *pParticles, void *pContext ) const + { + NoiseEmitterContext_t *pCtx = reinterpret_cast( pContext ); + float flStartTime = m_flStartTime + pCtx->m_flTimeOffset; + if ( flTime <= flStartTime ) + return; + + float flControlPointScale = pParticles->GetHighestControlPoint(); + flControlPointScale *= m_flEmissionScale; + float flEmissionRate = m_flEmitRate; + + float flEmitStrength; + if ( pParticles->CheckIfOperatorShouldRun( this, &flEmitStrength ) ) + { + flEmissionRate *= flEmitStrength; + } + + if ( flControlPointScale != 0.0f ) + { + flEmissionRate *= flControlPointScale; + } + pCtx->m_flTotalActualParticlesSoFar = ( pParticles->m_flCurTime - flStartTime ) * flEmissionRate + 1; + + //if ( !IsInfinitelyEmitting() ) + // pCtx->m_flTotalActualParticlesSoFar = min( pCtx->m_ActualParticlesToEmit, pCtx->m_flTotalActualParticlesSoFar ); + pCtx->m_nTotalEmittedSoFar = 0; + + } + + size_t GetRequiredContextBytes( void ) const + { + return sizeof( NoiseEmitterContext_t ); + } + + virtual bool MayCreateMoreParticles( CParticleCollection *pParticles, void *pContext ) const + { + NoiseEmitterContext_t *pCtx = reinterpret_cast( pContext ); + if ( pCtx->m_bStoppedEmission ) + return false; + + if ( m_flEmitRate <= 0.0f ) + return false; + + float flStartTime = m_flStartTime + pCtx->m_flTimeOffset; + if ( m_flEmissionDuration != 0.0f && ( pParticles->m_flCurTime - pParticles->m_flDt ) > ( flStartTime + m_flEmissionDuration ) ) + return false; + + return true; + } + + float m_flEmissionDuration; + float m_flStartTime; + float m_flEmitRate; + float m_flTimePerEmission; + float m_flEmissionScale; + bool m_bAbsVal, m_bAbsValInv; + float m_flOffset; + float m_flOutputMin; + float m_flOutputMax; + float m_flNoiseScale, m_flNoiseScaleLoc; + Vector m_vecOffsetLoc; + float m_flWorldTimeScale; +}; + + +DEFINE_PARTICLE_OPERATOR( C_OP_NoiseEmitter, "emit noise", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_NoiseEmitter ) + DMXELEMENT_UNPACK_FIELD( "emission_start_time", "0", float, m_flStartTime ) + DMXELEMENT_UNPACK_FIELD( "emission_duration", "0", float, m_flEmissionDuration ) + DMXELEMENT_UNPACK_FIELD( "scale emission to used control points", "0.0", float, m_flEmissionScale ) + DMXELEMENT_UNPACK_FIELD( "time noise coordinate scale","0.1",float,m_flNoiseScale) + //DMXELEMENT_UNPACK_FIELD( "spatial noise coordinate scale","0.001",float,m_flNoiseScaleLoc) + DMXELEMENT_UNPACK_FIELD( "time coordinate offset","0", float, m_flOffset ) + //DMXELEMENT_UNPACK_FIELD( "spatial coordinate offset","0 0 0", Vector, m_vecOffsetLoc ) + DMXELEMENT_UNPACK_FIELD( "absolute value","0", bool, m_bAbsVal ) + DMXELEMENT_UNPACK_FIELD( "invert absolute value","0", bool, m_bAbsValInv ) + DMXELEMENT_UNPACK_FIELD( "emission minimum","0", float, m_flOutputMin ) + DMXELEMENT_UNPACK_FIELD( "emission maximum","100", float, m_flOutputMax ) + DMXELEMENT_UNPACK_FIELD( "world time noise coordinate scale","0", float, m_flWorldTimeScale ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_NoiseEmitter ) + +uint32 C_OP_NoiseEmitter::Emit( CParticleCollection *pParticles, float flCurStrength, + void *pContext ) const +{ + // Have we emitted all the particles we're going to emit? + // NOTE: Using C_OP_ContinuousEmitter:: avoids a virtual function call + NoiseEmitterContext_t *pCtx = reinterpret_cast( pContext ); + + //Allows for dynamic scaling via changes in number of control points. + float flControlPointScale = pParticles->GetHighestControlPoint(); + //The emission scale here allows for a scalar value per controlpoint, like 2 or .25... + flControlPointScale *= m_flEmissionScale; + + float flAbsScale; + int nAbsVal; + nAbsVal = 0xffffffff; + flAbsScale = 0.5; + if ( m_bAbsVal ) + { + nAbsVal = 0x7fffffff; + flAbsScale = 1.0; + } + + float fMin = m_flOutputMin; + float fMax = m_flOutputMax; + + + float CoordScale = m_flNoiseScale; + //float CoordScaleLoc = m_flNoiseScaleLoc; + + + float ValueScale, ValueBase; + + Vector Coord, CoordLoc, CoordWorldTime; + //CoordLoc.x = pxyz[0]; + //CoordLoc.y = pxyz[4]; + //CoordLoc.z = pxyz[8]; + //CoordLoc += m_vecOffsetLoc; + + float Offset = m_flOffset; + Coord = Vector ( (pParticles->m_flCurTime + Offset), (pParticles->m_flCurTime + Offset), (pParticles->m_flCurTime + Offset) ); + Coord *= CoordScale; + //CoordLoc *= CoordScaleLoc; + //Coord += CoordLoc; + + CoordWorldTime = Vector( (Plat_MSTime() * m_flWorldTimeScale), (Plat_MSTime() * m_flWorldTimeScale), (Plat_MSTime() * m_flWorldTimeScale) ); + Coord += CoordWorldTime; + + fltx4 flNoise128; + FourVectors fvNoise; + + fvNoise.DuplicateVector( Coord ); + flNoise128 = NoiseSIMD( fvNoise ); + float flNoise = SubFloat( flNoise128, 0 ); + + *( (int *) &flNoise) &= nAbsVal; + + ValueScale = ( flAbsScale *( fMax - fMin ) ); + ValueBase = ( fMin+ ( ( 1.0 - flAbsScale ) *( fMax - fMin ) ) ); + + if ( m_bAbsValInv ) + { + flNoise = 1.0 - flNoise; + } + + float flInitialNoise = ( ValueBase + ( ValueScale * flNoise ) ); + flInitialNoise = clamp(flInitialNoise, 0.0f, (float) INT_MAX ); + + //Global strength scale brought in by operator fade in/fade out/oscillate + float flEmissionRate = flInitialNoise * flCurStrength; + if ( flControlPointScale != 0.0f ) + { + flEmissionRate *= flControlPointScale; + } + + if ( flEmissionRate == 0.0f ) + return 0; + + if ( !C_OP_NoiseEmitter::MayCreateMoreParticles( pParticles, pContext ) ) + return 0; + + float flStartTime = m_flStartTime + pCtx->m_flTimeOffset; + if ( pParticles->m_flCurTime < flStartTime ) + return 0; + + Assert( flEmissionRate != 0.0f ); + + // determine our previous and current draw times and clamp them to start time and emission duration + float flPrevDrawTime = pParticles->m_flCurTime - pParticles->m_flDt; + float flCurrDrawTime = pParticles->m_flCurTime; + + if ( !IsInfinitelyEmitting() ) + { + if ( flPrevDrawTime < flStartTime ) + { + flPrevDrawTime = flStartTime; + } + if ( flCurrDrawTime > flStartTime + m_flEmissionDuration ) + { + flCurrDrawTime = flStartTime + m_flEmissionDuration; + } + } + + float flDeltaTime = flCurrDrawTime - flPrevDrawTime; + + //Calculate emission rate by delta time from last frame to determine number of particles to emit this frame as a fractional float + float flActualParticlesToEmit = flEmissionRate * flDeltaTime; + + //Add emitted particle to float counter to allow for fractional emission + pCtx->m_flTotalActualParticlesSoFar += flActualParticlesToEmit; + + //Floor float accumulated value and subtract whole int emitted so far from the result to determine total whole particles to emit this frame + int nParticlesToEmit = floor ( pCtx->m_flTotalActualParticlesSoFar ) - pCtx->m_nTotalEmittedSoFar; + + //Add emitted particles to running int total. + pCtx->m_nTotalEmittedSoFar += nParticlesToEmit; + + + if ( nParticlesToEmit == 0 ) + return 0; + + // We're only allowed to emit so many particles, though.. + // If we run out of room, only emit the last N particles + int nActualParticlesToEmit = nParticlesToEmit; + int nAllowedParticlesToEmit = pParticles->m_nMaxAllowedParticles - pParticles->m_nActiveParticles; + if ( nAllowedParticlesToEmit < nParticlesToEmit ) + { + nActualParticlesToEmit = nAllowedParticlesToEmit; + //flStartEmissionTime = pCtx->m_flNextEmitTime - flTimePerEmission * nActualParticlesToEmit; + } + if ( nActualParticlesToEmit == 0 ) + return 0; + + int nStartParticle = pParticles->m_nActiveParticles; + pParticles->SetNActiveParticles( nActualParticlesToEmit + pParticles->m_nActiveParticles ); + + float flTimeStampStep = ( flCurrDrawTime - flPrevDrawTime ) / ( nActualParticlesToEmit ); + float flTimeStep = flPrevDrawTime + flTimeStampStep; + + // Set the particle creation time to the exact sub-frame particle emission time + // !! speed!! do sse init here + for( int i = nStartParticle; i < nStartParticle + nActualParticlesToEmit; i++ ) + { + float *pTimeStamp = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_CREATION_TIME, i ); + flTimeStep = min( flTimeStep, flCurrDrawTime ); + *pTimeStamp = flTimeStep; + flTimeStep += flTimeStampStep; + } + + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; +} + + +void AddBuiltInParticleEmitters( void ) +{ + REGISTER_PARTICLE_OPERATOR( FUNCTION_EMITTER, C_OP_ContinuousEmitter ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_EMITTER, C_OP_InstantaneousEmitter ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_EMITTER, C_OP_NoiseEmitter ); +} + diff --git a/particles/builtin_particle_forces.cpp b/particles/builtin_particle_forces.cpp new file mode 100644 index 0000000..11c9919 --- /dev/null +++ b/particles/builtin_particle_forces.cpp @@ -0,0 +1,544 @@ +//========= 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" + +#ifdef USE_BLOBULATOR +// TODO: These should be in public by the time the SDK ships +#include "../common/blobulator/Physics/PhysParticle.h" +#include "../common/blobulator/Physics/PhysParticleCache_inl.h" +#include "../common/blobulator/Physics/PhysTiler.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class C_OP_RandomForce : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_RandomForce ); + + uint32 GetWrittenAttributes( void ) const + { + return 0; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + + virtual void AddForces( FourVectors *pAccumulatedForces, + CParticleCollection *pParticles, + int nBlocks, + float flStrength, + void *pContext ) const; + + Vector m_MinForce; + Vector m_MaxForce; +}; + +void C_OP_RandomForce::AddForces( FourVectors *pAccumulatedForces, + CParticleCollection *pParticles, + int nBlocks, + float flStrength, + void *pContext ) const +{ + FourVectors box_min,box_max; + box_min.DuplicateVector( m_MinForce * flStrength ); + box_max.DuplicateVector( m_MaxForce * flStrength); + box_max -= box_min; + int nContext = GetSIMDRandContext(); + for(int i=0;ix = AddSIMD( + pAccumulatedForces->x, AddSIMD( box_min.x, MulSIMD( box_max.x, RandSIMD( nContext) ) ) ); + pAccumulatedForces->y = AddSIMD( + pAccumulatedForces->y, AddSIMD( box_min.y, MulSIMD( box_max.y, RandSIMD( nContext) ) ) ); + pAccumulatedForces->z = AddSIMD( + pAccumulatedForces->z, AddSIMD( box_min.z, MulSIMD( box_max.z, RandSIMD( nContext) ) ) ); + pAccumulatedForces++; + } + ReleaseSIMDRandContext( nContext ); +} + +DEFINE_PARTICLE_OPERATOR( C_OP_RandomForce, "random force", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_RandomForce ) + DMXELEMENT_UNPACK_FIELD( "min force", "0 0 0", Vector, m_MinForce ) + DMXELEMENT_UNPACK_FIELD( "max force", "0 0 0", Vector, m_MaxForce ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_RandomForce ) + +class C_OP_TwistAroundAxis : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_TwistAroundAxis ); + + uint32 GetWrittenAttributes( void ) const + { + return 0; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK; + } + + + virtual void AddForces( FourVectors *pAccumulatedForces, + CParticleCollection *pParticles, + int nBlocks, + float flStrength, + void *pContext ) const; + + float m_fForceAmount; + Vector m_TwistAxis; + bool m_bLocalSpace; +}; + +void C_OP_TwistAroundAxis::AddForces( FourVectors *pAccumulatedForces, + CParticleCollection *pParticles, + int nBlocks, + float flStrength, + void *pContext ) const +{ + FourVectors Twist_AxisInWorldSpace; + Twist_AxisInWorldSpace.DuplicateVector( pParticles->TransformAxis( m_TwistAxis, m_bLocalSpace ) ); + Twist_AxisInWorldSpace.VectorNormalize(); + + Vector vecCenter; + pParticles->GetControlPointAtTime( 0, pParticles->m_flCurTime, &vecCenter ); + FourVectors Center; + Center.DuplicateVector( vecCenter ); + size_t nPosStride; + fltx4 ForceScale = ReplicateX4( m_fForceAmount * flStrength ); + const FourVectors *pPos=pParticles->Get4VAttributePtr( PARTICLE_ATTRIBUTE_XYZ, &nPosStride ); + for(int i=0;iGetControlPointAtTime( m_nControlPointNumber, pParticles->m_flCurTime, &vecCenter ); + FourVectors Center; + Center.DuplicateVector( vecCenter ); + size_t nPosStride; + const FourVectors *pPos=pParticles->Get4VAttributePtr( PARTICLE_ATTRIBUTE_XYZ, &nPosStride ); + + for(int i=0;igroup == b->group) // In the same group + { + float p = a->radius * 2.0f / (d+FLT_EPSILON); + float p2 = p * p; + float p4 = p2 * p2; + + + // Surface tension: + + //Notes: + // Can average the neighbor count between the two particles... + // I tried this, and discovered that rather than averaging, I can take maybe take the + // larger of the two neighbor counts, so the attraction between two particles on the surface will be strong, but + // the attraction between a particle inside and a particle on the surface will be weak. I can also try + // taking the min so that the attraction between a particle on the surface and a particle inside the fluid will + // be strong, but the attraction between two particles completely on the inside will be weak. + // + // int symmetric_neighbor_count = min(a->neighbor_count, b->neighbor_count); + // + // Can try having neighbors only cause stronger attraction (no repulsion) + // Can try lower exponents for the LennardJones forces. + + // This is a trick to prevent single particles from floating off... the less neighbors a particle has.. the more it sticks + // This also tends to simulate surface tension + float surface_tension_modifier = ((24.0f * m_fSurfaceTension) / (a->neighbor_count + b->neighbor_count + 0.1f)) + 1.0f; + //float lennard_jones_force = fLennardJones * 2.0f * (p2 - (p4 * p4)); + float lennard_jones_force = m_fLennardJonesAttraction * p2 - m_fLennardJonesRepulsion*p4; + f = surface_tension_modifier * lennard_jones_force; + + // This is some older code: + //f = ((35.0f * LampScene::simulationSurfaceTension) / (a->neighbor_count + 0.1f)) * (p2 - (p4 * p4)); + // used to be 68' + + + //float factor = (b->neighbor_count < 13 && neighbor_count < 13 ? 4.0f : 0.5f); + //f = factor * (p2 - (p2 * p2 * p2 * p2)); + } + else + { + // This was 3.5 ... made 3.0 so particles get closer when they collide + if(d > a->radius * 3.0f) return; + + float p = a->radius * 4.0f / d; + f = -1.0f * p * p; + } + + // These checks are great to have, but are they really necessary? + // It might also be good to have a limit on velocity + + // Attraction is a positive value. + // Repulsion is negative. + if(f < -m_fMaxRepulsion) f = -m_fMaxRepulsion; + if(f > m_fMaxAttraction) f = m_fMaxAttraction; + + Point3D scaledr = (b->center - a->center) * (f/(d+FLT_EPSILON)); // Dividing by d scales distance down to a unit vector + a->force.add(scaledr); + b->force.subtract(scaledr); +} + +void C_OP_LennardJonesForce::AddForces( FourVectors *pAccumulatedForces, + CParticleCollection *pParticles, + int nBlocks, + float flStrength, + void *pContext ) const +{ + int nParticles = pParticles->m_nActiveParticles; // Not sure if this is correct! + + size_t nPosStride; + const FourVectors *pPos=pParticles->Get4VAttributePtr( PARTICLE_ATTRIBUTE_XYZ, &nPosStride ); + + // The +4 is because particles are stored by PET in blocks of 4 + // However, not every block is full. Thus, nParticles may be + // less than nBlocks*4. Could get rid of this if the swizzling/unswizzling + // loop were better written. + static SmartArray imp_particles_sa; // This doesn't specify alignment, might have problems with SSE + while(imp_particles_sa.size < nParticles+4) + { + imp_particles_sa.pushAutoSize(PhysParticle()); + } + + /* + size_t nPrevPosStride; + const FourVectors *pPrevPos=pParticles->Get4VAttributePtr( PARTICLE_ATTRIBUTE_PREV_XYZ, &nPrevPosStride ); + */ + + //m_pParticleCache->beginFrame(); + //m_pParticleCache->beginTile(nParticles); + + m_pPhysTiler->beginFrame(Point3D(0.0f, 0.0f, 0.0f)); + + // Unswizzle from the FourVectors format into particles + for(int i=0, p=0;iforce.clear(); + if(p < nParticles) + { + particle->center = ofs.Vec(0); + particle->group = 0; + particle->neighbor_count = 0; + m_pPhysTiler->insertParticle(particle); + } + p++; + + particle = &(imp_particles_sa[p]); + particle->force.clear(); + if(p < nParticles) + { + particle->center = ofs.Vec(1); + particle->group = 0; + particle->neighbor_count = 0; + m_pPhysTiler->insertParticle(particle); + } + p++; + + particle = &(imp_particles_sa[p]); + particle->force.clear(); + if(p < nParticles) + { + particle->center = ofs.Vec(2); + particle->group = 0; + particle->neighbor_count = 0; + m_pPhysTiler->insertParticle(particle); + } + p++; + + particle = &(imp_particles_sa[p]); + particle->force.clear(); + if(p < nParticles) + { + particle->center = ofs.Vec(3); + particle->group = 0; + particle->neighbor_count = 0; + m_pPhysTiler->insertParticle(particle); + } + p++; + + pPos += nPosStride; + } + + m_pPhysTiler->processTiles(); + + + float timeStep = 1.0f; // This should be customizable + float nearNeighborInteractionRadius = 2.3f; + float nearNeighborInteractionRadiusSq = nearNeighborInteractionRadius * nearNeighborInteractionRadius; + + PhysParticleCache* pCache = m_pPhysTiler->getParticleCache(); + + // Calculate number of near neighbors for each particle + for(int i = 0; i < nParticles; i++) + { + PhysParticle *b1 = &(imp_particles_sa[i]); + + PhysParticleAndDist* node = pCache->get(b1); + + while(node->particle != NULL) + { + PhysParticle* b2 = node->particle; + + // Compare addresses of the two particles. This makes sure we apply a force only once between a pair of particles. + if(b1 < b2 && node->distSq < nearNeighborInteractionRadiusSq) + { + b1->neighbor_count++; + b2->neighbor_count++; + + } + + node++; + } + } + + // Calculate forces on particles due to other particles + for(int i = 0; i < nParticles; i++) + { + PhysParticle *b1 = &(imp_particles_sa[i]); + + PhysParticleAndDist* node = pCache->get(b1); + + while(node->particle != NULL) + { + PhysParticle* b2 = node->particle; + + // Compare addresses of the two particles. This makes sure we apply a force only once between a pair of particles. + if(b1 < b2) + { + addParticleForce(b1, b2, node->distSq, flStrength, timeStep); + } + + node++; + } + } + + + /* + for(ParticleListNode* bit3 = particles; bit3; bit3 = bit3->next) + { + Particle* b = bit3->particle; + b->prev_group = b->group; // Set prev group + //b1->addDirDragForce(); + b->move(ts); // Move the particle (it should never be used again until next iteration) + } + */ + + m_pPhysTiler->endFrame(); + + // Swizzle forces back into FourVectors format + for(int i=0;iX(0) += imp_particles_sa[i*4].force[0]; + pAccumulatedForces->Y(0) += imp_particles_sa[i*4].force[1]; + pAccumulatedForces->Z(0) += imp_particles_sa[i*4].force[2]; + + pAccumulatedForces->X(1) += imp_particles_sa[i*4+1].force[0]; + pAccumulatedForces->Y(1) += imp_particles_sa[i*4+1].force[1]; + pAccumulatedForces->Z(1) += imp_particles_sa[i*4+1].force[2]; + + pAccumulatedForces->X(2) += imp_particles_sa[i*4+2].force[0]; + pAccumulatedForces->Y(2) += imp_particles_sa[i*4+2].force[1]; + pAccumulatedForces->Z(2) += imp_particles_sa[i*4+2].force[2]; + + pAccumulatedForces->X(3) += imp_particles_sa[i*4+3].force[0]; + pAccumulatedForces->Y(3) += imp_particles_sa[i*4+3].force[1]; + pAccumulatedForces->Z(3) += imp_particles_sa[i*4+3].force[2]; + + pAccumulatedForces++; + } + +} + +DEFINE_PARTICLE_OPERATOR( C_OP_LennardJonesForce, "lennard jones force", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_LennardJonesForce ) +DMXELEMENT_UNPACK_FIELD( "interaction radius", "4", float, m_fInteractionRadius ) +DMXELEMENT_UNPACK_FIELD( "surface tension", "1", float, m_fSurfaceTension ) +DMXELEMENT_UNPACK_FIELD( "lennard jones attractive force", "1", float, m_fLennardJonesAttraction ) +DMXELEMENT_UNPACK_FIELD( "lennard jones repulsive force", "1", float, m_fLennardJonesRepulsion ) +DMXELEMENT_UNPACK_FIELD( "max repulsion", "100", float, m_fMaxRepulsion ) +DMXELEMENT_UNPACK_FIELD( "max attraction", "100", float, m_fMaxAttraction ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_LennardJonesForce ) + +#endif + + +void AddBuiltInParticleForceGenerators( void ) +{ + REGISTER_PARTICLE_OPERATOR( FUNCTION_FORCEGENERATOR, C_OP_RandomForce ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_FORCEGENERATOR, C_OP_TwistAroundAxis ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_FORCEGENERATOR, C_OP_AttractToControlPoint ); + #ifdef USE_BLOBULATOR + REGISTER_PARTICLE_OPERATOR( FUNCTION_FORCEGENERATOR, C_OP_LennardJonesForce ); + #endif +} + diff --git a/particles/builtin_particle_ops.cpp b/particles/builtin_particle_ops.cpp new file mode 100644 index 0000000..c265469 --- /dev/null +++ b/particles/builtin_particle_ops.cpp @@ -0,0 +1,4584 @@ +//========= 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 "tier2/renderutils.h" +#include "tier1/UtlStringMap.h" +#include "tier1/strtools.h" +#include "studio.h" +#include "bspflags.h" +#include "tier0/vprof.h" +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +#if MEASURE_PARTICLE_PERF + +#if VPROF_LEVEL > 0 +#define START_OP float flOpStartTime = Plat_FloatTime(); VPROF_ENTER_SCOPE(pOp->GetDefinition()->GetName()) +#else +#define START_OP float flOpStartTime = Plat_FloatTime(); +#endif + +#if VPROF_LEVEL > 0 +#define END_OP if ( 1 ) { \ + float flETime = Plat_FloatTime() - flOpStartTime; \ + IParticleOperatorDefinition *pDef = (IParticleOperatorDefinition *) pOp->GetDefinition(); \ + pDef->RecordExecutionTime( flETime ); \ +} \ + VPROF_EXIT_SCOPE() +#else +#define END_OP if ( 1 ) { \ + float flETime = Plat_FloatTime() - flOpStartTime; \ + IParticleOperatorDefinition *pDef = (IParticleOperatorDefinition *) pOp->GetDefinition(); \ + pDef->RecordExecutionTime( flETime ); \ +} +#endif +#else +#define START_OP +#define END_OP +#endif + +//----------------------------------------------------------------------------- +// Standard movement operator +//----------------------------------------------------------------------------- +class C_OP_BasicMovement : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_BasicMovement ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_PREV_XYZ_MASK | PARTICLE_ATTRIBUTE_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + Vector m_Gravity; + float m_fDrag; + int m_nMaxConstraintPasses; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_BasicMovement, "Movement Basic", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_BasicMovement ) + DMXELEMENT_UNPACK_FIELD( "gravity", "0 0 0", Vector, m_Gravity ) + DMXELEMENT_UNPACK_FIELD( "drag", "0", float, m_fDrag ) + DMXELEMENT_UNPACK_FIELD( "max constraint passes", "3", int, m_nMaxConstraintPasses ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_BasicMovement ) + + +#define MAXIMUM_NUMBER_OF_CONSTRAINTS 100 +//#define CHECKALL 1 + +#ifdef NDEBUG +#define CHECKSYSTEM( p ) 0 +#else +#ifdef CHECKALL +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] ) ); + } +} +#else +#define CHECKSYSTEM( p ) 0 +#endif +#endif + +void C_OP_BasicMovement::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + C4VAttributeWriteIterator prev_xyz( PARTICLE_ATTRIBUTE_PREV_XYZ, pParticles ); + C4VAttributeWriteIterator xyz( PARTICLE_ATTRIBUTE_XYZ, pParticles ); + + // fltx4 adj_dt = ReplicateX4( (1.0-m_fDrag) * ( pParticles->m_flDt / pParticles->m_flPreviousDt ) ); + fltx4 adj_dt = ReplicateX4( ( pParticles->m_flDt / pParticles->m_flPreviousDt ) * ExponentialDecay( (1.0f-max(0.f,m_fDrag)), (1.0f/30.0f), pParticles->m_flDt ) ); + + size_t nForceStride=0; + Vector acc = m_Gravity; + fltx4 accFactorX = ReplicateX4( acc.x ); + fltx4 accFactorY = ReplicateX4( acc.y ); + fltx4 accFactorZ = ReplicateX4( acc.z ); + + int nAccumulators = pParticles->m_pDef->m_ForceGenerators.Count(); + + FourVectors PerParticleForceAccumulator[MAX_PARTICLES_IN_A_SYSTEM / 4]; // xbox fixme - memory + + FourVectors *pAccOut = PerParticleForceAccumulator; + if (nAccumulators) + { + // we do have per particle force accumulators + nForceStride = 1; + int nblocks = pParticles->m_nPaddedActiveParticles; + for(int i=0;ix = accFactorX; + pAccOut->y = accFactorY; + pAccOut->z = accFactorZ; + pAccOut++; + } + // now, call all force accumulators + for(int i=0;i < nAccumulators ; i++ ) + { + float flStrengthOp; + CParticleOperatorInstance *pOp = pParticles->m_pDef->m_ForceGenerators[i]; + if ( pParticles->CheckIfOperatorShouldRun( pOp, &flStrengthOp )) + { + START_OP; + pParticles->m_pDef->m_ForceGenerators[i]->AddForces( + PerParticleForceAccumulator, + pParticles, + nblocks, + flStrengthOp, + pParticles->m_pOperatorContextData + + pParticles->m_pDef->m_nForceGeneratorsCtxOffsets[i] ); + END_OP; + } + } + } + else + { + pAccOut->x = accFactorX; + pAccOut->y = accFactorY; + pAccOut->z = accFactorZ; + // we just have gravity + } + + CHECKSYSTEM( pParticles ); + fltx4 DtSquared = ReplicateX4( pParticles->m_flDt * pParticles->m_flDt ); + int ctr = pParticles->m_nPaddedActiveParticles; + FourVectors *pAccIn = PerParticleForceAccumulator; + do + { + accFactorX = MulSIMD( pAccIn->x, DtSquared ); + accFactorY = MulSIMD( pAccIn->y, DtSquared ); + accFactorZ = MulSIMD( pAccIn->z, DtSquared ); + + // we will write prev xyz, and swap prev and cur at the end + prev_xyz->x = AddSIMD( xyz->x, + AddSIMD( accFactorX, MulSIMD( adj_dt, SubSIMD( xyz->x, prev_xyz->x ) ) ) ); + prev_xyz->y = AddSIMD( xyz->y, + AddSIMD( accFactorY, MulSIMD( adj_dt, SubSIMD( xyz->y, prev_xyz->y ) ) ) ); + prev_xyz->z = AddSIMD( xyz->z, + AddSIMD( accFactorZ, MulSIMD( adj_dt, SubSIMD( xyz->z, prev_xyz->z ) ) ) ); + CHECKSYSTEM( pParticles ); + ++prev_xyz; + ++xyz; + pAccIn += nForceStride; + } while (--ctr); + + CHECKSYSTEM( pParticles ); + pParticles->SwapPosAndPrevPos(); + // now, enforce constraints + int nConstraints = pParticles->m_pDef->m_Constraints.Count(); + if ( nConstraints && pParticles->m_nPaddedActiveParticles ) + { + bool bConstraintSatisfied[ MAXIMUM_NUMBER_OF_CONSTRAINTS ]; + bool bFinalConstraint[ MAXIMUM_NUMBER_OF_CONSTRAINTS ]; + for(int i=0;im_pDef->m_Constraints[i]->IsFinalConstraint(); + + bConstraintSatisfied[i] = false; + pParticles->m_pDef->m_Constraints[i]->SetupConstraintPerFrameData( + pParticles, pParticles->m_pOperatorContextData + + pParticles->m_pDef->m_nConstraintsCtxOffsets[i] ); + } + + // constraints get to see their own per psystem per op random #s + for(int p=0; p < m_nMaxConstraintPasses ; p++ ) + { +// int nSaveOffset=pParticles->m_nOperatorRandomSampleOffset; + for(int i=0;im_nOperatorRandomSampleOffset += 23; + if ( ! bConstraintSatisfied[i] ) + { + CParticleOperatorInstance *pOp = pParticles->m_pDef->m_Constraints[i]; + bConstraintSatisfied[i] = true; + if ( ( !bFinalConstraint[i] ) && ( pParticles->CheckIfOperatorShouldRun( pOp ) ) ) + { + START_OP; + bool bDidSomething = pOp->EnforceConstraint( + 0, pParticles->m_nPaddedActiveParticles, pParticles, + pParticles->m_pOperatorContextData + + pParticles->m_pDef->m_nConstraintsCtxOffsets[i], + pParticles->m_nActiveParticles ); + END_OP; + CHECKSYSTEM( pParticles ); + if ( bDidSomething ) + { + // other constraints now not satisfied, maybe + for( int j=0; jm_nOperatorRandomSampleOffset = nSaveOffset; + } + // now, run final constraints + for(int i=0;im_pDef->m_Constraints[i]; + if ( ( bFinalConstraint[i] ) && + ( pParticles->CheckIfOperatorShouldRun( + pOp ) ) ) + { + START_OP; + pOp->EnforceConstraint( + 0, pParticles->m_nPaddedActiveParticles, pParticles, + pParticles->m_pOperatorContextData + + pParticles->m_pDef->m_nConstraintsCtxOffsets[i], + pParticles->m_nActiveParticles ); + END_OP; + CHECKSYSTEM( pParticles ); + } + } + } + CHECKSYSTEM( pParticles ); +} + + +//----------------------------------------------------------------------------- +// Fade and kill operator +//----------------------------------------------------------------------------- +class C_OP_FadeAndKill : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_FadeAndKill ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_ALPHA_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK; + } + + uint32 GetReadInitialAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_ALPHA_MASK; + } + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ); + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + float m_flStartFadeInTime; + float m_flEndFadeInTime; + float m_flStartFadeOutTime; + float m_flEndFadeOutTime; + float m_flStartAlpha; + float m_flEndAlpha; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_FadeAndKill, "Alpha Fade and Decay", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_FadeAndKill ) + DMXELEMENT_UNPACK_FIELD( "start_alpha","1", float, m_flStartAlpha ) + DMXELEMENT_UNPACK_FIELD( "end_alpha","0", float, m_flEndAlpha ) + DMXELEMENT_UNPACK_FIELD( "start_fade_in_time","0", float, m_flStartFadeInTime ) + DMXELEMENT_UNPACK_FIELD( "end_fade_in_time","0.5", float, m_flEndFadeInTime ) + DMXELEMENT_UNPACK_FIELD( "start_fade_out_time","0.5", float, m_flStartFadeOutTime ) + DMXELEMENT_UNPACK_FIELD( "end_fade_out_time","1", float, m_flEndFadeOutTime ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_FadeAndKill ) + +void C_OP_FadeAndKill::InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) +{ + // Cache off and validate values + if ( m_flEndFadeInTime < m_flStartFadeInTime ) + { + m_flEndFadeInTime = m_flStartFadeInTime; + } + if ( m_flEndFadeOutTime < m_flStartFadeOutTime ) + { + m_flEndFadeOutTime = m_flStartFadeOutTime; + } + + if ( m_flStartFadeOutTime < m_flStartFadeInTime ) + { + V_swap( m_flStartFadeInTime, m_flStartFadeOutTime ); + } + + if ( m_flEndFadeOutTime < m_flEndFadeInTime ) + { + V_swap( m_flEndFadeInTime, m_flEndFadeOutTime ); + } +} + +void C_OP_FadeAndKill::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + CM128AttributeIterator pCreationTime( PARTICLE_ATTRIBUTE_CREATION_TIME, pParticles ); + CM128AttributeIterator pLifeDuration( PARTICLE_ATTRIBUTE_LIFE_DURATION, pParticles ); + CM128InitialAttributeIterator pInitialAlpha( PARTICLE_ATTRIBUTE_ALPHA, pParticles ); + CM128AttributeWriteIterator pAlpha( PARTICLE_ATTRIBUTE_ALPHA, pParticles ); + + fltx4 fl4StartFadeInTime = ReplicateX4( m_flStartFadeInTime ); + fltx4 fl4StartFadeOutTime = ReplicateX4( m_flStartFadeOutTime ); + fltx4 fl4EndFadeInTime = ReplicateX4( m_flEndFadeInTime ); + fltx4 fl4EndFadeOutTime = ReplicateX4( m_flEndFadeOutTime ); + fltx4 fl4EndAlpha = ReplicateX4( m_flEndAlpha ); + fltx4 fl4StartAlpha = ReplicateX4( m_flStartAlpha ); + + fltx4 fl4CurTime = pParticles->m_fl4CurTime; + int nLimit = pParticles->m_nPaddedActiveParticles << 2; + + fltx4 fl4FadeInDuration = ReplicateX4( m_flEndFadeInTime - m_flStartFadeInTime ); + fltx4 fl4OOFadeInDuration = ReciprocalEstSIMD( fl4FadeInDuration ); + + fltx4 fl4FadeOutDuration = ReplicateX4( m_flEndFadeOutTime - m_flStartFadeOutTime ); + fltx4 fl4OOFadeOutDuration = ReciprocalEstSIMD( fl4FadeOutDuration ); + + for ( int i = 0; i < nLimit; i+= 4 ) + { + fltx4 fl4Age = SubSIMD( fl4CurTime, *pCreationTime ); + fltx4 fl4ParticleLifeTime = *pLifeDuration; + fltx4 fl4KillMask = CmpGeSIMD( fl4Age, *pLifeDuration ); // takes care of lifeduration = 0 div 0 + fl4Age = MulSIMD( fl4Age, ReciprocalEstSIMD( fl4ParticleLifeTime ) ); // age 0..1 + fltx4 fl4FadingInMask = AndNotSIMD( fl4KillMask, + AndSIMD( + CmpLeSIMD( fl4StartFadeInTime, fl4Age ), CmpGtSIMD(fl4EndFadeInTime, fl4Age ) ) ); + fltx4 fl4FadingOutMask = AndNotSIMD( fl4KillMask, + AndSIMD( + CmpLeSIMD( fl4StartFadeOutTime, fl4Age ), CmpGtSIMD(fl4EndFadeOutTime, fl4Age ) ) ); + if ( IsAnyNegative( fl4FadingInMask ) ) + { + fltx4 fl4Goal = MulSIMD( *pInitialAlpha, fl4StartAlpha ); + fltx4 fl4NewAlpha = SimpleSplineRemapValWithDeltasClamped( fl4Age, fl4StartFadeInTime, fl4FadeInDuration, fl4OOFadeInDuration, + fl4Goal, SubSIMD( *pInitialAlpha, fl4Goal ) ); + + *pAlpha = MaskedAssign( fl4FadingInMask, fl4NewAlpha, *pAlpha ); + } + if ( IsAnyNegative( fl4FadingOutMask ) ) + { + fltx4 fl4Goal = MulSIMD( *pInitialAlpha, fl4EndAlpha ); + fltx4 fl4NewAlpha = SimpleSplineRemapValWithDeltasClamped( fl4Age, fl4StartFadeOutTime, fl4FadeOutDuration, fl4OOFadeOutDuration, + *pInitialAlpha, SubSIMD( fl4Goal, *pInitialAlpha ) ); + *pAlpha = MaskedAssign( fl4FadingOutMask, fl4NewAlpha, *pAlpha ); + } + if ( IsAnyNegative( fl4KillMask ) ) + { + int nMask = TestSignSIMD( fl4KillMask ); + if ( nMask & 1 ) + pParticles->KillParticle( i ); + if ( nMask & 2 ) + pParticles->KillParticle( i + 1 ); + if ( nMask & 4 ) + pParticles->KillParticle( i + 2 ); + if ( nMask & 8 ) + pParticles->KillParticle( i + 3 ); + } + ++pCreationTime; + ++pLifeDuration; + ++pInitialAlpha; + ++pAlpha; + } +} + + +//----------------------------------------------------------------------------- +// Fade In Operator +//----------------------------------------------------------------------------- +class C_OP_FadeIn : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_FadeIn ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_ALPHA_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK | PARTICLE_ATTRIBUTE_PARTICLE_ID_MASK; + } + + uint32 GetReadInitialAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_ALPHA_MASK; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + float m_flFadeInTimeMin; + float m_flFadeInTimeMax; + float m_flFadeInTimeExp; + bool m_bProportional; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_FadeIn, "Alpha Fade In Random", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_FadeIn ) + DMXELEMENT_UNPACK_FIELD( "fade in time min",".25", float, m_flFadeInTimeMin ) + DMXELEMENT_UNPACK_FIELD( "fade in time max",".25", float, m_flFadeInTimeMax ) + DMXELEMENT_UNPACK_FIELD( "fade in time exponent","1", float, m_flFadeInTimeExp ) + DMXELEMENT_UNPACK_FIELD( "proportional 0/1","1", bool, m_bProportional ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_FadeIn ) + + +void C_OP_FadeIn::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + CM128AttributeIterator pCreationTime( PARTICLE_ATTRIBUTE_CREATION_TIME, pParticles ); + CM128AttributeIterator pLifeDuration( PARTICLE_ATTRIBUTE_LIFE_DURATION, pParticles ); + CM128InitialAttributeIterator pInitialAlpha( PARTICLE_ATTRIBUTE_ALPHA, pParticles ); + CM128AttributeWriteIterator pAlpha( PARTICLE_ATTRIBUTE_ALPHA, pParticles ); + C4IAttributeIterator pParticleID( PARTICLE_ATTRIBUTE_PARTICLE_ID, pParticles ); + int nRandomOffset = pParticles->OperatorRandomSampleOffset(); + + fltx4 CurTime = pParticles->m_fl4CurTime; + + int nCtr = pParticles->m_nPaddedActiveParticles; + int nSSEFixedExponent = m_flFadeInTimeExp*4.0; + + fltx4 FadeTimeMin = ReplicateX4( m_flFadeInTimeMin ); + fltx4 FadeTimeWidth = ReplicateX4( m_flFadeInTimeMax - m_flFadeInTimeMin ); + + do + { + fltx4 FadeInTime= Pow_FixedPoint_Exponent_SIMD( + pParticles->RandomFloat( *pParticleID, nRandomOffset ), + nSSEFixedExponent); + FadeInTime = AddSIMD( FadeTimeMin, MulSIMD( FadeTimeWidth, FadeInTime ) ); + + // Find our life percentage + fltx4 flLifeTime = SubSIMD( CurTime, *pCreationTime ); + if ( m_bProportional ) + { + flLifeTime = + MaxSIMD( Four_Zeros, + MinSIMD( Four_Ones, + MulSIMD( flLifeTime, ReciprocalEstSIMD( *pLifeDuration ) ) ) ); + } + + fltx4 ApplyMask = CmpGtSIMD( FadeInTime, flLifeTime ); + if ( IsAnyNegative( ApplyMask ) ) + { + // Fading in + fltx4 NewAlpha = + SimpleSplineRemapValWithDeltasClamped( + flLifeTime, Four_Zeros, + FadeInTime, ReciprocalEstSIMD( FadeInTime ), + Four_Zeros, *pInitialAlpha ); + *( pAlpha ) = MaskedAssign( ApplyMask, NewAlpha, *( pAlpha ) ); + } + ++pCreationTime; + ++pLifeDuration; + ++pInitialAlpha; + ++pAlpha; + ++pParticleID; + } while( --nCtr ); +} + + + +//----------------------------------------------------------------------------- +// Fade Out Operator +//----------------------------------------------------------------------------- +class C_OP_FadeOut : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_FadeOut ); + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_ALPHA_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK | PARTICLE_ATTRIBUTE_PARTICLE_ID_MASK; + } + + uint32 GetReadInitialAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_ALPHA_MASK; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + float flBias = ( m_flFadeBias != 0.0f ) ? m_flFadeBias : 0.5f; + m_fl4BiasParam = PreCalcBiasParameter( ReplicateX4( flBias ) ); + if ( m_flFadeOutTimeMin == 0.0f && m_flFadeOutTimeMax == 0.0f ) + { + m_flFadeOutTimeMin = m_flFadeOutTimeMax = FLT_EPSILON; + } + } + + float m_flFadeOutTimeMin; + float m_flFadeOutTimeMax; + float m_flFadeOutTimeExp; + float m_flFadeBias; + fltx4 m_fl4BiasParam; + bool m_bProportional; + bool m_bEaseInAndOut; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_FadeOut, "Alpha Fade Out Random", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_FadeOut ) + DMXELEMENT_UNPACK_FIELD( "fade out time min",".25", float, m_flFadeOutTimeMin ) + DMXELEMENT_UNPACK_FIELD( "fade out time max",".25", float, m_flFadeOutTimeMax ) + DMXELEMENT_UNPACK_FIELD( "fade out time exponent","1", float, m_flFadeOutTimeExp ) + DMXELEMENT_UNPACK_FIELD( "proportional 0/1","1", bool, m_bProportional ) + DMXELEMENT_UNPACK_FIELD( "ease in and out","1", bool, m_bEaseInAndOut ) + DMXELEMENT_UNPACK_FIELD( "fade bias", "0.5", float, m_flFadeBias ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_FadeOut ) + + +void C_OP_FadeOut::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + CM128AttributeIterator pCreationTime( PARTICLE_ATTRIBUTE_CREATION_TIME, pParticles ); + CM128AttributeIterator pLifeDuration( PARTICLE_ATTRIBUTE_LIFE_DURATION, pParticles ); + CM128InitialAttributeIterator pInitialAlpha( PARTICLE_ATTRIBUTE_ALPHA, pParticles ); + CM128AttributeWriteIterator pAlpha( PARTICLE_ATTRIBUTE_ALPHA, pParticles ); + C4IAttributeIterator pParticleID( PARTICLE_ATTRIBUTE_PARTICLE_ID, pParticles ); + int nRandomOffset = pParticles->OperatorRandomSampleOffset(); + + fltx4 fl4CurTime = pParticles->m_fl4CurTime; + + int nCtr = pParticles->m_nPaddedActiveParticles; + int nSSEFixedExponent = m_flFadeOutTimeExp*4.0; + + fltx4 FadeTimeMin = ReplicateX4( m_flFadeOutTimeMin ); + fltx4 FadeTimeWidth = ReplicateX4( m_flFadeOutTimeMax - m_flFadeOutTimeMin ); + + do + { + fltx4 fl4FadeOutTime = Pow_FixedPoint_Exponent_SIMD( + pParticles->RandomFloat( *pParticleID, nRandomOffset ), + nSSEFixedExponent ); + fl4FadeOutTime = AddSIMD( FadeTimeMin, MulSIMD( FadeTimeWidth, fl4FadeOutTime ) ); + + fltx4 fl4Lifespan; + + // Find our life percentage + fltx4 fl4LifeTime = SubSIMD( fl4CurTime, *pCreationTime ); + fltx4 fl4LifeDuration = *pLifeDuration; + + if ( m_bProportional ) + { + fl4LifeTime = MulSIMD( fl4LifeTime, ReciprocalEstSIMD( fl4LifeDuration ) ); + fl4FadeOutTime = SubSIMD( Four_Ones, fl4FadeOutTime ); + fl4Lifespan = SubSIMD ( Four_Ones, fl4FadeOutTime ); + } + else + { + fl4FadeOutTime = SubSIMD( *pLifeDuration, fl4FadeOutTime ); + fl4Lifespan = SubSIMD( *pLifeDuration, fl4FadeOutTime ) ; + } + + fltx4 ApplyMask = CmpLtSIMD( fl4FadeOutTime, fl4LifeTime ); + if ( IsAnyNegative( ApplyMask ) ) + { + // Fading out + fltx4 NewAlpha; + if ( m_bEaseInAndOut ) + { + NewAlpha = SimpleSplineRemapValWithDeltasClamped( + fl4LifeTime, fl4FadeOutTime, + fl4Lifespan, ReciprocalEstSIMD( fl4Lifespan ), + *pInitialAlpha, SubSIMD ( Four_Zeros, *pInitialAlpha ) ); + NewAlpha = MaxSIMD( Four_Zeros, NewAlpha ); + } + else + { + fltx4 fl4Frac = MulSIMD( SubSIMD( fl4LifeTime, fl4FadeOutTime ), ReciprocalEstSIMD( fl4Lifespan ) ); + fl4Frac = MinSIMD( Four_Ones, MaxSIMD( Four_Zeros, fl4Frac ) ); + fl4Frac = BiasSIMD( fl4Frac, m_fl4BiasParam ); + fl4Frac = SubSIMD( Four_Ones, fl4Frac ); + NewAlpha = MulSIMD( *pInitialAlpha, fl4Frac ); + } + + *( pAlpha ) = MaskedAssign( ApplyMask, NewAlpha, *( pAlpha ) ); + } + ++pCreationTime; + ++pLifeDuration; + ++pInitialAlpha; + ++pAlpha; + ++pParticleID; + } while( --nCtr ); +} + + + + +//----------------------------------------------------------------------------- +// Oscillating Scalar operator +// performs an oscillation operation on any scalar (fade, radius, etc.) +//----------------------------------------------------------------------------- +class C_OP_OscillateScalar : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_OscillateScalar ); + + uint32 GetWrittenAttributes( void ) const + { + return 1 << m_nField; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK | + PARTICLE_ATTRIBUTE_PARTICLE_ID_MASK; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + float m_RateMin; + float m_RateMax; + float m_FrequencyMin; + float m_FrequencyMax; + int m_nField; + bool m_bProportional, m_bProportionalOp; + float m_flStartTime_min; + float m_flStartTime_max; + float m_flEndTime_min; + float m_flEndTime_max; + float m_flOscMult; + float m_flOscAdd; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_OscillateScalar, "Oscillate Scalar", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_OscillateScalar ) + DMXELEMENT_UNPACK_FIELD_USERDATA( "oscillation field", "7", int, m_nField, "intchoice particlefield_scalar" ) + DMXELEMENT_UNPACK_FIELD( "oscillation rate min", "0", float, m_RateMin ) + DMXELEMENT_UNPACK_FIELD( "oscillation rate max", "0", float, m_RateMax ) + DMXELEMENT_UNPACK_FIELD( "oscillation frequency min", "1", float, m_FrequencyMin ) + DMXELEMENT_UNPACK_FIELD( "oscillation frequency max", "1", float, m_FrequencyMax ) + DMXELEMENT_UNPACK_FIELD( "proportional 0/1", "1", bool, m_bProportional ) + DMXELEMENT_UNPACK_FIELD( "start time min", "0", float, m_flStartTime_min ) + DMXELEMENT_UNPACK_FIELD( "start time max", "0", float, m_flStartTime_max ) + DMXELEMENT_UNPACK_FIELD( "end time min", "1", float, m_flEndTime_min ) + DMXELEMENT_UNPACK_FIELD( "end time max", "1", float, m_flEndTime_max ) + DMXELEMENT_UNPACK_FIELD( "start/end proportional", "1", bool, m_bProportionalOp ) + DMXELEMENT_UNPACK_FIELD( "oscillation multiplier", "2", float, m_flOscMult ) + DMXELEMENT_UNPACK_FIELD( "oscillation start phase", ".5", float, m_flOscAdd ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_OscillateScalar ) + +void C_OP_OscillateScalar::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + CM128AttributeIterator pCreationTime( PARTICLE_ATTRIBUTE_CREATION_TIME, pParticles ); + CM128AttributeIterator pLifeDuration( PARTICLE_ATTRIBUTE_LIFE_DURATION, pParticles ); + C4IAttributeIterator pParticleId ( PARTICLE_ATTRIBUTE_PARTICLE_ID, pParticles ); + CM128AttributeWriteIterator pOscField ( m_nField, pParticles) ; + + fltx4 fl4CurTime = pParticles->m_fl4CurTime; + + int nRandomOffset = pParticles->OperatorRandomSampleOffset(); + + fltx4 fl4OscVal; + + fltx4 fl4ScaleFactor = ReplicateX4( flStrength * pParticles->m_flDt ); + + fltx4 fl4CosFactorMultiplier = ReplicateX4( m_flOscMult ); + fltx4 fl4CosFactorAdd = ReplicateX4( m_flOscAdd ); + + fltx4 fl4CosFactor = AddSIMD( MulSIMD( fl4CosFactorMultiplier, fl4CurTime ), fl4CosFactorAdd ); + fltx4 fl4CosFactorProp = fl4CosFactorMultiplier; + + fltx4 fl4StartTimeMin = ReplicateX4( m_flStartTime_min ); + fltx4 fl4StartTimeWidth = ReplicateX4( m_flStartTime_max - m_flStartTime_min ); + fltx4 fl4EndTimeMin = ReplicateX4( m_flEndTime_min ); + fltx4 fl4EndTimeWidth = ReplicateX4( m_flEndTime_max - m_flEndTime_min ); + + fltx4 fl4FrequencyMin = ReplicateX4( m_FrequencyMin ); + fltx4 fl4FrequencyWidth = ReplicateX4( m_FrequencyMax - m_FrequencyMin ); + fltx4 fl4RateMin = ReplicateX4( m_RateMin ); + fltx4 fl4RateWidth = ReplicateX4( m_RateMax - m_RateMin ); + + int nCtr = pParticles->m_nPaddedActiveParticles; + + + do + { + fltx4 fl4LifeDuration = *pLifeDuration; + fltx4 fl4GoodMask = CmpGtSIMD( fl4LifeDuration, Four_Zeros ); + fltx4 fl4LifeTime; + if ( m_bProportionalOp ) + { + fl4LifeTime = MulSIMD( SubSIMD( fl4CurTime, *pCreationTime ), ReciprocalEstSIMD( fl4LifeDuration ) ); // maybe need accurate div here? + } + else + { + fl4LifeTime = SubSIMD( fl4CurTime, *pCreationTime ); + } + + fltx4 fl4StartTime= pParticles->RandomFloat( *pParticleId, nRandomOffset + 11); + fl4StartTime = AddSIMD( fl4StartTimeMin, MulSIMD( fl4StartTimeWidth, fl4StartTime ) ); + fltx4 fl4EndTime= pParticles->RandomFloat( *pParticleId, nRandomOffset + 12); + fl4EndTime = AddSIMD( fl4EndTimeMin, MulSIMD( fl4EndTimeWidth, fl4EndTime ) ); + fl4GoodMask = AndSIMD( fl4GoodMask, CmpGeSIMD( fl4LifeTime, fl4StartTime ) ); + fl4GoodMask = AndSIMD( fl4GoodMask, CmpLtSIMD( fl4LifeTime, fl4EndTime ) ); + if ( IsAnyNegative( fl4GoodMask ) ) + { + fltx4 fl4Frequency = pParticles->RandomFloat( *pParticleId, nRandomOffset ); + fl4Frequency = AddSIMD( fl4FrequencyMin, MulSIMD( fl4FrequencyWidth, fl4Frequency ) ); + fltx4 fl4Rate= pParticles->RandomFloat( *pParticleId, nRandomOffset + 1); + fl4Rate = AddSIMD( fl4RateMin, MulSIMD( fl4RateWidth, fl4Rate ) ); + fltx4 fl4Cos; + if ( m_bProportional ) + { + fl4LifeTime = MulSIMD( SubSIMD( fl4CurTime, *pCreationTime ), ReciprocalEstSIMD( fl4LifeDuration ) ); + fl4Cos = AddSIMD( MulSIMD( fl4CosFactorProp, MulSIMD( fl4LifeTime, fl4Frequency )), fl4CosFactorAdd ); + } + else + { + fl4Cos = MulSIMD( fl4CosFactor, fl4Frequency ); + } + fltx4 fl4OscMultiplier = MulSIMD( fl4Rate, fl4ScaleFactor); + fl4OscVal = AddSIMD ( *pOscField, MulSIMD ( fl4OscMultiplier, SinEst01SIMD( fl4Cos ) ) ); + if ( m_nField == 7) + { + *pOscField = MaskedAssign( fl4GoodMask, + MaxSIMD( MinSIMD( fl4OscVal, Four_Ones), Four_Zeros ), *pOscField ); + } + else + { + *pOscField = MaskedAssign( fl4GoodMask, fl4OscVal, *pOscField ); + } + } + ++pCreationTime; + ++pLifeDuration; + ++pOscField; + ++pParticleId; + } while (--nCtr ); + +}; + + + + +//----------------------------------------------------------------------------- +// Oscillating Vector operator +// performs an oscillation operation on any vector (location, tint) +//----------------------------------------------------------------------------- +class C_OP_OscillateVector : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_OscillateVector ); + + uint32 GetWrittenAttributes( void ) const + { + return 1 << m_nField; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK | + PARTICLE_ATTRIBUTE_PARTICLE_ID_MASK; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + Vector m_RateMin; + Vector m_RateMax; + Vector m_FrequencyMin; + Vector m_FrequencyMax; + int m_nField; + bool m_bProportional, m_bProportionalOp; + bool m_bAccelerator; + float m_flStartTime_min; + float m_flStartTime_max; + float m_flEndTime_min; + float m_flEndTime_max; + float m_flOscMult; + float m_flOscAdd; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_OscillateVector, "Oscillate Vector", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_OscillateVector ) + DMXELEMENT_UNPACK_FIELD_USERDATA( "oscillation field", "0", int, m_nField, "intchoice particlefield_vector" ) + DMXELEMENT_UNPACK_FIELD( "oscillation rate min", "0 0 0", Vector, m_RateMin ) + DMXELEMENT_UNPACK_FIELD( "oscillation rate max", "0 0 0", Vector, m_RateMax ) + DMXELEMENT_UNPACK_FIELD( "oscillation frequency min", "1 1 1", Vector, m_FrequencyMin ) + DMXELEMENT_UNPACK_FIELD( "oscillation frequency max", "1 1 1", Vector, m_FrequencyMax ) + DMXELEMENT_UNPACK_FIELD( "proportional 0/1", "1", bool, m_bProportional ) + DMXELEMENT_UNPACK_FIELD( "start time min", "0", float, m_flStartTime_min ) + DMXELEMENT_UNPACK_FIELD( "start time max", "0", float, m_flStartTime_max ) + DMXELEMENT_UNPACK_FIELD( "end time min", "1", float, m_flEndTime_min ) + DMXELEMENT_UNPACK_FIELD( "end time max", "1", float, m_flEndTime_max ) + DMXELEMENT_UNPACK_FIELD( "start/end proportional", "1", bool, m_bProportionalOp ) + DMXELEMENT_UNPACK_FIELD( "oscillation multiplier", "2", float, m_flOscMult ) + DMXELEMENT_UNPACK_FIELD( "oscillation start phase", ".5", float, m_flOscAdd ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_OscillateVector ) + + +void C_OP_OscillateVector::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + CM128AttributeIterator pCreationTime( PARTICLE_ATTRIBUTE_CREATION_TIME, pParticles ); + CM128AttributeIterator pLifeDuration( PARTICLE_ATTRIBUTE_LIFE_DURATION, pParticles ); + C4IAttributeIterator pParticleId ( PARTICLE_ATTRIBUTE_PARTICLE_ID, pParticles ); + C4VAttributeWriteIterator pOscField ( m_nField, pParticles) ; + + fltx4 fl4CurTime = pParticles->m_fl4CurTime; + + int nRandomOffset = pParticles->OperatorRandomSampleOffset(); + + FourVectors fvOscVal; + + fltx4 fl4ScaleFactor = ReplicateX4( flStrength * pParticles->m_flDt ); + + fltx4 fl4CosFactorMultiplier = ReplicateX4( m_flOscMult ); + fltx4 fl4CosFactorAdd = ReplicateX4( m_flOscAdd ); + + fltx4 fl4CosFactor = AddSIMD( MulSIMD( fl4CosFactorMultiplier, fl4CurTime ), fl4CosFactorAdd ); + fltx4 fl4CosFactorProp = fl4CosFactorMultiplier; + + fltx4 fl4StartTimeMin = ReplicateX4( m_flStartTime_min ); + fltx4 fl4StartTimeWidth = ReplicateX4( m_flStartTime_max - m_flStartTime_min ); + fltx4 fl4EndTimeMin = ReplicateX4( m_flEndTime_min ); + fltx4 fl4EndTimeWidth = ReplicateX4( m_flEndTime_max - m_flEndTime_min ); + + FourVectors fvFrequencyMin; + fvFrequencyMin.DuplicateVector( m_FrequencyMin ); + FourVectors fvFrequencyWidth; + fvFrequencyWidth.DuplicateVector( m_FrequencyMax - m_FrequencyMin ); + FourVectors fvRateMin; + fvRateMin.DuplicateVector( m_RateMin ); + FourVectors fvRateWidth; + fvRateWidth.DuplicateVector( m_RateMax - m_RateMin ); + + int nCtr = pParticles->m_nPaddedActiveParticles; + + + do + { + fltx4 fl4LifeDuration = *pLifeDuration; + fltx4 fl4GoodMask = CmpGtSIMD( fl4LifeDuration, Four_Zeros ); + fltx4 fl4LifeTime; + if ( m_bProportionalOp ) + { + fl4LifeTime = MulSIMD( SubSIMD( fl4CurTime, *pCreationTime ), ReciprocalEstSIMD( fl4LifeDuration ) ); // maybe need accurate div here? + } + else + { + fl4LifeTime = SubSIMD( fl4CurTime, *pCreationTime ); + } + + fltx4 fl4StartTime= pParticles->RandomFloat( *pParticleId, nRandomOffset + 11); + fl4StartTime = AddSIMD( fl4StartTimeMin, MulSIMD( fl4StartTimeWidth, fl4StartTime ) ); + fltx4 fl4EndTime= pParticles->RandomFloat( *pParticleId, nRandomOffset + 12); + fl4EndTime = AddSIMD( fl4EndTimeMin, MulSIMD( fl4EndTimeWidth, fl4EndTime ) ); + fl4GoodMask = AndSIMD( fl4GoodMask, CmpGeSIMD( fl4LifeTime, fl4StartTime ) ); + fl4GoodMask = AndSIMD( fl4GoodMask, CmpLtSIMD( fl4LifeTime, fl4EndTime ) ); + if ( IsAnyNegative( fl4GoodMask ) ) + { + FourVectors fvFrequency; + fvFrequency.x = pParticles->RandomFloat( *pParticleId, nRandomOffset + 8 ); + fvFrequency.y = pParticles->RandomFloat( *pParticleId, nRandomOffset + 12 ); + fvFrequency.z = pParticles->RandomFloat( *pParticleId, nRandomOffset + 15 ); + fvFrequency.VProduct( fvFrequencyWidth ); + fvFrequency += fvFrequencyMin; + + FourVectors fvRate; + fvRate.x = pParticles->RandomFloat( *pParticleId, nRandomOffset + 3); + fvRate.y = pParticles->RandomFloat( *pParticleId, nRandomOffset + 7); + fvRate.z = pParticles->RandomFloat( *pParticleId, nRandomOffset + 9); + + //fvRate = AddSIMD( fvRateMin, MulSIMD( fvRateWidth, fvRate ) ); + fvRate.VProduct( fvRateWidth ); + fvRate += fvRateMin; + + FourVectors fvCos; + if ( m_bProportional ) + { + fl4LifeTime = MulSIMD( SubSIMD( fl4CurTime, *pCreationTime ), ReciprocalEstSIMD( fl4LifeDuration ) ); + fvCos.x = AddSIMD( MulSIMD( fl4CosFactorProp, MulSIMD( fvFrequency.x, fl4LifeTime )), fl4CosFactorAdd ); + fvCos.y = AddSIMD( MulSIMD( fl4CosFactorProp, MulSIMD( fvFrequency.y, fl4LifeTime )), fl4CosFactorAdd ); + fvCos.z = AddSIMD( MulSIMD( fl4CosFactorProp, MulSIMD( fvFrequency.z, fl4LifeTime )), fl4CosFactorAdd ); + } + else + { + //fvCos = MulSIMD( fl4CosFactor, fvFrequency ); + fvCos.x = MulSIMD( fvFrequency.x, fl4CosFactor ); + fvCos.y = MulSIMD( fvFrequency.y, fl4CosFactor ); + fvCos.z = MulSIMD( fvFrequency.z, fl4CosFactor ); + } + + FourVectors fvOscMultiplier; + fvOscMultiplier.x = MulSIMD( fvRate.x, fl4ScaleFactor); + fvOscMultiplier.y = MulSIMD( fvRate.y, fl4ScaleFactor); + fvOscMultiplier.z = MulSIMD( fvRate.z, fl4ScaleFactor); + + FourVectors fvOutput = *pOscField; + + fvOscVal.x = AddSIMD ( fvOutput.x, MulSIMD ( fvOscMultiplier.x, SinEst01SIMD( fvCos.x ) ) ); + fvOscVal.y = AddSIMD ( fvOutput.y, MulSIMD ( fvOscMultiplier.y, SinEst01SIMD( fvCos.y ) ) ); + fvOscVal.z = AddSIMD ( fvOutput.z, MulSIMD ( fvOscMultiplier.z, SinEst01SIMD( fvCos.z ) ) ); + + if ( m_nField == 6) + { + pOscField->x = MaskedAssign( fl4GoodMask, + MaxSIMD( MinSIMD( fvOscVal.x, Four_Ones), Four_Zeros ), fvOutput.x ); + pOscField->y = MaskedAssign( fl4GoodMask, + MaxSIMD( MinSIMD( fvOscVal.y, Four_Ones), Four_Zeros ), fvOutput.y ); + pOscField->z = MaskedAssign( fl4GoodMask, + MaxSIMD( MinSIMD( fvOscVal.z, Four_Ones), Four_Zeros ), fvOutput.z ); + } + else + { + pOscField->x = MaskedAssign( fl4GoodMask, fvOscVal.x, fvOutput.x ); + pOscField->y = MaskedAssign( fl4GoodMask, fvOscVal.y, fvOutput.y ); + pOscField->z = MaskedAssign( fl4GoodMask, fvOscVal.z, fvOutput.z ); + } + } + ++pCreationTime; + ++pLifeDuration; + ++pOscField; + ++pParticleId; + } while (--nCtr ); +}; + + + + +//----------------------------------------------------------------------------- +// Remap Scalar Operator +//----------------------------------------------------------------------------- +class C_OP_RemapScalar : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_RemapScalar ); + + uint32 GetWrittenAttributes( void ) const + { + return 1 << m_nFieldOutput; + } + + uint32 GetReadAttributes( void ) const + { + return 1 << m_nFieldInput; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + int m_nFieldInput; + int m_nFieldOutput; + float m_flInputMin; + float m_flInputMax; + float m_flOutputMin; + float m_flOutputMax; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_RemapScalar, "Remap Scalar", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_RemapScalar ) + DMXELEMENT_UNPACK_FIELD_USERDATA( "input field", "7", int, m_nFieldInput, "intchoice particlefield_scalar" ) + DMXELEMENT_UNPACK_FIELD( "input minimum","0", float, m_flInputMin ) + DMXELEMENT_UNPACK_FIELD( "input maximum","1", float, m_flInputMax ) + DMXELEMENT_UNPACK_FIELD_USERDATA( "output field", "3", int, m_nFieldOutput, "intchoice particlefield_scalar" ) + DMXELEMENT_UNPACK_FIELD( "output minimum","0", float, m_flOutputMin ) + DMXELEMENT_UNPACK_FIELD( "output maximum","1", float, m_flOutputMax ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_RemapScalar ) + +void C_OP_RemapScalar::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + // clamp the result to 0 and 1 if it's alpha + float flMin=m_flOutputMin; + float flMax=m_flOutputMax; + if ( ATTRIBUTES_WHICH_ARE_0_TO_1 & ( 1 << m_nFieldOutput ) ) + { + flMin = clamp(m_flOutputMin, 0.0f, 1.0f ); + flMax = clamp(m_flOutputMax, 0.0f, 1.0f ); + } + + // FIXME: SSE-ize + for ( int i = 0; i < pParticles->m_nActiveParticles; ++i ) + { + const float *pInput = pParticles->GetFloatAttributePtr( m_nFieldInput, i ); + float *pOutput = pParticles->GetFloatAttributePtrForWrite( m_nFieldOutput, i ); + float flOutput = RemapValClamped( *pInput, m_flInputMin, m_flInputMax, flMin, flMax ); + *pOutput = Lerp (flStrength, *pOutput, flOutput); + } +} + + +//----------------------------------------------------------------------------- +// noise Operator +//----------------------------------------------------------------------------- +class C_OP_Noise : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_Noise ); + + uint32 GetWrittenAttributes( void ) const + { + return 1 << m_nFieldOutput; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + int m_nFieldOutput; + float m_flOutputMin; + float m_flOutputMax; + fltx4 m_fl4NoiseScale; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_Noise, "Noise Scalar", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_Noise ) + DMXELEMENT_UNPACK_FLTX4( "noise coordinate scale", "0.1", m_fl4NoiseScale) + DMXELEMENT_UNPACK_FIELD_USERDATA( "output field", "3", int, m_nFieldOutput, "intchoice particlefield_scalar" ) + DMXELEMENT_UNPACK_FIELD( "output minimum","0", float, m_flOutputMin ) + DMXELEMENT_UNPACK_FIELD( "output maximum","1", float, m_flOutputMax ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_Noise ); + +void C_OP_Noise::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + CM128AttributeWriteIterator pAttr( m_nFieldOutput, pParticles ); + + C4VAttributeIterator pXYZ( PARTICLE_ATTRIBUTE_XYZ, pParticles ); + + fltx4 CoordScale=m_fl4NoiseScale; + + float fMin = m_flOutputMin; + float fMax = m_flOutputMax; + + if ( ATTRIBUTES_WHICH_ARE_ANGLES & (1 << m_nFieldOutput ) ) + { + fMin *= ( M_PI / 180.0f ); + fMax *= ( M_PI / 180.0f ); + } + // calculate coefficients. noise retuns -1..1 + fltx4 ValueScale=ReplicateX4( 0.5*(fMax-fMin ) ); + fltx4 ValueBase=ReplicateX4( fMin + 0.5*( fMax - fMin ) ); + int nActive = pParticles->m_nPaddedActiveParticles; + do + { + FourVectors Coord = *pXYZ; + Coord *= CoordScale; + *( pAttr )=AddSIMD( ValueBase, MulSIMD( ValueScale, NoiseSIMD( Coord ) ) ); + + ++pAttr; + ++pXYZ; + } while( --nActive ); +} +//----------------------------------------------------------------------------- +// vector noise Operator +//----------------------------------------------------------------------------- +class C_OP_VectorNoise : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_VectorNoise ); + + uint32 GetWrittenAttributes( void ) const + { + return 1 << m_nFieldOutput; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + int m_nFieldOutput; + Vector m_vecOutputMin; + Vector m_vecOutputMax; + fltx4 m_fl4NoiseScale; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_VectorNoise, "Noise Vector", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_VectorNoise ) + DMXELEMENT_UNPACK_FLTX4( "noise coordinate scale", "0.1", m_fl4NoiseScale) + DMXELEMENT_UNPACK_FIELD_USERDATA( "output field", "6", int, m_nFieldOutput, "intchoice particlefield_vector" ) + DMXELEMENT_UNPACK_FIELD( "output minimum","0 0 0", Vector, m_vecOutputMin ) + DMXELEMENT_UNPACK_FIELD( "output maximum","1 1 1", Vector, m_vecOutputMax ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_VectorNoise ); + +void C_OP_VectorNoise::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + C4VAttributeWriteIterator pAttr( m_nFieldOutput, pParticles ); + + C4VAttributeIterator pXYZ( PARTICLE_ATTRIBUTE_XYZ, pParticles ); + + fltx4 CoordScale = m_fl4NoiseScale; + + // calculate coefficients. noise retuns -1..1 + fltx4 ValueScaleX = ReplicateX4( 0.5*(m_vecOutputMax.x-m_vecOutputMin.x ) ); + fltx4 ValueBaseX = ReplicateX4(m_vecOutputMin.x+0.5*( m_vecOutputMax.x-m_vecOutputMin.x ) ); + + fltx4 ValueScaleY = ReplicateX4( 0.5*(m_vecOutputMax.y-m_vecOutputMin.y ) ); + fltx4 ValueBaseY = ReplicateX4(m_vecOutputMin.y+0.5*( m_vecOutputMax.y-m_vecOutputMin.y ) ); + + fltx4 ValueScaleZ = ReplicateX4( 0.5*(m_vecOutputMax.z-m_vecOutputMin.z ) ); + fltx4 ValueBaseZ = ReplicateX4(m_vecOutputMin.z+0.5*( m_vecOutputMax.z-m_vecOutputMin.z ) ); + + FourVectors ofs_y; + ofs_y.DuplicateVector( Vector( 100000.5, 300000.25, 9000000.75 ) ); + FourVectors ofs_z; + ofs_z.DuplicateVector( Vector( 110000.25, 310000.75, 9100000.5 ) ); + + int nActive = pParticles->m_nActiveParticles; + for( int i=0; i < nActive; i+=4 ) + { + FourVectors Coord = *pXYZ; + Coord *= CoordScale; + pAttr->x=AddSIMD( ValueBaseX, MulSIMD( ValueScaleX, NoiseSIMD( Coord ) ) ); + Coord += ofs_y; + pAttr->y=AddSIMD( ValueBaseY, MulSIMD( ValueScaleY, NoiseSIMD( Coord ) ) ); + Coord += ofs_z; + pAttr->z=AddSIMD( ValueBaseZ, MulSIMD( ValueScaleZ, NoiseSIMD( Coord ) ) ); + + ++pAttr; + ++pXYZ; + } +} + +//----------------------------------------------------------------------------- +// Decay Operator (Lifespan limiter - kills dead particles) +//----------------------------------------------------------------------------- +class C_OP_Decay : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_Decay ); + + uint32 GetWrittenAttributes( void ) const + { + return 0; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_Decay, "Lifespan Decay", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_Decay ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_Decay ) + + +void C_OP_Decay::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + fltx4 fl4CurTime = pParticles->m_fl4CurTime; + + CM128AttributeIterator pCreationTime( PARTICLE_ATTRIBUTE_CREATION_TIME, pParticles ); + CM128AttributeIterator pLifeDuration( PARTICLE_ATTRIBUTE_LIFE_DURATION, pParticles ); + + int nLimit = pParticles->m_nPaddedActiveParticles << 2; + + for ( int i = 0; i < nLimit; i+= 4 ) + { + fltx4 fl4LifeDuration = *pLifeDuration; + + fltx4 fl4KillMask = CmpLeSIMD( fl4LifeDuration, Four_Zeros ); + + fltx4 fl4Age = SubSIMD( fl4CurTime, *pCreationTime ); + + fl4KillMask = OrSIMD( fl4KillMask, CmpGeSIMD( fl4Age, fl4LifeDuration ) ); + if ( IsAnyNegative( fl4KillMask ) ) + { + // not especially pretty - we need to kill some particles. + int nMask = TestSignSIMD( fl4KillMask ); + if ( nMask & 1 ) + pParticles->KillParticle( i ); + if ( nMask & 2 ) + pParticles->KillParticle( i + 1 ); + if ( nMask & 4 ) + pParticles->KillParticle( i + 2 ); + if ( nMask & 8 ) + pParticles->KillParticle( i + 3 ); + + } + ++pCreationTime; + ++pLifeDuration; + } +} + + + +//----------------------------------------------------------------------------- +// Lifespan Minimum Velocity Decay Operator (kills particles if they cease moving) +//----------------------------------------------------------------------------- +class C_OP_VelocityDecay : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_VelocityDecay ); + + uint32 GetWrittenAttributes( void ) const + { + return 0; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + float m_flMinVelocity; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_VelocityDecay, "Lifespan Minimum Velocity Decay", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_VelocityDecay ) + DMXELEMENT_UNPACK_FIELD( "minimum velocity","1", float, m_flMinVelocity ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_VelocityDecay ) + + +void C_OP_VelocityDecay::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + fltx4 fl4MinVelocity = ReplicateX4( m_flMinVelocity ); + fltx4 fl4Dt = ReplicateX4( pParticles->m_flDt ); + + CM128AttributeIterator pXYZ( PARTICLE_ATTRIBUTE_XYZ, pParticles ); + CM128AttributeIterator pPrevXYZ( PARTICLE_ATTRIBUTE_PREV_XYZ, pParticles ); + + int nLimit = pParticles->m_nPaddedActiveParticles << 2; + + for ( int i = 0; i < nLimit; i+= 4 ) + { + fltx4 fl4KillMask = CmpLeSIMD( DivSIMD ( SubSIMD( *pXYZ, *pPrevXYZ ), fl4Dt ), fl4MinVelocity ); + + if ( IsAnyNegative( fl4KillMask ) ) + { + // not especially pretty - we need to kill some particles. + int nMask = TestSignSIMD( fl4KillMask ); + if ( nMask & 1 ) + pParticles->KillParticle( i ); + if ( nMask & 2 ) + pParticles->KillParticle( i + 1 ); + if ( nMask & 4 ) + pParticles->KillParticle( i + 2 ); + if ( nMask & 8 ) + pParticles->KillParticle( i + 3 ); + } + ++pXYZ; + ++pPrevXYZ; + } +} + + +//----------------------------------------------------------------------------- +// Random Cull Operator - Randomly culls particles before their lifespan +//----------------------------------------------------------------------------- +class C_OP_Cull : public CParticleOperatorInstance +{ + float m_flCullPerc; + float m_flCullStart; + float m_flCullEnd; + float m_flCullExp; + + DECLARE_PARTICLE_OPERATOR( C_OP_Cull ); + + uint32 GetWrittenAttributes( void ) const + { + return 0; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK | PARTICLE_ATTRIBUTE_PARTICLE_ID_MASK; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_Cull, "Cull Random", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_Cull ) + DMXELEMENT_UNPACK_FIELD( "Cull Start Time", "0", float, m_flCullStart ) + DMXELEMENT_UNPACK_FIELD( "Cull End Time", "1", float, m_flCullEnd ) + DMXELEMENT_UNPACK_FIELD( "Cull Time Exponent", "1", float, m_flCullExp ) + DMXELEMENT_UNPACK_FIELD( "Cull Percentage", "0.5", float, m_flCullPerc ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_Cull ) + + +void C_OP_Cull::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + const float *pCreationTime; + const float *pLifeDuration; + float flLifeTime; + int nRandomOffset = pParticles->OperatorRandomSampleOffset(); + + // FIXME: SSE-ize + for ( int i = 0; i < pParticles->m_nActiveParticles; ++i ) + { + pCreationTime = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, i ); + pLifeDuration = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_LIFE_DURATION, i ); + int nParticleId = *pParticles->GetIntAttributePtr( PARTICLE_ATTRIBUTE_PARTICLE_ID, i ); + float flCullRank = pParticles->RandomFloat( nParticleId + nRandomOffset + 15, 0.0f, 1.0f); + float flCullTime = pParticles->RandomFloatExp( nParticleId + nRandomOffset + 12, m_flCullStart, m_flCullEnd, m_flCullExp ); + + if ( flCullRank > ( m_flCullPerc * flStrength ) ) + { + continue; + } + // Find our life percentage + flLifeTime = clamp( ( pParticles->m_flCurTime - *pCreationTime ) / ( *pLifeDuration ), 0.0f, 1.0f ); + if ( flLifeTime >= m_flCullStart && flLifeTime <= m_flCullEnd && flLifeTime >= flCullTime ) + { + pParticles->KillParticle( i ); + } + } +} + + +//----------------------------------------------------------------------------- +// generic spin operator +//----------------------------------------------------------------------------- +class CGeneralSpin : public CParticleOperatorInstance +{ +protected: + virtual int GetAttributeToSpin( void ) const =0; + + uint32 GetWrittenAttributes( void ) const + { + if ( m_nSpinRateDegrees != 0.0 ) + return (1 << GetAttributeToSpin() ); + return 0; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK; + } + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_fSpinRateRadians = (float) m_nSpinRateDegrees * ( M_PI / 180.0f ); + m_fSpinRateMinRadians = (float) m_nSpinRateMinDegrees * ( M_PI / 180.0f ); + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + int m_nSpinRateDegrees; + int m_nSpinRateMinDegrees; + float m_fSpinRateRadians; + float m_fSpinRateStopTime; + float m_fSpinRateMinRadians; +}; + +void CGeneralSpin::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + float fCurSpinRate = m_fSpinRateRadians * flStrength; + + if ( fCurSpinRate == 0.0 ) + return; + + float dt = pParticles->m_flDt; + float drot = dt * fabs( fCurSpinRate * 2.0f * M_PI ); + if ( m_fSpinRateStopTime == 0.0f ) + { + drot = fmod( drot, (float)(2.0f * M_PI) ); + } + if ( fCurSpinRate < 0.0f ) + { + drot = -drot; + } + fltx4 Rot_Add = ReplicateX4( drot ); + fltx4 Pi_2 = ReplicateX4( 2.0*M_PI ); + fltx4 nPi_2 = ReplicateX4( -2.0*M_PI ); + + // FIXME: This is wrong + fltx4 minSpeedRadians = ReplicateX4( dt * fabs( m_fSpinRateMinRadians * 2.0f * M_PI ) ); + + fltx4 now = pParticles->m_fl4CurTime; + fltx4 SpinRateStopTime = ReplicateX4( m_fSpinRateStopTime ); + + CM128AttributeIterator pCreationTimeStamp( PARTICLE_ATTRIBUTE_CREATION_TIME, pParticles ); + + CM128AttributeIterator pLifeDuration( PARTICLE_ATTRIBUTE_LIFE_DURATION, pParticles ); + + CM128AttributeWriteIterator pRot( GetAttributeToSpin(), pParticles ); + + int nActive = pParticles->m_nActiveParticles; + for( int i=0; i < nActive; i+=4 ) + { + // HACK: Rather than redo this, I'm simply remapping the stop time into the percentage of lifetime, rather than seconds + fltx4 LifeSpan = *pLifeDuration; + fltx4 SpinFadePerc = Four_Zeros; + fltx4 OOSpinFadeRate = Four_Zeros; + if ( m_fSpinRateStopTime ) + { + SpinFadePerc = MulSIMD( LifeSpan, SpinRateStopTime ); + OOSpinFadeRate = DivSIMD( Four_Ones, SpinFadePerc ); + } + + fltx4 Age = SubSIMD( now, *pCreationTimeStamp ); + fltx4 RScale = MaxSIMD( Four_Zeros, + SubSIMD( Four_Ones, MulSIMD( Age, OOSpinFadeRate ) ) ); + + // Cap the rotation at a minimum speed + fltx4 deltaRot = MulSIMD( Rot_Add, RScale ); + fltx4 Tooslow = CmpLeSIMD( deltaRot, minSpeedRadians ); + deltaRot = OrSIMD( AndSIMD( Tooslow, minSpeedRadians ), AndNotSIMD( Tooslow, deltaRot ) ); + fltx4 NewRot = AddSIMD( *pRot, deltaRot ); + + // now, cap at +/- 2*pi + fltx4 Toobig =CmpGeSIMD( NewRot, Pi_2 ); + fltx4 Toosmall = CmpLeSIMD( NewRot, nPi_2 ); + + NewRot = OrSIMD( AndSIMD( Toobig, SubSIMD( NewRot, Pi_2 ) ), + AndNotSIMD( Toobig, NewRot ) ); + + NewRot = OrSIMD( AndSIMD( Toosmall, AddSIMD( NewRot, Pi_2 ) ), + AndNotSIMD( Toosmall, NewRot ) ); + + *( pRot )= NewRot; + + ++pRot; + ++pCreationTimeStamp; + ++pLifeDuration; + } +} + + +//----------------------------------------------------------------------------- +// generic spin operator, version 2. Uses rotation_speed +//----------------------------------------------------------------------------- +class CSpinUpdateBase : public CParticleOperatorInstance +{ +protected: + virtual int GetAttributeToSpin( void ) const =0; + virtual int GetSpinSpeedAttribute( void ) const =0; + + uint32 GetWrittenAttributes( void ) const + { + return (1 << GetAttributeToSpin() ); + } + + uint32 GetReadAttributes( void ) const + { + return ( 1 << GetAttributeToSpin() ) | ( 1 << GetSpinSpeedAttribute() ) | + PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; +}; + +void CSpinUpdateBase::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + CM128AttributeIterator pCreationTimeStamp( PARTICLE_ATTRIBUTE_CREATION_TIME, pParticles ); + CM128AttributeIterator pRotationSpeed( GetSpinSpeedAttribute(), pParticles ); + CM128AttributeWriteIterator pRot( GetAttributeToSpin(), pParticles ); + + fltx4 fl4CurTime = pParticles->m_fl4CurTime; + fltx4 fl4Dt = ReplicateX4( pParticles->m_flDt ); + fltx4 fl4ScaleFactor = ReplicateX4( flStrength ); + + int nActive = pParticles->m_nActiveParticles; + for( int i=0; i < nActive; i += 4 ) + { + fltx4 fl4SimTime = MinSIMD( fl4Dt, SubSIMD( fl4CurTime, *pCreationTimeStamp ) ); + fl4SimTime = MulSIMD( fl4SimTime, fl4ScaleFactor ); + *pRot = MaddSIMD( fl4SimTime, *pRotationSpeed, *pRot ); + + ++pRot; + ++pRotationSpeed; + ++pCreationTimeStamp; + } +} + + +class C_OP_Spin : public CGeneralSpin +{ + DECLARE_PARTICLE_OPERATOR( C_OP_Spin ); + + int GetAttributeToSpin( void ) const + { + return PARTICLE_ATTRIBUTE_ROTATION; + } +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_Spin, "Rotation Spin Roll", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_Spin ) + DMXELEMENT_UNPACK_FIELD( "spin_rate_degrees", "0", int, m_nSpinRateDegrees ) + DMXELEMENT_UNPACK_FIELD( "spin_stop_time", "0", float, m_fSpinRateStopTime ) + DMXELEMENT_UNPACK_FIELD( "spin_rate_min", "0", int, m_nSpinRateMinDegrees ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_Spin ) + +class C_OP_SpinUpdate : public CSpinUpdateBase +{ + DECLARE_PARTICLE_OPERATOR( C_OP_SpinUpdate ); + + virtual int GetAttributeToSpin( void ) const + { + return PARTICLE_ATTRIBUTE_ROTATION; + } + + virtual int GetSpinSpeedAttribute( void ) const + { + return PARTICLE_ATTRIBUTE_ROTATION_SPEED; + } +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_SpinUpdate, "Rotation Basic", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_SpinUpdate ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_SpinUpdate ) + +class C_OP_SpinYaw : public CGeneralSpin +{ + DECLARE_PARTICLE_OPERATOR( C_OP_SpinYaw ); + + int GetAttributeToSpin( void ) const + { + return PARTICLE_ATTRIBUTE_YAW; + } +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_SpinYaw, "Rotation Spin Yaw", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_SpinYaw ) + DMXELEMENT_UNPACK_FIELD( "yaw_rate_degrees", "0", int, m_nSpinRateDegrees ) + DMXELEMENT_UNPACK_FIELD( "yaw_stop_time", "0", float, m_fSpinRateStopTime ) + DMXELEMENT_UNPACK_FIELD( "yaw_rate_min", "0", int, m_nSpinRateMinDegrees ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_SpinYaw ) + + + +//----------------------------------------------------------------------------- +// Size changing operator +//----------------------------------------------------------------------------- +class C_OP_InterpolateRadius : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_InterpolateRadius ); + + uint32 GetReadInitialAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_RADIUS_MASK; + } + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_RADIUS_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_flBias = ( m_flBias != 0.0f ) ? m_flBias : 0.5f; + m_fl4BiasParam = PreCalcBiasParameter( ReplicateX4( m_flBias ) ); + } + + float m_flStartTime; + float m_flEndTime; + float m_flStartScale; + float m_flEndScale; + bool m_bEaseInAndOut; + float m_flBias; + fltx4 m_fl4BiasParam; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_InterpolateRadius, "Radius Scale", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_InterpolateRadius ) + DMXELEMENT_UNPACK_FIELD( "start_time", "0", float, m_flStartTime ) + DMXELEMENT_UNPACK_FIELD( "end_time", "1", float, m_flEndTime ) + DMXELEMENT_UNPACK_FIELD( "radius_start_scale", "1", float, m_flStartScale ) + DMXELEMENT_UNPACK_FIELD( "radius_end_scale", "1", float, m_flEndScale ) + DMXELEMENT_UNPACK_FIELD( "ease_in_and_out", "0", bool, m_bEaseInAndOut ) + DMXELEMENT_UNPACK_FIELD( "scale_bias", "0.5", float, m_flBias ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_InterpolateRadius ) + +void C_OP_InterpolateRadius::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + if ( m_flEndTime <= m_flStartTime ) + return; + CM128AttributeIterator pCreationTime( PARTICLE_ATTRIBUTE_CREATION_TIME, pParticles ); + CM128AttributeIterator pLifeDuration( PARTICLE_ATTRIBUTE_LIFE_DURATION, pParticles ); + CM128AttributeWriteIterator pRadius( PARTICLE_ATTRIBUTE_RADIUS, pParticles ); + CM128InitialAttributeIterator pInitialRadius( PARTICLE_ATTRIBUTE_RADIUS, pParticles ); + + fltx4 fl4StartTime = ReplicateX4( m_flStartTime ); + fltx4 fl4EndTime = ReplicateX4( m_flEndTime ); + fltx4 fl4OOTimeWidth = ReciprocalSIMD( SubSIMD( fl4EndTime, fl4StartTime ) ); + + fltx4 fl4ScaleWidth = ReplicateX4( m_flEndScale - m_flStartScale ); + fltx4 fl4StartScale = ReplicateX4( m_flStartScale ); + + fltx4 fl4CurTime = pParticles->m_fl4CurTime; + + int nCtr = pParticles->m_nPaddedActiveParticles; + + if ( m_bEaseInAndOut ) + { + do + { + fltx4 fl4LifeDuration = *pLifeDuration; + fltx4 fl4GoodMask = CmpGtSIMD( fl4LifeDuration, Four_Zeros ); + fltx4 fl4LifeTime = MulSIMD( SubSIMD( fl4CurTime, *pCreationTime ), ReciprocalEstSIMD( fl4LifeDuration ) ); // maybe need accurate div here? + fl4GoodMask = AndSIMD( fl4GoodMask, CmpGeSIMD( fl4LifeTime, fl4StartTime ) ); + fl4GoodMask = AndSIMD( fl4GoodMask, CmpLtSIMD( fl4LifeTime, fl4EndTime ) ); + if ( IsAnyNegative( fl4GoodMask ) ) + { + fltx4 fl4FadeWindow = MulSIMD( SubSIMD( fl4LifeTime, fl4StartTime ), fl4OOTimeWidth ); + fl4FadeWindow = AddSIMD( fl4StartScale, MulSIMD( SimpleSpline( fl4FadeWindow ), fl4ScaleWidth ) ); + // !!speed!! - can anyone really tell the diff between spline and lerp here? + *pRadius = MaskedAssign( + fl4GoodMask, MulSIMD( *pInitialRadius, fl4FadeWindow ), *pRadius ); + } + ++pCreationTime; + ++pLifeDuration; + ++pRadius; + ++pInitialRadius; + } while (--nCtr ); + } + else + { + if ( m_flBias == 0.5f ) // no bias case + { + do + { + fltx4 fl4LifeDuration = *pLifeDuration; + fltx4 fl4GoodMask = CmpGtSIMD( fl4LifeDuration, Four_Zeros ); + fltx4 fl4LifeTime = MulSIMD( SubSIMD( fl4CurTime, *pCreationTime ), ReciprocalEstSIMD( fl4LifeDuration ) ); // maybe need accurate div here? + fl4GoodMask = AndSIMD( fl4GoodMask, CmpGeSIMD( fl4LifeTime, fl4StartTime ) ); + fl4GoodMask = AndSIMD( fl4GoodMask, CmpLtSIMD( fl4LifeTime, fl4EndTime ) ); + if ( IsAnyNegative( fl4GoodMask ) ) + { + fltx4 fl4FadeWindow = MulSIMD( SubSIMD( fl4LifeTime, fl4StartTime ), fl4OOTimeWidth ); + fl4FadeWindow = AddSIMD( fl4StartScale, MulSIMD( fl4FadeWindow, fl4ScaleWidth ) ); + *pRadius = MaskedAssign( fl4GoodMask, MulSIMD( *pInitialRadius, fl4FadeWindow ), *pRadius ); + } + ++pCreationTime; + ++pLifeDuration; + ++pRadius; + ++pInitialRadius; + } while (--nCtr ); + } + else + { + // use rational approximation to bias + do + { + fltx4 fl4LifeDuration = *pLifeDuration; + fltx4 fl4GoodMask = CmpGtSIMD( fl4LifeDuration, Four_Zeros ); + fltx4 fl4LifeTime = MulSIMD( SubSIMD( fl4CurTime, *pCreationTime ), ReciprocalEstSIMD( fl4LifeDuration ) ); // maybe need accurate div here? + fl4GoodMask = AndSIMD( fl4GoodMask, CmpGeSIMD( fl4LifeTime, fl4StartTime ) ); + fl4GoodMask = AndSIMD( fl4GoodMask, CmpLtSIMD( fl4LifeTime, fl4EndTime ) ); + if ( IsAnyNegative( fl4GoodMask ) ) + { + fltx4 fl4FadeWindow = MulSIMD( SubSIMD( fl4LifeTime, fl4StartTime ), fl4OOTimeWidth ); +#ifdef FP_EXCEPTIONS_ENABLED + // Wherever fl4GoodMask is zero we need to ensure that fl4FadeWindow is not zero + // to avoid 0/0 divides in BiasSIMD. Setting those elements to fl4EndTime + // should do the trick... + fl4FadeWindow = OrSIMD( AndSIMD( fl4GoodMask, fl4EndTime ), AndNotSIMD( fl4GoodMask, fl4EndTime ) ); +#endif + fl4FadeWindow = AddSIMD( fl4StartScale, MulSIMD( BiasSIMD( fl4FadeWindow, m_fl4BiasParam ), fl4ScaleWidth ) ); + *pRadius = MaskedAssign( + fl4GoodMask, + MulSIMD( *pInitialRadius, fl4FadeWindow ), *pRadius ); + } + ++pCreationTime; + ++pLifeDuration; + ++pRadius; + ++pInitialRadius; + } while (--nCtr ); + } + } +} + + + +//----------------------------------------------------------------------------- +// Color Fade +//----------------------------------------------------------------------------- +class C_OP_ColorInterpolate : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_ColorInterpolate ); + + uint32 GetReadInitialAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_TINT_RGB_MASK; + } + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_TINT_RGB_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK; + } + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_flColorFade[0] = m_ColorFade[0] / 255.0f; + m_flColorFade[1] = m_ColorFade[1] / 255.0f; + m_flColorFade[2] = m_ColorFade[2] / 255.0f; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + Color m_ColorFade; + float m_flColorFade[3]; + float m_flFadeStartTime; + float m_flFadeEndTime; + bool m_bEaseInOut; +}; + + + +void C_OP_ColorInterpolate::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + C4VAttributeWriteIterator pColor( PARTICLE_ATTRIBUTE_TINT_RGB, pParticles ); + CM128AttributeIterator pCreationTime( PARTICLE_ATTRIBUTE_CREATION_TIME, pParticles ); + CM128AttributeIterator pLifeDuration( PARTICLE_ATTRIBUTE_LIFE_DURATION, pParticles ); + C4VInitialAttributeIterator pInitialColor( PARTICLE_ATTRIBUTE_TINT_RGB, pParticles ); + if ( m_flFadeEndTime == m_flFadeStartTime ) + return; + + fltx4 ooInRange = ReplicateX4( 1.0 / ( m_flFadeEndTime - m_flFadeStartTime ) ); + + fltx4 curTime = pParticles->m_fl4CurTime; + fltx4 lowRange = ReplicateX4( m_flFadeStartTime ); + + fltx4 targetR = ReplicateX4( m_flColorFade[0] ); + fltx4 targetG = ReplicateX4( m_flColorFade[1] ); + fltx4 targetB = ReplicateX4( m_flColorFade[2] ); + + int nCtr = pParticles->m_nPaddedActiveParticles; + + if ( m_bEaseInOut ) + { + do + { + fltx4 goodMask = CmpGtSIMD( *pLifeDuration, Four_Zeros ); + if ( IsAnyNegative( goodMask ) ) + { + fltx4 flLifeTime = DivSIMD( SubSIMD( curTime, *pCreationTime ), *pLifeDuration ); + + fltx4 T = MulSIMD( SubSIMD( flLifeTime, lowRange ), ooInRange ); + T = MinSIMD( Four_Ones, MaxSIMD( Four_Zeros, T ) ); + T = SimpleSpline( T ); + pColor->x = MaskedAssign( goodMask, AddSIMD( pInitialColor->x, MulSIMD( T, SubSIMD( targetR, pInitialColor->x ) ) ), pColor->x ); + pColor->y = MaskedAssign( goodMask, AddSIMD( pInitialColor->y, MulSIMD( T, SubSIMD( targetG, pInitialColor->y ) ) ), pColor->y ); + pColor->z = MaskedAssign( goodMask, AddSIMD( pInitialColor->z, MulSIMD( T, SubSIMD( targetB, pInitialColor->z ) ) ), pColor->z ); + } + ++pColor; + ++pCreationTime; + ++pLifeDuration; + ++pInitialColor; + + } while( --nCtr ); + } + else + { + do + { + fltx4 goodMask = CmpGtSIMD( *pLifeDuration, Four_Zeros ); + if ( IsAnyNegative( goodMask ) ) + { + fltx4 flLifeTime = DivSIMD( SubSIMD( curTime, *pCreationTime ), *pLifeDuration ); + + fltx4 T = MulSIMD( SubSIMD( flLifeTime, lowRange ), ooInRange ); + T = MinSIMD( Four_Ones, MaxSIMD( Four_Zeros, T ) ); + + pColor->x = MaskedAssign( goodMask, AddSIMD( pInitialColor->x, MulSIMD( T, SubSIMD( targetR, pInitialColor->x ) ) ), pColor->x ); + pColor->y = MaskedAssign( goodMask, AddSIMD( pInitialColor->y, MulSIMD( T, SubSIMD( targetG, pInitialColor->y ) ) ), pColor->y ); + pColor->z = MaskedAssign( goodMask, AddSIMD( pInitialColor->z, MulSIMD( T, SubSIMD( targetB, pInitialColor->z ) ) ), pColor->z ); + } + ++pColor; + ++pCreationTime; + ++pLifeDuration; + ++pInitialColor; + + } while( --nCtr ); + } +} + +DEFINE_PARTICLE_OPERATOR( C_OP_ColorInterpolate, "Color Fade", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_ColorInterpolate ) + DMXELEMENT_UNPACK_FIELD( "color_fade", "255 255 255 255", Color, m_ColorFade ) + DMXELEMENT_UNPACK_FIELD( "fade_start_time", "0", float, m_flFadeStartTime ) + DMXELEMENT_UNPACK_FIELD( "fade_end_time", "1", float, m_flFadeEndTime ) + DMXELEMENT_UNPACK_FIELD( "ease_in_and_out", "1", bool, m_bEaseInOut ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_ColorInterpolate ) + + +//----------------------------------------------------------------------------- +// Position Lock to Control Point +// Locks all particles to the specified control point +// Useful for making particles move with their emitter and so forth +//----------------------------------------------------------------------------- +class C_OP_PositionLock : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_PositionLock ); + + struct C_OP_PositionLockContext_t + { + Vector m_vPrevPosition; + matrix3x4_t m_matPrevTransform; + }; + + int m_nControlPointNumber; + Vector m_vPrevPosition; + float m_flStartTime_min; + float m_flStartTime_max; + float m_flStartTime_exp; + float m_flEndTime_min; + float m_flEndTime_max; + float m_flEndTime_exp; + float m_flRange; + bool m_bLockRot; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_PREV_XYZ_MASK | PARTICLE_ATTRIBUTE_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK | + PARTICLE_ATTRIBUTE_PARTICLE_ID_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + return 1ULL << m_nControlPointNumber; + } + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nControlPointNumber = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nControlPointNumber ) ); + } + + size_t GetRequiredContextBytes( void ) const + { + return sizeof( C_OP_PositionLockContext_t ); + } + + virtual void InitializeContextData( CParticleCollection *pParticles, void *pContext ) const + { + C_OP_PositionLockContext_t *pCtx=reinterpret_cast( pContext ); + pCtx->m_vPrevPosition = vec3_origin; + pParticles->GetControlPointTransformAtTime( m_nControlPointNumber, pParticles->m_flCurTime, &pCtx->m_matPrevTransform ); + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_PositionLock , "Movement Lock to Control Point", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_PositionLock ) + DMXELEMENT_UNPACK_FIELD( "control_point_number", "0", int, m_nControlPointNumber ) + DMXELEMENT_UNPACK_FIELD( "start_fadeout_min", "1", float, m_flStartTime_min ) + DMXELEMENT_UNPACK_FIELD( "start_fadeout_max", "1", float, m_flStartTime_max ) + DMXELEMENT_UNPACK_FIELD( "start_fadeout_exponent", "1", float, m_flStartTime_exp ) + DMXELEMENT_UNPACK_FIELD( "end_fadeout_min", "1", float, m_flEndTime_min ) + DMXELEMENT_UNPACK_FIELD( "end_fadeout_max", "1", float, m_flEndTime_max ) + DMXELEMENT_UNPACK_FIELD( "end_fadeout_exponent", "1", float, m_flEndTime_exp ) + DMXELEMENT_UNPACK_FIELD( "distance fade range", "0", float, m_flRange ) + DMXELEMENT_UNPACK_FIELD( "lock rotation", "0", bool, m_bLockRot ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_PositionLock ) + +#ifdef OLD_NON_SSE_POSLOCK_FOR_TESTING +void C_OP_PositionLock::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + Vector vecControlPoint = pParticles->GetControlPointAtCurrentTime( m_nControlPointNumber ); + + // At initialization, set prevposition to the control point to prevent random placements/velocities + + C_OP_PositionLockContext_t *pCtx=reinterpret_cast( pContext ); + + if ( pCtx->m_vPrevPosition == Vector (0, 0, 0) ) + + { + pCtx->m_vPrevPosition = vecControlPoint; + } + + // Control point movement delta + + int nRandomOffset = pParticles->OperatorRandomSampleOffset(); + // FIXME: SSE-ize + for ( int i = 0; i < pParticles->m_nActiveParticles; ++i ) + { + Vector vecPrevCPPos = pCtx->m_vPrevPosition; + + const float *pCreationTime; + const float *pLifeDuration; + pCreationTime = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, i ); + pLifeDuration = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_LIFE_DURATION, i ); + float flLifeTime = *pLifeDuration != 0.0f ? clamp( ( pParticles->m_flCurTime - *pCreationTime ) / ( *pLifeDuration ), 0.0f, 1.0f ) : 0.0f; + if ( *pCreationTime >= ( pParticles->m_flCurTime - pParticles->m_flDt ) ) + { + pParticles->GetControlPointAtTime( m_nControlPointNumber, *pCreationTime, &vecPrevCPPos ); + } + + Vector vDelta = vecControlPoint - vecPrevCPPos; + vDelta *= flStrength; + + // clamp activity to start/end time + int nParticleId = *pParticles->GetIntAttributePtr( PARTICLE_ATTRIBUTE_PARTICLE_ID, i ); + float flStartTime = pParticles->RandomFloatExp( nParticleId + nRandomOffset + 9, m_flStartTime_min, m_flStartTime_max, m_flStartTime_exp ); + float flEndTime = pParticles->RandomFloatExp( nParticleId + nRandomOffset + 10, m_flEndTime_min, m_flEndTime_max, m_flEndTime_exp ); + + // bias attachedness by fadeout + float flLockScale = SimpleSplineRemapValClamped( flLifeTime, flStartTime, flEndTime, 1.0f, 0.0f ); + + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, i ); + float *xyz_prev = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, i ); + + Vector vecParticlePosition, vecParticlePosition_prev ; + SetVectorFromAttribute( vecParticlePosition, xyz ); + SetVectorFromAttribute( vecParticlePosition_prev, xyz_prev ); + float flDampenAmount = 1; + if ( m_flRange != 0 ) + { + Vector ofs; + ofs = (vecParticlePosition + ( vDelta * flLockScale ) ) - vecControlPoint; + float flDistance = ofs.Length(); + flDampenAmount = SimpleSplineRemapValClamped( flDistance, 0, m_flRange, 1.0f, 0.0f ); + flDampenAmount = Bias( flDampenAmount, .2 ); + } + Vector vParticleDelta = vDelta * flLockScale * flDampenAmount; + + + vecParticlePosition += vParticleDelta; + vecParticlePosition_prev += vParticleDelta; + SetVectorAttribute( xyz, vecParticlePosition ); + SetVectorAttribute( xyz_prev, vecParticlePosition_prev ); + } + + // Store off the control point position for the next delta computation + pCtx->m_vPrevPosition = vecControlPoint; + + +}; + +#else +void C_OP_PositionLock::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + Vector vecControlPoint = pParticles->GetControlPointAtCurrentTime( m_nControlPointNumber ); + + // At initialization, set prevposition to the control point to prevent random placements/velocities + + C_OP_PositionLockContext_t *pCtx=reinterpret_cast( pContext ); + + if ( pCtx->m_vPrevPosition == Vector (0, 0, 0) ) + + { + pCtx->m_vPrevPosition = vecControlPoint; + pParticles->GetControlPointTransformAtTime( m_nControlPointNumber, pParticles->m_flCurTime, &pCtx->m_matPrevTransform ); + } + + Vector vDelta; + matrix3x4_t matCurrentTransform; + matrix3x4_t matTransformLock; + + if ( m_bLockRot ) + { + pParticles->GetControlPointTransformAtTime( m_nControlPointNumber, pParticles->m_flCurTime, &matCurrentTransform ); + matrix3x4_t matPrev; + //if ( MatricesAreEqual ( matCurrentTransform, pCtx->m_matPrevTransform ) ) + // return; + MatrixInvert( pCtx->m_matPrevTransform, matPrev ); + MatrixMultiply( matCurrentTransform, matPrev, matTransformLock); + } + + int nContext = GetSIMDRandContext(); + + // Control point movement delta - not full transform + vDelta = vecControlPoint - pCtx->m_vPrevPosition; + //if ( vDelta == vec3_origin && !m_bLockRot ) + // return; + + vDelta *= flStrength; + FourVectors v4Delta; + v4Delta.DuplicateVector( vDelta ); + + FourVectors v4ControlPoint; + v4ControlPoint.DuplicateVector( vecControlPoint ); + C4VAttributeWriteIterator pXYZ( PARTICLE_ATTRIBUTE_XYZ, pParticles ); + C4VAttributeWriteIterator pPrevXYZ( PARTICLE_ATTRIBUTE_PREV_XYZ, pParticles ); + fltx4 fl4_Dt = ReplicateX4( pParticles->m_flDt ); + + int nCtr = pParticles->m_nPaddedActiveParticles; + bool bUseRange = ( m_flRange != 0.0 ); + fltx4 fl4OORange; + if ( bUseRange ) + fl4OORange = ReplicateX4( 1.0 / m_flRange ); + + fltx4 fl4BiasParm = PreCalcBiasParameter( ReplicateX4( 0.2 ) ); + if ( m_flStartTime_min >= 1.0 ) // always locked on + { + CM128AttributeIterator pCreationTime( PARTICLE_ATTRIBUTE_CREATION_TIME, pParticles ); + do + { + fltx4 fl4ParticleAge = SubSIMD( pParticles->m_fl4CurTime, *pCreationTime); + fltx4 fl4CreationFrameBias = MinSIMD( fl4ParticleAge, fl4_Dt ); + fl4CreationFrameBias = MulSIMD( DivSIMD( Four_Ones, fl4_Dt ), fl4CreationFrameBias ); + FourVectors v4ScaledDelta = v4Delta; + v4ScaledDelta *= fl4CreationFrameBias; + + fltx4 fl4LockStrength = ReplicateX4( flStrength ); + // ok, some of these particles should be moved + if ( bUseRange ) + { + FourVectors ofs = *pXYZ; + ofs += v4ScaledDelta; + ofs -= v4ControlPoint; + fltx4 fl4Dist = ofs.length(); + fl4Dist = BiasSIMD( MinSIMD( Four_Ones, MulSIMD( fl4Dist, fl4OORange ) ), fl4BiasParm ); + v4ScaledDelta *= SubSIMD( Four_Ones, fl4Dist ); + fl4LockStrength = SubSIMD( Four_Ones, MulSIMD ( fl4Dist, fl4LockStrength ) ); + } + if ( m_bLockRot ) + { + fl4LockStrength = MulSIMD( fl4LockStrength, fl4CreationFrameBias ); + FourVectors fvCurPos = *pXYZ; + FourVectors fvPrevPos = *pPrevXYZ; + fvCurPos.TransformBy( matTransformLock ); + fvPrevPos.TransformBy( matTransformLock ); + fvCurPos -= *pXYZ; + fvCurPos *= fl4LockStrength; + fvPrevPos -= *pPrevXYZ; + fvPrevPos *= fl4LockStrength; + *(pXYZ) += fvCurPos; + *(pPrevXYZ) += fvPrevPos; + } + else + { + *(pXYZ) += v4ScaledDelta; + *(pPrevXYZ) += v4ScaledDelta; + } + ++pCreationTime; + ++pXYZ; + ++pPrevXYZ; + } while ( --nCtr ); + } + else + { + CM128AttributeIterator pCreationTime( PARTICLE_ATTRIBUTE_CREATION_TIME, pParticles ); + CM128AttributeIterator pLifeDuration( PARTICLE_ATTRIBUTE_LIFE_DURATION, pParticles ); + fltx4 fl4CurTime = pParticles->m_fl4CurTime; + fltx4 fl4StartRange = ReplicateX4( m_flStartTime_max - m_flStartTime_min ); + fltx4 fl4StartBias = ReplicateX4( m_flStartTime_min ); + fltx4 fl4EndRange = ReplicateX4( m_flEndTime_max - m_flEndTime_min ); + fltx4 fl4EndBias = ReplicateX4( m_flEndTime_min ); + int nSSEStartExponent = m_flStartTime_exp * 4.0; + int nSSEEndExponent = m_flEndTime_exp * 4.0; + do + { + + fltx4 fl4LifeTime = SubSIMD( fl4CurTime, *pCreationTime ); + fltx4 fl4CreationFrameBias = MinSIMD( fl4LifeTime, fl4_Dt ); + fl4CreationFrameBias = MulSIMD( DivSIMD( Four_Ones, fl4_Dt ), fl4CreationFrameBias ); + + FourVectors v4ScaledDelta = v4Delta; + v4ScaledDelta *= fl4CreationFrameBias; + + fl4LifeTime = MaxSIMD( Four_Zeros, MinSIMD( Four_Ones, + MulSIMD( fl4LifeTime, ReciprocalEstSIMD( *pLifeDuration ) ) ) ); + fltx4 fl4StartTime = Pow_FixedPoint_Exponent_SIMD( RandSIMD( nContext ), nSSEStartExponent ); + fl4StartTime = AddSIMD( fl4StartBias, MulSIMD( fl4StartTime, fl4StartRange ) ); + + fltx4 fl4EndTime = Pow_FixedPoint_Exponent_SIMD( RandSIMD( nContext ), nSSEEndExponent ); + fl4EndTime = AddSIMD( fl4EndBias, MulSIMD( fl4EndTime, fl4EndRange ) ); + + // now, determine "lockedness" + fltx4 fl4LockScale = DivSIMD( SubSIMD( fl4LifeTime, fl4StartTime ), SubSIMD( fl4EndTime, fl4StartTime ) ); + fl4LockScale = SubSIMD( Four_Ones, MaxSIMD( Four_Zeros, MinSIMD( Four_Ones, fl4LockScale ) ) ); + if ( IsAnyNegative( CmpGtSIMD( fl4LockScale, Four_Zeros ) ) ) + { + //fl4LockScale = MulSIMD( fl4LockScale, fl4CreationFrameBias ); + v4ScaledDelta *= fl4LockScale; + fltx4 fl4LockStrength = fl4LockScale ; + // ok, some of these particles should be moved + if ( bUseRange ) + { + FourVectors ofs = *pXYZ; + ofs += v4ScaledDelta; + ofs -= v4ControlPoint; + fltx4 fl4Dist = ofs.length(); + fl4Dist = BiasSIMD( MinSIMD( Four_Ones, MulSIMD( fl4Dist, fl4OORange ) ), fl4BiasParm ); + v4ScaledDelta *= SubSIMD( Four_Ones, fl4Dist ); + fl4LockStrength = SubSIMD( Four_Ones, MulSIMD ( fl4Dist, fl4LockStrength ) ); + } + if ( m_bLockRot ) + { + fl4LockStrength = MulSIMD( fl4LockStrength, fl4CreationFrameBias ); + FourVectors fvCurPos = *pXYZ; + FourVectors fvPrevPos = *pPrevXYZ; + fvCurPos.TransformBy( matTransformLock ); + fvPrevPos.TransformBy( matTransformLock ); + fvCurPos -= *pXYZ; + fvCurPos *= fl4LockStrength; + fvPrevPos -= *pPrevXYZ; + fvPrevPos *= fl4LockStrength; + *(pXYZ) += fvCurPos; + *(pPrevXYZ) += fvPrevPos; + } + else + { + *(pXYZ) += v4ScaledDelta; + *(pPrevXYZ) += v4ScaledDelta; + } + } + ++pCreationTime; + ++pLifeDuration; + ++pXYZ; + ++pPrevXYZ; + } while ( --nCtr ); + } + // Store off the control point position for the next delta computation + pCtx->m_vPrevPosition = vecControlPoint; + pCtx->m_matPrevTransform = matCurrentTransform; + ReleaseSIMDRandContext( nContext ); +}; +#endif + + + + +//----------------------------------------------------------------------------- +// Controlpoint Light +// Determines particle color/fakes lighting using the influence of control +// points +//----------------------------------------------------------------------------- +class C_OP_ControlpointLight : public CParticleOperatorInstance +{ + float m_flScale; + LightDesc_t m_LightNode1, m_LightNode2, m_LightNode3, m_LightNode4; + int m_nControlPoint1, m_nControlPoint2, m_nControlPoint3, m_nControlPoint4; + Vector m_vecCPOffset1, m_vecCPOffset2, m_vecCPOffset3, m_vecCPOffset4; + float m_LightFiftyDist1, m_LightZeroDist1, m_LightFiftyDist2, m_LightZeroDist2, + m_LightFiftyDist3, m_LightZeroDist3, m_LightFiftyDist4, m_LightZeroDist4; + Color m_LightColor1, m_LightColor2, m_LightColor3, m_LightColor4; + bool m_bLightType1, m_bLightType2, m_bLightType3, m_bLightType4, m_bLightDynamic1, + m_bLightDynamic2, m_bLightDynamic3, m_bLightDynamic4, m_bUseNormal, m_bUseHLambert, + m_bLightActive1, m_bLightActive2, m_bLightActive3, m_bLightActive4, + m_bClampLowerRange, m_bClampUpperRange; + + DECLARE_PARTICLE_OPERATOR( C_OP_ControlpointLight ); + + uint32 GetReadInitialAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_TINT_RGB_MASK; + } + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_TINT_RGB_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + return ( 1ULL << m_nControlPoint1 ) | ( 1ULL << m_nControlPoint2 ) | + ( 1ULL << m_nControlPoint3 ) | ( 1ULL << m_nControlPoint4 ); + } + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_LightNode1.m_Color[0] = m_LightColor1[0] / 255.0f; + m_LightNode1.m_Color[1] = m_LightColor1[1] / 255.0f; + m_LightNode1.m_Color[2] = m_LightColor1[2] / 255.0f; + m_LightNode2.m_Color[0] = m_LightColor2[0] / 255.0f; + m_LightNode2.m_Color[1] = m_LightColor2[1] / 255.0f; + m_LightNode2.m_Color[2] = m_LightColor2[2] / 255.0f; + m_LightNode3.m_Color[0] = m_LightColor3[0] / 255.0f; + m_LightNode3.m_Color[1] = m_LightColor3[1] / 255.0f; + m_LightNode3.m_Color[2] = m_LightColor3[2] / 255.0f; + m_LightNode4.m_Color[0] = m_LightColor4[0] / 255.0f; + m_LightNode4.m_Color[1] = m_LightColor4[1] / 255.0f; + m_LightNode4.m_Color[2] = m_LightColor4[2] / 255.0f; + m_LightNode1.m_Range = 0; + m_LightNode2.m_Range = 0; + m_LightNode3.m_Range = 0; + m_LightNode4.m_Range = 0; + m_LightNode1.m_Falloff=5.0; + m_LightNode2.m_Falloff=5.0; + m_LightNode3.m_Falloff=5.0; + m_LightNode4.m_Falloff=5.0; + m_LightNode1.m_Attenuation0 = 0; + m_LightNode1.m_Attenuation1 = 0; + m_LightNode1.m_Attenuation2 = 1; + m_LightNode2.m_Attenuation0 = 0; + m_LightNode2.m_Attenuation1 = 0; + m_LightNode2.m_Attenuation2 = 1; + m_LightNode3.m_Attenuation0 = 0; + m_LightNode3.m_Attenuation1 = 0; + m_LightNode3.m_Attenuation2 = 1; + m_LightNode4.m_Attenuation0 = 0; + m_LightNode4.m_Attenuation1 = 0; + m_LightNode4.m_Attenuation2 = 1; + + if ( !m_bLightType1 ) + { + m_LightNode1.m_Type = MATERIAL_LIGHT_POINT; + } + else + { + m_LightNode1.m_Type = MATERIAL_LIGHT_SPOT; + } + + if ( !m_bLightType2 ) + { + m_LightNode2.m_Type = MATERIAL_LIGHT_POINT; + } + else + { + m_LightNode2.m_Type = MATERIAL_LIGHT_SPOT; + } + + if ( !m_bLightType3 ) + { + m_LightNode3.m_Type = MATERIAL_LIGHT_POINT; + } + + else + { + m_LightNode3.m_Type = MATERIAL_LIGHT_SPOT; + } + + if ( !m_bLightType4 ) + { + m_LightNode4.m_Type = MATERIAL_LIGHT_POINT; + } + else + { + m_LightNode4.m_Type = MATERIAL_LIGHT_SPOT; + } + + if ( !m_bLightDynamic1 && ( m_LightColor1 != Color( 0, 0, 0, 255 ) ) ) + { + m_bLightActive1 = true; + } + else + { + m_bLightActive1 = false; + } + if ( !m_bLightDynamic2 && ( m_LightColor2 != Color( 0, 0, 0, 255 ) ) ) + { + m_bLightActive2 = true; + } + else + { + m_bLightActive2 = false; + } + if ( !m_bLightDynamic3 && ( m_LightColor3 != Color( 0, 0, 0, 255 ) ) ) + { + m_bLightActive3 = true; + } + else + { + m_bLightActive3 = false; + } + if ( !m_bLightDynamic4 && ( m_LightColor4 != Color( 0, 0, 0, 255 ) ) ) + { + m_bLightActive4 = true; + } + else + { + m_bLightActive4 = false; + } + m_LightNode1.SetupNewStyleAttenuation ( m_LightFiftyDist1, m_LightZeroDist1 ); + m_LightNode2.SetupNewStyleAttenuation ( m_LightFiftyDist2, m_LightZeroDist2 ); + m_LightNode3.SetupNewStyleAttenuation ( m_LightFiftyDist3, m_LightZeroDist3 ); + m_LightNode4.SetupNewStyleAttenuation ( m_LightFiftyDist4, m_LightZeroDist4 ); + + } + + void Render( CParticleCollection *pParticles ) const; + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_ControlpointLight, "Color Light from Control Point", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_ControlpointLight ) + DMXELEMENT_UNPACK_FIELD( "Light 1 Control Point", "0", int, m_nControlPoint1 ) + DMXELEMENT_UNPACK_FIELD( "Light 1 Control Point Offset", "0 0 0", Vector, m_vecCPOffset1 ) + DMXELEMENT_UNPACK_FIELD( "Light 1 Type 0=Point 1=Spot", "0", bool, m_bLightType1 ) + DMXELEMENT_UNPACK_FIELD( "Light 1 Color", "0 0 0 255", Color, m_LightColor1 ) + DMXELEMENT_UNPACK_FIELD( "Light 1 Dynamic Light", "0", bool, m_bLightDynamic1 ) + DMXELEMENT_UNPACK_FIELD( "Light 1 Direction", "0 0 0", Vector, m_LightNode1.m_Direction ) + DMXELEMENT_UNPACK_FIELD( "Light 1 50% Distance", "100", float, m_LightFiftyDist1 ) + DMXELEMENT_UNPACK_FIELD( "Light 1 0% Distance", "200", float, m_LightZeroDist1 ) + DMXELEMENT_UNPACK_FIELD( "Light 1 Spot Inner Cone", "30.0", float, m_LightNode1.m_Theta ) + DMXELEMENT_UNPACK_FIELD( "Light 1 Spot Outer Cone", "45.0", float, m_LightNode1.m_Phi ) + DMXELEMENT_UNPACK_FIELD( "Light 2 Control Point", "0", int, m_nControlPoint2 ) + DMXELEMENT_UNPACK_FIELD( "Light 2 Control Point Offset", "0 0 0", Vector, m_vecCPOffset2 ) + DMXELEMENT_UNPACK_FIELD( "Light 2 Type 0=Point 1=Spot", "0", bool, m_bLightType2 ) + DMXELEMENT_UNPACK_FIELD( "Light 2 Color", "0 0 0 255", Color, m_LightColor2 ) + DMXELEMENT_UNPACK_FIELD( "Light 2 Dynamic Light", "0", bool, m_bLightDynamic2 ) + DMXELEMENT_UNPACK_FIELD( "Light 2 Direction", "0 0 0", Vector, m_LightNode2.m_Direction ) + DMXELEMENT_UNPACK_FIELD( "Light 2 50% Distance", "100", float, m_LightFiftyDist2 ) + DMXELEMENT_UNPACK_FIELD( "Light 2 0% Distance", "200", float, m_LightZeroDist2 ) + DMXELEMENT_UNPACK_FIELD( "Light 2 Spot Inner Cone", "30.0", float, m_LightNode2.m_Theta ) + DMXELEMENT_UNPACK_FIELD( "Light 2 Spot Outer Cone", "45.0", float, m_LightNode2.m_Phi ) + DMXELEMENT_UNPACK_FIELD( "Light 3 Control Point", "0", int, m_nControlPoint3 ) + DMXELEMENT_UNPACK_FIELD( "Light 3 Control Point Offset", "0 0 0", Vector, m_vecCPOffset3 ) + DMXELEMENT_UNPACK_FIELD( "Light 3 Type 0=Point 1=Spot", "0", bool, m_bLightType3 ) + DMXELEMENT_UNPACK_FIELD( "Light 3 Color", "0 0 0 255", Color, m_LightColor3 ) + DMXELEMENT_UNPACK_FIELD( "Light 3 Dynamic Light", "0", bool, m_bLightDynamic3 ) + DMXELEMENT_UNPACK_FIELD( "Light 3 Direction", "0 0 0", Vector, m_LightNode3.m_Direction ) + DMXELEMENT_UNPACK_FIELD( "Light 3 50% Distance", "100", float, m_LightFiftyDist3 ) + DMXELEMENT_UNPACK_FIELD( "Light 3 0% Distance", "200", float, m_LightZeroDist3 ) + DMXELEMENT_UNPACK_FIELD( "Light 3 Spot Inner Cone", "30.0", float, m_LightNode3.m_Theta ) + DMXELEMENT_UNPACK_FIELD( "Light 3 Spot Outer Cone", "45.0", float, m_LightNode3.m_Phi ) + DMXELEMENT_UNPACK_FIELD( "Light 4 Control Point", "0", int, m_nControlPoint4 ) + DMXELEMENT_UNPACK_FIELD( "Light 4 Control Point Offset", "0 0 0", Vector, m_vecCPOffset4 ) + DMXELEMENT_UNPACK_FIELD( "Light 4 Type 0=Point 1=Spot", "0", bool, m_bLightType4 ) + DMXELEMENT_UNPACK_FIELD( "Light 4 Color", "0 0 0 255", Color, m_LightColor4 ) + DMXELEMENT_UNPACK_FIELD( "Light 4 Dynamic Light", "0", bool, m_bLightDynamic4 ) + DMXELEMENT_UNPACK_FIELD( "Light 4 Direction", "0 0 0", Vector, m_LightNode4.m_Direction ) + DMXELEMENT_UNPACK_FIELD( "Light 4 50% Distance", "100", float, m_LightFiftyDist4 ) + DMXELEMENT_UNPACK_FIELD( "Light 4 0% Distance", "200", float, m_LightZeroDist4 ) + DMXELEMENT_UNPACK_FIELD( "Light 4 Spot Inner Cone", "30.0", float, m_LightNode4.m_Theta ) + DMXELEMENT_UNPACK_FIELD( "Light 4 Spot Outer Cone", "45.0", float, m_LightNode4.m_Phi ) + DMXELEMENT_UNPACK_FIELD( "Initial Color Bias", "0.0", float, m_flScale ) + DMXELEMENT_UNPACK_FIELD( "Clamp Minimum Light Value to Initial Color", "0", bool, m_bClampLowerRange ) + DMXELEMENT_UNPACK_FIELD( "Clamp Maximum Light Value to Initial Color", "0", bool, m_bClampUpperRange ) + DMXELEMENT_UNPACK_FIELD( "Compute Normals From Control Points", "0", bool, m_bUseNormal ) + DMXELEMENT_UNPACK_FIELD( "Half-Lambert Normals", "1", bool, m_bUseHLambert ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_ControlpointLight ) + +void C_OP_ControlpointLight::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + //Set up location of each light - this needs to be done every time as the CP's can move + Vector vecLocation1, vecLocation2, vecLocation3, vecLocation4; + vecLocation1 = pParticles->GetControlPointAtCurrentTime( m_nControlPoint1 ); + vecLocation2 = pParticles->GetControlPointAtCurrentTime( m_nControlPoint2 ); + vecLocation3 = pParticles->GetControlPointAtCurrentTime( m_nControlPoint3 ); + vecLocation4 = pParticles->GetControlPointAtCurrentTime( m_nControlPoint4 ); + + + LightDesc_t LightNode1 = m_LightNode1; + LightDesc_t LightNode2 = m_LightNode2; + LightDesc_t LightNode3 = m_LightNode3; + LightDesc_t LightNode4 = m_LightNode3; + + // Apply any offsets + LightNode1.m_Position = vecLocation1 + m_vecCPOffset1; + LightNode2.m_Position = vecLocation2 + m_vecCPOffset2; + LightNode3.m_Position = vecLocation3 + m_vecCPOffset3; + LightNode4.m_Position = vecLocation4 + m_vecCPOffset4; + + + C4VAttributeIterator pInitialColor( PARTICLE_ATTRIBUTE_TINT_RGB, pParticles ); + C4VAttributeWriteIterator pColor( PARTICLE_ATTRIBUTE_TINT_RGB, pParticles ); + C4VAttributeIterator pXYZ( PARTICLE_ATTRIBUTE_XYZ, pParticles ); + + + // Set up lighting conditions and attenuation + if ( m_bLightDynamic1 ) + { + // Get the color and luminosity at this position + Color lc; + g_pParticleSystemMgr->Query()->GetLightingAtPoint( LightNode1.m_Position, lc ); + LightNode1.m_Color[0] = lc[0] / 255.0f; + LightNode1.m_Color[1] = lc[1] / 255.0f; + LightNode1.m_Color[2] = lc[2] / 255.0f; + } + if ( m_bLightDynamic2 ) + { + // Get the color and luminosity at this position + Color lc; + g_pParticleSystemMgr->Query()->GetLightingAtPoint( LightNode2.m_Position, lc ); + LightNode2.m_Color[0] = lc[0] / 255.0f; + LightNode2.m_Color[1] = lc[1] / 255.0f; + LightNode2.m_Color[2] = lc[2] / 255.0f; + } + if ( m_bLightDynamic3 ) + { + // Get the color and luminosity at this position + Color lc; + g_pParticleSystemMgr->Query()->GetLightingAtPoint( LightNode3.m_Position, lc ); + LightNode3.m_Color[0] = lc[0] / 255.0f; + LightNode3.m_Color[1] = lc[1] / 255.0f; + LightNode3.m_Color[2] = lc[2] / 255.0f; + } + if ( m_bLightDynamic4 ) + { + // Get the color and luminosity at this position + Color lc; + g_pParticleSystemMgr->Query()->GetLightingAtPoint( LightNode4.m_Position, lc ); + LightNode4.m_Color[0] = lc[0] / 255.0f; + LightNode4.m_Color[1] = lc[1] / 255.0f; + LightNode4.m_Color[2] = lc[2] / 255.0f; + } + LightNode1.RecalculateDerivedValues(); + LightNode2.RecalculateDerivedValues(); + LightNode3.RecalculateDerivedValues(); + LightNode4.RecalculateDerivedValues(); + + FourVectors vScale; + vScale.DuplicateVector( Vector(m_flScale, m_flScale, m_flScale) ); + + if ( m_bUseNormal ) + { + FourVectors vCPPosition1, vCPPosition2, vCPPosition3, vCPPosition4; + //vCPPosition1.DuplicateVector( LightNode1.m_Position ); + vCPPosition1.DuplicateVector( vecLocation1 ); + vCPPosition2.DuplicateVector( vecLocation2 ); + vCPPosition3.DuplicateVector( vecLocation3 ); + vCPPosition4.DuplicateVector( vecLocation4 ); + + int nCtr = pParticles->m_nPaddedActiveParticles; + do + { + FourVectors vLighting = vScale; + vLighting *= *pInitialColor; + FourVectors vNormal = *pXYZ; + vNormal -= vCPPosition1; + vNormal.VectorNormalizeFast(); + LightNode1.ComputeLightAtPoints( *pXYZ, vNormal, vLighting, m_bUseHLambert ); + vNormal = *pXYZ; + vNormal -= vCPPosition2; + vNormal.VectorNormalizeFast(); + LightNode2.ComputeLightAtPoints( *pXYZ, vNormal, vLighting, m_bUseHLambert ); + vNormal = *pXYZ; + vNormal -= vCPPosition3; + vNormal.VectorNormalizeFast(); + LightNode3.ComputeLightAtPoints( *pXYZ, vNormal, vLighting, m_bUseHLambert ); + vNormal = *pXYZ; + vNormal -= vCPPosition4; + vNormal.VectorNormalizeFast(); + LightNode4.ComputeLightAtPoints( *pXYZ, vNormal, vLighting, m_bUseHLambert ); + + if ( m_bClampLowerRange ) + { + FourVectors vInitialClamp = *pInitialColor; + vLighting.x = MaxSIMD( vLighting.x, vInitialClamp.x ); + vLighting.y = MaxSIMD( vLighting.y, vInitialClamp.y ); + vLighting.z = MaxSIMD( vLighting.z, vInitialClamp.z ); + } + else + { + vLighting.x = MaxSIMD( vLighting.x, Four_Zeros ); + vLighting.y = MaxSIMD( vLighting.y, Four_Zeros ); + vLighting.z = MaxSIMD( vLighting.z, Four_Zeros ); + } + if ( m_bClampUpperRange ) + { + FourVectors vInitialClamp = *pInitialColor; + vLighting.x = MinSIMD( vLighting.x, vInitialClamp.x ); + vLighting.y = MinSIMD( vLighting.y, vInitialClamp.y ); + vLighting.z = MinSIMD( vLighting.z, vInitialClamp.z ); + } + else + { + vLighting.x = MinSIMD( vLighting.x, Four_Ones ); + vLighting.y = MinSIMD( vLighting.y, Four_Ones ); + vLighting.z = MinSIMD( vLighting.z, Four_Ones ); + } + + *pColor = vLighting; + + ++pColor; + ++pXYZ; + ++pInitialColor; + } while (--nCtr); + } + else + { + int nCtr = pParticles->m_nPaddedActiveParticles; + do + { + FourVectors vLighting = vScale; + vLighting *= *pInitialColor; + + LightNode1.ComputeNonincidenceLightAtPoints( *pXYZ, vLighting ); + LightNode2.ComputeNonincidenceLightAtPoints( *pXYZ, vLighting ); + LightNode3.ComputeNonincidenceLightAtPoints( *pXYZ, vLighting ); + LightNode4.ComputeNonincidenceLightAtPoints( *pXYZ, vLighting ); + + + if ( m_bClampLowerRange ) + { + FourVectors vInitialClamp = *pInitialColor; + vLighting.x = MaxSIMD( vLighting.x, vInitialClamp.x ); + vLighting.y = MaxSIMD( vLighting.y, vInitialClamp.y ); + vLighting.z = MaxSIMD( vLighting.z, vInitialClamp.z ); + } + else + { + vLighting.x = MaxSIMD( vLighting.x, Four_Zeros ); + vLighting.y = MaxSIMD( vLighting.y, Four_Zeros ); + vLighting.z = MaxSIMD( vLighting.z, Four_Zeros ); + } + if ( m_bClampUpperRange ) + { + FourVectors vInitialClamp = *pInitialColor; + vLighting.x = MinSIMD( vLighting.x, vInitialClamp.x ); + vLighting.y = MinSIMD( vLighting.y, vInitialClamp.y ); + vLighting.z = MinSIMD( vLighting.z, vInitialClamp.z ); + } + else + { + vLighting.x = MinSIMD( vLighting.x, Four_Ones ); + vLighting.y = MinSIMD( vLighting.y, Four_Ones ); + vLighting.z = MinSIMD( vLighting.z, Four_Ones ); + } + + + *pColor = vLighting; + + ++pColor; + ++pXYZ; + ++pInitialColor; + } while (--nCtr); + } +}; + + +//----------------------------------------------------------------------------- +// Render visualization +//----------------------------------------------------------------------------- +void C_OP_ControlpointLight::Render( CParticleCollection *pParticles ) const +{ + Vector vecOrigin1 = pParticles->GetControlPointAtCurrentTime( m_nControlPoint1 ); + vecOrigin1 += m_vecCPOffset1; + Vector vecOrigin2 = pParticles->GetControlPointAtCurrentTime( m_nControlPoint2 ); + vecOrigin2 += m_vecCPOffset2; + Vector vecOrigin3 = pParticles->GetControlPointAtCurrentTime( m_nControlPoint3 ); + vecOrigin3 += m_vecCPOffset3; + Vector vecOrigin4 = pParticles->GetControlPointAtCurrentTime( m_nControlPoint4 ); + vecOrigin4 += m_vecCPOffset4; + + Color LightColor1Outer; + LightColor1Outer[0] = m_LightColor1[0] / 2.0f; + LightColor1Outer[1] = m_LightColor1[1] / 2.0f; + LightColor1Outer[2] = m_LightColor1[2] / 2.0f; + LightColor1Outer[3] = 255; + Color LightColor2Outer; + LightColor2Outer[0] = m_LightColor2[0] / 2.0f; + LightColor2Outer[1] = m_LightColor2[1] / 2.0f; + LightColor2Outer[2] = m_LightColor2[2] / 2.0f; + LightColor2Outer[3] = 255; + Color LightColor3Outer; + LightColor3Outer[0] = m_LightColor3[0] / 2.0f; + LightColor3Outer[1] = m_LightColor3[1] / 2.0f; + LightColor3Outer[2] = m_LightColor3[2] / 2.0f; + LightColor3Outer[3] = 255; + Color LightColor4Outer; + LightColor4Outer[0] = m_LightColor4[0] / 2.0f; + LightColor4Outer[1] = m_LightColor4[1] / 2.0f; + LightColor4Outer[2] = m_LightColor4[2] / 2.0f; + LightColor4Outer[3] = 255; + if ( m_bLightActive1 ) + { + RenderWireframeSphere( vecOrigin1, m_LightFiftyDist1, 16, 8, m_LightColor1, false ); + RenderWireframeSphere( vecOrigin1, m_LightZeroDist1, 16, 8, LightColor1Outer, false ); + } + if ( m_bLightActive2 ) + { + RenderWireframeSphere( vecOrigin2, m_LightFiftyDist2, 16, 8, m_LightColor2, false ); + RenderWireframeSphere( vecOrigin2, m_LightZeroDist2, 16, 8, LightColor2Outer, false ); + } + if ( m_bLightActive3 ) + { + RenderWireframeSphere( vecOrigin3, m_LightFiftyDist3, 16, 8, m_LightColor3, false ); + RenderWireframeSphere( vecOrigin3, m_LightZeroDist3, 16, 8, LightColor3Outer, false ); + } + if ( m_bLightActive4 ) + { + RenderWireframeSphere( vecOrigin4, m_LightFiftyDist4, 16, 8, m_LightColor4, false ); + RenderWireframeSphere( vecOrigin4, m_LightZeroDist4, 16, 8, LightColor4Outer, false ); + } + +} + + + +// set child controlpoints - copy the positions of our particles to the control points of a child +class C_OP_SetChildControlPoints : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_SetChildControlPoints ); + + int m_nChildGroupID; + int m_nFirstControlPoint; + int m_nNumControlPoints; + int m_nFirstSourcePoint; + + uint32 GetWrittenAttributes( void ) const + { + return 0; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_SetChildControlPoints, "Set child control points from particle positions", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_SetChildControlPoints ) + DMXELEMENT_UNPACK_FIELD( "Group ID to affect", "0", int, m_nChildGroupID ) + DMXELEMENT_UNPACK_FIELD( "First control point to set", "0", int, m_nFirstControlPoint ) + DMXELEMENT_UNPACK_FIELD( "# of control points to set", "1", int, m_nNumControlPoints ) + DMXELEMENT_UNPACK_FIELD( "first particle to copy", "0", int, m_nFirstSourcePoint ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_SetChildControlPoints ) + + +void C_OP_SetChildControlPoints::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + int nFirst=max(0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nFirstControlPoint ) ); + int nToSet=min( pParticles->m_nActiveParticles-m_nFirstSourcePoint, m_nNumControlPoints ); + nToSet=min( nToSet, MAX_PARTICLE_CONTROL_POINTS-nFirst ); + if ( nToSet ) + { + for( CParticleCollection *pChild = pParticles->m_Children.m_pHead; pChild; pChild = pChild->m_pNext ) + { + if ( pChild->GetGroupID() == m_nChildGroupID ) + { + for( int p=0; p < nToSet; p++ ) + { + const float *pXYZ = pParticles->GetFloatAttributePtr( + PARTICLE_ATTRIBUTE_XYZ, p + m_nFirstSourcePoint ); + Vector cPnt( pXYZ[0], pXYZ[4], pXYZ[8] ); + pChild->SetControlPoint( p+nFirst, cPnt ); + } + } + } + } +} + + + + +//----------------------------------------------------------------------------- +// Set Control Point Positions +//----------------------------------------------------------------------------- +class C_OP_SetControlPointPositions : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_SetControlPointPositions ); + + bool m_bUseWorldLocation; + int m_nCP1, m_nCP1Parent; + int m_nCP2, m_nCP2Parent; + int m_nCP3, m_nCP3Parent; + int m_nCP4, m_nCP4Parent; + Vector m_vecCP1Pos, m_vecCP2Pos, m_vecCP3Pos, m_vecCP4Pos; + int m_nHeadLocation; + + uint32 GetWrittenAttributes( void ) const + { + return 0; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + bool ShouldRunBeforeEmitters( void ) const + { + return true; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_SetControlPointPositions, "Set Control Point Positions", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_SetControlPointPositions ) + DMXELEMENT_UNPACK_FIELD( "First Control Point Number", "1", int, m_nCP1 ) + DMXELEMENT_UNPACK_FIELD( "First Control Point Parent", "0", int, m_nCP1Parent ) + DMXELEMENT_UNPACK_FIELD( "First Control Point Location", "128 0 0", Vector, m_vecCP1Pos ) + DMXELEMENT_UNPACK_FIELD( "Second Control Point Number", "2", int, m_nCP2 ) + DMXELEMENT_UNPACK_FIELD( "Second Control Point Parent", "0", int, m_nCP2Parent ) + DMXELEMENT_UNPACK_FIELD( "Second Control Point Location", "0 128 0", Vector, m_vecCP2Pos ) + DMXELEMENT_UNPACK_FIELD( "Third Control Point Number", "3", int, m_nCP3 ) + DMXELEMENT_UNPACK_FIELD( "Third Control Point Parent", "0", int, m_nCP3Parent ) + DMXELEMENT_UNPACK_FIELD( "Third Control Point Location", "-128 0 0", Vector, m_vecCP3Pos ) + DMXELEMENT_UNPACK_FIELD( "Fourth Control Point Number", "4", int, m_nCP4 ) + DMXELEMENT_UNPACK_FIELD( "Fourth Control Point Parent", "0", int, m_nCP4Parent ) + DMXELEMENT_UNPACK_FIELD( "Fourth Control Point Location", "0 -128 0", Vector, m_vecCP4Pos ) + DMXELEMENT_UNPACK_FIELD( "Set positions in world space", "0", bool, m_bUseWorldLocation ) + DMXELEMENT_UNPACK_FIELD( "Control Point to offset positions from", "0", int, m_nHeadLocation ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_SetControlPointPositions ) + +void C_OP_SetControlPointPositions::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + if ( !m_bUseWorldLocation ) + { + Vector vecControlPoint = pParticles->GetControlPointAtCurrentTime( m_nHeadLocation ); + matrix3x4_t mat; + pParticles->GetControlPointTransformAtTime( m_nHeadLocation, pParticles->m_flCurTime, &mat ); + Vector vecTransformLocal = vec3_origin; + + VectorTransform( m_vecCP1Pos, mat, vecTransformLocal ); + pParticles->SetControlPoint( m_nCP1, vecTransformLocal ); + pParticles->SetControlPointParent( m_nCP1, m_nCP1Parent ); + + VectorTransform( m_vecCP2Pos, mat, vecTransformLocal ); + pParticles->SetControlPoint( m_nCP2, vecTransformLocal ); + pParticles->SetControlPointParent( m_nCP2, m_nCP2Parent ); + + VectorTransform( m_vecCP3Pos, mat, vecTransformLocal ); + pParticles->SetControlPoint( m_nCP3, vecTransformLocal ); + pParticles->SetControlPointParent( m_nCP3, m_nCP3Parent ); + + VectorTransform( m_vecCP4Pos, mat, vecTransformLocal ); + pParticles->SetControlPoint( m_nCP4, vecTransformLocal ); + pParticles->SetControlPointParent( m_nCP4, m_nCP4Parent ); + } + else + { + pParticles->SetControlPoint( m_nCP1, m_vecCP1Pos ); + pParticles->SetControlPointParent( m_nCP1, m_nCP1Parent ); + pParticles->SetControlPoint( m_nCP2, m_vecCP2Pos ); + pParticles->SetControlPointParent( m_nCP2, m_nCP2Parent ); + pParticles->SetControlPoint( m_nCP3, m_vecCP3Pos ); + pParticles->SetControlPointParent( m_nCP3, m_nCP3Parent ); + pParticles->SetControlPoint( m_nCP4, m_vecCP4Pos ); + pParticles->SetControlPointParent( m_nCP4, m_nCP4Parent ); + } +} + + +//----------------------------------------------------------------------------- +// Dampen Movement Relative to Control Point +// The closer a particle is the the assigned control point, the less +// it can move +//----------------------------------------------------------------------------- +class C_OP_DampenToCP : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_DampenToCP ); + + int m_nControlPointNumber; + float m_flRange, m_flScale; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK | + PARTICLE_ATTRIBUTE_PARTICLE_ID_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + return ( 1ULL << m_nControlPointNumber ); + } + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nControlPointNumber = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nControlPointNumber ) ); + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_DampenToCP , "Movement Dampen Relative to Control Point", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_DampenToCP ) +DMXELEMENT_UNPACK_FIELD( "control_point_number", "0", int, m_nControlPointNumber ) +DMXELEMENT_UNPACK_FIELD( "falloff range", "100", float, m_flRange ) +DMXELEMENT_UNPACK_FIELD( "dampen scale", "1", float, m_flScale ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_DampenToCP ) + +void C_OP_DampenToCP::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + if ( m_flRange <= 0.0f ) + return; + + Vector vecControlPoint = pParticles->GetControlPointAtCurrentTime( m_nControlPointNumber ); + + // FIXME: SSE-ize + for ( int i = 0; i < pParticles->m_nActiveParticles; ++i ) + { + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, i ); + float *xyz_prev = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, i ); + + Vector vecParticlePosition, vecParticlePosition_prev, vParticleDelta ; + + SetVectorFromAttribute( vecParticlePosition, xyz ); + SetVectorFromAttribute( vecParticlePosition_prev, xyz_prev ); + Vector ofs; + ofs = vecParticlePosition - vecControlPoint; + float flDistance = ofs.Length(); + float flDampenAmount; + if ( flDistance > m_flRange ) + { + continue; + } + else + { + flDampenAmount = flDistance / m_flRange; + flDampenAmount = pow( flDampenAmount, m_flScale); + } + + vParticleDelta = vecParticlePosition - vecParticlePosition_prev; + Vector vParticleDampened = vParticleDelta * flDampenAmount; + vecParticlePosition = vecParticlePosition_prev + vParticleDampened; + Vector vecParticlePositionOrg; + SetVectorFromAttribute( vecParticlePositionOrg, xyz ); + VectorLerp (vecParticlePositionOrg, vecParticlePosition, flStrength, vecParticlePosition ); + SetVectorAttribute( xyz, vecParticlePosition ); + } +}; + + + + +//----------------------------------------------------------------------------- +// Distance Between CP Operator +//----------------------------------------------------------------------------- +class C_OP_DistanceBetweenCPs : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_DistanceBetweenCPs ); + + uint32 GetWrittenAttributes( void ) const + { + return 1 << m_nFieldOutput; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + uint32 GetReadInitialAttributes( void ) const + { + return 1 << m_nFieldOutput; + } + + virtual uint64 GetReadControlPointMask() const + { + return ( 1ULL << m_nStartCP ) | ( 1ULL << m_nEndCP ); + } + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nCollisionGroupNumber = g_pParticleSystemMgr->Query()->GetCollisionGroupFromName( m_CollisionGroupName ); + m_nStartCP = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nStartCP ) ); + m_nEndCP = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nEndCP ) ); + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + int m_nFieldOutput; + float m_flInputMin; + float m_flInputMax; + float m_flOutputMin; + float m_flOutputMax; + int m_nStartCP; + int m_nEndCP; + bool m_bLOS; + char m_CollisionGroupName[128]; + int m_nCollisionGroupNumber; + float m_flMaxTraceLength; + float m_flLOSScale; + bool m_bScaleInitialRange; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_DistanceBetweenCPs, "Remap Distance Between Two Control Points to Scalar", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_DistanceBetweenCPs ) +DMXELEMENT_UNPACK_FIELD( "distance minimum","0", float, m_flInputMin ) +DMXELEMENT_UNPACK_FIELD( "distance maximum","128", float, m_flInputMax ) +DMXELEMENT_UNPACK_FIELD_USERDATA( "output field", "3", int, m_nFieldOutput, "intchoice particlefield_scalar" ) +DMXELEMENT_UNPACK_FIELD( "output minimum","0", float, m_flOutputMin ) +DMXELEMENT_UNPACK_FIELD( "output maximum","1", float, m_flOutputMax ) +DMXELEMENT_UNPACK_FIELD( "starting control point","0", int, m_nStartCP ) +DMXELEMENT_UNPACK_FIELD( "ending control point","1", int, m_nEndCP ) +DMXELEMENT_UNPACK_FIELD( "ensure line of sight","0", bool, m_bLOS ) +DMXELEMENT_UNPACK_FIELD_STRING( "LOS collision group", "NONE", m_CollisionGroupName ) +DMXELEMENT_UNPACK_FIELD( "Maximum Trace Length", "-1", float, m_flMaxTraceLength ) +DMXELEMENT_UNPACK_FIELD( "LOS Failure Scalar", "0", float, m_flLOSScale ) +DMXELEMENT_UNPACK_FIELD( "output is scalar of initial random range","0", bool, m_bScaleInitialRange ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_DistanceBetweenCPs ) + +void C_OP_DistanceBetweenCPs::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + // clamp the result to 0 and 1 if it's alpha + float flMin=m_flOutputMin; + float flMax=m_flOutputMax; + if ( ATTRIBUTES_WHICH_ARE_0_TO_1 & ( 1 << m_nFieldOutput ) ) + { + flMin = clamp(m_flOutputMin, 0.0f, 1.0f ); + flMax = clamp(m_flOutputMax, 0.0f, 1.0f ); + } + Vector vecControlPoint1 = pParticles->GetControlPointAtCurrentTime( m_nStartCP ); + Vector vecControlPoint2 = pParticles->GetControlPointAtCurrentTime( m_nEndCP ); + Vector vecDelta = vecControlPoint1 - vecControlPoint2; + float flDistance = vecDelta.Length(); + + + if ( m_bLOS ) + { + Vector vecEndPoint = vecControlPoint2; + if ( m_flMaxTraceLength != -1.0f && m_flMaxTraceLength < flDistance ) + { + VectorNormalize(vecEndPoint); + vecEndPoint *= m_flMaxTraceLength; + vecEndPoint += vecControlPoint1; + } + CBaseTrace tr; + g_pParticleSystemMgr->Query()->TraceLine( vecControlPoint1, vecEndPoint, MASK_OPAQUE_AND_NPCS, NULL, m_nCollisionGroupNumber, &tr ); + if (tr.fraction != 1.0f) + { + flDistance *= tr.fraction * m_flLOSScale; + } + + } + + // FIXME: SSE-ize + for ( int i = 0; i < pParticles->m_nActiveParticles; ++i ) + { + float flOutput = RemapValClamped( flDistance, m_flInputMin, m_flInputMax, flMin, flMax ); + if ( m_bScaleInitialRange ) + { + const float *pInitialOutput = pParticles->GetInitialFloatAttributePtr( m_nFieldOutput, i ); + flOutput = *pInitialOutput * flOutput; + } + float *pOutput = pParticles->GetFloatAttributePtrForWrite( m_nFieldOutput, i ); + + *pOutput = Lerp (flStrength, *pOutput, flOutput); + } +} + + + +//----------------------------------------------------------------------------- +// Distance to CP Operator +//----------------------------------------------------------------------------- +class C_OP_DistanceToCP : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_DistanceToCP ); + + uint32 GetWrittenAttributes( void ) const + { + return 1 << m_nFieldOutput; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK; + } + + uint32 GetReadInitialAttributes( void ) const + { + return 1 << m_nFieldOutput; + } + + virtual uint64 GetReadControlPointMask() const + { + return ( 1ULL << m_nStartCP ) | ( 1ULL << m_nEndCP ); + } + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nCollisionGroupNumber = g_pParticleSystemMgr->Query()->GetCollisionGroupFromName( m_CollisionGroupName ); + m_nStartCP = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nStartCP ) ); + m_nEndCP = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nEndCP ) ); + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + int m_nFieldOutput; + float m_flInputMin; + float m_flInputMax; + float m_flOutputMin; + float m_flOutputMax; + int m_nStartCP; + int m_nEndCP; + bool m_bLOS; + char m_CollisionGroupName[128]; + int m_nCollisionGroupNumber; + float m_flMaxTraceLength; + float m_flLOSScale; + bool m_bScaleInitialRange; + bool m_bActiveRange; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_DistanceToCP, "Remap Distance to Control Point to Scalar", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_DistanceToCP ) +DMXELEMENT_UNPACK_FIELD( "distance minimum","0", float, m_flInputMin ) +DMXELEMENT_UNPACK_FIELD( "distance maximum","128", float, m_flInputMax ) +DMXELEMENT_UNPACK_FIELD_USERDATA( "output field", "3", int, m_nFieldOutput, "intchoice particlefield_scalar" ) +DMXELEMENT_UNPACK_FIELD( "output minimum","0", float, m_flOutputMin ) +DMXELEMENT_UNPACK_FIELD( "output maximum","1", float, m_flOutputMax ) +DMXELEMENT_UNPACK_FIELD( "control point","0", int, m_nStartCP ) +DMXELEMENT_UNPACK_FIELD( "ensure line of sight","0", bool, m_bLOS ) +DMXELEMENT_UNPACK_FIELD_STRING( "LOS collision group", "NONE", m_CollisionGroupName ) +DMXELEMENT_UNPACK_FIELD( "Maximum Trace Length", "-1", float, m_flMaxTraceLength ) +DMXELEMENT_UNPACK_FIELD( "LOS Failure Scalar", "0", float, m_flLOSScale ) +DMXELEMENT_UNPACK_FIELD( "output is scalar of initial random range","0", bool, m_bScaleInitialRange ) +DMXELEMENT_UNPACK_FIELD( "only active within specified distance","0", bool, m_bActiveRange ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_DistanceToCP ) + +void C_OP_DistanceToCP::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + // clamp the result to 0 and 1 if it's alpha + float flMin=m_flOutputMin; + float flMax=m_flOutputMax; + if ( ATTRIBUTES_WHICH_ARE_0_TO_1 & ( 1 << m_nFieldOutput ) ) + { + flMin = clamp(m_flOutputMin, 0.0f, 1.0f ); + flMax = clamp(m_flOutputMax, 0.0f, 1.0f ); + } + Vector vecControlPoint1 = pParticles->GetControlPointAtCurrentTime( m_nStartCP ); + + // FIXME: SSE-ize + for ( int i = 0; i < pParticles->m_nActiveParticles; ++i ) + { + Vector vecPosition2; + const float *pXYZ = pParticles->GetFloatAttributePtr(PARTICLE_ATTRIBUTE_XYZ, i ); + vecPosition2 = Vector(pXYZ[0], pXYZ[4], pXYZ[8]); + Vector vecDelta = vecControlPoint1 - vecPosition2; + float flDistance = vecDelta.Length(); + if ( m_bActiveRange && ( flDistance < m_flInputMin || flDistance > m_flInputMax ) ) + { + continue; + } + if ( m_bLOS ) + { + Vector vecEndPoint = vecPosition2; + if ( m_flMaxTraceLength != -1.0f && m_flMaxTraceLength < flDistance ) + { + VectorNormalize(vecEndPoint); + vecEndPoint *= m_flMaxTraceLength; + vecEndPoint += vecControlPoint1; + } + CBaseTrace tr; + g_pParticleSystemMgr->Query()->TraceLine( vecControlPoint1, vecEndPoint, MASK_OPAQUE_AND_NPCS, NULL , m_nCollisionGroupNumber, &tr ); + if (tr.fraction != 1.0f) + { + flDistance *= tr.fraction * m_flLOSScale; + } + + } + + float flOutput = RemapValClamped( flDistance, m_flInputMin, m_flInputMax, flMin, flMax ); + if ( m_bScaleInitialRange ) + { + const float *pInitialOutput = pParticles->GetInitialFloatAttributePtr( m_nFieldOutput, i ); + flOutput = *pInitialOutput * flOutput; + } + float *pOutput = pParticles->GetFloatAttributePtrForWrite( m_nFieldOutput, i ); + + *pOutput = Lerp (flStrength, *pOutput, flOutput); + //float *pOutput = pParticles->GetFloatAttributePtrForWrite( m_nFieldOutput, i ); + //float flOutput = RemapValClamped( flDistance, m_flInputMin, m_flInputMax, flMin, flMax ); + //*pOutput = Lerp (flStrength, *pOutput, flOutput); + } +} + +//----------------------------------------------------------------------------- +// Assign CP to Player +//----------------------------------------------------------------------------- +class C_OP_SetControlPointToPlayer : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_SetControlPointToPlayer ); + + int m_nCP1; + + Vector m_vecCP1Pos; + + uint32 GetWrittenAttributes( void ) const + { + return 0; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nCP1 = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nCP1 ) ); + } + + bool ShouldRunBeforeEmitters( void ) const + { + return true; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_SetControlPointToPlayer, "Set Control Point To Player", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_SetControlPointToPlayer ) +DMXELEMENT_UNPACK_FIELD( "Control Point Number", "1", int, m_nCP1 ) +DMXELEMENT_UNPACK_FIELD( "Control Point Offset", "0 0 0", Vector, m_vecCP1Pos ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_SetControlPointToPlayer ) + +void C_OP_SetControlPointToPlayer::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + Vector vecClientPos =g_pParticleSystemMgr->Query()->GetLocalPlayerPos(); + pParticles->SetControlPoint( m_nCP1, m_vecCP1Pos + vecClientPos ); + Vector vecForward; + Vector vecRight; + Vector vecUp; + g_pParticleSystemMgr->Query()->GetLocalPlayerEyeVectors( &vecForward, &vecRight, &vecUp); + pParticles->SetControlPointOrientation( m_nCP1, vecForward, vecRight, vecUp ); +} + + + + + +//------------------------- +// Emits particles from particles +//NOT FINISHED +//------------------------- +class C_OP_PerParticleEmitter : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_PerParticleEmitter ); + + struct C_OP_PerParticleEmitterContext_t + { + float m_flTotalActualParticlesSoFar; + int m_nTotalEmittedSoFar; + bool m_bStoppedEmission; + }; + + int m_nChildGroupID; + bool m_bInheritVelocity; + float m_flEmitRate; + float m_flVelocityScale; + float m_flStartTime; + float m_flEmissionDuration; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_CREATION_TIME; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ | PARTICLE_ATTRIBUTE_PREV_XYZ; + } + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + if ( m_flEmitRate < 0.0f ) + { + m_flEmitRate = 0.0f; + } + if ( m_flEmissionDuration < 0.0f ) + { + m_flEmissionDuration = 0.0f; + } + } + + inline bool IsInfinitelyEmitting() const + { + return ( m_flEmissionDuration == 0.0f ); + } + + virtual bool MayCreateMoreParticles( CParticleCollection *pParticles, void *pContext ) const + { + C_OP_PerParticleEmitterContext_t *pCtx = reinterpret_cast( pContext ); + if ( pCtx->m_bStoppedEmission ) + return false; + + if ( m_flEmitRate <= 0.0f ) + return false; + + if ( m_flEmissionDuration != 0.0f && ( pParticles->m_flCurTime - pParticles->m_flDt ) > ( m_flStartTime + m_flEmissionDuration ) ) + return false; + + return true; + } + + virtual void InitializeContextData( CParticleCollection *pParticles, void *pContext ) const + { + C_OP_PerParticleEmitterContext_t *pCtx=reinterpret_cast( pContext ); + pCtx->m_flTotalActualParticlesSoFar = 0.0f; + pCtx->m_nTotalEmittedSoFar = 0; + pCtx->m_bStoppedEmission = false; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_PerParticleEmitter, "Per Particle Emitter", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_PerParticleEmitter ) +DMXELEMENT_UNPACK_FIELD( "Group ID to affect", "1", int, m_nChildGroupID ) +DMXELEMENT_UNPACK_FIELD( "Inherit Velocity", "0", int, m_bInheritVelocity ) +DMXELEMENT_UNPACK_FIELD( "Emission Rate", "100", float, m_flEmitRate ) +DMXELEMENT_UNPACK_FIELD( "Velocity Scale", "0", int, m_flVelocityScale ) +DMXELEMENT_UNPACK_FIELD( "Emission Start Time", "0", float, m_flStartTime ) +DMXELEMENT_UNPACK_FIELD( "Emission Duration", "0", float, m_flEmissionDuration ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_PerParticleEmitter ) + + +void C_OP_PerParticleEmitter::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + for( CParticleCollection *pChild = pParticles->m_Children.m_pHead; pChild; pChild = pChild->m_pNext ) + { + if ( pChild->GetGroupID() == m_nChildGroupID ) + { + for ( int i = 0; i < pParticles->m_nActiveParticles; ++i ) + { + C_OP_PerParticleEmitterContext_t *pCtx=reinterpret_cast( pContext ); + const float *pXYZ = pParticles->GetFloatAttributePtr( + PARTICLE_ATTRIBUTE_XYZ, i ); + const float *pXYZ_Prev = pParticles->GetFloatAttributePtr( + PARTICLE_ATTRIBUTE_PREV_XYZ, i ); + Vector vecParticlePosition, vecParticlePosition_prev, vParticleDelta ; + + vecParticlePosition = Vector ( pXYZ[0], pXYZ[4], pXYZ[8] ); + vecParticlePosition_prev = Vector ( pXYZ_Prev[0], pXYZ_Prev[4], pXYZ_Prev[8] ); + vParticleDelta = vecParticlePosition - vecParticlePosition_prev; + + float flEmissionRate = m_flEmitRate * flStrength; + + if ( m_flVelocityScale != 0.0f ) + { + float flVelocity = vParticleDelta.Length(); + flEmissionRate *= flVelocity * m_flVelocityScale * pParticles->m_flDt; + } + + if ( flEmissionRate == 0.0f ) + continue; + + if ( !C_OP_PerParticleEmitter::MayCreateMoreParticles( pChild, pContext ) ) + continue; + + Assert( flEmissionRate != 0.0f ); + + // determine our previous and current draw times and clamp them to start time and emission duration + float flPrevDrawTime = pParticles->m_flCurTime - pParticles->m_flDt; + float flCurrDrawTime = pParticles->m_flCurTime; + + if ( !IsInfinitelyEmitting() ) + { + if ( flPrevDrawTime < m_flStartTime ) + { + flPrevDrawTime = m_flStartTime; + } + if ( flCurrDrawTime > m_flStartTime + m_flEmissionDuration ) + { + flCurrDrawTime = m_flStartTime + m_flEmissionDuration; + } + } + + float flDeltaTime = flCurrDrawTime - flPrevDrawTime; + + //Calculate emission rate by delta time from last frame to determine number of particles to emit this frame as a fractional float + float flActualParticlesToEmit = flEmissionRate * flDeltaTime; + int nParticlesEmitted = pCtx->m_nTotalEmittedSoFar; + //Add emitted particle to float counter to allow for fractional emission + pCtx->m_flTotalActualParticlesSoFar += flActualParticlesToEmit; + + //Floor float accumulated value and subtract whole int emitted so far from the result to determine total whole particles to emit this frame + int nParticlesToEmit = floor ( pCtx->m_flTotalActualParticlesSoFar ) - pCtx->m_nTotalEmittedSoFar; + + //Add emitted particles to running int total. + pCtx->m_nTotalEmittedSoFar += nParticlesToEmit; + + + if ( nParticlesToEmit == 0 ) + continue; + + // We're only allowed to emit so many particles, though.. + // If we run out of room, only emit the last N particles + int nActualParticlesToEmit = nParticlesToEmit; + int nAllowedParticlesToEmit = pChild->m_nMaxAllowedParticles - pParticles->m_nActiveParticles; + if ( nAllowedParticlesToEmit < nParticlesToEmit ) + { + nActualParticlesToEmit = nAllowedParticlesToEmit; + } + if ( nActualParticlesToEmit == 0 ) + continue; + + int nStartParticle = pChild->m_nActiveParticles; + pChild->SetNActiveParticles( nActualParticlesToEmit + pChild->m_nActiveParticles ); + + + float flTimeStampStep = ( flDeltaTime ) / ( nActualParticlesToEmit ); + float flTimeStep = flPrevDrawTime + flTimeStampStep; + Vector vecMoveStampStep = vParticleDelta / nActualParticlesToEmit ; + Vector vecMoveStep = vecParticlePosition_prev + vecMoveStampStep ; + + if ( nParticlesEmitted != pChild->m_nActiveParticles ) + { + + uint32 nInittedMask = ( PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK | PARTICLE_ATTRIBUTE_CREATION_TIME_MASK ); + // init newly emitted particles + pChild->InitializeNewParticles( nParticlesEmitted, pChild->m_nActiveParticles - nParticlesEmitted, nInittedMask ); + //CHECKSYSTEM( this ); + } + + // Set the particle creation time to the exact sub-frame particle emission time + // !! speed!! do sse init here + for( int j = nStartParticle; j < nStartParticle + nActualParticlesToEmit; j++ ) + { + float *pTimeStamp = pChild->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_CREATION_TIME, j ); + flTimeStep = min( flTimeStep, flCurrDrawTime ); + *pTimeStamp = flTimeStep; + flTimeStep += flTimeStampStep; + float *pXYZ_Child = pChild->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, j ); + float *pXYZ_Prev_Child = pChild->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, j ); + Vector vecChildXYZ; + SetVectorFromAttribute ( vecChildXYZ, pXYZ_Child); + vecChildXYZ = vecMoveStep; + SetVectorAttribute ( pXYZ_Child, vecChildXYZ); + vecMoveStep += vecMoveStampStep; + if ( m_bInheritVelocity ) + { + *pXYZ_Prev_Child = *pXYZ_Prev; + } + else + { + *pXYZ_Prev_Child = *pXYZ_Child; + } + + } + + } + } + } +} + + +class C_OP_LockToBone : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_LockToBone ); + + int m_nControlPointNumber; + float m_flLifeTimeFadeStart; + float m_flLifeTimeFadeEnd; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + int ret= PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK | + PARTICLE_ATTRIBUTE_HITBOX_RELATIVE_XYZ_MASK | PARTICLE_ATTRIBUTE_HITBOX_INDEX_MASK; + ret |= PARTICLE_ATTRIBUTE_CREATION_TIME_MASK; + return ret; + + } + + virtual uint64 GetReadControlPointMask() const + { + return ( 1ULL << m_nControlPointNumber ); + } + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nControlPointNumber = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nControlPointNumber ) ); + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_LockToBone , "Movement Lock to Bone", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_LockToBone ) + DMXELEMENT_UNPACK_FIELD( "control_point_number", "0", int, m_nControlPointNumber ) + DMXELEMENT_UNPACK_FIELD( "lifetime fade start", "0", float, m_flLifeTimeFadeStart ) + DMXELEMENT_UNPACK_FIELD( "lifetime fade end", "0", float, m_flLifeTimeFadeEnd ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_LockToBone ) + +void C_OP_LockToBone::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + pParticles->UpdateHitBoxInfo( m_nControlPointNumber ); + if ( pParticles->m_ControlPointHitBoxes[m_nControlPointNumber].CurAndPrevValid() ) + { + float flAgeThreshold = m_flLifeTimeFadeEnd; + if ( flAgeThreshold <= 0.0 ) + flAgeThreshold = 1.0e20; + float flIScale = 0.0; + if ( m_flLifeTimeFadeEnd > m_flLifeTimeFadeStart ) + flIScale = 1.0/( m_flLifeTimeFadeEnd - m_flLifeTimeFadeStart ); + + for ( int i = 0; i < pParticles->m_nActiveParticles; ++i ) + { + float *pXYZ = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, i ); + float *pPrevXYZ = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, i ); + const float *pUVW = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_HITBOX_RELATIVE_XYZ, i ); + const int nBoxIndex = *pParticles->GetIntAttributePtr( PARTICLE_ATTRIBUTE_HITBOX_INDEX, i ); + float const *pCreationTime = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, i ); + + float flAge = pParticles->m_flCurTime -*pCreationTime; + + if ( flAge < flAgeThreshold ) + { + if ( + ( nBoxIndex < pParticles->m_ControlPointHitBoxes[m_nControlPointNumber].m_nNumHitBoxes ) && + ( nBoxIndex < pParticles->m_ControlPointHitBoxes[m_nControlPointNumber].m_nNumPrevHitBoxes ) && + ( nBoxIndex >= 0 ) + ) + { + Vector vecParticlePosition; + ModelHitBoxInfo_t const &hb = pParticles->m_ControlPointHitBoxes[m_nControlPointNumber].m_pHitBoxes[ nBoxIndex ]; + vecParticlePosition.x = Lerp( pUVW[0], hb.m_vecBoxMins.x, hb.m_vecBoxMaxes.x ); + vecParticlePosition.y = Lerp( pUVW[4], hb.m_vecBoxMins.y, hb.m_vecBoxMaxes.y ); + vecParticlePosition.z = Lerp( pUVW[8], hb.m_vecBoxMins.z, hb.m_vecBoxMaxes.z ); + Vector vecWorldPosition; + VectorTransform( vecParticlePosition, hb.m_Transform, vecWorldPosition ); + + Vector vecPrevParticlePosition; + ModelHitBoxInfo_t phb = pParticles->m_ControlPointHitBoxes[m_nControlPointNumber].m_pPrevBoxes[ nBoxIndex ]; + vecPrevParticlePosition.x = Lerp( pUVW[0], phb.m_vecBoxMins.x, phb.m_vecBoxMaxes.x ); + vecPrevParticlePosition.y = Lerp( pUVW[4], phb.m_vecBoxMins.y, phb.m_vecBoxMaxes.y ); + vecPrevParticlePosition.z = Lerp( pUVW[8], phb.m_vecBoxMins.z, phb.m_vecBoxMaxes.z ); + Vector vecPrevWorldPosition; + VectorTransform( vecPrevParticlePosition, phb.m_Transform, vecPrevWorldPosition ); + + Vector Delta = vecWorldPosition-vecPrevWorldPosition; + + if ( flAge > m_flLifeTimeFadeStart ) + Delta *= flStrength * ( 1.0- ( ( flAge - m_flLifeTimeFadeStart ) * flIScale ) ); + + Vector xyz; + SetVectorFromAttribute( xyz, pXYZ ); + xyz += Delta; + SetVectorAttribute( pXYZ, xyz ); + + Vector prevxyz; + SetVectorFromAttribute( prevxyz, pPrevXYZ ); + prevxyz += Delta; + SetVectorAttribute( pPrevXYZ, prevxyz ); + } + } + } + } +}; + +//----------------------------------------------------------------------------- +// Plane Cull Operator - cull particles on the "wrong" side of a plane +//----------------------------------------------------------------------------- +class C_OP_PlaneCull : public CParticleOperatorInstance +{ + int m_nPlaneControlPoint; + Vector m_vecPlaneDirection; + float m_flPlaneOffset; + + DECLARE_PARTICLE_OPERATOR( C_OP_PlaneCull ); + + uint32 GetWrittenAttributes( void ) const + { + return 0; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + return ( 1ULL << m_nPlaneControlPoint ); + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_PlaneCull, "Cull when crossing plane", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_PlaneCull ) + DMXELEMENT_UNPACK_FIELD( "Control Point for point on plane", "0", int, m_nPlaneControlPoint ) + DMXELEMENT_UNPACK_FIELD( "Cull plane offset", "0", float, m_flPlaneOffset ) + DMXELEMENT_UNPACK_FIELD( "Plane Normal", "0 0 1", Vector, m_vecPlaneDirection ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_PlaneCull ) + +void C_OP_PlaneCull::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + C4VAttributeIterator pXYZ( PARTICLE_ATTRIBUTE_XYZ, pParticles ); + int nLimit = pParticles->m_nPaddedActiveParticles << 2; + + // setup vars + FourVectors v4N ; + v4N.DuplicateVector( m_vecPlaneDirection ); + v4N.VectorNormalize(); + FourVectors v4Pnt; + v4Pnt.DuplicateVector( pParticles->GetControlPointAtCurrentTime( m_nPlaneControlPoint ) ); + FourVectors ofs = v4N; + ofs *= ReplicateX4( m_flPlaneOffset ); + v4Pnt -= ofs; + + for ( int i = 0; i < nLimit; i+= 4 ) + { + FourVectors f4PlaneRel = (*pXYZ ); + f4PlaneRel -= v4Pnt; + fltx4 fl4PlaneEq = ( f4PlaneRel * v4N ); + if ( IsAnyNegative( fl4PlaneEq ) ) + { + // not especially pretty - we need to kill some particles. + int nMask = TestSignSIMD( fl4PlaneEq ); + if ( nMask & 1 ) + pParticles->KillParticle( i ); + if ( nMask & 2 ) + pParticles->KillParticle( i + 1 ); + if ( nMask & 4 ) + pParticles->KillParticle( i + 2 ); + if ( nMask & 8 ) + pParticles->KillParticle( i + 3 ); + + } + ++pXYZ; + } +} + +//----------------------------------------------------------------------------- +// Model Cull Operator - cull particles inside or outside of a brush/animated model +//----------------------------------------------------------------------------- +class C_OP_ModelCull : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_ModelCull ); + + int m_nControlPointNumber; + bool m_bBoundBox; + bool m_bCullOutside; + + uint32 GetWrittenAttributes( void ) const + { + return 0; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK; + } + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nControlPointNumber = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nControlPointNumber ) ); + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_ModelCull , "Cull relative to model", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_ModelCull ) +DMXELEMENT_UNPACK_FIELD( "control_point_number", "0", int, m_nControlPointNumber ) +DMXELEMENT_UNPACK_FIELD( "use only bounding box", "0", bool, m_bBoundBox ) +DMXELEMENT_UNPACK_FIELD( "cull outside instead of inside", "0", bool, m_bCullOutside ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_ModelCull ) + +void C_OP_ModelCull::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + pParticles->UpdateHitBoxInfo( m_nControlPointNumber ); + if ( pParticles->m_ControlPointHitBoxes[m_nControlPointNumber].CurAndPrevValid() ) + { + for ( int i = 0; i < pParticles->m_nActiveParticles; ++i ) + { + float *pXYZ = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, i ); + Vector vecParticlePosition; + + SetVectorFromAttribute( vecParticlePosition, pXYZ ); + + bool bInside = g_pParticleSystemMgr->Query()->IsPointInControllingObjectHitBox( pParticles, m_nControlPointNumber, vecParticlePosition, m_bBoundBox ); + if ( ( bInside && m_bCullOutside ) || ( !bInside && !m_bCullOutside )) + continue; + + pParticles->KillParticle(i); + } + } +}; + +//----------------------------------------------------------------------------- +// Assign CP to Center +//----------------------------------------------------------------------------- +class C_OP_SetControlPointToCenter : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_SetControlPointToCenter ); + + int m_nCP1; + + Vector m_vecCP1Pos; + + uint32 GetWrittenAttributes( void ) const + { + return 0; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nCP1 = max( 0, min( MAX_PARTICLE_CONTROL_POINTS-1, m_nCP1 ) ); + } + + bool ShouldRunBeforeEmitters( void ) const + { + return true; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_SetControlPointToCenter, "Set Control Point To Particles' Center", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_SetControlPointToCenter ) +DMXELEMENT_UNPACK_FIELD( "Control Point Number to Set", "1", int, m_nCP1 ) +DMXELEMENT_UNPACK_FIELD( "Center Offset", "0 0 0", Vector, m_vecCP1Pos ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_SetControlPointToCenter ) + +void C_OP_SetControlPointToCenter::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + + Vector vecMinBounds; + Vector vecMaxBounds; + + pParticles->GetBounds( &vecMinBounds, &vecMaxBounds ); + + Vector vecCenter = ( ( vecMinBounds + vecMaxBounds ) / 2 ); + + pParticles->SetControlPoint( m_nCP1, m_vecCP1Pos + vecCenter ); +} + + + + + +//----------------------------------------------------------------------------- +// Velocity Match a group of particles +//----------------------------------------------------------------------------- +class C_OP_VelocityMatchingForce : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_VelocityMatchingForce ); + + float m_flDirScale; + float m_flSpdScale; + int m_nCPBroadcast; + + struct VelocityMatchingForceContext_t + { + Vector m_vecAvgVelocity; + float m_flAvgSpeed; + }; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK ; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK ; + } + + virtual void InitializeContextData( CParticleCollection *pParticles, void *pContext ) const + { + VelocityMatchingForceContext_t *pCtx = reinterpret_cast( pContext ); + pCtx->m_vecAvgVelocity = vec3_origin; + pCtx->m_flAvgSpeed = 0; + } + + size_t GetRequiredContextBytes( void ) const + { + return sizeof( VelocityMatchingForceContext_t ); + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_VelocityMatchingForce , "Movement Match Particle Velocities", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_VelocityMatchingForce ) +DMXELEMENT_UNPACK_FIELD( "Direction Matching Strength", "0.25", float, m_flDirScale ) +DMXELEMENT_UNPACK_FIELD( "Speed Matching Strength", "0.25", float, m_flSpdScale ) +DMXELEMENT_UNPACK_FIELD( "Control Point to Broadcast Speed and Direction To", "-1", int, m_nCPBroadcast ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_VelocityMatchingForce ) + +void C_OP_VelocityMatchingForce::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + VelocityMatchingForceContext_t *pCtx = reinterpret_cast( pContext ); + + Vector vecVelocityAvg = vec3_origin; + float flAvgSpeed = 0; + + // FIXME: SSE-ize + for ( int i = 0; i < pParticles->m_nActiveParticles; ++i ) + { + + + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, i ); + float *xyz_prev = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, i ); + + Vector vecXYZ; + Vector vecPXYZ; + SetVectorFromAttribute( vecXYZ, xyz ); + SetVectorFromAttribute( vecPXYZ, xyz_prev ); + Vector vecVelocityCur = ( ( vecXYZ - vecPXYZ ) / pParticles->m_flDt ); + vecVelocityAvg += vecVelocityCur; + float flSpeed = vecVelocityCur.Length(); + flAvgSpeed += flSpeed; + + if ( pCtx->m_vecAvgVelocity != vec3_origin ) + { + Vector vecScaledXYZ; + VectorNormalizeFast(vecVelocityCur); + VectorLerp( vecVelocityCur, pCtx->m_vecAvgVelocity, m_flDirScale, vecScaledXYZ ); + VectorNormalizeFast(vecScaledXYZ); + flSpeed = Lerp ( m_flSpdScale, flSpeed, pCtx->m_flAvgSpeed ); + vecScaledXYZ *= flSpeed; + vecScaledXYZ = ( ( vecScaledXYZ * pParticles->m_flDt ) + vecPXYZ ); + SetVectorAttribute( xyz, vecScaledXYZ ); + } + } + + VectorNormalizeFast( vecVelocityAvg ); + pCtx->m_vecAvgVelocity = vecVelocityAvg; + pCtx->m_flAvgSpeed = ( flAvgSpeed / pParticles->m_nActiveParticles ); + if ( m_nCPBroadcast != -1 ) + { + pParticles->SetControlPoint( m_nCPBroadcast, Vector ( pCtx->m_flAvgSpeed, pCtx->m_flAvgSpeed, pCtx->m_flAvgSpeed ) ); + pParticles->SetControlPointForwardVector( m_nCPBroadcast, pCtx->m_vecAvgVelocity ); + } +}; + + + +//----------------------------------------------------------------------------- +// Orient to heading +//----------------------------------------------------------------------------- +class C_OP_OrientTo2dDirection : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_OrientTo2dDirection ); + + float m_flRotOffset; + float m_flSpinStrength; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_ROTATION_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK ; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_OrientTo2dDirection , "Rotation Orient to 2D Direction", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_OrientTo2dDirection ) +DMXELEMENT_UNPACK_FIELD( "Rotation Offset", "0", float, m_flRotOffset ) +DMXELEMENT_UNPACK_FIELD( "Spin Strength", "1", float, m_flSpinStrength ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_OrientTo2dDirection ) + +void C_OP_OrientTo2dDirection::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + + float flRotOffset = m_flRotOffset * ( M_PI / 180.0f ); + // FIXME: SSE-ize + 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 ); + float *roll = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_ROTATION, i ); + + Vector vecXYZ; + Vector vecPXYZ; + vecXYZ.x = xyz[0]; + vecXYZ.y = xyz[4]; + vecXYZ.z = xyz[8]; + vecPXYZ.x = xyz_prev[0]; + vecPXYZ.y = xyz_prev[4]; + vecPXYZ.z = xyz_prev[8]; + Vector vecVelocityCur = ( vecXYZ - vecPXYZ ); + + vecVelocityCur.z = 0.0f; + VectorNormalizeFast ( vecVelocityCur ); + + float flCurRot = *roll; + + float flVelRot = atan2(vecVelocityCur.y, vecVelocityCur.x ) + M_PI; + + flVelRot += flRotOffset; + + float flRotation = Lerp ( m_flSpinStrength, flCurRot, flVelRot ); + *roll = flRotation; + } + +}; + + + +//----------------------------------------------------------------------------- +// Orient relative to CP +//----------------------------------------------------------------------------- +class C_OP_Orient2DRelToCP : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_Orient2DRelToCP ); + + float m_flRotOffset; + float m_flSpinStrength; + int m_nCP; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_ROTATION_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK ; + } + + virtual uint64 GetReadControlPointMask() const + { + return ( 1ULL << m_nCP ); + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_Orient2DRelToCP , "Rotation Orient Relative to CP", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_Orient2DRelToCP ) +DMXELEMENT_UNPACK_FIELD( "Rotation Offset", "0", float, m_flRotOffset ) +DMXELEMENT_UNPACK_FIELD( "Spin Strength", "1", float, m_flSpinStrength ) +DMXELEMENT_UNPACK_FIELD( "Control Point", "0", int, m_nCP ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_Orient2DRelToCP ) + +void C_OP_Orient2DRelToCP::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + + float flRotOffset = m_flRotOffset * ( M_PI / 180.0f ); + // FIXME: SSE-ize + for ( int i = 0; i < pParticles->m_nActiveParticles; ++i ) + { + + const float *xyz = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_XYZ, i ); + float *roll = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_ROTATION, i ); + + Vector vecXYZ; + Vector vecCP; + vecCP = pParticles->GetControlPointAtCurrentTime( m_nCP ); + vecXYZ.x = xyz[0]; + vecXYZ.y = xyz[4]; + vecXYZ.z = xyz[8]; + + Vector vecVelocityCur = ( vecXYZ - vecCP ); + + vecVelocityCur.z = 0.0f; + VectorNormalizeFast ( vecVelocityCur ); + + float flCurRot = *roll; + + float flVelRot = atan2(vecVelocityCur.y, vecVelocityCur.x ) + M_PI; + + flVelRot += flRotOffset; + + float flRotation = Lerp ( m_flSpinStrength, flCurRot, flVelRot ); + *roll = flRotation; + } +}; + + +//----------------------------------------------------------------------------- +// Max Velocity - clamps the maximum velocity of a particle +//----------------------------------------------------------------------------- +class C_OP_MaxVelocity : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_MaxVelocity ); + + float m_flMaxVelocity; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK ; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK ; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_MaxVelocity , "Movement Max Velocity", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_MaxVelocity ) +DMXELEMENT_UNPACK_FIELD( "Maximum Velocity", "0", float, m_flMaxVelocity ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_MaxVelocity ) + +void C_OP_MaxVelocity::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + + + // FIXME: SSE-ize + for ( int i = 0; i < pParticles->m_nActiveParticles; ++i ) + { + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, i ); + float *xyz_prev = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, i ); + + Vector vecXYZ; + Vector vecPXYZ; + SetVectorFromAttribute( vecXYZ, xyz ); + SetVectorFromAttribute( vecPXYZ, xyz_prev ); + Vector vecVelocityCur = ( ( vecXYZ - vecPXYZ ) ); + float flSpeed = vecVelocityCur.Length(); + VectorNormalizeFast( vecVelocityCur ); + float flMaxVelocityNormalized = m_flMaxVelocity * pParticles->m_flDt; + vecVelocityCur *= min( flSpeed, flMaxVelocityNormalized); + vecXYZ = vecPXYZ + vecVelocityCur; + SetVectorAttribute( xyz, vecXYZ ); + } +}; + +//----------------------------------------------------------------------------- +// Maintain position along a path +//----------------------------------------------------------------------------- +struct SequentialPositionContext_t +{ + int m_nParticleCount; + float m_flStep; + int m_nCountAmount; +}; + +class C_OP_MaintainSequentialPath : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_MaintainSequentialPath ); + + float m_fMaxDistance; + float m_flNumToAssign; + bool m_bLoop; + float m_flCohesionStrength; + struct CPathParameters m_PathParams; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + virtual uint64 GetReadControlPointMask() const + { + uint64 nStartMask = ( 1ULL << m_PathParams.m_nStartControlPointNumber ) - 1; + uint64 nEndMask = ( 1ULL << ( m_PathParams.m_nEndControlPointNumber + 1 ) ) - 1; + return nEndMask & (~nStartMask); + } + + virtual void InitializeContextData( CParticleCollection *pParticles, void *pContext ) const + { + SequentialPositionContext_t *pCtx = reinterpret_cast( pContext ); + pCtx->m_nParticleCount = 0; + if ( m_flNumToAssign > 1.0f ) + { + pCtx->m_flStep = 1.0f / ( m_flNumToAssign - 1 ); + } + else + { + pCtx->m_flStep = 0.0f; + } + pCtx->m_nCountAmount = 1; + } + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_PathParams.ClampControlPointIndices(); + } + + size_t GetRequiredContextBytes( void ) const + { + return sizeof( SequentialPositionContext_t ); + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_MaintainSequentialPath, "Movement Maintain Position Along Path", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_MaintainSequentialPath ) + DMXELEMENT_UNPACK_FIELD( "maximum distance", "0", float, m_fMaxDistance ) + DMXELEMENT_UNPACK_FIELD( "bulge", "0", float, m_PathParams.m_flBulge ) + DMXELEMENT_UNPACK_FIELD( "start control point number", "0", int, m_PathParams.m_nStartControlPointNumber ) + DMXELEMENT_UNPACK_FIELD( "end control point number", "0", int, m_PathParams.m_nEndControlPointNumber ) + DMXELEMENT_UNPACK_FIELD( "bulge control 0=random 1=orientation of start pnt 2=orientation of end point", "0", int, m_PathParams.m_nBulgeControl ) + DMXELEMENT_UNPACK_FIELD( "mid point position", "0.5", float, m_PathParams.m_flMidPoint ) + DMXELEMENT_UNPACK_FIELD( "particles to map from start to end", "100", float, m_flNumToAssign ) + DMXELEMENT_UNPACK_FIELD( "restart behavior (0 = bounce, 1 = loop )", "1", bool, m_bLoop ) + DMXELEMENT_UNPACK_FIELD( "cohesion strength", "1", float, m_flCohesionStrength ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_MaintainSequentialPath ) + + +void C_OP_MaintainSequentialPath::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + // NOTE: Using C_OP_ContinuousEmitter:: avoids a virtual function call + SequentialPositionContext_t *pCtx = reinterpret_cast( pContext ); + + float fl_Cohesion = ( 1 - m_flCohesionStrength ); + + for ( int i = 0; i < pParticles->m_nActiveParticles; ++i ) + { + float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, i ); + float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, i ); + + Vector StartPnt, MidP, EndPnt; + pParticles->CalculatePathValues( m_PathParams, pParticles->m_flCurTime, &StartPnt, &MidP, &EndPnt); + if ( pCtx->m_nParticleCount >= m_flNumToAssign || pCtx->m_nParticleCount < 0 ) + { + if ( m_bLoop ) + { + pCtx->m_nParticleCount = 0; + } + else + { + pCtx->m_nCountAmount *= -1; + pCtx->m_nParticleCount = min ( pCtx->m_nParticleCount, (int)( m_flNumToAssign - 1) ); + pCtx->m_nParticleCount = max ( pCtx->m_nParticleCount, 1 ); + } + } + + float t= pCtx->m_nParticleCount * pCtx->m_flStep; + + + // form delta terms needed for quadratic bezier + Vector Delta0=MidP-StartPnt; + Vector Delta1 = EndPnt-MidP; + + Vector L0 = StartPnt+t*Delta0; + Vector L1 = MidP+t*Delta1; + + Vector Pnt = L0+(L1-L0)*t; + + // Allow an offset distance and position lerp + Vector vecXYZ; + Vector vecPXYZ; + + SetVectorFromAttribute( vecXYZ, xyz ); + SetVectorFromAttribute( vecPXYZ, pxyz ); + + vecXYZ -= Pnt; + vecPXYZ -= Pnt; + + float flXYZOffset = min (vecXYZ.Length(), m_fMaxDistance ); + float flPXYZOffset = min (vecPXYZ.Length(), m_fMaxDistance ); + + VectorNormalizeFast( vecXYZ ); + vecXYZ *= flXYZOffset * fl_Cohesion; + VectorNormalizeFast( vecPXYZ ); + vecPXYZ *= flPXYZOffset * fl_Cohesion; + + vecXYZ += Pnt; + vecPXYZ += Pnt; + + xyz[0] = vecXYZ.x; + xyz[4] = vecXYZ.y; + xyz[8] = vecXYZ.z; + pxyz[0] = vecPXYZ.x; + pxyz[4] = vecPXYZ.y; + pxyz[8] = vecPXYZ.z; + + pCtx->m_nParticleCount += pCtx->m_nCountAmount; + } +} + + +//----------------------------------------------------------------------------- +// Remap Dot Product to Scalar Operator +//----------------------------------------------------------------------------- +class C_OP_RemapDotProductToScalar : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_RemapDotProductToScalar ); + + uint32 GetWrittenAttributes( void ) const + { + return 1 << m_nFieldOutput; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK; + } + + virtual uint64 GetReadControlPointMask() const + { + return ( 1ULL << m_nInputCP1 ) | ( 1ULL << m_nInputCP2 ); + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + int m_nInputCP1; + int m_nInputCP2; + int m_nFieldOutput; + float m_flInputMin; + float m_flInputMax; + float m_flOutputMin; + float m_flOutputMax; + bool m_bUseParticleVelocity; + bool m_bScaleInitialRange; + bool m_bActiveRange; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_RemapDotProductToScalar, "Remap Dot Product to Scalar", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_RemapDotProductToScalar ) + DMXELEMENT_UNPACK_FIELD( "use particle velocity for first input", "0", bool, m_bUseParticleVelocity ) + DMXELEMENT_UNPACK_FIELD( "first input control point", "0", int, m_nInputCP1 ) + DMXELEMENT_UNPACK_FIELD( "second input control point", "0", int, m_nInputCP2 ) + DMXELEMENT_UNPACK_FIELD( "input minimum (-1 to 1)","0", float, m_flInputMin ) + DMXELEMENT_UNPACK_FIELD( "input maximum (-1 to 1)","1", float, m_flInputMax ) + DMXELEMENT_UNPACK_FIELD_USERDATA( "output field", "3", int, m_nFieldOutput, "intchoice particlefield_scalar" ) + DMXELEMENT_UNPACK_FIELD( "output minimum","0", float, m_flOutputMin ) + DMXELEMENT_UNPACK_FIELD( "output maximum","1", float, m_flOutputMax ) + DMXELEMENT_UNPACK_FIELD( "output is scalar of initial random range","0", bool, m_bScaleInitialRange ) + DMXELEMENT_UNPACK_FIELD( "only active within specified input range","0", bool, m_bActiveRange ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_RemapDotProductToScalar ) + +void C_OP_RemapDotProductToScalar::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + // clamp the result to 0 and 1 if it's alpha + float flMin=m_flOutputMin; + float flMax=m_flOutputMax; + if ( ATTRIBUTES_WHICH_ARE_0_TO_1 & ( 1 << m_nFieldOutput ) ) + { + flMin = clamp(m_flOutputMin, 0.0f, 1.0f ); + flMax = clamp(m_flOutputMax, 0.0f, 1.0f ); + } + + Vector vecInput1; + Vector vecInput2; + + CParticleSIMDTransformation pXForm1; + CParticleSIMDTransformation pXForm2; + pParticles->GetControlPointTransformAtTime( m_nInputCP1, pParticles->m_flCurTime, &pXForm1 ); + pParticles->GetControlPointTransformAtTime( m_nInputCP2, pParticles->m_flCurTime, &pXForm2 ); + + vecInput1 = pXForm1.m_v4Fwd.Vec( 0 ); + vecInput2 = pXForm2.m_v4Fwd.Vec( 0 ); + + float flInput = DotProduct( vecInput1, vecInput2 ); + + // only use within start/end time frame and, if set, active input range + if ( ( m_bActiveRange && !m_bUseParticleVelocity && ( flInput < m_flInputMin || flInput > m_flInputMax ) ) ) + return; + + // FIXME: SSE-ize + for ( int i = 0; i < pParticles->m_nActiveParticles; ++i ) + { + if ( m_bUseParticleVelocity ) + { + const float *xyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_XYZ, i ); + const float *pxyz = pParticles->GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_PREV_XYZ, i ); + Vector vecXYZ; + Vector vecPXYZ; + + vecXYZ.x = xyz[0]; + vecXYZ.y = xyz[4]; + vecXYZ.z = xyz[8]; + vecPXYZ.x = pxyz[0]; + vecPXYZ.y = pxyz[4]; + vecPXYZ.z = pxyz[8]; + + vecInput1 = vecXYZ - vecPXYZ; + VectorNormalizeFast( vecInput1 ); + + float flInputDot = DotProduct( vecInput1, vecInput2 ); + + // only use within start/end time frame and, if set, active input range + if ( ( m_bActiveRange && (flInputDot < m_flInputMin || flInputDot > m_flInputMax ) ) ) + continue; + } + + float *pOutput = pParticles->GetFloatAttributePtrForWrite( m_nFieldOutput, i ); + float flOutput = RemapValClamped( flInput, m_flInputMin, m_flInputMax, flMin, flMax ); + if ( m_bScaleInitialRange ) + { + flOutput *= *pOutput; + } + if ( ATTRIBUTES_WHICH_ARE_INTS & ( 1 << m_nFieldOutput ) ) + { + *pOutput = int ( flOutput ); + } + else + { + *pOutput = flOutput; + } + } +} + + + +//----------------------------------------------------------------------------- +// Remap CP to Scalar Operator +//----------------------------------------------------------------------------- +class C_OP_RemapCPtoScalar : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_RemapCPtoScalar ); + + uint32 GetWrittenAttributes( void ) const + { + return 1 << m_nFieldOutput; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + virtual uint64 GetReadControlPointMask() const + { + return 1ULL << m_nCPInput; + } + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + m_nField = int (clamp (m_nField, 0, 2)); + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + int m_nCPInput; + int m_nFieldOutput; + int m_nField; + float m_flInputMin; + float m_flInputMax; + float m_flOutputMin; + float m_flOutputMax; + float m_flStartTime; + float m_flEndTime; + bool m_bScaleInitialRange; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_RemapCPtoScalar, "Remap Control Point to Scalar", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_RemapCPtoScalar ) +DMXELEMENT_UNPACK_FIELD( "emitter lifetime start time (seconds)", "-1", float, m_flStartTime ) +DMXELEMENT_UNPACK_FIELD( "emitter lifetime end time (seconds)", "-1", float, m_flEndTime ) +DMXELEMENT_UNPACK_FIELD( "input control point number", "0", int, m_nCPInput ) +DMXELEMENT_UNPACK_FIELD( "input minimum","0", float, m_flInputMin ) +DMXELEMENT_UNPACK_FIELD( "input maximum","1", float, m_flInputMax ) +DMXELEMENT_UNPACK_FIELD( "input field 0-2 X/Y/Z","0", int, m_nField ) +DMXELEMENT_UNPACK_FIELD_USERDATA( "output field", "3", int, m_nFieldOutput, "intchoice particlefield_scalar" ) +DMXELEMENT_UNPACK_FIELD( "output minimum","0", float, m_flOutputMin ) +DMXELEMENT_UNPACK_FIELD( "output maximum","1", float, m_flOutputMax ) +DMXELEMENT_UNPACK_FIELD( "output is scalar of initial random range","0", bool, m_bScaleInitialRange ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_RemapCPtoScalar ) + +void C_OP_RemapCPtoScalar::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + const float *pCreationTime; + // clamp the result to 0 and 1 if it's alpha + float flMin=m_flOutputMin; + float flMax=m_flOutputMax; + if ( ATTRIBUTES_WHICH_ARE_0_TO_1 & ( 1 << m_nFieldOutput ) ) + { + flMin = clamp(m_flOutputMin, 0.0f, 1.0f ); + flMax = clamp(m_flOutputMax, 0.0f, 1.0f ); + } + Vector vecControlPoint = pParticles->GetControlPointAtCurrentTime( m_nCPInput ); + + float flInput = vecControlPoint[m_nField]; + + // FIXME: SSE-ize + for( int i = 0; i < pParticles->m_nActiveParticles; ++i ) + { + pCreationTime = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, i ); + // using raw creation time to map to emitter lifespan + float flLifeTime = *pCreationTime; + + // only use within start/end time frame + if ( ( ( flLifeTime < m_flStartTime ) || ( flLifeTime >= m_flEndTime ) ) && ( ( m_flStartTime != -1.0f) && ( m_flEndTime != -1.0f) ) ) + continue; + + + float *pOutput = pParticles->GetFloatAttributePtrForWrite( m_nFieldOutput, i ); + float flOutput = RemapValClamped( flInput, m_flInputMin, m_flInputMax, flMin, flMax ); + if ( m_bScaleInitialRange ) + { + flOutput = *pOutput * flOutput; + } + if ( ATTRIBUTES_WHICH_ARE_INTS & ( 1 << m_nFieldOutput ) ) + { + *pOutput = int ( flOutput ); + } + else + { + *pOutput = flOutput; + } + } +} + +//----------------------------------------------------------------------------- +// Rotate Particle around axis +//----------------------------------------------------------------------------- +class C_OP_MovementRotateParticleAroundAxis : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_MovementRotateParticleAroundAxis ); + + Vector m_vecRotAxis; + float m_flRotRate; + int m_nCP; + bool m_bLocalSpace; + + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK ; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK ; + } + + virtual uint64 GetReadControlPointMask() const + { + return 1ULL << m_nCP; + } + + void InitParams( CParticleSystemDefinition *pDef ) + { + VectorNormalize( m_vecRotAxis ); + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_MovementRotateParticleAroundAxis , "Movement Rotate Particle Around Axis", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_MovementRotateParticleAroundAxis ) +DMXELEMENT_UNPACK_FIELD( "Rotation Axis", "0 0 1", Vector, m_vecRotAxis ) +DMXELEMENT_UNPACK_FIELD( "Rotation Rate", "180", float, m_flRotRate ) +DMXELEMENT_UNPACK_FIELD( "Control Point", "0", int, m_nCP ) +DMXELEMENT_UNPACK_FIELD( "Use Local Space", "0", bool, m_bLocalSpace ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_MovementRotateParticleAroundAxis ) + +void C_OP_MovementRotateParticleAroundAxis::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + float flRotRate = m_flRotRate * pParticles->m_flDt; + + matrix3x4_t matRot; + + Vector vecRotAxis = m_vecRotAxis; + + if ( m_bLocalSpace ) + { + matrix3x4_t matLocalCP; + pParticles->GetControlPointTransformAtCurrentTime( m_nCP, &matLocalCP ); + VectorRotate( m_vecRotAxis, matLocalCP, vecRotAxis ); + } + + MatrixBuildRotationAboutAxis ( vecRotAxis, flRotRate, matRot ); + + Vector vecCPPos = pParticles->GetControlPointAtCurrentTime( m_nCP ); + + FourVectors fvCPPos; + fvCPPos.DuplicateVector( vecCPPos ); + + fltx4 fl4Strength = ReplicateX4( flStrength ); + + C4VAttributeWriteIterator pXYZ( PARTICLE_ATTRIBUTE_XYZ, pParticles ); + C4VAttributeWriteIterator pPrevXYZ( PARTICLE_ATTRIBUTE_PREV_XYZ, pParticles ); + + int nCtr = pParticles->m_nPaddedActiveParticles; + do + { + FourVectors fvCurPos = *pXYZ; + fvCurPos -= fvCPPos; + FourVectors fvPrevPos = *pPrevXYZ; + fvPrevPos -= fvCPPos; + + fvCurPos.RotateBy( matRot ); + fvPrevPos.RotateBy( matRot ); + + fvCurPos += fvCPPos; + fvCurPos -= *pXYZ; + fvCurPos *= fl4Strength; + *pXYZ += fvCurPos; + fvPrevPos += fvCPPos; + fvPrevPos -= *pPrevXYZ; + fvPrevPos *= fl4Strength; + *pPrevXYZ += fvPrevPos; + + ++pXYZ; + ++pPrevXYZ; + } while ( --nCtr ); + +}; + +//----------------------------------------------------------------------------- +// Remap Speed to CP Operator +//----------------------------------------------------------------------------- +class C_OP_RemapSpeedtoCP : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_RemapSpeedtoCP ); + + uint32 GetWrittenAttributes( void ) const + { + return 0; + } + + uint32 GetReadAttributes( void ) const + { + return 0; + } + + virtual uint64 GetReadControlPointMask() const + { + return ( 1ULL << m_nInControlPointNumber ) | ( 1ULL << m_nOutControlPointNumber ); + } + + bool ShouldRunBeforeEmitters( void ) const + { + return true; + } + virtual void InitParams(CParticleSystemDefinition *pDef ) + { + // Safety for bogus input->output feedback loop + if ( m_nInControlPointNumber == m_nOutControlPointNumber ) + m_nOutControlPointNumber = -1; + } + + virtual void Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const; + + int m_nInControlPointNumber; + int m_nOutControlPointNumber; + int m_nField; + float m_flInputMin; + float m_flInputMax; + float m_flOutputMin; + float m_flOutputMax; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_RemapSpeedtoCP, "Remap CP Speed to CP", OPERATOR_GENERIC ); +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_RemapSpeedtoCP ) +DMXELEMENT_UNPACK_FIELD( "input control point", "0", int, m_nInControlPointNumber ) +DMXELEMENT_UNPACK_FIELD( "input minimum","0", float, m_flInputMin ) +DMXELEMENT_UNPACK_FIELD( "input maximum","1", float, m_flInputMax ) +DMXELEMENT_UNPACK_FIELD( "output control point", "-1", int, m_nOutControlPointNumber ) +DMXELEMENT_UNPACK_FIELD( "Output field 0-2 X/Y/Z","0", int, m_nField ) +DMXELEMENT_UNPACK_FIELD( "output minimum","0", float, m_flOutputMin ) +DMXELEMENT_UNPACK_FIELD( "output maximum","1", float, m_flOutputMax ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_RemapSpeedtoCP ); + +void C_OP_RemapSpeedtoCP::Operate( CParticleCollection *pParticles, float flStrength, void *pContext ) const +{ + if ( m_nOutControlPointNumber >= 0 ) + { + Vector vecPrevPos; + pParticles->GetControlPointAtPrevTime( m_nInControlPointNumber, &vecPrevPos ); + Vector vecDelta; + vecDelta = pParticles->GetControlPointAtCurrentTime( m_nInControlPointNumber ) - vecPrevPos; + float flSpeed = vecDelta.Length() / pParticles->m_flPreviousDt; + float flOutput = RemapValClamped( flSpeed, m_flInputMin, m_flInputMax, m_flOutputMin, m_flOutputMax ); + + Vector vecControlPoint = pParticles->GetControlPointAtCurrentTime( m_nOutControlPointNumber ); + vecControlPoint[m_nField] = flOutput; + pParticles->SetControlPoint( m_nOutControlPointNumber, vecControlPoint ); + } +} + + +void AddBuiltInParticleOperators( void ) +{ + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_BasicMovement ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_Decay ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_VelocityDecay ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_FadeAndKill ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_FadeIn ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_FadeOut ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_Spin ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_SpinUpdate ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_SpinYaw ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_OrientTo2dDirection ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_Orient2DRelToCP ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_InterpolateRadius ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_ColorInterpolate ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_OscillateScalar ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_OscillateVector ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_DampenToCP ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_PositionLock ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_LockToBone ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_DistanceBetweenCPs ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_DistanceToCP ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_SetControlPointToPlayer ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_SetControlPointToCenter ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_SetChildControlPoints ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_SetControlPointPositions ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_PlaneCull ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_ModelCull ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_Cull ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_ControlpointLight ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_RemapScalar ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_Noise ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_VectorNoise ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_VelocityMatchingForce ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_MaxVelocity ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_MaintainSequentialPath ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_RemapDotProductToScalar ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_RemapCPtoScalar ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_MovementRotateParticleAroundAxis ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_OPERATOR, C_OP_RemapSpeedtoCP ); +} + diff --git a/particles/builtin_particle_render_ops.cpp b/particles/builtin_particle_render_ops.cpp new file mode 100644 index 0000000..84b1a3a --- /dev/null +++ b/particles/builtin_particle_render_ops.cpp @@ -0,0 +1,2416 @@ +//========= 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 "tier2/renderutils.h" +#include "tier2/beamsegdraw.h" +#include "tier1/UtlStringMap.h" +#include "tier1/strtools.h" +#include "materialsystem/imesh.h" +#include "materialsystem/itexture.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/imaterialvar.h" +#include "psheet.h" +#include "tier0/vprof.h" + +#ifdef USE_BLOBULATOR +// TODO: These should be in public by the time the SDK ships + #include "../common/blobulator/Implicit/ImpDefines.h" + #include "../common/blobulator/Implicit/ImpRenderer.h" + #include "../common/blobulator/Implicit/ImpTiler.h" + #include "../common/blobulator/Implicit/UserFunctions.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Vertex instancing (1 vert submitted per particle, duplicated to 4 (a quad) on the GPU) is supported only on 360 +const bool bUseInstancing = IsX360(); + + +//----------------------------------------------------------------------------- +// Utility method to compute the max # of particles per batch +//----------------------------------------------------------------------------- +static inline int GetMaxParticlesPerBatch( IMatRenderContext *pRenderContext, IMaterial *pMaterial, bool bWithInstancing ) +{ + int nMaxVertices = pRenderContext->GetMaxVerticesToRender( pMaterial ); + int nMaxIndices = pRenderContext->GetMaxIndicesToRender(); + + if ( bWithInstancing ) + return nMaxVertices; + else + return min( (nMaxVertices / 4), (nMaxIndices / 6) ); +} + +void SetupParticleVisibility( CParticleCollection *pParticles, CParticleVisibilityData *pVisibilityData, const CParticleVisibilityInputs *pVisibilityInputs, int *nQueryHandle ) +{ + float flScale = pVisibilityInputs->m_flProxyRadius; + Vector vecOrigin; + /* + if ( pVisibilityInputs->m_bUseBBox ) + { + Vector vecMinBounds; + Vector vecMaxBounds; + Vector mins; + Vector maxs; + + pParticles->GetBounds( &vecMinBounds, &vecMaxBounds ); + + vecOrigin = ( ( vecMinBounds + vecMaxBounds ) / 2 ); + + Vector vecBounds = ( vecMaxBounds - vecMinBounds ); + + flScale = ( max(vecBounds.x, max (vecBounds.y, vecBounds.z) ) * pVisibilityInputs->m_flBBoxScale ); + } + if ( pVisibilityInputs->m_nCPin >= 0 ) + { + vecOrigin = pParticles->GetControlPointAtCurrentTime( pVisibilityInputs->m_nCPin ); + } + */ + vecOrigin = pParticles->GetControlPointAtCurrentTime( pVisibilityInputs->m_nCPin ); + float flVisibility = g_pParticleSystemMgr->Query()->GetPixelVisibility( nQueryHandle, vecOrigin, flScale ); + + pVisibilityData->m_flAlphaVisibility = RemapValClamped( flVisibility, pVisibilityInputs->m_flInputMin, + pVisibilityInputs->m_flInputMax, pVisibilityInputs->m_flAlphaScaleMin, pVisibilityInputs->m_flAlphaScaleMax ); + pVisibilityData->m_flRadiusVisibility = RemapValClamped( flVisibility, pVisibilityInputs->m_flInputMin, + pVisibilityInputs->m_flInputMax, pVisibilityInputs->m_flRadiusScaleMin, pVisibilityInputs->m_flRadiusScaleMax ); + + pVisibilityData->m_flCameraBias = pVisibilityInputs->m_flCameraBias; +} + +static SheetSequenceSample_t s_DefaultSheetSequence = +{ + { + { 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f }, // SequenceSampleTextureCoords_t image 0 + { 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f } // SequenceSampleTextureCoords_t image 1 + }, + 1.0f // m_fBlendFactor +}; + + +class C_OP_RenderPoints : public CParticleRenderOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_RenderPoints ); + + uint32 GetWrittenAttributes( void ) const + { + return 0; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK; + } + + virtual void Render( IMatRenderContext *pRenderContext, CParticleCollection *pParticles, void *pContext ) const; + + struct C_OP_RenderPointsContext_t + { + CParticleVisibilityData m_VisibilityData; + int m_nQueryHandle; + }; + + size_t GetRequiredContextBytes( void ) const + { + return sizeof( C_OP_RenderPointsContext_t ); + } + + virtual void InitializeContextData( CParticleCollection *pParticles, void *pContext ) const + { + C_OP_RenderPointsContext_t *pCtx = reinterpret_cast( pContext ); + pCtx->m_VisibilityData.m_bUseVisibility = false; + pCtx->m_VisibilityData.m_flCameraBias = VisibilityInputs.m_flCameraBias; + } +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_RenderPoints, "render_points", OPERATOR_SINGLETON ); + +BEGIN_PARTICLE_RENDER_OPERATOR_UNPACK( C_OP_RenderPoints ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_RenderPoints ) + +void C_OP_RenderPoints::Render( IMatRenderContext *pRenderContext, CParticleCollection *pParticles, void *pContext ) const +{ + C_OP_RenderPointsContext_t *pCtx = reinterpret_cast( pContext ); + IMaterial *pMaterial = pParticles->m_pDef->GetMaterial(); + + int nParticles; + const ParticleRenderData_t *pRenderList = + pParticles->GetRenderList( pRenderContext, true, &nParticles, &pCtx->m_VisibilityData ); + + size_t xyz_stride; + const fltx4 *xyz = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_XYZ, &xyz_stride ); + + pRenderContext->Bind( pMaterial ); + + CMeshBuilder meshBuilder; + + int nMaxVertices = pRenderContext->GetMaxVerticesToRender( pMaterial ); + while ( nParticles ) + { + IMesh* pMesh = pRenderContext->GetDynamicMesh( true ); + + int nParticlesInBatch = min( nMaxVertices, nParticles ); + nParticles -= nParticlesInBatch; + g_pParticleSystemMgr->TallyParticlesRendered( nParticlesInBatch ); + + meshBuilder.Begin( pMesh, MATERIAL_POINTS, nParticlesInBatch ); + for( int i = 0; i < nParticlesInBatch; i++ ) + { + int hParticle = (--pRenderList)->m_nIndex; + int nIndex = ( hParticle / 4 ) * xyz_stride; + int nOffset = hParticle & 0x3; + meshBuilder.Position3f( SubFloat( xyz[nIndex], nOffset ), SubFloat( xyz[nIndex+1], nOffset ), SubFloat( xyz[nIndex+2], nOffset ) ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.AdvanceVertex(); + } + meshBuilder.End(); + pMesh->Draw(); + } +} + +//----------------------------------------------------------------------------- +// +// Sprite Rendering +// +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Utility struct to help with sprite rendering +//----------------------------------------------------------------------------- +struct SpriteRenderInfo_t +{ + size_t m_nXYZStride; + const fltx4 *m_pXYZ; + size_t m_nRotStride; + const fltx4 *m_pRot; + size_t m_nYawStride; + const fltx4 *m_pYaw; + size_t m_nRGBStride; + const fltx4 *m_pRGB; + size_t m_nCreationTimeStride; + const fltx4 *m_pCreationTimeStamp; + size_t m_nSequenceStride; + const fltx4 *m_pSequenceNumber; + size_t m_nSequence1Stride; + const fltx4 *m_pSequence1Number; + float m_flAgeScale; + float m_flAgeScale2; + + CSheet *m_pSheet; + int m_nVertexOffset; + CParticleCollection *m_pParticles; + + void Init( CParticleCollection *pParticles, int nVertexOffset, float flAgeScale, float flAgeScale2, CSheet *pSheet ) + { + m_pParticles = pParticles; + m_pXYZ = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_XYZ, &m_nXYZStride ); + m_pRot = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_ROTATION, &m_nRotStride ); + m_pYaw = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_YAW, &m_nYawStride ); + m_pRGB = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_TINT_RGB, &m_nRGBStride ); + m_pCreationTimeStamp = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, &m_nCreationTimeStride ); + m_pSequenceNumber = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER, &m_nSequenceStride ); + m_pSequence1Number = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER1, &m_nSequence1Stride ); + m_flAgeScale = flAgeScale; + m_flAgeScale2 = flAgeScale2; + m_pSheet = pSheet; + m_nVertexOffset = nVertexOffset; + } +}; + +class C_OP_RenderSprites : public C_OP_RenderPoints +{ + DECLARE_PARTICLE_OPERATOR( C_OP_RenderSprites ); + + struct C_OP_RenderSpritesContext_t + { + unsigned int m_nOrientationVarToken; + unsigned int m_nOrientationMatrixVarToken; + CParticleVisibilityData m_VisibilityData; + int m_nQueryHandle; + }; + + size_t GetRequiredContextBytes( void ) const + { + return sizeof( C_OP_RenderSpritesContext_t ); + } + + virtual void InitializeContextData( CParticleCollection *pParticles, void *pContext ) const + { + C_OP_RenderSpritesContext_t *pCtx = reinterpret_cast( pContext ); + pCtx->m_nOrientationVarToken = 0; + pCtx->m_nOrientationMatrixVarToken = 0; + if ( VisibilityInputs.m_nCPin >= 0 ) + pCtx->m_VisibilityData.m_bUseVisibility = true; + else + pCtx->m_VisibilityData.m_bUseVisibility = false; + + pCtx->m_VisibilityData.m_flCameraBias = VisibilityInputs.m_flCameraBias; + } + + virtual uint64 GetReadControlPointMask() const + { + if ( m_nOrientationControlPoint >= 0 ) + return 1ULL << m_nOrientationControlPoint; + return 0; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_ROTATION_MASK | PARTICLE_ATTRIBUTE_RADIUS_MASK | + PARTICLE_ATTRIBUTE_TINT_RGB_MASK | PARTICLE_ATTRIBUTE_ALPHA_MASK | PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | + PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER1_MASK | + PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER_MASK | PARTICLE_ATTRIBUTE_LIFE_DURATION_MASK; + } + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ); + virtual int GetParticlesToRender( CParticleCollection *pParticles, void *pContext, int nFirstParticle, int nRemainingVertices, int nRemainingIndices, int *pVertsUsed, int *pIndicesUsed ) const; + virtual void Render( IMatRenderContext *pRenderContext, CParticleCollection *pParticles, void *pContext ) const; + virtual void RenderUnsorted( CParticleCollection *pParticles, void *pContext, IMatRenderContext *pRenderContext, CMeshBuilder &meshBuilder, int nVertexOffset, int nFirstParticle, int nParticleCount ) const; + void RenderSpriteCard( CMeshBuilder &meshBuilder, C_OP_RenderSpritesContext_t *pCtx, SpriteRenderInfo_t& info, int hParticle, ParticleRenderData_t const *pSortList, Vector *pCamera ) const; + void RenderTwoSequenceSpriteCard( CMeshBuilder &meshBuilder, C_OP_RenderSpritesContext_t *pCtx, SpriteRenderInfo_t& info, int hParticle, ParticleRenderData_t const *pSortList, Vector *pCamera ) const; + + void RenderNonSpriteCardCameraFacing( CParticleCollection *pParticles, void *pContext, IMatRenderContext *pRenderContext, IMaterial *pMaterial ) const; + + void RenderNonSpriteCardZRotating( CMeshBuilder &meshBuilder, C_OP_RenderSpritesContext_t *pCtx, SpriteRenderInfo_t& info, int hParticle, const Vector& vecCameraPos, ParticleRenderData_t const *pSortList ) const; + void RenderNonSpriteCardZRotating( CParticleCollection *pParticles, void *pContext, IMatRenderContext *pRenderContext, IMaterial *pMaterial ) const; + void RenderUnsortedNonSpriteCardZRotating( CParticleCollection *pParticles, void *pContext, IMatRenderContext *pRenderContext, CMeshBuilder &meshBuilder, int nVertexOffset, int nFirstParticle, int nParticleCount ) const; + + void RenderNonSpriteCardOriented( CMeshBuilder &meshBuilder, C_OP_RenderSpritesContext_t *pCtx, SpriteRenderInfo_t& info, int hParticle, const Vector& vecCameraPos, ParticleRenderData_t const *pSortList, bool bUseYaw ) const; + void RenderNonSpriteCardOriented( CParticleCollection *pParticles, void *pContext, IMatRenderContext *pRenderContext, IMaterial *pMaterial, bool bUseYaw ) const; + void RenderUnsortedNonSpriteCardOriented( CParticleCollection *pParticles, void *pContext, IMatRenderContext *pRenderContext, CMeshBuilder &meshBuilder, int nVertexOffset, int nFirstParticle, int nParticleCount ) const; + + // cycles per second + float m_flAnimationRate; + float m_flAnimationRate2; + bool m_bFitCycleToLifetime; + bool m_bAnimateInFPS; + int m_nOrientationType; + + + int m_nOrientationControlPoint; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_RenderSprites, "render_animated_sprites", OPERATOR_GENERIC ); + +BEGIN_PARTICLE_RENDER_OPERATOR_UNPACK( C_OP_RenderSprites ) + DMXELEMENT_UNPACK_FIELD( "animation rate", ".1", float, m_flAnimationRate ) + DMXELEMENT_UNPACK_FIELD( "animation_fit_lifetime", "0", bool, m_bFitCycleToLifetime ) + DMXELEMENT_UNPACK_FIELD( "orientation_type", "0", int, m_nOrientationType ) + DMXELEMENT_UNPACK_FIELD( "orientation control point", "-1", int, m_nOrientationControlPoint ) + DMXELEMENT_UNPACK_FIELD( "second sequence animation rate", "0", float, m_flAnimationRate2 ) + DMXELEMENT_UNPACK_FIELD( "use animation rate as FPS", "0", bool, m_bAnimateInFPS ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_RenderSprites ) + +void C_OP_RenderSprites::InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) +{ +} + +const SheetSequenceSample_t *GetSampleForSequence( CSheet *pSheet, float flCreationTime, float flCurTime, float flAgeScale, int nSequence ) +{ + if ( pSheet == NULL ) + return NULL; + + if ( pSheet->m_nNumFrames[nSequence] == 1 ) + return (const SheetSequenceSample_t *) &pSheet->m_pSamples[nSequence][0]; + + float flAge = flCurTime - flCreationTime; + + flAge *= flAgeScale; + unsigned int nFrame = flAge; + if ( pSheet->m_bClamp[nSequence] ) + { + nFrame = min( nFrame, (unsigned int)SEQUENCE_SAMPLE_COUNT-1 ); + } + else + { + nFrame &= SEQUENCE_SAMPLE_COUNT-1; + } + + return (const SheetSequenceSample_t *) &pSheet->m_pSamples[nSequence][nFrame]; +} + +int C_OP_RenderSprites::GetParticlesToRender( CParticleCollection *pParticles, + void *pContext, int nFirstParticle, + int nRemainingVertices, int nRemainingIndices, + int *pVertsUsed, int *pIndicesUsed ) const +{ + int nMaxParticles = ( (nRemainingVertices / 4) > (nRemainingIndices / 6) ) ? nRemainingIndices / 6 : nRemainingVertices / 4; + int nParticleCount = pParticles->m_nActiveParticles - nFirstParticle; + if ( nParticleCount > nMaxParticles ) + { + nParticleCount = nMaxParticles; + } + *pVertsUsed = nParticleCount * 4; + *pIndicesUsed = nParticleCount * 6; + return nParticleCount; +} + +void C_OP_RenderSprites::RenderNonSpriteCardCameraFacing( CParticleCollection *pParticles, void *pContext, IMatRenderContext *pRenderContext, IMaterial *pMaterial ) const +{ + + C_OP_RenderSpritesContext_t *pCtx = reinterpret_cast( pContext ); + + // generate the sort list before this code starts messing with the matrices + int nParticles; + const ParticleRenderData_t *pSortList = pParticles->GetRenderList( pRenderContext, true, &nParticles, &pCtx->m_VisibilityData ); + + bool bCameraBias = ( &pCtx->m_VisibilityData )->m_flCameraBias != 0.0f; + float flCameraBias = (&pCtx->m_VisibilityData )->m_flCameraBias; + + Vector vecCamera; + pRenderContext->GetWorldSpaceCameraPosition( &vecCamera ); + + // NOTE: This is interesting to support because at first we won't have all the various + // pixel-shader versions of SpriteCard, like modulate, twotexture, etc. etc. + VMatrix tempView; + + // Store matrices off so we can restore them in RenderEnd(). + pRenderContext->GetMatrix(MATERIAL_VIEW, &tempView); + + // Force the user clip planes to use the old view matrix + pRenderContext->EnableUserClipTransformOverride( true ); + pRenderContext->UserClipTransform( tempView ); + + // The particle renderers want to do things in camera space + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + + size_t xyz_stride; + const fltx4 *xyz = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_XYZ, &xyz_stride ); + + size_t rot_stride; + const fltx4 *pRot = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_ROTATION, &rot_stride ); + + size_t rgb_stride; + const fltx4 *pRGB = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_TINT_RGB, &rgb_stride ); + + size_t ct_stride; + const fltx4 *pCreationTimeStamp = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, &ct_stride ); + + size_t seq_stride; + const fltx4 *pSequenceNumber = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER, &seq_stride ); + + size_t ld_stride; + const fltx4 *pLifeDuration = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_LIFE_DURATION, &ld_stride ); + + + float flAgeScale; + int nMaxParticlesInBatch = GetMaxParticlesPerBatch( pRenderContext, pMaterial, false ); + + CSheet *pSheet = pParticles->m_Sheet(); + while ( nParticles ) + { + int nParticlesInBatch = min( nMaxParticlesInBatch, nParticles ); + nParticles -= nParticlesInBatch; + g_pParticleSystemMgr->TallyParticlesRendered( nParticlesInBatch * 4 ); + + IMesh* pMesh = pRenderContext->GetDynamicMesh( true ); + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_QUADS, nParticlesInBatch ); + for( int i = 0; i < nParticlesInBatch; i++ ) + { + int hParticle = (--pSortList)->m_nIndex; + int nGroup = hParticle / 4; + int nOffset = hParticle & 0x3; + + unsigned char ac = pSortList->m_nAlpha; + if ( ac == 0 ) + continue; + + int nColorIndex = nGroup * rgb_stride; + float r = SubFloat( pRGB[nColorIndex], nOffset ); + float g = SubFloat( pRGB[nColorIndex+1], nOffset ); + float b = SubFloat( pRGB[nColorIndex+2], nOffset ); + + Assert( IsFinite(r) && IsFinite(g) && IsFinite(b) ); + Assert( (r >= 0.0f) && (g >= 0.0f) && (b >= 0.0f) ); + Assert( (r <= 1.0f) && (g <= 1.0f) && (b <= 1.0f) ); + + unsigned char rc = FastFToC( r ); + unsigned char gc = FastFToC( g ); + unsigned char bc = FastFToC( b ); + + float rad = pSortList->m_flRadius; + + int nXYZIndex = nGroup * xyz_stride; + Vector vecWorldPos( SubFloat( xyz[ nXYZIndex ], nOffset ), SubFloat( xyz[ nXYZIndex+1 ], nOffset ), SubFloat( xyz[ nXYZIndex+2 ], nOffset ) ); + + // Move the Particle if their is a camerabias + if ( bCameraBias ) + { + Vector vEyeDir = vecCamera - vecWorldPos; + VectorNormalizeFast( vEyeDir ); + vEyeDir *= flCameraBias; + vecWorldPos += vEyeDir; + } + + Vector vecViewPos; + Vector3DMultiplyPosition( tempView, vecWorldPos, vecViewPos ); + + if ( !IsFinite( vecViewPos.x ) ) + continue; + + float rot = SubFloat( pRot[ nGroup * rot_stride ], nOffset ); + float ca = (float)cos(rot); + float sa = (float)sin(rot); + + // Find the sample for this frame + const SheetSequenceSample_t *pSample = &s_DefaultSheetSequence; + if ( pSheet ) + { + if ( m_bFitCycleToLifetime ) + { + float flLifetime = SubFloat( pLifeDuration[ nGroup * ld_stride ], nOffset ); + flAgeScale = ( flLifetime > 0.0f ) ? ( 1.0f / flLifetime ) * SEQUENCE_SAMPLE_COUNT : 0.0f; + } + else + { + flAgeScale = m_flAnimationRate * SEQUENCE_SAMPLE_COUNT; + if ( m_bAnimateInFPS ) + { + int nSequence = SubFloat( pSequenceNumber[ nGroup * seq_stride ], nOffset ); + flAgeScale = flAgeScale / pSheet->m_flFrameSpan[nSequence]; + } + } + pSample = GetSampleForSequence( pSheet, + SubFloat( pCreationTimeStamp[ nGroup * ct_stride ], nOffset ), + pParticles->m_flCurTime, + flAgeScale, + SubFloat( pSequenceNumber[ nGroup * seq_stride ], nOffset ) ); + } + const SequenceSampleTextureCoords_t *pSample0 = &(pSample->m_TextureCoordData[0]); + + meshBuilder.Position3f( vecViewPos.x + (-ca + sa) * rad, vecViewPos.y + (-sa - ca) * rad, vecViewPos.z ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord2f( 0, pSample0->m_fLeft_U0, pSample0->m_fBottom_V0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( vecViewPos.x + (-ca - sa) * rad, vecViewPos.y + (-sa + ca) * rad, vecViewPos.z ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord2f( 0, pSample0->m_fLeft_U0, pSample0->m_fTop_V0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( vecViewPos.x + (ca - sa) * rad, vecViewPos.y + (sa + ca) * rad, vecViewPos.z ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord2f( 0, pSample0->m_fRight_U0, pSample0->m_fTop_V0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( vecViewPos.x + (ca + sa) * rad, vecViewPos.y + (sa - ca) * rad, vecViewPos.z ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord2f( 0, pSample0->m_fRight_U0, pSample0->m_fBottom_V0 ); + meshBuilder.AdvanceVertex(); + } + meshBuilder.End(); + pMesh->Draw(); + } + + pRenderContext->EnableUserClipTransformOverride( false ); + + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->PopMatrix(); +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_OP_RenderSprites::RenderNonSpriteCardZRotating( CMeshBuilder &meshBuilder, C_OP_RenderSpritesContext_t *pCtx, SpriteRenderInfo_t& info, int hParticle, const Vector& vecCameraPos, ParticleRenderData_t const *pSortList ) const +{ + Assert( hParticle != -1 ); + int nGroup = hParticle / 4; + int nOffset = hParticle & 0x3; + + unsigned char ac = pSortList->m_nAlpha; + if ( ac == 0 ) + return; + + bool bCameraBias = ( &pCtx->m_VisibilityData )->m_flCameraBias != 0.0f; + float flCameraBias = ( &pCtx->m_VisibilityData )->m_flCameraBias; + + int nColorIndex = nGroup * info.m_nRGBStride; + float r = SubFloat( info.m_pRGB[nColorIndex], nOffset ); + float g = SubFloat( info.m_pRGB[nColorIndex+1], nOffset ); + float b = SubFloat( info.m_pRGB[nColorIndex+2], nOffset ); + + Assert( IsFinite(r) && IsFinite(g) && IsFinite(b) ); + Assert( (r >= 0.0f) && (g >= 0.0f) && (b >= 0.0f) ); + Assert( (r <= 1.0f) && (g <= 1.0f) && (b <= 1.0f) ); + + unsigned char rc = FastFToC( r ); + unsigned char gc = FastFToC( g ); + unsigned char bc = FastFToC( b ); + + float rad = pSortList->m_flRadius; + float rot = SubFloat( info.m_pRot[ nGroup * info.m_nRotStride ], nOffset ); + + float ca = (float)cos(-rot); + float sa = (float)sin(-rot); + + int nXYZIndex = nGroup * info.m_nXYZStride; + Vector vecWorldPos( SubFloat( info.m_pXYZ[ nXYZIndex ], nOffset ), SubFloat( info.m_pXYZ[ nXYZIndex+1 ], nOffset ), SubFloat( info.m_pXYZ[ nXYZIndex+2 ], nOffset ) ); + + // Move the Particle if their is a camerabias + if ( bCameraBias ) + { + Vector vEyeDir = vecCameraPos - vecWorldPos; + VectorNormalizeFast( vEyeDir ); + vEyeDir *= flCameraBias; + vecWorldPos += vEyeDir; + } + + Vector vecViewToPos; + VectorSubtract( vecWorldPos, vecCameraPos, vecViewToPos ); + float flLength = vecViewToPos.Length(); + if ( flLength < rad / 2 ) + return; + + Vector vecUp( 0, 0, 1 ); + Vector vecRight; + CrossProduct( vecUp, vecCameraPos, vecRight ); + VectorNormalize( vecRight ); + + // Find the sample for this frame + const SheetSequenceSample_t *pSample = &s_DefaultSheetSequence; + if ( info.m_pSheet ) + { + pSample = GetSampleForSequence( info.m_pSheet, + SubFloat( info.m_pCreationTimeStamp[ nGroup * info.m_nCreationTimeStride ], nOffset ), + info.m_pParticles->m_flCurTime, + info.m_flAgeScale, + SubFloat( info.m_pSequenceNumber[ nGroup * info.m_nSequenceStride ], nOffset ) ); + } + + const SequenceSampleTextureCoords_t *pSample0 = &(pSample->m_TextureCoordData[0]); + vecRight *= rad; + + float x, y; + Vector vecCorner; + + x = - ca - sa; y = - ca + sa; + VectorMA( vecWorldPos, x, vecRight, vecCorner ); + meshBuilder.Position3f( vecCorner.x, vecCorner.y, vecCorner.z + y * rad ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord2f( 0, pSample0->m_fLeft_U0, pSample0->m_fBottom_V0 ); + meshBuilder.AdvanceVertex(); + + x = - ca + sa; y = + ca + sa; + VectorMA( vecWorldPos, x, vecRight, vecCorner ); + meshBuilder.Position3f( vecCorner.x, vecCorner.y, vecCorner.z + y * rad ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord2f( 0, pSample0->m_fLeft_U0, pSample0->m_fTop_V0 ); + meshBuilder.AdvanceVertex(); + + x = + ca + sa; y = + ca - sa; + VectorMA( vecWorldPos, x, vecRight, vecCorner ); + meshBuilder.Position3f( vecCorner.x, vecCorner.y, vecCorner.z + y * rad ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord2f( 0, pSample0->m_fRight_U0, pSample0->m_fTop_V0 ); + meshBuilder.AdvanceVertex(); + + x = + ca - sa; y = - ca - sa; + VectorMA( vecWorldPos, x, vecRight, vecCorner ); + meshBuilder.Position3f( vecCorner.x, vecCorner.y, vecCorner.z + y * rad ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord2f( 0, pSample0->m_fRight_U0, pSample0->m_fBottom_V0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.FastIndex( info.m_nVertexOffset ); + meshBuilder.FastIndex( info.m_nVertexOffset + 1 ); + meshBuilder.FastIndex( info.m_nVertexOffset + 2 ); + meshBuilder.FastIndex( info.m_nVertexOffset ); + meshBuilder.FastIndex( info.m_nVertexOffset + 2 ); + meshBuilder.FastIndex( info.m_nVertexOffset + 3 ); + info.m_nVertexOffset += 4; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_OP_RenderSprites::RenderNonSpriteCardZRotating( CParticleCollection *pParticles, void *pContext, IMatRenderContext *pRenderContext, IMaterial *pMaterial ) const +{ + C_OP_RenderSpritesContext_t *pCtx = reinterpret_cast( pContext ); + + // NOTE: This is interesting to support because at first we won't have all the various + // pixel-shader versions of SpriteCard, like modulate, twotexture, etc. etc. + Vector vecCameraPos; + pRenderContext->GetWorldSpaceCameraPosition( &vecCameraPos ); + float flAgeScale = m_flAnimationRate * SEQUENCE_SAMPLE_COUNT; + + SpriteRenderInfo_t info; + info.Init( pParticles, 0, flAgeScale, 0, pParticles->m_Sheet() ); + + int nParticles; + const ParticleRenderData_t *pSortList = pParticles->GetRenderList( pRenderContext, true, &nParticles, &pCtx->m_VisibilityData ); + + int nMaxParticlesInBatch = GetMaxParticlesPerBatch( pRenderContext, pMaterial, false ); + while ( nParticles ) + { + int nParticlesInBatch = min( nMaxParticlesInBatch, nParticles ); + nParticles -= nParticlesInBatch; + + g_pParticleSystemMgr->TallyParticlesRendered( nParticlesInBatch * 4 * 3, nParticlesInBatch * 6 * 3 ); + + IMesh* pMesh = pRenderContext->GetDynamicMesh( true ); + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nParticlesInBatch * 4, nParticlesInBatch * 6 ); + info.m_nVertexOffset = 0; + + for( int i = 0; i < nParticlesInBatch; i++ ) + { + int hParticle = (--pSortList)->m_nIndex; + RenderNonSpriteCardZRotating( meshBuilder, pCtx, info, hParticle, vecCameraPos, pSortList ); + } + meshBuilder.End(); + pMesh->Draw(); + } +} + +void C_OP_RenderSprites::RenderUnsortedNonSpriteCardZRotating( CParticleCollection *pParticles, void *pContext, IMatRenderContext *pRenderContext, CMeshBuilder &meshBuilder, int nVertexOffset, int nFirstParticle, int nParticleCount ) const +{ + C_OP_RenderSpritesContext_t *pCtx = reinterpret_cast( pContext ); + // NOTE: This is interesting to support because at first we won't have all the various + // pixel-shader versions of SpriteCard, like modulate, twotexture, etc. etc. + Vector vecCameraPos; + pRenderContext->GetWorldSpaceCameraPosition( &vecCameraPos ); + + float flAgeScale = m_flAnimationRate * SEQUENCE_SAMPLE_COUNT; + SpriteRenderInfo_t info; + info.Init( pParticles, nVertexOffset, flAgeScale, 0, pParticles->m_Sheet() ); + + int nParticles; + const ParticleRenderData_t *pSortList = pParticles->GetRenderList( pRenderContext, false, &nParticles, &pCtx->m_VisibilityData ); + + int hParticle = nFirstParticle; + for( int i = 0; i < nParticleCount; i++, hParticle++ ) + { + RenderNonSpriteCardZRotating( meshBuilder, pCtx, info, hParticle, vecCameraPos, pSortList ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_OP_RenderSprites::RenderNonSpriteCardOriented( + CMeshBuilder &meshBuilder, C_OP_RenderSpritesContext_t *pCtx, SpriteRenderInfo_t& info, int hParticle, const Vector& vecCameraPos, ParticleRenderData_t const *pSortList, bool bUseYaw ) const +{ + Assert( hParticle != -1 ); + int nGroup = hParticle / 4; + int nOffset = hParticle & 0x3; + + unsigned char ac = pSortList->m_nAlpha; + if ( ac == 0 ) + return; + + bool bCameraBias = ( &pCtx->m_VisibilityData )->m_flCameraBias != 0.0f; + float flCameraBias = ( &pCtx->m_VisibilityData )->m_flCameraBias; + + int nColorIndex = nGroup * info.m_nRGBStride; + float r = SubFloat( info.m_pRGB[nColorIndex], nOffset ); + float g = SubFloat( info.m_pRGB[nColorIndex+1], nOffset ); + float b = SubFloat( info.m_pRGB[nColorIndex+2], nOffset ); + + Assert( IsFinite(r) && IsFinite(g) && IsFinite(b) ); + Assert( (r >= 0.0f) && (g >= 0.0f) && (b >= 0.0f) ); + Assert( (r <= 1.0f) && (g <= 1.0f) && (b <= 1.0f) ); + + unsigned char rc = FastFToC( r ); + unsigned char gc = FastFToC( g ); + unsigned char bc = FastFToC( b ); + + float rad = pSortList->m_flRadius; + float rot = SubFloat( info.m_pRot[ nGroup * info.m_nRotStride ], nOffset ); + + float ca = (float)cos(-rot); + float sa = (float)sin(-rot); + + int nXYZIndex = nGroup * info.m_nXYZStride; + Vector vecWorldPos( SubFloat( info.m_pXYZ[ nXYZIndex ], nOffset ), SubFloat( info.m_pXYZ[ nXYZIndex+1 ], nOffset ), SubFloat( info.m_pXYZ[ nXYZIndex+2 ], nOffset ) ); + + // Move the Particle if their is a camerabias + if ( bCameraBias ) + { + Vector vEyeDir = vecCameraPos - vecWorldPos; + VectorNormalizeFast( vEyeDir ); + vEyeDir *= flCameraBias; + vecWorldPos += vEyeDir; + } + + Vector vecViewToPos; + VectorSubtract( vecWorldPos, vecCameraPos, vecViewToPos ); + float flLength = vecViewToPos.Length(); + if ( flLength < rad / 2 ) + return; + + Vector vecNormal, vecRight, vecUp; + if ( m_nOrientationControlPoint < 0 ) + { + vecNormal.Init( 0, 0, 1 ); + vecRight.Init( 1, 0, 0 ); + vecUp.Init( 0, -1, 0 ); + } + else + { + info.m_pParticles->GetControlPointOrientationAtCurrentTime( + m_nOrientationControlPoint, &vecRight, &vecUp, &vecNormal ); + } + + if ( bUseYaw ) + { + float yaw = SubFloat( info.m_pYaw[nGroup * info.m_nYawStride], nOffset ); + + if ( yaw != 0.0f ) + { + Vector particleRight = Vector( 1, 0, 0 ); + yaw = RAD2DEG( yaw ); // I hate you source (VectorYawRotate will undo this) + matrix3x4_t matRot; + MatrixBuildRotationAboutAxis( vecUp, yaw, matRot ); + VectorRotate( vecRight, matRot, particleRight ); + vecRight = particleRight; + } + } + + // Find the sample for this frame + const SheetSequenceSample_t *pSample = &s_DefaultSheetSequence; + if ( info.m_pSheet ) + { + pSample = GetSampleForSequence( info.m_pSheet, + SubFloat( info.m_pCreationTimeStamp[ nGroup * info.m_nCreationTimeStride ], nOffset ), + info.m_pParticles->m_flCurTime, + info.m_flAgeScale, + SubFloat( info.m_pSequenceNumber[ nGroup * info.m_nSequenceStride ], nOffset ) ); + } + + const SequenceSampleTextureCoords_t *pSample0 = &(pSample->m_TextureCoordData[0]); + vecRight *= rad; + vecUp *= rad; + + float x, y; + Vector vecCorner; + + x = + ca - sa; y = - ca - sa; + VectorMA( vecWorldPos, x, vecRight, vecCorner ); + VectorMA( vecCorner, y, vecUp, vecCorner ); + meshBuilder.Position3f( vecCorner.x, vecCorner.y, vecCorner.z ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord2f( 0, pSample0->m_fRight_U0, pSample0->m_fBottom_V0 ); + meshBuilder.AdvanceVertex(); + + x = + ca + sa; y = + ca - sa; + VectorMA( vecWorldPos, x, vecRight, vecCorner ); + VectorMA( vecCorner, y, vecUp, vecCorner ); + meshBuilder.Position3f( vecCorner.x, vecCorner.y, vecCorner.z ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord2f( 0, pSample0->m_fRight_U0, pSample0->m_fTop_V0 ); + meshBuilder.AdvanceVertex(); + + x = - ca + sa; y = + ca + sa; + VectorMA( vecWorldPos, x, vecRight, vecCorner ); + VectorMA( vecCorner, y, vecUp, vecCorner ); + meshBuilder.Position3f( vecCorner.x, vecCorner.y, vecCorner.z ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord2f( 0, pSample0->m_fLeft_U0, pSample0->m_fTop_V0 ); + meshBuilder.AdvanceVertex(); + + x = - ca - sa; y = - ca + sa; + VectorMA( vecWorldPos, x, vecRight, vecCorner ); + VectorMA( vecCorner, y, vecUp, vecCorner ); + meshBuilder.Position3f( vecCorner.x, vecCorner.y, vecCorner.z ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord2f( 0, pSample0->m_fLeft_U0, pSample0->m_fBottom_V0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.FastIndex( info.m_nVertexOffset ); + meshBuilder.FastIndex( info.m_nVertexOffset + 1 ); + meshBuilder.FastIndex( info.m_nVertexOffset + 2 ); + meshBuilder.FastIndex( info.m_nVertexOffset ); + meshBuilder.FastIndex( info.m_nVertexOffset + 2 ); + meshBuilder.FastIndex( info.m_nVertexOffset + 3 ); + info.m_nVertexOffset += 4; +} + +void C_OP_RenderSprites::RenderNonSpriteCardOriented( CParticleCollection *pParticles, void *pContext, IMatRenderContext *pRenderContext, IMaterial *pMaterial, bool bUseYaw ) const +{ + C_OP_RenderSpritesContext_t *pCtx = reinterpret_cast( pContext ); + + // NOTE: This is interesting to support because at first we won't have all the various + // pixel-shader versions of SpriteCard, like modulate, twotexture, etc. etc. + Vector vecCameraPos; + pRenderContext->GetWorldSpaceCameraPosition( &vecCameraPos ); + + float flAgeScale = m_flAnimationRate * SEQUENCE_SAMPLE_COUNT; + SpriteRenderInfo_t info; + info.Init( pParticles, 0, flAgeScale, 0, pParticles->m_Sheet() ); + + int nParticles; + const ParticleRenderData_t *pSortList = pParticles->GetRenderList( pRenderContext, true, &nParticles, &pCtx->m_VisibilityData ); + + int nMaxParticlesInBatch = GetMaxParticlesPerBatch( pRenderContext, pMaterial, false ); + while ( nParticles ) + { + int nParticlesInBatch = min( nMaxParticlesInBatch, nParticles ); + nParticles -= nParticlesInBatch; + + g_pParticleSystemMgr->TallyParticlesRendered( nParticlesInBatch * 4 * 3, nParticlesInBatch * 6 * 3 ); + + IMesh* pMesh = pRenderContext->GetDynamicMesh( true ); + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nParticlesInBatch * 4, nParticlesInBatch * 6 ); + info.m_nVertexOffset = 0; + + for( int i = 0; i < nParticlesInBatch; i++) + { + int hParticle = (--pSortList)->m_nIndex; + RenderNonSpriteCardOriented( meshBuilder, pCtx, info, hParticle, vecCameraPos, pSortList, bUseYaw ); + } + + meshBuilder.End(); + pMesh->Draw(); + } +} + +void C_OP_RenderSprites::RenderUnsortedNonSpriteCardOriented( CParticleCollection *pParticles, void *pContext, IMatRenderContext *pRenderContext, CMeshBuilder &meshBuilder, int nVertexOffset, int nFirstParticle, int nParticleCount ) const +{ + C_OP_RenderSpritesContext_t *pCtx = reinterpret_cast( pContext ); + // NOTE: This is interesting to support because at first we won't have all the various + // pixel-shader versions of SpriteCard, like modulate, twotexture, etc. etc. + Vector vecCameraPos; + pRenderContext->GetWorldSpaceCameraPosition( &vecCameraPos ); + + float flAgeScale = m_flAnimationRate * SEQUENCE_SAMPLE_COUNT; + SpriteRenderInfo_t info; + info.Init( pParticles, nVertexOffset, flAgeScale, 0, pParticles->m_Sheet() ); + + int nParticles; + const ParticleRenderData_t *pSortList = pParticles->GetRenderList( pRenderContext, false, &nParticles, &pCtx->m_VisibilityData ); + + int hParticle = nFirstParticle; + for( int i = 0; i < nParticleCount; i++, hParticle++ ) + { + RenderNonSpriteCardOriented( meshBuilder, pCtx, info, hParticle, vecCameraPos, pSortList, false ); + } +} + +void C_OP_RenderSprites::RenderSpriteCard( CMeshBuilder &meshBuilder, C_OP_RenderSpritesContext_t *pCtx, SpriteRenderInfo_t& info, int hParticle, ParticleRenderData_t const *pSortList, Vector *pCamera ) const +{ + Assert( hParticle != -1 ); + int nGroup = hParticle / 4; + int nOffset = hParticle & 0x3; + + int nColorIndex = nGroup * info.m_nRGBStride; + float r = SubFloat( info.m_pRGB[nColorIndex], nOffset ); + float g = SubFloat( info.m_pRGB[nColorIndex+1], nOffset ); + float b = SubFloat( info.m_pRGB[nColorIndex+2], nOffset ); + + Assert( IsFinite(r) && IsFinite(g) && IsFinite(b) ); + Assert( (r >= -1e-6f) && (g >= -1e-6f) && (b >= -1e-6f) ); + if ( !HushAsserts() ) + Assert( (r <= 1.0f) && (g <= 1.0f) && (b <= 1.0f) ); + + unsigned char rc = FastFToC( r ); + unsigned char gc = FastFToC( g ); + unsigned char bc = FastFToC( b ); + unsigned char ac = pSortList->m_nAlpha; + + float rad = pSortList->m_flRadius; + if ( !IsFinite( rad ) ) + { + return; + } + + bool bCameraBias = ( &pCtx->m_VisibilityData )->m_flCameraBias != 0.0f; + float flCameraBias = ( &pCtx->m_VisibilityData )->m_flCameraBias; + + float rot = SubFloat( info.m_pRot[ nGroup * info.m_nRotStride ], nOffset ); + float yaw = SubFloat( info.m_pYaw[ nGroup * info.m_nYawStride ], nOffset ); + + int nXYZIndex = nGroup * info.m_nXYZStride; + Vector vecWorldPos; + vecWorldPos.x = SubFloat( info.m_pXYZ[ nXYZIndex ], nOffset ); + vecWorldPos.y = SubFloat( info.m_pXYZ[ nXYZIndex+1 ], nOffset ); + vecWorldPos.z = SubFloat( info.m_pXYZ[ nXYZIndex+2 ], nOffset ); + + if ( bCameraBias ) + { + Vector vEyeDir = *pCamera - vecWorldPos; + VectorNormalizeFast( vEyeDir ); + vEyeDir *= flCameraBias; + vecWorldPos += vEyeDir; + } + + // Find the sample for this frame + const SheetSequenceSample_t *pSample = &s_DefaultSheetSequence; + if ( info.m_pSheet ) + { + float flAgeScale = info.m_flAgeScale; +// if ( m_bFitCycleToLifetime ) +// { +// float flLifetime = SubFloat( pLifeDuration[ nGroup * ld_stride ], nOffset ); +// flAgeScale = ( flLifetime > 0.0f ) ? ( 1.0f / flLifetime ) * SEQUENCE_SAMPLE_COUNT : 0.0f; +// } + if ( m_bAnimateInFPS ) + { + int nSequence = SubFloat( info.m_pSequenceNumber[ nGroup * info.m_nSequenceStride ], nOffset ); + flAgeScale = flAgeScale / info.m_pParticles->m_Sheet()->m_flFrameSpan[nSequence]; + } + pSample = GetSampleForSequence( info.m_pSheet, + SubFloat( info.m_pCreationTimeStamp[ nGroup * info.m_nCreationTimeStride ], nOffset ), + info.m_pParticles->m_flCurTime, + flAgeScale, + SubFloat( info.m_pSequenceNumber[ nGroup * info.m_nSequenceStride ], nOffset ) ); + } + + const SequenceSampleTextureCoords_t *pSample0 = &(pSample->m_TextureCoordData[0]); + const SequenceSampleTextureCoords_t *pSecondTexture0 = &(pSample->m_TextureCoordData[1]); + + // Submit 1 (instanced) or 4 (non-instanced) verts (if we're instancing, we don't produce indices either) + meshBuilder.Position3f( vecWorldPos.x, vecWorldPos.y, vecWorldPos.z ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord4f( 0, pSample0->m_fLeft_U0, pSample0->m_fTop_V0, pSample0->m_fRight_U0, pSample0->m_fBottom_V0 ); + meshBuilder.TexCoord4f( 1, pSample0->m_fLeft_U1, pSample0->m_fTop_V1, pSample0->m_fRight_U1, pSample0->m_fBottom_V1 ); + meshBuilder.TexCoord4f( 2, pSample->m_fBlendFactor, rot, rad, yaw ); + // FIXME: change the vertex decl (remove texcoord3/cornerid) if instancing - need to adjust elements beyond texcoord3 down, though + if ( !bUseInstancing ) + meshBuilder.TexCoord2f( 3, 0, 0 ); + meshBuilder.TexCoord4f( 4, pSecondTexture0->m_fLeft_U0, pSecondTexture0->m_fTop_V0, pSecondTexture0->m_fRight_U0, pSecondTexture0->m_fBottom_V0 ); + meshBuilder.AdvanceVertex(); + + if ( !bUseInstancing ) + { + meshBuilder.Position3f( vecWorldPos.x, vecWorldPos.y, vecWorldPos.z ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord4f( 0, pSample0->m_fLeft_U0, pSample0->m_fTop_V0, pSample0->m_fRight_U0, pSample0->m_fBottom_V0 ); + meshBuilder.TexCoord4f( 1, pSample0->m_fLeft_U1, pSample0->m_fTop_V1, pSample0->m_fRight_U1, pSample0->m_fBottom_V1 ); + meshBuilder.TexCoord4f( 2, pSample->m_fBlendFactor, rot, rad, yaw ); + meshBuilder.TexCoord2f( 3, 1, 0 ); + meshBuilder.TexCoord4f( 4, pSecondTexture0->m_fLeft_U0, pSecondTexture0->m_fTop_V0, pSecondTexture0->m_fRight_U0, pSecondTexture0->m_fBottom_V0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( vecWorldPos.x, vecWorldPos.y, vecWorldPos.z ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord4f( 0, pSample0->m_fLeft_U0, pSample0->m_fTop_V0, pSample0->m_fRight_U0, pSample0->m_fBottom_V0 ); + meshBuilder.TexCoord4f( 1, pSample0->m_fLeft_U1, pSample0->m_fTop_V1, pSample0->m_fRight_U1, pSample0->m_fBottom_V1 ); + meshBuilder.TexCoord4f( 2, pSample->m_fBlendFactor, rot, rad, yaw ); + meshBuilder.TexCoord2f( 3, 1, 1 ); + meshBuilder.TexCoord4f( 4, pSecondTexture0->m_fLeft_U0, pSecondTexture0->m_fTop_V0, pSecondTexture0->m_fRight_U0, pSecondTexture0->m_fBottom_V0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( vecWorldPos.x, vecWorldPos.y, vecWorldPos.z ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord4f( 0, pSample0->m_fLeft_U0, pSample0->m_fTop_V0, pSample0->m_fRight_U0, pSample0->m_fBottom_V0 ); + meshBuilder.TexCoord4f( 1, pSample0->m_fLeft_U1, pSample0->m_fTop_V1, pSample0->m_fRight_U1, pSample0->m_fBottom_V1 ); + meshBuilder.TexCoord4f( 2, pSample->m_fBlendFactor, rot, rad, yaw ); + meshBuilder.TexCoord2f( 3, 0, 1 ); + meshBuilder.TexCoord4f( 4, pSecondTexture0->m_fLeft_U0, pSecondTexture0->m_fTop_V0, pSecondTexture0->m_fRight_U0, pSecondTexture0->m_fBottom_V0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.FastIndex( info.m_nVertexOffset ); + meshBuilder.FastIndex( info.m_nVertexOffset + 1 ); + meshBuilder.FastIndex( info.m_nVertexOffset + 2 ); + meshBuilder.FastIndex( info.m_nVertexOffset ); + meshBuilder.FastIndex( info.m_nVertexOffset + 2 ); + meshBuilder.FastIndex( info.m_nVertexOffset + 3 ); + info.m_nVertexOffset += 4; + } +} + +void C_OP_RenderSprites::RenderTwoSequenceSpriteCard( CMeshBuilder &meshBuilder, C_OP_RenderSpritesContext_t *pCtx, SpriteRenderInfo_t& info, int hParticle, ParticleRenderData_t const *pSortList, Vector *pCamera ) const +{ + Assert( hParticle != -1 ); + int nGroup = hParticle / 4; + int nOffset = hParticle & 0x3; + + int nColorIndex = nGroup * info.m_nRGBStride; + float r = SubFloat( info.m_pRGB[nColorIndex], nOffset ); + float g = SubFloat( info.m_pRGB[nColorIndex+1], nOffset ); + float b = SubFloat( info.m_pRGB[nColorIndex+2], nOffset ); + + Assert( IsFinite(r) && IsFinite(g) && IsFinite(b) ); + Assert( (r >= 0.0f) && (g >= 0.0f) && (b >= 0.0f) ); + Assert( (r <= 1.0f) && (g <= 1.0f) && (b <= 1.0f) ); + + unsigned char rc = FastFToC( r ); + unsigned char gc = FastFToC( g ); + unsigned char bc = FastFToC( b ); + unsigned char ac = pSortList->m_nAlpha; + + bool bCameraBias = ( &pCtx->m_VisibilityData )->m_flCameraBias != 0.0f; + float flCameraBias = ( &pCtx->m_VisibilityData )->m_flCameraBias; + + float rad = pSortList->m_flRadius; + float rot = SubFloat( info.m_pRot[ nGroup * info.m_nRotStride ], nOffset ); + float yaw = SubFloat( info.m_pYaw[ nGroup * info.m_nYawStride ], nOffset ); + + int nXYZIndex = nGroup * info.m_nXYZStride; + Vector vecWorldPos; + vecWorldPos.x = SubFloat( info.m_pXYZ[ nXYZIndex ], nOffset ); + vecWorldPos.y = SubFloat( info.m_pXYZ[ nXYZIndex+1 ], nOffset ); + vecWorldPos.z = SubFloat( info.m_pXYZ[ nXYZIndex+2 ], nOffset ); + + if ( bCameraBias ) + { + Vector vEyeDir = *pCamera - vecWorldPos; + VectorNormalizeFast( vEyeDir ); + vEyeDir *= flCameraBias; + vecWorldPos += vEyeDir; + } + + // Find the sample for this frame + const SheetSequenceSample_t *pSample = &s_DefaultSheetSequence; + const SheetSequenceSample_t *pSample1 = &s_DefaultSheetSequence; + if ( info.m_pSheet ) + { + pSample = GetSampleForSequence( info.m_pSheet, + SubFloat( info.m_pCreationTimeStamp[ nGroup * info.m_nCreationTimeStride ], nOffset ), + info.m_pParticles->m_flCurTime, + info.m_flAgeScale, + SubFloat( info.m_pSequenceNumber[ nGroup * info.m_nSequenceStride ], nOffset ) ); + pSample1 = GetSampleForSequence( info.m_pSheet, + SubFloat( info.m_pCreationTimeStamp[ nGroup * info.m_nCreationTimeStride ], nOffset ), + info.m_pParticles->m_flCurTime, + info.m_flAgeScale2, + SubFloat( info.m_pSequence1Number[ nGroup * info.m_nSequence1Stride ], nOffset ) ); + } + + const SequenceSampleTextureCoords_t *pSample0 = &(pSample->m_TextureCoordData[0]); + const SequenceSampleTextureCoords_t *pSecondTexture0 = &(pSample->m_TextureCoordData[1]); + const SequenceSampleTextureCoords_t *pSample1Frame = &(pSample1->m_TextureCoordData[0]); + + // Submit 1 (instanced) or 4 (non-instanced) verts (if we're instancing, we don't produce indices either) + meshBuilder.Position3f( vecWorldPos.x, vecWorldPos.y, vecWorldPos.z ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord4f( 0, pSample0->m_fLeft_U0, pSample0->m_fTop_V0, pSample0->m_fRight_U0, pSample0->m_fBottom_V0 ); + meshBuilder.TexCoord4f( 1, pSample0->m_fLeft_U1, pSample0->m_fTop_V1, pSample0->m_fRight_U1, pSample0->m_fBottom_V1 ); + meshBuilder.TexCoord4f( 2, pSample->m_fBlendFactor, rot, rad, yaw ); + // FIXME: change the vertex decl (remove texcoord3/cornerid) if instancing - need to adjust elements beyond texcoord3 down, though + if ( ! bUseInstancing ) + meshBuilder.TexCoord2f( 3, 0, 0 ); + meshBuilder.TexCoord4f( 4, pSecondTexture0->m_fLeft_U0, pSecondTexture0->m_fTop_V0, pSecondTexture0->m_fRight_U0, pSecondTexture0->m_fBottom_V0 ); + meshBuilder.TexCoord4f( 5, pSample1Frame->m_fLeft_U0, pSample1Frame->m_fTop_V0, pSample1Frame->m_fRight_U0, pSample1Frame->m_fBottom_V0 ); + meshBuilder.TexCoord4f( 6, pSample1Frame->m_fLeft_U1, pSample1Frame->m_fTop_V1, pSample1Frame->m_fRight_U1, pSample1Frame->m_fBottom_V1 ); + meshBuilder.TexCoord4f( 7, pSample1->m_fBlendFactor, 0, 0, 0 ); + meshBuilder.AdvanceVertex(); + + if ( !bUseInstancing ) + { + meshBuilder.Position3f( vecWorldPos.x, vecWorldPos.y, vecWorldPos.z ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord4f( 0, pSample0->m_fLeft_U0, pSample0->m_fTop_V0, pSample0->m_fRight_U0, pSample0->m_fBottom_V0 ); + meshBuilder.TexCoord4f( 1, pSample0->m_fLeft_U1, pSample0->m_fTop_V1, pSample0->m_fRight_U1, pSample0->m_fBottom_V1 ); + meshBuilder.TexCoord4f( 2, pSample->m_fBlendFactor, rot, rad, yaw ); + meshBuilder.TexCoord2f( 3, 1, 0 ); + meshBuilder.TexCoord4f( 4, pSecondTexture0->m_fLeft_U0, pSecondTexture0->m_fTop_V0, pSecondTexture0->m_fRight_U0, pSecondTexture0->m_fBottom_V0 ); + meshBuilder.TexCoord4f( 5, pSample1Frame->m_fLeft_U0, pSample1Frame->m_fTop_V0, pSample1Frame->m_fRight_U0, pSample1Frame->m_fBottom_V0 ); + meshBuilder.TexCoord4f( 6, pSample1Frame->m_fLeft_U1, pSample1Frame->m_fTop_V1, pSample1Frame->m_fRight_U1, pSample1Frame->m_fBottom_V1 ); + meshBuilder.TexCoord4f( 7, pSample1->m_fBlendFactor, 0, 0, 0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( vecWorldPos.x, vecWorldPos.y, vecWorldPos.z ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord4f( 0, pSample0->m_fLeft_U0, pSample0->m_fTop_V0, pSample0->m_fRight_U0, pSample0->m_fBottom_V0 ); + meshBuilder.TexCoord4f( 1, pSample0->m_fLeft_U1, pSample0->m_fTop_V1, pSample0->m_fRight_U1, pSample0->m_fBottom_V1 ); + meshBuilder.TexCoord4f( 2, pSample->m_fBlendFactor, rot, rad, yaw ); + meshBuilder.TexCoord2f( 3, 1, 1 ); + meshBuilder.TexCoord4f( 4, pSecondTexture0->m_fLeft_U0, pSecondTexture0->m_fTop_V0, pSecondTexture0->m_fRight_U0, pSecondTexture0->m_fBottom_V0 ); + meshBuilder.TexCoord4f( 5, pSample1Frame->m_fLeft_U0, pSample1Frame->m_fTop_V0, pSample1Frame->m_fRight_U0, pSample1Frame->m_fBottom_V0 ); + meshBuilder.TexCoord4f( 6, pSample1Frame->m_fLeft_U1, pSample1Frame->m_fTop_V1, pSample1Frame->m_fRight_U1, pSample1Frame->m_fBottom_V1 ); + meshBuilder.TexCoord4f( 7, pSample1->m_fBlendFactor, 0, 0, 0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( vecWorldPos.x, vecWorldPos.y, vecWorldPos.z ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord4f( 0, pSample0->m_fLeft_U0, pSample0->m_fTop_V0, pSample0->m_fRight_U0, pSample0->m_fBottom_V0 ); + meshBuilder.TexCoord4f( 1, pSample0->m_fLeft_U1, pSample0->m_fTop_V1, pSample0->m_fRight_U1, pSample0->m_fBottom_V1 ); + meshBuilder.TexCoord4f( 2, pSample->m_fBlendFactor, rot, rad, yaw ); + meshBuilder.TexCoord2f( 3, 0, 1 ); + meshBuilder.TexCoord4f( 4, pSecondTexture0->m_fLeft_U0, pSecondTexture0->m_fTop_V0, pSecondTexture0->m_fRight_U0, pSecondTexture0->m_fBottom_V0 ); + meshBuilder.TexCoord4f( 5, pSample1Frame->m_fLeft_U0, pSample1Frame->m_fTop_V0, pSample1Frame->m_fRight_U0, pSample1Frame->m_fBottom_V0 ); + meshBuilder.TexCoord4f( 6, pSample1Frame->m_fLeft_U1, pSample1Frame->m_fTop_V1, pSample1Frame->m_fRight_U1, pSample1Frame->m_fBottom_V1 ); + meshBuilder.TexCoord4f( 7, pSample1->m_fBlendFactor, 0, 0, 0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.FastIndex( info.m_nVertexOffset ); + meshBuilder.FastIndex( info.m_nVertexOffset + 1 ); + meshBuilder.FastIndex( info.m_nVertexOffset + 2 ); + meshBuilder.FastIndex( info.m_nVertexOffset ); + meshBuilder.FastIndex( info.m_nVertexOffset + 2 ); + meshBuilder.FastIndex( info.m_nVertexOffset + 3 ); + info.m_nVertexOffset += 4; + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_OP_RenderSprites::Render( IMatRenderContext *pRenderContext, CParticleCollection *pParticles, void *pContext ) const +{ + IMaterial *pMaterial = pParticles->m_pDef->GetMaterial(); + C_OP_RenderSpritesContext_t *pCtx = reinterpret_cast( pContext ); + + if ( pCtx->m_VisibilityData.m_bUseVisibility ) + { + SetupParticleVisibility( pParticles, &pCtx->m_VisibilityData, &VisibilityInputs, &pCtx->m_nQueryHandle ); + } + + + IMaterialVar* pVar = pMaterial->FindVarFast( "$orientation", &pCtx->m_nOrientationVarToken ); + if ( pVar ) + { + pVar->SetIntValue( m_nOrientationType ); + } + + pRenderContext->Bind( pMaterial ); + + if ( !pMaterial->IsSpriteCard() ) + { + switch( m_nOrientationType ) + { + case 0: + RenderNonSpriteCardCameraFacing( pParticles, pContext, pRenderContext, pMaterial ); + break; + + case 1: + RenderNonSpriteCardZRotating( pParticles, pContext, pRenderContext, pMaterial ); + break; + + case 2: + RenderNonSpriteCardOriented( pParticles, pContext, pRenderContext, pMaterial, false ); + break; + + case 3: + RenderNonSpriteCardOriented( pParticles, pContext, pRenderContext, pMaterial, true ); + break; + } + + return; + } + + if ( m_nOrientationType == 2 ) + { + pVar = pMaterial->FindVarFast( "$orientationMatrix", &pCtx->m_nOrientationMatrixVarToken ); + if ( pVar ) + { + VMatrix mat; + if ( m_nOrientationControlPoint < 0 ) + { + MatrixSetIdentity( mat ); + } + else + { + pParticles->GetControlPointTransformAtCurrentTime( m_nOrientationControlPoint, &mat ); + } + pVar->SetMatrixValue( mat ); + } + } + + float flAgeScale = m_flAnimationRate * SEQUENCE_SAMPLE_COUNT; + float flAgeScale2 = m_flAnimationRate2 * SEQUENCE_SAMPLE_COUNT; + + SpriteRenderInfo_t info; + info.Init( pParticles, 0, flAgeScale, flAgeScale2, pParticles->m_Sheet() ); + + MaterialPrimitiveType_t primType = bUseInstancing ? MATERIAL_INSTANCED_QUADS : MATERIAL_TRIANGLES; + int nMaxParticlesInBatch = GetMaxParticlesPerBatch( pRenderContext, pMaterial, bUseInstancing ); + + int nParticles; + const ParticleRenderData_t *pSortList = pParticles->GetRenderList( pRenderContext, true, &nParticles, &pCtx->m_VisibilityData ); + + Vector vecCamera; + pRenderContext->GetWorldSpaceCameraPosition( &vecCamera ); + + while ( nParticles ) + { + int nParticlesInBatch = min( nMaxParticlesInBatch, nParticles ); + nParticles -= nParticlesInBatch; + + int vertexCount = bUseInstancing ? nParticlesInBatch : nParticlesInBatch * 4; + int indexCount = bUseInstancing ? 0 : nParticlesInBatch * 6; + + IMesh* pMesh = pRenderContext->GetDynamicMesh( true ); + CMeshBuilder meshBuilder; + + if ( bUseInstancing ) + { + g_pParticleSystemMgr->TallyParticlesRendered( vertexCount * ( primType == MATERIAL_TRIANGLES ? 3 : 4 ) ); + meshBuilder.Begin( pMesh, primType, vertexCount ); + } + else + { + g_pParticleSystemMgr->TallyParticlesRendered( vertexCount * ( primType == MATERIAL_TRIANGLES ? 3 : 4 ), indexCount * ( primType == MATERIAL_TRIANGLES ? 3 : 4 ) ); + meshBuilder.Begin( pMesh, primType, vertexCount, indexCount ); + } + info.m_nVertexOffset = 0; + if ( meshBuilder.TextureCoordinateSize( 5 ) ) // second sequence? + { + for( int i = 0; i < nParticlesInBatch; i++ ) + { + int hParticle = (--pSortList)->m_nIndex; + RenderTwoSequenceSpriteCard( meshBuilder, pCtx, info, hParticle, pSortList, &vecCamera ); + } + } + else + { + for( int i = 0; i < nParticlesInBatch; i++ ) + { + int hParticle = (--pSortList)->m_nIndex; + RenderSpriteCard( meshBuilder, pCtx, info, hParticle, pSortList, &vecCamera ); + } + } + meshBuilder.End(); + pMesh->Draw(); + } +} + + +void C_OP_RenderSprites::RenderUnsorted( CParticleCollection *pParticles, void *pContext, IMatRenderContext *pRenderContext, CMeshBuilder &meshBuilder, int nVertexOffset, int nFirstParticle, int nParticleCount ) const +{ + if ( !pParticles->m_pDef->GetMaterial()->IsSpriteCard() ) + { + switch( m_nOrientationType ) + { + case 0: + // FIXME: Implement! Requires removing MATERIAL_VIEW modification from sorted version + Warning( "C_OP_RenderSprites::RenderUnsorted: Attempting to use an unimplemented sprite renderer for system \"%s\"!\n", + pParticles->m_pDef->GetName() ); +// RenderUnsortedNonSpriteCardCameraFacing( pParticles, pContext, pRenderContext, meshBuilder, nVertexOffset, nFirstParticle, nParticleCount ); + break; + + case 1: + RenderUnsortedNonSpriteCardZRotating( pParticles, pContext, pRenderContext, meshBuilder, nVertexOffset, nFirstParticle, nParticleCount ); + break; + + case 2: + RenderUnsortedNonSpriteCardOriented( pParticles, pContext, pRenderContext, meshBuilder, nVertexOffset, nFirstParticle, nParticleCount ); + break; + } + return; + } + + C_OP_RenderSpritesContext_t *pCtx = reinterpret_cast( pContext ); + + float flAgeScale = m_flAnimationRate * SEQUENCE_SAMPLE_COUNT; + float flAgeScale2 = m_flAnimationRate2 * SEQUENCE_SAMPLE_COUNT; + + SpriteRenderInfo_t info; + info.Init( pParticles, 0, flAgeScale, flAgeScale2, pParticles->m_Sheet() ); + + int hParticle = nFirstParticle; + + int nParticles; + const ParticleRenderData_t *pSortList = pParticles->GetRenderList( pRenderContext, false, &nParticles, &pCtx->m_VisibilityData ); + + Vector vecCamera; + pRenderContext->GetWorldSpaceCameraPosition( &vecCamera ); + + for( int i = 0; i < nParticleCount; i++, hParticle++ ) + { + RenderSpriteCard( meshBuilder, pCtx, info, hParticle, pSortList, &vecCamera ); + } +} + +// +// +// +// + +struct SpriteTrailRenderInfo_t : public SpriteRenderInfo_t +{ + size_t m_nPrevXYZStride; + const fltx4 *m_pPrevXYZ; + size_t length_stride; + const fltx4 *m_pLength; + + const fltx4 *m_pCreationTime; + size_t m_nCreationTimeStride; + + + void Init( CParticleCollection *pParticles, int nVertexOffset, float flAgeScale, CSheet *pSheet ) + { + SpriteRenderInfo_t::Init( pParticles, nVertexOffset, flAgeScale, 0, pSheet ); + m_pParticles = pParticles; + m_pPrevXYZ = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_PREV_XYZ, &m_nPrevXYZStride ); + m_pLength = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_TRAIL_LENGTH, &length_stride ); + m_pCreationTime = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_CREATION_TIME, &m_nCreationTimeStride ); + } +}; + +class C_OP_RenderSpritesTrail : public CParticleRenderOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_RenderSpritesTrail ); + + struct C_OP_RenderSpriteTrailContext_t + { + CParticleVisibilityData m_VisibilityData; + int m_nQueryHandle; + }; + + size_t GetRequiredContextBytes( void ) const + { + return sizeof( C_OP_RenderSpriteTrailContext_t ); + } + + virtual void InitializeContextData( CParticleCollection *pParticles, void *pContext ) const + { + C_OP_RenderSpriteTrailContext_t *pCtx = reinterpret_cast( pContext ); + if ( VisibilityInputs.m_nCPin >= 0 ) + pCtx->m_VisibilityData.m_bUseVisibility = true; + else + pCtx->m_VisibilityData.m_bUseVisibility = false; + pCtx->m_VisibilityData.m_flCameraBias = VisibilityInputs.m_flCameraBias; + } + + uint32 GetWrittenAttributes( void ) const + { + return 0; + } + + void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK | PARTICLE_ATTRIBUTE_RADIUS_MASK | + PARTICLE_ATTRIBUTE_TINT_RGB_MASK | PARTICLE_ATTRIBUTE_ALPHA_MASK | PARTICLE_ATTRIBUTE_CREATION_TIME_MASK | + PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER_MASK | PARTICLE_ATTRIBUTE_TRAIL_LENGTH_MASK; + } + + virtual int GetParticlesToRender( CParticleCollection *pParticles, void *pContext, int nFirstParticle, int nRemainingVertices, int nRemainingIndices, int *pVertsUsed, int *pIndicesUsed ) const ; + virtual void Render( IMatRenderContext *pRenderContext, CParticleCollection *pParticles, void *pContext ) const; + virtual void RenderUnsorted( CParticleCollection *pParticles, void *pContext, IMatRenderContext *pRenderContext, CMeshBuilder &meshBuilder, int nVertexOffset, int nFirstParticle, int nParticleCount ) const; + + void RenderSpriteTrail( CMeshBuilder &meshBuilder, SpriteTrailRenderInfo_t& info, int hParticle, const Vector &vecCameraPos, float flOODt, ParticleRenderData_t const *pSortlist ) const; + + float m_flAnimationRate; + float m_flLengthFadeInTime; + float m_flMaxLength; + float m_flMinLength; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_RenderSpritesTrail, "render_sprite_trail", OPERATOR_SINGLETON ); + +BEGIN_PARTICLE_RENDER_OPERATOR_UNPACK( C_OP_RenderSpritesTrail ) + DMXELEMENT_UNPACK_FIELD( "animation rate", ".1", float, m_flAnimationRate ) + DMXELEMENT_UNPACK_FIELD( "length fade in time", "0", float, m_flLengthFadeInTime ) + DMXELEMENT_UNPACK_FIELD( "max length", "2000", float, m_flMaxLength ) + DMXELEMENT_UNPACK_FIELD( "min length", "0", float, m_flMinLength ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_RenderSpritesTrail ) + +int C_OP_RenderSpritesTrail::GetParticlesToRender( CParticleCollection *pParticles, + void *pContext, int nFirstParticle, int nRemainingVertices, + int nRemainingIndices, + int *pVertsUsed, int *pIndicesUsed ) const +{ + int nMaxParticles = ( (nRemainingVertices / 4) > (nRemainingIndices / 6) ) ? nRemainingIndices / 6 : nRemainingVertices / 4; + int nParticleCount = pParticles->m_nActiveParticles - nFirstParticle; + if ( nParticleCount > nMaxParticles ) + { + nParticleCount = nMaxParticles; + } + *pVertsUsed = nParticleCount * 4; + *pIndicesUsed = nParticleCount * 6; + return nParticleCount; +} + +void C_OP_RenderSpritesTrail::RenderSpriteTrail( CMeshBuilder &meshBuilder, + SpriteTrailRenderInfo_t& info, int hParticle, + const Vector &vecCameraPos, float flOODt, ParticleRenderData_t const *pSortList ) const +{ + Assert( hParticle != -1 ); + int nGroup = hParticle / 4; + int nOffset = hParticle & 0x3; + + // Setup our alpha + unsigned char ac = pSortList->m_nAlpha; + if ( ac == 0 ) + return; + + // Setup our colors + int nColorIndex = nGroup * info.m_nRGBStride; + float r = SubFloat( info.m_pRGB[nColorIndex], nOffset ); + float g = SubFloat( info.m_pRGB[nColorIndex+1], nOffset ); + float b = SubFloat( info.m_pRGB[nColorIndex+2], nOffset ); + + Assert( IsFinite(r) && IsFinite(g) && IsFinite(b) ); + Assert( (r >= -1e-6f) && (g >= -1e-6f) && (b >= -1e-6f) ); + Assert( (r <= 1.0f) && (g <= 1.0f) && (b <= 1.0f) ); + + unsigned char rc = FastFToC( r ); + unsigned char gc = FastFToC( g ); + unsigned char bc = FastFToC( b ); + + // Setup the scale and rotation + float rad = pSortList->m_flRadius; + + // Find the sample for this frame + const SheetSequenceSample_t *pSample = &s_DefaultSheetSequence; + if ( info.m_pSheet ) + { + pSample = GetSampleForSequence( info.m_pSheet, + SubFloat( info.m_pCreationTimeStamp[ nGroup * info.m_nCreationTimeStride ], nOffset ), + info.m_pParticles->m_flCurTime, + info.m_flAgeScale, + SubFloat( info.m_pSequenceNumber[ nGroup * info.m_nSequenceStride ], nOffset ) ); + } + + const SequenceSampleTextureCoords_t *pSample0 = &(pSample->m_TextureCoordData[0]); + + int nCreationTimeIndex = nGroup * info.m_nCreationTimeStride; + float flAge = info.m_pParticles->m_flCurTime - SubFloat( info.m_pCreationTimeStamp[ nCreationTimeIndex ], nOffset ); + + float flLengthScale = ( flAge >= m_flLengthFadeInTime ) ? 1.0 : ( flAge / m_flLengthFadeInTime ); + + int nXYZIndex = nGroup * info.m_nXYZStride; + Vector vecWorldPos( SubFloat( info.m_pXYZ[ nXYZIndex ], nOffset ), SubFloat( info.m_pXYZ[ nXYZIndex+1 ], nOffset ), SubFloat( info.m_pXYZ[ nXYZIndex+2 ], nOffset ) ); + Vector vecViewPos = vecWorldPos; + + // Get our screenspace last position + int nPrevXYZIndex = nGroup * info.m_nPrevXYZStride; + Vector vecPrevWorldPos( SubFloat( info.m_pPrevXYZ[ nPrevXYZIndex ], nOffset ), SubFloat( info.m_pPrevXYZ[ nPrevXYZIndex+1 ], nOffset ), SubFloat( info.m_pPrevXYZ[ nPrevXYZIndex+2 ], nOffset ) ); + Vector vecPrevViewPos = vecPrevWorldPos; + + // Get the delta direction and find the magnitude, then scale the length by the desired length amount + Vector vecDelta; + VectorSubtract( vecPrevViewPos, vecViewPos, vecDelta ); + float flMag = VectorNormalize( vecDelta ); + float flLength = flLengthScale * flMag * flOODt * SubFloat( info.m_pLength[ nGroup * info.length_stride ], nOffset ); + if ( flLength <= 0.0f ) + return; + + flLength = max( m_flMinLength, min( m_flMaxLength, flLength ) ); + + vecDelta *= flLength; + + // Fade the width as the length fades to keep it at a square aspect ratio + if ( flLength < rad ) + { + rad = flLength; + } + + // Find our tangent direction which "fattens" the line + Vector vDirToBeam, vTangentY; + VectorSubtract( vecWorldPos, vecCameraPos, vDirToBeam ); + CrossProduct( vDirToBeam, vecDelta, vTangentY ); + VectorNormalizeFast( vTangentY ); + + // Calculate the verts we'll use as our points + Vector verts[4]; + VectorMA( vecWorldPos, rad*0.5f, vTangentY, verts[0] ); + VectorMA( vecWorldPos, -rad*0.5f, vTangentY, verts[1] ); + VectorAdd( verts[0], vecDelta, verts[3] ); + VectorAdd( verts[1], vecDelta, verts[2] ); + Assert( verts[0].IsValid() && verts[1].IsValid() && verts[2].IsValid() && verts[3].IsValid() ); + + meshBuilder.Position3fv( verts[0].Base() ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord2f( 0, pSample0->m_fLeft_U0, pSample0->m_fBottom_V0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( verts[1].Base() ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord2f( 0, pSample0->m_fRight_U0, pSample0->m_fBottom_V0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( verts[2].Base() ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord2f( 0, pSample0->m_fRight_U0, pSample0->m_fTop_V0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( verts[3].Base() ); + meshBuilder.Color4ub( rc, gc, bc, ac ); + meshBuilder.TexCoord2f( 0, pSample0->m_fLeft_U0, pSample0->m_fTop_V0 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.FastIndex( info.m_nVertexOffset ); + meshBuilder.FastIndex( info.m_nVertexOffset + 1 ); + meshBuilder.FastIndex( info.m_nVertexOffset + 2 ); + meshBuilder.FastIndex( info.m_nVertexOffset ); + meshBuilder.FastIndex( info.m_nVertexOffset + 2 ); + meshBuilder.FastIndex( info.m_nVertexOffset + 3 ); + info.m_nVertexOffset += 4; +} + + +void C_OP_RenderSpritesTrail::Render( IMatRenderContext *pRenderContext, CParticleCollection *pParticles, void *pContext ) const +{ + C_OP_RenderSpriteTrailContext_t *pCtx = reinterpret_cast( pContext ); + IMaterial *pMaterial = pParticles->m_pDef->GetMaterial(); + + if ( pCtx->m_VisibilityData.m_bUseVisibility ) + { + SetupParticleVisibility( pParticles, &pCtx->m_VisibilityData, &VisibilityInputs, &pCtx->m_nQueryHandle ); + } + + // Right now we only have a meshbuilder version! + if ( !HushAsserts() ) + Assert( pMaterial->IsSpriteCard() == false ); + if ( pMaterial->IsSpriteCard() ) + return; + + // Store matrices off so we can restore them in RenderEnd(). + pRenderContext->Bind( pMaterial ); + + float flAgeScale = m_flAnimationRate * SEQUENCE_SAMPLE_COUNT; + + // Get the camera's worldspace position + Vector vecCameraPos; + pRenderContext->GetWorldSpaceCameraPosition( &vecCameraPos ); + + SpriteTrailRenderInfo_t info; + info.Init( pParticles, 0, flAgeScale, pParticles->m_Sheet() ); + + int nParticles; + const ParticleRenderData_t *pSortList = pParticles->GetRenderList( pRenderContext, true, &nParticles, &pCtx->m_VisibilityData ); + + int nMaxParticlesInBatch = GetMaxParticlesPerBatch( pRenderContext, pMaterial, false ); + float flOODt = ( pParticles->m_flDt != 0.0f ) ? ( 1.0f / pParticles->m_flDt ) : 1.0f; + while ( nParticles ) + { + int nParticlesInBatch = min( nMaxParticlesInBatch, nParticles ); + nParticles -= nParticlesInBatch; + + g_pParticleSystemMgr->TallyParticlesRendered( nParticlesInBatch * 4 * 3, nParticlesInBatch * 6 * 3 ); + + IMesh* pMesh = pRenderContext->GetDynamicMesh( true ); + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nParticlesInBatch * 4, nParticlesInBatch * 6 ); + info.m_nVertexOffset = 0; + for( int i = 0; i < nParticlesInBatch; i++ ) + { + int hParticle = (--pSortList)->m_nIndex; + RenderSpriteTrail( meshBuilder, info, hParticle, vecCameraPos, flOODt, pSortList ); + } + meshBuilder.End(); + pMesh->Draw(); + } +} + +void C_OP_RenderSpritesTrail::RenderUnsorted( CParticleCollection *pParticles, void *pContext, IMatRenderContext *pRenderContext, CMeshBuilder &meshBuilder, int nVertexOffset, int nFirstParticle, int nParticleCount ) const +{ + C_OP_RenderSpriteTrailContext_t *pCtx = reinterpret_cast( pContext ); + // NOTE: This is interesting to support because at first we won't have all the various + // pixel-shader versions of SpriteCard, like modulate, twotexture, etc. etc. + Vector vecCameraPos; + pRenderContext->GetWorldSpaceCameraPosition( &vecCameraPos ); + + float flAgeScale = m_flAnimationRate * SEQUENCE_SAMPLE_COUNT; + SpriteTrailRenderInfo_t info; + info.Init( pParticles, nVertexOffset, flAgeScale, pParticles->m_Sheet() ); + + int nParticles; + const ParticleRenderData_t *pSortList = pParticles->GetRenderList( pRenderContext, false, &nParticles, &pCtx->m_VisibilityData ); + + float flOODt = ( pParticles->m_flDt != 0.0f ) ? ( 1.0f / pParticles->m_flDt ) : 1.0f; + int hParticle = nFirstParticle; + for( int i = 0; i < nParticleCount; i++, hParticle++ ) + { + RenderSpriteTrail( meshBuilder, info, hParticle, vecCameraPos, flOODt, pSortList ); + } +} + +//----------------------------------------------------------------------------- +// +// Rope renderer +// +//----------------------------------------------------------------------------- +struct RopeRenderInfo_t +{ + size_t m_nXYZStride; + const fltx4 *m_pXYZ; + size_t m_nRadStride; + const fltx4 *m_pRadius; + size_t m_nRGBStride; + const fltx4 *m_pRGB; + size_t m_nAlphaStride; + const fltx4 *m_pAlpha; + CParticleCollection *m_pParticles; + + void Init( CParticleCollection *pParticles ) + { + m_pParticles = pParticles; + m_pXYZ = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_XYZ, &m_nXYZStride ); + m_pRadius = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_RADIUS, &m_nRadStride ); + m_pRGB = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_TINT_RGB, &m_nRGBStride ); + m_pAlpha = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_ALPHA, &m_nAlphaStride ); + } + + void GenerateSeg( int hParticle, BeamSeg_t& seg ) + { + Assert( hParticle != -1 ); + int nGroup = hParticle / 4; + int nOffset = hParticle & 0x3; + + int nXYZIndex = nGroup * m_nXYZStride; + int nColorIndex = nGroup * m_nRGBStride; + seg.m_vPos.Init( SubFloat( m_pXYZ[ nXYZIndex ], nOffset ), SubFloat( m_pXYZ[ nXYZIndex+1 ], nOffset ), SubFloat( m_pXYZ[ nXYZIndex+2 ], nOffset ) ); + seg.m_vColor.Init( SubFloat( m_pRGB[ nColorIndex ], nOffset ), SubFloat( m_pRGB[ nColorIndex+1 ], nOffset ), SubFloat( m_pRGB[nColorIndex+2], nOffset ) ); + seg.m_flAlpha = SubFloat( m_pAlpha[ nGroup * m_nAlphaStride ], nOffset ); + seg.m_flWidth = SubFloat( m_pRadius[ nGroup * m_nRadStride ], nOffset ); + } +}; + + +struct RenderRopeContext_t +{ + float m_flRenderedRopeLength; +}; + +class C_OP_RenderRope : public CParticleOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_RenderRope ); + + uint32 GetWrittenAttributes( void ) const + { + return 0; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_RADIUS_MASK | + PARTICLE_ATTRIBUTE_TINT_RGB_MASK | PARTICLE_ATTRIBUTE_ALPHA_MASK; + } + + virtual void InitializeContextData( CParticleCollection *pParticles, void *pContext ) const + { + RenderRopeContext_t *pCtx = reinterpret_cast( pContext ); + pCtx->m_flRenderedRopeLength = false; + float *pSubdivList = (float*)( pCtx + 1 ); + for ( int iSubdiv = 0; iSubdiv < m_nSubdivCount; iSubdiv++ ) + { + pSubdivList[iSubdiv] = (float)iSubdiv / (float)m_nSubdivCount; + } + + // NOTE: Has to happen here, and not in InitParams, since the material isn't set up yet + const_cast( this )->m_flTextureScale = 1.0f / ( pParticles->m_pDef->GetMaterial()->GetMappingHeight() * m_flTexelSizeInUnits ); + } + + size_t GetRequiredContextBytes( void ) const + { + return sizeof( RenderRopeContext_t ) + m_nSubdivCount * sizeof(float); + } + + virtual void InitParams( CParticleSystemDefinition *pDef, CDmxElement *pElement ) + { + if ( m_nSubdivCount <= 0 ) + { + m_nSubdivCount = 1; + } + if ( m_flTexelSizeInUnits <= 0 ) + { + m_flTexelSizeInUnits = 1.0f; + } + m_flTStep = 1.0 / m_nSubdivCount; + } + + virtual int GetParticlesToRender( CParticleCollection *pParticles, void *pContext, int nFirstParticle, int nRemainingVertices, int nRemainingIndices, int *pVertsUsed, int *pIndicesUsed ) const; + virtual void Render( IMatRenderContext *pRenderContext, CParticleCollection *pParticles, void *pContext ) const; + virtual void RenderSpriteCard( CParticleCollection *pParticles, void *pContext, IMaterial *pMaterial ) const; + virtual void RenderUnsorted( CParticleCollection *pParticles, void *pContext, IMatRenderContext *pRenderContext, CMeshBuilder &meshBuilder, int nVertexOffset, int nFirstParticle, int nParticleCount ) const; + + // We connect neighboring particle instances to each other, so if the order isn't maintained we will have a particle that jumps + // back to the wrong place and look terrible. + virtual bool RequiresOrderInvariance( void ) const OVERRIDE + { + return true; + } + + int m_nSubdivCount; + + float m_flTexelSizeInUnits; + float m_flTextureScale; + float m_flTextureScrollRate; + float m_flTStep; + +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_RenderRope, "render_rope", OPERATOR_SINGLETON ); + +BEGIN_PARTICLE_OPERATOR_UNPACK( C_OP_RenderRope ) + DMXELEMENT_UNPACK_FIELD( "subdivision_count", "3", int, m_nSubdivCount ) + DMXELEMENT_UNPACK_FIELD( "texel_size", "4.0f", float, m_flTexelSizeInUnits ) + DMXELEMENT_UNPACK_FIELD( "texture_scroll_rate", "0.0f", float, m_flTextureScrollRate ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_RenderRope ) + + +//----------------------------------------------------------------------------- +// Returns the number of particles to render +//----------------------------------------------------------------------------- +int C_OP_RenderRope::GetParticlesToRender( CParticleCollection *pParticles, + void *pContext, int nFirstParticle, int nRemainingVertices, int nRemainingIndices, + int *pVertsUsed, int *pIndicesUsed ) const +{ + if ( ( nFirstParticle >= pParticles->m_nActiveParticles - 1 ) || ( pParticles->m_nActiveParticles <= 1 ) ) + { + *pVertsUsed = 0; + *pIndicesUsed = 0; + return 0; + } + + // NOTE: This is only true for particles *after* the first particle. + // First particle takes 2 verts, no indices. + int nVertsPerParticle = 2 * m_nSubdivCount; + int nIndicesPerParticle = 6 * m_nSubdivCount; + + // Subtract 2 is because the first particle uses an extra pair of vertices + int nMaxParticleCount = 1 + ( nRemainingVertices - 2 ) / nVertsPerParticle; + int nMaxParticleCount2 = nRemainingIndices / nIndicesPerParticle; + if ( nMaxParticleCount > nMaxParticleCount2 ) + { + nMaxParticleCount = nMaxParticleCount2; + } + + int nParticleCount = pParticles->m_nActiveParticles - nFirstParticle; + + // We can't choose a max particle count so that we only have 1 particle to render next time + if ( nMaxParticleCount == nParticleCount - 1 ) + { + --nMaxParticleCount; + Assert( nMaxParticleCount > 0 ); + } + + if ( nParticleCount > nMaxParticleCount ) + { + nParticleCount = nMaxParticleCount; + } + + *pVertsUsed = ( nParticleCount - 1 ) * m_nSubdivCount * 2 + 2; + *pIndicesUsed = nParticleCount * m_nSubdivCount * 6; + return nParticleCount; +} + + +#define OUTPUT_2SPLINE_VERTS( t ) \ + meshBuilder.Color4ub( FastFToC( vecColor.x ), FastFToC( vecColor.y), FastFToC( vecColor.z), FastFToC( vecColor.w ) ); \ + meshBuilder.Position3f( (t), flU, 0 ); \ + meshBuilder.TexCoord4fv( 0, vecP0.Base() ); \ + meshBuilder.TexCoord4fv( 1, vecP1.Base() ); \ + meshBuilder.TexCoord4fv( 2, vecP2.Base() ); \ + meshBuilder.TexCoord4fv( 3, vecP3.Base() ); \ + meshBuilder.AdvanceVertex(); \ + meshBuilder.Color4ub( FastFToC( vecColor.x ), FastFToC( vecColor.y), FastFToC( vecColor.z), FastFToC( vecColor.w ) ); \ + meshBuilder.Position3f( (t), flU, 1 ); \ + meshBuilder.TexCoord4fv( 0, vecP0.Base() ); \ + meshBuilder.TexCoord4fv( 1, vecP1.Base() ); \ + meshBuilder.TexCoord4fv( 2, vecP2.Base() ); \ + meshBuilder.TexCoord4fv( 3, vecP3.Base() ); \ + meshBuilder.AdvanceVertex(); + +void C_OP_RenderRope::RenderSpriteCard( CParticleCollection *pParticles, void *pContext, IMaterial *pMaterial ) const +{ + int nParticles = pParticles->m_nActiveParticles; + + int nSegmentsToRender = nParticles - 1; + if ( ! nSegmentsToRender ) + return; + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->Bind( pMaterial ); + + int nMaxVertices = pRenderContext->GetMaxVerticesToRender( pMaterial ); + int nMaxIndices = pRenderContext->GetMaxIndicesToRender(); + + int nNumIndicesPerSegment = 6 * m_nSubdivCount; + int nNumVerticesPerSegment = 2 * m_nSubdivCount; + + int nNumSegmentsPerBatch = min( ( nMaxVertices - 2 )/nNumVerticesPerSegment, + ( nMaxIndices ) / nNumIndicesPerSegment ); + + const float *pXYZ = pParticles->GetFloatAttributePtr( + PARTICLE_ATTRIBUTE_XYZ, 0 ); + const float *pColor = pParticles->GetFloatAttributePtr( + PARTICLE_ATTRIBUTE_TINT_RGB, 0 ); + + const float *pRadius = pParticles->GetFloatAttributePtr( + PARTICLE_ATTRIBUTE_RADIUS, 0 ); + const float *pAlpha = pParticles->GetFloatAttributePtr( + PARTICLE_ATTRIBUTE_ALPHA, 0 ); + + IMesh* pMesh = pRenderContext->GetDynamicMesh( true ); + CMeshBuilder meshBuilder; + + int nNumSegmentsIWillRenderPerBatch = min( nNumSegmentsPerBatch, nSegmentsToRender ); + + bool bFirstPoint = true; + + float flTexOffset = m_flTextureScrollRate * pParticles->m_flCurTime; + float flU = flTexOffset; + + // initialize first spline segment + Vector4D vecP1( pXYZ[0], pXYZ[4], pXYZ[8], pRadius[0] ); + Vector4D vecP2( pXYZ[1], pXYZ[5], pXYZ[9], pRadius[1] ); + Vector4D vecP0 = vecP1; + Vector4D vecColor( pColor[0], pColor[4], pColor[8], pAlpha[0] ); + Vector4D vecDelta = vecP2; + vecDelta -= vecP1; + vecP0 -= vecDelta; + + Vector4D vecP3; + + if ( nParticles < 3 ) + { + vecP3 = vecP2; + vecP3 += vecDelta; + } + else + { + vecP3.Init( pXYZ[2], pXYZ[6], pXYZ[10], pRadius[2] ); + } + int nPnt = 3; + int nCurIDX = 0; + + int nSegmentsAvailableInBuffer = nNumSegmentsIWillRenderPerBatch; + + g_pParticleSystemMgr->TallyParticlesRendered( 2 + nNumSegmentsIWillRenderPerBatch * nNumVerticesPerSegment * 3, nNumIndicesPerSegment * nNumSegmentsIWillRenderPerBatch * 3 ); + + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, + 2 + nNumSegmentsIWillRenderPerBatch * nNumVerticesPerSegment, + nNumIndicesPerSegment * nNumSegmentsIWillRenderPerBatch ); + + + + float flDUScale = ( m_flTStep * m_flTexelSizeInUnits ); + float flT = 0; + + do + { + if ( ! nSegmentsAvailableInBuffer ) + { + meshBuilder.End(); + pMesh->Draw(); + + g_pParticleSystemMgr->TallyParticlesRendered( 2 + nNumSegmentsIWillRenderPerBatch * nNumVerticesPerSegment * 3, nNumIndicesPerSegment * nNumSegmentsIWillRenderPerBatch * 3 ); + + + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, 2 + nNumSegmentsIWillRenderPerBatch * nNumVerticesPerSegment, + nNumIndicesPerSegment * nNumSegmentsIWillRenderPerBatch ); + + // copy the last emitted points + OUTPUT_2SPLINE_VERTS( flT ); + nSegmentsAvailableInBuffer = nNumSegmentsIWillRenderPerBatch; + nCurIDX = 0; + } + nSegmentsAvailableInBuffer--; + flT = 0.; + float flDu = flDUScale * ( vecP2.AsVector3D() - vecP1.AsVector3D() ).Length(); + for( int nSlice = 0 ; nSlice < m_nSubdivCount; nSlice++ ) + { + OUTPUT_2SPLINE_VERTS( flT ); + flT += m_flTStep; + flU += flDu; + if ( ! bFirstPoint ) + { + meshBuilder.FastIndex( nCurIDX ); + meshBuilder.FastIndex( nCurIDX+1 ); + meshBuilder.FastIndex( nCurIDX+2 ); + meshBuilder.FastIndex( nCurIDX+1 ); + meshBuilder.FastIndex( nCurIDX+3 ); + meshBuilder.FastIndex( nCurIDX+2 ); + nCurIDX += 2; + } + bFirstPoint = false; + } + // next segment + if ( nSegmentsToRender > 1 ) + { + vecP0 = vecP1; + vecP1 = vecP2; + vecP2 = vecP3; + pRadius = pParticles->GetFloatAttributePtr( + PARTICLE_ATTRIBUTE_RADIUS, nPnt ); + pAlpha = pParticles->GetFloatAttributePtr( + PARTICLE_ATTRIBUTE_ALPHA, nPnt -2 ); + vecColor.Init( pColor[0], pColor[4], pColor[8], pAlpha[0] ); + + if ( nPnt < nParticles ) + { + pXYZ = pParticles->GetFloatAttributePtr( + PARTICLE_ATTRIBUTE_XYZ, nPnt ); + vecP3.Init( pXYZ[0], pXYZ[4], pXYZ[8], pRadius[0] ); + nPnt++; + } + else + { + // fake last point by extrapolating + vecP3 += vecP2; + vecP3 -= vecP1; + } + } + } while( --nSegmentsToRender ); + + // output last piece + OUTPUT_2SPLINE_VERTS( 1.0 ); + meshBuilder.FastIndex( nCurIDX ); + meshBuilder.FastIndex( nCurIDX+1 ); + meshBuilder.FastIndex( nCurIDX+2 ); + meshBuilder.FastIndex( nCurIDX+1 ); + meshBuilder.FastIndex( nCurIDX+3 ); + meshBuilder.FastIndex( nCurIDX+2 ); + + + meshBuilder.End(); + pMesh->Draw(); +} + +//----------------------------------------------------------------------------- +// Renders particles, sorts them (?) +//----------------------------------------------------------------------------- +void C_OP_RenderRope::Render( IMatRenderContext *pRenderContext, CParticleCollection *pParticles, void *pContext ) const +{ + // FIXME: What does this even mean? Ropes can't really be sorted. + + IMaterial *pMaterial = pParticles->m_pDef->GetMaterial(); + if ( pMaterial->IsSpriteCard() ) + { + RenderSpriteCard( pParticles, pContext, pMaterial ); + return; + } + + pRenderContext->Bind( pMaterial ); + + int nMaxVertices = pRenderContext->GetMaxVerticesToRender( pMaterial ); + int nMaxIndices = pRenderContext->GetMaxIndicesToRender(); + int nParticles = pParticles->m_nActiveParticles; + + int nFirstParticle = 0; + while ( nParticles ) + { + int nVertCount, nIndexCount; + int nParticlesInBatch = GetParticlesToRender( pParticles, pContext, nFirstParticle, nMaxVertices, nMaxIndices, &nVertCount, &nIndexCount ); + if ( nParticlesInBatch == 0 ) + break; + + nParticles -= nParticlesInBatch; + + g_pParticleSystemMgr->TallyParticlesRendered( nVertCount * 3, nIndexCount * 3 ); + + IMesh* pMesh = pRenderContext->GetDynamicMesh( true ); + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nVertCount, nIndexCount ); + + RenderUnsorted( pParticles, pContext, pRenderContext, meshBuilder, 0, nFirstParticle, nParticlesInBatch ); + + meshBuilder.End(); + pMesh->Draw(); + + nFirstParticle += nParticlesInBatch; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_OP_RenderRope::RenderUnsorted( CParticleCollection *pParticles, void *pContext, IMatRenderContext *pRenderContext, CMeshBuilder &meshBuilder, int nVertexOffset, int nFirstParticle, int nParticleCount ) const +{ + IMaterial *pMaterial = pParticles->m_pDef->GetMaterial(); + + // Right now we only have a meshbuilder version! + Assert( pMaterial->IsSpriteCard() == false ); + if ( pMaterial->IsSpriteCard() ) + return; + + RenderRopeContext_t *pCtx = reinterpret_cast( pContext ); + float *pSubdivList = (float*)( pCtx + 1 ); + if ( nFirstParticle == 0 ) + { + pCtx->m_flRenderedRopeLength = 0.0f; + } + + float flTexOffset = m_flTextureScrollRate * pParticles->m_flCurTime; + + RopeRenderInfo_t info; + info.Init( pParticles ); + + CBeamSegDraw beamSegment; + beamSegment.Start( pRenderContext, ( nParticleCount - 1 ) * m_nSubdivCount + 1, pMaterial, &meshBuilder, nVertexOffset ); + + Vector vecCatmullRom[4]; + BeamSeg_t seg[2]; + info.GenerateSeg( nFirstParticle, seg[0] ); + seg[0].m_flTexCoord = ( pCtx->m_flRenderedRopeLength + flTexOffset ) * m_flTextureScale; + + beamSegment.NextSeg( &seg[0] ); + vecCatmullRom[1] = seg[0].m_vPos; + if ( nFirstParticle == 0 ) + { + vecCatmullRom[0] = vecCatmullRom[1]; + } + else + { + int nGroup = ( nFirstParticle-1 ) / 4; + int nOffset = ( nFirstParticle-1 ) & 0x3; + int nXYZIndex = nGroup * info.m_nXYZStride; + vecCatmullRom[0].Init( SubFloat( info.m_pXYZ[ nXYZIndex ], nOffset ), SubFloat( info.m_pXYZ[ nXYZIndex+1 ], nOffset ), SubFloat( info.m_pXYZ[ nXYZIndex+2 ], nOffset ) ); + } + + float flOOSubDivCount = 1.0f / m_nSubdivCount; + int hParticle = nFirstParticle + 1; + for ( int i = 1; i < nParticleCount; ++i, ++hParticle ) + { + int nCurr = i & 1; + int nPrev = 1 - nCurr; + info.GenerateSeg( hParticle, seg[nCurr] ); + pCtx->m_flRenderedRopeLength += seg[nCurr].m_vPos.DistTo( seg[nPrev].m_vPos ); + seg[nCurr].m_flTexCoord = ( pCtx->m_flRenderedRopeLength + flTexOffset ) * m_flTextureScale; + + if ( m_nSubdivCount > 1 ) + { + vecCatmullRom[ (i+1) & 0x3 ] = seg[nCurr].m_vPos; + if ( hParticle != info.m_pParticles->m_nActiveParticles - 1 ) + { + int nGroup = ( hParticle+1 ) / 4; + int nOffset = ( hParticle+1 ) & 0x3; + int nXYZIndex = nGroup * info.m_nXYZStride; + vecCatmullRom[ (i+2) & 0x3 ].Init( SubFloat( info.m_pXYZ[ nXYZIndex ], nOffset ), SubFloat( info.m_pXYZ[ nXYZIndex+1 ], nOffset ), SubFloat( info.m_pXYZ[ nXYZIndex+2 ], nOffset ) ); + } + else + { + vecCatmullRom[ (i+2) & 0x3 ] = vecCatmullRom[ (i+1) & 0x3 ]; + } + + BeamSeg_t &subDivSeg = seg[nPrev]; + Vector vecColorInc = ( seg[nCurr].m_vColor - seg[nPrev].m_vColor ) * flOOSubDivCount; + float flAlphaInc = ( seg[nCurr].m_flAlpha - seg[nPrev].m_flAlpha ) * flOOSubDivCount; + float flTexcoordInc = ( seg[nCurr].m_flTexCoord - seg[nPrev].m_flTexCoord ) * flOOSubDivCount; + float flWidthInc = ( seg[nCurr].m_flWidth - seg[nPrev].m_flWidth ) * flOOSubDivCount; + for( int iSubdiv = 1; iSubdiv < m_nSubdivCount; ++iSubdiv ) + { + subDivSeg.m_vColor += vecColorInc; + subDivSeg.m_vColor.x = clamp( subDivSeg.m_vColor.x, 0.0f, 1.0f ); + subDivSeg.m_vColor.y = clamp( subDivSeg.m_vColor.y, 0.0f, 1.0f ); + subDivSeg.m_vColor.z = clamp( subDivSeg.m_vColor.z, 0.0f, 1.0f ); + subDivSeg.m_flAlpha += flAlphaInc; + subDivSeg.m_flAlpha = clamp( subDivSeg.m_flAlpha, 0.0f, 1.0f ); + subDivSeg.m_flTexCoord += flTexcoordInc; + subDivSeg.m_flWidth += flWidthInc; + + Catmull_Rom_Spline( vecCatmullRom[ (i+3) & 0x3 ], vecCatmullRom[ i & 0x3 ], + vecCatmullRom[ (i+1) & 0x3 ], vecCatmullRom[ (i+2) & 0x3 ], + pSubdivList[iSubdiv], subDivSeg.m_vPos ); + + beamSegment.NextSeg( &subDivSeg ); + } + } + + beamSegment.NextSeg( &seg[nCurr] ); + } + + beamSegment.End(); +} + +#ifdef USE_BLOBULATOR // Enable blobulator for EP3 + +//----------------------------------------------------------------------------- +// Installs renderers +//----------------------------------------------------------------------------- +class C_OP_RenderBlobs : public CParticleRenderOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_RenderBlobs ); + + float m_cubeWidth; + float m_cutoffRadius; + float m_renderRadius; + + + struct C_OP_RenderBlobsContext_t + { + CParticleVisibilityData m_VisibilityData; + int m_nQueryHandle; + }; + + size_t GetRequiredContextBytes( void ) const + { + return sizeof( C_OP_RenderBlobsContext_t ); + } + + virtual void InitializeContextData( CParticleCollection *pParticles, void *pContext ) const + { + C_OP_RenderBlobsContext_t *pCtx = reinterpret_cast( pContext ); + if ( VisibilityInputs.m_nCPin >= 0 ) + pCtx->m_VisibilityData.m_bUseVisibility = true; + else + pCtx->m_VisibilityData.m_bUseVisibility = false; + pCtx->m_VisibilityData.m_flCameraBias = VisibilityInputs.m_flCameraBias; + } + + uint32 GetWrittenAttributes( void ) const + { + return 0; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK; + } + + virtual void Render( IMatRenderContext *pRenderContext, CParticleCollection *pParticles, void *pContext ) const; + + virtual bool IsBatchable() const + { + return false; + } +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_RenderBlobs, "render_blobs", OPERATOR_SINGLETON ); + +BEGIN_PARTICLE_RENDER_OPERATOR_UNPACK( C_OP_RenderBlobs ) + DMXELEMENT_UNPACK_FIELD( "cube_width", "1.0f", float, m_cubeWidth ) + DMXELEMENT_UNPACK_FIELD( "cutoff_radius", "3.3f", float, m_cutoffRadius ) + DMXELEMENT_UNPACK_FIELD( "render_radius", "1.3f", float, m_renderRadius ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_RenderBlobs ) + + +void C_OP_RenderBlobs::Render( IMatRenderContext *pRenderContext, CParticleCollection *pParticles, void *pContext ) const +{ + ImpTiler* tiler = ImpTilerFactory::factory->getTiler(); + //RENDERER_CLASS* sweepRenderer = tiler->getRenderer(); + + C_OP_RenderBlobsContext_t *pCtx = reinterpret_cast( pContext ); + + if ( pCtx->m_VisibilityData.m_bUseVisibility ) + { + SetupParticleVisibility( pParticles, &pCtx->m_VisibilityData, &VisibilityInputs, &pCtx->m_nQueryHandle ); + } + + + + #if 0 + // Note: it is not good to have these static variables here. + static RENDERER_CLASS* sweepRenderer = NULL; + static ImpTiler* tiler = NULL; + if(!sweepRenderer) + { + sweepRenderer = new RENDERER_CLASS(); + tiler = new ImpTiler(sweepRenderer); + } + #endif + + // TODO: I should get rid of this static array and static calls + // to setCubeWidth, etc... + static SmartArray imp_particles_sa; // This doesn't specify alignment, might have problems with SSE + + RENDERER_CLASS::setCubeWidth(m_cubeWidth); + RENDERER_CLASS::setRenderR(m_renderRadius); + RENDERER_CLASS::setCutoffR(m_cutoffRadius); + + RENDERER_CLASS::setCalcSignFunc(calcSign); + RENDERER_CLASS::setCalcSign2Func(calcSign2); + #if 0 + RENDERER_CLASS::setCalcCornerFunc(CALC_CORNER_NORMAL_COLOR_CI_SIZE, calcCornerNormalColor); + RENDERER_CLASS::setCalcVertexFunc(calcVertexNormalNColor); + #endif + #if 1 + RENDERER_CLASS::setCalcCornerFunc(CALC_CORNER_NORMAL_CI_SIZE, calcCornerNormal); + RENDERER_CLASS::setCalcVertexFunc(calcVertexNormalDebugColor); + #endif + #if 0 + RENDERER_CLASS::setCalcCornerFunc(CALC_CORNER_NORMAL_COLOR_CI_SIZE, calcCornerNormalHiFreqColor); + RENDERER_CLASS::setCalcVertexFunc(calcVertexNormalNColor); + #endif + + + IMaterial *pMaterial = pParticles->m_pDef->GetMaterial(); + + // TODO: I don't need to load this as a sorted list. See Lennard Jones forces for better way! + int nParticles; + const ParticleRenderData_t *pSortList = pParticles->GetRenderList( pRenderContext, false, &nParticles, &pCtx->m_VisibilityData ); + size_t xyz_stride; + const fltx4 *xyz = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_XYZ, &xyz_stride ); + + Vector bbMin; + Vector bbMax; + pParticles->GetBounds( &bbMin, &bbMax ); + Vector bbCenter = 0.5f * ( bbMin + bbMax ); + + // FIXME: Make this configurable. Not all shaders perform lighting. Although it's pretty likely for isosurface shaders. + g_pParticleSystemMgr->Query()->SetUpLightingEnvironment( bbCenter ); + + // FIXME: Ugly hack to get particle system location to a special blob shader lighting proxy + pRenderContext->Bind( pMaterial, &bbCenter ); + + //CMeshBuilder meshBuilder; + //int nMaxVertices = pRenderContext->GetMaxVerticesToRender( pMaterial ); + + tiler->beginFrame(Point3D(0.0f, 0.0f, 0.0f), (void*)&pRenderContext); + + while(imp_particles_sa.size < nParticles) + { + imp_particles_sa.pushAutoSize(ImpParticle()); + } + + for( int i = 0; i < nParticles; i++ ) + { + int hParticle = (--pSortList)->m_nIndex; + int nIndex = ( hParticle / 4 ) * xyz_stride; + int nOffset = hParticle & 0x3; + float x = SubFloat( xyz[nIndex], nOffset ); + float y = SubFloat( xyz[nIndex+1], nOffset ); + float z = SubFloat( xyz[nIndex+2], nOffset ); + + ImpParticle* imp_particle = &imp_particles_sa[i]; + imp_particle->center[0]=x; + imp_particle->center[1]=y; + imp_particle->center[2]=z; + imp_particle->setFieldScale(1.0f); + //imp_particle->interpolants1.set(1.0f, 1.0f, 1.0f); + //imp_particle->interpolants1[3] = 0.0f; //m_flSurfaceV[i]; + tiler->insertParticle(imp_particle); + } + + + tiler->drawSurface(); // NOTE: need to call drawSurfaceSorted for transparency + tiler->endFrame(); + + ImpTilerFactory::factory->returnTiler(tiler); + +} + +#endif //blobs + + + +//----------------------------------------------------------------------------- +// Installs renderers +//----------------------------------------------------------------------------- +class C_OP_RenderScreenVelocityRotate : public CParticleRenderOperatorInstance +{ + DECLARE_PARTICLE_OPERATOR( C_OP_RenderScreenVelocityRotate ); + + float m_flRotateRateDegrees; + float m_flForwardDegrees; + + struct C_OP_RenderScreenVelocityRotateContext_t + { + CParticleVisibilityData m_VisibilityData; + int m_nQueryHandle; + }; + + size_t GetRequiredContextBytes( void ) const + { + return sizeof( C_OP_RenderScreenVelocityRotateContext_t ); + } + + virtual void InitializeContextData( CParticleCollection *pParticles, void *pContext ) const + { + C_OP_RenderScreenVelocityRotateContext_t *pCtx = reinterpret_cast( pContext ); + if ( VisibilityInputs.m_nCPin >= 0 ) + pCtx->m_VisibilityData.m_bUseVisibility = true; + else + pCtx->m_VisibilityData.m_bUseVisibility = false; + pCtx->m_VisibilityData.m_flCameraBias = VisibilityInputs.m_flCameraBias; + } + uint32 GetWrittenAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_ROTATION_MASK; + } + + uint32 GetReadAttributes( void ) const + { + return PARTICLE_ATTRIBUTE_XYZ_MASK | PARTICLE_ATTRIBUTE_PREV_XYZ_MASK | PARTICLE_ATTRIBUTE_ROTATION_MASK ; + } + + virtual void Render( IMatRenderContext *pRenderContext, CParticleCollection *pParticles, void *pContext ) const; +}; + +DEFINE_PARTICLE_OPERATOR( C_OP_RenderScreenVelocityRotate, "render_screen_velocity_rotate", OPERATOR_SINGLETON ); + +BEGIN_PARTICLE_RENDER_OPERATOR_UNPACK( C_OP_RenderScreenVelocityRotate ) + DMXELEMENT_UNPACK_FIELD( "rotate_rate(dps)", "0.0f", float, m_flRotateRateDegrees ) + DMXELEMENT_UNPACK_FIELD( "forward_angle", "-90.0f", float, m_flForwardDegrees ) +END_PARTICLE_OPERATOR_UNPACK( C_OP_RenderScreenVelocityRotate ) + + +void C_OP_RenderScreenVelocityRotate::Render( IMatRenderContext *pRenderContext, CParticleCollection *pParticles, void *pContext ) const +{ + C_OP_RenderScreenVelocityRotateContext_t *pCtx = reinterpret_cast( pContext ); + if ( pCtx->m_VisibilityData.m_bUseVisibility ) + { + SetupParticleVisibility( pParticles, &pCtx->m_VisibilityData, &VisibilityInputs, &pCtx->m_nQueryHandle ); + } + + + + // NOTE: This is interesting to support because at first we won't have all the various + // pixel-shader versions of SpriteCard, like modulate, twotexture, etc. etc. + VMatrix tempView; + + // Store matrices off so we can restore them in RenderEnd(). + pRenderContext->GetMatrix(MATERIAL_VIEW, &tempView); + + int nParticles; + const ParticleRenderData_t *pSortList = pParticles->GetRenderList( pRenderContext, false, &nParticles, &pCtx->m_VisibilityData ); + + size_t xyz_stride; + const fltx4 *xyz = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_XYZ, &xyz_stride ); + + size_t prev_xyz_stride; + const fltx4 *prev_xyz = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_PREV_XYZ, &prev_xyz_stride ); + + size_t rot_stride; + // const fltx4 *pRot = pParticles->GetM128AttributePtr( PARTICLE_ATTRIBUTE_ROTATION, &rot_stride ); + fltx4 *pRot = pParticles->GetM128AttributePtrForWrite( PARTICLE_ATTRIBUTE_ROTATION, &rot_stride ); + + float flForwardRadians = m_flForwardDegrees * ( M_PI / 180.0f ); + //float flRotateRateRadians = m_flRotateRateDegrees * ( M_PI / 180.0f ); + + for( int i = 0; i < nParticles; i++ ) + { + int hParticle = (--pSortList)->m_nIndex; + int nGroup = ( hParticle / 4 ); + int nOffset = hParticle & 0x3; + + int nXYZIndex = nGroup * xyz_stride; + Vector vecWorldPos( SubFloat( xyz[ nXYZIndex ], nOffset ), SubFloat( xyz[ nXYZIndex+1 ], nOffset ), SubFloat( xyz[ nXYZIndex+2 ], nOffset ) ); + Vector vecViewPos; + Vector3DMultiplyPosition( tempView, vecWorldPos, vecViewPos ); + + if (!IsFinite(vecViewPos.x)) + continue; + + int nPrevXYZIndex = nGroup * prev_xyz_stride; + Vector vecPrevWorldPos( SubFloat( prev_xyz[ nPrevXYZIndex ], nOffset ), SubFloat( prev_xyz[ nPrevXYZIndex+1 ], nOffset ), SubFloat( prev_xyz[ nPrevXYZIndex+2 ], nOffset ) ); + Vector vecPrevViewPos; + Vector3DMultiplyPosition( tempView, vecPrevWorldPos, vecPrevViewPos ); + + float rot = atan2( vecViewPos.y - vecPrevViewPos.y, vecViewPos.x - vecPrevViewPos.x ) + flForwardRadians; + SubFloat( pRot[ nGroup * rot_stride ], nOffset ) = rot; + } +} + + + + +//----------------------------------------------------------------------------- +// Installs renderers +//----------------------------------------------------------------------------- +void AddBuiltInParticleRenderers( void ) +{ +#ifdef _DEBUG + REGISTER_PARTICLE_OPERATOR( FUNCTION_RENDERER, C_OP_RenderPoints ); +#endif + REGISTER_PARTICLE_OPERATOR( FUNCTION_RENDERER, C_OP_RenderSprites ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_RENDERER, C_OP_RenderSpritesTrail ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_RENDERER, C_OP_RenderRope ); + REGISTER_PARTICLE_OPERATOR( FUNCTION_RENDERER, C_OP_RenderScreenVelocityRotate ); +#ifdef USE_BLOBULATOR + REGISTER_PARTICLE_OPERATOR( FUNCTION_RENDERER, C_OP_RenderBlobs ); +#endif // blobs +} + + + + diff --git a/particles/particle_sort.cpp b/particles/particle_sort.cpp new file mode 100644 index 0000000..0b4787c --- /dev/null +++ b/particles/particle_sort.cpp @@ -0,0 +1,194 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: particle system code +// +//===========================================================================// + +#include +#include "tier0/platform.h" +#include "tier0/vprof.h" +#include "particles/particles.h" +#include "psheet.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +static ALIGN16 ParticleRenderData_t s_SortedIndexList[MAX_PARTICLES_IN_A_SYSTEM] ALIGN16_POST; + + +enum EParticleSortKeyType +{ + SORT_KEY_NONE, + SORT_KEY_DISTANCE, + SORT_KEY_CREATION_TIME, +}; + + +template void s_GenerateData( Vector CameraPos, CParticleVisibilityData *pVisibilityData, CParticleCollection *pParticles ) +{ + fltx4 *pOutUnSorted = reinterpret_cast( s_SortedIndexList ); + + C4VAttributeIterator pXYZ( PARTICLE_ATTRIBUTE_XYZ, pParticles ); + CM128AttributeIterator pCreationTimeStamp( PARTICLE_ATTRIBUTE_CREATION_TIME, pParticles ); + CM128AttributeIterator pAlpha( PARTICLE_ATTRIBUTE_ALPHA, pParticles ); + CM128AttributeIterator pAlpha2( PARTICLE_ATTRIBUTE_ALPHA2, pParticles ); + CM128AttributeIterator pRadius( PARTICLE_ATTRIBUTE_RADIUS, pParticles ); + + int nParticles = pParticles->m_nActiveParticles; + + FourVectors EyePos; + EyePos.DuplicateVector( CameraPos ); + + + fltx4 fl4AlphaVis = ReplicateX4( pVisibilityData->m_flAlphaVisibility ); + fltx4 fl4RadVis = ReplicateX4( pVisibilityData->m_flRadiusVisibility ); + + // indexing. We will generate the index as float and use magicf2i to convert to integer + fltx4 fl4OutIdx = g_SIMD_0123; // 0 1 2 3 + + fl4OutIdx = AddSIMD( fl4OutIdx, Four_2ToThe23s); // fix as int + + bool bUseVis = pVisibilityData->m_bUseVisibility; + bool bCameraBias = pVisibilityData->m_flCameraBias != 0.0f; + fltx4 fl4Bias = ReplicateX4( pVisibilityData->m_flCameraBias ); + + fltx4 fl4AlphaScale = ReplicateX4( 255.0 ); + + do + { + fltx4 fl4X = pXYZ->x; + fltx4 fl4Y = pXYZ->y; + fltx4 fl4Z = pXYZ->z; + + fltx4 fl4SortKey; + if ( eSortKeyMode == SORT_KEY_DISTANCE ) + { + fltx4 Xdiff = SubSIMD( EyePos.x, fl4X ); + fltx4 Ydiff = SubSIMD( EyePos.y, fl4Y ); + fltx4 Zdiff = SubSIMD( EyePos.z, fl4Z ); + + if ( bCameraBias ) + { + FourVectors v4CameraBias; + v4CameraBias.x = Xdiff; + v4CameraBias.y = Ydiff; + v4CameraBias.z = Zdiff; + //v4CameraBias = VectorNormalizeFast( v4CameraBias ); + v4CameraBias.VectorNormalizeFast(); + v4CameraBias *= fl4Bias; + fl4X = SubSIMD( fl4X, v4CameraBias.x ); + fl4Y = SubSIMD( fl4Y, v4CameraBias.y ); + fl4Z = SubSIMD( fl4Z, v4CameraBias.z ); + + Xdiff = SubSIMD( EyePos.x, fl4X ); + Ydiff = SubSIMD( EyePos.y, fl4Y ); + Zdiff = SubSIMD( EyePos.z, fl4Z ); + } + + fl4SortKey = AddSIMD( MulSIMD( Xdiff, Xdiff ), + AddSIMD( MulSIMD( Ydiff, Ydiff ), + MulSIMD( Zdiff, Zdiff ) ) ); + } + else + { + Assert ( eSortKeyMode == SORT_KEY_CREATION_TIME || eSortKeyMode == SORT_KEY_NONE ); + fl4SortKey = *pCreationTimeStamp; + } + + fltx4 fl4FinalAlpha = MulSIMD( *pAlpha, *pAlpha2 ); + fltx4 fl4FinalRadius = *pRadius; + + if ( bUseVis ) + { + fl4FinalAlpha = MaxSIMD ( Four_Zeros, MinSIMD( Four_Ones, MulSIMD( fl4FinalAlpha, fl4AlphaVis) ) ); + fl4FinalRadius = MulSIMD( fl4FinalRadius, fl4RadVis ); + } + + // convert float 0..1 to int 0..255 + fl4FinalAlpha = AddSIMD( MulSIMD( fl4FinalAlpha, fl4AlphaScale ), Four_2ToThe23s ); + + // now, we will use simd transpose to write the output + fltx4 i4Indices = AndSIMD( fl4OutIdx, LoadAlignedSIMD( (float *) g_SIMD_Low16BitsMask ) ); + TransposeSIMD( fl4SortKey, i4Indices, fl4FinalRadius, fl4FinalAlpha ); + pOutUnSorted[0] = fl4SortKey; + pOutUnSorted[1] = i4Indices; + pOutUnSorted[2] = fl4FinalRadius; + pOutUnSorted[3] = fl4FinalAlpha; + + pOutUnSorted += 4; + fl4OutIdx = AddSIMD( fl4OutIdx, Four_Fours ); + + nParticles -= 4; + + ++pXYZ; + ++pAlpha; + ++pAlpha2; + ++pRadius; + } while( nParticles > 0 ); // we're not called with 0 + +} + + + +#define TREATASINT(x) ( *( ( (int32 const *)( &(x) ) ) ) ) + +static bool SortLessFunc( const ParticleRenderData_t &left, const ParticleRenderData_t &right ) +{ + return TREATASINT( left.m_flSortKey ) < TREATASINT( right.m_flSortKey ); + +} + + +void CParticleCollection::GenerateSortedIndexList( Vector vecCamera, CParticleVisibilityData *pVisibilityData, bool bSorted ) +{ + VPROF_BUDGET( "CParticleCollection::GenerateSortedIndexList", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + + if ( bSorted ) + { + s_GenerateData( vecCamera, pVisibilityData, this ); + } + else + s_GenerateData( vecCamera, pVisibilityData, this ); + +// check data +#if 0 + bool bBad = false; + for( int i = 0; i < m_nActiveParticles; i++ ) + { + Assert( s_SortedIndexList[i].m_nIndex == i ); + if ( s_SortedIndexList[i].m_nIndex != i ) + bBad = true; + } + if ( bBad ) + { + s_GenerateData( vecCamera, pVisibilityData, this ); + } +#endif + +#ifndef SWDS + if ( bSorted ) + { + // sort the output in place + std::make_heap( s_SortedIndexList, s_SortedIndexList + m_nActiveParticles, SortLessFunc ); + std::sort_heap( s_SortedIndexList, s_SortedIndexList + m_nActiveParticles, SortLessFunc ); + } +#endif +} + + +const ParticleRenderData_t *CParticleCollection::GetRenderList( IMatRenderContext *pRenderContext, bool bSorted, int *pNparticles, CParticleVisibilityData *pVisibilityData) +{ + if ( bSorted ) + bSorted = m_pDef->m_bShouldSort; + + Vector vecCamera; + pRenderContext->GetWorldSpaceCameraPosition( &vecCamera ); + *pNparticles = m_nActiveParticles; + GenerateSortedIndexList( vecCamera, pVisibilityData, bSorted ); + return s_SortedIndexList+m_nActiveParticles; +} + + + + diff --git a/particles/particles.cpp b/particles/particles.cpp new file mode 100644 index 0000000..62c22f6 --- /dev/null +++ b/particles/particles.cpp @@ -0,0 +1,3881 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: particle system code +// +//===========================================================================// + +#include "tier0/platform.h" +#include "particles/particles.h" +#include "psheet.h" +#include "filesystem.h" +#include "tier2/tier2.h" +#include "tier2/fileutils.h" +#include "tier1/utlbuffer.h" +#include "tier1/UtlStringMap.h" +#include "tier1/strtools.h" +#include "dmxloader/dmxloader.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/imaterialvar.h" +#include "materialsystem/itexture.h" +#include "materialsystem/imesh.h" +#include "tier0/vprof.h" +#include "tier1/KeyValues.h" +#include "tier1/lzmaDecoder.h" +#include "random_floats.h" +#include "vtf/vtf.h" +#include "studio.h" +#include "particles_internal.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + + + +// rename table from the great rename +static const char *s_RemapOperatorNameTable[]={ + "alpha_fade", "Alpha Fade and Decay", + "alpha_fade_in_random", "Alpha Fade In Random", + "alpha_fade_out_random", "Alpha Fade Out Random", + "basic_movement", "Movement Basic", + "color_fade", "Color Fade", + "controlpoint_light", "Color Light From Control Point", + "Dampen Movement Relative to Control Point", "Movement Dampen Relative to Control Point", + "Distance Between Control Points Scale", "Remap Distance Between Two Control Points to Scalar", + "Distance to Control Points Scale", "Remap Distance to Control Point to Scalar", + "lifespan_decay", "Lifespan Decay", + "lock to bone", "Movement Lock to Bone", + "postion_lock_to_controlpoint", "Movement Lock to Control Point", + "maintain position along path", "Movement Maintain Position Along Path", + "Match Particle Velocities", "Movement Match Particle Velocities", + "Max Velocity", "Movement Max Velocity", + "noise", "Noise Scalar", + "vector noise", "Noise Vector", + "oscillate_scalar", "Oscillate Scalar", + "oscillate_vector", "Oscillate Vector", + "Orient Rotation to 2D Direction", "Rotation Orient to 2D Direction", + "radius_scale", "Radius Scale", + "Random Cull", "Cull Random", + "remap_scalar", "Remap Scalar", + "rotation_movement", "Rotation Basic", + "rotation_spin", "Rotation Spin Roll", + "rotation_spin yaw", "Rotation Spin Yaw", + "alpha_random", "Alpha Random", + "color_random", "Color Random", + "create from parent particles", "Position From Parent Particles", + "Create In Hierarchy", "Position In CP Hierarchy", + "random position along path", "Position Along Path Random", + "random position on model", "Position on Model Random", + "sequential position along path", "Position Along Path Sequential", + "position_offset_random", "Position Modify Offset Random", + "position_warp_random", "Position Modify Warp Random", + "position_within_box", "Position Within Box Random", + "position_within_sphere", "Position Within Sphere Random", + "Inherit Velocity", "Velocity Inherit from Control Point", + "Initial Repulsion Velocity", "Velocity Repulse from World", + "Initial Velocity Noise", "Velocity Noise", + "Initial Scalar Noise", "Remap Noise to Scalar", + "Lifespan from distance to world", "Lifetime from Time to Impact", + "Pre-Age Noise", "Lifetime Pre-Age Noise", + "lifetime_random", "Lifetime Random", + "radius_random", "Radius Random", + "random yaw", "Rotation Yaw Random", + "Randomly Flip Yaw", "Rotation Yaw Flip Random", + "rotation_random", "Rotation Random", + "rotation_speed_random", "Rotation Speed Random", + "sequence_random", "Sequence Random", + "second_sequence_random", "Sequence Two Random", + "trail_length_random", "Trail Length Random", + "velocity_random", "Velocity Random", +}; + +static char const *RemapOperatorName( char const *pOpName ) +{ + for( int i = 0 ; i < ARRAYSIZE( s_RemapOperatorNameTable ) ; i += 2 ) + { + if ( Q_stricmp( pOpName, s_RemapOperatorNameTable[i] ) == 0 ) + { + return s_RemapOperatorNameTable[i + 1 ]; + } + } + return pOpName; +} + + +// This is the soft limit - if we exceed this, we spit out a report of all the particles in the frame +#define MAX_PARTICLE_VERTS 50000 +// These are some limits that control g_pParticleSystemMgr->ParticleThrottleScaling() and g_pParticleSystemMgr->ParticleThrottleRandomEnable() +//ConVar cl_particle_scale_lower ( "cl_particle_scale_lower", "20000", FCVAR_CLIENTDLL | FCVAR_CHEAT ); +//ConVar cl_particle_scale_upper ( "cl_particle_scale_upper", "40000", FCVAR_CLIENTDLL | FCVAR_CHEAT ); +#define CL_PARTICLE_SCALE_LOWER 20000 +#define CL_PARTICLE_SCALE_UPPER 40000 + + +//----------------------------------------------------------------------------- +// Default implementation of particle system mgr +//----------------------------------------------------------------------------- +static CParticleSystemMgr s_ParticleSystemMgr; +CParticleSystemMgr *g_pParticleSystemMgr = &s_ParticleSystemMgr; + + +int g_nParticle_Multiplier = 1; + + +//----------------------------------------------------------------------------- +// Particle dictionary +//----------------------------------------------------------------------------- +class CParticleSystemDictionary +{ +public: + ~CParticleSystemDictionary(); + + CParticleSystemDefinition* AddParticleSystem( CDmxElement *pParticleSystem ); + int Count() const; + int NameCount() const; + CParticleSystemDefinition* GetParticleSystem( int i ); + ParticleSystemHandle_t FindParticleSystemHandle( const char *pName ); + CParticleSystemDefinition* FindParticleSystem( ParticleSystemHandle_t h ); + CParticleSystemDefinition* FindParticleSystem( const char *pName ); + CParticleSystemDefinition* FindParticleSystem( const DmObjectId_t &id ); + + CParticleSystemDefinition* operator[]( int idx ) + { + return m_ParticleNameMap[ idx ]; + } + +private: + typedef CUtlStringMap< CParticleSystemDefinition * > ParticleNameMap_t; + typedef CUtlVector< CParticleSystemDefinition* > ParticleIdMap_t; + + void DestroyExistingElement( CDmxElement *pElement ); + + ParticleNameMap_t m_ParticleNameMap; + ParticleIdMap_t m_ParticleIdMap; +}; + + +//----------------------------------------------------------------------------- +// Destructor +//----------------------------------------------------------------------------- +CParticleSystemDictionary::~CParticleSystemDictionary() +{ + int nCount = m_ParticleIdMap.Count(); + for ( int i = 0; i < nCount; ++i ) + { + delete m_ParticleIdMap[i]; + } +} + + +//----------------------------------------------------------------------------- +// Destroys an existing element, returns if this element should be added to the name list +//----------------------------------------------------------------------------- +void CParticleSystemDictionary::DestroyExistingElement( CDmxElement *pElement ) +{ + const char *pParticleSystemName = pElement->GetName(); + bool bPreventNameBasedLookup = pElement->GetValue( "preventNameBasedLookup" ); + if ( !bPreventNameBasedLookup ) + { + if ( m_ParticleNameMap.Defined( pParticleSystemName ) ) + { + CParticleSystemDefinition *pDef = m_ParticleNameMap[ pParticleSystemName ]; + delete pDef; + m_ParticleNameMap[ pParticleSystemName ] = NULL; + } + return; + } + + // Use id based lookup instead + int nCount = m_ParticleIdMap.Count(); + const DmObjectId_t& id = pElement->GetId(); + for ( int i = 0; i < nCount; ++i ) + { + // Was already removed by the name lookup + if ( !IsUniqueIdEqual( m_ParticleIdMap[i]->GetId(), id ) ) + continue; + + CParticleSystemDefinition *pDef = m_ParticleIdMap[ i ]; + m_ParticleIdMap.FastRemove( i ); + delete pDef; + break; + } +} + + +//----------------------------------------------------------------------------- +// Adds a destructor +//----------------------------------------------------------------------------- +CParticleSystemDefinition* CParticleSystemDictionary::AddParticleSystem( CDmxElement *pParticleSystem ) +{ + if ( Q_stricmp( pParticleSystem->GetTypeString(), "DmeParticleSystemDefinition" ) ) + return NULL; + + DestroyExistingElement( pParticleSystem ); + + CParticleSystemDefinition *pDef = new CParticleSystemDefinition; + + // Must add the def to the maps before Read() because Read() may create new child particle systems + bool bPreventNameBasedLookup = pParticleSystem->GetValue( "preventNameBasedLookup" ); + if ( !bPreventNameBasedLookup ) + { + m_ParticleNameMap[ pParticleSystem->GetName() ] = pDef; + } + else + { + m_ParticleIdMap.AddToTail( pDef ); + } + + pDef->Read( pParticleSystem ); + return pDef; +} + +int CParticleSystemDictionary::NameCount() const +{ + return m_ParticleNameMap.GetNumStrings(); +} + +int CParticleSystemDictionary::Count() const +{ + return m_ParticleIdMap.Count(); +} + +CParticleSystemDefinition* CParticleSystemDictionary::GetParticleSystem( int i ) +{ + return m_ParticleIdMap[i]; +} + +ParticleSystemHandle_t CParticleSystemDictionary::FindParticleSystemHandle( const char *pName ) +{ + return m_ParticleNameMap.Find( pName ); +} + +CParticleSystemDefinition* CParticleSystemDictionary::FindParticleSystem( ParticleSystemHandle_t h ) +{ + if ( h == UTL_INVAL_SYMBOL || h >= m_ParticleNameMap.GetNumStrings() ) + return NULL; + return m_ParticleNameMap[ h ]; +} + +CParticleSystemDefinition* CParticleSystemDictionary::FindParticleSystem( const char *pName ) +{ + if ( m_ParticleNameMap.Defined( pName ) ) + return m_ParticleNameMap[ pName ]; + return NULL; +} + +CParticleSystemDefinition* CParticleSystemDictionary::FindParticleSystem( const DmObjectId_t &id ) +{ + int nCount = m_ParticleIdMap.Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( IsUniqueIdEqual( m_ParticleIdMap[i]->GetId(), id ) ) + return m_ParticleIdMap[i]; + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// For editing, create a faked particle operator definition for children +// The only thing used in here is GetUnpackStructure. +//----------------------------------------------------------------------------- +BEGIN_DMXELEMENT_UNPACK( ParticleChildrenInfo_t ) + DMXELEMENT_UNPACK_FIELD( "delay", "0.0", float, m_flDelay ) +END_DMXELEMENT_UNPACK( ParticleChildrenInfo_t, s_ChildrenInfoUnpack ) + +class CChildOperatorDefinition : public IParticleOperatorDefinition +{ +public: + virtual const char *GetName() const { Assert(0); return NULL; } + virtual CParticleOperatorInstance *CreateInstance( const DmObjectId_t &id ) const { Assert(0); return NULL; } + // virtual void DestroyInstance( CParticleOperatorInstance *pInstance ) const { Assert(0); } + virtual const DmxElementUnpackStructure_t* GetUnpackStructure() const + { + return s_ChildrenInfoUnpack; + } + virtual ParticleOperatorId_t GetId() const { return OPERATOR_GENERIC; } + virtual bool IsObsolete() const { return false; } + virtual size_t GetClassSize() const { return 0; } +}; + +static CChildOperatorDefinition s_ChildOperatorDefinition; + + +//----------------------------------------------------------------------------- +// +// CParticleSystemDefinition +// +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Unpack structure for CParticleSystemDefinition +//----------------------------------------------------------------------------- +BEGIN_DMXELEMENT_UNPACK( CParticleSystemDefinition ) + DMXELEMENT_UNPACK_FIELD( "max_particles", "1000", int, m_nMaxParticles ) + DMXELEMENT_UNPACK_FIELD( "initial_particles", "0", int, m_nInitialParticles ) + DMXELEMENT_UNPACK_FIELD_STRING_USERDATA( "material", "vgui/white", m_pszMaterialName, "vmtPicker" ) + DMXELEMENT_UNPACK_FIELD( "bounding_box_min", "-10 -10 -10", Vector, m_BoundingBoxMin ) + DMXELEMENT_UNPACK_FIELD( "bounding_box_max", "10 10 10", Vector, m_BoundingBoxMax ) + DMXELEMENT_UNPACK_FIELD( "cull_radius", "0", float, m_flCullRadius ) + DMXELEMENT_UNPACK_FIELD( "cull_cost", "1", float, m_flCullFillCost ) + DMXELEMENT_UNPACK_FIELD( "cull_control_point", "0", int, m_nCullControlPoint ) + DMXELEMENT_UNPACK_FIELD_STRING( "cull_replacement_definition", "", m_pszCullReplacementName ) + DMXELEMENT_UNPACK_FIELD( "radius", "5", float, m_flConstantRadius ) + DMXELEMENT_UNPACK_FIELD( "color", "255 255 255 255", Color, m_ConstantColor ) + DMXELEMENT_UNPACK_FIELD( "rotation", "0", float, m_flConstantRotation ) + DMXELEMENT_UNPACK_FIELD( "rotation_speed", "0", float, m_flConstantRotationSpeed ) + DMXELEMENT_UNPACK_FIELD( "sequence_number", "0", int, m_nConstantSequenceNumber ) + DMXELEMENT_UNPACK_FIELD( "sequence_number 1", "0", int, m_nConstantSequenceNumber1 ) + DMXELEMENT_UNPACK_FIELD( "group id", "0", int, m_nGroupID ) + DMXELEMENT_UNPACK_FIELD( "maximum time step", "0.1", float, m_flMaximumTimeStep ) + DMXELEMENT_UNPACK_FIELD( "maximum sim tick rate", "0.0", float, m_flMaximumSimTime ) + DMXELEMENT_UNPACK_FIELD( "minimum sim tick rate", "0.0", float, m_flMinimumSimTime ) + DMXELEMENT_UNPACK_FIELD( "minimum rendered frames", "0", int, m_nMinimumFrames ) + DMXELEMENT_UNPACK_FIELD( "control point to disable rendering if it is the camera", "-1", int, m_nSkipRenderControlPoint ) + DMXELEMENT_UNPACK_FIELD( "maximum draw distance", "100000.0", float, m_flMaxDrawDistance ) + DMXELEMENT_UNPACK_FIELD( "time to sleep when not drawn", "8", float, m_flNoDrawTimeToGoToSleep ) + DMXELEMENT_UNPACK_FIELD( "Sort particles", "1", bool, m_bShouldSort ) + DMXELEMENT_UNPACK_FIELD( "batch particle systems", "0", bool, m_bShouldBatch ) + DMXELEMENT_UNPACK_FIELD( "view model effect", "0", bool, m_bViewModelEffect ) +END_DMXELEMENT_UNPACK( CParticleSystemDefinition, s_pParticleSystemDefinitionUnpack ) + + + +//----------------------------------------------------------------------------- +// +// CParticleOperatorDefinition begins here +// A template describing how a particle system will function +// +//----------------------------------------------------------------------------- +void CParticleSystemDefinition::UnlinkAllCollections() +{ + while ( m_pFirstCollection ) + { + m_pFirstCollection->UnlinkFromDefList(); + } +} + +const char *CParticleSystemDefinition::GetName() const +{ + return m_Name; +} + + +//----------------------------------------------------------------------------- +// Should we always precache this? +//----------------------------------------------------------------------------- +bool CParticleSystemDefinition::ShouldAlwaysPrecache() const +{ + return m_bAlwaysPrecache; +} + + +//----------------------------------------------------------------------------- +// Precache/uncache +//----------------------------------------------------------------------------- +void CParticleSystemDefinition::Precache() +{ + if ( m_bIsPrecached ) + return; + + m_bIsPrecached = true; +#ifndef SWDS + m_Material.Init( MaterialName(), TEXTURE_GROUP_OTHER, true ); +#endif + + int nChildCount = m_Children.Count(); + for ( int i = 0; i < nChildCount; ++i ) + { + CParticleSystemDefinition *pChild; + if ( m_Children[i].m_bUseNameBasedLookup ) + { + pChild = g_pParticleSystemMgr->FindParticleSystem( m_Children[i].m_Name ); + } + else + { + pChild = g_pParticleSystemMgr->FindParticleSystem( m_Children[i].m_Id ); + } + + if ( pChild ) + { + pChild->Precache(); + } + } +} + +void CParticleSystemDefinition::Uncache() +{ + if ( !m_bIsPrecached ) + return; + + m_bIsPrecached = false; + m_Material.Shutdown(); +// m_Material.Init( "debug/particleerror", TEXTURE_GROUP_OTHER, true ); + + int nChildCount = m_Children.Count(); + for ( int i = 0; i < nChildCount; ++i ) + { + CParticleSystemDefinition *pChild; + if ( m_Children[i].m_bUseNameBasedLookup ) + { + pChild = g_pParticleSystemMgr->FindParticleSystem( m_Children[i].m_Name ); + } + else + { + pChild = g_pParticleSystemMgr->FindParticleSystem( m_Children[i].m_Id ); + } + + if ( pChild ) + { + pChild->Uncache(); + } + } +} + + +//----------------------------------------------------------------------------- +// Has this been precached? +//----------------------------------------------------------------------------- +bool CParticleSystemDefinition::IsPrecached() const +{ + return m_bIsPrecached; +} + +//----------------------------------------------------------------------------- +// Helper methods to help with unserialization +//----------------------------------------------------------------------------- +void CParticleSystemDefinition::ParseOperators( + const char *pszOpKey, ParticleFunctionType_t nFunctionType, + CDmxElement *pElement, + CUtlVector &outList) +{ + const CDmxAttribute* pAttribute = pElement->GetAttribute( pszOpKey ); + if ( !pAttribute || pAttribute->GetType() != AT_ELEMENT_ARRAY ) + return; + + const CUtlVector &flist = g_pParticleSystemMgr->GetAvailableParticleOperatorList( nFunctionType ); + + const CUtlVector< CDmxElement* >& ops = pAttribute->GetArray( ); + int nCount = ops.Count(); + for ( int i = 0; i < nCount; ++i ) + { + const char *pOrigName = ops[i]->GetValueString( "functionName" ); + char const *pOpName = RemapOperatorName( pOrigName ); + if ( pOpName != pOrigName ) + { + pElement->SetValue( "functionName", pOpName ); + } + bool bFound = false; + int nFunctionCount = flist.Count(); + for( int j = 0; j < nFunctionCount; ++j ) + { + if ( Q_stricmp( pOpName, flist[j]->GetName() ) ) + continue; + + // found it! + bFound = true; + + CParticleOperatorInstance *pNewRef = flist[j]->CreateInstance( ops[i]->GetId() ); + const DmxElementUnpackStructure_t *pUnpack = flist[j]->GetUnpackStructure(); + if ( pUnpack ) + { + ops[i]->UnpackIntoStructure( pNewRef, flist[j]->GetClassSize(), pUnpack ); + } + pNewRef->InitParams( this, pElement ); + m_nAttributeReadMask |= pNewRef->GetReadAttributes(); + m_nControlPointReadMask |= pNewRef->GetReadControlPointMask(); + + switch( nFunctionType ) + { + case FUNCTION_INITIALIZER: + case FUNCTION_EMITTER: + m_nPerParticleInitializedAttributeMask |= pNewRef->GetWrittenAttributes(); + Assert( pNewRef->GetReadInitialAttributes() == 0 ); + break; + + case FUNCTION_OPERATOR: + m_nPerParticleUpdatedAttributeMask |= pNewRef->GetWrittenAttributes(); + m_nInitialAttributeReadMask |= pNewRef->GetReadInitialAttributes(); + break; + + case FUNCTION_RENDERER: + m_nPerParticleUpdatedAttributeMask |= pNewRef->GetWrittenAttributes(); + m_nInitialAttributeReadMask |= pNewRef->GetReadInitialAttributes(); + break; + } + + // Special case: Reading particle ID means we're reading the initial particle id + if ( ( pNewRef->GetReadAttributes() | pNewRef->GetReadInitialAttributes() ) & PARTICLE_ATTRIBUTE_PARTICLE_ID_MASK ) + { + m_nInitialAttributeReadMask |= PARTICLE_ATTRIBUTE_PARTICLE_ID_MASK; + m_nPerParticleInitializedAttributeMask |= PARTICLE_ATTRIBUTE_PARTICLE_ID_MASK; + } + + outList.AddToTail( pNewRef ); + break; + } + + if ( !bFound ) + { + if ( flist.Count() ) // don't warn if no ops of that type defined (server) + Warning( "Didn't find particle function %s\n", pOpName ); + } + } +} + +void CParticleSystemDefinition::ParseChildren( CDmxElement *pElement ) +{ + const CUtlVector& children = pElement->GetArray( "children" ); + int nCount = children.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmxElement *pChild = children[i]->GetValue( "child" ); + if ( !pChild || Q_stricmp( pChild->GetTypeString(), "DmeParticleSystemDefinition" ) ) + continue; + + int j = m_Children.AddToTail(); + children[i]->UnpackIntoStructure( &m_Children[j], sizeof( m_Children[j] ), s_ChildrenInfoUnpack ); + m_Children[j].m_bUseNameBasedLookup = !pChild->GetValue( "preventNameBasedLookup" ); + if ( m_Children[j].m_bUseNameBasedLookup ) + { + m_Children[j].m_Name = pChild->GetName(); + } + else + { + CopyUniqueId( pChild->GetId(), &m_Children[j].m_Id ); + } + + // Check to see if this child has been encountered already, and if not, then + // create a new particle definition for this child + g_pParticleSystemMgr->AddParticleSystem( pChild ); + } +} + +void CParticleSystemDefinition::Read( CDmxElement *pElement ) +{ + m_Name = pElement->GetName(); + CopyUniqueId( pElement->GetId(), &m_Id ); + pElement->UnpackIntoStructure( this, sizeof( *this ), s_pParticleSystemDefinitionUnpack ); + +#ifndef SWDS // avoid material/ texture load +// NOTE: This makes a X appear for uncached particles. +// m_Material.Init( "debug/particleerror", TEXTURE_GROUP_OTHER, true ); +#endif + + if ( m_nInitialParticles < 0 ) + { + m_nInitialParticles = 0; + } + if ( m_nMaxParticles < 1 ) + { + m_nMaxParticles = 1; + } + m_nMaxParticles *= g_nParticle_Multiplier; + m_nMaxParticles = min( m_nMaxParticles, MAX_PARTICLES_IN_A_SYSTEM ); + if ( m_flCullRadius > 0 ) + { + m_nControlPointReadMask |= 1ULL << m_nCullControlPoint; + } + + ParseOperators( "renderers", FUNCTION_RENDERER, pElement, m_Renderers ); + ParseOperators( "operators", FUNCTION_OPERATOR, pElement, m_Operators ); + ParseOperators( "initializers", FUNCTION_INITIALIZER, pElement, m_Initializers ); + ParseOperators( "emitters", FUNCTION_EMITTER, pElement, m_Emitters ); + ParseChildren( pElement ); + ParseOperators( "forces", FUNCTION_FORCEGENERATOR, pElement, m_ForceGenerators ); + ParseOperators( "constraints", FUNCTION_CONSTRAINT, pElement, m_Constraints ); + SetupContextData(); +} + +IMaterial *CParticleSystemDefinition::GetMaterial() const +{ + // NOTE: This has to be this way to ensure we don't load every freaking material @ startup + Assert( IsPrecached() ); + if ( !IsPrecached() ) + return NULL; + return m_Material; +} + + +//---------------------------------------------------------------------------------- +// Does the particle system use the power of two frame buffer texture (refraction?) +//---------------------------------------------------------------------------------- +bool CParticleSystemDefinition::UsesPowerOfTwoFrameBufferTexture() +{ + // NOTE: This has to be this way to ensure we don't load every freaking material @ startup + Assert( IsPrecached() ); + return m_Material->NeedsPowerOfTwoFrameBufferTexture( false ); // The false checks if it will ever need the frame buffer, not just this frame +} + +//---------------------------------------------------------------------------------- +// Does the particle system use the power of two frame buffer texture (refraction?) +//---------------------------------------------------------------------------------- +bool CParticleSystemDefinition::UsesFullFrameBufferTexture() +{ + // NOTE: This has to be this way to ensure we don't load every freaking material @ startup + Assert( IsPrecached() ); + return m_Material->NeedsFullFrameBufferTexture( false ); // The false checks if it will ever need the frame buffer, not just this frame +} + +//----------------------------------------------------------------------------- +// Helper methods to write particle systems +//----------------------------------------------------------------------------- +void CParticleSystemDefinition::WriteOperators( CDmxElement *pElement, + const char *pOpKeyName, const CUtlVector &inList ) +{ + CDmxElementModifyScope modify( pElement ); + CDmxAttribute* pAttribute = pElement->AddAttribute( pOpKeyName ); + CUtlVector< CDmxElement* >& ops = pAttribute->GetArrayForEdit( ); + + int nCount = inList.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmxElement *pOperator = CreateDmxElement( "DmeParticleOperator" ); + ops.AddToTail( pOperator ); + + const IParticleOperatorDefinition *pDef = inList[i]->GetDefinition(); + pOperator->SetValue( "name", pDef->GetName() ); + pOperator->SetValue( "functionName", pDef->GetName() ); + + const DmxElementUnpackStructure_t *pUnpack = pDef->GetUnpackStructure(); + if ( pUnpack ) + { + pOperator->AddAttributesFromStructure( inList[i], pUnpack ); + } + } +} + +void CParticleSystemDefinition::WriteChildren( CDmxElement *pElement ) +{ + CDmxElementModifyScope modify( pElement ); + CDmxAttribute* pAttribute = pElement->AddAttribute( "children" ); + CUtlVector< CDmxElement* >& children = pAttribute->GetArrayForEdit( ); + int nCount = m_Children.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmxElement *pChildRef = CreateDmxElement( "DmeParticleChild" ); + children.AddToTail( pChildRef ); + children[i]->AddAttributesFromStructure( &m_Children[i], s_ChildrenInfoUnpack ); + CDmxElement *pChildParticleSystem; + if ( m_Children[i].m_bUseNameBasedLookup ) + { + pChildParticleSystem = g_pParticleSystemMgr->CreateParticleDmxElement( m_Children[i].m_Name ); + } + else + { + pChildParticleSystem = g_pParticleSystemMgr->CreateParticleDmxElement( m_Children[i].m_Id ); + } + pChildRef->SetValue( "name", pChildParticleSystem->GetName() ); + pChildRef->SetValue( "child", pChildParticleSystem ); + } +} + +CDmxElement *CParticleSystemDefinition::Write() +{ + const char *pName = GetName(); + + CDmxElement *pElement = CreateDmxElement( "DmeParticleSystemDefinition" ); + pElement->SetValue( "name", pName ); + pElement->AddAttributesFromStructure( this, s_pParticleSystemDefinitionUnpack ); + WriteOperators( pElement, "renderers",m_Renderers ); + WriteOperators( pElement, "operators", m_Operators ); + WriteOperators( pElement, "initializers", m_Initializers ); + WriteOperators( pElement, "emitters", m_Emitters ); + WriteChildren( pElement ); + WriteOperators( pElement, "forces", m_ForceGenerators ); + WriteOperators( pElement, "constraints", m_Constraints ); + + return pElement; +} + +void CParticleSystemDefinition::SetupContextData( void ) +{ + // calcuate sizes and offsets for context data + CUtlVector *olists[] = { + &m_Operators, &m_Renderers, &m_Initializers, &m_Emitters, &m_ForceGenerators, + &m_Constraints + }; + CUtlVector *offsetLists[] = { + &m_nOperatorsCtxOffsets, &m_nRenderersCtxOffsets, + &m_nInitializersCtxOffsets, &m_nEmittersCtxOffsets, + &m_nForceGeneratorsCtxOffsets, &m_nConstraintsCtxOffsets, + }; + + // loop through all operators, fill in offset entries, and calulate total data needed + m_nContextDataSize = 0; + for( int i = 0; i < NELEMS( olists ); i++ ) + { + int nCount = olists[i]->Count(); + for( int j = 0; j < nCount; j++ ) + { + offsetLists[i]->AddToTail( m_nContextDataSize ); + m_nContextDataSize += (*olists[i])[j]->GetRequiredContextBytes(); + // align context data + m_nContextDataSize = (m_nContextDataSize + 15) & (~0xf ); + } + } +} + + +//----------------------------------------------------------------------------- +// Finds an operator by id +//----------------------------------------------------------------------------- +CUtlVector *CParticleSystemDefinition::GetOperatorList( ParticleFunctionType_t type ) +{ + switch( type ) + { + case FUNCTION_EMITTER: + return &m_Emitters; + case FUNCTION_RENDERER: + return &m_Renderers; + case FUNCTION_INITIALIZER: + return &m_Initializers; + case FUNCTION_OPERATOR: + return &m_Operators; + case FUNCTION_FORCEGENERATOR: + return &m_ForceGenerators; + case FUNCTION_CONSTRAINT: + return &m_Constraints; + default: + Assert(0); + return NULL; + } +} + + +//----------------------------------------------------------------------------- +// Finds an operator by id +//----------------------------------------------------------------------------- +CParticleOperatorInstance *CParticleSystemDefinition::FindOperatorById( ParticleFunctionType_t type, const DmObjectId_t &id ) +{ + CUtlVector *pVec = GetOperatorList( type ); + if ( !pVec ) + return NULL; + + int nCount = pVec->Count(); + for ( int i = 0; i < nCount; ++i ) + { + if ( IsUniqueIdEqual( id, pVec->Element(i)->GetId() ) ) + return pVec->Element(i); + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// +// CParticleOperatorInstance +// +//----------------------------------------------------------------------------- +void CParticleOperatorInstance::InitNewParticles( CParticleCollection *pParticles, + int nFirstParticle, int nParticleCount, + int nAttributeWriteMask, void *pContext ) const +{ + if ( !nParticleCount ) + return; + + if ( nParticleCount < 16 ) // don't bother with vectorizing + // unless enough particles to bother + { + InitNewParticlesScalar( pParticles, nFirstParticle, nParticleCount, nAttributeWriteMask, pContext ); + return; + } + + int nHead = nFirstParticle & 3; + if ( nHead ) + { + // need to init up to 3 particles before we are block aligned + int nHeadCount = min( nParticleCount, 4 - nHead ); + InitNewParticlesScalar( pParticles, nFirstParticle, nHeadCount, nAttributeWriteMask, pContext ); + nParticleCount -= nHeadCount; + nFirstParticle += nHeadCount; + } + + // now, we are aligned + int nBlockCount = nParticleCount / 4; + if ( nBlockCount ) + { + InitNewParticlesBlock( pParticles, nFirstParticle / 4, nBlockCount, nAttributeWriteMask, pContext ); + nParticleCount -= 4 * nBlockCount; + nFirstParticle += 4 * nBlockCount; + } + + // do tail + if ( nParticleCount ) + { + InitNewParticlesScalar( pParticles, nFirstParticle, nParticleCount, nAttributeWriteMask, pContext ); + } +} + + +//----------------------------------------------------------------------------- +// +// CParticleCollection +// +//----------------------------------------------------------------------------- + +//------------------------------------------------------------------------------ +// need custom new/delete for alignment for simd +//------------------------------------------------------------------------------ +#include "tier0/memdbgoff.h" +void *CParticleCollection::operator new( size_t nSize ) +{ + return MemAlloc_AllocAligned( nSize, 16 ); +} + +void* CParticleCollection::operator new( size_t nSize, int nBlockUse, const char *pFileName, int nLine ) +{ + return MemAlloc_AllocAligned( nSize, 16, pFileName, nLine ); +} + +void CParticleCollection::operator delete(void *pData) +{ + if ( pData ) + { + MemAlloc_FreeAligned( pData ); + } +} + +void CParticleCollection::operator delete( void* pData, int nBlockUse, const char *pFileName, int nLine ) +{ + if ( pData ) + { + MemAlloc_FreeAligned( pData ); + } +} + +void *CWorldCollideContextData::operator new( size_t nSize ) +{ + return MemAlloc_AllocAligned( nSize, 16 ); +} + +void* CWorldCollideContextData::operator new( size_t nSize, int nBlockUse, const char *pFileName, int nLine ) +{ + return MemAlloc_AllocAligned( nSize, 16, pFileName, nLine ); +} + +void CWorldCollideContextData::operator delete(void *pData) +{ + if ( pData ) + { + MemAlloc_FreeAligned( pData ); + } +} + +void CWorldCollideContextData::operator delete( void* pData, int nBlockUse, const char *pFileName, int nLine ) +{ + if ( pData ) + { + MemAlloc_FreeAligned( pData ); + } +} + + +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CParticleCollection::CParticleCollection( ) +{ + COMPILE_TIME_ASSERT( ( MAX_RANDOM_FLOATS & ( MAX_RANDOM_FLOATS - 1 ) ) == 0 ); + COMPILE_TIME_ASSERT( sizeof( s_pRandomFloats ) / sizeof( float ) >= MAX_RANDOM_FLOATS ); + + m_pNextDef = m_pPrevDef = NULL; + m_nUniqueParticleId = 0; + m_nRandomQueryCount = 0; + m_bIsScrubbable = false; + m_bIsRunningInitializers = false; + m_bIsRunningOperators = false; + m_bIsTranslucent = false; + m_bIsTwoPass = false; + m_bIsBatchable = false; + m_bUsesPowerOfTwoFrameBufferTexture = false; + m_bUsesFullFrameBufferTexture = false; + m_pRenderOp = NULL; + m_nControlPointReadMask = 0; + + m_flLastMinDistSqr = m_flLastMaxDistSqr = 0.0f; + m_flMinDistSqr = m_flMaxDistSqr = 0.0f; + m_flOOMaxDistSqr = 1.0f; + m_vecLastCameraPos.Init(); + m_MinBounds.Init(); + m_MaxBounds.Init(); + m_bBoundsValid = false; + + memset( m_ControlPoints, 0, sizeof(m_ControlPoints) ); + + // align all control point orientations with the global world + for( int i=0; i < MAX_PARTICLE_CONTROL_POINTS; i++ ) + { + m_ControlPoints[i].m_ForwardVector.Init( 0, 1, 0 ); + m_ControlPoints[i].m_UpVector.Init( 0, 0, 1 ); + m_ControlPoints[i].m_RightVector.Init( 1, 0, 0 ); + } + + memset( m_pParticleInitialAttributes, 0, sizeof(m_pParticleInitialAttributes) ); + + m_nPerParticleUpdatedAttributeMask = 0; + m_nPerParticleInitializedAttributeMask = 0; + m_nPerParticleReadInitialAttributeMask = 0; + m_pParticleMemory = NULL; + m_pParticleInitialMemory = NULL; + m_pConstantMemory = NULL; + m_nActiveParticles = 0; + m_nPaddedActiveParticles = 0; + m_flCurTime = 0.0f; + m_fl4CurTime = Four_Zeros; + m_flDt = 0.0f; + m_flPreviousDt = 0.05f; + m_nParticleFlags = PCFLAGS_FIRST_FRAME; + m_pOperatorContextData = NULL; + m_pNext = m_pPrev = NULL; + m_nRandomSeed = 0; + m_pDef = NULL; + m_nAllocatedParticles = 0; + m_nMaxAllowedParticles = 0; + m_bDormant = false; + m_bEmissionStopped = false; + m_bRequiresOrderInvariance = false; + m_nSimulatedFrames = 0; + + m_nNumParticlesToKill = 0; + m_pParticleKillList = NULL; + m_nHighestCP = 0; + memset( m_pCollisionCacheData, 0, sizeof( m_pCollisionCacheData ) ); + m_pParent = NULL; + m_LocalLighting = Color(255, 255, 255, 255); + m_LocalLightingCP = -1; + +} + +CParticleCollection::~CParticleCollection( void ) +{ + UnlinkFromDefList(); + + m_Children.Purge(); + + if ( m_pParticleMemory ) + { + delete[] m_pParticleMemory; + } + if ( m_pParticleInitialMemory ) + { + delete[] m_pParticleInitialMemory; + } + if ( m_pConstantMemory ) + { + delete[] m_pConstantMemory; + } + if ( m_pOperatorContextData ) + { + MemAlloc_FreeAligned( m_pOperatorContextData ); + } + + for( int i = 0 ; i < ARRAYSIZE( m_pCollisionCacheData ) ; i++ ) + { + if ( m_pCollisionCacheData[i] ) + { + delete m_pCollisionCacheData[i]; + } + } +} + + +//----------------------------------------------------------------------------- +// Initialization +//----------------------------------------------------------------------------- +void CParticleCollection::Init( CParticleSystemDefinition *pDef, float flDelay, int nRandomSeed ) +{ + m_pDef = pDef; + + // Link into def list + LinkIntoDefList(); + + InitStorage( pDef ); + + // Initialize sheet data + m_Sheet.Set( g_pParticleSystemMgr->FindOrLoadSheet( pDef->GetMaterial() ) ); + + // FIXME: This seed needs to be recorded per instance! + m_bIsScrubbable = ( nRandomSeed != 0 ); + if ( m_bIsScrubbable ) + { + m_nRandomSeed = nRandomSeed; + } + else + { + m_nRandomSeed = (int)this; +#ifndef _DEBUG + m_nRandomSeed += Plat_MSTime(); +#endif + } + + SetAttributeToConstant( PARTICLE_ATTRIBUTE_XYZ, 0.0f, 0.0f, 0.0f ); + SetAttributeToConstant( PARTICLE_ATTRIBUTE_PREV_XYZ, 0.0f, 0.0f, 0.0f ); + SetAttributeToConstant( PARTICLE_ATTRIBUTE_LIFE_DURATION, 1.0f ); + SetAttributeToConstant( PARTICLE_ATTRIBUTE_RADIUS, pDef->m_flConstantRadius ); + SetAttributeToConstant( PARTICLE_ATTRIBUTE_ROTATION, pDef->m_flConstantRotation ); + SetAttributeToConstant( PARTICLE_ATTRIBUTE_ROTATION_SPEED, pDef->m_flConstantRotationSpeed ); + SetAttributeToConstant( PARTICLE_ATTRIBUTE_TINT_RGB, + pDef->m_ConstantColor.r() / 255.0f, pDef->m_ConstantColor.g() / 255.0f, + pDef->m_ConstantColor.g() / 255.0f ); + SetAttributeToConstant( PARTICLE_ATTRIBUTE_ALPHA, pDef->m_ConstantColor.a() / 255.0f ); + SetAttributeToConstant( PARTICLE_ATTRIBUTE_CREATION_TIME, 0.0f ); + SetAttributeToConstant( PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER, pDef->m_nConstantSequenceNumber ); + SetAttributeToConstant( PARTICLE_ATTRIBUTE_SEQUENCE_NUMBER1, pDef->m_nConstantSequenceNumber1 ); + SetAttributeToConstant( PARTICLE_ATTRIBUTE_TRAIL_LENGTH, 0.1f ); + SetAttributeToConstant( PARTICLE_ATTRIBUTE_PARTICLE_ID, 0 ); + SetAttributeToConstant( PARTICLE_ATTRIBUTE_YAW, 0 ); + SetAttributeToConstant( PARTICLE_ATTRIBUTE_ALPHA2, 1.0f ); + + // Offset the child in time + m_flCurTime = -flDelay; + m_fl4CurTime = ReplicateX4( m_flCurTime ); + if ( m_pDef->m_nContextDataSize ) + { + m_pOperatorContextData = reinterpret_cast + ( MemAlloc_AllocAligned( m_pDef->m_nContextDataSize, 16 ) ); + } + + m_flNextSleepTime = g_pParticleSystemMgr->GetLastSimulationTime() + pDef->m_flNoDrawTimeToGoToSleep; + + // now, init context data + CUtlVector *olists[] = + { + &(m_pDef->m_Operators), &(m_pDef->m_Renderers), + &(m_pDef->m_Initializers), &(m_pDef->m_Emitters), + &(m_pDef->m_ForceGenerators), + &(m_pDef->m_Constraints), + }; + CUtlVector *offsetlists[]= + { + &(m_pDef->m_nOperatorsCtxOffsets), &(m_pDef->m_nRenderersCtxOffsets), + &(m_pDef->m_nInitializersCtxOffsets), &(m_pDef->m_nEmittersCtxOffsets), + &(m_pDef->m_nForceGeneratorsCtxOffsets), + &(m_pDef->m_nConstraintsCtxOffsets), + + }; + + for( int i=0; iCount(); + for( int j=0; j < nOperatorCount; j++ ) + { + (*olists[i])[j]->InitializeContextData( this, m_pOperatorContextData+ (*offsetlists)[i][j] ); + } + } + + m_nControlPointReadMask = pDef->m_nControlPointReadMask; + + // Instance child particle systems + int nChildCount = pDef->m_Children.Count(); + for ( int i = 0; i < nChildCount; ++i ) + { + if ( nRandomSeed != 0 ) + { + nRandomSeed += 129; + } + + CParticleCollection *pChild; + if ( pDef->m_Children[i].m_bUseNameBasedLookup ) + { + pChild = g_pParticleSystemMgr->CreateParticleCollection( pDef->m_Children[i].m_Name, -m_flCurTime + pDef->m_Children[i].m_flDelay, nRandomSeed ); + } + else + { + pChild = g_pParticleSystemMgr->CreateParticleCollection( pDef->m_Children[i].m_Id, -m_flCurTime + pDef->m_Children[i].m_flDelay, nRandomSeed ); + } + if ( pChild ) + { + pChild->m_pParent = this; + m_Children.AddToTail( pChild ); + m_nControlPointReadMask |= pChild->m_nControlPointReadMask; + } + } + + if ( !IsValid() ) + return; + + m_bIsTranslucent = ComputeIsTranslucent(); + m_bIsTwoPass = ComputeIsTwoPass(); + m_bIsBatchable = ComputeIsBatchable(); + LabelTextureUsage(); + m_bAnyUsesPowerOfTwoFrameBufferTexture = ComputeUsesPowerOfTwoFrameBufferTexture(); + m_bAnyUsesFullFrameBufferTexture = ComputeUsesFullFrameBufferTexture(); + m_bRequiresOrderInvariance = ComputeRequiresOrderInvariance(); +} + + +//----------------------------------------------------------------------------- +// Used by client code +//----------------------------------------------------------------------------- +bool CParticleCollection::Init( CParticleSystemDefinition *pDef ) +{ + if ( !pDef ) // || !pDef->IsPrecached() ) + { + Warning( "Particlelib: Missing precache for particle system type \"%s\"!\n", pDef ? pDef->GetName() : "unknown" ); + CParticleSystemDefinition *pErrorDef = g_pParticleSystemMgr->FindParticleSystem( "error" ); + if ( pErrorDef ) + { + pDef = pErrorDef; + } + } + + Init( pDef, 0.0f, 0 ); + return IsValid(); +} + +bool CParticleCollection::Init( const char *pParticleSystemName ) +{ + if ( !pParticleSystemName ) + return false; + + CParticleSystemDefinition *pDef = g_pParticleSystemMgr->FindParticleSystem( pParticleSystemName ); + if ( !pDef ) + { + Warning( "Attempted to create unknown particle system type \"%s\"!\n", pParticleSystemName ); + return false; + } + return Init( pDef ); +} + + +//----------------------------------------------------------------------------- +// List management for collections sharing the same particle definition +//----------------------------------------------------------------------------- +void CParticleCollection::LinkIntoDefList( ) +{ + Assert( !m_pPrevDef && !m_pNextDef ); + + m_pPrevDef = NULL; + m_pNextDef = m_pDef->m_pFirstCollection; + m_pDef->m_pFirstCollection = this; + if ( m_pNextDef ) + { + m_pNextDef->m_pPrevDef = this; + } + +#ifdef _DEBUG + CParticleCollection *pCollection = m_pDef->FirstCollection(); + while ( pCollection ) + { + Assert( pCollection->m_pDef == m_pDef ); + pCollection = pCollection->GetNextCollectionUsingSameDef(); + } +#endif +} + +void CParticleCollection::UnlinkFromDefList( ) +{ + if ( !m_pDef ) + return; + + if ( m_pDef->m_pFirstCollection == this ) + { + m_pDef->m_pFirstCollection = m_pNextDef; + Assert( !m_pPrevDef ); + } + else + { + Assert( m_pPrevDef ); + m_pPrevDef->m_pNextDef = m_pNextDef; + } + + if ( m_pNextDef ) + { + m_pNextDef->m_pPrevDef = m_pPrevDef; + } + + m_pNextDef = m_pPrevDef = NULL; + +#ifdef _DEBUG + CParticleCollection *pCollection = m_pDef->FirstCollection(); + while ( pCollection ) + { + Assert( pCollection->m_pDef == m_pDef ); + pCollection = pCollection->GetNextCollectionUsingSameDef(); + } +#endif +} + +//----------------------------------------------------------------------------- +// Determine if this particle has moved since the last time it was simulated, +// which will let us know if the bbox needs to be updated. +//----------------------------------------------------------------------------- +bool CParticleCollection::HasMoved() const +{ + // It's weird that this is possible, but it apparently is (see the many other functions that + // check). + if ( !m_pDef ) + return false; + + Vector prevCP; + for ( int i = 0; i <= m_nHighestCP; ++i ) + { + if ( !m_pDef->ReadsControlPoint( i ) ) + continue; + + GetControlPointAtPrevTime( i, &prevCP ); + if ( prevCP != GetControlPointAtCurrentTime( i ) ) + { + return true; + } + } + + for ( CParticleCollection *child = m_Children.m_pHead; child; child = child->m_pNext ) + { + if ( child->HasMoved() ) + { + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Particle memory initialization +//----------------------------------------------------------------------------- +void CParticleCollection::InitStorage( CParticleSystemDefinition *pDef ) +{ + Assert( pDef->m_nMaxParticles < 65536 ); + + m_nMaxAllowedParticles = min ( MAX_PARTICLES_IN_A_SYSTEM, pDef->m_nMaxParticles ); + m_nAllocatedParticles = 4 + 4 * ( ( m_nMaxAllowedParticles + 3 ) / 4 ); + + int nConstantMemorySize = 3 * 4 * MAX_PARTICLE_ATTRIBUTES * sizeof(float) + 16; + + // Align allocation for constant attributes to 16 byte boundaries + m_pConstantMemory = new unsigned char[nConstantMemorySize]; + m_pConstantAttributes = (float*)( (size_t)( m_pConstantMemory + 15 ) & ~0xF ); + + // We have to zero-init the memory so that any attributes that are not initialized + // get predictable and sensible values. + memset( m_pConstantMemory, 0, nConstantMemorySize ); + + m_nPerParticleInitializedAttributeMask = pDef->m_nPerParticleInitializedAttributeMask; + m_nPerParticleUpdatedAttributeMask = pDef->m_nPerParticleUpdatedAttributeMask; + + // Only worry about initial attributes that are per-particle *and* are updated at a later time + // If they aren't updated at a later time, then we can just point the initial + current pointers at the same memory + m_nPerParticleReadInitialAttributeMask = pDef->m_nInitialAttributeReadMask & + ( pDef->m_nPerParticleInitializedAttributeMask & pDef->m_nPerParticleUpdatedAttributeMask ); + + // This is the mask of attributes which are initialized per-particle, but never updated + // *and* where operators want to read initial particle state + int nPerParticleReadConstantAttributeMask = pDef->m_nInitialAttributeReadMask & + ( pDef->m_nPerParticleInitializedAttributeMask & ( ~pDef->m_nPerParticleUpdatedAttributeMask ) ); + + int sz = 0; + int nInitialAttributeSize = 0; + int nPerParticleAttributeMask = m_nPerParticleInitializedAttributeMask | m_nPerParticleUpdatedAttributeMask; + for( int bit = 0; bit < MAX_PARTICLE_ATTRIBUTES; bit++ ) + { + int nAttrSize = ( ( 1 << bit ) & ATTRIBUTES_WHICH_ARE_VEC3S_MASK ) ? 3 : 1; + if ( nPerParticleAttributeMask & ( 1 << bit ) ) + { + sz += nAttrSize; + } + if ( m_nPerParticleReadInitialAttributeMask & ( 1 << bit ) ) + { + nInitialAttributeSize += nAttrSize; + } + } + + // Gotta allocate a couple extra floats to account for + int nAllocationSize = m_nAllocatedParticles * sz * sizeof(float) + 16; + m_pParticleMemory = new unsigned char[ nAllocationSize ]; + memset( m_pParticleMemory, 0, nAllocationSize ); + + // Allocate space for the initial attributes + if ( nInitialAttributeSize != 0 ) + { + int nInitialAllocationSize = m_nAllocatedParticles * nInitialAttributeSize * sizeof(float) + 16; + m_pParticleInitialMemory = new unsigned char[ nInitialAllocationSize ]; + memset( m_pParticleInitialMemory, 0, nInitialAllocationSize ); + } + + // Align allocation to 16-byte boundaries + float *pMem = (float*)( (size_t)( m_pParticleMemory + 15 ) & ~0xF ); + float *pInitialMem = (float*)( (size_t)( m_pParticleInitialMemory + 15 ) & ~0xF ); + + // Point each attribute to memory associated with that attribute + for( int bit = 0; bit < MAX_PARTICLE_ATTRIBUTES; bit++ ) + { + int nAttrSize = ( ( 1 << bit ) & ATTRIBUTES_WHICH_ARE_VEC3S_MASK ) ? 3 : 1; + + if ( nPerParticleAttributeMask & ( 1 << bit ) ) + { + m_pParticleAttributes[ bit ] = pMem; + m_nParticleFloatStrides[ bit ] = nAttrSize * 4; + pMem += nAttrSize * m_nAllocatedParticles; + } + else + { + m_pParticleAttributes[ bit ] = GetConstantAttributeMemory( bit ); + m_nParticleFloatStrides[ bit ] = 0; + } + + // Are we reading + if ( pDef->m_nInitialAttributeReadMask & ( 1 << bit ) ) + { + if ( m_nPerParticleReadInitialAttributeMask & ( 1 << bit ) ) + { + Assert( pInitialMem ); + m_pParticleInitialAttributes[ bit ] = pInitialMem; + m_nParticleInitialFloatStrides[ bit ] = nAttrSize * 4; + pInitialMem += nAttrSize * m_nAllocatedParticles; + } + else if ( nPerParticleReadConstantAttributeMask & ( 1 << bit ) ) + { + m_pParticleInitialAttributes[ bit ] = m_pParticleAttributes[ bit ]; + m_nParticleInitialFloatStrides[ bit ] = m_nParticleFloatStrides[ bit ]; + } + else + { + m_pParticleInitialAttributes[ bit ] = GetConstantAttributeMemory( bit ); + m_nParticleInitialFloatStrides[ bit ] = 0; + } + } + else + { + // Catch errors where code is reading data it didn't request + m_pParticleInitialAttributes[ bit ] = NULL; + m_nParticleInitialFloatStrides[ bit ] = 0; + } + } +} + + +//----------------------------------------------------------------------------- +// Returns the particle collection name +//----------------------------------------------------------------------------- +const char *CParticleCollection::GetName() const +{ + return m_pDef ? m_pDef->GetName() : ""; +} + + +//----------------------------------------------------------------------------- +// Does the particle system use the frame buffer texture (refraction?) +//----------------------------------------------------------------------------- +bool CParticleCollection::UsesPowerOfTwoFrameBufferTexture( bool bThisFrame ) const +{ + if ( ! m_bAnyUsesPowerOfTwoFrameBufferTexture ) // quick out if neither us or our children ever use + { + return false; + } + if ( bThisFrame ) + { + return SystemContainsParticlesWithBoolSet( &CParticleCollection::m_bUsesPowerOfTwoFrameBufferTexture ); + } + return true; +} +//----------------------------------------------------------------------------- +// Does the particle system use the full frame buffer texture (soft particles) +//----------------------------------------------------------------------------- +bool CParticleCollection::UsesFullFrameBufferTexture( bool bThisFrame ) const +{ + if ( ! m_bAnyUsesFullFrameBufferTexture ) // quick out if neither us or our children ever use + { + return false; + } + if ( bThisFrame ) + { + return SystemContainsParticlesWithBoolSet( &CParticleCollection::m_bUsesFullFrameBufferTexture ); + } + return true; +} + +bool CParticleCollection::SystemContainsParticlesWithBoolSet( bool CParticleCollection::*pField ) const +{ + if ( m_nActiveParticles && ( this->*pField ) ) + return true; + for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext ) + { + if ( p->SystemContainsParticlesWithBoolSet( pField ) ) + return true; + } + return false; + +} + +void CParticleCollection::LabelTextureUsage( void ) +{ + if ( m_pDef ) + { + m_bUsesPowerOfTwoFrameBufferTexture = m_pDef->UsesPowerOfTwoFrameBufferTexture(); + m_bUsesFullFrameBufferTexture = m_pDef->UsesFullFrameBufferTexture(); + } + + for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext ) + { + p->LabelTextureUsage(); + } +} + +bool CParticleCollection::ComputeUsesPowerOfTwoFrameBufferTexture() +{ + if ( !m_pDef ) + return false; + + if ( m_pDef->UsesPowerOfTwoFrameBufferTexture() ) + return true; + + for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext ) + { + if ( p->UsesPowerOfTwoFrameBufferTexture( false ) ) + return true; + } + + return false; +} + +bool CParticleCollection::ComputeUsesFullFrameBufferTexture() +{ + if ( !m_pDef ) + return false; + + if ( m_pDef->UsesFullFrameBufferTexture() ) + return true; + + for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext ) + { + if ( p->UsesFullFrameBufferTexture( false ) ) + return true; + } + + return false; +} +//----------------------------------------------------------------------------- +// Is the particle system two-pass? +//----------------------------------------------------------------------------- +bool CParticleCollection::ContainsOpaqueCollections() +{ + if ( !m_pDef ) + return false; + + if ( !m_pDef->GetMaterial()->IsTranslucent() ) + return true; + + for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext ) + { + if ( p->ContainsOpaqueCollections( ) ) + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Is the particle system two-pass? +//----------------------------------------------------------------------------- +bool CParticleCollection::IsTwoPass() const +{ + return m_bIsTwoPass; +} + +bool CParticleCollection::ComputeIsTwoPass() +{ + if ( !ComputeIsTranslucent() ) + return false; + + return ContainsOpaqueCollections(); +} + + +//----------------------------------------------------------------------------- +// Is the particle system translucent +//----------------------------------------------------------------------------- +bool CParticleCollection::IsTranslucent() const +{ + return m_bIsTranslucent; +} + +bool CParticleCollection::ComputeIsTranslucent() +{ + if ( !m_pDef ) + return false; + + if ( m_pDef->GetMaterial()->IsTranslucent() ) + return true; + + for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext ) + { + if ( p->IsTranslucent( ) ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Is the particle system batchable +//----------------------------------------------------------------------------- +bool CParticleCollection::IsBatchable() const +{ + return m_bIsBatchable; +} + +bool CParticleCollection::ComputeIsBatchable() +{ + int nRendererCount = GetRendererCount(); + for( int i = 0; i < nRendererCount; i++ ) + { + if ( !GetRenderer( i )->IsBatchable() ) + return false; + } + + for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext ) + { + if ( !p->IsBatchable() ) + return false; + } + + return true; +} +//----------------------------------------------------------------------------- +// Does this system require order invariance of the particles? +//----------------------------------------------------------------------------- +bool CParticleCollection::ComputeRequiresOrderInvariance() +{ + const int nRendererCount = GetRendererCount(); + for( int i = 0; i < nRendererCount; i++ ) + { + if ( GetRenderer( i )->RequiresOrderInvariance() ) + return true; + } + + for (CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext) + { + if ( p->m_bRequiresOrderInvariance ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Renderer iteration +//----------------------------------------------------------------------------- +int CParticleCollection::GetRendererCount() const +{ + return IsValid() ? m_pDef->m_Renderers.Count() : 0; +} + +CParticleOperatorInstance *CParticleCollection::GetRenderer( int i ) +{ + return IsValid() ? m_pDef->m_Renderers[i] : NULL; +} + +void *CParticleCollection::GetRendererContext( int i ) +{ + return IsValid() ? m_pOperatorContextData + m_pDef->m_nRenderersCtxOffsets[i] : NULL; +} + + +//----------------------------------------------------------------------------- +// Visualize operators (for editing/debugging) +//----------------------------------------------------------------------------- +void CParticleCollection::VisualizeOperator( const DmObjectId_t *pOpId ) +{ + m_pRenderOp = NULL; + if ( !pOpId || !m_pDef ) + return; + + m_pRenderOp = m_pDef->FindOperatorById( FUNCTION_EMITTER, *pOpId ); + if ( !m_pRenderOp ) + { + m_pRenderOp = m_pDef->FindOperatorById( FUNCTION_INITIALIZER, *pOpId ); + if ( !m_pRenderOp ) + { + m_pRenderOp = m_pDef->FindOperatorById( FUNCTION_OPERATOR, *pOpId ); + } + } +} + + +float FadeInOut( float flFadeInStart, float flFadeInEnd, float flFadeOutStart, float flFadeOutEnd, float flCurTime ) +{ + if ( flFadeInStart > flCurTime ) // started yet? + return 0.0; + + if ( ( flFadeOutEnd > 0. ) && ( flFadeOutEnd < flCurTime ) ) // timed out? + return 0.; + + // handle out of order cases + flFadeInEnd = max( flFadeInEnd, flFadeInStart ); + flFadeOutStart = max( flFadeOutStart, flFadeInEnd ); + flFadeOutEnd = max( flFadeOutEnd, flFadeOutStart ); + + float flStrength = 1.0; + if ( + ( flFadeInEnd > flCurTime ) && + ( flFadeInEnd > flFadeInStart ) ) + flStrength = min( flStrength, FLerp( 0, 1, flFadeInStart, flFadeInEnd, flCurTime ) ); + + if ( ( flCurTime > flFadeOutStart) && + ( flFadeOutEnd > flFadeOutStart) ) + flStrength = min ( flStrength, FLerp( 0, 1, flFadeOutEnd, flFadeOutStart, flCurTime ) ); + + return flStrength; + +} + +bool CParticleCollection::CheckIfOperatorShouldRun( + CParticleOperatorInstance const * pOp , + float *pflCurStrength) +{ + float flTime=m_flCurTime; + if ( pOp->m_flOpFadeOscillatePeriod > 0.0 ) + { + flTime=fmod( m_flCurTime*( 1.0/pOp->m_flOpFadeOscillatePeriod ), 1.0 ); + } + + float flStrength = FadeInOut( pOp->m_flOpStartFadeInTime, pOp->m_flOpEndFadeInTime, + pOp->m_flOpStartFadeOutTime, pOp->m_flOpEndFadeOutTime, + flTime ); + if ( pflCurStrength ) + *pflCurStrength = flStrength; + return ( flStrength > 0.0 ); +} + + +//----------------------------------------------------------------------------- +// Restarts a particle system +//----------------------------------------------------------------------------- +void CParticleCollection::Restart() +{ + int i; + int nEmitterCount = m_pDef->m_Emitters.Count(); + for( i = 0; i < nEmitterCount; i++ ) + { + m_pDef->m_Emitters[i]->Restart( this, m_pOperatorContextData + m_pDef->m_nEmittersCtxOffsets[i] ); + } + + // Update all children + CParticleCollection *pChild; + for( i = 0, pChild = m_Children.m_pHead; pChild != NULL; pChild = pChild->m_pNext, i++ ) + { + // Remove any delays from the time (otherwise we're offset by it oddly) + pChild->Restart( ); + } +} + + +//----------------------------------------------------------------------------- +// Main entry point for rendering +//----------------------------------------------------------------------------- +void CParticleCollection::Render( IMatRenderContext *pRenderContext, bool bTranslucentOnly, void *pCameraObject ) +{ + if ( !IsValid() ) + return; + + m_flNextSleepTime = Max ( m_flNextSleepTime, ( g_pParticleSystemMgr->GetLastSimulationTime() + m_pDef->m_flNoDrawTimeToGoToSleep )); + + if ( m_nActiveParticles != 0 ) + { + if ( !bTranslucentOnly || m_pDef->GetMaterial()->IsTranslucent() ) + { + int nCount = m_pDef->m_Renderers.Count(); + for( int i = 0; i < nCount; i++ ) + { + if ( CheckIfOperatorShouldRun( m_pDef->m_Renderers[i] ) ) + { +// pRenderContext->MatrixMode( MATERIAL_VIEW ); +// pRenderContext->PushMatrix(); +// pRenderContext->LoadIdentity(); +// pRenderContext->MatrixMode( MATERIAL_PROJECTION ); +// pRenderContext->PushMatrix(); +// pRenderContext->LoadIdentity(); +// pRenderContext->Ortho( -100, -100, 100, 100, -100, 100 ); + m_pDef->m_Renderers[i]->Render( + pRenderContext, this, m_pOperatorContextData + m_pDef->m_nRenderersCtxOffsets[i] ); +// pRenderContext->MatrixMode( MATERIAL_VIEW ); +// pRenderContext->PopMatrix(); +// pRenderContext->MatrixMode( MATERIAL_PROJECTION ); +// pRenderContext->PopMatrix(); + } + } + } + } + + // let children render + for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext ) + { + p->Render( pRenderContext, bTranslucentOnly, pCameraObject ); + } + + // Visualize specific ops for debugging/editing + if ( m_pRenderOp ) + { + m_pRenderOp->Render( this ); + } +} + +void CParticleCollection::UpdatePrevControlPoints( float dt ) +{ + m_flPreviousDt = dt; + for(int i=0; i <= m_nHighestCP; i++ ) + m_ControlPoints[i].m_PrevPosition = m_ControlPoints[i].m_Position; + m_nParticleFlags |= PCFLAGS_PREV_CONTROL_POINTS_INITIALIZED; +} + +#if MEASURE_PARTICLE_PERF + +#if VPROF_LEVEL > 0 +#define START_OP float flOpStartTime = Plat_FloatTime(); VPROF_ENTER_SCOPE(pOp->GetDefinition()->GetName()) +#else +#define START_OP float flOpStartTime = Plat_FloatTime(); +#endif + +#if VPROF_LEVEL > 0 +#define END_OP if ( 1 ) { \ + float flETime = Plat_FloatTime() - flOpStartTime; \ + IParticleOperatorDefinition *pDef = (IParticleOperatorDefinition *) pOp->m_pDef; \ + pDef->RecordExecutionTime( flETime ); \ +} \ + VPROF_EXIT_SCOPE() +#else +#define END_OP if ( 1 ) { \ + float flETime = Plat_FloatTime() - flOpStartTime; \ + IParticleOperatorDefinition *pDef = (IParticleOperatorDefinition *) pOp->m_pDef; \ + pDef->RecordExecutionTime( flETime ); \ +} +#endif +#else +#define START_OP +#define END_OP +#endif + +void CParticleCollection::InitializeNewParticles( int nFirstParticle, int nParticleCount, uint32 nInittedMask ) +{ + VPROF_BUDGET( "CParticleCollection::InitializeNewParticles", VPROF_BUDGETGROUP_PARTICLE_SIMULATION ); + +#ifdef _DEBUG + m_bIsRunningInitializers = true; +#endif + + // now, initialize the attributes of all the new particles + int nPerParticleAttributeMask = m_nPerParticleInitializedAttributeMask | m_nPerParticleUpdatedAttributeMask; + int nAttrsLeftToInit = nPerParticleAttributeMask & ~nInittedMask; + int nInitializerCount = m_pDef->m_Initializers.Count(); + for ( int i = 0; i < nInitializerCount; i++ ) + { + CParticleOperatorInstance *pOp = m_pDef->m_Initializers[i]; + int nInitializerAttrMask = pOp->GetWrittenAttributes(); + if ( ( ( nInitializerAttrMask & nAttrsLeftToInit ) == 0 ) || pOp->InitMultipleOverride() ) + continue; + + void *pContext = m_pOperatorContextData + m_pDef->m_nInitializersCtxOffsets[i]; + START_OP; + if ( m_bIsScrubbable && !pOp->IsScrubSafe() ) + { + for ( int j = 0; j < nParticleCount; ++j ) + { + pOp->InitNewParticles( this, nFirstParticle + j, 1, nAttrsLeftToInit, pContext ); + } + } + else + { + pOp->InitNewParticles( this, nFirstParticle, nParticleCount, nAttrsLeftToInit, pContext ); + } + END_OP; + nAttrsLeftToInit &= ~nInitializerAttrMask; + } + + // always run second tier initializers (modifiers) after first tier - this ensures they don't get stomped. + for ( int i = 0; i < nInitializerCount; i++ ) + { + int nInitializerAttrMask = m_pDef->m_Initializers[i]->GetWrittenAttributes(); + CParticleOperatorInstance *pOp = m_pDef->m_Initializers[i]; + if ( !pOp->InitMultipleOverride() ) + continue; + + void *pContext = m_pOperatorContextData + m_pDef->m_nInitializersCtxOffsets[i]; + START_OP; + if ( m_bIsScrubbable && !pOp->IsScrubSafe() ) + { + for ( int j = 0; j < nParticleCount; ++j ) + { + pOp->InitNewParticles( this, nFirstParticle + j, 1, nAttrsLeftToInit, pContext ); + } + } + else + { + pOp->InitNewParticles( this, nFirstParticle, nParticleCount, nAttrsLeftToInit, pContext ); + } + END_OP; + nAttrsLeftToInit &= ~nInitializerAttrMask; + } + +#ifdef _DEBUG + m_bIsRunningInitializers = false; +#endif + + InitParticleAttributes( nFirstParticle, nParticleCount, nAttrsLeftToInit ); + + CopyInitialAttributeValues( nFirstParticle, nParticleCount ); +} + +void CParticleCollection::SkipToTime( float t ) +{ + if ( t > m_flCurTime ) + { + UpdatePrevControlPoints( t - m_flCurTime ); + m_flCurTime = t; + m_fl4CurTime = ReplicateX4( t ); + m_nParticleFlags &= ~PCFLAGS_FIRST_FRAME; + + // FIXME: In future, we may have to tell operators, initializers about this too + int nEmitterCount = m_pDef->m_Emitters.Count(); + int i; + for( i = 0; i < nEmitterCount; i++ ) + { + m_pDef->m_Emitters[i]->SkipToTime( t, this, m_pOperatorContextData + m_pDef->m_nEmittersCtxOffsets[i] ); + } + + CParticleCollection *pChild; + + // Update all children + for( i = 0, pChild = m_Children.m_pHead; pChild != NULL; pChild = pChild->m_pNext, i++ ) + { + // Remove any delays from the time (otherwise we're offset by it oddly) + pChild->SkipToTime( t - m_pDef->m_Children[i].m_flDelay ); + } + } +} + +#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 ); +/* + const float *rad = pParticles->GetFloatAttributePtr( PARTICLE_ATTRIBUTE_RADIUS, i ); + Assert( IsFinite( rad[0] ) ); + + RJ: Disabling this assert. While the proper way is to fix the math which leads to the bad number, the fix would result in more particles being drawn and in a post shipping world, users were not happy. + See Changelists #1368648, #1368635, and #1368434 for proper math calculation fixes. + + In a post shipping world, as these particles would not render with infinites, code was added C_OP_RenderSprites::RenderSpriteCard() to check for the infinite and not add the vert to meshbuilder. +*/ + 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 + +void CParticleCollection::SimulateFirstFrame( ) +{ + m_flDt = 0.0f; + m_nDrawnFrames = 0; + m_nSimulatedFrames = 1; + + // For the first frame, copy over the initial control points + if ( ( m_nParticleFlags & PCFLAGS_PREV_CONTROL_POINTS_INITIALIZED ) == 0 ) + { + UpdatePrevControlPoints( 0.05f ); + } + + m_nOperatorRandomSampleOffset = 0; + int nCount = m_pDef->m_Operators.Count(); + for( int i = 0; i < nCount; i++ ) + { + float flStrength; + CParticleOperatorInstance *pOp = m_pDef->m_Operators[i]; + if ( pOp->ShouldRunBeforeEmitters() && + CheckIfOperatorShouldRun( pOp, &flStrength ) ) + { + pOp->Operate( this, flStrength, m_pOperatorContextData + m_pDef->m_nOperatorsCtxOffsets[i] ); + CHECKSYSTEM( this ); + UpdatePrevControlPoints( 0.05f ); + } + m_nOperatorRandomSampleOffset += 17; + } + + // first, create initial particles + int nNumToCreate = min( m_pDef->m_nInitialParticles, m_nMaxAllowedParticles ); + if ( nNumToCreate > 0 ) + { + SetNActiveParticles( nNumToCreate ); + InitializeNewParticles( 0, nNumToCreate, 0 ); + CHECKSYSTEM( this ); + } +} + + + +void CParticleCollection::Simulate( float dt, bool updateBboxOnly ) +{ + VPROF_BUDGET( "CParticleCollection::Simulate", VPROF_BUDGETGROUP_PARTICLE_SIMULATION ); + if ( dt < 0.0f ) + return; + + if ( !m_pDef ) + return; + + // Don't do anything until we've hit t == 0 + // This is used for delayed children + if ( m_flCurTime < 0.0f ) + { + if ( dt >= 1.0e-22 ) + { + m_flCurTime += dt; + m_fl4CurTime = ReplicateX4( m_flCurTime ); + UpdatePrevControlPoints( dt ); + } + return; + } + + // run initializers if necessary (once we hit t == 0) + if ( m_nParticleFlags & PCFLAGS_FIRST_FRAME ) + { + SimulateFirstFrame(); + m_nParticleFlags &= ~PCFLAGS_FIRST_FRAME; + } + + if ( dt < 1.0e-22 ) + return; + + +#if MEASURE_PARTICLE_PERF + float flStartSimTime = Plat_FloatTime(); +#endif + + bool bAttachedKillList = false; + + if (!HasAttachedKillList()) + { + g_pParticleSystemMgr->AttachKillList(this); + bAttachedKillList = true; + } + + if (!updateBboxOnly) + { + ++m_nSimulatedFrames; + + float flRemainingDt = dt; + float flMaxDT = 0.1; // default + if ( m_pDef->m_flMaximumTimeStep > 0.0 ) + flMaxDT = m_pDef->m_flMaximumTimeStep; + + // Limit timestep if needed (prevents short lived particles from being created and destroyed before being rendered. + //if ( m_pDef->m_flMaximumSimTime != 0.0 && !m_bHasDrawnOnce ) + if ( m_pDef->m_flMaximumSimTime != 0.0 && ( m_nDrawnFrames <= m_pDef->m_nMinimumFrames ) ) + { + if ( ( flRemainingDt + m_flCurTime ) > m_pDef->m_flMaximumSimTime ) + { + //if delta+current > checkpoint then delta = checkpoint - current + flRemainingDt = m_pDef->m_flMaximumSimTime - m_flCurTime; + flRemainingDt = max( m_pDef->m_flMinimumSimTime, flRemainingDt ); + } + m_nDrawnFrames += 1; + } + + flRemainingDt = min( flRemainingDt, 10 * flMaxDT ); // no more than 10 passes ever + + while( flRemainingDt > 0.0 ) + { + float flDT_ThisStep = min( flRemainingDt, flMaxDT ); + flRemainingDt -= flDT_ThisStep; + m_flDt = flDT_ThisStep; + m_flCurTime += flDT_ThisStep; + m_fl4CurTime = ReplicateX4( m_flCurTime ); + +#ifdef _DEBUG + m_bIsRunningOperators = true; +#endif + + m_nOperatorRandomSampleOffset = 0; + int nCount = m_pDef->m_Operators.Count(); + for( int i = 0; i < nCount; i++ ) + { + float flStrength; + CParticleOperatorInstance *pOp = m_pDef->m_Operators[i]; + if ( pOp->ShouldRunBeforeEmitters() && + CheckIfOperatorShouldRun( pOp, &flStrength ) ) + { + START_OP; + pOp->Operate( this, flStrength, m_pOperatorContextData + m_pDef->m_nOperatorsCtxOffsets[i] ); + END_OP; + CHECKSYSTEM( this ); + if ( m_nNumParticlesToKill ) + { + ApplyKillList(); + } + m_nOperatorRandomSampleOffset += 17; + } + } +#ifdef _DEBUG + m_bIsRunningOperators = false; +#endif + + + int nEmitterCount = m_pDef->m_Emitters.Count(); + for( int i=0; i < nEmitterCount; i++ ) + { + int nOldParticleCount = m_nActiveParticles; + float flEmitStrength; + if ( CheckIfOperatorShouldRun( m_pDef->m_Emitters[i], &flEmitStrength ) ) + { + uint32 nInittedMask = m_pDef->m_Emitters[i]->Emit( + this, flEmitStrength, + m_pOperatorContextData + m_pDef->m_nEmittersCtxOffsets[i] ); + if ( nOldParticleCount != m_nActiveParticles ) + { + // init newly emitted particles + InitializeNewParticles( nOldParticleCount, m_nActiveParticles - nOldParticleCount, nInittedMask ); + CHECKSYSTEM( this ); + } + } + } + + m_nOperatorRandomSampleOffset = 0; + nCount = m_pDef->m_Operators.Count(); + if ( m_nActiveParticles ) + { +#ifdef FP_EXCEPTIONS_ENABLED + const int processedParticles = m_nPaddedActiveParticles * 4; + for ( int unusedParticle = m_nActiveParticles; unusedParticle < processedParticles; ++unusedParticle ) + { + // Set the unused-but-processed particle lifetimes to a value that + // won't cause division by zero or other madness. This allows us + // to enable floating-point exceptions during particle processing, + // which helps us to find bugs. + float *dtime = GetFloatAttributePtrForWrite( PARTICLE_ATTRIBUTE_LIFE_DURATION, unusedParticle ); + *dtime = 1.0f; + } +#endif +#ifdef _DEBUG + m_bIsRunningOperators = true; +#endif + for( int i = 0; i < nCount; i++ ) + { + float flStrength; + CParticleOperatorInstance *pOp = m_pDef->m_Operators[i]; + if ( (! pOp->ShouldRunBeforeEmitters() ) && + CheckIfOperatorShouldRun( pOp, &flStrength ) ) + { + START_OP; + pOp->Operate( this, flStrength, m_pOperatorContextData + m_pDef->m_nOperatorsCtxOffsets[i] ); + END_OP; + CHECKSYSTEM( this ); + if ( m_nNumParticlesToKill ) + { + ApplyKillList(); + if ( ! m_nActiveParticles ) + break; // don't run any more operators + } + m_nOperatorRandomSampleOffset += 17; + } + } +#ifdef _DEBUG + m_bIsRunningOperators = false; +#endif + } + } + +#if MEASURE_PARTICLE_PERF + m_pDef->m_nMaximumActiveParticles = max( m_pDef->m_nMaximumActiveParticles, m_nActiveParticles ); + float flETime = Plat_FloatTime() - flStartSimTime; + m_pDef->m_flUncomittedTotalSimTime += flETime; + m_pDef->m_flMaxMeasuredSimTime = max( m_pDef->m_flMaxMeasuredSimTime, flETime ); +#endif + } + + // let children simulate + for (CParticleCollection *i = m_Children.m_pHead; i; i = i->m_pNext) + { + LoanKillListTo(i); // re-use the allocated kill list for the children + i->Simulate(dt, updateBboxOnly); + i->m_pParticleKillList = NULL; + } + + if (bAttachedKillList) + g_pParticleSystemMgr->DetachKillList(this); + + UpdatePrevControlPoints(dt); + + // Bloat the bounding box by bounds around the control point + BloatBoundsUsingControlPoint(); + +} + + +//----------------------------------------------------------------------------- +// Copies the constant attributes into the per-particle attributes +//----------------------------------------------------------------------------- +void CParticleCollection::InitParticleAttributes( int nStartParticle, int nNumParticles, int nAttrsLeftToInit ) +{ + if ( nAttrsLeftToInit == 0 ) + return; + + // !! speed!! do sse init here + for( int i = nStartParticle; i < nStartParticle + nNumParticles; i++ ) + { + for ( int nAttr = 0; nAttr < MAX_PARTICLE_ATTRIBUTES; ++nAttr ) + { + if ( ( nAttrsLeftToInit & ( 1 << nAttr ) ) == 0 ) + continue; + + float *pAttrData = GetFloatAttributePtrForWrite( nAttr, i ); + + // Special case for particle id + if ( nAttr == PARTICLE_ATTRIBUTE_PARTICLE_ID ) + { + *( (int*)pAttrData ) = ( m_nRandomSeed + m_nUniqueParticleId ) & RANDOM_FLOAT_MASK; + m_nUniqueParticleId++; + continue; + } + + // Special case for the creation time mask + if ( nAttr == PARTICLE_ATTRIBUTE_CREATION_TIME ) + { + *pAttrData = m_flCurTime; + continue; + } + + // If this assertion fails, it means we're writing into constant memory, which is a nono + Assert( m_nParticleFloatStrides[nAttr] != 0 ); + float *pConstantAttr = GetConstantAttributeMemory( nAttr ); + *pAttrData = *pConstantAttr; + if ( m_nParticleFloatStrides[nAttr] == 12 ) + { + pAttrData[4] = pConstantAttr[4]; + pAttrData[8] = pConstantAttr[8]; + } + } + } +} + +void CParticleCollection::CopyInitialAttributeValues( int nStartParticle, int nNumParticles ) +{ + if ( m_nPerParticleReadInitialAttributeMask == 0 ) + return; + + // FIXME: Do SSE copy here + for( int i = nStartParticle; i < nStartParticle + nNumParticles; i++ ) + { + for ( int nAttr = 0; nAttr < MAX_PARTICLE_ATTRIBUTES; ++nAttr ) + { + if ( m_nPerParticleReadInitialAttributeMask & (1 << nAttr) ) + { + const float *pSrcAttribute = GetFloatAttributePtr( nAttr, i ); + float *pDestAttribute = GetInitialFloatAttributePtrForWrite( nAttr, i ); + Assert( m_nParticleInitialFloatStrides[nAttr] != 0 ); + Assert( m_nParticleFloatStrides[nAttr] == m_nParticleInitialFloatStrides[nAttr] ); + *pDestAttribute = *pSrcAttribute; + if ( m_nParticleFloatStrides[nAttr] == 12 ) + { + pDestAttribute[4] = pSrcAttribute[4]; + pDestAttribute[8] = pSrcAttribute[8]; + } + } + } + } +} + + +//-----------------------------------------------------------------------------e +// Computes a random vector inside a sphere +//----------------------------------------------------------------------------- +float CParticleCollection::RandomVectorInUnitSphere( int nRandomSampleId, Vector *pVector ) +{ + // Guarantee uniform random distribution within a sphere + // Graphics gems III contains this algorithm ("Nonuniform random point sets via warping") + float u = RandomFloat( nRandomSampleId, 0.0001f, 1.0f ); + float v = RandomFloat( nRandomSampleId+1, 0.0001f, 1.0f ); + float w = RandomFloat( nRandomSampleId+2, 0.0001f, 1.0f ); + + float flPhi = acos( 1 - 2 * u ); + float flTheta = 2 * M_PI * v; + float flRadius = powf( w, 1.0f / 3.0f ); + + float flSinPhi, flCosPhi; + float flSinTheta, flCosTheta; + SinCos( flPhi, &flSinPhi, &flCosPhi ); + SinCos( flTheta, &flSinTheta, &flCosTheta ); + + pVector->x = flRadius * flSinPhi * flCosTheta; + pVector->y = flRadius * flSinPhi * flSinTheta; + pVector->z = flRadius * flCosPhi; + return flRadius; +} + + + +//----------------------------------------------------------------------------- +// Used to retrieve the position of a control point +// somewhere between m_flCurTime and m_flCurTime - m_fPreviousDT +//----------------------------------------------------------------------------- +void CParticleCollection::GetControlPointAtTime( int nControlPoint, float flTime, Vector *pControlPoint ) const +{ + Assert( m_pDef->ReadsControlPoint( nControlPoint ) ); + if ( nControlPoint > GetHighestControlPoint() ) + { + DevWarning(2, "Warning : Particle system (%s) using unassigned ControlPoint %d!\n", GetName(), nControlPoint ); + } + if ( m_flDt == 0.0f ) + { + VectorCopy( m_ControlPoints[nControlPoint].m_Position, *pControlPoint ); + return; + } + + // The original calculation for 't' was this: + // float flPrevTime = m_flCurTime - m_flDt; + // float t = ( flTime - flPrevTime ) / m_flDt; + // Which is mathematically equivalent to this: + // float t = ( flTime - ( m_flCurTime - m_flDt ) ) / m_flDt; + // However if m_flCurTime and flTime are large then significant precision + // is lost during subtraction -- catastrophic cancellation + // is the technical term. This starts out being an error of one part in + // ten million, but after running for just a few minutes it increases to + // one part in ten thousand -- and continues to get worse. + // This calculation even fails in the simple case where flTime == m_flCurTime, + // giving an answer that is not 1.0 and may be out of range. + + // If the calculation is arranged as shown below then, because flTime and + // m_flCurTime are close to each other, the subtraction loses *no* precision. + // The subtraction will not necessarily be 'correct', since eventually flTime + // and m_flCurTime will not have enough precision, but it will give results + // that are as accurate as possible given the inputs. + // flHowLongAgo stores how far before the current time flTime is. + const float flHowLongAgo = m_flCurTime - flTime; + float t = ( m_flDt - flHowLongAgo ) / m_flDt; + // The original code had a comment saying: + // Precision errors can cause this problem + // in regards to issues that can cause t to go negative. Actually this function + // is just sometimes called (from InitNewParticlesScalar) with values that cause + // 't' to go massively negative. So I clamp it. + if ( t < 0.0f ) + t = 0.0f; + Assert( t <= 1.0f ); + + VectorLerp( m_ControlPoints[nControlPoint].m_PrevPosition, m_ControlPoints[nControlPoint].m_Position, t, *pControlPoint ); + Assert( IsFinite(pControlPoint->x) && IsFinite(pControlPoint->y) && IsFinite(pControlPoint->z) ); +} + +//----------------------------------------------------------------------------- +// Used to retrieve the previous position of a control point +// +//----------------------------------------------------------------------------- +void CParticleCollection::GetControlPointAtPrevTime( int nControlPoint, Vector *pControlPoint ) const +{ + Assert( m_pDef->ReadsControlPoint( nControlPoint ) ); + *pControlPoint = m_ControlPoints[nControlPoint].m_PrevPosition; +} + +void CParticleCollection::GetControlPointTransformAtCurrentTime( int nControlPoint, matrix3x4_t *pMat ) +{ + Assert( m_pDef->ReadsControlPoint( nControlPoint ) ); + const Vector &vecControlPoint = GetControlPointAtCurrentTime( nControlPoint ); + + // FIXME: Use quaternion lerp to get control point transform at time + Vector left; + VectorMultiply( m_ControlPoints[nControlPoint].m_RightVector, -1.0f, left ); + pMat->Init( m_ControlPoints[nControlPoint].m_ForwardVector, left, m_ControlPoints[nControlPoint].m_UpVector, vecControlPoint ); +} + +void CParticleCollection::GetControlPointTransformAtCurrentTime( int nControlPoint, VMatrix *pMat ) +{ + GetControlPointTransformAtCurrentTime( nControlPoint, const_cast ( &pMat->As3x4() ) ); + pMat->m[3][0] = pMat->m[3][1] = pMat->m[3][2] = 0.0f; pMat->m[3][3] = 1.0f; +} + +void CParticleCollection::GetControlPointOrientationAtTime( int nControlPoint, float flTime, Vector *pForward, Vector *pRight, Vector *pUp ) +{ + Assert( m_pDef->ReadsControlPoint( nControlPoint ) ); + + // FIXME: Use quaternion lerp to get control point transform at time + *pForward = m_ControlPoints[nControlPoint].m_ForwardVector; + *pRight = m_ControlPoints[nControlPoint].m_RightVector; + *pUp = m_ControlPoints[nControlPoint].m_UpVector; +} + +void CParticleCollection::GetControlPointTransformAtTime( int nControlPoint, float flTime, matrix3x4_t *pMat ) +{ + Assert( m_pDef->ReadsControlPoint( nControlPoint ) ); + Vector vecControlPoint; + GetControlPointAtTime( nControlPoint, flTime, &vecControlPoint ); + + // FIXME: Use quaternion lerp to get control point transform at time + Vector left; + VectorMultiply( m_ControlPoints[nControlPoint].m_RightVector, -1.0f, left ); + pMat->Init( m_ControlPoints[nControlPoint].m_ForwardVector, left, m_ControlPoints[nControlPoint].m_UpVector, vecControlPoint ); +} + +void CParticleCollection::GetControlPointTransformAtTime( int nControlPoint, float flTime, VMatrix *pMat ) +{ + GetControlPointTransformAtTime( nControlPoint, flTime, const_cast< matrix3x4_t * > ( &pMat->As3x4() ) ); + pMat->m[3][0] = pMat->m[3][1] = pMat->m[3][2] = 0.0f; pMat->m[3][3] = 1.0f; +} + +void CParticleCollection::GetControlPointTransformAtTime( int nControlPoint, float flTime, CParticleSIMDTransformation *pXForm ) +{ + Assert( m_pDef->ReadsControlPoint( nControlPoint ) ); + Vector vecControlPoint; + GetControlPointAtTime( nControlPoint, flTime, &vecControlPoint ); + + pXForm->m_v4Fwd.DuplicateVector( m_ControlPoints[nControlPoint].m_ForwardVector ); + pXForm->m_v4Up.DuplicateVector( m_ControlPoints[nControlPoint].m_UpVector ); + //Vector left; + //VectorMultiply( m_ControlPoints[nControlPoint].m_RightVector, -1.0f, left ); + pXForm->m_v4Right.DuplicateVector( m_ControlPoints[nControlPoint].m_RightVector ); + +} + +int CParticleCollection::GetHighestControlPoint( void ) const +{ + return m_nHighestCP; +} + +//----------------------------------------------------------------------------- +// Returns the render bounds +//----------------------------------------------------------------------------- +void CParticleCollection::GetBounds( Vector *pMin, Vector *pMax ) +{ + *pMin = m_MinBounds; + *pMax = m_MaxBounds; +} + +//----------------------------------------------------------------------------- +// Bloat the bounding box by bounds around the control point +//----------------------------------------------------------------------------- +void CParticleCollection::BloatBoundsUsingControlPoint() +{ + // more specifically, some particle systems were using "start" as an input, so it got set as control point 1, + // so other particle systems had an extra point in their bounding box, that generally remained at the world origin + RecomputeBounds(); + + // Don't do the bounding box fixup until after the second simulation (first real simulation) + // so that we know they're in their correct position. + if ( m_nSimulatedFrames > 2 ) + { + // Include control points in the bbox. + for (int i = 0; i <= m_nHighestCP; ++i) { + if ( !m_pDef->ReadsControlPoint( i ) ) + continue; + + const Vector& cp = GetControlPointAtCurrentTime(i); + VectorMin( m_MinBounds, cp, m_MinBounds ); + VectorMax( m_MaxBounds, cp, m_MaxBounds ); + } + } + + // Deal with children + // NOTE: Bounds have been recomputed for children prior to this call in Simulate + Vector vecMins, vecMaxs; + for( CParticleCollection *i = m_Children.m_pHead; i; i = i->m_pNext ) + { + i->GetBounds( &vecMins, &vecMaxs ); + VectorMin( m_MinBounds, vecMins, m_MinBounds ); + VectorMax( m_MaxBounds, vecMaxs, m_MaxBounds ); + } +} + + +//----------------------------------------------------------------------------- +// Recomputes the bounds +//----------------------------------------------------------------------------- +void CParticleCollection::RecomputeBounds( void ) +{ + if ( m_nActiveParticles == 0.0f ) + { + m_bBoundsValid = false; + m_MinBounds.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + m_MaxBounds.Init( -FLT_MAX, -FLT_MAX, -FLT_MAX ); + m_Center.Init(); + return; + } + + fltx4 min_x = ReplicateX4(1.0e23); + fltx4 min_y = min_x; + fltx4 min_z = min_x; + fltx4 max_x = ReplicateX4(-1.0e23); + fltx4 max_y = max_x; + fltx4 max_z = max_x; + + fltx4 sum_x = Four_Zeros; + fltx4 sum_y = Four_Zeros; + fltx4 sum_z = Four_Zeros; + + size_t xyz_stride; + const fltx4 *xyz = GetM128AttributePtr( PARTICLE_ATTRIBUTE_XYZ, &xyz_stride ); + + int ctr = m_nActiveParticles/4; + while ( ctr-- ) + { + min_x = MinSIMD( min_x, xyz[0] ); + max_x = MaxSIMD( max_x, xyz[0] ); + sum_x = AddSIMD( sum_x, xyz[0] ); + + min_y = MinSIMD( min_y, xyz[1] ); + max_y = MaxSIMD( max_y, xyz[1] ); + sum_y = AddSIMD( sum_y, xyz[1] ); + + min_z = MinSIMD( min_z, xyz[2] ); + max_z = MaxSIMD( max_z, xyz[2] ); + sum_z = AddSIMD( sum_z, xyz[2] ); + + xyz += xyz_stride; + } + m_bBoundsValid = true; + m_MinBounds.x = min( min( SubFloat( min_x, 0 ), SubFloat( min_x, 1 ) ), min( SubFloat( min_x, 2 ), SubFloat( min_x, 3 ) ) ); + m_MinBounds.y = min( min( SubFloat( min_y, 0 ), SubFloat( min_y, 1 ) ), min( SubFloat( min_y, 2 ), SubFloat( min_y, 3 ) ) ); + m_MinBounds.z = min( min( SubFloat( min_z, 0 ), SubFloat( min_z, 1 ) ), min( SubFloat( min_z, 2 ), SubFloat( min_z, 3 ) ) ); + + m_MaxBounds.x = max( max( SubFloat( max_x, 0 ), SubFloat( max_x, 1 ) ), max( SubFloat( max_x, 2 ), SubFloat( max_x, 3 ) ) ); + m_MaxBounds.y = max( max( SubFloat( max_y, 0 ), SubFloat( max_y, 1 ) ), max( SubFloat( max_y, 2 ), SubFloat( max_y, 3 ) ) ); + m_MaxBounds.z = max( max( SubFloat( max_z, 0 ), SubFloat( max_z, 1 ) ), max( SubFloat( max_z, 2 ), SubFloat( max_z, 3 ) ) ); + + float fsum_x = SubFloat( sum_x, 0 ) + SubFloat( sum_x, 1 ) + SubFloat( sum_x, 2 ) + SubFloat( sum_x, 3 ); + float fsum_y = SubFloat( sum_y, 0 ) + SubFloat( sum_y, 1 ) + SubFloat( sum_y, 2 ) + SubFloat( sum_y, 3 ); + float fsum_z = SubFloat( sum_z, 0 ) + SubFloat( sum_z, 1 ) + SubFloat( sum_z, 2 ) + SubFloat( sum_z, 3 ); + + // now, handle "tail" in a non-sse manner + for( int i=0; i < (m_nActiveParticles & 3); i++) + { + m_MinBounds.x = min( m_MinBounds.x, SubFloat( xyz[0], i ) ); + m_MaxBounds.x = max( m_MaxBounds.x, SubFloat( xyz[0], i ) ); + fsum_x += SubFloat( xyz[0], i ); + + m_MinBounds.y = min( m_MinBounds.y, SubFloat( xyz[1], i ) ); + m_MaxBounds.y = max( m_MaxBounds.y, SubFloat( xyz[1], i ) ); + fsum_y += SubFloat( xyz[1], i ); + + m_MinBounds.z = min( m_MinBounds.z, SubFloat( xyz[2], i ) ); + m_MaxBounds.z = max( m_MaxBounds.z, SubFloat( xyz[2], i ) ); + fsum_z += SubFloat( xyz[2], i ); + } + + VectorAdd( m_MinBounds, m_pDef->m_BoundingBoxMin, m_MinBounds ); + VectorAdd( m_MaxBounds, m_pDef->m_BoundingBoxMax, m_MaxBounds ); + + // calculate center + float flOONumParticles = 1.0 / m_nActiveParticles; + m_Center.x = flOONumParticles * fsum_x; + m_Center.y = flOONumParticles * fsum_y; + m_Center.z = flOONumParticles * fsum_z; +} + + +//----------------------------------------------------------------------------- +// Is the particle system finished emitting + all its particles are dead? +//----------------------------------------------------------------------------- +bool CParticleCollection::IsFinished( void ) +{ + if ( !m_pDef ) + return true; + if ( m_nParticleFlags & PCFLAGS_FIRST_FRAME ) + return false; + if ( m_nActiveParticles ) + return false; + if ( m_bDormant ) + return false; + + // no particles. See if any emmitters intead to create more particles + int nEmitterCount = m_pDef->m_Emitters.Count(); + for( int i=0; i < nEmitterCount; i++ ) + { + if ( m_pDef->m_Emitters[i]->MayCreateMoreParticles( this, m_pOperatorContextData+m_pDef->m_nEmittersCtxOffsets[i] ) ) + return false; + } + + // make sure all children are finished + for( CParticleCollection *i = m_Children.m_pHead; i; i=i->m_pNext ) + { + if ( !i->IsFinished() ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Stop emitting particles +//----------------------------------------------------------------------------- +void CParticleCollection::StopEmission( bool bInfiniteOnly, bool bRemoveAllParticles, bool bWakeOnStop ) +{ + if ( !m_pDef ) + return; + + // Whenever we call stop emission, we clear out our dormancy. This ensures we + // get deleted if we're told to stop emission while dormant. SetDormant() ensures + // dormancy is set to true after stopping out emission. + m_bDormant = false; + + if ( bWakeOnStop ) + { + // Set next sleep time - an additional fudge factor is added over the normal time + // so that existing particles have a chance to go away. + m_flNextSleepTime = Max ( m_flNextSleepTime, ( g_pParticleSystemMgr->GetLastSimulationTime() + 10 )); + } + + m_bEmissionStopped = true; + + for( int i=0; i < m_pDef->m_Emitters.Count(); i++ ) + { + m_pDef->m_Emitters[i]->StopEmission( this, m_pOperatorContextData + m_pDef->m_nEmittersCtxOffsets[i], bInfiniteOnly ); + } + + if ( bRemoveAllParticles ) + { + SetNActiveParticles( 0 ); + } + + // Stop our children as well + for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext ) + { + p->StopEmission( bInfiniteOnly, bRemoveAllParticles ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Stop emitting particles +//----------------------------------------------------------------------------- +void CParticleCollection::StartEmission( bool bInfiniteOnly ) +{ + if ( !m_pDef ) + return; + + m_bEmissionStopped = false; + + for( int i=0; i < m_pDef->m_Emitters.Count(); i++ ) + { + m_pDef->m_Emitters[i]->StartEmission( this, m_pOperatorContextData + m_pDef->m_nEmittersCtxOffsets[i], bInfiniteOnly ); + } + + // Stop our children as well + for( CParticleCollection *p = m_Children.m_pHead; p; p = p->m_pNext ) + { + p->StartEmission( bInfiniteOnly ); + } + + // Set our sleep time to some time in the future so we update again + m_flNextSleepTime = g_pParticleSystemMgr->GetLastSimulationTime() + m_pDef->m_flNoDrawTimeToGoToSleep; +} + +//----------------------------------------------------------------------------- +// Purpose: Dormant particle systems simulate their particles, but don't emit +// new ones. Unlike particle systems that have StopEmission() called +// dormant particle systems don't remove themselves when they're +// out of particles, assuming they'll return at some point. +//----------------------------------------------------------------------------- +void CParticleCollection::SetDormant( bool bDormant ) +{ + // Don't stop or start emission if we are not changing dormancy state + if ( bDormant == m_bDormant ) + return; + + // If emissions have already been stopped, don't go dormant, we're supposed to be dying. + if ( m_bEmissionStopped && bDormant ) + return; + + if ( bDormant ) + { + StopEmission(); + } + else + { + StartEmission(); + } + + m_bDormant = bDormant; +} + + +void CParticleCollection::MoveParticle( int nInitialIndex, int nNewIndex ) +{ + // Copy the per-particle attributes + for( int p = 0; p < MAX_PARTICLE_ATTRIBUTES; ++p ) + { + switch( m_nParticleFloatStrides[ p ] ) + { + case 4: // move a float + m_pParticleAttributes[p][nNewIndex] = m_pParticleAttributes[p][nInitialIndex]; + break; + + case 12: // move a vec3 + { + // sse weirdness + int oldidxsse = 12 * ( nNewIndex >> 2 ); + int oldofs = oldidxsse + ( nNewIndex & 3 ); + int lastidxsse = 12 * ( nInitialIndex >> 2 ); + int lastofs = lastidxsse + ( nInitialIndex & 3 ); + + m_pParticleAttributes[p][oldofs] = m_pParticleAttributes[p][lastofs]; + m_pParticleAttributes[p][4+oldofs] = m_pParticleAttributes[p][4+lastofs]; + m_pParticleAttributes[p][8+oldofs] = m_pParticleAttributes[p][8+lastofs]; + break; + } + } + + switch( m_nParticleInitialFloatStrides[ p ] ) + { + case 4: // move a float + m_pParticleInitialAttributes[p][nNewIndex] = m_pParticleInitialAttributes[p][nInitialIndex]; + break; + + case 12: // move a vec3 + { + // sse weirdness + int oldidxsse = 12 * ( nNewIndex>>2 ); + int oldofs = oldidxsse + ( nNewIndex & 3 ); + int lastidxsse = 12 * ( nInitialIndex >> 2 ); + int lastofs = lastidxsse + ( nInitialIndex & 3 ); + + m_pParticleInitialAttributes[p][oldofs] = m_pParticleInitialAttributes[p][lastofs]; + m_pParticleInitialAttributes[p][4+oldofs] = m_pParticleInitialAttributes[p][4+lastofs]; + m_pParticleInitialAttributes[p][8+oldofs] = m_pParticleInitialAttributes[p][8+lastofs]; + break; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Kill List processing. +//----------------------------------------------------------------------------- + +#define THREADED_PARTICLES 1 + +#if THREADED_PARTICLES +#define MAX_SIMULTANEOUS_KILL_LISTS 16 +static volatile int g_nKillBufferInUse[MAX_SIMULTANEOUS_KILL_LISTS]; +static int32 *g_pKillBuffers[MAX_SIMULTANEOUS_KILL_LISTS]; + +void CParticleSystemMgr::DetachKillList( CParticleCollection *pParticles ) +{ + if ( pParticles->m_pParticleKillList ) + { + // find which it is + for(int i=0; i < NELEMS( g_pKillBuffers ); i++) + { + if ( g_pKillBuffers[i] == pParticles->m_pParticleKillList ) + { + pParticles->m_pParticleKillList = NULL; + g_nKillBufferInUse[i] = 0; // no need to interlock + return; + } + } + Assert( 0 ); // how did we get here? + } +} + +void CParticleSystemMgr::AttachKillList( CParticleCollection *pParticles ) +{ + // look for a free slot + for(;;) + { + for(int i=0; i < NELEMS( g_nKillBufferInUse ); i++) + { + if ( ! g_nKillBufferInUse[i] ) // available? + { + // try to take it! + if ( ThreadInterlockedAssignIf( &( g_nKillBufferInUse[i]), 1, 0 ) ) + { + if ( ! g_pKillBuffers[i] ) + { + g_pKillBuffers[i] = new int32[MAX_PARTICLES_IN_A_SYSTEM]; + } + pParticles->m_pParticleKillList = g_pKillBuffers[i]; + return; // done! + } + + } + } + Assert(0); // why don't we have enough buffers? + ThreadSleep(); + } +} +#else +// use one static kill list. no worries because of not threading +static int g_nParticleKillList[MAX_PARTICLES_IN_A_SYSTEM]; +void CParticleSystemMgr::AttachKillList( CParticleCollection *pParticles ) +{ + pParticles->m_pParticleKillList = g_nParticleKillList; +} +void CParticleCollection::DetachKillList( CParticleCollection *pParticles ) +{ + Assert( pParticles->m_nNumParticlesToKill == 0 ); + pParticles->m_pParticleKillList = NULL; +} +#endif + + +void CParticleCollection::ApplyKillList( void ) +{ + int nLeftInKillList = m_nNumParticlesToKill; + if ( nLeftInKillList == 0 ) + return; + + int nParticlesActiveNow = m_nActiveParticles; + const int *pCurKillListSlot = m_pParticleKillList; + +#ifdef _DEBUG + // This algorithm assumes the particles listed in the kill list are in ascending order + for ( int i = 1; i < nLeftInKillList; ++i ) + { + Assert( pCurKillListSlot[i] > pCurKillListSlot[i-1] ); + } +#endif + + // first, kill particles past bounds + while ( nLeftInKillList && pCurKillListSlot[nLeftInKillList - 1] >= nParticlesActiveNow ) + { + nLeftInKillList--; + } + + Assert( nLeftInKillList <= m_nActiveParticles ); + + // now, execute kill list + // Previously, this code would swap the last item that wasn't dead into this slot. + // However, some lists require order invariance, so we need to collapse over holes instead of + // doing the (cheaper) swap from the end to the hole. + if ( !m_bRequiresOrderInvariance ) + { + while( nLeftInKillList ) + { + int nKillIndex = *(pCurKillListSlot++); + nLeftInKillList--; + + // now, we will move a particle from the end to where we are + // first, we have to find the last particle (which is not in the kill list) + while ( nLeftInKillList && + ( pCurKillListSlot[ nLeftInKillList-1 ] == nParticlesActiveNow-1 )) + { + nLeftInKillList--; + nParticlesActiveNow--; + } + + // we might be killing the last particle + if ( nKillIndex == nParticlesActiveNow-1 ) + { + // killing last one + nParticlesActiveNow--; + break; // we are done + } + + // move the last particle to this one and chop off the end of the list + MoveParticle( nParticlesActiveNow-1, nKillIndex ); + nParticlesActiveNow--; + } + } + else + { + // The calling code may tell us to kill particles that are already out of bounds. + // That causes this code to kill more particles than we're supposed to (possibly even causing a crash). + // So remember how many particles we had left to kill so we can properly decrement the count below. + int decrementValue = nLeftInKillList; + int writeLoc = *(pCurKillListSlot++); + --nLeftInKillList; + + for ( int readLoc = 1 + writeLoc; readLoc < nParticlesActiveNow; ++readLoc ) + { + if ( nLeftInKillList > 0 && readLoc == *pCurKillListSlot ) + { + pCurKillListSlot++; + --nLeftInKillList; + continue; + } + + MoveParticle( readLoc, writeLoc ); + ++writeLoc; + } + + nParticlesActiveNow -= decrementValue; + } + + // set count in system and wipe kill list + SetNActiveParticles( nParticlesActiveNow ); + m_nNumParticlesToKill = 0; +} + +void CParticleCollection::CalculatePathValues( CPathParameters const &PathIn, + float flTimeStamp, + Vector *pStartPnt, + Vector *pMidPnt, + Vector *pEndPnt + ) +{ + Vector StartPnt; + GetControlPointAtTime( PathIn.m_nStartControlPointNumber, flTimeStamp, &StartPnt ); + Vector EndPnt; + GetControlPointAtTime( PathIn.m_nEndControlPointNumber, flTimeStamp, &EndPnt ); + + Vector MidP; + VectorLerp(StartPnt, EndPnt, PathIn.m_flMidPoint, MidP); + + if ( PathIn.m_nBulgeControl ) + { + Vector vTarget=(EndPnt-StartPnt); + float flBulgeScale = 0.0; + int nCP=PathIn.m_nStartControlPointNumber; + if ( PathIn.m_nBulgeControl == 2) + nCP = PathIn.m_nEndControlPointNumber; + Vector Fwd = m_ControlPoints[nCP].m_ForwardVector; + float len=VectorLength( vTarget); + if ( len > 1.0e-6 ) + { + vTarget *= (1.0/len); // normalize + flBulgeScale = 1.0-fabs( DotProduct( vTarget, Fwd )); // bulge inversely scaled + } + Vector Potential_MidP=Fwd; + float flOffsetDist = VectorLength( Potential_MidP ); + if ( flOffsetDist > 1.0e-6 ) + { + Potential_MidP *= (PathIn.m_flBulge*len*flBulgeScale)/flOffsetDist; + MidP += Potential_MidP; + } + } + else + { + Vector RndVector; + RandomVector( 0, -PathIn.m_flBulge, PathIn.m_flBulge, &RndVector); + MidP+=RndVector; + } + + *pStartPnt = StartPnt; + *pMidPnt = MidP; + *pEndPnt = EndPnt; +} + + +//----------------------------------------------------------------------------- +// +// Default impelemtation of the query +// +//----------------------------------------------------------------------------- + +class CDefaultParticleSystemQuery : public CBaseAppSystem< IParticleSystemQuery > +{ +public: + virtual void GetLightingAtPoint( const Vector& vecOrigin, Color &tint ) + { + tint.SetColor( 255, 255, 255, 255 ); + } + virtual void TraceLine( const Vector& vecAbsStart, + const Vector& vecAbsEnd, unsigned int mask, + const class IHandleEntity *ignore, + int collisionGroup, CBaseTrace *ptr ) + { + ptr->fraction = 1.0; // no hit + } + + virtual void GetRandomPointsOnControllingObjectHitBox( + CParticleCollection *pParticles, + int nControlPointNumber, + int nNumPtsOut, + float flBBoxScale, + int nNumTrysToGetAPointInsideTheModel, + Vector *pPntsOut, + Vector vecDirectionBias, + Vector *pHitBoxRelativeCoordOut, int *pHitBoxIndexOut ) + { + for ( int i = 0; i < nNumPtsOut; ++i ) + { + pPntsOut[i].Init(); + } + } + + virtual float GetPixelVisibility( int *pQueryHandle, const Vector &vecOrigin, float flScale ) { return 0.0f; } +}; + +static CDefaultParticleSystemQuery s_DefaultParticleSystemQuery; + + +//----------------------------------------------------------------------------- +// +// Particle system manager +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CParticleSystemMgr::CParticleSystemMgr() + // m_SheetList( DefLessFunc( ITexture * ) ) +{ + m_pQuery = &s_DefaultParticleSystemQuery; + m_bDidInit = false; + m_bUsingDefaultQuery = true; + m_bShouldLoadSheets = true; + m_pParticleSystemDictionary = NULL; + m_nNumFramesMeasured = 0; + m_flLastSimulationTime = 0.0f; + m_nParticleVertexCount = m_nParticleIndexCount = 0; + m_bFrameWarningNeeded = false; + + for ( int i = 0; i < c_nNumFramesTracked; i++ ) + { + m_nParticleVertexCountHistory[i] = 0; + } + m_fParticleCountScaling = 1.0f; +} + +CParticleSystemMgr::~CParticleSystemMgr() +{ + if ( m_pParticleSystemDictionary ) + { + delete m_pParticleSystemDictionary; + m_pParticleSystemDictionary = NULL; + } + FlushAllSheets(); +} + + +//----------------------------------------------------------------------------- +// Initialize the particle system +//----------------------------------------------------------------------------- +bool CParticleSystemMgr::Init( IParticleSystemQuery *pQuery ) +{ + if ( !g_pMaterialSystem->QueryInterface( MATERIAL_SYSTEM_INTERFACE_VERSION ) ) + { + Msg( "CParticleSystemMgr compiled using an old IMaterialSystem\n" ); + return false; + } + + if ( m_bUsingDefaultQuery && pQuery ) + { + m_pQuery = pQuery; + m_bUsingDefaultQuery = false; + } + + if ( !m_bDidInit ) + { + m_pParticleSystemDictionary = new CParticleSystemDictionary; + // NOTE: This is for the editor only + AddParticleOperator( FUNCTION_CHILDREN, &s_ChildOperatorDefinition ); + + m_pShadowDepthMaterial = NULL; + if( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 90 ) + { + KeyValues *pVMTKeyValues = new KeyValues( "DepthWrite" ); + pVMTKeyValues->SetInt( "$no_fullbright", 1 ); + pVMTKeyValues->SetInt( "$model", 0 ); + pVMTKeyValues->SetInt( "$alphatest", 0 ); + m_pShadowDepthMaterial = g_pMaterialSystem->CreateMaterial( "__particlesDepthWrite", pVMTKeyValues ); + } + + SeedRandSIMD( 12345678 ); + m_bDidInit = true; + } + + return true; +} + +//---------------------------------------------------------------------------------- +// Cache/uncache materials used by particle systems +//---------------------------------------------------------------------------------- +void CParticleSystemMgr::PrecacheParticleSystem( const char *pName ) +{ + if ( !pName || !pName[0] ) + { + return; + } + + CParticleSystemDefinition* pDef = FindParticleSystem( pName ); + if ( !pDef ) + { + Warning( "Attemped to precache unknown particle system \"%s\"!\n", pName ); + return; + } + + pDef->Precache(); +} + +void CParticleSystemMgr::UncacheAllParticleSystems() +{ + if ( !m_pParticleSystemDictionary ) + return; + + int nCount = m_pParticleSystemDictionary->Count(); + for ( int i = 0; i < nCount; ++i ) + { + m_pParticleSystemDictionary->GetParticleSystem( i )->Uncache(); + } + + nCount = m_pParticleSystemDictionary->NameCount(); + for ( ParticleSystemHandle_t h = 0; h < nCount; ++h ) + { + m_pParticleSystemDictionary->FindParticleSystem( h )->Uncache(); + } + + // Flush sheets, as they can accumulate several MB of memory per map + FlushAllSheets(); +} + + +//----------------------------------------------------------------------------- +// return the particle field name +//----------------------------------------------------------------------------- +static const char *s_pParticleFieldNames[MAX_PARTICLE_ATTRIBUTES] = +{ + "Position", // XYZ, 0 + "Life Duration", // LIFE_DURATION, 1 ); + NULL, // PREV_XYZ is for internal use only + "Radius", // RADIUS, 3 ); + + "Roll", // ROTATION, 4 ); + "Roll Speed", // ROTATION_SPEED, 5 ); + "Color", // TINT_RGB, 6 ); + "Alpha", // ALPHA, 7 ); + + "Creation Time", // CREATION_TIME, 8 ); + "Sequence Number", // SEQUENCE_NUMBER, 9 ); + "Trail Length", // TRAIL_LENGTH, 10 ); + "Particle ID", // PARTICLE_ID, 11 ); + + "Yaw", // YAW, 12 ); + "Sequence Number 1", // SEQUENCE_NUMBER1, 13 ); + NULL, // HITBOX_INDEX is for internal use only + NULL, // HITBOX_XYZ_RELATIVE is for internal use only + + "Alpha Alternate", // ALPHA2, 16 + NULL, + NULL, + NULL, + + NULL, + NULL, + NULL, + NULL, + + NULL, + NULL, + NULL, + NULL, + + NULL, + NULL, + NULL, + NULL, +}; + +const char* CParticleSystemMgr::GetParticleFieldName( int nParticleField ) const +{ + return s_pParticleFieldNames[nParticleField]; +} + + +//----------------------------------------------------------------------------- +// Returns the available particle operators +//----------------------------------------------------------------------------- +void CParticleSystemMgr::AddParticleOperator( ParticleFunctionType_t nOpType, + IParticleOperatorDefinition *pOpFactory ) +{ + m_ParticleOperators[nOpType].AddToTail( pOpFactory ); +} + +CUtlVector< IParticleOperatorDefinition *> &CParticleSystemMgr::GetAvailableParticleOperatorList( ParticleFunctionType_t nWhichList ) +{ + return m_ParticleOperators[nWhichList]; +} + +const DmxElementUnpackStructure_t *CParticleSystemMgr::GetParticleSystemDefinitionUnpackStructure() +{ + return s_pParticleSystemDefinitionUnpack; +} + + +//------------------------------------------------------------------------------ +// custom allocators for operators so simd aligned +//------------------------------------------------------------------------------ +#include "tier0/memdbgoff.h" +void *CParticleOperatorInstance::operator new( size_t nSize ) +{ + return MemAlloc_AllocAligned( nSize, 16 ); +} + +void* CParticleOperatorInstance::operator new( size_t nSize, int nBlockUse, const char *pFileName, int nLine ) +{ + return MemAlloc_AllocAligned( nSize, 16, pFileName, nLine ); +} + +void CParticleOperatorInstance::operator delete(void *pData) +{ + if ( pData ) + { + MemAlloc_FreeAligned( pData ); + } +} + +void CParticleOperatorInstance::operator delete( void* pData, int nBlockUse, const char *pFileName, int nLine ) +{ + if ( pData ) + { + MemAlloc_FreeAligned( pData ); + } +} + +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Read the particle config file from a utlbuffer +//----------------------------------------------------------------------------- +bool CParticleSystemMgr::ReadParticleDefinitions( CUtlBuffer &buf, const char *pFileName, bool bPrecache, bool bDecommitTempMemory ) +{ + DECLARE_DMX_CONTEXT_DECOMMIT( bDecommitTempMemory ); + + CDmxElement *pRoot; + if ( !UnserializeDMX( buf, &pRoot, pFileName ) || !pRoot ) + { + Warning( "Unable to read particle definition %s! UtlBuffer is the wrong type!\n", pFileName ); + return false; + } + + if ( !Q_stricmp( pRoot->GetTypeString(), "DmeParticleSystemDefinition" ) ) + { + CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->AddParticleSystem( pRoot ); + if ( pDef && bPrecache ) + { + pDef->m_bAlwaysPrecache = true; + if ( IsPC() ) + { + pDef->Precache(); + } + } + CleanupDMX( pRoot ); + return true; + } + + const CDmxAttribute *pDefinitions = pRoot->GetAttribute( "particleSystemDefinitions" ); + if ( !pDefinitions || pDefinitions->GetType() != AT_ELEMENT_ARRAY ) + { + CleanupDMX( pRoot ); + return false; + } + + const CUtlVector< CDmxElement* >& definitions = pDefinitions->GetArray( ); + int nCount = definitions.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->AddParticleSystem( definitions[i] ); + if ( pDef && bPrecache ) + { + pDef->m_bAlwaysPrecache = true; + if ( IsPC() ) + { + pDef->Precache(); + } + } + } + + CleanupDMX( pRoot ); + return true; +} + + +//----------------------------------------------------------------------------- +// Decommits temporary memory +//----------------------------------------------------------------------------- +void CParticleSystemMgr::DecommitTempMemory() +{ + DecommitDMXMemory(); +} + + +//----------------------------------------------------------------------------- +// Sets the last simulation time, used for particle system sleeping logic +//----------------------------------------------------------------------------- +void CParticleSystemMgr::SetLastSimulationTime( float flTime ) +{ + m_flLastSimulationTime = flTime; + + int nParticleVertexCountHistoryMax = 0; + for ( int i = 0; i < c_nNumFramesTracked-1; i++ ) + { + m_nParticleVertexCountHistory[i] = m_nParticleVertexCountHistory[i+1]; + nParticleVertexCountHistoryMax = Max ( nParticleVertexCountHistoryMax, m_nParticleVertexCountHistory[i] ); + } + m_nParticleVertexCountHistory[c_nNumFramesTracked-1] = m_nParticleVertexCount; + nParticleVertexCountHistoryMax = Max ( nParticleVertexCountHistoryMax, m_nParticleVertexCount ); + + // We need to take an average over a decent number of frames because this throttling has a direct feedback effect. Worried about oscillation problems! + int nLower = CL_PARTICLE_SCALE_LOWER;//cl_particle_scale_lower.GetInt(); + m_fParticleCountScaling = 1.0f; + int nHowManyOver = nParticleVertexCountHistoryMax - nLower; + if ( nHowManyOver > 0 ) + { + int nUpper = CL_PARTICLE_SCALE_UPPER;//cl_particle_scale_upper.GetInt(); + int nRange = nUpper - nLower; + m_fParticleCountScaling = 1.0f - ( (float)nHowManyOver / (float)nRange ); + m_fParticleCountScaling = Clamp ( m_fParticleCountScaling, 0.0f, 1.0f ); + } + + m_nParticleVertexCount = m_nParticleIndexCount = 0; +} + +float CParticleSystemMgr::GetLastSimulationTime() const +{ + return m_flLastSimulationTime; +} + +bool CParticleSystemMgr::Debug_FrameWarningNeededTestAndReset() +{ + bool bTemp = m_bFrameWarningNeeded; + m_bFrameWarningNeeded = false; + return bTemp; +} + +int CParticleSystemMgr::Debug_GetTotalParticleCount() const +{ + return m_nParticleVertexCountHistory[c_nNumFramesTracked-1]; +} + +float CParticleSystemMgr::ParticleThrottleScaling() const +{ + return m_fParticleCountScaling; +} + +bool CParticleSystemMgr::ParticleThrottleRandomEnable() const +{ + if ( m_fParticleCountScaling == 1.0f ) + { + // No throttling. + return true; + } + else if ( m_fParticleCountScaling > RandomFloat ( 0.0f, 1.0f ) ) + { + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +// Unserialization-related methods +//----------------------------------------------------------------------------- +void CParticleSystemMgr::AddParticleSystem( CDmxElement *pParticleSystem ) +{ + m_pParticleSystemDictionary->AddParticleSystem( pParticleSystem ); +} + +CParticleSystemDefinition* CParticleSystemMgr::FindParticleSystem( const char *pName ) +{ + return m_pParticleSystemDictionary->FindParticleSystem( pName ); +} + +CParticleSystemDefinition* CParticleSystemMgr::FindParticleSystem( const DmObjectId_t& id ) +{ + return m_pParticleSystemDictionary->FindParticleSystem( id ); +} + + +//----------------------------------------------------------------------------- +// Read the particle config file from a utlbuffer +//----------------------------------------------------------------------------- +bool CParticleSystemMgr::ReadParticleConfigFile( CUtlBuffer &buf, bool bPrecache, bool bDecommitTempMemory, const char *pFileName ) +{ + return ReadParticleDefinitions( buf, pFileName, bPrecache, bDecommitTempMemory ); +} + + +//----------------------------------------------------------------------------- +// Read the particle config file from a utlbuffer +//----------------------------------------------------------------------------- +bool CParticleSystemMgr::ReadParticleConfigFile( const char *pFileName, bool bPrecache, bool bDecommitTempMemory ) +{ + // Names starting with a '!' are always precached. + if ( pFileName[0] == '!' ) + { + bPrecache = true; + ++pFileName; + } + + if ( IsX360() ) + { + char szTargetName[MAX_PATH]; + CreateX360Filename( pFileName, szTargetName, sizeof( szTargetName ) ); + + CUtlBuffer fileBuffer; + bool bHaveParticles = g_pFullFileSystem->ReadFile( szTargetName, "GAME", fileBuffer ); + if ( bHaveParticles ) + { + fileBuffer.SetBigEndian( false ); + return ReadParticleConfigFile( fileBuffer, bPrecache, bDecommitTempMemory, szTargetName ); + } + else if ( g_pFullFileSystem->GetDVDMode() != DVDMODE_OFF ) + { + // 360 version should have been there, 360 zips can only have binary particles + Warning( "Particles: Missing '%s'\n", szTargetName ); + return false; + } + } + + char pFallbackBuf[MAX_PATH]; + if ( IsPC() ) + { + // Look for fallback particle systems + char pTemp[MAX_PATH]; + Q_StripExtension( pFileName, pTemp, sizeof(pTemp) ); + const char *pExt = Q_GetFileExtension( pFileName ); + if ( !pExt ) + { + pExt = "pcf"; + } + + if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 90 ) + { + Q_snprintf( pFallbackBuf, sizeof(pFallbackBuf), "%s_dx80.%s", pTemp, pExt ); + if ( g_pFullFileSystem->FileExists( pFallbackBuf ) ) + { + pFileName = pFallbackBuf; + } + } + else if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() == 90 && g_pMaterialSystemHardwareConfig->PreferReducedFillrate() ) + { + Q_snprintf( pFallbackBuf, sizeof(pFallbackBuf), "%s_dx90_slow.%s", pTemp, pExt ); + if ( g_pFullFileSystem->FileExists( pFallbackBuf ) ) + { + pFileName = pFallbackBuf; + } + } + } + + CUtlBuffer buf( 0, 0, 0 ); + if ( IsX360() ) + { + // fell through, load as pc particle resource file + buf.ActivateByteSwapping( true ); + } + + if ( g_pFullFileSystem->ReadFile( pFileName, "GAME", buf ) ) + { + return ReadParticleConfigFile( buf, bPrecache, bDecommitTempMemory, pFileName ); + } + + Warning( "Particles: Missing '%s'\n", pFileName ); + return false; +} + + +//----------------------------------------------------------------------------- +// Write a specific particle config to a utlbuffer +//----------------------------------------------------------------------------- +bool CParticleSystemMgr::WriteParticleConfigFile( const char *pParticleSystemName, CUtlBuffer &buf, bool bPreventNameBasedLookup ) +{ + DECLARE_DMX_CONTEXT(); + // Create DMX elements representing the particle system definition + CDmxElement *pParticleSystem = CreateParticleDmxElement( pParticleSystemName ); + return WriteParticleConfigFile( pParticleSystem, buf, bPreventNameBasedLookup ); +} + +bool CParticleSystemMgr::WriteParticleConfigFile( const DmObjectId_t& id, CUtlBuffer &buf, bool bPreventNameBasedLookup ) +{ + DECLARE_DMX_CONTEXT(); + // Create DMX elements representing the particle system definition + CDmxElement *pParticleSystem = CreateParticleDmxElement( id ); + return WriteParticleConfigFile( pParticleSystem, buf, bPreventNameBasedLookup ); +} + +bool CParticleSystemMgr::WriteParticleConfigFile( CDmxElement *pParticleSystem, CUtlBuffer &buf, bool bPreventNameBasedLookup ) +{ + pParticleSystem->SetValue( "preventNameBasedLookup", bPreventNameBasedLookup ); + + CDmxAttribute* pAttribute = pParticleSystem->GetAttribute( "children" ); + const CUtlVector< CDmxElement* >& children = pAttribute->GetArray( ); + int nCount = children.Count(); + for ( int i = 0; i < nCount; ++i ) + { + CDmxElement *pChildRef = children[ i ]; + CDmxElement *pChild = pChildRef->GetValue( "child" ); + pChild->SetValue( "preventNameBasedLookup", bPreventNameBasedLookup ); + } + + // Now write the DMX elements out + bool bOk = SerializeDMX( buf, pParticleSystem ); + CleanupDMX( pParticleSystem ); + return bOk; +} + +ParticleSystemHandle_t CParticleSystemMgr::GetParticleSystemIndex( const char *pParticleSystemName ) +{ + if ( !pParticleSystemName ) + return UTL_INVAL_SYMBOL; + + return m_pParticleSystemDictionary->FindParticleSystemHandle( pParticleSystemName ); +} + +const char *CParticleSystemMgr::GetParticleSystemNameFromIndex( ParticleSystemHandle_t iIndex ) +{ + CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->FindParticleSystem( iIndex ); + return pDef ? pDef->GetName() : "Unknown"; +} + +int CParticleSystemMgr::GetParticleSystemCount( void ) +{ + return m_pParticleSystemDictionary->NameCount(); +} + + +//----------------------------------------------------------------------------- +// Factory method for creating particle collections +//----------------------------------------------------------------------------- +CParticleCollection *CParticleSystemMgr::CreateParticleCollection( const char *pParticleSystemName, float flDelay, int nRandomSeed ) +{ + if ( !pParticleSystemName ) + return NULL; + + CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->FindParticleSystem( pParticleSystemName ); + if ( !pDef ) + { + Warning( "Attempted to create unknown particle system type %s\n", pParticleSystemName ); + return NULL; + } + + CParticleCollection *pParticleCollection = new CParticleCollection; + pParticleCollection->Init( pDef, flDelay, nRandomSeed ); + return pParticleCollection; +} + + +CParticleCollection *CParticleSystemMgr::CreateParticleCollection( const DmObjectId_t &id, float flDelay, int nRandomSeed ) +{ + if ( !IsUniqueIdValid( id ) ) + return NULL; + + CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->FindParticleSystem( id ); + if ( !pDef ) + { + char pBuf[256]; + UniqueIdToString( id, pBuf, sizeof(pBuf) ); + Warning( "Attempted to create unknown particle system id %s\n", pBuf ); + return NULL; + } + + CParticleCollection *pParticleCollection = new CParticleCollection; + pParticleCollection->Init( pDef, flDelay, nRandomSeed ); + return pParticleCollection; +} + + +//-------------------------------------------------------------------------------- +// Is a particular particle system defined? +//-------------------------------------------------------------------------------- +bool CParticleSystemMgr::IsParticleSystemDefined( const DmObjectId_t &id ) +{ + if ( !IsUniqueIdValid( id ) ) + return false; + + CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->FindParticleSystem( id ); + return ( pDef != NULL ); +} + + +//-------------------------------------------------------------------------------- +// Is a particular particle system defined? +//-------------------------------------------------------------------------------- +bool CParticleSystemMgr::IsParticleSystemDefined( const char *pName ) +{ + if ( !pName || !pName[0] ) + return false; + + CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->FindParticleSystem( pName ); + return ( pDef != NULL ); +} + + +//-------------------------------------------------------------------------------- +// Particle kill list +//-------------------------------------------------------------------------------- + + +//-------------------------------------------------------------------------------- +// Serialization-related methods +//-------------------------------------------------------------------------------- +CDmxElement *CParticleSystemMgr::CreateParticleDmxElement( const DmObjectId_t &id ) +{ + CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->FindParticleSystem( id ); + + // Create DMX elements representing the particle system definition + return pDef->Write( ); +} + +CDmxElement *CParticleSystemMgr::CreateParticleDmxElement( const char *pParticleSystemName ) +{ + CParticleSystemDefinition *pDef = m_pParticleSystemDictionary->FindParticleSystem( pParticleSystemName ); + + // Create DMX elements representing the particle system definition + return pDef->Write( ); +} + +//-------------------------------------------------------------------------------- +// Client loads sheets for rendering, server doesn't need to. +//-------------------------------------------------------------------------------- +void CParticleSystemMgr::ShouldLoadSheets( bool bLoadSheets ) +{ + m_bShouldLoadSheets = bLoadSheets; +} + +//-------------------------------------------------------------------------------- +// Particle sheets +//-------------------------------------------------------------------------------- +CSheet *CParticleSystemMgr::FindOrLoadSheet( char const *pszFname, ITexture *pTexture ) +{ + if ( !m_bShouldLoadSheets ) + return NULL; + + if ( m_SheetList.Defined( pszFname ) ) + return m_SheetList[ pszFname ]; + + CSheet *pNewSheet = NULL; + + size_t numBytes; + void const *pSheet = pTexture->GetResourceData( VTF_RSRC_SHEET, &numBytes ); + + if ( pSheet ) + { + CUtlBuffer bufLoad( pSheet, numBytes, CUtlBuffer::READ_ONLY ); + pNewSheet = new CSheet( bufLoad ); + } + + m_SheetList[ pszFname ] = pNewSheet; + return pNewSheet; +} + +CSheet *CParticleSystemMgr::FindOrLoadSheet( IMaterial *pMaterial ) +{ + if ( !pMaterial ) + return NULL; + + bool bFoundVar = false; + IMaterialVar *pVar = pMaterial->FindVar( "$basetexture", &bFoundVar, true ); + + if ( bFoundVar && pVar && pVar->IsDefined() ) + { + ITexture *pTex = pVar->GetTextureValue(); + if ( pTex && !pTex->IsError() ) + return FindOrLoadSheet( pTex->GetName(), pTex ); + } + + return NULL; +} + +void CParticleSystemMgr::FlushAllSheets( void ) +{ +// for( int i = 0, iEnd = m_SheetList.Count(); i < iEnd; i++ ) +// { +// delete m_SheetList.Element(i); +// } +// +// m_SheetList.RemoveAll(); + + m_SheetList.PurgeAndDeleteElements(); +} + + +//----------------------------------------------------------------------------- +// Render cache +//----------------------------------------------------------------------------- +void CParticleSystemMgr::ResetRenderCache( void ) +{ + int nCount = m_RenderCache.Count(); + for ( int i = 0; i < nCount; ++i ) + { + m_RenderCache[i].m_ParticleCollections.RemoveAll(); + } +} + +void CParticleSystemMgr::AddToRenderCache( CParticleCollection *pParticles ) +{ + if ( !pParticles->IsValid() || pParticles->m_pDef->GetMaterial()->IsTranslucent() ) + return; + + pParticles->m_flNextSleepTime = Max ( pParticles->m_flNextSleepTime, ( g_pParticleSystemMgr->GetLastSimulationTime() + pParticles->m_pDef->m_flNoDrawTimeToGoToSleep )); + // Find the current rope list. + int iRenderCache = 0; + int nRenderCacheCount = m_RenderCache.Count(); + for ( ; iRenderCache < nRenderCacheCount; ++iRenderCache ) + { + if ( ( pParticles->m_pDef->GetMaterial() == m_RenderCache[iRenderCache].m_pMaterial ) ) + break; + } + + // A full rope list should have been generate in CreateRenderCache + // If we didn't find one, then allocate the mofo. + if ( iRenderCache == nRenderCacheCount ) + { + iRenderCache = m_RenderCache.AddToTail(); + m_RenderCache[iRenderCache].m_pMaterial = pParticles->m_pDef->GetMaterial(); + } + + m_RenderCache[iRenderCache].m_ParticleCollections.AddToTail( pParticles ); + + for( CParticleCollection *p = pParticles->m_Children.m_pHead; p; p = p->m_pNext ) + { + AddToRenderCache( p ); + } +} + + +void CParticleSystemMgr::BuildBatchList( int iRenderCache, IMatRenderContext *pRenderContext, CUtlVector< Batch_t >& batches ) +{ + batches.RemoveAll(); + + IMaterial *pMaterial = m_RenderCache[iRenderCache].m_pMaterial; + int nMaxVertices = pRenderContext->GetMaxVerticesToRender( pMaterial ); + int nMaxIndices = pRenderContext->GetMaxIndicesToRender(); + + int nRemainingVertices = nMaxVertices; + int nRemainingIndices = nMaxIndices; + + int i = batches.AddToTail(); + Batch_t* pBatch = &batches[i]; + pBatch->m_nVertCount = 0; + pBatch->m_nIndexCount = 0; + + // Ask each renderer about the # of verts + ints it will draw + int nCacheCount = m_RenderCache[iRenderCache].m_ParticleCollections.Count(); + for ( int iCache = 0; iCache < nCacheCount; ++iCache ) + { + CParticleCollection *pParticles = m_RenderCache[iRenderCache].m_ParticleCollections[iCache]; + if ( !pParticles->IsValid() ) + continue; + + int nRenderCount = pParticles->GetRendererCount(); + for ( int j = 0; j < nRenderCount; ++j ) + { + int nFirstParticle = 0; + while ( nFirstParticle < pParticles->m_nActiveParticles ) + { + int iPart; + BatchStep_t step; + step.m_pParticles = pParticles; + step.m_pRenderer = pParticles->GetRenderer( j ); + step.m_pContext = pParticles->GetRendererContext( j ); + step.m_nFirstParticle = nFirstParticle; + step.m_nParticleCount = step.m_pRenderer->GetParticlesToRender( pParticles, + step.m_pContext, nFirstParticle, nRemainingVertices, nRemainingIndices, &step.m_nVertCount, &iPart ); + nFirstParticle += step.m_nParticleCount; + + if ( step.m_nParticleCount > 0 ) + { + pBatch->m_nVertCount += step.m_nVertCount; + pBatch->m_nIndexCount += iPart; + pBatch->m_BatchStep.AddToTail( step ); + Assert( pBatch->m_nVertCount <= nMaxVertices && pBatch->m_nIndexCount <= nMaxIndices ); + } + else + { + if ( pBatch->m_nVertCount == 0 ) + break; + + // Not enough room + Assert( pBatch->m_nVertCount > 0 && pBatch->m_nIndexCount > 0 ); + pBatch = &batches[batches.AddToTail()]; + pBatch->m_nVertCount = 0; + pBatch->m_nIndexCount = 0; + nRemainingVertices = nMaxVertices; + nRemainingIndices = nMaxIndices; + } + } + } + } + + if ( pBatch->m_nVertCount <= 0 || pBatch->m_nIndexCount <= 0 ) + { + batches.FastRemove( batches.Count() - 1 ); + } +} + +void CParticleSystemMgr::DumpProfileInformation( void ) +{ +#if MEASURE_PARTICLE_PERF + FileHandle_t fh = g_pFullFileSystem->Open( "particle_profile.csv", "w" ); + g_pFullFileSystem->FPrintf( fh, "numframes,%d\n", m_nNumFramesMeasured ); + g_pFullFileSystem->FPrintf( fh, "name, total time, max time, max particles, allocated particles\n"); + for( int i=0; i < m_pParticleSystemDictionary->NameCount(); i++ ) + { + CParticleSystemDefinition *p = ( *m_pParticleSystemDictionary )[ i ]; + if ( p->m_nMaximumActiveParticles ) + g_pFullFileSystem->FPrintf( fh, "%s,%f,%f,%d,%d\n", p->m_Name.Get(), p->m_flTotalSimTime, p->m_flMaxMeasuredSimTime, p->m_nMaximumActiveParticles, p->m_nMaxParticles ); + } + g_pFullFileSystem->FPrintf( fh, "\n\nopname, total time, max time\n"); + for(int i=0; i < ARRAYSIZE( m_ParticleOperators ); i++) + { + for(int j=0; j < m_ParticleOperators[i].Count() ; j++ ) + { + float flmax = m_ParticleOperators[i][j]->MaximumRecordedExecutionTime(); + float fltotal = m_ParticleOperators[i][j]->TotalRecordedExecutionTime(); + if ( fltotal > 0.0 ) + g_pFullFileSystem->FPrintf( fh, "%s,%f,%f\n", + m_ParticleOperators[i][j]->GetName(), fltotal, flmax ); + } + } + g_pFullFileSystem->Close( fh ); +#endif +} + +void CParticleSystemMgr::CommitProfileInformation( bool bCommit ) +{ +#if MEASURE_PARTICLE_PERF + if ( 1 ) + { + if ( bCommit ) + m_nNumFramesMeasured++; + for( int i=0; i < m_pParticleSystemDictionary->NameCount(); i++ ) + { + CParticleSystemDefinition *p = ( *m_pParticleSystemDictionary )[ i ]; + if ( bCommit ) + p->m_flTotalSimTime += p->m_flUncomittedTotalSimTime; + p->m_flUncomittedTotalSimTime = 0.; + } + for(int i=0; i < ARRAYSIZE( m_ParticleOperators ); i++) + { + for(int j=0; j < m_ParticleOperators[i].Count() ; j++ ) + { + if ( bCommit ) + m_ParticleOperators[i][j]->m_flTotalExecutionTime += m_ParticleOperators[i][j]->m_flUncomittedTime; + m_ParticleOperators[i][j]->m_flUncomittedTime = 0; + } + } + } +#endif +} + +void CParticleSystemMgr::DrawRenderCache( bool bShadowDepth ) +{ + int nRenderCacheCount = m_RenderCache.Count(); + if ( nRenderCacheCount == 0 ) + return; + + VPROF_BUDGET( "CParticleSystemMgr::DrawRenderCache", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + + CUtlVector< Batch_t > batches( 0, 8 ); + + for ( int iRenderCache = 0; iRenderCache < nRenderCacheCount; ++iRenderCache ) + { + int nCacheCount = m_RenderCache[iRenderCache].m_ParticleCollections.Count(); + if ( nCacheCount == 0 ) + continue; + + // FIXME: When rendering shadow depth, do it all in 1 batch + IMaterial *pMaterial = bShadowDepth ? m_pShadowDepthMaterial : m_RenderCache[iRenderCache].m_pMaterial; + + BuildBatchList( iRenderCache, pRenderContext, batches ); + int nBatchCount = batches.Count(); + if ( nBatchCount == 0 ) + continue; + + pRenderContext->Bind( pMaterial ); + CMeshBuilder meshBuilder; + IMesh* pMesh = pRenderContext->GetDynamicMesh( ); + + for ( int i = 0; i < nBatchCount; ++i ) + { + const Batch_t& batch = batches[i]; + Assert( batch.m_nVertCount > 0 && batch.m_nIndexCount > 0 ); + + g_pParticleSystemMgr->TallyParticlesRendered( batch.m_nVertCount * 3, batch.m_nIndexCount * 3 ); + + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, batch.m_nVertCount, batch.m_nIndexCount ); + + int nVertexOffset = 0; + int nBatchStepCount = batch.m_BatchStep.Count(); + for ( int j = 0; j < nBatchStepCount; ++j ) + { + const BatchStep_t &step = batch.m_BatchStep[j]; + // FIXME: this will break if it ever calls into C_OP_RenderSprites::Render[TwoSequence]SpriteCard() + // (need to protect against that and/or split the meshBuilder batch to support that path here) + step.m_pRenderer->RenderUnsorted( step.m_pParticles, step.m_pContext, pRenderContext, + meshBuilder, nVertexOffset, step.m_nFirstParticle, step.m_nParticleCount ); + nVertexOffset += step.m_nVertCount; + } + + meshBuilder.End(); + pMesh->Draw(); + } + } + + ResetRenderCache( ); + + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->PopMatrix(); +} + + +void CParticleSystemMgr::TallyParticlesRendered( int nVertexCount, int nIndexCount ) +{ + m_nParticleIndexCount += nIndexCount; + m_nParticleVertexCount += nVertexCount; + + if ( m_nParticleVertexCount > MAX_PARTICLE_VERTS ) + { + m_bFrameWarningNeeded = true; + } +} + + + + + + +void IParticleSystemQuery::GetRandomPointsOnControllingObjectHitBox( + CParticleCollection *pParticles, + int nControlPointNumber, + int nNumPtsOut, + float flBBoxScale, + int nNumTrysToGetAPointInsideTheModel, + Vector *pPntsOut, + Vector vecDirectionalBias, + Vector *pHitBoxRelativeCoordOut, + int *pHitBoxIndexOut + ) +{ + for(int i=0; i < nNumPtsOut; i++) + { + pPntsOut[i]=pParticles->m_ControlPoints[nControlPointNumber].m_Position; + if ( pHitBoxRelativeCoordOut ) + pHitBoxRelativeCoordOut[i].Init(); + if ( pHitBoxIndexOut ) + pHitBoxIndexOut[i] = -1; + } +} + + + +void CParticleCollection::UpdateHitBoxInfo( int nControlPointNumber ) +{ + CModelHitBoxesInfo &hb = m_ControlPointHitBoxes[nControlPointNumber]; + + if ( hb.m_flLastUpdateTime == m_flCurTime ) + return; // up to date + + hb.m_flLastUpdateTime = m_flCurTime; + + // make sure space allocated + if ( ! hb.m_pHitBoxes ) + hb.m_pHitBoxes = new ModelHitBoxInfo_t[ MAXSTUDIOBONES ]; + if ( ! hb.m_pPrevBoxes ) + hb.m_pPrevBoxes = new ModelHitBoxInfo_t[ MAXSTUDIOBONES ]; + + // save current into prev + hb.m_nNumPrevHitBoxes = hb.m_nNumHitBoxes; + hb.m_flPrevLastUpdateTime = hb.m_flLastUpdateTime; + V_swap( hb.m_pHitBoxes, hb.m_pPrevBoxes ); + + // issue hitbox query + hb.m_nNumHitBoxes = g_pParticleSystemMgr->Query()->GetControllingObjectHitBoxInfo( + this, nControlPointNumber, MAXSTUDIOBONES, hb.m_pHitBoxes ); + +} diff --git a/particles/particles.vpc b/particles/particles.vpc new file mode 100644 index 0000000..adf68db --- /dev/null +++ b/particles/particles.vpc @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// PARTICLES.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR ".." +$Include "$SRCDIR\vpc_scripts\source_lib_base.vpc" + +$Project "particles" +{ + $Folder "Source Files" + { + $File "builtin_constraints.cpp" + $File "builtin_initializers.cpp" + $File "builtin_particle_emitters.cpp" + $File "builtin_particle_forces.cpp" + $File "addbuiltin_ops.cpp" + $File "builtin_particle_ops.cpp" + $File "builtin_particle_render_ops.cpp" + $File "particle_sort.cpp" + $File "particles.cpp" + $File "psheet.cpp" + } + + $Folder "Header Files" + { + $File "$SRCDIR\public\particles\particles.h" + $File "random_floats.h" + $File "particles_internal.h" + } +} diff --git a/particles/particles_internal.h b/particles/particles_internal.h new file mode 100644 index 0000000..7b7fc3b --- /dev/null +++ b/particles/particles_internal.h @@ -0,0 +1,52 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: sheet code for particles and other sprite functions +// +//===========================================================================// + +#ifndef PARTICLES_INTERNAL_H +#define PARTICLES_INTERNAL_H + +#ifdef _WIN32 +#pragma once +#endif + + +#include "tier1/UtlStringMap.h" +#include "tier1/utlbuffer.h" +#include "tier2/fileutils.h" + +#define MAX_WORLD_PLANAR_CONSTRAINTS ( 26 + 5 + 10 ) + +#define COLLISION_MODE_PER_PARTICLE_TRACE 0 +#define COLLISION_MODE_PER_FRAME_PLANESET 1 +#define COLLISION_MODE_INITIAL_TRACE_DOWN 2 +#define COLLISION_MODE_USE_NEAREST_TRACE 3 + +struct CWorldCollideContextData +{ + FourVectors m_TraceStartPnt[MAX_WORLD_PLANAR_CONSTRAINTS]; + FourVectors m_TraceEndPnt[MAX_WORLD_PLANAR_CONSTRAINTS]; + FourVectors m_PointOnPlane[MAX_WORLD_PLANAR_CONSTRAINTS]; + FourVectors m_PlaneNormal[MAX_WORLD_PLANAR_CONSTRAINTS]; + + int m_nActivePlanes; + int m_nNumFixedPlanes; + float m_flLastUpdateTime; + Vector m_vecLastUpdateOrigin; + bool m_bPlaneActive[MAX_WORLD_PLANAR_CONSTRAINTS]; + + void *operator new( size_t nSize ); + void *operator new( size_t nSize, int nBlockUse, const char *pFileName, int nLine ); + void operator delete(void *pData); + void operator delete( void* p, int nBlockUse, const char *pFileName, int nLine ); + + void SetBaseTrace( int nIndex, Vector const &rayStart, Vector const &traceDir, int nCollisionGroup, bool bKeepMisses ); + + void CalculatePlanes( CParticleCollection *pParticles, int nCollisionMode, int nCollisionGroupNumber, + Vector const *pCpOffset = NULL, float flMovementTolerance = 0. ); +}; + +#endif // PARTICLES_INTERNAL_H + + diff --git a/particles/psheet.cpp b/particles/psheet.cpp new file mode 100644 index 0000000..9579081 --- /dev/null +++ b/particles/psheet.cpp @@ -0,0 +1,146 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: sheet code for particles and other sprite functions +// +//===========================================================================// + +#include "psheet.h" +#include "tier1/UtlStringMap.h" +#include "tier1/utlbuffer.h" +#include "tier2/fileutils.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +CSheet::CSheet( void ) +{ + memset( m_pSamples, 0, sizeof( m_pSamples ) ); + memset( m_bClamp, 0, sizeof( m_bClamp ) ); +} + +CSheet::CSheet( CUtlBuffer &buf ) +{ + memset( m_pSamples, 0, sizeof( m_pSamples ) ); + memset( m_bClamp, 0, sizeof( m_bClamp ) ); + memset( m_bSequenceIsCopyOfAnotherSequence, 0, sizeof( m_bSequenceIsCopyOfAnotherSequence ) ); + + // lets read a sheet + buf.ActivateByteSwappingIfBigEndian(); + int nVersion = buf.GetInt(); // version# + int nNumCoordsPerFrame = (nVersion)?MAX_IMAGES_PER_FRAME_ON_DISK:1; + + int nNumSequences = buf.GetInt(); + + + while ( nNumSequences-- ) + { + int nSequenceNumber = buf.GetInt(); + if ( ( nSequenceNumber < 0 ) || (nSequenceNumber >= MAX_SEQUENCES ) ) + { + Warning("sequence number %d too high in sheet file!!!\n", nSequenceNumber); + return; + } + m_bClamp[ nSequenceNumber ] = ( buf.GetInt() != 0 ); + int nFrameCount = buf.GetInt(); + // Save off how many frames we have for this sequence + m_nNumFrames[ nSequenceNumber ] = nFrameCount; + bool bSingleFrameSequence = ( nFrameCount == 1 ); + + int nTimeSamples = bSingleFrameSequence ? 1 : SEQUENCE_SAMPLE_COUNT; + + m_pSamples[ nSequenceNumber ] = + new SheetSequenceSample_t[ nTimeSamples ]; + + int fTotalSequenceTime = buf.GetFloat(); + float InterpKnot[SEQUENCE_SAMPLE_COUNT]; + float InterpValue[SEQUENCE_SAMPLE_COUNT]; + SheetSequenceSample_t Samples[SEQUENCE_SAMPLE_COUNT]; + float fCurTime = 0.; + for( int nFrm = 0 ; nFrm < nFrameCount; nFrm++ ) + { + float fThisDuration = buf.GetFloat(); + InterpValue[ nFrm ] = nFrm; + InterpKnot [ nFrm ] = SEQUENCE_SAMPLE_COUNT*( fCurTime/ fTotalSequenceTime ); + SheetSequenceSample_t &seq = Samples[ nFrm ]; + seq.m_fBlendFactor = 0.0f; + for(int nImage = 0 ; nImage< nNumCoordsPerFrame; nImage++ ) + { + SequenceSampleTextureCoords_t &s=seq.m_TextureCoordData[nImage]; + s.m_fLeft_U0 = buf.GetFloat(); + s.m_fTop_V0 = buf.GetFloat(); + s.m_fRight_U0 = buf.GetFloat(); + s.m_fBottom_V0 = buf.GetFloat(); + } + if ( nNumCoordsPerFrame == 1 ) + seq.CopyFirstFrameToOthers(); + fCurTime += fThisDuration; + m_flFrameSpan[nSequenceNumber] = fCurTime; + } + // now, fill in the whole table + for( int nIdx = 0; nIdx < nTimeSamples; nIdx++ ) + { + float flIdxA, flIdxB, flInterp; + GetInterpolationData( InterpKnot, InterpValue, nFrameCount, + SEQUENCE_SAMPLE_COUNT, + nIdx, + ! ( m_bClamp[nSequenceNumber] ), + &flIdxA, &flIdxB, &flInterp ); + SheetSequenceSample_t sA = Samples[(int) flIdxA]; + SheetSequenceSample_t sB = Samples[(int) flIdxB]; + SheetSequenceSample_t &oseq = m_pSamples[nSequenceNumber][nIdx]; + + oseq.m_fBlendFactor = flInterp; + for(int nImage = 0 ; nImage< MAX_IMAGES_PER_FRAME_IN_MEMORY; nImage++ ) + { + SequenceSampleTextureCoords_t &src0=sA.m_TextureCoordData[nImage]; + SequenceSampleTextureCoords_t &src1=sB.m_TextureCoordData[nImage]; + SequenceSampleTextureCoords_t &o=oseq.m_TextureCoordData[nImage]; + o.m_fLeft_U0 = src0.m_fLeft_U0; + o.m_fTop_V0 = src0.m_fTop_V0; + o.m_fRight_U0 = src0.m_fRight_U0; + o.m_fBottom_V0 = src0.m_fBottom_V0; + o.m_fLeft_U1 = src1.m_fLeft_U0; + o.m_fTop_V1 = src1.m_fTop_V0; + o.m_fRight_U1 = src1.m_fRight_U0; + o.m_fBottom_V1 = src1.m_fBottom_V0; + } + } + } + // now, fill in all unseen sequences with copies of the first seen sequence to prevent crashes + // while editing + int nFirstSequence = -1; + for(int i=0 ; i m_References; + + SheetSequenceSample_t *m_pSamples[MAX_SEQUENCES]; + bool m_bClamp[MAX_SEQUENCES]; + bool m_bSequenceIsCopyOfAnotherSequence[MAX_SEQUENCES]; + int m_nNumFrames[MAX_SEQUENCES]; + float m_flFrameSpan[MAX_SEQUENCES]; + +}; + + +#endif // PSHEET_H + diff --git a/particles/random_floats.h b/particles/random_floats.h new file mode 100644 index 0000000..62da67d --- /dev/null +++ b/particles/random_floats.h @@ -0,0 +1,537 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: static random floats for deterministic random #s in particle system +// +// $Workfile: $ +// $NoKeywords: $ +//===========================================================================// +// +// **** DO NOT EDIT THIS FILE. GENERATED BY DATAGEN.PL **** +// +#ifndef RANDOM_FLOATS_H +#define RANDOM_FLOATS_H + +#ifdef _WIN32 +#pragma once +#endif + +ALIGN16 float s_pRandomFloats[]={ + 0.336914,0.671387,0.539307,0.165039,0.258301,0.146973,0.475342,0.993408, + 0.882080,0.733887,0.152588,0.192871,0.873291,0.637939,0.859131,0.836914, + 0.277344,0.026367,0.864502,0.327393,0.428711,0.684082,0.916992,0.935547, + 0.643311,0.082764,0.033447,0.086182,0.575684,0.121826,0.342285,0.419189, + 0.409424,0.177490,0.049072,0.252930,0.459717,0.561035,0.581543,0.470703, + 0.662842,0.936035,0.912354,0.965332,0.523682,0.661865,0.484131,0.030029, + 0.958984,0.370605,0.626709,0.736084,0.241943,0.307129,0.969482,0.269287, + 0.215820,0.995605,0.300781,0.984375,0.621338,0.785645,0.432373,0.895264, + 0.098389,0.857178,0.220947,0.981689,0.667969,0.898682,0.603027,0.588623, + 0.206055,0.536133,0.245361,0.999023,0.758545,0.152100,0.015869,0.546387, + 0.101563,0.920410,0.566650,0.793457,0.077148,0.675049,0.032715,0.645264, + 0.654053,0.536377,0.599121,0.300537,0.404053,0.115967,0.646484,0.162842, + 0.886230,0.412109,0.552490,0.632080,0.623291,0.846436,0.313965,0.505859, + 0.487305,0.644287,0.517578,0.892090,0.146240,0.218750,0.484619,0.904785, + 0.551758,0.809814,0.721191,0.871582,0.847656,0.062256,0.647949,0.708252, + 0.690186,0.593750,0.061768,0.406982,0.169434,0.874756,0.139893,0.792236, + 0.783691,0.855957,0.198975,0.706299,0.594971,0.739746,0.307373,0.123291, + 0.480713,0.027100,0.697266,0.534424,0.070068,0.641602,0.472900,0.598145, + 0.211670,0.977051,0.145264,0.311279,0.975098,0.904297,0.999268,0.725586, + 0.039307,0.669434,0.578369,0.285889,0.867188,0.982666,0.797119,0.755371, + 0.950195,0.383789,0.426270,0.337402,0.075439,0.841553,0.794678,0.074707, + 0.021240,0.627686,0.036377,0.921143,0.229248,0.208740,0.698730,0.622803, + 0.943848,0.673828,0.826416,0.107910,0.366943,0.352539,0.256592,0.930176, + 0.556885,0.378418,0.205566,0.286133,0.623047,0.986328,0.781494,0.974609, + 0.239014,0.883789,0.859375,0.884277,0.856445,0.521484,0.332764,0.489746, + 0.880615,0.553955,0.961182,0.360352,0.489990,0.175781,0.777832,0.020020, + 0.346436,0.250732,0.094482,0.900146,0.185547,0.317139,0.686768,0.517334, + 0.350342,0.774902,0.978027,0.819580,0.967773,0.811523,0.038574,0.791504, + 0.196045,0.543701,0.705566,0.586426,0.585449,0.016846,0.413086,0.583496, + 0.772461,0.728271,0.289307,0.482666,0.389404,0.302002,0.331055,0.759277, + 0.885742,0.745605,0.444092,0.341797,0.607422,0.436279,0.102783,0.874023, + 0.868408,0.662109,0.287354,0.918701,0.892334,0.685791,0.500244,0.433594, + 0.664795,0.734863,0.933350,0.078369,0.808105,0.790039,0.404541,0.005371, + 0.976318,0.800537,0.482178,0.472412,0.656494,0.835449,0.713135,0.629395, + 0.468506,0.038330,0.143555,0.375000,0.140381,0.427734,0.822754,0.267090, + 0.253662,0.683350,0.788818,0.531738,0.214600,0.877930,0.659668,0.640381, + 0.954346,0.227539,0.167969,0.491211,0.084473,0.272949,0.961670,0.065430, + 0.696045,0.625732,0.177734,0.782715,0.934814,0.385742,0.401611,0.018555, + 0.686279,0.749023,0.114014,0.106689,0.501709,0.471924,0.151855,0.088623, + 0.677734,0.425293,0.910645,0.483398,0.872314,0.782471,0.163330,0.574463, + 0.119385,0.809570,0.947510,0.197510,0.887207,0.577637,0.021484,0.020508, + 0.641846,0.321533,0.957520,0.893555,0.625488,0.424072,0.612793,0.003906, + 0.060059,0.388184,0.724365,0.195068,0.263184,0.919434,0.094971,0.550781, + 0.997559,0.763916,0.300293,0.530029,0.583984,0.767578,0.745361,0.691650, + 0.170410,0.762695,0.949463,0.349365,0.757324,0.302490,0.498047,0.397217, + 0.213379,0.019775,0.587402,0.562988,0.573975,0.360840,0.320313,0.258545, + 0.948486,0.186279,0.976563,0.190430,0.865723,0.162598,0.547607,0.339111, + 0.392090,0.273193,0.714844,0.797363,0.403564,0.449219,0.747314,0.844238, + 0.835205,0.110596,0.134033,0.214355,0.766113,0.109131,0.013184,0.875000, + 0.737061,0.320801,0.639893,0.744141,0.751465,0.127197,0.011963,0.353027, + 0.693359,0.440674,0.264160,0.780029,0.529785,0.800781,0.671143,0.332031, + 0.400635,0.981445,0.770264,0.435059,0.298340,0.437988,0.163574,0.271973, + 0.039551,0.043457,0.866943,0.009766,0.651123,0.846680,0.653076,0.125732, + 0.530762,0.868896,0.268799,0.958008,0.142578,0.461914,0.303955,0.010986, + 0.620850,0.522217,0.510986,0.338135,0.914551,0.411865,0.604004,0.706543, + 0.238525,0.510254,0.103271,0.632324,0.422119,0.440186,0.265137,0.277100, + 0.221924,0.201172,0.599365,0.244629,0.177002,0.293945,0.368652,0.649902, + 0.100586,0.622559,0.905762,0.209961,0.257324,0.249512,0.428467,0.369141, + 0.166016,0.635986,0.798340,0.940918,0.993896,0.803467,0.320068,0.929443, + 0.650146,0.058594,0.065918,0.609375,0.616943,0.379639,0.221191,0.200684, + 0.486084,0.437500,0.871338,0.795898,0.863037,0.700684,0.517090,0.821533, + 0.319092,0.397461,0.852295,0.864014,0.591797,0.514404,0.925049,0.715820, + 0.384766,0.734131,0.899170,0.703125,0.210938,0.783203,0.687012,0.850586, + 0.532471,0.025635,0.975342,0.852051,0.612305,0.910889,0.159668,0.407227, + 0.669678,0.096924,0.733398,0.304688,0.015625,0.372803,0.329590,0.858398, + 0.033203,0.830811,0.914307,0.022949,0.451416,0.106201,0.664551,0.895752, + 0.366455,0.230225,0.595459,0.296631,0.291992,0.375488,0.952393,0.052734, + 0.132813,0.549805,0.828125,0.065674,0.318848,0.031494,0.605713,0.534912, + 0.611328,0.259766,0.445068,0.660156,0.848633,0.055176,0.323730,0.122803, + 0.697510,0.097412,0.989014,0.348877,0.536865,0.442139,0.269043,0.762451, + 0.926270,0.118896,0.458740,0.496094,0.253906,0.084717,0.648926,0.754639, + 0.648438,0.488281,0.533691,0.048828,0.050781,0.847168,0.431152,0.638184, + 0.938232,0.092285,0.970215,0.037354,0.852539,0.586670,0.729248,0.227295, + 0.482910,0.051025,0.135254,0.082520,0.812500,0.391602,0.598389,0.605957, + 0.949707,0.464600,0.863770,0.024902,0.989502,0.937012,0.435547,0.565186, + 0.370850,0.801758,0.143311,0.354492,0.715088,0.423828,0.796387,0.384033, + 0.093018,0.813965,0.356201,0.535889,0.229492,0.553711,0.080811,0.711426, + 0.082275,0.713623,0.097168,0.847900,0.197754,0.057617,0.970703,0.282959, + 0.252686,0.082031,0.847412,0.274658,0.806641,0.027588,0.844727,0.748535, + 0.667480,0.200195,0.814941,0.912598,0.463379,0.304199,0.815918,0.481445, + 0.345947,0.936279,0.851318,0.796631,0.119141,0.328369,0.185303,0.911621, + 0.527100,0.342529,0.652100,0.435303,0.537354,0.051758,0.998535,0.597168, + 0.031006,0.056396,0.003174,0.702637,0.262207,0.433838,0.052002,0.776367, + 0.993652,0.836182,0.055908,0.694092,0.768311,0.073242,0.908203,0.548584, + 0.873535,0.205078,0.551514,0.726074,0.302979,0.136475,0.381348,0.702393, + 0.054932,0.796143,0.079346,0.049561,0.539795,0.731201,0.175049,0.570068, + 0.171631,0.899902,0.467041,0.623779,0.913086,0.386230,0.451904,0.878418, + 0.880371,0.316406,0.468994,0.145752,0.920166,0.162354,0.944092,0.816162, + 0.887695,0.802246,0.546631,0.803955,0.491455,0.064453,0.284424,0.153564, + 0.291260,0.730713,0.173584,0.942383,0.640137,0.480469,0.552979,0.413818, + 0.222656,0.364502,0.736816,0.351074,0.245605,0.183105,0.697754,0.308350, + 0.467285,0.333740,0.400391,0.301514,0.601074,0.874512,0.571289,0.994873, + 0.438721,0.865234,0.795410,0.476807,0.409912,0.651855,0.375244,0.139404, + 0.776611,0.713379,0.406494,0.359619,0.635742,0.261963,0.210449,0.669922, + 0.612061,0.363281,0.013916,0.504639,0.113770,0.814697,0.826660,0.016602, + 0.079834,0.668945,0.453125,0.591553,0.258789,0.345703,0.271240,0.198486, + 0.239258,0.975830,0.203613,0.974121,0.408691,0.775391,0.071777,0.503418, + 0.483887,0.223877,0.805420,0.813721,0.326660,0.327148,0.616455,0.615479, + 0.430176,0.153076,0.979736,0.611572,0.543457,0.095459,0.897461,0.406250, + 0.657471,0.108887,0.002930,0.959961,0.396973,0.901367,0.572998,0.056152, + 0.630371,0.762939,0.910156,0.965820,0.397705,0.171143,0.401367,0.453857, + 0.493896,0.837158,0.636719,0.596436,0.645752,0.894043,0.990479,0.885254, + 0.863525,0.650391,0.761963,0.947266,0.573730,0.941650,0.212891,0.805664, + 0.153809,0.957275,0.044678,0.743408,0.579590,0.240967,0.172363,0.056641, + 0.327637,0.787842,0.431885,0.652344,0.616699,0.629150,0.893311,0.691406, + 0.054199,0.840088,0.930664,0.708008,0.398193,0.988770,0.959717,0.666992, + 0.000244,0.696777,0.365967,0.547119,0.127930,0.141113,0.348633,0.531494, + 0.584961,0.398926,0.728516,0.709229,0.391113,0.265869,0.244141,0.972900, + 0.663086,0.258057,0.317871,0.740723,0.704834,0.138672,0.819092,0.802002, + 0.563232,0.072754,0.085938,0.868652,0.521240,0.206543,0.696533,0.420654, + 0.897705,0.783936,0.067627,0.686035,0.537598,0.660400,0.784180,0.292725, + 0.444336,0.128662,0.862793,0.260742,0.789551,0.820557,0.319824,0.045166, + 0.483154,0.533203,0.022705,0.249756,0.201660,0.124023,0.966064,0.117920, + 0.129395,0.241699,0.585205,0.481201,0.904053,0.627441,0.979004,0.041992, + 0.619141,0.525879,0.682373,0.497803,0.907471,0.303711,0.562012,0.495117, + 0.500488,0.674072,0.552734,0.249023,0.088867,0.293213,0.221680,0.457031, + 0.187988,0.950439,0.453369,0.014160,0.881348,0.050049,0.686523,0.476318, + 0.524170,0.552246,0.077393,0.621826,0.829834,0.030518,0.295654,0.215088, + 0.438965,0.271729,0.384277,0.580322,0.191895,0.096680,0.281982,0.984131, + 0.675537,0.638672,0.329834,0.426025,0.768799,0.484375,0.287598,0.694824, + 0.822510,0.351563,0.736328,0.905029,0.755127,0.661377,0.138916,0.236572, + 0.662354,0.299316,0.906250,0.771240,0.680420,0.039063,0.029297,0.246338, + 0.876221,0.506348,0.474365,0.476074,0.289063,0.514648,0.751221,0.909424, + 0.921631,0.121094,0.628174,0.171875,0.881836,0.819824,0.002441,0.955811, + 0.407715,0.288818,0.690918,0.193359,0.090088,0.461670,0.057861,0.597412, + 0.247559,0.479492,0.222900,0.106934,0.147705,0.750977,0.322754,0.435791, + 0.672852,0.795654,0.036621,0.125488,0.036865,0.224854,0.012695,0.023438, + 0.191650,0.816650,0.381592,0.448975,0.112305,0.944336,0.017822,0.645020, + 0.113037,0.881104,0.157715,0.692383,0.331299,0.177979,0.771729,0.223145, + 0.854004,0.698242,0.821777,0.341553,0.638428,0.161133,0.040039,0.592529, + 0.679932,0.558350,0.363770,0.423584,0.710938,0.219482,0.738037,0.339844, + 0.433105,0.104736,0.653320,0.688965,0.087402,0.593994,0.387451,0.111328, + 0.254639,0.346680,0.615234,0.588379,0.273682,0.141602,0.418701,0.294678, + 0.569336,0.442383,0.598877,0.075684,0.677979,0.761230,0.919189,0.861572, + 0.320557,0.790771,0.064697,0.144531,0.934570,0.721436,0.684814,0.251465, + 0.762207,0.767090,0.528076,0.385498,0.269531,0.879639,0.576416,0.419434, + 0.875732,0.672607,0.823242,0.785156,0.877686,0.432861,0.416260,0.066406, + 0.233643,0.703857,0.719482,0.352783,0.837646,0.615967,0.593018,0.137451, + 0.199951,0.712891,0.885498,0.923828,0.777588,0.669189,0.546875,0.400146, + 0.374023,0.455322,0.577881,0.328125,0.954590,0.680908,0.836426,0.699219, + 0.803711,0.350586,0.259277,0.878174,0.798096,0.904541,0.314453,0.820313, + 0.949951,0.125000,0.782227,0.008057,0.303467,0.976074,0.189941,0.216064, + 0.069092,0.070557,0.680176,0.983643,0.182861,0.656250,0.133545,0.389893, + 0.277588,0.229004,0.148438,0.193115,0.562500,0.835938,0.172607,0.550293, + 0.290527,0.146729,0.334229,0.334717,0.707031,0.023193,0.969727,0.593262, + 0.708496,0.884766,0.329346,0.362305,0.305176,0.072021,0.591309,0.135010, + 0.434570,0.394287,0.239746,0.532715,0.751709,0.760986,0.083740,0.763184, + 0.765381,0.230713,0.312988,0.819336,0.338623,0.619385,0.152832,0.971436, + 0.656982,0.585693,0.089844,0.513184,0.732178,0.548828,0.980469,0.022461, + 0.340820,0.411377,0.829346,0.644531,0.804443,0.086914,0.468018,0.368164, + 0.243408,0.824463,0.350830,0.416748,0.025879,0.156494,0.989990,0.129150, + 0.814453,0.574707,0.790527,0.296387,0.510498,0.990234,0.480957,0.565430, + 0.550537,0.461182,0.359863,0.966553,0.316650,0.187500,0.032959,0.460693, + 0.692871,0.294434,0.857666,0.217041,0.128174,0.207764,0.309814,0.846924, + 0.531982,0.047607,0.555664,0.198730,0.049316,0.576904,0.709473,0.518799, + 0.069336,0.601563,0.228516,0.048096,0.479980,0.108643,0.080566,0.386719, + 0.535400,0.465088,0.472168,0.105957,0.310059,0.489014,0.987549,0.406006, + 0.673096,0.366211,0.681885,0.509766,0.600342,0.506592,0.558594,0.875488, + 0.413574,0.083984,0.015137,0.559082,0.093750,0.805176,0.933838,0.775635, + 0.826172,0.451660,0.779785,0.158691,0.346191,0.601318,0.407471,0.545410, + 0.712646,0.845947,0.741211,0.337891,0.393799,0.407959,0.095703,0.817139, + 0.198242,0.818359,0.272461,0.051270,0.350098,0.445557,0.485352,0.168945, + 0.855469,0.427246,0.189453,0.646973,0.648193,0.717041,0.737305,0.222168, + 0.704346,0.235352,0.126221,0.617676,0.108154,0.287842,0.237549,0.544678, + 0.761719,0.832764,0.781250,0.501221,0.168213,0.262451,0.325684,0.396240, + 0.970459,0.631348,0.134277,0.337158,0.006592,0.217529,0.333496,0.698486, + 0.810059,0.391846,0.361572,0.265625,0.919922,0.408447,0.299561,0.524414, + 0.076660,0.233887,0.147217,0.247803,0.720459,0.655273,0.178711,0.642822, + 0.076904,0.408936,0.113281,0.201416,0.730957,0.200439,0.885010,0.100342, + 0.369385,0.395264,0.542480,0.940430,0.684326,0.666504,0.228760,0.699463, + 0.127441,0.212646,0.835693,0.029785,0.656006,0.354736,0.278564,0.339600, + 0.549561,0.149658,0.593506,0.298096,0.492432,0.447754,0.264648,0.634766, + 0.166992,0.515137,0.382568,0.142822,0.783447,0.953125,0.439697,0.267822, + 0.121338,0.735840,0.087891,0.507568,0.595703,0.655518,0.610107,0.541748, + 0.645508,0.057373,0.624512,0.191406,0.832031,0.702148,0.330566,0.087646, + 0.242188,0.099854,0.307861,0.430908,0.684570,0.939453,0.031250,0.924805, + 0.462646,0.120361,0.545654,0.789063,0.719971,0.072266,0.353271,0.996826, + 0.627930,0.093994,0.920654,0.058105,0.430664,0.403076,0.448486,0.589111, + 0.524658,0.148682,0.671875,0.470947,0.851807,0.367188,0.749268,0.746582, + 0.429199,0.466064,0.519531,0.645996,0.125977,0.549072,0.547363,0.294189, + 0.254883,0.634277,0.663330,0.528320,0.178955,0.730225,0.063232,0.979248, + 0.733154,0.543945,0.668213,0.228027,0.896729,0.167480,0.551270,0.174316, + 0.271484,0.519043,0.310791,0.664307,0.984619,0.534668,0.541992,0.604492, + 0.946045,0.870117,0.161865,0.660889,0.587646,0.770996,0.573486,0.035156, + 0.895996,0.812988,0.452881,0.854248,0.404297,0.371094,0.561768,0.132324, + 0.009033,0.954834,0.522461,0.134521,0.538574,0.145996,0.968994,0.620117, + 0.582275,0.609863,0.325195,0.620361,0.070801,0.633301,0.116455,0.236084, + 0.485840,0.519287,0.264404,0.008301,0.632568,0.091553,0.142334,0.050537, + 0.358154,0.126953,0.572021,0.471436,0.017334,0.754883,0.735596,0.313232, + 0.780518,0.903809,0.314209,0.324951,0.188721,0.586182,0.421875,0.565918, + 0.179443,0.723389,0.242676,0.592773,0.818604,0.501465,0.092529,0.126709, + 0.250244,0.787354,0.104248,0.758301,0.395508,0.804932,0.090820,0.828857, + 0.014893,0.899414,0.716797,0.942871,0.839111,0.493164,0.625244,0.867432, + 0.504150,0.718750,0.725098,0.365479,0.256104,0.653564,0.355469,0.808838, + 0.933594,0.525146,0.808350,0.454346,0.775879,0.184326,0.052979,0.658936, + 0.243896,0.799072,0.603516,0.854980,0.947021,0.004639,0.503174,0.003418, + 0.682617,0.492920,0.887451,0.924316,0.475098,0.766602,0.917480,0.964111, + 0.971191,0.145020,0.071289,0.722900,0.144775,0.335205,0.613770,0.643066, + 0.186035,0.081299,0.393311,0.123047,0.261719,0.196533,0.433350,0.263916, + 0.935303,0.278076,0.732422,0.328613,0.937988,0.706787,0.467529,0.203857, + 0.279541,0.957764,0.600586,0.226074,0.213867,0.563721,0.132568,0.802490, + 0.318359,0.035400,0.606201,0.721680,0.735352,0.917725,0.045898,0.173828, + 0.486328,0.241211,0.243164,0.181152,0.637695,0.670166,0.964844,0.367676, + 0.826904,0.903076,0.457764,0.041260,0.161377,0.955322,0.752197,0.769043, + 0.163818,0.117188,0.971924,0.611084,0.560303,0.319580,0.194824,0.325928, + 0.477051,0.264893,0.043213,0.002686,0.690430,0.896240,0.409180,0.765137, + 0.741455,0.689941,0.860107,0.704590,0.983887,0.869629,0.452637,0.376953, + 0.839355,0.951416,0.378906,0.042969,0.287109,0.064209,0.195801,0.494385, + 0.720947,0.139648,0.570313,0.250000,0.181396,0.461426,0.921875,0.939209, + 0.690674,0.393555,0.693115,0.614502,0.322266,0.720215,0.636475,0.840820, + 0.958496,0.931641,0.063965,0.770020,0.298828,0.606445,0.886719,0.506104, + 0.958740,0.525635,0.297119,0.567871,0.358643,0.365234,0.187012,0.570801, + 0.116211,0.786621,0.336182,0.388428,0.913330,0.494141,0.696289,0.848145, + 0.699951,0.596680,0.276123,0.015381,0.685303,0.677490,0.463623,0.866699, + 0.283447,0.909912,0.215332,0.062012,0.495361,0.307617,0.465820,0.005127, + 0.937256,0.606934,0.465332,0.379150,0.685547,0.111084,0.753662,0.516113, + 0.412354,0.058350,0.553223,0.181885,0.963867,0.639648,0.502930,0.018799, + 0.588867,0.974365,0.657227,0.122559,0.773926,0.526367,0.243652,0.254150, + 0.618408,0.589355,0.657959,0.209229,0.670898,0.621582,0.523926,0.572510, + 0.053223,0.093262,0.187744,0.291504,0.083496,0.229980,0.109375,0.340332, + 0.496582,0.659424,0.626221,0.183350,0.059814,0.711914,0.526611,0.425781, + 0.995361,0.784424,0.170654,0.275146,0.402344,0.972656,0.624268,0.923584, + 0.744385,0.902100,0.361816,0.682861,0.971680,0.441895,0.186768,0.144043, + 0.801270,0.853760,0.099121,0.584717,0.443604,0.423096,0.855225,0.976807, + 0.326904,0.614014,0.337646,0.821289,0.109863,0.405762,0.000732,0.157471, + 0.377441,0.987793,0.766846,0.059570,0.434814,0.186523,0.332275,0.214844, + 0.072510,0.640625,0.280029,0.105469,0.280762,0.946533,0.845703,0.028320, + 0.044922,0.603760,0.059082,0.255371,0.143799,0.678711,0.182129,0.085205, + 0.915771,0.610352,0.457275,0.617188,0.060547,0.774658,0.829590,0.854492, + 0.313721,0.592041,0.156006,0.469482,0.743896,0.750732,0.004150,0.352051, + 0.853271,0.313477,0.456543,0.841797,0.007080,0.389648,0.849365,0.942627, + 0.561279,0.391357,0.214111,0.164551,0.554199,0.246582,0.012939,0.364746, + 0.104980,0.046387,0.002197,0.420410,0.802979,0.156738,0.028076,0.115479, + 0.405029,0.708984,0.782959,0.851074,0.748291,0.088135,0.681152,0.099365, + 0.624756,0.338379,0.642090,0.221436,0.997314,0.548340,0.259521,0.464111, + 0.016113,0.793945,0.773193,0.930908,0.316895,0.522705,0.755615,0.361328, + 0.923096,0.323242,0.487061,0.474854,0.800293,0.025391,0.417236,0.895020, + 0.700195,0.508789,0.031982,0.467773,0.890137,0.450684,0.710693,0.204834, + 0.724121,0.707764,0.425537,0.233154,0.716064,0.668701,0.255615,0.388672, + 0.893799,0.224609,0.978516,0.195557,0.145508,0.499268,0.890625,0.495850, + 0.362793,0.900879,0.902344,0.882324,0.811279,0.579346,0.128418,0.126465, + 0.283936,0.406738,0.588135,0.281738,0.358887,0.946289,0.641357,0.616211, + 0.747559,0.651611,0.872070,0.858887,0.834717,0.694336,0.005615,0.744873, + 0.836670,0.643799,0.602539,0.020996,0.107422,0.055664,0.961426,0.900391, + 0.867676,0.133057,0.995850,0.737549,0.807617,0.289795,0.354004,0.972168, + 0.139160,0.582520,0.497314,0.514893,0.759033,0.778564,0.128906,0.862549, + 0.600830,0.070313,0.966797,0.097900,0.252197,0.758789,0.529541,0.840576, + 0.373291,0.268066,0.728760,0.376221,0.394775,0.434082,0.840332,0.729004, + 0.759521,0.908691,0.884521,0.504883,0.460449,0.288086,0.983398,0.213623, + 0.816406,0.716553,0.629883,0.414307,0.158936,0.828369,0.008545,0.780762, + 0.364014,0.204102,0.465576,0.675781,0.666260,0.503906,0.299072,0.171387, + 0.511475,0.072998,0.809326,0.248291,0.182373,0.853516,0.364258,0.466797, + 0.399902,0.001465,0.722412,0.830566,0.115234,0.740967,0.566406,0.068115, + 0.140869,0.107178,0.242432,0.290039,0.079590,0.591064,0.620605,0.515381, + 0.229736,0.770752,0.707275,0.455811,0.114502,0.485596,0.628418,0.590088, + 0.175293,0.289551,0.077637,0.787109,0.042480,0.357666,0.790283,0.512451, + 0.447998,0.825195,0.724609,0.893066,0.769287,0.739014,0.101318,0.152344, + 0.096436,0.752441,0.494873,0.020752,0.297852,0.036133,0.907227,0.477295, + 0.869385,0.742920,0.952148,0.714600,0.053711,0.687744,0.660645,0.633545, + 0.439453,0.888428,0.908936,0.443848,0.030273,0.885986,0.284912,0.238281, + 0.933105,0.343750,0.963623,0.194580,0.996338,0.473877,0.545166,0.931885, + 0.399658,0.871826,0.398438,0.410400,0.344482,0.921387,0.850830,0.944580, + 0.250977,0.150879,0.834229,0.267578,0.734375,0.939697,0.913574,0.636230, + 0.283691,0.948730,0.306885,0.557861,0.764648,0.253418,0.582764,0.505615, + 0.340088,0.261230,0.727783,0.016357,0.658447,0.559326,0.102051,0.714111, + 0.535645,0.650635,0.622314,0.383545,0.916748,0.955566,0.116943,0.018311, + 0.750244,0.208252,0.773438,0.281006,0.095215,0.928467,0.426514,0.177246, + 0.219727,0.367920,0.495605,0.443115,0.380127,0.175537,0.452148,0.641113, + 0.786133,0.602295,0.784668,0.034912,0.061523,0.962891,0.715332,0.580078, + 0.363525,0.533936,0.009277,0.740479,0.285156,0.402832,0.821045,0.441406, + 0.568848,0.336670,0.185791,0.211182,0.218994,0.492676,0.205811,0.655029, + 0.784912,0.676270,0.709717,0.335449,0.081787,0.529053,0.794434,0.301270, + 0.449951,0.929932,0.232178,0.717773,0.548096,0.644775,0.590820,0.774170, + 0.458252,0.560059,0.117432,0.007324,0.630859,0.096191,0.182617,0.905273, + 0.848389,0.587158,0.699707,0.390137,0.618896,0.597656,0.812744,0.998291, + 0.590576,0.991699,0.838379,0.331543,0.166504,0.993164,0.525391,0.811768, + 0.629639,0.312744,0.285400,0.608643,0.512939,0.621094,0.953369,0.965576, + 0.223633,0.295410,0.133301,0.267334,0.678223,0.439941,0.010010,0.693604, + 0.619629,0.306641,0.475830,0.952637,0.074951,0.218262,0.873047,0.469238, + 0.236328,0.791992,0.310303,0.355957,0.450928,0.274170,0.509033,0.421143, + 0.683105,0.839600,0.466309,0.986084,0.516602,0.691895,0.105713,0.724854, + 0.695068,0.026123,0.579834,0.617432,0.107666,0.208984,0.496338,0.673584, + 0.448242,0.940674,0.999756,0.384521,0.053955,0.807373,0.165283,0.906006, + 0.838623,0.844482,0.335693,0.483643,0.133789,0.437012,0.778809,0.544434, + 0.755859,0.508301,0.712402,0.705322,0.634033,0.129883,0.013672,0.377686, + 0.661133,0.456787,0.888672,0.947998,0.043701,0.356445,0.366699,0.470215, + 0.176270,0.639160,0.239502,0.557373,0.342773,0.968506,0.185059,0.518311, + 0.378174,0.279053,0.663818,0.673340,0.001221,0.437744,0.188232,0.317383, + 0.203369,0.252441,0.226318,0.188477,0.160400,0.703613,0.265381,0.940186, + 0.833252,0.636963,0.429932,0.638916,0.208008,0.968262,0.343262,0.130127, + 0.650879,0.563965,0.689697,0.044434,0.387207,0.447021,0.773682,0.151611, + 0.463867,0.210693,0.889893,0.674316,0.261475,0.927734,0.903320,0.124268, + 0.713867,0.234619,0.992432,0.119629,0.606689,0.183838,0.555176,0.266113, + 0.618652,0.402100,0.204590,0.083008,0.995117,0.226563,0.417725,0.633789, + 0.581299,0.906494,0.948975,0.561523,0.679443,0.768555,0.095947,0.667725, + 0.376709,0.075928,0.827637,0.118164,0.502197,0.057129,0.871094,0.602051, + 0.891113,0.206299,0.890381,0.581787,0.353760,0.371338,0.653809,0.834473, + 0.917236,0.856201,0.939941,0.273926,0.834961,0.322021,0.382080,0.990967, + 0.281250,0.564453,0.633057,0.001709,0.614990,0.502686,0.838867,0.219238, + 0.266846,0.355225,0.062500,0.075195,0.703369,0.071045,0.907715,0.260254, + 0.160645,0.216553,0.863281,0.277832,0.623535,0.883301,0.099609,0.197998, + 0.000977,0.590332,0.488037,0.259033,0.918213,0.427979,0.599854,0.263428, + 0.913818,0.365723,0.327881,0.912842,0.837891,0.815186,0.255127,0.764893, + 0.760254,0.093506,0.739990,0.830322,0.067383,0.138428,0.535156,0.680664, + 0.103516,0.817871,0.200928,0.929688,0.248535,0.180664,0.781982,0.966309, + 0.860840,0.469727,0.576660,0.436035,0.167236,0.975586,0.450439,0.027832, + 0.897217,0.679688,0.666748,0.060303,0.131592,0.400879,0.125244,0.142090, + 0.706055,0.402588,0.963379,0.711182,0.831299,0.697998,0.898193,0.532959, + 0.530273,0.356689,0.244873,0.837402,0.300049,0.440918,0.157227,0.911377, + 0.625000,0.151367,0.917969,0.962646,0.179932,0.471191,0.286377,0.475586, + 0.034668,0.748779,0.208496,0.124512,0.471680,0.742432,0.777100,0.812012, + 0.991943,0.344727,0.822021,0.729736,0.559570,0.276367,0.906982,0.449707, + 0.066650,0.383301,0.769775,0.416504,0.654297,0.988525,0.358398,0.538086, + 0.401855,0.385986,0.225586,0.698975,0.909180,0.662598,0.061035,0.098145, + 0.136230,0.244385,0.542969,0.827881,0.978271,0.723145,0.922607,0.008789, + 0.571533,0.491699,0.498535,0.687256,0.884033,0.311768,0.042236,0.958252, + 0.604980,0.237061,0.122314,0.377930,0.575195,0.727295,0.950684,0.608398, + 0.231445,0.091064,0.630127,0.347168,0.973389,0.123779,0.817627,0.278320, + 0.150391,0.765869,0.368408,0.793701,0.615723,0.012451,0.956299,0.749512, + 0.509521,0.529297,0.649170,0.291016,0.956543,0.332520,0.513672,0.916504, + 0.012207,0.374512,0.932129,0.418945,0.607178,0.347656,0.825439,0.392578, + 0.667236,0.777344,0.123535,0.357422,0.967041,0.439209,0.248047,0.446289, + 0.218506,0.445801,0.135742,0.905518,0.915283,0.515869,0.063477,0.740234, + 0.910400,0.462158,0.614746,0.996582,0.250488,0.368896,0.555420,0.514160, + 0.102539,0.876953,0.464355,0.315186,0.333984,0.114258,0.110352,0.527832, + 0.296143,0.786377,0.605469,0.331787,0.377197,0.085449,0.549316,0.796875, + 0.110840,0.204346,0.419678,0.105225,0.521973,0.772705,0.670654,0.843750, + 0.830078,0.635254,0.280518,0.886963,0.293701,0.473389,0.507813,0.775146, + 0.813232,0.986572,0.042725,0.011475,0.134766,0.199463,0.906738,0.256836, + 0.044189,0.419922,0.815674,0.822998,0.977783,0.760498,0.541016,0.895508, + 0.681641,0.528564,0.632813,0.813477,0.513428,0.373535,0.305664,0.299805, + 0.659912,0.460205,0.047363,0.333252,0.372314,0.073486,0.505127,0.771484, + 0.336426,0.997803,0.158447,0.960938,0.347900,0.449463,0.502441,0.594238, + 0.132080,0.749756,0.941895,0.005859,0.304443,0.149902,0.564209,0.968018, + 0.371826,0.199219,0.260498,0.609619,0.089355,0.019287,0.359131,0.413330, + 0.817383,0.322510,0.806152,0.176025,0.041748,0.555908,0.950928,0.207520, + 0.898926,0.308594,0.367432,0.799805,0.792969,0.842041,0.742188,0.927979, + 0.952881,0.370361,0.534180,0.683594,0.380859,0.116699,0.827148,0.665771, + 0.292480,0.270508,0.676514,0.230957,0.474609,0.600098,0.428955,0.901855, + 0.199707,0.315674,0.349854,0.930420,0.811035,0.077881,0.064941,0.527344, + 0.427002,0.459961,0.308105,0.120605,0.468750,0.225830,0.944824,0.010498, + 0.718994,0.997070,0.726563,0.715576,0.493652,0.894287,0.338867,0.251221, + 0.789795,0.197021,0.114990,0.603271,0.213135,0.357178,0.902832,0.658691, + 0.459229,0.224121,0.687988,0.595947,0.862061,0.085693,0.092041,0.360107, + 0.347412,0.656738,0.379395,0.218018,0.753906,0.170166,0.390381,0.455566, + 0.858154,0.831787,0.779297,0.760010,0.027344,0.553467,0.328857,0.695801, + 0.572266,0.443359,0.500977,0.189209,0.424316,0.178467,0.094238,0.013428, + 0.792480,0.181641,0.998047,0.501953,0.383057,0.079102,0.721924,0.122070, + 0.870361,0.532227,0.424561,0.674805,0.192139,0.482422,0.306152,0.756836, + 0.493408,0.843506,0.765625,0.710449,0.321777,0.676025,0.164307,0.567627, + 0.081543,0.490479,0.571045,0.943115,0.161621,0.330322,0.537842,0.114746, + 0.450195,0.006836,0.392334,0.053467,0.598633,0.977539,0.111572,0.098633, + 0.511963,0.069580,0.387695,0.546143,0.941162,0.727051,0.490234,0.861084, + 0.928955,0.520508,0.115723,0.649658,0.642578,0.295166,0.965088,0.918457, + 0.918945,0.163086,0.312012,0.462891,0.447510,0.420166,0.665527,0.211914, + 0.520996,0.846191,0.473633,0.778320,0.736572,0.091797,0.988281,0.436523, + 0.040527,0.717529,0.318604,0.432617,0.011230,0.614258,0.810791,0.648682, + 0.569580,0.324707,0.750000,0.947754,0.155273,0.788086,0.010742,0.249268, + 0.144287,0.349609,0.520752,0.729980,0.806396,0.059326,0.303223,0.067139, + 0.421387,0.843994,0.637207,0.858643,0.814209,0.554932,0.251709,0.730469, + 0.109619,0.630615,0.379883,0.456055,0.274902,0.349121,0.580811,0.723633, + 0.018066,0.541260,0.387939,0.039795,0.040771,0.960205,0.458984,0.343994, + 0.141846,0.231934,0.017090,0.791748,0.931152,0.797852,0.731934,0.694580, + 0.691162,0.276855,0.785889,0.745850,0.942139,0.487793,0.228271,0.807861, + 0.023926,0.889160,0.140625,0.232666,0.110107,0.315918,0.372070,0.582031, + 0.121582,0.180176,0.386475,0.058838,0.038086,0.731445,0.202637,0.574951, + 0.446533,0.050293,0.542236,0.227051,0.194336,0.850098,0.857422,0.937744, + 0.886475,0.232422,0.542725,0.316162,0.880859,0.797607,0.575439,0.953613, + 0.066895,0.657715,0.753174,0.283203,0.705811,0.541504,0.220703,0.701172, + 0.454102,0.032471,0.452393,0.409668,0.926514,0.903564,0.263672,0.785400, + 0.422363,0.841064,0.410889,0.613281,0.004883,0.426758,0.106445,0.738770, + 0.291748,0.508545,0.578125,0.565674,0.480225,0.581055,0.415527,0.849854, + 0.104004,0.184814,0.418213,0.628662,0.324219,0.348145,0.831543,0.635010, + 0.986816,0.583008,0.124756,0.710205,0.382813,0.545898,0.727539,0.890869, + 0.869141,0.260986,0.170898,0.654785,0.801025,0.429443,0.442871,0.245117, + 0.756104,0.772949,0.577393,0.000488,0.631592,0.659180,0.048584,0.956787, + 0.850342,0.153320,0.627197,0.026611,0.025146,0.522949,0.928711,0.507324, + 0.028809,0.518066,0.512207,0.031738,0.945068,0.369873,0.692139,0.100830, + 0.752686,0.190674,0.510010,0.294922,0.741943,0.478271,0.757568,0.953857, + 0.232910,0.000000,0.746094,0.831055,0.915039,0.738525,0.275391,0.069824, + 0.354980,0.799561,0.611816,0.363037,0.101807,0.423340,0.934082,0.284668, + 0.859863,0.351807,0.652588,0.596191,0.519775,0.286865,0.451172,0.962158, + 0.612549,0.587891,0.887939,0.431641,0.169922,0.302246,0.117676,0.725342, + 0.049805,0.869873,0.127686,0.677246,0.429688,0.458008,0.172852,0.804688, + 0.308838,0.248779,0.491943,0.061279,0.037842,0.864746,0.326172,0.520264, + 0.925781,0.268311,0.412598,0.414551,0.352295,0.701904,0.866455,0.574219, + 0.074463,0.718018,0.375732,0.704102,0.592285,0.397949,0.239990,0.341309, + 0.768066,0.019531,0.174805,0.945313,0.290283,0.865967,0.877441,0.029541, + 0.478516,0.389160,0.827393,0.982910,0.868164,0.608154,0.180420,0.103760, + 0.257813,0.103027,0.902588,0.670410,0.100098,0.538818,0.045654,0.899658, + 0.405518,0.220215,0.381836,0.187256,0.321045,0.987061,0.411133,0.761475, + 0.150146,0.809082,0.440430,0.454590,0.155762,0.800049,0.666016,0.084229, + 0.810547,0.257080,0.278809,0.410645,0.949219,0.969971,0.948242,0.943604, + 0.298584,0.092773,0.370117,0.506836,0.499023,0.237793,0.739258,0.168701, + 0.510742,0.900635,0.460938,0.908447,0.539063,0.531006,0.380615,0.230469, + 0.159912,0.014648,0.823730,0.931396,0.824951,0.166748,0.324463,0.517822, + 0.536621,0.490723,0.279785,0.985352,0.165527,0.586914,0.226807,0.326416, + 0.604248,0.119873,0.148926,0.026855,0.689453,0.282471,0.631836,0.235596, + 0.828613,0.225342,0.394531,0.877197,0.901123,0.169678,0.399170,0.646729, + 0.737793,0.021729,0.135498,0.011719,0.911133,0.554688,0.810303,0.954102, + 0.339355,0.605225,0.361084,0.872803,0.360596,0.500000,0.159180,0.162109, + 0.108398,0.478760,0.227783,0.343018,0.486816,0.916016,0.861816,0.938965, + 0.174561,0.538330,0.078125,0.763428,0.757080,0.071533,0.457520,0.665039, + 0.194092,0.567383,0.270752,0.859619,0.791260,0.617920,0.888916,0.165771, + 0.202881,0.515625,0.486572,0.746826,0.470459,0.412842,0.714355,0.017578, + 0.396484,0.312256,0.311035,0.458496,0.417480,0.922363,0.646240,0.889648, + 0.607666,0.946777,0.321289,0.445313,0.479004,0.825684,0.272217,0.301758, + 0.274414,0.212402,0.503662,0.484863,0.364990,0.112061,0.302734,0.097656, + 0.499512,0.557617,0.742676,0.154541,0.280273,0.595215,0.689209,0.234375, + 0.247070,0.839844,0.215576,0.362061,0.853027,0.891846,0.262695,0.424805, + 0.193604,0.856689,0.487549,0.190186,0.881592,0.155029,0.076172,0.201904, + 0.712158,0.296875,0.314697,0.223389,0.951904,0.416992,0.060791,0.310547, + 0.981201,0.702881,0.540283,0.385254,0.297607,0.547852,0.168457,0.184082, + 0.870850,0.178223,0.685059,0.268555,0.088379,0.922852,0.418457,0.340576, + 0.179688,0.808594,0.041504,0.380371,0.874268,0.131104,0.855713,0.934326, + 0.747803,0.820801,0.101074,0.920898,0.774414,0.963135,0.732910,0.746338, + 0.728027,0.540039,0.207031,0.972412,0.983154,0.589844,0.589600,0.664063, + 0.919678,0.594482,0.174072,0.860596,0.551025,0.479736,0.889404,0.994629, + 0.562256,0.188965,0.189697,0.007568,0.430420,0.780273,0.038818,0.148193, + 0.743164,0.579102,0.404785,0.354248,0.150635,0.362549,0.982178,0.345215, + 0.619873,0.078857,0.212158,0.720703,0.233398,0.257568,0.697021,0.678955, + 0.748047,0.915527,0.497559,0.505371,0.160889,0.240479,0.718262,0.992676, + 0.021973,0.052490,0.833740,0.851563,0.138184,0.427490,0.104492,0.644043, + 0.584229,0.001953,0.991211,0.489502,0.932373,0.511719,0.862305,0.309570, + 0.068604,0.843018,0.959229,0.046875,0.823486,0.754395,0.560547,0.672363, + 0.028564,0.073730,0.346924,0.860352,0.731689,0.596924,0.539551,0.982422, + 0.945801,0.679199,0.481934,0.348389,0.477539,0.991455,0.794922,0.663574, + 0.490967,0.812256,0.476563,0.047119,0.892578,0.888184,0.793213,0.033691, + 0.312500,0.523193,0.883057,0.334473,0.492188,0.006348,0.990723,0.084961, + 0.757813,0.497070,0.172119,0.202148,0.709961,0.624023,0.275635,0.432129, + 0.752930,0.974854,0.438232,0.164063,0.388916,0.960693,0.878662,0.802734, + 0.192383,0.824707,0.977295,0.309082,0.513916,0.597900,0.305908,0.390869, + 0.447266,0.357910,0.754150,0.120117,0.203125,0.113525,0.089600,0.688477, + 0.335938,0.537109,0.572754,0.091309,0.098877,0.235107,0.956055,0.833008, + 0.196777,0.010254,0.955078,0.080322,0.390625,0.668457,0.701416,0.643555, + 0.601807,0.772217,0.035645,0.292236,0.196289,0.375977,0.967529,0.398682, + 0.732666,0.225098,0.393066,0.682129,0.938721,0.722168,0.907959,0.719727, + 0.681396,0.980957,0.594727,0.454834,0.531250,0.222412,0.717285,0.845459, + 0.892822,0.642334,0.479248,0.936523,0.083252,0.879395,0.856934,0.422852, + 0.442627,0.156250,0.849609,0.076416,0.550049,0.864990,0.926025,0.568115, + 0.989258,0.708740,0.578613,0.896484,0.805908,0.941406,0.778076,0.745117, + 0.688232,0.416016,0.245850,0.652832,0.169189,0.571777,0.459473,0.024170, + 0.879883,0.861328,0.319336,0.146484,0.700928,0.240234,0.154297,0.197266, + 0.472656,0.651367,0.716309,0.854736,0.544189,0.052246,0.981934,0.372559, + 0.266357,0.272705,0.035889,0.290771,0.741699,0.359375,0.566162,0.293457, + 0.431396,0.167725,0.970947,0.266602,0.875977,0.425049,0.743652,0.935059, + 0.173096,0.634521,0.677002,0.927490,0.988037,0.112793,0.494629,0.707520, + 0.086670,0.089111,0.943359,0.613037,0.776123,0.832275,0.045410,0.824219, + 0.062744,0.415771,0.206787,0.569824,0.507080,0.473145,0.401123,0.438477, + 0.345459,0.217773,0.033936,0.865479,0.376465,0.676758,0.330078,0.973145, + 0.516357,0.395020,0.464844,0.240723,0.967285,0.231689,0.022217,0.766357, + 0.776855,0.216309,0.305420,0.158203,0.019043,0.841309,0.054688,0.131348, + 0.270264,0.511230,0.073975,0.671631,0.718506,0.701660,0.009521,0.980713, + 0.355713,0.822266,0.023682,0.781006,0.973877,0.509277,0.288574,0.034180, + 0.500732,0.176514,0.968750,0.678467,0.334961,0.779541,0.985596,0.726807, + 0.006104,0.647705,0.928223,0.979492,0.477783,0.130371,0.209473,0.695313, + 0.311523,0.382324,0.504395,0.521729,0.147461,0.909668,0.585938,0.674561, + 0.747070,0.041016,0.207275,0.323486,0.558838,0.891357,0.526123,0.722656, + 0.985840,0.488770,0.961914,0.481689,0.801514,0.180908,0.788330,0.758057, + 0.891602,0.563477,0.466553,0.552002,0.760742,0.577148,0.705078,0.314941, + 0.852783,0.323975,0.738281,0.297363,0.136963,0.512695,0.356934,0.210205, + 0.378662,0.848877,0.411621,0.978760,0.723877,0.149170,0.635498,0.734619, + 0.779053,0.533447,0.610596,0.396729,0.242920,0.448730,0.251953,0.838135, + 0.695557,0.499756,0.935791,0.788574,0.247314,0.530518,0.524902,0.137695, + 0.231201,0.216797,0.130615,0.436768,0.980225,0.556641,0.807129,0.927246, + 0.927002,0.446777,0.599609,0.867920,0.969238,0.217285,0.792725,0.219971, + 0.444824,0.878906,0.996094,0.540527,0.341064,0.048340,0.688721,0.744629, + 0.604736,0.584473,0.700439,0.444580,0.037109,0.136719,0.131836,0.237305, + 0.756348,0.422607,0.901611,0.735107,0.568359,0.756592,0.410156,0.649414, + 0.875244,0.090576,0.876465,0.191162,0.626953,0.373779,0.864258,0.051514, + 0.999512,0.763672,0.994141,0.374756,0.911865,0.179199,0.415283,0.046143, + 0.381104,0.518555,0.675293,0.926758,0.260010,0.951660,0.608887,0.488525, + 0.580566,0.932861,0.879150,0.318115,0.613525,0.043945,0.040283,0.719238, + 0.353516,0.647461,0.570557,0.994385,0.602783,0.825928,0.394043,0.003662, + 0.951172,0.818848,0.786865,0.528809,0.750488,0.255859,0.154053,0.876709, + 0.154785,0.485107,0.007813,0.453613,0.034424,0.932617,0.897949,0.558105, + 0.030762,0.173340,0.753418,0.403809,0.434326,0.137207,0.282227,0.080078, + 0.849121,0.711670,0.065186,0.090332,0.925293,0.883545,0.456299,0.573242, + 0.583252,0.325439,0.074219,0.047852,0.392822,0.984863,0.330811,0.560791, + 0.441650,0.235840,0.559814,0.395752,0.234131,0.733643,0.520020,0.833984, + 0.764160,0.129639,0.446045,0.639404,0.068848,0.024414,0.478027,0.112549, + 0.276611,0.270020,0.844971,0.527588,0.566895,0.804199,0.896973,0.183594, + 0.631104,0.924072,0.246826,0.094727,0.304932,0.781738,0.118408,0.842529, + 0.269775,0.014404,0.626465,0.767822,0.729492,0.190918,0.544922,0.564697, + 0.992920,0.317627,0.024658,0.925537,0.414063,0.583740,0.569092,0.143066, + 0.655762,0.759766,0.799316,0.056885,0.273438,0.882813,0.281494,0.795166, + 0.020264,0.066162,0.523438,0.238037,0.880127,0.157959,0.236816,0.992188, + 0.924561,0.578857,0.498291,0.130859,0.344238,0.964600,0.798584,0.672119, + 0.622070,0.794189,0.661621,0.474121,0.399414,0.842285,0.166260,0.220459, + 0.640869,0.351318,0.764404,0.202393,0.726318,0.498779,0.959473,0.395996, + 0.929199,0.415039,0.246094,0.301025,0.516846,0.164795,0.141357,0.789307, + 0.286621,0.343506,0.625977,0.147949,0.654541,0.279297,0.770508,0.937500, + 0.282715,0.205322,0.833496,0.665283,0.751953,0.964355,0.462402,0.184570, + 0.195313,0.557129,0.791016,0.882568,0.610840,0.004395,0.787598,0.556396, + 0.209717,0.408203,0.816895,0.373047,0.369629,0.344971,0.870605,0.120850, + 0.987305,0.118652,0.658203,0.873779,0.292969,0.692627,0.568604,0.922119, + 0.469971,0.575928,0.985107,0.288330,0.192627,0.437256,0.414795,0.628906, + 0.647217,0.540771,0.111816,0.342041,0.420898,0.767334,0.224365,0.739502, + 0.843262,0.894531,0.916260,0.820068,0.564941,0.256348,0.238770,0.386963, + 0.960449,0.284180,0.385010,0.687500,0.683838,0.275879,0.945557,0.936768, + 0.032227,0.607910,0.062988,0.322998,0.241455,0.818115,0.567139,0.973633, + 0.829102,0.938477,0.046631,0.962402,0.576172,0.309326,0.979980,0.176758, + 0.421631,0.329102,0.315430,0.333008,0.160156,0.455078,0.562744,0.417969, + 0.898438,0.771973,0.725830,0.159424,0.254395,0.441162,0.371582,0.102295, + 0.151123,0.468262,0.637451,0.087158,0.405273,0.063721,0.155518,0.403320, + 0.989746,0.957031,0.137939,0.543213,0.428223,0.054443,0.872559,0.823975, + 0.815430,0.609131,0.912109,0.806885,0.067871,0.556152,0.923340,0.769531, + 0.618164,0.055420,0.803223,0.156982,0.262939,0.693848,0.894775,0.270996, + 0.463135,0.998779,0.845215,0.149414,0.081055,0.253174,0.037598,0.842773, + 0.526855,0.306396,0.068359,0.078613,0.211426,0.508057,0.295898,0.496826, + 0.374268,0.234863,0.832520,0.489258,0.140137,0.193848,0.029053,0.798828, + 0.914063,0.086426,0.866211,0.857910,0.914795,0.554443,0.285645,0.135986 +}; + +#define MAX_RANDOM_FLOATS 4096 +#define RANDOM_FLOAT_MASK ( MAX_RANDOM_FLOATS - 1 ) + + +#endif // RANDOM_FLOATS