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