You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1236 lines
40 KiB

/*==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 <http://www.gnu.org/licenses/>.
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 <windowsx.h>
#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 <vector>
//#include <string>
#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<const plAGModifier *>(FindModifierByClass(obj, plAGModifier::Index()));
plAGModifier *agMod = const_cast<plAGModifier *>(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<Class_ID> 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<hsGMaterial*> 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);
}