/*==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 "MaxAPI.h"

#include "pnKeyedObject/plKey.h"
#include "hsMatrix44.h"
#include "plRenderLevel.h"

#include "plMaxNodeBase.h"
#include "plMaxNodeData.h"
#include "MaxComponent/plComponentBase.h"

#include <algorithm>
#include <set>
#include <vector>

// To support the new Plasma Light Objs, the classes are included below
#include "MaxPlasmaLights/plRealTimeLightBase.h"

#include "GlobalUtility.h"  // Only needed for PLASMA_MAX_CLASSID, fix?

#include "pnKeyedObject/plUoid.h"

#include "pnModifier/plModifier.h"

#include "MaxPlasmaMtls/Materials/plDecalMtl.h"

CoreExport void *__cdecl MAX_new(size_t size);
CoreExport void __cdecl MAX_delete(void* mem);

void plMaxNodeBase::SetMaxNodeData(plMaxNodeData * pdat)
{
    auto dbgNodeName = GetName();

    // If object is a component, don't add node data
    if (IsComponent())
        return;

    AppDataChunk *adc = GetAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaMaxNodeDataChunk);

    // If pointer is nil, remove any data
    if (!pdat)
    {
        if( adc )
        {
            plMaxNodeData *pDataChunk = (plMaxNodeData*)adc->data;
            pDataChunk->DeInit();
        }
        RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaMaxNodeDataChunk);
        return;     
    }

    if (!adc)
    {
        // Does not exist, create a new one...
        int len = sizeof(plMaxNodeData);
        plMaxNodeData *pDataChunk = (plMaxNodeData *)MAX_new(len);
        memcpy(pDataChunk, pdat, sizeof(*pdat));
        pDataChunk->Init();
        *pDataChunk = *pdat;
        AddAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaMaxNodeDataChunk, len, pDataChunk);
    }
    else
    {
        plMaxNodeData *pDataChunk = (plMaxNodeData*)adc->data;
        // if someone does a GetMaxNodeData, they get a pointer to the data,
        // No need to set it, other wise set the Data chunk from the  MaxNodeData passed in
        if (pDataChunk != pdat)
            *pDataChunk = *pdat;
    }
}

plMaxNodeData *plMaxNodeBase::GetMaxNodeData()
{
    AppDataChunk *adc = GetAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaMaxNodeDataChunk);
    if (adc)
        return (plMaxNodeData*)adc->data;

    return nullptr;
}

#define GetMD   plMaxNodeData *pMD = GetMaxNodeData(); //hsAssert(pMD,"Missing MaxNodeData");   // Get MaxNode Data

plKey           plMaxNodeBase::GetKey()             { GetMD; return (pMD) ? pMD->GetKey() : nullptr; }
plSceneObject*  plMaxNodeBase::GetSceneObject()     { GetMD; return (pMD) ? pMD->GetSceneObject() : nullptr; }
bool            plMaxNodeBase::GetForceLocal()          { GetMD; return (pMD) ? pMD->GetForceLocal() : false; }
bool            plMaxNodeBase::GetReverseSort()         { GetMD; return (pMD) ? pMD->GetReverseSort() : false;}
bool            plMaxNodeBase::GetSortAsOpaque()        { GetMD; return (pMD) ? pMD->GetSortAsOpaque() : false;}
bool            plMaxNodeBase::GetVS()                  { GetMD; return (pMD) ? pMD->GetVS() : false;}
bool            plMaxNodeBase::GetHasWaterHeight()      { GetMD; return (pMD) ? pMD->GetHasWaterHeight() : false; }
float           plMaxNodeBase::GetWaterHeight()         { GetMD; return (pMD) ? pMD->GetWaterHeight() : 0; }
bool            plMaxNodeBase::GetSmoothAll()           { GetMD; return (pMD) ? pMD->GetSmoothAll() : false;}
bool            plMaxNodeBase::GetForceSortable()       { GetMD; return (pMD) ? pMD->GetForceSortable() : false;}
bool            plMaxNodeBase::GetConcave()             { GetMD; return (pMD) ? pMD->GetConcave() : false;}
bool            plMaxNodeBase::GetCalcEdgeLens()        { GetMD; return (pMD) ? pMD->GetCalcEdgeLens() : false;}
bool            plMaxNodeBase::GetRunTimeLight()        { GetMD; return (pMD) ? pMD->GetRunTimeLight() : false;}
bool            plMaxNodeBase::GetForceMatShade()       { GetMD; return (pMD) ? pMD->GetForceMatShade() : false;}
bool            plMaxNodeBase::GetForceVisLOS()         { GetMD; return (pMD) ? pMD->GetForceVisLOS() : false;}
bool            plMaxNodeBase::GetEnviron()     { GetMD; return (pMD) ? pMD->GetEnviron() : false;}
bool            plMaxNodeBase::GetEnvironOnly()     { GetMD; return (pMD) ? pMD->GetEnvironOnly() : false;}
bool            plMaxNodeBase::GetWaterDecEnv()         { GetMD; return (pMD) ? pMD->GetWaterDecEnv() : false; }
bool            plMaxNodeBase::GetNoPreShade()          { GetMD; return (pMD) ? pMD->GetNoPreShade() && !pMD->GetForcePreShade() : false;}
bool            plMaxNodeBase::GetForcePreShade()       { GetMD; return (pMD) ? pMD->GetForcePreShade() : false;}
plKey           plMaxNodeBase::GetRoomKey()         { GetMD; return (pMD) ? pMD->GetRoomKey() : nullptr; }
bool            plMaxNodeBase::GetDrawable()            { GetMD; return (pMD) ? pMD->GetDrawable() : false;   }
bool            plMaxNodeBase::GetPhysical()            { GetMD; return (pMD) ? pMD->GetPhysical() : false;   }
bool            plMaxNodeBase::GetItinerant()           { GetMD; return (pMD) ? pMD->GetItinerant() : false;  }
bool            plMaxNodeBase::GetUnBounded()           { GetMD; return (pMD) ? pMD->GetUnBounded() : false;  }
bool            plMaxNodeBase::GetDisableNormal()       { GetMD; return (pMD) ? pMD->GetDisableNormal() : false;  }
uint32_t        plMaxNodeBase::GetDecalLevel()          { GetMD; return (pMD) ? pMD->GetDecalLevel() : 0; }
bool            plMaxNodeBase::GetMovable()         { GetMD; return (pMD) ? pMD->GetMovable() : false;    }
bool            plMaxNodeBase::GetIsBarney()            { GetMD; return (pMD) ? pMD->GetIsBarney() : false;   }
bool            plMaxNodeBase::GetForceShadow()         { GetMD; return (pMD) ? pMD->GetForceShadow() : false;    }
bool            plMaxNodeBase::GetAlphaTestHigh()       { GetMD; return (pMD) ? pMD->GetAlphaTestHigh() : false;  }
bool            plMaxNodeBase::GetFilterInherit()           { GetMD; return (pMD) ? pMD->GetFilterInherit() : false;  }
bool            plMaxNodeBase::GetNoShadow()            { GetMD; return (pMD) ? pMD->GetNoShadow() : false;   }
bool            plMaxNodeBase::GetNoSpanSort()          { GetMD; return (pMD) ? pMD->GetNoSpanSort() : false; }
bool            plMaxNodeBase::GetNoSpanReSort()        { GetMD; return (pMD) ? pMD->GetNoSpanReSort() : false; }
bool            plMaxNodeBase::GetNoFaceSort()          { GetMD; return (pMD) ? pMD->GetNoFaceSort() : false; }
bool            plMaxNodeBase::GetNoDeferDraw()     { GetMD; return (pMD) ? pMD->GetNoDeferDraw() : false; }
bool            plMaxNodeBase::GetBlendToFB()       { GetMD; return (pMD) ? pMD->GetBlendToFB() : false; }
bool            plMaxNodeBase::GetForceMaterialCopy()   { GetMD; return (pMD) ? pMD->GetForceMaterialCopy() : false; }
bool            plMaxNodeBase::GetInstanced()           { GetMD; return (pMD) ? pMD->GetInstanced() : false; }
bool            plMaxNodeBase::GetParticleRelated() { GetMD; return (pMD) ? pMD->GetParticleRelated() : false; }
uint32_t        plMaxNodeBase::GetSoundIdxCounter() { GetMD; return (pMD) ? pMD->GetSoundIdxCounter() : 0; }
plSceneObject*  plMaxNodeBase::GetAvatarSO()            { GetMD; return (pMD) ? pMD->GetAvatarSO() : nullptr; }
BOOL            plMaxNodeBase::HasFade()                { GetMD; return (pMD) ? pMD->HasFade() : false; }
Box3            plMaxNodeBase::GetFade()                { GetMD; return (pMD) ? pMD->GetFade() : Box3(Point3(0,0,0), Point3(0,0,0)); }
bool            plMaxNodeBase::GetDup2Sided()           { GetMD; return (pMD) ? pMD->GetDup2Sided() : false;}
bool            plMaxNodeBase::GetRadiateNorms()        { GetMD; return (pMD) ? pMD->GetRadiateNorms() : false;}
BOOL            plMaxNodeBase::HasNormalChan()          { GetMD; return (pMD) ? pMD->HasNormalChan() : false; }
int             plMaxNodeBase::GetNormalChan()          { GetMD; return (pMD) ? pMD->GetNormalChan() : 0; }
bool            plMaxNodeBase::GetIsGUI()               { GetMD; return (pMD) ? pMD->GetIsGUI() : false; }
plSharedMesh*   plMaxNodeBase::GetSwappableGeom()       { GetMD; return (pMD) ? pMD->GetSwappableGeom() : nullptr; }
uint32_t        plMaxNodeBase::GetSwappableGeomTarget()     { GetMD; return (pMD) ? pMD->GetSwappableGeomTarget() : -1; }
plMaxBoneMap*   plMaxNodeBase::GetBoneMap()                 { GetMD; return (pMD) ? pMD->GetBoneMap() : nullptr; }
bool            plMaxNodeBase::GetOverrideHighLevelSDL()    { GetMD; return (pMD) ? pMD->GetOverrideHighLevelSDL() : false; }
uint8_t         plMaxNodeBase::GetAnimCompress()                    { GetMD; return (pMD) ? pMD->GetAnimCompress() : false; }
float           plMaxNodeBase::GetKeyReduceThreshold()              { GetMD; return (pMD) ? pMD->GetKeyReduceThreshold() : 0; }
int             plMaxNodeBase::NumRenderDependencies()              { GetMD; return (pMD) ? pMD->NumRenderDependencies() : 0; }
plMaxNodeBase*  plMaxNodeBase::GetRenderDependency(int i)           { GetMD; return (pMD) ? pMD->GetRenderDependency(i) : nullptr; }

int             plMaxNodeBase::NumBones()                           { GetMD; return (pMD) ? pMD->NumBones() : 0;    }
plMaxNodeBase*  plMaxNodeBase::GetBone(int i)                       { GetMD; return (pMD) ? pMD->GetBone(i) : nullptr; }


//------------------------------
// Set Data from MaxNodeData
//------------------------------
void            plMaxNodeBase::SetCanConvert(bool b)              { GetMD; pMD->SetCanConvert(b);         }
void            plMaxNodeBase::SetMesh(hsGMesh *p)                  { GetMD; pMD->SetMesh(p);               }       
void            plMaxNodeBase::SetRoomKey(plKey p)                  { GetMD; pMD->SetRoomKey(p);            }
void            plMaxNodeBase::SetDrawable(bool b)                { GetMD; pMD->SetDrawable(b);           }
void            plMaxNodeBase::SetPhysical(bool b)                { GetMD; pMD->SetPhysical(b);           }
//void          plMaxNodeBase::SetItinerant(bool b);
void            plMaxNodeBase::SetUnBounded(bool b)               { GetMD; pMD->SetUnBounded(b);      }
void            plMaxNodeBase::SetDisableNormal(bool b)           { GetMD; pMD->SetDisableNormal(b);      }
void            plMaxNodeBase::SetDecalLevel(uint32_t i)              { GetMD; pMD->SetDecalLevel(i);         }
void            plMaxNodeBase::SetMovable(bool b)                 { GetMD; pMD->SetMovable(b); pMD->SetRunTimeLight(b); pMD->SetNoPreShade(b);            }
void            plMaxNodeBase::SetReverseSort(bool b)             { GetMD; pMD->SetReverseSort(b); }
void            plMaxNodeBase::SetSortAsOpaque(bool b)            { GetMD; pMD->SetSortAsOpaque(b); }
void            plMaxNodeBase::SetVS(bool b)                      { GetMD; pMD->SetVS(b); }
void            plMaxNodeBase::SetHasWaterHeight(bool b)          { GetMD; pMD->SetHasWaterHeight(b); }
void            plMaxNodeBase::SetWaterHeight(float h)           { GetMD; pMD->SetWaterHeight(h); }
void            plMaxNodeBase::SetSmoothAll(bool b)               { GetMD; pMD->SetSmoothAll(b); }
void            plMaxNodeBase::SetForceSortable(bool b)           { GetMD; pMD->SetForceSortable(b); }
void            plMaxNodeBase::SetConcave(bool b)                 { GetMD; pMD->SetConcave(b); }
void            plMaxNodeBase::SetCalcEdgeLens(bool b)            { GetMD; pMD->SetCalcEdgeLens(b); }
void            plMaxNodeBase::SetRunTimeLight(bool b)            { GetMD; pMD->SetRunTimeLight(b); }
void            plMaxNodeBase::SetForceMatShade(bool b)           { GetMD; pMD->SetForceMatShade(b); }
void            plMaxNodeBase::SetForceVisLOS(bool b)             { GetMD; pMD->SetForceVisLOS(b); }
void            plMaxNodeBase::SetEnviron(bool b)                 { GetMD; pMD->SetEnviron(b); }
void            plMaxNodeBase::SetEnvironOnly(bool b)             { GetMD; pMD->SetEnvironOnly(b); }
void            plMaxNodeBase::SetWaterDecEnv(bool b)             { GetMD; pMD->SetWaterDecEnv(b); }
void            plMaxNodeBase::SetNoPreShade(bool b)              { GetMD; pMD->SetNoPreShade(b);         }
void            plMaxNodeBase::SetForcePreShade(bool b)           { GetMD; pMD->SetForcePreShade(b);      }
void            plMaxNodeBase::SetForceLocal(bool b)              { GetMD; pMD->SetForceLocal(b);         }
void            plMaxNodeBase::SetIsBarney(bool b)                { GetMD; pMD->SetIsBarney(b);           }
void            plMaxNodeBase::SetForceShadow(bool b)             { GetMD; pMD->SetForceShadow(b);        }
void            plMaxNodeBase::SetAlphaTestHigh(bool b)           { GetMD; pMD->SetAlphaTestHigh(b);      }
void            plMaxNodeBase::SetFilterInherit(bool b)           { GetMD; pMD->SetFilterInherit(b);      }
void            plMaxNodeBase::SetNoShadow(bool b)                { GetMD; pMD->SetNoShadow(b);           }
void            plMaxNodeBase::SetNoSpanSort(bool b)              { GetMD; pMD->SetNoSpanSort(b);         }
void            plMaxNodeBase::SetNoSpanReSort(bool b)            { GetMD; pMD->SetNoSpanReSort(b);       }
void            plMaxNodeBase::SetNoFaceSort(bool b)              { GetMD; pMD->SetNoFaceSort(b);         }
void            plMaxNodeBase::SetNoDeferDraw(bool b)             { GetMD; pMD->SetNoDeferDraw(b);        }
void            plMaxNodeBase::SetBlendToFB(bool b)               { GetMD; pMD->SetBlendToFB(b);      }
void            plMaxNodeBase::SetForceMaterialCopy(bool b)       { GetMD; pMD->SetForceMaterialCopy(b);  }
void            plMaxNodeBase::SetInstanced(bool b)               { GetMD; pMD->SetInstanced(b);          }
void            plMaxNodeBase::SetParticleRelated(bool b)         { GetMD; pMD->SetParticleRelated(b);    }
void            plMaxNodeBase::SetSoundIdxCounter(uint32_t ctr)       { GetMD; pMD->SetSoundIdxCounter(ctr);  }
void            plMaxNodeBase::SetAvatarSO(plSceneObject *so)       { GetMD; pMD->SetAvatarSO(so);          }
void            plMaxNodeBase::SetFade(const Box3& b)               { GetMD; pMD->SetFade(b);               }
void            plMaxNodeBase::SetDup2Sided(bool b)               { GetMD; pMD->SetDup2Sided(b);          }
void            plMaxNodeBase::SetRadiateNorms(bool b)            { GetMD; pMD->SetRadiateNorms(b);       }
void            plMaxNodeBase::SetNormalChan(int n)                 { GetMD; pMD->SetNormalChan(n);         }
void            plMaxNodeBase::SetIsGUI(bool b)                   { GetMD; pMD->SetIsGUI(b);          }
void            plMaxNodeBase::SetSwappableGeom(plSharedMesh *sm)   { GetMD; pMD->SetSwappableGeom(sm); }
void            plMaxNodeBase::SetSwappableGeomTarget(uint32_t id)    { GetMD; pMD->SetSwappableGeomTarget(id);   }
void            plMaxNodeBase::SetBoneMap(plMaxBoneMap *bones)      { GetMD; pMD->SetBoneMap(bones);    }
void            plMaxNodeBase::SetOverrideHighLevelSDL(bool b)    { GetMD; pMD->SetOverrideHighLevelSDL(b); }
void            plMaxNodeBase::SetAnimCompress(uint8_t v)             { GetMD; pMD->SetAnimCompress(v); }
void            plMaxNodeBase::SetKeyReduceThreshold(float v)    { GetMD; pMD->SetKeyReduceThreshold(v); }
void            plMaxNodeBase::ClearRenderDependencies()            { GetMD; pMD->ClearRenderDependencies(); }

void            plMaxNodeBase::AddBone(plMaxNodeBase* m)            { GetMD; if(pMD) pMD->AddBone(m);       }
void            plMaxNodeBase::ClearBones()                         { GetMD; if(pMD) pMD->ClearBones();     }

plLocation plMaxNodeBase::GetLocation()
{
    plKey rmKey= GetRoomKey();
    plLocation loc;
    loc.Invalidate();
    if (rmKey)
        loc = rmKey->GetUoid().GetLocation();
    return loc;
}

bool plMaxNodeBase::GetDirty(uint8_t i)
{
    uint8_t *dirty = IGetSceneViewerChunk();
    return *dirty & i;
}

void plMaxNodeBase::SetDirty(uint8_t i, bool b)
{
    uint8_t *dirty = IGetSceneViewerChunk();

    if (b)
        *dirty |= i;
    else
        *dirty &= ~i;
}

bool plMaxNodeBase::HasLoadMask()
{
    GetMD;
    return pMD->HasLoadMask();
}

plLoadMask plMaxNodeBase::GetLoadMask()
{
    GetMD;
    return pMD->GetLoadMask();
}

void plMaxNodeBase::AddLoadMask(const plLoadMask& m)
{
    GetMD;
    pMD->AddLoadMask(m);
}

bool plMaxNodeBase::RenderDependsOn(plMaxNodeBase* m)
{
    if( m == this )
        return true;

    int i;
    for( i = 0; i < NumRenderDependencies(); i++ )
    {
        if( GetRenderDependency(i)->RenderDependsOn(m) )
            return true;
    }
    return false;
}

bool plMaxNodeBase::AddRenderDependency(plMaxNodeBase* m) 
{ 
    if( m->RenderDependsOn(this) )
        return false;
    GetMD; 
    pMD->AddRenderDependency(m); 
    return true;
}

uint8_t *plMaxNodeBase::IGetSceneViewerChunk()
{
    uint8_t *SVChunk = nullptr;

    AppDataChunk *adc = GetAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaSceneViewerChunk);
    if (adc)
        SVChunk = (uint8_t*)adc->data;
    else
    {
        // Does not exist, create a new one...
        SVChunk = (uint8_t*)MAX_new(1);
        *SVChunk = 0;
        AddAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaSceneViewerChunk, 1, SVChunk);
    }

    hsAssert(SVChunk, "SceneViewer chunk not found or created");
    return SVChunk;
}

bool plMaxNodeBase::CanConvert(bool recalculate)
{
    // Try and find a cached return value
    plMaxNodeData *md = GetMaxNodeData();
    if (md && !recalculate)
        return md->CanConvert();

    if (UserPropExists(_M("IGNORE")))
        return false;

    Object *obj = EvalWorldState(0/*hsConverterUtils::Instance().GetTime(GetInterface())*/).obj;
    if (obj)
    {
        if  (  obj->CanConvertToType(triObjectClassID)      // MeshObjs are accepted here
            || obj->ClassID() == Class_ID(DUMMY_CLASS_ID,0) // Dummy boxes are accepted here
            || obj->SuperClassID() == CAMERA_CLASS_ID       // All Camera types are accepted here
            || obj->ClassID() == Class_ID(UTILITY_CLASS_ID, 0)      // All Camera targets are accepted here
            || (  obj->ClassID() ==  RTOMNI_LIGHT_CLASSID
                || obj->ClassID() == RTSPOT_LIGHT_CLASSID
                || obj->ClassID() == RTDIR_LIGHT_CLASSID 
                || obj->ClassID() == RTPDIR_LIGHT_CLASSID )
            || (  obj->SuperClassID() == LIGHT_CLASS_ID     // All run time lights are accepted here
               && UserPropExists(_M("RunTimeLight")))

            || IsGroupMember()                              // Group objects are accepted here
            )
        return true;
    }
    return false;
}

