/*==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 "max.h"
#include "dummy.h"
#include "resource.h"
#include "plComponent.h"
#include "plComponentReg.h"
#include "plMiscComponents.h"
#include "plSoftVolumeComponent.h"

#include "MaxMain/plPlasmaRefMsgs.h"
#include "MaxMain/plMaxNode.h"

#include "plIntersect/plSoftVolumeTypes.h"
#include "plIntersect/plVolumeIsect.h"
#include "pnKeyedObject/plKey.h"
#include "pnSceneObject/plSceneObject.h"
#include "pnMessage/plObjRefMsg.h"
#include "hsResMgr.h"

#include "plGLight/plLightInfo.h"
#include "plScene/plOccluder.h"

#include "pnSceneObject/plDrawInterface.h"
#include "plScene/plVisRegion.h"
#include "plScene/plRelevanceRegion.h"

void DummyCodeIncludeFuncSoftVolume() {}


/////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////
class plVolumeHitCallback : public HitByNameDlgCallback
{
protected:
    INode*          fOwner;
    IParamBlock2*   fPB;
    ParamID         fNodeListID;
    BOOL            fSingleSel;
    TCHAR           fTitle[ 128 ];

public:
    plVolumeHitCallback(INode* owner, IParamBlock2 *pb, ParamID nodeListID, TCHAR *title = nil, BOOL singleSel=false );

    virtual TCHAR *dialogTitle() { return fTitle; }
    virtual TCHAR *buttonText() { return "OK"; }
    virtual int filter(INode *node);
    virtual void proc(INodeTab &nodeTab);
    virtual BOOL showHiddenAndFrozen() { return TRUE; }
    virtual BOOL singleSelect() { return fSingleSel; }
};

plVolumeHitCallback::plVolumeHitCallback(INode* owner, IParamBlock2 *pb, ParamID nodeListID, TCHAR *title, BOOL singleSel)
:   fOwner(owner),
    fPB(pb),
    fNodeListID(nodeListID),
    fSingleSel(singleSel)
{
    strcpy( fTitle, title );
}

int plVolumeHitCallback::filter(INode *node)
{
    if( node == fOwner )
        return FALSE;

    plComponentBase *comp = ((plMaxNodeBase*)node)->ConvertToComponent();

    // If this is an activator type component
    if( comp )
    {
        if( (comp->ClassID() == SOFTVOLUME_CID)
            || (comp->ClassID() == SOFTVOLUME_UNION_CID)
            || (comp->ClassID() == SOFTVOLUME_ISECT_CID)
            || (comp->ClassID() == SOFTVOLUME_NEGATE_CID) )
        {

            if( !fSingleSel )
            {
                // And we don't already reference it
                int i;
                for( i = 0; i < fPB->Count(fNodeListID); i++ )
                {
                    if( fPB->GetINode(fNodeListID, 0, i) == node )
                        return FALSE;
                }
            }

            // And this wouldn't create a cyclical reference (Max doesn't like those)
            if (comp->TestForLoop(FOREVER, fPB) == REF_FAIL)
                return FALSE;

            return TRUE;
        }
    }

    return FALSE;
}

void plVolumeHitCallback::proc(INodeTab &nodeTab)
{
    if( fSingleSel )
    {
        if( nodeTab.Count() )
            fPB->SetValue(fNodeListID, TimeValue(0), nodeTab[0]);
        else
            fPB->SetValue(fNodeListID, TimeValue(0), (INode*)nil);
    }
    else
        fPB->Append(fNodeListID, nodeTab.Count(), &nodeTab[0]);
}

/////////////////////////////////////////////////////////////////////////////////////////////////
//// plSingleCompSelProc Functions //////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////

plSingleCompSelProc::plSingleCompSelProc(ParamID nodeID, int dlgItem, TCHAR *title) : fNodeID(nodeID), fDlgItem(dlgItem) 
{
    strcpy( fTitle, title );
}

BOOL plSingleCompSelProc::DlgProc(TimeValue t, IParamMap2 *paramMap, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_INITDIALOG:
        {
            IParamBlock2 *pb = paramMap->GetParamBlock();
            INode* node = pb->GetINode(fNodeID);
            TSTR newName(node ? node->GetName() : "Pick");
            ::SetWindowText(::GetDlgItem(hWnd, fDlgItem), newName);
        }
        return true;

    case WM_COMMAND:
        if( (HIWORD(wParam) == BN_CLICKED) && (LOWORD(wParam) == fDlgItem) )
        {
            // Adding a volume.  Set it and refresh the UI to show it in our list.
            IParamBlock2 *pb = paramMap->GetParamBlock();
            plVolumeHitCallback hitCB((INode*)pb->GetOwner(), pb, fNodeID, fTitle, true );
            GetCOREInterface()->DoHitByNameDialog(&hitCB);
            INode* node = pb->GetINode(fNodeID);
            TSTR newName(node ? node->GetName() : "Pick");
            ::SetWindowText(::GetDlgItem(hWnd, fDlgItem), newName);
            paramMap->Invalidate(fNodeID);
            ShowWindow(hWnd, SW_HIDE);
            ShowWindow(hWnd, SW_SHOW);

            return false;
        }
        return true;
    }

    return false;
}

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

// Moved class declaration to .h -mcn


hsBool plSoftVolBaseComponent::SetupProperties(plMaxNode* pNode, plErrorMsg* errMsg) 
{ 
    fSoftKey = nil; 
    fValid = false; 
    return true; 
}

plKey plSoftVolBaseComponent::GetSoftVolume()
{ 
    if( !fSoftKey )
        ICreateSoftVolume(); 
    return fSoftKey; 
}

plSoftVolBaseComponent* plSoftVolBaseComponent::GetSoftComponent(INode* node)
{
    if( node == nil )
        return nil;

    plComponentBase *comp = ((plMaxNodeBase*)node)->ConvertToComponent();
    if( comp == nil )
        return nil;

    return GetSoftComponent( comp );
}

plSoftVolBaseComponent* plSoftVolBaseComponent::GetSoftComponent(plComponentBase *comp)
{
    if( comp != nil && 
           (comp->ClassID() == SOFTVOLUME_CID)
        || (comp->ClassID() == SOFTVOLUME_UNION_CID)
        || (comp->ClassID() == SOFTVOLUME_ISECT_CID)
        || (comp->ClassID() == SOFTVOLUME_NEGATE_CID) )
    {
        return (plSoftVolBaseComponent*)comp;
    }
    return nil;
}

void plSoftVolBaseComponent::IAddSubVolume(plKey masterKey, plKey subKey)
{
    if( masterKey && subKey )
        hsgResMgr::ResMgr()->AddViaNotify(subKey, new plGenRefMsg(masterKey, plRefMsg::kOnCreate, 0, plSoftVolume::kSubVolume), plRefFlags::kActiveRef);
}

plKey plSoftVolBaseComponent::ISetVolumeKey(plSoftVolume* vol)
{
    int i;
    for( i = 0; i < NumTargets(); i++ )
    {
        if( GetTarget(i) )
            break;
    }
    hsAssert(i < NumTargets(), "We're not attached to anything?");
    plKey key = hsgResMgr::ResMgr()->NewKey(plString::FromUtf8(GetINode()->GetName()), vol, GetTarget(i)->GetLocation());

    return key;
}

plKey plSoftVolBaseComponent::IInvertVolume(plKey subKey)
{
    if( !subKey )
        return nil;

    plSoftVolumeInvert* invert = new plSoftVolumeInvert;
    plKey invertKey = ISetVolumeKey(invert);

    IAddSubVolume(invertKey, subKey);

    return invertKey;
}

hsBool plSoftVolBaseComponent::DeInit(plMaxNode *node, plErrorMsg *pErrMsg)
{
    fSoftKey = nil;
    return true;
}

/////////////////////////////////////////////////////////////////////////////////////////////////
//
//  SoftVolume Component
//
//Class that accesses the paramblock below.
class plSoftVolComponent : public plSoftVolBaseComponent
{
public:
    enum {
        kSoftDistance,
        kPartialEnabled,
        kInsidePower,
        kOutsidePower
    };

private:

    plKey               ISetFromIsect(plMaxNodeBase* pNode, plVolumeIsect* isect);
    plKey               ICreateSoftVolume();
    plKey               ICreateFromNode(plMaxNodeBase* pNode);
    plKey               ICreateFromDummyObject(plMaxNodeBase* pNode, Object* obj);
    plKey               ICreateFromTriObject(plMaxNodeBase* pNode, Object* obj);

protected:
    void                ICreateVolume();
public:
    plSoftVolComponent();
    void DeleteThis() { delete this; }

    hsBool SetupProperties(plMaxNode* pNode, plErrorMsg* errMsg);

    virtual void CollectNonDrawables(INodeTab& nonDrawables) { AddTargetsToList(nonDrawables); }
};

//
// 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.
class plSoftVolObjAccessor : public PBAccessor
{
public:
    void Set(PB2Value& v, ReferenceMaker* owner, ParamID id, int tabIndex, TimeValue t)
    {
    }
};
plSoftVolObjAccessor gSoftVolObjAccessor;


class plSoftVolComponentProc : public ParamMap2UserDlgProc
{
public:
    BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        switch (msg)
        {
        case WM_INITDIALOG:
            {
                    IParamBlock2 *pb = map->GetParamBlock();
                    map->SetTooltip(plSoftVolComponent::kSoftDistance, TRUE, "Distance effect fades in and out." );
            }
            return true;

        }

        return false;
    }
    void DeleteThis() {}
};
static plSoftVolComponentProc gSoftVolProc;



CLASS_DESC(plSoftVolComponent, gSoftVolDesc, "Soft Region",  "SoftRegion", COMP_TYPE_VOLUME, SOFTVOLUME_CID)



ParamBlockDesc2 gSoftVolBk
(
    plComponent::kBlkComp, _T("SoftRegion"), 0, &gSoftVolDesc, P_AUTO_CONSTRUCT+P_AUTO_UI, plComponent::kRefComp,

    IDD_COMP_SOFTVOLUME, IDS_COMP_SOFTVOLUMES,  0, 0, &gSoftVolProc,

    plSoftVolComponent::kSoftDistance,      _T("Soft Distance"),        TYPE_FLOAT,     0, 0,   
        p_default, 0.0,
        p_range, 0.0, 500.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_SOFTVOL_SOFT, IDC_COMP_SOFTVOL_SOFT_SPIN, 1.0,
        end,

    plSoftVolComponent::kPartialEnabled,  _T("EnablePartial"), TYPE_BOOL,       0, 0,
        p_default,  FALSE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_SOFTVOL_ENABLEPARTIAL,
        p_enable_ctrls,     2, plSoftVolComponent::kInsidePower, plSoftVolComponent::kOutsidePower,
        end,

    plSoftVolComponent::kInsidePower,       _T("PowerInside"),      TYPE_FLOAT,     0, 0,   
        p_default, 100.0,
        p_range, 0.0, 100.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_SOFTVOL_INSIDE, IDC_COMP_SOFTVOL_INSIDE_SPIN, 1.0,
        end,

    plSoftVolComponent::kOutsidePower,      _T("PowerOutside"),     TYPE_FLOAT,     0, 0,   
        p_default, 0.0,
        p_range, 0.0, 100.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_SOFTVOL_OUTSIDE, IDC_COMP_SOFTVOL_OUTSIDE_SPIN, 1.0,
        end,

    end

);

plSoftVolComponent::plSoftVolComponent()
{
    fClassDesc = &gSoftVolDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

hsBool plSoftVolComponent::SetupProperties(plMaxNode *pNode,  plErrorMsg *errMsg)
{
    if( !plSoftVolBaseComponent::SetupProperties(pNode, errMsg) )
        return false;

    int i;
    for( i = 0; i < pNode->NumAttachedComponents(); i++ )
    {
        plComponentBase* comp = pNode->GetAttachedComponent(i);
        if( comp 
            && (comp != this)
            && (comp->ClassID() == ClassID()) )
        {
            errMsg->Set(true, pNode->GetName(), "Multiple SoftRegion components attached, there can be only one").CheckAndAsk();
            errMsg->Set(false);

            fValid = false;
            return true;
        }
    }
    //
    // The node must either point to a Mesh or a dummy box, otherwise skip it.
    //
    Object *obj = pNode->EvalWorldState(TimeValue(0)).obj;
    if( !obj )
    {
        return true;
    }

    if( obj->ClassID() == Class_ID(DUMMY_CLASS_ID,0) )
    {
        fValid = true;
    }
    else if( obj->CanConvertToType(triObjectClassID) )
    {
        fValid = true;
    }
    pNode->SetDrawable(false);

    return true;
}

plKey plSoftVolComponent::ICreateSoftVolume()
{
    if( !fValid )
        return nil;

    if( !NumTargets() )
        return nil;

    if( NumTargets() < 2 )
    {
        return fSoftKey = ICreateFromNode(GetTarget(0));
    }

    plSoftVolumeUnion* compound = new plSoftVolumeUnion;
    fSoftKey = ISetVolumeKey(compound);

    int i;
    for( i = 0; i < NumTargets(); i++ )
    {
        if( GetTarget(i) )
        {
            plKey subKey = ICreateFromNode(GetTarget(i));
            IAddSubVolume(fSoftKey, subKey);
        }
    }
    if( !compound->GetNumSubs() )
    {
        delete compound;
        compound = nil;
        fSoftKey = nil;
    }

    return fSoftKey;
}

plKey plSoftVolComponent::ICreateFromNode(plMaxNodeBase* pNode)
{
    if( !pNode )
        return nil;

    if( !fValid )
        return nil;

    if( !pNode->GetSceneObject() )
        return nil;

    // Go ahead and make it here, so it'll be available for aggregaters in the Convert pass
    Object *obj = pNode->EvalWorldState(TimeValue(0)).obj;

    if( obj->ClassID() == Class_ID(DUMMY_CLASS_ID,0) )
    {
        return ICreateFromDummyObject(pNode, obj);
    }
    else if( obj->CanConvertToType(triObjectClassID) )
    {
        return ICreateFromTriObject(pNode, obj);
    }

    return nil;
}

plKey plSoftVolComponent::ICreateFromDummyObject(plMaxNodeBase* pNode, Object* obj)
{
    DummyObject* dummy = (DummyObject*)obj;
    Box3 bnd = dummy->GetBox();

    plParallelIsect* isect = new plParallelIsect;
    isect->SetNumPlanes(3);

    hsMatrix44 v2l = pNode->GetVertToLocal44();
    hsMatrix44 l2v = pNode->GetLocalToVert44();

    hsPoint3 corner(bnd.pmin.x, bnd.pmin.y, bnd.pmin.z);
    hsVector3 axis(bnd.pmax.x - bnd.pmin.x, bnd.pmax.y - bnd.pmin.y, bnd.pmax.z - bnd.pmin.z);

    int i;
    for( i = 0; i < 3; i++ )
    {
        hsPoint3 bot = v2l * corner;
        hsPoint3 top = corner;
        top[i] += axis[i];
        top = v2l * top;

        isect->SetPlane(i, bot, top);
    }

    return ISetFromIsect(pNode, isect);
}

plKey plSoftVolComponent::ICreateFromTriObject(plMaxNodeBase* pNode, Object* obj)
{
    TriObject   *meshObj = (TriObject *)obj->ConvertToType(TimeValue(0), triObjectClassID);

    Mesh* mesh = &meshObj->mesh;

    hsMatrix44 v2l = pNode->GetVertToLocal44();
    hsMatrix44 l2v = pNode->GetLocalToVert44();

    plConvexIsect* isect = new plConvexIsect;
    int i;
    for( i = 0; i < mesh->getNumFaces(); i++ )
    {
        Face        *maxFace = &mesh->faces[ i ];

        Point3 v0 = mesh->verts[ maxFace->v[ 0 ] ];
        Point3 v1 = mesh->verts[ maxFace->v[ 1 ] ];
        Point3 v2 = mesh->verts[ maxFace->v[ 2 ] ];

        hsPoint3 p0(v0.x, v0.y, v0.z);
        hsPoint3 p1(v1.x, v1.y, v1.z);
        hsPoint3 p2(v2.x, v2.y, v2.z);

        p0 = v2l * p0;
        p1 = v2l * p1;
        p2 = v2l * p2;

        hsVector3 n = hsVector3(&p1, &p0) % hsVector3(&p2, &p0);

        isect->AddPlane(n, p0);
    }
    
    plKey retVal = ISetFromIsect(pNode, isect);

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

    return retVal;
}

plKey plSoftVolComponent::ISetFromIsect(plMaxNodeBase* pNode, plVolumeIsect* isect)
{
    isect->SetTransform(pNode->GetLocalToWorld44(), pNode->GetWorldToLocal44());

    plSoftVolumeSimple* simple = new plSoftVolumeSimple;
    simple->SetVolume(isect);
    simple->SetDistance(fCompPB->GetFloat(kSoftDistance));

    if( fCompPB->GetInt(kPartialEnabled) )
    {
        simple->SetInsideStrength(fCompPB->GetFloat(kInsidePower) * 0.01f);
        simple->SetOutsideStrength(fCompPB->GetFloat(kOutsidePower) * 0.01f);
    }

    plSceneObject* sceneObj = pNode->GetSceneObject();

    plKey retVal = ISetVolumeKey(simple);
    
    hsgResMgr::ResMgr()->AddViaNotify(simple->GetKey(), new plObjRefMsg(sceneObj->GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kInterface), plRefFlags::kActiveRef);

    return retVal;
}

/////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////
// Union of volumes
/////////////////////////////////////////////////////////////////////////////////////////////////

//Class that accesses the paramblock below.
class plSoftVolUnionComponent : public plSoftVolBaseComponent
{
public:
    enum {
        kSubVolumes,
        kPartialEnabled,
        kInsidePower,
        kOutsidePower
    };
protected:

    plKey               ICreateSoftVolume();

public:
    plSoftVolUnionComponent();
    void DeleteThis() { delete this; }

    hsBool SetupProperties(plMaxNode* pNode, plErrorMsg* errMsg);
};

// 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.
class plSoftVolUnionAccessor : public PBAccessor
{
public:
    void Set(PB2Value& v, ReferenceMaker* owner, ParamID id, int tabIndex, TimeValue t)
    {
        if (id == plSoftVolUnionComponent::kSubVolumes)
        {
            plSoftVolUnionComponent *comp = (plSoftVolUnionComponent*)owner;
            comp->NotifyDependents(FOREVER, PART_ALL, REFMSG_USER_COMP_REF_CHANGED);
        }
    }
};
plSoftVolUnionAccessor gSoftVolUnionAccessor;


class plSoftVolUnionComponentProc : public ParamMap2UserDlgProc
{
public:
    BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        switch (msg)
        {
        case WM_INITDIALOG:
            {
                    IParamBlock2 *pb = map->GetParamBlock();
                    map->SetTooltip(plSoftVolUnionComponent::kSubVolumes, TRUE, "Select sub-volumes to combine into larger." );
            }
            return true;

        case WM_COMMAND:
            if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_ADD_VOLUME)
            {
                // Adding a volume.  Set it and refresh the UI to show it in our list.
                plVolumeHitCallback hitCB((INode*)map->GetParamBlock()->GetOwner(), map->GetParamBlock(), plSoftVolUnionComponent::kSubVolumes, "Select sub-volumes");
                GetCOREInterface()->DoHitByNameDialog(&hitCB);
                map->Invalidate(plSoftVolUnionComponent::kSubVolumes);
                return TRUE;
            }
            break;
        }

        return false;
    }
    void DeleteThis() {}
};
static plSoftVolUnionComponentProc gSoftVolUnionProc;



CLASS_DESC(plSoftVolUnionComponent, gSoftVolUnionDesc, "Soft Region Union",  "SoftRegionUnion", COMP_TYPE_VOLUME, SOFTVOLUME_UNION_CID)



ParamBlockDesc2 gSoftVolUnionBk
(
    plComponent::kBlkComp, _T("SoftRegionUnion"), 0, &gSoftVolUnionDesc, P_AUTO_CONSTRUCT+P_AUTO_UI, plComponent::kRefComp,

    IDD_COMP_SOFTVOLUME_UNION, IDS_COMP_SOFTVOLUME_UNION,  0, 0, &gSoftVolUnionProc,

    plSoftVolUnionComponent::kSubVolumes,   _T("SubRegions"),   TYPE_INODE_TAB, 0,      P_CAN_CONVERT, 0,
        p_ui,           TYPE_NODELISTBOX, IDC_LIST_TARGS, 0, 0, IDC_DEL_TARGS,
        p_classID,      SOFTVOLUME_BASE_CID,
        p_accessor,     &gSoftVolUnionAccessor,
        end,

    plSoftVolUnionComponent::kPartialEnabled,  _T("EnablePartial"), TYPE_BOOL,      0, 0,
        p_default,  FALSE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_SOFTUNION_ENABLEPARTIAL,
        p_enable_ctrls,     2, plSoftVolUnionComponent::kInsidePower, plSoftVolUnionComponent::kOutsidePower,
        end,

    plSoftVolUnionComponent::kInsidePower,      _T("PowerInside"),      TYPE_FLOAT,     0, 0,   
        p_default, 100.0,
        p_range, 0.0, 100.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_SOFTUNION_INSIDE, IDC_COMP_SOFTUNION_INSIDE_SPIN, 1.0,
        end,

    plSoftVolComponent::kOutsidePower,      _T("PowerOutside"),     TYPE_FLOAT,     0, 0,   
        p_default, 0.0,
        p_range, 0.0, 100.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_SOFTUNION_OUTSIDE, IDC_COMP_SOFTUNION_OUTSIDE_SPIN, 1.0,
        end,

    end

);

plSoftVolUnionComponent::plSoftVolUnionComponent()
{
    fClassDesc = &gSoftVolUnionDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

hsBool plSoftVolUnionComponent::SetupProperties(plMaxNode *pNode,  plErrorMsg *errMsg)
{
    return plSoftVolBaseComponent::SetupProperties(pNode, errMsg);
}

plKey plSoftVolUnionComponent::ICreateSoftVolume()
{
    int numSubs = fCompPB->Count(kSubVolumes);
    if( numSubs < 0 )
        return nil;

    if( numSubs < 2 )
        return fSoftKey = plSoftVolBaseComponent::GetSoftComponent(fCompPB->GetINode(kSubVolumes, 0, 0))->GetSoftVolume();

    plSoftVolumeUnion* compound = new plSoftVolumeUnion;
    fSoftKey = ISetVolumeKey(compound);

    int i;
    for( i = 0; i < numSubs; i++ )
    {
        plSoftVolBaseComponent *comp = plSoftVolBaseComponent::GetSoftComponent(fCompPB->GetINode(kSubVolumes, 0, i));
        if (comp)
        {
            plKey subKey = comp->GetSoftVolume();
            IAddSubVolume(fSoftKey, subKey);
        }
    }

    if( fCompPB->GetInt(kPartialEnabled) )
    {
        compound->SetInsideStrength(fCompPB->GetFloat(kInsidePower) * 0.01f);
        compound->SetOutsideStrength(fCompPB->GetFloat(kOutsidePower) * 0.01f);
    }

    return fSoftKey;
}

/////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////
// Intersection of volumes
/////////////////////////////////////////////////////////////////////////////////////////////////
//Class that accesses the paramblock below.
class plSoftVolIsectComponent : public plSoftVolBaseComponent
{
public:
    enum {
        kSubVolumes,
        kPartialEnabled,
        kInsidePower,
        kOutsidePower
    };
protected:

    plKey               ICreateSoftVolume();

public:
    plSoftVolIsectComponent();
    void DeleteThis() { delete this; }

    hsBool SetupProperties(plMaxNode* pNode, plErrorMsg* errMsg);
};

// 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.
class plSoftVolIsectAccessor : public PBAccessor
{
public:
    void Set(PB2Value& v, ReferenceMaker* owner, ParamID id, int tabIndex, TimeValue t)
    {
        if (id == plSoftVolIsectComponent::kSubVolumes)
        {
            plSoftVolIsectComponent *comp = (plSoftVolIsectComponent*)owner;
            comp->NotifyDependents(FOREVER, PART_ALL, REFMSG_USER_COMP_REF_CHANGED);
        }
    }
};
plSoftVolIsectAccessor gSoftVolIsectAccessor;


class plSoftVolIsectComponentProc : public ParamMap2UserDlgProc
{
public:
    BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        switch (msg)
        {
        case WM_INITDIALOG:
            {
                    IParamBlock2 *pb = map->GetParamBlock();
                    map->SetTooltip(plSoftVolIsectComponent::kSubVolumes, TRUE, "Select sub-volumes to combine into larger." );
            }
            return true;

        case WM_COMMAND:
            if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_ADD_VOLUME)
            {
                // Adding a volume.  Set it and refresh the UI to show it in our list.
                plVolumeHitCallback hitCB((INode*)map->GetParamBlock()->GetOwner(), map->GetParamBlock(), plSoftVolIsectComponent::kSubVolumes, "Select sub-volumes");
                GetCOREInterface()->DoHitByNameDialog(&hitCB);
                map->Invalidate(plSoftVolIsectComponent::kSubVolumes);
                return TRUE;
            }
            break;
        }

        return false;
    }
    void DeleteThis() {}
};
static plSoftVolIsectComponentProc gSoftVolIsectProc;



CLASS_DESC(plSoftVolIsectComponent, gSoftVolIsectDesc, "Soft Region Intersection",  "SoftRegionIsect", COMP_TYPE_VOLUME, SOFTVOLUME_ISECT_CID)



ParamBlockDesc2 gSoftVolIsectBk
(
    plComponent::kBlkComp, _T("SoftRegionIsect"), 0, &gSoftVolIsectDesc, P_AUTO_CONSTRUCT+P_AUTO_UI, plComponent::kRefComp,

    IDD_COMP_SOFTVOLUME_ISECT, IDS_COMP_SOFTVOLUME_ISECT,  0, 0, &gSoftVolIsectProc,

    plSoftVolIsectComponent::kSubVolumes,   _T("SubRegions"),   TYPE_INODE_TAB, 0,      P_CAN_CONVERT, 0,
        p_ui,           TYPE_NODELISTBOX, IDC_LIST_TARGS, 0, 0, IDC_DEL_TARGS,
        p_classID,      SOFTVOLUME_BASE_CID,
        p_accessor,     &gSoftVolIsectAccessor,
        end,

    plSoftVolIsectComponent::kPartialEnabled,  _T("EnablePartial"), TYPE_BOOL,      0, 0,
        p_default,  FALSE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_SOFTISECT_ENABLEPARTIAL,
        p_enable_ctrls,     2, plSoftVolIsectComponent::kInsidePower, plSoftVolIsectComponent::kOutsidePower,
        end,

    plSoftVolIsectComponent::kInsidePower,      _T("PowerInside"),      TYPE_FLOAT,     0, 0,   
        p_default, 100.0,
        p_range, 0.0, 100.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_SOFTISECT_INSIDE, IDC_COMP_SOFTISECT_INSIDE_SPIN, 1.0,
        end,

    plSoftVolIsectComponent::kOutsidePower,     _T("PowerOutside"),     TYPE_FLOAT,     0, 0,   
        p_default, 0.0,
        p_range, 0.0, 100.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_SOFTISECT_OUTSIDE, IDC_COMP_SOFTISECT_OUTSIDE_SPIN, 1.0,
        end,

    end

);

plSoftVolIsectComponent::plSoftVolIsectComponent()
{
    fClassDesc = &gSoftVolIsectDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

hsBool plSoftVolIsectComponent::SetupProperties(plMaxNode *pNode,  plErrorMsg *errMsg)
{
    return plSoftVolBaseComponent::SetupProperties(pNode, errMsg);
}

plKey plSoftVolIsectComponent::ICreateSoftVolume()
{
    int numSubs = fCompPB->Count(kSubVolumes);
    if( numSubs < 0 )
        return nil;

    if( numSubs < 2 )
        return fSoftKey = plSoftVolBaseComponent::GetSoftComponent(fCompPB->GetINode(kSubVolumes, 0, 0))->GetSoftVolume();

    plSoftVolumeIntersect* compound = new plSoftVolumeIntersect;
    fSoftKey = ISetVolumeKey(compound);

    int i;
    for( i = 0; i < numSubs; i++ )
    {
        plKey subKey = plSoftVolBaseComponent::GetSoftComponent(fCompPB->GetINode(kSubVolumes, 0, i))->GetSoftVolume();
        IAddSubVolume(fSoftKey, subKey);
    }

    if( fCompPB->GetInt(kPartialEnabled) )
    {
        compound->SetInsideStrength(fCompPB->GetFloat(kInsidePower) * 0.01f);
        compound->SetOutsideStrength(fCompPB->GetFloat(kOutsidePower) * 0.01f);
    }

    return fSoftKey;
}

/////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////
// Inversion of a (possibly complex) volume
/////////////////////////////////////////////////////////////////////////////////////////////////
//Class that accesses the paramblock below.
class plSoftVolNegateComponent : public plSoftVolBaseComponent
{
public:
    enum {
        kSubVolume,
        kPartialEnabled,
        kInsidePower,
        kOutsidePower
    };
protected:

    plKey               ICreateSoftVolume();

public:
    plSoftVolNegateComponent();
    void DeleteThis() { delete this; }

    hsBool SetupProperties(plMaxNode* pNode, plErrorMsg* errMsg);
};

// 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.
class plSoftVolNegateAccessor : public PBAccessor
{
public:
    void Set(PB2Value& v, ReferenceMaker* owner, ParamID id, int tabIndex, TimeValue t)
    {
        if (id == plSoftVolNegateComponent::kSubVolume)
        {
            plSoftVolNegateComponent *comp = (plSoftVolNegateComponent*)owner;
            comp->NotifyDependents(FOREVER, PART_ALL, REFMSG_USER_COMP_REF_CHANGED);
        }
    }
};
plSoftVolNegateAccessor gSoftVolNegateAccessor;


static plSingleCompSelProc gSoftVolNegateSingleSel(plSoftVolNegateComponent::kSubVolume, IDC_COMP_SOFTVOLUME_NEGATE_CHOOSE_SUB, "Select sub-volumes");



CLASS_DESC(plSoftVolNegateComponent, gSoftVolNegateDesc, "Soft Region Inverted",  "SoftRegionInvert", COMP_TYPE_VOLUME, SOFTVOLUME_NEGATE_CID)



ParamBlockDesc2 gSoftVolNegateBk
(
    plComponent::kBlkComp, _T("SoftRegionNegate"), 0, &gSoftVolNegateDesc, P_AUTO_CONSTRUCT+P_AUTO_UI, plComponent::kRefComp,

    IDD_COMP_SOFTVOLUME_NEGATE, IDS_COMP_SOFTVOLUME_NEGATE,  0, 0, &gSoftVolNegateSingleSel, 

    plSoftVolNegateComponent::kSubVolume, _T("SubRegion"),  TYPE_INODE,     0, 0,
        p_prompt, IDS_COMP_SOFTVOLUME_NEGATE,
        p_accessor, &gSoftVolNegateAccessor,
        end,

    plSoftVolNegateComponent::kPartialEnabled,  _T("EnablePartial"), TYPE_BOOL,         0, 0,
        p_default,  FALSE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_SOFTNEGATE_ENABLEPARTIAL,
        p_enable_ctrls,     2, plSoftVolComponent::kInsidePower, plSoftVolComponent::kOutsidePower,
        end,

    plSoftVolNegateComponent::kInsidePower,     _T("PowerInside"),      TYPE_FLOAT,     0, 0,   
        p_default, 100.0,
        p_range, 0.0, 100.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_SOFTNEGATE_INSIDE, IDC_COMP_SOFTNEGATE_INSIDE_SPIN, 1.0,
        end,

    plSoftVolNegateComponent::kOutsidePower,        _T("PowerOutside"),     TYPE_FLOAT,     0, 0,   
        p_default, 0.0,
        p_range, 0.0, 100.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_SOFTNEGATE_OUTSIDE, IDC_COMP_SOFTNEGATE_OUTSIDE_SPIN, 1.0,
        end,


    end

);

plSoftVolNegateComponent::plSoftVolNegateComponent()
{
    fClassDesc = &gSoftVolNegateDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

hsBool plSoftVolNegateComponent::SetupProperties(plMaxNode *pNode,  plErrorMsg *errMsg)
{
    return plSoftVolBaseComponent::SetupProperties(pNode, errMsg);
}

plKey plSoftVolNegateComponent::ICreateSoftVolume()
{
    if( NumTargets() < 1 )
        return nil;

    INode* subNode = fCompPB->GetINode(kSubVolume);
    if( subNode )
    {
        plKey subKey = plSoftVolBaseComponent::GetSoftComponent(fCompPB->GetINode(kSubVolume))->GetSoftVolume();
        if( subKey )
        {
            fSoftKey = IInvertVolume(subKey);

            if( fCompPB->GetInt(kPartialEnabled) )
            {
                plSoftVolumeInvert* invert = plSoftVolumeInvert::ConvertNoRef(fSoftKey->GetObjectPtr());
                if( invert )
                {
                    invert->SetInsideStrength(fCompPB->GetFloat(kInsidePower) * 0.01f);
                    invert->SetOutsideStrength(fCompPB->GetFloat(kOutsidePower) * 0.01f);
                }
            }
            return fSoftKey;
        }
    }
    return nil;
}

/////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////
// Soft Light Region
/////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////

//Class that accesses the paramblock below.
class plLightRegionComponent : public plComponent
{
public:
    enum {
        kSoftVolume
    };
public:
    plLightRegionComponent();
    void DeleteThis() { delete this; }

    // SetupProperties - Internal setup and write-only set properties on the MaxNode. No reading
    // of properties on the MaxNode, as it's still indeterminant.
    hsBool SetupProperties(plMaxNode *pNode, plErrorMsg *errMsg);

    hsBool PreConvert(plMaxNode *pNode, plErrorMsg *errMsg);
    hsBool Convert(plMaxNode *node, plErrorMsg *errMsg);
};

// 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.
class plLightRegionAccessor : public PBAccessor
{
public:
    void Set(PB2Value& v, ReferenceMaker* owner, ParamID id, int tabIndex, TimeValue t)
    {
        if (id == plLightRegionComponent::kSoftVolume)
        {
            plLightRegionComponent *comp = (plLightRegionComponent*)owner;
            comp->NotifyDependents(FOREVER, PART_ALL, REFMSG_USER_COMP_REF_CHANGED);
        }
    }
};
plLightRegionAccessor gLightRegionAccessor;


static plSingleCompSelProc gLightRegionSingleSel(plLightRegionComponent::kSoftVolume, IDC_COMP_LIGHTREGION_CHOOSE_VOLUME, "Select soft region for light");


//Max desc stuff necessary below.
CLASS_DESC(plLightRegionComponent, gLightRegionDesc, "Light Region",  "LightRegion", COMP_TYPE_VOLUME, LIGHTREGION_CID)


ParamBlockDesc2 gLightRegionBk
(   // KLUDGE: not the defined block ID, but kept for backwards compat.
 plComponent::kBlkComp, _T("LightRegion"), 0, &gLightRegionDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,

    IDD_COMP_LIGHTREGION, IDS_COMP_LIGHTREGION, 0, 0, &gLightRegionSingleSel,

    plLightRegionComponent::kSoftVolume, _T("Region"),  TYPE_INODE,     0, 0,
        p_accessor,     &gLightRegionAccessor,
        end,
    end
);

plLightRegionComponent::plLightRegionComponent()
{
    fClassDesc = &gLightRegionDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

hsBool plLightRegionComponent::Convert(plMaxNode *node, plErrorMsg *errMsg)
{
    if( !fCompPB->GetINode(kSoftVolume) )
        return true;

    plSceneObject* sceneObj = node->GetSceneObject();
    if( !sceneObj )
        return true;

    plLightInfo* li = plLightInfo::ConvertNoRef(sceneObj->GetGenericInterface(plLightInfo::Index()));
    if( !li )
        return true;

    plSoftVolBaseComponent* softComp = plSoftVolBaseComponent::GetSoftComponent(fCompPB->GetINode(kSoftVolume));
    if( !softComp )
        return true;

    plKey softKey = softComp->GetSoftVolume();
    if( !softKey )
        return true;

    hsgResMgr::ResMgr()->AddViaNotify(softKey, new plGenRefMsg(li->GetKey(), plRefMsg::kOnCreate, 0, plLightInfo::kSoftVolume), plRefFlags::kActiveRef);

    return true;
}

hsBool plLightRegionComponent::PreConvert(plMaxNode *pNode,  plErrorMsg *errMsg)
{

    return true;
}

// SetupProperties - Internal setup and write-only set properties on the MaxNode. No reading
// of properties on the MaxNode, as it's still indeterminant.
hsBool plLightRegionComponent::SetupProperties(plMaxNode *pNode,  plErrorMsg *errMsg)
{

    return true;
}


/////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////
// Soft Visibility Region
/////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////

//Class that accesses the paramblock below.
class plVisRegionSingleSel : public plSingleCompSelProc
{
public:
    plVisRegionSingleSel(ParamID nodeID, int dlgItem, TCHAR *title)
        :   plSingleCompSelProc(nodeID, dlgItem, title)
    {
    }
    BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        switch (msg)
        {
        case WM_INITDIALOG:
            {
                IParamBlock2 *pb = map->GetParamBlock();
                if( pb->GetInt(plVisRegionComponent::kExcludes) )
                {
                    map->Enable(plVisRegionComponent::kDisableNormal, FALSE);
                    map->Enable(plVisRegionComponent::kAffectDraw, TRUE);
                    map->Enable(plVisRegionComponent::kAffectLight, TRUE);
                    map->Enable(plVisRegionComponent::kAffectOcc, TRUE);
                }
                else
                {
                    map->Enable(plVisRegionComponent::kDisableNormal, TRUE);
                    if( pb->GetInt(plVisRegionComponent::kDisableNormal) )
                    {
                        map->Enable(plVisRegionComponent::kAffectDraw, FALSE);
                        map->Enable(plVisRegionComponent::kAffectLight, FALSE);
                        map->Enable(plVisRegionComponent::kAffectOcc, FALSE);
                    }
                    else
                    {
                        map->Enable(plVisRegionComponent::kAffectDraw, TRUE);
                        map->Enable(plVisRegionComponent::kAffectLight, TRUE);
                        map->Enable(plVisRegionComponent::kAffectOcc, TRUE);
                    }
                }
            }
            break;
        case WM_COMMAND:
            {
                if( (LOWORD(wParam) == IDC_COMP_VISREGION_NOT) || (LOWORD(wParam) == IDC_COMP_VISREGION_DIS) )
                {
                    IParamBlock2 *pb = map->GetParamBlock();
                    if( pb->GetInt(plVisRegionComponent::kExcludes) )
                    {
                        map->Enable(plVisRegionComponent::kDisableNormal, FALSE);
                        map->Enable(plVisRegionComponent::kAffectDraw, TRUE);
                        map->Enable(plVisRegionComponent::kAffectLight, TRUE);
                        map->Enable(plVisRegionComponent::kAffectOcc, TRUE);
                    }
                    else
                    {
                        map->Enable(plVisRegionComponent::kDisableNormal, TRUE);
                        if( pb->GetInt(plVisRegionComponent::kDisableNormal) )
                        {
                            map->Enable(plVisRegionComponent::kAffectDraw, FALSE);
                            map->Enable(plVisRegionComponent::kAffectLight, FALSE);
                            map->Enable(plVisRegionComponent::kAffectOcc, FALSE);
                        }
                        else
                        {
                            map->Enable(plVisRegionComponent::kAffectDraw, TRUE);
                            map->Enable(plVisRegionComponent::kAffectLight, TRUE);
                            map->Enable(plVisRegionComponent::kAffectOcc, TRUE);
                        }
                    }
                    return TRUE;
                }
            }
            break;
        }
        return plSingleCompSelProc::DlgProc(t, map, hWnd, msg, wParam, lParam);
    }
};

static plVisRegionSingleSel gVisRegionSingleSel(plVisRegionComponent::kSoftVolume, IDC_COMP_VISREGION_CHOOSE_VOLUME, "Select region for visibility");


//Max desc stuff necessary below.
CLASS_DESC(plVisRegionComponent, gVisRegionDesc, "Visibility Region",  "VisRegion", COMP_TYPE_VOLUME, VISREGION_CID)


ParamBlockDesc2 gVisRegionBk
(   // KLUDGE: not the defined block ID, but kept for backwards compat.
 plComponent::kBlkComp, _T("VisRegion"), 0, &gVisRegionDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,

    IDD_COMP_VISREGION, IDS_COMP_VISREGION, 0, 0, &gVisRegionSingleSel,

    plVisRegionComponent::kSoftVolume, _T("Region"),    TYPE_INODE,     0, 0,
        p_accessor,     nil,
        end,

    plVisRegionComponent::kAffectDraw,  _T("AffectDraw"), TYPE_BOOL,        0, 0,
        p_default,  TRUE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_VISREGION_DRAW,
        end,

    plVisRegionComponent::kAffectLight,  _T("AffectLight"), TYPE_BOOL,      0, 0,
        p_default,  TRUE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_VISREGION_LIGHT,
        end,

    plVisRegionComponent::kAffectOcc,  _T("AffectOcc"), TYPE_BOOL,      0, 0,
        p_default,  FALSE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_VISREGION_OCC,
        end,

    plVisRegionComponent::kExcludes,  _T("Excludes"), TYPE_BOOL,        0, 0,
        p_default,  FALSE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_VISREGION_NOT,
        end,

    plVisRegionComponent::kDisableNormal,   _T("DisableNormal"), TYPE_BOOL,         0, 0,
        p_default,  FALSE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_VISREGION_DIS,
        end,

    end
);

plVisRegionComponent::plVisRegionComponent()
{
    fClassDesc = &gVisRegionDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

void plVisRegionComponent::ICheckVisRegion(const plLocation& loc)
{
    if( !fVisReg )
    {
        if( !fCompPB->GetINode(kSoftVolume) )
            return;

        plSoftVolBaseComponent* softComp = plSoftVolBaseComponent::GetSoftComponent(fCompPB->GetINode(kSoftVolume));
        if( !softComp )
            return;

        plKey softKey = softComp->GetSoftVolume();
        if( !softKey )
            return;

        fVisReg = new plVisRegion;
        plKey key = hsgResMgr::ResMgr()->NewKey(plString::FromUtf8(GetINode()->GetName()), fVisReg, loc);


        hsBool excludes = fCompPB->GetInt(kExcludes);
        hsBool disableNormal = excludes ? false : fCompPB->GetInt(kDisableNormal);

        fVisReg->SetProperty(plVisRegion::kIsNot, excludes);
        fVisReg->SetProperty(plVisRegion::kReplaceNormal, true);
        fVisReg->SetProperty(plVisRegion::kDisableNormal, disableNormal);

        plGenRefMsg* refMsg = new plGenRefMsg(fVisReg->GetKey(), plRefMsg::kOnCreate, 0, plVisRegion::kRefRegion);
        hsgResMgr::ResMgr()->SendRef(softKey, refMsg, plRefFlags::kActiveRef);
    }
}

hsBool plVisRegionComponent::Convert(plMaxNode *node, plErrorMsg *errMsg)
{
    const char* dbgNodeName = node->GetName();
    plSceneObject* obj = node->GetSceneObject();
    if( !obj )
        return true;

    hsBool excludes = fCompPB->GetInt(kExcludes);
    hsBool disableNormal = excludes ? false : fCompPB->GetInt(kDisableNormal);
    hsBool affectDraw = disableNormal ? true : fCompPB->GetInt(kAffectDraw);
    hsBool affectOcc = disableNormal ? true : fCompPB->GetInt(kAffectOcc);
    hsBool affectLight = disableNormal ? true : fCompPB->GetInt(kAffectLight);

    const plDrawInterface* di = affectDraw ? obj->GetDrawInterface() : nil;
    plOccluder* occ = affectOcc ? (plOccluder*)obj->GetGenericInterface(plOccluder::Index()) : nil;
    plLightInfo* li = affectLight ? (plLightInfo*)obj->GetGenericInterface(plLightInfo::Index()) : nil;
    if( !(disableNormal || di || occ || li) )
        return true;

    ICheckVisRegion(node->GetLocation());
    if( !fVisReg )
        return true;

    if( di )
        hsgResMgr::ResMgr()->AddViaNotify(fVisReg->GetKey(), new plGenRefMsg(di->GetKey(), plRefMsg::kOnCreate, 0, plDrawInterface::kRefVisRegion), plRefFlags::kActiveRef);

    if( occ )
    {
        hsgResMgr::ResMgr()->AddViaNotify(fVisReg->GetKey(), new plGenRefMsg(occ->GetKey(), plRefMsg::kOnCreate, 0, plOccluder::kRefVisRegion), plRefFlags::kActiveRef);
    }

    if( li )
    {
        hsgResMgr::ResMgr()->AddViaNotify(fVisReg->GetKey(), new plGenRefMsg(li->GetKey(), plRefMsg::kOnCreate, 0, plLightInfo::kVisRegion), plRefFlags::kActiveRef);
    }

    if( !(di || occ || li) )
    {
        hsgResMgr::ResMgr()->AddViaNotify(fVisReg->GetKey(), new plObjRefMsg(obj->GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
    }

    return true;
}

hsBool plVisRegionComponent::PreConvert(plMaxNode *pNode,  plErrorMsg *errMsg)
{
    fVisReg = nil;

    return true;
}

// SetupProperties - Internal setup and write-only set properties on the MaxNode. No reading
// of properties on the MaxNode, as it's still indeterminant.
hsBool plVisRegionComponent::SetupProperties(plMaxNode *pNode,  plErrorMsg *errMsg)
{

    return true;
}

void plVisRegionComponent::CollectRegions(plMaxNode* node, hsTArray<plVisRegion*>& regions)
{
    int i;
    for( i = 0; i < node->NumAttachedComponents(); i++ )
    {
        plComponentBase* comp = node->GetAttachedComponent(i);
        if( comp && comp->ClassID() == VISREGION_CID )
        {
            plVisRegionComponent* regComp = (plVisRegionComponent*)comp;
            if( regComp )
            {
                regComp->ICheckVisRegion(node->GetLocation());
                if( regComp->fVisReg )
                    regions.Append(regComp->fVisReg);
            }
        }
    }
}

/////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////
// Relevance Region
/////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////

//Class that accesses the paramblock below.
class plRelevanceRegionComponent : public plComponent
{
public:
    enum {
        kSoftVolume,
        kName,
    };
protected:
    plRelevanceRegion* fRegion;

public:
    plRelevanceRegionComponent();
    void DeleteThis() { delete this; }

    hsBool PreConvert(plMaxNode *pNode, plErrorMsg *errMsg);
    hsBool Convert(plMaxNode *node, plErrorMsg *errMsg);
};


static plSingleCompSelProc gRelevanceRegionSingleSel(plRelevanceRegionComponent::kSoftVolume, IDC_COMP_RELREGION_CHOOSE_VOLUME, "Select region");


//Max desc stuff necessary below.
CLASS_DESC(plRelevanceRegionComponent, gRelevanceRegionDesc, "Relevance Region",  "RelevanceRegion", COMP_TYPE_VOLUME, RELREGION_CID)


ParamBlockDesc2 gRelevanceRegionBk
(
    plComponent::kBlkComp, _T("RelevanceRegion"), 0, &gRelevanceRegionDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,

    IDD_COMP_RELREGION, IDS_COMP_RELREGION, 0, 0, &gRelevanceRegionSingleSel,

    plRelevanceRegionComponent::kSoftVolume, _T("Region"),  TYPE_INODE,     0, 0,
        p_accessor,     nil,
        end,
        
    end
);

plRelevanceRegionComponent::plRelevanceRegionComponent()
{
    fClassDesc = &gRelevanceRegionDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

hsBool plRelevanceRegionComponent::Convert(plMaxNode *node, plErrorMsg *errMsg)
{
    const char* dbgNodeName = node->GetName();
    plSceneObject* obj = node->GetSceneObject();
    if( !obj )
        return true;

    if( !fRegion )
    {
        if( !fCompPB->GetINode(kSoftVolume) )
            return true;

        plSoftVolBaseComponent* softComp = plSoftVolBaseComponent::GetSoftComponent(fCompPB->GetINode(kSoftVolume));
        if( !softComp )
            return true;

        plKey softKey = softComp->GetSoftVolume();
        if( !softKey )
            return true;

        fRegion = new plRelevanceRegion;
        plKey key = hsgResMgr::ResMgr()->NewKey(plString::FromUtf8(GetINode()->GetName()), fRegion, node->GetLocation());

        plGenRefMsg* refMsg = new plGenRefMsg(fRegion->GetKey(), plRefMsg::kOnCreate, 0, 0);
        hsgResMgr::ResMgr()->SendRef(softKey, refMsg, plRefFlags::kActiveRef);
    }

    hsgResMgr::ResMgr()->AddViaNotify(fRegion->GetKey(), new plObjRefMsg(obj->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
    
    return true;
}

hsBool plRelevanceRegionComponent::PreConvert(plMaxNode *pNode,  plErrorMsg *errMsg)
{
    fRegion = nil;

    return true;
}

/////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////
// Special Effects Visibility Set
/////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////



//Max desc stuff necessary below.
CLASS_DESC(plEffVisSetComponent, gEffVisSetDesc, "Effect Vis Set",  "EffVisSet", COMP_TYPE_VOLUME, EFFVISSET_CID)


ParamBlockDesc2 gEffVisSetBk
(   // KLUDGE: not the defined block ID, but kept for backwards compat.
 plComponent::kBlkComp, _T("EffVisSet"), 0, &gEffVisSetDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,

    IDD_COMP_EFFVISSET, IDS_COMP_EFFVISSET, 0, 0, NULL,

    plEffVisSetComponent::kHideNormal,  _T("HideNormal"), TYPE_BOOL,        0, 0,
        p_default,  FALSE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_EFFVISSET_HIDENORMAL,
        end,

    end
);

plEffVisSetComponent::plEffVisSetComponent()
{
    fClassDesc = &gEffVisSetDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

hsBool plEffVisSetComponent::Convert(plMaxNode *node, plErrorMsg *errMsg)
{
    const char* dbgNodeName = node->GetName();
    plSceneObject* obj = node->GetSceneObject();
    if( !obj )
        return true;

    const plDrawInterface* di = obj->GetDrawInterface();
    plOccluder* occ = (plOccluder*)obj->GetGenericInterface(plOccluder::Index());
    plLightInfo* li = (plLightInfo*)obj->GetGenericInterface(plLightInfo::Index());
    if( !(di || occ || li) )
        return true;

    if( !GetVisRegion(node) )
        return false;

    if( di )
        hsgResMgr::ResMgr()->AddViaNotify(fVisReg->GetKey(), new plGenRefMsg(di->GetKey(), plRefMsg::kOnCreate, 0, plDrawInterface::kRefVisRegion), plRefFlags::kActiveRef);

    if( occ )
    {
        hsgResMgr::ResMgr()->AddViaNotify(fVisReg->GetKey(), new plGenRefMsg(occ->GetKey(), plRefMsg::kOnCreate, 0, plOccluder::kRefVisRegion), plRefFlags::kActiveRef);
    }

    if( li )
    {
        hsgResMgr::ResMgr()->AddViaNotify(fVisReg->GetKey(), new plGenRefMsg(li->GetKey(), plRefMsg::kOnCreate, 0, plLightInfo::kVisRegion), plRefFlags::kActiveRef);
    }

    return true;
}

// SetupProperties - Internal setup and write-only set properties on the MaxNode. No reading
// of properties on the MaxNode, as it's still indeterminant.
hsBool plEffVisSetComponent::SetupProperties(plMaxNode *pNode,  plErrorMsg *errMsg)
{
    fVisReg = nil;

    return true;
}

plVisRegion* plEffVisSetComponent::GetVisRegion(plMaxNode* node)
{
    if( !fVisReg )
    {
        fVisReg = new plVisRegion;
        plKey key = hsgResMgr::ResMgr()->NewKey(plString::FromUtf8(GetINode()->GetName()), fVisReg, node->GetLocation());

        fVisReg->SetProperty(plVisRegion::kIsNot, false);
        fVisReg->SetProperty(plVisRegion::kReplaceNormal, fCompPB->GetInt(kHideNormal));
        fVisReg->SetProperty(plVisRegion::kDisable, true);
    }

    return fVisReg;
}


plEffVisSetComponent* plEffVisSetComponent::ConvertToEffVisSetComponent(plMaxNode* node)
{
    if( !node )
        return nil;

    plComponentBase *comp = node->ConvertToComponent();

    // If this is an activator type component
    if( comp )
    {
        if( comp->ClassID() == EFFVISSET_CID )
        {
            return (plEffVisSetComponent*)comp;
        }
    }
    return nil;
}

void plEffVisSetComponent::CollectRegions(plMaxNode* node, hsTArray<plVisRegion*>& regions)
{
    int i;
    for( i = 0; i < node->NumAttachedComponents(); i++ )
    {
        plComponentBase* comp = node->GetAttachedComponent(i);
        if( comp && comp->ClassID() == EFFVISSET_CID )
        {
            plEffVisSetComponent* regComp = (plEffVisSetComponent*)comp;
            if( regComp )
            {
                if( regComp->fVisReg )
                    regions.Append(regComp->fVisReg);
            }
        }
    }
}

