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

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 "resource.h"
#include "plComponent.h"
#include "plComponentReg.h"

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

#include "MaxConvert/hsConverterUtils.h"
#include "MaxConvert/hsControlConverter.h"
#include "plInterp/plController.h"
#include "MaxMain/plMaxNode.h"
#include "pnKeyedObject/plKey.h"

#include "plgDispatch.h"
#include "MaxMain/plPluginResManager.h"

#include "pnSceneObject/plSceneObject.h"
#include "pnSceneObject/plCoordinateInterface.h"

// Swivel related
#include "pfAnimation/plViewFaceModifier.h" // ViewFace Comp

// Line Follow related
#include "plInterp/plAnimPath.h"
#include "pfAnimation/plLineFollowMod.h"
#include "pfAnimation/plFollowMod.h"

#include "pnMessage/plRefMsg.h"

// Stereizer
#include "pfAnimation/plStereizer.h"

const Class_ID STEREIZE_COMP_CID(0x15066ec7, 0x64ea7381);
const Class_ID LINEFOLLOW_COMP_CID(0x64ec57f6, 0x292d47f6);
const Class_ID SWIVEL_COMP_CID(0x106a466b, 0x1c1700f7);

void DummyCodeIncludeFuncLineFollow()
{
}

/////////////////////////////////////////////////////////////////////////////////////////////////
//
//  LineFollow Component
//
//

enum    
{
    kFollowModeRadio,
    kPathObjectSel,
    kFollowObjectSel,
    kOffsetActive,
    kOffsetDegrees,
    kOffsetClampActive,
    kOffsetClamp,
    kForceToLine,
    kSpeedClampActive,
    kSpeedClamp
};

// 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 plLineObjAccessor : public PBAccessor
{
public:
    void Set(PB2Value& v, ReferenceMaker* owner, ParamID id, int tabIndex, TimeValue t)
    {
        if( (id == kFollowObjectSel) || (id == kPathObjectSel) )
        {
            plComponentBase* comp = (plComponentBase*)owner;
            comp->NotifyDependents(FOREVER, PART_ALL, REFMSG_USER_COMP_REF_CHANGED);
        }
    }
};
plLineObjAccessor gLineObjAccessor;


class plLineFollowComponentProc : 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(kPathObjectSel, TRUE, "Press the button, & select the path source object in one of the Viewports" );
                    map->SetTooltip(kFollowObjectSel, TRUE, "Press the button, & select the object to follow in one of the Viewports" );
                    map->SetTooltip(kOffsetDegrees, TRUE, "Positive angle to right, negative to left." );
                    if( pb->GetInt(kFollowModeRadio) == plLineFollowMod::kFollowObject )
                        map->Enable(kFollowObjectSel, TRUE);
                    else
                        map->Enable(kFollowObjectSel, FALSE);
            }
            return true;

//////////////////
        case WM_COMMAND:
            {
                if( (LOWORD(wParam) == IDC_RADIO_LISTENER)
                    || (LOWORD(wParam) == IDC_RADIO_CAMERA)
                    || (LOWORD(wParam) == IDC_RADIO_OBJECT) )
                {
                    IParamBlock2* pb = map->GetParamBlock();
                    if( pb->GetInt(kFollowModeRadio) == plLineFollowMod::kFollowObject )
                        map->Enable(kFollowObjectSel, TRUE);
                    else
                        map->Enable(kFollowObjectSel, FALSE);
                    
                    return true;
                }
            }
            
        }

        return false;
    }
    void DeleteThis() {}
};
static plLineFollowComponentProc gLineFollowProc;


//Class that accesses the paramblock below.
class plLineFollowComponent : public plComponent
{
private:
    hsBool          fValid;

    plLineFollowMod*    fLineMod;

    hsBool      IMakeLineMod(plMaxNode* pNode, plErrorMsg* pErrMsg);

public:
    plLineFollowComponent();

