//+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+
//|							From IPFOperator								 |
//+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+
bool PFTestGoToRotation::Proceed(IObject* pCont, 
									 PreciseTimeValue timeStart, 
									 PreciseTimeValue& timeEnd,
									 Object* pSystem,
									 INode* pNode,
									 INode* actionNode,
									 IPFIntegrator* integrator)
{
	if (postProceed()) return doPostProceed(pCont, timeStart, timeEnd, pSystem, pNode, actionNode, integrator);

	if (pblock() == NULL) return false;
	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
	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 info for a particle
	IParticleChannelNewR* chNew = GetParticleChannelNewRInterface(pCont);
	if (chNew == NULL) return false; // can't find newly entered particles for speedGoToTarget calculation
	IParticleChannelPTVR* chBirth = GetParticleChannelBirthTimeRInterface(pCont);
	if (chBirth == NULL) return false; // can't read birth time data
	IParticleChannelQuatR* chOrient = GetParticleChannelOrientationRInterface(pCont);
	if (chOrient == NULL) return false; // can't read current orientation for a particle

	// may create and initialize spin channel if it is not present
	IParticleChannelAngAxisR* chSpinR = NULL;
	IParticleChannelAngAxisW* chSpinW = NULL;
	bool initSpin = false;
	chSpinR = (IParticleChannelAngAxisR*)chCont->EnsureInterface(PARTICLECHANNELSPINR_INTERFACE,
																ParticleChannelAngAxis_Class_ID,
																true, PARTICLECHANNELSPINR_INTERFACE,
																PARTICLECHANNELSPINW_INTERFACE, true,
																actionNode, NULL, &initSpin);
	if (chSpinR == NULL) return false; // can't read spin data
	if (initSpin) {
		chSpinW = GetParticleChannelSpinWInterface(pCont);
		if (chSpinW == NULL) return false; // can't modify spin data
	}
	if (initSpin) {
		AngAxis aa(Point3::XAxis, 0.0f);
		if (!chNew->IsAllOld())
			for(i=0; i<count; i++) {
				if (chNew->IsNew(i))
					chSpinW->SetValue(i, aa);
			}
	}

	// create channel to store the start moment of the transition process
	// the time is when a particle enters the event
	IParticleChannelPTVW* chStartTimeW = NULL;
	bool initStartTime = false;
	chStartTimeW = (IParticleChannelPTVW*)chCont->EnsureInterface(PARTICLECHANNELSTARTTIMEW_INTERFACE,
																ParticleChannelPTV_Class_ID,
																true, PARTICLECHANNELSTARTTIMER_INTERFACE,
																PARTICLECHANNELSTARTTIMEW_INTERFACE, true,
																actionNode, (Object*)this, &initStartTime);
	if (chStartTimeW == NULL) return false; // can't modify the start time
	
	// create channel to store the end moment of the transition process
	// the time is used to determine when a particle should go to the next event, and when to finish the transition process
	IParticleChannelPTVW* chEndTimeW = NULL;
	bool initEndTime = false;
	chEndTimeW = (IParticleChannelPTVW*)chCont->EnsureInterface(PARTICLECHANNELENDTIMEW_INTERFACE,
																ParticleChannelPTV_Class_ID,
																true, PARTICLECHANNELENDTIMER_INTERFACE,
																PARTICLECHANNELENDTIMEW_INTERFACE, true,
																actionNode, (Object*)this, &initEndTime);
	if (chEndTimeW == NULL) return false; // can't modify the end time

	// create channel to store info about the last time for each particle for the proceed function
	// the data is used to rollback the effect of integration to find the desirable orientation
	IParticleChannelPTVW* chProceedTimeW = NULL;
	chProceedTimeW = (IParticleChannelPTVW*)chCont->EnsureInterface(PARTICLECHANNELPROCEEDTIMEW_INTERFACE,
																ParticleChannelPTV_Class_ID,
																true, PARTICLECHANNELPROCEEDTIMER_INTERFACE,
																PARTICLECHANNELPROCEEDTIMEW_INTERFACE, false,
																actionNode, (Object*)this);
	if (chProceedTimeW == NULL) return false; // can't modify the proceed time
	for(i=0; i<count; i++) chProceedTimeW->SetValue(i, chTime->GetValue(i));

	// create channel to store info if the final rotation has been initialized
	IParticleChannelBoolW* chGotInitW = NULL;
	bool initGotInit = false;
	chGotInitW = (IParticleChannelBoolW*)chCont->EnsureInterface(PARTICLECHANNELGOTINITW_INTERFACE,
															ParticleChannelBool_Class_ID,
															true, PARTICLECHANNELGOTINITR_INTERFACE,
															PARTICLECHANNELGOTINITW_INTERFACE, true,
															actionNode, (Object*)this, &initGotInit);
	if (chGotInitW == NULL) return false; // can't modify if init data

	// create channel to store initial rotation
	IParticleChannelQuatW* chStartRotW = NULL;
	bool initStartRot = false;
	chStartRotW = (IParticleChannelQuatW*)chCont->EnsureInterface(PARTICLECHANNELSTARTROTW_INTERFACE,
																ParticleChannelQuat_Class_ID,
																true, PARTICLECHANNELSTARTROTR_INTERFACE,
																PARTICLECHANNELSTARTROTW_INTERFACE, true,
																actionNode, (Object*)this, &initStartRot);
	if (chStartRotW == NULL) return false; // can't modify the start rotation

	// create channel to store end rotation
	IParticleChannelQuatW* chEndRotW = NULL;
	bool initEndRot = false;
	chEndRotW = (IParticleChannelQuatW*)chCont->EnsureInterface(PARTICLECHANNELENDROTW_INTERFACE,
																ParticleChannelQuat_Class_ID,
																true, PARTICLECHANNELENDROTR_INTERFACE,
																PARTICLECHANNELENDROTW_INTERFACE, true,
																actionNode, (Object*)this, &initEndRot);
	if (chEndRotW == NULL) return false; // can't modify the end rotation

	// create channel to store initial spin
	IParticleChannelAngAxisW* chStartSpinW = NULL;
	bool initStartSpin = false;
	chStartSpinW = (IParticleChannelAngAxisW*)chCont->EnsureInterface(PARTICLECHANNELSTARTSPINW_INTERFACE,
																ParticleChannelAngAxis_Class_ID,
																true, PARTICLECHANNELSTARTSPINR_INTERFACE,
																PARTICLECHANNELSTARTSPINW_INTERFACE, true,
																actionNode, (Object*)this, &initStartSpin);
	if (chStartSpinW == NULL) return false; // can't modify the start rotation
	
	// create channel to store final spin rate as a float
	IParticleChannelFloatW* chEndSpinW = NULL;
	bool initEndSpin = false;
	chEndSpinW = (IParticleChannelFloatW*)chCont->EnsureInterface(PARTICLECHANNELENDSPINW_INTERFACE,
																ParticleChannelFloat_Class_ID,
																true, PARTICLECHANNELENDSPINR_INTERFACE,
																PARTICLECHANNELENDSPINW_INTERFACE, true,
																actionNode, (Object*)this, &initEndSpin);
	if (chEndSpinW == NULL) return false; // can't modify the start rotation

	int sync = pblock()->GetInt(kGoToRotation_syncBy, timeEnd);
	TimeValue time = pblock()->GetTimeValue(kGoToRotation_time, timeEnd);
	TimeValue timeVar = pblock()->GetTimeValue(kGoToRotation_variation, timeEnd);
	int matchSpin = pblock()->GetInt(kGoToRotation_matchSpin, timeEnd);
	float spin = GetPFFloat(pblock(), kGoToRotation_spin, timeEnd.TimeValue())/TIME_TICKSPERSEC;
	float spinVar = GetPFFloat(pblock(), kGoToRotation_spinVariation, timeEnd.TimeValue())/TIME_TICKSPERSEC;

	RandGenerator* randGen = randLinker().GetRandGenerator(pCont);
	if (randGen == NULL) return false;

	if (!chNew->IsAllOld()) {
		for(i=0; i<count; i++) {
			if (!chNew->IsNew(i)) continue;
			if (initStartTime) 
				chStartTimeW->SetValue(i, chTime->GetValue(i) );
			if (initEndTime) {
				PreciseTimeValue endTime(time);
				switch(sync) {
				case kGoToRotation_syncBy_age:
					endTime += chBirth->GetValue(i);
					break;
				case kGoToRotation_syncBy_event:
					endTime += chTime->GetValue(i);
					break;
				}
				if (timeVar > 0) {
					int sign = randGen->RandSign();
					endTime += PreciseTimeValue(sign*randGen->Rand0X(timeVar));
				} else {
					randGen->RandSign();
					randGen->Rand0X(10);
				}
				chEndTimeW->SetValue(i, endTime);
			}
			if (initGotInit)
				chGotInitW->SetValue(i, false);
			if (initStartRot)
				chStartRotW->SetValue(i, chOrient->GetValue(i));
			if (initEndRot)
				chEndRotW->SetValue(i, chOrient->GetValue(i));
			if (initStartSpin)
				chStartSpinW->SetValue(i, chSpinR->GetValue(i));
			if (initEndSpin) {
				float endSpin = 0;
				if (matchSpin) {
					AngAxis aa = chSpinR->GetValue(i);
					endSpin = aa.angle;
				} else endSpin = spin;
				if (spinVar > 0.0f) endSpin += spinVar*randGen->Rand11();
				else randGen->Rand11();
				chEndSpinW->SetValue(i, endSpin);
			}
		}
	}

	return true;
}
bool PFTestGoToRotation::doPostProceed(IObject* pCont, 
									 PreciseTimeValue timeStart, 
									 PreciseTimeValue& timeEnd,
									 Object* pSystem,
									 INode* pNode,
									 INode* actionNode,
									 IPFIntegrator* integrator)
{
	if (pblock() == NULL) return false;
	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
	int i, count = chAmount->Count();
	if (count == 0) return true; // no particles to modify
	IParticleChannelQuatR* chOrientR = GetParticleChannelOrientationRInterface(pCont);
	if (chOrientR == NULL) return false; // can't read current orientation for a particle
	IParticleChannelQuatW* chOrientW = GetParticleChannelOrientationWInterface(pCont);
	if (chOrientW == NULL) return false; // can't modify current orientation for a particle
	IParticleChannelAngAxisR* chSpinR = GetParticleChannelSpinRInterface(pCont);
	if (chSpinR == NULL) return false; // can't read current spin for a particle
	IParticleChannelAngAxisW* chSpinW = GetParticleChannelSpinWInterface(pCont);
	if (chSpinW == NULL) return false; // can't modify current spin for a particle

	// acquire private channels
	IParticleChannelPTVR* chStartTime = (IParticleChannelPTVR*)(chCont->GetPrivateInterface(PARTICLECHANNELSTARTTIMER_INTERFACE, (Object*)this));
	if (chStartTime == NULL) return false;
	IParticleChannelPTVR* chEndTime = (IParticleChannelPTVR*)(chCont->GetPrivateInterface(PARTICLECHANNELENDTIMER_INTERFACE, (Object*)this));
	if (chEndTime == NULL) return false;
	IParticleChannelPTVR* chProceedTime = (IParticleChannelPTVR*)(chCont->GetPrivateInterface(PARTICLECHANNELPROCEEDTIMER_INTERFACE, (Object*)this));
	if (chProceedTime == NULL) return false;
		
	int targetType = pblock()->GetInt(kGoToRotation_targetType, timeEnd);
	bool initOnce = (targetType == kGoToRotation_targetType_constant);
	IParticleChannelBoolR* chGotInitR = (IParticleChannelBoolR*)(chCont->GetPrivateInterface(PARTICLECHANNELGOTINITR_INTERFACE, (Object*)this));
	IParticleChannelBoolW* chGotInitW = (IParticleChannelBoolW*)(chCont->GetPrivateInterface(PARTICLECHANNELGOTINITW_INTERFACE, (Object*)this));
	if ((chGotInitR == NULL) || (chGotInitW == NULL)) return false;

	IParticleChannelQuatR* chStartRot = (IParticleChannelQuatR*)(chCont->GetPrivateInterface(PARTICLECHANNELSTARTROTR_INTERFACE, (Object*)this));
	if (chStartRot == NULL) return false;
	IParticleChannelQuatR* chEndRotR = (IParticleChannelQuatR*)(chCont->GetPrivateInterface(PARTICLECHANNELENDROTR_INTERFACE, (Object*)this));
	if (chEndRotR == NULL) return false;
	IParticleChannelQuatW* chEndRotW = (IParticleChannelQuatW*)(chCont->GetPrivateInterface(PARTICLECHANNELENDROTW_INTERFACE, (Object*)this));
	if (chEndRotW == NULL) return false;
	
	IParticleChannelAngAxisR* chStartSpin = (IParticleChannelAngAxisR*)(chCont->GetPrivateInterface(PARTICLECHANNELSTARTSPINR_INTERFACE, (Object*)this));
	if (chStartSpin == NULL) return false;
	IParticleChannelFloatR* chEndSpin = (IParticleChannelFloatR*)(chCont->GetPrivateInterface(PARTICLECHANNELENDSPINR_INTERFACE, (Object*)this));
	if (chEndSpin == NULL) return false;
	
	float easyIn = GetPFFloat(pblock(), kGoToRotation_easeIn, timeEnd.TimeValue() );

	// particle properties modification
	for(i=0; i<count; i++) {
		// if the particle out of the transition period then do nothing
		PreciseTimeValue startT = chStartTime->GetValue(i);
		PreciseTimeValue endT = chEndTime->GetValue(i);
		if (endT < timeEnd) continue;
		if (endT <= startT) continue;

		// rollback the current rotation: remove the effect of the integration to know the real orientation value
		// need that if not initialized, or the target rotation is changing
		Quat curOrient = chOrientR->GetValue(i);
		bool needRollback = true;
		if (initOnce)
			if (chGotInitR->GetValue(i)) needRollback = false;
		if (needRollback) {
			AngAxis curSpin = chSpinR->GetValue(i);
			float timeDif = float(timeEnd - chProceedTime->GetValue(i));
			curSpin.angle *= -timeDif;
			curOrient += Quat(curSpin);
		}
		if (initOnce) {
			if (!chGotInitR->GetValue(i)) {
				chEndRotW->SetValue(i, curOrient);
			}
		} else {
			chEndRotW->SetValue(i, curOrient);
		}
		chGotInitW->SetValue(i, true);
	
		Quat resRot;
		AngAxis resSpin;
		InterpolateRotation(chStartRot->GetValue(i), chEndRotR->GetValue(i), chStartSpin->GetValue(i),
							chEndSpin->GetValue(i), startT, endT, timeEnd,
							easyIn, initOnce, resRot, resSpin);
		chOrientW->SetValue(i, resRot);
		chSpinW->SetValue(i, resSpin);
	}

	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;
}