bool PFOperatorMaterialStatic::Proceed(IObject* pCont, 
									 PreciseTimeValue timeStart, 
									 PreciseTimeValue& timeEnd,
									 Object* pSystem,
									 INode* pNode,
									 INode* actionNode,
									 IPFIntegrator* integrator)
{
	if (pblock() == NULL) return false;
	int assignID = pblock()->GetInt(kMaterialStatic_assignID, timeEnd);
	if (assignID == 0) return true; // nothing to assign

	int showInViewport = pblock()->GetInt(kMaterialStatic_showInViewport, timeEnd);
	if (!showInViewport) { // check if the system is in render; if not then return
		IPFSystem* iSystem = GetPFSystemInterface(pSystem);
		if (iSystem == NULL) return false;
		if (!iSystem->IsRenderState()) return true; // nothing to show in viewport
	}

	int type = pblock()->GetInt(kMaterialStatic_type, timeEnd);

	// acquire absolutely necessary particle channels
	IParticleChannelAmountR* chAmount = GetParticleChannelAmountRInterface(pCont);
	if (chAmount == NULL) return false; // can't find number of particles in the container
	int i, count = chAmount->Count();
	if (count == 0) return true; // no particles to modify
	IParticleChannelPTVR* chTime = GetParticleChannelTimeRInterface(pCont);
	if (chTime == NULL) return false; // can't read timing for a particle
	IParticleChannelNewR* chNew = GetParticleChannelNewRInterface(pCont);
	if (chNew == NULL) return false; // can't find newly entered particles for duration calculation
	
	IChannelContainer* chCont = GetChannelContainerInterface(pCont);
	if (chCont == NULL) return false;

	// ensure materialStatic index channel
	IParticleChannelIntW* chMtlIDW = (IParticleChannelIntW*)chCont->EnsureInterface(PARTICLECHANNELMTLINDEXW_INTERFACE,
																ParticleChannelInt_Class_ID,
																true, PARTICLECHANNELMTLINDEXR_INTERFACE,
																PARTICLECHANNELMTLINDEXW_INTERFACE, true);
	if (chMtlIDW == NULL) return false; // can't modify MaterialStatic Index channel in the container
	IParticleChannelIntR* chMtlIDR = GetParticleChannelMtlIndexRInterface(pCont);

	RandGenerator* randGen = randLinker().GetRandGenerator(pCont);

	int rateType = pblock()->GetInt(kMaterialStatic_rateType, timeEnd);
	int numSubMtls = pblock()->GetInt(kMaterialStatic_numSubMtls, timeEnd);
	if (numSubMtls < 1) numSubMtls = 1;
	int cycleLoop = pblock()->GetInt(kMaterialStatic_loop, timeEnd);
	float rateSec = pblock()->GetFloat(kMaterialStatic_ratePerSecond, timeEnd);
	float ratePart = pblock()->GetFloat(kMaterialStatic_ratePerParticle, timeEnd);
	int mtlID;

	bool useRatePerSec = ((type != kMaterialStatic_type_id) && (rateType == kMaterialStatic_rateType_second));
	bool useRatePerPart = ((type != kMaterialStatic_type_id) && (rateType == kMaterialStatic_rateType_particle));

	bool initRands = false;
	// recalc offset if necessary
	if (_offsetTime(pCont) == TIME_NegInfinity) {
		_offsetTime(pCont) = timeStart.TimeValue();
		// find the earliest time of the coming particles
		PreciseTimeValue minTime = chTime->GetValue(0);
		for(i=1; i<count; i++)
			if (minTime > chTime->GetValue(i)) 
				minTime = chTime->GetValue(i);
		if (useRatePerSec)
			if (type == kMaterialStatic_type_cycle)
				_cycleOffset(pCont) = float(PreciseTimeValue(_offsetTime(pCont)) - minTime)*rateSec/TIME_TICKSPERSEC;
		initRands = true;
	} else if (_offsetTime(pCont) != timeStart.TimeValue()) {
		if (useRatePerSec) {
			if (type == kMaterialStatic_type_cycle) {
				float timeDelta = float(timeStart.TimeValue() - _offsetTime(pCont));
				float addOffset = timeDelta*rateSec/TIME_TICKSPERSEC;
				_cycleOffset(pCont) += addOffset;
				if (_cycleOffset(pCont) >= numSubMtls) {
					if (cycleLoop) _cycleOffset(pCont) -= numSubMtls*int(_cycleOffset(pCont)/numSubMtls);
					else _cycleOffset(pCont) = numSubMtls - 1.0f;
				}
			}
		}
		_offsetTime(pCont) = timeStart.TimeValue();
		initRands = true;
	}
	if (initRands && useRatePerSec && (type == kMaterialStatic_type_random)) {
		int intervalDelta = int(timeEnd.TimeValue() - timeStart.TimeValue()) + 1;
		_randMtlIndex(pCont).SetCount(intervalDelta);
		float curOffset = _cycleOffset(pCont);
		int curMtlID = int(curOffset);
		_randMtlIndex(pCont)[0] = curMtlID;
		for(int i=1; i<intervalDelta; i++) {
			float addOffset = rateSec/TIME_TICKSPERSEC;
			curOffset += addOffset;
			if (int(curOffset) != curMtlID) {
				curOffset = randGen->Rand0X(numSubMtls-1) + (curOffset - floor(curOffset));
				curMtlID = int(curOffset);
			}
			_randMtlIndex(pCont)[i] = curMtlID;
			_cycleOffset(pCont) = curOffset;
		}
	}

	float curCycleOffset = _cycleOffset(pCont);
	float ratePerPart = pblock()->GetFloat(kMaterialStatic_ratePerParticle, timeEnd);

	for(i=0; i<count; i++) {
		if (!chNew->IsNew(i)) continue; // the ID is already set
		switch(type) {
		case kMaterialStatic_type_id:
			mtlID = GetPFInt(pblock(), kMaterialStatic_materialID, chTime->GetValue(i).TimeValue());
			mtlID--;
			break;
		case kMaterialStatic_type_cycle:
			if (rateType == kMaterialStatic_rateType_second) {
				float timeDelta = float(chTime->GetValue(i) - timeStart);
				float addOffset = timeDelta*rateSec/TIME_TICKSPERSEC;
				mtlID = int(curCycleOffset + addOffset);
				if (mtlID >= numSubMtls) {
					if (cycleLoop) mtlID = mtlID%numSubMtls;
					else mtlID = numSubMtls - 1;
				}
			} else { // per particle rate type
				mtlID = int(curCycleOffset);
				if (mtlID >= numSubMtls) {
					if (cycleLoop) mtlID = mtlID%numSubMtls;
					else mtlID = numSubMtls - 1;
				}
				if (ratePart > 0.0f) {
					curCycleOffset += 1.0f/ratePart;
					if (curCycleOffset >= numSubMtls) {
						if (cycleLoop) curCycleOffset -= numSubMtls*int(curCycleOffset/numSubMtls);
						else curCycleOffset = numSubMtls - 1.0f;
					}
				}
			}
			break;
		case kMaterialStatic_type_random:
			if (rateType == kMaterialStatic_rateType_second) {
				int timeDelta = int( chTime->GetValue(i) - timeStart.TimeValue() );
				mtlID = _randMtlIndex(pCont)[timeDelta];
			} else { // per particle rate type
				mtlID = int(curCycleOffset);
				int oldMtlID = mtlID;
				if (mtlID >= numSubMtls) {
					if (cycleLoop) mtlID = mtlID%numSubMtls;
					else mtlID = numSubMtls - 1;
				}
				if (ratePart > 0.0f) {
					curCycleOffset += 1.0f/ratePart;
					if (int(curCycleOffset) != oldMtlID)
						curCycleOffset = randGen->Rand0X(numSubMtls-1) + (curCycleOffset - floor(curCycleOffset));
				}
			}
			break;
		default: DbgAssert(0);
		}
		if (mtlID < 0) mtlID = 0;
		chMtlIDW->SetValue(i, mtlID);
	}

	if (useRatePerPart)
		_cycleOffset(pCont) = curCycleOffset;
	
	return true;
}
//+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+
//|							From IPFTest									 |
//+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+
bool PFTestSplitByAmount::Proceed(IObject* pCont, 
							PreciseTimeValue timeStart, 
							PreciseTimeValue& timeEnd, 
							Object* pSystem, 
							INode* pNode, 
							INode* actionNode, 
							IPFIntegrator* integrator, 
							BitArray& testResult, 
							Tab<float>& testTime)
{
	int contIndex;
	if (!hasParticleContainer(pCont, contIndex)) return false;
	_lastUpdate(contIndex) = timeEnd.TimeValue();
	bool exactStep = IsExactIntegrationStep(timeEnd, pSystem);

	// update all other systems to the current time; everybody should be in sync
	// for proper accumulation amounts
	int i;
	for(i=0; i<allParticleContainers().Count(); i++) {
		if (allParticleContainer(i) == pCont) continue;
		if (allSystemNode(i) == pNode) continue;
		if (lastUpdate(i) == timeEnd.TimeValue()) continue;
		TimeValue timeToUpdateTo = timeEnd.TimeValue();
		allSystemNode(i)->NotifyDependents(FOREVER, PartID(&timeToUpdateTo), kPFMSG_UpdateToTime, NOTIFY_ALL, TRUE );
	}

	// get channel container interface
	IChannelContainer* chCont;
	chCont = GetChannelContainerInterface(pCont);
	if (chCont == NULL) return false;

	// acquire absolutely necessary particle channels
	IParticleChannelAmountR* chAmount = GetParticleChannelAmountRInterface(pCont);
	if (chAmount == NULL) return false; // can't find number of particles in the container
	IParticleChannelNewR* chNew = GetParticleChannelNewRInterface(pCont);
	if (chNew == NULL) return false; // can't find "new" property of particles in the container

	// acquire TestSplitByAmount private particle channel; if not present then create it		
	IParticleChannelBoolW* chTestW = (IParticleChannelBoolW*)chCont->EnsureInterface(PARTICLECHANNELTESTSPLITBYAMOUNTW_INTERFACE,
																			ParticleChannelBool_Class_ID,
																			true, PARTICLECHANNELTESTSPLITBYAMOUNTR_INTERFACE,
																			PARTICLECHANNELTESTSPLITBYAMOUNTW_INTERFACE, false,
																			actionNode, (Object*)this);
	IParticleChannelBoolR* chTestR = (IParticleChannelBoolR*)chCont->GetPrivateInterface(PARTICLECHANNELTESTSPLITBYAMOUNTR_INTERFACE, (Object*)this);
	if ((chTestR == NULL) || (chTestW == NULL)) return false; // can't set test value for newly entered particles

	int count = chAmount->Count();
	
	// check if all particles are "old". If some particles are "new" then we
	// have to calculate test values for those.
	if (!chNew->IsAllOld())
	{
		RandGenerator* randGen = randLinker().GetRandGenerator(pCont);
		if (randGen == NULL) return false;

		int testType	= pblock()->GetInt(kSplitByAmount_testType, timeStart);
		float fraction = GetPFFloat(pblock(), kSplitByAmount_fraction, timeStart);
		int everyN = GetPFInt(pblock(), kSplitByAmount_everyN, timeStart);
		int firstN = pblock()->GetInt(kSplitByAmount_firstN, timeStart);
		bool perSource = (pblock()->GetInt(kSplitByAmount_perSource, timeStart) != 0);
		int curWentThru = perSource ? wentThruTotal(pNode) : wentThruTotal();

		// number of "first N" particles is adjusted by multiplier coefficient
		// of the master particle system. This is done to make "first N"
		// parameter to be consistent to "total" number of particles acclaimed
		// by a birth operator
		IPFSystem* pfSys = PFSystemInterface(pSystem);
		if (pfSys == NULL) return false; // no handle for PFSystem interface
		firstN *= pfSys->GetMultiplier(timeStart); 

		for(i=0; i<count; i++) {
			if (chNew->IsNew(i)) { // calculate test value only for new particles
				bool sendOut = false;
				switch(testType) {
				case kSplitByAmount_testType_fraction:
					sendOut = (randGen->Rand01() <= fraction);
					break;
				case kSplitByAmount_testType_everyN:
					_wentThruAccum(contIndex) += 1;
					if (wentThruAccum(contIndex) >= everyN) {
						sendOut = true;
						_wentThruAccum(contIndex) = 0;
					}
					break;
				case kSplitByAmount_testType_firstN:
					_wentThruTotal(contIndex) += 1;
					if (curWentThru++ < firstN) sendOut = true;
					break;
				case kSplitByAmount_testType_afterFirstN:
					_wentThruTotal(contIndex) += 1;
					if (curWentThru++ >= firstN) sendOut = true;
					break;
				}
				chTestW->SetValue(i, sendOut);
			}
		}
	}

	// check all particles by predefined test channel
	testResult.SetSize(count);
	testResult.ClearAll();
	testTime.SetCount(count);
	if (exactStep) {
		for(i=0; i<count; i++)
		{	
			if (chTestR->GetValue(i)) {
				testResult.Set(i);
				testTime[i] = 0.0f;
			}
		}
	}
	return true;
}