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.
4116 lines
136 KiB
4116 lines
136 KiB
/*==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 "plgDispatch.h" |
|
#include "hsFastMath.h" |
|
#include "pnKeyedObject/plKey.h" |
|
#include "plRenderLevel.h" |
|
#include "hsSTLStream.h" |
|
#include "hsStringTokenizer.h" |
|
#include "hsTemplates.h" |
|
|
|
#include "plMaxNode.h" |
|
#include "plMaxNodeData.h" |
|
#include "MaxComponent/plComponent.h" |
|
|
|
#include <guplib.h> |
|
#include <iparamm2.h> |
|
#include <iskin.h> |
|
#include <mnmath.h> |
|
#include <utilapi.h> |
|
#pragma hdrstop |
|
|
|
#include "GlobalUtility.h" |
|
#include "plPluginResManager.h" |
|
|
|
#include "MaxConvert/plConvert.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 "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 "plAnimation/plAGModifier.h" |
|
#include "plAnimation/plAGAnim.h" |
|
#include "plAnimation/plPointChannel.h" |
|
#include "plAnimation/plScalarChannel.h" |
|
#include "plAnimation/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 "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/plAnimComponent.h" |
|
#include "MaxComponent/plComponentTools.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; |
|
|
|
bool 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 plString &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 plString &segName, hsTArray<plKey>& keys); |
|
// In plAudioComponents |
|
int GetSoundNameAndIdx(plComponentBase *comp, plMaxNodeBase *node, const char*& name); |
|
|
|
static plString GetAnimCompAnimName(plComponentBase *comp) |
|
{ |
|
if (comp->ClassID() == ANIM_COMP_CID || comp->ClassID() == ANIM_GROUP_COMP_CID) |
|
return ((plAnimComponentBase*)comp)->GetAnimName(); |
|
return plString::Null; |
|
} |
|
|
|
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_t plMaxBoneMap::GetIndex(plMaxNodeBase *bone) |
|
{ |
|
hsAssert(fBones.find(bone) != fBones.end(), "Bone missing in remap!"); |
|
return fBones[bone]; |
|
} |
|
|
|
uint32_t plMaxBoneMap::GetBaseMatrixIndex(plDrawable *draw) |
|
{ |
|
if (fBaseMatrices.find(draw) == fBaseMatrices.end()) |
|
return (uint32_t)-1; |
|
|
|
return fBaseMatrices[draw]; |
|
} |
|
|
|
void plMaxBoneMap::SetBaseMatrixIndex(plDrawable *draw, uint32_t 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 = 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++) |
|
{ |
|
bool 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 plString& name) |
|
{ |
|
plKey modKey = pMod->GetKey(); |
|
if (!modKey) |
|
modKey = hsgResMgr::ResMgr()->NewKey(name, pMod, GetLocation()); |
|
hsgResMgr::ResMgr()->AddViaNotify(modKey, new plObjRefMsg(GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kModifier), plRefFlags::kActiveRef); |
|
return modKey; |
|
} |
|
|
|
bool 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. |
|
bool 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; |
|
} |
|
|
|
bool 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. |
|
bool 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_t 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; |
|
} |
|
|
|
bool 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((const char *)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((const char *)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_t 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); |
|
} |
|
} |
|
} |
|
} |
|
|
|
bool 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 = new plSceneObject; |
|
objKey = hsgResMgr::ResMgr()->NewKey(plString::FromUtf8(GetName()), pso, nodeLoc, GetLoadMask()); |
|
|
|
// Remember info in MaxNodeData block for later |
|
plMaxNodeData *pDat = GetMaxNodeData(); |
|
pDat->SetKey(objKey); |
|
pDat->SetSceneObject(pso); |
|
|
|
CheckSynchOptions(pso); |
|
|
|
return true; |
|
} |
|
|
|
bool 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; |
|
} |
|
|
|
bool 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" |
|
|
|
bool 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 = std::min(mesh.fVerts[i].fX, minV.fX); |
|
minV.fY = std::min(mesh.fVerts[i].fY, minV.fY); |
|
minV.fZ = std::min(mesh.fVerts[i].fZ, minV.fZ); |
|
maxV.fX = std::max(mesh.fVerts[i].fX, maxV.fX); |
|
maxV.fY = std::max(mesh.fVerts[i].fY, maxV.fY); |
|
maxV.fZ = std::max(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 = std::min(mesh.fVerts[i].fX, minV.fX); |
|
minV.fY = std::min(mesh.fVerts[i].fY, minV.fY); |
|
minV.fZ = std::min(mesh.fVerts[i].fZ, minV.fZ); |
|
maxV.fX = std::max(mesh.fVerts[i].fX, maxV.fX); |
|
maxV.fY = std::max(mesh.fVerts[i].fY, maxV.fY); |
|
maxV.fZ = std::max(mesh.fVerts[i].fZ, maxV.fZ); |
|
} |
|
hsPoint3 width = maxV - minV; |
|
recipe.radius = std::max({ width.fX, 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 = new plPXPhysical; |
|
|
|
// add the object to the resource manager, keyed to the new name |
|
plLocation nodeLoc = GetKey()->GetUoid().GetLocation(); |
|
plString 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 = new plSimulationInterface; |
|
plKey pSiKey = hsgResMgr::ResMgr()->NewKey(objName, si, nodeLoc, GetLoadMask()); |
|
|
|
// link the simulation interface to the scene object |
|
hsgResMgr::ResMgr()->AddViaNotify(pSiKey, new plObjRefMsg(GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef); |
|
|
|
// add the physical to the simulation interface |
|
hsgResMgr::ResMgr()->AddViaNotify(physKey , new plIntRefMsg(pSiKey, plRefMsg::kOnCreate, 0, plIntRefMsg::kPhysical), plRefFlags::kActiveRef); |
|
|
|
return true; |
|
} |
|
|
|
bool plMaxNode::MakeController(plErrorMsg *pErrMsg, plConvertSettings *settings) |
|
{ |
|
if (!CanConvert()) |
|
return false; |
|
|
|
bool forceLocal = hsControlConverter::Instance().ForceLocal(this); |
|
// Rember the force Local setting |
|
bool 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; |
|
} |
|
|
|
bool plMaxNode::MakeCoordinateInterface(plErrorMsg *pErrMsg, plConvertSettings *settings) |
|
{ |
|
const char* dbgNodeName = GetName(); |
|
if (!CanConvert()) |
|
return false; |
|
plCoordinateInterface* ci = nil; |
|
|
|
bool forceLocal = GetForceLocal(); |
|
|
|
bool 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 = new plFilterCoordInterface; |
|
else |
|
ci = new plCoordinateInterface; |
|
//------------------------- |
|
// Get data from Node, then its key, then its name |
|
//------------------------- |
|
plKey pNodeKey = GetKey(); |
|
hsAssert(pNodeKey, "Missing key for this Object"); |
|
plString pName = pNodeKey->GetName(); |
|
plLocation nodeLoc = GetLocation(); |
|
|
|
plKey pCiKey = hsgResMgr::ResMgr()->NewKey(pName, ci,nodeLoc, GetLoadMask()); |
|
ci->SetLocalToParent(loc2Par, par2Loc); |
|
|
|
bool usesPhysics = GetPhysicalProps()->IsUsed(); |
|
ci->SetProperty(plCoordinateInterface::kCanEverDelayTransform, !usesPhysics); |
|
ci->SetProperty(plCoordinateInterface::kDelayedTransformEval, !usesPhysics); |
|
|
|
hsgResMgr::ResMgr()->AddViaNotify(pCiKey, new plObjRefMsg(pNodeKey, plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef); |
|
} |
|
return true; |
|
} |
|
|
|
bool plMaxNode::MakeModifiers(plErrorMsg *pErrMsg, plConvertSettings *settings) |
|
{ |
|
if (!CanConvert()) |
|
return false; |
|
|
|
bool forceLocal = GetForceLocal(); |
|
const char *dbgNodeName = GetName(); |
|
|
|
bool addMods = (!GetParentNode()->IsRootNode()) |
|
|| forceLocal; |
|
|
|
if (addMods) |
|
{ |
|
// create / add modifiers |
|
|
|
// mf horse hack testing ViewFace which is already obsolete |
|
if ( UserPropExists("ViewFacing") ) |
|
{ |
|
plViewFaceModifier* pMod = 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 = float(atof(token)); |
|
break; |
|
case 1: |
|
scale.fX = scale.fZ; |
|
scale.fY = float(atof(token)); |
|
scale.fZ = 1.f; |
|
break; |
|
case 2: |
|
scale.fZ = float(atof(token)); |
|
break; |
|
} |
|
nGot++; |
|
} |
|
pMod->SetScale(scale); |
|
} |
|
AddModifier(pMod, plString::FromUtf8(GetName())); |
|
} |
|
} |
|
return true; |
|
|
|
} |
|
|
|
bool 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 = new plIntRefMsg(parKey, plRefMsg::kOnCreate, -1, plIntRefMsg::kChildObject); |
|
msg->SetRef(pso); |
|
hsgResMgr::ResMgr()->AddViaNotify(msg, plRefFlags::kPassiveRef); |
|
} |
|
|
|
hsgResMgr::ResMgr()->AddViaNotify(pso->GetKey(), new plNodeRefMsg(GetRoomKey(), plRefMsg::kOnCreate, -1, plNodeRefMsg::kObject), plRefFlags::kActiveRef); |
|
return true; |
|
} |
|
|
|
void plMaxNode::IWipeBranchDrawable(bool 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. |
|
|
|
bool 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); |
|
} |
|
} |
|
} |
|
|
|
bool 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_t* idx = span->fIndexData; |
|
int numFaces = span->fNumIndices / 3; |
|
|
|
uint32_t stride = span->GetVertexSize(span->fFormat); |
|
|
|
uint8_t* vertData = span->fVertexData; |
|
int numVerts = span->fNumVerts; |
|
|
|
bool someIn = false; |
|
bool 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); |
|
float faceDist = faceNorm.InnerProduct(pos[0]); |
|
|
|
int j; |
|
for( j = 0; j < numVerts; j++ ) |
|
{ |
|
hsPoint3* p = (hsPoint3*)(vertData + idx[0] * stride); |
|
|
|
|
|
float dist = p->InnerProduct(faceNorm) - faceDist; |
|
|
|
const float 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 = new hsTArray<int> [numVerts]; |
|
|
|
hsTArray<hsVector3>* normList = new hsTArray<hsVector3> [numVerts]; |
|
hsTArray<float>* distList = new hsTArray<float> [numVerts]; |
|
|
|
uint16_t* idx = span->fIndexData; |
|
|
|
uint32_t stride = span->GetVertexSize(span->fFormat); |
|
|
|
uint8_t* 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); |
|
float 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; |
|
} |
|
|
|
bool someIn = false; |
|
bool 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); |
|
float dist = pos->InnerProduct(normList[i][k]) - distList[i][k]; |
|
|
|
const float 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; |
|
} |
|
|
|
bool plMaxNode::MakeMesh(plErrorMsg *pErrMsg, plConvertSettings *settings) |
|
{ |
|
hsTArray<plGeometrySpan *> spanArray; |
|
plDrawInterface *newDI = nil; |
|
|
|
bool gotMade = false; |
|
bool haveAddedToSceneNode = false; |
|
hsGMesh *myMesh = nil; |
|
uint32_t i, triMeshIndex = (uint32_t)-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_t)-1) |
|
{ |
|
// This node has no geometry on export, but will have some added at runtime, |
|
// so it needs a special drawInterface |
|
|
|
plInstanceDrawInterface *newDI = new plInstanceDrawInterface; |
|
newDI->fTargetID = GetSwappableGeomTarget(); |
|
plKey pDiKey = hsgResMgr::ResMgr()->NewKey( GetKey()->GetName(), newDI, nodeLoc, GetLoadMask() ); |
|
hsgResMgr::ResMgr()->AddViaNotify(pDiKey, new plObjRefMsg(GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef); |
|
|
|
plSwapSpansRefMsg *sMsg = 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_t 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). |
|
bool 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_t 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; |
|
} |
|
|
|
bool DecalMat = false; |
|
bool 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; |
|
} |
|
|
|
bool isDecal = IsLegalDecal(false); // Don't complain about the parent |
|
|
|
/// Get some stuff |
|
bool 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 = 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)); |
|
|
|
plString tmpName = plFormat("{}_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 = new plDrawInterface; |
|
plKey pDiKey = hsgResMgr::ResMgr()->NewKey( GetKey()->GetName(), newDI, nodeLoc, GetLoadMask() ); |
|
hsgResMgr::ResMgr()->AddViaNotify(pDiKey, 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_t oIndex = (uint32_t)-1, bIndex = (uint32_t)-1, sIndex = uint32_t(-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 ) |
|
{ |
|
bool 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_t iDraw = di->GetNumDrawables(); |
|
di->SetDrawable( iDraw, oSpans ); |
|
di->SetDrawableMeshIndex( iDraw, oIndex ); |
|
} |
|
|
|
if( bSpans ) |
|
{ |
|
uint8_t iDraw = di->GetNumDrawables(); |
|
di->SetDrawable( iDraw, bSpans ); |
|
di->SetDrawableMeshIndex( iDraw, bIndex ); |
|
} |
|
|
|
if( sSpans ) |
|
{ |
|
uint8_t 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_t &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_t baseMatrix, uint32_t 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_t)-1) |
|
{ |
|
SetSpansBoneInfo(spanArray, boneMap->GetBaseMatrixIndex(drawable), boneMap->fNumBones); |
|
return; |
|
} |
|
|
|
int baseMatrix, i; |
|
|
|
uint8_t numBones = (boneMap ? boneMap->fNumBones : NumBones()) + 1; |
|
plMaxNodeBase **boneArray = 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_t(-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_t 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 = new plDrawInterface; |
|
plKey diKey = hsgResMgr::ResMgr()->NewKey(GetKey()->GetName(), di, nodeLoc, GetLoadMask()); |
|
hsgResMgr::ResMgr()->AddViaNotify(diKey, new plObjRefMsg(obj->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef); |
|
} |
|
|
|
if( di->GetNumDrawables() <= iDraw ) |
|
{ |
|
uint32_t 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. |
|
|
|
bool plMaxNode::IMakeInstanceSpans( plMaxNode *node, hsTArray<plGeometrySpan *> &spanArray, |
|
plErrorMsg *pErrMsg, plConvertSettings *settings ) |
|
{ |
|
uint8_t iDraw; |
|
int index, i; |
|
|
|
|
|
plSceneObject *obj = node->GetSceneObject(); |
|
if( !obj ) |
|
return false; |
|
|
|
const plDrawInterface *di = obj->GetDrawInterface(); |
|
if( !di ) |
|
return false; |
|
|
|
bool setVisDists = false; |
|
float 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_t)-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 ] = 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_t plMaxNode::IBuildInstanceList( Object *obj, TimeValue t, hsTArray<plMaxNode *> &nodes, bool 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. |
|
|
|
bool plMaxNode::IMaterialsMatch( plMaxNode *otherNode, bool 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; |
|
} |
|
|
|
bool 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_t iDraw; |
|
for( iDraw = 0; iDraw < di->GetNumDrawables(); iDraw++ ) |
|
{ |
|
plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable(iDraw)); |
|
if( !dr ) |
|
continue; |
|
if( di->GetDrawableMeshIndex(iDraw) == (uint32_t)-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; |
|
} |
|
|
|
bool plMaxNode::MakeOccluder(plErrorMsg *pErrMsg, plConvertSettings *settings) |
|
{ |
|
if( !UserPropExists("Occluder") ) |
|
return true; |
|
|
|
bool twoSided = UserPropExists("OccTwoSided"); |
|
bool 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); |
|
} |
|
} |
|
} |
|
|
|
bool plMaxNode::ConvertToOccluder(plErrorMsg* pErrMsg, bool twoSided, bool isHole) |
|
{ |
|
if( !CanConvert() ) |
|
return false; |
|
|
|
/// Get some stuff |
|
plLocation nodeLoc = GetLocation(); |
|
|
|
bool moving = IsMovable(); |
|
if( moving ) |
|
moving++; |
|
|
|
Matrix3 tmp(true); |
|
|
|
Matrix3 maxL2V = GetLocalToVert(TimeValue(0)); |
|
Matrix3 maxV2L = GetVertToLocal(TimeValue(0)); |
|
|
|
hsTArray<plCullPoly> polys; |
|
|
|
uint32_t 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 = M_PI / 20.f; |
|
const float kEdgeThresh = M_PI / 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 * M_PI / 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); |
|
|
|
|
|
float dotSq = aXb.InnerProduct(bXc); |
|
dotSq *= dotSq; |
|
|
|
const float kMinLenSq = 1.e-8f; |
|
const float kMinDotFracSq = 0.998f * 0.998f; |
|
|
|
float 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 = new plMobileOccluder; |
|
occ = mob; |
|
} |
|
else |
|
{ |
|
occ = new plOccluder; |
|
} |
|
|
|
occ->SetPolyList(polys); |
|
occ->ComputeFromPolys(); |
|
|
|
// Register it. |
|
plString tmpName; |
|
if( GetKey() && !GetKey()->GetName().IsEmpty() ) |
|
{ |
|
tmpName = plFormat("{}_Occluder", GetKey()->GetName()); |
|
} |
|
else |
|
{ |
|
static int numOcc = 0; |
|
tmpName = plFormat("Occluder_{_04d}", numOcc); |
|
} |
|
plKey key = hsgResMgr::ResMgr()->NewKey( tmpName, occ, nodeLoc, GetLoadMask() ); |
|
|
|
hsgResMgr::ResMgr()->AddViaNotify(occ->GetKey(), new plObjRefMsg(GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kInterface), plRefFlags::kActiveRef); |
|
|
|
} |
|
return true; |
|
} |
|
|
|
bool plMaxNode::MakeLight(plErrorMsg *pErrMsg, plConvertSettings *settings) |
|
{ |
|
|
|
if (!CanConvert()) |
|
return false; |
|
|
|
if (!GetRunTimeLight()) |
|
return true; |
|
|
|
/// Get some stuff |
|
plLocation nodeLoc = GetLocation(); |
|
bool 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(), 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()); |
|
|
|
float 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); |
|
|
|
} |
|
|
|
bool plMaxNode::IGetRTLightAttenValues(IParamBlock2* ProperPB, float& attenConst, float& attenLinear, float& attenQuadratic, float &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) |
|
{ |
|
float 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) |
|
{ |
|
|
|
float inner = hsDegreesToRadians(ls.hotsize); |
|
float outer = hsDegreesToRadians(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()); |
|
float inner, outer; |
|
|
|
inner = hsDegreesToRadians(ProperPB->GetFloat(plRTLightBase::kHotSpot, timeVal)); //ls.hotsize); |
|
outer = hsDegreesToRadians(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 = 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 = 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 = 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 = 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 = new plSpotLightInfo; |
|
|
|
if(!ThisObjPB->GetInt(plRTLightBase::kLightOn)) |
|
spot->SetProperty(plLightInfo::kDisable, true); |
|
|
|
IGetRTLightColors(spot,ThisObjPB); |
|
|
|
IGetRTLightAttenuation(spot,ThisObjPB); |
|
|
|
IGetRTCone(spot, ThisObjPB); |
|
|
|
//plSpotModifier* liMod = 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 = new plOmniLightInfo; |
|
|
|
if(!ThisObjPB->GetInt(plRTLightBase::kLightOn)) |
|
omni->SetProperty(plLightInfo::kDisable, true); |
|
|
|
IGetRTLightAttenuation(omni, ThisObjPB); |
|
|
|
IGetRTLightColors(omni, ThisObjPB); |
|
|
|
//plOmniModifier* liMod = 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 = new plDirectionalLightInfo; |
|
|
|
if(!ThisObjPB->GetInt(plRTLightBase::kLightOn)) |
|
direct->SetProperty(plLightInfo::kDisable, true); |
|
|
|
IGetRTLightColors(direct, ThisObjPB); |
|
|
|
//plLightModifier* liMod = 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 = 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 = new plLightModifier; |
|
|
|
//GetRTLightColAnim( mainPB, liMod ); |
|
|
|
//IAttachRTLightModifier(liMod); |
|
|
|
return light; |
|
} |
|
|
|
bool plMaxNode::IGetProjection(plLightInfo* li, plErrorMsg* pErrMsg) |
|
{ |
|
bool 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; |
|
|
|
bool 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(), 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", (const char *)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; |
|
} |
|
/* |
|
bool 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 = GetParamBlock2Controller(pb, ParamID( plRTLightBase::kLightColor ) ); |
|
if (colorCtl && cc.HasKeyTimes(colorCtl)) |
|
return true; |
|
|
|
// Is the specularity animated? |
|
Control *specCtl = GetParamBlock2Controller(pb, 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 = GetParamBlock2Controller(pb, ParamID( plRTLightBase::kAttenMaxFalloffEdit ) ); |
|
if (falloffCtl && cc.HasKeyTimes(falloffCtl)) |
|
return true; |
|
} |
|
|
|
// Is the cone animated? (Spot only) |
|
if (cid == RTSPOT_LIGHT_CLASSID) |
|
{ |
|
Control *innerCtl = GetParamBlock2Controller(pb, ParamID( plRTLightBase::kHotSpot ) ); |
|
if (innerCtl && cc.HasKeyTimes(innerCtl)) |
|
return true; |
|
|
|
Control *outerCtl = GetParamBlock2Controller(pb, 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 = GetParamBlock2Controller(ProperPB, ParamID(plRTLightBase::kAttenMaxFalloffEdit)); |
|
if( falloffCtl ) |
|
{ |
|
plLeafController* subCtl; |
|
if (!anim->GetName().Compare(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 = new plOmniCutoffApplicator(); |
|
app->SetChannelName(GetName()); |
|
plScalarControllerChannel *chan = new plScalarControllerChannel(subCtl); |
|
app->SetChannel(chan); |
|
anim->AddApplicator(app); |
|
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME)) |
|
anim->ExtendToLength(subCtl->GetLength()); |
|
} |
|
else |
|
{ |
|
bool distSq = ProperPB->GetInt(plRTLightBase::kAttenTypeRadio, TimeValue(0)); |
|
|
|
int i; |
|
for( i = 0; i < subCtl->GetNumKeys(); i++ ) |
|
{ |
|
hsScalarKey *key = subCtl->GetScalarKey(i); |
|
if (key) |
|
{ |
|
float attenEnd = key->fValue; |
|
TimeValue tv = key->fFrame * MAX_TICKS_PER_FRAME; |
|
float intens = ProperPB->GetFloat(plRTLightBase::kIntensity, tv); |
|
float newVal = (intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / attenEnd; |
|
if( distSq ) |
|
newVal /= attenEnd; |
|
|
|
key->fValue = newVal; |
|
} |
|
hsBezScalarKey *bezKey = subCtl->GetBezScalarKey(i); |
|
if (bezKey) |
|
{ |
|
float attenEnd = bezKey->fValue; |
|
TimeValue tv = bezKey->fFrame * MAX_TICKS_PER_FRAME; |
|
float intens = ProperPB->GetFloat(plRTLightBase::kIntensity, tv); |
|
float 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 = new plOmniSqApplicator; |
|
else |
|
app = new plOmniApplicator; |
|
|
|
app->SetChannelName(GetName()); |
|
plScalarControllerChannel *chan = new plScalarControllerChannel(subCtl); |
|
app->SetChannel(chan); |
|
anim->AddApplicator(app); |
|
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME)) |
|
anim->ExtendToLength(subCtl->GetLength()); |
|
|
|
float 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; |
|
float intens = ProperPB->GetFloat(plRTLightBase::kIntensity, tv); |
|
key->fValue *= intens; |
|
} |
|
hsBezPoint3Key* bezKey = simp->GetBezPoint3Key(i); |
|
if (bezKey) |
|
{ |
|
TimeValue tv = bezKey->fFrame * MAX_TICKS_PER_FRAME; |
|
float 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 = GetParamBlock2Controller(ProperPB, ParamID(plRTLightBase::kLightColor)); |
|
Control* specCtl = GetParamBlock2Controller(ProperPB, ParamID(plRTLightBase::kSpecularColorSwatch)); |
|
plPointControllerChannel *chan; |
|
|
|
if( ambientCtl ) |
|
{ |
|
plController* ctl; |
|
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME)) |
|
ctl = hsControlConverter::Instance().MakeColorController(ambientCtl, this); |
|
else |
|
ctl = hsControlConverter::Instance().MakeColorController(ambientCtl, this, anim->GetStart(), anim->GetEnd()); |
|
|
|
if( ctl ) |
|
{ |
|
plLightAmbientApplicator *app = new plLightAmbientApplicator(); |
|
app->SetChannelName(GetName()); |
|
chan = new plPointControllerChannel(ctl); |
|
app->SetChannel(chan); |
|
anim->AddApplicator(app); |
|
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME)) |
|
anim->ExtendToLength(ctl->GetLength()); |
|
} |
|
} |
|
if( colorCtl ) |
|
{ |
|
plController* ctl; |
|
if (!anim->GetName().Compare(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 = new plLightDiffuseApplicator(); |
|
app->SetChannelName(GetName()); |
|
chan = new plPointControllerChannel(ctl); |
|
app->SetChannel(chan); |
|
anim->AddApplicator(app); |
|
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME)) |
|
anim->ExtendToLength(ctl->GetLength()); |
|
} |
|
} |
|
if( specCtl ) |
|
{ |
|
plController* ctl; |
|
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME)) |
|
ctl = hsControlConverter::Instance().MakeColorController(specCtl, this); |
|
else |
|
ctl = hsControlConverter::Instance().MakeColorController(specCtl, this, anim->GetStart(), anim->GetEnd()); |
|
|
|
if( ctl ) |
|
{ |
|
plLightSpecularApplicator *app = new plLightSpecularApplicator(); |
|
app->SetChannelName(GetName()); |
|
chan = new plPointControllerChannel(ctl); |
|
app->SetChannel(chan); |
|
anim->AddApplicator(app); |
|
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME)) |
|
anim->ExtendToLength(ctl->GetLength()); |
|
} |
|
} |
|
} |
|
|
|
void plMaxNode::GetRTConeAnim(IParamBlock2* ProperPB, plAGAnim *anim) |
|
{ |
|
Control* innerCtl = GetParamBlock2Controller(ProperPB, ParamID(plRTLightBase::kHotSpot)); |
|
Control* outerCtl = GetParamBlock2Controller(ProperPB, ParamID(plRTLightBase::kFallOff)); |
|
plScalarControllerChannel *chan; |
|
|
|
if( innerCtl ) |
|
{ |
|
plLeafController* ctl; |
|
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME)) |
|
ctl = hsControlConverter::Instance().MakeScalarController(innerCtl, this); |
|
else |
|
ctl = hsControlConverter::Instance().MakeScalarController(innerCtl, this, anim->GetStart(), anim->GetEnd()); |
|
|
|
if( ctl ) |
|
{ |
|
plSpotInnerApplicator *app = new plSpotInnerApplicator(); |
|
app->SetChannelName(GetName()); |
|
chan = new plScalarControllerChannel(ctl); |
|
app->SetChannel(chan); |
|
anim->AddApplicator(app); |
|
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME)) |
|
anim->ExtendToLength(ctl->GetLength()); |
|
} |
|
} |
|
if( outerCtl ) |
|
{ |
|
plController* ctl; |
|
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME)) |
|
ctl = hsControlConverter::Instance().MakeScalarController(outerCtl, this); |
|
else |
|
ctl = hsControlConverter::Instance().MakeScalarController(outerCtl, this, anim->GetStart(), anim->GetEnd()); |
|
|
|
if( ctl ) |
|
{ |
|
plSpotOuterApplicator *app = new plSpotOuterApplicator(); |
|
app->SetChannelName(GetName()); |
|
chan = new plScalarControllerChannel(ctl); |
|
app->SetChannel(chan); |
|
anim->AddApplicator(app); |
|
if (!anim->GetName().Compare(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_t count = NumAttachedComponents(); |
|
|
|
// Go through all the components attached to this node |
|
for (uint32_t 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_t count = NumAttachedComponents(); |
|
|
|
// Go through all the components attached to this node |
|
for (uint32_t 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(bool needBlending, bool needSorting) |
|
{ |
|
plRenderLevel level = needBlending ? GetRenderLevel(needBlending) : plRenderLevel::OpaqueRenderLevel(); |
|
|
|
if( GetSortAsOpaque() ) |
|
level.Set(plRenderLevel::kOpaqueMajorLevel, level.Minor()); |
|
|
|
uint32_t 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, bool needBlending, bool needSorting ) |
|
{ |
|
|
|
plDrawableSpans *spans; |
|
plString tmpName; |
|
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 = new plDrawableSpans; |
|
if( needBlending ) |
|
{ |
|
/// Blending (deferred) spans |
|
spans->SetCriteria( crit ); |
|
tmpName = plFormat("{}_{_08x}_{x}BlendSpans", node->GetKeyName(), crit.fLevel.fLevel, crit.fCriteria); |
|
} |
|
else |
|
{ |
|
/// Normal spans |
|
spans->SetCriteria( crit ); |
|
tmpName = plFormat("{}_{_08x}_{x}Spans", node->GetKeyName(), crit.fLevel.fLevel, crit.fCriteria); |
|
} |
|
|
|
if (GetSwappableGeomTarget() != (uint32_t)-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; |
|
} |
|
|
|
bool plMaxNode::SetupPropertiesPass(plErrorMsg *pErrMsg, plConvertSettings *settings) |
|
{ |
|
// TEMP |
|
if (IsComponent()) |
|
return false; |
|
// End TEMP |
|
|
|
bool ret = true; |
|
|
|
uint32_t count = NumAttachedComponents(); |
|
|
|
// Go through all the components attached to this node |
|
for (uint32_t 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; |
|
} |
|
|
|
bool plMaxNode::FirstComponentPass(plErrorMsg *pErrMsg, plConvertSettings *settings) |
|
{ |
|
// TEMP |
|
if (IsComponent()) |
|
return false; |
|
// End TEMP |
|
|
|
bool ret = true; |
|
|
|
if (!CanConvert()) |
|
return ret; |
|
uint32_t count = NumAttachedComponents(); |
|
|
|
// Go through all the components attached to this node |
|
for (uint32_t 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; |
|
} |
|
|
|
bool plMaxNode::ConvertComponents(plErrorMsg *pErrMsg, plConvertSettings *settings) |
|
{ |
|
// TEMP |
|
if (IsComponent()) |
|
return false; |
|
// End TEMP |
|
|
|
bool ret = true; |
|
|
|
char *dbgNodeName = GetName(); |
|
if (!CanConvert()) |
|
return ret; |
|
|
|
uint32_t count = NumAttachedComponents(); |
|
|
|
// Go through all the components attached to this node |
|
for (uint32_t 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; |
|
} |
|
|
|
bool plMaxNode::DeInitComponents(plErrorMsg *pErrMsg, plConvertSettings *settings) |
|
{ |
|
// TEMP |
|
if (IsComponent()) |
|
return false; |
|
// End TEMP |
|
|
|
bool ret = true; |
|
|
|
char *dbgNodeName = GetName(); |
|
if (!CanConvert()) |
|
return ret; |
|
|
|
uint32_t count = NumAttachedComponents(); |
|
|
|
// Go through all the components attached to this node |
|
for (uint32_t 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; |
|
} |
|
|
|
bool 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()) { |
|
plString 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 = strlen("BoneName="); |
|
if(start && UserPropExists("BoneName")) |
|
{ |
|
start+=len; |
|
int i=0; |
|
while(*start != '\n' && *start) |
|
{ |
|
hsAssert(i<256, "localName overflow"); |
|
localName[i++]=*start++; |
|
} |
|
localName[i]=0; |
|
|
|
nameToUse = plString::FromUtf8(localName); |
|
|
|
} |
|
else |
|
{ |
|
plString nodeName = plString::FromUtf8(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 = new plAliasModifier; |
|
pAlMod->SetAlias(aliasName); |
|
AddModifier(pAlMod); |
|
} |
|
*/ |
|
plAGModifier *mod = new plAGModifier(nameToUse); |
|
AddModifier(mod, plString::FromUtf8(GetName())); |
|
} |
|
} |
|
|
|
int j = 0; |
|
for( j = 0; j < NumberOfChildren(); j++ ) |
|
((plMaxNode*)GetChildNode(j))->SetupBonesAliasesRecur(rootName); |
|
} |
|
|
|
void plMaxNode::SetDISceneNodeSpans( plDrawInterface *di, bool 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 = 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(); |
|
} |
|
|
|
bool plMaxNode::IsLegalDecal(bool 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() |
|
{ |
|
bool 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 = 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? |
|
bool plMaxNode::VtxAlphaNotAvailable() |
|
{ |
|
if( NonVtxPreshaded() || GetParticleRelated()) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
bool 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(bool& 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_t plMaxNode::GetNextSoundIdx( void ) |
|
{ |
|
uint32_t 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) |
|
|
|
bool 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_t classIdx, const plString &name ) |
|
{ |
|
return hsgResMgr::ResMgr()->FindKey( plUoid( GetLocation(), classIdx, name ) ); |
|
} |
|
|
|
const 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. |
|
|
|
bool plMaxNode::MakeIfaceReferences(plErrorMsg *pErrMsg, plConvertSettings *settings) |
|
{ |
|
bool ret = true; |
|
|
|
char *dbgNodeName = GetName(); |
|
if (!CanConvert()) |
|
return ret; |
|
|
|
uint32_t 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_t 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 = new plInterfaceInfoModifier; |
|
|
|
plKey modifierKey = hsgResMgr::ResMgr()->NewKey(plString::FromUtf8(GetName()), pMod, GetLocation(), GetLoadMask()); |
|
hsgResMgr::ResMgr()->AddViaNotify(modifierKey, 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; |
|
}
|
|
|