bool plMaxNodeBase::IsTMAnimated()
{
    Control* tmControl = GetTMController();
    return (tmControl && tmControl->IsAnimated());
}

//// IsTMAnimatedRecur  - test recursively up the chain ///////////////////////////////////////////////////////////////
bool plMaxNodeBase::IsTMAnimatedRecur()
{
    auto dbgNodeName = GetName();

    if( !CanConvert() )
        return false;
    if( IsTMAnimated() )
        return true;

    return ((plMaxNodeBase*)GetParentNode())->IsTMAnimatedRecur();
}

//// IsMovable ///////////////////////////////////////////////////////////////
//  Returns whether this node is "animated" (i.e. could move at runtime)
bool plMaxNodeBase::IsMovable()
{
    auto        dbgNodeName = GetName();
    bool        shouldBe = false;


    if( !CanConvert() )
        return false;

    if( GetMovable() )
        return true;

    if( GetItinerant() )
        shouldBe = true;
    else if( FindSkinModifier() )
        shouldBe = true;

    // Moved this to plAnimComponent (so GetMovable() will reveal it)
    /*
    else if( IsTMAnimated() )
        shouldBe = true;
    */
    if( shouldBe )
    {
        SetMovable( true );
        return true;
    }

    return ((plMaxNodeBase*)GetParentNode())->IsMovable();
}

