/*==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 "meshdlib.h" 
#include "dummy.h"
#include "resource.h"
#include "plComponent.h"
#include "plComponentReg.h"
#include "MaxMain/plPlasmaRefMsgs.h"

#include "MaxMain/plMaxNode.h"

#include "plWaterComponent.h"
#include "plSoftVolumeComponent.h"

#include "hsTypes.h"
#include "plTweak.h"

#include "plDrawable/plWaveSetBase.h"
#include "plDrawable/plWaveSet7.h"
#include "plDrawable/plFixedWaterState7.h"

#include "plPipeline/plDynamicEnvMap.h"

#include "MaxMain/plPluginResManager.h"

#include "pnSceneObject/plSceneObject.h"
#include "pnMessage/plObjRefMsg.h"

#include "plScene/plVisRegion.h"

static const float kPercentToFrac(1.e-2f);
static const float kDegreeToRad(hsScalarPI/180.f);


// Preliminary setup bookkeeping
void DummyCodeIncludeFuncWater()
{
}

CLASS_DESC(plWaterComponent, gWaterCompDesc, "Large Water",  "Water", COMP_TYPE_WATER, WATER_COMP_CID)

ParamBlockDesc2 gWaterBk
(   
    plComponent::kBlkComp, _T("Water"), 0, &gWaterCompDesc, P_AUTO_CONSTRUCT + P_AUTO_UI + P_MULTIMAP, plComponent::kRefComp,

    plWaterComponent::kNumRollups,
        plWaterComponent::kRef, IDD_COMP_W_REFOBJECT, IDS_COMP_W_REFOBJECT, 0, 0, 0,
//      plWaterComponent::kBasicWater, IDD_COMP_W_BASICWATER, IDS_COMP_W_BASICWATER, 0, 0, 0,
        plWaterComponent::kGeoWater, IDD_COMP_W_GEOWATER, IDS_COMP_W_GEOWATER, 0, 0, 0,
        plWaterComponent::kTexWater, IDD_COMP_W_TEXWATER, IDS_COMP_W_TEXWATER, 0, 0, 0,
        plWaterComponent::kAdvWater, IDD_COMP_W_ADVWATER, IDS_COMP_W_ADVWATER, 0, 0, 0,
        plWaterComponent::kEnvMap, IDD_COMP_W_ENVMAP, IDS_COMP_W_ENVMAP, 0, 0, 0,
        plWaterComponent::kVtxHelp, IDD_COMP_W_VTXHELP, IDS_COMP_W_VTXHELP, 0, 0, 0,
        plWaterComponent::kBasicShore, IDD_COMP_W_BASICSHORE, IDS_COMP_W_BASICSHORE, 0, 0, 0,
        plWaterComponent::kAdvShore, IDD_COMP_W_ADVSHORE, IDS_COMP_W_ADVSHORE, 0, 0, 0,

    plWaterComponent::kRefObject, _T("RefObject"),  TYPE_INODE,     0, 0,
        p_ui,   plWaterComponent::kRef, TYPE_PICKNODEBUTTON, IDC_COMP_W_REFOBJECT,
        p_prompt, IDS_COMP_CHOOSE_OBJECT,
        end,

    // WATER BASIC
    plWaterComponent::kWindSpeed, _T("WindSpeed"), TYPE_FLOAT,  0, 0,   
//      p_default, 30.0,
//      p_range, -1.0, 50.0,
//      p_ui,   plWaterComponent::kBasicWater, TYPE_SPINNER,    EDITTYPE_POS_FLOAT, 
//      IDC_COMP_W_WINDSPEED, IDC_COMP_W_WINDSPEED_SPIN, 1.0,
        end,    

    plWaterComponent::kWaterTint,   _T("WaterTint"), TYPE_RGBA, 0, 0,
//      p_default, Color(0.1, 0.2, 0.2),
//      p_ui, plWaterComponent::kBasicWater, TYPE_COLORSWATCH,  IDC_COMP_W_WATERTINT,
        end,

    plWaterComponent::kWaterOpac, _T("WaterOpac"), TYPE_FLOAT,  0, 0,   
//      p_default, 100.0,
//      p_range, 0.0, 100.0,
//      p_ui,   plWaterComponent::kBasicWater, TYPE_SPINNER,    EDITTYPE_POS_FLOAT, 
//      IDC_COMP_W_WATEROPAC, IDC_COMP_W_WATEROPAC_SPIN, 1.0,
        end,    

    plWaterComponent::kSpecularTint,    _T("SpecularTint"), TYPE_RGBA,  0, 0,
        p_default, Color(1.0, 1.0, 1.0),
        p_ui, plWaterComponent::kGeoWater, TYPE_COLORSWATCH,  IDC_COMP_W_SPECULARTINT,
        end,

    plWaterComponent::kRippleScale, _T("RippleScale"), TYPE_FLOAT,  0, 0,   
        p_default, 25.0,
        p_range, 5.0, 1000.0,
        p_ui,   plWaterComponent::kTexWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_TEX_RIPPLESCALE, IDC_COMP_W_TEX_RIPPLESCALE_SPIN, 1.0,
        end,    

    plWaterComponent::kDispersion, _T("Dispersion"), TYPE_FLOAT,    0, 0,   
//      p_default, 0.0,
//      p_range, 0.0, 100.0,
//      p_ui,   plWaterComponent::kBasicWater, TYPE_SPINNER,    EDITTYPE_POS_FLOAT, 
//      IDC_COMP_W_DISPERSION, IDC_COMP_W_DISPERSION_SPIN, 1.0,
        end,    

    // WATER ADVANCED
    plWaterComponent::kDepthOpac, _T("DepthOpac"), TYPE_FLOAT,  0, 0,   
        p_default, 3.0,
        p_range, 0.5, 20.0,
        p_ui,   plWaterComponent::kAdvWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_DEPTHOPAC, IDC_COMP_W_DEPTHOPAC_SPIN, 1.0,
        end,    

    plWaterComponent::kDepthRefl, _T("DepthRefl"), TYPE_FLOAT,  0, 0,   
        p_default, 3.0,
        p_range, 0.5, 20.0,
        p_ui,   plWaterComponent::kAdvWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_DEPTHREFL, IDC_COMP_W_DEPTHREFL_SPIN, 1.0,
        end,    

    plWaterComponent::kDepthWave, _T("DepthWave"), TYPE_FLOAT,  0, 0,   
        p_default, 4.0,
        p_range, 0.5, 20.0,
        p_ui,   plWaterComponent::kAdvWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_DEPTHWAVE, IDC_COMP_W_DEPTHWAVE_SPIN, 1.0,
        end,    

    plWaterComponent::kZeroOpac, _T("ZeroOpac"), TYPE_FLOAT,    0, 0,   
        p_default, -1.0,
        p_range, -10.0, 10.0,
        p_ui,   plWaterComponent::kAdvWater, TYPE_SPINNER,  EDITTYPE_FLOAT, 
        IDC_COMP_W_ZEROOPAC, IDC_COMP_W_ZEROOPAC_SPIN, 1.0,
        end,    

    plWaterComponent::kZeroRefl, _T("ZeroRefl"), TYPE_FLOAT,    0, 0,   
        p_default, 0.0,
        p_range, -10.0, 10.0,
        p_ui,   plWaterComponent::kAdvWater, TYPE_SPINNER,  EDITTYPE_FLOAT, 
        IDC_COMP_W_ZEROREFL, IDC_COMP_W_ZEROREFL_SPIN, 1.0,
        end,    

    plWaterComponent::kZeroWave, _T("ZeroWave"), TYPE_FLOAT,    0, 0,   
        p_default, 0.0,
        p_range, -10.0, 10.0,
        p_ui,   plWaterComponent::kAdvWater, TYPE_SPINNER,  EDITTYPE_FLOAT, 
        IDC_COMP_W_ZEROWAVE, IDC_COMP_W_ZEROWAVE_SPIN, 1.0,
        end,    

    // SHORE BASIC
    plWaterComponent::kShoreTint,   _T("ShoreTint"), TYPE_RGBA, 0, 0,
        p_default, Color(0.2, 0.4, 0.4),
        p_ui, plWaterComponent::kBasicShore, TYPE_COLORSWATCH,  IDC_COMP_W_SHORETINT,
        end,

    plWaterComponent::kShoreOpac, _T("ShoreOpac"), TYPE_FLOAT,  0, 0,   
        p_default, 40.0,
        p_range, 0.0, 100.0,
        p_ui,   plWaterComponent::kBasicShore, TYPE_SPINNER,    EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_SHOREOPAC, IDC_COMP_W_SHOREOPAC_SPIN, 1.0,
        end,    

    plWaterComponent::kWispiness, _T("Wispiness"), TYPE_FLOAT,  0, 0,   
        p_default, 50.0,
        p_range, 0.0, 200.0,
        p_ui,   plWaterComponent::kBasicShore, TYPE_SPINNER,    EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_WISPINESS, IDC_COMP_W_WISPINESS_SPIN, 1.0,
        end,    

    // SHORE ADVANCED
    plWaterComponent::kPeriod, _T("Period"), TYPE_FLOAT,    0, 0,   
        p_default, 100.0,
        p_range, 50.0, 200.0,
        p_ui,   plWaterComponent::kAdvShore, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_PERIOD, IDC_COMP_W_PERIOD_SPIN, 1.0,
        end,    

    plWaterComponent::kFinger, _T("Finger"), TYPE_FLOAT,    0, 0,   
        p_default, 100.0,
        p_range, 50.0, 300.0,
        p_ui,   plWaterComponent::kAdvShore, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_FINGER, IDC_COMP_W_FINGER_SPIN, 1.0,
        end,    

    plWaterComponent::kEnvObject, _T("EnvObject"),  TYPE_INODE,     0, 0,
        p_ui,   plWaterComponent::kEnvMap, TYPE_PICKNODEBUTTON, IDC_COMP_W_ENVOBJECT,
        p_prompt, IDS_COMP_CHOOSE_OBJECT,
        end,

    plWaterComponent::kEnvSize, _T("EnvSize"), TYPE_INT, 0, 0,
        p_default, 256,
        p_range, 32, 512,
        p_ui,   plWaterComponent::kEnvMap, TYPE_SPINNER, EDITTYPE_INT, 
        IDC_COMP_W_ENVSIZE, IDC_COMP_W_ENVSIZE_SPIN,    1.f,
        end,

    plWaterComponent::kEnvRadius, _T("EnvRadius"), TYPE_FLOAT,  0, 0,   
        p_default, 500.0,
        p_range, 5.0, 10000.0,
        p_ui,   plWaterComponent::kEnvMap, TYPE_SPINNER,    EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_ENVRADIUS, IDC_COMP_W_ENVRADIUS_SPIN, 1.0,
        end,    

    plWaterComponent::kEdgeOpac, _T("EdgeOpac"), TYPE_FLOAT,    0, 0,   
        p_default, 100.0,
        p_range, 0.0, 100.0,
        p_ui,   plWaterComponent::kAdvShore, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_EDGEOPAC, IDC_COMP_W_EDGEOPAC_SPIN, 1.0,
        end,    

    plWaterComponent::kEdgeRadius, _T("EdgeRadius"), TYPE_FLOAT,    0, 0,   
        p_default, 100.0,
        p_range, 50.0, 300.0,
        p_ui,   plWaterComponent::kAdvShore, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_EDGERADIUS, IDC_COMP_W_EDGERADIUS_SPIN, 1.0,
        end,    


    plWaterComponent::kEnvRefresh, _T("EnvRefresh"), TYPE_FLOAT,    0, 0,   
        p_default, 0.0,
        p_range, 0.0, 3600.0,
        p_ui,   plWaterComponent::kEnvMap, TYPE_SPINNER,    EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_ENVREFRESH, IDC_COMP_W_ENVREFRESH_SPIN, 10.0,
        end,    

    plWaterComponent::kGeoMinLen, _T("GeoMinLen"), TYPE_FLOAT,  0, 0,   
        p_default, 4.0,
        p_range, 0.1, 50.0,
        p_ui,   plWaterComponent::kGeoWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_GEO_MINLEN, IDC_COMP_W_GEO_MINLEN_SPIN, 1.0,
        end,    

    plWaterComponent::kGeoMaxLen, _T("GeoMaxLen"), TYPE_FLOAT,  0, 0,   
        p_default, 8.0,
        p_range, 0.1, 50.0,
        p_ui,   plWaterComponent::kGeoWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_GEO_MAXLEN, IDC_COMP_W_GEO_MAXLEN_SPIN, 1.0,
        end,    

    plWaterComponent::kGeoAmpOverLen, _T("GeoAngleDev"), TYPE_FLOAT,    0, 0,   
        p_default, 10.0,
        p_range, 0.0, 100.0,
        p_ui,   plWaterComponent::kGeoWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_GEO_AMPOVERLEN, IDC_COMP_W_GEO_AMPOVERLEN_SPIN, 1.0,
        end,    

    plWaterComponent::kGeoAngleDev, _T("GeoAngleDev"), TYPE_FLOAT,  0, 0,   
        p_default, 20.0,
        p_range, 0.0, 180.0,
        p_ui,   plWaterComponent::kGeoWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_GEO_ANGLEDEV, IDC_COMP_W_GEO_ANGLEDEV_SPIN, 10.0,
        end,    

    plWaterComponent::kGeoChop, _T("GeoChop"), TYPE_FLOAT,  0, 0,   
        p_default, 50.0,
        p_range, 0.0, 500.0,
        p_ui,   plWaterComponent::kGeoWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_GEO_CHOP, IDC_COMP_W_GEO_CHOP_SPIN, 1.0,
        end,    

    plWaterComponent::kTexMinLen, _T("TexMinLen"), TYPE_FLOAT,  0, 0,   
        p_default, 0.1,
        p_range, 0.01, 4.0,
        p_ui,   plWaterComponent::kTexWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_TEX_MINLEN, IDC_COMP_W_TEX_MINLEN_SPIN, 1.0,
        end,    

    plWaterComponent::kTexMaxLen, _T("TexMaxLen"), TYPE_FLOAT,  0, 0,   
        p_default, 4.0,
        p_range, 0.1, 50.0,
        p_ui,   plWaterComponent::kTexWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_TEX_MAXLEN, IDC_COMP_W_TEX_MAXLEN_SPIN, 1.0,
        end,    

    plWaterComponent::kTexAmpOverLen, _T("TexAngleDev"), TYPE_FLOAT,    0, 0,   
        p_default, 10.0,
        p_range, 0.0, 100.0,
        p_ui,   plWaterComponent::kTexWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_TEX_AMPOVERLEN, IDC_COMP_W_TEX_AMPOVERLEN_SPIN, 1.0,
        end,    

    plWaterComponent::kTexAngleDev, _T("TexAngleDev"), TYPE_FLOAT,  0, 0,   
        p_default, 20.0,
        p_range, 0.0, 180.0,
        p_ui,   plWaterComponent::kTexWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_TEX_ANGLEDEV, IDC_COMP_W_TEX_ANGLEDEV_SPIN, 1.0,
        end,    

    plWaterComponent::kNoise, _T("Noise"), TYPE_FLOAT,  0, 0,   
        p_default, 50.0,
        p_range, 0.0, 300.0,
        p_ui,   plWaterComponent::kTexWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_TEX_NOISE, IDC_COMP_W_TEX_NOISE_SPIN, 1.0,
        end,    

    plWaterComponent::kTexChop, _T("TexChop"), TYPE_FLOAT,  0, 0,   
        p_default, 50.0,
        p_range, 0.0, 500.0,
        p_ui,   plWaterComponent::kTexWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_TEX_CHOP, IDC_COMP_W_TEX_CHOP_SPIN, 1.0,
        end,    

    plWaterComponent::kSpecStart, _T("SpecStart"), TYPE_FLOAT,  0, 0,   
        p_default, 50.0,
        p_range, 0.0, 1000.0,
        p_ui,   plWaterComponent::kTexWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_TEX_SPECSTART, IDC_COMP_W_TEX_SPECSTART_SPIN, 10.0,
        end,    

    plWaterComponent::kSpecEnd, _T("SpecEnd"), TYPE_FLOAT,  0, 0,   
        p_default, 1000.0,
        p_range, 0.0, 10000.0,
        p_ui,   plWaterComponent::kTexWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_TEX_SPECEND, IDC_COMP_W_TEX_SPECEND_SPIN, 10.0,
        end,    

    plWaterComponent::kSpecularMute, _T("SpecularMute"), TYPE_FLOAT,    0, 0,   
        p_default, 30.0,
        p_range, 0.0, 100.0,
        p_ui,   plWaterComponent::kGeoWater, TYPE_SPINNER,  EDITTYPE_POS_FLOAT, 
        IDC_COMP_W_GEO_SPECMUTE, IDC_COMP_W_GEO_SPECMUTE_SPIN, 1.0,
        end,    

    end
);


class plWaterCompPostLoadCallback : public PostLoadCallback
{
public:
    plWaterComponent*   fWaterComp;

    plWaterCompPostLoadCallback(plWaterComponent* wc) : fWaterComp(wc) {}

    void proc(ILoad *iload) 
    {
        fWaterComp->CheckForObsoleteParams();

        delete this;
    }
};

IOResult plWaterComponent::Load(ILoad* iLoad)
{
    iLoad->RegisterPostLoadCallback(new plWaterCompPostLoadCallback(this));

    return plComponent::Load(iLoad);
}

void plWaterComponent::CheckForObsoleteParams()
{
    if( (fCompPB->GetFloat(kDispersion) >= 0)
        ||(fCompPB->GetFloat(kWindSpeed) >= 0) )
    {
        // Okay, these are old. Need to set some default values based
        // on the old obsolete ones. Basically, we need to go from:
        //
        // kDispersion =>
        //      GeoAngleDev
        //      TexAngleDev
        //
        // kWindSpeed
        //      GeoMinLen
        //      GeoMaxLen
        //      GeoAmpOverLen = 0.1
        //
        //      TexMinLen
        //      TexMaxLen
        //      TexAmpOverLen
        //
        //      Noise
        //
        // kEnvRadius
        //      SpecStart = kEnvRadius / 2.f
        //      SpecEnd = kEnvRadius * 2.f

        // Okay, here we go.
        hsScalar dispersion = fCompPB->GetFloat(kDispersion) / 100.f;
        plConst(hsScalar) kMinAng(5.f);
        plConst(hsScalar) kMaxAng(180.f);
        
        hsScalar angleDev = kMinAng + dispersion * (kMaxAng - kMinAng);
        fCompPB->SetValue(kGeoAngleDev, TimeValue(0), angleDev);
        fCompPB->SetValue(kTexAngleDev, TimeValue(0), angleDev);

        hsScalar windSpeed = fCompPB->GetFloat(kWindSpeed);
        const hsScalar kGravConst(32.f); // ft/s^2
        hsScalar waveLen = windSpeed * windSpeed / kGravConst;
        waveLen /= 2.f;
        if( waveLen < 1.f )
            waveLen = 1.f;
        fCompPB->SetValue(kGeoMinLen, TimeValue(0), waveLen/2.f);
        fCompPB->SetValue(kGeoMaxLen, TimeValue(0), waveLen*2.f);
        fCompPB->SetValue(kGeoAmpOverLen, TimeValue(0), 10.f);

        hsScalar rippleScale = fCompPB->GetFloat(kRippleScale);
        fCompPB->SetValue(kTexMinLen, TimeValue(0), 4.f / 256.f * rippleScale);
        fCompPB->SetValue(kTexMaxLen, TimeValue(0), 32.f / 256.f * rippleScale);
        hsScalar amp = 0.01f;
        hsScalar specMute = 0.5f;
        if( windSpeed < 15.f )
        {
            hsScalar p = windSpeed / 15.f;
            amp += p * (0.1f - 0.01f);

            specMute += (1-p)* 0.5f;
        }
        fCompPB->SetValue(kTexAmpOverLen, TimeValue(0), amp*100.f);
        fCompPB->SetValue(kSpecularMute, TimeValue(0), specMute*100.f);

        fCompPB->SetValue(kNoise, TimeValue(0), 50.f);

        hsScalar envRad = fCompPB->GetFloat(kEnvRadius);
        hsScalar specStart = envRad / 2.f;
        hsScalar specEnd = envRad * 2.f;
        fCompPB->SetValue(kSpecStart, TimeValue(0), specStart);
        fCompPB->SetValue(kSpecEnd, TimeValue(0), specEnd);
        
        // Set them negative so we don't keep doing this.
        fCompPB->SetValue(kDispersion, TimeValue(0), -1.f);
        fCompPB->SetValue(kWindSpeed, TimeValue(0), -1.f);
    }
}


// Component implementation
plWaterComponent::plWaterComponent()
:   fWaveSet(nil)
{
    fClassDesc = &gWaterCompDesc;
    fClassDesc->MakeAutoParamBlocks(this);
}

hsBool plWaterComponent::SetupProperties(plMaxNode* node, plErrorMsg* pErrMsg)
{
    node->SetRunTimeLight(true);
    node->SetForceMaterialCopy(true);
    node->SetForceShadow(false);
    node->SetNoShadow(true);
    node->SetSmoothAll(true);
    node->SetForceSortable(true);
    node->SetNoSpanSort(true);
    node->SetNoPreShade(true);

    // Turn on calculation of edge lengths XXX
    node->SetCalcEdgeLens(true);

    // Make a note that we're vertex shaded (at least ripplecomponent needs to know).XXX
    node->SetVS(true);

    // Turn off the convexity test. We want to be sorted.XXX
    node->SetConcave(true);

    node->SetReverseSort(true);

    node->SetWaterHeight(IGetWaterHeight());

    return true;
}

hsBool plWaterComponent::PreConvert(plMaxNode* node, plErrorMsg* pErrMsg)
{ 
    if( !fWaveSet )
        IMakeWaveSet(node, pErrMsg);

    // Do it again in case some idiot is trying to override.XXX

    pErrMsg->Set(!node->HasLoadMask(), node->GetName(), "PS water has no representation component").CheckAndAsk();
    pErrMsg->Set(false);

    return true; 
}

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

    plObjRefMsg* refMsg = TRACKED_NEW plObjRefMsg(node->GetKey(), plRefMsg::kOnRequest, -1, plObjRefMsg::kModifier);
    hsgResMgr::ResMgr()->AddViaNotify(fWaveSet->GetKey(), refMsg, plRefFlags::kActiveRef);

    return true;
}

hsBool plWaterComponent::DeInit(plMaxNode* node, plErrorMsg* pErrMsg)
{ 
    if( fWaveSet )
        fWaveSet->GetKey()->UnRefObject();
    fWaveSet = nil;

    return true; 
}

hsBool plWaterComponent::IReadRefObject(plMaxNodeBase* node, plFixedWaterState7& ws)
{
    INode* ref = fCompPB->GetINode(kRefObject);
    if( !ref )
    {
        ref = node;
    }
    if( !ref )
        return false;

    Matrix3 xfm = ref->GetNodeTM(TimeValue(0));
    ws.fWaterHeight = xfm.GetTrans().z;

    Point3 y = xfm.GetRow(1);
    hsVector3 dir(-y.x, -y.y, 0);
    dir.Normalize();
    ws.fWindDir = dir;

    return true;
}

hsBool plWaterComponent::IReadEnvObject(plMaxNode* node, plErrorMsg* pErrMsg, plFixedWaterState7& ws)
{
    INode* ref = fCompPB->GetINode(kEnvObject);
    if( !ref )
    {
        ref = node;
    }
    plDynamicEnvMap* env = plEnvMapComponent::GetEnvMap((plMaxNode*)ref);
    if( !env )
    {
        UInt32 size = fCompPB->GetInt(kEnvSize);
        UInt32 i;
        for( i = 9; i > 5; i-- )
        {
            if( (1UL << i) <= size )
                break;
        }
        size = UInt32(1 << i);

        env = TRACKED_NEW plDynamicEnvMap(size, size, 32);
        hsgResMgr::ResMgr()->NewKey(ref->GetName(), env, node->GetLocation(), node->GetLoadMask());

        Point3 pos = ref->GetNodeTM(TimeValue(0)).GetTrans();
        env->SetPosition(hsPoint3(pos.x, pos.y, pos.z));
        env->SetYon(10000.f);
        env->SetRefreshRate(fCompPB->GetFloat(kEnvRefresh));
    }
    if( !env )
        return false;

    ws.fEnvCenter = env->GetPosition();
    ws.fEnvRefresh = env->GetRefreshRate();

    fWaveSet->SetEnvSize(env->GetWidth());


    plGenRefMsg* refMsg = TRACKED_NEW plGenRefMsg(fWaveSet->GetKey(), plRefMsg::kOnCreate, -1, plWaveSet7::kRefEnvMap);
    hsgResMgr::ResMgr()->SendRef(env->GetKey(), refMsg, plRefFlags::kActiveRef);

    ws.fEnvRadius = fCompPB->GetFloat(kEnvRadius);


    return true;
}

hsBool plWaterComponent::IGetRefObject(plMaxNode* node)
{
    plMaxNode* ref = (plMaxNode*)fCompPB->GetINode(kRefObject);
    if( (ref != node)           // We have an exterior reference node
        && ref->CanConvert()    // it's being exported
        && ref->IsTMAnimated() )
    {
        plSceneObject* refObj = ref->GetSceneObject();

        fWaveSet->SetRefObject(refObj);

        return true;
    }

    return false;
}

hsBool plWaterComponent::IMakeWaveSet(plMaxNode* node, plErrorMsg* pErrMsg)
{
    // Go ahead and create the WaveSet modifier. There will be just
    // one created by this component, everyone has to share.
    fWaveSet = TRACKED_NEW plWaveSet7;
    hsgResMgr::ResMgr()->NewKey( IGetUniqueName(node), fWaveSet, node->GetLocation(), node->GetLoadMask());

    // Set up the parameters
    plFixedWaterState7 ws;

    // Things we get off the reference (plane) object
    // First we look to see if it's an animated runtime object we need to keep track of.
    // Either way, we just grab the static info we need off of it, for init or forever.
    IGetRefObject(node);
    IReadRefObject(node, ws);

    // Things we get from our paramblock
    plFixedWaterState7::WaveState& geoState = ws.fGeoState;
    geoState.fMaxLength = fCompPB->GetFloat(kGeoMaxLen);
    geoState.fMinLength = fCompPB->GetFloat(kGeoMinLen);
    geoState.fAmpOverLen = fCompPB->GetFloat(kGeoAmpOverLen) * kPercentToFrac;
    geoState.fChop = fCompPB->GetFloat(kGeoChop) * kPercentToFrac;
    geoState.fAngleDev = fCompPB->GetFloat(kGeoAngleDev) * kDegreeToRad;

    plFixedWaterState7::WaveState& texState = ws.fTexState;
    texState.fMaxLength = fCompPB->GetFloat(kTexMaxLen);
    texState.fMinLength = fCompPB->GetFloat(kTexMinLen);
    texState.fAmpOverLen = fCompPB->GetFloat(kTexAmpOverLen) * kPercentToFrac;
    texState.fChop = fCompPB->GetFloat(kTexChop) * kPercentToFrac;
    texState.fAngleDev = fCompPB->GetFloat(kTexAngleDev) * kDegreeToRad;

    hsVector3 specVec;
    specVec[ws.kNoise] = fCompPB->GetFloat(kNoise) * kPercentToFrac;
    specVec[ws.kSpecStart] = fCompPB->GetFloat(kSpecStart);
    specVec[ws.kSpecEnd] = fCompPB->GetFloat(kSpecEnd);
    ws.fSpecVec = specVec;

    ws.fWispiness = fCompPB->GetFloat(kWispiness) * kPercentToFrac;
    ws.fPeriod = fCompPB->GetFloat(kPeriod) * kPercentToFrac;
    ws.fRippleScale = fCompPB->GetFloat(kRippleScale);
    ws.fFingerLength = fCompPB->GetFloat(kFinger) * kPercentToFrac;

    ws.fDepthFalloff = hsVector3(fCompPB->GetFloat(kDepthOpac), fCompPB->GetFloat(kDepthRefl), fCompPB->GetFloat(kDepthWave));
    ws. fWaterOffset = hsVector3(-fCompPB->GetFloat(kZeroOpac), -fCompPB->GetFloat(kZeroRefl), -fCompPB->GetFloat(kZeroWave));
    ws.fMaxAtten = hsVector3(1.f, 1.f, 1.f);
    ws.fMinAtten = hsVector3(0, 0, 0);

    IReadEnvObject(node, pErrMsg, ws);

    // Some colors
    ws.fWaterTint = hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f);

    Color specTint = fCompPB->GetColor(kSpecularTint);
    float specMute = fCompPB->GetFloat(kSpecularMute) * kPercentToFrac;
    ws.fSpecularTint = hsColorRGBA().Set(specTint.r, specTint.g, specTint.b, specMute);

    ws.fMaxColor = hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f); // Always white/opaque?
    Color shoreTint = fCompPB->GetColor(kShoreTint);
    float shoreOpac = fCompPB->GetFloat(kShoreOpac) * kPercentToFrac;
    ws.fMinColor = hsColorRGBA().Set(shoreTint.r, shoreTint.g, shoreTint.b, shoreOpac);

    // Not really used.
    ws.fShoreTint = hsColorRGBA().Set(1.f, 1.f, 1.f, 1.f);

    ws.fEdgeOpac = fCompPB->GetFloat(kEdgeOpac) * kPercentToFrac;
    ws.fEdgeRadius = fCompPB->GetFloat(kEdgeRadius) * kPercentToFrac;

    fWaveSet->SetState(ws, 0.f);

    // Add a ref to the waveset.
    fWaveSet->GetKey()->RefObject();

    return true;
}

hsScalar plWaterComponent::IGetWaterHeight()
{
    plMaxNodeBase* node = nil;
    
    int i;
    for( i = 0; i < NumTargets(); i++ )
    {
        node = GetTarget(i);
        if( node )
            break;
    }

    plFixedWaterState7 ws;
    IReadRefObject(node, ws);

    return ws.fWaterHeight;
}

hsScalar plWaterComponent::GetWaterHeight(INode* node)
{
    if( !node )
        return 0.f;

    plComponentBase *comp = ((plMaxNodeBase*)node)->ConvertToComponent();
    if( !comp )
        return 0.f;

    if( comp->ClassID() != WATER_COMP_CID )
        return 0.f;

    plWaterComponent* water = (plWaterComponent*)comp;
    return water->IGetWaterHeight();
}


plWaveSetBase* plWaterComponent::GetWaveSet(INode* node)
{
    if( !node )
        return nil;

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

    if( comp->ClassID() != WATER_COMP_CID )
        return nil;

    plWaterComponent* water = (plWaterComponent*)comp;
    return water->IGetWaveSet();
}

plWaveSetBase* plWaterComponent::GetWaveSetFromNode(plMaxNode* node)
{
    if( !node )
        return nil;

    int n = node->NumAttachedComponents();
    int i;
    for( i = 0; i < n; i++ )
    {
        plComponentBase* comp = node->GetAttachedComponent(i);
        if( comp && (comp->ClassID() == WATER_COMP_CID) )
        {
            plWaterComponent* water = (plWaterComponent*)comp;
            return water->IGetWaveSet();
        }
    }
    return nil;
}

static void ISetWaterDependencies(plMaxNode* node, INode* waterNode)
{
    if( !waterNode )
        return;

    plComponentBase *comp = ((plMaxNodeBase*)waterNode)->ConvertToComponent();
    if( !comp )
        return;

    if( comp->ClassID() != WATER_COMP_CID )
        return;

    INodeTab nodeList;
    comp->AddTargetsToList(nodeList);
    int i;
    for( i = 0; i < nodeList.Count(); i++ )
    {
        if( nodeList[i] )
            node->AddRenderDependency((plMaxNodeBase*)nodeList[i]);
    }
}


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

class plShoreCompSelProc : public ParamMap2UserDlgProc
{
public:
    BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
    void DeleteThis() { }
};

#include "plPickNode.h"

BOOL plShoreCompSelProc::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(plShoreComponent::kWaveSet);
            TSTR newName(node ? node->GetName() : "Pick");
            ::SetWindowText(::GetDlgItem(hWnd, IDC_COMP_SHORE_CHOSE), newName);
        }
        return true;

    case WM_COMMAND:
        if( (HIWORD(wParam) == BN_CLICKED) && (LOWORD(wParam) == IDC_COMP_SHORE_CHOSE) )
        {
            IParamBlock2 *pb = paramMap->GetParamBlock();
            std::vector<Class_ID> cids;
            cids.push_back(WATER_COMP_CID);
            if( plPick::Node(pb, plShoreComponent::kWaveSet, &cids, true, true) )
            {
                INode* node = pb->GetINode(plShoreComponent::kWaveSet);
                TSTR newName(node ? node->GetName() : "Pick");
                ::SetWindowText(::GetDlgItem(hWnd, IDC_COMP_SHORE_CHOSE), newName);
                paramMap->Invalidate(plShoreComponent::kWaveSet);
                ShowWindow(hWnd, SW_HIDE);
                ShowWindow(hWnd, SW_SHOW);
            }

            return false;
        }
        return true;
    }

    return false;
}

plShoreCompSelProc gShoreCompSelProc;

CLASS_DESC(plShoreComponent, gShoreCompDesc, "Shore Line",  "Shore", COMP_TYPE_WATER, SHORE_COMP_CID)



ParamBlockDesc2 gShoreCompBk
(
    plComponent::kBlkComp, _T("Shore"), 0, &gShoreCompDesc, P_AUTO_CONSTRUCT+P_AUTO_UI, plComponent::kRefComp,

    IDD_COMP_SHORE, IDS_COMP_SHORE,  0, 0, &gShoreCompSelProc,

    plShoreComponent::kWaveSet, _T("WaveSet"),  TYPE_INODE,     0, 0,
        end,

    end
);

plShoreComponent::plShoreComponent()
{
    fClassDesc = &gShoreCompDesc;
    fClassDesc->MakeAutoParamBlocks(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 plShoreComponent::SetupProperties(plMaxNode* node, plErrorMsg* pErrMsg)
{
    node->SetRunTimeLight(true);
    node->SetForceMaterialCopy(true);
    node->SetForceShadow(false);
    node->SetNoShadow(true);
    node->SetSmoothAll(true);
    node->SetForceSortable(true);
    node->SetNoSpanSort(true);
    node->SetNoPreShade(true);

    ISetWaterDependencies(node, fCompPB->GetINode(kWaveSet, 0, 0));

    // Turn on calculation of edge lengths XXX
    node->SetCalcEdgeLens(true);

    // Make a note that we're vertex shaded (at least ripplecomponent needs to know).XXX
    node->SetVS(true);

    // Turn off the convexity test. We want to be sorted.XXX
    node->SetConcave(true);

    node->SetReverseSort(true);

    node->SetWaterHeight(plWaterComponent::GetWaterHeight(fCompPB->GetINode(kWaveSet, 0, 0)));

    return true;
}

hsBool plShoreComponent::PreConvert(plMaxNode* node, plErrorMsg* pErrMsg)
{
    pErrMsg->Set(!node->HasLoadMask(), node->GetName(), "PS shore has no representation component").CheckAndAsk();
    pErrMsg->Set(false);


    return true;
}

hsBool plShoreComponent::Convert(plMaxNode* node, plErrorMsg* pErrMsg)
{
    plWaveSetBase* waveSet = plWaterComponent::GetWaveSet(fCompPB->GetINode(kWaveSet, 0, 0));
    if( waveSet )
    {
        plSceneObject* obj = node->GetSceneObject();
        if( obj )
        {
            waveSet->AddShore(obj->GetKey());
        }
        else
        {
            pErrMsg->Set(true, node->GetName(), "Invalid object selected for shore. Ignoring").CheckAndAsk();
            pErrMsg->Set(false);
        }
    }
    else
    {
        pErrMsg->Set(true, node->GetName(), "No Water Component selected for shore. Ignoring").CheckAndAsk();
        pErrMsg->Set(false);
    }
    return true;
}

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

class plWDecalCompSelProc : public ParamMap2UserDlgProc
{
public:
    BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
    void DeleteThis() { }
};

#include "plPickNode.h"

BOOL plWDecalCompSelProc::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(plWDecalComponent::kWaveSet);
            TSTR newName(node ? node->GetName() : "Pick");
            ::SetWindowText(::GetDlgItem(hWnd, IDC_COMP_WDECAL_CHOSE), newName);
        }
        return true;

    case WM_COMMAND:
        if( (HIWORD(wParam) == BN_CLICKED) && (LOWORD(wParam) == IDC_COMP_WDECAL_CHOSE) )
        {
            IParamBlock2 *pb = paramMap->GetParamBlock();
            std::vector<Class_ID> cids;
            cids.push_back(WATER_COMP_CID);
            if( plPick::Node(pb, plWDecalComponent::kWaveSet, &cids, true, true) )
            {
                INode* node = pb->GetINode(plWDecalComponent::kWaveSet);
                TSTR newName(node ? node->GetName() : "Pick");
                ::SetWindowText(::GetDlgItem(hWnd, IDC_COMP_WDECAL_CHOSE), newName);
                paramMap->Invalidate(plWDecalComponent::kWaveSet);
                ShowWindow(hWnd, SW_HIDE);
                ShowWindow(hWnd, SW_SHOW);
            }

            return false;
        }
        return true;
    }

    return false;
}

plWDecalCompSelProc gWDecalCompSelProc;

CLASS_DESC(plWDecalComponent, gWDecalCompDesc, "Water Decal",  "WDecal", COMP_TYPE_WATER, WDECAL_COMP_CID)



ParamBlockDesc2 gWDecalCompBk
(
    plComponent::kBlkComp, _T("WDecal"), 0, &gWDecalCompDesc, P_AUTO_CONSTRUCT+P_AUTO_UI, plComponent::kRefComp,

    IDD_COMP_WDECAL, IDS_COMP_WDECAL,  0, 0, &gWDecalCompSelProc,

    plWDecalComponent::kWaveSet, _T("WaveSet"), TYPE_INODE,     0, 0,
        end,

    plWDecalComponent::kEnv,  _T("Env"), TYPE_BOOL,         0, 0,
        p_default,  FALSE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_WDECAL_ENV,
        end,

    end
);

plWDecalComponent::plWDecalComponent()
{
    fClassDesc = &gWDecalCompDesc;
    fClassDesc->MakeAutoParamBlocks(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 plWDecalComponent::SetupProperties(plMaxNode* node, plErrorMsg* pErrMsg)
{
    node->SetRunTimeLight(true);
    node->SetForceMaterialCopy(true);
    node->SetForceShadow(false);
    node->SetNoShadow(true);
    node->SetSmoothAll(true);
    node->SetForceSortable(true);
    node->SetNoSpanSort(true);
    node->SetNoPreShade(true);

    // This should be optional.
    if( fCompPB->GetInt(kEnv) )
        node->SetWaterDecEnv(true);

    ISetWaterDependencies(node, fCompPB->GetINode(kWaveSet, 0, 0));

    // Turn on calculation of edge lengths XXX
    node->SetCalcEdgeLens(true);

    // Make a note that we're vertex shaded (at least ripplecomponent needs to know).XXX
    node->SetVS(true);

    // Turn off the convexity test. We want to be sorted.XXX
    node->SetConcave(true);

    node->SetReverseSort(true);

    node->SetWaterHeight(plWaterComponent::GetWaterHeight(fCompPB->GetINode(kWaveSet, 0, 0)));

    return true;
}

hsBool plWDecalComponent::PreConvert(plMaxNode* node, plErrorMsg* pErrMsg)
{
    pErrMsg->Set(!node->HasLoadMask(), node->GetName(), "PS water decal has no representation component").CheckAndAsk();
    pErrMsg->Set(false);


    return true;
}

hsBool plWDecalComponent::Convert(plMaxNode* node, plErrorMsg* pErrMsg)
{
    plWaveSetBase* waveSet = plWaterComponent::GetWaveSet(fCompPB->GetINode(kWaveSet, 0, 0));
    if( waveSet )
    {
        plSceneObject* obj = node->GetSceneObject();
        if( obj )
        {
            waveSet->AddDecal(obj->GetKey());
        }
        else
        {
            pErrMsg->Set(true, node->GetName(), "Invalid object selected for decal. Ignoring").CheckAndAsk();
            pErrMsg->Set(false);
        }
    }
    else
    {
        pErrMsg->Set(true, node->GetName(), "No Water Component selected for decal. Ignoring").CheckAndAsk();
        pErrMsg->Set(false);
    }
    return true;
}

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

class plEnvMapCompSelProc : public ParamMap2UserDlgProc
{
public:
    BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        switch (msg)
        {
        case WM_INITDIALOG:
            {
                for(int i = 0; i < map->GetParamBlock()->Count(plEnvMapComponent::kVisSetNames); i++ )
                {
                    HWND hList = GetDlgItem(hWnd, IDC_COMP_ENVMAP_NAMES_LISTBOX);
                    ListBox_AddString(hList, map->GetParamBlock()->GetStr(plEnvMapComponent::kVisSetNames, 0, i));
                }
            }
            return true;

        case WM_COMMAND:
            if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_ADD_TARGS)
            {
                std::vector<Class_ID> cids;
                cids.push_back(EFFVISSET_CID);
                IParamBlock2 *pb = map->GetParamBlock();
                plPick::Node(pb, plEnvMapComponent::kVisSets, &cids, false, false);

                map->Invalidate(plEnvMapComponent::kVisSets);
                return TRUE;
            }
            else if(HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_COMP_ENVMAP_ADD_STRING)
            {
                char str[256];
                char *pStr = str;
                ICustEdit *custEdit = GetICustEdit(GetDlgItem(hWnd, IDC_COMP_ENVMAP_ADD_STRING_BOX));
                custEdit->GetText(str, 256);
                custEdit->SetText("");  // clear text box

                if(!strcmp(str, ""))    // don't allow empty strings
                    return TRUE;

                HWND hList = GetDlgItem(hWnd, IDC_COMP_ENVMAP_NAMES_LISTBOX);
                ListBox_AddString(hList, pStr);
                map->GetParamBlock()->Append(plEnvMapComponent::kVisSetNames, 1, &pStr);
                return TRUE;
            }
            else if(HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_COMP_ENVMAP_REMOVE_STRING)
            {
                HWND hList = GetDlgItem(hWnd, IDC_COMP_ENVMAP_NAMES_LISTBOX);
                int curSel = ((int)(DWORD)SNDMSG((hList), LB_GETCURSEL, 0L, 0L));
                ListBox_DeleteString(hList, curSel);
                if (curSel >= 0)
                    map->GetParamBlock()->Delete(ParamID(plEnvMapComponent::kVisSetNames), curSel, 1);

                return TRUE;
            }
            break;
        }

        return false;
    }
    void DeleteThis() {}
};
static plEnvMapCompSelProc gEnvMapCompSelProc;

CLASS_DESC(plEnvMapComponent, gEnvMapCompDesc, "Environment Map",  "EnvMap", COMP_TYPE_WATER, ENVMAP_COMP_CID)



ParamBlockDesc2 gEnvMapCompBk
(
    plComponent::kBlkComp, _T("EnvMap"), 0, &gEnvMapCompDesc, P_AUTO_CONSTRUCT+P_AUTO_UI, plComponent::kRefComp,

    IDD_COMP_ENVMAP, IDS_COMP_ENVMAP,  0, 0, &gEnvMapCompSelProc,

    plEnvMapComponent::kVisSets,    _T("VisSets"),  TYPE_INODE_TAB, 0,      0, 0,
        p_ui,           TYPE_NODELISTBOX, IDC_LIST_TARGS, 0, 0, IDC_DEL_TARGS,
        end,

    plEnvMapComponent::kHither, _T("Hither"), TYPE_FLOAT,   0, 0,   
        p_default, 1.0,
        p_range, 0.0, 500.0,
//      p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
//      IDC_COMP_ENVMAP_HITHER, IDC_COMP_ENVMAP_HITHER_SPIN, 1.0,
        end,    

    plEnvMapComponent::kYon, _T("Yon"), TYPE_FLOAT,     0, 0,   
        p_default, 1000.0,
        p_range, 10.0, 50000.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_ENVMAP_YON, IDC_COMP_ENVMAP_YON_SPIN, 1.0,
        end,    

    plEnvMapComponent::kFogEnable,  _T("FogEnable"), TYPE_BOOL,         0, 0,
        p_default,  FALSE,
//      p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_ENVMAP_FOGENABLE,
//      p_enable_ctrls,     2, plEnvMapComponent::kFogStart, plEnvMapComponent::kFogColor,
        end,

    plEnvMapComponent::kFogStart, _T("FogStart"), TYPE_FLOAT,   0, 0,   
        p_default, 0.0,
        p_range, 0.0, 200.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_ENVMAP_FOGSTART, IDC_COMP_ENVMAP_FOGSTART_SPIN, 1.0,
        end,    


    plEnvMapComponent::kFogColor, _T("FogColor"), TYPE_RGBA, 0, 0,
        p_ui, TYPE_COLORSWATCH,         IDC_COMP_ENVMAP_FOGCOLOR,
        p_default, Color(0,0,0),
        end,

    plEnvMapComponent::kRefreshRate, _T("RefreshRate"), TYPE_FLOAT,     0, 0,   
        p_default, 0.0,
        p_range, 0.0, 3600.0,
        p_ui,   TYPE_SPINNER,   EDITTYPE_POS_FLOAT, 
        IDC_COMP_ENVMAP_REFRESHRATE, IDC_COMP_ENVMAP_REFRESHRATE_SPIN, 1.0,
        end,    

    plEnvMapComponent::kEnvSize, _T("EnvSize"), TYPE_INT, 0, 0,
        p_default, 256,
        p_range, 32, 1024,
        p_ui,   TYPE_SPINNER, EDITTYPE_INT, 
        IDC_COMP_ENVMAP_ENVSIZE, IDC_COMP_ENVMAP_ENVSIZE_SPIN,  1.f,
        end,

    plEnvMapComponent::kIncChars,  _T("IncChars"), TYPE_BOOL,       0, 0,
        p_default,  FALSE,
        p_ui,   TYPE_SINGLECHEKBOX, IDC_COMP_ENVMAP_INCCHARS,
        end,

    plEnvMapComponent::kMapType, _T("mapType"), TYPE_INT,       0, 0,
        p_ui,       TYPE_RADIO, 2, IDC_COMP_ENVMAP_CUBIC, IDC_COMP_ENVMAP_SINGLE_CAM,
        p_vals,     plEnvMapComponent::kMapCubic, plEnvMapComponent::kMapSingle,
        p_default,  plEnvMapComponent::kMapCubic,
        end,

    plEnvMapComponent::kVisSetNames, _T("VisSetNames"), TYPE_STRING_TAB, 0, 0, 0,
        end,

    end
);

plEnvMapComponent::plEnvMapComponent()
{
    fClassDesc = &gEnvMapCompDesc;
    fClassDesc->MakeAutoParamBlocks(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 plEnvMapComponent::SetupProperties(plMaxNode* node, plErrorMsg* pErrMsg)
{
    fMap = nil;

    node->SetForceLocal(true);

    return true;
}

hsBool plEnvMapComponent::PreConvert(plMaxNode* node, plErrorMsg* pErrMsg)
{

    return true;
}

hsBool plEnvMapComponent::Convert(plMaxNode* node, plErrorMsg* pErrMsg)
{
    // If we make a handler that will update the EnvMap's position when this object
    // moves, we can put it's creation here. Otherwise, there's nought to do, since
    // we generate the envmap on demand.
    return true;
}

plDynamicEnvMap* plEnvMapComponent::GetEnvMap()
{
    return plDynamicEnvMap::ConvertNoRef(IGetMap());
}

plDynamicCamMap* plEnvMapComponent::GetCamMap()
{
    return plDynamicCamMap::ConvertNoRef(IGetMap());
}

plRenderTarget* plEnvMapComponent::IGetMap()
{
    plMaxNode* firstTarg = nil;
    int numTarg = NumTargets();
    int i;
    for( i = 0; i < numTarg; i++ )
    {
        if( GetTarget(i) )
        {
            firstTarg = (plMaxNode*)GetTarget(i);
            break;
        }
    }
    if( !firstTarg )
        return nil;

    if( !fMap )
    {
        UInt32 size = fCompPB->GetInt(kEnvSize);
        for( i = 9; i > 5; i-- )
        {
            if( (1UL << UInt32(i)) <= size )
                break;
        }
        size = 1 << UInt32(i);

        plDynamicEnvMap* env = nil;
        plDynamicCamMap* cam = nil;
        fMap = nil;
        if (fCompPB->GetInt((ParamID(kMapType))) == kMapCubic)
            fMap = env = TRACKED_NEW plDynamicEnvMap(size, size, 32);
        else if (fCompPB->GetInt((ParamID(kMapType))) == kMapSingle)
            fMap = cam = TRACKED_NEW plDynamicCamMap(size, size, 32);

        // Need to assign the key before we call all the setup functions.
        hsgResMgr::ResMgr()->NewKey(GetINode()->GetName(), fMap, firstTarg->GetLocation(), firstTarg->GetLoadMask());


        if (fCompPB->GetInt((ParamID(kMapType))) == kMapCubic)
        {
            Point3 pos = firstTarg->GetNodeTM(TimeValue(0)).GetTrans();
            env->SetPosition(hsPoint3(pos.x, pos.y, pos.z));
            env->SetRefreshRate(fCompPB->GetFloat(kRefreshRate));
            env->SetHither(fCompPB->GetFloat(kHither));
            env->SetYon(fCompPB->GetFloat(kYon));
            env->SetFogStart(fCompPB->GetFloat(kFogStart) * kPercentToFrac);
            Color fogColor = fCompPB->GetColor(kFogColor);
            env->SetColor(hsColorRGBA().Set(fogColor.r, fogColor.g, fogColor.b, 1.f));
        }
        else if (fCompPB->GetInt((ParamID(kMapType))) == kMapSingle)
        {
            cam->SetRefreshRate(fCompPB->GetFloat(ParamID(kRefreshRate)));
            cam->fHither = fCompPB->GetFloat(ParamID(kHither));
            cam->fYon = fCompPB->GetFloat(ParamID(kYon));
            cam->fFogStart = fCompPB->GetFloat(ParamID(kFogStart)) * kPercentToFrac;
            Color fogColor = fCompPB->GetColor(kFogColor);
            cam->fColor.Set(fogColor.r, fogColor.g, fogColor.b, 1.f);
        }
        if (!fMap)
            return nil;

        int visGot = 0;
        int numVis = fCompPB->Count(kVisSets);
        for( i = 0; i < numVis; i++ )
        {
            plEffVisSetComponent* effComp = plEffVisSetComponent::ConvertToEffVisSetComponent((plMaxNode*)fCompPB->GetINode(kVisSets, 0, i));
            if( effComp )
            {
                plVisRegion* effReg = effComp->GetVisRegion(firstTarg);
                if( effReg )
                {
                    plGenRefMsg* refMsg = TRACKED_NEW plGenRefMsg(fMap->GetKey(), plRefMsg::kOnCreate, -1, plDynamicEnvMap::kRefVisSet);
                    hsgResMgr::ResMgr()->SendRef(effReg->GetKey(), refMsg, plRefFlags::kPassiveRef);

                    visGot++;
                }
            }
        }

        // This allows you to enter the name of an effect vis set(key name), from another max file and use it 
        // as if it we're in the same max file.
        int numVisNames = fCompPB->Count(kVisSetNames);
        for( i = 0; i < numVisNames; i++)
        {
            fMap->SetVisRegionName((char*)fCompPB->GetStr(kVisSetNames, 0, i));
        }

        if (visGot)
        {
            if (env)
                env->SetIncludeCharacters(fCompPB->GetInt(ParamID(kIncChars)) != 0);
            if (cam)
                cam->SetIncludeCharacters(fCompPB->GetInt(ParamID(kIncChars)) != 0);
        }

        // Right now, the envMap doesn't use this, but I plan to make it do so, so I'm
        // going ahead and adding the ref regardless of which type of map we made.
        UInt8 refType = cam ? plDynamicCamMap::kRefRootNode : plDynamicEnvMap::kRefRootNode;
        hsgResMgr::ResMgr()->AddViaNotify(firstTarg->GetSceneObject()->GetKey(), TRACKED_NEW plGenRefMsg(fMap->GetKey(), plRefMsg::kOnCreate, -1, refType), plRefFlags::kPassiveRef);
    }
    return fMap;
}

plDynamicEnvMap* plEnvMapComponent::GetEnvMap(plMaxNode* node)
{
    plEnvMapComponent* envComp = GetEnvMapComponent(node);
    if (envComp)
        return envComp->GetEnvMap();

    return nil;
}

plDynamicCamMap* plEnvMapComponent::GetCamMap(plMaxNode *node)
{
    plEnvMapComponent *envComp = GetEnvMapComponent(node);
    if (envComp)
        return envComp->GetCamMap();

    return nil;
}

plEnvMapComponent *plEnvMapComponent::GetEnvMapComponent(plMaxNode *node)
{
    if (!node)
        return nil;

    int n = node->NumAttachedComponents();
    int i;
    for (i = 0; i < n; i++)
    {
        plComponentBase *comp = node->GetAttachedComponent(i);
        if (comp && (comp->ClassID() == ENVMAP_COMP_CID))
        {
            return (plEnvMapComponent*)comp;
        }
    }
    return nil;
}