/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Additional permissions under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "HeadSpin.h"
#include "plgDispatch.h"
#include "hsResMgr.h"
#include "hsStringTokenizer.h"
#include "plComponentProcBase.h"
#include "plComponent.h"
#include "plComponentReg.h"
#include "plAudioComponents.h"
#include "plPhysicalComponents.h"
#include "MaxMain/plMaxNode.h"
#include "resource.h"
#include
#pragma hdrstop
#include "plAvatarComponent.h"
#include "plMaxAnimUtils.h"
#include "plPickNode.h"
#include "plPickMaterialMap.h"
#include "MaxConvert/hsConverterUtils.h"
#include "pnSceneObject/plSceneObject.h"
#include "plScene/plSceneNode.h"
#include "MaxConvert/hsConverterUtils.h"
#include "MaxConvert/hsControlConverter.h"
#include "MaxConvert/hsMaterialConverter.h"
#include "MaxConvert/plBitmapCreator.h"
#include "pnMessage/plNodeRefMsg.h"
#include "pnMessage/plObjRefMsg.h"
#include "pnMessage/plIntRefMsg.h"
#include "plMessage/plMatRefMsg.h"
#include "plMessage/plLayRefMsg.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 "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 "MaxMain/plPhysicalProps.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 = 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
bool 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);
}
}
bool plArmatureComponent::PreConvert(plMaxNode* node, plErrorMsg* pErrMsg)
{
// add audio interface and record/playback component
pl2WayWinAudible* pAudible = new pl2WayWinAudible;
// Add a key for it
plKey key = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), pAudible, node->GetLocation() );
plAudioInterface* ai = new plAudioInterface;
plKey pAiKey = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), (hsKeyedObject*)ai,node->GetLocation());
hsgResMgr::ResMgr()->AddViaNotify(pAiKey, new plObjRefMsg(node->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
plIntRefMsg* pMsg = 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....
bool 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 = new plArmatureEffectsMgr();
hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), effects, node->GetLocation());
plGenRefMsg *msg = 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 = 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;
}
bool plArmatureComponent::IVerifyUsedNode(INode* thisNode, plErrorMsg* pErrMsg, bool 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;
// bool fUseNewMovement;
//
// AvatarStats(float a, float b, float c, float d, bool 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)
{
plString 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 = 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(new plAvBrainCritter());
else
avMod->PushBrain(new plAvBrainHuman(skeletonType == plArmatureMod::kBoneBaseActor));
avMod->SetBodyAgeName(node->GetAgeName());
avMod->SetBodyFootstepSoundPage(fCompPB->GetStr(ParamID(kBodyFootstepSoundPage)));
avMod->SetAnimationPrefix(plString::FromUtf8(fCompPB->GetStr(ParamID(kAnimationPrefix))));
//AddLinkSound(node, node->GetSceneObject()->GetKey(), pErrMsg );
plKey avKey = hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), avMod, node->GetLocation());
plObjRefMsg *objRefMsg = 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;
plString keyName;
TSTR sdata;
hsStringTokenizer toker;
if (mod == nil)
{
hsAssert(false, "Adding clothes to a nil armatureMod.");
return;
}
plClothingBase *base = new plClothingBase();
if (node->GetUserPropString("layout", sdata))
{
toker.Reset(sdata, hsConverterUtils::fTagSeps);
base->SetLayoutName(toker.next());
}
else
base->SetLayoutName("BasicHuman");
keyName = plFormat("{}_ClothingBase", node->GetName());
hsgResMgr::ResMgr()->NewKey(keyName, base, node->GetLocation());
plClothingOutfit *outfit = new plClothingOutfit();
outfit->fGroup = group;
keyName = plFormat("{}_outfit", mod->GetKey()->GetName());
hsgResMgr::ResMgr()->NewKey(keyName, outfit, node->GetLocation());
msg = new plGenRefMsg(outfit->GetKey(), plRefMsg::kOnCreate, -1, -1);
hsgResMgr::ResMgr()->AddViaNotify(base->GetKey(), msg, plRefFlags::kActiveRef); // Add clothing base to outfit
msg = 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 = new plGenRefMsg(base->GetKey(), plRefMsg::kOnCreate, -1, -1);
hsgResMgr::ResMgr()->SendRef(baseTex->GetKey(), msg, plRefFlags::kActiveRef); // Set base texture of avatar
plLayRefMsg* layRef = new plLayRefMsg(li->GetKey(), plRefMsg::kOnRemove, 0, plLayRefMsg::kTexture);
hsgResMgr::ResMgr()->SendRef(baseTex->GetKey(), layRef, plRefFlags::kActiveRef); // Remove it from the material
msg = new plGenRefMsg(outfit->GetKey(), plRefMsg::kOnCreate, -1, -1);
hsgResMgr::ResMgr()->AddViaNotify(li->GetKey(), msg, plRefFlags::kActiveRef); // Set outfit's target layer interface
msg = 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);
}
bool 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 : public plComponent
{
public:
plCompoundCtrlComponent();
bool SetupProperties(plMaxNode *node, plErrorMsg *pErrMsg);
bool 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);
}
bool plCompoundCtrlComponent::SetupProperties(plMaxNode *node, plErrorMsg *pErrMsg)
{
node->SetMovable(true);
node->SetForceLocal(true);
node->SetItinerant(true);
return true;
}
bool plCompoundCtrlComponent::Convert(plMaxNode* node, plErrorMsg *pErrMsg)
{
plString name = node->GetKey()->GetName();
node->MakeCharacterHierarchy(pErrMsg);
node->SetupBonesAliasesRecur(name.c_str());
// create and register the player modifier
plAGMasterMod *agMaster = 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)
{
plString avatarName = node->GetKey()->GetName();
plMaxNode *animRoot = (plMaxNode *)fCompPB->GetINode(plLODAvatarComponent::kRootNodeAddBtn);
plKey animRootKey = animRoot->GetSceneObject()->GetKey();
plArmatureLODMod* avMod = new plArmatureLODMod(avatarName);
int skeletonType = fCompPB->GetInt(ParamID(kSkeleton));
avMod->SetBodyType( skeletonType );
if (skeletonType == plArmatureLODMod::kBoneBaseCritter)
avMod->PushBrain(new plAvBrainCritter());
else
avMod->PushBrain(new plAvBrainHuman(skeletonType == plArmatureMod::kBoneBaseActor));
avMod->SetBodyAgeName(node->GetAgeName());
avMod->SetBodyFootstepSoundPage(fCompPB->GetStr(ParamID(kBodyFootstepSoundPage)));
avMod->SetAnimationPrefix(plString::FromUtf8(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);
}
bool 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);
}
bool plLODAvatarComponent::PreConvert(plMaxNode* node, plErrorMsg* pErrMsg)
{
bool 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;
}
bool 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 = 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 = 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);
}