// Recursively set so we don't have to recursively check.
void plMaxNodeBase::SetItinerant(bool b)
{ 
    auto dbgNodeName = GetName();

    if( !CanConvert() )
        return;

    GetMD; 
    pMD->SetItinerant(b);           

    int i;
    for( i = 0; i < NumChildren(); i++ )
    {
        ((plMaxNodeBase*)GetChildNode(i))->SetItinerant(b);
    }
}

//// FindSkinModifier ///////////////////////////////////////////////////////
//  Given an INode, gets the ISkin object of that node, or nullptr if there is
//  none. Taken from the Max4 SDK, ISkin.h
ISkin* plMaxNodeBase::FindSkinModifier()
{
    int modStackIndex;

    // Get object from node. Abort if no object.
    Object *pObj = GetObjectRef();
    if (pObj == nullptr)
        return nullptr;

    // Is derived object ?
    while( pObj->SuperClassID() == GEN_DERIVOB_CLASS_ID )
    {
        IDerivedObject *pDerObj = (IDerivedObject *)pObj;

        // Iterate over all entries of the modifier stack.
        for( modStackIndex = 0; modStackIndex < pDerObj->NumModifiers(); modStackIndex++ )
        {
            // Get current modifier.
            Modifier *mod = pDerObj->GetModifier( modStackIndex );

            // Is this Skin ?
            if( mod->ClassID() == SKIN_CLASSID )
            {
                ISkin* skin = (ISkin*)mod->GetInterface(I_SKIN);
                if( skin->GetNumBones() > 0 )
                    return skin;
            }
        }
        pObj = pDerObj->GetObjRef();
    }

    // Not found.
    return nullptr;
}

