//+--------------------------------------------------------------------------+
//|							From IPViewItem									 |
//+--------------------------------------------------------------------------+
bool PFTestSplitByAmount::HasDynamicName(TSTR& nameSuffix)
{
	Control* ctrl;
	bool isAnimated;
	int type = pblock()->GetInt(kSplitByAmount_testType, 0);
	TCHAR buf[32];
	switch(type) {
	case kSplitByAmount_testType_fraction:
		ctrl = pblock()->GetController(kSplitByAmount_fraction);
		isAnimated = (ctrl != NULL) ? (ctrl->IsAnimated() != 0) : false;
		if (isAnimated) {
			nameSuffix = GetString(IDS_FRACTION);
		} else {
			sprintf(buf,"%d%%",int(pblock()->GetFloat(kSplitByAmount_fraction, 0)*100.0f + 0.5f));
			nameSuffix = TSTR(buf);
		}
		break;
	case kSplitByAmount_testType_everyN:
		ctrl = pblock()->GetController(kSplitByAmount_everyN);
		isAnimated = (ctrl != NULL) ? (ctrl->IsAnimated() != 0) : false;
		if (isAnimated) {
			nameSuffix = GetString(IDS_EVERY);
			nameSuffix += TSTR(" N");
			nameSuffix += GetString(IDS_TH_LikeInNth);
		} else {
			int n = pblock()->GetInt(kSplitByAmount_everyN, 0);
			sprintf(buf," %d",n);
			nameSuffix += buf;
			if (n == 1) {
				nameSuffix = GetString(IDS_ALL);
			} else {
				nameSuffix = GetString(IDS_EVERY);
				sprintf(buf," %d",pblock()->GetInt(kSplitByAmount_everyN, 0));
				nameSuffix += TSTR(buf);
				if ((n%100 > 10) && (n%100 < 20)) {
					nameSuffix += GetString(IDS_TH_LikeInNth);
				} else {
					switch(n%10) {
					case 1: nameSuffix += GetString(IDS_ST_LikeIn1st); break;
					case 2: nameSuffix += GetString(IDS_ND_LikeIn2nd); break;
					case 3: nameSuffix += GetString(IDS_RD_LikeIn3rd); break;
					default: nameSuffix += GetString(IDS_TH_LikeInNth); break;
					}
				}
			}
		}
		break;
	case kSplitByAmount_testType_firstN:
		nameSuffix = GetString(IDS_FIRST);
		sprintf(buf," %d",pblock()->GetInt(kSplitByAmount_firstN, 0));
		nameSuffix += buf;
		break;
	case kSplitByAmount_testType_afterFirstN:
		nameSuffix = GetString(IDS_AFTER);
		sprintf(buf," %d",pblock()->GetInt(kSplitByAmount_firstN, 0));
		nameSuffix += buf;
		break;
	}
	return true;
}
Exemple #2
0
//+--------------------------------------------------------------------------+
//|							From IPViewItem									 |
//+--------------------------------------------------------------------------+
bool PFTestScale::HasDynamicName(TSTR& nameSuffix)
{
	int testType	= pblock()->GetInt(kScaleTest_testType, 0);
	switch(testType) {
	case kScaleTest_testType_preSize:
		nameSuffix = GetString(IDS_PRESIZE);
		break;
	case kScaleTest_testType_postSize:
		nameSuffix = GetString(IDS_POSTSIZE);
		break;
	case kScaleTest_testType_scale:
		nameSuffix = GetString(IDS_SCALE);
		Control* ctrl = pblock()->GetControllerByID(kScaleTest_scaleValue);
		bool isValueAnimated = (ctrl != NULL) ? (ctrl->IsAnimated() != 0) : false;
		int testValue = int(GetPFFloat(pblock(), kScaleTest_scaleValue, 0)*100.0f);
		ctrl = pblock()->GetControllerByID(kScaleTest_scaleVariation);
		bool isVarAnimated = (ctrl != NULL) ? (ctrl->IsAnimated() != 0) : false;
		int testVar = int(GetPFFloat(pblock(), kScaleTest_scaleVariation, 0)*100.0f);
		if ((!isValueAnimated) && (!isVarAnimated)) {
			int condType	= pblock()->GetInt(kScaleTest_conditionType, 0);
			if (condType == kScaleTest_conditionType_less)
				nameSuffix += _T("<");
			else
				nameSuffix += _T(">");
			TCHAR buf[32];
			_stprintf(buf, _T("%d"), testValue);
			nameSuffix += buf;
			if (testVar > 0) {
				_tcscpy(buf, GetString(IDS_PLUS_OR_MINUS_CHAR));
				nameSuffix += buf;
				_stprintf(buf, _T("%d"), testVar);
				nameSuffix += buf;
			}
			if (testType == kScaleTest_testType_scale)
				nameSuffix += _T("%");
		}
		break;
	}
	return true;
}
void SGMExporter::CollectProperties(Scene3DMesh *mesh, IGameMesh *gMesh)
{
	IPropertyContainer *propsContainer = gMesh->GetIPropertyContainer();
	if (propsContainer == NULL || propsContainer->GetNumberOfProperties() == 0)
	{
		Log::LogT("Mesh %s has no properties", mesh->name.c_str());
		return;
	}
	
	Log::LogT("properties count: %d", propsContainer->GetNumberOfProperties());

	for (int i = 0; i < propsContainer->GetNumberOfProperties(); i++)
	{
		IGameProperty *gProp = propsContainer->GetProperty(i);
		if (gProp == NULL)
			continue;

		int propType = gProp->GetType();
		std::string propName = StringUtils::ToNarrow(gProp->GetName());

		Log::LogT("eporting %s with type %d", propName.c_str(), propType);

		if (propType == IGAME_UNKNOWN_PROP)
		{
			Log::LogT("property %s has unknown type", propName.c_str());
			continue;
		}

		Property::AnimationType propAnimType = Property::AnimationType_None;

		Property *prop = NULL; 

		if (!gProp->IsPropAnimated())
		{
			Log::LogT("property %s has no animation", propName.c_str());

			prop = new Property(propName, PropTypeConv(propType), Property::AnimationType_None);
			switch (propType)
			{
			case IGAME_FLOAT_PROP:
				{
					float val;	
					gProp->GetPropertyValue(val);
					prop->SetValue(val);
				}
				break;

			case IGAME_INT_PROP:
				{
					int val;
					gProp->GetPropertyValue(val);
					prop->SetValue(val);
				}
				break;

			case IGAME_POINT3_PROP:
				{
					Point3 val;
					gProp->GetPropertyValue(val);
					prop->SetValue(sm::Vec3(val.x, val.y, val.z));
				}
				break;
			}
		}
		else
		{
			IGameControl *ctrl = gProp->GetIGameControl();

			if (ctrl == NULL)
			{
				Log::LogT("%s IGameControl is NULL", propName.c_str());
				continue;
			}

			switch (propType)
			{
			case IGAME_FLOAT_PROP:
				{
					Control *maxControl = ctrl->GetMaxControl(IGAME_FLOAT);
					if (maxControl != NULL && maxControl->IsAnimated())
					{
						if (maxControl->ClassID() == Class_ID(LININTERP_FLOAT_CLASS_ID, 0))
						{	
							Log::LogT("%s float liniowe scierwo", propName.c_str());
							prop = new Property(propName, Property::PropertyType_Float, Property::AnimationType_Linear);
							IGameKeyTab keys;
							if (ctrl->GetLinearKeys(keys, IGAME_FLOAT))
							{
								for (int j = 0; j < keys.Count(); j++)
								{
									prop->SetValue(keys[j].linearKey.fval, TicksToSec(keys[j].t));
								}
							}
						}
						if (maxControl->ClassID() == Class_ID(TCBINTERP_FLOAT_CLASS_ID, 0))
						{
							Log::LogT("%s float tcb scierwo", propName.c_str());
							prop = new Property(propName, Property::PropertyType_Float, Property::AnimationType_TCB);
							IGameKeyTab keys;
							if (ctrl->GetTCBKeys(keys, IGAME_FLOAT))
							{
								for (int j = 0; j < keys.Count(); j++)
								{
									prop->SetValue(keys[j].tcbKey.fval, TicksToSec(keys[j].t));
								}
							}
						}
					}
				}

				break;

			case IGAME_INT_PROP:
				{
					Control *maxControl = ctrl->GetMaxControl(IGAME_FLOAT);
					if (maxControl != NULL && maxControl->IsAnimated())
					{
						if (maxControl->ClassID() == Class_ID(LININTERP_FLOAT_CLASS_ID, 0))
						{
							Log::LogT("%s int liniowe scierwo", propName.c_str());
							//prop = new Property(propName, Property::PropertyType_Int, Property::AnimationType_Linear);
							// it should be always state interpolator for int
							prop = new Property(propName, Property::PropertyType_Int, Property::AnimationType_State);
							IGameKeyTab keys;
							if (ctrl->GetLinearKeys(keys, IGAME_FLOAT))
							{
								Log::LogT("eksportowanie %d keyframow", keys.Count());
								for (int j = 0; j < keys.Count(); j++)
								{
									prop->SetValue((int)keys[j].linearKey.fval, TicksToSec(keys[j].t));
								}
							}
						}
						if (maxControl->ClassID() == Class_ID(TCBINTERP_FLOAT_CLASS_ID, 0))
						{
							Log::LogT("%s int tcb scierwo", propName.c_str());
							//prop = new Property(propName, Property::PropertyType_Int, Property::AnimationType_TCB);
							// it should be always state interpolator for int
							prop = new Property(propName, Property::PropertyType_Int, Property::AnimationType_State);
							IGameKeyTab keys;
							if (ctrl->GetTCBKeys(keys, IGAME_FLOAT))
							{
								for (int j = 0; j < keys.Count(); j++)
								{
									prop->SetValue((int)keys[j].linearKey.fval, TicksToSec(keys[j].t));
								}
							}
						}
					}
					else
					{
					}
				}

				break;
			}
		}

		if (prop != NULL)
			mesh->properties.push_back(prop);
	}
}
Exemple #4
0
//+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+
//|							From IPFTest									 |
//+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+
bool PFTestSpeed::Proceed(IObject* pCont, 
							PreciseTimeValue timeStart, 
							PreciseTimeValue& timeEnd, 
							Object* pSystem, 
							INode* pNode, 
							INode* actionNode, 
							IPFIntegrator* integrator, 
							BitArray& testResult, 
							Tab<float>& testTime)
{
	bool exactStep = IsExactIntegrationStep(timeEnd, pSystem);

	// get the constant properties of the test
	int testType = pblock()->GetInt(kSpeedTest_testType, timeEnd);
	int condType = pblock()->GetInt(kSpeedTest_conditionType, timeEnd);
	int syncType = pblock()->GetInt(kSpeedTest_sync, timeEnd);
	ParamID varParamID = (testType == kSpeedTest_testType_steering) ? kSpeedTest_angleVariation : kSpeedTest_unitVariation;
	bool hasTestVariation = (pblock()->GetFloat(varParamID, 0) != 0.0f);
	if (!hasTestVariation) {
		Control* ctrl = pblock()->GetControllerByID(varParamID);
		if (ctrl != NULL)
			hasTestVariation = (ctrl->IsAnimated() != 0);
	}
	if (testType >= kSpeedTest_testType_whenAccels) {
		hasTestVariation = false;
		syncType = kSpeedTest_sync_time;
	}
	bool needPrevValue = (testType >= kSpeedTest_testType_accel);

	// 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
	int count = chAmount->Count();
	if (count == 0) return true; // no particles to test
	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 duration calculation
	IParticleChannelPoint3R* chSpeed = GetParticleChannelSpeedRInterface(pCont);
	if (chSpeed == NULL) return false; // can't read speed values

	// acquire more particle channels
	IParticleChannelPTVR* chBirthTime = NULL;
	if (syncType == kSpeedTest_sync_age && (testType < kSpeedTest_testType_whenAccels))
	{
		chBirthTime = GetParticleChannelBirthTimeRInterface(pCont);
		if (chBirthTime == NULL) return false; // can't read particle age
	}
	IParticleChannelPTVR* chEventStartR = NULL;
	IParticleChannelPTVW* chEventStartW = NULL;
	bool initEventStart = false;
	if (syncType == kSpeedTest_sync_event && (testType < kSpeedTest_testType_whenAccels)) {
		chEventStartR = (IParticleChannelPTVR*)chCont->EnsureInterface(PARTICLECHANNELEVENTSTARTR_INTERFACE,
																		ParticleChannelPTV_Class_ID,
																		true, PARTICLECHANNELEVENTSTARTR_INTERFACE,
																		PARTICLECHANNELEVENTSTARTW_INTERFACE, false,
																		actionNode, NULL, &initEventStart);
		if (chEventStartR == NULL) return false; // can't read event start time
		if (initEventStart) {
			chEventStartW = GetParticleChannelEventStartWInterface(pCont);
			if (chEventStartW == NULL) return false; // can't write event start time
		}
	}
	IParticleChannelFloatR* chRandFloatR = NULL;
	IParticleChannelFloatW* chRandFloatW = NULL;
	bool initRandFloat = false;
	if (hasTestVariation) {
		chRandFloatW = (IParticleChannelFloatW*)chCont->EnsureInterface(PARTICLECHANNELRANDFLOATW_INTERFACE,
																		ParticleChannelFloat_Class_ID,
																		true, PARTICLECHANNELRANDFLOATR_INTERFACE,
																		PARTICLECHANNELRANDFLOATW_INTERFACE, true,
																		actionNode, (Object*)this, &initRandFloat);
		chRandFloatR = (IParticleChannelFloatR*)chCont->GetPrivateInterface(PARTICLECHANNELRANDFLOATR_INTERFACE, (Object*)this);
		if ((chRandFloatR == NULL) || (chRandFloatW == NULL)) return false; // can't set rand float value for newly entered particles
	}
	IParticleChannelPoint3R* chPrevSpeedR = NULL;
	IParticleChannelPoint3W* chPrevSpeedW = NULL;
	IParticleChannelPTVR* chPrevTimeR = NULL;
	IParticleChannelPTVW* chPrevTimeW = NULL;
	bool initPrevValue = false;
	if (needPrevValue) {
		chPrevSpeedW = (IParticleChannelPoint3W*)chCont->EnsureInterface(PARTICLECHANNELPREVSPEEDW_INTERFACE,
																		ParticleChannelPoint3_Class_ID,
																		true, PARTICLECHANNELPREVSPEEDR_INTERFACE,
																		PARTICLECHANNELPREVSPEEDW_INTERFACE, true,
																		actionNode, (Object*)this, &initPrevValue);
		chPrevSpeedR = (IParticleChannelPoint3R*)chCont->GetPrivateInterface(PARTICLECHANNELPREVSPEEDR_INTERFACE, (Object*)this);
		chPrevTimeW = (IParticleChannelPTVW*)chCont->EnsureInterface(PARTICLECHANNELPREVTIMEW_INTERFACE,
																		ParticleChannelPTV_Class_ID,
																		true, PARTICLECHANNELPREVTIMER_INTERFACE,
																		PARTICLECHANNELPREVTIMEW_INTERFACE, true,
																		actionNode, (Object*)this, &initPrevValue);
		chPrevTimeR = (IParticleChannelPTVR*)chCont->GetPrivateInterface(PARTICLECHANNELPREVTIMER_INTERFACE, (Object*)this);
		if ((chPrevSpeedR == NULL) || (chPrevSpeedW == NULL) || (chPrevTimeR == NULL) || (chPrevTimeW == NULL)) return false; 
	}

	// grab the rand generator for test variation
	RandGenerator* randGen = randLinker().GetRandGenerator(pCont);
	if (randGen == NULL) return false;

	// check all particles
	testResult.SetSize(count);
	testResult.ClearAll();
	testTime.SetCount(count);
	for(int i=0; i<count; i++)
	{
		if (chNew->IsNew(i)) { // initialize some channels
			if (initEventStart)
				chEventStartW->SetValue(i, chTime->GetValue(i));
			if (initRandFloat)
				chRandFloatW->SetValue(i, randGen->Rand11());
		}

		PreciseTimeValue prevTime;
		Point3 prevSpeed;
		Point3 currentSpeed = chSpeed->GetValue(i);
		PreciseTimeValue currentTime = chTime->GetValue(i);
		if (needPrevValue) {
			prevTime = chPrevTimeR->GetValue(i);
			prevSpeed = chPrevSpeedR->GetValue(i);
			chPrevTimeW->SetValue(i, currentTime);
			chPrevSpeedW->SetValue(i, currentSpeed);
			if (initPrevValue && chNew->IsNew(i))
				continue; // particle just came into the event and doesn't have previous value
		}

		PreciseTimeValue syncTime = currentTime;
		switch(syncType) {
		case kSpeedTest_sync_age:
			syncTime -= chBirthTime->GetValue(i);
			break;
		case kSpeedTest_sync_event:
			syncTime -= chEventStartR->GetValue(i);
			break;
		}
		TimeValue syncTimeTV = TimeValue(syncTime);

		float testValue = 0.0f;
		if (testType < kSpeedTest_testType_whenAccels) {
			if (testType == kSpeedTest_testType_steering) {
				testValue = GetPFFloat(pblock(), kSpeedTest_angleValue, syncTimeTV);
				if (hasTestVariation)
					testValue += chRandFloatR->GetValue(i)*GetPFFloat(pblock(), kSpeedTest_angleVariation, syncTimeTV);
			} else {
				testValue = GetPFFloat(pblock(), kSpeedTest_unitValue, syncTimeTV);
				if (hasTestVariation)
					testValue += chRandFloatR->GetValue(i)*GetPFFloat(pblock(), kSpeedTest_unitVariation, syncTimeTV);
			}
			testValue /= TIME_TICKSPERSEC;
		}

		float currentValue = 0.0f;
		bool testSatisfied = false;
		if (testType < kSpeedTest_testType_whenAccels) {
			if (testType < kSpeedTest_testType_accel) {
				switch(testType) {
				case kSpeedTest_testType_speed:
					currentValue = Length(currentSpeed);
					break;
				case kSpeedTest_testType_speedX:
					currentValue = currentSpeed.x;
					break;
				case kSpeedTest_testType_speedY:
					currentValue = currentSpeed.y;
					break;
				case kSpeedTest_testType_speedZ:
					currentValue = currentSpeed.z;
					break;
				}
			} else if (testType == kSpeedTest_testType_steering) {
				float timeDif = float(currentTime - prevTime);
				if (timeDif <= 0.0f) continue; // no time difference
				float normFactor = Length(currentSpeed)*Length(prevSpeed);
				if (normFactor <= 0.0f) continue; // steering rate is not calculatable
				float vv = DotProd(currentSpeed,prevSpeed)/normFactor;
				float uu = 0.0;
				if (vv >= 1.0f) uu = 0.0;
				else if (vv <= -1.0f) uu = PI;
				else uu = acos(vv);
				currentValue = uu/timeDif;
			} else { // acceleration
				float timeDif = float(currentTime - prevTime);
				if (timeDif <= 0.0f) continue; // no time difference
				Point3 curAccel;
				switch(testType) {
				case kSpeedTest_testType_accel:
					currentValue = Length((currentSpeed - prevSpeed)/timeDif);
					break;
				case kSpeedTest_testType_accelX:
					currentValue = (currentSpeed.x - prevSpeed.x)/timeDif;	
					break;
				case kSpeedTest_testType_accelY:
					currentValue = (currentSpeed.y - prevSpeed.y)/timeDif;	
					break;
				case kSpeedTest_testType_accelZ:
					currentValue = (currentSpeed.z - prevSpeed.z)/timeDif;	
					break;
				}
				testValue /= TIME_TICKSPERSEC; // acceleration is per second squared
			}
			testSatisfied = (condType == kSpeedTest_conditionType_less) ?
								(currentValue < testValue) : (currentValue > testValue);
		} else {
			if (testType == kSpeedTest_testType_whenAccels) {
				testSatisfied = (Length(currentSpeed) > Length(prevSpeed));
			} else {
				testSatisfied = (Length(currentSpeed) < Length(prevSpeed));
			}
		}

		if (testSatisfied && exactStep) {
			testResult.Set(i);
			testTime[i] = 0.0f;
		}
	}

	return true;
}
Exemple #5
0
//+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+
//|							From IPFTest									 |
//+>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>+
bool PFTestScale::Proceed(IObject* pCont, 
							PreciseTimeValue timeStart, 
							PreciseTimeValue& timeEnd, 
							Object* pSystem, 
							INode* pNode, 
							INode* actionNode, 
							IPFIntegrator* integrator, 
							BitArray& testResult, 
							Tab<float>& testTime)
{
	bool exactStep = IsExactIntegrationStep(timeEnd, pSystem);

	// get the constant properties of the test
	int testType = pblock()->GetInt(kScaleTest_testType, timeEnd);
	int axisType = pblock()->GetInt(kScaleTest_axisType, timeEnd);
	int condType = pblock()->GetInt(kScaleTest_conditionType, timeEnd);
	int syncType = pblock()->GetInt(kScaleTest_sync, timeEnd);
	ParamID varParamID = (testType == kScaleTest_testType_scale) ? kScaleTest_scaleVariation : kScaleTest_sizeVariation;
	bool hasTestVariation = (pblock()->GetFloat(varParamID, 0) != 0.0f);
	if (!hasTestVariation) {
		Control* ctrl = pblock()->GetControllerByID(varParamID);
		if (ctrl != NULL)
			hasTestVariation = (ctrl->IsAnimated() != 0);
	}
	
	// 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
	int count = chAmount->Count();
	if (count == 0) return true; // no particles to test
	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 duration calculation

	// acquire more particle channels
	IParticleChannelPTVR* chBirthTime = NULL;
	if (syncType == kScaleTest_sync_age)
	{
		chBirthTime = GetParticleChannelBirthTimeRInterface(pCont);
		if (chBirthTime == NULL) return false; // can't read particle age
	}
	IParticleChannelPTVR* chEventStartR = NULL;
	IParticleChannelPTVW* chEventStartW = NULL;
	bool initEventStart = false;
	if (syncType == kScaleTest_sync_event) {
		chEventStartR = (IParticleChannelPTVR*)chCont->EnsureInterface(PARTICLECHANNELEVENTSTARTR_INTERFACE,
																		ParticleChannelPTV_Class_ID,
																		true, PARTICLECHANNELEVENTSTARTR_INTERFACE,
																		PARTICLECHANNELEVENTSTARTW_INTERFACE, false,
																		actionNode, NULL, &initEventStart);
		if (chEventStartR == NULL) return false; // can't read event start time
		if (initEventStart) {
			chEventStartW = GetParticleChannelEventStartWInterface(pCont);
			if (chEventStartW == NULL) return false; // can't write event start time
		}
	}
	IParticleChannelMeshR* chShape = NULL;
	if (testType != kScaleTest_testType_scale) {
		chShape = GetParticleChannelShapeRInterface(pCont);
		if (chShape == NULL) return false; // can't read particle shape to find bounding box
	}
	IParticleChannelPoint3R* chScale = NULL;
	if (testType != kScaleTest_testType_preSize) {
		chScale = GetParticleChannelScaleRInterface(pCont);
		if (chScale == NULL) return false; // can't read particle scale
	}
	IParticleChannelFloatR* chRandFloatR = NULL;
	IParticleChannelFloatW* chRandFloatW = NULL;
	bool initRandFloat = false;
	if (hasTestVariation) {
		chRandFloatW = (IParticleChannelFloatW*)chCont->EnsureInterface(PARTICLECHANNELRANDFLOATW_INTERFACE,
																		ParticleChannelFloat_Class_ID,
																		true, PARTICLECHANNELRANDFLOATR_INTERFACE,
																		PARTICLECHANNELRANDFLOATW_INTERFACE, true,
																		actionNode, (Object*)this, &initRandFloat);
		chRandFloatR = (IParticleChannelFloatR*)chCont->GetPrivateInterface(PARTICLECHANNELRANDFLOATR_INTERFACE, (Object*)this);
		if ((chRandFloatR == NULL) || (chRandFloatW == NULL)) return false; // can't set rand float value for newly entered particles
	}

	// grab the rand generator for test variation
	RandGenerator* randGen = randLinker().GetRandGenerator(pCont);
	if (randGen == NULL) return false;

	// check all particles
	testResult.SetSize(count);
	testResult.ClearAll();
	testTime.SetCount(count);
	for(int i=0; i<count; i++)
	{
		if (chNew->IsNew(i)) { // initialize some channels
			if (initEventStart)
				chEventStartW->SetValue(i, chTime->GetValue(i));
			if (initRandFloat)
				chRandFloatW->SetValue(i, randGen->Rand11());
		}

		PreciseTimeValue syncTime = chTime->GetValue(i);
		switch(syncType) {
		case kScaleTest_sync_age:
			syncTime -= chBirthTime->GetValue(i);
			break;
		case kScaleTest_sync_event:
			syncTime -= chEventStartR->GetValue(i);
			break;
		}
		TimeValue syncTimeTV = TimeValue(syncTime);

		float testValue = 0.0f;
		if (testType == kScaleTest_testType_scale) {
			testValue = GetPFFloat(pblock(), kScaleTest_scaleValue, syncTimeTV);
			if (hasTestVariation)
				testValue += chRandFloatR->GetValue(i)*GetPFFloat(pblock(), kScaleTest_scaleVariation, syncTimeTV);
		} else {
			testValue = GetPFFloat(pblock(), kScaleTest_sizeValue, syncTimeTV);
			if (hasTestVariation)
				testValue += chRandFloatR->GetValue(i)*GetPFFloat(pblock(), kScaleTest_sizeVariation, syncTimeTV);
		}

		Point3 cur3DValue;
		if (testType == kScaleTest_testType_scale) {
			cur3DValue = chScale->GetValue(i);
		} else {
			Mesh* curMesh = const_cast <Mesh*>(chShape->GetValue(i));
			if (curMesh == NULL) continue;
			Box3 curBox = curMesh->getBoundingBox();
			cur3DValue = curBox.pmax - curBox.pmin;
			if (testType == kScaleTest_testType_postSize)
				cur3DValue *= chScale->GetValue(i);
		}

		float currentValue = 0.0f;
		switch(axisType) {
		case kScaleTest_axisType_average:
			currentValue = (cur3DValue.x + cur3DValue.y + cur3DValue.z)/3.0f;
			break;
		case kScaleTest_axisType_minimum:
			currentValue = min(cur3DValue.x, min(cur3DValue.y, cur3DValue.z));
			break;
		case kScaleTest_axisType_median:
			currentValue = max(min(cur3DValue.x, cur3DValue.y), 
								max(min(cur3DValue.x, cur3DValue.z), min(cur3DValue.y, cur3DValue.z)));
			break;
		case kScaleTest_axisType_maximum:
			currentValue = max(cur3DValue.x, max(cur3DValue.y, cur3DValue.z));
			break;
		case kScaleTest_axisType_x:
			currentValue = cur3DValue.x;
			break;
		case kScaleTest_axisType_y:
			currentValue = cur3DValue.y;
			break;
		case kScaleTest_axisType_z:
			currentValue = cur3DValue.z;
			break;
		}

		bool testSatisfied = (condType == kScaleTest_conditionType_less) ?
								(currentValue < testValue) : (currentValue > testValue);
		if (testSatisfied && exactStep) {
			testResult.Set(i);
			testTime[i] = 0.0f;
		}
	}

	return true;
}
hsBool plMaxNodeBase::IsTMAnimated()
{
    Control* tmControl = GetTMController();
    return (tmControl && tmControl->IsAnimated());
}