    hsBool SetupProperties(plMaxNode* pNode, plErrorMsg* pErrMsg);
    hsBool PreConvert(plMaxNode* pNode, plErrorMsg* pErrMsg);
    hsBool Convert(plMaxNode* node, plErrorMsg* pErrMsg);

    plLineFollowMod*    GetLineMod(plErrorMsg* pErrMsg);
};



CLASS_DESC(plLineFollowComponent, gLineFollowDesc, "LineFollow",  "LineFollow", COMP_TYPE_GRAPHICS, LINEFOLLOW_COMP_CID)



ParamBlockDesc2 gLineFollowBk
(
    plComponent::kBlkComp, _T("LineFollow"), 0, &gLineFollowDesc, P_AUTO_CONSTRUCT+P_AUTO_UI, plComponent::kRefComp,

    IDD_COMP_LINEFOLLOW, IDS_COMP_LINEFOLLOWS,  0, 0, &gLineFollowProc,

    kFollowModeRadio, _T("FollowMode"),     TYPE_INT,       0, 0,
        p_ui,       TYPE_RADIO, 3,  IDC_RADIO_LISTENER,                 IDC_RADIO_CAMERA,               IDC_RADIO_OBJECT,   
        p_vals,                     plLineFollowMod::kFollowListener,   plLineFollowMod::kFollowCamera, plLineFollowMod::kFollowObject,     
        p_default, plLineFollowMod::kFollowListener,
        end,

    kPathObjectSel, _T("PathObjectChoice"), TYPE_INODE,     0, 0,
        p_ui,   TYPE_PICKNODEBUTTON, IDC_COMP_LINE_CHOOSE_PATH,
        p_prompt, IDS_COMP_LINE_CHOSE_PATH,
        p_accessor, &gLineObjAccessor,
        end,

    kFollowObjectSel, _T("FollowObjectChoice"), TYPE_INODE,     0, 0,
        p_ui,   TYPE_PICKNODEBUTTON, IDC_COMP_LINE_CHOOSE_OBJECT,
        p_prompt, IDS_COMP_LINE_CHOSE_OBJECT,
        p_accessor, &gLineObjAccessor,
        end,

    kOffsetActive,  _T("OffsetActive"), TYPE_BOOL,      0, 0,
        p_default,  FALSE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_LINE_OFFSETACTIVE,
        p_enable_ctrls,     4, kOffsetDegrees, kOffsetClampActive, kOffsetClamp, kForceToLine,
        end,

    kOffsetDegrees, _T("OffsetDegrees"), TYPE_FLOAT,    0, 0,   
        p_default, 0.0,
        p_range, -85.0, 85.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_FLOAT, 
        IDC_COMP_LINE_OFFSETDEGREES, IDC_COMP_LINE_OFFSETDEGREES_SPIN, 1.0,
        end,    
    
    kOffsetClampActive,  _T("OffsetClampActive"), TYPE_BOOL,        0, 0,
        p_default,  FALSE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_LINE_OFFSETCLAMPACTIVE,
        p_enable_ctrls,     1, kOffsetClamp,
        end,

    kOffsetClamp, _T("OffsetClamp"), TYPE_FLOAT,    0, 0,   
        p_default, 0.0,
        p_range, 0.0, 1000.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_LINE_OFFSETCLAMP, IDC_COMP_LINE_OFFSETCLAMP_SPIN, 1.0,
        end,    
    
    kForceToLine,  _T("ForceToLine"), TYPE_BOOL,        0, 0,
        p_default,  FALSE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_LINE_FORCETOLINE,
        end,

    kSpeedClampActive,  _T("SpeedClampActive"), TYPE_BOOL,      0, 0,
        p_default,  FALSE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_LINE_SPEEDCLAMPACTIVE,
        p_enable_ctrls,     1, kSpeedClamp,
        end,

    kSpeedClamp, _T("SpeedClamp"), TYPE_FLOAT,  0, 0,   
        p_default, 30.0,
        p_range, 3.0, 1000.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_LINE_SPEEDCLAMP, IDC_COMP_LINE_SPEEDCLAMP_SPIN, 1.0,
        end,    
    
    end

);