bool plMaxNodeBase::IsXRef()
{
    // Is this an XRef'd object?
    Object *obj = GetObjectRef();
    if (obj->SuperClassID() == SYSTEM_CLASS_ID && obj->ClassID() == XREFOBJ_COMPAT_CLASS_ID)
        return true;

    //
    // Is this part of an XRef'd scene?
    //
    // Walk up to our root node
    INode *root = GetParentNode();
    while (!root->IsRootNode())
        root = root->GetParentNode();
    // If our root isn't the main root, we're in an XRef'd scene.
    if (root != GetCOREInterface()->GetRootNode())
        return true;

    return false;
}

bool plMaxNodeBase::IsComponent(Object *obj)
{
    if (!obj)
        obj = GetObjectRef();

    if (obj && obj->CanConvertToType(COMPONENT_CLASSID))
        return true;

    return false;
}

bool plMaxNodeBase::IsExternComponent(Object *obj)
{
    if (!obj)
        obj = GetObjectRef();

    if (obj && obj->CanConvertToType(EXT_COMPONENT_CLASSID))
        return true;

    return false;
}

plComponentBase *plMaxNodeBase::ConvertToComponent()
{
    if (IsComponent())
        return (plComponentBase*)GetObjectRef();

    return nullptr;
}

// There isn't an easy way to determine if there are any components attached to a node.
// This method goes through the reflist of a node and backtracks up each ref (using
// IRefMakerToComponent) to determine if it is a component.  There are cases though
// where a component can show up as two or more refs to a node, when it has a legitimate
// target ref and some other ref like a proxy object.  To catch this case we maintain a
// list of the components found so far and ensure there aren't any duplicates.  Maybe it
// would be easier to just go through every component in the scene and check their target
// lists... -Colin
uint32_t plMaxNodeBase::NumAttachedComponents(bool all)
{
    uint32_t numComponents = 0;
    std::vector<plComponentBase*> comps;

    // Go through this item's reflist, looking for components
    DependentIterator di(this);
    ReferenceMaker* item = di.Next();
    while (item)
    {
        plComponentBase *comp = IRefMakerToComponent(item, all);
        if (comp && std::find(comps.begin(), comps.end(), comp) == comps.end())
        {
            comps.push_back(comp);
            numComponents++;
        }

        item = di.Next();
    }

    return numComponents;
}

