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