plLineFollowComponent::plLineFollowComponent()
:   fValid(false),
    fLineMod(nil)
{
    fClassDesc = &gLineFollowDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

hsBool plLineFollowComponent::IMakeLineMod(plMaxNode* pNode, plErrorMsg* pErrMsg)
{
    plLineFollowMod::FollowMode mode = plLineFollowMod::FollowMode(fCompPB->GetInt(kFollowModeRadio));

    plLineFollowMod* lineMod = TRACKED_NEW plLineFollowMod;
    hsgResMgr::ResMgr()->NewKey(IGetUniqueName(pNode), lineMod, pNode->GetLocation());

    if( plLineFollowMod::kFollowObject == mode )
    {
        if(fCompPB->GetINode(kFollowObjectSel) != NULL)


        {
            plMaxNode* targNode = (plMaxNode*)fCompPB->GetINode(kFollowObjectSel);
            //plMaxNodeData* pMD = targNode->GetMaxNodeData();
            if( targNode->CanConvert() )
            {
                plSceneObject* targObj = targNode->GetSceneObject();
                if( targObj )
                {
                    plGenRefMsg* refMsg = TRACKED_NEW plGenRefMsg(lineMod->GetKey(), plRefMsg::kOnCreate, 0, plLineFollowMod::kRefObject);
                    hsgResMgr::ResMgr()->AddViaNotify(targObj->GetKey(), refMsg, plRefFlags::kPassiveRef);

                    lineMod->SetFollowMode(plLineFollowMod::kFollowObject);
                }
            }
        }
    }
    else
    {
        lineMod->SetFollowMode(mode);
    }

    plMaxNode* pathNode = (plMaxNode*)fCompPB->GetINode(kPathObjectSel);
    if(!pathNode)
    {
        pErrMsg->Set(true, "Path Node Failure", "Path Node %s was set to be Ignored or empty. Path Component ignored.", ((INode*)pathNode)->GetName()).Show();
        pErrMsg->Set(false);
        return false;
    }
    //hsAssert(pathNode, "If valid is true, this must be set");
    Control* maxTm = pathNode->GetTMController();

    plCompoundController* tmc = hsControlConverter::Instance().MakeTransformController(maxTm, pathNode);

    Matrix3 w2p(true);
    Matrix3 l2w = pathNode->GetNodeTM(TimeValue(0));
    if( !pathNode->GetParentNode()->IsRootNode() )
        w2p = Inverse(pathNode->GetParentNode()->GetNodeTM(TimeValue(0)));
    hsMatrix44 loc2Par = pathNode->Matrix3ToMatrix44(l2w * w2p);

    gemAffineParts ap;
    decomp_affine(loc2Par.fMap, &ap); 
    hsAffineParts initParts;
    AP_SET(initParts, ap);

    plAnimPath* animPath = TRACKED_NEW plAnimPath;
    animPath->SetController(tmc);
    animPath->InitParts(initParts);

    lineMod->SetPath(animPath);

    if( !pathNode->GetParentNode()->IsRootNode() )
    {
        plMaxNode* parNode = (plMaxNode*)pathNode->GetParentNode();
        plSceneObject* parObj = parNode->GetSceneObject();
        if( parObj )
        {
            plGenRefMsg* refMsg = TRACKED_NEW plGenRefMsg(lineMod->GetKey(), plRefMsg::kOnCreate, 0, plLineFollowMod::kRefParent);
            hsgResMgr::ResMgr()->AddViaNotify(parObj->GetKey(), refMsg, plRefFlags::kPassiveRef);
        }
    }

    if( fCompPB->GetInt(kOffsetActive) )
    {
        lineMod->SetOffsetDegrees(fCompPB->GetFloat(kOffsetDegrees));
        
        if( fCompPB->GetInt(kOffsetClampActive) )
        {
            lineMod->SetOffsetClamp(fCompPB->GetFloat(kOffsetClamp));
        }

        lineMod->SetForceToLine(fCompPB->GetInt(kForceToLine));
    }

    if( fCompPB->GetInt(kSpeedClampActive) )
    {
        lineMod->SetSpeedClamp(fCompPB->GetFloat(kSpeedClamp));
    }

    fLineMod = lineMod;

    return true;
}

plLineFollowMod* plLineFollowComponent::GetLineMod(plErrorMsg* pErrMsg)
{
    if( !fValid )
        return nil;
    if( !fLineMod )
    {
        int i;
        for( i = 0; i < NumTargets(); i++ )
        {
            plMaxNode* targ = (plMaxNode*)GetTarget(i);
            IMakeLineMod(targ, pErrMsg);
            if( fLineMod )
                break;
        }
    }
    return fLineMod;
}

hsBool plLineFollowComponent::Convert(plMaxNode* node, plErrorMsg* pErrMsg)
{
    if( !fValid )
        return true;

    if( !fLineMod )
    {
        if( !IMakeLineMod(node, pErrMsg) )
        {
            fValid = false;
            return true;
        }
    }

    node->AddModifier(fLineMod, IGetUniqueName(node));

    return true;
}

hsBool plLineFollowComponent::SetupProperties(plMaxNode* pNode,  plErrorMsg* pErrMsg)
{
    fValid = false;
    fLineMod = nil;

    if( !fCompPB->GetINode(kPathObjectSel) )
    {
        return true;
    }
    plMaxNode* pathNode = (plMaxNode*)fCompPB->GetINode(kPathObjectSel);
    if( !pathNode )
    {
        return true;
    }
    if( !pathNode->IsTMAnimated() )
    {
        return true;
    }
    pathNode->SetCanConvert(false);

    if( plLineFollowMod::kFollowObject == fCompPB->GetInt(kFollowModeRadio) )
    {
        if( !fCompPB->GetINode(kFollowObjectSel) )
        {
            return true;
        }
    }
    fValid = true;
    pNode->SetForceLocal(true);
    pNode->SetMovable(true);
    return true;
}

hsBool plLineFollowComponent::PreConvert(plMaxNode* pNode, plErrorMsg* pErrMsg)
{
    if( !fValid )
        return true;
    fValid = false;

    if( plLineFollowMod::kFollowObject == fCompPB->GetInt(kFollowModeRadio) )
    {
        plMaxNode* followNode = (plMaxNode*)fCompPB->GetINode(kFollowObjectSel);
        if( !followNode->CanConvert() )
        {
            return true;
        }
    }
    plMaxNode* pathNode = (plMaxNode*)fCompPB->GetINode(kPathObjectSel);
    if( !pathNode->GetParentNode()->IsRootNode() )
    {
        if( !((plMaxNode*)pathNode->GetParentNode())->CanConvert() )
        {
            return true;
        }
    }
    fValid = true;


    return true;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
//
// The stereizer component is sort of related to LineFollow. Really.

class plStereizeComp : public plComponent
{
public:
    enum
    {
        kLeft,
        kAmbientDist,
        kTransition,
        kSepAngle,
        kMaxDist,
        kMinDist
    };

    plLineFollowMod*    ISetMaster(plStereizer* stereo, plMaxNode* node, plErrorMsg* pErrMsg);

public:
    plStereizeComp();
    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.
    virtual hsBool SetupProperties(plMaxNode* node, plErrorMsg* pErrMsg);
    virtual hsBool PreConvert(plMaxNode* node, plErrorMsg* pErrMsg);
    virtual hsBool Convert(plMaxNode* node, plErrorMsg* pErrMsg);

    hsBool  Bail(plMaxNode* node, const char* msg, plErrorMsg* pErrMsg);

};



CLASS_DESC(plStereizeComp, gStereizeDesc, "Stereo-ize",  "Stereize", COMP_TYPE_AUDIO, STEREIZE_COMP_CID)

ParamBlockDesc2 gStereizeBk
(   
    plComponent::kBlkComp, _T("Stereize"), 0, &gStereizeDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,

    IDD_COMP_STEREIZE, IDS_COMP_STEREIZE, 0, 0, NULL,

    plStereizeComp::kLeft,  _T("Left"), TYPE_BOOL,      0, 0,
        p_default,  FALSE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_STEREIZE_LEFT,
        end,

    plStereizeComp::kAmbientDist, _T("AmbientDist"), TYPE_FLOAT,    0, 0,   
        p_default, 50.0,
        p_range, 0.0, 1000.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_STEREIZE_AMB, IDC_COMP_STEREIZE_AMB_SPIN, 1.0,
        end,    

    plStereizeComp::kTransition, _T("Transition"), TYPE_FLOAT,  0, 0,   
        p_default, 25.0,
        p_range, 1.0, 500.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_STEREIZE_TRANS, IDC_COMP_STEREIZE_TRANS_SPIN, 1.0,
        end,    

    plStereizeComp::kSepAngle, _T("SepAngle"), TYPE_FLOAT,  0, 0,   
        p_default, 30.0,
        p_range, 1.0, 80.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_STEREIZE_ANG, IDC_COMP_STEREIZE_ANG_SPIN, 1.0,
        end,    

    plStereizeComp::kMaxDist, _T("MaxDist"), TYPE_FLOAT,    0, 0,   
        p_default, 100.0,
        p_range, 1.0, 1000.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_STEREIZE_MAXDIST, IDC_COMP_STEREIZE_MAXDIST_SPIN, 1.0,
        end,    

    plStereizeComp::kMinDist, _T("MinDist"), TYPE_FLOAT,    0, 0,   
        p_default, 5.0,
        p_range, 1.0, 50.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_STEREIZE_MINDIST, IDC_COMP_STEREIZE_MINDIST_SPIN, 1.0,
        end,    

    end
);

plStereizeComp::plStereizeComp()
{
    fClassDesc = &gStereizeDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

hsBool plStereizeComp::SetupProperties(plMaxNode* node, plErrorMsg* pErrMsg)
{
    node->SetForceLocal(true);
    if( !node->GetParentNode()->IsRootNode() )
        ((plMaxNode*)node->GetParentNode())->SetForceLocal(true);

    return true;
}

hsBool plStereizeComp::PreConvert(plMaxNode* node, plErrorMsg* pErrMsg)
{
    return true;
}

hsBool plStereizeComp::Bail(plMaxNode* node, const char* msg, plErrorMsg* pErrMsg)
{
    pErrMsg->Set(true, node->GetName(), msg).CheckAndAsk();
    pErrMsg->Set(false);
    return true;
}

hsBool plStereizeComp::Convert(plMaxNode* node, plErrorMsg* pErrMsg)
{
    plStereizer* stereo = TRACKED_NEW plStereizer;

    stereo->SetAmbientDist(fCompPB->GetFloat(kAmbientDist));
    stereo->SetTransition(fCompPB->GetFloat(kTransition));

    hsScalar ang = fCompPB->GetFloat(kSepAngle);
    if( ang > 80.f )
        ang = 80.f;
    stereo->SetSepAngle(hsScalarDegToRad(ang));

    stereo->SetMaxSepDist(fCompPB->GetFloat(kMaxDist));
    stereo->SetMinSepDist(fCompPB->GetFloat(kMinDist));

    stereo->SetParentInitPos(node->GetLocalToParent44().GetTranslate());

    stereo->SetAsLeftChannel(fCompPB->GetInt(kLeft));

    node->AddModifier(stereo, IGetUniqueName(node));

    // Do this after AddModifier, cuz that's when stereo gets a plKey.
    ISetMaster(stereo, node, pErrMsg);

    return true;
}

plLineFollowMod* plStereizeComp::ISetMaster(plStereizer* stereo, plMaxNode* node, plErrorMsg* pErrMsg)
{
    int numComp = node->NumAttachedComponents(false);

    int i;
    for( i = 0; i < numComp; i++ )
    {
        plComponentBase *comp = node->GetAttachedComponent(i);
        if( comp && (comp->ClassID() == LINEFOLLOW_COMP_CID) )
        {
            plLineFollowComponent* lineComp = (plLineFollowComponent*)comp;
            plLineFollowMod* lineMod = lineComp->GetLineMod(pErrMsg);
            if( lineMod )
            {
                lineMod->AddStereizer(stereo->GetKey());
                return lineMod;
            }
        }
    }
    return nil;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
//
// The swivel component is also sort of related to LineFollow. And stereizer. Really.

class plSwivelComp : public plComponent
{
public:
    enum
    {
        kFaceTypeRadio,
        kFaceObjectSel,
        kPivotY,
        kOffsetActive,
        kOffsetX,
        kOffsetY,
        kOffsetZ,
        kOffsetLocal
    };
    enum
    {
        kFaceCamera,
        kFaceListener,
        kFacePlayer,
        kFaceObject
    };

public:
    plSwivelComp();
    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.
    virtual hsBool SetupProperties(plMaxNode* node, plErrorMsg* pErrMsg);
    virtual hsBool PreConvert(plMaxNode* node, plErrorMsg* pErrMsg);
    virtual hsBool Convert(plMaxNode* node, plErrorMsg* pErrMsg);

    hsBool  Bail(plMaxNode* node, const char* msg, plErrorMsg* pErrMsg);

};


class plSwivelComponentProc : 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();
                    if( pb->GetInt(plSwivelComp::kFaceTypeRadio) == plSwivelComp::kFaceObject )
                        map->Enable(plSwivelComp::kFaceObjectSel, TRUE);
                    else
                        map->Enable(plSwivelComp::kFaceObjectSel, FALSE);
            }
            return true;

//////////////////
        case WM_COMMAND:
            {
                if( (LOWORD(wParam) == IDC_RADIO_LISTENER)
                    || (LOWORD(wParam) == IDC_RADIO_PLAYER)
                    || (LOWORD(wParam) == IDC_RADIO_CAMERA)
                    || (LOWORD(wParam) == IDC_RADIO_OBJECT) )
                {
                    IParamBlock2* pb = map->GetParamBlock();
                    if( pb->GetInt(plSwivelComp::kFaceTypeRadio) == plSwivelComp::kFaceObject )
                        map->Enable(plSwivelComp::kFaceObjectSel, TRUE);
                    else
                        map->Enable(plSwivelComp::kFaceObjectSel, FALSE);
                    
                    return true;
                }
            }
            
        }

        return false;
    }
    void DeleteThis() {}
};
static plSwivelComponentProc gSwivelProc;