plComponentBase *plMaxNodeBase::GetAttachedComponent(uint32_t i, bool all)
{
    uint32_t numComponents = 0;
    std::vector<plComponentBase*> comps;

    // Go through this item's reflist, looking for components
    DependentIterator di(this);
    ReferenceMaker* item = di.Next();
    while (item)
    {
        plComponentBase *comp = IRefMakerToComponent(item, all);
        if (comp && std::find(comps.begin(), comps.end(), comp) == comps.end())
        {
            if (numComponents == i)
                return comp;

            comps.push_back(comp);
            numComponents++;
        }

        item = di.Next();;
    }

    return nullptr;
}

plComponentBase *plMaxNodeBase::IRefMakerToComponent(ReferenceMaker *maker, bool all)
{
    if (!maker)
        return nullptr;

    // Is the refmaker a paramblock?  If so, it may be the
    // targets block of a component
    if (maker->SuperClassID() == PARAMETER_BLOCK2_CLASS_ID)
    {
        IParamBlock2 *pb = (IParamBlock2*)maker;
        ReferenceMaker *pbowner = pb->GetOwner();

        // Is the owner of the paramblock a helper object (component superclass)?
        if (pbowner && pbowner->SuperClassID() == HELPER_CLASS_ID)
        {
            Object *obj = (Object*)pbowner;
            // Is the owner of the paramblock a component?
            if (IsComponent(obj))
            {
                plComponentBase *comp = (plComponentBase*)obj;

                if (!all)
                {
                    // Does this component actually ref us? (A component can have other
                    // refs to a node, like a proxy object, so we want to make sure this
                    // node is actually in the target list.)
                    for (uint32_t i = 0; i < comp->NumTargets(); i++)
                    {
                        if (comp->GetTarget(i) == this)
                            return comp;
                    }
                }
                else
                    return comp;
            }
        }
    }

    return nullptr;
}

bool plMaxNodeBase::IRenderLevelSet(bool forBlend)
{
    plMaxNodeData* md = GetMaxNodeData();
    if( md )
        return forBlend ? md->BlendLevelSet() : md->OpaqueLevelSet();
    return false;
}

void plMaxNodeBase::ISetRenderLevel(const plRenderLevel& l, bool forBlend)
{
    plMaxNodeData* md = GetMaxNodeData();
    if( md )
    {
        if( forBlend )
            md->SetBlendLevel(l);
        else
            md->SetOpaqueLevel(l);
    }
}

