/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Additional permissions under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK (or a modified version of those libraries), containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of OpenSSL and IJG JPEG Library used as well as that of the covered work. You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ #include "HeadSpin.h" #include #include "plComponentProcBase.h" #include "resource.h" #include "plComponent.h" #include "plComponentReg.h" #include "../MaxConvert/hsConverterUtils.h" #include "../pnSceneObject/plSceneObject.h" #include "plgDispatch.h" #include "../plScene/plSceneNode.h" #include "../MaxConvert/hsConverterUtils.h" #include "../MaxConvert/hsControlConverter.h" #include "../MaxConvert/hsMaterialConverter.h" #include "../MaxConvert/plBitmapCreator.h" #include "hsStringTokenizer.h" #include "../MaxMain/plMaxNode.h" #include "../pnKeyedObject/plKey.h" #include "hsResMgr.h" #include "../pnMessage/plNodeRefMsg.h" #include "../pnMessage/plObjRefMsg.h" #include "../pnMessage/plIntRefMsg.h" #include "../plMessage/plMatRefMsg.h" #include "../plMessage/plLayRefMsg.h" #include "plMaxAnimUtils.h" #include "../plInterp/plController.h" #include "../plPhysical/plSimDefs.h" #include "plPhysicsGroups.h" #include "../plAudible/plWinAudible.h" #include "../pnSceneObject/plAudioInterface.h" #include "../pnSceneObject/plCoordinateInterface.h" #include "../plSurface/plLayerAnimation.h" #include "../plSurface/hsGMaterial.h" #include "../plAudio/plWin32StaticSound.h" #include "../plAudioCore/plSoundBuffer.h" #include "plAudioComponents.h" #include "../MaxMain/plPlasmaRefMsgs.h" #include "../plAvatar/plArmatureMod.h" #include "../plAvatar/plAvBrainHuman.h" #include "../plAvatar/plAvBrainCritter.h" #include "../plAvatar/plAvatarClothing.h" #include "../plAvatar/plArmatureEffects.h" #include "../plGImage/plMipmap.h" #include "../plGImage/plLODMipmap.h" // Auto generation of shadows here. #include "plShadowComponents.h" #include "../plGLight/plShadowCaster.h" #include "plAvatarComponent.h" #include "../../tools/MaxComponent/plPhysicalComponents.h" #include "../MaxMain/plPhysicalProps.h" //#include //#include #include "plPickNode.h" #include "plPickMaterialMap.h" #include "../MaxMain/plMtlCollector.h" //#define BOB_SORT_AVATAR_FACES // CONSTANTS float kStdRestitution = 0.5f; float kStdFriction = 0.1f; void DummyCodeIncludeAvatarFunc() {} // PROTOTYPES class plAvatarComponent; class plCritterComponent; class plArmatureComponent; class plCritterCommands; plArmatureMod* plArmatureComponent::IGenerateMyArmMod(plHKPhysical* myHKPhys, plMaxNode* node) { plArmatureMod *avMod = TRACKED_NEW plArmatureMod(); avMod->SetRootName(node->GetKey()->GetName()); return avMod; } void plArmatureComponent::ISetupAvatarRenderPropsRecurse(plMaxNode *node) { node->SetNoSpanSort(true); node->SetNoFaceSort(true); node->SetNoDeferDraw(true); int i; for (i = 0; i < node->NumberOfChildren(); i++) { plMaxNode *pChild = (plMaxNode *)node->GetChildNode(i); ISetupAvatarRenderPropsRecurse(pChild); } } //SETUPPROPERTIES // Tests if IPB2 pointers are healthy and sets up the MaxNode Data hsBool plArmatureComponent::SetupProperties(plMaxNode* node, plErrorMsg* pErrMsg) { // Global issues node->SetMovable(true); node->SetForceLocal(true); #ifndef BOB_SORT_AVATAR_FACES ISetupAvatarRenderPropsRecurse(node); #endif // float mass, friction, restitution; // if(ClassID() == AVATAR_CLASS_ID || ClassID() == LOD_AVATAR_CLASS_ID) // { // mass = 12; // *** good number from old physical player // restitution = 0.5f; // friction = 0.1f; // } plMaxNode *animRoot = nil; if(ClassID() == AVATAR_CLASS_ID) animRoot = (plMaxNode *)fCompPB->GetINode(plAvatarComponent::kRootNode); else if (ClassID() == LOD_AVATAR_CLASS_ID) animRoot = (plMaxNode *)fCompPB->GetINode(plLODAvatarComponent::kRootNodeAddBtn); if(animRoot) { const char *nodeName = animRoot->GetName(); animRoot->SetDrawable(false); // make sure our root bone is invisible } // Ignore all of the old physicals, so old scenes can export if (ClassID() == AVATAR_CLASS_ID || ClassID() == LOD_AVATAR_CLASS_ID) { bool isLOD = ((ClassID() == LOD_AVATAR_CLASS_ID) != 0); plMaxNode* ignoreNode = (plMaxNode*)fCompPB->GetINode(isLOD ? plLODAvatarComponent::kPhysicsProxyFeet_DEAD : plAvatarComponent::kPhysicsProxyFeet_DEAD); if (ignoreNode) ignoreNode->SetCanConvert(false); ignoreNode = (plMaxNode*)fCompPB->GetINode(isLOD ? plLODAvatarComponent::kPhysicsProxyTorso_DEAD : plAvatarComponent::kPhysicsProxyTorso_DEAD); if (ignoreNode) ignoreNode->SetCanConvert(false); ignoreNode = (plMaxNode*)fCompPB->GetINode(isLOD ? plLODAvatarComponent::kPhysicsProxyHead_DEAD : plAvatarComponent::kPhysicsProxyHead_DEAD); if (ignoreNode) ignoreNode->SetCanConvert(false); } return true; } //// ISETARMATURESORECURSE // Do some strange magic to make sure that we know when all the parts of the avatar are loaded. void plArmatureComponent::ISetArmatureSORecurse(plMaxNode *node, plSceneObject *so) { if (node->CanConvert()) node->SetAvatarSO(so); int i; for (i = 0; i < node->NumberOfChildren(); i++) { plMaxNode *pChild = (plMaxNode *)node->GetChildNode(i); ISetArmatureSORecurse(pChild, so); } } hsBool plArmatureComponent::PreConvert(plMaxNode* node, plErrorMsg* pErrMsg) { // add audio interface and record/playback component pl2WayWinAudible* pAudible = TRACKED_NEW pl2WayWinAudible; // Add a key for it plKey key = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), pAudible, node->GetLocation() ); plAudioInterface* ai = TRACKED_NEW plAudioInterface; plKey pAiKey = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), (hsKeyedObject*)ai,node->GetLocation()); hsgResMgr::ResMgr()->AddViaNotify(pAiKey, TRACKED_NEW plObjRefMsg(node->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef); plIntRefMsg* pMsg = TRACKED_NEW plIntRefMsg(node->GetKey(), plRefMsg::kOnCreate, 0, plIntRefMsg::kAudible); hsgResMgr::ResMgr()->AddViaNotify(pAudible->GetKey(), pMsg, plRefFlags::kActiveRef ); ISetArmatureSORecurse(node, node->GetSceneObject()); // Uncomment this line to enable a single bone pallete for the entire avatar. node->SetupBoneHierarchyPalette(); return true; } // this is a little gross...the armature component shouldn't know that the subclasses // actually exist....it's a hard-to-detect implementation detail that breaks new subclasses.... hsBool plArmatureComponent::Convert(plMaxNode* node, plErrorMsg *pErrMsg) { // plHKPhysical *physical = plHKPhysical::ConvertToPhysical(node->GetSceneObject()); // physical->SetProperty(plSimulationInterface::kUpright, true); IAttachModifiers(node, pErrMsg); ISetupClothes(node, fArmMod, pErrMsg); // ArmatureEffects plArmatureEffectsMgr *effects = TRACKED_NEW plArmatureEffectsMgr(); hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), effects, node->GetLocation()); plGenRefMsg *msg = TRACKED_NEW plGenRefMsg(fArmMod->GetKey(), plRefMsg::kOnCreate, -1, -1); hsgResMgr::ResMgr()->AddViaNotify(effects->GetKey(), msg, plRefFlags::kActiveRef); // Attach effects plSceneObject *obj = node->GetSceneObject(); node->MakeCharacterHierarchy(pErrMsg); const plAGModifier *temp = static_cast(FindModifierByClass(obj, plAGModifier::Index())); plAGModifier *agMod = const_cast(temp); hsAssert(agMod, "Armature root didn't get a agmod. I'll make one for you."); if( ! agMod) { // MakeCharacterHierarchy will attach agmodifiers to all the bones in the hierarchy; // have to manually add any for non-bone objects... agMod = TRACKED_NEW plAGModifier("Handle"); // the player root is known as the handle node->AddModifier(agMod, IGetUniqueName(node)); } agMod->SetChannelName("Handle"); // Get the position and radius of the head and torso physicals if (ClassID() == AVATAR_CLASS_ID || ClassID() == LOD_AVATAR_CLASS_ID) { bool isLOD = ((ClassID() == LOD_AVATAR_CLASS_ID) != 0); float height = fCompPB->GetFloat(isLOD ? plLODAvatarComponent::kPhysicsHeight : plAvatarComponent::kPhysicsHeight); float width = fCompPB->GetFloat(isLOD ? plLODAvatarComponent::kPhysicsWidth : plAvatarComponent::kPhysicsWidth); fArmMod->SetPhysicalDims(height, width); } // node->SetupBonesAliasesRecur(node->GetKey()->GetName()); return true; } hsBool plArmatureComponent::IVerifyUsedNode(INode* thisNode, plErrorMsg* pErrMsg, hsBool IsHull) { if(thisNode != NULL) { if(((plMaxNode*)thisNode)->CanConvert()) { if(IsHull) ((plMaxNode*)thisNode)->SetDrawable(false); }else { pErrMsg->Set(true, "Ignored Node Selection", "The object that Node Ptr %s refs was set to be Ignored. Avatar Component failure.", thisNode->GetName()); pErrMsg->Set(false); return false; } }else{ pErrMsg->Set(true, "Empty Node in Avatar Component", "It is imperative that all the node pickers have real values.\n Avatar Component failure.").Show(); pErrMsg->Set(false); return false; } return true; } void plArmatureComponent::IAttachShadowCastModifiersRecur(plMaxNode* node, plShadowCaster* caster) { if( !node || !caster ) return; // Add test here for whether we want this guy to cast a shadow. plShadowCastComponent::AddShadowCastModifier(node, caster); int i; for( i = 0; i < node->NumberOfChildren(); i++ ) IAttachShadowCastModifiersRecur((plMaxNode*)node->GetChildNode(i), caster); } //class AvatarStats //{ //public: // float fFriction; // float fMaxVel; // float fAccel; // float fTurnForce; // hsBool fUseNewMovement; // // AvatarStats(float a, float b, float c, float d, hsBool newMove) // : fFriction(a), fMaxVel(b), fAccel(c), fTurnForce(d), fUseNewMovement(newMove) {}; // AvatarStats() : fFriction(-1.0), fMaxVel(-1.0), fAccel(-1.0), fTurnForce(-1.0) {}; //}; enum { kArmMain, kArmBounce, kArmReport, }; CLASS_DESC(plAvatarComponent, gAvatarCompDesc, "Avatar", "Avatar", COMP_TYPE_AVATAR, AVATAR_CLASS_ID) //Max paramblock2 stuff below. ParamBlockDesc2 gPAvatarBk ( plComponent::kBlkComp, _T("Avatar"), 0, &gAvatarCompDesc, P_AUTO_CONSTRUCT + P_AUTO_UI + P_MULTIMAP, plComponent::kRefComp, //Roll out 1, kArmMain, IDD_COMP_AVATAR, IDS_COMP_AVATARS, 0, 0, &gAvatarCompDlgProc, // kArmBounce, IDD_COMP_PHYS_CORE_GROUP, IDS_COMP_PHYS_BOUNCE, 0, APPENDROLL_CLOSED, &gBounceGroupProc, // kArmReport, IDD_COMP_PHYS_CORE_GROUP, IDS_COMP_PHYS_REPORT, 0, APPENDROLL_CLOSED, &gReportGroupProc, // params plAvatarComponent::kPhysicsProxyFeet_DEAD, _T("ProxyFeet"), TYPE_INODE, 0, 0, // p_ui, kArmMain, TYPE_PICKNODEBUTTON, IDC_COMP_AVATAR_PROXY_PICKB, // p_sclassID, GEOMOBJECT_CLASS_ID, // p_prompt, IDS_COMP_AVATAR_PROXYS, end, plAvatarComponent::kFriction, _T("Friction"), TYPE_FLOAT, 0, 0, p_ui, kArmMain, TYPE_SPINNER, EDITTYPE_POS_FLOAT, IDC_COMP_AVATAR_FRICTION_EDIT, IDC_COMP_AVATAR_FRICTION_SPIN, 0.1f, p_default, 0.9f, end, plAvatarComponent::kRootNode, _T("RootNode"), TYPE_INODE, 0, 0, p_ui, kArmMain, TYPE_PICKNODEBUTTON, IDC_COMP_AVATAR_ROOT_PICKB, p_prompt, IDS_COMP_AVATAR_PROXYS, end, plAvatarComponent::kMeshNode, _T("MeshNode"), TYPE_INODE, 0, 0, p_ui, kArmMain, TYPE_PICKNODEBUTTON, IDC_COMP_AVATAR_MESH_PICKB, //p_sclassID, GEOMOBJECT_CLASS_ID, p_prompt, IDS_COMP_AVATAR_PROXYS, end, plAvatarComponent::kClothingGroup, _T("ClothingGroup"), TYPE_INT, 0, 0, p_default, plClothingMgr::kClothingBaseMale, end, plArmatureComponent::kBounceGroups, _T("bounceGroups"), TYPE_INT, 0,0, p_default, plPhysicsGroups_DEAD::kCreatures | plPhysicsGroups_DEAD::kStaticSimulated | plPhysicsGroups_DEAD::kDynamicSimulated | plPhysicsGroups_DEAD::kAnimated, end, plArmatureComponent::kReportGroups, _T("reportGroups"), TYPE_INT, 0,0, end, plAvatarComponent::kBrainType, _T("Brain"), TYPE_INT, 0, 0, p_default, plAvatarComponent::kBrainHuman, end, plAvatarComponent::kSkeleton, _T("Skeleton"), TYPE_INT, 0, 0, p_default, plArmatureMod::kBoneBaseMale, end, plAvatarComponent::kPhysicsProxyTorso_DEAD, _T("ProxyTorso"), TYPE_INODE, 0, 0, // p_ui, kArmMain, TYPE_PICKNODEBUTTON, IDC_COMP_AVATAR_PROXY_PICKT, // p_sclassID, GEOMOBJECT_CLASS_ID, // p_prompt, IDS_COMP_AVATAR_PROXYS, end, plAvatarComponent::kPhysicsProxyHead_DEAD, _T("ProxyHead"), TYPE_INODE, 0, 0, // p_ui, kArmMain, TYPE_PICKNODEBUTTON, IDC_COMP_AVATAR_PROXY_PICKH, // p_sclassID, GEOMOBJECT_CLASS_ID, // p_prompt, IDS_COMP_AVATAR_PROXYS, end, plAvatarComponent::kPhysicsHeight, _T("physHeight"), TYPE_FLOAT, 0, 0, p_range, 0.1f, 50.0f, p_default, 5.f, p_ui, kArmMain, TYPE_SPINNER, EDITTYPE_FLOAT, IDC_PHYS_HEIGHT_EDIT, IDC_PHYS_HEIGHT_SPIN, SPIN_AUTOSCALE, end, plAvatarComponent::kPhysicsWidth, _T("physWidth"), TYPE_FLOAT, 0, 0, p_range, 0.1f, 50.0f, p_default, 2.5f, p_ui, kArmMain, TYPE_SPINNER, EDITTYPE_FLOAT, IDC_PHYS_WIDTH_EDIT, IDC_PHYS_WIDTH_SPIN, SPIN_AUTOSCALE, end, plAvatarComponent::kBodyFootstepSoundPage, _T("bodyFootstepPage"), TYPE_STRING, 0, 0, p_default, "Audio", p_ui, kArmMain, TYPE_EDITBOX, IDC_BODYFOOTSTEPPAGE_EDIT, end, plAvatarComponent::kAnimationPrefix,_T("animationPrefix"), TYPE_STRING, 0, 0, p_default, "Male", p_ui, kArmMain, TYPE_EDITBOX, IDC_ANIMATIONPREFIX_EDIT, end, end ); plAvatarComponent::plAvatarComponent() { fClassDesc = &gAvatarCompDesc; fClassDesc->MakeAutoParamBlocks(this); } void plAvatarComponent::IAttachModifiers(plMaxNode *node, plErrorMsg *pErrMsg) { const char *name = node->GetKey()->GetName(); plMaxNode *meshNode = (plMaxNode *)fCompPB->GetINode(plAvatarComponent::kMeshNode); plKey meshKey = meshNode->GetSceneObject()->GetKey(); plMaxNode *animRootNode = (plMaxNode *)fCompPB->GetINode(plAvatarComponent::kRootNode); plKey animRootKey = animRootNode->GetSceneObject()->GetKey(); plSceneObject * bodySO = node->GetSceneObject(); plArmatureMod* avMod = TRACKED_NEW plArmatureMod(); avMod->SetRootName(name); avMod->AppendMeshKey(meshKey); int skeletonType = fCompPB->GetInt(ParamID(kSkeleton)); avMod->SetBodyType( skeletonType ); // only make a human brain if we're a human if (skeletonType == plArmatureMod::kBoneBaseCritter) avMod->PushBrain(TRACKED_NEW plAvBrainCritter()); else avMod->PushBrain(TRACKED_NEW plAvBrainHuman(skeletonType == plArmatureMod::kBoneBaseActor)); avMod->SetBodyAgeName(node->GetAgeName()); avMod->SetBodyFootstepSoundPage(fCompPB->GetStr(ParamID(kBodyFootstepSoundPage))); avMod->SetAnimationPrefix(fCompPB->GetStr(ParamID(kAnimationPrefix))); //AddLinkSound(node, node->GetSceneObject()->GetKey(), pErrMsg ); plKey avKey = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), avMod, node->GetLocation()); plObjRefMsg *objRefMsg = TRACKED_NEW plObjRefMsg(bodySO->GetKey(), plRefMsg::kOnCreate,-1, plObjRefMsg::kModifier); hsgResMgr::ResMgr()->AddViaNotify(avKey, objRefMsg, plRefFlags::kActiveRef); fArmMod = avMod; } // Helper function for the Avatar and LOD Avatar components void AddClothingToMod(plMaxNode *node, plArmatureMod *mod, int group, hsGMaterial *mat, plErrorMsg *pErrMsg) { plGenRefMsg *msg; char keyName[256]; TSTR sdata; hsStringTokenizer toker; if (mod == nil) { hsAssert(false, "Adding clothes to a nil armatureMod."); return; } plClothingBase *base = TRACKED_NEW plClothingBase(); if (node->GetUserPropString("layout", sdata)) { toker.Reset(sdata, hsConverterUtils::fTagSeps); base->SetLayoutName(toker.next()); } else base->SetLayoutName("BasicHuman"); sprintf(keyName, "%s_ClothingBase", node->GetName()); hsgResMgr::ResMgr()->NewKey(keyName, base, node->GetLocation()); plClothingOutfit *outfit = TRACKED_NEW plClothingOutfit(); outfit->fGroup = group; sprintf(keyName, "%s_outfit", mod->GetKey()->GetName()); hsgResMgr::ResMgr()->NewKey(keyName, outfit, node->GetLocation()); msg = TRACKED_NEW plGenRefMsg(outfit->GetKey(), plRefMsg::kOnCreate, -1, -1); hsgResMgr::ResMgr()->AddViaNotify(base->GetKey(), msg, plRefFlags::kActiveRef); // Add clothing base to outfit msg = TRACKED_NEW plGenRefMsg(mod->GetKey(), plRefMsg::kOnCreate, -1, -1); hsgResMgr::ResMgr()->AddViaNotify(outfit->GetKey(), msg, plRefFlags::kActiveRef); // Attach outfit plMipmap *baseTex = nil; plLayerInterface *li = nil; if (mat != nil) { li = mat->GetLayer(0)->BottomOfStack(); baseTex = plMipmap::ConvertNoRef(li->GetTexture()); hsAssert(li->GetTexture() == baseTex, "Base texture mismatch on avatar construction?"); } if (mat != nil && baseTex != nil) { // Let's fix up these bitmap flags. Normally, they are set on convert based on // the contents of the bitmap. But here our contents are subject to change at runtime. // Fortunately, we've got a pretty good idea what to expect. // I'm making a subjective decision here on forcing 32bit even when we run in 16 bit, // because an A4R4G4B4 makes the avatar look like it has a rare skin disease. baseTex->SetFlags(plMipmap::kAlphaChannelFlag | plMipmap::kForce32Bit | plMipmap::kDontThrowAwayImage); // The tex is what the outfit and the material hold onto. It's what // gets rendered. The base will hang onto the original baseTex. msg = TRACKED_NEW plGenRefMsg(base->GetKey(), plRefMsg::kOnCreate, -1, -1); hsgResMgr::ResMgr()->SendRef(baseTex->GetKey(), msg, plRefFlags::kActiveRef); // Set base texture of avatar plLayRefMsg* layRef = TRACKED_NEW plLayRefMsg(li->GetKey(), plRefMsg::kOnRemove, 0, plLayRefMsg::kTexture); hsgResMgr::ResMgr()->SendRef(baseTex->GetKey(), layRef, plRefFlags::kActiveRef); // Remove it from the material msg = TRACKED_NEW plGenRefMsg(outfit->GetKey(), plRefMsg::kOnCreate, -1, -1); hsgResMgr::ResMgr()->AddViaNotify(li->GetKey(), msg, plRefFlags::kActiveRef); // Set outfit's target layer interface msg = TRACKED_NEW plGenRefMsg(outfit->GetKey(), plRefMsg::kOnCreate, -1, -1); hsgResMgr::ResMgr()->AddViaNotify(mat->GetKey(), msg, plRefFlags::kActiveRef); // Outfit needs the material } else { pErrMsg->Set(outfit->fGroup != plClothingMgr::kClothingBaseNoOptions, node->GetName(), "This avatar expects clothing options, but has no material on which to place them. " "It will not be visable at runtime.").CheckAskOrCancel(); outfit->fGroup = plClothingMgr::kClothingBaseNoOptions; } } void plAvatarComponent::ISetupClothes(plMaxNode *node, plArmatureMod *mod, plErrorMsg *pErrMsg) { AddClothingToMod(node, mod, fCompPB->GetInt(kClothingGroup), nil, pErrMsg); } hsBool plAvatarComponent::SetupProperties(plMaxNode* node, plErrorMsg* pErrMsg) { node->SetItinerant(true); // if(!IVerifyUsedNode(fCompPB->GetINode(plAvatarComponent::kPhysicsProxyFeet), pErrMsg, true)) // return false; // if(!IVerifyUsedNode(fCompPB->GetINode(plAvatarComponent::kPhysicsProxyTorso), pErrMsg, true)) // return false; // if(!IVerifyUsedNode(fCompPB->GetINode(plAvatarComponent::kPhysicsProxyHead), pErrMsg, true)) // return false; if(!IVerifyUsedNode(fCompPB->GetINode(plAvatarComponent::kMeshNode), pErrMsg, false)) return false; if(!IVerifyUsedNode(fCompPB->GetINode(plAvatarComponent::kRootNode), pErrMsg, false)) return false; plMaxNode *meshNode = (plMaxNode *)fCompPB->GetINode(plAvatarComponent::kMeshNode); if (meshNode) { if (meshNode->GetObjectRef()->ClassID() == Class_ID(DUMMY_CLASS_ID, 0)) { meshNode->SetSwappableGeomTarget(plArmatureMod::kSwapTargetShadow); } } return plArmatureComponent::SetupProperties(node, pErrMsg); } class AvatarCompDlgProc : public ParamMap2UserDlgProc { public: AvatarCompDlgProc() {} ~AvatarCompDlgProc() {} BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { int id = LOWORD(wParam); int code = HIWORD(wParam); IParamBlock2 *pb = map->GetParamBlock(); HWND cbox = NULL; char* buffer = NULL; int selection; switch (msg) { case WM_INITDIALOG: int j; for (j = 0; j < plClothingMgr::kMaxGroup; j++) { cbox = GetDlgItem(hWnd, IDC_COMP_AVATAR_CLOTHING_GROUP); SendMessage(cbox, CB_ADDSTRING, 0, (LPARAM)plClothingMgr::GroupStrings[j]); } selection = pb->GetInt(ParamID(plAvatarComponent::kClothingGroup)); SendMessage(cbox, CB_SETCURSEL, selection, 0); for (j = 0; j < plArmatureMod::kMaxBoneBase; j++) { cbox = GetDlgItem(hWnd, IDC_COMP_AVATAR_SKELETON); SendMessage(cbox, CB_ADDSTRING, 0, (LPARAM)plArmatureMod::BoneStrings[j]); } selection = pb->GetInt(ParamID(plAvatarComponent::kSkeleton)); SendMessage(cbox, CB_SETCURSEL, selection, 0); return TRUE; case WM_COMMAND: if (id == IDC_COMP_AVATAR_CLOTHING_GROUP) { selection = SendMessage(GetDlgItem(hWnd, id), CB_GETCURSEL, 0, 0); pb->SetValue(plAvatarComponent::kClothingGroup, t, selection); return TRUE; } if (id == IDC_COMP_AVATAR_SKELETON) { selection = SendMessage(GetDlgItem(hWnd, id), CB_GETCURSEL, 0, 0); pb->SetValue(plAvatarComponent::kSkeleton, t, selection); return TRUE; } break; } return FALSE; } void DeleteThis() {} }; static AvatarCompDlgProc gAvatarCompDlgProc; /////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////// class plCompoundCtrlComponent : plComponent { public: plCompoundCtrlComponent(); hsBool SetupProperties(plMaxNode *node, plErrorMsg *pErrMsg); hsBool Convert(plMaxNode* node,plErrorMsg *pErrMsg); }; CLASS_DESC(plCompoundCtrlComponent, gCompoundCtrlCompDesc, "Compound Controller", "CompoundCtrl", COMP_TYPE_AVATAR, Class_ID(0x3f2a790f, 0x30354673)) plCompoundCtrlComponent::plCompoundCtrlComponent() { fClassDesc = &gCompoundCtrlCompDesc; fClassDesc->MakeAutoParamBlocks(this); } hsBool plCompoundCtrlComponent::SetupProperties(plMaxNode *node, plErrorMsg *pErrMsg) { node->SetMovable(true); node->SetForceLocal(true); node->SetItinerant(true); return true; } hsBool plCompoundCtrlComponent::Convert(plMaxNode* node, plErrorMsg *pErrMsg) { const char *name = node->GetKey()->GetName(); node->MakeCharacterHierarchy(pErrMsg); node->SetupBonesAliasesRecur(name); // create and register the player modifier plAGMasterMod *agMaster = TRACKED_NEW plAGMasterMod(); node->AddModifier(agMaster, IGetUniqueName(node)); return true; } ///////////////////////////////////////////////////////////////////////////// // // LOD Avatar Stuff below class plLODAvatarComponentProc : public plVSBaseComponentProc { protected: IParamBlock2 *fPB; plLODAvatarComponent* fComp; HWND fMstrDlg; public: plLODAvatarComponentProc() : fComp(nil), fPB(nil) {} void UpdateBoneDisplay(IParamMap2 *pm) { HWND hWnd = pm->GetHWnd(); HWND hList = GetDlgItem(hWnd, IDC_COMP_LOD_AVATAR_BONELIST); IParamBlock2 *pb = pm->GetParamBlock(); ListBox_ResetContent(hList); int group = fComp->GetCurGroupIdx(); int startIdx = fComp->GetStartIndex(group); int endIdx = fComp->GetEndIndex(group); while (startIdx < endIdx) { INode *curNode = pb->GetINode(ParamID(plLODAvatarComponent::kBoneList), 0, startIdx); if (curNode == nil) { fComp->RemoveBone(startIdx); endIdx--; continue; } ListBox_AddString(hList, curNode->GetName()); startIdx++; } } virtual void Update(TimeValue t, Interval& valid, IParamMap2* pmap) { UpdateBoneDisplay(pmap); } BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { int selection; HWND cbox = NULL; HWND hList = GetDlgItem(hWnd, IDC_COMP_LOD_AVATAR_BONELIST); switch (msg) { case WM_INITDIALOG: { int LodBeginState = map->GetParamBlock()->GetInt(plLODAvatarComponent::kLODState); HWND LODCombo = GetDlgItem(hWnd, IDC_COMP_LOD_AVATAR_STATE); fMstrDlg = hWnd; fPB = map->GetParamBlock(); fComp = (plLODAvatarComponent*) fPB->GetOwner(); VCharArray Nilptr; ILoadComboBox(LODCombo, fComp->fLODLevels); SendMessage(LODCombo, CB_SETCURSEL, LodBeginState, 0); // select the right one int i; for (i = 0; i < plClothingMgr::kMaxGroup; i++) { cbox = GetDlgItem(hWnd, IDC_COMP_AVATAR_CLOTHING_GROUP); SendMessage(cbox, CB_ADDSTRING, 0, (LPARAM)plClothingMgr::GroupStrings[i]); } selection = fPB->GetInt(ParamID(plLODAvatarComponent::kClothingGroup)); SendMessage(cbox, CB_SETCURSEL, selection, 0); for (i = 0; i < plArmatureMod::kMaxBoneBase; i++) { cbox = GetDlgItem(hWnd, IDC_COMP_AVATAR_SKELETON); SendMessage(cbox, CB_ADDSTRING, 0, (LPARAM)plArmatureMod::BoneStrings[i]); } selection = fPB->GetInt(ParamID(plLODAvatarComponent::kSkeleton)); SendMessage(cbox, CB_SETCURSEL, selection, 0); Mtl *mat = fPB->GetMtl(plLODAvatarComponent::kMaterial); Button_SetText(GetDlgItem(hWnd, IDC_COMP_LOD_AVATAR_MTL), (mat ? mat->GetName() : "(none)")); UpdateBoneDisplay(map); return true; } case WM_COMMAND: if (LOWORD(wParam) == IDC_COMP_AVATAR_CLOTHING_GROUP) { selection = SendMessage(GetDlgItem(hWnd, IDC_COMP_AVATAR_CLOTHING_GROUP), CB_GETCURSEL, 0, 0); fPB->SetValue(ParamID(plLODAvatarComponent::kClothingGroup), t, selection); return TRUE; } else if (LOWORD(wParam) == IDC_COMP_AVATAR_SKELETON) { selection = SendMessage(GetDlgItem(hWnd, IDC_COMP_AVATAR_SKELETON), CB_GETCURSEL, 0, 0); fPB->SetValue(ParamID(plLODAvatarComponent::kSkeleton), t, selection); return TRUE; } else if (HIWORD(wParam) == BN_CLICKED) { if (LOWORD(wParam) == IDC_COMP_LOD_AVATAR_BONE_ADD) { std::vector cids; cids.push_back(Class_ID(TRIOBJ_CLASS_ID, 0)); cids.push_back(Class_ID(EDITTRIOBJ_CLASS_ID, 0)); if (plPick::NodeRefKludge(fPB, plLODAvatarComponent::kLastPick, &cids, true, false)) fComp->AddSelectedBone(); return TRUE; } // Remove the currently selected material else if (LOWORD(wParam) == IDC_COMP_LOD_AVATAR_BONE_REMOVE) { int curSel = SendMessage(hList, LB_GETCURSEL, 0, 0); if (curSel >= 0) fComp->RemoveBone(curSel); return TRUE; } else if (LOWORD(wParam) == IDC_COMP_LOD_AVATAR_MTL) { Mtl *pickedMtl = plPickMaterialMap::PickMaterial(plMtlCollector::kPlasmaOnly); fPB->SetValue(plLODAvatarComponent::kMaterial, 0, pickedMtl); Button_SetText(GetDlgItem(hWnd, IDC_COMP_LOD_AVATAR_MTL), (pickedMtl ? pickedMtl->GetName() : "(none)")); return TRUE; } } else { int LodBeginState = map->GetParamBlock()->GetInt(plLODAvatarComponent::kLODState); if(fPB->GetINode(plLODAvatarComponent::kMeshNodeAddBtn,t)) fPB->SetValue(plLODAvatarComponent::kMeshNodeTab, t, fPB->GetINode(plLODAvatarComponent::kMeshNodeAddBtn,t), LodBeginState); if(LOWORD(wParam) == IDC_COMP_LOD_AVATAR_STATE && HIWORD(wParam) == CBN_SELCHANGE) { int idx = SendMessage((HWND)lParam, CB_GETCURSEL, 0, 0); fPB->SetValue(plLODAvatarComponent::kLODState, 0, idx); if(fPB->GetINode(plLODAvatarComponent::kMeshNodeTab, t, idx)) fPB->SetValue(plLODAvatarComponent::kMeshNodeAddBtn, t, fPB->GetINode(plLODAvatarComponent::kMeshNodeTab,t, idx)); else fPB->Reset(plLODAvatarComponent::kMeshNodeAddBtn); return true; } } break; case WM_CLOSE: { int LodBeginState = map->GetParamBlock()->GetInt(plLODAvatarComponent::kLODState); if(fPB->GetINode(plLODAvatarComponent::kMeshNodeAddBtn,t)) fPB->SetValue(plLODAvatarComponent::kMeshNodeTab, t, fPB->GetINode(plLODAvatarComponent::kMeshNodeAddBtn,t), LodBeginState); return false; } } return false; } void DeleteThis() {} }; //! A static variable. /*! A static instance used in the ParamBlock2 processing of the physical Dialog UIs. \sa plPhysCoreComponentProc() */ static plLODAvatarComponentProc gLODAvComponentProc; class plLODAvAccessor : public PBAccessor { public: //! Public Accessor Class, used in ParamBlock2 processing. /*! Workhorse for this Accessor Class (derived from Max's PBAccessor). When one of our parameters that is a ref changes, send out the component ref changed message. Normally, messages from component refs are ignored since they pass along all the messages of the ref, which generates a lot of false converts. */ void Set(PB2Value& v, ReferenceMaker* owner, ParamID id, int tabIndex, TimeValue t) { if (id == plLODAvatarComponent::kMeshNodeAddBtn) { plLODAvatarComponent *comp = (plLODAvatarComponent *)owner; IParamBlock2 *pb = comp->GetParamBlockByID(plLODAvatarComponent::kBlkComp); int LodBeginState = pb->GetInt(plLODAvatarComponent::kLODState); INode *node = pb->GetINode(plLODAvatarComponent::kMeshNodeAddBtn, t); if (node) pb->SetValue(plLODAvatarComponent::kMeshNodeTab, t, node, LodBeginState); } } }; plLODAvAccessor gLODAvatarAccessor; ///////////////////////////////////////////////////////////////////////////////////////// /////// // PLLODAVATARCOMPONENT CLASS_DESC(plLODAvatarComponent, gLODAvatarCompDesc, "LODAvatar", "LODAvatar", COMP_TYPE_AVATAR, LOD_AVATAR_CLASS_ID) //Max paramblock2 stuff below. ParamBlockDesc2 gPLODAvatarBk ( plComponent::kBlkComp, _T("Avatar"), 0, &gLODAvatarCompDesc, P_AUTO_CONSTRUCT + P_AUTO_UI + P_MULTIMAP, plComponent::kRefComp, //Roll out 1, kArmMain, IDD_COMP_LOD_AVATAR, IDS_COMP_LOD_AVATARS, 0, 0, &gLODAvComponentProc, // kArmBounce, IDD_COMP_PHYS_CORE_GROUP, IDS_COMP_PHYS_BOUNCE, 0, APPENDROLL_CLOSED, &gBounceGroupProc, // kArmReport, IDD_COMP_PHYS_CORE_GROUP, IDS_COMP_PHYS_REPORT, 0, APPENDROLL_CLOSED, &gReportGroupProc, // params plLODAvatarComponent::kPhysicsProxyFeet_DEAD, _T("ProxyFeet"), TYPE_INODE, 0, 0, // p_ui, kArmMain, TYPE_PICKNODEBUTTON, IDC_COMP_AVATAR_PROXY_PICKB, // p_sclassID, GEOMOBJECT_CLASS_ID, // p_prompt, IDS_COMP_AVATAR_PROXYS, end, plLODAvatarComponent::kFriction, _T("Friction"), TYPE_FLOAT, 0, 0, p_ui, kArmMain, TYPE_SPINNER, EDITTYPE_POS_FLOAT, IDC_COMP_AVATAR_FRICTION_EDIT, IDC_COMP_AVATAR_FRICTION_SPIN, 0.1f, p_default, 0.9f, end, plLODAvatarComponent::kLODState, _T("LODState"), TYPE_INT, 0, 0, p_range, 1, plLODAvatarComponent::kMaxNumLODLevels, end, plLODAvatarComponent::kMeshNodeTab, _T("MeshObject"), TYPE_INODE_TAB, plLODAvatarComponent::kMaxNumLODLevels, 0, 0, p_accessor, &gLODAvatarAccessor, end, plLODAvatarComponent::kRootNodeAddBtn, _T("RtNodePicker"), TYPE_INODE, 0, 0, p_ui, kArmMain, TYPE_PICKNODEBUTTON, IDC_COMP_LOD_AVATAR_ROOT_PICKB, p_prompt, IDS_COMP_AVATAR_PROXYS, end, plLODAvatarComponent::kMeshNodeAddBtn, _T("MshNodePicker"), TYPE_INODE, 0, 0, p_ui, kArmMain, TYPE_PICKNODEBUTTON, IDC_COMP_LOD_AVATAR_MESH_PICKB, //p_sclassID, GEOMOBJECT_CLASS_ID, p_prompt, IDS_COMP_AVATAR_PROXYS, end, plLODAvatarComponent::kClothingGroup, _T("ClothingGroup"), TYPE_INT, 0, 0, p_default, plClothingMgr::kClothingBaseMale, end, plArmatureComponent::kBounceGroups, _T("bounceGroups"), TYPE_INT, 0,0, p_default, plPhysicsGroups_DEAD::kCreatures | plPhysicsGroups_DEAD::kStaticSimulated | plPhysicsGroups_DEAD::kDynamicSimulated | plPhysicsGroups_DEAD::kAnimated, end, plArmatureComponent::kReportGroups, _T("reportGroups"), TYPE_INT, 0,0, end, plLODAvatarComponent::kBrainType, _T("Brain"), TYPE_INT, 0, 0, p_default, plLODAvatarComponent::kBrainHuman, end, plLODAvatarComponent::kGroupIdx, _T("GroupIndex"), TYPE_INT, 0, 0, p_default, 0, p_range, 0, plLODAvatarComponent::kMaxNumLODLevels - 1, p_ui, kArmMain, TYPE_SPINNER, EDITTYPE_INT, IDC_COMP_LOD_AVATAR_GROUP, IDC_COMP_LOD_AVATAR_GROUP_SPIN, 1.f, end, plLODAvatarComponent::kBoneList, _T("Bones"), TYPE_INODE_TAB, 0, 0, 0, end, plLODAvatarComponent::kGroupTotals, _T("Totals"), TYPE_INT_TAB, plLODAvatarComponent::kMaxNumLODLevels, 0, 0, p_default, 0, end, plLODAvatarComponent::kLastPick, _T("LastPick"), TYPE_INODE, 0, 0, // Temp storage space for the bone picker end, plLODAvatarComponent::kSkeleton, _T("Skeleton"), TYPE_INT, 0, 0, p_default, plArmatureMod::kBoneBaseMale, end, plLODAvatarComponent::kMaterial, _T("Material"), TYPE_MTL, 0, 0, end, plLODAvatarComponent::kPhysicsProxyTorso_DEAD, _T("ProxyTorso"), TYPE_INODE, 0, 0, // p_ui, kArmMain, TYPE_PICKNODEBUTTON, IDC_COMP_AVATAR_PROXY_PICKT, // p_sclassID, GEOMOBJECT_CLASS_ID, // p_prompt, IDS_COMP_AVATAR_PROXYS, end, // plLODAvatarComponent::kPhysicsProxyHead_DEAD, _T("ProxyHead"), TYPE_INODE, 0, 0, // p_ui, kArmMain, TYPE_PICKNODEBUTTON, IDC_COMP_AVATAR_PROXY_PICKH, // p_sclassID, GEOMOBJECT_CLASS_ID, // p_prompt, IDS_COMP_AVATAR_PROXYS, end, plLODAvatarComponent::kPhysicsHeight, _T("physHeight"), TYPE_FLOAT, 0, 0, p_range, 0.1f, 50.0f, p_default, 5.f, p_ui, kArmMain, TYPE_SPINNER, EDITTYPE_FLOAT, IDC_PHYS_HEIGHT_EDIT, IDC_PHYS_HEIGHT_SPIN, SPIN_AUTOSCALE, end, plLODAvatarComponent::kPhysicsWidth, _T("physWidth"), TYPE_FLOAT, 0, 0, p_range, 0.1f, 50.0f, p_default, 2.5f, p_ui, kArmMain, TYPE_SPINNER, EDITTYPE_FLOAT, IDC_PHYS_WIDTH_EDIT, IDC_PHYS_WIDTH_SPIN, SPIN_AUTOSCALE, end, plLODAvatarComponent::kBodyFootstepSoundPage, _T("bodyFootstepPage"), TYPE_STRING, 0, 0, p_default, "Audio", p_ui, kArmMain, TYPE_EDITBOX, IDC_BODYFOOTSTEPPAGE_EDIT, end, plLODAvatarComponent::kAnimationPrefix,_T("animationPrefix"), TYPE_STRING, 0, 0, p_default, "Male", p_ui, kArmMain, TYPE_EDITBOX, IDC_ANIMATIONPREFIX_EDIT, end, end ); plLODAvatarComponent::plLODAvatarComponent() : fMaterial(nil) { fClassDesc = &gLODAvatarCompDesc; fClassDesc->MakeAutoParamBlocks(this); fLODLevels.push_back("High"); fLODLevels.push_back("Medium"); fLODLevels.push_back("Low"); } void plLODAvatarComponent::IAttachModifiers( plMaxNode *node, plErrorMsg *pErrMsg) { const char *avatarName = node->GetKey()->GetName(); plMaxNode *animRoot = (plMaxNode *)fCompPB->GetINode(plLODAvatarComponent::kRootNodeAddBtn); plKey animRootKey = animRoot->GetSceneObject()->GetKey(); plArmatureLODMod* avMod = TRACKED_NEW plArmatureLODMod(avatarName); int skeletonType = fCompPB->GetInt(ParamID(kSkeleton)); avMod->SetBodyType( skeletonType ); if (skeletonType == plArmatureLODMod::kBoneBaseCritter) avMod->PushBrain(TRACKED_NEW plAvBrainCritter()); else avMod->PushBrain(TRACKED_NEW plAvBrainHuman(skeletonType == plArmatureMod::kBoneBaseActor)); avMod->SetBodyAgeName(node->GetAgeName()); avMod->SetBodyFootstepSoundPage(fCompPB->GetStr(ParamID(kBodyFootstepSoundPage))); avMod->SetAnimationPrefix(fCompPB->GetStr(ParamID(kAnimationPrefix))); int iLODCount = fCompPB->Count(plLODAvatarComponent::kMeshNodeTab); for (int i = 0; i < iLODCount; i++) { plMaxNode *meshNode = (plMaxNode *)fCompPB->GetINode(plLODAvatarComponent::kMeshNodeTab, 0, i); plKey meshKey = meshNode->GetSceneObject()->GetKey(); avMod->AppendMeshKey(meshKey); } node->AddModifier(avMod, IGetUniqueName(node)); fArmMod = avMod; IAttachShadowCastToLODs(node); } hsBool plLODAvatarComponent::SetupProperties(plMaxNode* node, plErrorMsg* pErrMsg) { node->SetItinerant(true); // if(!IVerifyUsedNode(fCompPB->GetINode(plLODAvatarComponent::kPhysicsProxyFeet), pErrMsg, true)) // return false; // if(!IVerifyUsedNode(fCompPB->GetINode(plLODAvatarComponent::kPhysicsProxyTorso), pErrMsg, true)) // return false; // if(!IVerifyUsedNode(fCompPB->GetINode(plLODAvatarComponent::kPhysicsProxyHead), pErrMsg, true)) // return false; if(!IVerifyUsedNode(fCompPB->GetINode(plLODAvatarComponent::kRootNodeAddBtn), pErrMsg, false)) return false; for(int i = 0; i < plLODAvatarComponent::kMaxNumLODLevels; i++) { plMaxNode *meshNode = (plMaxNode *)fCompPB->GetINode(plLODAvatarComponent::kMeshNodeTab, 0, i); if (!IVerifyUsedNode(meshNode, pErrMsg, false)) { return false; } else { if (meshNode->GetObjectRef()->ClassID() == Class_ID(DUMMY_CLASS_ID, 0)) { meshNode->SetSwappableGeomTarget(plArmatureMod::kSwapTargetShadow); } } } return plArmatureComponent::SetupProperties(node, pErrMsg); } hsBool plLODAvatarComponent::PreConvert(plMaxNode* node, plErrorMsg* pErrMsg) { hsBool result = plArmatureComponent::PreConvert(node, pErrMsg); hsTArray mats; Mtl *mtl = fCompPB->GetMtl(kMaterial); if (mtl) { hsMaterialConverter::Instance().GetMaterialArray(mtl, node, mats); fMaterial = (mats.GetCount() > 0 ? mats[0] : nil); } return result; } hsBool plLODAvatarComponent::Convert(plMaxNode* node, plErrorMsg* pErrMsg) { plArmatureComponent::Convert(node, pErrMsg); // Bone LOD stuff int numSoFar = 0; int i; for (i = 0; i < kMaxNumLODLevels; i++) { int numBones = fCompPB->GetInt(ParamID(kGroupTotals), 0, i); plKeyVector *keyVec = TRACKED_NEW plKeyVector; int j; for (j = 0; j < numBones; j++) { plMaxNode *compNode = (plMaxNode*)fCompPB->GetINode(ParamID(kBoneList), 0, numSoFar + j); if (compNode) { plAGModifier *agMod = compNode->HasAGMod(); keyVec->push_back(agMod ? agMod->GetKey() : nil); } } plArmatureLODMod::ConvertNoRef(fArmMod)->AppendBoneVec(keyVec); numSoFar += numBones; } return true; } void plLODAvatarComponent::ISetupClothes(plMaxNode *node, plArmatureMod *mod, plErrorMsg* pErrMsg) { AddClothingToMod(node, mod, fCompPB->GetInt(kClothingGroup), fMaterial, pErrMsg); } void plLODAvatarComponent::IAttachShadowCastModifiersRecur(plMaxNode* node, plShadowCaster* caster) { if( !node || !caster ) return; if( !node->GetSwappableGeom() && node->GetObjectRef()->ClassID() != Class_ID(DUMMY_CLASS_ID, 0)) plShadowCastComponent::AddShadowCastModifier(node, caster); int i; for( i = 0; i < node->NumberOfChildren(); i++ ) IAttachShadowCastModifiersRecur((plMaxNode*)node->GetChildNode(i), caster); } void plLODAvatarComponent::IAttachShadowCastToLODs(plMaxNode* rootNode) { plShadowCaster* caster = TRACKED_NEW plShadowCaster; hsgResMgr::ResMgr()->NewKey(IGetUniqueName(rootNode), caster, rootNode->GetLocation()); caster->SetSelfShadow(true); int iLODCount = fCompPB->Count(plLODAvatarComponent::kMeshNodeTab); for (int i = 0; i < iLODCount; i++) { plMaxNode *meshNode = (plMaxNode *)fCompPB->GetINode(plLODAvatarComponent::kMeshNodeTab, 0, i); if( meshNode ) { plShadowCastComponent::AddShadowCastModifier(meshNode, caster); // The LOD roots are a special case. IAttachShadowCastModifiersRecur(meshNode, caster); } } } int plLODAvatarComponent::GetCurGroupIdx() { return fCompPB->GetInt(ParamID(kGroupIdx)); } int plLODAvatarComponent::GetStartIndex(int group) { int result = 0; int i; for (i = 0; i < group; i++) result += fCompPB->GetInt(ParamID(kGroupTotals), 0, i); return result; } int plLODAvatarComponent::GetEndIndex(int group) { return GetStartIndex(group) + fCompPB->GetInt(ParamID(kGroupTotals), 0, group); } void plLODAvatarComponent::AddSelectedBone() { int group = GetCurGroupIdx(); int boneIdx = GetEndIndex(group); INode *node = fCompPB->GetINode(ParamID(kLastPick)); fCompPB->Insert(ParamID(kBoneList), boneIdx, 1, &node); fCompPB->SetValue(ParamID(kGroupTotals), 0, fCompPB->GetInt(ParamID(kGroupTotals), 0, group) + 1, group); } void plLODAvatarComponent::RemoveBone(int index) { int group = GetCurGroupIdx(); int boneIdx = GetStartIndex(group) + index; fCompPB->Delete(ParamID(kBoneList), boneIdx, 1); fCompPB->SetValue(ParamID(kGroupTotals), 0, fCompPB->GetInt(ParamID(kGroupTotals), 0, group) - 1, group); }