You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4121 lines
118 KiB
4121 lines
118 KiB
4 years ago
|
/*==LICENSE==*
|
||
|
|
||
|
CyanWorlds.com Engine - MMOG client, server and tools
|
||
|
Copyright (C) 2011 Cyan Worlds, Inc.
|
||
|
|
||
|
This program is free software: you can redistribute it and/or modify
|
||
|
it under the terms of the GNU General Public License as published by
|
||
|
the Free Software Foundation, either version 3 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
Additional permissions under GNU GPL version 3 section 7
|
||
|
|
||
|
If you modify this Program, or any covered work, by linking or
|
||
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
|
||
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
|
||
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
|
||
|
(or a modified version of those libraries),
|
||
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
|
||
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
|
||
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
|
||
|
licensors of this Program grant you additional
|
||
|
permission to convey the resulting work. Corresponding Source for a
|
||
|
non-source form of such a combination shall include the source code for
|
||
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
||
|
work.
|
||
|
|
||
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
||
|
or by snail mail at:
|
||
|
Cyan Worlds, Inc.
|
||
|
14617 N Newport Hwy
|
||
|
Mead, WA 99021
|
||
|
|
||
|
*==LICENSE==*/
|
||
|
#include "HeadSpin.h"
|
||
|
#include "max.h"
|
||
|
#include "iparamm2.h"
|
||
|
#include "iparamb2.h"
|
||
|
#include "ISkin.h"
|
||
|
#include "MNMath.h"
|
||
|
|
||
|
#include "plMaxNode.h"
|
||
|
//#include "../MaxComponent/resource.h"
|
||
|
#include "GlobalUtility.h"
|
||
|
|
||
|
#include "plgDispatch.h"
|
||
|
#include "plPluginResManager.h"
|
||
|
#include "plMaxNodeData.h"
|
||
|
#include "hsUtils.h"
|
||
|
|
||
|
#include "../MaxConvert/plConvert.h"
|
||
|
#include "hsTemplates.h"
|
||
|
#include "hsStringTokenizer.h"
|
||
|
|
||
|
#include "../MaxConvert/hsConverterUtils.h"
|
||
|
#include "../MaxConvert/hsControlConverter.h"
|
||
|
#include "../MaxConvert/plMeshConverter.h"
|
||
|
#include "../MaxConvert/hsMaterialConverter.h"
|
||
|
#include "../MaxConvert/plLayerConverter.h"
|
||
|
#include "../MaxConvert/UserPropMgr.h"
|
||
|
#include "../MaxExport/plErrorMsg.h"
|
||
|
#include "../MaxConvert/hsVertexShader.h"
|
||
|
#include "../MaxConvert/plLightMapGen.h"
|
||
|
#include "plMaxMeshExtractor.h"
|
||
|
#include "../MaxPlasmaMtls/Layers/plLayerTex.h"
|
||
|
|
||
|
#include "../pnKeyedObject/plKey.h"
|
||
|
|
||
|
#include "../pnSceneObject/plSceneObject.h"
|
||
|
#include "../plScene/plSceneNode.h"
|
||
|
#include "../plPhysX/plPXPhysical.h"
|
||
|
#include "../plDrawable/plInstanceDrawInterface.h"
|
||
|
#include "../plDrawable/plSharedMesh.h"
|
||
|
#include "../pnSceneObject/plSimulationInterface.h"
|
||
|
#include "../pnSceneObject/plAudioInterface.h"
|
||
|
#include "../pnSceneObject/plCoordinateInterface.h"
|
||
|
#include "../pfAnimation/plFilterCoordInterface.h"
|
||
|
#include "../plParticleSystem/plBoundInterface.h"
|
||
|
#include "../plPhysical/plPickingDetector.h"
|
||
|
#include "../plModifier/plLogicModifier.h"
|
||
|
#include "../plModifier/plResponderModifier.h"
|
||
|
#include "../plModifier/plInterfaceInfoModifier.h"
|
||
|
#include "../pfAnimation/plLightModifier.h"
|
||
|
#include "../pfCharacter/plPlayerModifier.h"
|
||
|
#include "../plAvatar/plAGModifier.h"
|
||
|
#include "../plAvatar/plAGAnim.h"
|
||
|
#include "../plAvatar/plPointChannel.h"
|
||
|
#include "../plAvatar/plScalarChannel.h"
|
||
|
#include "../plAvatar/plAGMasterMod.h"
|
||
|
#include "../plMessage/plReplaceGeometryMsg.h"
|
||
|
#include "../plGImage/plMipmap.h"
|
||
|
#include "../plModifier/plSpawnModifier.h"
|
||
|
#include "../plInterp/plController.h"
|
||
|
#include "../plInterp/hsInterp.h"
|
||
|
#include "../pnMessage/plTimeMsg.h"
|
||
|
#include "../pfAnimation/plViewFaceModifier.h" // mf horse temp hack testing to be thrown away
|
||
|
|
||
|
#include "../plScene/plOccluder.h"
|
||
|
#include "hsFastMath.h"
|
||
|
|
||
|
|
||
|
#include "../plDrawable/plDrawableSpans.h"
|
||
|
#include "../plDrawable/plGeometrySpan.h"
|
||
|
#include "../plPipeline/plFogEnvironment.h"
|
||
|
|
||
|
#include "../plGLight/plLightInfo.h"
|
||
|
#include "../plGLight/plLightKonstants.h"
|
||
|
#include "../plSurface/plLayerInterface.h"
|
||
|
#include "../plSurface/plLayer.h"
|
||
|
#include "../plSurface/hsGMaterial.h"
|
||
|
|
||
|
#include "../pnMessage/plObjRefMsg.h"
|
||
|
#include "../pnMessage/plNodeRefMsg.h"
|
||
|
#include "../pnMessage/plIntRefMsg.h"
|
||
|
|
||
|
#include "../MaxExport/plExportProgressBar.h"
|
||
|
|
||
|
#include "../MaxPlasmaMtls/Materials/plDecalMtl.h"
|
||
|
|
||
|
#include "../MaxComponent/plComponentTools.h"
|
||
|
#include "../MaxComponent/plComponent.h"
|
||
|
#include "../MaxComponent/plComponentExt.h"
|
||
|
#include "../MaxComponent/plFlexibilityComponent.h"
|
||
|
#include "../MaxComponent/plLightMapComponent.h"
|
||
|
#include "../MaxComponent/plXImposter.h"
|
||
|
#include "../MaxComponent/plMiscComponents.h"
|
||
|
|
||
|
#include "../plParticleSystem/plParticleSystem.h"
|
||
|
#include "../plParticleSystem/plParticleEmitter.h"
|
||
|
#include "../plParticleSystem/plParticleEffect.h"
|
||
|
#include "../plParticleSystem/plParticleGenerator.h"
|
||
|
#include "../plParticleSystem/plConvexVolume.h"
|
||
|
|
||
|
#include "../MaxPlasmaLights/plRealTimeLightBase.h"
|
||
|
#include "../MaxPlasmaLights/plRTProjDirLight.h"
|
||
|
|
||
|
|
||
|
extern UserPropMgr gUserPropMgr;
|
||
|
|
||
|
hsBool ThreePlaneIntersect(const hsVector3& norm0, const hsPoint3& point0,
|
||
|
const hsVector3& norm1, const hsPoint3& point1,
|
||
|
const hsVector3& norm2, const hsPoint3& point2, hsPoint3& loc);
|
||
|
|
||
|
// Begin external component toolbox ///////////////////////////////////////////////////////////////
|
||
|
static plKey ExternAddModifier(plMaxNodeBase *node, plModifier *mod)
|
||
|
{
|
||
|
return nil;//((plMaxNode*)node)->AddModifier(mod);
|
||
|
}
|
||
|
|
||
|
static plKey ExternGetNewKey(const char *name, plModifier *mod, plLocation loc)
|
||
|
{
|
||
|
return nil;//hsgResMgr::ResMgr()->NewKey(name, mod, loc);
|
||
|
}
|
||
|
|
||
|
// In plResponderComponent (for no apparent reason).
|
||
|
int GetMatAnimModKey(Mtl* mtl, plMaxNodeBase* node, const char *segName, hsTArray<plKey>& keys);
|
||
|
// In plAudioComponents
|
||
|
int GetSoundNameAndIdx(plComponentBase *comp, plMaxNodeBase *node, const char*& name);
|
||
|
|
||
|
#include "../MaxComponent/plAnimComponent.h"
|
||
|
|
||
|
static const char *GetAnimCompAnimName(plComponentBase *comp)
|
||
|
{
|
||
|
if (comp->ClassID() == ANIM_COMP_CID || comp->ClassID() == ANIM_GROUP_COMP_CID)
|
||
|
return ((plAnimComponentBase*)comp)->GetAnimName();
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
static plKey GetAnimCompModKey(plComponentBase *comp, plMaxNodeBase *node)
|
||
|
{
|
||
|
if (comp->ClassID() == ANIM_COMP_CID || comp->ClassID() == ANIM_GROUP_COMP_CID)
|
||
|
return ((plAnimComponentBase*)comp)->GetModKey((plMaxNode*)node);
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
plComponentTools gComponentTools(ExternAddModifier,
|
||
|
ExternGetNewKey,
|
||
|
nil,
|
||
|
GetAnimCompModKey,
|
||
|
GetAnimCompAnimName,
|
||
|
GetMatAnimModKey,
|
||
|
GetSoundNameAndIdx);
|
||
|
|
||
|
// End external component toolbox //////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void plMaxBoneMap::AddBone(plMaxNodeBase *bone)
|
||
|
{
|
||
|
char *dbgNodeName = bone->GetName();
|
||
|
if (fBones.find(bone) == fBones.end())
|
||
|
fBones[bone] = fNumBones++;
|
||
|
}
|
||
|
|
||
|
void plMaxBoneMap::FillBoneArray(plMaxNodeBase **boneArray)
|
||
|
{
|
||
|
BoneMap::const_iterator boneIt = fBones.begin();
|
||
|
for (; boneIt != fBones.end(); boneIt++)
|
||
|
boneArray[(*boneIt).second] = (*boneIt).first;
|
||
|
}
|
||
|
|
||
|
UInt8 plMaxBoneMap::GetIndex(plMaxNodeBase *bone)
|
||
|
{
|
||
|
hsAssert(fBones.find(bone) != fBones.end(), "Bone missing in remap!");
|
||
|
return fBones[bone];
|
||
|
}
|
||
|
|
||
|
UInt32 plMaxBoneMap::GetBaseMatrixIndex(plDrawable *draw)
|
||
|
{
|
||
|
if (fBaseMatrices.find(draw) == fBaseMatrices.end())
|
||
|
return (UInt32)-1;
|
||
|
|
||
|
return fBaseMatrices[draw];
|
||
|
}
|
||
|
|
||
|
void plMaxBoneMap::SetBaseMatrixIndex(plDrawable *draw, UInt32 idx)
|
||
|
{
|
||
|
fBaseMatrices[draw] = idx;
|
||
|
}
|
||
|
|
||
|
// Don't call this after you've started assigning indices to spans, or
|
||
|
// you'll be hosed (duh).
|
||
|
void plMaxBoneMap::SortBones()
|
||
|
{
|
||
|
plMaxNodeBase **tempBones = TRACKED_NEW plMaxNodeBase*[fNumBones];
|
||
|
FillBoneArray(tempBones);
|
||
|
|
||
|
// Look ma! An n^2 bubble sort!
|
||
|
// (It's a 1-time thing for an array of less than 100 items. Speed is not essential here)
|
||
|
int i,j;
|
||
|
for (i = 0; i < fNumBones; i++)
|
||
|
{
|
||
|
hsBool swap = false;
|
||
|
for (j = i + 1; j < fNumBones; j++)
|
||
|
{
|
||
|
if (strcmp(tempBones[i]->GetName(), tempBones[j]->GetName()) > 0)
|
||
|
{
|
||
|
plMaxNodeBase *temp = tempBones[i];
|
||
|
tempBones[i] = tempBones[j];
|
||
|
tempBones[j] = temp;
|
||
|
swap = true;
|
||
|
}
|
||
|
}
|
||
|
if (!swap)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < fNumBones; i++)
|
||
|
fBones[tempBones[i]] = i;
|
||
|
|
||
|
delete [] tempBones;
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
plKey plMaxNode::AddModifier(plModifier *pMod, const char* name)
|
||
|
{
|
||
|
plKey modKey = pMod->GetKey();
|
||
|
if (!modKey)
|
||
|
modKey = hsgResMgr::ResMgr()->NewKey(name, pMod, GetLocation());
|
||
|
hsgResMgr::ResMgr()->AddViaNotify(modKey, TRACKED_NEW plObjRefMsg(GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kModifier), plRefFlags::kActiveRef);
|
||
|
return modKey;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::DoRecur(PMaxNodeFunc pDoFunction,plErrorMsg *pErrMsg, plConvertSettings *settings,plExportProgressBar*bar)
|
||
|
{
|
||
|
#ifdef HS_DEBUGGING
|
||
|
const char *tmpName = GetName();
|
||
|
#endif
|
||
|
|
||
|
// If there is a progess bar, update it with the current node
|
||
|
// If the user cancels during the update, set bogus so we'll exit
|
||
|
if (bar && bar->Update(GetName()))
|
||
|
{
|
||
|
pErrMsg->Set(true);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// If we can't convert (and we aren't the root node) stop recursing.
|
||
|
if (!IsRootNode() && !CanConvert())
|
||
|
return false;
|
||
|
|
||
|
(this->*pDoFunction)(pErrMsg, settings);
|
||
|
|
||
|
for (int i = 0; (!pErrMsg || !pErrMsg->IsBogus()) && i < NumberOfChildren(); i++)
|
||
|
{
|
||
|
plMaxNode *pChild = (plMaxNode *)GetChildNode(i);
|
||
|
pChild->DoRecur(pDoFunction, pErrMsg, settings, bar);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// This is the same as DoRecur except that it ignores the canconvert field. We
|
||
|
// need this for things like clearing the old data, where we need to ignore the old value.
|
||
|
hsBool plMaxNode::DoAllRecur(PMaxNodeFunc pDoFunction,plErrorMsg *pErrMsg, plConvertSettings *settings,plExportProgressBar*bar)
|
||
|
{
|
||
|
#ifdef HS_DEBUGGING
|
||
|
const char *tmpName = GetName();
|
||
|
#endif
|
||
|
|
||
|
// If there is a progess bar, update it with the current node
|
||
|
// If the user cancels during the update, set bogus so we'll exit
|
||
|
if (bar && bar->Update(GetName()))
|
||
|
{
|
||
|
pErrMsg->Set(true);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
(this->*pDoFunction)(pErrMsg, settings);
|
||
|
|
||
|
for (int i = 0; (!pErrMsg || !pErrMsg->IsBogus()) && i < NumberOfChildren(); i++)
|
||
|
{
|
||
|
plMaxNode *pChild = (plMaxNode *)GetChildNode(i);
|
||
|
pChild->DoAllRecur(pDoFunction, pErrMsg, settings, bar);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::ConvertValidate(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
TimeValue t = hsConverterUtils::Instance().GetTime(GetInterface());
|
||
|
Object *obj = EvalWorldState( t ).obj;
|
||
|
const char* dbgName = GetName();
|
||
|
|
||
|
// Always want to recalculate if this object can convert at this point.
|
||
|
// In general there won't be any cached flag anyway, but in the SceneViewer
|
||
|
// there can be if we're reconverting.
|
||
|
hsBool canConvert = CanConvert(true);
|
||
|
|
||
|
plMaxNodeData thisNodeData; // Extra data stored for each node
|
||
|
|
||
|
if (IsTarget())
|
||
|
{
|
||
|
plMaxNode* targetNode = ((plMaxNode*)GetLookatNode());
|
||
|
if (targetNode)
|
||
|
{
|
||
|
Object* targObj = targetNode->EvalWorldState( 0 ).obj;
|
||
|
|
||
|
if (targObj && targObj->SuperClassID() == CAMERA_CLASS_ID)
|
||
|
canConvert = true;
|
||
|
else
|
||
|
canConvert = false;
|
||
|
}
|
||
|
else
|
||
|
canConvert = false;
|
||
|
}
|
||
|
if (canConvert && obj->SuperClassID() == LIGHT_CLASS_ID)
|
||
|
{
|
||
|
thisNodeData.SetDrawable(false);
|
||
|
thisNodeData.SetRunTimeLight(true);
|
||
|
thisNodeData.SetForceLocal(true);
|
||
|
}
|
||
|
|
||
|
if (UserPropExists("Occluder"))
|
||
|
{
|
||
|
// thisNodeData.SetDrawable(false);
|
||
|
}
|
||
|
if( UserPropExists("PSRunTimeLight") )
|
||
|
thisNodeData.SetRunTimeLight(true);
|
||
|
|
||
|
if (GetParticleRelated())
|
||
|
thisNodeData.SetForceLocal(true);
|
||
|
// if (UserPropExists("cloth"))
|
||
|
// {
|
||
|
// thisNodeData.SetForceLocal(true);
|
||
|
// }
|
||
|
|
||
|
// If we want a physicals only world, set everything to not drawable by default.
|
||
|
if (settings->fPhysicalsOnly)
|
||
|
thisNodeData.SetDrawable(false);
|
||
|
|
||
|
// Remember info in MaxNodeData block for later
|
||
|
thisNodeData.SetCanConvert(canConvert);
|
||
|
|
||
|
SetMaxNodeData(&thisNodeData);
|
||
|
|
||
|
#define MF_DISABLE_INSTANCING
|
||
|
#ifndef MF_DISABLE_INSTANCING
|
||
|
// Send this node off to the instance list, to see if we're instanced
|
||
|
if( CanMakeMesh( obj, pErrMsg, settings ) )
|
||
|
{
|
||
|
hsTArray<plMaxNode *> nodes;
|
||
|
UInt32 numInstances = IBuildInstanceList( GetObjectRef(), t, nodes );
|
||
|
if( numInstances > 1 )
|
||
|
{
|
||
|
/// INSTANCED. Make sure to force local on us
|
||
|
SetForceLocal( true );
|
||
|
SetInstanced( true );
|
||
|
}
|
||
|
}
|
||
|
#endif // MF_DISABLE_INSTANCING
|
||
|
|
||
|
// If this is for the SceneViewer, turn off the dirty flags so we won't try
|
||
|
// reconverting this node again.
|
||
|
if (settings->fSceneViewer)
|
||
|
SetDirty(kAllDirty, false);
|
||
|
|
||
|
return canConvert;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::ClearMaxNodeData(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
// The right place to delete the boneMap is really in ~plMaxNodeData, but that class
|
||
|
// is only allowed to know about stuff in nucleusLib.
|
||
|
if (GetBoneMap() && GetBoneMap()->fOwner == this)
|
||
|
delete GetBoneMap();
|
||
|
|
||
|
if (GetSwappableGeom())
|
||
|
{
|
||
|
// Ref and unref it, so it goes away if no one kept it around (i.e. we just
|
||
|
// looked at the mesh during export for reference, but don't want to keep it.)
|
||
|
GetSwappableGeom()->GetKey()->RefObject();
|
||
|
GetSwappableGeom()->GetKey()->UnRefObject();
|
||
|
}
|
||
|
|
||
|
SetMaxNodeData( nil );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
#include "plGetLocationDlg.h"
|
||
|
|
||
|
//
|
||
|
// Helper for setting synchedObject options, until we have a GUI
|
||
|
//
|
||
|
#include "../plResMgr/plKeyFinder.h"
|
||
|
#include "plMaxCFGFile.h"
|
||
|
#include "../plAgeDescription/plAgeDescription.h"
|
||
|
#include "../plResMgr/plPageInfo.h"
|
||
|
#include "../pnNetCommon/plSDLTypes.h"
|
||
|
|
||
|
void plMaxNode::CheckSynchOptions(plSynchedObject* so)
|
||
|
{
|
||
|
if (so)
|
||
|
{
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
// TEMP - remove
|
||
|
//
|
||
|
TSTR sdata,sdataList[128];
|
||
|
int i,num;
|
||
|
|
||
|
//
|
||
|
// check for LocalOnly or DontPersist props
|
||
|
//
|
||
|
if (gUserPropMgr.UserPropExists(this, "LocalOnly"))
|
||
|
so->SetLocalOnly(true); // disable net synching and persistence
|
||
|
else
|
||
|
if (gUserPropMgr.UserPropExists(this, "DontPersistAny")) // disable all types of persistence
|
||
|
so->SetSynchFlagsBit(plSynchedObject::kExcludeAllPersistentState);
|
||
|
else
|
||
|
{
|
||
|
if (gUserPropMgr.GetUserPropStringList(this, "DontPersist", num, sdataList))
|
||
|
{
|
||
|
for(i=0;i<num;i++)
|
||
|
so->AddToSDLExcludeList(sdataList[i]); // disable a type of persistence
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Check for Volatile prop
|
||
|
//
|
||
|
if (gUserPropMgr.UserPropExists(this, "VolatileAll")) // make all sdl types on this object Volatile
|
||
|
so->SetSynchFlagsBit(plSynchedObject::kAllStateIsVolatile);
|
||
|
else
|
||
|
{
|
||
|
if (gUserPropMgr.GetUserPropStringList(this, "Volatile", num, sdataList))
|
||
|
{
|
||
|
for(i=0;i<num;i++)
|
||
|
so->AddToSDLVolatileList(sdataList[i]); // make volatile a type of persistence
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool tempOldOverride = (gUserPropMgr.UserPropExists(this, "OverrideHighLevelSDL") != 0);
|
||
|
|
||
|
//
|
||
|
// TEMP - remove
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
// If this object isn't in a global room, turn off sync flags
|
||
|
if ((!tempOldOverride && !GetOverrideHighLevelSDL()) && !GetLocation().IsReserved())
|
||
|
{
|
||
|
bool isDynSim = GetPhysicalProps()->GetGroup() == plSimDefs::kGroupDynamic;
|
||
|
bool hasPFC = false;
|
||
|
int count = NumAttachedComponents();
|
||
|
for (UInt32 x = 0; x < count; x++)
|
||
|
{
|
||
|
plComponentBase *comp = GetAttachedComponent(x);
|
||
|
if (comp->ClassID() == Class_ID(0x670d3629, 0x559e4f11))
|
||
|
{
|
||
|
hasPFC = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (!isDynSim && !hasPFC)
|
||
|
{
|
||
|
so->SetSynchFlagsBit(plSynchedObject::kExcludeAllPersistentState);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
so->AddToSDLExcludeList(kSDLAGMaster);
|
||
|
so->AddToSDLExcludeList(kSDLResponder);
|
||
|
so->AddToSDLExcludeList(kSDLLayer);
|
||
|
so->AddToSDLExcludeList(kSDLSound);
|
||
|
so->AddToSDLExcludeList(kSDLXRegion);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::MakeSceneObject(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
const char* dbgName = GetName();
|
||
|
if (!CanConvert())
|
||
|
return false;
|
||
|
|
||
|
plLocation nodeLoc = GetLocation();//GetLocFromStrings(); // After this we can use GetLocation()
|
||
|
if (!nodeLoc.IsValid())
|
||
|
{
|
||
|
// If we are reconverting, we don't want to bother the user about a room.
|
||
|
// In most cases, if it doesn't have a room we are in the middle of creating
|
||
|
// it. We don't want to pop up a dialog at that point.
|
||
|
if (settings->fReconvert)
|
||
|
{
|
||
|
SetCanConvert(false);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!plGetLocationDlg::Instance().GetLocation(this, pErrMsg))
|
||
|
return false;
|
||
|
nodeLoc = GetLocation();
|
||
|
}
|
||
|
|
||
|
plSceneObject* pso;
|
||
|
plKey objKey;
|
||
|
|
||
|
// Handle this as a SceneObject
|
||
|
pso = TRACKED_NEW plSceneObject;
|
||
|
objKey = hsgResMgr::ResMgr()->NewKey(GetName(), pso, nodeLoc, GetLoadMask());
|
||
|
|
||
|
// Remember info in MaxNodeData block for later
|
||
|
plMaxNodeData *pDat = GetMaxNodeData();
|
||
|
pDat->SetKey(objKey);
|
||
|
pDat->SetSceneObject(pso);
|
||
|
|
||
|
CheckSynchOptions(pso);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::PrepareSkin(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
if( !IFindBones(pErrMsg, settings) )
|
||
|
return false;
|
||
|
|
||
|
if( !NumBones() )
|
||
|
return true;
|
||
|
|
||
|
int i;
|
||
|
for( i = 0; i < NumBones(); i++ )
|
||
|
{
|
||
|
GetBone(i)->SetForceLocal(true);
|
||
|
GetBone(i)->SetDrawable(false);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::IFindBones(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
if( !CanConvert() )
|
||
|
return false;
|
||
|
|
||
|
if (UserPropExists("Bone"))
|
||
|
{
|
||
|
AddBone(this);
|
||
|
SetForceLocal(true);
|
||
|
}
|
||
|
|
||
|
ISkin* skin = FindSkinModifier();
|
||
|
if( skin && skin->GetNumBones() )
|
||
|
{
|
||
|
char *dbgNodeName = GetName();
|
||
|
|
||
|
// BoneUpdate
|
||
|
//SetForceLocal(true);
|
||
|
int i;
|
||
|
for( i = 0; i < skin->GetNumBones(); i++ )
|
||
|
{
|
||
|
plMaxNode* bone = (plMaxNode*)skin->GetBone(i);
|
||
|
if( bone )
|
||
|
{
|
||
|
if( !bone->CanConvert() || !bone->GetMaxNodeData() )
|
||
|
{
|
||
|
if( pErrMsg->Set(true, GetName(), "Trouble connecting to bone %s - skipping", bone->GetName()).CheckAndAsk() )
|
||
|
SetDrawable(false);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
AddBone(bone);
|
||
|
bone->SetForceLocal(true);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if( pErrMsg->Set(true, GetName(), "Trouble finding bone - skipping").CheckAndAsk() )
|
||
|
SetDrawable(false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
#include "plMaxMeshExtractor.h"
|
||
|
#include "plPhysXCooking.h"
|
||
|
#include "../plPhysX/plPXStream.h"
|
||
|
#include "../plPhysX/plSimulationMgr.h"
|
||
|
#include "hsSTLStream.h"
|
||
|
|
||
|
hsBool plMaxNode::MakePhysical(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
const char* dbgNodeName = GetName();
|
||
|
|
||
|
if( !CanConvert() )
|
||
|
return false;
|
||
|
|
||
|
if( !GetPhysical() )
|
||
|
return true;
|
||
|
|
||
|
plPhysicalProps *physProps = GetPhysicalProps();
|
||
|
|
||
|
if (!physProps->IsUsed())
|
||
|
return true;
|
||
|
|
||
|
//hsStatusMessageF("Making phys for %s", dbgNodeName);
|
||
|
|
||
|
plSimDefs::Group group = (plSimDefs::Group)physProps->GetGroup();
|
||
|
plSimDefs::Bounds bounds = (plSimDefs::Bounds)physProps->GetBoundsType();
|
||
|
float mass = physProps->GetMass();
|
||
|
|
||
|
plMaxNode *proxyNode = physProps->GetProxyNode();
|
||
|
if (!proxyNode)
|
||
|
proxyNode = this;
|
||
|
|
||
|
// We want to draw solid physicals only. If it is something avatars bounce off,
|
||
|
// set it to drawable.
|
||
|
if (settings->fPhysicalsOnly)
|
||
|
{
|
||
|
if (group == plSimDefs::kGroupStatic ||
|
||
|
group == plSimDefs::kGroupAvatarBlocker ||
|
||
|
group == plSimDefs::kGroupDynamic)
|
||
|
proxyNode->SetDrawable(true);
|
||
|
}
|
||
|
|
||
|
// If mass is zero and we're animated, set the mass to 1 so it will get a rigid
|
||
|
// body. Otherwise PhysX will make assumptions about the physical which will
|
||
|
// fail when it gets moved.
|
||
|
if (physProps->GetPhysAnim() && mass == 0.f)
|
||
|
mass = 1.f;
|
||
|
|
||
|
TSTR sdata;
|
||
|
plMaxNode* baseNode = this;
|
||
|
while (!baseNode->GetParentNode()->IsRootNode())
|
||
|
baseNode = (plMaxNode*)baseNode->GetParentNode();
|
||
|
plKey roomKey = baseNode->GetRoomKey();
|
||
|
if (!roomKey)
|
||
|
{
|
||
|
pErrMsg->Set(true, "Room Processing Error - Physics" "The Room that physics component %s is attached to should have already been\nprocessed.", GetName());
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
plMaxNode* subworld = physProps->GetSubworld();
|
||
|
|
||
|
PhysRecipe recipe;
|
||
|
recipe.mass = mass;
|
||
|
recipe.friction = physProps->GetFriction();
|
||
|
recipe.restitution = physProps->GetRestitution();
|
||
|
recipe.bounds = (plSimDefs::Bounds)physProps->GetBoundsType();
|
||
|
recipe.group = group;
|
||
|
recipe.reportsOn = physProps->GetReportGroup();
|
||
|
recipe.objectKey = GetKey();
|
||
|
recipe.sceneNode = roomKey;
|
||
|
recipe.worldKey = subworld ? subworld->GetKey() : nil;
|
||
|
|
||
|
plMaxMeshExtractor::NeutralMesh mesh;
|
||
|
plMaxMeshExtractor::Extract(mesh, proxyNode, bounds == plSimDefs::kBoxBounds, this);
|
||
|
|
||
|
if (subworld)
|
||
|
recipe.l2s = subworld->GetWorldToLocal44() * mesh.fL2W;
|
||
|
else
|
||
|
recipe.l2s = mesh.fL2W;
|
||
|
|
||
|
switch (bounds)
|
||
|
{
|
||
|
case plSimDefs::kBoxBounds:
|
||
|
{
|
||
|
hsPoint3 minV(FLT_MAX, FLT_MAX, FLT_MAX), maxV(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
||
|
for (int i = 0; i < mesh.fNumVerts; i++)
|
||
|
{
|
||
|
minV.fX = hsMinimum(mesh.fVerts[i].fX, minV.fX);
|
||
|
minV.fY = hsMinimum(mesh.fVerts[i].fY, minV.fY);
|
||
|
minV.fZ = hsMinimum(mesh.fVerts[i].fZ, minV.fZ);
|
||
|
maxV.fX = hsMaximum(mesh.fVerts[i].fX, maxV.fX);
|
||
|
maxV.fY = hsMaximum(mesh.fVerts[i].fY, maxV.fY);
|
||
|
maxV.fZ = hsMaximum(mesh.fVerts[i].fZ, maxV.fZ);
|
||
|
}
|
||
|
hsPoint3 width = maxV - minV;
|
||
|
recipe.bDimensions = width / 2;
|
||
|
recipe.bOffset = minV + (width / 2.f);
|
||
|
}
|
||
|
break;
|
||
|
case plSimDefs::kProxyBounds:
|
||
|
case plSimDefs::kExplicitBounds:
|
||
|
{
|
||
|
// if this is a detector then try to convert to a convex hull first... if that doesn't succeed then do it as an exact
|
||
|
if ( group == plSimDefs::kGroupDetector )
|
||
|
{
|
||
|
// try converting to a convex hull mesh
|
||
|
recipe.meshStream = plPhysXCooking::CookHull(mesh.fNumVerts, mesh.fVerts,false);
|
||
|
if (recipe.meshStream)
|
||
|
{
|
||
|
plPXStream pxs(recipe.meshStream);
|
||
|
recipe.convexMesh = plSimulationMgr::GetInstance()->GetSDK()->createConvexMesh(pxs);
|
||
|
recipe.bounds = plSimDefs::kHullBounds;
|
||
|
// then test to see if the original mesh was convex (unless they said to skip 'em)
|
||
|
#ifdef WARNINGS_ON_CONCAVE_PHYSX_WORKAROUND
|
||
|
if ( !plPhysXCooking::fSkipErrors )
|
||
|
{
|
||
|
if ( !plPhysXCooking::TestIfConvex(recipe.convexMesh, mesh.fNumVerts, mesh.fVerts) )
|
||
|
{
|
||
|
int retStatus = pErrMsg->Set(true, "Physics Warning: PhysX workaround", "Detector region that is marked as exact and is concave but switching to convex hull for PhysX: %s", GetName()).CheckAskOrCancel();
|
||
|
pErrMsg->Set();
|
||
|
if ( retStatus == 1 ) // cancel?
|
||
|
plPhysXCooking::fSkipErrors = true;
|
||
|
}
|
||
|
}
|
||
|
#endif // WARNINGS_ON_CONCAVE_PHYSX_WORKAROUND
|
||
|
}
|
||
|
if (!recipe.meshStream)
|
||
|
{
|
||
|
if ( !pErrMsg->Set(true, "Physics Warning", "Detector region exact failed to be made a Hull, trying trimesh: %s", GetName()).Show() )
|
||
|
pErrMsg->Set();
|
||
|
recipe.meshStream = plPhysXCooking::CookTrimesh(mesh.fNumVerts, mesh.fVerts, mesh.fNumFaces, mesh.fFaces);
|
||
|
if (!recipe.meshStream)
|
||
|
{
|
||
|
pErrMsg->Set(true, "Physics Error", "Trimesh creation failed for physical %s", GetName()).Show();
|
||
|
return false;
|
||
|
}
|
||
|
plPXStream pxs(recipe.meshStream);
|
||
|
recipe.triMesh = plSimulationMgr::GetInstance()->GetSDK()->createTriangleMesh(pxs);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
recipe.meshStream = plPhysXCooking::CookTrimesh(mesh.fNumVerts, mesh.fVerts, mesh.fNumFaces, mesh.fFaces);
|
||
|
if (!recipe.meshStream)
|
||
|
{
|
||
|
pErrMsg->Set(true, "Physics Error", "Trimesh creation failed for physical %s", GetName()).Show();
|
||
|
return false;
|
||
|
}
|
||
|
plPXStream pxs(recipe.meshStream);
|
||
|
recipe.triMesh = plSimulationMgr::GetInstance()->GetSDK()->createTriangleMesh(pxs);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case plSimDefs::kSphereBounds:
|
||
|
{
|
||
|
hsPoint3 minV(FLT_MAX, FLT_MAX, FLT_MAX), maxV(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
||
|
for (int i = 0; i < mesh.fNumVerts; i++)
|
||
|
{
|
||
|
minV.fX = hsMinimum(mesh.fVerts[i].fX, minV.fX);
|
||
|
minV.fY = hsMinimum(mesh.fVerts[i].fY, minV.fY);
|
||
|
minV.fZ = hsMinimum(mesh.fVerts[i].fZ, minV.fZ);
|
||
|
maxV.fX = hsMaximum(mesh.fVerts[i].fX, maxV.fX);
|
||
|
maxV.fY = hsMaximum(mesh.fVerts[i].fY, maxV.fY);
|
||
|
maxV.fZ = hsMaximum(mesh.fVerts[i].fZ, maxV.fZ);
|
||
|
}
|
||
|
hsPoint3 width = maxV - minV;
|
||
|
recipe.radius = hsMaximum(width.fX, hsMaximum(width.fY, width.fZ));
|
||
|
recipe.radius /= 2.f;
|
||
|
recipe.offset = minV + (width / 2.f);
|
||
|
}
|
||
|
break;
|
||
|
case plSimDefs::kHullBounds:
|
||
|
{
|
||
|
|
||
|
|
||
|
|
||
|
if ( group == plSimDefs::kGroupDynamic )
|
||
|
{
|
||
|
|
||
|
|
||
|
recipe.meshStream = plPhysXCooking::IMakePolytope(mesh);
|
||
|
|
||
|
|
||
|
if (!recipe.meshStream)
|
||
|
{
|
||
|
pErrMsg->Set(true, "Physics Error", "polyTope-convexhull failed for physical %s", GetName()).Show();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
recipe.meshStream = plPhysXCooking::CookHull(mesh.fNumVerts, mesh.fVerts,false);
|
||
|
if(!recipe.meshStream)
|
||
|
{
|
||
|
pErrMsg->Set(true, "Physics Error", "Convex hull creation failed for physical %s", GetName()).Show();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
plPXStream pxs(recipe.meshStream);
|
||
|
recipe.convexMesh = plSimulationMgr::GetInstance()->GetSDK()->createConvexMesh(pxs);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
delete [] mesh.fFaces;
|
||
|
delete [] mesh.fVerts;
|
||
|
|
||
|
//
|
||
|
// Create the physical
|
||
|
//
|
||
|
plPXPhysical* physical = TRACKED_NEW plPXPhysical;
|
||
|
|
||
|
// add the object to the resource manager, keyed to the new name
|
||
|
plLocation nodeLoc = GetKey()->GetUoid().GetLocation();
|
||
|
const char *objName = GetKey()->GetName();
|
||
|
plKey physKey = hsgResMgr::ResMgr()->NewKey(objName, physical, nodeLoc, GetLoadMask());
|
||
|
|
||
|
if (!physical->Init(recipe))
|
||
|
{
|
||
|
pErrMsg->Set(true, "Physics Error", "Physical creation failed for object %s", GetName()).Show();
|
||
|
physKey->RefObject();
|
||
|
physKey->UnRefObject();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
physical->SetProperty(plSimulationInterface::kPinned, physProps->GetPinned());
|
||
|
physical->SetProperty(plSimulationInterface::kPhysAnim, physProps->GetPhysAnim());
|
||
|
physical->SetProperty(plSimulationInterface::kNoSynchronize, (physProps->GetNoSynchronize() != 0));
|
||
|
physical->SetProperty(plSimulationInterface::kStartInactive, (physProps->GetStartInactive() != 0));
|
||
|
physical->SetProperty(plSimulationInterface::kAvAnimPushable, (physProps->GetAvAnimPushable() != 0));
|
||
|
|
||
|
if(physProps->GetLOSBlockCamera())
|
||
|
physical->AddLOSDB(plSimDefs::kLOSDBCameraBlockers);
|
||
|
if(physProps->GetLOSBlockUI())
|
||
|
physical->AddLOSDB(plSimDefs::kLOSDBUIBlockers);
|
||
|
if(physProps->GetLOSBlockCustom())
|
||
|
physical->AddLOSDB(plSimDefs::kLOSDBCustom);
|
||
|
if(physProps->GetLOSUIItem())
|
||
|
physical->AddLOSDB(plSimDefs::kLOSDBUIItems);
|
||
|
if(physProps->GetLOSShootable())
|
||
|
physical->AddLOSDB(plSimDefs::kLOSDBShootableItems);
|
||
|
if(physProps->GetLOSAvatarWalkable())
|
||
|
physical->AddLOSDB(plSimDefs::kLOSDBAvatarWalkable);
|
||
|
if(physProps->GetLOSSwimRegion())
|
||
|
physical->AddLOSDB(plSimDefs::kLOSDBSwimRegion);
|
||
|
|
||
|
plSimulationInterface* si = TRACKED_NEW plSimulationInterface;
|
||
|
plKey pSiKey = hsgResMgr::ResMgr()->NewKey(objName, si, nodeLoc, GetLoadMask());
|
||
|
|
||
|
// link the simulation interface to the scene object
|
||
|
hsgResMgr::ResMgr()->AddViaNotify(pSiKey, TRACKED_NEW plObjRefMsg(GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
|
||
|
|
||
|
// add the physical to the simulation interface
|
||
|
hsgResMgr::ResMgr()->AddViaNotify(physKey , TRACKED_NEW plIntRefMsg(pSiKey, plRefMsg::kOnCreate, 0, plIntRefMsg::kPhysical), plRefFlags::kActiveRef);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::MakeController(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
if (!CanConvert())
|
||
|
return false;
|
||
|
|
||
|
hsBool forceLocal = hsControlConverter::Instance().ForceLocal(this);
|
||
|
// Rember the force Local setting
|
||
|
hsBool CurrForceLocal = GetForceLocal(); // dont want to clobber it with false if componentPass made it true
|
||
|
forceLocal = (CurrForceLocal || forceLocal) ? true : false; // if it was set before, or is true now, make it true...
|
||
|
SetForceLocal(forceLocal);
|
||
|
|
||
|
if( IsTMAnimated() && (!GetParentNode()->IsRootNode()) )
|
||
|
{
|
||
|
((plMaxNode*)GetParentNode())->SetForceLocal(true);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::MakeCoordinateInterface(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
const char* dbgNodeName = GetName();
|
||
|
if (!CanConvert())
|
||
|
return false;
|
||
|
plCoordinateInterface* ci = nil;
|
||
|
|
||
|
hsBool forceLocal = GetForceLocal();
|
||
|
|
||
|
hsBool needCI = (!GetParentNode()->IsRootNode())
|
||
|
|| NumberOfChildren()
|
||
|
|| forceLocal;
|
||
|
// If we have a transform, set up a coordinateinterface
|
||
|
if( needCI )
|
||
|
{
|
||
|
hsMatrix44 loc2Par = GetLocalToParent44();
|
||
|
hsMatrix44 par2Loc = GetParentToLocal44();
|
||
|
if( GetFilterInherit() )
|
||
|
ci = TRACKED_NEW plFilterCoordInterface;
|
||
|
else
|
||
|
ci = TRACKED_NEW plCoordinateInterface;
|
||
|
//-------------------------
|
||
|
// Get data from Node, then its key, then its name
|
||
|
//-------------------------
|
||
|
plKey pNodeKey = GetKey();
|
||
|
hsAssert(pNodeKey, "Missing key for this Object");
|
||
|
const char *pName = pNodeKey->GetName();
|
||
|
plLocation nodeLoc = GetLocation();
|
||
|
|
||
|
plKey pCiKey = hsgResMgr::ResMgr()->NewKey(pName, ci,nodeLoc, GetLoadMask());
|
||
|
ci->SetLocalToParent(loc2Par, par2Loc);
|
||
|
|
||
|
hsBool usesPhysics = GetPhysicalProps()->IsUsed();
|
||
|
ci->SetProperty(plCoordinateInterface::kCanEverDelayTransform, !usesPhysics);
|
||
|
ci->SetProperty(plCoordinateInterface::kDelayedTransformEval, !usesPhysics);
|
||
|
|
||
|
hsgResMgr::ResMgr()->AddViaNotify(pCiKey, TRACKED_NEW plObjRefMsg(pNodeKey, plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::MakeModifiers(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
if (!CanConvert())
|
||
|
return false;
|
||
|
|
||
|
hsBool forceLocal = GetForceLocal();
|
||
|
const char *dbgNodeName = GetName();
|
||
|
|
||
|
hsBool addMods = (!GetParentNode()->IsRootNode())
|
||
|
|| forceLocal;
|
||
|
|
||
|
if (addMods)
|
||
|
{
|
||
|
// create / add modifiers
|
||
|
|
||
|
// mf horse hack testing ViewFace which is already obsolete
|
||
|
if ( UserPropExists("ViewFacing") )
|
||
|
{
|
||
|
plViewFaceModifier* pMod = TRACKED_NEW plViewFaceModifier;
|
||
|
if( UserPropExists("VFPivotFavorY") )
|
||
|
pMod->SetFlag(plViewFaceModifier::kPivotFavorY);
|
||
|
else if( UserPropExists("VFPivotY") )
|
||
|
pMod->SetFlag(plViewFaceModifier::kPivotY);
|
||
|
else if( UserPropExists("VFPivotTumble") )
|
||
|
pMod->SetFlag(plViewFaceModifier::kPivotTumble);
|
||
|
else
|
||
|
pMod->SetFlag(plViewFaceModifier::kPivotFace);
|
||
|
if( UserPropExists("VFScale") )
|
||
|
{
|
||
|
pMod->SetFlag(plViewFaceModifier::kScale);
|
||
|
TSTR sdata;
|
||
|
GetUserPropString("VFScale",sdata);
|
||
|
hsStringTokenizer toker;
|
||
|
toker.Reset(sdata, hsConverterUtils::fTagSeps);
|
||
|
int nGot = 0;
|
||
|
char* token;
|
||
|
hsVector3 scale;
|
||
|
scale.Set(1.f,1.f,1.f);
|
||
|
while( (nGot < 3) && (token = toker.next()) )
|
||
|
{
|
||
|
switch( nGot )
|
||
|
{
|
||
|
case 0:
|
||
|
scale.fZ = hsScalar(atof(token));
|
||
|
break;
|
||
|
case 1:
|
||
|
scale.fX = scale.fZ;
|
||
|
scale.fY = hsScalar(atof(token));
|
||
|
scale.fZ = 1.f;
|
||
|
break;
|
||
|
case 2:
|
||
|
scale.fZ = hsScalar(atof(token));
|
||
|
break;
|
||
|
}
|
||
|
nGot++;
|
||
|
}
|
||
|
pMod->SetScale(scale);
|
||
|
}
|
||
|
AddModifier(pMod, GetName());
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::MakeParentOrRoomConnection(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
if (!CanConvert())
|
||
|
return false;
|
||
|
|
||
|
char *dbgNodeName = GetName();
|
||
|
plSceneObject *pso = GetSceneObject();
|
||
|
if( !GetParentNode()->IsRootNode() )
|
||
|
{
|
||
|
plKey parKey = GetParentKey();
|
||
|
plCoordinateInterface* ci = const_cast <plCoordinateInterface*> (pso->GetCoordinateInterface());
|
||
|
hsAssert(ci,"Missing CI");
|
||
|
|
||
|
|
||
|
plIntRefMsg* msg = TRACKED_NEW plIntRefMsg(parKey, plRefMsg::kOnCreate, -1, plIntRefMsg::kChildObject);
|
||
|
msg->SetRef(pso);
|
||
|
hsgResMgr::ResMgr()->AddViaNotify(msg, plRefFlags::kPassiveRef);
|
||
|
}
|
||
|
|
||
|
hsgResMgr::ResMgr()->AddViaNotify(pso->GetKey(), TRACKED_NEW plNodeRefMsg(GetRoomKey(), plRefMsg::kOnCreate, -1, plNodeRefMsg::kObject), plRefFlags::kActiveRef);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void plMaxNode::IWipeBranchDrawable(hsBool b)
|
||
|
{
|
||
|
SetDrawable(b);
|
||
|
for (int i = 0; i < NumberOfChildren(); i++)
|
||
|
{
|
||
|
plMaxNode *pChild = (plMaxNode *)GetChildNode(i);
|
||
|
pChild->IWipeBranchDrawable(b);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//// CanMakeMesh /////////////////////////////////////////////////////////////
|
||
|
// Returns true if MakeMesh() on this node will result in spans being stored
|
||
|
// on a drawable. Takes in the object pointer to avoid having to do redundant
|
||
|
// work to get it.
|
||
|
// 9.25.2001 mcn - Made public so components can figure out if this node is
|
||
|
// meshable.
|
||
|
|
||
|
hsBool plMaxNode::CanMakeMesh( Object *obj, plErrorMsg *pErrMsg, plConvertSettings *settings )
|
||
|
{
|
||
|
if( obj == nil )
|
||
|
return false;
|
||
|
|
||
|
if( UserPropExists( "Plasma2_Camera" ) )
|
||
|
return false;
|
||
|
|
||
|
if( !GetSwappableGeom() && !GetDrawable() )
|
||
|
return false;
|
||
|
|
||
|
if( GetParticleRelated() )
|
||
|
return false;
|
||
|
|
||
|
if( obj->CanConvertToType( triObjectClassID ) )
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void ITestAdjacencyRecur(const hsTArray<int>* vertList, int iVert, hsBitVector& adjVerts)
|
||
|
{
|
||
|
adjVerts.SetBit(iVert);
|
||
|
|
||
|
int i;
|
||
|
for( i = 0; i < vertList[iVert].GetCount(); i++ )
|
||
|
{
|
||
|
if( !adjVerts.IsBitSet(vertList[iVert][i]) )
|
||
|
{
|
||
|
ITestAdjacencyRecur(vertList, vertList[iVert][i], adjVerts);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
hsBool ITestAdjacency(const hsTArray<int>* vertList, int numVerts)
|
||
|
{
|
||
|
hsBitVector adjVerts;
|
||
|
ITestAdjacencyRecur(vertList, 0, adjVerts);
|
||
|
|
||
|
int i;
|
||
|
for( i = 0; i < numVerts; i++ )
|
||
|
{
|
||
|
if( !adjVerts.IsBitSet(i) )
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
int IsGeoSpanConvexExhaust(const plGeometrySpan* span)
|
||
|
{
|
||
|
// Brute force, check every point against every face
|
||
|
|
||
|
UInt16* idx = span->fIndexData;
|
||
|
int numFaces = span->fNumIndices / 3;
|
||
|
|
||
|
UInt32 stride = span->GetVertexSize(span->fFormat);
|
||
|
|
||
|
UInt8* vertData = span->fVertexData;
|
||
|
int numVerts = span->fNumVerts;
|
||
|
|
||
|
hsBool someIn = false;
|
||
|
hsBool someOut = false;
|
||
|
|
||
|
int i;
|
||
|
for( i = 0; i < numFaces; i++ )
|
||
|
{
|
||
|
// compute norm and dist for face
|
||
|
hsPoint3* pos[3];
|
||
|
pos[0] = (hsPoint3*)(vertData + idx[0] * stride);
|
||
|
pos[1] = (hsPoint3*)(vertData + idx[1] * stride);
|
||
|
pos[2] = (hsPoint3*)(vertData + idx[2] * stride);
|
||
|
|
||
|
hsVector3 edge01(pos[1], pos[0]);
|
||
|
hsVector3 edge02(pos[2], pos[0]);
|
||
|
|
||
|
hsVector3 faceNorm = edge01 % edge02;
|
||
|
hsFastMath::NormalizeAppr(faceNorm);
|
||
|
hsScalar faceDist = faceNorm.InnerProduct(pos[0]);
|
||
|
|
||
|
int j;
|
||
|
for( j = 0; j < numVerts; j++ )
|
||
|
{
|
||
|
hsPoint3* p = (hsPoint3*)(vertData + idx[0] * stride);
|
||
|
|
||
|
|
||
|
hsScalar dist = p->InnerProduct(faceNorm) - faceDist;
|
||
|
|
||
|
const hsScalar kSmall = 1.e-3f;
|
||
|
if( dist < -kSmall )
|
||
|
someIn = true;
|
||
|
else if( dist > kSmall )
|
||
|
someOut = true;
|
||
|
|
||
|
if( someIn && someOut )
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
idx += 3;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
int IsGeoSpanConvex(plMaxNode* node, const plGeometrySpan* span)
|
||
|
{
|
||
|
static int skipTest = false;
|
||
|
if( skipTest )
|
||
|
return 0;
|
||
|
|
||
|
// May not be now, but could become.
|
||
|
if( span->fFormat & plGeometrySpan::kSkinWeightMask )
|
||
|
return 0;
|
||
|
|
||
|
// May not be now, but could become.
|
||
|
if( node->GetConcave() || node->UserPropExists("XXXWaterColor") )
|
||
|
return 0;
|
||
|
|
||
|
if( span->fMaterial && span->fMaterial->GetLayer(0) && (span->fMaterial->GetLayer(0)->GetMiscFlags() & hsGMatState::kMiscTwoSided) )
|
||
|
return 0;
|
||
|
|
||
|
int numVerts = span->fNumVerts;
|
||
|
if( !numVerts )
|
||
|
return 0;
|
||
|
|
||
|
int numFaces = span->fNumIndices / 3;
|
||
|
if( !numFaces )
|
||
|
return 0;
|
||
|
|
||
|
const int kSmallNumFaces = 20;
|
||
|
if( numFaces <= kSmallNumFaces )
|
||
|
return IsGeoSpanConvexExhaust(span);
|
||
|
|
||
|
hsTArray<int>* vertList = TRACKED_NEW hsTArray<int> [numVerts];
|
||
|
|
||
|
hsTArray<hsVector3>* normList = TRACKED_NEW hsTArray<hsVector3> [numVerts];
|
||
|
hsTArray<hsScalar>* distList = TRACKED_NEW hsTArray<hsScalar> [numVerts];
|
||
|
|
||
|
UInt16* idx = span->fIndexData;
|
||
|
|
||
|
UInt32 stride = span->GetVertexSize(span->fFormat);
|
||
|
|
||
|
UInt8* vertData = span->fVertexData;
|
||
|
|
||
|
// For each face
|
||
|
int iFace;
|
||
|
for( iFace = 0; iFace < numFaces; iFace++ )
|
||
|
{
|
||
|
// compute norm and dist for face
|
||
|
hsPoint3* pos[3];
|
||
|
pos[0] = (hsPoint3*)(vertData + idx[0] * stride);
|
||
|
pos[1] = (hsPoint3*)(vertData + idx[1] * stride);
|
||
|
pos[2] = (hsPoint3*)(vertData + idx[2] * stride);
|
||
|
|
||
|
hsVector3 edge01(pos[1], pos[0]);
|
||
|
hsVector3 edge02(pos[2], pos[0]);
|
||
|
|
||
|
hsVector3 faceNorm = edge01 % edge02;
|
||
|
hsFastMath::NormalizeAppr(faceNorm);
|
||
|
hsScalar faceDist = faceNorm.InnerProduct(pos[0]);
|
||
|
|
||
|
|
||
|
// For each vert
|
||
|
int iVtx;
|
||
|
for( iVtx = 0; iVtx < 3; iVtx++ )
|
||
|
{
|
||
|
int jVtx;
|
||
|
for( jVtx = 0; jVtx < 3; jVtx++ )
|
||
|
{
|
||
|
if( iVtx != jVtx )
|
||
|
{
|
||
|
// if idx[jVtx] not in list vertList[idx[iVtx]], add it
|
||
|
if( vertList[idx[iVtx]].kMissingIndex == vertList[idx[iVtx]].Find(idx[jVtx]) )
|
||
|
vertList[idx[iVtx]].Append(idx[jVtx]);
|
||
|
}
|
||
|
}
|
||
|
normList[idx[iVtx]].Append(faceNorm);
|
||
|
distList[idx[iVtx]].Append(faceDist);
|
||
|
|
||
|
}
|
||
|
idx += 3;
|
||
|
}
|
||
|
|
||
|
hsBool someIn = false;
|
||
|
hsBool someOut = false;
|
||
|
int i;
|
||
|
for( i = 0; i < numVerts; i++ )
|
||
|
{
|
||
|
int k;
|
||
|
for( k = 0; k < normList[i].GetCount(); k++ )
|
||
|
{
|
||
|
int j;
|
||
|
for( j = 0; j < vertList[i].GetCount(); j++ )
|
||
|
{
|
||
|
hsPoint3* pos = (hsPoint3*)(vertData + vertList[i][j] * stride);
|
||
|
hsScalar dist = pos->InnerProduct(normList[i][k]) - distList[i][k];
|
||
|
|
||
|
const hsScalar kSmall = 1.e-3f;
|
||
|
if( dist < -kSmall )
|
||
|
someIn = true;
|
||
|
else if( dist > kSmall )
|
||
|
someOut = true;
|
||
|
|
||
|
if( someIn && someOut )
|
||
|
goto cleanUp;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( !ITestAdjacency(vertList, numVerts) )
|
||
|
someIn = someOut = true;
|
||
|
|
||
|
cleanUp:
|
||
|
delete [] vertList;
|
||
|
delete [] normList;
|
||
|
delete [] distList;
|
||
|
|
||
|
if( someIn && someOut )
|
||
|
return 0;
|
||
|
|
||
|
return someIn ? -1 : 1;
|
||
|
}
|
||
|
|
||
|
// Returns nil if there isn't a sceneobject and a drawinterface.
|
||
|
plDrawInterface* plMaxNode::GetDrawInterface()
|
||
|
{
|
||
|
plDrawInterface* di = nil;
|
||
|
plSceneObject* obj = GetSceneObject();
|
||
|
if( obj )
|
||
|
{
|
||
|
di = obj->GetVolatileDrawInterface();
|
||
|
}
|
||
|
return di;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::MakeMesh(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
hsTArray<plGeometrySpan *> spanArray;
|
||
|
plDrawInterface *newDI = nil;
|
||
|
|
||
|
hsBool gotMade = false;
|
||
|
hsBool haveAddedToSceneNode = false;
|
||
|
hsGMesh *myMesh = nil;
|
||
|
UInt32 i, triMeshIndex = (UInt32)-1;
|
||
|
const char *dbgNodeName = GetName();
|
||
|
TSTR sdata;
|
||
|
hsStringTokenizer toker;
|
||
|
plLocation nodeLoc = GetLocation();
|
||
|
|
||
|
if (!GetSwappableGeom())
|
||
|
{
|
||
|
if (!CanConvert())
|
||
|
return false;
|
||
|
|
||
|
if( UserPropExists( "Plasma2_Camera" ) || !GetDrawable() )
|
||
|
{
|
||
|
SetMesh( nil );
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( GetSwappableGeomTarget() != (UInt32)-1)
|
||
|
{
|
||
|
// This node has no geometry on export, but will have some added at runtime,
|
||
|
// so it needs a special drawInterface
|
||
|
|
||
|
plInstanceDrawInterface *newDI = TRACKED_NEW plInstanceDrawInterface;
|
||
|
newDI->fTargetID = GetSwappableGeomTarget();
|
||
|
plKey pDiKey = hsgResMgr::ResMgr()->NewKey( GetKey()->GetName(), newDI, nodeLoc, GetLoadMask() );
|
||
|
hsgResMgr::ResMgr()->AddViaNotify(pDiKey, TRACKED_NEW plObjRefMsg(GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
|
||
|
|
||
|
plSwapSpansRefMsg *sMsg = TRACKED_NEW plSwapSpansRefMsg(pDiKey, plRefMsg::kOnCreate, -1, -1);
|
||
|
plDrawableSpans *drawable = IGetSceneNodeSpans(IGetDrawableSceneNode(pErrMsg), true, true);
|
||
|
hsgResMgr::ResMgr()->AddViaNotify(drawable->GetKey(), sMsg, plRefFlags::kActiveRef);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if( GetInstanced() )
|
||
|
{
|
||
|
hsTArray<plMaxNode *> nodes;
|
||
|
TimeValue t = hsConverterUtils::Instance().GetTime(GetInterface());
|
||
|
UInt32 numInstances = IBuildInstanceList( GetObjectRef(), t, nodes, true );
|
||
|
|
||
|
/// Instanced, find an iNode in the list that's been converted already
|
||
|
for( i = 0; i < numInstances; i++ )
|
||
|
{
|
||
|
if( nodes[ i ]->GetSceneObject() && nodes[ i ]->GetSceneObject()->GetDrawInterface() )
|
||
|
{
|
||
|
/// Found it!
|
||
|
if( !IMakeInstanceSpans( nodes[ i ], spanArray, pErrMsg, settings ) )
|
||
|
return false;
|
||
|
|
||
|
gotMade = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
/// If we didn't find anything, nothing's got converted yet, so convert the first one
|
||
|
/// like normal
|
||
|
}
|
||
|
|
||
|
// This has the side effect of calling SetMovable(true) if it should be and
|
||
|
// isn't already. So it needs to be before we make the mesh (and material).
|
||
|
// (Really, whatever makes it movable should do so then, but that has the potential
|
||
|
// to break other stuff, which I don't want to do 2 weeks before we ship).
|
||
|
hsBool movable = IsMovable();
|
||
|
|
||
|
if( !gotMade )
|
||
|
{
|
||
|
if( !plMeshConverter::Instance().CreateSpans( this, spanArray, !settings->fDoPreshade ) )
|
||
|
return false;
|
||
|
}
|
||
|
if( !spanArray.GetCount() )
|
||
|
return true;
|
||
|
|
||
|
for( i = 0; i < spanArray.GetCount(); i++ )
|
||
|
spanArray[i]->fMaxOwner = GetKey()->GetName();
|
||
|
|
||
|
UInt32 shadeFlags = 0;
|
||
|
if( GetNoPreShade() )
|
||
|
shadeFlags |= plGeometrySpan::kPropNoPreShade;
|
||
|
if( GetRunTimeLight() )
|
||
|
shadeFlags |= plGeometrySpan::kPropRunTimeLight;
|
||
|
if( GetNoShadow() )
|
||
|
shadeFlags |= plGeometrySpan::kPropNoShadow;
|
||
|
if( GetForceShadow() || GetAvatarSO() )
|
||
|
shadeFlags |= plGeometrySpan::kPropForceShadow;
|
||
|
if( GetReverseSort() )
|
||
|
shadeFlags |= plGeometrySpan::kPropReverseSort;
|
||
|
if( GetForceVisLOS() )
|
||
|
shadeFlags |= plGeometrySpan::kVisLOS;
|
||
|
if( shadeFlags )
|
||
|
{
|
||
|
for( i = 0; i < spanArray.GetCount(); i++ )
|
||
|
spanArray[ i ]->fProps |= shadeFlags;
|
||
|
}
|
||
|
|
||
|
hsBool DecalMat = false;
|
||
|
hsBool NonDecalMat = false;
|
||
|
|
||
|
for (i = 0; i < spanArray.GetCount(); i++)
|
||
|
{
|
||
|
if (spanArray[i]->fMaterial->IsDecal())
|
||
|
DecalMat = true;
|
||
|
else
|
||
|
NonDecalMat = true;
|
||
|
}
|
||
|
if (!(DecalMat ^ NonDecalMat))
|
||
|
{
|
||
|
for( i = 0; i < spanArray.GetCount(); i++ )
|
||
|
spanArray[ i ]->ClearBuffers();
|
||
|
|
||
|
if (pErrMsg->Set((plConvert::Instance().fWarned & plConvert::kWarnedDecalAndNonDecal) == 0, GetName(),
|
||
|
"This node has both regular and decal materials, and thus will be ignored.").CheckAskOrCancel())
|
||
|
{
|
||
|
plConvert::Instance().fWarned |= plConvert::kWarnedDecalAndNonDecal;
|
||
|
}
|
||
|
pErrMsg->Set(false);
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
hsBool isDecal = IsLegalDecal(false); // Don't complain about the parent
|
||
|
|
||
|
/// Get some stuff
|
||
|
hsBool forceLocal = GetForceLocal();
|
||
|
|
||
|
hsMatrix44 l2w = GetLocalToWorld44();
|
||
|
hsMatrix44 w2l = GetWorldToLocal44();
|
||
|
|
||
|
/// 4.17.2001 mcn - TEMP HACK to test fog by adding a key to a bogus fogEnviron object to ALL spans
|
||
|
/* plFogEnvironment *myFog = nil;
|
||
|
plKey myFogKey = hsgResMgr::ResMgr()->FindExportAlias( "HACK_FOG", plFogEnvironment::Index() );
|
||
|
if( myFogKey != nil )
|
||
|
myFog = plFogEnvironment::ConvertNoRef( myFogKey->GetObjectPtr() );
|
||
|
else
|
||
|
{
|
||
|
hsColorRGBA color;
|
||
|
color.Set( 0.5, 0.5, 1, 1 );
|
||
|
|
||
|
// Exp fog
|
||
|
myFog = TRACKED_NEW plFogEnvironment( plFogEnvironment::kExpFog, 700.f, 1.f, color );
|
||
|
myFogKey = hsgResMgr::ResMgr()->NewKey( "HACK_FOG", myFog, nodeLoc );
|
||
|
hsgResMgr::ResMgr()->AddExportAlias( "HACK_FOG", plFogEnvironment::Index(), myFogKey );
|
||
|
}
|
||
|
|
||
|
for( int j = 0; j < spanArray.GetCount(); j++ )
|
||
|
{
|
||
|
spanArray[ j ].fFogEnviron = myFog;
|
||
|
}
|
||
|
*/ /// 4.17.2001 mcn - TEMP HACK end
|
||
|
|
||
|
|
||
|
plDrawable* drawable = nil;
|
||
|
plSceneNode* tmpNode = nil;
|
||
|
|
||
|
/// Find the ice to add it to
|
||
|
|
||
|
if (GetSwappableGeom()) // We just want to make a geo span, not actually add it to a drawable(interface)
|
||
|
{
|
||
|
plMaxNode *drawableSource = (plMaxNode *)(GetParentNode()->IsRootNode() ? this : GetParentNode());
|
||
|
plSceneNode *tmpNode = drawableSource->IGetDrawableSceneNode(pErrMsg);
|
||
|
|
||
|
plDrawableSpans *drawable = IGetSceneNodeSpans(tmpNode, true, true);
|
||
|
ISetupBones(drawable, spanArray, l2w, w2l, pErrMsg, settings);
|
||
|
|
||
|
hsTArray<plGeometrySpan *> *swapSpans = &GetSwappableGeom()->fSpans;
|
||
|
for (i = 0; i < spanArray.GetCount(); i++)
|
||
|
swapSpans->Append(spanArray.Get(i));
|
||
|
|
||
|
char tmpName[256];
|
||
|
sprintf(tmpName, "%s_SMsh", GetName());
|
||
|
hsgResMgr::ResMgr()->NewKey(tmpName, GetSwappableGeom(), GetLocation(), GetLoadMask());
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
plMaxNode *nonDecalParent = this;
|
||
|
if( GetRoomKey() )
|
||
|
{
|
||
|
tmpNode = plSceneNode::ConvertNoRef( GetRoomKey()->GetObjectPtr() );
|
||
|
|
||
|
if (isDecal) // If we're a decal, we just want to use our parent's drawable
|
||
|
{
|
||
|
plMaxNode *parent = (plMaxNode *)GetParentNode();
|
||
|
|
||
|
SetDecalLevel(parent->GetDecalLevel() + 1);
|
||
|
for( i = 0; i < spanArray.GetCount(); i++ )
|
||
|
spanArray[ i ]->fDecalLevel = GetDecalLevel();
|
||
|
}
|
||
|
|
||
|
{
|
||
|
/// Make a new drawInterface (will assign stuff to it later)
|
||
|
newDI = TRACKED_NEW plDrawInterface;
|
||
|
plKey pDiKey = hsgResMgr::ResMgr()->NewKey( GetKey()->GetName(), newDI, nodeLoc, GetLoadMask() );
|
||
|
hsgResMgr::ResMgr()->AddViaNotify(pDiKey, TRACKED_NEW plObjRefMsg(GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
|
||
|
|
||
|
/// Attach the processed spans to the DI (through drawables)
|
||
|
IAssignSpansToDrawables( spanArray, newDI, pErrMsg, settings );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
plSceneNode *plMaxNode::IGetDrawableSceneNode(plErrorMsg *pErrMsg)
|
||
|
{
|
||
|
plSceneNode *sn = nil;
|
||
|
|
||
|
sn = plSceneNode::ConvertNoRef( GetRoomKey()->GetObjectPtr() );
|
||
|
|
||
|
return sn;
|
||
|
}
|
||
|
|
||
|
//// IAssignSpansToDrawables /////////////////////////////////////////////////
|
||
|
// Given a span array, adds it to the node's drawables, creating them if
|
||
|
// necessary. Then it takes the resulting indices and drawable pointers
|
||
|
// and assigns them to the given drawInterface.
|
||
|
|
||
|
void plMaxNode::IAssignSpansToDrawables( hsTArray<plGeometrySpan *> &spanArray, plDrawInterface *di,
|
||
|
plErrorMsg *pErrMsg, plConvertSettings *settings )
|
||
|
{
|
||
|
hsTArray<plGeometrySpan *> opaqueArray, blendingArray, sortingArray;
|
||
|
plDrawableSpans *oSpans = nil, *bSpans = nil, *sSpans = nil;
|
||
|
|
||
|
int sCount, oCount, bCount, i;
|
||
|
plSceneNode *tmpNode = nil;
|
||
|
hsMatrix44 l2w = GetLocalToWorld44();
|
||
|
hsMatrix44 w2l = GetWorldToLocal44();
|
||
|
UInt32 oIndex = (UInt32)-1, bIndex = (UInt32)-1, sIndex = UInt32(-1);
|
||
|
|
||
|
tmpNode = IGetDrawableSceneNode(pErrMsg);
|
||
|
/*
|
||
|
/// Get sceneNode. If we're itinerant and not the parent node, this won't just
|
||
|
/// be GetRoomKey()->GetObjectPtr()....
|
||
|
if( GetItinerant() && !GetParentNode()->IsRootNode() )
|
||
|
{
|
||
|
/// Step up to the top of the chain
|
||
|
plMaxNode *baseNode = this;
|
||
|
while( !baseNode->GetParentNode()->IsRootNode() )
|
||
|
baseNode = (plMaxNode *)baseNode->GetParentNode();
|
||
|
|
||
|
if( baseNode->GetItinerant() )
|
||
|
tmpNode = plSceneNode::ConvertNoRef( baseNode->GetRoomKey()->GetObjectPtr() );
|
||
|
else
|
||
|
{
|
||
|
tmpNode = plSceneNode::ConvertNoRef( GetRoomKey()->GetObjectPtr() );
|
||
|
|
||
|
/// Warn, since we should only be itinerant if our parent is as well
|
||
|
pErrMsg->Set( true, "Warning", "Itinerant flag in child '%s' of non-itinerant tree. This should never happen. You should inform a programmer...", GetName() ).Show();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
tmpNode = plSceneNode::ConvertNoRef( GetRoomKey()->GetObjectPtr() );
|
||
|
*/
|
||
|
|
||
|
hsBitVector convexBits;
|
||
|
/// Separate the array into two arrays, one opaque and one blending
|
||
|
for( sCount = 0, oCount = 0, bCount = 0, i = 0; i < spanArray.GetCount(); i++ )
|
||
|
{
|
||
|
if( spanArray[ i ]->fProps & plGeometrySpan::kRequiresBlending )
|
||
|
{
|
||
|
hsBool needFaceSort = !GetNoFaceSort() && !IsGeoSpanConvex(this, spanArray[i]);
|
||
|
if( needFaceSort )
|
||
|
{
|
||
|
sCount++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
convexBits.SetBit(i);
|
||
|
bCount++;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
oCount++;
|
||
|
}
|
||
|
|
||
|
// Done this way, since expanding an hsTArray has the nasty side effect of just copying data, which we don't
|
||
|
// want when we have memory pointers...
|
||
|
opaqueArray.SetCount( oCount );
|
||
|
blendingArray.SetCount( bCount );
|
||
|
sortingArray.SetCount( sCount );
|
||
|
for( sCount = 0, oCount = 0, bCount = 0, i = 0; i < spanArray.GetCount(); i++ )
|
||
|
{
|
||
|
if( spanArray[ i ]->fProps & plGeometrySpan::kRequiresBlending )
|
||
|
{
|
||
|
if( convexBits.IsBitSet(i) )
|
||
|
blendingArray[ bCount++ ] = spanArray[ i ];
|
||
|
else
|
||
|
sortingArray [ sCount++ ] = spanArray[ i ];
|
||
|
}
|
||
|
else
|
||
|
opaqueArray[ oCount++ ] = spanArray[ i ];
|
||
|
}
|
||
|
|
||
|
/// Get some drawable pointers
|
||
|
if( opaqueArray.GetCount() > 0 )
|
||
|
oSpans = plDrawableSpans::ConvertNoRef( IGetSceneNodeSpans( tmpNode, false ) );
|
||
|
if( blendingArray.GetCount() > 0 )
|
||
|
bSpans = plDrawableSpans::ConvertNoRef( IGetSceneNodeSpans( tmpNode, true, false ) );
|
||
|
if( sortingArray.GetCount() > 0 )
|
||
|
sSpans = plDrawableSpans::ConvertNoRef( IGetSceneNodeSpans( tmpNode, true, true ) );
|
||
|
|
||
|
if( oSpans != nil )
|
||
|
IAssignSpan( oSpans, opaqueArray, oIndex, l2w, w2l, pErrMsg, settings );
|
||
|
if( bSpans != nil )
|
||
|
IAssignSpan( bSpans, blendingArray, bIndex, l2w, w2l, pErrMsg, settings );
|
||
|
if( sSpans )
|
||
|
IAssignSpan( sSpans, sortingArray, sIndex, l2w, w2l, pErrMsg, settings );
|
||
|
|
||
|
/// Now assign to the interface
|
||
|
if( oSpans )
|
||
|
{
|
||
|
UInt8 iDraw = di->GetNumDrawables();
|
||
|
di->SetDrawable( iDraw, oSpans );
|
||
|
di->SetDrawableMeshIndex( iDraw, oIndex );
|
||
|
}
|
||
|
|
||
|
if( bSpans )
|
||
|
{
|
||
|
UInt8 iDraw = di->GetNumDrawables();
|
||
|
di->SetDrawable( iDraw, bSpans );
|
||
|
di->SetDrawableMeshIndex( iDraw, bIndex );
|
||
|
}
|
||
|
|
||
|
if( sSpans )
|
||
|
{
|
||
|
UInt8 iDraw = di->GetNumDrawables();
|
||
|
di->SetDrawable( iDraw, sSpans );
|
||
|
di->SetDrawableMeshIndex( iDraw, sIndex );
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
//// IAssignSpan /////////////////////////////////////////////////////////////
|
||
|
// Small utility function for IAssignSpansToDrawables, just does some of
|
||
|
// the low-down work that's identical for each drawable/spans/etc.
|
||
|
|
||
|
void plMaxNode::IAssignSpan( plDrawableSpans *drawable, hsTArray<plGeometrySpan *> &spanArray, UInt32 &index,
|
||
|
hsMatrix44 &l2w, hsMatrix44 &w2l,
|
||
|
plErrorMsg *pErrMsg, plConvertSettings *settings )
|
||
|
{
|
||
|
if( NumBones() )
|
||
|
ISetupBones( drawable, spanArray, l2w, w2l, pErrMsg, settings );
|
||
|
|
||
|
// Assign spans to the drawables, plus set the volatile flag on the
|
||
|
// drawables for the SceneViewer, just in case it hasn't been set yet
|
||
|
if( settings->fSceneViewer )
|
||
|
{
|
||
|
drawable->SetNativeProperty( plDrawable::kPropVolatile, true );
|
||
|
index = drawable->AppendDISpans( spanArray, index, false );
|
||
|
}
|
||
|
else
|
||
|
index = drawable->AddDISpans( spanArray, index );
|
||
|
|
||
|
if( GetItinerant() )
|
||
|
drawable->SetNativeProperty(plDrawable::kPropCharacter, true);
|
||
|
}
|
||
|
|
||
|
// Tiny helper for the function below
|
||
|
void SetSpansBoneInfo(hsTArray<plGeometrySpan *> &spanArray, UInt32 baseMatrix, UInt32 numMatrices)
|
||
|
{
|
||
|
int i;
|
||
|
for( i = 0; i < spanArray.GetCount(); i++ )
|
||
|
{
|
||
|
spanArray[ i ]->fBaseMatrix = baseMatrix;
|
||
|
spanArray[ i ]->fNumMatrices = numMatrices;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//// ISetupBones /////////////////////////////////////////////////////////////
|
||
|
// Adds the given bones to the given drawable, then sets up the given spans
|
||
|
// with the right indices and sets the initial bone positions.
|
||
|
void plMaxNode::ISetupBones(plDrawableSpans *drawable, hsTArray<plGeometrySpan *> &spanArray,
|
||
|
hsMatrix44 &l2w, hsMatrix44 &w2l,
|
||
|
plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
const char* dbgNodeName = GetName();
|
||
|
|
||
|
if( !NumBones() )
|
||
|
return;
|
||
|
|
||
|
plMaxBoneMap *boneMap = GetBoneMap();
|
||
|
if (boneMap && boneMap->GetBaseMatrixIndex(drawable) != (UInt32)-1)
|
||
|
{
|
||
|
SetSpansBoneInfo(spanArray, boneMap->GetBaseMatrixIndex(drawable), boneMap->fNumBones);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
int baseMatrix, i;
|
||
|
|
||
|
UInt8 numBones = (boneMap ? boneMap->fNumBones : NumBones()) + 1;
|
||
|
plMaxNodeBase **boneArray = TRACKED_NEW plMaxNodeBase*[numBones];
|
||
|
|
||
|
if (boneMap)
|
||
|
boneMap->FillBoneArray(boneArray);
|
||
|
else
|
||
|
{
|
||
|
for (i = 0; i < NumBones(); i++)
|
||
|
{
|
||
|
boneArray[i] = GetBone(i);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
hsTArray<hsMatrix44> initialB2W;
|
||
|
hsTArray<hsMatrix44> initialW2B;
|
||
|
initialB2W.SetCount(numBones);
|
||
|
initialW2B.SetCount(numBones);
|
||
|
|
||
|
hsTArray<hsMatrix44> initialL2B;
|
||
|
hsTArray<hsMatrix44> initialB2L;
|
||
|
initialL2B.SetCount(numBones);
|
||
|
initialB2L.SetCount(numBones);
|
||
|
|
||
|
initialB2W[0].Reset();
|
||
|
initialW2B[0].Reset();
|
||
|
|
||
|
initialL2B[0].Reset();
|
||
|
initialB2L[0].Reset();
|
||
|
|
||
|
for( i = 1; i < numBones; i++ )
|
||
|
{
|
||
|
hsMatrix44 b2w;
|
||
|
hsMatrix44 w2b;
|
||
|
hsMatrix44 l2b;
|
||
|
hsMatrix44 b2l;
|
||
|
|
||
|
plMaxNodeBase *bone = boneArray[i-1];
|
||
|
const char* dbgBoneName = bone->GetName();
|
||
|
|
||
|
Matrix3 localTM = bone->GetNodeTM(TimeValue(0));
|
||
|
|
||
|
b2w = Matrix3ToMatrix44(localTM);
|
||
|
b2w.GetInverse(&w2b);
|
||
|
|
||
|
l2b = w2b * l2w;
|
||
|
b2l = w2l * b2w;
|
||
|
|
||
|
initialB2W[i] = b2w;
|
||
|
initialW2B[i] = w2b;
|
||
|
|
||
|
initialL2B[i] = l2b;
|
||
|
initialB2L[i] = b2l;
|
||
|
}
|
||
|
|
||
|
// First, see if the bones are already set up appropriately.
|
||
|
// Appropriately means:
|
||
|
// a) Associated with the correct drawable (maybe others too, we don't care).
|
||
|
// b) InitialBone transforms match. If we (or another user of the same bone)
|
||
|
// are force localed, Our InitialBone won't match, because it also includes
|
||
|
// our transform as well as the bone's. If we've been flattened into world
|
||
|
// space, our transform is ident and we can share. This is the normal case
|
||
|
// in scene boning. So InitialBones have to match in count and matrix value.
|
||
|
baseMatrix = drawable->FindBoneBaseMatrix(initialL2B, GetSwappableGeom() != nil);
|
||
|
if( baseMatrix != UInt32(-1) )
|
||
|
{
|
||
|
SetSpansBoneInfo(spanArray, baseMatrix, numBones);
|
||
|
delete [] boneArray;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
baseMatrix = drawable->AppendDIMatrixSpans(numBones);
|
||
|
SetSpansBoneInfo(spanArray, baseMatrix, numBones);
|
||
|
if (boneMap)
|
||
|
boneMap->SetBaseMatrixIndex(drawable, baseMatrix);
|
||
|
|
||
|
for( i = 1; i < numBones; i++ )
|
||
|
{
|
||
|
plMaxNodeBase *bone = boneArray[i-1];
|
||
|
plSceneObject* obj = bone->GetSceneObject();
|
||
|
const char *dbgBoneName = bone->GetName();
|
||
|
|
||
|
// Pick which drawable to point the DI to
|
||
|
UInt8 iDraw = 0;
|
||
|
|
||
|
/// Now create the actual bone DI, or grab it if it's already created
|
||
|
plDrawInterface *di = obj->GetVolatileDrawInterface();
|
||
|
if( di )
|
||
|
{
|
||
|
for( iDraw = 0; iDraw < di->GetNumDrawables(); iDraw++ )
|
||
|
{
|
||
|
if( di->GetDrawable(iDraw) == drawable )
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
plLocation nodeLoc = bone->GetLocation();
|
||
|
di = TRACKED_NEW plDrawInterface;
|
||
|
plKey diKey = hsgResMgr::ResMgr()->NewKey(GetKey()->GetName(), di, nodeLoc, GetLoadMask());
|
||
|
hsgResMgr::ResMgr()->AddViaNotify(diKey, TRACKED_NEW plObjRefMsg(obj->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
|
||
|
}
|
||
|
|
||
|
if( di->GetNumDrawables() <= iDraw )
|
||
|
{
|
||
|
UInt32 diIndex = drawable->NewDIMatrixIndex();
|
||
|
di->SetDrawableMeshIndex(iDraw, diIndex);
|
||
|
|
||
|
di->SetDrawable(iDraw, drawable);
|
||
|
}
|
||
|
|
||
|
|
||
|
plDISpanIndex& skinIndices = drawable->GetDISpans(di->GetDrawableMeshIndex(iDraw));
|
||
|
skinIndices.Append(baseMatrix + i);
|
||
|
|
||
|
drawable->SetInitialBone(baseMatrix + i, initialL2B[i], initialB2L[i]);
|
||
|
di->SetTransform(initialB2W[i], initialW2B[i]);
|
||
|
}
|
||
|
delete [] boneArray;
|
||
|
}
|
||
|
|
||
|
//// IMakeInstanceSpans //////////////////////////////////////////////////////
|
||
|
// Given an instance node, instances the geoSpans that the node owns and
|
||
|
// stores them in the given array.
|
||
|
|
||
|
hsBool plMaxNode::IMakeInstanceSpans( plMaxNode *node, hsTArray<plGeometrySpan *> &spanArray,
|
||
|
plErrorMsg *pErrMsg, plConvertSettings *settings )
|
||
|
{
|
||
|
UInt8 iDraw;
|
||
|
int index, i;
|
||
|
|
||
|
|
||
|
plSceneObject *obj = node->GetSceneObject();
|
||
|
if( !obj )
|
||
|
return false;
|
||
|
|
||
|
const plDrawInterface *di = obj->GetDrawInterface();
|
||
|
if( !di )
|
||
|
return false;
|
||
|
|
||
|
hsBool setVisDists = false;
|
||
|
hsScalar minDist, maxDist;
|
||
|
if( hsMaterialConverter::HasVisDists(this, 0, minDist, maxDist) )
|
||
|
{
|
||
|
setVisDists = true;
|
||
|
}
|
||
|
|
||
|
index = 0;
|
||
|
spanArray.Reset();
|
||
|
for( iDraw = 0; iDraw < di->GetNumDrawables(); iDraw++ )
|
||
|
{
|
||
|
plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable(iDraw));
|
||
|
if( !dr )
|
||
|
continue;
|
||
|
if( di->GetDrawableMeshIndex(iDraw) == (UInt32)-1 )
|
||
|
continue;
|
||
|
|
||
|
plDISpanIndex disi = dr->GetDISpans(di->GetDrawableMeshIndex(iDraw));
|
||
|
|
||
|
spanArray.ExpandAndZero( spanArray.GetCount() + disi.fIndices.GetCount() );
|
||
|
for( i = 0; i < disi.fIndices.GetCount(); i++ )
|
||
|
{
|
||
|
spanArray[ index ] = TRACKED_NEW plGeometrySpan;
|
||
|
spanArray[ index ]->MakeInstanceOf( dr->GetGeometrySpan( disi.fIndices[ i ] ) );
|
||
|
|
||
|
if( setVisDists )
|
||
|
{
|
||
|
spanArray[ index ]->fMinDist = (minDist);
|
||
|
spanArray[ index ]->fMaxDist = (maxDist);
|
||
|
}
|
||
|
|
||
|
dr->GetGeometrySpan(disi.fIndices[i])->fProps |= plGeometrySpan::kInstanced;
|
||
|
|
||
|
spanArray[ index++ ]->fProps |= plGeometrySpan::kInstanced;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Now that we have all of our instanced spans, we need to make sure we
|
||
|
// have the right materials. Why? There are some isolated cases (such as when "force
|
||
|
// material copy" is set) where we want to still instance the geometry but we want
|
||
|
// separate materials. In this case, GetMaterialArray() will return the right array of
|
||
|
// materials for our instanced node. However, since we've tossed everything except the
|
||
|
// final plGeometrySpans from MakeMesh(), we have to do a reverse lookup to see what
|
||
|
// materials get assigned to whom. GetMaterialArray() is guaranteed (according to Bob)
|
||
|
// to return materials in the same order for instanced nodes, so what we do is call
|
||
|
// GMA() for the old node and the new node (the old one should just be a lookup), then
|
||
|
// for each geoSpan look its old material up in the old array, find the matching material
|
||
|
// in the new array (i.e. same position) and assign that new material to the span.
|
||
|
#if 1 // Change this to 0 to just always use the same materials on instances (old, incorrect way)
|
||
|
Mtl *newMtl = GetMtl(), *origMtl = node->GetMtl();
|
||
|
if( newMtl != nil && newMtl == origMtl ) // newMtl should == origMtl, but check just in case
|
||
|
{
|
||
|
hsTArray<hsGMaterial *> oldMaterials, newMaterials;
|
||
|
|
||
|
if( hsMaterialConverter::IsMultiMat( newMtl ) )
|
||
|
{
|
||
|
for( i = 0; i < newMtl->NumSubMtls(); i++ )
|
||
|
{
|
||
|
hsMaterialConverter::Instance().GetMaterialArray( origMtl->GetSubMtl( i ), node, oldMaterials );
|
||
|
hsMaterialConverter::Instance().GetMaterialArray( newMtl->GetSubMtl( i ), this, newMaterials );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hsMaterialConverter::Instance().GetMaterialArray( origMtl, node, oldMaterials );
|
||
|
hsMaterialConverter::Instance().GetMaterialArray( newMtl, this, newMaterials );
|
||
|
}
|
||
|
|
||
|
/// Now we have two arrays to let us map, so walk through our geoSpans and translate them!
|
||
|
/// The good thing is that this is all done before the spans are added to the drawable,
|
||
|
/// so we don't have to worry about reffing or unreffing or any of that messiness; all of
|
||
|
/// that will be done for us as part of the normal AppendDISpans() process.
|
||
|
for( i = 0; i < spanArray.GetCount(); i++ )
|
||
|
{
|
||
|
int j;
|
||
|
|
||
|
|
||
|
// Find the span's original material
|
||
|
for( j = 0; j < oldMaterials.GetCount(); j++ )
|
||
|
{
|
||
|
if( spanArray[ i ]->fMaterial == oldMaterials[ j ] )
|
||
|
{
|
||
|
spanArray[ i ]->fMaterial = newMaterials[ j ];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//// IBuildInstanceList //////////////////////////////////////////////////////
|
||
|
// For the given object, builds a list of all the iNodes that have that
|
||
|
// object as their object. Returns the total node count
|
||
|
|
||
|
UInt32 plMaxNode::IBuildInstanceList( Object *obj, TimeValue t, hsTArray<plMaxNode *> &nodes, hsBool beMoreAccurate )
|
||
|
{
|
||
|
Object *thisObj = EvalWorldState( t ).obj;
|
||
|
DependentIterator di( obj );
|
||
|
ReferenceMaker *rm;
|
||
|
plMaxNode *node;
|
||
|
plKey sceneNodeKey = GetRoomKey();
|
||
|
|
||
|
|
||
|
/// Use the DependentIterator to loop through all the dependents of the object,
|
||
|
/// looking for nodes that use it
|
||
|
nodes.Reset();
|
||
|
while( rm = di.Next() )
|
||
|
{
|
||
|
if( rm->SuperClassID() == BASENODE_CLASS_ID )
|
||
|
{
|
||
|
node = (plMaxNode *)rm;
|
||
|
if( node->EvalWorldState( t ).obj == thisObj )
|
||
|
{
|
||
|
// Note: we CANNOT instance across pages (i.e. sceneNodes), so we need to make sure this
|
||
|
// INode will be in the same page as our master object
|
||
|
|
||
|
// Also note: RoomKeys will be nil until we've finished the first component pass, so when
|
||
|
// we test this in ConvertValidate(), the keys will be nil and all objects will be "in the
|
||
|
// same room", even though they're not. This is not too bad, though, since the worst that
|
||
|
// could happen is the object gets forced local even when there ends up not being any other
|
||
|
// instances of it in the same page. Ooooh.
|
||
|
|
||
|
if( sceneNodeKey == node->GetRoomKey() )
|
||
|
{
|
||
|
// Make sure the materials generated for both of these nodes will be the same
|
||
|
if( IMaterialsMatch( node, beMoreAccurate ) )
|
||
|
nodes.Append( node );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nodes.GetCount();
|
||
|
}
|
||
|
|
||
|
//// IMaterialsMatch /////////////////////////////////////////////////////////
|
||
|
// Given two nodes that are instances of each other, this function determines
|
||
|
// whether the resulting exported materials for both will be the same or not.
|
||
|
// If not, we need to not instance/share the geometry, since the UV channels
|
||
|
// could (and most likely will) be different.
|
||
|
// To test this, all we really need to do is check the return values of
|
||
|
// AlphaHackLayersNeeded(), since all the other material parameters will be
|
||
|
// identical due to these nodes being instances of each other.
|
||
|
|
||
|
hsBool plMaxNode::IMaterialsMatch( plMaxNode *otherNode, hsBool beMoreAccurate )
|
||
|
{
|
||
|
Mtl *mtl = GetMtl(), *otherMtl = otherNode->GetMtl();
|
||
|
if( mtl != otherMtl )
|
||
|
return false; // The two objects have different materials, no way we
|
||
|
// can try to instance them now
|
||
|
if( mtl == nil )
|
||
|
return true; // Both nodes have no material, works for me
|
||
|
|
||
|
// If we're not told to be accurate, then we just quit here. This is because
|
||
|
// in the early passes, we *can't* be more accurate, since we won't have all
|
||
|
// the info yet, so we don't bother checking it
|
||
|
if( !beMoreAccurate )
|
||
|
return true;
|
||
|
|
||
|
if( hsMaterialConverter::IsMultiMat( mtl ) )
|
||
|
{
|
||
|
int i;
|
||
|
for( i = 0; i < mtl->NumSubMtls(); i++ )
|
||
|
{
|
||
|
if( AlphaHackLayersNeeded( i ) != otherNode->AlphaHackLayersNeeded( i ) )
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if( AlphaHackLayersNeeded( -1 ) != otherNode->AlphaHackLayersNeeded( -1 ) )
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// They're close enough!
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::ShadeMesh(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
const char* dbgNodeName = GetName();
|
||
|
|
||
|
hsTArray<plGeometrySpan *> spanArray;
|
||
|
|
||
|
if( !(CanConvert() && GetDrawable()) )
|
||
|
return true;
|
||
|
|
||
|
plSceneObject* obj = GetSceneObject();
|
||
|
if( !obj )
|
||
|
return true;
|
||
|
|
||
|
const plDrawInterface* di = obj->GetDrawInterface();
|
||
|
if( !di )
|
||
|
return true;
|
||
|
|
||
|
UInt8 iDraw;
|
||
|
for( iDraw = 0; iDraw < di->GetNumDrawables(); iDraw++ )
|
||
|
{
|
||
|
plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable(iDraw));
|
||
|
if( !dr )
|
||
|
continue;
|
||
|
if( di->GetDrawableMeshIndex(iDraw) == (UInt32)-1 )
|
||
|
continue;
|
||
|
|
||
|
plDISpanIndex disi = dr->GetDISpans(di->GetDrawableMeshIndex(iDraw));
|
||
|
|
||
|
int i;
|
||
|
for( i = 0; i < disi.fIndices.GetCount(); i++ )
|
||
|
{
|
||
|
spanArray.Append( dr->GetGeometrySpan( disi.fIndices[ i ] ) );
|
||
|
}
|
||
|
|
||
|
hsMatrix44 l2w = GetLocalToWorld44();
|
||
|
hsMatrix44 w2l = GetWorldToLocal44();
|
||
|
|
||
|
/// Shade the spans now
|
||
|
// Either do vertex shading or generate a light map.
|
||
|
if( GetLightMapComponent() )
|
||
|
{
|
||
|
plLightMapGen::Instance().MakeMaps(this, l2w, w2l, spanArray, pErrMsg, nil);
|
||
|
|
||
|
// Since they were already pointers to the geometry spans, we don't have
|
||
|
// to re-stuff them. Horray!
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hsVertexShader::Instance().ShadeNode(this, l2w, w2l, spanArray);
|
||
|
}
|
||
|
|
||
|
if (settings && settings->fSceneViewer)
|
||
|
dr->RefreshDISpans(di->GetDrawableMeshIndex(iDraw));
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::MakeOccluder(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
if( !UserPropExists("Occluder") )
|
||
|
return true;
|
||
|
|
||
|
hsBool twoSided = UserPropExists("OccTwoSided");
|
||
|
hsBool isHole = UserPropExists("OccHole");
|
||
|
|
||
|
return ConvertToOccluder(pErrMsg, twoSided, isHole);
|
||
|
}
|
||
|
|
||
|
static void IRemoveCollinearPoints(hsTArray<Point3>& facePts)
|
||
|
{
|
||
|
int i;
|
||
|
for( i = 0; i < facePts.GetCount(); )
|
||
|
{
|
||
|
int j = i + 1 >= facePts.GetCount() ? 0 : i + 1;
|
||
|
int k = j + 1 >= facePts.GetCount() ? 0 : j + 1;
|
||
|
Point3 ab = FNormalize(facePts[i] - facePts[j]);
|
||
|
Point3 bc = FNormalize(facePts[j] - facePts[k]);
|
||
|
|
||
|
const float kDotCutoff = 1.f - 1.e-3f;
|
||
|
float dot = DotProd(ab, bc);
|
||
|
if( (dot < kDotCutoff) && (dot > -kDotCutoff) )
|
||
|
{
|
||
|
i++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
facePts.Remove(j);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::ConvertToOccluder(plErrorMsg* pErrMsg, hsBool twoSided, hsBool isHole)
|
||
|
{
|
||
|
if( !CanConvert() )
|
||
|
return false;
|
||
|
|
||
|
/// Get some stuff
|
||
|
plLocation nodeLoc = GetLocation();
|
||
|
|
||
|
hsBool moving = IsMovable();
|
||
|
if( moving )
|
||
|
moving++;
|
||
|
|
||
|
Matrix3 tmp(true);
|
||
|
|
||
|
Matrix3 maxL2V = GetLocalToVert(TimeValue(0));
|
||
|
Matrix3 maxV2L = GetVertToLocal(TimeValue(0));
|
||
|
|
||
|
hsTArray<plCullPoly> polys;
|
||
|
|
||
|
UInt32 polyInitFlags = plCullPoly::kNone;
|
||
|
if( isHole )
|
||
|
polyInitFlags |= plCullPoly::kHole;
|
||
|
else
|
||
|
if( twoSided )
|
||
|
polyInitFlags |= plCullPoly::kTwoSided;
|
||
|
|
||
|
Object *obj = EvalWorldState(TimeValue(0)).obj;
|
||
|
if( obj->CanConvertToType(triObjectClassID) )
|
||
|
{
|
||
|
TriObject *meshObj = (TriObject *)obj->ConvertToType(TimeValue(0), triObjectClassID);
|
||
|
if( meshObj )
|
||
|
{
|
||
|
|
||
|
Mesh mesh(meshObj->mesh);
|
||
|
|
||
|
const float kNormThresh = hsScalarPI / 20.f;
|
||
|
const float kEdgeThresh = hsScalarPI / 20.f;
|
||
|
const float kBias = 0.1f;
|
||
|
const float kMaxEdge = -1.f;
|
||
|
const DWORD kOptFlags = OPTIMIZE_SAVESMOOTHBOUNDRIES;
|
||
|
|
||
|
mesh.Optimize(
|
||
|
kNormThresh, // threshold of normal differences to preserve
|
||
|
kEdgeThresh, // When the angle between adjacent surface normals is less than this value the auto edge is performed (if the OPTIMIZE_AUTOEDGE flag is set). This angle is specified in radians.
|
||
|
kBias, // Increasing the bias parameter keeps triangles from becoming degenerate. range [0..1] (0 = no bias).
|
||
|
kMaxEdge, // This will prevent the optimize function from creating edges longer than this value. If this parameter is <=0 no limit is placed on the length of the edges.
|
||
|
kOptFlags, // Let them input using smoothing groups, but nothing else.
|
||
|
NULL); // progress bar
|
||
|
|
||
|
|
||
|
MNMesh mnMesh(mesh);
|
||
|
|
||
|
mnMesh.EliminateCollinearVerts();
|
||
|
mnMesh.EliminateCoincidentVerts(0.1f);
|
||
|
|
||
|
// Documentation recommends MakeConvexPolyMesh over MakePolyMesh. Naturally, MakePolyMesh works better.
|
||
|
// mnMesh.MakeConvexPolyMesh();
|
||
|
mnMesh.MakePolyMesh();
|
||
|
mnMesh.MakeConvex();
|
||
|
// mnMesh.MakePlanar(1.f * hsScalarPI / 180.f); // Completely ineffective. Winding up with majorly non-planar polys.
|
||
|
|
||
|
mnMesh.Transform(maxV2L);
|
||
|
|
||
|
polys.SetCount(mesh.getNumFaces());
|
||
|
polys.SetCount(0);
|
||
|
|
||
|
// Unfortunate problem here. Max is assuming that eventually this will get rendered, and so
|
||
|
// we need to avoid T-junctions. Fact is, T-junctions don't bother us at all, where-as colinear
|
||
|
// verts within a poly do (just as added overhead).
|
||
|
// So, to make this as painless (ha ha) as possible, we could detach each poly as we go to
|
||
|
// its own mnMesh, then eliminate colinear verts on that single poly mesh. Except
|
||
|
// EliminateCollinearVerts doesn't seem to actually do that. So we'll just have to
|
||
|
// manually detect and skip collinear verts.
|
||
|
hsTArray<Point3> facePts;
|
||
|
int i;
|
||
|
for( i = 0; i < mnMesh.numf; i++ )
|
||
|
{
|
||
|
MNFace& face = mnMesh.f[i];
|
||
|
|
||
|
facePts.SetCount(0);
|
||
|
int j;
|
||
|
for( j = 0; j < face.deg; j++ )
|
||
|
{
|
||
|
facePts.Append(mnMesh.v[face.vtx[j]].p);
|
||
|
}
|
||
|
IRemoveCollinearPoints(facePts);
|
||
|
|
||
|
if( facePts.GetCount() < 3 )
|
||
|
continue;
|
||
|
|
||
|
int lastAdded = 2;
|
||
|
|
||
|
plCullPoly* poly = polys.Push();
|
||
|
poly->fVerts.SetCount(0);
|
||
|
|
||
|
Point3 p;
|
||
|
hsPoint3 pt;
|
||
|
|
||
|
p = facePts[0];
|
||
|
pt.Set(p.x, p.y, p.z);
|
||
|
poly->fVerts.Append(pt);
|
||
|
|
||
|
p = facePts[1];
|
||
|
pt.Set(p.x, p.y, p.z);
|
||
|
poly->fVerts.Append(pt);
|
||
|
|
||
|
p = facePts[2];
|
||
|
pt.Set(p.x, p.y, p.z);
|
||
|
poly->fVerts.Append(pt);
|
||
|
|
||
|
for( j = lastAdded+1; j < facePts.GetCount(); j++ )
|
||
|
{
|
||
|
p = facePts[j];
|
||
|
pt.Set(p.x, p.y, p.z);
|
||
|
|
||
|
hsVector3 a = hsVector3(&pt, &poly->fVerts[0]);
|
||
|
hsVector3 b = hsVector3(&poly->fVerts[lastAdded], &poly->fVerts[0]);
|
||
|
hsVector3 c = hsVector3(&poly->fVerts[lastAdded-1], &poly->fVerts[0]);
|
||
|
|
||
|
hsVector3 aXb = a % b;
|
||
|
hsVector3 bXc = b % c;
|
||
|
|
||
|
hsFastMath::Normalize(aXb);
|
||
|
hsFastMath::Normalize(bXc);
|
||
|
|
||
|
|
||
|
hsScalar dotSq = aXb.InnerProduct(bXc);
|
||
|
dotSq *= dotSq;
|
||
|
|
||
|
const hsScalar kMinLenSq = 1.e-8f;
|
||
|
const hsScalar kMinDotFracSq = 0.998f * 0.998f;
|
||
|
|
||
|
hsScalar lenSq = aXb.MagnitudeSquared() * bXc.MagnitudeSquared();
|
||
|
if( lenSq < kMinLenSq )
|
||
|
continue;
|
||
|
|
||
|
// If not planar, move to new poly.
|
||
|
if( dotSq < lenSq * kMinDotFracSq )
|
||
|
{
|
||
|
poly->InitFromVerts(polyInitFlags);
|
||
|
|
||
|
poly = polys.Push();
|
||
|
plCullPoly* lastPoly = &polys[polys.GetCount()-2];
|
||
|
poly->fVerts.SetCount(0);
|
||
|
poly->fVerts.Append(lastPoly->fVerts[0]);
|
||
|
poly->fVerts.Append(lastPoly->fVerts[lastAdded]);
|
||
|
|
||
|
lastAdded = 1;
|
||
|
}
|
||
|
|
||
|
poly->fVerts.Append(pt);
|
||
|
lastAdded++;
|
||
|
}
|
||
|
|
||
|
poly->InitFromVerts(polyInitFlags);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( polys.GetCount() )
|
||
|
{
|
||
|
plOccluder* occ = nil;
|
||
|
plMobileOccluder* mob = nil;
|
||
|
if( moving )
|
||
|
{
|
||
|
mob = TRACKED_NEW plMobileOccluder;
|
||
|
occ = mob;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
occ = TRACKED_NEW plOccluder;
|
||
|
}
|
||
|
|
||
|
occ->SetPolyList(polys);
|
||
|
occ->ComputeFromPolys();
|
||
|
|
||
|
// Register it.
|
||
|
char tmpName[256];
|
||
|
if( GetKey() && GetKey()->GetName() && *GetKey()->GetName() )
|
||
|
{
|
||
|
sprintf(tmpName, "%s_%s", GetKey()->GetName(), "Occluder");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
static int numOcc = 0;
|
||
|
sprintf(tmpName, "%s_%4.4d", "Occluder", numOcc);
|
||
|
}
|
||
|
plKey key = hsgResMgr::ResMgr()->NewKey( tmpName, occ, nodeLoc, GetLoadMask() );
|
||
|
|
||
|
hsgResMgr::ResMgr()->AddViaNotify(occ->GetKey(), TRACKED_NEW plObjRefMsg(GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
|
||
|
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::MakeLight(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
|
||
|
if (!CanConvert())
|
||
|
return false;
|
||
|
|
||
|
if (!GetRunTimeLight())
|
||
|
return true;
|
||
|
|
||
|
/// Get some stuff
|
||
|
plLocation nodeLoc = GetLocation();
|
||
|
hsBool forceLocal = GetForceLocal();
|
||
|
|
||
|
hsMatrix44 l2w = GetLocalToWorld44();
|
||
|
hsMatrix44 w2l = GetWorldToLocal44();
|
||
|
|
||
|
hsMatrix44 lt2l = GetVertToLocal44();
|
||
|
hsMatrix44 l2lt = GetLocalToVert44();
|
||
|
|
||
|
|
||
|
plLightInfo* liInfo = nil;
|
||
|
|
||
|
|
||
|
liInfo = IMakeLight(pErrMsg, settings);
|
||
|
|
||
|
if( liInfo )
|
||
|
{
|
||
|
// 12.03.01 mcn - Um, we want RT lights to affect static objects if they're animated. So
|
||
|
// why wasn't this here a long time ago? :~
|
||
|
if( IsMovable() || IsAnimatedLight() )
|
||
|
liInfo->SetProperty(plLightInfo::kLPMovable, true);
|
||
|
|
||
|
liInfo->SetTransform(l2w, w2l);
|
||
|
liInfo->SetLocalToLight(l2lt, lt2l);
|
||
|
|
||
|
plKey key = hsgResMgr::ResMgr()->NewKey( GetKey()->GetName(), liInfo, nodeLoc, GetLoadMask() );
|
||
|
|
||
|
hsgResMgr::ResMgr()->AddViaNotify(liInfo->GetKey(), TRACKED_NEW plObjRefMsg(GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
|
||
|
|
||
|
|
||
|
// Only support projection for spots and dir lights for now.
|
||
|
if( plLimitedDirLightInfo::ConvertNoRef(liInfo) || plSpotLightInfo::ConvertNoRef(liInfo) )
|
||
|
{
|
||
|
// Have to do this after the drawable gets a key.
|
||
|
IGetProjection(liInfo, pErrMsg);
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
plLightInfo* plMaxNode::IMakeLight(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
|
||
|
plLightInfo* liInfo = nil;
|
||
|
Object *obj = EvalWorldState(timeVal).obj;
|
||
|
if( obj->ClassID() == Class_ID(OMNI_LIGHT_CLASS_ID, 0) )
|
||
|
liInfo = IMakeOmni(pErrMsg, settings);
|
||
|
else
|
||
|
if( (obj->ClassID() == Class_ID(SPOT_LIGHT_CLASS_ID, 0)) || (obj->ClassID() == Class_ID(FSPOT_LIGHT_CLASS_ID, 0)) )
|
||
|
liInfo = IMakeSpot(pErrMsg, settings);
|
||
|
else
|
||
|
if( (obj->ClassID() == Class_ID(DIR_LIGHT_CLASS_ID, 0)) || (obj->ClassID() == Class_ID(TDIR_LIGHT_CLASS_ID, 0)) )
|
||
|
liInfo = IMakeDirectional(pErrMsg, settings);
|
||
|
else
|
||
|
if( obj->ClassID() == RTOMNI_LIGHT_CLASSID )
|
||
|
liInfo = IMakeRTOmni(pErrMsg, settings);
|
||
|
else
|
||
|
if( (obj->ClassID() == RTSPOT_LIGHT_CLASSID) ) //|| (obj->ClassID() == Class_ID(FSPOT_LIGHT_CLASS_ID, 0)) )
|
||
|
liInfo = IMakeRTSpot(pErrMsg, settings);
|
||
|
else
|
||
|
if( (obj->ClassID() == RTDIR_LIGHT_CLASSID) ) //|| (obj->ClassID() == Class_ID(FSPOT_LIGHT_CLASS_ID, 0)) )
|
||
|
liInfo = IMakeRTDirectional(pErrMsg, settings);
|
||
|
else
|
||
|
if( obj->ClassID() == RTPDIR_LIGHT_CLASSID )
|
||
|
liInfo = IMakeRTProjDirectional( pErrMsg, settings );
|
||
|
|
||
|
return liInfo;
|
||
|
}
|
||
|
|
||
|
void plMaxNode::IGetLightAttenuation(plOmniLightInfo* liInfo, LightObject* light, LightState& ls)
|
||
|
{
|
||
|
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
|
||
|
|
||
|
hsScalar attenConst, attenLinear, attenQuadratic;
|
||
|
|
||
|
float intens = ls.intens >= 0 ? ls.intens : -ls.intens;
|
||
|
float attenEnd = ls.attenEnd;
|
||
|
// Decay type 0:None, 1:Linear, 2:Squared
|
||
|
if( ls.useAtten )
|
||
|
{
|
||
|
switch(((GenLight*)light)->GetDecayType())
|
||
|
{
|
||
|
case 0:
|
||
|
case 1:
|
||
|
attenConst = 1.f;
|
||
|
attenLinear = (intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / attenEnd;
|
||
|
attenQuadratic = 0;
|
||
|
break;
|
||
|
case 2:
|
||
|
attenConst = 1.f;
|
||
|
attenLinear = 0;
|
||
|
attenQuadratic = (intens * plSillyLightKonstants::GetFarPowerKonst() -1.f) / (attenEnd * attenEnd);
|
||
|
break;
|
||
|
case 3:
|
||
|
attenConst = intens;
|
||
|
attenLinear = 0.f;
|
||
|
attenQuadratic = 0.f;
|
||
|
liInfo->SetCutoffAttenuation( ( (GenLight *)light )->GetDecayRadius( timeVal ) );
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
attenConst = 1.f;
|
||
|
attenLinear = 0.f;
|
||
|
attenQuadratic = 0.f;
|
||
|
}
|
||
|
|
||
|
liInfo->SetConstantAttenuation(attenConst);
|
||
|
liInfo->SetLinearAttenuation(attenLinear);
|
||
|
liInfo->SetQuadraticAttenuation(attenQuadratic);
|
||
|
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::IGetRTLightAttenValues(IParamBlock2* ProperPB, hsScalar& attenConst, hsScalar& attenLinear, hsScalar& attenQuadratic, hsScalar &attenCutoff )
|
||
|
{
|
||
|
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
|
||
|
|
||
|
float intens = ProperPB->GetFloat(plRTLightBase::kIntensity, timeVal);
|
||
|
if( intens < 0 )
|
||
|
intens = -intens;
|
||
|
float attenEnd;
|
||
|
|
||
|
attenEnd = ProperPB->GetFloat(plRTLightBase::kAttenMaxFalloffEdit, timeVal);//ls.attenEnd;
|
||
|
|
||
|
// Decay Type New == 0 for Linear and 1 for Squared.... OLD and OBSOLETE:Decay type 0:None, 1:Linear, 2:Squared
|
||
|
// Oh, and now 2 = cutoff attenuation
|
||
|
if( ProperPB->GetInt(plRTLightBase::kUseAttenuationBool, timeVal))
|
||
|
{
|
||
|
switch(ProperPB->GetInt(plRTLightBase::kAttenTypeRadio, timeVal))//((GenLight*)light)->GetDecayType())
|
||
|
{
|
||
|
case 0:
|
||
|
attenConst = 1.f;
|
||
|
attenLinear = (intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / attenEnd;
|
||
|
if( attenLinear < 0 )
|
||
|
attenLinear = 0;
|
||
|
attenQuadratic = 0;
|
||
|
attenCutoff = attenEnd;
|
||
|
break;
|
||
|
case 1:
|
||
|
attenConst = 1.f;
|
||
|
attenLinear = 0;
|
||
|
attenQuadratic = (intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / (attenEnd * attenEnd);
|
||
|
if( attenQuadratic < 0 )
|
||
|
attenQuadratic = 0;
|
||
|
attenCutoff = attenEnd;
|
||
|
break;
|
||
|
case 2:
|
||
|
attenConst = intens;
|
||
|
attenLinear = 0.f;
|
||
|
attenQuadratic = 0.f;
|
||
|
attenCutoff = attenEnd;
|
||
|
break;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
attenConst = 1.f;
|
||
|
attenLinear = 0.f;
|
||
|
attenQuadratic = 0.f;
|
||
|
attenCutoff = 0.f;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void plMaxNode::IGetRTLightAttenuation(plOmniLightInfo* liInfo, IParamBlock2* ProperPB)
|
||
|
{
|
||
|
hsScalar attenConst, attenLinear, attenQuadratic, attenCutoff;
|
||
|
|
||
|
if( IGetRTLightAttenValues(ProperPB, attenConst, attenLinear, attenQuadratic, attenCutoff) )
|
||
|
{
|
||
|
liInfo->SetConstantAttenuation(attenConst);
|
||
|
liInfo->SetLinearAttenuation(attenLinear);
|
||
|
liInfo->SetQuadraticAttenuation(attenQuadratic);
|
||
|
liInfo->SetCutoffAttenuation( attenCutoff );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void plMaxNode::IGetLightColors(plLightInfo* liInfo, LightObject* light, LightState& ls)
|
||
|
{
|
||
|
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
|
||
|
|
||
|
Point3 color = light->GetRGBColor(timeVal);
|
||
|
float intensity = light->GetIntensity(timeVal);
|
||
|
|
||
|
color *= intensity;
|
||
|
|
||
|
liInfo->SetAmbient(hsColorRGBA().Set(0,0,0,1.f));
|
||
|
if( ls.affectDiffuse )
|
||
|
liInfo->SetDiffuse(hsColorRGBA().Set(color.x, color.y, color.z, intensity));
|
||
|
else
|
||
|
liInfo->SetDiffuse(hsColorRGBA().Set(0,0,0,intensity));
|
||
|
if( ls.affectSpecular )
|
||
|
liInfo->SetSpecular(hsColorRGBA().Set(color.x, color.y, color.z, intensity));
|
||
|
else
|
||
|
liInfo->SetSpecular(hsColorRGBA().Set(0,0,0,intensity));
|
||
|
|
||
|
}
|
||
|
|
||
|
void plMaxNode::IGetRTLightColors(plLightInfo* liInfo, IParamBlock2* ProperPB)
|
||
|
{
|
||
|
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
|
||
|
|
||
|
Point3 color = ProperPB->GetPoint3(plRTLightBase::kLightColor, timeVal);//light->GetRGBColor(timeVal);
|
||
|
float intensity = ProperPB->GetFloat(plRTLightBase::kIntensity, timeVal); //light->GetIntensity(timeVal);
|
||
|
|
||
|
color *= intensity;
|
||
|
|
||
|
liInfo->SetAmbient(hsColorRGBA().Set(0,0,0,1.f));
|
||
|
if( ProperPB->GetInt( plRTLightBase::kAffectDiffuse, timeVal ) )
|
||
|
liInfo->SetDiffuse(hsColorRGBA().Set(color.x, color.y, color.z, intensity));
|
||
|
else
|
||
|
liInfo->SetDiffuse(hsColorRGBA().Set(0,0,0,intensity));
|
||
|
if( ProperPB->GetInt(plRTLightBase::kSpec, timeVal)) //ls.affectSpecular )
|
||
|
{
|
||
|
Color spec = ProperPB->GetColor(plRTLightBase::kSpecularColorSwatch);
|
||
|
liInfo->SetSpecular(hsColorRGBA().Set(spec.r, spec.g, spec.b, intensity));
|
||
|
}
|
||
|
else
|
||
|
liInfo->SetSpecular(hsColorRGBA().Set(0,0,0,intensity));
|
||
|
}
|
||
|
|
||
|
void plMaxNode::IGetCone(plSpotLightInfo* liInfo, LightObject* light, LightState& ls)
|
||
|
{
|
||
|
|
||
|
hsScalar inner = hsScalarDegToRad(ls.hotsize);
|
||
|
hsScalar outer = hsScalarDegToRad(ls.fallsize);
|
||
|
|
||
|
/// 4.26.2001 mcn - MAX gives us full angles, but we want to store half angles
|
||
|
liInfo->SetSpotInner( inner / 2.0f );
|
||
|
liInfo->SetSpotOuter( outer / 2.0f );
|
||
|
liInfo->SetFalloff(1.f);
|
||
|
}
|
||
|
|
||
|
void plMaxNode::IGetRTCone(plSpotLightInfo* liInfo, IParamBlock2* ProperPB)
|
||
|
{
|
||
|
|
||
|
//TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
|
||
|
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
|
||
|
hsScalar inner, outer;
|
||
|
|
||
|
inner = hsScalarDegToRad(ProperPB->GetFloat(plRTLightBase::kHotSpot, timeVal)); //ls.hotsize);
|
||
|
outer = hsScalarDegToRad(ProperPB->GetFloat(plRTLightBase::kFallOff, timeVal)); //ls.fallsize);
|
||
|
|
||
|
/// 4.26.2001 mcn - MAX gives us full angles, but we want to store half angles
|
||
|
liInfo->SetSpotInner( inner / 2.0f );
|
||
|
liInfo->SetSpotOuter( outer / 2.0f );
|
||
|
liInfo->SetFalloff(1.f);
|
||
|
|
||
|
}
|
||
|
|
||
|
plLightInfo* plMaxNode::IMakeSpot(plErrorMsg* pErrMsg, plConvertSettings* settings)
|
||
|
{
|
||
|
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
|
||
|
Object *obj = EvalWorldState(timeVal).obj;
|
||
|
LightObject *light = (LightObject*)obj->ConvertToType(timeVal, Class_ID(SPOT_LIGHT_CLASS_ID,0));
|
||
|
if( !light )
|
||
|
light = (LightObject*)obj->ConvertToType(timeVal, Class_ID(FSPOT_LIGHT_CLASS_ID,0));
|
||
|
|
||
|
LightState ls;
|
||
|
if (!(REF_SUCCEED == light->EvalLightState(timeVal, Interval(timeVal, timeVal), &ls)))
|
||
|
{
|
||
|
pErrMsg->Set(true, GetName(), "Trouble evaluating light").CheckAndAsk();
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
plSpotLightInfo* spot = TRACKED_NEW plSpotLightInfo;
|
||
|
|
||
|
IGetLightColors(spot, light, ls);
|
||
|
|
||
|
IGetLightAttenuation(spot, light, ls);
|
||
|
|
||
|
IGetCone(spot, light, ls);
|
||
|
|
||
|
if( obj != light )
|
||
|
light->DeleteThis();
|
||
|
|
||
|
return spot;
|
||
|
}
|
||
|
|
||
|
plLightInfo* plMaxNode::IMakeOmni(plErrorMsg* pErrMsg, plConvertSettings* settings)
|
||
|
{
|
||
|
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
|
||
|
|
||
|
Object *obj = EvalWorldState(timeVal).obj;
|
||
|
LightObject *light = (LightObject*)obj->ConvertToType(timeVal,
|
||
|
Class_ID(OMNI_LIGHT_CLASS_ID,0));
|
||
|
|
||
|
LightState ls;
|
||
|
if (!(REF_SUCCEED == light->EvalLightState(timeVal, Interval(timeVal, timeVal), &ls)))
|
||
|
{
|
||
|
pErrMsg->Set(true, GetName(), "Trouble evaluating light").CheckAndAsk();
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
plOmniLightInfo* omni = TRACKED_NEW plOmniLightInfo;
|
||
|
|
||
|
IGetLightAttenuation(omni, light, ls);
|
||
|
|
||
|
IGetLightColors(omni, light, ls);
|
||
|
|
||
|
if( obj != light )
|
||
|
light->DeleteThis();
|
||
|
|
||
|
return omni;
|
||
|
}
|
||
|
|
||
|
plLightInfo* plMaxNode::IMakeDirectional(plErrorMsg* pErrMsg, plConvertSettings* settings)
|
||
|
{
|
||
|
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
|
||
|
|
||
|
Object *obj = EvalWorldState(timeVal).obj;
|
||
|
LightObject *light = (LightObject*)obj->ConvertToType(timeVal, Class_ID(DIR_LIGHT_CLASS_ID,0));
|
||
|
if( !light )
|
||
|
light = (LightObject*)obj->ConvertToType(timeVal, Class_ID(TDIR_LIGHT_CLASS_ID,0));
|
||
|
|
||
|
LightState ls;
|
||
|
if (!(REF_SUCCEED == light->EvalLightState(timeVal, Interval(timeVal, timeVal), &ls)))
|
||
|
{
|
||
|
pErrMsg->Set(true, GetName(), "Trouble evaluating light").CheckAndAsk();
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
plLightInfo* plasLight = nil;
|
||
|
if( light->GetProjMap() )
|
||
|
{
|
||
|
plLimitedDirLightInfo* ldl = TRACKED_NEW plLimitedDirLightInfo;
|
||
|
|
||
|
float sz = light->GetFallsize(timeVal, FOREVER);
|
||
|
float depth = 1000.f;
|
||
|
ldl->SetWidth(sz);
|
||
|
ldl->SetHeight(sz);
|
||
|
ldl->SetDepth(depth);
|
||
|
|
||
|
plasLight = ldl;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
|
||
|
plDirectionalLightInfo* direct = TRACKED_NEW plDirectionalLightInfo;
|
||
|
plasLight = direct;
|
||
|
}
|
||
|
|
||
|
IGetLightColors(plasLight, light, ls);
|
||
|
|
||
|
if( obj != light )
|
||
|
light->DeleteThis();
|
||
|
|
||
|
return plasLight;
|
||
|
}
|
||
|
|
||
|
plLightInfo* plMaxNode::IMakeRTSpot(plErrorMsg* pErrMsg, plConvertSettings* settings)
|
||
|
{
|
||
|
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
|
||
|
Object *obj = EvalWorldState(timeVal).obj;
|
||
|
Object *ThisObj = ((INode*)this)->GetObjectRef();
|
||
|
IParamBlock2* ThisObjPB = ThisObj->GetParamBlockByID(plRTLightBase::kBlkSpotLight);
|
||
|
|
||
|
|
||
|
if(!obj->CanConvertToType(RTSPOT_LIGHT_CLASSID))
|
||
|
{
|
||
|
pErrMsg->Set(true, GetName(), "Trouble evaluating light, improper classID").CheckAndAsk();
|
||
|
return nil;
|
||
|
|
||
|
}
|
||
|
|
||
|
plSpotLightInfo* spot = TRACKED_NEW plSpotLightInfo;
|
||
|
|
||
|
if(!ThisObjPB->GetInt(plRTLightBase::kLightOn))
|
||
|
spot->SetProperty(plLightInfo::kDisable, true);
|
||
|
|
||
|
IGetRTLightColors(spot,ThisObjPB);
|
||
|
|
||
|
IGetRTLightAttenuation(spot,ThisObjPB);
|
||
|
|
||
|
IGetRTCone(spot, ThisObjPB);
|
||
|
|
||
|
//plSpotModifier* liMod = TRACKED_NEW plSpotModifier;
|
||
|
|
||
|
//GetRTLightColAnim(ThisObjPB, liMod);
|
||
|
//GetRTLightAttenAnim(ThisObjPB, liMod);
|
||
|
//GetRTConeAnim(ThisObjPB, liMod);
|
||
|
|
||
|
//IAttachRTLightModifier(liMod);
|
||
|
|
||
|
// if( obj != light )
|
||
|
// light->DeleteThis();
|
||
|
|
||
|
return spot;
|
||
|
}
|
||
|
|
||
|
|
||
|
plLightInfo* plMaxNode::IMakeRTOmni(plErrorMsg* pErrMsg, plConvertSettings* settings)
|
||
|
{
|
||
|
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
|
||
|
|
||
|
Object *obj = EvalWorldState(timeVal).obj;
|
||
|
|
||
|
Object *ThisObj = ((INode*)this)->GetObjectRef();
|
||
|
IParamBlock2* ThisObjPB = ThisObj->GetParamBlockByID(plRTLightBase::kBlkOmniLight);
|
||
|
|
||
|
plOmniLightInfo* omni = TRACKED_NEW plOmniLightInfo;
|
||
|
|
||
|
if(!ThisObjPB->GetInt(plRTLightBase::kLightOn))
|
||
|
omni->SetProperty(plLightInfo::kDisable, true);
|
||
|
|
||
|
IGetRTLightAttenuation(omni, ThisObjPB);
|
||
|
|
||
|
IGetRTLightColors(omni, ThisObjPB);
|
||
|
|
||
|
//plOmniModifier* liMod = TRACKED_NEW plOmniModifier;
|
||
|
|
||
|
//GetRTLightColAnim(ThisObjPB, liMod);
|
||
|
//GetRTLightAttenAnim(ThisObjPB, liMod);
|
||
|
|
||
|
//IAttachRTLightModifier(liMod);
|
||
|
|
||
|
|
||
|
// if( obj != light )
|
||
|
// light->DeleteThis();
|
||
|
|
||
|
return omni;
|
||
|
}
|
||
|
|
||
|
|
||
|
plLightInfo* plMaxNode::IMakeRTDirectional(plErrorMsg* pErrMsg, plConvertSettings* settings)
|
||
|
{
|
||
|
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
|
||
|
|
||
|
Object *obj = EvalWorldState(timeVal).obj;
|
||
|
Object *ThisObj = ((INode*)this)->GetObjectRef();
|
||
|
IParamBlock2* ThisObjPB = ThisObj->GetParamBlockByID(plRTLightBase::kBlkTSpotLight);
|
||
|
|
||
|
|
||
|
plDirectionalLightInfo* direct = TRACKED_NEW plDirectionalLightInfo;
|
||
|
|
||
|
if(!ThisObjPB->GetInt(plRTLightBase::kLightOn))
|
||
|
direct->SetProperty(plLightInfo::kDisable, true);
|
||
|
|
||
|
IGetRTLightColors(direct, ThisObjPB);
|
||
|
|
||
|
//plLightModifier* liMod = TRACKED_NEW plLightModifier;
|
||
|
|
||
|
//GetRTLightColAnim(ThisObjPB, liMod);
|
||
|
|
||
|
//IAttachRTLightModifier(liMod);
|
||
|
|
||
|
|
||
|
// if( obj != light )
|
||
|
// light->DeleteThis();
|
||
|
|
||
|
return direct;
|
||
|
}
|
||
|
|
||
|
//// IMakeRTProjDirectional //////////////////////////////////////////////////
|
||
|
// Conversion function for RT Projected Directional lights
|
||
|
|
||
|
plLightInfo *plMaxNode::IMakeRTProjDirectional( plErrorMsg *pErrMsg, plConvertSettings *settings )
|
||
|
{
|
||
|
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
|
||
|
|
||
|
Object *obj = EvalWorldState(timeVal).obj;
|
||
|
Object *ThisObj = ((INode*)this)->GetObjectRef();
|
||
|
|
||
|
IParamBlock2 *mainPB = ThisObj->GetParamBlockByID( plRTLightBase::kBlkMain );
|
||
|
IParamBlock2 *projPB = ThisObj->GetParamBlockByID( plRTProjDirLight::kBlkProj );
|
||
|
|
||
|
plLimitedDirLightInfo *light = TRACKED_NEW plLimitedDirLightInfo;
|
||
|
|
||
|
light->SetWidth( projPB->GetFloat( plRTProjDirLight::kWidth ) );
|
||
|
light->SetHeight( projPB->GetFloat( plRTProjDirLight::kHeight ) );
|
||
|
light->SetDepth( projPB->GetFloat( plRTProjDirLight::kRange ) );
|
||
|
|
||
|
if( !mainPB->GetInt( plRTLightBase::kLightOn ) )
|
||
|
light->SetProperty( plLightInfo::kDisable, true );
|
||
|
|
||
|
IGetRTLightColors( light, mainPB );
|
||
|
|
||
|
//plLightModifier *liMod = TRACKED_NEW plLightModifier;
|
||
|
|
||
|
//GetRTLightColAnim( mainPB, liMod );
|
||
|
|
||
|
//IAttachRTLightModifier(liMod);
|
||
|
|
||
|
return light;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::IGetProjection(plLightInfo* li, plErrorMsg* pErrMsg)
|
||
|
{
|
||
|
hsBool persp = false;
|
||
|
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
|
||
|
Object *obj = EvalWorldState(timeVal).obj;
|
||
|
LightObject *light = (LightObject*)obj->ConvertToType(timeVal, RTSPOT_LIGHT_CLASSID);
|
||
|
|
||
|
if( light )
|
||
|
persp = true;
|
||
|
|
||
|
if( !light )
|
||
|
light = (LightObject*)obj->ConvertToType(timeVal, RTPDIR_LIGHT_CLASSID);
|
||
|
|
||
|
if( !light )
|
||
|
return false;
|
||
|
|
||
|
hsBool retVal = false;
|
||
|
Texmap* projMap = light->GetProjMap();
|
||
|
if( !projMap )
|
||
|
return false;
|
||
|
|
||
|
plConvert& convert = plConvert::Instance();
|
||
|
if( projMap->ClassID() != LAYER_TEX_CLASS_ID )
|
||
|
{
|
||
|
if( pErrMsg->Set(!(convert.fWarned & plConvert::kWarnedWrongProj), GetName(),
|
||
|
"Only Plasma Layers supported for projection").CheckAskOrCancel() )
|
||
|
{
|
||
|
convert.fWarned |= plConvert::kWarnedWrongProj;
|
||
|
}
|
||
|
pErrMsg->Set(false);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
IParamBlock2 *pb = nil;
|
||
|
|
||
|
Class_ID cid = obj->ClassID();
|
||
|
|
||
|
// Get the paramblock
|
||
|
if (cid == RTSPOT_LIGHT_CLASSID)
|
||
|
pb = obj->GetParamBlockByID(plRTLightBase::kBlkSpotLight);
|
||
|
else if (cid == RTOMNI_LIGHT_CLASSID)
|
||
|
pb = obj->GetParamBlockByID(plRTLightBase::kBlkOmniLight);
|
||
|
else if (cid == RTDIR_LIGHT_CLASSID)
|
||
|
pb = obj->GetParamBlockByID(plRTLightBase::kBlkTSpotLight);
|
||
|
else if (cid == RTPDIR_LIGHT_CLASSID)
|
||
|
pb = obj->GetParamBlockByID(plRTProjDirLight::kBlkProj);
|
||
|
|
||
|
// Have the layer converter process this layer directly
|
||
|
plLayerConverter::Instance().MuteWarnings();
|
||
|
plLayerInterface* proj = plLayerConverter::Instance().ConvertTexmap( projMap, this, 0, true, false );
|
||
|
plLayerConverter::Instance().UnmuteWarnings();
|
||
|
if( proj )
|
||
|
{
|
||
|
plLayer* projLay = plLayer::ConvertNoRef(proj->BottomOfStack());
|
||
|
if( projLay && projLay->GetTexture() )
|
||
|
{
|
||
|
if( persp )
|
||
|
projLay->SetMiscFlags(projLay->GetMiscFlags() | hsGMatState::kMiscPerspProjection);
|
||
|
else
|
||
|
projLay->SetMiscFlags(projLay->GetMiscFlags() | hsGMatState::kMiscOrthoProjection);
|
||
|
projLay->SetUVWSrc(projLay->GetUVWSrc() | plLayerInterface::kUVWPosition);
|
||
|
projLay->SetClampFlags(hsGMatState::kClampTexture);
|
||
|
projLay->SetZFlags(hsGMatState::kZNoZWrite);
|
||
|
|
||
|
switch( pb->GetInt(plRTLightBase::kProjTypeRadio) )
|
||
|
{
|
||
|
default:
|
||
|
case plRTLightBase::kIlluminate:
|
||
|
projLay->SetBlendFlags(hsGMatState::kBlendMult);
|
||
|
li->SetProperty(plLightInfo::kLPOverAll, false);
|
||
|
break;
|
||
|
case plRTLightBase::kAdd:
|
||
|
projLay->SetBlendFlags(hsGMatState::kBlendAdd);
|
||
|
li->SetProperty(plLightInfo::kLPOverAll, true);
|
||
|
break;
|
||
|
case plRTLightBase::kMult:
|
||
|
projLay->SetBlendFlags(hsGMatState::kBlendMult | hsGMatState::kBlendInvertColor | hsGMatState::kBlendInvertFinalColor);
|
||
|
li->SetProperty(plLightInfo::kLPOverAll, true);
|
||
|
break;
|
||
|
case plRTLightBase::kMADD:
|
||
|
projLay->SetBlendFlags(hsGMatState::kBlendMADD);
|
||
|
li->SetProperty(plLightInfo::kLPOverAll, true);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
hsgResMgr::ResMgr()->AddViaNotify(proj->GetKey(), TRACKED_NEW plGenRefMsg(li->GetKey(), plRefMsg::kOnCreate, 0, 0), plRefFlags::kActiveRef);
|
||
|
|
||
|
li->SetShadowCaster(false);
|
||
|
|
||
|
li->SetProperty(plLightInfo::kLPMovable, true);
|
||
|
|
||
|
|
||
|
retVal = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
char buff[256];
|
||
|
if( projMap && projMap->GetName() && *projMap->GetName() )
|
||
|
sprintf(buff, "Can't find projected bitmap - %s", projMap->GetName());
|
||
|
else
|
||
|
sprintf(buff, "Can't find projected bitmap - <unknown>");
|
||
|
if( pErrMsg->Set(!(convert.fWarned & plConvert::kWarnedMissingProj), GetName(),
|
||
|
buff).CheckAskOrCancel() )
|
||
|
convert.fWarned |= plConvert::kWarnedMissingProj;
|
||
|
pErrMsg->Set(false);
|
||
|
retVal = false;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if( pErrMsg->Set(!(convert.fWarned & plConvert::kWarnedWrongProj), GetName(),
|
||
|
"Failure to convert projection map - check type.").CheckAskOrCancel() )
|
||
|
convert.fWarned |= plConvert::kWarnedWrongProj;
|
||
|
pErrMsg->Set(false);
|
||
|
retVal = false;
|
||
|
}
|
||
|
|
||
|
if( light != obj )
|
||
|
light->DeleteThis();
|
||
|
|
||
|
return retVal;
|
||
|
}
|
||
|
/*
|
||
|
hsBool plMaxNode::IAttachRTLightModifier(plLightModifier* liMod)
|
||
|
{
|
||
|
if( liMod->HasAnima() )
|
||
|
{
|
||
|
liMod->DefaultAnimation();
|
||
|
CreateModifierKey(liMod, "_ANIMA");
|
||
|
AddModifier(liMod);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
delete liMod;
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
*/
|
||
|
bool plMaxNode::IsAnimatedLight()
|
||
|
{
|
||
|
Object *obj = GetObjectRef();
|
||
|
if (!obj)
|
||
|
return false;
|
||
|
|
||
|
const char* dbgNodeName = GetName();
|
||
|
|
||
|
Class_ID cid = obj->ClassID();
|
||
|
|
||
|
if (!(cid == RTSPOT_LIGHT_CLASSID ||
|
||
|
cid == RTOMNI_LIGHT_CLASSID ||
|
||
|
cid == RTDIR_LIGHT_CLASSID ||
|
||
|
cid == RTPDIR_LIGHT_CLASSID))
|
||
|
return false;
|
||
|
|
||
|
IParamBlock2 *pb = nil;
|
||
|
|
||
|
// Get the paramblock
|
||
|
if (cid == RTSPOT_LIGHT_CLASSID)
|
||
|
pb = obj->GetParamBlockByID(plRTLightBase::kBlkSpotLight);
|
||
|
else if (cid == RTOMNI_LIGHT_CLASSID)
|
||
|
pb = obj->GetParamBlockByID(plRTLightBase::kBlkOmniLight);
|
||
|
else if (cid == RTDIR_LIGHT_CLASSID)
|
||
|
pb = obj->GetParamBlockByID(plRTLightBase::kBlkTSpotLight);
|
||
|
else if (cid == RTPDIR_LIGHT_CLASSID)
|
||
|
pb = obj->GetParamBlockByID(plRTLightBase::kBlkMain);
|
||
|
|
||
|
hsControlConverter& cc = hsControlConverter::Instance();
|
||
|
|
||
|
// Is the color animated?
|
||
|
Control *colorCtl = pb->GetController( ParamID( plRTLightBase::kLightColor ) );
|
||
|
if (colorCtl && cc.HasKeyTimes(colorCtl))
|
||
|
return true;
|
||
|
|
||
|
// Is the specularity animated?
|
||
|
Control *specCtl = pb->GetController( ParamID( plRTLightBase::kSpecularColorSwatch ) );
|
||
|
if (specCtl && cc.HasKeyTimes(specCtl))
|
||
|
return true;
|
||
|
|
||
|
// Is the attenuation animated? (Spot and Omni lights only)
|
||
|
if (cid == RTSPOT_LIGHT_CLASSID || cid == RTOMNI_LIGHT_CLASSID)
|
||
|
{
|
||
|
Control *falloffCtl = pb->GetController( ParamID( plRTLightBase::kAttenMaxFalloffEdit ) );
|
||
|
if (falloffCtl && cc.HasKeyTimes(falloffCtl))
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Is the cone animated? (Spot only)
|
||
|
if (cid == RTSPOT_LIGHT_CLASSID)
|
||
|
{
|
||
|
Control *innerCtl = pb->GetController( ParamID( plRTLightBase::kHotSpot ) );
|
||
|
if (innerCtl && cc.HasKeyTimes(innerCtl))
|
||
|
return true;
|
||
|
|
||
|
Control *outerCtl = pb->GetController( ParamID( plRTLightBase::kFallOff ) );
|
||
|
if (outerCtl && cc.HasKeyTimes(outerCtl))
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
void plMaxNode::GetRTLightAttenAnim(IParamBlock2* ProperPB, plAGAnim *anim)
|
||
|
{
|
||
|
if( ProperPB->GetInt(plRTLightBase::kUseAttenuationBool, TimeValue(0)) )
|
||
|
{
|
||
|
Control* falloffCtl = ProperPB->GetController(ParamID(plRTLightBase::kAttenMaxFalloffEdit));
|
||
|
if( falloffCtl )
|
||
|
{
|
||
|
plLeafController* subCtl;
|
||
|
if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
|
||
|
subCtl = hsControlConverter::Instance().MakeScalarController(falloffCtl, this);
|
||
|
else
|
||
|
subCtl = hsControlConverter::Instance().MakeScalarController(falloffCtl, this,
|
||
|
anim->GetStart(), anim->GetEnd());
|
||
|
|
||
|
if( subCtl )
|
||
|
{
|
||
|
if( ProperPB->GetInt(plRTLightBase::kAttenTypeRadio, TimeValue(0)) == 2 )
|
||
|
{
|
||
|
// Animation of a cutoff attenuation, which only needs a scalar channel
|
||
|
plOmniCutoffApplicator *app = TRACKED_NEW plOmniCutoffApplicator();
|
||
|
app->SetChannelName(GetName());
|
||
|
plScalarControllerChannel *chan = TRACKED_NEW plScalarControllerChannel(subCtl);
|
||
|
app->SetChannel(chan);
|
||
|
anim->AddApplicator(app);
|
||
|
if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
|
||
|
anim->ExtendToLength(subCtl->GetLength());
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hsBool distSq = ProperPB->GetInt(plRTLightBase::kAttenTypeRadio, TimeValue(0));
|
||
|
|
||
|
int i;
|
||
|
for( i = 0; i < subCtl->GetNumKeys(); i++ )
|
||
|
{
|
||
|
hsScalarKey *key = subCtl->GetScalarKey(i);
|
||
|
if (key)
|
||
|
{
|
||
|
hsScalar attenEnd = key->fValue;
|
||
|
TimeValue tv = key->fFrame * MAX_TICKS_PER_FRAME;
|
||
|
hsScalar intens = ProperPB->GetFloat(plRTLightBase::kIntensity, tv);
|
||
|
hsScalar newVal = (intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / attenEnd;
|
||
|
if( distSq )
|
||
|
newVal /= attenEnd;
|
||
|
|
||
|
key->fValue = newVal;
|
||
|
}
|
||
|
hsBezScalarKey *bezKey = subCtl->GetBezScalarKey(i);
|
||
|
if (bezKey)
|
||
|
{
|
||
|
hsScalar attenEnd = bezKey->fValue;
|
||
|
TimeValue tv = bezKey->fFrame * MAX_TICKS_PER_FRAME;
|
||
|
hsScalar intens = ProperPB->GetFloat(plRTLightBase::kIntensity, tv);
|
||
|
hsScalar newVal = (intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / attenEnd;
|
||
|
if( distSq )
|
||
|
newVal /= attenEnd;
|
||
|
|
||
|
/// From the chain rule, fix our tangents.
|
||
|
bezKey->fInTan *= -(intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / (attenEnd*attenEnd);
|
||
|
if( distSq )
|
||
|
bezKey->fInTan *= 2.f / attenEnd;
|
||
|
|
||
|
bezKey->fOutTan *= -(intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / (attenEnd*attenEnd);
|
||
|
if( distSq )
|
||
|
bezKey->fOutTan *= 2.f / attenEnd;
|
||
|
|
||
|
bezKey->fValue = newVal;
|
||
|
}
|
||
|
}
|
||
|
plAGApplicator *app;
|
||
|
if (distSq)
|
||
|
app = TRACKED_NEW plOmniSqApplicator;
|
||
|
else
|
||
|
app = TRACKED_NEW plOmniApplicator;
|
||
|
|
||
|
app->SetChannelName(GetName());
|
||
|
plScalarControllerChannel *chan = TRACKED_NEW plScalarControllerChannel(subCtl);
|
||
|
app->SetChannel(chan);
|
||
|
anim->AddApplicator(app);
|
||
|
if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
|
||
|
anim->ExtendToLength(subCtl->GetLength());
|
||
|
|
||
|
hsScalar attenConst, attenLinear, attenQuadratic, attenCutoff;
|
||
|
IGetRTLightAttenValues(ProperPB, attenConst, attenLinear, attenQuadratic, attenCutoff);
|
||
|
|
||
|
plOmniLightInfo *info = plOmniLightInfo::ConvertNoRef(GetSceneObject()->GetGenericInterface(plOmniLightInfo::Index()));
|
||
|
if (info)
|
||
|
{
|
||
|
hsPoint3 initAtten(attenConst, attenLinear, attenQuadratic);
|
||
|
info->SetConstantAttenuation(attenConst);
|
||
|
info->SetLinearAttenuation(attenLinear);
|
||
|
info->SetQuadraticAttenuation(attenQuadratic);
|
||
|
}
|
||
|
else
|
||
|
hsAssert(false, "Failed to find light info");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void plMaxNode::IAdjustRTColorByIntensity(plController* ctl, IParamBlock2* ProperPB)
|
||
|
{
|
||
|
plLeafController* simp = plLeafController::ConvertNoRef(ctl);
|
||
|
plCompoundController* comp;
|
||
|
if( simp )
|
||
|
{
|
||
|
int i;
|
||
|
for( i = 0; i < simp->GetNumKeys(); i++ )
|
||
|
{
|
||
|
hsPoint3Key* key = simp->GetPoint3Key(i);
|
||
|
if (key)
|
||
|
{
|
||
|
TimeValue tv = key->fFrame * MAX_TICKS_PER_FRAME;
|
||
|
hsScalar intens = ProperPB->GetFloat(plRTLightBase::kIntensity, tv);
|
||
|
key->fValue *= intens;
|
||
|
}
|
||
|
hsBezPoint3Key* bezKey = simp->GetBezPoint3Key(i);
|
||
|
if (bezKey)
|
||
|
{
|
||
|
TimeValue tv = bezKey->fFrame * MAX_TICKS_PER_FRAME;
|
||
|
hsScalar intens = ProperPB->GetFloat(plRTLightBase::kIntensity, tv);
|
||
|
bezKey->fInTan *= intens;
|
||
|
bezKey->fOutTan *= intens;
|
||
|
bezKey->fValue *= intens;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if( comp = plCompoundController::ConvertNoRef(ctl) )
|
||
|
{
|
||
|
int j;
|
||
|
for( j = 0; j < 3; j++ )
|
||
|
{
|
||
|
IAdjustRTColorByIntensity(comp->GetController(j), ProperPB);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void plMaxNode::GetRTLightColAnim(IParamBlock2* ProperPB, plAGAnim *anim)
|
||
|
{
|
||
|
Control* ambientCtl = nil; // Ambient not currently supported
|
||
|
Control* colorCtl = ProperPB->GetController(ParamID(plRTLightBase::kLightColor));
|
||
|
Control* specCtl = ProperPB->GetController(ParamID(plRTLightBase::kSpecularColorSwatch));
|
||
|
plPointControllerChannel *chan;
|
||
|
|
||
|
if( ambientCtl )
|
||
|
{
|
||
|
plController* ctl;
|
||
|
if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
|
||
|
ctl = hsControlConverter::Instance().MakeColorController(ambientCtl, this);
|
||
|
else
|
||
|
ctl = hsControlConverter::Instance().MakeColorController(ambientCtl, this, anim->GetStart(), anim->GetEnd());
|
||
|
|
||
|
if( ctl )
|
||
|
{
|
||
|
plLightAmbientApplicator *app = TRACKED_NEW plLightAmbientApplicator();
|
||
|
app->SetChannelName(GetName());
|
||
|
chan = TRACKED_NEW plPointControllerChannel(ctl);
|
||
|
app->SetChannel(chan);
|
||
|
anim->AddApplicator(app);
|
||
|
if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
|
||
|
anim->ExtendToLength(ctl->GetLength());
|
||
|
}
|
||
|
}
|
||
|
if( colorCtl )
|
||
|
{
|
||
|
plController* ctl;
|
||
|
if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
|
||
|
ctl = hsControlConverter::Instance().MakeColorController(colorCtl, this);
|
||
|
else
|
||
|
ctl = hsControlConverter::Instance().MakeColorController(colorCtl, this, anim->GetStart(), anim->GetEnd());
|
||
|
|
||
|
if( ctl )
|
||
|
{
|
||
|
IAdjustRTColorByIntensity(ctl, ProperPB);
|
||
|
plLightDiffuseApplicator *app = TRACKED_NEW plLightDiffuseApplicator();
|
||
|
app->SetChannelName(GetName());
|
||
|
chan = TRACKED_NEW plPointControllerChannel(ctl);
|
||
|
app->SetChannel(chan);
|
||
|
anim->AddApplicator(app);
|
||
|
if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
|
||
|
anim->ExtendToLength(ctl->GetLength());
|
||
|
}
|
||
|
}
|
||
|
if( specCtl )
|
||
|
{
|
||
|
plController* ctl;
|
||
|
if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
|
||
|
ctl = hsControlConverter::Instance().MakeColorController(specCtl, this);
|
||
|
else
|
||
|
ctl = hsControlConverter::Instance().MakeColorController(specCtl, this, anim->GetStart(), anim->GetEnd());
|
||
|
|
||
|
if( ctl )
|
||
|
{
|
||
|
plLightSpecularApplicator *app = TRACKED_NEW plLightSpecularApplicator();
|
||
|
app->SetChannelName(GetName());
|
||
|
chan = TRACKED_NEW plPointControllerChannel(ctl);
|
||
|
app->SetChannel(chan);
|
||
|
anim->AddApplicator(app);
|
||
|
if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
|
||
|
anim->ExtendToLength(ctl->GetLength());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void plMaxNode::GetRTConeAnim(IParamBlock2* ProperPB, plAGAnim *anim)
|
||
|
{
|
||
|
Control* innerCtl = ProperPB->GetController(ParamID(plRTLightBase::kHotSpot));
|
||
|
Control* outerCtl = ProperPB->GetController(ParamID(plRTLightBase::kFallOff));
|
||
|
plScalarControllerChannel *chan;
|
||
|
|
||
|
if( innerCtl )
|
||
|
{
|
||
|
plLeafController* ctl;
|
||
|
if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
|
||
|
ctl = hsControlConverter::Instance().MakeScalarController(innerCtl, this);
|
||
|
else
|
||
|
ctl = hsControlConverter::Instance().MakeScalarController(innerCtl, this, anim->GetStart(), anim->GetEnd());
|
||
|
|
||
|
if( ctl )
|
||
|
{
|
||
|
plSpotInnerApplicator *app = TRACKED_NEW plSpotInnerApplicator();
|
||
|
app->SetChannelName(GetName());
|
||
|
chan = TRACKED_NEW plScalarControllerChannel(ctl);
|
||
|
app->SetChannel(chan);
|
||
|
anim->AddApplicator(app);
|
||
|
if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
|
||
|
anim->ExtendToLength(ctl->GetLength());
|
||
|
}
|
||
|
}
|
||
|
if( outerCtl )
|
||
|
{
|
||
|
plController* ctl;
|
||
|
if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
|
||
|
ctl = hsControlConverter::Instance().MakeScalarController(outerCtl, this);
|
||
|
else
|
||
|
ctl = hsControlConverter::Instance().MakeScalarController(outerCtl, this, anim->GetStart(), anim->GetEnd());
|
||
|
|
||
|
if( ctl )
|
||
|
{
|
||
|
plSpotOuterApplicator *app = TRACKED_NEW plSpotOuterApplicator();
|
||
|
app->SetChannelName(GetName());
|
||
|
chan = TRACKED_NEW plScalarControllerChannel(ctl);
|
||
|
app->SetChannel(chan);
|
||
|
anim->AddApplicator(app);
|
||
|
if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
|
||
|
anim->ExtendToLength(ctl->GetLength());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
plXImposterComp* plMaxNode::GetXImposterComp()
|
||
|
{
|
||
|
int count = NumAttachedComponents();
|
||
|
int i;
|
||
|
for( i = 0; i < count; i++ )
|
||
|
{
|
||
|
// See if any are a x-imposter component.
|
||
|
plComponentBase *comp = GetAttachedComponent(i);
|
||
|
if( comp && (comp->ClassID() == XIMPOSTER_COMP_CID) )
|
||
|
{
|
||
|
plXImposterComp* ximp = (plXImposterComp*)comp;
|
||
|
return ximp;
|
||
|
}
|
||
|
}
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
Point3 plMaxNode::GetFlexibility()
|
||
|
{
|
||
|
UInt32 count = NumAttachedComponents();
|
||
|
|
||
|
// Go through all the components attached to this node
|
||
|
for (UInt32 i = 0; i < count; i++)
|
||
|
{
|
||
|
// See if any are a flexibility component.
|
||
|
plComponentBase *comp = GetAttachedComponent(i);
|
||
|
if( comp && (comp->ClassID() == FLEXIBILITY_COMP_CID) )
|
||
|
{
|
||
|
plFlexibilityComponent* flex = (plFlexibilityComponent*)comp;
|
||
|
return flex->GetFlexibility();
|
||
|
}
|
||
|
}
|
||
|
return Point3(0.f, 0.f, 0.f);
|
||
|
}
|
||
|
|
||
|
plLightMapComponent* plMaxNode::GetLightMapComponent()
|
||
|
{
|
||
|
UInt32 count = NumAttachedComponents();
|
||
|
|
||
|
// Go through all the components attached to this node
|
||
|
for (UInt32 i = 0; i < count; i++)
|
||
|
{
|
||
|
// See if any are a flexibility component.
|
||
|
plComponentBase *comp = GetAttachedComponent(i);
|
||
|
if( comp && (comp->ClassID() == LIGHTMAP_COMP_CID) )
|
||
|
{
|
||
|
plLightMapComponent* lmap = (plLightMapComponent*)comp;
|
||
|
return lmap;
|
||
|
}
|
||
|
}
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
plDrawableCriteria plMaxNode::GetDrawableCriteria(hsBool needBlending, hsBool needSorting)
|
||
|
{
|
||
|
plRenderLevel level = needBlending ? GetRenderLevel(needBlending) : plRenderLevel::OpaqueRenderLevel();
|
||
|
|
||
|
if( GetSortAsOpaque() )
|
||
|
level.Set(plRenderLevel::kOpaqueMajorLevel, level.Minor());
|
||
|
|
||
|
UInt32 crit = 0;
|
||
|
if( needBlending )
|
||
|
{
|
||
|
if( needSorting && !GetNoFaceSort() )
|
||
|
crit |= plDrawable::kCritSortFaces;
|
||
|
if( !GetNoSpanSort() )
|
||
|
crit |= plDrawable::kCritSortSpans;
|
||
|
}
|
||
|
|
||
|
if( GetItinerant() )
|
||
|
crit |= plDrawable::kCritCharacter;
|
||
|
|
||
|
plDrawableCriteria retVal(crit, level, GetLoadMask());
|
||
|
|
||
|
if( GetEnviron() )
|
||
|
retVal.fType |= plDrawable::kEnviron;
|
||
|
if( GetEnvironOnly() )
|
||
|
retVal.fType &= ~plDrawable::kNormal;
|
||
|
|
||
|
return retVal;
|
||
|
}
|
||
|
|
||
|
//// IGetSceneNodeSpans //////////////////////////////////////////////////////
|
||
|
// Gets the required drawableSpans from a sceneNode. Creates a new one
|
||
|
// if it can't find one.
|
||
|
|
||
|
plDrawableSpans *plMaxNode::IGetSceneNodeSpans( plSceneNode *node, hsBool needBlending, hsBool needSorting )
|
||
|
{
|
||
|
|
||
|
plDrawableSpans *spans;
|
||
|
char tmpName[ 512 ];
|
||
|
plLocation nodeLoc = GetLocation();
|
||
|
|
||
|
if( !needBlending )
|
||
|
needSorting = false;
|
||
|
|
||
|
plDrawableCriteria crit = GetDrawableCriteria(needBlending, needSorting);
|
||
|
|
||
|
spans = plDrawableSpans::ConvertNoRef( node->GetMatchingDrawable( crit ) );
|
||
|
|
||
|
if( spans != nil )
|
||
|
{
|
||
|
if( GetNoSpanReSort() )
|
||
|
{
|
||
|
spans->SetNativeProperty(plDrawable::kPropNoReSort, true);
|
||
|
}
|
||
|
return spans;
|
||
|
}
|
||
|
|
||
|
|
||
|
/// Couldn't find--create and return it
|
||
|
spans = TRACKED_NEW plDrawableSpans;
|
||
|
if( needBlending )
|
||
|
{
|
||
|
/// Blending (deferred) spans
|
||
|
spans->SetCriteria( crit );
|
||
|
sprintf( tmpName, "%s_%8.8x_%xBlendSpans", node->GetKeyName(), crit.fLevel.fLevel, crit.fCriteria);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/// Normal spans
|
||
|
spans->SetCriteria( crit );
|
||
|
sprintf( tmpName, "%s_%8.8x_%xSpans", node->GetKeyName(), crit.fLevel.fLevel, crit.fCriteria);
|
||
|
}
|
||
|
|
||
|
if (GetSwappableGeomTarget() != (UInt32)-1 || GetSwappableGeom()) // We intend to swap geometry with this node... flag the drawable as volatile
|
||
|
{
|
||
|
if( GetItinerant() )
|
||
|
spans->SetNativeProperty(plDrawable::kPropCharacter, true);
|
||
|
|
||
|
spans->SetNativeProperty( plDrawable::kPropVolatile, true );
|
||
|
}
|
||
|
|
||
|
// Add a key for the spans
|
||
|
plKey key = hsgResMgr::ResMgr()->NewKey( tmpName, spans, nodeLoc, GetLoadMask() );
|
||
|
|
||
|
spans->SetSceneNode(node->GetKey());
|
||
|
|
||
|
/// Created! Return it now...
|
||
|
if( GetNoSpanReSort() )
|
||
|
spans->SetNativeProperty(plDrawable::kPropNoReSort, true);
|
||
|
|
||
|
return spans;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::SetupPropertiesPass(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
// TEMP
|
||
|
if (IsComponent())
|
||
|
return false;
|
||
|
// End TEMP
|
||
|
|
||
|
hsBool ret = true;
|
||
|
|
||
|
UInt32 count = NumAttachedComponents();
|
||
|
|
||
|
// Go through all the components attached to this node
|
||
|
for (UInt32 i = 0; i < count; i++)
|
||
|
{
|
||
|
// For each one, call the requested function. If any of the attached components
|
||
|
// return false this function will return false.
|
||
|
plComponentBase *comp = GetAttachedComponent(i);
|
||
|
|
||
|
if (comp->IsExternal())
|
||
|
{
|
||
|
if (!((plComponentExt*)comp)->SetupProperties(this, &gComponentTools, pErrMsg))
|
||
|
ret = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!((plComponent*)comp)->SetupProperties(this, pErrMsg))
|
||
|
ret = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( ret )
|
||
|
{
|
||
|
// Now loop through all the plPassMtlBase-derived materials that are applied to this node
|
||
|
Mtl *mtl = GetMtl();
|
||
|
if( mtl != nil && !GetParticleRelated() )
|
||
|
{
|
||
|
if( hsMaterialConverter::IsMultiMat( mtl ) || hsMaterialConverter::IsMultipassMat( mtl ) || hsMaterialConverter::IsCompositeMat( mtl ) )
|
||
|
{
|
||
|
int i;
|
||
|
for (i = 0; i < mtl->NumSubMtls(); i++)
|
||
|
{
|
||
|
plPassMtlBase *pass = plPassMtlBase::ConvertToPassMtl( mtl->GetSubMtl( i ) );
|
||
|
if( pass != nil )
|
||
|
{
|
||
|
if( !pass->SetupProperties( this, pErrMsg ) )
|
||
|
ret = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
plPassMtlBase *pass = plPassMtlBase::ConvertToPassMtl( mtl );
|
||
|
if( pass != nil )
|
||
|
{
|
||
|
if( !pass->SetupProperties( this, pErrMsg ) )
|
||
|
ret = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if( ret )
|
||
|
{
|
||
|
plMaxNode* parent = (plMaxNode*)GetParentNode();
|
||
|
if( parent && IsLegalDecal(false) )
|
||
|
{
|
||
|
AddRenderDependency(parent);
|
||
|
SetNoSpanSort(true);
|
||
|
SetNoFaceSort(true);
|
||
|
SetNoDeferDraw(true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::FirstComponentPass(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
// TEMP
|
||
|
if (IsComponent())
|
||
|
return false;
|
||
|
// End TEMP
|
||
|
|
||
|
hsBool ret = true;
|
||
|
|
||
|
if (!CanConvert())
|
||
|
return ret;
|
||
|
UInt32 count = NumAttachedComponents();
|
||
|
|
||
|
// Go through all the components attached to this node
|
||
|
for (UInt32 i = 0; i < count; i++)
|
||
|
{
|
||
|
// For each one, call the requested function. If any of the attached components
|
||
|
// return false this function will return false.
|
||
|
plComponentBase *comp = GetAttachedComponent(i);
|
||
|
|
||
|
if (comp->IsExternal())
|
||
|
{
|
||
|
if (!((plComponentExt*)comp)->PreConvert(this, &gComponentTools, pErrMsg))
|
||
|
ret = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!((plComponent*)comp)->PreConvert(this, pErrMsg))
|
||
|
ret = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::ConvertComponents(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
// TEMP
|
||
|
if (IsComponent())
|
||
|
return false;
|
||
|
// End TEMP
|
||
|
|
||
|
hsBool ret = true;
|
||
|
|
||
|
char *dbgNodeName = GetName();
|
||
|
if (!CanConvert())
|
||
|
return ret;
|
||
|
|
||
|
UInt32 count = NumAttachedComponents();
|
||
|
|
||
|
// Go through all the components attached to this node
|
||
|
for (UInt32 i = 0; i < count; i++)
|
||
|
{
|
||
|
// For each one, call the requested function. If any of the attached components
|
||
|
// return false this function will return false.
|
||
|
plComponentBase *comp = GetAttachedComponent(i);
|
||
|
|
||
|
if (comp->IsExternal())
|
||
|
{
|
||
|
if (!((plComponentExt*)comp)->Convert(this, &gComponentTools, pErrMsg))
|
||
|
ret = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!((plComponent*)comp)->Convert(this, pErrMsg))
|
||
|
ret = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::DeInitComponents(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
// TEMP
|
||
|
if (IsComponent())
|
||
|
return false;
|
||
|
// End TEMP
|
||
|
|
||
|
hsBool ret = true;
|
||
|
|
||
|
char *dbgNodeName = GetName();
|
||
|
if (!CanConvert())
|
||
|
return ret;
|
||
|
|
||
|
UInt32 count = NumAttachedComponents();
|
||
|
|
||
|
// Go through all the components attached to this node
|
||
|
for (UInt32 i = 0; i < count; i++)
|
||
|
{
|
||
|
// For each one, call the requested function. If any of the attached components
|
||
|
// return false this function will return false.
|
||
|
plComponentBase *comp = GetAttachedComponent(i);
|
||
|
|
||
|
if (comp->IsExternal())
|
||
|
{
|
||
|
if (!((plComponentExt*)comp)->DeInit(this, &gComponentTools, pErrMsg))
|
||
|
ret = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (!((plComponent*)comp)->DeInit(this, pErrMsg))
|
||
|
ret = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( ret )
|
||
|
{
|
||
|
// Now loop through all the plPassMtlBase-derived materials that are applied to this node
|
||
|
// So we can call ConvertDeInit() on them
|
||
|
Mtl *mtl = GetMtl();
|
||
|
if( mtl != nil && !GetParticleRelated() )
|
||
|
{
|
||
|
if( hsMaterialConverter::IsMultiMat( mtl ) || hsMaterialConverter::IsMultipassMat( mtl ) || hsMaterialConverter::IsCompositeMat( mtl ) )
|
||
|
{
|
||
|
int i;
|
||
|
for (i = 0; i < mtl->NumSubMtls(); i++)
|
||
|
{
|
||
|
plPassMtlBase *pass = plPassMtlBase::ConvertToPassMtl( mtl->GetSubMtl( i ) );
|
||
|
if( pass != nil )
|
||
|
{
|
||
|
if( !pass->ConvertDeInit( this, pErrMsg ) )
|
||
|
ret = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
plPassMtlBase *pass = plPassMtlBase::ConvertToPassMtl( mtl );
|
||
|
if( pass != nil )
|
||
|
{
|
||
|
if( !pass->ConvertDeInit( this, pErrMsg ) )
|
||
|
ret = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::ClearData(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaAgeChunk);
|
||
|
RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaDistChunk);
|
||
|
RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaRoomChunk);
|
||
|
|
||
|
RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaMaxNodeDataChunk);
|
||
|
// RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaSceneViewerChunk);
|
||
|
RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaLightChunk);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// HASAGMOD
|
||
|
// Little special-purpose thing to see if a node has an animation graph modifier on it.
|
||
|
plAGModifier *plMaxNode::HasAGMod()
|
||
|
{
|
||
|
char *name = GetName();
|
||
|
if (CanConvert())
|
||
|
{
|
||
|
plSceneObject *SO = GetSceneObject();
|
||
|
int numMods = SO->GetNumModifiers();
|
||
|
|
||
|
for (int i = 0; i < numMods; i++)
|
||
|
{
|
||
|
const plModifier *mod = SO->GetModifier(i);
|
||
|
|
||
|
if(plAGModifier::ConvertNoRef(mod)) {
|
||
|
return (plAGModifier *)mod;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
plAGMasterMod *plMaxNode::GetAGMasterMod()
|
||
|
{
|
||
|
char *name = GetName();
|
||
|
if (CanConvert())
|
||
|
{
|
||
|
plSceneObject *SO = GetSceneObject();
|
||
|
int numMods = SO->GetNumModifiers();
|
||
|
|
||
|
for (int i = 0; i < numMods; i++)
|
||
|
{
|
||
|
const plModifier *mod = SO->GetModifier(i);
|
||
|
|
||
|
if(plAGMasterMod::ConvertNoRef(mod)) {
|
||
|
return (plAGMasterMod *)mod;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
|
||
|
// SETUPBONESALIASESRECUR
|
||
|
void plMaxNode::SetupBonesAliasesRecur(const char *rootName)
|
||
|
{
|
||
|
if(CanConvert()) {
|
||
|
if (!HasAGMod()) {
|
||
|
const char *nameToUse;
|
||
|
|
||
|
// parse UserPropsBuf for entire BoneName line
|
||
|
char localName[256];
|
||
|
TSTR propsBuf;
|
||
|
GetUserPropBuffer(propsBuf);
|
||
|
char* start=strstr(propsBuf, "BoneName=");
|
||
|
if (!start)
|
||
|
start=strstr(propsBuf, "bonename=");
|
||
|
const int len = hsStrlen("BoneName=");
|
||
|
if(start && UserPropExists("BoneName"))
|
||
|
{
|
||
|
start+=len;
|
||
|
int i=0;
|
||
|
while(*start != '\n' && *start != '\0' && *start)
|
||
|
{
|
||
|
hsAssert(i<256, "localName overflow");
|
||
|
localName[i++]=*start++;
|
||
|
}
|
||
|
localName[i]=0;
|
||
|
|
||
|
nameToUse = localName;
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
const char *nodeName = GetName();
|
||
|
// char str[256];
|
||
|
// sprintf(str, "Missing 'BoneName=foo' UserProp, on object %s, using node name", nodeName ? nodeName : "?");
|
||
|
// hsAssert(false, str);
|
||
|
|
||
|
nameToUse = nodeName;
|
||
|
}
|
||
|
|
||
|
/* char aliasName[256];
|
||
|
sprintf(aliasName, "%s_%s", rootName, nameToUse);
|
||
|
|
||
|
plUoid* uoid = hsgResMgr::ResMgr()->FindAlias(aliasName, plSceneObject::Index());
|
||
|
if( !uoid )
|
||
|
{
|
||
|
plAliasModifier* pAlMod = TRACKED_NEW plAliasModifier;
|
||
|
pAlMod->SetAlias(aliasName);
|
||
|
AddModifier(pAlMod);
|
||
|
}
|
||
|
*/
|
||
|
plAGModifier *mod = TRACKED_NEW plAGModifier(nameToUse);
|
||
|
AddModifier(mod, GetName());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int j = 0;
|
||
|
for( j = 0; j < NumberOfChildren(); j++ )
|
||
|
((plMaxNode*)GetChildNode(j))->SetupBonesAliasesRecur(rootName);
|
||
|
}
|
||
|
|
||
|
void plMaxNode::SetDISceneNodeSpans( plDrawInterface *di, hsBool needBlending )
|
||
|
{
|
||
|
// This poorly named function is currently only used by ParticleComponent.
|
||
|
// di->GetNumDrawables() will always be zero. In general, particles only
|
||
|
// need a blending drawable, which can always be index zero since it's the only
|
||
|
// one.
|
||
|
di->SetDrawable( di->GetNumDrawables(),
|
||
|
IGetSceneNodeSpans(plSceneNode::ConvertNoRef( GetRoomKey()->GetObjectPtr() ),
|
||
|
needBlending));
|
||
|
}
|
||
|
|
||
|
plMaxNode* plMaxNode::GetBonesRoot()
|
||
|
{
|
||
|
ISkin* skin = FindSkinModifier();
|
||
|
if( !skin )
|
||
|
return nil;
|
||
|
|
||
|
INode* bone = skin->GetBone(0);
|
||
|
|
||
|
if( !bone )
|
||
|
return nil;
|
||
|
|
||
|
while( !bone->GetParentNode()->IsRootNode() )
|
||
|
bone = bone->GetParentNode();
|
||
|
|
||
|
plMaxNode* boneRoot = (plMaxNode*)bone;
|
||
|
|
||
|
if( !(boneRoot && boneRoot->CanConvert()) )
|
||
|
return nil;
|
||
|
|
||
|
return boneRoot;
|
||
|
}
|
||
|
|
||
|
void plMaxNode::GetBonesRootsRecur(hsTArray<plMaxNode*>& nodes)
|
||
|
{
|
||
|
plMaxNode* bRoot = GetBonesRoot();
|
||
|
if( bRoot )
|
||
|
{
|
||
|
int idx = nodes.Find(bRoot);
|
||
|
if( idx == nodes.kMissingIndex )
|
||
|
nodes.Append(bRoot);
|
||
|
}
|
||
|
|
||
|
int i;
|
||
|
for( i = 0; i < NumberOfChildren(); i++ )
|
||
|
((plMaxNode*)GetChildNode(i))->GetBonesRootsRecur(nodes);
|
||
|
}
|
||
|
|
||
|
plSceneObject* plMaxNode::MakeCharacterHierarchy(plErrorMsg *pErrMsg)
|
||
|
{
|
||
|
plSceneObject* playerRoot = GetSceneObject();
|
||
|
if( pErrMsg->Set(playerRoot->GetDrawInterface() != nil, GetName(), "Non-helper as player root").CheckAndAsk() )
|
||
|
return nil;
|
||
|
const char *playerRootName = GetName();
|
||
|
|
||
|
hsTArray<plMaxNode*> bonesRoots;
|
||
|
int i;
|
||
|
for( i = 0; i < NumberOfChildren(); i++ )
|
||
|
((plMaxNode*)GetChildNode(i))->GetBonesRootsRecur(bonesRoots);
|
||
|
|
||
|
if( pErrMsg->Set(bonesRoots.GetCount() > 1, playerRootName, "Found multiple bones hierarchies").CheckAndAsk() )
|
||
|
return nil;
|
||
|
|
||
|
if( bonesRoots.GetCount() )
|
||
|
{
|
||
|
bonesRoots[0]->SetupBonesAliasesRecur(playerRootName);
|
||
|
|
||
|
plSceneObject* boneRootObj = bonesRoots[0]->GetSceneObject();
|
||
|
|
||
|
if( pErrMsg->Set(boneRootObj == nil, playerRootName, "No scene object for the bones root").CheckAndAsk() )
|
||
|
return nil;
|
||
|
|
||
|
if( boneRootObj != playerRoot )
|
||
|
hsMessageBox("This avatar's bone hierarchy does not have the avatar root node linked as a parent. "
|
||
|
"This may cause the avatar draw incorrectly.", playerRootName, hsMessageBoxNormal);
|
||
|
}
|
||
|
|
||
|
return playerRoot;
|
||
|
}
|
||
|
|
||
|
// Takes all bones found on this node (and any descendents) and sets up a single palette
|
||
|
void plMaxNode::SetupBoneHierarchyPalette(plMaxBoneMap *bones /* = nil */)
|
||
|
{
|
||
|
const char* dbgNodeName = GetName();
|
||
|
|
||
|
if( !CanConvert() )
|
||
|
return;
|
||
|
|
||
|
if (GetBoneMap())
|
||
|
return;
|
||
|
|
||
|
if (bones == nil)
|
||
|
{
|
||
|
bones = TRACKED_NEW plMaxBoneMap();
|
||
|
bones->fOwner = this;
|
||
|
}
|
||
|
|
||
|
if (UserPropExists("Bone"))
|
||
|
bones->AddBone(this);
|
||
|
|
||
|
int i;
|
||
|
for (i = 0; i < NumBones(); i++)
|
||
|
bones->AddBone(GetBone(i));
|
||
|
|
||
|
SetBoneMap(bones);
|
||
|
|
||
|
for (i = 0; i < NumberOfChildren(); i++)
|
||
|
((plMaxNode*)GetChildNode(i))->SetupBoneHierarchyPalette(bones);
|
||
|
|
||
|
// If we were the one to start this whole thing, then sort all the bones now that
|
||
|
// they've been added.
|
||
|
if (bones->fOwner == this)
|
||
|
bones->SortBones();
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::IsLegalDecal(hsBool checkParent /* = true */)
|
||
|
{
|
||
|
Mtl *mtl = GetMtl();
|
||
|
if (mtl == nil || GetParticleRelated())
|
||
|
return false;
|
||
|
if (hsMaterialConverter::IsMultiMat(mtl))
|
||
|
{
|
||
|
int i;
|
||
|
for (i = 0; i < mtl->NumSubMtls(); i++)
|
||
|
{
|
||
|
if (!hsMaterialConverter::IsDecalMat(mtl->GetSubMtl(i)))
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
else if (!hsMaterialConverter::IsDecalMat(mtl))
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
int plMaxNode::NumUVWChannels()
|
||
|
{
|
||
|
hsBool deleteIt;
|
||
|
|
||
|
TriObject* triObj = GetTriObject(deleteIt);
|
||
|
if( triObj )
|
||
|
{
|
||
|
Mesh* mesh = &(triObj->mesh);
|
||
|
|
||
|
// note: There's been a bit of a change with UV accounting. There are two variables, numChannels
|
||
|
// and numBlendChannels. The first represents texture UV channels MAX gives us, the latter is
|
||
|
// the number of extra blending channels the current material uses to handle its effects.
|
||
|
|
||
|
// numMaps includes map #0, which is the vertex colors. So subtract 1 to get the # of uv maps...
|
||
|
int numChannels = mesh->getNumMaps() - 1;
|
||
|
if( numChannels > plGeometrySpan::kMaxNumUVChannels )
|
||
|
numChannels = plGeometrySpan::kMaxNumUVChannels;
|
||
|
|
||
|
/// Check the mapping channels. See, MAX likes to tell us we have mapping channels
|
||
|
/// but the actual channel pointer is nil. When MAX tries to render the scene, it says that the
|
||
|
/// object in question has no UV channel. So apparently we have to check numChannels *and*
|
||
|
/// that each mapChannel is non-nil...
|
||
|
int i;
|
||
|
for( i = 0; i < numChannels; i++ )
|
||
|
{
|
||
|
// i + 1 is exactly what IGenerateUVs uses, so I'm not questioning it...
|
||
|
if( mesh->mapFaces( i + 1 ) == nil )
|
||
|
{
|
||
|
numChannels = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
int numUsed = hsMaterialConverter::MaxUsedUVWSrc(this, GetMtl());
|
||
|
plLightMapComponent* lmc = GetLightMapComponent();
|
||
|
if( lmc )
|
||
|
{
|
||
|
if( (lmc->GetUVWSrc() < numChannels) && (lmc->GetUVWSrc() >= numUsed) )
|
||
|
numUsed = lmc->GetUVWSrc() + 1;
|
||
|
}
|
||
|
if( numChannels > numUsed )
|
||
|
numChannels = numUsed;
|
||
|
|
||
|
if( GetWaterDecEnv() )
|
||
|
numChannels = 3;
|
||
|
|
||
|
if( deleteIt )
|
||
|
triObj->DeleteThis();
|
||
|
|
||
|
return numChannels;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
//// IGet/SetCachedAlphaHackValue ////////////////////////////////////////////
|
||
|
// Pair of functions to handle accessing the TArray cache on plMaxNodeData.
|
||
|
// See AlphaHackLayersNeeded() for details.
|
||
|
|
||
|
int plMaxNode::IGetCachedAlphaHackValue( int iSubMtl )
|
||
|
{
|
||
|
plMaxNodeData *pDat = GetMaxNodeData();
|
||
|
if( pDat == nil )
|
||
|
return -1;
|
||
|
|
||
|
hsTArray<int> *cache = pDat->GetAlphaHackLayersCache();
|
||
|
if( cache == nil )
|
||
|
return -1;
|
||
|
|
||
|
iSubMtl++;
|
||
|
if( iSubMtl >= cache->GetCount() )
|
||
|
return -1;
|
||
|
|
||
|
return (*cache)[ iSubMtl ];
|
||
|
}
|
||
|
|
||
|
void plMaxNode::ISetCachedAlphaHackValue( int iSubMtl, int value )
|
||
|
{
|
||
|
plMaxNodeData *pDat = GetMaxNodeData();
|
||
|
if( pDat == nil )
|
||
|
return;
|
||
|
|
||
|
hsTArray<int> *cache = pDat->GetAlphaHackLayersCache();
|
||
|
if( cache == nil )
|
||
|
{
|
||
|
cache = TRACKED_NEW hsTArray<int>;
|
||
|
pDat->SetAlphaHackLayersCache( cache );
|
||
|
}
|
||
|
|
||
|
iSubMtl++;
|
||
|
|
||
|
if( iSubMtl >= cache->GetCount() )
|
||
|
{
|
||
|
int i = cache->GetCount();
|
||
|
cache->ExpandAndZero( iSubMtl + 1 );
|
||
|
for( ; i < cache->GetCount(); i++ )
|
||
|
(*cache)[ i ] = -1;
|
||
|
}
|
||
|
|
||
|
(*cache)[ iSubMtl ] = value;
|
||
|
}
|
||
|
|
||
|
//// AlphaHackLayersNeeded ///////////////////////////////////////////////////
|
||
|
// Updated 8.13.02 mcn - Turns out this function is actually very slow, and
|
||
|
// it also happens to be used a lot in testing instanced objects and whether
|
||
|
// they really can be instanced or not. Since the return value of this
|
||
|
// function will be constant after the SetupProperties() pass (and undefined
|
||
|
// before), we cache the value now after the first time we calculate it.
|
||
|
// Note: mf said that putting long comments in are good so long as most of
|
||
|
// them aren't obscenities, so I'm trying to keep the #*$&(*#$ obscenities
|
||
|
// to a minimum here.
|
||
|
|
||
|
int plMaxNode::AlphaHackLayersNeeded(int iSubMtl)
|
||
|
{
|
||
|
const char* dbgNodeName = GetName();
|
||
|
|
||
|
int cached = IGetCachedAlphaHackValue( iSubMtl );
|
||
|
if( cached != -1 )
|
||
|
return cached;
|
||
|
|
||
|
int numVtxOpacChanAvail = VtxAlphaNotAvailable() ? 0 : 1;
|
||
|
|
||
|
int numVtxOpacChanNeeded = hsMaterialConverter::NumVertexOpacityChannelsRequired(this, iSubMtl);
|
||
|
|
||
|
cached = numVtxOpacChanNeeded - numVtxOpacChanAvail;
|
||
|
ISetCachedAlphaHackValue( iSubMtl, cached );
|
||
|
return cached;
|
||
|
}
|
||
|
|
||
|
// Will our lighting pay attention to vertex alpha values?
|
||
|
hsBool plMaxNode::VtxAlphaNotAvailable()
|
||
|
{
|
||
|
if( NonVtxPreshaded() || GetParticleRelated())
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
hsBool plMaxNode::NonVtxPreshaded()
|
||
|
{
|
||
|
if( GetForceMatShade() )
|
||
|
return false;
|
||
|
|
||
|
if( GetAvatarSO() != nil ||
|
||
|
hsMaterialConverter::Instance().HasMaterialDiffuseOrOpacityAnimation(this) )
|
||
|
return false;
|
||
|
|
||
|
if( GetRunTimeLight() && !hsMaterialConverter::Instance().HasEmissiveLayer(this) )
|
||
|
return true;
|
||
|
|
||
|
return( GetLightMapComponent() != nil );
|
||
|
}
|
||
|
|
||
|
TriObject* plMaxNode::GetTriObject(hsBool& deleteIt)
|
||
|
{
|
||
|
// Get da object
|
||
|
Object *obj = EvalWorldState(TimeValue(0)).obj;
|
||
|
if( obj == nil )
|
||
|
return nil;
|
||
|
|
||
|
if( !obj->CanConvertToType(triObjectClassID) )
|
||
|
return nil;
|
||
|
|
||
|
// Convert to triMesh object
|
||
|
TriObject *meshObj = (TriObject *)obj->ConvertToType(TimeValue(0), triObjectClassID);
|
||
|
if( meshObj == nil )
|
||
|
return nil;
|
||
|
|
||
|
deleteIt = meshObj != obj;
|
||
|
|
||
|
return meshObj;
|
||
|
}
|
||
|
|
||
|
//// GetNextSoundIdx /////////////////////////////////////////////////////////
|
||
|
// Starting at 0, returns an incrementing index for each maxNode. Useful for
|
||
|
// assigning indices to sound objects attached to the node.
|
||
|
|
||
|
UInt32 plMaxNode::GetNextSoundIdx( void )
|
||
|
{
|
||
|
UInt32 idx = GetSoundIdxCounter();
|
||
|
SetSoundIdxCounter( idx + 1 );
|
||
|
return idx;
|
||
|
}
|
||
|
|
||
|
//// IsPhysical //////////////////////////////////////////////////////////////
|
||
|
// Fun temp hack function to tell if a maxNode is physical. Useful after
|
||
|
// preConvert (checks for a physical on the simInterface)
|
||
|
|
||
|
hsBool plMaxNode::IsPhysical( void )
|
||
|
{
|
||
|
if( GetSceneObject() && GetSceneObject()->GetSimulationInterface() &&
|
||
|
GetSceneObject()->GetSimulationInterface()->GetPhysical() )
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
plPhysicalProps *plMaxNode::GetPhysicalProps()
|
||
|
{
|
||
|
plMaxNodeData *pDat = GetMaxNodeData();
|
||
|
if (pDat)
|
||
|
return pDat->GetPhysicalProps();
|
||
|
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
//// FindPageKey /////////////////////////////////////////////////////////////
|
||
|
// Little helper function. Calls FindKey() in the resManager using the location (page) of this node
|
||
|
|
||
|
plKey plMaxNode::FindPageKey( UInt16 classIdx, const char *name )
|
||
|
{
|
||
|
return hsgResMgr::ResMgr()->FindKey( plUoid( GetLocation(), classIdx, name ) );
|
||
|
}
|
||
|
|
||
|
char *plMaxNode::GetAgeName()
|
||
|
{
|
||
|
int i;
|
||
|
for (i = 0; i < NumAttachedComponents(); i++)
|
||
|
{
|
||
|
plComponentBase *comp = GetAttachedComponent(i);
|
||
|
if (comp->ClassID() == PAGEINFO_CID)
|
||
|
return ((plPageInfoComponent*)comp)->GetAgeName();
|
||
|
}
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
// create a list of keys used by the run-time interface for things like
|
||
|
// determining cursor changes, what kind of object this is, etc.
|
||
|
// we're doing this here because multiple logic triggers can be attached to a
|
||
|
// single object and tracking down all their run-time counterpart objects (who might
|
||
|
// need a message sent to them) is a huge pain and very ugly. This will capture anything
|
||
|
// important in a single list.
|
||
|
|
||
|
hsBool plMaxNode::MakeIfaceReferences(plErrorMsg *pErrMsg, plConvertSettings *settings)
|
||
|
{
|
||
|
hsBool ret = true;
|
||
|
|
||
|
char *dbgNodeName = GetName();
|
||
|
if (!CanConvert())
|
||
|
return ret;
|
||
|
|
||
|
UInt32 count = GetSceneObject()->GetNumModifiers();
|
||
|
hsTArray<plKey> keys;
|
||
|
// Go through all the modifiers attached to this node's scene object
|
||
|
// and grab keys for objects who we would need to send interface messages to
|
||
|
for (UInt32 i = 0; i < count; i++)
|
||
|
{
|
||
|
const plModifier* pMod = GetSceneObject()->GetModifier(i);
|
||
|
// right now all we care about are these, but I guarentee you we will
|
||
|
// care about more as the interface gets more complex
|
||
|
const plPickingDetector* pDet = plPickingDetector::ConvertNoRef(pMod);
|
||
|
const plLogicModifier* pLog = plLogicModifier::ConvertNoRef(pMod);
|
||
|
if( pDet )
|
||
|
{
|
||
|
for (int j = 0; j < pDet->GetNumReceivers(); j++)
|
||
|
keys.Append(pDet->GetReceiver(j));
|
||
|
}
|
||
|
else
|
||
|
if( pLog )
|
||
|
{
|
||
|
keys.Append(pLog->GetKey());
|
||
|
}
|
||
|
}
|
||
|
// if there is anything there, create an 'interface object modifier' which simply stores
|
||
|
// the list in a handy form
|
||
|
if (keys.Count())
|
||
|
{
|
||
|
plInterfaceInfoModifier* pMod = TRACKED_NEW plInterfaceInfoModifier;
|
||
|
|
||
|
plKey modifierKey = hsgResMgr::ResMgr()->NewKey(GetName(), pMod, GetLocation(), GetLoadMask());
|
||
|
hsgResMgr::ResMgr()->AddViaNotify(modifierKey, TRACKED_NEW plObjRefMsg(GetSceneObject()->GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kModifier), plRefFlags::kActiveRef);
|
||
|
|
||
|
for(int i = 0; i < keys.Count(); i++)
|
||
|
pMod->AddRefdKey(keys[i]);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|