const plRenderLevel& plMaxNodeBase::IGetRenderLevel(bool forBlend)
{
    plMaxNodeData* md = GetMaxNodeData();
    if( !md )
    {
        static plRenderLevel defRenderLevel;
        return defRenderLevel;
    }
    return forBlend ? md->GetBlendLevel() : md->GetOpaqueLevel();
}

uint32_t plMaxNodeBase::IGetMajorRenderLevel(bool forBlend)
{
    if( GetBlendToFB() )
        return plRenderLevel::kFBMajorLevel;

    if( GetNoDeferDraw() || GetAvatarSO() )
        forBlend = false;

    int numDep = NumRenderDependencies();
    if( !numDep )
        return forBlend ? plRenderLevel::kBlendRendMajorLevel : plRenderLevel::kDefRendMajorLevel;

    int iMaxDep = 0;
    auto dbgNodeName = GetName();

    int i;
    for( i = 0; i < numDep; i++ )
    {
        plMaxNodeBase* dep = GetRenderDependency(i);
        if( dep )
        {
            auto depNodeName = dep->GetName();
            int iDep = GetRenderDependency(i)->GetRenderLevel(forBlend).Major();
            if( iDep > iMaxDep )
                iMaxDep = iDep;
        }
    }

    return iMaxDep;
}

uint32_t plMaxNodeBase::IGetMinorRenderLevel(bool forBlend)
{
    if( GetAvatarSO() )
        return plRenderLevel::kAvatarRendMinorLevel;

    int numDep = NumRenderDependencies();
    if( !numDep )
        return plRenderLevel::kDefRendMinorLevel;

    int iMaxDep = 0;

    auto dbgNodeName = GetName();

    int i;
    for( i = 0; i < numDep; i++ )
    {
        plMaxNodeBase* dep = GetRenderDependency(i);
        if( dep )
        {
            auto depNodeName = dep->GetName();
            int iDep = GetRenderDependency(i)->GetRenderLevel(forBlend).Minor();
            if( iDep > iMaxDep )
                iMaxDep = iDep;
        }
    }

    return iMaxDep + 4;
}

plRenderLevel plMaxNodeBase::ICalcRenderLevel(bool forBlend)
{
    if( GetBlendToFB() )
        return plRenderLevel::kFBMajorLevel;

    if( GetAvatarSO() )
        return plRenderLevel(plRenderLevel::kOpaqueMajorLevel, plRenderLevel::kAvatarRendMinorLevel);

    if( GetNoDeferDraw() )
        forBlend = false;

    int numDep = NumRenderDependencies();
    if( !numDep )
        return forBlend 
            ? plRenderLevel(plRenderLevel::kBlendRendMajorLevel, plRenderLevel::kDefRendMinorLevel)
            : plRenderLevel(plRenderLevel::kDefRendMajorLevel, plRenderLevel::kDefRendMinorLevel);

    plRenderLevel maxLevel(plRenderLevel::kFBMajorLevel, plRenderLevel::kDefRendMinorLevel);

    auto dbgNodeName = GetName();

    int i;
    for( i = 0; i < numDep; i++ )
    {
        plMaxNodeBase* dep = GetRenderDependency(i);
        if( dep )
        {
            auto depNodeName = dep->GetName();

            plRenderLevel depLev = GetRenderDependency(i)->GetRenderLevel(forBlend);
            if( depLev > maxLevel )
                maxLevel = depLev;
        }
    }
    maxLevel.Set(maxLevel.Level() + 4);

    return maxLevel;
}

const plRenderLevel& plMaxNodeBase::GetRenderLevel(bool forBlend)
{
    if( !CanConvert() )
    {
        static plRenderLevel retVal;
        return retVal;
    }

    if( !IRenderLevelSet(forBlend) )
    {
#if 0

        uint32_t major = IGetMajorRenderLevel(forBlend);
        uint32_t minor = IGetMinorRenderLevel(forBlend);

        ISetRenderLevel(plRenderLevel(major, minor), forBlend);
#else
        ISetRenderLevel(ICalcRenderLevel(forBlend), forBlend);
#endif
    }

    return IGetRenderLevel(forBlend);
}

BOOL plMaxNodeBase::GetGeoDice(int& maxFaces, float& maxSize, int& minFaces)
{
    plMaxNodeData* md = GetMaxNodeData();
    if( md && md->GetGeoDice() )
    {
        maxFaces = md->GetGeoDiceMaxFaces();
        maxSize = md->GetGeoDiceMaxSize();
        minFaces = md->GetGeoDiceMinFaces();
        return true;
    }
    maxFaces = 0;
    maxSize = 0;
    minFaces = 0;
    return false;
}

void plMaxNodeBase::SetGeoDice(BOOL on, int maxFaces, float maxSize, int minFaces)
{
    plMaxNodeData* md = GetMaxNodeData();
    if( md )
    {
        md->SetGeoDice(on);
        md->SetGeoDiceMaxFaces(maxFaces);
        md->SetGeoDiceMaxSize(maxSize);
        md->SetGeoDiceMinFaces(minFaces);
    }
}

bool plMaxNodeBase::Contains(const Point3& worldPt)
{
    TimeValue currTime = 0;//hsConverterUtils::Instance().GetTime(GetInterface());
    Object *obj = EvalWorldState(currTime).obj;
    if( !obj )
        return false;

    Matrix3 l2w = GetObjectTM(currTime);
    Matrix3 w2l = Inverse(l2w);
    Point3 pt = w2l * worldPt;

    if( obj->ClassID() == Class_ID(DUMMY_CLASS_ID,0) )
    {
        DummyObject* dummy = (DummyObject*)obj;
        Box3 bnd = dummy->GetBox();
        return bnd.Contains(pt);
    }
    if( obj->CanConvertToType(triObjectClassID) )
    {
        TriObject   *meshObj = (TriObject *)obj->ConvertToType(currTime, triObjectClassID);
        if( !meshObj )
            return false;

        Mesh& mesh = meshObj->mesh;
        Box3 bnd = mesh.getBoundingBox();
        if( !bnd.Contains(pt) )
        {
            if( meshObj != obj )
                meshObj->DeleteThis();
            return false;
        }

        bool retVal = true;
        int i;
        for( i = 0; i < mesh.getNumFaces(); i++ )
        {
            Face& face = mesh.faces[i];

            Point3 p0 = mesh.verts[face.v[0]];
            Point3 p1 = mesh.verts[face.v[1]];
            Point3 p2 = mesh.verts[face.v[2]];

            Point3 n = CrossProd(p1 - p0, p2 - p0);

            if( DotProd(pt, n) > DotProd(p0, n) )
            {
                retVal = false;
                break;
            }
        }

        if( meshObj != obj )
            meshObj->DeleteThis();

        return retVal;
    }

    // If we can't figure out what it is, the point isn't inside it.
    return false;
}

