/*==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 "plComponent.h"
#include "plComponentReg.h"
#include "plAudioComponents.h"
#include "plPhysicalComponents.h"
#include "MaxMain/plMaxNode.h"

#include "resource.h"

#include "MaxMain/MaxAPI.h"
#include "MaxMain/hsMStringTokenizer.h"

#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 "plAnimation/plAGModifier.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 = nullptr;

    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)
    {
        auto 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);

    // Attach footstep sounds if we didn't leave the field blank
    if (!fArmMod->GetBodyFootstepSoundPage().empty()) {
        // 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 != nullptr)
    {
        if(((plMaxNode*)thisNode)->CanConvert())
        {
            if(IsHull)
                ((plMaxNode*)thisNode)->SetDrawable(false);

        }else
        {
            pErrMsg->Set(true, "Ignored Node Selection", ST::format("The object that Node Ptr {} 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,
        p_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,
        p_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,
        p_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,
        p_end,
        
    plAvatarComponent::kClothingGroup,  _T("ClothingGroup"),    TYPE_INT,   0, 0,
        p_default, plClothingMgr::kClothingBaseMale,
        p_end,

    plArmatureComponent::kBounceGroups, _T("bounceGroups"), TYPE_INT,   0,0,
        p_default,  plPhysicsGroups_DEAD::kCreatures |
                    plPhysicsGroups_DEAD::kStaticSimulated |
                    plPhysicsGroups_DEAD::kDynamicSimulated |
                    plPhysicsGroups_DEAD::kAnimated,
        p_end,

    plArmatureComponent::kReportGroups, _T("reportGroups"), TYPE_INT,   0,0,
        p_end,

    plAvatarComponent::kBrainType, _T("Brain"), TYPE_INT, 0, 0,
        p_default, plAvatarComponent::kBrainHuman,
        p_end,
        
    plAvatarComponent::kSkeleton, _T("Skeleton"),   TYPE_INT, 0, 0,
        p_default, plArmatureMod::kBoneBaseMale,
        p_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,
        p_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,
        p_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,
        p_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,
        p_end,

    plAvatarComponent::kBodyFootstepSoundPage,  _T("bodyFootstepPage"), TYPE_STRING,    0,  0,
        p_default, _T("Audio"),
        p_ui,   kArmMain, TYPE_EDITBOX, IDC_BODYFOOTSTEPPAGE_EDIT,
        p_end,

    plAvatarComponent::kAnimationPrefix,_T("animationPrefix"), TYPE_STRING, 0,  0,
        p_default, _T("Male"),
        p_ui,   kArmMain, TYPE_EDITBOX, IDC_ANIMATIONPREFIX_EDIT,
        p_end,

    p_end
);

plAvatarComponent::plAvatarComponent()
{
    fClassDesc = &gAvatarCompDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}


void plAvatarComponent::IAttachModifiers(plMaxNode *node, plErrorMsg *pErrMsg)
{
    ST::string 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));

    ST::string footstepPage = M2ST(fCompPB->GetStr(ParamID(kBodyFootstepSoundPage)));
    if (!footstepPage.empty() && footstepPage.compare_ni("Audio", 5) != 0)
        avMod->SetBodyAgeName(node->GetAgeName()); // Custom footstep sounds
    else
        avMod->SetBodyAgeName("GlobalAvatars"); // Fallback to the Global ones

    avMod->SetBodyFootstepSoundPage(footstepPage);
    avMod->SetAnimationPrefix(M2ST(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;
    ST::string keyName;
    MSTR sdata;
    hsMStringTokenizer toker;

    if (mod == nullptr)
    {
        hsAssert(false, "Adding clothes to a nil armatureMod.");
        return;
    }

    if (group == plClothingMgr::kClothingBaseNoOptions) {
        // We don't need a clothing outfit for avatars with no clothing options
        return;
    }

    plClothingBase *base = new plClothingBase();
    if (node->GetUserPropString(_M("layout"), sdata))
    {
        toker.Reset(sdata.data(), hsConverterUtils::fTagSeps);
        base->SetLayoutName(M2ST(toker.next()));
    }
    else 
        base->SetLayoutName("BasicHuman");
    keyName = ST::format("{}_ClothingBase", node->GetName());
    hsgResMgr::ResMgr()->NewKey(keyName, base, node->GetLocation());
    plClothingOutfit *outfit = new plClothingOutfit();
    outfit->fGroup = group;
    keyName = ST::format("{}_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 = nullptr;
    plLayerInterface *li = nullptr;

    if (mat != nullptr)
    {
        li = mat->GetLayer(0)->BottomOfStack();
        baseTex = plMipmap::ConvertNoRef(li->GetTexture());
        hsAssert(li->GetTexture() == baseTex, "Base texture mismatch on avatar construction?");
    }
    if (mat != nullptr && baseTex != nullptr)
    {
        // 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), nullptr, 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() {}

    INT_PTR DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) override
    {
        int id = LOWORD(wParam);
        int code = HIWORD(wParam);

        IParamBlock2 *pb = map->GetParamBlock();
        HWND cbox = nullptr;

        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)ST2T(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)ST2T(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 = (int)SendMessage(GetDlgItem(hWnd, id), CB_GETCURSEL, 0, 0);
                pb->SetValue(plAvatarComponent::kClothingGroup, t, selection);
                return TRUE;
            }
            if (id == IDC_COMP_AVATAR_SKELETON)
            {
                selection = (int)SendMessage(GetDlgItem(hWnd, id), CB_GETCURSEL, 0, 0);
                pb->SetValue(plAvatarComponent::kSkeleton, t, selection);
                return TRUE;
            }

            break;
        }   
        return FALSE;
    }
    void DeleteThis() override { }
};
static AvatarCompDlgProc gAvatarCompDlgProc;

///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////

class plCompoundCtrlComponent : public plComponent
{
public:
    plCompoundCtrlComponent();
    bool SetupProperties(plMaxNode *node, plErrorMsg *pErrMsg) override;
    bool Convert(plMaxNode* node,plErrorMsg *pErrMsg) override;
};

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)
{
    ST::string name = node->GetKey()->GetName();

    node->MakeCharacterHierarchy(pErrMsg);
    node->SetupBonesAliasesRecur(name);


    // 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(), fPB() { }

    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 == nullptr)
            {
                fComp->RemoveBone(startIdx);
                endIdx--;
                continue;
            }
            ListBox_AddString(hList, curNode->GetName());
            startIdx++;
        }
    }

    void Update(TimeValue t, Interval& valid, IParamMap2* pmap) override { UpdateBoneDisplay(pmap); }

    INT_PTR DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) override
    {
        int selection;
        HWND cbox = nullptr;
        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();

                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)ST2T(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)ST2T(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() : _T("(none)")));

                UpdateBoneDisplay(map);
                return TRUE;
            }

        case WM_COMMAND:
            if (LOWORD(wParam) == IDC_COMP_AVATAR_CLOTHING_GROUP)
            {
                selection = (int)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 = (int)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 = (int)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() : _T("(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 = (int)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() override { }
};




//! 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) override
    {
        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,
        p_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,
        p_end,

    plLODAvatarComponent::kLODState, _T("LODState"),        TYPE_INT,   0, 0,
        p_range, 1, plLODAvatarComponent::kMaxNumLODLevels,
        p_end,

    plLODAvatarComponent::kMeshNodeTab, _T("MeshObject"),   TYPE_INODE_TAB, plLODAvatarComponent::kMaxNumLODLevels,     0, 0,
        p_accessor,     &gLODAvatarAccessor,
        p_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,
        p_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,
        p_end,

    plLODAvatarComponent::kClothingGroup,   _T("ClothingGroup"),    TYPE_INT,   0, 0,
        p_default, plClothingMgr::kClothingBaseMale,
        p_end,

    plArmatureComponent::kBounceGroups, _T("bounceGroups"), TYPE_INT,   0,0,
        p_default,  plPhysicsGroups_DEAD::kCreatures |
                    plPhysicsGroups_DEAD::kStaticSimulated |
                    plPhysicsGroups_DEAD::kDynamicSimulated |
                    plPhysicsGroups_DEAD::kAnimated,
        p_end,

    plArmatureComponent::kReportGroups, _T("reportGroups"), TYPE_INT,   0,0,
        p_end,

    plLODAvatarComponent::kBrainType, _T("Brain"), TYPE_INT, 0, 0,
        p_default, plLODAvatarComponent::kBrainHuman,
        p_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,
        p_end,

    plLODAvatarComponent::kBoneList, _T("Bones"), TYPE_INODE_TAB, 0,    0, 0,
        p_end,

    plLODAvatarComponent::kGroupTotals, _T("Totals"), TYPE_INT_TAB, plLODAvatarComponent::kMaxNumLODLevels, 0, 0,
        p_default, 0,
        p_end,

    plLODAvatarComponent::kLastPick, _T("LastPick"), TYPE_INODE,    0, 0, // Temp storage space for the bone picker
        p_end,

    plLODAvatarComponent::kSkeleton, _T("Skeleton"),    TYPE_INT, 0, 0,
        p_default, plArmatureMod::kBoneBaseMale,
        p_end,

    plLODAvatarComponent::kMaterial, _T("Material"),    TYPE_MTL, 0, 0,
        p_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,
        p_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,
        p_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,
        p_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,
        p_end,

    plLODAvatarComponent::kBodyFootstepSoundPage,   _T("bodyFootstepPage"), TYPE_STRING,    0,  0,
        p_default, _T("Audio"),
        p_ui,   kArmMain, TYPE_EDITBOX, IDC_BODYFOOTSTEPPAGE_EDIT,
        p_end,

    plLODAvatarComponent::kAnimationPrefix,_T("animationPrefix"), TYPE_STRING,  0,  0,
        p_default, _T("Male"),
        p_ui,   kArmMain, TYPE_EDITBOX, IDC_ANIMATIONPREFIX_EDIT,
        p_end,

    p_end
);

plLODAvatarComponent::plLODAvatarComponent() : fMaterial()
{
    fClassDesc = &gLODAvatarCompDesc;
    fClassDesc->MakeAutoParamBlocks(this);

    fLODLevels = {
        _M("High"),
        _M("Medium"),
        _M("Low"),
    };

}

void plLODAvatarComponent::IAttachModifiers(    plMaxNode *node, plErrorMsg *pErrMsg)
{
    ST::string 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(M2ST(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); 

    std::vector<hsGMaterial*> mats;
    Mtl *mtl = fCompPB->GetMtl(kMaterial);
    if (mtl)
    {
        hsMaterialConverter::Instance().GetMaterialArray(mtl, node, mats);
        fMaterial = (!mats.empty() ? mats[0] : nullptr);
    }
    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() : nullptr);
            }
        }
        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);
}

