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