bool plMaxNodeBase::Contains(const Box3& bnd, const Matrix3& l2w)
{
    int i;
    for( i = 0; i < 8; i++ )
    {
        if( !Contains(l2w * bnd[i]) )
            return false;
    }
    return true;
}

float plMaxNodeBase::BoxVolume(const Box3& bnd, const Matrix3& l2w)
{
    Point3 corner = l2w * bnd[0]; // min, min, min
    float len[3];

    len[0] = Length((l2w * bnd[1]) - corner); // max, min, min
    len[1] = Length((l2w * bnd[2]) - corner); // min, max, min
    len[2] = Length((l2w * bnd[4]) - corner); // min, min, max

    return len[0] * len[1] * len[2];
}

float plMaxNodeBase::RegionPriority()
{
    TimeValue currTime = 0;//hsConverterUtils::Instance().GetTime(GetInterface());
    Object *obj = EvalWorldState(currTime).obj;
    if( !obj )
        return 0;

    Matrix3 l2w = GetObjectTM(currTime);

    if( obj->ClassID() == Class_ID(DUMMY_CLASS_ID,0) )
    {
        DummyObject* dummy = (DummyObject*)obj;
        Box3 bnd = dummy->GetBox();

        return BoxVolume(bnd, l2w);
    }

    if( obj->CanConvertToType(triObjectClassID) )
    {
        TriObject   *meshObj = (TriObject *)obj->ConvertToType(currTime, triObjectClassID);
        if( !meshObj )
            return 0;

        Mesh& mesh = meshObj->mesh;
        Box3 bnd = mesh.getBoundingBox();
        
        if( meshObj != obj )
            meshObj->DeleteThis();

        return BoxVolume(bnd, l2w);
    }

    // Don't know how to interpret other, it's not contained.
    return 0;
}

hsMatrix44 plMaxNodeBase::GetLocalToParent44(TimeValue t)
{
    Matrix3 m3 = GetLocalToParent(t);
    hsMatrix44 m44 = Matrix3ToMatrix44(m3);
    return m44;
}

hsMatrix44 plMaxNodeBase::GetParentToLocal44(TimeValue t)
{
    Matrix3 m3 = GetParentToLocal(t);
    hsMatrix44 m44 = Matrix3ToMatrix44(m3);
    return m44;
}

hsMatrix44 plMaxNodeBase::GetLocalToWorld44(TimeValue t)
{
    Matrix3 m3 = GetLocalToWorld(t);
    hsMatrix44 m44 = Matrix3ToMatrix44(m3);
    return m44;
}

hsMatrix44 plMaxNodeBase::GetWorldToLocal44(TimeValue t)
{
    Matrix3 m3 = GetWorldToLocal(t);
    hsMatrix44 m44 = Matrix3ToMatrix44(m3);
    return m44;
}

hsMatrix44 plMaxNodeBase::GetOTM44(TimeValue t)
{
    Matrix3 m3 = GetOTM(t);
    hsMatrix44 m44 = Matrix3ToMatrix44(m3);
    return m44;
}

hsMatrix44 plMaxNodeBase::GetVertToLocal44(TimeValue t)
{
    Matrix3 m3 = GetVertToLocal(t);
    hsMatrix44 m44 = Matrix3ToMatrix44(m3);
    return m44;
}

hsMatrix44 plMaxNodeBase::GetLocalToVert44(TimeValue t)
{
    Matrix3 m3 = GetLocalToVert(t);
    hsMatrix44 m44 = Matrix3ToMatrix44(m3);
    return m44;
}

hsMatrix44 plMaxNodeBase::GetLocalToOBB44(TimeValue t)
{
    Matrix3 m3 = GetLocalToOBB(t);
    hsMatrix44 m44 = Matrix3ToMatrix44(m3);
    return m44;
}

hsMatrix44 plMaxNodeBase::GetOBBToLocal44(TimeValue t)
{
    Matrix3 m3 = GetOBBToLocal(t);
    hsMatrix44 m44 = Matrix3ToMatrix44(m3);
    return m44;
}

Matrix3 plMaxNodeBase::GetParentToWorld(TimeValue t)
{
    return Inverse(GetWorldToParent(t));
}

Matrix3 plMaxNodeBase::GetWorldToParent(TimeValue t)
{
    // This may look back-ass-ward, but that's only because it
    // is. If we've got inheritance filtering on, then our localtoworld
    // is no longer parentl2w * l2p, because we'll be ignoring
    // some of our parent's transform. More precisely, we'll be
    // clobbering parts of the product of our parent's current transform
    // and our current local to parent. So we're going to calculate
    // a parent to world transform here that would get us to the
    // right point and orientation in space, even though it has
    // little or nothing to do with our parent's real transform.
    // Note that we only go through this charade if we've got
    // filtering of inheritance active for this node.
    plMaxNodeBase* parent = (plMaxNodeBase*)GetParentNode();
    if( !GetFilterInherit() )
        return parent->GetWorldToLocal(t);

    // l2w = l2p * parentL2W
    // l2w * parentW2L = l2p
    // parentW2L = w2l * l2p
    Point3 pos;
    float rot[4];
    ScaleValue scl;
    Interval posInv;
    Interval rotInv;
    Interval sclInv;

    Matrix3Indirect parentMatrix(parent->GetNodeTM(t));

    TMComponentsArg cmpts(&pos, &posInv, rot, &rotInv, &scl, &sclInv);
    GetTMController()->GetLocalTMComponents(t, cmpts, parentMatrix);

    Quat q;
    if( cmpts.rotRep == TMComponentsArg::kQuat )
        q = Quat(rot);
    else
        EulerToQuat(rot, q, cmpts.rotRep);

    Matrix3 l2p(true);
    l2p.PreTranslate(pos);
    PreRotateMatrix(l2p, q);
    l2p.PreScale(scl.s);
    PreRotateMatrix(l2p, scl.q);

    Matrix3 w2l = GetWorldToLocal(t);

    return w2l * l2p;
}