CLASS_DESC(plSwivelComp, gSwivelDesc, "Swivel",  "Swivel", COMP_TYPE_GRAPHICS, SWIVEL_COMP_CID)

ParamBlockDesc2 gSwivelBk
(   
    plComponent::kBlkComp, _T("Swivel"), 0, &gSwivelDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,

    IDD_COMP_SWIVEL, IDS_COMP_SWIVEL, 0, 0, &gSwivelProc,

    plSwivelComp::kFaceTypeRadio, _T("FaceType"),       TYPE_INT,       0, 0,
        p_ui,       TYPE_RADIO, 4,  IDC_RADIO_CAMERA, IDC_RADIO_LISTENER, IDC_RADIO_PLAYER, IDC_RADIO_OBJECT,   
        p_vals,                     plSwivelComp::kFaceCamera, plSwivelComp::kFaceListener, plSwivelComp::kFacePlayer, plSwivelComp::kFaceObject,
        p_default, plSwivelComp::kFacePlayer,
        end,

    plSwivelComp::kFaceObjectSel, _T("FaceObjectChoice"),   TYPE_INODE,     0, 0,
        p_ui,   TYPE_PICKNODEBUTTON, IDC_COMP_CHOOSE_OBJECT,
        p_prompt, IDS_COMP_CHOOSE_OBJECT,
        end,

    plSwivelComp::kPivotY,  _T("PivotY"), TYPE_BOOL,        0, 0,
        p_default,  TRUE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_PIVOTY,
        end,

    plSwivelComp::kOffsetActive,  _T("OffsetActive"), TYPE_BOOL,        0, 0,
        p_default,  FALSE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_OFFSETACTIVE,
        p_enable_ctrls,     4, plSwivelComp::kOffsetX, plSwivelComp::kOffsetY, plSwivelComp::kOffsetZ, plSwivelComp::kOffsetLocal,
        end,

    plSwivelComp::kOffsetX, _T("X"), TYPE_FLOAT,    0, 0,   
        p_default, 0.0,
        p_range, 0.0, 100.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_OFFSETX, IDC_COMP_OFFSETX_SPIN, 1.0,
        end,    

    plSwivelComp::kOffsetY, _T("Y"), TYPE_FLOAT,    0, 0,   
        p_default, 0.0,
        p_range, 0.0, 100.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_OFFSETY, IDC_COMP_OFFSETY_SPIN, 1.0,
        end,    

    plSwivelComp::kOffsetZ, _T("Z"), TYPE_FLOAT,    0, 0,   
        p_default, 0.0,
        p_range, 0.0, 100.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_OFFSETZ, IDC_COMP_OFFSETZ_SPIN, 1.0,
        end,    

    plSwivelComp::kOffsetLocal,  _T("OffsetLocal"), TYPE_BOOL,      0, 0,
        p_default,  TRUE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_OFFSETLOCAL,
        end,

    end
);

