|
|
|
/*==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 "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 <windowsx.h>
|
|
|
|
#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<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 = 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<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)
|
|
|
|
{
|
|
|
|
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<hsGMaterial*> 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
|