Matrix3 plMaxNodeBase::GetLocalToParent(TimeValue t)
{
    // l2w = l2p * parentL2W
    // l2w * Inverse(parentL2W) = l2p
    // l2w * parentW2L = l2p
    Matrix3 l2w = GetLocalToWorld(t);
    Matrix3 w2p(true);
    if( !GetParentNode()->IsRootNode() )
        w2p = GetWorldToParent(t);

    Matrix3 l2p = l2w * w2p;
    return l2p;
}

Matrix3 plMaxNodeBase::GetParentToLocal(TimeValue t)
{
    Matrix3 loc2Par = GetLocalToParent(t);
    Matrix3 par2Loc = Inverse(loc2Par);
    return par2Loc;
}

Matrix3 plMaxNodeBase::GetLocalToWorld(TimeValue t)
{
    if( GetForceLocal() )
    {
        // v2l * l2w = objectTM
        // l2w = Inverse(v2l) * objectTM;
        // l2w = l2v * objectTM
        Matrix3 objectTM = GetObjectTM(t);
        Matrix3 l2w = GetLocalToVert(t) * objectTM ;
        return l2w;
    }
    if( !GetParentNode()->IsRootNode() )
        return GetParentToWorld(t);

    return Matrix3(true);
}

Matrix3 plMaxNodeBase::GetWorldToLocal(TimeValue t)
{
    Matrix3 l2w = GetLocalToWorld(t);
    Matrix3 w2l = Inverse(l2w);
    return w2l;
}

Matrix3 plMaxNodeBase::GetOTM(TimeValue t)
{
    // objectTM = otm * nodeTM
    // otm = objectTM * Inverse(nodeTM)
    Matrix3 objectTM = GetObjectTM(t);
    Matrix3 nodeTM = GetNodeTM(t);
    Matrix3 otm = objectTM * Inverse(nodeTM);
    otm.ValidateFlags();
    return otm;
}

Matrix3 plMaxNodeBase::GetVertToLocal(TimeValue t)
{
    Matrix3     objectTM;

    //
    // If animated or we want forced into local space
    // still return OTM to fold into vertices
    //
    if( GetForceLocal() )
    {
        return GetOTM(t);
    }

    // otherwise flatten our local to parent into the verts
    // note that our parent may have flattened their l2p into
    // their v2l as well.
    // so
    // objectTM = v2l * l2p * parentL2W
    // objectTM * Inverse(parentL2W) = v2l * l2p
    // objectTM * parentW2L = v2l (w/ l2p folded in)
    // 
    // Objects transformation ObjectTM = OTM * nodeTM 
    objectTM = GetObjectTM(t);

    Matrix3 w2p(true);
    if( !GetParentNode()->IsRootNode() )
        w2p = GetWorldToParent(t);

    Matrix3 v2l = objectTM * w2p;

    return v2l;
}

Matrix3 plMaxNodeBase::GetLocalToVert(TimeValue t)
{
    Matrix3 v2l = GetVertToLocal(t);
    Matrix3 l2v = Inverse(v2l);
    return l2v;
}

Matrix3 plMaxNodeBase::GetLocalToOBB(TimeValue t)
{
    return GetLocalToVert(t) * GetOTM(t);
}

Matrix3 plMaxNodeBase::GetOBBToLocal(TimeValue t)
{
    return Inverse(GetOTM(t)) * GetVertToLocal(t);
}

hsMatrix44 plMaxNodeBase::Matrix3ToMatrix44(const Matrix3& m3)
{
    const MRow* m = m3.GetAddr();

    hsMatrix44 m44;
    m44.Reset();
    m44.fMap[0][0] = m[0][0];
    m44.fMap[0][1] = m[1][0];
    m44.fMap[0][2] = m[2][0];
    m44.fMap[0][3] = m[3][0];
    
    m44.fMap[1][0] = m[0][1];
    m44.fMap[1][1] = m[1][1];
    m44.fMap[1][2] = m[2][1];
    m44.fMap[1][3] = m[3][1];
    
    m44.fMap[2][0] = m[0][2];
    m44.fMap[2][1] = m[1][2];
    m44.fMap[2][2] = m[2][2];
    m44.fMap[2][3] = m[3][2];
    
    m44.IsIdentity();

    return m44;
}

Matrix3 plMaxNodeBase::Matrix44ToMatrix3(const hsMatrix44& m44)
{
    Matrix3 m3;

    MRow* m = m3.GetAddr();

    m[0][0] = m44.fMap[0][0];
    m[1][0] = m44.fMap[0][1];
    m[2][0] = m44.fMap[0][2];
    m[3][0] = m44.fMap[0][3];

    m[0][1] = m44.fMap[1][0];
    m[1][1] = m44.fMap[1][1];
    m[2][1] = m44.fMap[1][2];
    m[3][1] = m44.fMap[1][3];

    m[0][2] = m44.fMap[2][0];
    m[1][2] = m44.fMap[2][1];
    m[2][2] = m44.fMap[2][2];
    m[3][2] = m44.fMap[2][3];

    m3.ValidateFlags();

    return m3;
}