plSwivelComp::plSwivelComp()
{
    fClassDesc = &gSwivelDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

hsBool plSwivelComp::SetupProperties(plMaxNode* node, plErrorMsg* pErrMsg)
{
    node->SetForceLocal(true);
    node->SetMovable(true);

    return true;
}

hsBool plSwivelComp::PreConvert(plMaxNode* node, plErrorMsg* pErrMsg)
{
    return true;
}

hsBool plSwivelComp::Bail(plMaxNode* node, const char* msg, plErrorMsg* pErrMsg)
{
    pErrMsg->Set(true, node->GetName(), msg).CheckAndAsk();
    pErrMsg->Set(false);
    return true;
}

hsBool plSwivelComp::Convert(plMaxNode* node, plErrorMsg* pErrMsg)
{
    plViewFaceModifier* pMod = TRACKED_NEW plViewFaceModifier;
    pMod->SetOrigTransform(node->GetLocalToParent44(), node->GetParentToLocal44());
    node->AddModifier(pMod, IGetUniqueName(node));

    if( fCompPB->GetInt(kPivotY) )
        pMod->SetFlag(plViewFaceModifier::kPivotY);
    else
        pMod->SetFlag(plViewFaceModifier::kPivotFace);

    switch( fCompPB->GetInt(kFaceTypeRadio) )
    {
    case kFaceCamera:
        pMod->SetFollowMode(plViewFaceModifier::kFollowCamera);
        break;

    case kFaceListener:
        pMod->SetFollowMode(plViewFaceModifier::kFollowListener);
        break;

    case kFacePlayer:
        pMod->SetFollowMode(plViewFaceModifier::kFollowPlayer);
        break;

    case kFaceObject:
        {
            plMaxNode* targNode = (plMaxNode*)fCompPB->GetINode(kFaceObjectSel);
            if( targNode && targNode->CanConvert() )
            {
                pMod->SetFollowMode(plViewFaceModifier::kFollowObject, targNode->GetKey());
            }
            else
            {
                pErrMsg->Set(true, node->GetName(), "Swivel to look at component %s has no Plasma object to look at.", GetINode()->GetName()).Show();
                pErrMsg->Set(false);
                pMod->SetFollowMode(plViewFaceModifier::kFollowCamera);
            }
        }
        break;

    }

    pMod->SetOffsetActive(fCompPB->GetInt(kOffsetActive));
    if( fCompPB->GetInt(kOffsetActive) )
    {
        hsVector3 off(fCompPB->GetFloat(kOffsetX), fCompPB->GetFloat(kOffsetY), fCompPB->GetFloat(kOffsetZ));
        pMod->SetOffset(off, fCompPB->GetInt(kOffsetLocal));
    }

    return true;
}